diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2023-10-30 05:43:25 +1100 | 
|---|---|---|
| committer | James Elliott <james-d-elliott@users.noreply.github.com> | 2024-03-04 20:29:11 +1100 | 
| commit | 85562a2465218273161cf9240ffebfe2ba6b187f (patch) | |
| tree | a4d1ad1f62a5536056e2d169e9e896eedc071b35 | |
| parent | b5f63fde3b1cef156de4ca968939c6dafd1f88fb (diff) | |
refactor: misc fixes
This implements misc fixes as part of one of our betas.
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
28 files changed, 177 insertions, 173 deletions
diff --git a/api/openapi.yml b/api/openapi.yml index a721656b2..5a2c148dd 100644 --- a/api/openapi.yml +++ b/api/openapi.yml @@ -95,7 +95,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'    /api/state:      get:        tags: @@ -534,7 +534,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'        security:          - authelia_auth: []    /api/reset-password/identity/finish: @@ -560,7 +560,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'        security:          - authelia_auth: []    /api/reset-password: @@ -585,7 +585,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'        security:          - authelia_auth: []      delete: @@ -607,7 +607,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.API'        security:          - authelia_auth: []    {{- end }} @@ -665,7 +665,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'          "403":            description: Forbidden        security: @@ -707,7 +707,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'          "403":            description: Forbidden        security: @@ -752,9 +752,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' -        "403": -          description: Forbidden +                $ref: '#/components/schemas/middlewares.Response.API'        security:          - authelia_auth: []    {{- if .TOTP }} @@ -822,7 +820,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'          "403":            description: Forbidden        security: @@ -840,7 +838,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'          "403":            description: Forbidden        security: @@ -888,7 +886,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.ErrorResponse' +                $ref: '#/components/schemas/middlewares.Response.KO'        security:          - authelia_auth: []      delete: @@ -903,7 +901,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'          "403":            description: Forbidden        security: @@ -998,7 +996,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'          "401":            description: Unauthorized        security: @@ -1016,7 +1014,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'          "403":            description: Forbidden        security: @@ -1043,7 +1041,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'        security:          - authelia_auth: []      delete: @@ -1061,7 +1059,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'        security:          - authelia_auth: []    {{- end }} @@ -1124,7 +1122,7 @@ paths:            content:              application/json:                schema: -                $ref: '#/components/schemas/middlewares.OkResponse' +                $ref: '#/components/schemas/middlewares.Response.OK'          "401":            description: Unauthorized        security: @@ -2151,14 +2149,32 @@ components:          token:            type: string            example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDc5MjU1OTYsImlzcyI6IkF1dGhlbGlhIiwiYWN0aW9uIjoiUmVzZXRQYXNzd29yZCIsInVzZXJuYW1lIjoiQW1pciJ9.636yqRrUCGCe4jsMCsonleX5CYWHncYqZum-YYb6VaY -    middlewares.OkResponse: +    middlewares.Response.API: +      oneOf: +        - $ref: '#/components/schemas/middlewares.Response.OK' +        - $ref: '#/components/schemas/middlewares.Response.KO' +    middlewares.Response.OK:        type: object        properties:          status: +          enum: +            - 'OK'            type: string -          example: OK +          example: 'OK'          data:            type: object +          description: 'The data content for the response.' +    middlewares.Response.KO: +      type: object +      properties: +        status: +          enum: +            - 'KO' +          type: string +          example: 'KO' +        message: +          type: string +          example: 'Operation Failed.'      handlers.UserInfo:        type: object        properties: diff --git a/cmd/authelia-gen/const.go b/cmd/authelia-gen/const.go index afbc0b2bc..202709c3a 100644 --- a/cmd/authelia-gen/const.go +++ b/cmd/authelia-gen/const.go @@ -61,14 +61,6 @@ const (  )  const ( -	extJSON = ".json" -) - -const ( -	pathJSONSchema = "json-schema" -) - -const (  	pkgConfigSchema = "schema"  	pkgScriptsGen   = "cmd"  ) diff --git a/docs/content/en/configuration/identity-providers/openid-connect/clients.md b/docs/content/en/configuration/identity-providers/openid-connect/clients.md index 5590e930e..55ec1e74b 100644 --- a/docs/content/en/configuration/identity-providers/openid-connect/clients.md +++ b/docs/content/en/configuration/identity-providers/openid-connect/clients.md @@ -523,7 +523,7 @@ calculated in the [issuer_private_keys].  ### request_object_signing_alg -{{< confkey type="string" default="RSA256" required="no" >}} +{{< confkey type="string" default="RS256" required="no" >}}  The JWT signing algorithm accepted for request objects. diff --git a/docs/content/en/configuration/identity-validation/_index.md b/docs/content/en/configuration/identity-validation/_index.md index 52534e408..67ccb9430 100644 --- a/docs/content/en/configuration/identity-validation/_index.md +++ b/docs/content/en/configuration/identity-validation/_index.md @@ -2,7 +2,7 @@  title: "Identity Validation"  description: "Identity Validation Configuration"  lead: "" -date: 2023-10-28T18:57:18+11:00 +date: 2023-11-20T21:28:38+11:00  draft: false  images: []  weight: 105000 diff --git a/docs/content/en/configuration/identity-validation/elevated-session.md b/docs/content/en/configuration/identity-validation/elevated-session.md index e77c1ff1a..cc00805f8 100644 --- a/docs/content/en/configuration/identity-validation/elevated-session.md +++ b/docs/content/en/configuration/identity-validation/elevated-session.md @@ -2,7 +2,7 @@  title: "Elevated Session"  description: "Elevated Session Identity Validation Configuration"  lead: "Authelia uses multiple methods to verify the identity of users to prevent a malicious user from performing actions on behalf of them. This section describes the Elevated Session method." -date: 2023-10-28T18:57:18+11:00 +date: 2023-11-20T21:28:38+11:00  draft: false  images: []  menu: diff --git a/docs/content/en/configuration/identity-validation/introduction.md b/docs/content/en/configuration/identity-validation/introduction.md index 8298366ac..d2d73a780 100644 --- a/docs/content/en/configuration/identity-validation/introduction.md +++ b/docs/content/en/configuration/identity-validation/introduction.md @@ -2,7 +2,7 @@  title: "Identity Validation"  description: "Identity Validation Configuration"  lead: "Authelia uses multiple methods to verify the identity of users to prevent a malicious user from performing actions on behalf of them. This section describes these methods." -date: 2023-10-28T18:57:18+11:00 +date: 2023-11-20T21:28:38+11:00  draft: false  images: []  menu: diff --git a/docs/content/en/configuration/identity-validation/reset-password.md b/docs/content/en/configuration/identity-validation/reset-password.md index d20b91e66..a689289f1 100644 --- a/docs/content/en/configuration/identity-validation/reset-password.md +++ b/docs/content/en/configuration/identity-validation/reset-password.md @@ -2,7 +2,7 @@  title: "Reset Password"  description: "Reset Password Identity Validation Configuration"  lead: "Authelia uses multiple methods to verify the identity of users to prevent a malicious user from performing actions on behalf of them. This section describes Reset Password method." -date: 2023-10-28T18:57:18+11:00 +date: 2023-11-20T21:28:38+11:00  draft: false  images: []  menu: @@ -16,7 +16,7 @@ require (  	github.com/go-ldap/ldap/v3 v3.4.6  	github.com/go-rod/rod v0.114.7  	github.com/go-sql-driver/mysql v1.7.1 -	github.com/go-webauthn/webauthn v0.8.6 +	github.com/go-webauthn/webauthn v0.9.1  	github.com/golang-jwt/jwt/v5 v5.2.0  	github.com/golang/mock v1.6.0  	github.com/google/uuid v1.6.0 @@ -70,7 +70,7 @@ require (  	github.com/dustin/go-humanize v1.0.0 // indirect  	github.com/ecordell/optgen v0.0.6 // indirect  	github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect -	github.com/fxamacker/cbor/v2 v2.4.0 // indirect +	github.com/fxamacker/cbor/v2 v2.5.0 // indirect  	github.com/go-crypt/x v0.2.12 // indirect  	github.com/go-redis/redis/v8 v8.11.5 // indirect  	github.com/go-webauthn/x v0.1.4 // indirect @@ -113,8 +113,8 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3  github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=  github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=  github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=  github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=  github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=  github.com/go-crypt/crypt v0.2.18 h1:j3YYrcHTpF4lLCwRW8//d91Wi7XSFqgsOYw0SHxljEg= @@ -133,8 +133,8 @@ github.com/go-rod/rod v0.114.7/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684  github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=  github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=  github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-webauthn/webauthn v0.8.6 h1:bKMtL1qzd2WTFkf1mFTVbreYrwn7dsYmEPjTq6QN90E= -github.com/go-webauthn/webauthn v0.8.6/go.mod h1:emwVLMCI5yx9evTTvr0r+aOZCdWJqMfbRhF0MufyUog= +github.com/go-webauthn/webauthn v0.9.1 h1:KuZjvUX9JTuFjB2n7kZhM6n76BClLUFbFM8SLKnrXpo= +github.com/go-webauthn/webauthn v0.9.1/go.mod h1:m315kRGbUljOytw8b9FGWG9QzErjI5v02pNFCF3lwpI=  github.com/go-webauthn/x v0.1.4 h1:sGmIFhcY70l6k7JIDfnjVBiAAFEssga5lXIUXe0GtAs=  github.com/go-webauthn/x v0.1.4/go.mod h1:75Ug0oK6KYpANh5hDOanfDI+dvPWHk788naJVG/37H8=  github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go index f35cc2dbb..864eb55e4 100644 --- a/internal/configuration/provider_test.go +++ b/internal/configuration/provider_test.go @@ -89,7 +89,7 @@ func TestShouldHaveNotifier(t *testing.T) {  func TestShouldConfigureRefreshIntervalDisable(t *testing.T) {  	testSetEnv(t, "SESSION_SECRET", "abc")  	testSetEnv(t, "STORAGE_MYSQL_PASSWORD", "abc") -	testSetEnv(t, "JWT_SECRET", "abc") +	testSetEnv(t, "IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET", "abc")  	testSetEnv(t, "AUTHENTICATION_BACKEND_LDAP_PASSWORD", "abc")  	val := schema.NewStructValidator() diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go index 1e5c5a805..b3e276488 100644 --- a/internal/configuration/validator/identity_providers_test.go +++ b/internal/configuration/validator/identity_providers_test.go @@ -2934,7 +2934,7 @@ func TestValidateOIDCClientJWKS(t *testing.T) {  			nil,  		},  		{ -			"ShouldOnlyAllowRequetsObjectSigningAlgsThatTheClientHasKeysFor", +			"ShouldOnlyAllowRequestObjectSigningAlgsThatTheClientHasKeysFor",  			nil,  			[]schema.JWK{  				{KeyID: "test", Use: "", Algorithm: "", Key: keyECDSAP521.Public()}, diff --git a/internal/handlers/const.go b/internal/handlers/const.go index ad12c1781..efc4675c9 100644 --- a/internal/handlers/const.go +++ b/internal/handlers/const.go @@ -74,13 +74,13 @@ const (  )  const ( -	logFmtErrParseRequestBody     = "Failed to parse %s request body: %+v" -	logFmtErrWriteResponseBody    = "Failed to write %s response body for user '%s': %+v" -	logFmtErrRegulationFail       = "Failed to perform %s authentication regulation for user '%s': %+v" -	logFmtErrSessionRegenerate    = "Could not regenerate session during %s authentication for user '%s': %+v" -	logFmtErrSessionReset         = "Could not reset session during %s authentication for user '%s': %+v" -	logFmtErrSessionSave          = "Could not save session with the %s during %s authentication for user '%s': %+v" -	logFmtErrObtainProfileDetails = "Could not obtain profile details during %s authentication for user '%s': %+v" +	logFmtErrParseRequestBody     = "Failed to parse %s request body" +	logFmtErrWriteResponseBody    = "Failed to write %s response body for user '%s'" +	logFmtErrRegulationFail       = "Failed to perform %s authentication regulation for user '%s'" +	logFmtErrSessionRegenerate    = "Could not regenerate session during %s authentication for user '%s'" +	logFmtErrSessionReset         = "Could not reset session during %s authentication for user '%s'" +	logFmtErrSessionSave          = "Could not save session with the %s during %s authentication for user '%s'" +	logFmtErrObtainProfileDetails = "Could not obtain profile details during %s authentication for user '%s'"  	logFmtTraceProfileDetails     = "Profile details for user '%s' => groups: %s, emails %s"  ) diff --git a/internal/handlers/handler_firstfactor.go b/internal/handlers/handler_firstfactor.go index d90a1172f..5bcf2dd21 100644 --- a/internal/handlers/handler_firstfactor.go +++ b/internal/handlers/handler_firstfactor.go @@ -24,7 +24,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re  		bodyJSON := bodyFirstFactorRequest{}  		if err := ctx.ParseBody(&bodyJSON); err != nil { -			ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthType1FA, err) +			ctx.Logger.WithError(err).Errorf(logFmtErrParseRequestBody, regulation.AuthType1FA)  			respondUnauthorized(ctx, messageAuthenticationFailed) @@ -40,7 +40,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re  				return  			} -			ctx.Logger.Errorf(logFmtErrRegulationFail, regulation.AuthType1FA, bodyJSON.Username, err) +			ctx.Logger.WithError(err).Errorf(logFmtErrRegulationFail, regulation.AuthType1FA, bodyJSON.Username)  			respondUnauthorized(ctx, messageAuthenticationFailed) @@ -92,7 +92,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re  		// Reset all values from previous session except OIDC workflow before regenerating the cookie.  		if err = ctx.SaveSession(newSession); err != nil { -			ctx.Logger.Errorf(logFmtErrSessionReset, regulation.AuthType1FA, bodyJSON.Username, err) +			ctx.Logger.WithError(err).Errorf(logFmtErrSessionReset, regulation.AuthType1FA, bodyJSON.Username)  			respondUnauthorized(ctx, messageAuthenticationFailed) @@ -100,7 +100,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re  		}  		if err = ctx.RegenerateSession(); err != nil { -			ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthType1FA, bodyJSON.Username, err) +			ctx.Logger.WithError(err).Errorf(logFmtErrSessionRegenerate, regulation.AuthType1FA, bodyJSON.Username)  			respondUnauthorized(ctx, messageAuthenticationFailed) @@ -114,7 +114,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re  		if keepMeLoggedIn {  			err = provider.UpdateExpiration(ctx.RequestCtx, provider.Config.RememberMe)  			if err != nil { -				ctx.Logger.Errorf(logFmtErrSessionSave, "updated expiration", regulation.AuthType1FA, bodyJSON.Username, err) +				ctx.Logger.WithError(err).Errorf(logFmtErrSessionSave, "updated expiration", regulation.AuthType1FA, bodyJSON.Username)  				respondUnauthorized(ctx, messageAuthenticationFailed) @@ -125,7 +125,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re  		// Get the details of the given user from the user provider.  		userDetails, err := ctx.Providers.UserProvider.GetDetails(bodyJSON.Username)  		if err != nil { -			ctx.Logger.Errorf(logFmtErrObtainProfileDetails, regulation.AuthType1FA, bodyJSON.Username, err) +			ctx.Logger.WithError(err).Errorf(logFmtErrObtainProfileDetails, regulation.AuthType1FA, bodyJSON.Username)  			respondUnauthorized(ctx, messageAuthenticationFailed) @@ -141,7 +141,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re  		}  		if err = ctx.SaveSession(userSession); err != nil { -			ctx.Logger.Errorf(logFmtErrSessionSave, "updated profile", regulation.AuthType1FA, bodyJSON.Username, err) +			ctx.Logger.WithError(err).Errorf(logFmtErrSessionSave, "updated profile", regulation.AuthType1FA, bodyJSON.Username)  			respondUnauthorized(ctx, messageAuthenticationFailed) diff --git a/internal/handlers/handler_firstfactor_test.go b/internal/handlers/handler_firstfactor_test.go index fb00a51df..d7a2a1a14 100644 --- a/internal/handlers/handler_firstfactor_test.go +++ b/internal/handlers/handler_firstfactor_test.go @@ -6,7 +6,9 @@ import (  	"testing"  	"github.com/golang/mock/gomock" +	"github.com/sirupsen/logrus"  	"github.com/stretchr/testify/assert" +	"github.com/stretchr/testify/require"  	"github.com/stretchr/testify/suite"  	"github.com/valyala/fasthttp" @@ -36,7 +38,7 @@ func (s *FirstFactorSuite) TestShouldFailIfBodyIsNil() {  	FirstFactorPOST(nil)(s.mock.Ctx)  	// No body. -	assert.Equal(s.T(), "Failed to parse 1FA request body: unable to parse body: unexpected end of JSON input", s.mock.Hook.LastEntry().Message) +	AssertLogEntryMessageAndError(s.T(), s.mock.Hook.LastEntry(), "Failed to parse 1FA request body", "unable to parse body: unexpected end of JSON input")  	s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")  } @@ -47,7 +49,7 @@ func (s *FirstFactorSuite) TestShouldFailIfBodyIsInBadFormat() {  	}`)  	FirstFactorPOST(nil)(s.mock.Ctx) -	assert.Equal(s.T(), "Failed to parse 1FA request body: unable to validate body: password: non zero value required", s.mock.Hook.LastEntry().Message) +	AssertLogEntryMessageAndError(s.T(), s.mock.Hook.LastEntry(), "Failed to parse 1FA request body", "unable to validate body: password: non zero value required")  	s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")  } @@ -75,8 +77,8 @@ func (s *FirstFactorSuite) TestShouldFailIfUserProviderCheckPasswordFail() {  	}`)  	FirstFactorPOST(nil)(s.mock.Ctx) -	assert.Equal(s.T(), "Unsuccessful 1FA authentication attempt by user 'test'", s.mock.Hook.LastEntry().Message) -	assert.EqualError(s.T(), s.mock.Hook.LastEntry().Data["error"].(error), "failed") +	AssertLogEntryMessageAndError(s.T(), s.mock.Hook.LastEntry(), "Unsuccessful 1FA authentication attempt by user 'test'", "failed") +  	s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")  } @@ -155,7 +157,7 @@ func (s *FirstFactorSuite) TestShouldFailIfUserProviderGetDetailsFail() {  	}`)  	FirstFactorPOST(nil)(s.mock.Ctx) -	assert.Equal(s.T(), "Could not obtain profile details during 1FA authentication for user 'test': failed", s.mock.Hook.LastEntry().Message) +	AssertLogEntryMessageAndError(s.T(), s.mock.Hook.LastEntry(), "Could not obtain profile details during 1FA authentication for user 'test'", "failed")  	s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")  } @@ -177,8 +179,7 @@ func (s *FirstFactorSuite) TestShouldFailIfAuthenticationMarkFail() {  	}`)  	FirstFactorPOST(nil)(s.mock.Ctx) -	assert.Equal(s.T(), "Unable to mark 1FA authentication attempt by user 'test'", s.mock.Hook.LastEntry().Message) -	assert.EqualError(s.T(), s.mock.Hook.LastEntry().Data["error"].(error), "failed") +	AssertLogEntryMessageAndError(s.T(), s.mock.Hook.LastEntry(), "Unable to mark 1FA authentication attempt by user 'test'", "failed")  	s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")  } @@ -463,3 +464,23 @@ func TestFirstFactorSuite(t *testing.T) {  	suite.Run(t, new(FirstFactorSuite))  	suite.Run(t, new(FirstFactorRedirectionSuite))  } + +func AssertLogEntryMessageAndError(t *testing.T, entry *logrus.Entry, message, err string) { +	assert.Equal(t, message, entry.Message) + +	v, ok := entry.Data["error"] + +	if err == "" { +		assert.False(t, ok) +		assert.Nil(t, v) +	} else { +		assert.True(t, ok) +		require.NotNil(t, v) + +		theErr, ok := v.(error) +		assert.True(t, ok) +		require.NotNil(t, theErr) + +		assert.EqualError(t, theErr, err) +	} +} diff --git a/internal/handlers/handler_register_webauthn.go b/internal/handlers/handler_register_webauthn.go index dcd534c29..8c4d08f9b 100644 --- a/internal/handlers/handler_register_webauthn.go +++ b/internal/handlers/handler_register_webauthn.go @@ -3,6 +3,7 @@ package handlers  import (  	"bytes"  	"encoding/json" +	"errors"  	"strings"  	"github.com/go-webauthn/webauthn/protocol" @@ -35,7 +36,7 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {  	}  	if w, err = newWebAuthn(ctx); err != nil { -		ctx.Logger.Errorf("Unable to create provider to generate %s registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to create provider to generate %s registration challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -43,7 +44,7 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {  	}  	if err = json.Unmarshal(ctx.PostBody(), &bodyJSON); err != nil { -		ctx.Logger.Errorf("Unable to parse %s registration request PUT data for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to parse %s registration request PUT data for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -60,7 +61,7 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {  	devices, err := ctx.Providers.StorageProvider.LoadWebAuthnCredentialsByUsername(ctx, w.Config.RPID, userSession.Username)  	if err != nil && err != storage.ErrNoWebAuthnCredential { -		ctx.Logger.Errorf("Unable to load existing %s devices for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to load existing %s devices for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -79,7 +80,7 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {  	}  	if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil { -		ctx.Logger.Errorf("Unable to load %s devices for registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to load %s devices for registration challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -101,7 +102,7 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {  	}  	if creation, data.SessionData, err = w.BeginRegistration(user, opts...); err != nil { -		ctx.Logger.Errorf("Unable to create %s registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to create %s registration challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -111,7 +112,7 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {  	userSession.WebAuthn = &data  	if err = ctx.SaveSession(userSession); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionSave, "registration challenge", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionSave, "registration challenge", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -119,7 +120,7 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {  	}  	if err = ctx.SetJSONBody(creation); err != nil { -		ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -158,7 +159,7 @@ func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {  	}  	if w, err = newWebAuthn(ctx); err != nil { -		ctx.Logger.Errorf("Unable to configure %s during registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to configure %s during registration for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -166,11 +167,13 @@ func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {  	}  	if response, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil { -		switch e := err.(type) { -		case *protocol.Error: -			ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo) +		var e *protocol.Error + +		switch { +		case errors.As(err, &e): +			ctx.Logger.WithError(e).Errorf("Unable to parse %s registration for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo)  		default: -			ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +			ctx.Logger.WithError(err).Errorf("Unable to parse %s registration for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		}  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -187,11 +190,13 @@ func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {  	}  	if credential, err = w.CreateCredential(user, *userSession.WebAuthn.SessionData, response); err != nil { -		switch e := err.(type) { -		case *protocol.Error: -			ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo) +		var e *protocol.Error + +		switch { +		case errors.As(err, &e): +			ctx.Logger.WithError(e).Errorf("Unable to create %s credential for user '%s': %s", regulation.AuthTypeWebAuthn, userSession.Username, e.DevInfo)  		default: -			ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +			ctx.Logger.WithError(err).Errorf("Unable to create %s credential for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		}  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -204,7 +209,7 @@ func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {  	device.Discoverable = webauthnCredentialCreationIsDiscoverable(ctx, response)  	if err = ctx.Providers.StorageProvider.SaveWebAuthnCredential(ctx, device); err != nil { -		ctx.Logger.Errorf("Unable to save %s device registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to save %s device registration for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) @@ -214,7 +219,7 @@ func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {  	userSession.WebAuthn = nil  	if err = ctx.SaveSession(userSession); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the registration challenge", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionSave, "removal of the registration challenge", regulation.AuthTypeWebAuthn, userSession.Username)  	}  	ctx.ReplyOK() diff --git a/internal/handlers/handler_session_elevation.go b/internal/handlers/handler_session_elevation.go index 7c74cc97a..712cd8206 100755 --- a/internal/handlers/handler_session_elevation.go +++ b/internal/handlers/handler_session_elevation.go @@ -315,8 +315,6 @@ func UserSessionElevateDELETE(ctx *middlewares.AutheliaCtx) {  	if _, err = base64.RawURLEncoding.Decode(decoded, []byte(value)); err != nil {  		ctx.Logger.WithError(err).Error("Failed to base64 decode elevation identifier during elevation revocation.") - -		ctx.SetStatusCode(fasthttp.StatusForbidden)  		ctx.SetJSONError(messageOperationFailed)  		return @@ -324,8 +322,6 @@ func UserSessionElevateDELETE(ctx *middlewares.AutheliaCtx) {  	if id, err = uuid.FromBytes(decoded); err != nil {  		ctx.Logger.WithError(err).Error("Failed to parse decoded elevation identifier during elevation revocation.") - -		ctx.SetStatusCode(fasthttp.StatusForbidden)  		ctx.SetJSONError(messageOperationFailed)  		return @@ -333,8 +329,6 @@ func UserSessionElevateDELETE(ctx *middlewares.AutheliaCtx) {  	if otp, err = ctx.Providers.StorageProvider.LoadOneTimeCodeByPublicID(ctx, id); err != nil {  		ctx.Logger.WithError(err).Error("Failed to load the elevation One-Time Code row from the database during elevation revocation.") - -		ctx.SetStatusCode(fasthttp.StatusForbidden)  		ctx.SetJSONError(messageOperationFailed)  		return @@ -342,8 +336,6 @@ func UserSessionElevateDELETE(ctx *middlewares.AutheliaCtx) {  	if otp.RevokedAt.Valid {  		ctx.Logger.Error("Failed to revoke the One-Time Code during elevation revocation as it's already revoked.") - -		ctx.SetStatusCode(fasthttp.StatusForbidden)  		ctx.SetJSONError(messageOperationFailed)  		return @@ -351,8 +343,6 @@ func UserSessionElevateDELETE(ctx *middlewares.AutheliaCtx) {  	if otp.ConsumedAt.Valid {  		ctx.Logger.Error("Failed to revoke the One-Time Code during elevation revocation as it's consumed.") - -		ctx.SetStatusCode(fasthttp.StatusForbidden)  		ctx.SetJSONError(messageOperationFailed)  		return @@ -360,8 +350,6 @@ func UserSessionElevateDELETE(ctx *middlewares.AutheliaCtx) {  	if otp.Intent != model.OTCIntentUserSessionElevation {  		ctx.Logger.Error("Failed to revoke the One-Time Code during elevation revocation as it doesn't have the expected intent.") - -		ctx.SetStatusCode(fasthttp.StatusForbidden)  		ctx.SetJSONError(messageOperationFailed)  		return @@ -369,8 +357,6 @@ func UserSessionElevateDELETE(ctx *middlewares.AutheliaCtx) {  	if err = ctx.Providers.StorageProvider.RevokeOneTimeCode(ctx, id, model.NewIP(ctx.RemoteIP())); err != nil {  		ctx.Logger.WithError(err).Error("Failed to save the revocation information to the database during elevation revocation.") - -		ctx.SetStatusCode(fasthttp.StatusForbidden)  		ctx.SetJSONError(messageOperationFailed)  		return diff --git a/internal/handlers/handler_sign_duo.go b/internal/handlers/handler_sign_duo.go index b4cf072ee..d4b10d26a 100644 --- a/internal/handlers/handler_sign_duo.go +++ b/internal/handlers/handler_sign_duo.go @@ -24,7 +24,7 @@ func DuoPOST(duoAPI duo.API) middlewares.RequestHandler {  		)  		if err = ctx.ParseBody(bodyJSON); err != nil { -			ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeDuo, err) +			ctx.Logger.WithError(err).Errorf(logFmtErrParseRequestBody, regulation.AuthTypeDuo)  			respondUnauthorized(ctx, messageMFAValidationFailed) @@ -256,7 +256,7 @@ func HandleAllow(ctx *middlewares.AutheliaCtx, userSession *session.UserSession,  	)  	if err = ctx.RegenerateSession(); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeDuo, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeDuo, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -266,7 +266,7 @@ func HandleAllow(ctx *middlewares.AutheliaCtx, userSession *session.UserSession,  	userSession.SetTwoFactorDuo(ctx.Clock.Now())  	if err = ctx.SaveSession(*userSession); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionSave, "authentication time", regulation.AuthTypeTOTP, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionSave, "authentication time", regulation.AuthTypeTOTP, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) diff --git a/internal/handlers/handler_sign_totp.go b/internal/handlers/handler_sign_totp.go index 38b98bd0a..ee8f275b1 100644 --- a/internal/handlers/handler_sign_totp.go +++ b/internal/handlers/handler_sign_totp.go @@ -60,7 +60,7 @@ func TimeBasedOneTimePasswordPOST(ctx *middlewares.AutheliaCtx) {  	)  	if err = ctx.ParseBody(&bodyJSON); err != nil { -		ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeTOTP, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrParseRequestBody, regulation.AuthTypeTOTP)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -107,7 +107,7 @@ func TimeBasedOneTimePasswordPOST(ctx *middlewares.AutheliaCtx) {  	}  	if err = ctx.RegenerateSession(); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeTOTP, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeTOTP, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -127,7 +127,7 @@ func TimeBasedOneTimePasswordPOST(ctx *middlewares.AutheliaCtx) {  	userSession.SetTwoFactorTOTP(ctx.Clock.Now())  	if err = ctx.SaveSession(userSession); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionSave, "authentication time", regulation.AuthTypeTOTP, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionSave, "authentication time", regulation.AuthTypeTOTP, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) diff --git a/internal/handlers/handler_sign_webauthn.go b/internal/handlers/handler_sign_webauthn.go index 33fcf5b8b..fa6b512e9 100644 --- a/internal/handlers/handler_sign_webauthn.go +++ b/internal/handlers/handler_sign_webauthn.go @@ -30,7 +30,7 @@ func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {  	}  	if w, err = newWebAuthn(ctx); err != nil { -		ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to configure %s during authentication challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -38,7 +38,7 @@ func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {  	}  	if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil { -		ctx.Logger.Errorf("Unable to load %s user details during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to load %s user details during authentication challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -65,7 +65,7 @@ func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {  	)  	if assertion, data.SessionData, err = w.BeginLogin(user, opts...); err != nil { -		ctx.Logger.Errorf("Unable to create %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to create %s authentication challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -75,7 +75,7 @@ func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {  	userSession.WebAuthn = &data  	if err = ctx.SaveSession(userSession); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -83,7 +83,7 @@ func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {  	}  	if err = ctx.SetJSONBody(assertion); err != nil { -		ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -105,7 +105,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {  	)  	if err = ctx.ParseBody(&bodyJSON); err != nil { -		ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeWebAuthn, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrParseRequestBody, regulation.AuthTypeWebAuthn)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -129,7 +129,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {  	}  	if w, err = newWebAuthn(ctx); err != nil { -		ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to configure %s during authentication challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -143,7 +143,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {  	)  	if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(bytes.NewReader(bodyJSON.Response)); err != nil { -		ctx.Logger.Errorf("Unable to parse %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to parse %s authentication challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -151,7 +151,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {  	}  	if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil { -		ctx.Logger.Errorf("Unable to load %s credentials for authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf("Unable to load %s credentials for authentication challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -175,7 +175,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {  			found = true  			if err = ctx.Providers.StorageProvider.UpdateWebAuthnCredentialSignIn(ctx, device); err != nil { -				ctx.Logger.Errorf("Unable to save %s device signin count for authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) +				ctx.Logger.WithError(err).Errorf("Unable to save %s device signin count for authentication challenge for user '%s'", regulation.AuthTypeWebAuthn, userSession.Username)  				respondUnauthorized(ctx, messageMFAValidationFailed) @@ -195,7 +195,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {  	}  	if err = ctx.RegenerateSession(); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) @@ -213,7 +213,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {  		assertionResponse.Response.AuthenticatorData.Flags.HasUserVerified())  	if err = ctx.SaveSession(userSession); err != nil { -		ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the authentiation challenge and authentication time", regulation.AuthTypeWebAuthn, userSession.Username, err) +		ctx.Logger.WithError(err).Errorf(logFmtErrSessionSave, "removal of the authentiation challenge and authentication time", regulation.AuthTypeWebAuthn, userSession.Username)  		respondUnauthorized(ctx, messageMFAValidationFailed) diff --git a/internal/storage/migrations/mysql/V0012.WebAuthnMultiCookieDomain.down.sql b/internal/storage/migrations/mysql/V0012.WebAuthnMultiCookieDomain.down.sql index ebb0651c3..8e67ff5b9 100644 --- a/internal/storage/migrations/mysql/V0012.WebAuthnMultiCookieDomain.down.sql +++ b/internal/storage/migrations/mysql/V0012.WebAuthnMultiCookieDomain.down.sql @@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS webauthn_devices (      public_key BLOB NOT NULL,      attestation_type VARCHAR(32),      transport VARCHAR(64) DEFAULT '', -    aaguid CHAR(36) NOT NULL, +    aaguid CHAR(36) NULL,      sign_count INTEGER DEFAULT 0,      clone_warning BOOLEAN NOT NULL DEFAULT FALSE,      UNIQUE KEY (username, description), diff --git a/internal/storage/migrations/mysql/V0012.WebAuthnMultiCookieDomain.up.sql b/internal/storage/migrations/mysql/V0012.WebAuthnMultiCookieDomain.up.sql index 0a0ea7317..05b846464 100644 --- a/internal/storage/migrations/mysql/V0012.WebAuthnMultiCookieDomain.up.sql +++ b/internal/storage/migrations/mysql/V0012.WebAuthnMultiCookieDomain.up.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS webauthn_credentials (      username VARCHAR(100) NOT NULL,      description VARCHAR(30) NOT NULL,      kid VARCHAR(512) NOT NULL, -    aaguid CHAR(36) NOT NULL, +    aaguid CHAR(36) NULL,      attestation_type VARCHAR(32),      attachment VARCHAR(64) NOT NULL,      transport VARCHAR(64) DEFAULT '', diff --git a/internal/storage/migrations/postgres/V0012.WebAuthnMultiCookieDomain.down.sql b/internal/storage/migrations/postgres/V0012.WebAuthnMultiCookieDomain.down.sql index ab9264727..4c4a35611 100644 --- a/internal/storage/migrations/postgres/V0012.WebAuthnMultiCookieDomain.down.sql +++ b/internal/storage/migrations/postgres/V0012.WebAuthnMultiCookieDomain.down.sql @@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS webauthn_devices (      public_key BYTEA NOT NULL,      attestation_type VARCHAR(32),      transport VARCHAR(64) DEFAULT '', -    aaguid CHAR(36) NOT NULL, +    aaguid CHAR(36) NULL,      sign_count INTEGER DEFAULT 0,      clone_warning BOOLEAN NOT NULL DEFAULT FALSE  ); diff --git a/internal/storage/migrations/postgres/V0012.WebAuthnMultiCookieDomain.up.sql b/internal/storage/migrations/postgres/V0012.WebAuthnMultiCookieDomain.up.sql index 5d9fc7402..78249eece 100644 --- a/internal/storage/migrations/postgres/V0012.WebAuthnMultiCookieDomain.up.sql +++ b/internal/storage/migrations/postgres/V0012.WebAuthnMultiCookieDomain.up.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS webauthn_credentials (      username VARCHAR(100) NOT NULL,      description VARCHAR(64) NOT NULL,      kid VARCHAR(512) NOT NULL, -    aaguid CHAR(36) NOT NULL, +    aaguid CHAR(36) NULL,      attestation_type VARCHAR(32),      attachment VARCHAR(64) NOT NULL,      transport VARCHAR(64) DEFAULT '', diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index ed6f2050e..ee385ea5e 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -591,7 +591,6 @@ packages:    /@babel/parser@7.23.3:      resolution: {integrity: sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==}      engines: {node: '>=6.0.0'} -    hasBin: true      dependencies:        '@babel/types': 7.23.6      dev: true @@ -599,7 +598,6 @@ packages:    /@babel/parser@7.23.5:      resolution: {integrity: sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==}      engines: {node: '>=6.0.0'} -    hasBin: true      dependencies:        '@babel/types': 7.23.6      dev: true @@ -1690,7 +1688,6 @@ packages:    /@commitlint/cli@19.0.3(@types/node@20.11.24)(typescript@5.3.3):      resolution: {integrity: sha512-mGhh/aYPib4Vy4h+AGRloMY+CqkmtdeKPV9poMcZeImF5e3knQ5VYaSeAM0mEzps1dbKsHvABwaDpafLUuM96g==}      engines: {node: '>=v18'} -    hasBin: true      dependencies:        '@commitlint/format': 19.0.3        '@commitlint/lint': 19.0.3 @@ -3537,7 +3534,6 @@ packages:    /JSONStream@1.3.5:      resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} -    hasBin: true      dependencies:        jsonparse: 1.3.1        through: 2.3.8 @@ -3567,7 +3563,6 @@ packages:    /acorn@8.11.2:      resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==}      engines: {node: '>=0.4.0'} -    hasBin: true      dev: true    /ajv@6.12.6: @@ -3958,7 +3953,6 @@ packages:    /browserslist@4.21.10:      resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==}      engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} -    hasBin: true      dependencies:        caniuse-lite: 1.0.30001518        electron-to-chromium: 1.4.478 @@ -4175,7 +4169,6 @@ packages:    /conventional-commits-parser@5.0.0:      resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==}      engines: {node: '>=16'} -    hasBin: true      dependencies:        JSONStream: 1.3.5        is-text-path: 2.0.0 @@ -4865,7 +4858,6 @@ packages:    /esbuild@0.15.18:      resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==}      engines: {node: '>=12'} -    hasBin: true      requiresBuild: true      optionalDependencies:        '@esbuild/android-arm': 0.15.18 @@ -4926,7 +4918,6 @@ packages:    /esbuild@0.20.1:      resolution: {integrity: sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==}      engines: {node: '>=12'} -    hasBin: true      requiresBuild: true      optionalDependencies:        '@esbuild/aix-ppc64': 0.20.1 @@ -4973,7 +4964,6 @@ packages:    /eslint-config-prettier@9.1.0(eslint@8.57.0):      resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} -    hasBin: true      peerDependencies:        eslint: '>=7.0.0'      dependencies: @@ -6123,7 +6113,6 @@ packages:    /is-docker@2.2.1:      resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}      engines: {node: '>=8'} -    hasBin: true      dev: true    /is-docker@3.0.0: @@ -6387,7 +6376,6 @@ packages:    /jiti@1.20.0:      resolution: {integrity: sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==} -    hasBin: true      dev: true    /js-tokens@4.0.0: @@ -6414,13 +6402,11 @@ packages:    /jsesc@0.5.0:      resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} -    hasBin: true      dev: true    /jsesc@2.5.2:      resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}      engines: {node: '>=4'} -    hasBin: true      dev: true    /json-parse-even-better-errors@2.3.1: @@ -6448,7 +6434,6 @@ packages:    /json5@2.2.3:      resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}      engines: {node: '>=6'} -    hasBin: true      dev: true    /jsonc-parser@3.2.0: @@ -6637,7 +6622,6 @@ packages:    /loose-envify@1.4.0:      resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} -    hasBin: true      dependencies:        js-tokens: 4.0.0 @@ -6793,7 +6777,6 @@ packages:    /nanoid@3.3.7:      resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}      engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} -    hasBin: true      dev: true    /natural-compare@1.4.0: @@ -7183,7 +7166,6 @@ packages:    /prettier@3.2.5:      resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}      engines: {node: '>=14'} -    hasBin: true      dev: true    /pretty-format@27.5.1: @@ -7269,6 +7251,7 @@ packages:      dependencies:        loose-envify: 1.4.0        react: 18.2.0 +      scheduler: 0.23.0    /react-i18next@14.0.5(i18next@23.10.0)(react-dom@18.2.0)(react@18.2.0):      resolution: {integrity: sha512-5+bQSeEtgJrMBABBL5lO7jPdSNAbeAZ+MlFWDw//7FnVacuVu3l9EeWFzBQvZsKy+cihkbThWOAThEdH8YjGEw==} @@ -7528,7 +7511,6 @@ packages:    /rollup@2.79.1:      resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}      engines: {node: '>=10.0.0'} -    hasBin: true      optionalDependencies:        fsevents: 2.3.3      dev: true @@ -7536,7 +7518,6 @@ packages:    /rollup@4.4.1:      resolution: {integrity: sha512-idZzrUpWSblPJX66i+GzrpjKE3vbYrlWirUHteoAbjKReZwa0cohAErOYA5efoMmNCdvG9yrJS+w9Kl6csaH4w==}      engines: {node: '>=18.0.0', npm: '>=8.0.0'} -    hasBin: true      optionalDependencies:        '@rollup/rollup-android-arm-eabi': 4.4.1        '@rollup/rollup-android-arm64': 4.4.1 @@ -8149,7 +8130,6 @@ packages:    /typescript@5.3.3:      resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}      engines: {node: '>=14.17'} -    hasBin: true      dev: true    /ufo@1.3.2: @@ -8218,7 +8198,6 @@ packages:    /update-browserslist-db@1.0.11(browserslist@4.21.10):      resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} -    hasBin: true      peerDependencies:        browserslist: '>= 4.21.0'      dependencies: @@ -8246,7 +8225,6 @@ packages:    /vite-node@1.3.1(@types/node@20.11.24):      resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==}      engines: {node: ^18.0.0 || >=20.0.0} -    hasBin: true      dependencies:        cac: 6.7.14        debug: 4.3.4 @@ -8365,7 +8343,6 @@ packages:    /vite@3.2.5(@types/node@18.16.5):      resolution: {integrity: sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==}      engines: {node: ^14.18.0 || >=16.0.0} -    hasBin: true      peerDependencies:        '@types/node': '>= 14'        less: '*' @@ -8399,7 +8376,6 @@ packages:    /vite@5.1.4(@types/node@20.11.24):      resolution: {integrity: sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==}      engines: {node: ^18.0.0 || >=20.0.0} -    hasBin: true      peerDependencies:        '@types/node': ^18.0.0 || >=20.0.0        less: '*' @@ -8452,7 +8428,6 @@ packages:    /vitest@1.3.1(@types/node@20.11.24)(happy-dom@13.6.2):      resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==}      engines: {node: ^18.0.0 || >=20.0.0} -    hasBin: true      peerDependencies:        '@edge-runtime/vm': '*'        '@types/node': ^18.0.0 || >=20.0.0 @@ -8634,7 +8609,6 @@ packages:    /which@2.0.2:      resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}      engines: {node: '>= 8'} -    hasBin: true      dependencies:        isexe: 2.0.0      dev: true @@ -8642,7 +8616,6 @@ packages:    /why-is-node-running@2.2.2:      resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}      engines: {node: '>=8'} -    hasBin: true      dependencies:        siginfo: 2.0.0        stackback: 0.0.2 diff --git a/web/src/views/Settings/Common/IdentityVerificationDialog.tsx b/web/src/views/Settings/Common/IdentityVerificationDialog.tsx index 5aebe7feb..298901888 100644 --- a/web/src/views/Settings/Common/IdentityVerificationDialog.tsx +++ b/web/src/views/Settings/Common/IdentityVerificationDialog.tsx @@ -105,15 +105,17 @@ const IdentityVerificationDialog = function (props: Props) {              return;          } +        if (open) { +            return; +        } +          const attempt = await generateUserSessionElevation();          if (!attempt) throw new Error("Failed to load the data.");          setCodeDelete(attempt.delete_id); -        if (!open) { -            props.handleOpened(); -            setOpen(true); -        } +        props.handleOpened(); +        setOpen(true);      }, [handleClose, open, props]);      const handleSubmit = useCallback(async () => { diff --git a/web/src/views/Settings/TwoFactorAuthentication/OneTimePasswordRegisterDialog.tsx b/web/src/views/Settings/TwoFactorAuthentication/OneTimePasswordRegisterDialog.tsx index 48a5c1cde..79cbb408b 100644 --- a/web/src/views/Settings/TwoFactorAuthentication/OneTimePasswordRegisterDialog.tsx +++ b/web/src/views/Settings/TwoFactorAuthentication/OneTimePasswordRegisterDialog.tsx @@ -385,7 +385,6 @@ const OneTimePasswordRegisterDialog = function (props: Props) {                      <Fragment>                          <Grid xs={12} my={2}>                              <FormControlLabel -                                disabled={disableAdvanced}                                  control={                                      <Switch                                          checked={showQRCode} diff --git a/web/src/views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationOptionsPanel.tsx b/web/src/views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationOptionsPanel.tsx index f8a95e83c..1248781e2 100644 --- a/web/src/views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationOptionsPanel.tsx +++ b/web/src/views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationOptionsPanel.tsx @@ -12,8 +12,8 @@ import { Method2FA, isMethod2FA, setPreferred2FAMethod, toMethod2FA, toSecondFac  interface Props {      refresh: () => void; -    config?: Configuration; -    info?: UserInfo; +    config: Configuration; +    info: UserInfo;  }  const TwoFactorAuthenticationOptionsPanel = function (props: Props) { @@ -24,8 +24,7 @@ const TwoFactorAuthenticationOptionsPanel = function (props: Props) {      const [method, setMethod] = useState<string>();      const [methods, setMethods] = useState<string[]>([]); -    const hasMethods = -        props.info !== undefined && (props.info.has_totp || props.info.has_webauthn || props.info.has_duo); +    const hasMethods = props.info.has_totp || props.info.has_webauthn || props.info.has_duo;      useEffect(() => {          if (props.info === undefined) return; @@ -34,7 +33,7 @@ const TwoFactorAuthenticationOptionsPanel = function (props: Props) {      }, [props.info]);      useEffect(() => { -        if (!props.config || !hasMethods) return; +        if (!hasMethods) return;          let valuesFinal: string[] = [];          const values = Array.from(props.config.available_methods); @@ -45,20 +44,26 @@ const TwoFactorAuthenticationOptionsPanel = function (props: Props) {              if (!valuesFinal.includes(v)) {                  switch (value) {                      case SecondFactorMethod.WebAuthn: -                        valuesFinal.push(v); +                        if (props.info.has_webauthn) { +                            valuesFinal.push(v); +                        }                          break;                      case SecondFactorMethod.TOTP: -                        valuesFinal.push(v); +                        if (props.info.has_totp) { +                            valuesFinal.push(v); +                        }                          break;                      case SecondFactorMethod.MobilePush: -                        valuesFinal.push(v); +                        if (props.info.has_duo) { +                            valuesFinal.push(v); +                        }                          break;                  }              }          });          setMethods(valuesFinal); -    }, [props.config, hasMethods]); +    }, [props.config, hasMethods, props.info.has_webauthn, props.info.has_totp, props.info.has_duo]);      const handleMethodChanged = (event: ChangeEvent<HTMLInputElement>) => {          if (isMethod2FA(event.target.value)) { diff --git a/web/src/views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationView.tsx b/web/src/views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationView.tsx index 0888c1e43..787ed2b94 100755 --- a/web/src/views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationView.tsx +++ b/web/src/views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationView.tsx @@ -8,6 +8,7 @@ import { useNotifications } from "@hooks/NotificationsContext";  import { useUserInfoPOST } from "@hooks/UserInfo";  import { useUserInfoTOTPConfigurationOptional } from "@hooks/UserInfoTOTPConfiguration";  import { useUserWebAuthnCredentials } from "@hooks/WebAuthnCredentials"; +import { SecondFactorMethod } from "@models/Methods.ts";  import OneTimePasswordPanel from "@views/Settings/TwoFactorAuthentication/OneTimePasswordPanel";  import TwoFactorAuthenticationOptionsPanel from "@views/Settings/TwoFactorAuthentication/TwoFactorAuthenticationOptionsPanel";  import WebAuthnCredentialsPanel from "@views/Settings/TwoFactorAuthentication/WebAuthnCredentialsPanel"; @@ -118,20 +119,24 @@ const TwoFactorAuthenticationView = function (props: Props) {      return (          <Fragment>              <Grid container spacing={2}> -                <Grid xs={12}> -                    <OneTimePasswordPanel -                        info={userInfo} -                        config={userTOTPConfig} -                        handleRefreshState={handleRefreshTOTPState} -                    /> -                </Grid> -                <Grid xs={12}> -                    <WebAuthnCredentialsPanel -                        info={userInfo} -                        credentials={userWebAuthnCredentials} -                        handleRefreshState={handleRefreshWebAuthnState} -                    /> -                </Grid> +                {configuration?.available_methods.has(SecondFactorMethod.TOTP) ? ( +                    <Grid xs={12}> +                        <OneTimePasswordPanel +                            info={userInfo} +                            config={userTOTPConfig} +                            handleRefreshState={handleRefreshTOTPState} +                        /> +                    </Grid> +                ) : null} +                {configuration?.available_methods.has(SecondFactorMethod.WebAuthn) ? ( +                    <Grid xs={12}> +                        <WebAuthnCredentialsPanel +                            info={userInfo} +                            credentials={userWebAuthnCredentials} +                            handleRefreshState={handleRefreshWebAuthnState} +                        /> +                    </Grid> +                ) : null}                  {configuration && userInfo ? (                      <Grid xs={12}>                          <TwoFactorAuthenticationOptionsPanel  | 
