moved auth into middleware

This commit is contained in:
2026-01-23 23:34:22 -07:00
parent 649595718c
commit e4069e18e2
6 changed files with 103 additions and 129 deletions
+45 -77
View File
@@ -52,91 +52,69 @@ func NewAuth(
}
}
func (a *Auth) AddIdentity(fn response.HandlerFunc) response.HandlerFunc {
return func(c *gin.Context) (response.Response, error) {
c, err := a.AddIdentityToRequest(c)
if err != nil {
return nil, err
}
return fn(c)
// AddIdentityToRequest will add an Identity to the context that can then be retrieved via GetIdentity.
func (a *Auth) AddIdentityToRequest(c *gin.Context) {
if err := a.addIdentityToRequest(c); err != nil {
c.Error(err)
c.Abort()
}
}
func (a *Auth) AddIdentityToRequest(c *gin.Context) (*gin.Context, error) {
func (a *Auth) addIdentityToRequest(c *gin.Context) error {
r := c.Request
ck, err := r.Cookie("access_token")
if err != nil {
return c, nil
return nil
}
ctx := r.Context()
accessToken := ck.Value
claims, expiration, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
claims, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
if err != nil {
if errors.Is(err, consts.ErrNotFound) {
return c, nil
return nil
}
return c, response.Errorf("failed to load authentication details: %w", err)
return response.Errorf("failed to load authentication details: %w", err)
}
expiration := claims.Expiration
if expiration.Before(time.Now()) {
return c, nil
return nil
}
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
if err != nil {
return c, response.Errorf("failed to load user and account defails: %w", err)
return response.Errorf("failed to load user and account defails: %w", err)
}
c.Request = r.WithContext(SetIdentity(ctx, Identity{
c.Request = r.WithContext(SetIdentity(c, Identity{
AccessToken: accessToken,
Claims: claims,
User: user,
Account: acct,
}))
return c, nil
return nil
}
// TODO: use the new middleware pattern
// auth middleware to verify access_token cookie and set custom claims in the request context
func (a *Auth) AuthenticateAndAddIdentityGin(assertions ...AuthorizationAssertions) gin.HandlerFunc {
fn := a.AuthenticateAndAddIdentityToRequest(assertions...)
return response.Handler(fn)
}
func (a *Auth) AuthenticateAndAddIdentityToRequest(assertions ...AuthorizationAssertions) response.HandlerFunc {
// Authenticate should only be used along with and after AddIdentityToRequest
// Typically used with response.Handler to make a gin.HandlerFunc.
func (a *Auth) Authenticate(assertions ...AuthorizationAssertions) func(c *gin.Context) (response.Response, error) {
return func(c *gin.Context) (response.Response, error) {
r := c.Request
ck, err := r.Cookie("access_token")
if err != nil {
return response.TemporaryRedirect("/").
JSON("no access_token cookie provided"), nil
}
ctx := r.Context()
accessToken := ck.Value
claims, expiration, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
if err != nil {
if errors.Is(err, consts.ErrNotFound) {
u, err := a.newLoginURL(ctx, a.auth, r.URL.String())
if err != nil {
return nil, response.Errorf("failed to generate login url: %w", err)
}
return response.TemporaryRedirect(u), nil
}
return nil, response.Errorf("failed to authenticate: %w", err)
id, ok := getIdentity(c)
if !ok {
return nil, response.Unauthorized().
HTML([]byte(`
<h1>Unauthorized</h1>
<a href="/">Return to app</a>
`)) // TODO: would be nice to have a better page for this
}
expiration := id.Claims.Expiration
now := time.Now()
// refresh tokens, when the access token is "old enough"
@@ -144,7 +122,7 @@ func (a *Auth) AuthenticateAndAddIdentityToRequest(assertions ...AuthorizationAs
// id token lifetime is 48 hours, allowing a person to use the app everyday comfortably, with wiggle room, without having to log in.
const idTokenLifetime = 48 * time.Hour
if refreshFloor := expiration.Add(-(idTokenLifetime / 4)); refreshFloor.Before(now) {
accessToken, expiration, err = a.auth.RefreshAccessToken(ctx, accessToken)
accessToken, expiration, err := a.auth.RefreshAccessToken(c, id.AccessToken)
if err != nil {
a.log.Warn("failed to refresh access token", "error", err)
return response.TemporaryRedirect("/").
@@ -153,65 +131,55 @@ func (a *Auth) AuthenticateAndAddIdentityToRequest(assertions ...AuthorizationAs
}
// 'redirect' to same url, to set the new access_token cookie
return response.TemporaryRedirect(r.URL.String()).
return response.TemporaryRedirect(c.Request.URL.String()).
Cookie(cookies.AccessToken(accessToken, expiration)), nil
}
// add identity info to request context
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
if err != nil {
return nil, response.Errorf("failed to authorize: %w", err)
}
for _, as := range assertions {
res, err := as(c)
if res != nil || err != nil {
if res, err := as(c); res != nil || err != nil {
return res, err
}
}
id := Identity{
AccessToken: accessToken,
Claims: claims,
User: user,
Account: acct,
}
c.Set(identityKeyString, id)
c.Request = r.WithContext(SetIdentity(ctx, id))
return nil, nil
}
}
type identityKey struct{}
const identityKeyString = "identity"
// stores identity in context
func SetIdentity(ctx context.Context, id Identity) context.Context {
c, ok := ctx.(*gin.Context)
if ok {
c.Set(identityKey{}, id)
ctx = c.Request.Context()
}
return context.WithValue(ctx, identityKey{}, id)
}
// get identity from context
func GetIdentity(ctx context.Context) Identity {
id, _ := getIdentity(ctx)
return id
}
func getIdentity(ctx context.Context) (Identity, bool) {
id, ok := ctx.Value(identityKey{}).(Identity)
if ok {
return id
return id, true
}
c, ok := ctx.(*gin.Context)
if !ok {
return Identity{}
return Identity{}, false
}
v, ok := c.Get(identityKeyString)
v, ok := c.Get(identityKey{})
if !ok {
return Identity{}
return Identity{}, false
}
id, _ = v.(Identity)
id, ok = v.(Identity)
return id
return id, ok
}