new response middleware functions to simplify middleware
This commit is contained in:
@@ -96,7 +96,7 @@ func SetupRoutes(
|
||||
authMiddleware: authMiddleware,
|
||||
}
|
||||
|
||||
authAndServeTemplate := s.authMiddleware.AuthenticateAndAddIdentity(s.serveTemplate)
|
||||
authenticate := s.authMiddleware.AuthenticateAndAddIdentityToRequest()
|
||||
|
||||
r.GET("/*rest", response.Handler(func(c *gin.Context) (response.Response, error) {
|
||||
c.Request.URL.Path = c.Request.URL.Path[3:]
|
||||
@@ -113,7 +113,10 @@ func SetupRoutes(
|
||||
return s.serveTemplate(c)
|
||||
}
|
||||
|
||||
return authAndServeTemplate(c)
|
||||
if res, err := authenticate(c); res != nil || err != nil {
|
||||
return res, err
|
||||
}
|
||||
return s.serveTemplate(c)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -154,7 +157,7 @@ func (s *webpageRouter) serveTemplate(c *gin.Context) (response.Response, error)
|
||||
|
||||
nfb, nferr := s.templater.Execute("not-found", args...)
|
||||
if nferr != nil {
|
||||
s.log.Error("failed to render not-found page: %v", nferr)
|
||||
s.log.Errorf("failed to render not-found page: %v", nferr)
|
||||
return nil, werr
|
||||
}
|
||||
|
||||
|
||||
@@ -103,104 +103,20 @@ func (a *Auth) AddIdentityToRequest(c *gin.Context) (*gin.Context, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// TODO: after getting auth, consider making http handler functions take 'claims', etc, as function arguments
|
||||
// - then consider doing the same with authorizationAssertions
|
||||
|
||||
// TODO: use the new middleware pattern
|
||||
// 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(c *gin.Context) (response.Response, error) {
|
||||
c, res, err := a.AuthenticateAndAddIdentityToRequest(c, assertions...)
|
||||
if res != nil || err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
return f(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Auth) AuthenticateAndAddIdentityGin(assertions ...AuthorizationAssertions) gin.HandlerFunc {
|
||||
return a.AuthenticateAndAddIdentityToRequestGin(assertions...)
|
||||
fn := a.AuthenticateAndAddIdentityToRequest(assertions...)
|
||||
return response.Handler(fn)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (a *Auth) AuthenticateAndAddIdentityToRequestGin(assertions ...AuthorizationAssertions) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
func (a *Auth) AuthenticateAndAddIdentityToRequest(assertions ...AuthorizationAssertions) response.HandlerFunc {
|
||||
return func(c *gin.Context) (response.Response, error) {
|
||||
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
|
||||
return response.TemporaryRedirect("/").
|
||||
JSON("no access_token cookie provided"), nil
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
@@ -212,19 +128,13 @@ func (a *Auth) AuthenticateAndAddIdentityToRequestGin(assertions ...Authorizatio
|
||||
if errors.Is(err, consts.ErrNotFound) {
|
||||
u, err := a.newLoginURL(ctx, a.auth, r.URL.String())
|
||||
if err != nil {
|
||||
c.Error(response.Errorf("failed to generate login url: %w", err))
|
||||
c.Abort()
|
||||
return
|
||||
return nil, response.Errorf("failed to generate login url: %w", err)
|
||||
}
|
||||
|
||||
response.Write(c, response.TemporaryRedirect(u))
|
||||
c.Abort()
|
||||
return
|
||||
return response.TemporaryRedirect(u), nil
|
||||
}
|
||||
|
||||
c.Error(response.Errorf("failed to authenticate: %w", err))
|
||||
c.Abort()
|
||||
return
|
||||
return nil, response.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@@ -237,40 +147,27 @@ func (a *Auth) AuthenticateAndAddIdentityToRequestGin(assertions ...Authorizatio
|
||||
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("/").
|
||||
return 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
|
||||
Cookie(cookies.Expired("access_token")), nil
|
||||
}
|
||||
|
||||
// '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
|
||||
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 {
|
||||
c.Error(response.Errorf("failed to authorize: %w", err))
|
||||
c.Abort()
|
||||
return
|
||||
return nil, response.Errorf("failed to authorize: %w", err)
|
||||
}
|
||||
|
||||
for _, as := range assertions {
|
||||
res, err := as(c)
|
||||
if err != nil {
|
||||
c.Error(err)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if res != nil {
|
||||
response.Write(c, res)
|
||||
c.Abort()
|
||||
return
|
||||
if res != nil || err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +181,7 @@ func (a *Auth) AuthenticateAndAddIdentityToRequestGin(assertions ...Authorizatio
|
||||
c.Set(identityKeyString, id)
|
||||
c.Request = r.WithContext(SetIdentity(ctx, id))
|
||||
|
||||
c.Next()
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ type (
|
||||
HandlerFunc = func(c *gin.Context) (Response, error)
|
||||
|
||||
Middleware = func(HandlerFunc) HandlerFunc
|
||||
|
||||
responseKey struct{}
|
||||
)
|
||||
|
||||
func Handler(f HandlerFunc) gin.HandlerFunc {
|
||||
@@ -15,8 +17,10 @@ func Handler(f HandlerFunc) gin.HandlerFunc {
|
||||
res, err := f(c)
|
||||
if err != nil {
|
||||
c.Error(err)
|
||||
} else {
|
||||
Write(c, res)
|
||||
c.Abort()
|
||||
} else if res != nil {
|
||||
c.Set(responseKey{}, res)
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,26 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func HandleResponses(c *gin.Context) {
|
||||
c.Next()
|
||||
|
||||
if len(c.Errors) > 0 || c.Writer.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
v, ok := c.Get(responseKey{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, ok := v.(Response)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
writeResponse(c, res)
|
||||
}
|
||||
|
||||
func HandleErrors(c *gin.Context) {
|
||||
c.Next()
|
||||
|
||||
@@ -46,7 +66,7 @@ func HandleErrors(c *gin.Context) {
|
||||
c.String(status, msg)
|
||||
}
|
||||
|
||||
func Write(c *gin.Context, res Response) {
|
||||
func writeResponse(c *gin.Context, res Response) {
|
||||
w := c.Writer
|
||||
|
||||
// w.Header() must be set before ResponseWriter.WriteHeader is called
|
||||
|
||||
@@ -41,7 +41,10 @@ func NewRouter(
|
||||
) *Router {
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(response.HandleErrors)
|
||||
r.Use(
|
||||
response.HandleResponses,
|
||||
response.HandleErrors,
|
||||
)
|
||||
|
||||
// TODO: shouldn't this ACTUALLY be a middleware?
|
||||
// - only try to make this an actual middleware AFTER all the routers are broken out, so that way how the middleware is supposed to work can be known
|
||||
|
||||
Reference in New Issue
Block a user