Merge branch 'master' into patch-1

pull/4996/head
James Elliott 2023-03-02 11:50:21 +11:00 committed by GitHub
commit 66abe40180
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 529 additions and 329 deletions

View File

@ -76,6 +76,14 @@ paths:
schema: schema:
$ref: '#/components/schemas/handlers.configuration.PasswordPolicyConfigurationBody' $ref: '#/components/schemas/handlers.configuration.PasswordPolicyConfigurationBody'
/api/health: /api/health:
head:
tags:
- State
summary: Application Health
description: The health check endpoint provides information about the health of Authelia.
responses:
"200":
description: Successful Operation
get: get:
tags: tags:
- State - State

View File

@ -7,5 +7,5 @@
package cmd package cmd
const ( const (
versionSwaggerUI = "4.16.1" versionSwaggerUI = "4.17.0"
) )

View File

@ -75,7 +75,8 @@ level to `debug` or `trace` this will generate large amount of log entries. Admi
they rotate and/or truncate the logs over time to prevent significant long-term disk usage. they rotate and/or truncate the logs over time to prevent significant long-term disk usage.
If you include the value `%d` in the filename it will replace this value with a date time indicative of the time If you include the value `%d` in the filename it will replace this value with a date time indicative of the time
the logger was initialized using `2006-02-01T150405Z` as the format. the logger was initialized using [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) as the format which is
represented as `2006-01-02T15:04:05Z07:00` in go.
#### File Path Examples #### File Path Examples

