// 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) }) }}
// 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 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)
})
}
}