2019-04-24 21:52:08 +00:00
package middlewares
import (
"encoding/json"
2023-01-25 09:36:40 +00:00
"errors"
2019-04-24 21:52:08 +00:00
"fmt"
"net"
2021-05-04 22:06:05 +00:00
"net/url"
2023-01-25 09:36:40 +00:00
"path"
2019-04-24 21:52:08 +00:00
"strings"
"github.com/asaskevich/govalidator"
2020-04-05 12:37:21 +00:00
"github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/configuration/schema"
2022-01-20 23:46:13 +00:00
"github.com/authelia/authelia/v4/internal/logging"
2022-03-28 01:26:30 +00:00
"github.com/authelia/authelia/v4/internal/model"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/session"
"github.com/authelia/authelia/v4/internal/utils"
2019-04-24 21:52:08 +00:00
)
2019-12-11 07:52:02 +00:00
// NewRequestLogger create a new request logger for the given request.
func NewRequestLogger ( ctx * AutheliaCtx ) * logrus . Entry {
2022-01-20 23:46:13 +00:00
return logging . Logger ( ) . WithFields ( logrus . Fields {
2019-12-11 07:52:02 +00:00
"method" : string ( ctx . Method ( ) ) ,
"path" : string ( ctx . Path ( ) ) ,
"remote_ip" : ctx . RemoteIP ( ) . String ( ) ,
} )
}
2019-04-24 21:52:08 +00:00
// NewAutheliaCtx instantiate an AutheliaCtx out of a RequestCtx.
2022-06-10 01:34:43 +00:00
func NewAutheliaCtx ( requestCTX * fasthttp . RequestCtx , configuration schema . Configuration , providers Providers ) ( ctx * AutheliaCtx ) {
ctx = new ( AutheliaCtx )
ctx . RequestCtx = requestCTX
ctx . Providers = providers
ctx . Configuration = configuration
ctx . Logger = NewRequestLogger ( ctx )
ctx . Clock = utils . RealClock { }
return ctx
2019-04-24 21:52:08 +00:00
}
2022-03-28 01:26:30 +00:00
// AvailableSecondFactorMethods returns the available 2FA methods.
func ( ctx * AutheliaCtx ) AvailableSecondFactorMethods ( ) ( methods [ ] string ) {
methods = make ( [ ] string , 0 , 3 )
if ! ctx . Configuration . TOTP . Disable {
methods = append ( methods , model . SecondFactorMethodTOTP )
}
if ! ctx . Configuration . Webauthn . Disable {
2023-04-10 07:01:23 +00:00
methods = append ( methods , model . SecondFactorMethodWebAuthn )
2022-03-28 01:26:30 +00:00
}
2022-04-15 23:34:26 +00:00
if ! ctx . Configuration . DuoAPI . Disable {
2022-03-28 01:26:30 +00:00
methods = append ( methods , model . SecondFactorMethodDuo )
}
return methods
}
2019-11-16 19:50:58 +00:00
// Error reply with an error and display the stack trace in the logs.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) Error ( err error , message string ) {
ctx . SetJSONError ( message )
2021-11-29 03:09:14 +00:00
2022-02-06 13:37:28 +00:00
ctx . Logger . Error ( err )
2021-11-29 03:09:14 +00:00
}
// SetJSONError sets the body of the response to an JSON error KO message.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) SetJSONError ( message string ) {
2022-07-08 12:18:52 +00:00
if replyErr := ctx . ReplyJSON ( ErrorResponse { Status : "KO" , Message : message } , 0 ) ; replyErr != nil {
ctx . Logger . Error ( replyErr )
2019-11-17 01:05:46 +00:00
}
2019-04-24 21:52:08 +00:00
}
2020-05-02 05:06:39 +00:00
// ReplyError reply with an error but does not display any stack trace in the logs.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) ReplyError ( err error , message string ) {
2019-11-17 01:05:46 +00:00
b , marshalErr := json . Marshal ( ErrorResponse { Status : "KO" , Message : message } )
if marshalErr != nil {
2022-02-06 13:37:28 +00:00
ctx . Logger . Error ( marshalErr )
2019-11-17 01:05:46 +00:00
}
2022-12-17 00:49:05 +00:00
ctx . SetContentTypeApplicationJSON ( )
2022-02-06 13:37:28 +00:00
ctx . SetBody ( b )
ctx . Logger . Debug ( err )
2019-11-16 19:50:58 +00:00
}
2022-07-08 12:18:52 +00:00
// ReplyStatusCode resets a response and replies with the given status code and relevant message.
func ( ctx * AutheliaCtx ) ReplyStatusCode ( statusCode int ) {
ctx . Response . Reset ( )
ctx . SetStatusCode ( statusCode )
2022-12-17 00:49:05 +00:00
ctx . SetContentTypeTextPlain ( )
2022-07-08 12:18:52 +00:00
ctx . SetBodyString ( fmt . Sprintf ( "%d %s" , statusCode , fasthttp . StatusMessage ( statusCode ) ) )
}
// ReplyJSON writes a JSON response.
2022-10-05 05:05:23 +00:00
func ( ctx * AutheliaCtx ) ReplyJSON ( data any , statusCode int ) ( err error ) {
2022-07-08 12:18:52 +00:00
var (
body [ ] byte
)
if body , err = json . Marshal ( data ) ; err != nil {
return fmt . Errorf ( "unable to marshal JSON body: %w" , err )
}
if statusCode > 0 {
ctx . SetStatusCode ( statusCode )
}
2022-12-17 00:49:05 +00:00
ctx . SetContentTypeApplicationJSON ( )
2022-07-08 12:18:52 +00:00
ctx . SetBody ( body )
return nil
}
2020-05-02 05:06:39 +00:00
// ReplyUnauthorized response sent when user is unauthorized.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) ReplyUnauthorized ( ) {
2022-07-08 12:18:52 +00:00
ctx . ReplyStatusCode ( fasthttp . StatusUnauthorized )
2019-04-24 21:52:08 +00:00
}
2020-05-02 05:06:39 +00:00
// ReplyForbidden response sent when access is forbidden to user.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) ReplyForbidden ( ) {
2022-07-08 12:18:52 +00:00
ctx . ReplyStatusCode ( fasthttp . StatusForbidden )
2019-04-24 21:52:08 +00:00
}
2021-05-04 22:06:05 +00:00
// ReplyBadRequest response sent when bad request has been sent.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) ReplyBadRequest ( ) {
2022-07-08 12:18:52 +00:00
ctx . ReplyStatusCode ( fasthttp . StatusBadRequest )
2021-05-04 22:06:05 +00:00
}
2023-01-25 09:36:40 +00:00
// XForwardedMethod returns the content of the X-Forwarded-Method header.
func ( ctx * AutheliaCtx ) XForwardedMethod ( ) ( method [ ] byte ) {
return ctx . Request . Header . PeekBytes ( headerXForwardedMethod )
}
// XForwardedProto returns the content of the X-Forwarded-Proto header.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) XForwardedProto ( ) ( proto [ ] byte ) {
2023-01-25 09:36:40 +00:00
proto = ctx . Request . Header . PeekBytes ( headerXForwardedProto )
2022-02-06 13:37:28 +00:00
if proto == nil {
2023-01-25 09:36:40 +00:00
if ctx . IsTLS ( ) {
2022-02-06 13:37:28 +00:00
return protoHTTPS
}
return protoHTTP
}
return proto
2019-04-24 21:52:08 +00:00
}
2023-01-25 09:36:40 +00:00
// XForwardedHost returns the content of the X-Forwarded-Host header.
func ( ctx * AutheliaCtx ) XForwardedHost ( ) ( host [ ] byte ) {
return ctx . Request . Header . PeekBytes ( headerXForwardedHost )
2021-03-05 04:18:31 +00:00
}
2023-01-25 09:36:40 +00:00
// GetXForwardedHost returns the content of the X-Forwarded-Host header falling back to the Host header.
func ( ctx * AutheliaCtx ) GetXForwardedHost ( ) ( host [ ] byte ) {
host = ctx . XForwardedHost ( )
2022-02-06 13:37:28 +00:00
if host == nil {
return ctx . RequestCtx . Host ( )
}
return host
2019-04-24 21:52:08 +00:00
}
2023-01-25 09:36:40 +00:00
// XForwardedURI returns the content of the X-Forwarded-Uri header.
func ( ctx * AutheliaCtx ) XForwardedURI ( ) ( host [ ] byte ) {
return ctx . Request . Header . PeekBytes ( headerXForwardedURI )
}
// GetXForwardedURI returns the content of the X-Forwarded-URI header, falling back to the start-line request path.
func ( ctx * AutheliaCtx ) GetXForwardedURI ( ) ( uri [ ] byte ) {
uri = ctx . XForwardedURI ( )
2022-02-06 13:37:28 +00:00
if len ( uri ) == 0 {
2023-01-25 09:36:40 +00:00
return ctx . RequestURI ( )
2022-02-06 13:37:28 +00:00
}
return uri
2019-04-24 21:52:08 +00:00
}
2023-01-25 09:36:40 +00:00
// XOriginalMethod returns the content of the X-Original-Method header.
func ( ctx * AutheliaCtx ) XOriginalMethod ( ) [ ] byte {
return ctx . Request . Header . PeekBytes ( headerXOriginalMethod )
2022-12-17 00:49:05 +00:00
}
2023-01-25 09:36:40 +00:00
// XOriginalURL returns the content of the X-Original-URL header.
func ( ctx * AutheliaCtx ) XOriginalURL ( ) [ ] byte {
return ctx . Request . Header . PeekBytes ( headerXOriginalURL )
2022-12-17 00:49:05 +00:00
}
2023-01-25 09:36:40 +00:00
// XAutheliaURL returns the content of the X-Authelia-URL header which is used to communicate the location of the
2022-12-17 00:49:05 +00:00
// portal when using proxies like Envoy.
func ( ctx * AutheliaCtx ) XAutheliaURL ( ) [ ] byte {
2023-01-25 09:36:40 +00:00
return ctx . Request . Header . PeekBytes ( headerXAutheliaURL )
2022-10-01 11:47:09 +00:00
}
2023-01-25 09:36:40 +00:00
// QueryArgRedirect returns the content of the 'rd' query argument.
2022-12-17 00:49:05 +00:00
func ( ctx * AutheliaCtx ) QueryArgRedirect ( ) [ ] byte {
2023-01-25 09:36:40 +00:00
return ctx . QueryArgs ( ) . PeekBytes ( qryArgRedirect )
}
// QueryArgAutheliaURL returns the content of the 'authelia_url' query argument.
func ( ctx * AutheliaCtx ) QueryArgAutheliaURL ( ) [ ] byte {
return ctx . QueryArgs ( ) . PeekBytes ( qryArgAutheliaURL )
}
// AuthzPath returns the 'authz_path' value.
func ( ctx * AutheliaCtx ) AuthzPath ( ) ( uri [ ] byte ) {
if uv := ctx . UserValueBytes ( keyUserValueAuthzPath ) ; uv != nil {
return [ ] byte ( uv . ( string ) )
}
return nil
2022-10-01 11:47:09 +00:00
}
2021-08-10 00:31:08 +00:00
// BasePath returns the base_url as per the path visited by the client.
2022-12-17 00:49:05 +00:00
func ( ctx * AutheliaCtx ) BasePath ( ) string {
2023-01-25 09:36:40 +00:00
if baseURL := ctx . UserValueBytes ( keyUserValueBaseURL ) ; baseURL != nil {
2021-08-10 00:31:08 +00:00
return baseURL . ( string )
}
2022-12-17 00:49:05 +00:00
return ""
2021-08-10 00:31:08 +00:00
}
2021-05-04 22:06:05 +00:00
2022-12-17 00:49:05 +00:00
// BasePathSlash is the same as BasePath but returns a final slash as well.
func ( ctx * AutheliaCtx ) BasePathSlash ( ) string {
2023-01-25 09:36:40 +00:00
if baseURL := ctx . UserValueBytes ( keyUserValueBaseURL ) ; baseURL != nil {
2022-12-17 00:49:05 +00:00
return baseURL . ( string ) + strSlash
2021-08-10 00:31:08 +00:00
}
2022-12-17 00:49:05 +00:00
return strSlash
2021-05-04 22:06:05 +00:00
}
2022-12-17 00:49:05 +00:00
// RootURL returns the Root URL.
func ( ctx * AutheliaCtx ) RootURL ( ) ( issuerURL * url . URL ) {
return & url . URL {
Scheme : string ( ctx . XForwardedProto ( ) ) ,
2023-01-25 09:36:40 +00:00
Host : string ( ctx . GetXForwardedHost ( ) ) ,
2022-12-17 00:49:05 +00:00
Path : ctx . BasePath ( ) ,
2022-10-20 02:16:36 +00:00
}
}
2022-12-17 00:49:05 +00:00
// 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 ( ) ) ,
2023-01-25 09:36:40 +00:00
Host : string ( ctx . GetXForwardedHost ( ) ) ,
2022-12-17 00:49:05 +00:00
Path : ctx . BasePathSlash ( ) ,
}
2019-04-24 21:52:08 +00:00
}
2023-01-12 10:57:44 +00:00
// GetTargetURICookieDomain returns the session provider for the targetURI domain.
func ( ctx * AutheliaCtx ) GetTargetURICookieDomain ( targetURI * url . URL ) string {
2023-01-25 09:36:40 +00:00
if targetURI == nil {
return ""
}
2023-01-12 10:57:44 +00:00
hostname := targetURI . Hostname ( )
for _ , domain := range ctx . Configuration . Session . Cookies {
if utils . HasDomainSuffix ( hostname , domain . Domain ) {
return domain . Domain
}
}
return ""
}
// IsSafeRedirectionTargetURI returns true if the targetURI is within the scope of a cookie domain and secure.
func ( ctx * AutheliaCtx ) IsSafeRedirectionTargetURI ( targetURI * url . URL ) bool {
if ! utils . IsURISecure ( targetURI ) {
return false
}
return ctx . GetTargetURICookieDomain ( targetURI ) != ""
}
// GetCookieDomain returns the cookie domain for the current request.
func ( ctx * AutheliaCtx ) GetCookieDomain ( ) ( domain string , err error ) {
var targetURI * url . URL
2023-01-25 09:36:40 +00:00
if targetURI , err = ctx . GetXOriginalURLOrXForwardedURL ( ) ; err != nil {
2023-01-12 10:57:44 +00:00
return "" , fmt . Errorf ( "unable to retrieve cookie domain: %s" , err )
}
return ctx . GetTargetURICookieDomain ( targetURI ) , nil
}
2023-01-25 09:36:40 +00:00
// GetSessionProviderByTargetURL returns the session provider for the Request's domain.
func ( ctx * AutheliaCtx ) GetSessionProviderByTargetURL ( targetURL * url . URL ) ( provider * session . Session , err error ) {
domain := ctx . GetTargetURICookieDomain ( targetURL )
if domain == "" {
return nil , fmt . Errorf ( "unable to retrieve domain session: %w" , err )
}
return ctx . Providers . SessionProvider . Get ( domain )
}
2023-01-12 10:57:44 +00:00
// GetSessionProvider returns the session provider for the Request's domain.
func ( ctx * AutheliaCtx ) GetSessionProvider ( ) ( provider * session . Session , err error ) {
2023-01-25 09:36:40 +00:00
if ctx . session == nil {
var domain string
if domain , err = ctx . GetCookieDomain ( ) ; err != nil {
return nil , err
}
2023-01-12 10:57:44 +00:00
2023-01-25 09:36:40 +00:00
if ctx . session , err = ctx . GetCookieDomainSessionProvider ( domain ) ; err != nil {
return nil , err
}
2023-01-12 10:57:44 +00:00
}
2023-01-25 09:36:40 +00:00
return ctx . session , nil
}
// GetCookieDomainSessionProvider returns the session provider for the provided domain.
func ( ctx * AutheliaCtx ) GetCookieDomainSessionProvider ( domain string ) ( provider * session . Session , err error ) {
if domain == "" {
return nil , fmt . Errorf ( "unable to retrieve domain session: %w" , err )
2023-01-12 10:57:44 +00:00
}
2023-01-25 09:36:40 +00:00
return ctx . Providers . SessionProvider . Get ( domain )
2023-01-12 10:57:44 +00:00
}
2023-01-25 09:36:40 +00:00
// GetSession returns the user session provided the cookie provider could be discovered. It is recommended to get the
// provider itself if you also need to update or destroy sessions.
func ( ctx * AutheliaCtx ) GetSession ( ) ( userSession session . UserSession , err error ) {
var provider * session . Session
if provider , err = ctx . GetSessionProvider ( ) ; err != nil {
return userSession , err
2023-01-12 10:57:44 +00:00
}
2023-01-25 09:36:40 +00:00
if userSession , err = provider . GetSession ( ctx . RequestCtx ) ; err != nil {
2022-02-06 13:37:28 +00:00
ctx . Logger . Error ( "Unable to retrieve user session" )
2023-01-25 09:36:40 +00:00
return provider . NewDefaultUserSession ( ) , nil
2020-01-17 22:48:48 +00:00
}
2020-05-05 19:35:32 +00:00
2023-01-25 09:36:40 +00:00
if userSession . CookieDomain != provider . Config . Domain {
ctx . Logger . Warnf ( "Destroying session cookie as the cookie domain '%s' does not match the requests detected cookie domain '%s' which may be a sign a user tried to move this cookie from one domain to another" , userSession . CookieDomain , provider . Config . Domain )
if err = provider . DestroySession ( ctx . RequestCtx ) ; err != nil {
ctx . Logger . WithError ( err ) . Error ( "Error occurred trying to destroy the session cookie" )
}
userSession = provider . NewDefaultUserSession ( )
if err = provider . SaveSession ( ctx . RequestCtx , userSession ) ; err != nil {
ctx . Logger . WithError ( err ) . Error ( "Error occurred trying to save the new session cookie" )
}
}
return userSession , nil
2019-04-24 21:52:08 +00:00
}
2023-01-25 09:36:40 +00:00
// SaveSession saves the content of the session.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) SaveSession ( userSession session . UserSession ) error {
2023-01-12 10:57:44 +00:00
provider , err := ctx . GetSessionProvider ( )
if err != nil {
return fmt . Errorf ( "unable to save user session: %s" , err )
}
return provider . SaveSession ( ctx . RequestCtx , userSession )
}
2023-01-25 09:36:40 +00:00
// RegenerateSession regenerates a user session.
2023-01-12 10:57:44 +00:00
func ( ctx * AutheliaCtx ) RegenerateSession ( ) error {
provider , err := ctx . GetSessionProvider ( )
if err != nil {
return fmt . Errorf ( "unable to regenerate user session: %s" , err )
}
return provider . RegenerateSession ( ctx . RequestCtx )
}
2023-01-25 09:36:40 +00:00
// DestroySession destroys a user session.
2023-01-12 10:57:44 +00:00
func ( ctx * AutheliaCtx ) DestroySession ( ) error {
provider , err := ctx . GetSessionProvider ( )
if err != nil {
return fmt . Errorf ( "unable to destroy user session: %s" , err )
}
return provider . DestroySession ( ctx . RequestCtx )
2019-04-24 21:52:08 +00:00
}
2020-05-02 05:06:39 +00:00
// ReplyOK is a helper method to reply ok.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) ReplyOK ( ) {
2022-12-17 00:49:05 +00:00
ctx . SetContentTypeApplicationJSON ( )
2022-02-06 13:37:28 +00:00
ctx . SetBody ( okMessageBytes )
2019-04-24 21:52:08 +00:00
}
2020-05-02 05:06:39 +00:00
// ParseBody parse the request body into the type of value.
2022-10-05 05:05:23 +00:00
func ( ctx * AutheliaCtx ) ParseBody ( value any ) error {
2022-02-06 13:37:28 +00:00
err := json . Unmarshal ( ctx . PostBody ( ) , & value )
2019-04-24 21:52:08 +00:00
if err != nil {
2021-11-29 03:09:14 +00:00
return fmt . Errorf ( "unable to parse body: %w" , err )
2019-04-24 21:52:08 +00:00
}
valid , err := govalidator . ValidateStruct ( value )
if err != nil {
2021-11-29 03:09:14 +00:00
return fmt . Errorf ( "unable to validate body: %w" , err )
2019-04-24 21:52:08 +00:00
}
if ! valid {
return fmt . Errorf ( "Body is not valid" )
}
2020-05-05 19:35:32 +00:00
2019-04-24 21:52:08 +00:00
return nil
}
2023-01-25 09:36:40 +00:00
// SetContentTypeApplicationJSON sets the Content-Type header to 'application/json; charset=utf-8'.
func ( ctx * AutheliaCtx ) SetContentTypeApplicationJSON ( ) {
ctx . SetContentTypeBytes ( contentTypeApplicationJSON )
}
// 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 )
}
// SetContentTypeApplicationYAML efficiently sets the Content-Type header to 'application/yaml; charset=utf-8'.
func ( ctx * AutheliaCtx ) SetContentTypeApplicationYAML ( ) {
ctx . SetContentTypeBytes ( contentTypeApplicationYAML )
}
// SetContentSecurityPolicy sets the Content-Security-Policy header.
func ( ctx * AutheliaCtx ) SetContentSecurityPolicy ( value string ) {
ctx . Response . Header . SetBytesK ( headerContentSecurityPolicy , value )
}
// SetContentSecurityPolicyBytes sets the Content-Security-Policy header.
func ( ctx * AutheliaCtx ) SetContentSecurityPolicyBytes ( value [ ] byte ) {
ctx . Response . Header . SetBytesKV ( headerContentSecurityPolicy , value )
}
2020-05-02 05:06:39 +00:00
// SetJSONBody Set json body.
2022-10-05 05:05:23 +00:00
func ( ctx * AutheliaCtx ) SetJSONBody ( value any ) error {
2022-07-08 12:18:52 +00:00
return ctx . ReplyJSON ( OKResponse { Status : "OK" , Data : value } , 0 )
2019-04-24 21:52:08 +00:00
}
// RemoteIP return the remote IP taking X-Forwarded-For header into account if provided.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) RemoteIP ( ) net . IP {
XForwardedFor := ctx . Request . Header . PeekBytes ( headerXForwardedFor )
2019-04-24 21:52:08 +00:00
if XForwardedFor != nil {
ips := strings . Split ( string ( XForwardedFor ) , "," )
if len ( ips ) > 0 {
2019-12-11 07:29:32 +00:00
return net . ParseIP ( strings . Trim ( ips [ 0 ] , " " ) )
2019-04-24 21:52:08 +00:00
}
}
2020-05-05 19:35:32 +00:00
2022-02-06 13:37:28 +00:00
return ctx . RequestCtx . RemoteIP ( )
2019-04-24 21:52:08 +00:00
}
2021-05-04 22:06:05 +00:00
2023-01-25 09:36:40 +00:00
// GetXForwardedURL returns the parsed X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-URI request header as a
// *url.URL.
func ( ctx * AutheliaCtx ) GetXForwardedURL ( ) ( requestURI * url . URL , err error ) {
forwardedProto , forwardedHost , forwardedURI := ctx . XForwardedProto ( ) , ctx . GetXForwardedHost ( ) , ctx . GetXForwardedURI ( )
2021-05-04 22:06:05 +00:00
2023-01-25 09:36:40 +00:00
if forwardedProto == nil {
return nil , ErrMissingXForwardedProto
}
2021-05-04 22:06:05 +00:00
2023-01-25 09:36:40 +00:00
if forwardedHost == nil {
return nil , ErrMissingXForwardedHost
2021-05-04 22:06:05 +00:00
}
2023-01-25 09:36:40 +00:00
value := utils . BytesJoin ( forwardedProto , protoHostSeparator , forwardedHost , forwardedURI )
2021-05-04 22:06:05 +00:00
2023-01-25 09:36:40 +00:00
if requestURI , err = url . ParseRequestURI ( string ( value ) ) ; err != nil {
return nil , fmt . Errorf ( "failed to parse X-Forwarded Headers: %w" , err )
2021-05-04 22:06:05 +00:00
}
2023-01-25 09:36:40 +00:00
return requestURI , nil
}
// GetXOriginalURL returns the parsed X-OriginalURL request header as a *url.URL.
func ( ctx * AutheliaCtx ) GetXOriginalURL ( ) ( requestURI * url . URL , err error ) {
value := ctx . XOriginalURL ( )
if value == nil {
return nil , ErrMissingXOriginalURL
2021-05-04 22:06:05 +00:00
}
2023-01-25 09:36:40 +00:00
if requestURI , err = url . ParseRequestURI ( string ( value ) ) ; err != nil {
return nil , fmt . Errorf ( "failed to parse X-Original-URL header: %w" , err )
}
2021-05-04 22:06:05 +00:00
2023-01-25 09:36:40 +00:00
return requestURI , nil
}
2021-05-04 22:06:05 +00:00
2023-01-25 09:36:40 +00:00
// GetXOriginalURLOrXForwardedURL returns the parsed X-Original-URL request header if it's available or the parsed
// X-Forwarded request headers if not.
func ( ctx * AutheliaCtx ) GetXOriginalURLOrXForwardedURL ( ) ( requestURI * url . URL , err error ) {
requestURI , err = ctx . GetXOriginalURL ( )
switch {
case err == nil :
return requestURI , nil
case errors . Is ( err , ErrMissingXOriginalURL ) :
return ctx . GetXForwardedURL ( )
default :
return requestURI , err
2021-05-04 22:06:05 +00:00
}
2023-01-25 09:36:40 +00:00
}
2021-05-04 22:06:05 +00:00
2023-01-25 09:36:40 +00:00
// IssuerURL returns the expected Issuer.
func ( ctx * AutheliaCtx ) IssuerURL ( ) ( issuerURL * url . URL , err error ) {
issuerURL = & url . URL {
Scheme : strProtoHTTPS ,
}
2021-05-04 22:06:05 +00:00
2023-01-25 09:36:40 +00:00
if scheme := ctx . XForwardedProto ( ) ; scheme != nil {
issuerURL . Scheme = string ( scheme )
}
if host := ctx . GetXForwardedHost ( ) ; len ( host ) != 0 {
issuerURL . Host = string ( host )
} else {
return nil , ErrMissingXForwardedHost
}
if base := ctx . BasePath ( ) ; base != "" {
issuerURL . Path = path . Join ( issuerURL . Path , base )
}
return issuerURL , nil
2021-05-04 22:06:05 +00:00
}
2021-07-22 03:52:37 +00:00
// IsXHR returns true if the request is a XMLHttpRequest.
2022-09-03 01:51:02 +00:00
func ( ctx * AutheliaCtx ) IsXHR ( ) ( xhr bool ) {
2023-01-25 09:36:40 +00:00
if requestedWith := ctx . Request . Header . PeekBytes ( headerXRequestedWith ) ; requestedWith != nil && strings . EqualFold ( string ( requestedWith ) , headerValueXRequestedWithXHR ) {
return true
}
2021-07-22 03:52:37 +00:00
2023-01-25 09:36:40 +00:00
return false
2021-07-22 03:52:37 +00:00
}
// AcceptsMIME takes a mime type and returns true if the request accepts that type or the wildcard type.
2022-09-03 01:51:02 +00:00
func ( ctx * AutheliaCtx ) AcceptsMIME ( mime string ) ( acceptsMime bool ) {
2022-02-06 13:37:28 +00:00
accepts := strings . Split ( string ( ctx . Request . Header . PeekBytes ( headerAccept ) ) , "," )
2021-07-22 03:52:37 +00:00
for i , accept := range accepts {
mimeType := strings . Trim ( strings . SplitN ( accept , ";" , 2 ) [ 0 ] , " " )
if mimeType == mime || ( i == 0 && mimeType == "*/*" ) {
return true
}
}
return false
}
// SpecialRedirect performs a redirect similar to fasthttp.RequestCtx except it allows statusCode 401 and includes body
2023-04-08 04:48:55 +00:00
// content in the form of a link to the location if the request method was not head.
2022-02-06 13:37:28 +00:00
func ( ctx * AutheliaCtx ) SpecialRedirect ( uri string , statusCode int ) {
2023-04-08 04:48:55 +00:00
var u [ ] byte
u , statusCode = ctx . setSpecialRedirect ( uri , statusCode )
ctx . SetContentTypeTextHTML ( )
ctx . SetBodyString ( fmt . Sprintf ( "<a href=\"%s\">%d %s</a>" , utils . StringHTMLEscape ( string ( u ) ) , statusCode , fasthttp . StatusMessage ( statusCode ) ) )
}
// SpecialRedirectNoBody performs a redirect similar to fasthttp.RequestCtx except it allows statusCode 401 and includes
// no body.
func ( ctx * AutheliaCtx ) SpecialRedirectNoBody ( uri string , statusCode int ) {
_ , _ = ctx . setSpecialRedirect ( uri , statusCode )
}
func ( ctx * AutheliaCtx ) setSpecialRedirect ( uri string , statusCode int ) ( [ ] byte , int ) {
2021-07-22 03:52:37 +00:00
if statusCode < fasthttp . StatusMovedPermanently || ( statusCode > fasthttp . StatusSeeOther && statusCode != fasthttp . StatusTemporaryRedirect && statusCode != fasthttp . StatusPermanentRedirect && statusCode != fasthttp . StatusUnauthorized ) {
statusCode = fasthttp . StatusFound
}
2022-02-06 13:37:28 +00:00
ctx . SetStatusCode ( statusCode )
2021-07-22 03:52:37 +00:00
u := fasthttp . AcquireURI ( )
2022-02-06 13:37:28 +00:00
ctx . URI ( ) . CopyTo ( u )
2021-07-22 03:52:37 +00:00
u . Update ( uri )
2023-04-08 04:48:55 +00:00
raw := u . FullURI ( )
2021-07-22 03:52:37 +00:00
2023-04-08 04:48:55 +00:00
ctx . Response . Header . SetBytesKV ( headerLocation , raw )
2021-07-22 03:52:37 +00:00
fasthttp . ReleaseURI ( u )
2023-04-08 04:48:55 +00:00
return raw , statusCode
2021-07-22 03:52:37 +00:00
}
2022-06-14 07:20:13 +00:00
2023-01-25 09:36:40 +00:00
// RecordAuthn records authentication metrics.
func ( ctx * AutheliaCtx ) RecordAuthn ( success , regulated bool , method string ) {
2022-06-14 07:20:13 +00:00
if ctx . Providers . Metrics == nil {
return
}
2023-01-25 09:36:40 +00:00
ctx . Providers . Metrics . RecordAuthn ( success , regulated , method )
2023-01-03 03:49:02 +00:00
}