split oauth_tokens.claims column up; improved fonts; allow multiple sse connections per user; make navbar and use friendly
This commit is contained in:
@@ -2,7 +2,6 @@ package authentication
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@@ -131,8 +130,6 @@ func (a *Authenticator) Exchange(ctx context.Context, state, code string) (acces
|
||||
return "", "", time.Time{}, err
|
||||
}
|
||||
|
||||
claimsJSON, _ := json.Marshal(claims)
|
||||
|
||||
// store the token, and the potentially new user
|
||||
|
||||
if _, err := a.db.Exec(
|
||||
@@ -171,7 +168,12 @@ func (a *Authenticator) Exchange(ctx context.Context, state, code string) (acces
|
||||
id_token_nonce,
|
||||
id_token_access_token_hash,
|
||||
|
||||
claims -- might want to open this up
|
||||
id_token_custom_claims_family_name,
|
||||
id_token_custom_claims_given_name,
|
||||
id_token_custom_claims_name,
|
||||
id_token_custom_claims_nickname,
|
||||
id_token_custom_claims_picture,
|
||||
id_token_custom_claims_updated_at
|
||||
)
|
||||
SELECT
|
||||
@access_token,
|
||||
@@ -187,7 +189,12 @@ func (a *Authenticator) Exchange(ctx context.Context, state, code string) (acces
|
||||
@id_token_nonce,
|
||||
@id_token_access_token_hash,
|
||||
|
||||
@claims
|
||||
@id_token_custom_claims_family_name,
|
||||
@id_token_custom_claims_given_name,
|
||||
@id_token_custom_claims_name,
|
||||
@id_token_custom_claims_nickname,
|
||||
@id_token_custom_claims_picture,
|
||||
@id_token_custom_claims_updated_at
|
||||
FROM
|
||||
the_user
|
||||
`,
|
||||
@@ -205,7 +212,12 @@ func (a *Authenticator) Exchange(ctx context.Context, state, code string) (acces
|
||||
"id_token_nonce": idToken.Nonce,
|
||||
"id_token_access_token_hash": idToken.AccessTokenHash,
|
||||
|
||||
"claims": json.RawMessage(claimsJSON),
|
||||
"id_token_custom_claims_family_name": claims.FamilyName,
|
||||
"id_token_custom_claims_given_name": claims.GivenName,
|
||||
"id_token_custom_claims_name": claims.Name,
|
||||
"id_token_custom_claims_nickname": claims.Nickname,
|
||||
"id_token_custom_claims_picture": claims.Picture,
|
||||
"id_token_custom_claims_updated_at": claims.UpdatedAt,
|
||||
},
|
||||
); err != nil {
|
||||
return "", "", time.Time{}, fmt.Errorf("failed to perform query to save tokens: %w", err)
|
||||
@@ -291,8 +303,6 @@ func (a *Authenticator) RefreshAccessToken(
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
claimsJSON, _ := json.Marshal(claims)
|
||||
|
||||
if _, err = a.db.Exec(
|
||||
ctx,
|
||||
`
|
||||
@@ -318,7 +328,12 @@ func (a *Authenticator) RefreshAccessToken(
|
||||
id_token_nonce,
|
||||
id_token_access_token_hash,
|
||||
|
||||
claims -- might want to open this up
|
||||
id_token_custom_claims_family_name,
|
||||
id_token_custom_claims_given_name,
|
||||
id_token_custom_claims_name,
|
||||
id_token_custom_claims_nickname,
|
||||
id_token_custom_claims_picture,
|
||||
id_token_custom_claims_updated_at
|
||||
)
|
||||
VALUES (
|
||||
@new_access_token,
|
||||
@@ -334,7 +349,12 @@ func (a *Authenticator) RefreshAccessToken(
|
||||
@id_token_nonce,
|
||||
@id_token_access_token_hash,
|
||||
|
||||
@claims
|
||||
@id_token_custom_claims_family_name,
|
||||
@id_token_custom_claims_given_name,
|
||||
@id_token_custom_claims_name,
|
||||
@id_token_custom_claims_nickname,
|
||||
@id_token_custom_claims_picture,
|
||||
@id_token_custom_claims_updated_at
|
||||
)
|
||||
RETURNING
|
||||
access_token AS new_access_token
|
||||
@@ -363,7 +383,12 @@ func (a *Authenticator) RefreshAccessToken(
|
||||
"id_token_nonce": idToken.Nonce,
|
||||
"id_token_access_token_hash": idToken.AccessTokenHash,
|
||||
|
||||
"claims": json.RawMessage(claimsJSON),
|
||||
"id_token_custom_claims_family_name": claims.FamilyName,
|
||||
"id_token_custom_claims_given_name": claims.GivenName,
|
||||
"id_token_custom_claims_name": claims.Name,
|
||||
"id_token_custom_claims_nickname": claims.Nickname,
|
||||
"id_token_custom_claims_picture": claims.Picture,
|
||||
"id_token_custom_claims_updated_at": claims.UpdatedAt,
|
||||
},
|
||||
); err != nil {
|
||||
return "", time.Time{}, fmt.Errorf("failed to save new access token and delete old access token: %w", err)
|
||||
@@ -372,12 +397,14 @@ func (a *Authenticator) RefreshAccessToken(
|
||||
return tkn.AccessToken, tkn.Expiry.UTC(), nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) verifyIDTokenAndClaimsFromToken(ctx context.Context, tkn *oauth2.Token) (idToken *oidc.IDToken, claims map[string]any, err error) {
|
||||
func (a *Authenticator) verifyIDTokenAndClaimsFromToken(ctx context.Context, tkn *oauth2.Token) (idToken *oidc.IDToken, claims *AccessTokenClaims, err error) {
|
||||
if idToken, err = a.VerifyIDToken(ctx, tkn); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to verify id Token: %w", err)
|
||||
}
|
||||
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
claims = new(AccessTokenClaims)
|
||||
|
||||
if err := idToken.Claims(claims); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to obtain id token claims: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package authentication
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"ruben/inventory2/internal/consts"
|
||||
@@ -102,7 +101,14 @@ func (a *Authenticator) GetAccessTokenClaimsAndExpiration(ctx context.Context, a
|
||||
`
|
||||
SELECT
|
||||
expiry,
|
||||
claims
|
||||
|
||||
id_token_custom_claims_name,
|
||||
id_token_custom_claims_picture,
|
||||
id_token_custom_claims_nickname,
|
||||
id_token_custom_claims_given_name,
|
||||
id_token_custom_claims_family_name,
|
||||
id_token_custom_claims_updated_at
|
||||
|
||||
FROM
|
||||
oauth_tokens
|
||||
WHERE
|
||||
@@ -117,8 +123,13 @@ func (a *Authenticator) GetAccessTokenClaimsAndExpiration(ctx context.Context, a
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Expiry time.Time
|
||||
Claims json.RawMessage
|
||||
Expiry time.Time
|
||||
Id_token_custom_claims_name string
|
||||
Id_token_custom_claims_picture string
|
||||
Id_token_custom_claims_nickname string
|
||||
Id_token_custom_claims_given_name string
|
||||
Id_token_custom_claims_family_name string
|
||||
Id_token_custom_claims_updated_at time.Time
|
||||
}
|
||||
|
||||
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Row])
|
||||
@@ -129,9 +140,12 @@ func (a *Authenticator) GetAccessTokenClaimsAndExpiration(ctx context.Context, a
|
||||
return AccessTokenClaims{}, time.Time{}, fmt.Errorf("failed to scan row: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(r.Claims, &claims); err != nil {
|
||||
return AccessTokenClaims{}, time.Time{}, fmt.Errorf("failed to scan claims json: %w", err)
|
||||
}
|
||||
claims.Name = r.Id_token_custom_claims_name
|
||||
claims.Picture = r.Id_token_custom_claims_picture
|
||||
claims.Nickname = r.Id_token_custom_claims_nickname
|
||||
claims.GivenName = r.Id_token_custom_claims_given_name
|
||||
claims.FamilyName = r.Id_token_custom_claims_family_name
|
||||
claims.UpdatedAt = r.Id_token_custom_claims_updated_at
|
||||
|
||||
return claims, r.Expiry, nil
|
||||
}
|
||||
|
||||
@@ -11,12 +11,16 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
maxNumOpenConnectionsPerUser = 3
|
||||
)
|
||||
|
||||
type (
|
||||
sseRouter struct {
|
||||
log *logging.Logger
|
||||
sse *sse.Queue
|
||||
|
||||
users map[string]context.CancelFunc
|
||||
users map[string][maxNumOpenConnectionsPerUser]context.CancelFunc
|
||||
lock sync.Mutex
|
||||
}
|
||||
)
|
||||
@@ -30,7 +34,7 @@ func Routes(
|
||||
s := &sseRouter{
|
||||
log: logger,
|
||||
sse: sq,
|
||||
users: make(map[string]context.CancelFunc),
|
||||
users: make(map[string][maxNumOpenConnectionsPerUser]context.CancelFunc),
|
||||
}
|
||||
|
||||
r.GET("/", auth.AuthenticateAndAddIdentityGin(), response.Handler(s.serveEvents))
|
||||
@@ -46,10 +50,11 @@ func (r *sseRouter) serveEvents(c *gin.Context) (response.Response, error) {
|
||||
"accountID", acctID,
|
||||
"userID", userID,
|
||||
"email", email,
|
||||
"userAgent", c.Request.UserAgent(),
|
||||
)
|
||||
log.Info("user connected to sse queue")
|
||||
|
||||
ctx := r.closeExistingConnectionsForUserAndStoreCancelFuncForUser(c, userID)
|
||||
ctx := r.closeOutstandingConnectionsForUserAndStoreCancelFuncForUser(c, userID)
|
||||
|
||||
w := c.Writer
|
||||
|
||||
@@ -80,18 +85,34 @@ func (r *sseRouter) serveEvents(c *gin.Context) (response.Response, error) {
|
||||
return response.Status(200), nil
|
||||
}
|
||||
|
||||
func (r *sseRouter) closeExistingConnectionsForUserAndStoreCancelFuncForUser(ctx context.Context, userID string) context.Context {
|
||||
func (r *sseRouter) closeOutstandingConnectionsForUserAndStoreCancelFuncForUser(ctx context.Context, userID string) context.Context {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
closeConnFuncs := r.users[userID]
|
||||
defer func() {
|
||||
r.users[userID] = closeConnFuncs
|
||||
}()
|
||||
|
||||
// close existing connection
|
||||
if closeConn, ok := r.users[userID]; ok {
|
||||
closeConn()
|
||||
delete(r.users, userID)
|
||||
|
||||
for i := range maxNumOpenConnectionsPerUser {
|
||||
if fn := closeConnFuncs[i]; fn == nil {
|
||||
// not hit limit on connections.
|
||||
// save the cancellation func and done
|
||||
closeConnFuncs[i] = cancel
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
|
||||
// store reference to cancel func
|
||||
ctx, r.users[userID] = context.WithCancel(ctx)
|
||||
// close the oldest connection, shift all cancellation funcs down, and push the new one in
|
||||
closeConnFuncs[0]()
|
||||
for i := range maxNumOpenConnectionsPerUser - 1 {
|
||||
closeConnFuncs[i] = closeConnFuncs[i+1]
|
||||
}
|
||||
closeConnFuncs[maxNumOpenConnectionsPerUser-1] = cancel
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user