mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-12-31 20:42:31 +00:00
Merge branch 'main' into refactor/sqlc
This commit is contained in:
1
internal/assets/migrations/000003_oauth_sub.down.sql
Normal file
1
internal/assets/migrations/000003_oauth_sub.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "sessions" DROP COLUMN "oauth_sub";
|
||||
1
internal/assets/migrations/000003_oauth_sub.up.sql
Normal file
1
internal/assets/migrations/000003_oauth_sub.up.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "sessions" ADD COLUMN "oauth_sub" TEXT;
|
||||
@@ -11,10 +11,11 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/controller"
|
||||
"tinyauth/internal/repository"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
@@ -3,8 +3,9 @@ package bootstrap
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"tinyauth/internal/controller"
|
||||
"tinyauth/internal/middleware"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"tinyauth/internal/repository"
|
||||
"tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -45,7 +45,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
|
||||
|
||||
services.dockerService = dockerService
|
||||
|
||||
accessControlsService := service.NewAccessControlsService(dockerService)
|
||||
accessControlsService := service.NewAccessControlsService(dockerService, app.config.Apps)
|
||||
|
||||
err = accessControlsService.Init()
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ type Config struct {
|
||||
LogJSON bool `description:"Enable JSON formatted logs." yaml:"logJSON"`
|
||||
Server ServerConfig `description:"Server configuration." yaml:"server"`
|
||||
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
|
||||
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
|
||||
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"`
|
||||
UI UIConfig `description:"UI customization." yaml:"ui"`
|
||||
Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"`
|
||||
@@ -79,6 +80,7 @@ const DefaultNamePrefix = "TINYAUTH_"
|
||||
// OAuth/OIDC config
|
||||
|
||||
type Claims struct {
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
@@ -125,6 +127,7 @@ type SessionCookie struct {
|
||||
TotpPending bool
|
||||
OAuthGroups string
|
||||
OAuthName string
|
||||
OAuthSub string
|
||||
}
|
||||
|
||||
type UserContext struct {
|
||||
@@ -138,6 +141,7 @@ type UserContext struct {
|
||||
OAuthGroups string
|
||||
TotpEnabled bool
|
||||
OAuthName string
|
||||
OAuthSub string
|
||||
}
|
||||
|
||||
// API responses and queries
|
||||
@@ -153,61 +157,55 @@ type RedirectQuery struct {
|
||||
RedirectURI string `url:"redirect_uri"`
|
||||
}
|
||||
|
||||
// Labels
|
||||
// ACLs
|
||||
|
||||
type Apps struct {
|
||||
Apps map[string]App
|
||||
Apps map[string]App `description:"App ACLs configuration." yaml:"apps"`
|
||||
}
|
||||
|
||||
type App struct {
|
||||
Config AppConfig
|
||||
Users AppUsers
|
||||
OAuth AppOAuth
|
||||
IP AppIP
|
||||
Response AppResponse
|
||||
Path AppPath
|
||||
Config AppConfig `description:"App configuration." yaml:"config"`
|
||||
Users AppUsers `description:"User access configuration." yaml:"users"`
|
||||
OAuth AppOAuth `description:"OAuth access configuration." yaml:"oauth"`
|
||||
IP AppIP `description:"IP access configuration." yaml:"ip"`
|
||||
Response AppResponse `description:"Response customization." yaml:"response"`
|
||||
Path AppPath `description:"Path access configuration." yaml:"path"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Domain string
|
||||
Domain string `description:"The domain of the app." yaml:"domain"`
|
||||
}
|
||||
|
||||
type AppUsers struct {
|
||||
Allow string
|
||||
Block string
|
||||
Allow string `description:"Comma-separated list of allowed users." yaml:"allow"`
|
||||
Block string `description:"Comma-separated list of blocked users." yaml:"block"`
|
||||
}
|
||||
|
||||
type AppOAuth struct {
|
||||
Whitelist string
|
||||
Groups string
|
||||
Whitelist string `description:"Comma-separated list of allowed OAuth groups." yaml:"whitelist"`
|
||||
Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups"`
|
||||
}
|
||||
|
||||
type AppIP struct {
|
||||
Allow []string
|
||||
Block []string
|
||||
Bypass []string
|
||||
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
|
||||
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
|
||||
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication." yaml:"bypass"`
|
||||
}
|
||||
|
||||
type AppResponse struct {
|
||||
Headers []string
|
||||
BasicAuth AppBasicAuth
|
||||
Headers []string `description:"Custom headers to add to the response." yaml:"headers"`
|
||||
BasicAuth AppBasicAuth `description:"Basic authentication for the app." yaml:"basicAuth"`
|
||||
}
|
||||
|
||||
type AppBasicAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
PasswordFile string
|
||||
Username string `description:"Basic auth username." yaml:"username"`
|
||||
Password string `description:"Basic auth password." yaml:"password"`
|
||||
PasswordFile string `description:"Path to the file containing the basic auth password." yaml:"passwordFile"`
|
||||
}
|
||||
|
||||
type AppPath struct {
|
||||
Allow string
|
||||
Block string
|
||||
}
|
||||
|
||||
// Flags
|
||||
|
||||
type Providers struct {
|
||||
Providers map[string]OAuthServiceConfig
|
||||
Allow string `description:"Comma-separated list of allowed paths." yaml:"allow"`
|
||||
Block string `description:"Comma-separated list of blocked paths." yaml:"block"`
|
||||
}
|
||||
|
||||
// API server
|
||||
|
||||
@@ -3,7 +3,8 @@ package controller
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -20,6 +21,7 @@ type UserContextResponse struct {
|
||||
OAuth bool `json:"oauth"`
|
||||
TotpPending bool `json:"totpPending"`
|
||||
OAuthName string `json:"oauthName"`
|
||||
OAuthSub string `json:"oauthSub"`
|
||||
}
|
||||
|
||||
type AppContextResponse struct {
|
||||
@@ -88,6 +90,7 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
|
||||
OAuth: context.OAuth,
|
||||
TotpPending: context.TotpPending,
|
||||
OAuthName: context.OAuthName,
|
||||
OAuthSub: context.OAuthSub,
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/controller"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -43,6 +44,7 @@ var userContext = config.UserContext{
|
||||
TotpPending: false,
|
||||
OAuthGroups: "",
|
||||
TotpEnabled: false,
|
||||
OAuthSub: "",
|
||||
}
|
||||
|
||||
func setupContextController(middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) {
|
||||
|
||||
@@ -5,9 +5,10 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/service"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
@@ -196,6 +197,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
Provider: req.Provider,
|
||||
OAuthGroups: utils.CoalesceToString(user.Groups),
|
||||
OAuthName: service.GetName(),
|
||||
OAuthSub: user.Sub,
|
||||
}
|
||||
|
||||
log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
|
||||
@@ -5,9 +5,10 @@ import (
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/service"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
@@ -42,7 +43,8 @@ func NewProxyController(config ProxyControllerConfig, router *gin.RouterGroup, a
|
||||
|
||||
func (controller *ProxyController) SetupRoutes() {
|
||||
proxyGroup := controller.router.Group("/auth")
|
||||
proxyGroup.Any("/:proxy", controller.proxyHandler)
|
||||
proxyGroup.GET("/:proxy", controller.proxyHandler)
|
||||
proxyGroup.POST("/:proxy", controller.proxyHandler)
|
||||
}
|
||||
|
||||
func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
@@ -67,15 +69,6 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Proxy != "envoy" && c.Request.Method != http.MethodGet {
|
||||
log.Warn().Str("method", c.Request.Method).Msg("Invalid method for proxy")
|
||||
c.JSON(405, gin.H{
|
||||
"status": 405,
|
||||
"message": "Method Not Allowed",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
isBrowser := strings.Contains(c.Request.Header.Get("Accept"), "text/html")
|
||||
|
||||
if isBrowser {
|
||||
@@ -246,6 +239,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name))
|
||||
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
|
||||
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
|
||||
c.Header("Remote-Sub", utils.SanitizeHeader(userContext.OAuthSub))
|
||||
|
||||
controller.setHeaders(c, acls)
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ package controller_test
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/controller"
|
||||
"tinyauth/internal/service"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -40,7 +41,7 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En
|
||||
assert.NilError(t, dockerService.Init())
|
||||
|
||||
// Access controls
|
||||
accessControlsService := service.NewAccessControlsService(dockerService)
|
||||
accessControlsService := service.NewAccessControlsService(dockerService, map[string]config.App{})
|
||||
|
||||
assert.NilError(t, accessControlsService.Init())
|
||||
|
||||
@@ -80,13 +81,6 @@ func TestProxyHandler(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 400, recorder.Code)
|
||||
|
||||
// Test invalid method
|
||||
recorder = httptest.NewRecorder()
|
||||
req = httptest.NewRequest("POST", "/api/auth/traefik", nil)
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, 405, recorder.Code)
|
||||
|
||||
// Test logged out user (traefik/caddy)
|
||||
recorder = httptest.NewRecorder()
|
||||
req = httptest.NewRequest("GET", "/api/auth/traefik", nil)
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"tinyauth/internal/controller"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
@@ -3,9 +3,10 @@ package controller
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/service"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/controller"
|
||||
"tinyauth/internal/service"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
@@ -3,9 +3,10 @@ package middleware
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/service"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -65,6 +66,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
goto basic
|
||||
}
|
||||
|
||||
m.auth.RefreshSessionCookie(c)
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: cookie.Username,
|
||||
Name: cookie.Name,
|
||||
@@ -89,6 +91,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
goto basic
|
||||
}
|
||||
|
||||
m.auth.RefreshSessionCookie(c)
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: cookie.Username,
|
||||
Name: cookie.Name,
|
||||
@@ -96,6 +99,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
Provider: cookie.Provider,
|
||||
OAuthGroups: cookie.OAuthGroups,
|
||||
OAuthName: cookie.OAuthName,
|
||||
OAuthSub: cookie.OAuthSub,
|
||||
IsLoggedIn: true,
|
||||
OAuth: true,
|
||||
})
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"tinyauth/internal/assets"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/assets"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
14
internal/model/session_model.go
Normal file
14
internal/model/session_model.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
type Session struct {
|
||||
UUID string `gorm:"column:uuid;primaryKey"`
|
||||
Username string `gorm:"column:username"`
|
||||
Email string `gorm:"column:email"`
|
||||
Name string `gorm:"column:name"`
|
||||
Provider string `gorm:"column:provider"`
|
||||
TOTPPending bool `gorm:"column:totp_pending"`
|
||||
OAuthGroups string `gorm:"column:oauth_groups"`
|
||||
Expiry int64 `gorm:"column:expiry"`
|
||||
OAuthName string `gorm:"column:oauth_name"`
|
||||
OAuthSub string `gorm:"column:oauth_sub"`
|
||||
}
|
||||
@@ -1,122 +1,54 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"tinyauth/internal/config"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
/*
|
||||
Environment variable/flag based ACLs are disabled until v5 due to a technical challenge
|
||||
with the current parsing logic.
|
||||
|
||||
The current parser works for simple OAuth provider configs like:
|
||||
- PROVIDERS_MY_AMAZING_PROVIDER_CLIENT_ID
|
||||
|
||||
However, it breaks down when handling nested structs required for ACLs. The custom parsing
|
||||
solution that worked for v4 OAuth providers is incompatible with the ACL parsing logic,
|
||||
making the codebase unmaintainable and fragile.
|
||||
|
||||
A solution is being considered for v5 that would standardize the format to something like:
|
||||
- TINYAUTH_PROVIDERS_GOOGLE_CLIENTSECRET
|
||||
- TINYAUTH_APPS_MYAPP_CONFIG_DOMAIN
|
||||
|
||||
This would allow the Traefik parser to handle everything consistently, but requires a
|
||||
config migration. Until this is resolved, environment-based ACLs are disabled and only
|
||||
Docker label-based ACLs are supported.
|
||||
|
||||
See: https://discord.com/channels/1337450123600465984/1337459086270271538/1434986689935179838 for more information
|
||||
*/
|
||||
|
||||
type AccessControlsService struct {
|
||||
docker *DockerService
|
||||
// envACLs config.Apps
|
||||
static map[string]config.App
|
||||
}
|
||||
|
||||
func NewAccessControlsService(docker *DockerService) *AccessControlsService {
|
||||
func NewAccessControlsService(docker *DockerService, static map[string]config.App) *AccessControlsService {
|
||||
return &AccessControlsService{
|
||||
docker: docker,
|
||||
static: static,
|
||||
}
|
||||
}
|
||||
|
||||
func (acls *AccessControlsService) Init() error {
|
||||
// acls.envACLs = config.Apps{}
|
||||
// env := os.Environ()
|
||||
// appEnvVars := []string{}
|
||||
|
||||
// for _, e := range env {
|
||||
// if strings.HasPrefix(e, "TINYAUTH_APPS_") {
|
||||
// appEnvVars = append(appEnvVars, e)
|
||||
// }
|
||||
// }
|
||||
|
||||
// err := acls.loadEnvACLs(appEnvVars)
|
||||
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return nil
|
||||
|
||||
return nil
|
||||
|
||||
return nil // No initialization needed
|
||||
}
|
||||
|
||||
// func (acls *AccessControlsService) loadEnvACLs(appEnvVars []string) error {
|
||||
// if len(appEnvVars) == 0 {
|
||||
// return nil
|
||||
// }
|
||||
func (acls *AccessControlsService) lookupStaticACLs(domain string) (config.App, error) {
|
||||
for app, config := range acls.static {
|
||||
if config.Config.Domain == domain {
|
||||
log.Debug().Str("name", app).Msg("Found matching container by domain")
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// envAcls := map[string]string{}
|
||||
if strings.SplitN(domain, ".", 2)[0] == app {
|
||||
log.Debug().Str("name", app).Msg("Found matching container by app name")
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
return config.App{}, errors.New("no results")
|
||||
}
|
||||
|
||||
// for _, e := range appEnvVars {
|
||||
// parts := strings.SplitN(e, "=", 2)
|
||||
// if len(parts) != 2 {
|
||||
// continue
|
||||
// }
|
||||
func (acls *AccessControlsService) GetAccessControls(domain string) (config.App, error) {
|
||||
// First check in the static config
|
||||
app, err := acls.lookupStaticACLs(domain)
|
||||
|
||||
// key := parts[0]
|
||||
// key = strings.ToLower(key)
|
||||
// key = strings.ReplaceAll(key, "_", ".")
|
||||
// value := parts[1]
|
||||
// envAcls[key] = value
|
||||
// }
|
||||
|
||||
// apps, err := decoders.DecodeLabels(envAcls)
|
||||
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// acls.envACLs = apps
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (acls *AccessControlsService) lookupEnvACLs(appDomain string) *config.App {
|
||||
// if len(acls.envACLs.Apps) == 0 {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// for appName, appACLs := range acls.envACLs.Apps {
|
||||
// if appACLs.Config.Domain == appDomain {
|
||||
// return &appACLs
|
||||
// }
|
||||
|
||||
// if strings.SplitN(appDomain, ".", 2)[0] == appName {
|
||||
// return &appACLs
|
||||
// }
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (acls *AccessControlsService) GetAccessControls(appDomain string) (config.App, error) {
|
||||
// First check environment variables
|
||||
// envACLs := acls.lookupEnvACLs(appDomain)
|
||||
|
||||
// if envACLs != nil {
|
||||
// log.Debug().Str("domain", appDomain).Msg("Found matching access controls in environment variables")
|
||||
// return *envACLs, nil
|
||||
// }
|
||||
if err == nil {
|
||||
log.Debug().Msg("Using ACls from static configuration")
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// Fallback to Docker labels
|
||||
return acls.docker.GetLabels(appDomain)
|
||||
log.Debug().Msg("Falling back to Docker labels for ACLs")
|
||||
return acls.docker.GetLabels(domain)
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/repository"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/model"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
@@ -57,7 +58,6 @@ func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapS
|
||||
}
|
||||
|
||||
func (auth *AuthService) Init() error {
|
||||
auth.ctx = context.Background()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -215,6 +215,7 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
|
||||
OAuthGroups: data.OAuthGroups,
|
||||
Expiry: time.Now().Add(time.Duration(expiry) * time.Second).Unix(),
|
||||
OAuthName: data.OAuthName,
|
||||
OAuthSub: data.OAuthSub,
|
||||
}
|
||||
|
||||
_, err = auth.queries.CreateSession(c, session)
|
||||
@@ -228,6 +229,40 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) RefreshSessionCookie(c *gin.Context) error {
|
||||
cookie, err := c.Cookie(auth.config.SessionCookieName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session, err := gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).First(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentTime := time.Now().Unix()
|
||||
|
||||
if session.Expiry-currentTime > int64(time.Hour.Seconds()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
newExpiry := currentTime + int64(time.Hour.Seconds())
|
||||
|
||||
_, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Updates(c, model.Session{
|
||||
Expiry: newExpiry,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.SetCookie(auth.config.SessionCookieName, cookie, int(time.Hour.Seconds()), "/", fmt.Sprintf(".%s", auth.config.CookieDomain), auth.config.SecureCookie, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error {
|
||||
cookie, err := c.Cookie(auth.config.SessionCookieName)
|
||||
|
||||
@@ -282,6 +317,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
|
||||
TotpPending: session.TotpPending,
|
||||
OAuthGroups: session.OAuthGroups,
|
||||
OAuthName: session.OAuthName,
|
||||
OAuthSub: session.OAuthSub,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
92
internal/service/database_service.go
Normal file
92
internal/service/database_service.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/assets"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
sqliteMigrate "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DatabaseServiceConfig struct {
|
||||
DatabasePath string
|
||||
}
|
||||
|
||||
type DatabaseService struct {
|
||||
config DatabaseServiceConfig
|
||||
database *gorm.DB
|
||||
}
|
||||
|
||||
func NewDatabaseService(config DatabaseServiceConfig) *DatabaseService {
|
||||
return &DatabaseService{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (ds *DatabaseService) Init() error {
|
||||
dbPath := ds.config.DatabasePath
|
||||
if dbPath == "" {
|
||||
dbPath = "/data/tinyauth.db"
|
||||
}
|
||||
|
||||
dir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create database directory %s: %w", dir, err)
|
||||
}
|
||||
|
||||
gormDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sqlDB, err := gormDB.DB()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
|
||||
err = ds.migrateDatabase(sqlDB)
|
||||
|
||||
if err != nil && err != migrate.ErrNoChange {
|
||||
return err
|
||||
}
|
||||
|
||||
ds.database = gormDB
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *DatabaseService) migrateDatabase(sqlDB *sql.DB) error {
|
||||
data, err := iofs.New(assets.Migrations, "migrations")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := sqliteMigrate.WithInstance(sqlDB, &sqliteMigrate.Config{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrator, err := migrate.NewWithInstance("iofs", data, "tinyauth", target)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return migrator.Up()
|
||||
}
|
||||
|
||||
func (ds *DatabaseService) GetDatabase() *gorm.DB {
|
||||
return ds.database
|
||||
}
|
||||
@@ -3,8 +3,9 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/utils/decoders"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/decoders"
|
||||
|
||||
container "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
"tinyauth/internal/config"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"tinyauth/internal/config"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/endpoints"
|
||||
@@ -26,6 +28,7 @@ type GithubEmailResponse []struct {
|
||||
type GithubUserInfoResponse struct {
|
||||
Login string `json:"login"`
|
||||
Name string `json:"name"`
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
type GithubOAuthService struct {
|
||||
@@ -171,6 +174,7 @@ func (github *GithubOAuthService) Userinfo() (config.Claims, error) {
|
||||
|
||||
user.PreferredUsername = userInfo.Login
|
||||
user.Name = userInfo.Name
|
||||
user.Sub = strconv.Itoa(userInfo.ID)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@@ -10,18 +10,14 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"tinyauth/internal/config"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/endpoints"
|
||||
)
|
||||
|
||||
var GoogleOAuthScopes = []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"}
|
||||
|
||||
type GoogleUserInfoResponse struct {
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
var GoogleOAuthScopes = []string{"openid", "email", "profile"}
|
||||
|
||||
type GoogleOAuthService struct {
|
||||
config oauth2.Config
|
||||
@@ -90,7 +86,7 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) {
|
||||
|
||||
client := google.config.Client(google.context, google.token)
|
||||
|
||||
res, err := client.Get("https://www.googleapis.com/userinfo/v2/me")
|
||||
res, err := client.Get("https://openidconnect.googleapis.com/v1/userinfo")
|
||||
if err != nil {
|
||||
return config.Claims{}, err
|
||||
}
|
||||
@@ -105,16 +101,12 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) {
|
||||
return config.Claims{}, err
|
||||
}
|
||||
|
||||
var userInfo GoogleUserInfoResponse
|
||||
|
||||
err = json.Unmarshal(body, &userInfo)
|
||||
err = json.Unmarshal(body, &user)
|
||||
if err != nil {
|
||||
return config.Claims{}, err
|
||||
}
|
||||
|
||||
user.PreferredUsername = strings.Split(userInfo.Email, "@")[0]
|
||||
user.Name = userInfo.Name
|
||||
user.Email = userInfo.Email
|
||||
user.PreferredUsername = strings.SplitN(user.Email, "@", 2)[0]
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"tinyauth/internal/config"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"tinyauth/internal/config"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/weppos/publicsuffix-go/publicsuffix"
|
||||
|
||||
@@ -2,8 +2,9 @@ package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
@@ -2,8 +2,9 @@ package decoders_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/utils/decoders"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/decoders"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,8 @@ package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,8 @@ package loaders
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"tinyauth/internal/config"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
|
||||
"github.com/traefik/paerser/cli"
|
||||
"github.com/traefik/paerser/env"
|
||||
|
||||
@@ -3,7 +3,8 @@ package utils_test
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,8 @@ package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,8 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"tinyauth/internal/config"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
func ParseUsers(users string) ([]config.User, error) {
|
||||
|
||||
@@ -3,7 +3,8 @@ package utils_test
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user