refactor: webauthn naming (#5243)
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/5244/head
parent
37a49b21af
commit
2733fc040c
|
@ -29,9 +29,9 @@ tags:
|
||||||
- name: User Information
|
- name: User Information
|
||||||
description: User configuration endpoints
|
description: User configuration endpoints
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if (or .TOTP .Webauthn .Duo) }}
|
{{- if (or .TOTP .WebAuthn .Duo) }}
|
||||||
- name: Second Factor
|
- name: Second Factor
|
||||||
description: TOTP, Webauthn and Duo endpoints
|
description: TOTP, WebAuthn and Duo endpoints
|
||||||
externalDocs:
|
externalDocs:
|
||||||
url: https://www.authelia.com/configuration/second-factor/introduction/
|
url: https://www.authelia.com/configuration/second-factor/introduction/
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -721,13 +721,13 @@ paths:
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Webauthn }}
|
{{- if .WebAuthn }}
|
||||||
/api/secondfactor/webauthn/assertion:
|
/api/secondfactor/webauthn/assertion:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Second Factor
|
- Second Factor
|
||||||
summary: Second Factor Authentication - Webauthn (Request)
|
summary: Second Factor Authentication - WebAuthn (Request)
|
||||||
description: This endpoint starts the second factor authentication process with the FIDO2 Webauthn credential.
|
description: This endpoint starts the second factor authentication process with the FIDO2 WebAuthn credential.
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Successful Operation
|
description: Successful Operation
|
||||||
|
@ -742,8 +742,8 @@ paths:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Second Factor
|
- Second Factor
|
||||||
summary: Second Factor Authentication - Webauthn
|
summary: Second Factor Authentication - WebAuthn
|
||||||
description: This endpoint completes the second factor authentication process with the FIDO2 Webauthn credential.
|
description: This endpoint completes the second factor authentication process with the FIDO2 WebAuthn credential.
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
|
@ -765,9 +765,9 @@ paths:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Second Factor
|
- Second Factor
|
||||||
summary: Identity Verification Webauthn Credential Creation
|
summary: Identity Verification WebAuthn Credential Creation
|
||||||
description: >
|
description: >
|
||||||
This endpoint performs identity verification to begin the FIDO2 Webauthn credential attestation process
|
This endpoint performs identity verification to begin the FIDO2 WebAuthn credential attestation process
|
||||||
(registration).
|
(registration).
|
||||||
|
|
||||||
The session generated from this endpoint must be utilised for the subsequent steps in the
|
The session generated from this endpoint must be utilised for the subsequent steps in the
|
||||||
|
@ -785,9 +785,9 @@ paths:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Second Factor
|
- Second Factor
|
||||||
summary: Identity Verification FIDO2 Webauthn Credential Validation
|
summary: Identity Verification FIDO2 WebAuthn Credential Validation
|
||||||
description: >
|
description: >
|
||||||
This endpoint performs identity and token verification, upon success generates a FIDO2 Webauthn device
|
This endpoint performs identity and token verification, upon success generates a FIDO2 WebAuthn device
|
||||||
attestation challenge (registration).
|
attestation challenge (registration).
|
||||||
|
|
||||||
The session cookie generated from the `/api/secondfactor/webauthn/identity/start` endpoint must be utilised
|
The session cookie generated from the `/api/secondfactor/webauthn/identity/start` endpoint must be utilised
|
||||||
|
@ -811,8 +811,8 @@ paths:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Second Factor
|
- Second Factor
|
||||||
summary: Webauthn Credential Attestation
|
summary: WebAuthn Credential Attestation
|
||||||
description: This endpoint performs Webauthn credential attestation (registration).
|
description: This endpoint performs WebAuthn credential attestation (registration).
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
|
@ -1878,7 +1878,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
example: 'otpauth://totp/{{ .Domain | default "example.com" }}:john?algorithm=SHA1&digits=6&issuer=auth.{{ .Domain | default "example.com" }}&period=30&secret=5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q'
|
example: 'otpauth://totp/{{ .Domain | default "example.com" }}:john?algorithm=SHA1&digits=6&issuer=auth.{{ .Domain | default "example.com" }}&period=30&secret=5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q'
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Webauthn }}
|
{{- if .WebAuthn }}
|
||||||
webauthn.PublicKeyCredential:
|
webauthn.PublicKeyCredential:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -214,13 +214,13 @@ totp:
|
||||||
##
|
##
|
||||||
## Parameters used for WebAuthn.
|
## Parameters used for WebAuthn.
|
||||||
webauthn:
|
webauthn:
|
||||||
## Disable Webauthn.
|
## Disable WebAuthn.
|
||||||
disable: false
|
disable: false
|
||||||
|
|
||||||
## Adjust the interaction timeout for Webauthn dialogues.
|
## Adjust the interaction timeout for WebAuthn dialogues.
|
||||||
timeout: 60s
|
timeout: 60s
|
||||||
|
|
||||||
## The display name the browser should show the user for when using Webauthn to login/register.
|
## The display name the browser should show the user for when using WebAuthn to login/register.
|
||||||
display_name: Authelia
|
display_name: Authelia
|
||||||
|
|
||||||
## Conveyance preference controls if we collect the attestation statement including the AAGUID from the device.
|
## Conveyance preference controls if we collect the attestation statement including the AAGUID from the device.
|
||||||
|
@ -1167,7 +1167,7 @@ regulation:
|
||||||
##
|
##
|
||||||
## Notification Provider
|
## Notification Provider
|
||||||
##
|
##
|
||||||
## Notifications are sent to users when they require a password reset, a Webauthn registration or a TOTP registration.
|
## Notifications are sent to users when they require a password reset, a WebAuthn registration or a TOTP registration.
|
||||||
## The available providers are: filesystem, smtp. You must use only one of these providers.
|
## The available providers are: filesystem, smtp. You must use only one of these providers.
|
||||||
notifier:
|
notifier:
|
||||||
## You can disable the notifier startup check by setting this to true.
|
## You can disable the notifier startup check by setting this to true.
|
||||||
|
|
|
@ -451,7 +451,7 @@ func (ctx *CmdCtx) StorageUserWebAuthnExportRunE(cmd *cobra.Command, args []stri
|
||||||
}
|
}
|
||||||
|
|
||||||
for page := 0; true; page++ {
|
for page := 0; true; page++ {
|
||||||
if devices, err = ctx.providers.StorageProvider.LoadWebauthnDevices(ctx, limit, page); err != nil {
|
if devices, err = ctx.providers.StorageProvider.LoadWebAuthnDevices(ctx, limit, page); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,7 +522,7 @@ func (ctx *CmdCtx) StorageUserWebAuthnImportRunE(cmd *cobra.Command, args []stri
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, device := range export.WebAuthnDevices {
|
for _, device := range export.WebAuthnDevices {
|
||||||
if err = ctx.providers.StorageProvider.SaveWebauthnDevice(ctx, device); err != nil {
|
if err = ctx.providers.StorageProvider.SaveWebAuthnDevice(ctx, device); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -550,10 +550,10 @@ func (ctx *CmdCtx) StorageUserWebAuthnListRunE(cmd *cobra.Command, args []string
|
||||||
|
|
||||||
user := args[0]
|
user := args[0]
|
||||||
|
|
||||||
devices, err = ctx.providers.StorageProvider.LoadWebauthnDevicesByUsername(ctx, user)
|
devices, err = ctx.providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, user)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebauthnDevice)):
|
case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebAuthnDevice)):
|
||||||
return fmt.Errorf("user '%s' has no webauthn devices", user)
|
return fmt.Errorf("user '%s' has no webauthn devices", user)
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return fmt.Errorf("can't list devices for user '%s': %w", user, err)
|
return fmt.Errorf("can't list devices for user '%s': %w", user, err)
|
||||||
|
@ -586,7 +586,7 @@ func (ctx *CmdCtx) StorageUserWebAuthnListAllRunE(_ *cobra.Command, _ []string)
|
||||||
output := strings.Builder{}
|
output := strings.Builder{}
|
||||||
|
|
||||||
for page := 0; true; page++ {
|
for page := 0; true; page++ {
|
||||||
if devices, err = ctx.providers.StorageProvider.LoadWebauthnDevices(ctx, limit, page); err != nil {
|
if devices, err = ctx.providers.StorageProvider.LoadWebAuthnDevices(ctx, limit, page); err != nil {
|
||||||
return fmt.Errorf("failed to list devices: %w", err)
|
return fmt.Errorf("failed to list devices: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,13 +629,13 @@ func (ctx *CmdCtx) StorageUserWebAuthnDeleteRunE(cmd *cobra.Command, args []stri
|
||||||
}
|
}
|
||||||
|
|
||||||
if byKID {
|
if byKID {
|
||||||
if err = ctx.providers.StorageProvider.DeleteWebauthnDevice(ctx, kid); err != nil {
|
if err = ctx.providers.StorageProvider.DeleteWebAuthnDevice(ctx, kid); err != nil {
|
||||||
return fmt.Errorf("failed to delete webauthn device with kid '%s': %w", kid, err)
|
return fmt.Errorf("failed to delete webauthn device with kid '%s': %w", kid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Successfully deleted WebAuthn device with key id '%s'\n", kid)
|
fmt.Printf("Successfully deleted WebAuthn device with key id '%s'\n", kid)
|
||||||
} else {
|
} else {
|
||||||
err = ctx.providers.StorageProvider.DeleteWebauthnDeviceByUsername(ctx, user, description)
|
err = ctx.providers.StorageProvider.DeleteWebAuthnDeviceByUsername(ctx, user, description)
|
||||||
|
|
||||||
if all {
|
if all {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -214,13 +214,13 @@ totp:
|
||||||
##
|
##
|
||||||
## Parameters used for WebAuthn.
|
## Parameters used for WebAuthn.
|
||||||
webauthn:
|
webauthn:
|
||||||
## Disable Webauthn.
|
## Disable WebAuthn.
|
||||||
disable: false
|
disable: false
|
||||||
|
|
||||||
## Adjust the interaction timeout for Webauthn dialogues.
|
## Adjust the interaction timeout for WebAuthn dialogues.
|
||||||
timeout: 60s
|
timeout: 60s
|
||||||
|
|
||||||
## The display name the browser should show the user for when using Webauthn to login/register.
|
## The display name the browser should show the user for when using WebAuthn to login/register.
|
||||||
display_name: Authelia
|
display_name: Authelia
|
||||||
|
|
||||||
## Conveyance preference controls if we collect the attestation statement including the AAGUID from the device.
|
## Conveyance preference controls if we collect the attestation statement including the AAGUID from the device.
|
||||||
|
@ -1167,7 +1167,7 @@ regulation:
|
||||||
##
|
##
|
||||||
## Notification Provider
|
## Notification Provider
|
||||||
##
|
##
|
||||||
## Notifications are sent to users when they require a password reset, a Webauthn registration or a TOTP registration.
|
## Notifications are sent to users when they require a password reset, a WebAuthn registration or a TOTP registration.
|
||||||
## The available providers are: filesystem, smtp. You must use only one of these providers.
|
## The available providers are: filesystem, smtp. You must use only one of these providers.
|
||||||
notifier:
|
notifier:
|
||||||
## You can disable the notifier startup check by setting this to true.
|
## You can disable the notifier startup check by setting this to true.
|
||||||
|
|
|
@ -21,7 +21,7 @@ type Configuration struct {
|
||||||
Notifier NotifierConfiguration `koanf:"notifier"`
|
Notifier NotifierConfiguration `koanf:"notifier"`
|
||||||
Server ServerConfiguration `koanf:"server"`
|
Server ServerConfiguration `koanf:"server"`
|
||||||
Telemetry TelemetryConfig `koanf:"telemetry"`
|
Telemetry TelemetryConfig `koanf:"telemetry"`
|
||||||
Webauthn WebauthnConfiguration `koanf:"webauthn"`
|
WebAuthn WebAuthnConfiguration `koanf:"webauthn"`
|
||||||
PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
|
PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
|
||||||
PrivacyPolicy PrivacyPolicy `koanf:"privacy_policy"`
|
PrivacyPolicy PrivacyPolicy `koanf:"privacy_policy"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebauthnConfiguration represents the webauthn config.
|
// WebAuthnConfiguration represents the webauthn config.
|
||||||
type WebauthnConfiguration struct {
|
type WebAuthnConfiguration struct {
|
||||||
Disable bool `koanf:"disable"`
|
Disable bool `koanf:"disable"`
|
||||||
DisplayName string `koanf:"display_name"`
|
DisplayName string `koanf:"display_name"`
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ type WebauthnConfiguration struct {
|
||||||
Timeout time.Duration `koanf:"timeout"`
|
Timeout time.Duration `koanf:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultWebauthnConfiguration describes the default values for the WebauthnConfiguration.
|
// DefaultWebAuthnConfiguration describes the default values for the WebAuthnConfiguration.
|
||||||
var DefaultWebauthnConfiguration = WebauthnConfiguration{
|
var DefaultWebAuthnConfiguration = WebAuthnConfiguration{
|
||||||
DisplayName: "Authelia",
|
DisplayName: "Authelia",
|
||||||
Timeout: time.Second * 60,
|
Timeout: time.Second * 60,
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ func ValidateConfiguration(config *schema.Configuration, validator *schema.Struc
|
||||||
|
|
||||||
ValidateTOTP(config, validator)
|
ValidateTOTP(config, validator)
|
||||||
|
|
||||||
ValidateWebauthn(config, validator)
|
ValidateWebAuthn(config, validator)
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&config.AuthenticationBackend, validator)
|
ValidateAuthenticationBackend(&config.AuthenticationBackend, validator)
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St
|
||||||
enabledMethods = append(enabledMethods, "totp")
|
enabledMethods = append(enabledMethods, "totp")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.Webauthn.Disable {
|
if !config.WebAuthn.Disable {
|
||||||
enabledMethods = append(enabledMethods, "webauthn")
|
enabledMethods = append(enabledMethods, "webauthn")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -188,7 +188,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldAllowConfiguredMethodWebauthn",
|
desc: "ShouldAllowConfiguredMethodWebAuthn",
|
||||||
have: &schema.Configuration{
|
have: &schema.Configuration{
|
||||||
Default2FAMethod: "webauthn",
|
Default2FAMethod: "webauthn",
|
||||||
DuoAPI: schema.DuoAPIConfiguration{
|
DuoAPI: schema.DuoAPIConfiguration{
|
||||||
|
@ -225,7 +225,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldNotAllowDisabledMethodWebauthn",
|
desc: "ShouldNotAllowDisabledMethodWebAuthn",
|
||||||
have: &schema.Configuration{
|
have: &schema.Configuration{
|
||||||
Default2FAMethod: "webauthn",
|
Default2FAMethod: "webauthn",
|
||||||
DuoAPI: schema.DuoAPIConfiguration{
|
DuoAPI: schema.DuoAPIConfiguration{
|
||||||
|
@ -233,7 +233,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
|
||||||
IntegrationKey: "another key",
|
IntegrationKey: "another key",
|
||||||
Hostname: "none",
|
Hostname: "none",
|
||||||
},
|
},
|
||||||
Webauthn: schema.WebauthnConfiguration{Disable: true},
|
WebAuthn: schema.WebAuthnConfiguration{Disable: true},
|
||||||
},
|
},
|
||||||
expectedErrs: []string{
|
expectedErrs: []string{
|
||||||
"option 'default_2fa_method' must be one of the enabled options 'totp' or 'mobile_push' but it's configured as 'webauthn'",
|
"option 'default_2fa_method' must be one of the enabled options 'totp' or 'mobile_push' but it's configured as 'webauthn'",
|
||||||
|
|
|
@ -197,10 +197,10 @@ const (
|
||||||
"configured to an unsafe value, it should be above 8 but it's configured to %d"
|
"configured to an unsafe value, it should be above 8 but it's configured to %d"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Webauthn Error constants.
|
// WebAuthn Error constants.
|
||||||
const (
|
const (
|
||||||
errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of %s but it's configured as '%s'"
|
errFmtWebAuthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of %s but it's configured as '%s'"
|
||||||
errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of %s but it's configured as '%s'"
|
errFmtWebAuthnUserVerification = "webauthn: option 'user_verification' must be one of %s but it's configured as '%s'"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Access Control error constants.
|
// Access Control error constants.
|
||||||
|
@ -378,8 +378,8 @@ var (
|
||||||
validThemeNames = []string{"light", "dark", "grey", auto}
|
validThemeNames = []string{"light", "dark", "grey", auto}
|
||||||
validSessionSameSiteValues = []string{"none", "lax", "strict"}
|
validSessionSameSiteValues = []string{"none", "lax", "strict"}
|
||||||
validLogLevels = []string{"trace", "debug", "info", "warn", "error"}
|
validLogLevels = []string{"trace", "debug", "info", "warn", "error"}
|
||||||
validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
|
validWebAuthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
|
||||||
validWebauthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
|
validWebAuthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
|
||||||
validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
|
validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
|
||||||
validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"}
|
validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"}
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,27 +7,27 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateWebauthn validates and update Webauthn configuration.
|
// ValidateWebAuthn validates and update WebAuthn configuration.
|
||||||
func ValidateWebauthn(config *schema.Configuration, validator *schema.StructValidator) {
|
func ValidateWebAuthn(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if config.Webauthn.DisplayName == "" {
|
if config.WebAuthn.DisplayName == "" {
|
||||||
config.Webauthn.DisplayName = schema.DefaultWebauthnConfiguration.DisplayName
|
config.WebAuthn.DisplayName = schema.DefaultWebAuthnConfiguration.DisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Webauthn.Timeout <= 0 {
|
if config.WebAuthn.Timeout <= 0 {
|
||||||
config.Webauthn.Timeout = schema.DefaultWebauthnConfiguration.Timeout
|
config.WebAuthn.Timeout = schema.DefaultWebAuthnConfiguration.Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case config.Webauthn.ConveyancePreference == "":
|
case config.WebAuthn.ConveyancePreference == "":
|
||||||
config.Webauthn.ConveyancePreference = schema.DefaultWebauthnConfiguration.ConveyancePreference
|
config.WebAuthn.ConveyancePreference = schema.DefaultWebAuthnConfiguration.ConveyancePreference
|
||||||
case !utils.IsStringInSlice(string(config.Webauthn.ConveyancePreference), validWebauthnConveyancePreferences):
|
case !utils.IsStringInSlice(string(config.WebAuthn.ConveyancePreference), validWebAuthnConveyancePreferences):
|
||||||
validator.Push(fmt.Errorf(errFmtWebauthnConveyancePreference, strJoinOr(validWebauthnConveyancePreferences), config.Webauthn.ConveyancePreference))
|
validator.Push(fmt.Errorf(errFmtWebAuthnConveyancePreference, strJoinOr(validWebAuthnConveyancePreferences), config.WebAuthn.ConveyancePreference))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case config.Webauthn.UserVerification == "":
|
case config.WebAuthn.UserVerification == "":
|
||||||
config.Webauthn.UserVerification = schema.DefaultWebauthnConfiguration.UserVerification
|
config.WebAuthn.UserVerification = schema.DefaultWebAuthnConfiguration.UserVerification
|
||||||
case !utils.IsStringInSlice(string(config.Webauthn.UserVerification), validWebauthnUserVerificationRequirement):
|
case !utils.IsStringInSlice(string(config.WebAuthn.UserVerification), validWebAuthnUserVerificationRequirement):
|
||||||
validator.Push(fmt.Errorf(errFmtWebauthnUserVerification, strJoinOr(validWebauthnConveyancePreferences), config.Webauthn.UserVerification))
|
validator.Push(fmt.Errorf(errFmtWebAuthnUserVerification, strJoinOr(validWebAuthnConveyancePreferences), config.WebAuthn.UserVerification))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,39 +11,39 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWebauthnShouldSetDefaultValues(t *testing.T) {
|
func TestWebAuthnShouldSetDefaultValues(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := &schema.Configuration{
|
config := &schema.Configuration{
|
||||||
Webauthn: schema.WebauthnConfiguration{},
|
WebAuthn: schema.WebAuthnConfiguration{},
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateWebauthn(config, validator)
|
ValidateWebAuthn(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, schema.DefaultWebauthnConfiguration.DisplayName, config.Webauthn.DisplayName)
|
assert.Equal(t, schema.DefaultWebAuthnConfiguration.DisplayName, config.WebAuthn.DisplayName)
|
||||||
assert.Equal(t, schema.DefaultWebauthnConfiguration.Timeout, config.Webauthn.Timeout)
|
assert.Equal(t, schema.DefaultWebAuthnConfiguration.Timeout, config.WebAuthn.Timeout)
|
||||||
assert.Equal(t, schema.DefaultWebauthnConfiguration.ConveyancePreference, config.Webauthn.ConveyancePreference)
|
assert.Equal(t, schema.DefaultWebAuthnConfiguration.ConveyancePreference, config.WebAuthn.ConveyancePreference)
|
||||||
assert.Equal(t, schema.DefaultWebauthnConfiguration.UserVerification, config.Webauthn.UserVerification)
|
assert.Equal(t, schema.DefaultWebAuthnConfiguration.UserVerification, config.WebAuthn.UserVerification)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebauthnShouldSetDefaultTimeoutWhenNegative(t *testing.T) {
|
func TestWebAuthnShouldSetDefaultTimeoutWhenNegative(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := &schema.Configuration{
|
config := &schema.Configuration{
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
Timeout: -1,
|
Timeout: -1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateWebauthn(config, validator)
|
ValidateWebAuthn(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, schema.DefaultWebauthnConfiguration.Timeout, config.Webauthn.Timeout)
|
assert.Equal(t, schema.DefaultWebAuthnConfiguration.Timeout, config.WebAuthn.Timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebauthnShouldNotSetDefaultValuesWhenConfigured(t *testing.T) {
|
func TestWebAuthnShouldNotSetDefaultValuesWhenConfigured(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := &schema.Configuration{
|
config := &schema.Configuration{
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
DisplayName: "Test",
|
DisplayName: "Test",
|
||||||
Timeout: time.Second * 50,
|
Timeout: time.Second * 50,
|
||||||
ConveyancePreference: protocol.PreferNoAttestation,
|
ConveyancePreference: protocol.PreferNoAttestation,
|
||||||
|
@ -51,37 +51,37 @@ func TestWebauthnShouldNotSetDefaultValuesWhenConfigured(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateWebauthn(config, validator)
|
ValidateWebAuthn(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, "Test", config.Webauthn.DisplayName)
|
assert.Equal(t, "Test", config.WebAuthn.DisplayName)
|
||||||
assert.Equal(t, time.Second*50, config.Webauthn.Timeout)
|
assert.Equal(t, time.Second*50, config.WebAuthn.Timeout)
|
||||||
assert.Equal(t, protocol.PreferNoAttestation, config.Webauthn.ConveyancePreference)
|
assert.Equal(t, protocol.PreferNoAttestation, config.WebAuthn.ConveyancePreference)
|
||||||
assert.Equal(t, protocol.VerificationDiscouraged, config.Webauthn.UserVerification)
|
assert.Equal(t, protocol.VerificationDiscouraged, config.WebAuthn.UserVerification)
|
||||||
|
|
||||||
config.Webauthn.ConveyancePreference = protocol.PreferIndirectAttestation
|
config.WebAuthn.ConveyancePreference = protocol.PreferIndirectAttestation
|
||||||
config.Webauthn.UserVerification = protocol.VerificationPreferred
|
config.WebAuthn.UserVerification = protocol.VerificationPreferred
|
||||||
|
|
||||||
ValidateWebauthn(config, validator)
|
ValidateWebAuthn(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, protocol.PreferIndirectAttestation, config.Webauthn.ConveyancePreference)
|
assert.Equal(t, protocol.PreferIndirectAttestation, config.WebAuthn.ConveyancePreference)
|
||||||
assert.Equal(t, protocol.VerificationPreferred, config.Webauthn.UserVerification)
|
assert.Equal(t, protocol.VerificationPreferred, config.WebAuthn.UserVerification)
|
||||||
|
|
||||||
config.Webauthn.ConveyancePreference = protocol.PreferDirectAttestation
|
config.WebAuthn.ConveyancePreference = protocol.PreferDirectAttestation
|
||||||
config.Webauthn.UserVerification = protocol.VerificationRequired
|
config.WebAuthn.UserVerification = protocol.VerificationRequired
|
||||||
|
|
||||||
ValidateWebauthn(config, validator)
|
ValidateWebAuthn(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, protocol.PreferDirectAttestation, config.Webauthn.ConveyancePreference)
|
assert.Equal(t, protocol.PreferDirectAttestation, config.WebAuthn.ConveyancePreference)
|
||||||
assert.Equal(t, protocol.VerificationRequired, config.Webauthn.UserVerification)
|
assert.Equal(t, protocol.VerificationRequired, config.WebAuthn.UserVerification)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebauthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) {
|
func TestWebAuthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := &schema.Configuration{
|
config := &schema.Configuration{
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
DisplayName: "Test",
|
DisplayName: "Test",
|
||||||
Timeout: time.Second * 50,
|
Timeout: time.Second * 50,
|
||||||
ConveyancePreference: "no",
|
ConveyancePreference: "no",
|
||||||
|
@ -89,7 +89,7 @@ func TestWebauthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateWebauthn(config, validator)
|
ValidateWebAuthn(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 2)
|
require.Len(t, validator.Errors(), 2)
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ const (
|
||||||
// ActionTOTPRegistration is the string representation of the action for which the token has been produced.
|
// ActionTOTPRegistration is the string representation of the action for which the token has been produced.
|
||||||
ActionTOTPRegistration = "RegisterTOTPDevice"
|
ActionTOTPRegistration = "RegisterTOTPDevice"
|
||||||
|
|
||||||
// ActionWebauthnRegistration is the string representation of the action for which the token has been produced.
|
// ActionWebAuthnRegistration is the string representation of the action for which the token has been produced.
|
||||||
ActionWebauthnRegistration = "RegisterWebauthnDevice"
|
ActionWebAuthnRegistration = "RegisterWebAuthnDevice"
|
||||||
|
|
||||||
// ActionResetPassword is the string representation of the action for which the token has been produced.
|
// ActionResetPassword is the string representation of the action for which the token has been produced.
|
||||||
ActionResetPassword = "ResetPassword"
|
ActionResetPassword = "ResetPassword"
|
||||||
|
|
|
@ -36,7 +36,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldHaveAllConfiguredMethods
|
||||||
TOTP: schema.TOTPConfiguration{
|
TOTP: schema.TOTPConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
},
|
},
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
},
|
},
|
||||||
AccessControl: schema.AccessControlConfiguration{
|
AccessControl: schema.AccessControlConfiguration{
|
||||||
|
@ -66,7 +66,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveTOTPFromAvailableM
|
||||||
TOTP: schema.TOTPConfiguration{
|
TOTP: schema.TOTPConfiguration{
|
||||||
Disable: true,
|
Disable: true,
|
||||||
},
|
},
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
},
|
},
|
||||||
AccessControl: schema.AccessControlConfiguration{
|
AccessControl: schema.AccessControlConfiguration{
|
||||||
|
@ -88,7 +88,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveTOTPFromAvailableM
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveWebauthnFromAvailableMethodsWhenDisabled() {
|
func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveWebAuthnFromAvailableMethodsWhenDisabled() {
|
||||||
s.mock.Ctx.Configuration = schema.Configuration{
|
s.mock.Ctx.Configuration = schema.Configuration{
|
||||||
DuoAPI: schema.DuoAPIConfiguration{
|
DuoAPI: schema.DuoAPIConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
|
@ -96,7 +96,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveWebauthnFromAvaila
|
||||||
TOTP: schema.TOTPConfiguration{
|
TOTP: schema.TOTPConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
},
|
},
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
Disable: true,
|
Disable: true,
|
||||||
},
|
},
|
||||||
AccessControl: schema.AccessControlConfiguration{
|
AccessControl: schema.AccessControlConfiguration{
|
||||||
|
@ -126,7 +126,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveDuoFromAvailableMe
|
||||||
TOTP: schema.TOTPConfiguration{
|
TOTP: schema.TOTPConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
},
|
},
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
},
|
},
|
||||||
AccessControl: schema.AccessControlConfiguration{
|
AccessControl: schema.AccessControlConfiguration{
|
||||||
|
@ -156,7 +156,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveAllMethodsWhenNoTw
|
||||||
TOTP: schema.TOTPConfiguration{
|
TOTP: schema.TOTPConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
},
|
},
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
Disable: false,
|
Disable: false,
|
||||||
},
|
},
|
||||||
AccessControl: schema.AccessControlConfiguration{
|
AccessControl: schema.AccessControlConfiguration{
|
||||||
|
@ -186,7 +186,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveAllMethodsWhenAllD
|
||||||
TOTP: schema.TOTPConfiguration{
|
TOTP: schema.TOTPConfiguration{
|
||||||
Disable: true,
|
Disable: true,
|
||||||
},
|
},
|
||||||
Webauthn: schema.WebauthnConfiguration{
|
WebAuthn: schema.WebAuthnConfiguration{
|
||||||
Disable: true,
|
Disable: true,
|
||||||
},
|
},
|
||||||
AccessControl: schema.AccessControlConfiguration{
|
AccessControl: schema.AccessControlConfiguration{
|
||||||
|
|
|
@ -18,19 +18,19 @@ var WebauthnIdentityStart = middlewares.IdentityVerificationStart(middlewares.Id
|
||||||
MailTitle: "Register your key",
|
MailTitle: "Register your key",
|
||||||
MailButtonContent: "Register",
|
MailButtonContent: "Register",
|
||||||
TargetEndpoint: "/webauthn/register",
|
TargetEndpoint: "/webauthn/register",
|
||||||
ActionClaim: ActionWebauthnRegistration,
|
ActionClaim: ActionWebAuthnRegistration,
|
||||||
IdentityRetrieverFunc: identityRetrieverFromSession,
|
IdentityRetrieverFunc: identityRetrieverFromSession,
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
// WebauthnIdentityFinish the handler for finishing the identity validation.
|
// WebauthnIdentityFinish the handler for finishing the identity validation.
|
||||||
var WebauthnIdentityFinish = middlewares.IdentityVerificationFinish(
|
var WebauthnIdentityFinish = middlewares.IdentityVerificationFinish(
|
||||||
middlewares.IdentityVerificationFinishArgs{
|
middlewares.IdentityVerificationFinishArgs{
|
||||||
ActionClaim: ActionWebauthnRegistration,
|
ActionClaim: ActionWebAuthnRegistration,
|
||||||
IsTokenUserValidFunc: isTokenUserValidFor2FARegistration,
|
IsTokenUserValidFunc: isTokenUserValidFor2FARegistration,
|
||||||
}, SecondFactorWebauthnAttestationGET)
|
}, SecondFactorWebAuthnAttestationGET)
|
||||||
|
|
||||||
// SecondFactorWebauthnAttestationGET returns the attestation challenge from the server.
|
// SecondFactorWebAuthnAttestationGET returns the attestation challenge from the server.
|
||||||
func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string) {
|
func SecondFactorWebAuthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string) {
|
||||||
var (
|
var (
|
||||||
w *webauthn.WebAuthn
|
w *webauthn.WebAuthn
|
||||||
user *model.WebAuthnUser
|
user *model.WebAuthnUser
|
||||||
|
@ -39,15 +39,15 @@ func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
|
||||||
)
|
)
|
||||||
|
|
||||||
if userSession, err = ctx.GetSession(); err != nil {
|
if userSession, err = ctx.GetSession(); err != nil {
|
||||||
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s attestation challenge", regulation.AuthTypeWebauthn)
|
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s attestation challenge", regulation.AuthTypeWebAuthn)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if w, err = newWebauthn(ctx); err != nil {
|
if w, err = newWebAuthn(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -64,8 +64,8 @@ func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
|
||||||
|
|
||||||
var credentialCreation *protocol.CredentialCreation
|
var credentialCreation *protocol.CredentialCreation
|
||||||
|
|
||||||
if credentialCreation, userSession.Webauthn, err = w.BeginRegistration(user); err != nil {
|
if credentialCreation, userSession.WebAuthn, err = w.BeginRegistration(user); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.SaveSession(userSession); err != nil {
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "attestation challenge", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "attestation challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.SetJSONBody(credentialCreation); err != nil {
|
if err = ctx.SetJSONBody(credentialCreation); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
|
@ -89,8 +89,8 @@ func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebauthnAttestationPOST processes the attestation challenge response from the client.
|
// WebAuthnAttestationPOST processes the attestation challenge response from the client.
|
||||||
func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
|
func WebAuthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
w *webauthn.WebAuthn
|
w *webauthn.WebAuthn
|
||||||
|
@ -103,23 +103,23 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
)
|
)
|
||||||
|
|
||||||
if userSession, err = ctx.GetSession(); err != nil {
|
if userSession, err = ctx.GetSession(); err != nil {
|
||||||
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s attestation response", regulation.AuthTypeWebauthn)
|
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s attestation response", regulation.AuthTypeWebAuthn)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if userSession.Webauthn == nil {
|
if userSession.WebAuthn == nil {
|
||||||
ctx.Logger.Errorf("Webauthn session data is not present in order to handle attestation for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
|
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle attestation for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if w, err = newWebauthn(ctx); err != nil {
|
if w, err = newWebAuthn(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if attestationResponse, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
|
if attestationResponse, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -135,15 +135,15 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if credential, err = w.CreateCredential(user, *userSession.Webauthn, attestationResponse); err != nil {
|
if credential, err = w.CreateCredential(user, *userSession.WebAuthn, attestationResponse); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -152,21 +152,21 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
device := model.NewWebAuthnDeviceFromCredential(w.Config.RPID, userSession.Username, "Primary", credential)
|
device := model.NewWebAuthnDeviceFromCredential(w.Config.RPID, userSession.Username, "Primary", credential)
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.SaveWebauthnDevice(ctx, device); err != nil {
|
if err = ctx.Providers.StorageProvider.SaveWebAuthnDevice(ctx, device); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession.Webauthn = nil
|
userSession.WebAuthn = nil
|
||||||
if err = ctx.SaveSession(userSession); err != nil {
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the attestation challenge", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the attestation challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ReplyOK()
|
ctx.ReplyOK()
|
||||||
ctx.SetStatusCode(fasthttp.StatusCreated)
|
ctx.SetStatusCode(fasthttp.StatusCreated)
|
||||||
|
|
||||||
ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "Webauthn Credential", "Device Name": "Primary"})
|
ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "WebAuthn Credential", "Device Name": "Primary"})
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebauthnAssertionGET handler starts the assertion ceremony.
|
// WebAuthnAssertionGET handler starts the assertion ceremony.
|
||||||
func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
var (
|
var (
|
||||||
w *webauthn.WebAuthn
|
w *webauthn.WebAuthn
|
||||||
user *model.WebAuthnUser
|
user *model.WebAuthnUser
|
||||||
|
@ -29,8 +29,8 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if w, err = newWebauthn(ctx); err != nil {
|
if w, err = newWebAuthn(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -61,8 +61,8 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
var assertion *protocol.CredentialAssertion
|
var assertion *protocol.CredentialAssertion
|
||||||
|
|
||||||
if assertion, userSession.Webauthn, err = w.BeginLogin(user, opts...); err != nil {
|
if assertion, userSession.WebAuthn, err = w.BeginLogin(user, opts...); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.SaveSession(userSession); err != nil {
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.SetJSONBody(assertion); err != nil {
|
if err = ctx.SetJSONBody(assertion); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -86,21 +86,21 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebauthnAssertionPOST handler completes the assertion ceremony after verifying the challenge.
|
// WebAuthnAssertionPOST handler completes the assertion ceremony after verifying the challenge.
|
||||||
//
|
//
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
var (
|
var (
|
||||||
userSession session.UserSession
|
userSession session.UserSession
|
||||||
|
|
||||||
err error
|
err error
|
||||||
w *webauthn.WebAuthn
|
w *webauthn.WebAuthn
|
||||||
|
|
||||||
bodyJSON bodySignWebauthnRequest
|
bodyJSON bodySignWebAuthnRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
if err = ctx.ParseBody(&bodyJSON); err != nil {
|
if err = ctx.ParseBody(&bodyJSON); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeWebauthn, err)
|
ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeWebAuthn, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -115,16 +115,16 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if userSession.Webauthn == nil {
|
if userSession.WebAuthn == nil {
|
||||||
ctx.Logger.Errorf("Webauthn session data is not present in order to handle assertion for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
|
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle assertion for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if w, err = newWebauthn(ctx); err != nil {
|
if w, err = newWebAuthn(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
)
|
)
|
||||||
|
|
||||||
if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
|
if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -146,15 +146,15 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if credential, err = w.ValidateLogin(user, *userSession.Webauthn, assertionResponse); err != nil {
|
if credential, err = w.ValidateLogin(user, *userSession.WebAuthn, assertionResponse); err != nil {
|
||||||
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebauthn, err)
|
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebAuthn, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -169,8 +169,8 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.UpdateWebauthnDeviceSignIn(ctx, device.ID, device.RPID, device.LastUsedAt, device.SignCount, device.CloneWarning); err != nil {
|
if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceSignIn(ctx, device.ID, device.RPID, device.LastUsedAt, device.SignCount, device.CloneWarning); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s' device '%x' count '%d': unable to find device", regulation.AuthTypeWebauthn, userSession.Username, credential.ID, credential.Authenticator.SignCount)
|
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s' device '%x' count '%d': unable to find device", regulation.AuthTypeWebAuthn, userSession.Username, credential.ID, credential.Authenticator.SignCount)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -190,25 +190,25 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.RegenerateSession(); err != nil {
|
if err = ctx.RegenerateSession(); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = markAuthenticationAttempt(ctx, true, nil, userSession.Username, regulation.AuthTypeWebauthn, nil); err != nil {
|
if err = markAuthenticationAttempt(ctx, true, nil, userSession.Username, regulation.AuthTypeWebAuthn, nil); err != nil {
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession.SetTwoFactorWebauthn(ctx.Clock.Now(),
|
userSession.SetTwoFactorWebAuthn(ctx.Clock.Now(),
|
||||||
assertionResponse.Response.AuthenticatorData.Flags.UserPresent(),
|
assertionResponse.Response.AuthenticatorData.Flags.UserPresent(),
|
||||||
assertionResponse.Response.AuthenticatorData.Flags.UserVerified())
|
assertionResponse.Response.AuthenticatorData.Flags.UserVerified())
|
||||||
|
|
||||||
if err = ctx.SaveSession(userSession); err != nil {
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the assertion challenge and authentication time", regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the assertion challenge and authentication time", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,8 @@ type bodySignTOTPRequest struct {
|
||||||
WorkflowID string `json:"workflowID"`
|
WorkflowID string `json:"workflowID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// bodySignWebauthnRequest is the model of the request body of WebAuthn 2FA authentication endpoint.
|
// bodySignWebAuthnRequest is the model of the request body of WebAuthn 2FA authentication endpoint.
|
||||||
type bodySignWebauthnRequest struct {
|
type bodySignWebAuthnRequest struct {
|
||||||
TargetURL string `json:"targetURL"`
|
TargetURL string `json:"targetURL"`
|
||||||
Workflow string `json:"workflow"`
|
Workflow string `json:"workflow"`
|
||||||
WorkflowID string `json:"workflowID"`
|
WorkflowID string `json:"workflowID"`
|
||||||
|
|
|
@ -22,14 +22,14 @@ func getWebAuthnUser(ctx *middlewares.AutheliaCtx, userSession session.UserSessi
|
||||||
user.DisplayName = user.Username
|
user.DisplayName = user.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Devices, err = ctx.Providers.StorageProvider.LoadWebauthnDevicesByUsername(ctx, userSession.Username); err != nil {
|
if user.Devices, err = ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, userSession.Username); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWebauthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error) {
|
func newWebAuthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error) {
|
||||||
var (
|
var (
|
||||||
u *url.URL
|
u *url.URL
|
||||||
)
|
)
|
||||||
|
@ -42,22 +42,22 @@ func newWebauthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error)
|
||||||
origin := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
|
origin := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
|
||||||
|
|
||||||
config := &webauthn.Config{
|
config := &webauthn.Config{
|
||||||
RPDisplayName: ctx.Configuration.Webauthn.DisplayName,
|
RPDisplayName: ctx.Configuration.WebAuthn.DisplayName,
|
||||||
RPID: rpID,
|
RPID: rpID,
|
||||||
RPOrigin: origin,
|
RPOrigin: origin,
|
||||||
RPIcon: "",
|
RPIcon: "",
|
||||||
|
|
||||||
AttestationPreference: ctx.Configuration.Webauthn.ConveyancePreference,
|
AttestationPreference: ctx.Configuration.WebAuthn.ConveyancePreference,
|
||||||
AuthenticatorSelection: protocol.AuthenticatorSelection{
|
AuthenticatorSelection: protocol.AuthenticatorSelection{
|
||||||
AuthenticatorAttachment: protocol.CrossPlatform,
|
AuthenticatorAttachment: protocol.CrossPlatform,
|
||||||
UserVerification: ctx.Configuration.Webauthn.UserVerification,
|
UserVerification: ctx.Configuration.WebAuthn.UserVerification,
|
||||||
RequireResidentKey: protocol.ResidentKeyNotRequired(),
|
RequireResidentKey: protocol.ResidentKeyNotRequired(),
|
||||||
},
|
},
|
||||||
|
|
||||||
Timeout: int(ctx.Configuration.Webauthn.Timeout.Milliseconds()),
|
Timeout: int(ctx.Configuration.WebAuthn.Timeout.Milliseconds()),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Tracef("Creating new Webauthn RP instance with ID %s and Origins %s", config.RPID, config.RPOrigin)
|
ctx.Logger.Tracef("Creating new WebAuthn RP instance with ID %s and Origins %s", config.RPID, config.RPOrigin)
|
||||||
|
|
||||||
return webauthn.New(config)
|
return webauthn.New(config)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWebauthnGetUser(t *testing.T) {
|
func TestWebAuthnGetUser(t *testing.T) {
|
||||||
ctx := mocks.NewMockAutheliaCtx(t)
|
ctx := mocks.NewMockAutheliaCtx(t)
|
||||||
|
|
||||||
userSession := session.UserSession{
|
userSession := session.UserSession{
|
||||||
|
@ -21,7 +21,7 @@ func TestWebauthnGetUser(t *testing.T) {
|
||||||
DisplayName: "John Smith",
|
DisplayName: "John Smith",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
RPID: "https://example.com",
|
RPID: "https://example.com",
|
||||||
|
@ -99,14 +99,14 @@ func TestWebauthnGetUser(t *testing.T) {
|
||||||
assert.Equal(t, protocol.AuthenticatorTransport("nfc"), descriptors[1].Transport[1])
|
assert.Equal(t, protocol.AuthenticatorTransport("nfc"), descriptors[1].Transport[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebauthnGetUserWithoutDisplayName(t *testing.T) {
|
func TestWebAuthnGetUserWithoutDisplayName(t *testing.T) {
|
||||||
ctx := mocks.NewMockAutheliaCtx(t)
|
ctx := mocks.NewMockAutheliaCtx(t)
|
||||||
|
|
||||||
userSession := session.UserSession{
|
userSession := session.UserSession{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
RPID: "https://example.com",
|
RPID: "https://example.com",
|
||||||
|
@ -129,14 +129,14 @@ func TestWebauthnGetUserWithoutDisplayName(t *testing.T) {
|
||||||
assert.Equal(t, "john", user.DisplayName)
|
assert.Equal(t, "john", user.DisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebauthnGetUserWithErr(t *testing.T) {
|
func TestWebAuthnGetUserWithErr(t *testing.T) {
|
||||||
ctx := mocks.NewMockAutheliaCtx(t)
|
ctx := mocks.NewMockAutheliaCtx(t)
|
||||||
|
|
||||||
userSession := session.UserSession{
|
userSession := session.UserSession{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "john").Return(nil, errors.New("not found"))
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return(nil, errors.New("not found"))
|
||||||
|
|
||||||
user, err := getWebAuthnUser(ctx.Ctx, userSession)
|
user, err := getWebAuthnUser(ctx.Ctx, userSession)
|
||||||
|
|
||||||
|
@ -144,24 +144,24 @@ func TestWebauthnGetUserWithErr(t *testing.T) {
|
||||||
assert.Nil(t, user)
|
assert.Nil(t, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebauthnNewWebauthnShouldReturnErrWhenHeadersNotAvailable(t *testing.T) {
|
func TestWebAuthnNewWebAuthnShouldReturnErrWhenHeadersNotAvailable(t *testing.T) {
|
||||||
ctx := mocks.NewMockAutheliaCtx(t)
|
ctx := mocks.NewMockAutheliaCtx(t)
|
||||||
ctx.Ctx.Request.Header.Del("X-Forwarded-Host")
|
ctx.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||||
|
|
||||||
w, err := newWebauthn(ctx.Ctx)
|
w, err := newWebAuthn(ctx.Ctx)
|
||||||
|
|
||||||
assert.Nil(t, w)
|
assert.Nil(t, w)
|
||||||
assert.EqualError(t, err, "missing required X-Forwarded-Host header")
|
assert.EqualError(t, err, "missing required X-Forwarded-Host header")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebauthnNewWebauthnShouldReturnErrWhenWebauthnNotConfigured(t *testing.T) {
|
func TestWebAuthnNewWebAuthnShouldReturnErrWhenWebAuthnNotConfigured(t *testing.T) {
|
||||||
ctx := mocks.NewMockAutheliaCtx(t)
|
ctx := mocks.NewMockAutheliaCtx(t)
|
||||||
|
|
||||||
ctx.Ctx.Request.Header.Set("X-Forwarded-Host", "example.com")
|
ctx.Ctx.Request.Header.Set("X-Forwarded-Host", "example.com")
|
||||||
ctx.Ctx.Request.Header.Set("X-Forwarded-URI", "/")
|
ctx.Ctx.Request.Header.Set("X-Forwarded-URI", "/")
|
||||||
ctx.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
ctx.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
||||||
|
|
||||||
w, err := newWebauthn(ctx.Ctx)
|
w, err := newWebAuthn(ctx.Ctx)
|
||||||
|
|
||||||
assert.Nil(t, w)
|
assert.Nil(t, w)
|
||||||
assert.EqualError(t, err, "Configuration error: Missing RPDisplayName")
|
assert.EqualError(t, err, "Configuration error: Missing RPDisplayName")
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (ctx *AutheliaCtx) AvailableSecondFactorMethods() (methods []string) {
|
||||||
methods = append(methods, model.SecondFactorMethodTOTP)
|
methods = append(methods, model.SecondFactorMethodTOTP)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Configuration.Webauthn.Disable {
|
if !ctx.Configuration.WebAuthn.Disable {
|
||||||
methods = append(methods, model.SecondFactorMethodWebAuthn)
|
methods = append(methods, model.SecondFactorMethodWebAuthn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,7 +245,7 @@ func TestShouldReturnCorrectSecondFactorMethods(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, []string{model.SecondFactorMethodWebAuthn, model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
|
assert.Equal(t, []string{model.SecondFactorMethodWebAuthn, model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
|
||||||
|
|
||||||
mock.Ctx.Configuration.Webauthn.Disable = true
|
mock.Ctx.Configuration.WebAuthn.Disable = true
|
||||||
|
|
||||||
assert.Equal(t, []string{model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
|
assert.Equal(t, []string{model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ func (m *MockStorage) EXPECT() *MockStorageMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// AppendAuthenticationLog mocks base method.
|
// AppendAuthenticationLog mocks base method.
|
||||||
func (m *MockStorage) AppendAuthenticationLog(arg0 context.Context, arg1 model.AuthenticationAttempt) error {
|
func (m *MockStorage) AppendAuthenticationLog(arg0 context.Context, arg1 model.AuthenticationAttempt) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -167,32 +166,32 @@ func (mr *MockStorageMockRecorder) DeleteTOTPConfiguration(arg0, arg1 interface{
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTOTPConfiguration", reflect.TypeOf((*MockStorage)(nil).DeleteTOTPConfiguration), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTOTPConfiguration", reflect.TypeOf((*MockStorage)(nil).DeleteTOTPConfiguration), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWebauthnDevice mocks base method.
|
// DeleteWebAuthnDevice mocks base method.
|
||||||
func (m *MockStorage) DeleteWebauthnDevice(arg0 context.Context, arg1 string) error {
|
func (m *MockStorage) DeleteWebAuthnDevice(arg0 context.Context, arg1 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DeleteWebauthnDevice", arg0, arg1)
|
ret := m.ctrl.Call(m, "DeleteWebAuthnDevice", arg0, arg1)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWebauthnDevice indicates an expected call of DeleteWebauthnDevice.
|
// DeleteWebAuthnDevice indicates an expected call of DeleteWebAuthnDevice.
|
||||||
func (mr *MockStorageMockRecorder) DeleteWebauthnDevice(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockStorageMockRecorder) DeleteWebAuthnDevice(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWebauthnDevice", reflect.TypeOf((*MockStorage)(nil).DeleteWebauthnDevice), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWebAuthnDevice", reflect.TypeOf((*MockStorage)(nil).DeleteWebAuthnDevice), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWebauthnDeviceByUsername mocks base method.
|
// DeleteWebAuthnDeviceByUsername mocks base method.
|
||||||
func (m *MockStorage) DeleteWebauthnDeviceByUsername(arg0 context.Context, arg1, arg2 string) error {
|
func (m *MockStorage) DeleteWebAuthnDeviceByUsername(arg0 context.Context, arg1, arg2 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DeleteWebauthnDeviceByUsername", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "DeleteWebAuthnDeviceByUsername", arg0, arg1, arg2)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWebauthnDeviceByUsername indicates an expected call of DeleteWebauthnDeviceByUsername.
|
// DeleteWebAuthnDeviceByUsername indicates an expected call of DeleteWebAuthnDeviceByUsername.
|
||||||
func (mr *MockStorageMockRecorder) DeleteWebauthnDeviceByUsername(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockStorageMockRecorder) DeleteWebAuthnDeviceByUsername(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWebauthnDeviceByUsername", reflect.TypeOf((*MockStorage)(nil).DeleteWebauthnDeviceByUsername), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWebAuthnDeviceByUsername", reflect.TypeOf((*MockStorage)(nil).DeleteWebAuthnDeviceByUsername), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindIdentityVerification mocks base method.
|
// FindIdentityVerification mocks base method.
|
||||||
|
@ -420,34 +419,34 @@ func (mr *MockStorageMockRecorder) LoadUserOpaqueIdentifiers(arg0 interface{}) *
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadUserOpaqueIdentifiers", reflect.TypeOf((*MockStorage)(nil).LoadUserOpaqueIdentifiers), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadUserOpaqueIdentifiers", reflect.TypeOf((*MockStorage)(nil).LoadUserOpaqueIdentifiers), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadWebauthnDevices mocks base method.
|
// LoadWebAuthnDevices mocks base method.
|
||||||
func (m *MockStorage) LoadWebauthnDevices(arg0 context.Context, arg1, arg2 int) ([]model.WebAuthnDevice, error) {
|
func (m *MockStorage) LoadWebAuthnDevices(arg0 context.Context, arg1, arg2 int) ([]model.WebAuthnDevice, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "LoadWebauthnDevices", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "LoadWebAuthnDevices", arg0, arg1, arg2)
|
||||||
ret0, _ := ret[0].([]model.WebAuthnDevice)
|
ret0, _ := ret[0].([]model.WebAuthnDevice)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadWebauthnDevices indicates an expected call of LoadWebauthnDevices.
|
// LoadWebAuthnDevices indicates an expected call of LoadWebAuthnDevices.
|
||||||
func (mr *MockStorageMockRecorder) LoadWebauthnDevices(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockStorageMockRecorder) LoadWebAuthnDevices(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebauthnDevices", reflect.TypeOf((*MockStorage)(nil).LoadWebauthnDevices), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebAuthnDevices", reflect.TypeOf((*MockStorage)(nil).LoadWebAuthnDevices), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadWebauthnDevicesByUsername mocks base method.
|
// LoadWebAuthnDevicesByUsername mocks base method.
|
||||||
func (m *MockStorage) LoadWebauthnDevicesByUsername(arg0 context.Context, arg1 string) ([]model.WebAuthnDevice, error) {
|
func (m *MockStorage) LoadWebAuthnDevicesByUsername(arg0 context.Context, arg1 string) ([]model.WebAuthnDevice, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "LoadWebauthnDevicesByUsername", arg0, arg1)
|
ret := m.ctrl.Call(m, "LoadWebAuthnDevicesByUsername", arg0, arg1)
|
||||||
ret0, _ := ret[0].([]model.WebAuthnDevice)
|
ret0, _ := ret[0].([]model.WebAuthnDevice)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadWebauthnDevicesByUsername indicates an expected call of LoadWebauthnDevicesByUsername.
|
// LoadWebAuthnDevicesByUsername indicates an expected call of LoadWebAuthnDevicesByUsername.
|
||||||
func (mr *MockStorageMockRecorder) LoadWebauthnDevicesByUsername(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockStorageMockRecorder) LoadWebAuthnDevicesByUsername(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebauthnDevicesByUsername", reflect.TypeOf((*MockStorage)(nil).LoadWebauthnDevicesByUsername), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebAuthnDevicesByUsername", reflect.TypeOf((*MockStorage)(nil).LoadWebAuthnDevicesByUsername), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeOAuth2PARContext mocks base method.
|
// RevokeOAuth2PARContext mocks base method.
|
||||||
|
@ -689,18 +688,18 @@ func (mr *MockStorageMockRecorder) SaveUserOpaqueIdentifier(arg0, arg1 interface
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveUserOpaqueIdentifier", reflect.TypeOf((*MockStorage)(nil).SaveUserOpaqueIdentifier), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveUserOpaqueIdentifier", reflect.TypeOf((*MockStorage)(nil).SaveUserOpaqueIdentifier), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveWebauthnDevice mocks base method.
|
// SaveWebAuthnDevice mocks base method.
|
||||||
func (m *MockStorage) SaveWebauthnDevice(arg0 context.Context, arg1 model.WebAuthnDevice) error {
|
func (m *MockStorage) SaveWebAuthnDevice(arg0 context.Context, arg1 model.WebAuthnDevice) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SaveWebauthnDevice", arg0, arg1)
|
ret := m.ctrl.Call(m, "SaveWebAuthnDevice", arg0, arg1)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveWebauthnDevice indicates an expected call of SaveWebauthnDevice.
|
// SaveWebAuthnDevice indicates an expected call of SaveWebAuthnDevice.
|
||||||
func (mr *MockStorageMockRecorder) SaveWebauthnDevice(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockStorageMockRecorder) SaveWebAuthnDevice(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveWebauthnDevice", reflect.TypeOf((*MockStorage)(nil).SaveWebauthnDevice), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveWebAuthnDevice", reflect.TypeOf((*MockStorage)(nil).SaveWebAuthnDevice), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemaEncryptionChangeKey mocks base method.
|
// SchemaEncryptionChangeKey mocks base method.
|
||||||
|
@ -864,16 +863,16 @@ func (mr *MockStorageMockRecorder) UpdateTOTPConfigurationSignIn(arg0, arg1, arg
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTOTPConfigurationSignIn", reflect.TypeOf((*MockStorage)(nil).UpdateTOTPConfigurationSignIn), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTOTPConfigurationSignIn", reflect.TypeOf((*MockStorage)(nil).UpdateTOTPConfigurationSignIn), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateWebauthnDeviceSignIn mocks base method.
|
// UpdateWebAuthnDeviceSignIn mocks base method.
|
||||||
func (m *MockStorage) UpdateWebauthnDeviceSignIn(arg0 context.Context, arg1 int, arg2 string, arg3 sql.NullTime, arg4 uint32, arg5 bool) error {
|
func (m *MockStorage) UpdateWebAuthnDeviceSignIn(arg0 context.Context, arg1 int, arg2 string, arg3 sql.NullTime, arg4 uint32, arg5 bool) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UpdateWebauthnDeviceSignIn", arg0, arg1, arg2, arg3, arg4, arg5)
|
ret := m.ctrl.Call(m, "UpdateWebAuthnDeviceSignIn", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateWebauthnDeviceSignIn indicates an expected call of UpdateWebauthnDeviceSignIn.
|
// UpdateWebAuthnDeviceSignIn indicates an expected call of UpdateWebAuthnDeviceSignIn.
|
||||||
func (mr *MockStorageMockRecorder) UpdateWebauthnDeviceSignIn(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
func (mr *MockStorageMockRecorder) UpdateWebAuthnDeviceSignIn(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWebauthnDeviceSignIn", reflect.TypeOf((*MockStorage)(nil).UpdateWebauthnDeviceSignIn), arg0, arg1, arg2, arg3, arg4, arg5)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWebAuthnDeviceSignIn", reflect.TypeOf((*MockStorage)(nil).UpdateWebAuthnDeviceSignIn), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ type AuthenticationMethodsReferences struct {
|
||||||
UsernameAndPassword bool
|
UsernameAndPassword bool
|
||||||
TOTP bool
|
TOTP bool
|
||||||
Duo bool
|
Duo bool
|
||||||
Webauthn bool
|
WebAuthn bool
|
||||||
WebauthnUserPresence bool
|
WebAuthnUserPresence bool
|
||||||
WebauthnUserVerified bool
|
WebAuthnUserVerified bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FactorKnowledge returns true if a "something you know" factor of authentication was used.
|
// FactorKnowledge returns true if a "something you know" factor of authentication was used.
|
||||||
|
@ -17,7 +17,7 @@ func (r AuthenticationMethodsReferences) FactorKnowledge() bool {
|
||||||
|
|
||||||
// FactorPossession returns true if a "something you have" factor of authentication was used.
|
// FactorPossession returns true if a "something you have" factor of authentication was used.
|
||||||
func (r AuthenticationMethodsReferences) FactorPossession() bool {
|
func (r AuthenticationMethodsReferences) FactorPossession() bool {
|
||||||
return r.TOTP || r.Webauthn || r.Duo
|
return r.TOTP || r.WebAuthn || r.Duo
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultiFactorAuthentication returns true if multiple factors were used.
|
// MultiFactorAuthentication returns true if multiple factors were used.
|
||||||
|
@ -27,7 +27,7 @@ func (r AuthenticationMethodsReferences) MultiFactorAuthentication() bool {
|
||||||
|
|
||||||
// ChannelBrowser returns true if a browser was used to authenticate.
|
// ChannelBrowser returns true if a browser was used to authenticate.
|
||||||
func (r AuthenticationMethodsReferences) ChannelBrowser() bool {
|
func (r AuthenticationMethodsReferences) ChannelBrowser() bool {
|
||||||
return r.UsernameAndPassword || r.TOTP || r.Webauthn
|
return r.UsernameAndPassword || r.TOTP || r.WebAuthn
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelService returns true if a non-browser service was used to authenticate.
|
// ChannelService returns true if a non-browser service was used to authenticate.
|
||||||
|
@ -57,15 +57,15 @@ func (r AuthenticationMethodsReferences) MarshalRFC8176() []string {
|
||||||
amr = append(amr, AMRShortMessageService)
|
amr = append(amr, AMRShortMessageService)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Webauthn {
|
if r.WebAuthn {
|
||||||
amr = append(amr, AMRHardwareSecuredKey)
|
amr = append(amr, AMRHardwareSecuredKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.WebauthnUserPresence {
|
if r.WebAuthnUserPresence {
|
||||||
amr = append(amr, AMRUserPresence)
|
amr = append(amr, AMRUserPresence)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.WebauthnUserVerified {
|
if r.WebAuthnUserVerified {
|
||||||
amr = append(amr, AMRPersonalIdentificationNumber)
|
amr = append(amr, AMRPersonalIdentificationNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,9 +49,9 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Webauthn",
|
desc: "WebAuthn",
|
||||||
|
|
||||||
is: AuthenticationMethodsReferences{Webauthn: true},
|
is: AuthenticationMethodsReferences{WebAuthn: true},
|
||||||
want: testAMRWant{
|
want: testAMRWant{
|
||||||
FactorKnowledge: false,
|
FactorKnowledge: false,
|
||||||
FactorPossession: true,
|
FactorPossession: true,
|
||||||
|
@ -63,9 +63,9 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Webauthn User Presence",
|
desc: "WebAuthn User Presence",
|
||||||
|
|
||||||
is: AuthenticationMethodsReferences{WebauthnUserPresence: true},
|
is: AuthenticationMethodsReferences{WebAuthnUserPresence: true},
|
||||||
want: testAMRWant{
|
want: testAMRWant{
|
||||||
FactorKnowledge: false,
|
FactorKnowledge: false,
|
||||||
FactorPossession: false,
|
FactorPossession: false,
|
||||||
|
@ -77,9 +77,9 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Webauthn User Verified",
|
desc: "WebAuthn User Verified",
|
||||||
|
|
||||||
is: AuthenticationMethodsReferences{WebauthnUserVerified: true},
|
is: AuthenticationMethodsReferences{WebAuthnUserVerified: true},
|
||||||
want: testAMRWant{
|
want: testAMRWant{
|
||||||
FactorKnowledge: false,
|
FactorKnowledge: false,
|
||||||
FactorPossession: false,
|
FactorPossession: false,
|
||||||
|
@ -91,9 +91,9 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Webauthn with User Presence and Verified",
|
desc: "WebAuthn with User Presence and Verified",
|
||||||
|
|
||||||
is: AuthenticationMethodsReferences{Webauthn: true, WebauthnUserVerified: true, WebauthnUserPresence: true},
|
is: AuthenticationMethodsReferences{WebAuthn: true, WebAuthnUserVerified: true, WebAuthnUserPresence: true},
|
||||||
want: testAMRWant{
|
want: testAMRWant{
|
||||||
FactorKnowledge: false,
|
FactorKnowledge: false,
|
||||||
FactorPossession: true,
|
FactorPossession: true,
|
||||||
|
@ -119,9 +119,9 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Duo Webauthn TOTP",
|
desc: "Duo WebAuthn TOTP",
|
||||||
|
|
||||||
is: AuthenticationMethodsReferences{Duo: true, Webauthn: true, TOTP: true},
|
is: AuthenticationMethodsReferences{Duo: true, WebAuthn: true, TOTP: true},
|
||||||
want: testAMRWant{
|
want: testAMRWant{
|
||||||
FactorKnowledge: false,
|
FactorKnowledge: false,
|
||||||
FactorPossession: true,
|
FactorPossession: true,
|
||||||
|
|
|
@ -195,7 +195,7 @@ const (
|
||||||
// a user presence test. Evidence that the end user is present and interacting with the device. This is sometimes
|
// a user presence test. Evidence that the end user is present and interacting with the device. This is sometimes
|
||||||
// also referred to as "test of user presence" as per W3C.WD-webauthn-20170216.
|
// also referred to as "test of user presence" as per W3C.WD-webauthn-20170216.
|
||||||
//
|
//
|
||||||
// Authelia utilizes this when a user has used Webauthn to authenticate and the user presence flag was set.
|
// Authelia utilizes this when a user has used WebAuthn to authenticate and the user presence flag was set.
|
||||||
// Factor: Meta, Channel: Meta.
|
// Factor: Meta, Channel: Meta.
|
||||||
//
|
//
|
||||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||||
|
@ -208,7 +208,7 @@ const (
|
||||||
// containing only numbers) that a user enters to unlock a key on the device. This mechanism should have a way to
|
// containing only numbers) that a user enters to unlock a key on the device. This mechanism should have a way to
|
||||||
// deter an attacker from obtaining the PIN by trying repeated guesses.
|
// deter an attacker from obtaining the PIN by trying repeated guesses.
|
||||||
//
|
//
|
||||||
// Authelia utilizes this when a user has used Webauthn to authenticate and the user verified flag was set.
|
// Authelia utilizes this when a user has used WebAuthn to authenticate and the user verified flag was set.
|
||||||
// Factor: Meta, Channel: Meta.
|
// Factor: Meta, Channel: Meta.
|
||||||
//
|
//
|
||||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||||
|
@ -244,7 +244,7 @@ const (
|
||||||
// AMRHardwareSecuredKey is an RFC8176 Authentication Method Reference Value that
|
// AMRHardwareSecuredKey is an RFC8176 Authentication Method Reference Value that
|
||||||
// represents authentication via a proof-of-Possession (PoP) of a hardware-secured key.
|
// represents authentication via a proof-of-Possession (PoP) of a hardware-secured key.
|
||||||
//
|
//
|
||||||
// Authelia utilizes this when a user has used Webauthn to authenticate. Factor: Have, Channel: Browser.
|
// Authelia utilizes this when a user has used WebAuthn to authenticate. Factor: Have, Channel: Browser.
|
||||||
//
|
//
|
||||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||||
AMRHardwareSecuredKey = "hwk"
|
AMRHardwareSecuredKey = "hwk"
|
||||||
|
|
|
@ -12,8 +12,8 @@ const (
|
||||||
// AuthTypeTOTP is the string representing an auth log for second-factor authentication via TOTP.
|
// AuthTypeTOTP is the string representing an auth log for second-factor authentication via TOTP.
|
||||||
AuthTypeTOTP = "TOTP"
|
AuthTypeTOTP = "TOTP"
|
||||||
|
|
||||||
// AuthTypeWebauthn is the string representing an auth log for second-factor authentication via FIDO2/CTAP2/WebAuthn.
|
// AuthTypeWebAuthn is the string representing an auth log for second-factor authentication via FIDO2/CTAP2/WebAuthn.
|
||||||
AuthTypeWebauthn = "Webauthn"
|
AuthTypeWebAuthn = "WebAuthn"
|
||||||
|
|
||||||
// AuthTypeDuo is the string representing an auth log for second-factor authentication via DUO.
|
// AuthTypeDuo is the string representing an auth log for second-factor authentication via DUO.
|
||||||
AuthTypeDuo = "Duo"
|
AuthTypeDuo = "Duo"
|
||||||
|
|
|
@ -255,14 +255,14 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
|
||||||
r.POST("/api/secondfactor/totp", middleware1FA(handlers.TimeBasedOneTimePasswordPOST))
|
r.POST("/api/secondfactor/totp", middleware1FA(handlers.TimeBasedOneTimePasswordPOST))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.Webauthn.Disable {
|
if !config.WebAuthn.Disable {
|
||||||
// Webauthn Endpoints.
|
// WebAuthn Endpoints.
|
||||||
r.POST("/api/secondfactor/webauthn/identity/start", middleware1FA(handlers.WebauthnIdentityStart))
|
r.POST("/api/secondfactor/webauthn/identity/start", middleware1FA(handlers.WebauthnIdentityStart))
|
||||||
r.POST("/api/secondfactor/webauthn/identity/finish", middleware1FA(handlers.WebauthnIdentityFinish))
|
r.POST("/api/secondfactor/webauthn/identity/finish", middleware1FA(handlers.WebauthnIdentityFinish))
|
||||||
r.POST("/api/secondfactor/webauthn/attestation", middleware1FA(handlers.WebauthnAttestationPOST))
|
r.POST("/api/secondfactor/webauthn/attestation", middleware1FA(handlers.WebAuthnAttestationPOST))
|
||||||
|
|
||||||
r.GET("/api/secondfactor/webauthn/assertion", middleware1FA(handlers.WebauthnAssertionGET))
|
r.GET("/api/secondfactor/webauthn/assertion", middleware1FA(handlers.WebAuthnAssertionGET))
|
||||||
r.POST("/api/secondfactor/webauthn/assertion", middleware1FA(handlers.WebauthnAssertionPOST))
|
r.POST("/api/secondfactor/webauthn/assertion", middleware1FA(handlers.WebAuthnAssertionPOST))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure DUO api endpoint only if configuration exists.
|
// Configure DUO api endpoint only if configuration exists.
|
||||||
|
|
|
@ -272,7 +272,7 @@ func NewTemplatedFileOptions(config *schema.Configuration) (opts *TemplatedFileO
|
||||||
Theme: config.Theme,
|
Theme: config.Theme,
|
||||||
|
|
||||||
EndpointsPasswordReset: !(config.AuthenticationBackend.PasswordReset.Disable || config.AuthenticationBackend.PasswordReset.CustomURL.String() != ""),
|
EndpointsPasswordReset: !(config.AuthenticationBackend.PasswordReset.Disable || config.AuthenticationBackend.PasswordReset.CustomURL.String() != ""),
|
||||||
EndpointsWebauthn: !config.Webauthn.Disable,
|
EndpointsWebAuthn: !config.WebAuthn.Disable,
|
||||||
EndpointsTOTP: !config.TOTP.Disable,
|
EndpointsTOTP: !config.TOTP.Disable,
|
||||||
EndpointsDuo: !config.DuoAPI.Disable,
|
EndpointsDuo: !config.DuoAPI.Disable,
|
||||||
EndpointsOpenIDConnect: !(config.IdentityProviders.OIDC == nil),
|
EndpointsOpenIDConnect: !(config.IdentityProviders.OIDC == nil),
|
||||||
|
@ -304,7 +304,7 @@ type TemplatedFileOptions struct {
|
||||||
Theme string
|
Theme string
|
||||||
|
|
||||||
EndpointsPasswordReset bool
|
EndpointsPasswordReset bool
|
||||||
EndpointsWebauthn bool
|
EndpointsWebAuthn bool
|
||||||
EndpointsTOTP bool
|
EndpointsTOTP bool
|
||||||
EndpointsDuo bool
|
EndpointsDuo bool
|
||||||
EndpointsOpenIDConnect bool
|
EndpointsOpenIDConnect bool
|
||||||
|
@ -362,7 +362,7 @@ func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, domain, nonce st
|
||||||
|
|
||||||
Session: options.Session,
|
Session: options.Session,
|
||||||
PasswordReset: options.EndpointsPasswordReset,
|
PasswordReset: options.EndpointsPasswordReset,
|
||||||
Webauthn: options.EndpointsWebauthn,
|
WebAuthn: options.EndpointsWebAuthn,
|
||||||
TOTP: options.EndpointsTOTP,
|
TOTP: options.EndpointsTOTP,
|
||||||
Duo: options.EndpointsDuo,
|
Duo: options.EndpointsDuo,
|
||||||
OpenIDConnect: options.EndpointsOpenIDConnect,
|
OpenIDConnect: options.EndpointsOpenIDConnect,
|
||||||
|
@ -395,7 +395,7 @@ type TemplatedFileOpenAPIData struct {
|
||||||
CSPNonce string
|
CSPNonce string
|
||||||
Session string
|
Session string
|
||||||
PasswordReset bool
|
PasswordReset bool
|
||||||
Webauthn bool
|
WebAuthn bool
|
||||||
TOTP bool
|
TOTP bool
|
||||||
Duo bool
|
Duo bool
|
||||||
OpenIDConnect bool
|
OpenIDConnect bool
|
||||||
|
|
|
@ -179,7 +179,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
AuthenticationMethodRefs: oidc.AuthenticationMethodsReferences{UsernameAndPassword: true},
|
AuthenticationMethodRefs: oidc.AuthenticationMethodsReferences{UsernameAndPassword: true},
|
||||||
}, session)
|
}, session)
|
||||||
|
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
session.SetTwoFactorWebAuthn(timeTwoFactor, false, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -187,7 +187,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true}, session.AuthenticationMethodRefs)
|
assert.Equal(t, oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true}, session.AuthenticationMethodRefs)
|
||||||
assert.True(t, session.AuthenticationMethodRefs.MultiFactorAuthentication())
|
assert.True(t, session.AuthenticationMethodRefs.MultiFactorAuthentication())
|
||||||
|
|
||||||
authAt, err = session.AuthenticatedTime(authorization.OneFactor)
|
authAt, err = session.AuthenticatedTime(authorization.OneFactor)
|
||||||
|
@ -202,7 +202,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.EqualError(t, err, "invalid authorization level")
|
assert.EqualError(t, err, "invalid authorization level")
|
||||||
assert.Equal(t, timeZeroFactor, authAt)
|
assert.Equal(t, timeZeroFactor, authAt)
|
||||||
|
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
session.SetTwoFactorWebAuthn(timeTwoFactor, false, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -211,10 +211,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true},
|
||||||
session.AuthenticationMethodRefs)
|
session.AuthenticationMethodRefs)
|
||||||
|
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
session.SetTwoFactorWebAuthn(timeTwoFactor, false, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -223,10 +223,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true},
|
||||||
session.AuthenticationMethodRefs)
|
session.AuthenticationMethodRefs)
|
||||||
|
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
session.SetTwoFactorWebAuthn(timeTwoFactor, true, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -235,10 +235,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true, WebAuthnUserPresence: true},
|
||||||
session.AuthenticationMethodRefs)
|
session.AuthenticationMethodRefs)
|
||||||
|
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
session.SetTwoFactorWebAuthn(timeTwoFactor, true, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -247,10 +247,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true, WebAuthnUserPresence: true},
|
||||||
session.AuthenticationMethodRefs)
|
session.AuthenticationMethodRefs)
|
||||||
|
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
session.SetTwoFactorWebAuthn(timeTwoFactor, false, true)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -259,10 +259,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true, WebAuthnUserVerified: true},
|
||||||
session.AuthenticationMethodRefs)
|
session.AuthenticationMethodRefs)
|
||||||
|
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
session.SetTwoFactorWebAuthn(timeTwoFactor, false, true)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -271,7 +271,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true, WebAuthnUserVerified: true},
|
||||||
session.AuthenticationMethodRefs)
|
session.AuthenticationMethodRefs)
|
||||||
|
|
||||||
session.SetTwoFactorTOTP(timeTwoFactor)
|
session.SetTwoFactorTOTP(timeTwoFactor)
|
||||||
|
@ -283,7 +283,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, WebAuthn: true, WebAuthnUserVerified: true},
|
||||||
session.AuthenticationMethodRefs)
|
session.AuthenticationMethodRefs)
|
||||||
|
|
||||||
session.SetTwoFactorTOTP(timeTwoFactor)
|
session.SetTwoFactorTOTP(timeTwoFactor)
|
||||||
|
@ -295,7 +295,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, WebAuthn: true, WebAuthnUserVerified: true},
|
||||||
session.AuthenticationMethodRefs)
|
session.AuthenticationMethodRefs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ type UserSession struct {
|
||||||
|
|
||||||
AuthenticationMethodRefs oidc.AuthenticationMethodsReferences
|
AuthenticationMethodRefs oidc.AuthenticationMethodsReferences
|
||||||
|
|
||||||
// Webauthn holds the session registration data for this session.
|
// WebAuthn holds the session registration data for this session.
|
||||||
Webauthn *webauthn.SessionData
|
WebAuthn *webauthn.SessionData
|
||||||
|
|
||||||
// This boolean is set to true after identity verification and checked
|
// This boolean is set to true after identity verification and checked
|
||||||
// while doing the query actually updating the password.
|
// while doing the query actually updating the password.
|
||||||
|
|
|
@ -56,13 +56,13 @@ func (s *UserSession) SetTwoFactorDuo(now time.Time) {
|
||||||
s.AuthenticationMethodRefs.Duo = true
|
s.AuthenticationMethodRefs.Duo = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTwoFactorWebauthn sets the relevant Webauthn AMR's and sets the factor to 2FA.
|
// SetTwoFactorWebAuthn sets the relevant WebAuthn AMR's and sets the factor to 2FA.
|
||||||
func (s *UserSession) SetTwoFactorWebauthn(now time.Time, userPresence, userVerified bool) {
|
func (s *UserSession) SetTwoFactorWebAuthn(now time.Time, userPresence, userVerified bool) {
|
||||||
s.setTwoFactor(now)
|
s.setTwoFactor(now)
|
||||||
s.AuthenticationMethodRefs.Webauthn = true
|
s.AuthenticationMethodRefs.WebAuthn = true
|
||||||
s.AuthenticationMethodRefs.WebauthnUserPresence, s.AuthenticationMethodRefs.WebauthnUserVerified = userPresence, userVerified
|
s.AuthenticationMethodRefs.WebAuthnUserPresence, s.AuthenticationMethodRefs.WebAuthnUserVerified = userPresence, userVerified
|
||||||
|
|
||||||
s.Webauthn = nil
|
s.WebAuthn = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthenticatedTime returns the unix timestamp this session authenticated successfully at the given level.
|
// AuthenticatedTime returns the unix timestamp this session authenticated successfully at the given level.
|
||||||
|
|
|
@ -11,7 +11,7 @@ const (
|
||||||
tableTOTPConfigurations = "totp_configurations"
|
tableTOTPConfigurations = "totp_configurations"
|
||||||
tableUserOpaqueIdentifier = "user_opaque_identifier"
|
tableUserOpaqueIdentifier = "user_opaque_identifier"
|
||||||
tableUserPreferences = "user_preferences"
|
tableUserPreferences = "user_preferences"
|
||||||
tableWebauthnDevices = "webauthn_devices"
|
tableWebAuthnDevices = "webauthn_devices"
|
||||||
|
|
||||||
tableOAuth2BlacklistedJTI = "oauth2_blacklisted_jti"
|
tableOAuth2BlacklistedJTI = "oauth2_blacklisted_jti"
|
||||||
tableOAuth2ConsentSession = "oauth2_consent_session"
|
tableOAuth2ConsentSession = "oauth2_consent_session"
|
||||||
|
|
|
@ -11,8 +11,8 @@ var (
|
||||||
// ErrNoTOTPConfiguration error thrown when no TOTP configuration has been found in DB.
|
// ErrNoTOTPConfiguration error thrown when no TOTP configuration has been found in DB.
|
||||||
ErrNoTOTPConfiguration = errors.New("no TOTP configuration for user")
|
ErrNoTOTPConfiguration = errors.New("no TOTP configuration for user")
|
||||||
|
|
||||||
// ErrNoWebauthnDevice error thrown when no Webauthn device handle has been found in DB.
|
// ErrNoWebAuthnDevice error thrown when no WebAuthn device handle has been found in DB.
|
||||||
ErrNoWebauthnDevice = errors.New("no Webauthn device found")
|
ErrNoWebAuthnDevice = errors.New("no WebAuthn device found")
|
||||||
|
|
||||||
// ErrNoDuoDevice error thrown when no Duo device and method has been found in DB.
|
// ErrNoDuoDevice error thrown when no Duo device and method has been found in DB.
|
||||||
ErrNoDuoDevice = errors.New("no Duo device and method saved")
|
ErrNoDuoDevice = errors.New("no Duo device and method saved")
|
||||||
|
|
|
@ -38,12 +38,12 @@ type Provider interface {
|
||||||
LoadTOTPConfiguration(ctx context.Context, username string) (config *model.TOTPConfiguration, err error)
|
LoadTOTPConfiguration(ctx context.Context, username string) (config *model.TOTPConfiguration, err error)
|
||||||
LoadTOTPConfigurations(ctx context.Context, limit, page int) (configs []model.TOTPConfiguration, err error)
|
LoadTOTPConfigurations(ctx context.Context, limit, page int) (configs []model.TOTPConfiguration, err error)
|
||||||
|
|
||||||
SaveWebauthnDevice(ctx context.Context, device model.WebAuthnDevice) (err error)
|
SaveWebAuthnDevice(ctx context.Context, device model.WebAuthnDevice) (err error)
|
||||||
UpdateWebauthnDeviceSignIn(ctx context.Context, id int, rpid string, lastUsedAt sql.NullTime, signCount uint32, cloneWarning bool) (err error)
|
UpdateWebAuthnDeviceSignIn(ctx context.Context, id int, rpid string, lastUsedAt sql.NullTime, signCount uint32, cloneWarning bool) (err error)
|
||||||
DeleteWebauthnDevice(ctx context.Context, kid string) (err error)
|
DeleteWebAuthnDevice(ctx context.Context, kid string) (err error)
|
||||||
DeleteWebauthnDeviceByUsername(ctx context.Context, username, description string) (err error)
|
DeleteWebAuthnDeviceByUsername(ctx context.Context, username, description string) (err error)
|
||||||
LoadWebauthnDevices(ctx context.Context, limit, page int) (devices []model.WebAuthnDevice, err error)
|
LoadWebAuthnDevices(ctx context.Context, limit, page int) (devices []model.WebAuthnDevice, err error)
|
||||||
LoadWebauthnDevicesByUsername(ctx context.Context, username string) (devices []model.WebAuthnDevice, err error)
|
LoadWebAuthnDevicesByUsername(ctx context.Context, username string) (devices []model.WebAuthnDevice, err error)
|
||||||
|
|
||||||
SavePreferredDuoDevice(ctx context.Context, device model.DuoDevice) (err error)
|
SavePreferredDuoDevice(ctx context.Context, device model.DuoDevice) (err error)
|
||||||
DeletePreferredDuoDevice(ctx context.Context, username string) (err error)
|
DeletePreferredDuoDevice(ctx context.Context, username string) (err error)
|
||||||
|
|
|
@ -46,16 +46,16 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
|
||||||
sqlUpdateTOTPConfigRecordSignIn: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignIn, tableTOTPConfigurations),
|
sqlUpdateTOTPConfigRecordSignIn: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignIn, tableTOTPConfigurations),
|
||||||
sqlUpdateTOTPConfigRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignInByUsername, tableTOTPConfigurations),
|
sqlUpdateTOTPConfigRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignInByUsername, tableTOTPConfigurations),
|
||||||
|
|
||||||
sqlUpsertWebauthnDevice: fmt.Sprintf(queryFmtUpsertWebauthnDevice, tableWebauthnDevices),
|
sqlUpsertWebAuthnDevice: fmt.Sprintf(queryFmtUpsertWebAuthnDevice, tableWebAuthnDevices),
|
||||||
sqlSelectWebauthnDevices: fmt.Sprintf(queryFmtSelectWebauthnDevices, tableWebauthnDevices),
|
sqlSelectWebAuthnDevices: fmt.Sprintf(queryFmtSelectWebAuthnDevices, tableWebAuthnDevices),
|
||||||
sqlSelectWebauthnDevicesByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByUsername, tableWebauthnDevices),
|
sqlSelectWebAuthnDevicesByUsername: fmt.Sprintf(queryFmtSelectWebAuthnDevicesByUsername, tableWebAuthnDevices),
|
||||||
|
|
||||||
sqlUpdateWebauthnDeviceRecordSignIn: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignIn, tableWebauthnDevices),
|
sqlUpdateWebAuthnDeviceRecordSignIn: fmt.Sprintf(queryFmtUpdateWebAuthnDeviceRecordSignIn, tableWebAuthnDevices),
|
||||||
sqlUpdateWebauthnDeviceRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignInByUsername, tableWebauthnDevices),
|
sqlUpdateWebAuthnDeviceRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateWebAuthnDeviceRecordSignInByUsername, tableWebAuthnDevices),
|
||||||
|
|
||||||
sqlDeleteWebauthnDevice: fmt.Sprintf(queryFmtDeleteWebauthnDevice, tableWebauthnDevices),
|
sqlDeleteWebAuthnDevice: fmt.Sprintf(queryFmtDeleteWebAuthnDevice, tableWebAuthnDevices),
|
||||||
sqlDeleteWebauthnDeviceByUsername: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsername, tableWebauthnDevices),
|
sqlDeleteWebAuthnDeviceByUsername: fmt.Sprintf(queryFmtDeleteWebAuthnDeviceByUsername, tableWebAuthnDevices),
|
||||||
sqlDeleteWebauthnDeviceByUsernameAndDescription: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsernameAndDescription, tableWebauthnDevices),
|
sqlDeleteWebAuthnDeviceByUsernameAndDescription: fmt.Sprintf(queryFmtDeleteWebAuthnDeviceByUsernameAndDescription, tableWebAuthnDevices),
|
||||||
|
|
||||||
sqlUpsertDuoDevice: fmt.Sprintf(queryFmtUpsertDuoDevice, tableDuoDevices),
|
sqlUpsertDuoDevice: fmt.Sprintf(queryFmtUpsertDuoDevice, tableDuoDevices),
|
||||||
sqlDeleteDuoDevice: fmt.Sprintf(queryFmtDeleteDuoDevice, tableDuoDevices),
|
sqlDeleteDuoDevice: fmt.Sprintf(queryFmtDeleteDuoDevice, tableDuoDevices),
|
||||||
|
@ -63,7 +63,7 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
|
||||||
|
|
||||||
sqlUpsertPreferred2FAMethod: fmt.Sprintf(queryFmtUpsertPreferred2FAMethod, tableUserPreferences),
|
sqlUpsertPreferred2FAMethod: fmt.Sprintf(queryFmtUpsertPreferred2FAMethod, tableUserPreferences),
|
||||||
sqlSelectPreferred2FAMethod: fmt.Sprintf(queryFmtSelectPreferred2FAMethod, tableUserPreferences),
|
sqlSelectPreferred2FAMethod: fmt.Sprintf(queryFmtSelectPreferred2FAMethod, tableUserPreferences),
|
||||||
sqlSelectUserInfo: fmt.Sprintf(queryFmtSelectUserInfo, tableTOTPConfigurations, tableWebauthnDevices, tableDuoDevices, tableUserPreferences),
|
sqlSelectUserInfo: fmt.Sprintf(queryFmtSelectUserInfo, tableTOTPConfigurations, tableWebAuthnDevices, tableDuoDevices, tableUserPreferences),
|
||||||
|
|
||||||
sqlInsertUserOpaqueIdentifier: fmt.Sprintf(queryFmtInsertUserOpaqueIdentifier, tableUserOpaqueIdentifier),
|
sqlInsertUserOpaqueIdentifier: fmt.Sprintf(queryFmtInsertUserOpaqueIdentifier, tableUserOpaqueIdentifier),
|
||||||
sqlSelectUserOpaqueIdentifier: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifier, tableUserOpaqueIdentifier),
|
sqlSelectUserOpaqueIdentifier: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifier, tableUserOpaqueIdentifier),
|
||||||
|
@ -165,16 +165,16 @@ type SQLProvider struct {
|
||||||
sqlUpdateTOTPConfigRecordSignInByUsername string
|
sqlUpdateTOTPConfigRecordSignInByUsername string
|
||||||
|
|
||||||
// Table: webauthn_devices.
|
// Table: webauthn_devices.
|
||||||
sqlUpsertWebauthnDevice string
|
sqlUpsertWebAuthnDevice string
|
||||||
sqlSelectWebauthnDevices string
|
sqlSelectWebAuthnDevices string
|
||||||
sqlSelectWebauthnDevicesByUsername string
|
sqlSelectWebAuthnDevicesByUsername string
|
||||||
|
|
||||||
sqlUpdateWebauthnDeviceRecordSignIn string
|
sqlUpdateWebAuthnDeviceRecordSignIn string
|
||||||
sqlUpdateWebauthnDeviceRecordSignInByUsername string
|
sqlUpdateWebAuthnDeviceRecordSignInByUsername string
|
||||||
|
|
||||||
sqlDeleteWebauthnDevice string
|
sqlDeleteWebAuthnDevice string
|
||||||
sqlDeleteWebauthnDeviceByUsername string
|
sqlDeleteWebAuthnDeviceByUsername string
|
||||||
sqlDeleteWebauthnDeviceByUsernameAndDescription string
|
sqlDeleteWebAuthnDeviceByUsernameAndDescription string
|
||||||
|
|
||||||
// Table: duo_devices.
|
// Table: duo_devices.
|
||||||
sqlUpsertDuoDevice string
|
sqlUpsertDuoDevice string
|
||||||
|
@ -823,7 +823,7 @@ func (p *SQLProvider) SaveTOTPConfiguration(ctx context.Context, config model.TO
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTOTPConfigurationSignIn updates a registered Webauthn devices sign in information.
|
// UpdateTOTPConfigurationSignIn updates a registered WebAuthn devices sign in information.
|
||||||
func (p *SQLProvider) UpdateTOTPConfigurationSignIn(ctx context.Context, id int, lastUsedAt sql.NullTime) (err error) {
|
func (p *SQLProvider) UpdateTOTPConfigurationSignIn(ctx context.Context, id int, lastUsedAt sql.NullTime) (err error) {
|
||||||
if _, err = p.db.ExecContext(ctx, p.sqlUpdateTOTPConfigRecordSignIn, lastUsedAt, id); err != nil {
|
if _, err = p.db.ExecContext(ctx, p.sqlUpdateTOTPConfigRecordSignIn, lastUsedAt, id); err != nil {
|
||||||
return fmt.Errorf("error updating TOTP configuration id %d: %w", id, err)
|
return fmt.Errorf("error updating TOTP configuration id %d: %w", id, err)
|
||||||
|
@ -881,95 +881,95 @@ func (p *SQLProvider) LoadTOTPConfigurations(ctx context.Context, limit, page in
|
||||||
return configs, nil
|
return configs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveWebauthnDevice saves a registered Webauthn device.
|
// SaveWebAuthnDevice saves a registered WebAuthn device.
|
||||||
func (p *SQLProvider) SaveWebauthnDevice(ctx context.Context, device model.WebAuthnDevice) (err error) {
|
func (p *SQLProvider) SaveWebAuthnDevice(ctx context.Context, device model.WebAuthnDevice) (err error) {
|
||||||
if device.PublicKey, err = p.encrypt(device.PublicKey); err != nil {
|
if device.PublicKey, err = p.encrypt(device.PublicKey); err != nil {
|
||||||
return fmt.Errorf("error encrypting Webauthn device public key for user '%s' kid '%x': %w", device.Username, device.KID, err)
|
return fmt.Errorf("error encrypting WebAuthn device public key for user '%s' kid '%x': %w", device.Username, device.KID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = p.db.ExecContext(ctx, p.sqlUpsertWebauthnDevice,
|
if _, err = p.db.ExecContext(ctx, p.sqlUpsertWebAuthnDevice,
|
||||||
device.CreatedAt, device.LastUsedAt,
|
device.CreatedAt, device.LastUsedAt,
|
||||||
device.RPID, device.Username, device.Description,
|
device.RPID, device.Username, device.Description,
|
||||||
device.KID, device.PublicKey,
|
device.KID, device.PublicKey,
|
||||||
device.AttestationType, device.Transport, device.AAGUID, device.SignCount, device.CloneWarning,
|
device.AttestationType, device.Transport, device.AAGUID, device.SignCount, device.CloneWarning,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("error upserting Webauthn device for user '%s' kid '%x': %w", device.Username, device.KID, err)
|
return fmt.Errorf("error upserting WebAuthn device for user '%s' kid '%x': %w", device.Username, device.KID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateWebauthnDeviceSignIn updates a registered Webauthn devices sign in information.
|
// UpdateWebAuthnDeviceSignIn updates a registered WebAuthn devices sign in information.
|
||||||
func (p *SQLProvider) UpdateWebauthnDeviceSignIn(ctx context.Context, id int, rpid string, lastUsedAt sql.NullTime, signCount uint32, cloneWarning bool) (err error) {
|
func (p *SQLProvider) UpdateWebAuthnDeviceSignIn(ctx context.Context, id int, rpid string, lastUsedAt sql.NullTime, signCount uint32, cloneWarning bool) (err error) {
|
||||||
if _, err = p.db.ExecContext(ctx, p.sqlUpdateWebauthnDeviceRecordSignIn, rpid, lastUsedAt, signCount, cloneWarning, id); err != nil {
|
if _, err = p.db.ExecContext(ctx, p.sqlUpdateWebAuthnDeviceRecordSignIn, rpid, lastUsedAt, signCount, cloneWarning, id); err != nil {
|
||||||
return fmt.Errorf("error updating Webauthn signin metadata for id '%x': %w", id, err)
|
return fmt.Errorf("error updating WebAuthn signin metadata for id '%x': %w", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWebauthnDevice deletes a registered Webauthn device.
|
// DeleteWebAuthnDevice deletes a registered WebAuthn device.
|
||||||
func (p *SQLProvider) DeleteWebauthnDevice(ctx context.Context, kid string) (err error) {
|
func (p *SQLProvider) DeleteWebAuthnDevice(ctx context.Context, kid string) (err error) {
|
||||||
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDevice, kid); err != nil {
|
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebAuthnDevice, kid); err != nil {
|
||||||
return fmt.Errorf("error deleting webauthn device with kid '%s': %w", kid, err)
|
return fmt.Errorf("error deleting WebAuthn device with kid '%s': %w", kid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWebauthnDeviceByUsername deletes registered Webauthn devices by username or username and description.
|
// DeleteWebAuthnDeviceByUsername deletes registered WebAuthn devices by username or username and description.
|
||||||
func (p *SQLProvider) DeleteWebauthnDeviceByUsername(ctx context.Context, username, description string) (err error) {
|
func (p *SQLProvider) DeleteWebAuthnDeviceByUsername(ctx context.Context, username, description string) (err error) {
|
||||||
if len(username) == 0 {
|
if len(username) == 0 {
|
||||||
return fmt.Errorf("error deleting webauthn device with username '%s' and description '%s': username must not be empty", username, description)
|
return fmt.Errorf("error deleting WebAuthn device with username '%s' and description '%s': username must not be empty", username, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(description) == 0 {
|
if len(description) == 0 {
|
||||||
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDeviceByUsername, username); err != nil {
|
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebAuthnDeviceByUsername, username); err != nil {
|
||||||
return fmt.Errorf("error deleting webauthn devices for username '%s': %w", username, err)
|
return fmt.Errorf("error deleting WebAuthn devices for username '%s': %w", username, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDeviceByUsernameAndDescription, username, description); err != nil {
|
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebAuthnDeviceByUsernameAndDescription, username, description); err != nil {
|
||||||
return fmt.Errorf("error deleting webauthn device with username '%s' and description '%s': %w", username, description, err)
|
return fmt.Errorf("error deleting WebAuthn device with username '%s' and description '%s': %w", username, description, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadWebauthnDevices loads Webauthn device registrations.
|
// LoadWebAuthnDevices loads WebAuthn device registrations.
|
||||||
func (p *SQLProvider) LoadWebauthnDevices(ctx context.Context, limit, page int) (devices []model.WebAuthnDevice, err error) {
|
func (p *SQLProvider) LoadWebAuthnDevices(ctx context.Context, limit, page int) (devices []model.WebAuthnDevice, err error) {
|
||||||
devices = make([]model.WebAuthnDevice, 0, limit)
|
devices = make([]model.WebAuthnDevice, 0, limit)
|
||||||
|
|
||||||
if err = p.db.SelectContext(ctx, &devices, p.sqlSelectWebauthnDevices, limit, limit*page); err != nil {
|
if err = p.db.SelectContext(ctx, &devices, p.sqlSelectWebAuthnDevices, limit, limit*page); err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("error selecting Webauthn devices: %w", err)
|
return nil, fmt.Errorf("error selecting WebAuthn devices: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, device := range devices {
|
for i, device := range devices {
|
||||||
if devices[i].PublicKey, err = p.decrypt(device.PublicKey); err != nil {
|
if devices[i].PublicKey, err = p.decrypt(device.PublicKey); err != nil {
|
||||||
return nil, fmt.Errorf("error decrypting Webauthn public key for user '%s': %w", device.Username, err)
|
return nil, fmt.Errorf("error decrypting WebAuthn public key for user '%s': %w", device.Username, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return devices, nil
|
return devices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadWebauthnDevicesByUsername loads all webauthn devices registration for a given username.
|
// LoadWebAuthnDevicesByUsername loads all WebAuthn devices registration for a given username.
|
||||||
func (p *SQLProvider) LoadWebauthnDevicesByUsername(ctx context.Context, username string) (devices []model.WebAuthnDevice, err error) {
|
func (p *SQLProvider) LoadWebAuthnDevicesByUsername(ctx context.Context, username string) (devices []model.WebAuthnDevice, err error) {
|
||||||
if err = p.db.SelectContext(ctx, &devices, p.sqlSelectWebauthnDevicesByUsername, username); err != nil {
|
if err = p.db.SelectContext(ctx, &devices, p.sqlSelectWebAuthnDevicesByUsername, username); err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil, ErrNoWebauthnDevice
|
return nil, ErrNoWebAuthnDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("error selecting Webauthn devices for user '%s': %w", username, err)
|
return nil, fmt.Errorf("error selecting WebAuthn devices for user '%s': %w", username, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, device := range devices {
|
for i, device := range devices {
|
||||||
if devices[i].PublicKey, err = p.decrypt(device.PublicKey); err != nil {
|
if devices[i].PublicKey, err = p.decrypt(device.PublicKey); err != nil {
|
||||||
return nil, fmt.Errorf("error decrypting Webauthn public key for user '%s': %w", username, err)
|
return nil, fmt.Errorf("error decrypting WebAuthn public key for user '%s': %w", username, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo
|
||||||
|
|
||||||
// Specific alterations to this provider.
|
// Specific alterations to this provider.
|
||||||
// PostgreSQL doesn't have a UPSERT statement but has an ON CONFLICT operation instead.
|
// PostgreSQL doesn't have a UPSERT statement but has an ON CONFLICT operation instead.
|
||||||
provider.sqlUpsertWebauthnDevice = fmt.Sprintf(queryFmtUpsertWebauthnDevicePostgreSQL, tableWebauthnDevices)
|
provider.sqlUpsertWebAuthnDevice = fmt.Sprintf(queryFmtUpsertWebAuthnDevicePostgreSQL, tableWebAuthnDevices)
|
||||||
provider.sqlUpsertDuoDevice = fmt.Sprintf(queryFmtUpsertDuoDevicePostgreSQL, tableDuoDevices)
|
provider.sqlUpsertDuoDevice = fmt.Sprintf(queryFmtUpsertDuoDevicePostgreSQL, tableDuoDevices)
|
||||||
provider.sqlUpsertTOTPConfig = fmt.Sprintf(queryFmtUpsertTOTPConfigurationPostgreSQL, tableTOTPConfigurations)
|
provider.sqlUpsertTOTPConfig = fmt.Sprintf(queryFmtUpsertTOTPConfigurationPostgreSQL, tableTOTPConfigurations)
|
||||||
provider.sqlUpsertPreferred2FAMethod = fmt.Sprintf(queryFmtUpsertPreferred2FAMethodPostgreSQL, tableUserPreferences)
|
provider.sqlUpsertPreferred2FAMethod = fmt.Sprintf(queryFmtUpsertPreferred2FAMethodPostgreSQL, tableUserPreferences)
|
||||||
|
@ -59,13 +59,13 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo
|
||||||
provider.sqlDeleteTOTPConfig = provider.db.Rebind(provider.sqlDeleteTOTPConfig)
|
provider.sqlDeleteTOTPConfig = provider.db.Rebind(provider.sqlDeleteTOTPConfig)
|
||||||
provider.sqlSelectTOTPConfigs = provider.db.Rebind(provider.sqlSelectTOTPConfigs)
|
provider.sqlSelectTOTPConfigs = provider.db.Rebind(provider.sqlSelectTOTPConfigs)
|
||||||
|
|
||||||
provider.sqlSelectWebauthnDevices = provider.db.Rebind(provider.sqlSelectWebauthnDevices)
|
provider.sqlSelectWebAuthnDevices = provider.db.Rebind(provider.sqlSelectWebAuthnDevices)
|
||||||
provider.sqlSelectWebauthnDevicesByUsername = provider.db.Rebind(provider.sqlSelectWebauthnDevicesByUsername)
|
provider.sqlSelectWebAuthnDevicesByUsername = provider.db.Rebind(provider.sqlSelectWebAuthnDevicesByUsername)
|
||||||
provider.sqlUpdateWebauthnDeviceRecordSignIn = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceRecordSignIn)
|
provider.sqlUpdateWebAuthnDeviceRecordSignIn = provider.db.Rebind(provider.sqlUpdateWebAuthnDeviceRecordSignIn)
|
||||||
provider.sqlUpdateWebauthnDeviceRecordSignInByUsername = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceRecordSignInByUsername)
|
provider.sqlUpdateWebAuthnDeviceRecordSignInByUsername = provider.db.Rebind(provider.sqlUpdateWebAuthnDeviceRecordSignInByUsername)
|
||||||
provider.sqlDeleteWebauthnDevice = provider.db.Rebind(provider.sqlDeleteWebauthnDevice)
|
provider.sqlDeleteWebAuthnDevice = provider.db.Rebind(provider.sqlDeleteWebAuthnDevice)
|
||||||
provider.sqlDeleteWebauthnDeviceByUsername = provider.db.Rebind(provider.sqlDeleteWebauthnDeviceByUsername)
|
provider.sqlDeleteWebAuthnDeviceByUsername = provider.db.Rebind(provider.sqlDeleteWebAuthnDeviceByUsername)
|
||||||
provider.sqlDeleteWebauthnDeviceByUsernameAndDescription = provider.db.Rebind(provider.sqlDeleteWebauthnDeviceByUsernameAndDescription)
|
provider.sqlDeleteWebAuthnDeviceByUsernameAndDescription = provider.db.Rebind(provider.sqlDeleteWebAuthnDeviceByUsernameAndDescription)
|
||||||
|
|
||||||
provider.sqlSelectDuoDevice = provider.db.Rebind(provider.sqlSelectDuoDevice)
|
provider.sqlSelectDuoDevice = provider.db.Rebind(provider.sqlSelectDuoDevice)
|
||||||
provider.sqlDeleteDuoDevice = provider.db.Rebind(provider.sqlDeleteDuoDevice)
|
provider.sqlDeleteDuoDevice = provider.db.Rebind(provider.sqlDeleteDuoDevice)
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (p *SQLProvider) SchemaEncryptionChangeKey(ctx context.Context, key string)
|
||||||
|
|
||||||
encChangeFuncs := []EncryptionChangeKeyFunc{
|
encChangeFuncs := []EncryptionChangeKeyFunc{
|
||||||
schemaEncryptionChangeKeyTOTP,
|
schemaEncryptionChangeKeyTOTP,
|
||||||
schemaEncryptionChangeKeyWebauthn,
|
schemaEncryptionChangeKeyWebAuthn,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; true; i++ {
|
for i := 0; true; i++ {
|
||||||
|
@ -90,7 +90,7 @@ func (p *SQLProvider) SchemaEncryptionCheckKey(ctx context.Context, verbose bool
|
||||||
if verbose {
|
if verbose {
|
||||||
encCheckFuncs := []EncryptionCheckKeyFunc{
|
encCheckFuncs := []EncryptionCheckKeyFunc{
|
||||||
schemaEncryptionCheckKeyTOTP,
|
schemaEncryptionCheckKeyTOTP,
|
||||||
schemaEncryptionCheckKeyWebauthn,
|
schemaEncryptionCheckKeyWebAuthn,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; true; i++ {
|
for i := 0; true; i++ {
|
||||||
|
@ -153,10 +153,10 @@ func schemaEncryptionChangeKeyTOTP(ctx context.Context, provider *SQLProvider, t
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func schemaEncryptionChangeKeyWebauthn(ctx context.Context, provider *SQLProvider, tx *sqlx.Tx, key [32]byte) (err error) {
|
func schemaEncryptionChangeKeyWebAuthn(ctx context.Context, provider *SQLProvider, tx *sqlx.Tx, key [32]byte) (err error) {
|
||||||
var count int
|
var count int
|
||||||
|
|
||||||
if err = tx.GetContext(ctx, &count, fmt.Sprintf(queryFmtSelectRowCount, tableWebauthnDevices)); err != nil {
|
if err = tx.GetContext(ctx, &count, fmt.Sprintf(queryFmtSelectRowCount, tableWebAuthnDevices)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,29 +164,29 @@ func schemaEncryptionChangeKeyWebauthn(ctx context.Context, provider *SQLProvide
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
devices := make([]encWebauthnDevice, 0, count)
|
devices := make([]encWebAuthnDevice, 0, count)
|
||||||
|
|
||||||
if err = tx.SelectContext(ctx, &devices, fmt.Sprintf(queryFmtSelectWebauthnDevicesEncryptedData, tableWebauthnDevices)); err != nil {
|
if err = tx.SelectContext(ctx, &devices, fmt.Sprintf(queryFmtSelectWebAuthnDevicesEncryptedData, tableWebAuthnDevices)); err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("error selecting Webauthn devices: %w", err)
|
return fmt.Errorf("error selecting WebAuthn devices: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
query := provider.db.Rebind(fmt.Sprintf(queryFmtUpdateWebauthnDevicePublicKey, tableWebauthnDevices))
|
query := provider.db.Rebind(fmt.Sprintf(queryFmtUpdateWebAuthnDevicePublicKey, tableWebAuthnDevices))
|
||||||
|
|
||||||
for _, d := range devices {
|
for _, d := range devices {
|
||||||
if d.PublicKey, err = provider.decrypt(d.PublicKey); err != nil {
|
if d.PublicKey, err = provider.decrypt(d.PublicKey); err != nil {
|
||||||
return fmt.Errorf("error decrypting Webauthn device public key with id '%d': %w", d.ID, err)
|
return fmt.Errorf("error decrypting WebAuthn device public key with id '%d': %w", d.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.PublicKey, err = utils.Encrypt(d.PublicKey, &key); err != nil {
|
if d.PublicKey, err = utils.Encrypt(d.PublicKey, &key); err != nil {
|
||||||
return fmt.Errorf("error encrypting Webauthn device public key with id '%d': %w", d.ID, err)
|
return fmt.Errorf("error encrypting WebAuthn device public key with id '%d': %w", d.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = tx.ExecContext(ctx, query, d.PublicKey, d.ID); err != nil {
|
if _, err = tx.ExecContext(ctx, query, d.PublicKey, d.ID); err != nil {
|
||||||
return fmt.Errorf("error updating Webauthn device public key with id '%d': %w", d.ID, err)
|
return fmt.Errorf("error updating WebAuthn device public key with id '%d': %w", d.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,17 +262,17 @@ func schemaEncryptionCheckKeyTOTP(ctx context.Context, provider *SQLProvider) (t
|
||||||
return tableTOTPConfigurations, result
|
return tableTOTPConfigurations, result
|
||||||
}
|
}
|
||||||
|
|
||||||
func schemaEncryptionCheckKeyWebauthn(ctx context.Context, provider *SQLProvider) (table string, result EncryptionValidationTableResult) {
|
func schemaEncryptionCheckKeyWebAuthn(ctx context.Context, provider *SQLProvider) (table string, result EncryptionValidationTableResult) {
|
||||||
var (
|
var (
|
||||||
rows *sqlx.Rows
|
rows *sqlx.Rows
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if rows, err = provider.db.QueryxContext(ctx, fmt.Sprintf(queryFmtSelectWebauthnDevicesEncryptedData, tableWebauthnDevices)); err != nil {
|
if rows, err = provider.db.QueryxContext(ctx, fmt.Sprintf(queryFmtSelectWebAuthnDevicesEncryptedData, tableWebAuthnDevices)); err != nil {
|
||||||
return tableWebauthnDevices, EncryptionValidationTableResult{Error: fmt.Errorf("error selecting Webauthn devices: %w", err)}
|
return tableWebAuthnDevices, EncryptionValidationTableResult{Error: fmt.Errorf("error selecting WebAuthn devices: %w", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
var device encWebauthnDevice
|
var device encWebAuthnDevice
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
result.Total++
|
result.Total++
|
||||||
|
@ -280,7 +280,7 @@ func schemaEncryptionCheckKeyWebauthn(ctx context.Context, provider *SQLProvider
|
||||||
if err = rows.StructScan(&device); err != nil {
|
if err = rows.StructScan(&device); err != nil {
|
||||||
_ = rows.Close()
|
_ = rows.Close()
|
||||||
|
|
||||||
return tableWebauthnDevices, EncryptionValidationTableResult{Error: fmt.Errorf("error scanning Webauthn device to struct: %w", err)}
|
return tableWebAuthnDevices, EncryptionValidationTableResult{Error: fmt.Errorf("error scanning WebAuthn device to struct: %w", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = provider.decrypt(device.PublicKey); err != nil {
|
if _, err = provider.decrypt(device.PublicKey); err != nil {
|
||||||
|
@ -290,7 +290,7 @@ func schemaEncryptionCheckKeyWebauthn(ctx context.Context, provider *SQLProvider
|
||||||
|
|
||||||
_ = rows.Close()
|
_ = rows.Close()
|
||||||
|
|
||||||
return tableWebauthnDevices, result
|
return tableWebAuthnDevices, result
|
||||||
}
|
}
|
||||||
|
|
||||||
func schemaEncryptionCheckKeyOpenIDConnect(typeOAuth2Session OAuth2SessionType) EncryptionCheckKeyFunc {
|
func schemaEncryptionCheckKeyOpenIDConnect(typeOAuth2Session OAuth2SessionType) EncryptionCheckKeyFunc {
|
||||||
|
|
|
@ -119,59 +119,59 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
queryFmtSelectWebauthnDevices = `
|
queryFmtSelectWebAuthnDevices = `
|
||||||
SELECT id, created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning
|
SELECT id, created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning
|
||||||
FROM %s
|
FROM %s
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
OFFSET ?;`
|
OFFSET ?;`
|
||||||
|
|
||||||
queryFmtSelectWebauthnDevicesEncryptedData = `
|
queryFmtSelectWebAuthnDevicesEncryptedData = `
|
||||||
SELECT id, public_key
|
SELECT id, public_key
|
||||||
FROM %s;`
|
FROM %s;`
|
||||||
|
|
||||||
queryFmtSelectWebauthnDevicesByUsername = `
|
queryFmtSelectWebAuthnDevicesByUsername = `
|
||||||
SELECT id, created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning
|
SELECT id, created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning
|
||||||
FROM %s
|
FROM %s
|
||||||
WHERE username = ?;`
|
WHERE username = ?;`
|
||||||
|
|
||||||
queryFmtUpdateWebauthnDevicePublicKey = `
|
queryFmtUpdateWebAuthnDevicePublicKey = `
|
||||||
UPDATE %s
|
UPDATE %s
|
||||||
SET public_key = ?
|
SET public_key = ?
|
||||||
WHERE id = ?;`
|
WHERE id = ?;`
|
||||||
|
|
||||||
queryFmtUpdateWebauthnDeviceRecordSignIn = `
|
queryFmtUpdateWebAuthnDeviceRecordSignIn = `
|
||||||
UPDATE %s
|
UPDATE %s
|
||||||
SET
|
SET
|
||||||
rpid = ?, last_used_at = ?, sign_count = ?,
|
rpid = ?, last_used_at = ?, sign_count = ?,
|
||||||
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
|
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
|
||||||
WHERE id = ?;`
|
WHERE id = ?;`
|
||||||
|
|
||||||
queryFmtUpdateWebauthnDeviceRecordSignInByUsername = `
|
queryFmtUpdateWebAuthnDeviceRecordSignInByUsername = `
|
||||||
UPDATE %s
|
UPDATE %s
|
||||||
SET
|
SET
|
||||||
rpid = ?, last_used_at = ?, sign_count = ?,
|
rpid = ?, last_used_at = ?, sign_count = ?,
|
||||||
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
|
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
|
||||||
WHERE username = ? AND kid = ?;`
|
WHERE username = ? AND kid = ?;`
|
||||||
|
|
||||||
queryFmtUpsertWebauthnDevice = `
|
queryFmtUpsertWebAuthnDevice = `
|
||||||
REPLACE INTO %s (created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning)
|
REPLACE INTO %s (created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
|
||||||
|
|
||||||
queryFmtUpsertWebauthnDevicePostgreSQL = `
|
queryFmtUpsertWebAuthnDevicePostgreSQL = `
|
||||||
INSERT INTO %s (created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning)
|
INSERT INTO %s (created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
ON CONFLICT (username, description)
|
ON CONFLICT (username, description)
|
||||||
DO UPDATE SET created_at = $1, last_used_at = $2, rpid = $3, kid = $6, public_key = $7, attestation_type = $8, transport = $9, aaguid = $10, sign_count = $11, clone_warning = $12;`
|
DO UPDATE SET created_at = $1, last_used_at = $2, rpid = $3, kid = $6, public_key = $7, attestation_type = $8, transport = $9, aaguid = $10, sign_count = $11, clone_warning = $12;`
|
||||||
|
|
||||||
queryFmtDeleteWebauthnDevice = `
|
queryFmtDeleteWebAuthnDevice = `
|
||||||
DELETE FROM %s
|
DELETE FROM %s
|
||||||
WHERE kid = ?;`
|
WHERE kid = ?;`
|
||||||
|
|
||||||
queryFmtDeleteWebauthnDeviceByUsername = `
|
queryFmtDeleteWebAuthnDeviceByUsername = `
|
||||||
DELETE FROM %s
|
DELETE FROM %s
|
||||||
WHERE username = ?;`
|
WHERE username = ?;`
|
||||||
|
|
||||||
queryFmtDeleteWebauthnDeviceByUsernameAndDescription = `
|
queryFmtDeleteWebAuthnDeviceByUsernameAndDescription = `
|
||||||
DELETE FROM %s
|
DELETE FROM %s
|
||||||
WHERE username = ? AND description = ?;`
|
WHERE username = ? AND description = ?;`
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,7 +32,7 @@ type encOAuth2Session struct {
|
||||||
Session []byte `db:"session_data"`
|
Session []byte `db:"session_data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type encWebauthnDevice struct {
|
type encWebAuthnDevice struct {
|
||||||
ID int `db:"id"`
|
ID int `db:"id"`
|
||||||
PublicKey []byte `db:"public_key"`
|
PublicKey []byte `db:"public_key"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
IndexRoute,
|
IndexRoute,
|
||||||
LogoutRoute,
|
LogoutRoute,
|
||||||
RegisterOneTimePasswordRoute,
|
RegisterOneTimePasswordRoute,
|
||||||
RegisterWebauthnRoute,
|
RegisterWebAuthnRoute,
|
||||||
ResetPasswordStep1Route,
|
ResetPasswordStep1Route,
|
||||||
ResetPasswordStep2Route,
|
ResetPasswordStep2Route,
|
||||||
} from "@constants/Routes";
|
} from "@constants/Routes";
|
||||||
|
@ -28,7 +28,7 @@ import {
|
||||||
getTheme,
|
getTheme,
|
||||||
} from "@utils/Configuration";
|
} from "@utils/Configuration";
|
||||||
import RegisterOneTimePassword from "@views/DeviceRegistration/RegisterOneTimePassword";
|
import RegisterOneTimePassword from "@views/DeviceRegistration/RegisterOneTimePassword";
|
||||||
import RegisterWebauthn from "@views/DeviceRegistration/RegisterWebauthn";
|
import RegisterWebAuthn from "@views/DeviceRegistration/RegisterWebAuthn";
|
||||||
import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage";
|
import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage";
|
||||||
import ConsentView from "@views/LoginPortal/ConsentView/ConsentView";
|
import ConsentView from "@views/LoginPortal/ConsentView/ConsentView";
|
||||||
import LoginPortal from "@views/LoginPortal/LoginPortal";
|
import LoginPortal from "@views/LoginPortal/LoginPortal";
|
||||||
|
@ -89,7 +89,7 @@ const App: React.FC<Props> = (props: Props) => {
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={ResetPasswordStep1Route} element={<ResetPasswordStep1 />} />
|
<Route path={ResetPasswordStep1Route} element={<ResetPasswordStep1 />} />
|
||||||
<Route path={ResetPasswordStep2Route} element={<ResetPasswordStep2 />} />
|
<Route path={ResetPasswordStep2Route} element={<ResetPasswordStep2 />} />
|
||||||
<Route path={RegisterWebauthnRoute} element={<RegisterWebauthn />} />
|
<Route path={RegisterWebAuthnRoute} element={<RegisterWebAuthn />} />
|
||||||
<Route path={RegisterOneTimePasswordRoute} element={<RegisterOneTimePassword />} />
|
<Route path={RegisterOneTimePasswordRoute} element={<RegisterOneTimePassword />} />
|
||||||
<Route path={LogoutRoute} element={<SignOut />} />
|
<Route path={LogoutRoute} element={<SignOut />} />
|
||||||
<Route path={ConsentRoute} element={<ConsentView />} />
|
<Route path={ConsentRoute} element={<ConsentView />} />
|
||||||
|
|
|
@ -3,12 +3,12 @@ export const AuthenticatedRoute: string = "/authenticated";
|
||||||
export const ConsentRoute: string = "/consent";
|
export const ConsentRoute: string = "/consent";
|
||||||
|
|
||||||
export const SecondFactorRoute: string = "/2fa/";
|
export const SecondFactorRoute: string = "/2fa/";
|
||||||
export const SecondFactorWebauthnSubRoute: string = "webauthn";
|
export const SecondFactorWebAuthnSubRoute: string = "webauthn";
|
||||||
export const SecondFactorTOTPSubRoute: string = "one-time-password";
|
export const SecondFactorTOTPSubRoute: string = "one-time-password";
|
||||||
export const SecondFactorPushSubRoute: string = "push-notification";
|
export const SecondFactorPushSubRoute: string = "push-notification";
|
||||||
|
|
||||||
export const ResetPasswordStep1Route: string = "/reset-password/step1";
|
export const ResetPasswordStep1Route: string = "/reset-password/step1";
|
||||||
export const ResetPasswordStep2Route: string = "/reset-password/step2";
|
export const ResetPasswordStep2Route: string = "/reset-password/step2";
|
||||||
export const RegisterWebauthnRoute: string = "/webauthn/register";
|
export const RegisterWebAuthnRoute: string = "/webauthn/register";
|
||||||
export const RegisterOneTimePasswordRoute: string = "/one-time-password/register";
|
export const RegisterOneTimePasswordRoute: string = "/one-time-password/register";
|
||||||
export const LogoutRoute: string = "/logout";
|
export const LogoutRoute: string = "/logout";
|
||||||
|
|
|
@ -11,11 +11,11 @@ export const FirstFactorPath = basePath + "/api/firstfactor";
|
||||||
export const InitiateTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/start";
|
export const InitiateTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/start";
|
||||||
export const CompleteTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/finish";
|
export const CompleteTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/finish";
|
||||||
|
|
||||||
export const WebauthnIdentityStartPath = basePath + "/api/secondfactor/webauthn/identity/start";
|
export const WebAuthnIdentityStartPath = basePath + "/api/secondfactor/webauthn/identity/start";
|
||||||
export const WebauthnIdentityFinishPath = basePath + "/api/secondfactor/webauthn/identity/finish";
|
export const WebAuthnIdentityFinishPath = basePath + "/api/secondfactor/webauthn/identity/finish";
|
||||||
export const WebauthnAttestationPath = basePath + "/api/secondfactor/webauthn/attestation";
|
export const WebAuthnAttestationPath = basePath + "/api/secondfactor/webauthn/attestation";
|
||||||
|
|
||||||
export const WebauthnAssertionPath = basePath + "/api/secondfactor/webauthn/assertion";
|
export const WebAuthnAssertionPath = basePath + "/api/secondfactor/webauthn/assertion";
|
||||||
|
|
||||||
export const InitiateDuoDeviceSelectionPath = basePath + "/api/secondfactor/duo_devices";
|
export const InitiateDuoDeviceSelectionPath = basePath + "/api/secondfactor/duo_devices";
|
||||||
export const CompleteDuoDeviceSelectionPath = basePath + "/api/secondfactor/duo_device";
|
export const CompleteDuoDeviceSelectionPath = basePath + "/api/secondfactor/duo_device";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CompleteTOTPRegistrationPath, InitiateTOTPRegistrationPath, WebauthnIdentityStartPath } from "@services/Api";
|
import { CompleteTOTPRegistrationPath, InitiateTOTPRegistrationPath, WebAuthnIdentityStartPath } from "@services/Api";
|
||||||
import { Post, PostWithOptionalResponse } from "@services/Client";
|
import { Post, PostWithOptionalResponse } from "@services/Client";
|
||||||
|
|
||||||
export async function initiateTOTPRegistrationProcess() {
|
export async function initiateTOTPRegistrationProcess() {
|
||||||
|
@ -14,6 +14,6 @@ export async function completeTOTPRegistrationProcess(processToken: string) {
|
||||||
return Post<CompleteTOTPRegistrationResponse>(CompleteTOTPRegistrationPath, { token: processToken });
|
return Post<CompleteTOTPRegistrationResponse>(CompleteTOTPRegistrationPath, { token: processToken });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initiateWebauthnRegistrationProcess() {
|
export async function initiateWebAuthnRegistrationProcess() {
|
||||||
return PostWithOptionalResponse(WebauthnIdentityStartPath);
|
return PostWithOptionalResponse(WebAuthnIdentityStartPath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,14 @@ import {
|
||||||
import {
|
import {
|
||||||
OptionalDataServiceResponse,
|
OptionalDataServiceResponse,
|
||||||
ServiceResponse,
|
ServiceResponse,
|
||||||
WebauthnAssertionPath,
|
WebAuthnAssertionPath,
|
||||||
WebauthnAttestationPath,
|
WebAuthnAttestationPath,
|
||||||
WebauthnIdentityFinishPath,
|
WebAuthnIdentityFinishPath,
|
||||||
} from "@services/Api";
|
} from "@services/Api";
|
||||||
import { SignInResponse } from "@services/SignIn";
|
import { SignInResponse } from "@services/SignIn";
|
||||||
import { getBase64WebEncodingFromBytes, getBytesFromBase64 } from "@utils/Base64";
|
import { getBase64WebEncodingFromBytes, getBytesFromBase64 } from "@utils/Base64";
|
||||||
|
|
||||||
export function isWebauthnSecure(): boolean {
|
export function isWebAuthnSecure(): boolean {
|
||||||
if (window.isSecureContext) {
|
if (window.isSecureContext) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -35,12 +35,12 @@ export function isWebauthnSecure(): boolean {
|
||||||
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isWebauthnSupported(): boolean {
|
export function isWebAuthnSupported(): boolean {
|
||||||
return window?.PublicKeyCredential !== undefined && typeof window.PublicKeyCredential === "function";
|
return window?.PublicKeyCredential !== undefined && typeof window.PublicKeyCredential === "function";
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isWebauthnPlatformAuthenticatorAvailable(): Promise<boolean> {
|
export async function isWebAuthnPlatformAuthenticatorAvailable(): Promise<boolean> {
|
||||||
if (!isWebauthnSupported()) {
|
if (!isWebAuthnSupported()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ function getAssertionResultFromDOMException(
|
||||||
async function getAttestationCreationOptions(token: string): Promise<PublicKeyCredentialCreationOptionsStatus> {
|
async function getAttestationCreationOptions(token: string): Promise<PublicKeyCredentialCreationOptionsStatus> {
|
||||||
let response: AxiosResponse<ServiceResponse<CredentialCreation>>;
|
let response: AxiosResponse<ServiceResponse<CredentialCreation>>;
|
||||||
|
|
||||||
response = await axios.post<ServiceResponse<CredentialCreation>>(WebauthnIdentityFinishPath, {
|
response = await axios.post<ServiceResponse<CredentialCreation>>(WebAuthnIdentityFinishPath, {
|
||||||
token: token,
|
token: token,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ async function getAttestationCreationOptions(token: string): Promise<PublicKeyCr
|
||||||
export async function getAssertionRequestOptions(): Promise<PublicKeyCredentialRequestOptionsStatus> {
|
export async function getAssertionRequestOptions(): Promise<PublicKeyCredentialRequestOptionsStatus> {
|
||||||
let response: AxiosResponse<ServiceResponse<CredentialRequest>>;
|
let response: AxiosResponse<ServiceResponse<CredentialRequest>>;
|
||||||
|
|
||||||
response = await axios.get<ServiceResponse<CredentialRequest>>(WebauthnAssertionPath);
|
response = await axios.get<ServiceResponse<CredentialRequest>>(WebAuthnAssertionPath);
|
||||||
|
|
||||||
if (response.data.status !== "OK" || response.data.data == null) {
|
if (response.data.status !== "OK" || response.data.data == null) {
|
||||||
return {
|
return {
|
||||||
|
@ -317,7 +317,7 @@ async function postAttestationPublicKeyCredentialResult(
|
||||||
): Promise<AxiosResponse<OptionalDataServiceResponse<any>>> {
|
): Promise<AxiosResponse<OptionalDataServiceResponse<any>>> {
|
||||||
const credentialJSON = encodeAttestationPublicKeyCredential(credential);
|
const credentialJSON = encodeAttestationPublicKeyCredential(credential);
|
||||||
|
|
||||||
return axios.post<OptionalDataServiceResponse<any>>(WebauthnAttestationPath, credentialJSON);
|
return axios.post<OptionalDataServiceResponse<any>>(WebAuthnAttestationPath, credentialJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postAssertionPublicKeyCredentialResult(
|
export async function postAssertionPublicKeyCredentialResult(
|
||||||
|
@ -328,7 +328,7 @@ export async function postAssertionPublicKeyCredentialResult(
|
||||||
): Promise<AxiosResponse<ServiceResponse<SignInResponse>>> {
|
): Promise<AxiosResponse<ServiceResponse<SignInResponse>>> {
|
||||||
const credentialJSON = encodeAssertionPublicKeyCredential(credential, targetURL, workflow, workflowID);
|
const credentialJSON = encodeAssertionPublicKeyCredential(credential, targetURL, workflow, workflowID);
|
||||||
|
|
||||||
return axios.post<ServiceResponse<SignInResponse>>(WebauthnAssertionPath, credentialJSON);
|
return axios.post<ServiceResponse<SignInResponse>>(WebAuthnAssertionPath, credentialJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function performAttestationCeremony(token: string): Promise<AttestationResult> {
|
export async function performAttestationCeremony(token: string): Promise<AttestationResult> {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { AttestationResult } from "@models/Webauthn";
|
||||||
import { FirstFactorPath } from "@services/Api";
|
import { FirstFactorPath } from "@services/Api";
|
||||||
import { performAttestationCeremony } from "@services/Webauthn";
|
import { performAttestationCeremony } from "@services/Webauthn";
|
||||||
|
|
||||||
const RegisterWebauthn = function () {
|
const RegisterWebAuthn = function () {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { createErrorNotification } = useNotifications();
|
const { createErrorNotification } = useNotifications();
|
||||||
|
@ -99,7 +99,7 @@ const RegisterWebauthn = function () {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RegisterWebauthn;
|
export default RegisterWebAuthn;
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
icon: {
|
icon: {
|
|
@ -8,7 +8,7 @@ import {
|
||||||
SecondFactorPushSubRoute,
|
SecondFactorPushSubRoute,
|
||||||
SecondFactorRoute,
|
SecondFactorRoute,
|
||||||
SecondFactorTOTPSubRoute,
|
SecondFactorTOTPSubRoute,
|
||||||
SecondFactorWebauthnSubRoute,
|
SecondFactorWebAuthnSubRoute,
|
||||||
} from "@constants/Routes";
|
} from "@constants/Routes";
|
||||||
import { RedirectionURL } from "@constants/SearchParams";
|
import { RedirectionURL } from "@constants/SearchParams";
|
||||||
import { useConfiguration } from "@hooks/Configuration";
|
import { useConfiguration } from "@hooks/Configuration";
|
||||||
|
@ -144,7 +144,7 @@ const LoginPortal = function (props: Props) {
|
||||||
redirect(AuthenticatedRoute, false);
|
redirect(AuthenticatedRoute, false);
|
||||||
} else {
|
} else {
|
||||||
if (userInfo.method === SecondFactorMethod.Webauthn) {
|
if (userInfo.method === SecondFactorMethod.Webauthn) {
|
||||||
redirect(`${SecondFactorRoute}${SecondFactorWebauthnSubRoute}`);
|
redirect(`${SecondFactorRoute}${SecondFactorWebAuthnSubRoute}`);
|
||||||
} else if (userInfo.method === SecondFactorMethod.MobilePush) {
|
} else if (userInfo.method === SecondFactorMethod.MobilePush) {
|
||||||
redirect(`${SecondFactorRoute}${SecondFactorPushSubRoute}`);
|
redirect(`${SecondFactorRoute}${SecondFactorPushSubRoute}`);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Route, Routes, useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
SecondFactorPushSubRoute,
|
SecondFactorPushSubRoute,
|
||||||
SecondFactorTOTPSubRoute,
|
SecondFactorTOTPSubRoute,
|
||||||
SecondFactorWebauthnSubRoute,
|
SecondFactorWebAuthnSubRoute,
|
||||||
LogoutRoute as SignOutRoute,
|
LogoutRoute as SignOutRoute,
|
||||||
} from "@constants/Routes";
|
} from "@constants/Routes";
|
||||||
import { useNotifications } from "@hooks/NotificationsContext";
|
import { useNotifications } from "@hooks/NotificationsContext";
|
||||||
|
@ -16,10 +16,10 @@ import LoginLayout from "@layouts/LoginLayout";
|
||||||
import { Configuration } from "@models/Configuration";
|
import { Configuration } from "@models/Configuration";
|
||||||
import { SecondFactorMethod } from "@models/Methods";
|
import { SecondFactorMethod } from "@models/Methods";
|
||||||
import { UserInfo } from "@models/UserInfo";
|
import { UserInfo } from "@models/UserInfo";
|
||||||
import { initiateTOTPRegistrationProcess, initiateWebauthnRegistrationProcess } from "@services/RegisterDevice";
|
import { initiateTOTPRegistrationProcess, initiateWebAuthnRegistrationProcess } from "@services/RegisterDevice";
|
||||||
import { AuthenticationLevel } from "@services/State";
|
import { AuthenticationLevel } from "@services/State";
|
||||||
import { setPreferred2FAMethod } from "@services/UserInfo";
|
import { setPreferred2FAMethod } from "@services/UserInfo";
|
||||||
import { isWebauthnSupported } from "@services/Webauthn";
|
import { isWebAuthnSupported } from "@services/Webauthn";
|
||||||
import MethodSelectionDialog from "@views/LoginPortal/SecondFactor/MethodSelectionDialog";
|
import MethodSelectionDialog from "@views/LoginPortal/SecondFactor/MethodSelectionDialog";
|
||||||
import OneTimePasswordMethod from "@views/LoginPortal/SecondFactor/OneTimePasswordMethod";
|
import OneTimePasswordMethod from "@views/LoginPortal/SecondFactor/OneTimePasswordMethod";
|
||||||
import PushNotificationMethod from "@views/LoginPortal/SecondFactor/PushNotificationMethod";
|
import PushNotificationMethod from "@views/LoginPortal/SecondFactor/PushNotificationMethod";
|
||||||
|
@ -45,7 +45,7 @@ const SecondFactorForm = function (props: Props) {
|
||||||
const { t: translate } = useTranslation();
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setWebauthnSupported(isWebauthnSupported());
|
setWebauthnSupported(isWebAuthnSupported());
|
||||||
}, [setWebauthnSupported]);
|
}, [setWebauthnSupported]);
|
||||||
|
|
||||||
const initiateRegistration = (initiateRegistrationFunc: () => Promise<void>) => {
|
const initiateRegistration = (initiateRegistrationFunc: () => Promise<void>) => {
|
||||||
|
@ -124,14 +124,14 @@ const SecondFactorForm = function (props: Props) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={SecondFactorWebauthnSubRoute}
|
path={SecondFactorWebAuthnSubRoute}
|
||||||
element={
|
element={
|
||||||
<WebauthnMethod
|
<WebauthnMethod
|
||||||
id="webauthn-method"
|
id="webauthn-method"
|
||||||
authenticationLevel={props.authenticationLevel}
|
authenticationLevel={props.authenticationLevel}
|
||||||
// Whether the user has a Webauthn device registered already
|
// Whether the user has a Webauthn device registered already
|
||||||
registered={props.userInfo.has_webauthn}
|
registered={props.userInfo.has_webauthn}
|
||||||
onRegisterClick={initiateRegistration(initiateWebauthnRegistrationProcess)}
|
onRegisterClick={initiateRegistration(initiateWebAuthnRegistrationProcess)}
|
||||||
onSignInError={(err) => createErrorNotification(err.message)}
|
onSignInError={(err) => createErrorNotification(err.message)}
|
||||||
onSignInSuccess={props.onAuthenticationSuccess}
|
onSignInSuccess={props.onAuthenticationSuccess}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue