diff --git a/docs/docs/91-migrations.md b/docs/docs/91-migrations.md index 02c7cd08f..841dee3d0 100644 --- a/docs/docs/91-migrations.md +++ b/docs/docs/91-migrations.md @@ -2,6 +2,13 @@ Some versions need some changes to the server configuration or the pipeline configuration files. + + ## `next` - Deprecated `steps.[name].group` in favor of `steps.[name].depends_on` (see [workflow syntax](./20-usage/20-workflow-syntax.md#depends_on) to learn how to set dependencies) diff --git a/server/api/hook.go b/server/api/hook.go index ea5031de4..40a93ef35 100644 --- a/server/api/hook.go +++ b/server/api/hook.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" @@ -157,7 +158,7 @@ func PostHook(c *gin.Context) { c.Status(http.StatusNoContent) return } - oldFullName := repo.FullName + currentRepoFullName := repo.FullName if repo.UserID == 0 { log.Warn().Msgf("ignoring hook. repo %s has no owner.", repo.FullName) @@ -177,7 +178,7 @@ func PostHook(c *gin.Context) { // // get the token and verify the hook is authorized - parsed, err := token.ParseRequest(c.Request, func(_ *token.Token) (string, error) { + parsedToken, err := token.ParseRequest(c.Request, func(_ *token.Token) (string, error) { return repo.Hash, nil }) if err != nil { @@ -186,7 +187,9 @@ func PostHook(c *gin.Context) { c.String(http.StatusBadRequest, msg) return } - verifiedKey := parsed.Text == oldFullName + + // TODO: remove fallback for text full name in next major release + verifiedKey := parsedToken.Get("repo-id") == strconv.FormatInt(repo.ID, 10) || parsedToken.Get("text") == currentRepoFullName if !verifiedKey { verifiedKey, err = _store.HasRedirectionForRepo(repo.ID, repo.FullName) if err != nil { @@ -198,7 +201,7 @@ func PostHook(c *gin.Context) { } if !verifiedKey { - msg := fmt.Sprintf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text) + msg := fmt.Sprintf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsedToken.Get("text")) log.Debug().Msg(msg) c.String(http.StatusForbidden, msg) return @@ -208,7 +211,7 @@ func PostHook(c *gin.Context) { // 4. Update repo // - if oldFullName != tmpRepo.FullName { + if currentRepoFullName != tmpRepo.FullName { // create a redirection err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName}) if err != nil { diff --git a/server/api/login.go b/server/api/login.go index 349861ce1..bdd21dd73 100644 --- a/server/api/login.go +++ b/server/api/login.go @@ -18,6 +18,7 @@ import ( "encoding/base32" "errors" "net/http" + "strconv" "time" "github.com/gin-gonic/gin" @@ -138,7 +139,7 @@ func HandleAuth(c *gin.Context) { ForgeID: u.ForgeID, } if err := _store.OrgCreate(org); err != nil { - log.Error().Err(err).Msgf("on user creation, could create org for user") + log.Error().Err(err).Msgf("on user creation, could not create org for user") } u.OrgID = org.ID } @@ -185,7 +186,9 @@ func HandleAuth(c *gin.Context) { } exp := time.Now().Add(server.Config.Server.SessionExpires).Unix() - tokenString, err := token.New(token.SessToken, u.Login).SignExpires(u.Hash, exp) + _token := token.New(token.SessToken) + _token.Set("user-id", strconv.FormatInt(u.ID, 10)) + tokenString, err := _token.SignExpires(u.Hash, exp) if err != nil { log.Error().Msgf("cannot create token for %s", u.Login) c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=internal_error") @@ -262,7 +265,8 @@ func GetLoginToken(c *gin.Context) { } exp := time.Now().Add(server.Config.Server.SessionExpires).Unix() - newToken := token.New(token.SessToken, user.Login) + newToken := token.New(token.SessToken) + newToken.Set("user-id", strconv.FormatInt(user.ID, 10)) tokenStr, err := newToken.SignExpires(user.Hash, exp) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) diff --git a/server/api/repo.go b/server/api/repo.go index e7b46faf1..faca0444b 100644 --- a/server/api/repo.go +++ b/server/api/repo.go @@ -118,7 +118,8 @@ func PostRepo(c *gin.Context) { } // creates the jwt token used to verify the repository - t := token.New(token.HookToken, repo.FullName) + t := token.New(token.HookToken) + t.Set("repo-id", strconv.FormatInt(repo.ID, 10)) sig, err := t.Sign(repo.Hash) if err != nil { msg := "could not generate new jwt token." @@ -520,7 +521,8 @@ func MoveRepo(c *gin.Context) { } // creates the jwt token used to verify the repository - t := token.New(token.HookToken, repo.FullName) + t := token.New(token.HookToken) + t.Set("repo-id", strconv.FormatInt(repo.ID, 10)) sig, err := t.Sign(repo.Hash) if err != nil { c.String(http.StatusInternalServerError, err.Error()) @@ -536,7 +538,7 @@ func MoveRepo(c *gin.Context) { ) if err := _forge.Deactivate(c, user, repo, host); err != nil { - log.Trace().Err(err).Msgf("deactivate repo '%s' for move to activate later, got an error", repo.FullName) + log.Trace().Err(err).Msgf("deactivate repo '%s' for move to activate later, got an error", strconv.FormatInt(repo.ID, 10)) } if err := _forge.Activate(c, user, repo, hookURL); err != nil { c.String(http.StatusInternalServerError, err.Error()) @@ -622,7 +624,8 @@ func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) { } // creates the jwt token used to verify the repository - t := token.New(token.HookToken, repo.FullName) + t := token.New(token.HookToken) + t.Set("repo-id", strconv.FormatInt(repo.ID, 10)) sig, err := t.Sign(repo.Hash) if err != nil { c.String(http.StatusInternalServerError, err.Error()) diff --git a/server/api/user.go b/server/api/user.go index ac8863953..1785f1995 100644 --- a/server/api/user.go +++ b/server/api/user.go @@ -153,7 +153,9 @@ func GetRepos(c *gin.Context) { // @Param Authorization header string true "Insert your personal access token" default(Bearer ) func PostToken(c *gin.Context) { user := session.User(c) - tokenString, err := token.New(token.UserToken, user.Login).Sign(user.Hash) + t := token.New(token.UserToken) + t.Set("user-id", strconv.FormatInt(user.ID, 10)) + tokenString, err := t.Sign(user.Hash) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -182,7 +184,9 @@ func DeleteToken(c *gin.Context) { return } - tokenString, err := token.New(token.UserToken, user.Login).Sign(user.Hash) + t := token.New(token.UserToken) + t.Set("user-id", strconv.FormatInt(user.ID, 10)) + tokenString, err := t.Sign(user.Hash) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return diff --git a/server/router/middleware/session/user.go b/server/router/middleware/session/user.go index d1b1507ef..b413cf528 100644 --- a/server/router/middleware/session/user.go +++ b/server/router/middleware/session/user.go @@ -45,7 +45,11 @@ func SetUser() gin.HandlerFunc { t, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) { var err error - user, err = store.FromContext(c).GetUserLogin(t.Text) + userID, err := strconv.ParseInt(t.Get("user-id"), 10, 64) + if err != nil { + return "", err + } + user, err = store.FromContext(c).GetUser(userID) return user.Hash, err }) if err == nil { diff --git a/server/web/config.go b/server/web/config.go index 99e78461e..daa3a2fc5 100644 --- a/server/web/config.go +++ b/server/web/config.go @@ -17,6 +17,7 @@ package web import ( "encoding/json" "net/http" + "strconv" "text/template" "github.com/gin-gonic/gin" @@ -33,10 +34,9 @@ func Config(c *gin.Context) { var csrf string if user != nil { - csrf, _ = token.New( - token.CsrfToken, - user.Login, - ).Sign(user.Hash) + t := token.New(token.CsrfToken) + t.Set("user-id", strconv.FormatInt(user.ID, 10)) + csrf, _ = t.Sign(user.Hash) } // TODO: remove this and use the forge type from the corresponding repo diff --git a/shared/token/token.go b/shared/token/token.go index d5c48aafd..9b6504a00 100644 --- a/shared/token/token.go +++ b/shared/token/token.go @@ -36,12 +36,14 @@ const ( const SignerAlgo = "HS256" type Token struct { - Kind string - Text string + Kind string + claims jwt.MapClaims } func parse(raw string, fn SecretFunc) (*Token, error) { - token := &Token{} + token := &Token{ + claims: jwt.MapClaims{}, + } parsed, err := jwt.Parse(raw, keyFunc(token, fn)) if err != nil { return nil, err @@ -99,8 +101,8 @@ func CheckCsrf(r *http.Request, fn SecretFunc) error { return err } -func New(kind, text string) *Token { - return &Token{Kind: kind, Text: text} +func New(kind string) *Token { + return &Token{Kind: kind, claims: jwt.MapClaims{}} } // Sign signs the token using the given secret hash @@ -117,14 +119,32 @@ func (t *Token) SignExpires(secret string, exp int64) (string, error) { if !ok { return "", fmt.Errorf("token claim is not a MapClaims") } + + for k, v := range t.claims { + claims[k] = v + } + claims["type"] = t.Kind - claims["text"] = t.Text if exp > 0 { claims["exp"] = float64(exp) } + return token.SignedString([]byte(secret)) } +func (t *Token) Set(key, value string) { + t.claims[key] = value +} + +func (t *Token) Get(key string) string { + claim, ok := t.claims[key].(string) + if !ok { + return "" + } + + return claim +} + func keyFunc(token *Token, fn SecretFunc) jwt.Keyfunc { return func(t *jwt.Token) (any, error) { claims, ok := t.Claims.(jwt.MapClaims) @@ -137,21 +157,26 @@ func keyFunc(token *Token, fn SecretFunc) jwt.Keyfunc { return nil, jwt.ErrSignatureInvalid } - // extract the token kind and cast to - // the expected type. + // extract the token kind and cast to the expected type kind, ok := claims["type"] if !ok { return nil, jwt.ErrInvalidType } token.Kind, _ = kind.(string) - // extract the token value and cast to - // expected type. - text, ok := claims["text"] - if !ok { - return nil, jwt.ErrInvalidType + // copy custom claims + for k, v := range claims { + // skip the reserved claims https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 + if k == "iss" || k == "sub" || k == "aud" || k == "exp" || k == "nbf" || k == "iat" || k == "jti" { + continue + } + + if k == "type" { + continue + } + + token.claims[k] = v } - token.Text, _ = text.(string) // invoke the callback function to retrieve // the secret key used to verify