mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-01-10 01:12:31 +00:00
Compare commits
2 Commits
refactor/u
...
feat/globa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8a7144302 | ||
|
|
e553dae3c4 |
@@ -16,7 +16,7 @@
|
||||
"axios": "^1.13.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^25.7.4",
|
||||
"i18next": "^25.7.3",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"input-otp": "^1.4.2",
|
||||
@@ -27,7 +27,7 @@
|
||||
"react-hook-form": "^7.70.0",
|
||||
"react-i18next": "^16.5.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.12.0",
|
||||
"react-router": "^7.11.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
@@ -43,7 +43,7 @@
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"globals": "^17.0.0",
|
||||
"globals": "^16.5.0",
|
||||
"prettier": "3.7.4",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
@@ -563,7 +563,7 @@
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@17.0.0", "", {}, "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw=="],
|
||||
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
@@ -589,7 +589,7 @@
|
||||
|
||||
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
||||
|
||||
"i18next": ["i18next@25.7.4", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw=="],
|
||||
"i18next": ["i18next@25.7.3", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-2XaT+HpYGuc2uTExq9TVRhLsso+Dxym6PWaKpn36wfBmTI779OQ7iP/XaZHzrnGyzU4SHpFrTYLKfVyBfAhVNA=="],
|
||||
|
||||
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="],
|
||||
|
||||
@@ -807,7 +807,7 @@
|
||||
|
||||
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
||||
|
||||
"react-router": ["react-router@7.12.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw=="],
|
||||
"react-router": ["react-router@7.11.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ=="],
|
||||
|
||||
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"axios": "^1.13.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^25.7.4",
|
||||
"i18next": "^25.7.3",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"input-otp": "^1.4.2",
|
||||
@@ -33,7 +33,7 @@
|
||||
"react-hook-form": "^7.70.0",
|
||||
"react-i18next": "^16.5.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.12.0",
|
||||
"react-router": "^7.11.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
@@ -49,7 +49,7 @@
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"globals": "^17.0.0",
|
||||
"globals": "^16.5.0",
|
||||
"prettier": "3.7.4",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
|
||||
@@ -2,6 +2,7 @@ package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/middleware"
|
||||
@@ -14,7 +15,7 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
|
||||
engine.Use(gin.Recovery())
|
||||
|
||||
if len(app.config.Server.TrustedProxies) > 0 {
|
||||
err := engine.SetTrustedProxies(app.config.Server.TrustedProxies)
|
||||
err := engine.SetTrustedProxies(strings.Split(app.config.Server.TrustedProxies, ","))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to set trusted proxies: %w", err)
|
||||
|
||||
@@ -33,15 +33,15 @@ type Config struct {
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `description:"The port on which the server listens." yaml:"port"`
|
||||
Address string `description:"The address on which the server listens." yaml:"address"`
|
||||
SocketPath string `description:"The path to the Unix socket." yaml:"socketPath"`
|
||||
TrustedProxies []string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies"`
|
||||
Port int `description:"The port on which the server listens." yaml:"port"`
|
||||
Address string `description:"The address on which the server listens." yaml:"address"`
|
||||
SocketPath string `description:"The path to the Unix socket." yaml:"socketPath"`
|
||||
TrustedProxies string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies"`
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
IP IPConfig `description:"IP whitelisting config options." yaml:"ip"`
|
||||
Users []string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users"`
|
||||
Users string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users"`
|
||||
UsersFile string `description:"Path to the users file." yaml:"usersFile"`
|
||||
SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie"`
|
||||
SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry"`
|
||||
@@ -56,7 +56,7 @@ type IPConfig struct {
|
||||
}
|
||||
|
||||
type OAuthConfig struct {
|
||||
Whitelist []string `description:"Comma-separated list of allowed OAuth domains." yaml:"whitelist"`
|
||||
Whitelist string `description:"Comma-separated list of allowed OAuth domains." yaml:"whitelist"`
|
||||
AutoRedirect string `description:"The OAuth provider to use for automatic redirection." yaml:"autoRedirect"`
|
||||
Providers map[string]OAuthServiceConfig `description:"OAuth providers configuration." yaml:"providers"`
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En
|
||||
Password: "$2a$10$ne6z693sTgzT3ePoQ05PgOecUHnBjM7sSNj6M.l5CLUP.f6NyCnt.", // test
|
||||
},
|
||||
},
|
||||
OauthWhitelist: []string{},
|
||||
OauthWhitelist: "",
|
||||
SessionExpiry: 3600,
|
||||
SessionMaxLifetime: 0,
|
||||
SecureCookie: false,
|
||||
|
||||
@@ -60,7 +60,7 @@ func setupUserController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.Eng
|
||||
TotpSecret: totpSecret,
|
||||
},
|
||||
},
|
||||
OauthWhitelist: []string{},
|
||||
OauthWhitelist: "",
|
||||
SessionExpiry: 3600,
|
||||
SessionMaxLifetime: 0,
|
||||
SecureCookie: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: queries.sql
|
||||
// source: query.sql
|
||||
|
||||
package repository
|
||||
|
||||
@@ -27,7 +27,7 @@ type LoginAttempt struct {
|
||||
|
||||
type AuthServiceConfig struct {
|
||||
Users []config.User
|
||||
OauthWhitelist []string
|
||||
OauthWhitelist string
|
||||
SessionExpiry int
|
||||
SessionMaxLifetime int
|
||||
SecureCookie bool
|
||||
@@ -187,7 +187,7 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
|
||||
}
|
||||
|
||||
func (auth *AuthService) IsEmailWhitelisted(email string) bool {
|
||||
return utils.CheckFilter(strings.Join(auth.config.OauthWhitelist, ","), email)
|
||||
return utils.CheckFilter(auth.config.OauthWhitelist, email)
|
||||
}
|
||||
|
||||
func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.SessionCookie) error {
|
||||
|
||||
@@ -7,14 +7,22 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
func ParseUsers(usersStr []string) ([]config.User, error) {
|
||||
var users []config.User
|
||||
func ParseUsers(users string) ([]config.User, error) {
|
||||
var usersParsed []config.User
|
||||
|
||||
if len(usersStr) == 0 {
|
||||
users = strings.TrimSpace(users)
|
||||
|
||||
if users == "" {
|
||||
return []config.User{}, nil
|
||||
}
|
||||
|
||||
for _, user := range usersStr {
|
||||
userList := strings.Split(users, ",")
|
||||
|
||||
if len(userList) == 0 {
|
||||
return []config.User{}, errors.New("invalid user format")
|
||||
}
|
||||
|
||||
for _, user := range userList {
|
||||
if strings.TrimSpace(user) == "" {
|
||||
continue
|
||||
}
|
||||
@@ -22,71 +30,64 @@ func ParseUsers(usersStr []string) ([]config.User, error) {
|
||||
if err != nil {
|
||||
return []config.User{}, err
|
||||
}
|
||||
users = append(users, parsed)
|
||||
usersParsed = append(usersParsed, parsed)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
return usersParsed, nil
|
||||
}
|
||||
|
||||
func GetUsers(usersCfg []string, usersPath string) ([]config.User, error) {
|
||||
var usersStr []string
|
||||
func GetUsers(conf string, file string) ([]config.User, error) {
|
||||
var users string
|
||||
|
||||
if len(usersCfg) == 0 && usersPath == "" {
|
||||
if conf == "" && file == "" {
|
||||
return []config.User{}, nil
|
||||
}
|
||||
|
||||
if len(usersCfg) > 0 {
|
||||
usersStr = append(usersStr, usersCfg...)
|
||||
if conf != "" {
|
||||
users += conf
|
||||
}
|
||||
|
||||
if usersPath != "" {
|
||||
contents, err := ReadFile(usersPath)
|
||||
|
||||
if file != "" {
|
||||
contents, err := ReadFile(file)
|
||||
if err != nil {
|
||||
return []config.User{}, err
|
||||
}
|
||||
|
||||
lines := strings.SplitSeq(contents, "\n")
|
||||
|
||||
for line := range lines {
|
||||
lineTrimmed := strings.TrimSpace(line)
|
||||
if lineTrimmed == "" {
|
||||
continue
|
||||
}
|
||||
usersStr = append(usersStr, lineTrimmed)
|
||||
if users != "" {
|
||||
users += ","
|
||||
}
|
||||
users += ParseFileToLine(contents)
|
||||
}
|
||||
|
||||
return ParseUsers(usersStr)
|
||||
return ParseUsers(users)
|
||||
}
|
||||
|
||||
func ParseUser(userStr string) (config.User, error) {
|
||||
if strings.Contains(userStr, "$$") {
|
||||
userStr = strings.ReplaceAll(userStr, "$$", "$")
|
||||
func ParseUser(user string) (config.User, error) {
|
||||
if strings.Contains(user, "$$") {
|
||||
user = strings.ReplaceAll(user, "$$", "$")
|
||||
}
|
||||
|
||||
parts := strings.SplitN(userStr, ":", 4)
|
||||
userSplit := strings.Split(user, ":")
|
||||
|
||||
if len(parts) < 2 || len(parts) > 3 {
|
||||
if len(userSplit) < 2 || len(userSplit) > 3 {
|
||||
return config.User{}, errors.New("invalid user format")
|
||||
}
|
||||
|
||||
for i, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed == "" {
|
||||
for _, userPart := range userSplit {
|
||||
if strings.TrimSpace(userPart) == "" {
|
||||
return config.User{}, errors.New("invalid user format")
|
||||
}
|
||||
parts[i] = trimmed
|
||||
}
|
||||
|
||||
user := config.User{
|
||||
Username: parts[0],
|
||||
Password: parts[1],
|
||||
if len(userSplit) == 2 {
|
||||
return config.User{
|
||||
Username: strings.TrimSpace(userSplit[0]),
|
||||
Password: strings.TrimSpace(userSplit[1]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(parts) == 3 {
|
||||
user.TotpSecret = parts[2]
|
||||
}
|
||||
|
||||
return user, nil
|
||||
return config.User{
|
||||
Username: strings.TrimSpace(userSplit[0]),
|
||||
Password: strings.TrimSpace(userSplit[1]),
|
||||
TotpSecret: strings.TrimSpace(userSplit[2]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestGetUsers(t *testing.T) {
|
||||
defer os.Remove("/tmp/tinyauth_users_test.txt")
|
||||
|
||||
// Test file
|
||||
users, err := utils.GetUsers([]string{}, "/tmp/tinyauth_users_test.txt")
|
||||
users, err := utils.GetUsers("", "/tmp/tinyauth_users_test.txt")
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestGetUsers(t *testing.T) {
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[1].Password)
|
||||
|
||||
// Test config
|
||||
users, err = utils.GetUsers([]string{"user3:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", "user4:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G"}, "")
|
||||
users, err = utils.GetUsers("user3:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G,user4:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", "")
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestGetUsers(t *testing.T) {
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[1].Password)
|
||||
|
||||
// Test both
|
||||
users, err = utils.GetUsers([]string{"user5:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G"}, "/tmp/tinyauth_users_test.txt")
|
||||
users, err = utils.GetUsers("user5:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", "/tmp/tinyauth_users_test.txt")
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -60,14 +60,14 @@ func TestGetUsers(t *testing.T) {
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[2].Password)
|
||||
|
||||
// Test empty
|
||||
users, err = utils.GetUsers([]string{}, "")
|
||||
users, err = utils.GetUsers("", "")
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(users))
|
||||
|
||||
// Test non-existent file
|
||||
users, err = utils.GetUsers([]string{}, "/tmp/non_existent_file.txt")
|
||||
users, err = utils.GetUsers("", "/tmp/non_existent_file.txt")
|
||||
|
||||
assert.ErrorContains(t, err, "no such file or directory")
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestGetUsers(t *testing.T) {
|
||||
|
||||
func TestParseUsers(t *testing.T) {
|
||||
// Valid users
|
||||
users, err := utils.ParseUsers([]string{"user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", "user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF"}) // user2 has TOTP
|
||||
users, err := utils.ParseUsers("user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G,user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF") // user2 has TOTP
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -90,7 +90,7 @@ func TestParseUsers(t *testing.T) {
|
||||
assert.Equal(t, "ABCDEF", users[1].TotpSecret)
|
||||
|
||||
// Valid weirdly spaced users
|
||||
users, err = utils.ParseUsers([]string{" user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G ", " user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF "}) // Spacing is on purpose
|
||||
users, err = utils.ParseUsers(" user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G , user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF ") // Spacing is on purpose
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(users))
|
||||
|
||||
Reference in New Issue
Block a user