6
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/deckarep/golang-set/v2 v2.1.0 github.com/deckarep/golang-set/v2 v2.1.0
github.com/duosecurity/duo_api_golang v0.0.0-20230203160531-b221c950c2b0 github.com/duosecurity/duo_api_golang v0.0.0-20230203160531-b221c950c2b0
github.com/fasthttp/router v1.4.16 github.com/fasthttp/router v1.4.17
github.com/fasthttp/session/v2 v2.4.16 github.com/fasthttp/session/v2 v2.4.16
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/go-asn1-ber/asn1-ber v1.5.4 github.com/go-asn1-ber/asn1-ber v1.5.4
@ -20,7 +20,7 @@ require (
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/hashicorp/go-retryablehttp v0.7.2 github.com/hashicorp/go-retryablehttp v0.7.2
github.com/jackc/pgx/v5 v5.3.0 github.com/jackc/pgx/v5 v5.3.1
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/knadh/koanf/parsers/yaml v0.1.0 github.com/knadh/koanf/parsers/yaml v0.1.0
github.com/knadh/koanf/providers/confmap v0.1.0 github.com/knadh/koanf/providers/confmap v0.1.0
@ -101,7 +101,7 @@ require (
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/spf13/afero v1.9.3 // indirect github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect

11
go.sum
View File

@ -126,8 +126,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/fasthttp/router v1.4.16 h1:faWJ9OtaHvAtodreyQLps58M80YFNzphMJtOJzeESXs= github.com/fasthttp/router v1.4.17 h1:Z8fndZotdwcPoYTt8BWwnRBts2UQPnKmOxbb94n0GUc=
github.com/fasthttp/router v1.4.16/go.mod h1:NFNlTCilbRVkeLc+E5JDkcxUdkpiJGKDL8Zy7Ey2JTI= github.com/fasthttp/router v1.4.17/go.mod h1:EOMfK/dT1IMzbyPhzw6E2j90owHvY+/BY60bLxOye/8=
github.com/fasthttp/session/v2 v2.4.16 h1:JRvuEqr/+/cNMBkhGZN118FurLh6paUGscwJr26TxAQ= github.com/fasthttp/session/v2 v2.4.16 h1:JRvuEqr/+/cNMBkhGZN118FurLh6paUGscwJr26TxAQ=
github.com/fasthttp/session/v2 v2.4.16/go.mod h1:nv8SD6pAx3n3KjJsEt4k1p0vstqclbNcrCwjc1OjuCI= github.com/fasthttp/session/v2 v2.4.16/go.mod h1:nv8SD6pAx3n3KjJsEt4k1p0vstqclbNcrCwjc1OjuCI=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
@ -281,8 +281,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA= github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0= github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0=
github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss= github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
@ -457,8 +457,9 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=

View File

@ -1,8 +1,6 @@
package handlers package handlers
import ( import (
"net/url"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/middlewares"
@ -11,20 +9,11 @@ import (
// OpenIDConnectConfigurationWellKnownGET handles requests to a .well-known endpoint (RFC5785) which returns the // OpenIDConnectConfigurationWellKnownGET handles requests to a .well-known endpoint (RFC5785) which returns the
// OpenID Connect Discovery 1.0 metadata. // OpenID Connect Discovery 1.0 metadata.
// //
// https://datatracker.ietf.org/doc/html/rfc5785 // RFC5785: Defining Well-Known URIs (https://datatracker.ietf.org/doc/html/rfc5785)
// //
// https://openid.net/specs/openid-connect-discovery-1_0.html // OpenID Connect Discovery 1.0 (https://openid.net/specs/openid-connect-discovery-1_0.html)
func OpenIDConnectConfigurationWellKnownGET(ctx *middlewares.AutheliaCtx) { func OpenIDConnectConfigurationWellKnownGET(ctx *middlewares.AutheliaCtx) {
var ( if err := ctx.ReplyJSON(ctx.Providers.OpenIDConnect.GetOpenIDConnectWellKnownConfiguration(ctx.RootURL().String()), fasthttp.StatusOK); err != nil {
issuer *url.URL
err error
)
issuer = ctx.RootURL()
wellKnown := ctx.Providers.OpenIDConnect.GetOpenIDConnectWellKnownConfiguration(issuer.String())
if err = ctx.ReplyJSON(wellKnown, fasthttp.StatusOK); err != nil {
ctx.Logger.Errorf("Error occurred in JSON encode: %+v", err) ctx.Logger.Errorf("Error occurred in JSON encode: %+v", err)
// TODO: Determine if this is the appropriate error code here. // TODO: Determine if this is the appropriate error code here.
@ -37,20 +26,11 @@ func OpenIDConnectConfigurationWellKnownGET(ctx *middlewares.AutheliaCtx) {
// OAuthAuthorizationServerWellKnownGET handles requests to a .well-known endpoint (RFC5785) which returns the // OAuthAuthorizationServerWellKnownGET handles requests to a .well-known endpoint (RFC5785) which returns the
// OAuth 2.0 Authorization Server Metadata (RFC8414). // OAuth 2.0 Authorization Server Metadata (RFC8414).
// //
// https://datatracker.ietf.org/doc/html/rfc5785 // RFC5785: Defining Well-Known URIs (https://datatracker.ietf.org/doc/html/rfc5785)
// //
// https://datatracker.ietf.org/doc/html/rfc8414 // RFC8414: OAuth 2.0 Authorization Server Metadata (https://datatracker.ietf.org/doc/html/rfc8414)
func OAuthAuthorizationServerWellKnownGET(ctx *middlewares.AutheliaCtx) { func OAuthAuthorizationServerWellKnownGET(ctx *middlewares.AutheliaCtx) {
var ( if err := ctx.ReplyJSON(ctx.Providers.OpenIDConnect.GetOAuth2WellKnownConfiguration(ctx.RootURL().String()), fasthttp.StatusOK); err != nil {
issuer *url.URL
err error
)
issuer = ctx.RootURL()
wellKnown := ctx.Providers.OpenIDConnect.GetOAuth2WellKnownConfiguration(issuer.String())
if err = ctx.ReplyJSON(wellKnown, fasthttp.StatusOK); err != nil {
ctx.Logger.Errorf("Error occurred in JSON encode: %+v", err) ctx.Logger.Errorf("Error occurred in JSON encode: %+v", err)
// TODO: Determine if this is the appropriate error code here. // TODO: Determine if this is the appropriate error code here.

View File

@ -50,7 +50,7 @@ func InitializeLogger(config schema.LogConfiguration, log bool) error {
} }
if config.FilePath != "" { if config.FilePath != "" {
filePath := strings.ReplaceAll(config.FilePath, "%d", time.Now().Format("2006-02-01T150405Z")) filePath := strings.ReplaceAll(config.FilePath, "%d", time.Now().Format(time.RFC3339))
f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)

View File

@ -11,6 +11,7 @@ import (
"net/http" "net/http"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
@ -64,7 +65,15 @@ func newPublicHTMLEmbeddedHandler() fasthttp.RequestHandler {
} }
ctx.SetContentType(contentType) ctx.SetContentType(contentType)
ctx.SetBody(data)
switch {
case ctx.IsHead():
ctx.Response.ResetBody()
ctx.Response.SkipBody = true
ctx.Response.Header.Set(fasthttp.HeaderContentLength, strconv.Itoa(len(data)))
default:
ctx.SetBody(data)
}
} }
} }
@ -182,7 +191,14 @@ func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
middlewares.SetContentTypeApplicationJSON(ctx) middlewares.SetContentTypeApplicationJSON(ctx)
ctx.SetBody(data) switch {
case ctx.IsHead():
ctx.Response.ResetBody()
ctx.Response.SkipBody = true
ctx.Response.Header.Set(fasthttp.HeaderContentLength, strconv.Itoa(len(data)))
default:
ctx.SetBody(data)
}
} }
} }

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"fmt"
"net" "net"
"os" "os"
"path" "path"
@ -77,10 +78,10 @@ func handleError() func(ctx *fasthttp.RequestCtx, err error) {
func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler { func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) { return func(ctx *fasthttp.RequestCtx) {
path := strings.ToLower(string(ctx.Path())) uri := strings.ToLower(string(ctx.Path()))
for i := 0; i < len(dirsHTTPServer); i++ { for i := 0; i < len(dirsHTTPServer); i++ {
if path == dirsHTTPServer[i].name || strings.HasPrefix(path, dirsHTTPServer[i].prefix) { if uri == dirsHTTPServer[i].name || strings.HasPrefix(uri, dirsHTTPServer[i].prefix) {
handlers.SetStatusCodeResponse(ctx, fasthttp.StatusNotFound) handlers.SetStatusCodeResponse(ctx, fasthttp.StatusNotFound)
return return
@ -91,6 +92,13 @@ func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {
} }
} }
func handleMethodNotAllowed(ctx *fasthttp.RequestCtx) {
middlewares.SetContentTypeTextPlain(ctx)
ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed)
ctx.SetBodyString(fmt.Sprintf("%d %s", fasthttp.StatusMethodNotAllowed, fasthttp.StatusMessage(fasthttp.StatusMethodNotAllowed)))
}
//nolint:gocyclo //nolint:gocyclo
func handleRouter(config *schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler { func handleRouter(config *schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
log := logging.Logger() log := logging.Logger()
@ -115,29 +123,45 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
r := router.New() r := router.New()
// Static Assets. // Static Assets.
r.HEAD("/", bridge(serveIndexHandler))
r.GET("/", bridge(serveIndexHandler)) r.GET("/", bridge(serveIndexHandler))
for _, f := range filesRoot { for _, f := range filesRoot {
r.HEAD("/"+f, handlerPublicHTML)
r.GET("/"+f, handlerPublicHTML) r.GET("/"+f, handlerPublicHTML)
} }
r.HEAD("/favicon.ico", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerPublicHTML))
r.GET("/favicon.ico", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerPublicHTML)) r.GET("/favicon.ico", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerPublicHTML))
r.HEAD("/static/media/logo.png", middlewares.AssetOverride(config.Server.AssetPath, 2, handlerPublicHTML))
r.GET("/static/media/logo.png", middlewares.AssetOverride(config.Server.AssetPath, 2, handlerPublicHTML)) r.GET("/static/media/logo.png", middlewares.AssetOverride(config.Server.AssetPath, 2, handlerPublicHTML))
r.HEAD("/static/{filepath:*}", handlerPublicHTML)
r.GET("/static/{filepath:*}", handlerPublicHTML) r.GET("/static/{filepath:*}", handlerPublicHTML)
// Locales. // Locales.
r.HEAD("/locales/{language:[a-z]{1,3}}-{variant:[a-zA-Z0-9-]+}/{namespace:[a-z]+}.json", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerLocales))
r.GET("/locales/{language:[a-z]{1,3}}-{variant:[a-zA-Z0-9-]+}/{namespace:[a-z]+}.json", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerLocales)) r.GET("/locales/{language:[a-z]{1,3}}-{variant:[a-zA-Z0-9-]+}/{namespace:[a-z]+}.json", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerLocales))
r.HEAD("/locales/{language:[a-z]{1,3}}/{namespace:[a-z]+}.json", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerLocales))
r.GET("/locales/{language:[a-z]{1,3}}/{namespace:[a-z]+}.json", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerLocales)) r.GET("/locales/{language:[a-z]{1,3}}/{namespace:[a-z]+}.json", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerLocales))
// Swagger. // Swagger.
r.HEAD("/api/", bridge(serveOpenAPIHandler))
r.GET("/api/", bridge(serveOpenAPIHandler)) r.GET("/api/", bridge(serveOpenAPIHandler))
r.OPTIONS("/api/", policyCORSPublicGET.HandleOPTIONS) r.OPTIONS("/api/", policyCORSPublicGET.HandleOPTIONS)
r.HEAD("/api/index.html", bridge(serveOpenAPIHandler))
r.GET("/api/index.html", bridge(serveOpenAPIHandler)) r.GET("/api/index.html", bridge(serveOpenAPIHandler))
r.OPTIONS("/api/index.html", policyCORSPublicGET.HandleOPTIONS) r.OPTIONS("/api/index.html", policyCORSPublicGET.HandleOPTIONS)
r.HEAD("/api/openapi.yml", policyCORSPublicGET.Middleware(bridge(serveOpenAPISpecHandler)))
r.GET("/api/openapi.yml", policyCORSPublicGET.Middleware(bridge(serveOpenAPISpecHandler))) r.GET("/api/openapi.yml", policyCORSPublicGET.Middleware(bridge(serveOpenAPISpecHandler)))
r.OPTIONS("/api/openapi.yml", policyCORSPublicGET.HandleOPTIONS) r.OPTIONS("/api/openapi.yml", policyCORSPublicGET.HandleOPTIONS)
for _, file := range filesSwagger { for _, file := range filesSwagger {
r.HEAD("/api/"+file, handlerPublicHTML)
r.GET("/api/"+file, handlerPublicHTML) r.GET("/api/"+file, handlerPublicHTML)
} }
@ -150,7 +174,9 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
WithPostMiddlewares(middlewares.Require1FA). WithPostMiddlewares(middlewares.Require1FA).
Build() Build()
r.HEAD("/api/health", middlewareAPI(handlers.HealthGET))
r.GET("/api/health", middlewareAPI(handlers.HealthGET)) r.GET("/api/health", middlewareAPI(handlers.HealthGET))
r.GET("/api/state", middlewareAPI(handlers.StateGET)) r.GET("/api/state", middlewareAPI(handlers.StateGET))
r.GET("/api/configuration", middleware1FA(handlers.ConfigurationGET)) r.GET("/api/configuration", middleware1FA(handlers.ConfigurationGET))
@ -356,7 +382,7 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
} }
r.HandleMethodNotAllowed = true r.HandleMethodNotAllowed = true
r.MethodNotAllowed = handlers.Status(fasthttp.StatusMethodNotAllowed) r.MethodNotAllowed = handleMethodNotAllowed
r.NotFound = handleNotFound(bridge(serveIndexHandler)) r.NotFound = handleNotFound(bridge(serveIndexHandler))
handler := middlewares.LogRequest(r.Handler) handler := middlewares.LogRequest(r.Handler)

View File

@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -25,7 +26,7 @@ import (
// and generate a nonce to support a restrictive CSP while using material-ui. // and generate a nonce to support a restrictive CSP while using material-ui.
func ServeTemplatedFile(t templates.Template, opts *TemplatedFileOptions) middlewares.RequestHandler { func ServeTemplatedFile(t templates.Template, opts *TemplatedFileOptions) middlewares.RequestHandler {
isDevEnvironment := os.Getenv(environment) == dev isDevEnvironment := os.Getenv(environment) == dev
ext := filepath.Ext(t.Name()) ext := path.Ext(t.Name())
return func(ctx *middlewares.AutheliaCtx) { return func(ctx *middlewares.AutheliaCtx) {
var err error var err error
@ -67,18 +68,34 @@ func ServeTemplatedFile(t templates.Template, opts *TemplatedFileOptions) middle
rememberMe = strconv.FormatBool(!provider.Config.DisableRememberMe) rememberMe = strconv.FormatBool(!provider.Config.DisableRememberMe)
} }
if err = t.Execute(ctx.Response.BodyWriter(), opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride, rememberMe)); err != nil { data := &bytes.Buffer{}
ctx.RequestCtx.Error("an error occurred", 503)
if err = t.Execute(data, opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride, rememberMe)); err != nil {
ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable)
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template") ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
return return
} }
switch {
case ctx.IsHead():
ctx.Response.ResetBody()
ctx.Response.SkipBody = true
ctx.Response.Header.Set(fasthttp.HeaderContentLength, strconv.Itoa(data.Len()))
default:
if _, err = data.WriteTo(ctx.Response.BodyWriter()); err != nil {
ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable)
ctx.Logger.WithError(err).Errorf("Error occcurred writing body")
return
}
}
} }
} }
// ServeTemplatedOpenAPI serves templated OpenAPI related files. // ServeTemplatedOpenAPI serves templated OpenAPI related files.
func ServeTemplatedOpenAPI(t templates.Template, opts *TemplatedFileOptions) middlewares.RequestHandler { func ServeTemplatedOpenAPI(t templates.Template, opts *TemplatedFileOptions) middlewares.RequestHandler {
ext := filepath.Ext(t.Name()) ext := path.Ext(t.Name())
spec := ext == extYML spec := ext == extYML
@ -103,12 +120,28 @@ func ServeTemplatedOpenAPI(t templates.Template, opts *TemplatedFileOptions) mid
var err error var err error
if err = t.Execute(ctx.Response.BodyWriter(), opts.OpenAPIData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce)); err != nil { data := &bytes.Buffer{}
ctx.RequestCtx.Error("an error occurred", 503)
if err = t.Execute(data, opts.OpenAPIData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce)); err != nil {
ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable)
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template") ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
return return
} }
switch {
case ctx.IsHead():
ctx.Response.ResetBody()
ctx.Response.SkipBody = true
ctx.Response.Header.Set(fasthttp.HeaderContentLength, strconv.Itoa(data.Len()))
default:
if _, err = data.WriteTo(ctx.Response.BodyWriter()); err != nil {
ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable)
ctx.Logger.WithError(err).Errorf("Error occcurred writing body")
return
}
}
} }
} }
@ -139,6 +172,11 @@ func ETagRootURL(next middlewares.RequestHandler) middlewares.RequestHandler {
next(ctx) next(ctx)
if ctx.Response.SkipBody || ctx.Response.StatusCode() != fasthttp.StatusOK {
// Skip generating the ETag as the response body should be empty.
return
}
mu.Lock() mu.Lock()
h.Write(ctx.Response.Body()) h.Write(ctx.Response.Body())

View File

@ -7,61 +7,69 @@ import (
"testing" "testing"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/valyala/fasthttp"
) )
// WARNING: This scenario is intended to be used with TLS enabled in the authelia backend. // WARNING: This scenario is intended to be used with TLS enabled in the authelia backend.
type BackendProtectionScenario struct { type BackendProtectionScenario struct {
suite.Suite suite.Suite
client *http.Client
} }
func NewBackendProtectionScenario() *BackendProtectionScenario { func NewBackendProtectionScenario() *BackendProtectionScenario {
return &BackendProtectionScenario{} return &BackendProtectionScenario{}
} }
func (s *BackendProtectionScenario) SetupSuite() {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // Needs to be enabled in suites. Not used in production.
}
s.client = &http.Client{
Transport: tr,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
}
func (s *BackendProtectionScenario) AssertRequestStatusCode(method, url string, expectedStatusCode int) { func (s *BackendProtectionScenario) AssertRequestStatusCode(method, url string, expectedStatusCode int) {
s.Run(url, func() { s.Run(url, func() {
req, err := http.NewRequest(method, url, nil) req, err := http.NewRequest(method, url, nil)
s.Assert().NoError(err) s.Assert().NoError(err)
tr := &http.Transport{ res, err := s.client.Do(req)
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // Needs to be enabled in suites. Not used in production.
}
client := &http.Client{
Transport: tr,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
res, err := client.Do(req)
s.Assert().NoError(err) s.Assert().NoError(err)
s.Assert().Equal(expectedStatusCode, res.StatusCode) s.Assert().Equal(expectedStatusCode, res.StatusCode)
}) })
} }
func (s *BackendProtectionScenario) TestProtectionOfBackendEndpoints() { func (s *BackendProtectionScenario) TestProtectionOfBackendEndpoints() {
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/totp", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/secondfactor/totp", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/webauthn/assertion", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/secondfactor/webauthn/assertion", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/webauthn/attestation", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/secondfactor/webauthn/attestation", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/user/info/2fa_method", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/user/info/2fa_method", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/user/info", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodGet, fmt.Sprintf("%s/api/user/info", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/configuration", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodGet, fmt.Sprintf("%s/api/configuration", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/totp/identity/start", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/secondfactor/totp/identity/start", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/totp/identity/finish", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/secondfactor/totp/identity/finish", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/webauthn/identity/start", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/secondfactor/webauthn/identity/start", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/webauthn/identity/finish", AutheliaBaseURL), 403) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/secondfactor/webauthn/identity/finish", AutheliaBaseURL), 403)
} }
func (s *BackendProtectionScenario) TestInvalidEndpointsReturn404() { func (s *BackendProtectionScenario) TestInvalidEndpointsReturn404() {
s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/not_existing", AutheliaBaseURL), 404) s.AssertRequestStatusCode(fasthttp.MethodGet, fmt.Sprintf("%s/api/not_existing", AutheliaBaseURL), 404)
s.AssertRequestStatusCode("HEAD", fmt.Sprintf("%s/api/not_existing", AutheliaBaseURL), 404) s.AssertRequestStatusCode(fasthttp.MethodHead, fmt.Sprintf("%s/api/not_existing", AutheliaBaseURL), 404)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/not_existing", AutheliaBaseURL), 404) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/not_existing", AutheliaBaseURL), 404)
s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404) s.AssertRequestStatusCode(fasthttp.MethodGet, fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404)
s.AssertRequestStatusCode("HEAD", fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404) s.AssertRequestStatusCode(fasthttp.MethodHead, fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404) s.AssertRequestStatusCode(fasthttp.MethodPost, fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404)
} }
func (s *BackendProtectionScenario) TestInvalidEndpointsReturn405() { func (s *BackendProtectionScenario) TestInvalidEndpointsReturn405() {

View File

@ -0,0 +1,106 @@
package suites
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/suite"
"github.com/valyala/fasthttp"
)
func NewRequestMethodScenario() *RequestMethodScenario {
return &RequestMethodScenario{}
}
type RequestMethodScenario struct {
suite.Suite
client *http.Client
}
func (s *RequestMethodScenario) SetupSuite() {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // Needs to be enabled in suites. Not used in production.
}
s.client = &http.Client{
Transport: tr,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
}
func (s *RequestMethodScenario) TestShouldRespondWithAppropriateMethodNotAllowedHeaders() {
testCases := []struct {
name string
method string
uri string
expected []string
}{
{"RootPathShouldShowAllowedMethodsOnInvalidRequest", fasthttp.MethodPost, AutheliaBaseURL, []string{fasthttp.MethodGet, fasthttp.MethodHead, fasthttp.MethodOptions}},
{"OpenAPISpecificationShouldShowAllowedMethodsOnInvalidRequest", fasthttp.MethodPost, fmt.Sprintf("%s/api/openapi.yml", AutheliaBaseURL), []string{fasthttp.MethodGet, fasthttp.MethodHead, fasthttp.MethodOptions}},
{"LocalesShouldShowAllowedMethodsOnInvalidRequest", fasthttp.MethodPost, fmt.Sprintf("%s/locales/en/portal.json", AutheliaBaseURL), []string{fasthttp.MethodGet, fasthttp.MethodHead, fasthttp.MethodOptions}},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
req, err := http.NewRequest(tc.method, tc.uri, nil)
s.Assert().NoError(err)
res, err := s.client.Do(req)
s.Assert().NoError(err)
s.Assert().Equal(fasthttp.StatusMethodNotAllowed, res.StatusCode)
s.Assert().Equal(strings.Join(tc.expected, ", "), res.Header.Get(fasthttp.HeaderAllow))
})
}
}
func (s *RequestMethodScenario) TestShouldRespondWithAppropriateResponseWithMethodHEAD() {
testCases := []struct {
name string
uri string
expectedStatus int
expectedContentLength bool
}{
{"RootPathShouldShowContentLengthAndRespondOK", AutheliaBaseURL, fasthttp.StatusOK, true},
{"OpenAPISpecShouldShowContentLengthAndRespondOK", fmt.Sprintf("%s/api/openapi.yml", AutheliaBaseURL), fasthttp.StatusOK, true},
{"LocalesShouldShowContentLengthAndRespondOK", fmt.Sprintf("%s/locales/en/portal.json", AutheliaBaseURL), fasthttp.StatusOK, true},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
req, err := http.NewRequest(fasthttp.MethodHead, tc.uri, nil)
s.Assert().NoError(err)
res, err := s.client.Do(req)
s.Assert().NoError(err)
s.Assert().Equal(tc.expectedStatus, res.StatusCode)
if tc.expectedContentLength {
s.Assert().NotEqual(0, res.ContentLength)
} else {
s.Assert().Equal(0, res.ContentLength)
}
data, err := io.ReadAll(res.Body)
s.Assert().NoError(err)
s.Assert().Len(data, 0)
})
}
}
func TestRunRequestMethod(t *testing.T) {
if testing.Short() {
t.Skip("skipping suite test in short mode")
}
suite.Run(t, NewRequestMethodScenario())
}

