// Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package shared import ( "net/http" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/context" "github.com/go-chi/cors" ) func Middlewares() (stack []any) { stack = append(stack, securityHeaders()) if setting.CORSConfig.Enabled { stack = append(stack, cors.Handler(cors.Options{ AllowedOrigins: setting.CORSConfig.AllowDomain, AllowedMethods: setting.CORSConfig.Methods, AllowCredentials: setting.CORSConfig.AllowCredentials, AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP", "X-Forgejo-OTP"}, setting.CORSConfig.Headers...), MaxAge: int(setting.CORSConfig.MaxAge.Seconds()), })) } return append(stack, context.APIContexter(), checkDeprecatedAuthMethods, // Get user from session if logged in. apiAuth(buildAuthGroup()), verifyAuthWithOptions(&common.VerifyOptions{ SignInRequired: setting.Service.RequireSignInView, }), ) } func buildAuthGroup() *auth.Group { group := auth.NewGroup( &auth.OAuth2{}, &auth.HTTPSign{}, &auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API ) if setting.Service.EnableReverseProxyAuthAPI { group.Add(&auth.ReverseProxy{}) } if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) { group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI } return group } func apiAuth(authMethod auth.Method) func(*context.APIContext) { return func(ctx *context.APIContext) { ar, err := common.AuthShared(ctx.Base, nil, authMethod) if err != nil { ctx.Error(http.StatusUnauthorized, "APIAuth", err) return } ctx.Doer = ar.Doer ctx.IsSigned = ar.Doer != nil ctx.IsBasicAuth = ar.IsBasicAuth } } // verifyAuthWithOptions checks authentication according to options func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { // Check prohibit login users. if ctx.IsSigned { if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { ctx.Data["Title"] = ctx.Tr("auth.active_your_account") ctx.JSON(http.StatusForbidden, map[string]string{ "message": "This account is not activated.", }) return } if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin { log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr()) ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") ctx.JSON(http.StatusForbidden, map[string]string{ "message": "This account is prohibited from signing in, please contact your site administrator.", }) return } if ctx.Doer.MustChangePassword { ctx.JSON(http.StatusForbidden, map[string]string{ "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password", }) return } } // Redirect to dashboard if user tries to visit any non-login page. if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" { ctx.Redirect(setting.AppSubURL + "/") return } if options.SignInRequired { if !ctx.IsSigned { // Restrict API calls with error message. ctx.JSON(http.StatusForbidden, map[string]string{ "message": "Only signed in user is allowed to call APIs.", }) return } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { ctx.Data["Title"] = ctx.Tr("auth.active_your_account") ctx.JSON(http.StatusForbidden, map[string]string{ "message": "This account is not activated.", }) return } } if options.AdminRequired { if !ctx.Doer.IsAdmin { ctx.JSON(http.StatusForbidden, map[string]string{ "message": "You have no permission to request for this.", }) return } } } } // check for and warn against deprecated authentication options func checkDeprecatedAuthMethods(ctx *context.APIContext) { if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" { ctx.Resp.Header().Set("Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.") } } func securityHeaders() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { // CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers // http://stackoverflow.com/a/3146618/244009 resp.Header().Set("x-content-type-options", "nosniff") next.ServeHTTP(resp, req) }) } }