Compare commits

...

3 Commits

Author SHA1 Message Date
Stavros 62ffd2fd11 feat: finalize context functionality 2026-04-29 20:11:43 +03:00
Stavros a3ec07230c fix: fix oauth and oidc controller imports and context 2026-04-29 20:00:36 +03:00
Stavros b4eb7090bd fix: fix imports and context in proxy controller 2026-04-29 19:58:39 +03:00
8 changed files with 91 additions and 70 deletions
+4 -2
View File
@@ -30,7 +30,8 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
}
contextMiddleware := middleware.NewContextMiddleware(middleware.ContextMiddlewareConfig{
CookieDomain: app.context.cookieDomain,
CookieDomain: app.context.cookieDomain,
SessionCookieName: app.context.sessionCookieName,
}, app.services.authService, app.services.oauthBrokerService)
err := contextMiddleware.Init()
@@ -98,7 +99,8 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
proxyController.SetupRoutes()
userController := controller.NewUserController(controller.UserControllerConfig{
CookieDomain: app.context.cookieDomain,
CookieDomain: app.context.cookieDomain,
SessionCookieName: app.context.sessionCookieName,
}, apiRouter, app.services.authService)
userController.SetupRoutes()
+12
View File
@@ -0,0 +1,12 @@
package controller
type UnauthorizedQuery struct {
Username string `url:"username"`
Resource string `url:"resource"`
GroupErr bool `url:"groupErr"`
IP string `url:"ip"`
}
type RedirectQuery struct {
RedirectURI string `url:"redirect_uri"`
}
+5 -4
View File
@@ -6,7 +6,6 @@ import (
"strings"
"time"
"github.com/tinyauthapp/tinyauth/internal/config"
"github.com/tinyauthapp/tinyauth/internal/repository"
"github.com/tinyauthapp/tinyauth/internal/service"
"github.com/tinyauthapp/tinyauth/internal/utils"
@@ -176,7 +175,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
tlog.App.Warn().Str("email", user.Email).Msg("Email not whitelisted")
tlog.AuditLoginFailure(c, user.Email, req.Provider, "email not whitelisted")
queries, err := query.Values(config.UnauthorizedQuery{
queries, err := query.Values(UnauthorizedQuery{
Username: user.Email,
})
@@ -236,7 +235,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
tlog.App.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
err = controller.auth.CreateSessionCookie(c, &sessionCookie)
cookie, err := controller.auth.CreateSession(c, sessionCookie)
if err != nil {
tlog.App.Error().Err(err).Msg("Failed to create session cookie")
@@ -244,6 +243,8 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
return
}
http.SetCookie(c.Writer, cookie)
tlog.AuditLoginSuccess(c, sessionCookie.Username, sessionCookie.Provider)
if controller.isOidcRequest(oauthPendingSession.CallbackParams) {
@@ -259,7 +260,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
}
if oauthPendingSession.CallbackParams.RedirectURI != "" {
queries, err := query.Values(config.RedirectQuery{
queries, err := query.Values(RedirectQuery{
RedirectURI: oauthPendingSession.CallbackParams.RedirectURI,
})
+5 -4
View File
@@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/go-querystring/query"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/service"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
@@ -111,14 +112,14 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
return
}
userContext, err := utils.GetContext(c)
userContext, err := new(model.UserContext).NewFromGin(c)
if err != nil {
controller.authorizeError(c, err, "Failed to get user context", "User is not logged in or the session is invalid", "", "", "")
return
}
if !userContext.IsLoggedIn {
if !userContext.Authenticated {
controller.authorizeError(c, errors.New("err user not logged in"), "User not logged in", "The user is not logged in", "", "", "")
return
}
@@ -151,7 +152,7 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
}
// WARNING: Since Tinyauth is stateless, we cannot have a sub that never changes. We will just create a uuid out of the username and client name which remains stable, but if username or client name changes then sub changes too.
sub := utils.GenerateUUID(fmt.Sprintf("%s:%s", userContext.Username, client.ID))
sub := utils.GenerateUUID(fmt.Sprintf("%s:%s", userContext.GetUsername(), client.ID))
code := utils.GenerateString(32)
// Before storing the code, delete old session
@@ -170,7 +171,7 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
// We also need a snapshot of the user that authorized this (skip if no openid scope)
if slices.Contains(strings.Fields(req.Scope), "openid") {
err = controller.oidc.StoreUserinfo(c, sub, userContext, req)
err = controller.oidc.StoreUserinfo(c, sub, *userContext, req)
if err != nil {
tlog.App.Error().Err(err).Msg("Failed to insert user info into database")
+43 -42
View File
@@ -8,7 +8,7 @@ import (
"regexp"
"strings"
"github.com/tinyauthapp/tinyauth/internal/config"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/service"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
@@ -99,12 +99,16 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
if acls == nil {
acls = &model.App{}
}
tlog.App.Trace().Interface("acls", acls).Msg("ACLs for resource")
clientIP := c.ClientIP()
if controller.auth.IsBypassedIP(acls.IP, clientIP) {
controller.setHeaders(c, acls)
if controller.auth.IsBypassedIP(&acls.IP, clientIP) {
controller.setHeaders(c, *acls)
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
@@ -112,7 +116,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
authEnabled, err := controller.auth.IsAuthEnabled(proxyCtx.Path, acls.Path)
authEnabled, err := controller.auth.IsAuthEnabled(proxyCtx.Path, &acls.Path)
if err != nil {
tlog.App.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
@@ -122,7 +126,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
if !authEnabled {
tlog.App.Debug().Msg("Authentication disabled for resource, allowing access")
controller.setHeaders(c, acls)
controller.setHeaders(c, *acls)
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
@@ -130,8 +134,8 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
if !controller.auth.CheckIP(acls.IP, clientIP) {
queries, err := query.Values(config.UnauthorizedQuery{
if !controller.auth.CheckIP(&acls.IP, clientIP) {
queries, err := query.Values(UnauthorizedQuery{
Resource: strings.Split(proxyCtx.Host, ".")[0],
IP: clientIP,
})
@@ -157,28 +161,24 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
var userContext config.UserContext
context, err := utils.GetContext(c)
userContext, err := new(model.UserContext).NewFromGin(c)
if err != nil {
tlog.App.Debug().Msg("No user context found in request, treating as not logged in")
userContext = config.UserContext{
IsLoggedIn: false,
tlog.App.Debug().Err(err).Msg("No user context found in request, treating as unauthenticated")
userContext = &model.UserContext{
Authenticated: false,
}
} else {
userContext = context
}
tlog.App.Trace().Interface("context", userContext).Msg("User context from request")
if userContext.IsLoggedIn {
userAllowed := controller.auth.IsUserAllowed(c, userContext, acls)
if userContext.Authenticated {
userAllowed := controller.auth.IsUserAllowed(c, *userContext, acls)
if !userAllowed {
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User not allowed to access resource")
tlog.App.Warn().Str("user", userContext.GetUsername()).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User not allowed to access resource")
queries, err := query.Values(config.UnauthorizedQuery{
queries, err := query.Values(UnauthorizedQuery{
Resource: strings.Split(proxyCtx.Host, ".")[0],
})
@@ -188,10 +188,10 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
if userContext.OAuth {
queries.Set("username", userContext.Email)
if userContext.IsOAuth() {
queries.Set("username", userContext.GetEmail())
} else {
queries.Set("username", userContext.Username)
queries.Set("username", userContext.GetUsername())
}
redirectURL := fmt.Sprintf("%s/unauthorized?%s", controller.config.AppURL, queries.Encode())
@@ -209,19 +209,19 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
if userContext.OAuth || userContext.Provider == "ldap" {
if userContext.IsOAuth() || userContext.IsLDAP() {
var groupOK bool
if userContext.OAuth {
groupOK = controller.auth.IsInOAuthGroup(c, userContext, acls.OAuth.Groups)
if userContext.IsOAuth() {
groupOK = controller.auth.IsInOAuthGroup(c, *userContext, acls.OAuth.Groups)
} else {
groupOK = controller.auth.IsInLdapGroup(c, userContext, acls.LDAP.Groups)
groupOK = controller.auth.IsInLDAPGroup(c, *userContext, acls.LDAP.Groups)
}
if !groupOK {
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User groups do not match resource requirements")
tlog.App.Warn().Str("user", userContext.GetUsername()).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User groups do not match resource requirements")
queries, err := query.Values(config.UnauthorizedQuery{
queries, err := query.Values(UnauthorizedQuery{
Resource: strings.Split(proxyCtx.Host, ".")[0],
GroupErr: true,
})
@@ -232,10 +232,10 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
if userContext.OAuth {
queries.Set("username", userContext.Email)
if userContext.IsOAuth() {
queries.Set("username", userContext.GetEmail())
} else {
queries.Set("username", userContext.Username)
queries.Set("username", userContext.GetUsername())
}
redirectURL := fmt.Sprintf("%s/unauthorized?%s", controller.config.AppURL, queries.Encode())
@@ -254,19 +254,20 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
}
c.Header("Remote-User", utils.SanitizeHeader(userContext.Username))
c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name))
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
c.Header("Remote-User", utils.SanitizeHeader(userContext.GetUsername()))
c.Header("Remote-Name", utils.SanitizeHeader(userContext.GetName()))
c.Header("Remote-Email", utils.SanitizeHeader(userContext.GetEmail()))
if userContext.Provider == "ldap" {
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.LdapGroups))
} else if userContext.Provider != "local" {
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
if userContext.IsLDAP() {
c.Header("Remote-Groups", utils.SanitizeHeader(strings.Join(userContext.LDAP.Groups, ",")))
}
c.Header("Remote-Sub", utils.SanitizeHeader(userContext.OAuthSub))
if userContext.IsOAuth() {
c.Header("Remote-Groups", utils.SanitizeHeader(strings.Join(userContext.OAuth.Groups, ",")))
c.Header("Remote-Sub", utils.SanitizeHeader(userContext.OAuth.Sub))
}
controller.setHeaders(c, acls)
controller.setHeaders(c, *acls)
c.JSON(200, gin.H{
"status": 200,
@@ -275,7 +276,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
queries, err := query.Values(config.RedirectQuery{
queries, err := query.Values(RedirectQuery{
RedirectURI: fmt.Sprintf("%s://%s%s", proxyCtx.Proto, proxyCtx.Host, proxyCtx.Path),
})
@@ -299,7 +300,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}
func (controller *ProxyController) setHeaders(c *gin.Context, acls config.App) {
func (controller *ProxyController) setHeaders(c *gin.Context, acls model.App) {
c.Header("Authorization", c.Request.Header.Get("Authorization"))
headers := utils.ParseHeaders(acls.Response.Headers)
@@ -80,6 +80,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
http.SetCookie(c.Writer, cookie)
}
tlog.App.Trace().Msgf("Authenticated user from session cookie: %s", userContext.GetUsername())
c.Set("context", userContext)
c.Next()
return
-13
View File
@@ -218,19 +218,6 @@ type OIDCClientConfig struct {
Name string `description:"Client name in UI." yaml:"name"`
}
// API responses and queries
type UnauthorizedQuery struct {
Username string `url:"username"`
Resource string `url:"resource"`
GroupErr bool `url:"groupErr"`
IP string `url:"ip"`
}
type RedirectQuery struct {
RedirectURI string `url:"redirect_uri"`
}
// ACLs
type Apps struct {
+21 -5
View File
@@ -346,7 +346,7 @@ func (auth *AuthService) RefreshSession(ctx context.Context, uuid string) (*http
}
if session.Expiry-currentTime > refreshThreshold {
return nil, fmt.Errorf("session not eligible for refresh yet")
return nil, nil
}
newExpiry := session.Expiry + refreshThreshold
@@ -443,7 +443,11 @@ func (auth *AuthService) LDAPAuthConfigured() bool {
return auth.ldap.IsConfigured()
}
func (auth *AuthService) IsUserAllowed(c *gin.Context, context model.UserContext, acls model.App) bool {
func (auth *AuthService) IsUserAllowed(c *gin.Context, context model.UserContext, acls *model.App) bool {
if acls == nil {
return true
}
if context.Provider == model.ProviderOAuth {
tlog.App.Debug().Msg("Checking OAuth whitelist")
return utils.CheckFilter(acls.OAuth.Whitelist, context.OAuth.Email)
@@ -507,7 +511,11 @@ func (auth *AuthService) IsInLDAPGroup(c *gin.Context, context model.UserContext
return false
}
func (auth *AuthService) IsAuthEnabled(uri string, path model.AppPath) (bool, error) {
func (auth *AuthService) IsAuthEnabled(uri string, path *model.AppPath) (bool, error) {
if path == nil {
return true, nil
}
// Check for block list
if path.Block != "" {
regex, err := regexp.Compile(path.Block)
@@ -552,7 +560,11 @@ func (auth *AuthService) GetBasicAuth(req *http.Request) (*model.LocalUser, erro
}, nil
}
func (auth *AuthService) CheckIP(acls model.AppIP, ip string) bool {
func (auth *AuthService) CheckIP(acls *model.AppIP, ip string) bool {
if acls == nil {
acls = &model.AppIP{}
}
// Merge the global and app IP filter
blockedIps := append(auth.config.IP.Block, acls.Block...)
allowedIPs := append(auth.config.IP.Allow, acls.Allow...)
@@ -590,7 +602,11 @@ func (auth *AuthService) CheckIP(acls model.AppIP, ip string) bool {
return true
}
func (auth *AuthService) IsBypassedIP(acls model.AppIP, ip string) bool {
func (auth *AuthService) IsBypassedIP(acls *model.AppIP, ip string) bool {
if acls == nil {
return false
}
for _, bypassed := range acls.Bypass {
res, err := utils.FilterIP(bypassed, ip)
if err != nil {