View File

@ -346,6 +346,10 @@ func (s *StandaloneSuite) TestResetPasswordScenario() {
suite.Run(s.T(), NewResetPasswordScenario()) suite.Run(s.T(), NewResetPasswordScenario())
} }
func (s *StandaloneSuite) TestRequestMethodScenario() {
suite.Run(s.T(), NewRequestMethodScenario())
}
func (s *StandaloneSuite) TestAvailableMethodsScenario() { func (s *StandaloneSuite) TestAvailableMethodsScenario() {
suite.Run(s.T(), NewAvailableMethodsScenario([]string{"TIME-BASED ONE-TIME PASSWORD", "SECURITY KEY - WEBAUTHN"})) suite.Run(s.T(), NewAvailableMethodsScenario([]string{"TIME-BASED ONE-TIME PASSWORD", "SECURITY KEY - WEBAUTHN"}))
} }

View File

@ -25,9 +25,9 @@
"@fortawesome/free-regular-svg-icons": "6.3.0", "@fortawesome/free-regular-svg-icons": "6.3.0",
"@fortawesome/free-solid-svg-icons": "6.3.0", "@fortawesome/free-solid-svg-icons": "6.3.0",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.0",
"@mui/icons-material": "5.11.9", "@mui/icons-material": "5.11.11",
"@mui/material": "5.11.10", "@mui/material": "5.11.11",
"@mui/styles": "5.11.9", "@mui/styles": "5.11.11",
"axios": "1.3.4", "axios": "1.3.4",
"broadcast-channel": "4.20.2", "broadcast-channel": "4.20.2",
"classnames": "2.3.2", "classnames": "2.3.2",
@ -39,7 +39,7 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-i18next": "12.2.0", "react-i18next": "12.2.0",
"react-loading": "2.0.3", "react-loading": "2.0.3",
"react-router-dom": "6.8.1", "react-router-dom": "6.8.2",
"react18-input-otp": "1.1.2", "react18-input-otp": "1.1.2",
"zxcvbn": "4.4.2" "zxcvbn": "4.4.2"
}, },
@ -148,17 +148,17 @@
"@testing-library/jest-dom": "5.16.5", "@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "14.0.0", "@testing-library/react": "14.0.0",
"@types/jest": "29.4.0", "@types/jest": "29.4.0",
"@types/node": "18.14.1", "@types/node": "18.14.2",
"@types/qrcode.react": "1.0.2", "@types/qrcode.react": "1.0.2",
"@types/react": "18.0.28", "@types/react": "18.0.28",
"@types/react-dom": "18.0.11", "@types/react-dom": "18.0.11",
"@types/zxcvbn": "4.4.1", "@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.53.0", "@typescript-eslint/eslint-plugin": "5.54.0",
"@typescript-eslint/parser": "5.53.0", "@typescript-eslint/parser": "5.54.0",
"@vitejs/plugin-react": "3.1.0", "@vitejs/plugin-react": "3.1.0",
"esbuild": "0.17.10", "esbuild": "0.17.10",
"esbuild-jest": "0.5.0", "esbuild-jest": "0.5.0",
"eslint": "8.34.0", "eslint": "8.35.0",
"eslint-config-prettier": "8.6.0", "eslint-config-prettier": "8.6.0",
"eslint-config-react-app": "7.0.1", "eslint-config-react-app": "7.0.1",
"eslint-formatter-rdjson": "1.0.5", "eslint-formatter-rdjson": "1.0.5",
@ -178,7 +178,7 @@
"typescript": "4.9.5", "typescript": "4.9.5",
"vite": "4.1.4", "vite": "4.1.4",
"vite-plugin-eslint": "1.8.1", "vite-plugin-eslint": "1.8.1",
"vite-plugin-istanbul": "4.0.0", "vite-plugin-istanbul": "4.0.1",
"vite-plugin-svgr": "2.4.0", "vite-plugin-svgr": "2.4.0",
"vite-tsconfig-paths": "4.0.5" "vite-tsconfig-paths": "4.0.5"
} }

File diff suppressed because it is too large Load Diff