diff --git a/internal/assets/migrations/000003_oauth_sub.down.sql b/internal/assets/migrations/000003_oauth_sub.down.sql new file mode 100644 index 0000000..0260a7b --- /dev/null +++ b/internal/assets/migrations/000003_oauth_sub.down.sql @@ -0,0 +1 @@ +ALTER TABLE "sessions" DROP COLUMN "oauth_sub"; \ No newline at end of file diff --git a/internal/assets/migrations/000003_oauth_sub.up.sql b/internal/assets/migrations/000003_oauth_sub.up.sql new file mode 100644 index 0000000..def471e --- /dev/null +++ b/internal/assets/migrations/000003_oauth_sub.up.sql @@ -0,0 +1 @@ +ALTER TABLE "sessions" ADD COLUMN "oauth_sub" TEXT; diff --git a/internal/config/config.go b/internal/config/config.go index e664c4d..f69c473 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -79,6 +79,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 +126,7 @@ type SessionCookie struct { TotpPending bool OAuthGroups string OAuthName string + OAuthSub string } type UserContext struct { @@ -138,6 +140,7 @@ type UserContext struct { OAuthGroups string TotpEnabled bool OAuthName string + OAuthSub string } // API responses and queries diff --git a/internal/controller/context_controller.go b/internal/controller/context_controller.go index 62e8582..181c2f6 100644 --- a/internal/controller/context_controller.go +++ b/internal/controller/context_controller.go @@ -21,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 { @@ -89,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 { diff --git a/internal/controller/context_controller_test.go b/internal/controller/context_controller_test.go index a558abd..7dee2c8 100644 --- a/internal/controller/context_controller_test.go +++ b/internal/controller/context_controller_test.go @@ -44,6 +44,7 @@ var userContext = config.UserContext{ TotpPending: false, OAuthGroups: "", TotpEnabled: false, + OAuthSub: "", } func setupContextController(middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) { diff --git a/internal/controller/oauth_controller.go b/internal/controller/oauth_controller.go index e2353fe..3635e85 100644 --- a/internal/controller/oauth_controller.go +++ b/internal/controller/oauth_controller.go @@ -197,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") diff --git a/internal/controller/proxy_controller.go b/internal/controller/proxy_controller.go index 431c988..40f8374 100644 --- a/internal/controller/proxy_controller.go +++ b/internal/controller/proxy_controller.go @@ -239,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) diff --git a/internal/middleware/context_middleware.go b/internal/middleware/context_middleware.go index 3101bd9..16f7c05 100644 --- a/internal/middleware/context_middleware.go +++ b/internal/middleware/context_middleware.go @@ -99,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, }) diff --git a/internal/model/session_model.go b/internal/model/session_model.go index 0fdb6c3..283dd6b 100644 --- a/internal/model/session_model.go +++ b/internal/model/session_model.go @@ -10,4 +10,5 @@ type Session struct { OAuthGroups string `gorm:"column:oauth_groups"` Expiry int64 `gorm:"column:expiry"` OAuthName string `gorm:"column:oauth_name"` + OAuthSub string `gorm:"column:oauth_sub"` } diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index b55d77e..94fc1e6 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -213,6 +213,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 = gorm.G[model.Session](auth.database).Create(c, &session) @@ -314,6 +315,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie, TotpPending: session.TOTPPending, OAuthGroups: session.OAuthGroups, OAuthName: session.OAuthName, + OAuthSub: session.OAuthSub, }, nil } diff --git a/internal/service/github_oauth_service.go b/internal/service/github_oauth_service.go index a06abdb..7881ef2 100644 --- a/internal/service/github_oauth_service.go +++ b/internal/service/github_oauth_service.go @@ -173,6 +173,9 @@ func (github *GithubOAuthService) Userinfo() (config.Claims, error) { user.PreferredUsername = userInfo.Login user.Name = userInfo.Name + // Github does not implement OIDC, so no sub is available + user.Sub = "not_available_dont_use_me" + return user, nil } diff --git a/internal/service/google_oauth_service.go b/internal/service/google_oauth_service.go index 80b7ab8..a845df5 100644 --- a/internal/service/google_oauth_service.go +++ b/internal/service/google_oauth_service.go @@ -22,6 +22,7 @@ var GoogleOAuthScopes = []string{"https://www.googleapis.com/auth/userinfo.email type GoogleUserInfoResponse struct { Email string `json:"email"` Name string `json:"name"` + Id string `json:"id"` } type GoogleOAuthService struct { @@ -117,6 +118,9 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) { user.Name = userInfo.Name user.Email = userInfo.Email + // We can use the id as the sub + user.Sub = userInfo.Id + return user, nil }