package handler import ( "context" "database/sql" "net/http" "time" "projectreshoot/internal/view/component/form" "projectreshoot/pkg/config" "projectreshoot/pkg/contexts" "projectreshoot/pkg/cookies" "git.haelnorr.com/h/golib/hlog" "git.haelnorr.com/h/golib/jwt" "github.com/pkg/errors" ) // Get the tokens from the request func getTokens( tokenGen *jwt.TokenGenerator, tx *sql.Tx, r *http.Request, ) (*jwt.AccessToken, *jwt.RefreshToken, error) { // get the existing tokens from the cookies atStr, rtStr := cookies.GetTokenStrings(r) aT, err := tokenGen.ValidateAccess(tx, atStr) if err != nil { return nil, nil, errors.Wrap(err, "tokenGen.ValidateAccess") } rT, err := tokenGen.ValidateRefresh(tx, rtStr) if err != nil { return nil, nil, errors.Wrap(err, "tokenGen.ValidateRefresh") } return aT, rT, nil } // Revoke the given token pair func revokeTokenPair( tx *sql.Tx, aT *jwt.AccessToken, rT *jwt.RefreshToken, ) error { err := aT.Revoke(tx) if err != nil { return errors.Wrap(err, "aT.Revoke") } err = rT.Revoke(tx) if err != nil { return errors.Wrap(err, "rT.Revoke") } return nil } // Issue new tokens for the user, invalidating the old ones func refreshTokens( config *config.Config, tokenGen *jwt.TokenGenerator, tx *sql.Tx, w http.ResponseWriter, r *http.Request, ) error { aT, rT, err := getTokens(tokenGen, tx, r) if err != nil { return errors.Wrap(err, "getTokens") } rememberMe := map[string]bool{ "session": false, "exp": true, }[aT.TTL] // issue new tokens for the user user := contexts.GetUser(r.Context()) err = cookies.SetTokenCookies(w, r, config, tokenGen, user.User, true, rememberMe) if err != nil { return errors.Wrap(err, "cookies.SetTokenCookies") } err = revokeTokenPair(tx, aT, rT) if err != nil { return errors.Wrap(err, "revokeTokenPair") } return nil } // Validate the provided password func validatePassword( tx *sql.Tx, r *http.Request, ) error { r.ParseForm() password := r.FormValue("password") user := contexts.GetUser(r.Context()) err := user.CheckPassword(tx, password) if err != nil { return errors.Wrap(err, "user.CheckPassword") } return nil } // Handle request to reauthenticate (i.e. make token fresh again) func Reauthenticate( logger *hlog.Logger, config *config.Config, conn *sql.DB, tokenGen *jwt.TokenGenerator, ) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second) defer cancel() // Start the transaction tx, err := conn.BeginTx(ctx, nil) if err != nil { logger.Error().Err(err).Msg("Failed to start transaction") w.WriteHeader(http.StatusInternalServerError) return } defer tx.Rollback() err = validatePassword(tx, r) if err != nil { w.WriteHeader(445) form.ConfirmPassword("Incorrect password").Render(r.Context(), w) return } err = refreshTokens(config, tokenGen, tx, w, r) if err != nil { logger.Error().Err(err).Msg("Failed to refresh user tokens") w.WriteHeader(http.StatusInternalServerError) return } tx.Commit() w.WriteHeader(http.StatusOK) }, ) }