diff --git a/.yamllint.yml b/.yamllint.yml index d7fc7209f15ad..1d8112fd534ed 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -1,6 +1,13 @@ --- extends: default +locale: en_US.UTF-8 + +yaml-files: + - '*.yaml' + - '*.yml' + - '.yamllint' + ignore: | docs/pnpm-lock.yaml internal/configuration/test_resources/config_bad_quoting.yml diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index 567999e0146fb..1bf85a25f50a4 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -52,13 +52,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr return } - if issuer, err = ctx.IssuerURL(); err != nil { - ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred determining issuer: %+v", requester.GetID(), clientID, err) - - ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrIssuerCouldNotDerive) - - return - } + issuer = ctx.RootURL() userSession := ctx.GetSession() diff --git a/internal/handlers/handler_oidc_consent.go b/internal/handlers/handler_oidc_consent.go index bc5a81d62b45c..98935cba0e89e 100644 --- a/internal/handlers/handler_oidc_consent.go +++ b/internal/handlers/handler_oidc_consent.go @@ -130,12 +130,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) { query url.Values ) - if redirectURI, err = ctx.IssuerURL(); err != nil { - ctx.Logger.Errorf("Failed to parse the consent redirect URL: %+v", err) - ctx.SetJSONError(messageOperationFailed) - - return - } + redirectURI = ctx.RootURL() if query, err = url.ParseQuery(consent.Form); err != nil { ctx.Logger.Errorf("Failed to parse the consent form values: %+v", err) diff --git a/internal/handlers/handler_oidc_wellknown.go b/internal/handlers/handler_oidc_wellknown.go index 1ac8198175fa7..7c2f3d3b3e8af 100644 --- a/internal/handlers/handler_oidc_wellknown.go +++ b/internal/handlers/handler_oidc_wellknown.go @@ -20,13 +20,7 @@ func OpenIDConnectConfigurationWellKnownGET(ctx *middlewares.AutheliaCtx) { err error ) - if issuer, err = ctx.IssuerURL(); err != nil { - ctx.Logger.Errorf("Error occurred determining OpenID Connect issuer details: %+v", err) - - ctx.ReplyStatusCode(fasthttp.StatusBadRequest) - - return - } + issuer = ctx.RootURL() wellKnown := ctx.Providers.OpenIDConnect.GetOpenIDConnectWellKnownConfiguration(issuer.String()) @@ -52,13 +46,7 @@ func OAuthAuthorizationServerWellKnownGET(ctx *middlewares.AutheliaCtx) { err error ) - if issuer, err = ctx.IssuerURL(); err != nil { - ctx.Logger.Errorf("Error occurred determining OpenID Connect issuer details: %+v", err) - - ctx.ReplyStatusCode(fasthttp.StatusBadRequest) - - return - } + issuer = ctx.RootURL() wellKnown := ctx.Providers.OpenIDConnect.GetOAuth2WellKnownConfiguration(issuer.String()) diff --git a/internal/handlers/response.go b/internal/handlers/response.go index 33599ea8e7e0b..8669597f18e84 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -144,11 +144,7 @@ func handleOIDCWorkflowResponseWithTargetURL(ctx *middlewares.AutheliaCtx, targe return } - if issuerURL, err = ctx.IssuerURL(); err != nil { - ctx.Error(fmt.Errorf("unable to get issuer for redirection: %w", err), messageAuthenticationFailed) - - return - } + issuerURL = ctx.RootURL() if targetURL.Host != issuerURL.Host { ctx.Error(fmt.Errorf("unable to redirect to '%s': target host '%s' does not match expected issuer host '%s'", targetURL, targetURL.Host, issuerURL.Host), messageAuthenticationFailed) @@ -221,11 +217,7 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) { form url.Values ) - if targetURL, err = ctx.IssuerURL(); err != nil { - ctx.Error(fmt.Errorf("unable to get issuer for redirection: %w", err), messageAuthenticationFailed) - - return - } + targetURL = ctx.RootURL() if form, err = consent.GetForm(); err != nil { ctx.Error(fmt.Errorf("unable to get authorization form values from consent session with challenge id '%s': %w", consent.ChallengeID, err), messageAuthenticationFailed) diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go index 862f16d4d26b0..e30e58399da12 100644 --- a/internal/middlewares/authelia_context.go +++ b/internal/middlewares/authelia_context.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "net/url" - "path" "strings" "github.com/asaskevich/govalidator" @@ -81,7 +80,7 @@ func (ctx *AutheliaCtx) ReplyError(err error, message string) { ctx.Logger.Error(marshalErr) } - ctx.SetContentTypeBytes(contentTypeApplicationJSON) + ctx.SetContentTypeApplicationJSON() ctx.SetBody(b) ctx.Logger.Debug(err) } @@ -90,7 +89,7 @@ func (ctx *AutheliaCtx) ReplyError(err error, message string) { func (ctx *AutheliaCtx) ReplyStatusCode(statusCode int) { ctx.Response.Reset() ctx.SetStatusCode(statusCode) - ctx.SetContentTypeBytes(contentTypeTextPlain) + ctx.SetContentTypeTextPlain() ctx.SetBodyString(fmt.Sprintf("%d %s", statusCode, fasthttp.StatusMessage(statusCode))) } @@ -108,7 +107,7 @@ func (ctx *AutheliaCtx) ReplyJSON(data any, statusCode int) (err error) { ctx.SetStatusCode(statusCode) } - ctx.SetContentTypeBytes(contentTypeApplicationJSON) + ctx.SetContentTypeApplicationJSON() ctx.SetBody(body) return nil @@ -145,7 +144,7 @@ func (ctx *AutheliaCtx) XForwardedProto() (proto []byte) { } // XForwardedMethod return the content of the X-Forwarded-Method header. -func (ctx *AutheliaCtx) XForwardedMethod() (method []byte) { +func (ctx *AutheliaCtx) XForwardedMethod() []byte { return ctx.RequestCtx.Request.Header.PeekBytes(headerXForwardedMethod) } @@ -171,79 +170,61 @@ func (ctx *AutheliaCtx) XForwardedURI() (uri []byte) { return uri } -// XAutheliaURL return the content of the X-Authelia-URL header. -func (ctx *AutheliaCtx) XAutheliaURL() (autheliaURL []byte) { +// XOriginalURL returns the content of the X-Original-URL header. +func (ctx *AutheliaCtx) XOriginalURL() []byte { + return ctx.RequestCtx.Request.Header.PeekBytes(headerXOriginalURL) +} + +// XOriginalMethod return the content of the X-Original-Method header. +func (ctx *AutheliaCtx) XOriginalMethod() []byte { + return ctx.RequestCtx.Request.Header.PeekBytes(headerXOriginalMethod) +} + +// XAutheliaURL return the content of the X-Authelia-URL header which is used to communicate the location of the +// portal when using proxies like Envoy. +func (ctx *AutheliaCtx) XAutheliaURL() []byte { return ctx.RequestCtx.Request.Header.PeekBytes(headerXAutheliaURL) } // QueryArgRedirect return the content of the rd query argument. -func (ctx *AutheliaCtx) QueryArgRedirect() (val []byte) { - return ctx.RequestCtx.QueryArgs().PeekBytes(queryArgRedirect) +func (ctx *AutheliaCtx) QueryArgRedirect() []byte { + return ctx.RequestCtx.QueryArgs().PeekBytes(qryArgRedirect) } // BasePath returns the base_url as per the path visited by the client. -func (ctx *AutheliaCtx) BasePath() (base string) { +func (ctx *AutheliaCtx) BasePath() string { if baseURL := ctx.UserValueBytes(UserValueKeyBaseURL); baseURL != nil { return baseURL.(string) } - return base + return "" } -// ExternalRootURL gets the X-Forwarded-Proto, X-Forwarded-Host headers and the BasePath and forms them into a URL. -func (ctx *AutheliaCtx) ExternalRootURL() (string, error) { - protocol := ctx.XForwardedProto() - if protocol == nil { - return "", errMissingXForwardedProto - } - - host := ctx.XForwardedHost() - if host == nil { - return "", errMissingXForwardedHost - } - - externalRootURL := fmt.Sprintf("%s://%s", protocol, host) - - if base := ctx.BasePath(); base != "" { - externalBaseURL, err := url.ParseRequestURI(externalRootURL) - if err != nil { - return "", err - } - - externalBaseURL.Path = path.Join(externalBaseURL.Path, base) - - return externalBaseURL.String(), nil +// BasePathSlash is the same as BasePath but returns a final slash as well. +func (ctx *AutheliaCtx) BasePathSlash() string { + if baseURL := ctx.UserValueBytes(UserValueKeyBaseURL); baseURL != nil { + return baseURL.(string) + strSlash } - return externalRootURL, nil + return strSlash } -// IssuerURL returns the expected Issuer. -func (ctx *AutheliaCtx) IssuerURL() (issuerURL *url.URL, err error) { - issuerURL = &url.URL{ - Scheme: "https", - } - - if scheme := ctx.XForwardedProto(); scheme != nil { - issuerURL.Scheme = string(scheme) - } - - if host := ctx.XForwardedHost(); len(host) != 0 { - issuerURL.Host = string(host) - } else { - return nil, errMissingXForwardedHost - } - - if base := ctx.BasePath(); base != "" { - issuerURL.Path = path.Join(issuerURL.Path, base) +// RootURL returns the Root URL. +func (ctx *AutheliaCtx) RootURL() (issuerURL *url.URL) { + return &url.URL{ + Scheme: string(ctx.XForwardedProto()), + Host: string(ctx.XForwardedHost()), + Path: ctx.BasePath(), } - - return issuerURL, nil } -// XOriginalURL return the content of the X-Original-URL header. -func (ctx *AutheliaCtx) XOriginalURL() []byte { - return ctx.RequestCtx.Request.Header.PeekBytes(headerXOriginalURL) +// RootURLSlash is the same as RootURL but includes a final slash as well. +func (ctx *AutheliaCtx) RootURLSlash() (issuerURL *url.URL) { + return &url.URL{ + Scheme: string(ctx.XForwardedProto()), + Host: string(ctx.XForwardedHost()), + Path: ctx.BasePathSlash(), + } } // GetSession return the user session. Any update will be saved in cache. @@ -264,7 +245,7 @@ func (ctx *AutheliaCtx) SaveSession(userSession session.UserSession) error { // ReplyOK is a helper method to reply ok. func (ctx *AutheliaCtx) ReplyOK() { - ctx.SetContentTypeBytes(contentTypeApplicationJSON) + ctx.SetContentTypeApplicationJSON() ctx.SetBody(okMessageBytes) } @@ -377,7 +358,7 @@ func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) { statusCode = fasthttp.StatusFound } - ctx.SetContentTypeBytes(contentTypeTextHTML) + ctx.SetContentTypeTextHTML() ctx.SetStatusCode(statusCode) u := fasthttp.AcquireURI() @@ -400,3 +381,18 @@ func (ctx *AutheliaCtx) RecordAuthentication(success, regulated bool, method str ctx.Providers.Metrics.RecordAuthentication(success, regulated, method) } + +// SetContentTypeTextPlain efficiently sets the Content-Type header to 'text/plain; charset=utf-8'. +func (ctx *AutheliaCtx) SetContentTypeTextPlain() { + ctx.SetContentTypeBytes(contentTypeTextPlain) +} + +// SetContentTypeTextHTML efficiently sets the Content-Type header to 'text/html; charset=utf-8'. +func (ctx *AutheliaCtx) SetContentTypeTextHTML() { + ctx.SetContentTypeBytes(contentTypeTextHTML) +} + +// SetContentTypeApplicationJSON efficiently sets the Content-Type header to 'application/json; charset=utf-8'. +func (ctx *AutheliaCtx) SetContentTypeApplicationJSON() { + ctx.SetContentTypeBytes(contentTypeApplicationJSON) +} diff --git a/internal/middlewares/authelia_context_test.go b/internal/middlewares/authelia_context_test.go index ab8882f03a06d..bd857c8357b1e 100644 --- a/internal/middlewares/authelia_context_test.go +++ b/internal/middlewares/authelia_context_test.go @@ -21,7 +21,6 @@ func TestIssuerURL(t *testing.T) { name string proto, host, base string expected string - err string }{ { name: "Standard", @@ -36,7 +35,7 @@ func TestIssuerURL(t *testing.T) { { name: "NoHost", proto: "https", host: "", base: "", - err: "Missing header X-Forwarded-Host", + expected: "https:", }, } @@ -52,21 +51,14 @@ func TestIssuerURL(t *testing.T) { mock.Ctx.SetUserValue("base_url", tc.base) } - actual, err := mock.Ctx.IssuerURL() + actual := mock.Ctx.RootURL() - switch tc.err { - case "": - assert.NoError(t, err) - require.NotNil(t, actual) + require.NotNil(t, actual) - assert.Equal(t, tc.expected, actual.String()) - assert.Equal(t, tc.proto, actual.Scheme) - assert.Equal(t, tc.host, actual.Host) - assert.Equal(t, tc.base, actual.Path) - default: - assert.EqualError(t, err, tc.err) - assert.Nil(t, actual) - } + assert.Equal(t, tc.expected, actual.String()) + assert.Equal(t, tc.proto, actual.Scheme) + assert.Equal(t, tc.host, actual.Host) + assert.Equal(t, tc.base, actual.Path) }) } } diff --git a/internal/middlewares/const.go b/internal/middlewares/const.go index 88ef4472b1174..83f832545e004 100644 --- a/internal/middlewares/const.go +++ b/internal/middlewares/const.go @@ -20,6 +20,7 @@ var ( headerXForwardedURI = []byte("X-Forwarded-URI") headerXOriginalURL = []byte("X-Original-URL") + headerXOriginalMethod = []byte("X-Original-Method") headerXForwardedMethod = []byte("X-Forwarded-Method") headerVary = []byte(fasthttp.HeaderVary) @@ -67,13 +68,17 @@ var ( const ( strProtoHTTPS = "https" strProtoHTTP = "http" + strSlash = "/" + + queryArgRedirect = "rd" + queryArgToken = "token" ) var ( protoHTTPS = []byte(strProtoHTTPS) protoHTTP = []byte(strProtoHTTP) - queryArgRedirect = []byte("rd") + qryArgRedirect = []byte(queryArgRedirect) // UserValueKeyBaseURL is the User Value key where we store the Base URL. UserValueKeyBaseURL = []byte("base_url") diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go index e4cbabce93ed1..e41a0db5ceecd 100644 --- a/internal/middlewares/identity_verification.go +++ b/internal/middlewares/identity_verification.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/mail" + "path" "time" "github.com/golang-jwt/jwt/v4" @@ -51,7 +52,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - ss, err := token.SignedString([]byte(ctx.Configuration.JWTSecret)) + signedToken, err := token.SignedString([]byte(ctx.Configuration.JWTSecret)) if err != nil { ctx.Error(err, messageOperationFailed) return @@ -62,23 +63,23 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim return } - var ( - uri string - ) - - if uri, err = ctx.ExternalRootURL(); err != nil { - ctx.Error(err, messageOperationFailed) - return - } - disableHTML := false if ctx.Configuration.Notifier.SMTP != nil { disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails } + linkURL := ctx.RootURL() + + query := linkURL.Query() + + query.Set(queryArgToken, signedToken) + + linkURL.Path = path.Join(linkURL.Path, args.TargetEndpoint) + linkURL.RawQuery = query.Encode() + values := templates.EmailIdentityVerificationValues{ Title: args.MailTitle, - LinkURL: fmt.Sprintf("%s%s?token=%s", uri, args.TargetEndpoint, ss), + LinkURL: linkURL.String(), LinkText: args.MailButtonContent, DisplayName: identity.DisplayName, RemoteIP: ctx.RemoteIP().String(), diff --git a/internal/middlewares/identity_verification_test.go b/internal/middlewares/identity_verification_test.go index f317867aa5b06..2956484140f96 100644 --- a/internal/middlewares/identity_verification_test.go +++ b/internal/middlewares/identity_verification_test.go @@ -91,24 +91,6 @@ func TestShouldFailSendingAnEmail(t *testing.T) { assert.Equal(t, "no notif", mock.Hook.LastEntry().Message) } -func TestShouldFailWhenXForwardedHostHeaderIsMissing(t *testing.T) { - mock := mocks.NewMockAutheliaCtx(t) - defer mock.Close() - - mock.Ctx.Configuration.JWTSecret = testJWTSecret - mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http") - - mock.StorageMock.EXPECT(). - SaveIdentityVerification(mock.Ctx, gomock.Any()). - Return(nil) - - args := newArgs(defaultRetriever) - middlewares.IdentityVerificationStart(args, nil)(mock.Ctx) - - assert.Equal(t, 200, mock.Ctx.Response.StatusCode()) - assert.Equal(t, "Missing header X-Forwarded-Host", mock.Hook.LastEntry().Message) -} - func TestShouldSucceedIdentityVerificationStartProcess(t *testing.T) { mock := mocks.NewMockAutheliaCtx(t) diff --git a/internal/server/const.go b/internal/server/const.go index f4a7746747f79..d07c355e87673 100644 --- a/internal/server/const.go +++ b/internal/server/const.go @@ -11,6 +11,9 @@ const ( fileOpenAPI = "openapi.yml" fileIndexHTML = "index.html" fileLogo = "logo.png" + + extHTML = ".html" + extJSON = ".json" ) var ( @@ -47,6 +50,7 @@ var ( ) const ( + environment = "ENVIRONMENT" dev = "dev" f = "false" t = "true" diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 11ae57153a2c1..729eb93b361c3 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -3,7 +3,6 @@ package server import ( "net" "os" - "strconv" "strings" "time" @@ -92,21 +91,11 @@ func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler { } func handleRouter(config schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler { - rememberMe := strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled) - resetPassword := strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable) + optsTemplatedFile := NewTemplatedFileOptions(&config) - resetPasswordCustomURL := config.AuthenticationBackend.PasswordReset.CustomURL.String() - - duoSelfEnrollment := f - if !config.DuoAPI.Disable { - duoSelfEnrollment = strconv.FormatBool(config.DuoAPI.EnableSelfEnrollment) - } - - https := config.Server.TLS.Key != "" && config.Server.TLS.Certificate != "" - - serveIndexHandler := ServeTemplatedFile(assetsRoot, fileIndexHTML, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https) - serveSwaggerHandler := ServeTemplatedFile(assetsSwagger, fileIndexHTML, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https) - serveSwaggerAPIHandler := ServeTemplatedFile(assetsSwagger, fileOpenAPI, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https) + serveIndexHandler := ServeTemplatedFile(assetsRoot, fileIndexHTML, optsTemplatedFile) + serveSwaggerHandler := ServeTemplatedFile(assetsSwagger, fileIndexHTML, optsTemplatedFile) + serveSwaggerAPIHandler := ServeTemplatedFile(assetsSwagger, fileOpenAPI, optsTemplatedFile) handlerPublicHTML := newPublicHTMLEmbeddedHandler() handlerLocales := newLocalesEmbeddedHandler() @@ -115,7 +104,7 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers) WithPreMiddlewares(middlewares.SecurityHeaders).Build() policyCORSPublicGET := middlewares.NewCORSPolicyBuilder(). - WithAllowedMethods("OPTIONS", "GET"). + WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodGet). WithAllowedOrigins("*"). Build() diff --git a/internal/server/template.go b/internal/server/template.go index 0042f885662a3..02928f4ff12d4 100644 --- a/internal/server/template.go +++ b/internal/server/template.go @@ -6,11 +6,13 @@ import ( "os" "path" "path/filepath" + "strconv" "strings" "text/template" "github.com/valyala/fasthttp" + "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/logging" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/utils" @@ -19,7 +21,7 @@ import ( // ServeTemplatedFile serves a templated version of a specified file, // this is utilised to pass information between the backend and frontend // and generate a nonce to support a restrictive CSP while using material-ui. -func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, session, theme string, https bool) middlewares.RequestHandler { +func ServeTemplatedFile(publicDir, file string, opts *TemplatedFileOptions) middlewares.RequestHandler { logger := logging.Logger() a, err := assets.Open(path.Join(publicDir, file)) @@ -37,55 +39,40 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM logger.Fatalf("Unable to parse %s template: %s", file, err) } - return func(ctx *middlewares.AutheliaCtx) { - base := "" - if baseURL := ctx.UserValueBytes(middlewares.UserValueKeyBaseURL); baseURL != nil { - base = baseURL.(string) - } + isDevEnvironment := os.Getenv(environment) == dev + return func(ctx *middlewares.AutheliaCtx) { logoOverride := f - if assetPath != "" { - if _, err := os.Stat(filepath.Join(assetPath, fileLogo)); err == nil { + if opts.AssetPath != "" { + if _, err = os.Stat(filepath.Join(opts.AssetPath, fileLogo)); err == nil { logoOverride = t } } - var scheme = schemeHTTPS - - if !https { - proto := string(ctx.XForwardedProto()) - switch proto { - case "": - break - case schemeHTTP, schemeHTTPS: - scheme = proto - } - } - - baseURL := scheme + "://" + string(ctx.XForwardedHost()) + base + "/" - nonce := utils.RandomString(32, utils.CharSetAlphaNumeric, true) - switch extension := filepath.Ext(file); extension { - case ".html": - ctx.SetContentType("text/html; charset=utf-8") + case extHTML: + ctx.SetContentTypeTextHTML() + case extJSON: + ctx.SetContentTypeApplicationJSON() default: - ctx.SetContentType("text/plain; charset=utf-8") + ctx.SetContentTypeTextPlain() } + nonce := utils.RandomString(32, utils.CharSetAlphaNumeric, true) + switch { case publicDir == assetsSwagger: ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPSwagger, nonce, nonce)) case ctx.Configuration.Server.Headers.CSPTemplate != "": ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, strings.ReplaceAll(ctx.Configuration.Server.Headers.CSPTemplate, placeholderCSPNonce, nonce)) - case os.Getenv("ENVIRONMENT") == dev: + case isDevEnvironment: ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPDevelopment, nonce)) default: ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPDefault, nonce)) } - err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, BaseURL, CSPNonce, DuoSelfEnrollment, LogoOverride, RememberMe, ResetPassword, ResetPasswordCustomURL, Session, Theme string }{Base: base, BaseURL: baseURL, CSPNonce: nonce, DuoSelfEnrollment: duoSelfEnrollment, LogoOverride: logoOverride, RememberMe: rememberMe, ResetPassword: resetPassword, ResetPasswordCustomURL: resetPasswordCustomURL, Session: session, Theme: theme}) - if err != nil { + if err = tmpl.Execute(ctx.Response.BodyWriter(), opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride)); err != nil { ctx.RequestCtx.Error("an error occurred", 503) logger.Errorf("Unable to execute template: %v", err) @@ -128,3 +115,62 @@ func writeHealthCheckEnv(disabled bool, scheme, host, path string, port int) (er return err } + +// NewTemplatedFileOptions returns a new *TemplatedFileOptions. +func NewTemplatedFileOptions(config *schema.Configuration) (opts *TemplatedFileOptions) { + opts = &TemplatedFileOptions{ + AssetPath: config.Server.AssetPath, + DuoSelfEnrollment: f, + RememberMe: strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled), + ResetPassword: strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable), + ResetPasswordCustomURL: config.AuthenticationBackend.PasswordReset.CustomURL.String(), + Theme: config.Theme, + } + + if !config.DuoAPI.Disable { + opts.DuoSelfEnrollment = strconv.FormatBool(config.DuoAPI.EnableSelfEnrollment) + } + + return opts +} + +// TemplatedFileOptions is a struct which is used for many templated files. +type TemplatedFileOptions struct { + AssetPath string + DuoSelfEnrollment string + RememberMe string + ResetPassword string + ResetPasswordCustomURL string + Session string + Theme string +} + +// CommonData returns a TemplatedFileCommonData with the dynamic options. +func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverride string) TemplatedFileCommonData { + return TemplatedFileCommonData{ + Base: base, + BaseURL: baseURL, + CSPNonce: nonce, + LogoOverride: logoOverride, + DuoSelfEnrollment: options.DuoSelfEnrollment, + RememberMe: options.RememberMe, + ResetPassword: options.ResetPassword, + ResetPasswordCustomURL: options.ResetPasswordCustomURL, + Session: options.Session, + Theme: options.Theme, + } +} + +// TemplatedFileCommonData is a struct which is used for many templated files. +type TemplatedFileCommonData struct { + Base string + BaseURL string + CSPNonce string + LogoOverride string + DuoSelfEnrollment string + RememberMe string + ResetPassword string + ResetPasswordCustomURL string + Session string + Theme string +} diff --git a/internal/suites/example/compose/caddy/Caddyfile b/internal/suites/example/compose/caddy/Caddyfile index 18fd9eed9540d..c0fc6d90ef2b4 100644 --- a/internal/suites/example/compose/caddy/Caddyfile +++ b/internal/suites/example/compose/caddy/Caddyfile @@ -8,6 +8,7 @@ :8085 { log reverse_proxy authelia-backend:9091 { + header_up X-Forwarded-Proto https import tls-transport } } diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go index 93e65d0a0910e..95c26d1d282dd 100644 --- a/internal/suites/suite_cli_test.go +++ b/internal/suites/suite_cli_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/stretchr/testify/suite" - yaml "gopkg.in/yaml.v3" + "gopkg.in/yaml.v3" "github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/storage"