package middleware import ( "context" "database/sql" "net/http" "sync/atomic" "time" "projectreshoot/internal/config" "projectreshoot/internal/handler" "projectreshoot/internal/models" "projectreshoot/pkg/contexts" "git.haelnorr.com/h/golib/cookies" "git.haelnorr.com/h/golib/hlog" "git.haelnorr.com/h/golib/jwt" "github.com/pkg/errors" ) // Attempt to use a valid refresh token to generate a new token pair func refreshAuthTokens( config *config.Config, tokenGen *jwt.TokenGenerator, tx *sql.Tx, w http.ResponseWriter, req *http.Request, ref *jwt.RefreshToken, ) (*models.User, error) { user, err := models.GetUserFromID(tx, ref.SUB) if err != nil { return nil, errors.Wrap(err, "models.GetUser") } rememberMe := map[string]bool{ "session": false, "exp": true, }[ref.TTL] // Set fresh to true because new tokens coming from refresh request err = jwt.SetTokenCookies(w, req, tokenGen, user.ID, false, rememberMe, config.SSL) if err != nil { return nil, errors.Wrap(err, "cookies.SetTokenCookies") } // New tokens sent, revoke the used refresh token err = ref.Revoke(tx) if err != nil { return nil, errors.Wrap(err, "ref.Revoke") } // Return the authorized user return user, nil } // Check the cookies for token strings and attempt to authenticate them func getAuthenticatedUser( config *config.Config, tokenGen *jwt.TokenGenerator, tx *sql.Tx, w http.ResponseWriter, r *http.Request, ) (*contexts.AuthenticatedUser, error) { // Get token strings from cookies atStr, rtStr := jwt.GetTokenCookies(r) if atStr == "" && rtStr == "" { return nil, errors.New("No token strings provided") } // Attempt to parse the access token aT, err := tokenGen.ValidateAccess(tx, atStr) if err != nil { // Access token invalid, attempt to parse refresh token rT, err := tokenGen.ValidateRefresh(tx, rtStr) if err != nil { return nil, errors.Wrap(err, "tokenGen.ValidateRefresh") } // Refresh token valid, attempt to get a new token pair user, err := refreshAuthTokens(config, tokenGen, tx, w, r, rT) if err != nil { return nil, errors.Wrap(err, "refreshAuthTokens") } // New token pair sent, return the authorized user authUser := contexts.AuthenticatedUser{ User: user, Fresh: time.Now().Unix(), } return &authUser, nil } // Access token valid user, err := models.GetUserFromID(tx, aT.SUB) if err != nil { return nil, errors.Wrap(err, "models.GetUser") } authUser := contexts.AuthenticatedUser{ User: user, Fresh: aT.Fresh, } return &authUser, nil } // Attempt to authenticate the user and add their account details // to the request context func Authentication( logger *hlog.Logger, config *config.Config, conn *sql.DB, tokenGen *jwt.TokenGenerator, next http.Handler, maint *uint32, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/static/css/output.css" || r.URL.Path == "/static/favicon.ico" { next.ServeHTTP(w, r) return } ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() if atomic.LoadUint32(maint) == 1 { cancel() } // Start the transaction tx, err := conn.BeginTx(ctx, nil) if err != nil { // Failed to start transaction, skip auth logger.Warn().Err(err). Msg("Skipping Auth - unable to start a transaction") handler.ErrorPage(http.StatusServiceUnavailable, w, r) return } user, err := getAuthenticatedUser(config, tokenGen, tx, w, r) if err != nil { tx.Rollback() // User auth failed, delete the cookies to avoid repeat requests cookies.DeleteCookie(w, "access", "/") cookies.DeleteCookie(w, "refresh", "/") logger.Debug(). Str("remote_addr", r.RemoteAddr). Err(err). Msg("Failed to authenticate user") next.ServeHTTP(w, r) return } tx.Commit() uctx := contexts.SetUser(r.Context(), user) newReq := r.WithContext(uctx) next.ServeHTTP(w, newReq) }) }