diff --git a/service/internal/api/api.go b/service/internal/api/api.go index 629d039..f2c81da 100644 --- a/service/internal/api/api.go +++ b/service/internal/api/api.go @@ -392,6 +392,8 @@ func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[ap func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.LogoutRequest]) (*connect.Response[apiv1.LogoutResponse], error) { user := auth.UserFromApiCall(ctx, req, api.cfg) + auth.RevokeSessionForProvider(api.cfg, user.Provider, user.SID) + log.WithFields(log.Fields{ "username": user.Username, "provider": user.Provider, diff --git a/service/internal/auth/otoauth2/restapi_auth_oauth2.go b/service/internal/auth/otoauth2/restapi_auth_oauth2.go index a84e1f6..351a13b 100644 --- a/service/internal/auth/otoauth2/restapi_auth_oauth2.go +++ b/service/internal/auth/otoauth2/restapi_auth_oauth2.go @@ -391,6 +391,12 @@ func (h *OAuth2Handler) lookupOAuth2UserByState(state string) (*authTypes.Authen return user, true } +func (h *OAuth2Handler) RevokeSession(sid string) { + h.mu.Lock() + defer h.mu.Unlock() + delete(h.registeredStates, sid) +} + func (h *OAuth2Handler) CheckUserFromOAuth2Cookie(context *authTypes.AuthCheckingContext) *authTypes.AuthenticatedUser { cookie, err := context.Request.Cookie("olivetin-sid-oauth") if err != nil || cookie.Value == "" { diff --git a/service/internal/auth/sessions.go b/service/internal/auth/sessions.go index f11fc8a..c8d7416 100644 --- a/service/internal/auth/sessions.go +++ b/service/internal/auth/sessions.go @@ -25,8 +25,9 @@ type SessionStorage struct { } var ( - sessionStorage *SessionStorage - sessionStorageMutex sync.RWMutex + sessionStorage *SessionStorage + sessionStorageMutex sync.RWMutex + oauth2SessionRevoker func(sid string) ) func init() { @@ -58,6 +59,38 @@ func RegisterUserSession(cfg *config.Config, provider string, sid string, userna saveUserSessions(cfg) } +// RegisterOAuth2SessionRevoker registers a callback to revoke OAuth2 sessions on logout. +// OAuth2 uses its own session storage; the API calls this when provider is oauth2. +func RegisterOAuth2SessionRevoker(fn func(sid string)) { + oauth2SessionRevoker = fn +} + +// RevokeSessionForProvider invalidates the session for the given provider and SID (e.g. on logout). +// Local auth uses shared SessionStorage; OAuth2 uses a separate storage and revoker. +func RevokeSessionForProvider(cfg *config.Config, provider string, sid string) { + if sid == "" { + return + } + if provider == "oauth2" && oauth2SessionRevoker != nil { + oauth2SessionRevoker(sid) + return + } + RevokeUserSession(cfg, provider, sid) +} + +// RevokeUserSession removes a session from storage so it can no longer be used (e.g. on logout). +func RevokeUserSession(cfg *config.Config, provider string, sid string) { + sessionStorageMutex.Lock() + defer sessionStorageMutex.Unlock() + + if sessionStorage.Providers[provider] != nil { + delete(sessionStorage.Providers[provider].Sessions, sid) + if cfg != nil { + saveUserSessions(cfg) + } + } +} + // GetUserSession retrieves a user session func GetUserSession(provider string, sid string) *UserSession { sessionStorageMutex.Lock() diff --git a/service/internal/httpservers/frontend.go b/service/internal/httpservers/frontend.go index c1c1a1f..399c6fd 100644 --- a/service/internal/httpservers/frontend.go +++ b/service/internal/httpservers/frontend.go @@ -101,6 +101,7 @@ func StartFrontendMux(cfg *config.Config, ex *executor.Executor) { oauth2handler := otoauth2.NewOAuth2Handler(cfg) auth.AddAuthChainFunction(oauth2handler.CheckUserFromOAuth2Cookie) + auth.RegisterOAuth2SessionRevoker(oauth2handler.RevokeSession) mux.HandleFunc("/oauth/login", oauth2handler.HandleOAuthLogin) mux.HandleFunc("/oauth/callback", oauth2handler.HandleOAuthCallback)