mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-01-10 17:32:31 +00:00
Compare commits
9 Commits
feat/globa
...
feat/ldap-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b2bf3902c | ||
|
|
467c580ec4 | ||
|
|
98c0d7be24 | ||
|
|
e3f92ce4fc | ||
|
|
454612226b | ||
|
|
0aa8037edc | ||
|
|
8872e68589 | ||
|
|
1ffb838c0f | ||
|
|
caf993a738 |
@@ -16,7 +16,7 @@
|
||||
"axios": "^1.13.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^25.7.3",
|
||||
"i18next": "^25.7.4",
|
||||
"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.11.0",
|
||||
"react-router": "^7.12.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": "^16.5.0",
|
||||
"globals": "^17.0.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@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
||||
"globals": ["globals@17.0.0", "", {}, "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw=="],
|
||||
|
||||
"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.3", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-2XaT+HpYGuc2uTExq9TVRhLsso+Dxym6PWaKpn36wfBmTI779OQ7iP/XaZHzrnGyzU4SHpFrTYLKfVyBfAhVNA=="],
|
||||
"i18next": ["i18next@25.7.4", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw=="],
|
||||
|
||||
"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.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-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-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.3",
|
||||
"i18next": "^25.7.4",
|
||||
"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.11.0",
|
||||
"react-router": "^7.12.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": "^16.5.0",
|
||||
"globals": "^17.0.0",
|
||||
"prettier": "3.7.4",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
|
||||
1
internal/assets/migrations/000005_ldap_groups.down.sql
Normal file
1
internal/assets/migrations/000005_ldap_groups.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "sessions" DROP COLUMN "ldap_groups";
|
||||
1
internal/assets/migrations/000005_ldap_groups.up.sql
Normal file
1
internal/assets/migrations/000005_ldap_groups.up.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "sessions" ADD COLUMN "ldap_groups" TEXT;
|
||||
@@ -2,7 +2,6 @@ package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/middleware"
|
||||
@@ -15,7 +14,7 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
|
||||
engine.Use(gin.Recovery())
|
||||
|
||||
if len(app.config.Server.TrustedProxies) > 0 {
|
||||
err := engine.SetTrustedProxies(strings.Split(app.config.Server.TrustedProxies, ","))
|
||||
err := engine.SetTrustedProxies(app.config.Server.TrustedProxies)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to set trusted proxies: %w", err)
|
||||
|
||||
@@ -67,6 +67,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
|
||||
LoginTimeout: app.config.Auth.LoginTimeout,
|
||||
LoginMaxRetries: app.config.Auth.LoginMaxRetries,
|
||||
SessionCookieName: app.context.sessionCookieName,
|
||||
IP: app.config.Auth.IP,
|
||||
}, dockerService, services.ldapService, queries)
|
||||
|
||||
err = authService.Init()
|
||||
|
||||
@@ -33,24 +33,30 @@ 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 {
|
||||
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"`
|
||||
SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime"`
|
||||
LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout"`
|
||||
LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries"`
|
||||
IP IPConfig `description:"IP whitelisting config options." yaml:"ip"`
|
||||
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"`
|
||||
SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime"`
|
||||
LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout"`
|
||||
LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries"`
|
||||
}
|
||||
|
||||
type IPConfig struct {
|
||||
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
|
||||
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
@@ -116,23 +122,16 @@ type User struct {
|
||||
TotpSecret string
|
||||
}
|
||||
|
||||
type LdapUser struct {
|
||||
DN string
|
||||
Groups []string
|
||||
}
|
||||
|
||||
type UserSearch struct {
|
||||
Username string
|
||||
Type string // local, ldap or unknown
|
||||
}
|
||||
|
||||
type SessionCookie struct {
|
||||
UUID string
|
||||
Username string
|
||||
Name string
|
||||
Email string
|
||||
Provider string
|
||||
TotpPending bool
|
||||
OAuthGroups string
|
||||
OAuthName string
|
||||
OAuthSub string
|
||||
}
|
||||
|
||||
type UserContext struct {
|
||||
Username string
|
||||
Name string
|
||||
@@ -145,6 +144,7 @@ type UserContext struct {
|
||||
TotpEnabled bool
|
||||
OAuthName string
|
||||
OAuthSub string
|
||||
LdapGroups string
|
||||
}
|
||||
|
||||
// API responses and queries
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
@@ -190,7 +191,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
username = strings.Replace(user.Email, "@", "_", -1)
|
||||
}
|
||||
|
||||
sessionCookie := config.SessionCookie{
|
||||
sessionCookie := repository.Session{
|
||||
Username: username,
|
||||
Name: name,
|
||||
Email: user.Email,
|
||||
|
||||
@@ -179,9 +179,9 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if userContext.IsLoggedIn {
|
||||
appAllowed := controller.auth.IsResourceAllowed(c, userContext, acls)
|
||||
userAllowed := controller.auth.IsUserAllowed(c, userContext, acls)
|
||||
|
||||
if !appAllowed {
|
||||
if !userAllowed {
|
||||
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
|
||||
|
||||
if req.Proxy == "nginx" || !isBrowser {
|
||||
|
||||
@@ -57,7 +57,7 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En
|
||||
Password: "$2a$10$ne6z693sTgzT3ePoQ05PgOecUHnBjM7sSNj6M.l5CLUP.f6NyCnt.", // test
|
||||
},
|
||||
},
|
||||
OauthWhitelist: "",
|
||||
OauthWhitelist: []string{},
|
||||
SessionExpiry: 3600,
|
||||
SessionMaxLifetime: 0,
|
||||
SecureCookie: false,
|
||||
@@ -140,7 +140,7 @@ func TestProxyHandler(t *testing.T) {
|
||||
// Test logged in user
|
||||
c := gin.CreateTestContextOnly(recorder, router)
|
||||
|
||||
err := authService.CreateSessionCookie(c, &config.SessionCookie{
|
||||
err := authService.CreateSessionCookie(c, &repository.Session{
|
||||
Username: "testuser",
|
||||
Name: "testuser",
|
||||
Email: "testuser@example.com",
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
@@ -108,7 +108,7 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
if user.TotpSecret != "" {
|
||||
log.Debug().Str("username", req.Username).Msg("User has TOTP enabled, requiring TOTP verification")
|
||||
|
||||
err := controller.auth.CreateSessionCookie(c, &config.SessionCookie{
|
||||
err := controller.auth.CreateSessionCookie(c, &repository.Session{
|
||||
Username: user.Username,
|
||||
Name: utils.Capitalize(req.Username),
|
||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(req.Username), controller.config.CookieDomain),
|
||||
@@ -134,13 +134,28 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
sessionCookie := config.SessionCookie{
|
||||
sessionCookie := repository.Session{
|
||||
Username: req.Username,
|
||||
Name: utils.Capitalize(req.Username),
|
||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(req.Username), controller.config.CookieDomain),
|
||||
Provider: "username",
|
||||
}
|
||||
|
||||
if userSearch.Type == "ldap" {
|
||||
ldapUser, err := controller.auth.GetLdapUser(userSearch.Username)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("username", req.Username).Msg("Failed to get LDAP user details")
|
||||
c.JSON(500, gin.H{
|
||||
"status": 500,
|
||||
"message": "Internal Server Error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sessionCookie.LdapGroups = strings.Join(ldapUser.Groups, ",")
|
||||
}
|
||||
|
||||
log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
|
||||
err = controller.auth.CreateSessionCookie(c, &sessionCookie)
|
||||
@@ -237,7 +252,7 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
|
||||
controller.auth.RecordLoginAttempt(context.Username, true)
|
||||
|
||||
sessionCookie := config.SessionCookie{
|
||||
sessionCookie := repository.Session{
|
||||
Username: user.Username,
|
||||
Name: utils.Capitalize(user.Username),
|
||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(user.Username), controller.config.CookieDomain),
|
||||
|
||||
@@ -60,7 +60,7 @@ func setupUserController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.Eng
|
||||
TotpSecret: totpSecret,
|
||||
},
|
||||
},
|
||||
OauthWhitelist: "",
|
||||
OauthWhitelist: []string{},
|
||||
SessionExpiry: 3600,
|
||||
SessionMaxLifetime: 0,
|
||||
SecureCookie: false,
|
||||
|
||||
@@ -74,6 +74,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
Email: cookie.Email,
|
||||
Provider: "username",
|
||||
IsLoggedIn: true,
|
||||
LdapGroups: cookie.LdapGroups,
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
@@ -155,7 +156,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
Username: user.Username,
|
||||
Name: utils.Capitalize(user.Username),
|
||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(user.Username), m.config.CookieDomain),
|
||||
Provider: "basic",
|
||||
Provider: "username",
|
||||
IsLoggedIn: true,
|
||||
TotpEnabled: user.TotpSecret != "",
|
||||
})
|
||||
@@ -163,12 +164,22 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
return
|
||||
case "ldap":
|
||||
log.Debug().Msg("Basic auth user is LDAP")
|
||||
|
||||
ldapUser, err := m.auth.GetLdapUser(basic.Username)
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Error retrieving LDAP user details")
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: basic.Username,
|
||||
Name: utils.Capitalize(basic.Username),
|
||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), m.config.CookieDomain),
|
||||
Provider: "basic",
|
||||
Provider: "ldap",
|
||||
IsLoggedIn: true,
|
||||
LdapGroups: strings.Join(ldapUser.Groups, ","),
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
|
||||
@@ -16,4 +16,5 @@ type Session struct {
|
||||
CreatedAt int64
|
||||
OAuthName string
|
||||
OAuthSub string
|
||||
LdapGroups string
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: query.sql
|
||||
// source: queries.sql
|
||||
|
||||
package repository
|
||||
|
||||
@@ -21,11 +21,12 @@ INSERT INTO sessions (
|
||||
"expiry",
|
||||
"created_at",
|
||||
"oauth_name",
|
||||
"oauth_sub"
|
||||
"oauth_sub",
|
||||
"ldap_groups"
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING uuid, username, email, name, provider, totp_pending, oauth_groups, expiry, created_at, oauth_name, oauth_sub
|
||||
RETURNING uuid, username, email, name, provider, totp_pending, oauth_groups, expiry, created_at, oauth_name, oauth_sub, ldap_groups
|
||||
`
|
||||
|
||||
type CreateSessionParams struct {
|
||||
@@ -40,6 +41,7 @@ type CreateSessionParams struct {
|
||||
CreatedAt int64
|
||||
OAuthName string
|
||||
OAuthSub string
|
||||
LdapGroups string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) {
|
||||
@@ -55,6 +57,7 @@ func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (S
|
||||
arg.CreatedAt,
|
||||
arg.OAuthName,
|
||||
arg.OAuthSub,
|
||||
arg.LdapGroups,
|
||||
)
|
||||
var i Session
|
||||
err := row.Scan(
|
||||
@@ -69,6 +72,7 @@ func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (S
|
||||
&i.CreatedAt,
|
||||
&i.OAuthName,
|
||||
&i.OAuthSub,
|
||||
&i.LdapGroups,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -94,7 +98,7 @@ func (q *Queries) DeleteSession(ctx context.Context, uuid string) error {
|
||||
}
|
||||
|
||||
const getSession = `-- name: GetSession :one
|
||||
SELECT uuid, username, email, name, provider, totp_pending, oauth_groups, expiry, created_at, oauth_name, oauth_sub FROM "sessions"
|
||||
SELECT uuid, username, email, name, provider, totp_pending, oauth_groups, expiry, created_at, oauth_name, oauth_sub, ldap_groups FROM "sessions"
|
||||
WHERE "uuid" = ?
|
||||
`
|
||||
|
||||
@@ -113,6 +117,7 @@ func (q *Queries) GetSession(ctx context.Context, uuid string) (Session, error)
|
||||
&i.CreatedAt,
|
||||
&i.OAuthName,
|
||||
&i.OAuthSub,
|
||||
&i.LdapGroups,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -127,9 +132,10 @@ UPDATE "sessions" SET
|
||||
"oauth_groups" = ?,
|
||||
"expiry" = ?,
|
||||
"oauth_name" = ?,
|
||||
"oauth_sub" = ?
|
||||
"oauth_sub" = ?,
|
||||
"ldap_groups" = ?
|
||||
WHERE "uuid" = ?
|
||||
RETURNING uuid, username, email, name, provider, totp_pending, oauth_groups, expiry, created_at, oauth_name, oauth_sub
|
||||
RETURNING uuid, username, email, name, provider, totp_pending, oauth_groups, expiry, created_at, oauth_name, oauth_sub, ldap_groups
|
||||
`
|
||||
|
||||
type UpdateSessionParams struct {
|
||||
@@ -142,6 +148,7 @@ type UpdateSessionParams struct {
|
||||
Expiry int64
|
||||
OAuthName string
|
||||
OAuthSub string
|
||||
LdapGroups string
|
||||
UUID string
|
||||
}
|
||||
|
||||
@@ -156,6 +163,7 @@ func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) (S
|
||||
arg.Expiry,
|
||||
arg.OAuthName,
|
||||
arg.OAuthSub,
|
||||
arg.LdapGroups,
|
||||
arg.UUID,
|
||||
)
|
||||
var i Session
|
||||
@@ -171,6 +179,7 @@ func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) (S
|
||||
&i.CreatedAt,
|
||||
&i.OAuthName,
|
||||
&i.OAuthSub,
|
||||
&i.LdapGroups,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -27,7 +27,7 @@ type LoginAttempt struct {
|
||||
|
||||
type AuthServiceConfig struct {
|
||||
Users []config.User
|
||||
OauthWhitelist string
|
||||
OauthWhitelist []string
|
||||
SessionExpiry int
|
||||
SessionMaxLifetime int
|
||||
SecureCookie bool
|
||||
@@ -35,6 +35,7 @@ type AuthServiceConfig struct {
|
||||
LoginTimeout int
|
||||
LoginMaxRetries int
|
||||
SessionCookieName string
|
||||
IP config.IPConfig
|
||||
}
|
||||
|
||||
type AuthService struct {
|
||||
@@ -69,7 +70,7 @@ func (auth *AuthService) SearchUser(username string) config.UserSearch {
|
||||
}
|
||||
|
||||
if auth.ldap != nil {
|
||||
userDN, err := auth.ldap.Search(username)
|
||||
userDN, err := auth.ldap.GetUserDN(username)
|
||||
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("username", username).Msg("Failed to search for user in LDAP")
|
||||
@@ -130,6 +131,19 @@ func (auth *AuthService) GetLocalUser(username string) config.User {
|
||||
return config.User{}
|
||||
}
|
||||
|
||||
func (auth *AuthService) GetLdapUser(userDN string) (config.LdapUser, error) {
|
||||
groups, err := auth.ldap.GetUserGroups(userDN)
|
||||
|
||||
if err != nil {
|
||||
return config.LdapUser{}, err
|
||||
}
|
||||
|
||||
return config.LdapUser{
|
||||
DN: userDN,
|
||||
Groups: groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) CheckPassword(user config.User, password string) bool {
|
||||
return bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) == nil
|
||||
}
|
||||
@@ -186,10 +200,10 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
|
||||
}
|
||||
|
||||
func (auth *AuthService) IsEmailWhitelisted(email string) bool {
|
||||
return utils.CheckFilter(auth.config.OauthWhitelist, email)
|
||||
return utils.CheckFilter(strings.Join(auth.config.OauthWhitelist, ","), email)
|
||||
}
|
||||
|
||||
func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.SessionCookie) error {
|
||||
func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *repository.Session) error {
|
||||
uuid, err := uuid.NewRandom()
|
||||
|
||||
if err != nil {
|
||||
@@ -216,6 +230,7 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
|
||||
CreatedAt: time.Now().Unix(),
|
||||
OAuthName: data.OAuthName,
|
||||
OAuthSub: data.OAuthSub,
|
||||
LdapGroups: data.LdapGroups,
|
||||
}
|
||||
|
||||
_, err = auth.queries.CreateSession(c, session)
|
||||
@@ -269,6 +284,7 @@ func (auth *AuthService) RefreshSessionCookie(c *gin.Context) error {
|
||||
OAuthName: session.OAuthName,
|
||||
OAuthSub: session.OAuthSub,
|
||||
UUID: session.UUID,
|
||||
LdapGroups: session.LdapGroups,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -299,20 +315,20 @@ func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie, error) {
|
||||
func (auth *AuthService) GetSessionCookie(c *gin.Context) (repository.Session, error) {
|
||||
cookie, err := c.Cookie(auth.config.SessionCookieName)
|
||||
|
||||
if err != nil {
|
||||
return config.SessionCookie{}, err
|
||||
return repository.Session{}, err
|
||||
}
|
||||
|
||||
session, err := auth.queries.GetSession(c, cookie)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return config.SessionCookie{}, fmt.Errorf("session not found")
|
||||
return repository.Session{}, fmt.Errorf("session not found")
|
||||
}
|
||||
return config.SessionCookie{}, err
|
||||
return repository.Session{}, err
|
||||
}
|
||||
|
||||
currentTime := time.Now().Unix()
|
||||
@@ -323,7 +339,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to delete session exceeding max lifetime")
|
||||
}
|
||||
return config.SessionCookie{}, fmt.Errorf("session expired due to max lifetime exceeded")
|
||||
return repository.Session{}, fmt.Errorf("session expired due to max lifetime exceeded")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,10 +348,10 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to delete expired session")
|
||||
}
|
||||
return config.SessionCookie{}, fmt.Errorf("session expired")
|
||||
return repository.Session{}, fmt.Errorf("session expired")
|
||||
}
|
||||
|
||||
return config.SessionCookie{
|
||||
return repository.Session{
|
||||
UUID: session.UUID,
|
||||
Username: session.Username,
|
||||
Email: session.Email,
|
||||
@@ -345,6 +361,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
|
||||
OAuthGroups: session.OAuthGroups,
|
||||
OAuthName: session.OAuthName,
|
||||
OAuthSub: session.OAuthSub,
|
||||
LdapGroups: session.LdapGroups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -352,7 +369,7 @@ func (auth *AuthService) UserAuthConfigured() bool {
|
||||
return len(auth.config.Users) > 0 || auth.ldap != nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) IsResourceAllowed(c *gin.Context, context config.UserContext, acls config.App) bool {
|
||||
func (auth *AuthService) IsUserAllowed(c *gin.Context, context config.UserContext, acls config.App) bool {
|
||||
if context.OAuth {
|
||||
log.Debug().Msg("Checking OAuth whitelist")
|
||||
return utils.CheckFilter(acls.OAuth.Whitelist, context.Email)
|
||||
@@ -435,7 +452,11 @@ func (auth *AuthService) GetBasicAuth(c *gin.Context) *config.User {
|
||||
}
|
||||
|
||||
func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
|
||||
for _, blocked := range acls.Block {
|
||||
// Merge the global and app IP filter
|
||||
blockedIps := append(auth.config.IP.Block, acls.Block...)
|
||||
allowedIPs := append(auth.config.IP.Allow, acls.Allow...)
|
||||
|
||||
for _, blocked := range blockedIps {
|
||||
res, err := utils.FilterIP(blocked, ip)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
|
||||
@@ -447,7 +468,7 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
for _, allowed := range acls.Allow {
|
||||
for _, allowed := range allowedIPs {
|
||||
res, err := utils.FilterIP(allowed, ip)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
|
||||
@@ -459,7 +480,7 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if len(acls.Allow) > 0 {
|
||||
if len(allowedIPs) > 0 {
|
||||
log.Debug().Str("ip", ip).Msg("IP not in allow list, denying access")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -116,7 +118,7 @@ func (ldap *LdapService) connect() (*ldapgo.Conn, error) {
|
||||
return ldap.conn, nil
|
||||
}
|
||||
|
||||
func (ldap *LdapService) Search(username string) (string, error) {
|
||||
func (ldap *LdapService) GetUserDN(username string) (string, error) {
|
||||
// Escape the username to prevent LDAP injection
|
||||
escapedUsername := ldapgo.EscapeFilter(username)
|
||||
filter := fmt.Sprintf(ldap.config.SearchFilter, escapedUsername)
|
||||
@@ -145,6 +147,47 @@ func (ldap *LdapService) Search(username string) (string, error) {
|
||||
return userDN, nil
|
||||
}
|
||||
|
||||
func (ldap *LdapService) GetUserGroups(userDN string) ([]string, error) {
|
||||
searchRequest := ldapgo.NewSearchRequest(
|
||||
ldap.config.BaseDN,
|
||||
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
|
||||
"(objectclass=groupOfUniqueNames)",
|
||||
[]string{"uniquemember"},
|
||||
nil,
|
||||
)
|
||||
|
||||
ldap.mutex.Lock()
|
||||
defer ldap.mutex.Unlock()
|
||||
|
||||
searchResult, err := ldap.conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
groupDNs := []string{}
|
||||
|
||||
for _, entry := range searchResult.Entries {
|
||||
memberAttributes := entry.GetAttributeValues("uniquemember")
|
||||
// no need to escape username here, if it's malicious it won't match anything
|
||||
if slices.Contains(memberAttributes, userDN) {
|
||||
groupDNs = append(groupDNs, entry.DN)
|
||||
}
|
||||
}
|
||||
|
||||
// Should work for most ldap providers?
|
||||
groups := []string{}
|
||||
|
||||
for _, groupDN := range groupDNs {
|
||||
groupDN = strings.TrimPrefix(groupDN, "cn=")
|
||||
parts := strings.SplitN(groupDN, ",", 2)
|
||||
if len(parts) > 0 {
|
||||
groups = append(groups, parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (ldap *LdapService) BindService(rebind bool) error {
|
||||
// Locks must not be used for initial binding attempt
|
||||
if rebind {
|
||||
|
||||
@@ -7,22 +7,14 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
func ParseUsers(users string) ([]config.User, error) {
|
||||
var usersParsed []config.User
|
||||
func ParseUsers(usersStr []string) ([]config.User, error) {
|
||||
var users []config.User
|
||||
|
||||
users = strings.TrimSpace(users)
|
||||
|
||||
if users == "" {
|
||||
if len(usersStr) == 0 {
|
||||
return []config.User{}, nil
|
||||
}
|
||||
|
||||
userList := strings.Split(users, ",")
|
||||
|
||||
if len(userList) == 0 {
|
||||
return []config.User{}, errors.New("invalid user format")
|
||||
}
|
||||
|
||||
for _, user := range userList {
|
||||
for _, user := range usersStr {
|
||||
if strings.TrimSpace(user) == "" {
|
||||
continue
|
||||
}
|
||||
@@ -30,64 +22,71 @@ func ParseUsers(users string) ([]config.User, error) {
|
||||
if err != nil {
|
||||
return []config.User{}, err
|
||||
}
|
||||
usersParsed = append(usersParsed, parsed)
|
||||
users = append(users, parsed)
|
||||
}
|
||||
|
||||
return usersParsed, nil
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetUsers(conf string, file string) ([]config.User, error) {
|
||||
var users string
|
||||
func GetUsers(usersCfg []string, usersPath string) ([]config.User, error) {
|
||||
var usersStr []string
|
||||
|
||||
if conf == "" && file == "" {
|
||||
if len(usersCfg) == 0 && usersPath == "" {
|
||||
return []config.User{}, nil
|
||||
}
|
||||
|
||||
if conf != "" {
|
||||
users += conf
|
||||
if len(usersCfg) > 0 {
|
||||
usersStr = append(usersStr, usersCfg...)
|
||||
}
|
||||
|
||||
if file != "" {
|
||||
contents, err := ReadFile(file)
|
||||
if usersPath != "" {
|
||||
contents, err := ReadFile(usersPath)
|
||||
|
||||
if err != nil {
|
||||
return []config.User{}, err
|
||||
}
|
||||
if users != "" {
|
||||
users += ","
|
||||
|
||||
lines := strings.SplitSeq(contents, "\n")
|
||||
|
||||
for line := range lines {
|
||||
lineTrimmed := strings.TrimSpace(line)
|
||||
if lineTrimmed == "" {
|
||||
continue
|
||||
}
|
||||
usersStr = append(usersStr, lineTrimmed)
|
||||
}
|
||||
users += ParseFileToLine(contents)
|
||||
}
|
||||
|
||||
return ParseUsers(users)
|
||||
return ParseUsers(usersStr)
|
||||
}
|
||||
|
||||
func ParseUser(user string) (config.User, error) {
|
||||
if strings.Contains(user, "$$") {
|
||||
user = strings.ReplaceAll(user, "$$", "$")
|
||||
func ParseUser(userStr string) (config.User, error) {
|
||||
if strings.Contains(userStr, "$$") {
|
||||
userStr = strings.ReplaceAll(userStr, "$$", "$")
|
||||
}
|
||||
|
||||
userSplit := strings.Split(user, ":")
|
||||
parts := strings.SplitN(userStr, ":", 4)
|
||||
|
||||
if len(userSplit) < 2 || len(userSplit) > 3 {
|
||||
if len(parts) < 2 || len(parts) > 3 {
|
||||
return config.User{}, errors.New("invalid user format")
|
||||
}
|
||||
|
||||
for _, userPart := range userSplit {
|
||||
if strings.TrimSpace(userPart) == "" {
|
||||
for i, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed == "" {
|
||||
return config.User{}, errors.New("invalid user format")
|
||||
}
|
||||
parts[i] = trimmed
|
||||
}
|
||||
|
||||
if len(userSplit) == 2 {
|
||||
return config.User{
|
||||
Username: strings.TrimSpace(userSplit[0]),
|
||||
Password: strings.TrimSpace(userSplit[1]),
|
||||
}, nil
|
||||
user := config.User{
|
||||
Username: parts[0],
|
||||
Password: parts[1],
|
||||
}
|
||||
|
||||
return config.User{
|
||||
Username: strings.TrimSpace(userSplit[0]),
|
||||
Password: strings.TrimSpace(userSplit[1]),
|
||||
TotpSecret: strings.TrimSpace(userSplit[2]),
|
||||
}, nil
|
||||
if len(parts) == 3 {
|
||||
user.TotpSecret = parts[2]
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestGetUsers(t *testing.T) {
|
||||
defer os.Remove("/tmp/tinyauth_users_test.txt")
|
||||
|
||||
// Test file
|
||||
users, err := utils.GetUsers("", "/tmp/tinyauth_users_test.txt")
|
||||
users, err := utils.GetUsers([]string{}, "/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("user3:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G,user4:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", "")
|
||||
users, err = utils.GetUsers([]string{"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("user5:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", "/tmp/tinyauth_users_test.txt")
|
||||
users, err = utils.GetUsers([]string{"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("", "")
|
||||
users, err = utils.GetUsers([]string{}, "")
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(users))
|
||||
|
||||
// Test non-existent file
|
||||
users, err = utils.GetUsers("", "/tmp/non_existent_file.txt")
|
||||
users, err = utils.GetUsers([]string{}, "/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("user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G,user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF") // user2 has TOTP
|
||||
users, err := utils.ParseUsers([]string{"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(" user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G , user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF ") // Spacing is on purpose
|
||||
users, err = utils.ParseUsers([]string{" 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))
|
||||
|
||||
@@ -10,9 +10,10 @@ INSERT INTO sessions (
|
||||
"expiry",
|
||||
"created_at",
|
||||
"oauth_name",
|
||||
"oauth_sub"
|
||||
"oauth_sub",
|
||||
"ldap_groups"
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
@@ -34,7 +35,8 @@ UPDATE "sessions" SET
|
||||
"oauth_groups" = ?,
|
||||
"expiry" = ?,
|
||||
"oauth_name" = ?,
|
||||
"oauth_sub" = ?
|
||||
"oauth_sub" = ?,
|
||||
"ldap_groups" = ?
|
||||
WHERE "uuid" = ?
|
||||
RETURNING *;
|
||||
|
||||
@@ -9,5 +9,6 @@ CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"expiry" INTEGER NOT NULL,
|
||||
"created_at" INTEGER NOT NULL,
|
||||
"oauth_name" TEXT NULL,
|
||||
"oauth_sub" TEXT NULL
|
||||
"oauth_sub" TEXT NULL,
|
||||
"ldap_groups" TEXT NULL
|
||||
);
|
||||
6
sqlc.yml
6
sqlc.yml
@@ -1,8 +1,8 @@
|
||||
version: "2"
|
||||
sql:
|
||||
- engine: "sqlite"
|
||||
queries: "query.sql"
|
||||
schema: "schema.sql"
|
||||
queries: "sql/queries.sql"
|
||||
schema: "sql/schema.sql"
|
||||
gen:
|
||||
go:
|
||||
package: "repository"
|
||||
@@ -19,3 +19,5 @@ sql:
|
||||
go_type: "string"
|
||||
- column: "sessions.oauth_sub"
|
||||
go_type: "string"
|
||||
- column: "sessions.ldap_groups"
|
||||
go_type: "string"
|
||||
|
||||
Reference in New Issue
Block a user