auth: expose more auth info in gin context to support future login redirect capability

This commit is contained in:
2026-02-18 22:28:03 -07:00
parent a3ae872e9d
commit 5ec4fe67b0
3 changed files with 76 additions and 49 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ import (
func Routes( func Routes(
r *gin.RouterGroup, r *gin.RouterGroup,
logger *logging.Logger, logger *logging.Logger,
auth *auth.Auth, auth *auth.Service,
sq *sse.Queue, sq *sse.Queue,
accts *accounts.Store, accts *accounts.Store,
unp *sse.UpdateNotificationPublisher, unp *sse.UpdateNotificationPublisher,
+74 -47
View File
@@ -19,12 +19,18 @@ import (
) )
type ( type (
Auth struct { Service struct {
log *logging.Logger log *logging.Logger
auth *authentication.Authenticator auth *authentication.Authenticator
accts *accounts.Store accts *accounts.Store
} }
Auth struct {
Identity Identity
Found bool
Valid bool // eg not expired
}
Identity struct { Identity struct {
AccessToken string AccessToken string
Claims authentication.AccessTokenClaims Claims authentication.AccessTokenClaims
@@ -37,36 +43,40 @@ type (
AuthorizationAssertions = response.HandlerFunc AuthorizationAssertions = response.HandlerFunc
) )
func NewAuth( func NewService(
logger *logging.Logger, logger *logging.Logger,
auth *authentication.Authenticator, auth *authentication.Authenticator,
accts *accounts.Store, accts *accounts.Store,
) *Auth { ) *Service {
return &Auth{ return &Service{
log: logger, log: logger,
auth: auth, auth: auth,
accts: accts, accts: accts,
} }
} }
func (a *Auth) GetAuthenticator() *authentication.Authenticator { func (a *Service) GetAuthenticator() *authentication.Authenticator {
return a.auth return a.auth
} }
// Identify will add an Identity to the context that can then be retrieved via GetIdentity. // Identify will add an Auth to the context that can then be retrieved via GetAuth.
func (a *Auth) Identify(c *gin.Context) { func (a *Service) Identify(c *gin.Context) {
if err := a.addIdentity(c); err != nil { auth, err := a.getAuthFromRequest(c)
if auth != nil {
c.Request = c.Request.WithContext(SetAuth(c, *auth))
}
if err != nil {
c.Error(err) c.Error(err)
c.Abort() c.Abort()
} }
} }
func (a *Auth) addIdentity(c *gin.Context) error { func (a *Service) getAuthFromRequest(c *gin.Context) (*Auth, error) {
r := c.Request r := c.Request
ck, err := r.Cookie("access_token") ck, err := r.Cookie("access_token")
if err != nil { if err != nil {
return nil return nil, nil
} }
ctx := r.Context() ctx := r.Context()
@@ -76,50 +86,61 @@ func (a *Auth) addIdentity(c *gin.Context) error {
claims, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken) claims, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
if err != nil { if err != nil {
if errors.Is(err, consts.ErrNotFound) { if errors.Is(err, consts.ErrNotFound) {
return nil // need to log in, in order to access privilege pages/apis
return &Auth{}, nil
} }
return response.Errorf("failed to load authentication details: %w", err) return nil, response.Errorf("failed to load authentication details: %w", err)
} }
expiration := claims.Expiration expiration := claims.Expiration
if expiration.Before(time.Now()) { if expiration.Before(time.Now()) {
return nil // need to log in, in order to access privilege pages/apis
return &Auth{
Found: true,
}, nil
} }
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken) user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
if err != nil { if err != nil {
return response.Errorf("failed to load user and account defails: %w", err) return nil, response.Errorf("failed to load user and account defails: %w", err)
} }
c.Request = r.WithContext(SetIdentity(c, Identity{ return &Auth{
AccessToken: accessToken, Found: true,
Claims: claims, Valid: true,
User: user, Identity: Identity{
Account: acct, AccessToken: accessToken,
})) Claims: claims,
User: user,
return nil Account: acct,
},
}, nil
} }
// Authenticate should only be used along with and after Identify // Authenticate should only be used along with and after Identify
// Typically used with response.Handler to make a gin.HandlerFunc. // Typically used with response.Handler to make a gin.HandlerFunc.
func (a *Auth) Authenticate(assertions ...AuthorizationAssertions) func(c *gin.Context) { func (a *Service) Authenticate(assertions ...AuthorizationAssertions) func(c *gin.Context) {
return response.Handler(a.AuthenticateHandler(assertions...)) return response.Handler(a.AuthenticateHandler(assertions...))
} }
// See Authenticate. // See Authenticate.
func (a *Auth) AuthenticateHandler(assertions ...AuthorizationAssertions) func(c *gin.Context) (response.Response, error) { func (a *Service) AuthenticateHandler(assertions ...AuthorizationAssertions) func(c *gin.Context) (response.Response, error) {
return func(c *gin.Context) (response.Response, error) { return func(c *gin.Context) (response.Response, error) {
id, ok := getIdentity(c) auth, ok := getAuth(c)
if !ok { switch {
return nil, response.Unauthorized(). case !ok:
HTML([]byte(` // TODO: would be nice if we redirected to the login page then back to the original destination
<h1>Unauthorized</h1> return response.SeeOther("/"), nil
<a href="/">Return to app</a> case !auth.Found:
`)) // TODO: would be nice to have a better page for this // TODO: potentially handle differently
return response.SeeOther("/"), nil
case !auth.Valid:
// TODO: potentially handle differently
return response.SeeOther("/"), nil
} }
id := auth.Identity
expiration := id.Claims.Expiration expiration := id.Claims.Expiration
now := time.Now() now := time.Now()
@@ -151,41 +172,47 @@ func (a *Auth) AuthenticateHandler(assertions ...AuthorizationAssertions) func(c
} }
} }
type identityKey struct{} type authKey struct{}
// stores identity in context // stores auth in context
func SetIdentity(ctx context.Context, id Identity) context.Context { func SetAuth(ctx context.Context, a Auth) context.Context {
c, ok := ctx.(*gin.Context) c, ok := ctx.(*gin.Context)
if ok { if ok {
c.Set(identityKey{}, id) c.Set(authKey{}, a)
ctx = c.Request.Context() ctx = c.Request.Context()
} }
return context.WithValue(ctx, identityKey{}, id) return context.WithValue(ctx, authKey{}, a)
} }
// get identity from context // get auth from context
func GetAuth(ctx context.Context) Auth {
a, _ := getAuth(ctx)
return a
}
// a wrapper on GetAuth
func GetIdentity(ctx context.Context) Identity { func GetIdentity(ctx context.Context) Identity {
id, _ := getIdentity(ctx) a, _ := getAuth(ctx)
return id return a.Identity
} }
func getIdentity(ctx context.Context) (Identity, bool) { func getAuth(ctx context.Context) (Auth, bool) {
id, ok := ctx.Value(identityKey{}).(Identity) a, ok := ctx.Value(authKey{}).(Auth)
if ok { if ok {
return id, true return a, true
} }
c, ok := ctx.(*gin.Context) c, ok := ctx.(*gin.Context)
if !ok { if !ok {
return Identity{}, false return Auth{}, false
} }
v, ok := c.Get(identityKey{}) v, ok := c.Get(authKey{})
if !ok { if !ok {
return Identity{}, false return Auth{}, false
} }
id, ok = v.(Identity) a, ok = v.(Auth)
return id, ok return a, ok
} }
+1 -1
View File
@@ -33,7 +33,7 @@ func NewRouter(
etsy *etsy_platform.Platform, etsy *etsy_platform.Platform,
authr *authentication.Authenticator, authr *authentication.Authenticator,
) *Router { ) *Router {
authM := auth.NewAuth( authM := auth.NewService(
logger.WithGroup("auth-middleware"), logger.WithGroup("auth-middleware"),
authr, authr,
accts, accts,