146 lines · 4.5 KB · Go
RawDownload
// Copyright 2024 The Forgejo Authors. All rights reserved.// SPDX-License-Identifier: MIT package shared import (	"net/http" 	"forgejo.org/modules/log"	"forgejo.org/modules/setting"	"forgejo.org/routers/common"	"forgejo.org/services/auth"	"forgejo.org/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{})	} 	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 optionsfunc 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 optionsfunc 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)		})	}}