diff --git a/Dockerfile b/Dockerfile index bbc673613..e4c097536 100644 --- a/Dockerfile +++ b/Dockerfile @@ -79,6 +79,7 @@ ENV GITNESS_DATABASE_DRIVER sqlite3 ENV GITNESS_DATABASE_DATASOURCE /data/database.sqlite ENV GITNESS_METRIC_ENABLED=true ENV GITNESS_METRIC_ENDPOINT=https://stats.drone.ci/api/v1/gitness +ENV GITNESS_TOKEN_COOKIE_NAME=token COPY --from=builder /app/gitness /app/gitness COPY --from=cert-image /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 2b27b8396..50ec6ce9d 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -94,7 +94,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro controller := user.ProvideController(db, principalUID, authorizer, principalStore, tokenStore, membershipStore) serviceController := service.NewController(principalUID, authorizer, principalStore) bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController) - authenticator := authn.ProvideAuthenticator(principalStore, tokenStore) + authenticator := authn.ProvideAuthenticator(config, principalStore, tokenStore) provider, err := url.ProvideURLProvider(config) if err != nil { return nil, err diff --git a/internal/api/handler/account/cookie.go b/internal/api/handler/account/cookie.go index 514742e59..c3e50a19f 100644 --- a/internal/api/handler/account/cookie.go +++ b/internal/api/handler/account/cookie.go @@ -9,12 +9,15 @@ import ( "net/http" "time" - "github.com/harness/gitness/internal/api/request" "github.com/harness/gitness/types" ) -func includeTokenCookie(r *http.Request, w http.ResponseWriter, tokenResponse *types.TokenResponse) { - cookie := newEmptyTokenCookie(r) +func includeTokenCookie( + r *http.Request, w http.ResponseWriter, + tokenResponse *types.TokenResponse, + cookieName string, +) { + cookie := newEmptyTokenCookie(r, cookieName) cookie.Value = tokenResponse.AccessToken if tokenResponse.Token.ExpiresAt != nil { cookie.Expires = time.UnixMilli(*tokenResponse.Token.ExpiresAt) @@ -23,24 +26,24 @@ func includeTokenCookie(r *http.Request, w http.ResponseWriter, tokenResponse *t http.SetCookie(w, cookie) } -func deleteTokenCookieIfPresent(r *http.Request, w http.ResponseWriter) { +func deleteTokenCookieIfPresent(r *http.Request, w http.ResponseWriter, cookieName string) { // if no token is present in the cookies, nothing todo. // No other error type expected here - and even if there is, let's try best effort deletion. - _, err := r.Cookie(request.CookieToken) + _, err := r.Cookie(cookieName) if errors.Is(err, http.ErrNoCookie) { return } - cookie := newEmptyTokenCookie(r) + cookie := newEmptyTokenCookie(r, cookieName) cookie.Value = "" cookie.Expires = time.UnixMilli(0) // this effectively tells the browser to delete the cookie http.SetCookie(w, cookie) } -func newEmptyTokenCookie(r *http.Request) *http.Cookie { +func newEmptyTokenCookie(r *http.Request, cookieName string) *http.Cookie { return &http.Cookie{ - Name: request.CookieToken, + Name: cookieName, SameSite: http.SameSiteStrictMode, HttpOnly: true, Path: "/", diff --git a/internal/api/handler/account/login.go b/internal/api/handler/account/login.go index d26ea722e..c3a00c402 100644 --- a/internal/api/handler/account/login.go +++ b/internal/api/handler/account/login.go @@ -15,19 +15,13 @@ import ( // HandleLogin returns an http.HandlerFunc that authenticates // the user and returns an authentication token on success. -func HandleLogin(userCtrl *user.Controller) http.HandlerFunc { +func HandleLogin(userCtrl *user.Controller, cookieName string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) - includeCookie, err := request.GetIncludeCookieFromQueryOrDefault(r, false) - if err != nil { - render.TranslatedUserError(w, err) - return - } - in := new(user.LoginInput) - err = json.NewDecoder(r.Body).Decode(in) + err := json.NewDecoder(r.Body).Decode(in) if err != nil { render.BadRequestf(w, "Invalid request body: %s.", err) return @@ -39,8 +33,8 @@ func HandleLogin(userCtrl *user.Controller) http.HandlerFunc { return } - if includeCookie { - includeTokenCookie(r, w, tokenResponse) + if cookieName != "" { + includeTokenCookie(r, w, tokenResponse, cookieName) } render.JSON(w, http.StatusOK, tokenResponse) diff --git a/internal/api/handler/account/logout.go b/internal/api/handler/account/logout.go index 33fbe4e6f..64c9f3814 100644 --- a/internal/api/handler/account/logout.go +++ b/internal/api/handler/account/logout.go @@ -14,7 +14,7 @@ import ( // HandleLogout returns a http.HandlerFunc that deletes the // user token being used in the respective request and logs the user out. -func HandleLogout(userCtrl *user.Controller) http.HandlerFunc { +func HandleLogout(userCtrl *user.Controller, cookieName string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) @@ -24,7 +24,7 @@ func HandleLogout(userCtrl *user.Controller) http.HandlerFunc { // best effort delete cookie even in case of errors, to avoid clients being unable to remove the cookie. // WARNING: It could be that the cookie is removed even though the token is still there in the DB. // However, we have APIs to list and delete session tokens, and expiry time is usually short. - deleteTokenCookieIfPresent(r, w) + deleteTokenCookieIfPresent(r, w, cookieName) if err != nil { render.TranslatedUserError(w, err) diff --git a/internal/api/handler/account/register.go b/internal/api/handler/account/register.go index 360775cb1..0da9b8ef4 100644 --- a/internal/api/handler/account/register.go +++ b/internal/api/handler/account/register.go @@ -16,7 +16,7 @@ import ( // HandleRegister returns an http.HandlerFunc that processes an http.Request // to register the named user account with the system. -func HandleRegister(userCtrl *user.Controller, sysCtrl *system.Controller) http.HandlerFunc { +func HandleRegister(userCtrl *user.Controller, sysCtrl *system.Controller, cookieName string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -40,7 +40,7 @@ func HandleRegister(userCtrl *user.Controller, sysCtrl *system.Controller) http. } if includeCookie { - includeTokenCookie(r, w, tokenResponse) + includeTokenCookie(r, w, tokenResponse, cookieName) } render.JSON(w, http.StatusOK, tokenResponse) diff --git a/internal/api/request/auth.go b/internal/api/request/auth.go index 3524a3ac0..b35f903a4 100644 --- a/internal/api/request/auth.go +++ b/internal/api/request/auth.go @@ -11,7 +11,6 @@ import ( const ( QueryParamAccessToken = "access_token" QueryParamIncludeCookie = "include_cookie" - CookieToken = "token" ) func GetAccessTokenFromQuery(r *http.Request) (string, bool) { @@ -22,6 +21,6 @@ func GetIncludeCookieFromQueryOrDefault(r *http.Request, dflt bool) (bool, error return QueryParamAsBoolOrDefault(r, QueryParamIncludeCookie, dflt) } -func GetTokenFromCookie(r *http.Request) (string, bool) { - return GetCookie(r, CookieToken) +func GetTokenFromCookie(r *http.Request, cookieName string) (string, bool) { + return GetCookie(r, cookieName) } diff --git a/internal/auth/authn/jwt.go b/internal/auth/authn/jwt.go index 5ea444ba0..68b0e21ca 100644 --- a/internal/auth/authn/jwt.go +++ b/internal/auth/authn/jwt.go @@ -24,14 +24,18 @@ var _ Authenticator = (*JWTAuthenticator)(nil) // JWTAuthenticator uses the provided JWT to authenticate the caller. type JWTAuthenticator struct { + cookieName string principalStore store.PrincipalStore tokenStore store.TokenStore } func NewTokenAuthenticator( principalStore store.PrincipalStore, - tokenStore store.TokenStore) *JWTAuthenticator { + tokenStore store.TokenStore, + cookieName string, +) *JWTAuthenticator { return &JWTAuthenticator{ + cookieName: cookieName, principalStore: principalStore, tokenStore: tokenStore, } @@ -39,7 +43,7 @@ func NewTokenAuthenticator( func (a *JWTAuthenticator) Authenticate(r *http.Request, sourceRouter SourceRouter) (*auth.Session, error) { ctx := r.Context() - str := extractToken(r) + str := extractToken(r, a.cookieName) if len(str) == 0 { return nil, ErrNoAuthData @@ -122,7 +126,7 @@ func (a *JWTAuthenticator) metadataFromMembershipClaims( }, nil } -func extractToken(r *http.Request) string { +func extractToken(r *http.Request, cookieName string) string { // Check query param first (as that's most immediately visible to caller) if queryToken, ok := request.GetAccessTokenFromQuery(r); ok { return queryToken @@ -145,7 +149,7 @@ func extractToken(r *http.Request) string { } // check cookies last (as that's least visible to caller) - if cookieToken, ok := request.GetTokenFromCookie(r); ok { + if cookieToken, ok := request.GetTokenFromCookie(r, cookieName); ok { return cookieToken } diff --git a/internal/auth/authn/wire.go b/internal/auth/authn/wire.go index 36a98c287..14d61c281 100644 --- a/internal/auth/authn/wire.go +++ b/internal/auth/authn/wire.go @@ -6,6 +6,7 @@ package authn import ( "github.com/harness/gitness/internal/store" + "github.com/harness/gitness/types" "github.com/google/wire" ) @@ -15,6 +16,6 @@ var WireSet = wire.NewSet( ProvideAuthenticator, ) -func ProvideAuthenticator(principalStore store.PrincipalStore, tokenStore store.TokenStore) Authenticator { - return NewTokenAuthenticator(principalStore, tokenStore) +func ProvideAuthenticator(config *types.Config, principalStore store.PrincipalStore, tokenStore store.TokenStore) Authenticator { + return NewTokenAuthenticator(principalStore, tokenStore, config.Token.CookieName) } diff --git a/internal/router/api.go b/internal/router/api.go index c3f49b2f3..b2b0565ae 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -119,7 +119,7 @@ func NewAPIHandler( r.Use(middlewareauthn.Attempt(authenticator, authn.SourceRouterAPI)) r.Route("/v1", func(r chi.Router) { - setupRoutesV1(r, repoCtrl, executionCtrl, triggerCtrl, logCtrl, pipelineCtrl, + setupRoutesV1(r, config, repoCtrl, executionCtrl, triggerCtrl, logCtrl, pipelineCtrl, connectorCtrl, templateCtrl, pluginCtrl, secretCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl, sysCtrl) }) @@ -142,6 +142,7 @@ func corsHandler(config *types.Config) func(http.Handler) http.Handler { } func setupRoutesV1(r chi.Router, + config *types.Config, repoCtrl *repo.Controller, executionCtrl *execution.Controller, triggerCtrl *trigger.Controller, @@ -171,7 +172,7 @@ func setupRoutesV1(r chi.Router, setupPrincipals(r, principalCtrl) setupInternal(r, githookCtrl) setupAdmin(r, userCtrl) - setupAccount(r, userCtrl, sysCtrl) + setupAccount(r, userCtrl, sysCtrl, config) setupSystem(r, sysCtrl) setupResources(r) setupPlugins(r, pluginCtrl) @@ -598,8 +599,9 @@ func setupAdmin(r chi.Router, userCtrl *user.Controller) { }) } -func setupAccount(r chi.Router, userCtrl *user.Controller, sysCtrl *system.Controller) { - r.Post("/login", account.HandleLogin(userCtrl)) - r.Post("/register", account.HandleRegister(userCtrl, sysCtrl)) - r.Post("/logout", account.HandleLogout(userCtrl)) +func setupAccount(r chi.Router, userCtrl *user.Controller, sysCtrl *system.Controller, config *types.Config) { + cookieName := config.Token.CookieName + r.Post("/login", account.HandleLogin(userCtrl, cookieName)) + r.Post("/register", account.HandleRegister(userCtrl, sysCtrl, cookieName)) + r.Post("/logout", account.HandleLogout(userCtrl, cookieName)) } diff --git a/types/config.go b/types/config.go index 5bbd2ecae..5efa99300 100644 --- a/types/config.go +++ b/types/config.go @@ -102,7 +102,8 @@ type Config struct { // Token defines token configuration parameters. Token struct { - Expire time.Duration `envconfig:"GITNESS_TOKEN_EXPIRE" default:"720h"` + CookieName string `envconfig:"GITNESS_TOKEN_COOKIE_NAME" default:"token"` + Expire time.Duration `envconfig:"GITNESS_TOKEN_EXPIRE" default:"720h"` } Logs struct {