installed gin

This commit is contained in:
2026-01-13 22:07:20 -07:00
parent 851234d9fa
commit 7481cec0d5
29 changed files with 544 additions and 1094 deletions
+92 -74
View File
@@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"io"
"net/http"
"time"
"ruben/inventory2/internal/consts"
@@ -15,6 +14,8 @@ import (
"ruben/inventory2/internal/logging"
"ruben/inventory2/internal/server/cookies"
"ruben/inventory2/internal/server/response"
"github.com/gin-gonic/gin"
)
type (
@@ -52,20 +53,22 @@ func NewAuth(
}
func (a *Auth) AddIdentity(fn response.HandlerFunc) response.HandlerFunc {
return func(r *http.Request) (response.Response, error) {
r, err := a.AddIdentityToRequest(r)
return func(c *gin.Context) (response.Response, error) {
c, err := a.AddIdentityToRequest(c)
if err != nil {
return nil, err
}
return fn(r)
return fn(c)
}
}
func (a *Auth) AddIdentityToRequest(r *http.Request) (*http.Request, error) {
func (a *Auth) AddIdentityToRequest(c *gin.Context) (*gin.Context, error) {
r := c.Request
ck, err := r.Cookie("access_token")
if err != nil {
return r, nil
return c, nil
}
ctx := r.Context()
@@ -75,27 +78,29 @@ func (a *Auth) AddIdentityToRequest(r *http.Request) (*http.Request, error) {
claims, expiration, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
if err != nil {
if errors.Is(err, consts.ErrNotFound) {
return r, nil
return c, nil
}
return r, response.Errorf("failed to load authentication details: %w", err)
return c, response.Errorf("failed to load authentication details: %w", err)
}
if expiration.Before(time.Now()) {
return r, nil
return c, nil
}
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
if err != nil {
return r, response.Errorf("failed to load user and account defails: %w", err)
return c, response.Errorf("failed to load user and account defails: %w", err)
}
return r.WithContext(SetIdentity(ctx, Identity{
c.Request = r.WithContext(SetIdentity(ctx, Identity{
AccessToken: accessToken,
Claims: claims,
User: user,
Account: acct,
})), nil
}))
return c, nil
}
// TODO: after getting auth, consider making http handler functions take 'claims', etc, as function arguments
@@ -103,73 +108,86 @@ func (a *Auth) AddIdentityToRequest(r *http.Request) (*http.Request, error) {
// auth middleware to verify access_token cookie and set custom claims in the request context
func (a *Auth) AuthenticateAndAddIdentity(f response.HandlerFunc, assertions ...AuthorizationAssertions) response.HandlerFunc {
return func(r *http.Request) (response.Response, error) {
ck, err := r.Cookie("access_token")
if err != nil {
return response.TemporaryRedirect("/").
JSON("no access_token cookie provided"), nil
return func(c *gin.Context) (response.Response, error) {
c, res, err := a.AuthenticateAndAddIdentityToRequest(c, assertions...)
if res != nil || err != nil {
return res, 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)
}
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)
return response.TemporaryRedirect("/").
Body(io.NopCloser(bytes.NewBuffer([]byte(fmt.Sprintf("failed to refresh access token: %v", err))))).
Cookie(cookies.Expired("access_token")), nil
}
// 'redirect' to same url, to set the new access_token cookie
return response.TemporaryRedirect(r.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 {
if res, err := as(r); res != nil || err != nil {
return res, err
}
}
return f(r.WithContext(SetIdentity(ctx, Identity{
AccessToken: accessToken,
Claims: claims,
User: user,
Account: acct,
})))
return f(c)
}
}
func (a *Auth) AuthenticateAndAddIdentityToRequest(c *gin.Context, assertions ...AuthorizationAssertions) (*gin.Context, response.Response, error) {
r := c.Request
ck, err := r.Cookie("access_token")
if err != nil {
return c, 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 c, nil, response.Errorf("failed to generate login url: %w", err)
}
return c, response.TemporaryRedirect(u), nil
}
return c, nil, response.Errorf("failed to authenticate: %w", err)
}
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)
return c, response.TemporaryRedirect("/").
Body(io.NopCloser(bytes.NewBuffer([]byte(fmt.Sprintf("failed to refresh access token: %v", err))))).
Cookie(cookies.Expired("access_token")), nil
}
// 'redirect' to same url, to set the new access_token cookie
return c, response.TemporaryRedirect(r.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 c, nil, response.Errorf("failed to authorize: %w", err)
}
for _, as := range assertions {
if res, err := as(c); res != nil || err != nil {
return c, res, err
}
}
r = r.WithContext(SetIdentity(ctx, Identity{
AccessToken: accessToken,
Claims: claims,
User: user,
Account: acct,
}))
c.Request = r
return c, nil, nil
}
type identityKey struct{}
// stores identity in request context