sse support

This commit is contained in:
2026-01-20 20:48:19 -07:00
parent 88eb917381
commit 1130366bf5
15 changed files with 886 additions and 29 deletions
+122 -4
View File
@@ -111,13 +111,17 @@ func (a *Auth) AuthenticateAndAddIdentity(f response.HandlerFunc, assertions ...
return func(c *gin.Context) (response.Response, error) {
c, res, err := a.AuthenticateAndAddIdentityToRequest(c, assertions...)
if res != nil || err != nil {
return res, nil
return res, err
}
return f(c)
}
}
func (a *Auth) AuthenticateAndAddIdentityGin(assertions ...AuthorizationAssertions) gin.HandlerFunc {
return a.AuthenticateAndAddIdentityToRequestGin(assertions...)
}
func (a *Auth) AuthenticateAndAddIdentityToRequest(c *gin.Context, assertions ...AuthorizationAssertions) (*gin.Context, response.Response, error) {
r := c.Request
ck, err := r.Cookie("access_token")
@@ -188,15 +192,129 @@ func (a *Auth) AuthenticateAndAddIdentityToRequest(c *gin.Context, assertions ..
return c, nil, nil
}
func (a *Auth) AuthenticateAndAddIdentityToRequestGin(assertions ...AuthorizationAssertions) gin.HandlerFunc {
return func(c *gin.Context) {
r := c.Request
ck, err := r.Cookie("access_token")
if err != nil {
response.Write(c, response.TemporaryRedirect("/").
JSON("no access_token cookie provided"))
c.Abort()
return
}
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 {
response.WriteError(c, response.Errorf("failed to generate login url: %w", err))
c.Abort()
return
}
response.Write(c, response.TemporaryRedirect(u))
c.Abort()
return
}
response.WriteError(c, response.Errorf("failed to authenticate: %w", err))
c.Abort()
return
}
now := time.Now()
// refresh tokens, when the access token is "old enough"
// 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)
if err != nil {
a.log.Warn("failed to refresh access token", "error", err)
response.Write(c, response.TemporaryRedirect("/").
Body(io.NopCloser(bytes.NewBuffer([]byte(fmt.Sprintf("failed to refresh access token: %v", err))))).
Cookie(cookies.Expired("access_token")))
c.Abort()
return
}
// 'redirect' to same url, to set the new access_token cookie
response.Write(c, response.TemporaryRedirect(r.URL.String()).
Cookie(cookies.AccessToken(accessToken, expiration)))
c.Abort()
return
}
// add identity info to request context
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
if err != nil {
response.WriteError(c, response.Errorf("failed to authorize: %w", err))
c.Abort()
return
}
for _, as := range assertions {
res, err := as(c)
if err != nil {
response.WriteError(c, err)
c.Abort()
return
}
if res != nil {
response.Write(c, res)
c.Abort()
return
}
}
id := Identity{
AccessToken: accessToken,
Claims: claims,
User: user,
Account: acct,
}
c.Set(identityKeyString, id)
c.Request = r.WithContext(SetIdentity(ctx, id))
c.Next()
}
}
type identityKey struct{}
// stores identity in request context
const identityKeyString = "identity"
// stores identity in context
func SetIdentity(ctx context.Context, id Identity) context.Context {
return context.WithValue(ctx, identityKey{}, id)
}
// get identity from request context
// get identity from context
func GetIdentity(ctx context.Context) Identity {
id, _ := ctx.Value(identityKey{}).(Identity)
id, ok := ctx.Value(identityKey{}).(Identity)
if ok {
return id
}
c, ok := ctx.(*gin.Context)
if !ok {
return Identity{}
}
v, ok := c.Get(identityKeyString)
if !ok {
return Identity{}
}
id, _ = v.(Identity)
return id
}