Merge remote-tracking branch 'origin/master' into feat-settings-ui

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>

# Conflicts:
#	internal/handlers/handler_register_webauthn.go
#	internal/handlers/webauthn.go
#	internal/handlers/webauthn_test.go
#	internal/mocks/storage.go
#	internal/model/webauthn.go
#	internal/storage/provider.go
#	internal/storage/sql_provider.go
#	web/package.json
#	web/pnpm-lock.yaml
#	web/src/layouts/LoginLayout.tsx
feat-otp-verification
James Elliott 2023-04-11 14:40:09 +10:00
commit 7fdcc351d4
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
87 changed files with 4280 additions and 6011 deletions

View File

@ -57,7 +57,7 @@ This is a list of the key features of Authelia:
* Several second factor methods:
* **[Security Keys](https://www.authelia.com/overview/authentication/security-key/)** that support
[FIDO2]&nbsp;[Webauthn] with devices like a [YubiKey].
[FIDO2]&nbsp;[WebAuthn] with devices like a [YubiKey].
* **[Time-based One-Time password](https://www.authelia.com/overview/authentication/one-time-password/)**
with compatible authenticator applications.
* **[Mobile Push Notifications](https://www.authelia.com/overview/authentication/push-notification/)**
@ -399,7 +399,7 @@ Companies contributing to Authelia via Open Collective will have a special menti
[TOTP]: https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm
[FIDO2]: https://www.yubico.com/authentication-standards/fido2/
[YubiKey]: https://www.yubico.com/products/yubikey-5-overview/
[Webauthn]: https://www.yubico.com/authentication-standards/webauthn/
[WebAuthn]: https://www.yubico.com/authentication-standards/webauthn/
[auth_request]: https://nginx.org/en/docs/http/ngx_http_auth_request_module.html
[config.template.yml]: ./config.template.yml
[nginx]: https://www.authelia.com/integration/proxies/nginx/

View File

@ -7,5 +7,5 @@
package cmd
const (
versionSwaggerUI = "4.18.1"
versionSwaggerUI = "4.18.2"
)

View File

@ -49,7 +49,7 @@ authelia configuration, and authelia database prior to attempting to do so._
Notable Missing Features from this build:
- OpenID Connect 1.0 PAR
- Multi-Device Webauthn
- Multi-Device WebAuthn
- Device Registration OTP
- Container Images:
@ -144,7 +144,7 @@ Please see the [roadmap](../../roadmap/active/openid-connect.md) for more inform
##### Initial Implementation
_**Important Note:** This feature at the time of this writing, will not work well with Webauthn. Steps are being taken
_**Important Note:** This feature at the time of this writing, will not work well with WebAuthn. Steps are being taken
to address this however it will not specifically delay the release of this feature._
This release see's the initial implementation of multi-domain protection. Users will be able to configure more than a
@ -160,14 +160,14 @@ NGINX/NGINX Proxy Manager/SWAG/HAProxy with the use of the new
[Customizable Authorization Endpoints](#customizable-authorization-endpoints). This is important as it means you only
need to configure a single middleware or helper to perform automatic redirection.
## Webauthn
## WebAuthn
As part of our ongoing effort for comprehensive support for Webauthn we'll be introducing several important
As part of our ongoing effort for comprehensive support for WebAuthn we'll be introducing several important
features. Please see the [roadmap](../../roadmap/active/webauthn.md) for more information.
##### Multiple Webauthn Credentials Per-User
##### Multiple WebAuthn Credentials Per-User
In this release we see full support for multiple Webauthn credentials. This is a fairly basic feature but getting the
In this release we see full support for multiple WebAuthn credentials. This is a fairly basic feature but getting the
frontend experience right is important to us. This is going to be supported via the
[User Control Panel](#user-dashboard--control-panel).

View File

@ -37,3 +37,5 @@ this instance if you wanted to downgrade to pre1 you would need to use an Authel
| 6 | 4.37.0 | Adjusted the OpenID Connect tables to allow pre-configured consent improvements |
| 7 | 4.37.3 | Fixed some schema inconsistencies most notably the MySQL/MariaDB Engine and Collation |
| 8 | 4.38.0 | OpenID Connect 1.0 Pushed Authorization Requests |
| 9 | 4.38.0 | Fix a PostgreSQL NOT NULL constraint issue on the `aaguid` column of the `webauthn_devices` table |
| 10 | 4.38.0 | WebAuthn adjustments for multi-cookie domain changes |

View File

@ -2,7 +2,7 @@
title: "Testing"
description: "Authelia Development Testing Guidelines"
lead: "This section covers the testing guidelines."
date: 2022-06-15T17:51:47+10:00
date: 2023-03-20T15:03:52+11:00
draft: false
images: []
menu:

View File

@ -1,6 +1,6 @@
---
title: "Amir Zarrinkafsh"
date: 2022-06-15T17:51:47+10:00
date: 2023-03-19T16:29:12+10:00
draft: false
images: []
---

View File

@ -1,6 +1,6 @@
---
title: "Clément Michaud"
date: 2022-06-15T17:51:47+10:00
date: 2023-03-19T16:29:12+10:00
draft: false
images: []
---

View File

@ -1,6 +1,6 @@
---
title: "Manuel Nuñez"
date: 2022-06-15T17:51:47+10:00
date: 2023-03-19T16:29:12+10:00
draft: false
images: []
---

View File

@ -1,7 +1,7 @@
---
title: "About"
description: "About Authelia and the Authelia Team"
date: 2022-06-15T17:51:47+10:00
date: 2023-03-19T16:29:12+10:00
draft: false
images: []
aliases:

View File

@ -2,7 +2,7 @@
title: "Firezone"
description: "Integrating Firezone with the Authelia OpenID Connect Provider."
lead: ""
date: 2023-03-25T13:07:02+10:00
date: 2023-03-28T20:29:13+11:00
draft: false
images: []
menu:

View File

@ -2,7 +2,7 @@
title: "MinIO"
description: "Integrating MinIO with the Authelia OpenID Connect Provider."
lead: ""
date: 2022-06-15T17:51:47+10:00
date: 2023-03-21T11:21:23+11:00
draft: false
images: []
menu:

View File

@ -2,7 +2,7 @@
title: "Misago"
description: "Integrating Misago with the Authelia OpenID Connect Provider."
lead: ""
date: 2023-03-04T13:20:00+00:00
date: 2023-03-14T08:51:13+11:00
draft: false
images: []
menu:

View File

@ -73,7 +73,7 @@ serving Authelia at `auth.example.com`.
```nginx
## Set $authelia_backend to route requests to the current domain by default
set $authelia_backend $http_host;
## In order for Webauthn to work with multiple domains authelia must operate on a separate subdomain
## In order for WebAuthn to work with multiple domains authelia must operate on a separate subdomain
## To use authelia on a separate subdomain:
## * comment the $authelia_backend line above
## * rename /config/nginx/proxy-confs/authelia.conf.sample to /config/nginx/proxy-confs/authelia.conf
@ -88,7 +88,7 @@ serving Authelia at `auth.example.com`.
```nginx
## Set $authelia_backend to route requests to the current domain by default
# set $authelia_backend $http_host;
## In order for Webauthn to work with multiple domains authelia must operate on a separate subdomain
## In order for WebAuthn to work with multiple domains authelia must operate on a separate subdomain
## To use authelia on a separate subdomain:
## * comment the $authelia_backend line above
## * rename /config/nginx/proxy-confs/authelia.conf.sample to /config/nginx/proxy-confs/authelia.conf

View File

@ -63,5 +63,5 @@ authelia storage user --help
* [authelia storage](authelia_storage.md) - Manage the Authelia storage
* [authelia storage user identifiers](authelia_storage_user_identifiers.md) - Manage user opaque identifiers
* [authelia storage user totp](authelia_storage_user_totp.md) - Manage TOTP configurations
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage Webauthn devices
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage WebAuthn devices

View File

@ -14,13 +14,13 @@ toc: true
## authelia storage user webauthn
Manage Webauthn devices
Manage WebAuthn devices
### Synopsis
Manage Webauthn devices.
Manage WebAuthn devices.
This subcommand allows interacting with Webauthn devices.
This subcommand allows interacting with WebAuthn devices.
### Examples
@ -61,8 +61,8 @@ authelia storage user webauthn --help
### SEE ALSO
* [authelia storage user](authelia_storage_user.md) - Manages user settings
* [authelia storage user webauthn delete](authelia_storage_user_webauthn_delete.md) - Delete a Webauthn device
* [authelia storage user webauthn export](authelia_storage_user_webauthn_export.md) - Perform exports of the Webauthn devices
* [authelia storage user webauthn import](authelia_storage_user_webauthn_import.md) - Perform imports of the Webauthn devices
* [authelia storage user webauthn list](authelia_storage_user_webauthn_list.md) - List Webauthn devices
* [authelia storage user webauthn delete](authelia_storage_user_webauthn_delete.md) - Delete a WebAuthn device
* [authelia storage user webauthn export](authelia_storage_user_webauthn_export.md) - Perform exports of the WebAuthn devices
* [authelia storage user webauthn import](authelia_storage_user_webauthn_import.md) - Perform imports of the WebAuthn devices
* [authelia storage user webauthn list](authelia_storage_user_webauthn_list.md) - List WebAuthn devices

View File

@ -14,13 +14,13 @@ toc: true
## authelia storage user webauthn delete
Delete a Webauthn device
Delete a WebAuthn device
### Synopsis
Delete a Webauthn device.
Delete a WebAuthn device.
This subcommand allows deleting a Webauthn device directly from the database.
This subcommand allows deleting a WebAuthn device directly from the database.
```
authelia storage user webauthn delete [username] [flags]
@ -75,5 +75,5 @@ authelia storage user webauthn delete --kid abc123 --encryption-key b3453fde-ecc
### SEE ALSO
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage Webauthn devices
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage WebAuthn devices

View File

@ -14,13 +14,13 @@ toc: true
## authelia storage user webauthn export
Perform exports of the Webauthn devices
Perform exports of the WebAuthn devices
### Synopsis
Perform exports of the Webauthn devices.
Perform exports of the WebAuthn devices.
This subcommand allows exporting Webauthn devices to various formats.
This subcommand allows exporting WebAuthn devices to various formats.
```
authelia storage user webauthn export [flags]
@ -68,5 +68,5 @@ authelia storage user webauthn export--encryption-key b3453fde-ecc2-4a1f-9422-27
### SEE ALSO
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage Webauthn devices
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage WebAuthn devices

View File

@ -14,13 +14,13 @@ toc: true
## authelia storage user webauthn import
Perform imports of the Webauthn devices
Perform imports of the WebAuthn devices
### Synopsis
Perform imports of the Webauthn devices.
Perform imports of the WebAuthn devices.
This subcommand allows importing Webauthn devices from various formats.
This subcommand allows importing WebAuthn devices from various formats.
```
authelia storage user webauthn import <filename> [flags]
@ -67,5 +67,5 @@ authelia storage user webauthn import --file authelia.export.webauthn.yaml --enc
### SEE ALSO
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage Webauthn devices
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage WebAuthn devices

View File

@ -14,13 +14,13 @@ toc: true
## authelia storage user webauthn list
List Webauthn devices
List WebAuthn devices
### Synopsis
List Webauthn devices.
List WebAuthn devices.
This subcommand allows listing Webauthn devices.
This subcommand allows listing WebAuthn devices.
```
authelia storage user webauthn list [username] [flags]
@ -69,5 +69,5 @@ authelia storage user webauthn list john --encryption-key b3453fde-ecc2-4a1f-942
### SEE ALSO
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage Webauthn devices
* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage WebAuthn devices

View File

@ -39,27 +39,27 @@
},
"devDependencies": {
"@babel/cli": "7.21.0",
"@babel/core": "7.21.0",
"@babel/preset-env": "7.20.2",
"@babel/core": "7.21.4",
"@babel/preset-env": "7.21.4",
"@fullhuman/postcss-purgecss": "5.0.0",
"@hyas/images": "0.3.2",
"@popperjs/core": "2.11.6",
"@popperjs/core": "2.11.7",
"auto-changelog": "2.4.0",
"autoprefixer": "10.4.13",
"autoprefixer": "10.4.14",
"bootstrap": "5.2.3",
"bootstrap-icons": "1.10.3",
"bootstrap-icons": "1.10.4",
"clipboard": "2.0.11",
"eslint": "8.35.0",
"eslint": "8.38.0",
"exec-bin": "1.0.0",
"flexsearch": "0.7.31",
"highlight.js": "11.7.0",
"hugo-installer": "4.0.1",
"instant.page": "5.1.1",
"instant.page": "5.2.0",
"katex": "0.16.4",
"lazysizes": "5.3.2",
"markdownlint-cli2": "0.6.0",
"netlify-plugin-submit-sitemap": "0.4.0",
"node-fetch": "3.3.0",
"node-fetch": "3.3.1",
"postcss": "8.4.21",
"postcss-cli": "10.1.0",
"purgecss-whitelister": "2.4.0",
@ -68,6 +68,6 @@
"stylelint-config-standard-scss": "6.1.0"
},
"otherDependencies": {
"hugo": "0.111.2"
"hugo": "0.111.3"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -177,56 +177,56 @@ This subcommand allows manually adding an opaque identifier for a user to the da
authelia storage user identifiers add john --identifier f0919359-9d15-4e15-bcba-83b41620a073 --config config.yml
authelia storage user identifiers add john --identifier f0919359-9d15-4e15-bcba-83b41620a073 --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw`
cmdAutheliaStorageUserWebauthnShort = "Manage Webauthn devices"
cmdAutheliaStorageUserWebAuthnShort = "Manage WebAuthn devices"
cmdAutheliaStorageUserWebauthnLong = `Manage Webauthn devices.
cmdAutheliaStorageUserWebAuthnLong = `Manage WebAuthn devices.
This subcommand allows interacting with Webauthn devices.`
This subcommand allows interacting with WebAuthn devices.`
cmdAutheliaStorageUserWebauthnExample = `authelia storage user webauthn --help`
cmdAutheliaStorageUserWebAuthnExample = `authelia storage user webauthn --help`
cmdAutheliaStorageUserWebauthnImportShort = "Perform imports of the Webauthn devices"
cmdAutheliaStorageUserWebAuthnImportShort = "Perform imports of the WebAuthn devices"
cmdAutheliaStorageUserWebauthnImportLong = `Perform imports of the Webauthn devices.
cmdAutheliaStorageUserWebAuthnImportLong = `Perform imports of the WebAuthn devices.
This subcommand allows importing Webauthn devices from various formats.`
This subcommand allows importing WebAuthn devices from various formats.`
cmdAutheliaStorageUserWebauthnImportExample = `authelia storage user webauthn export
cmdAutheliaStorageUserWebAuthnImportExample = `authelia storage user webauthn export
authelia storage user webauthn import --file authelia.export.webauthn.yaml
authelia storage user webauthn import --file authelia.export.webauthn.yaml --config config.yml
authelia storage user webauthn import --file authelia.export.webauthn.yaml --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw`
cmdAutheliaStorageUserWebauthnExportShort = "Perform exports of the Webauthn devices"
cmdAutheliaStorageUserWebAuthnExportShort = "Perform exports of the WebAuthn devices"
cmdAutheliaStorageUserWebauthnExportLong = `Perform exports of the Webauthn devices.
cmdAutheliaStorageUserWebAuthnExportLong = `Perform exports of the WebAuthn devices.
This subcommand allows exporting Webauthn devices to various formats.`
This subcommand allows exporting WebAuthn devices to various formats.`
cmdAutheliaStorageUserWebauthnExportExample = `authelia storage user webauthn export
cmdAutheliaStorageUserWebAuthnExportExample = `authelia storage user webauthn export
authelia storage user webauthn export --file authelia.export.webauthn.yaml
authelia storage user webauthn export --config config.yml
authelia storage user webauthn export--encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw`
cmdAutheliaStorageUserWebauthnListShort = "List Webauthn devices"
cmdAutheliaStorageUserWebAuthnListShort = "List WebAuthn devices"
cmdAutheliaStorageUserWebauthnListLong = `List Webauthn devices.
cmdAutheliaStorageUserWebAuthnListLong = `List WebAuthn devices.
This subcommand allows listing Webauthn devices.`
This subcommand allows listing WebAuthn devices.`
cmdAutheliaStorageUserWebauthnListExample = `authelia storage user webauthn list
cmdAutheliaStorageUserWebAuthnListExample = `authelia storage user webauthn list
authelia storage user webauthn list john
authelia storage user webauthn list --config config.yml
authelia storage user webauthn list john --config config.yml
authelia storage user webauthn list --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
authelia storage user webauthn list john --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw`
cmdAutheliaStorageUserWebauthnDeleteShort = "Delete a Webauthn device"
cmdAutheliaStorageUserWebAuthnDeleteShort = "Delete a WebAuthn device"
cmdAutheliaStorageUserWebauthnDeleteLong = `Delete a Webauthn device.
cmdAutheliaStorageUserWebAuthnDeleteLong = `Delete a WebAuthn device.
This subcommand allows deleting a Webauthn device directly from the database.`
This subcommand allows deleting a WebAuthn device directly from the database.`
cmdAutheliaStorageUserWebauthnDeleteExample = `authelia storage user webauthn delete john --all
cmdAutheliaStorageUserWebAuthnDeleteExample = `authelia storage user webauthn delete john --all
authelia storage user webauthn delete john --all --config config.yml
authelia storage user webauthn delete john --all --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
authelia storage user webauthn delete john --description Primary

View File

@ -68,7 +68,7 @@ func storageTOTPGenerateRunEOptsFromFlags(flags *pflag.FlagSet) (force bool, fil
return force, filename, secret, nil
}
func storageWebauthnDeleteRunEOptsFromFlags(flags *pflag.FlagSet, args []string) (all, byKID bool, description, kid, user string, err error) {
func storageWebAuthnDeleteRunEOptsFromFlags(flags *pflag.FlagSet, args []string) (all, byKID bool, description, kid, user string, err error) {
if len(args) != 0 {
user = args[0]
}

View File

@ -124,7 +124,7 @@ func newStorageUserCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd.AddCommand(
newStorageUserIdentifiersCmd(ctx),
newStorageUserTOTPCmd(ctx),
newStorageUserWebauthnCmd(ctx),
newStorageUserWebAuthnCmd(ctx),
)
return cmd
@ -221,34 +221,34 @@ func newStorageUserIdentifiersAddCmd(ctx *CmdCtx) (cmd *cobra.Command) {
return cmd
}
func newStorageUserWebauthnCmd(ctx *CmdCtx) (cmd *cobra.Command) {
func newStorageUserWebAuthnCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "webauthn",
Short: cmdAutheliaStorageUserWebauthnShort,
Long: cmdAutheliaStorageUserWebauthnLong,
Example: cmdAutheliaStorageUserWebauthnExample,
Short: cmdAutheliaStorageUserWebAuthnShort,
Long: cmdAutheliaStorageUserWebAuthnLong,
Example: cmdAutheliaStorageUserWebAuthnExample,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
}
cmd.AddCommand(
newStorageUserWebauthnListCmd(ctx),
newStorageUserWebauthnDeleteCmd(ctx),
newStorageUserWebauthnExportCmd(ctx),
newStorageUserWebauthnImportCmd(ctx),
newStorageUserWebAuthnListCmd(ctx),
newStorageUserWebAuthnDeleteCmd(ctx),
newStorageUserWebAuthnExportCmd(ctx),
newStorageUserWebAuthnImportCmd(ctx),
)
return cmd
}
func newStorageUserWebauthnImportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
func newStorageUserWebAuthnImportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseImportFileName,
Short: cmdAutheliaStorageUserWebauthnImportShort,
Long: cmdAutheliaStorageUserWebauthnImportLong,
Example: cmdAutheliaStorageUserWebauthnImportExample,
RunE: ctx.StorageUserWebauthnImportRunE,
Short: cmdAutheliaStorageUserWebAuthnImportShort,
Long: cmdAutheliaStorageUserWebAuthnImportLong,
Example: cmdAutheliaStorageUserWebAuthnImportExample,
RunE: ctx.StorageUserWebAuthnImportRunE,
Args: cobra.ExactArgs(1),
DisableAutoGenTag: true,
@ -257,13 +257,13 @@ func newStorageUserWebauthnImportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
return cmd
}
func newStorageUserWebauthnExportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
func newStorageUserWebAuthnExportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseExport,
Short: cmdAutheliaStorageUserWebauthnExportShort,
Long: cmdAutheliaStorageUserWebauthnExportLong,
Example: cmdAutheliaStorageUserWebauthnExportExample,
RunE: ctx.StorageUserWebauthnExportRunE,
Short: cmdAutheliaStorageUserWebAuthnExportShort,
Long: cmdAutheliaStorageUserWebAuthnExportLong,
Example: cmdAutheliaStorageUserWebAuthnExportExample,
RunE: ctx.StorageUserWebAuthnExportRunE,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
@ -274,13 +274,13 @@ func newStorageUserWebauthnExportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
return cmd
}
func newStorageUserWebauthnListCmd(ctx *CmdCtx) (cmd *cobra.Command) {
func newStorageUserWebAuthnListCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "list [username]",
Short: cmdAutheliaStorageUserWebauthnListShort,
Long: cmdAutheliaStorageUserWebauthnListLong,
Example: cmdAutheliaStorageUserWebauthnListExample,
RunE: ctx.StorageUserWebauthnListRunE,
Short: cmdAutheliaStorageUserWebAuthnListShort,
Long: cmdAutheliaStorageUserWebAuthnListLong,
Example: cmdAutheliaStorageUserWebAuthnListExample,
RunE: ctx.StorageUserWebAuthnListRunE,
Args: cobra.MaximumNArgs(1),
DisableAutoGenTag: true,
@ -289,13 +289,13 @@ func newStorageUserWebauthnListCmd(ctx *CmdCtx) (cmd *cobra.Command) {
return cmd
}
func newStorageUserWebauthnDeleteCmd(ctx *CmdCtx) (cmd *cobra.Command) {
func newStorageUserWebAuthnDeleteCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "delete [username]",
Short: cmdAutheliaStorageUserWebauthnDeleteShort,
Long: cmdAutheliaStorageUserWebauthnDeleteLong,
Example: cmdAutheliaStorageUserWebauthnDeleteExample,
RunE: ctx.StorageUserWebauthnDeleteRunE,
Short: cmdAutheliaStorageUserWebAuthnDeleteShort,
Long: cmdAutheliaStorageUserWebAuthnDeleteLong,
Example: cmdAutheliaStorageUserWebAuthnDeleteExample,
RunE: ctx.StorageUserWebAuthnDeleteRunE,
Args: cobra.MaximumNArgs(1),
DisableAutoGenTag: true,

View File

@ -415,7 +415,7 @@ func (ctx *CmdCtx) StorageSchemaInfoRunE(_ *cobra.Command, _ []string) (err erro
return nil
}
func (ctx *CmdCtx) StorageUserWebauthnExportRunE(cmd *cobra.Command, args []string) (err error) {
func (ctx *CmdCtx) StorageUserWebAuthnExportRunE(cmd *cobra.Command, args []string) (err error) {
defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
@ -443,19 +443,19 @@ func (ctx *CmdCtx) StorageUserWebauthnExportRunE(cmd *cobra.Command, args []stri
count := 0
var (
devices []model.WebauthnDevice
devices []model.WebAuthnDevice
)
export := &model.WebauthnDeviceExport{
WebauthnDevices: nil,
export := &model.WebAuthnDeviceExport{
WebAuthnDevices: nil,
}
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
}
export.WebauthnDevices = append(export.WebauthnDevices, devices...)
export.WebAuthnDevices = append(export.WebAuthnDevices, devices...)
l := len(devices)
@ -476,12 +476,12 @@ func (ctx *CmdCtx) StorageUserWebauthnExportRunE(cmd *cobra.Command, args []stri
return fmt.Errorf("error occurred writing to file '%s': %w", filename, err)
}
fmt.Printf(cliOutputFmtSuccessfulUserExportFile, count, "Webauthn devices", "YAML", filename)
fmt.Printf(cliOutputFmtSuccessfulUserExportFile, count, "WebAuthn devices", "YAML", filename)
return nil
}
func (ctx *CmdCtx) StorageUserWebauthnImportRunE(cmd *cobra.Command, args []string) (err error) {
func (ctx *CmdCtx) StorageUserWebAuthnImportRunE(cmd *cobra.Command, args []string) (err error) {
defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
@ -507,58 +507,58 @@ func (ctx *CmdCtx) StorageUserWebauthnImportRunE(cmd *cobra.Command, args []stri
return err
}
export := &model.WebauthnDeviceExport{}
export := &model.WebAuthnDeviceExport{}
if err = yaml.Unmarshal(data, export); err != nil {
return err
}
if len(export.WebauthnDevices) == 0 {
return fmt.Errorf("can't import a YAML file without Webauthn devices data")
if len(export.WebAuthnDevices) == 0 {
return fmt.Errorf("can't import a YAML file without WebAuthn devices data")
}
if err = ctx.CheckSchema(); err != nil {
return storageWrapCheckSchemaErr(err)
}
for _, device := range export.WebauthnDevices {
if err = ctx.providers.StorageProvider.SaveWebauthnDevice(ctx, device); err != nil {
for _, device := range export.WebAuthnDevices {
if err = ctx.providers.StorageProvider.SaveWebAuthnDevice(ctx, device); err != nil {
return err
}
}
fmt.Printf(cliOutputFmtSuccessfulUserImportFile, len(export.WebauthnDevices), "Webauthn devices", "YAML", filename)
fmt.Printf(cliOutputFmtSuccessfulUserImportFile, len(export.WebAuthnDevices), "WebAuthn devices", "YAML", filename)
return nil
}
// StorageUserWebauthnListRunE is the RunE for the authelia storage user webauthn list command.
func (ctx *CmdCtx) StorageUserWebauthnListRunE(cmd *cobra.Command, args []string) (err error) {
// StorageUserWebAuthnListRunE is the RunE for the authelia storage user webauthn list command.
func (ctx *CmdCtx) StorageUserWebAuthnListRunE(cmd *cobra.Command, args []string) (err error) {
defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
if len(args) == 0 || args[0] == "" {
return ctx.StorageUserWebauthnListAllRunE(cmd, args)
return ctx.StorageUserWebAuthnListAllRunE(cmd, args)
}
if err = ctx.CheckSchema(); err != nil {
return storageWrapCheckSchemaErr(err)
}
var devices []model.WebauthnDevice
var devices []model.WebAuthnDevice
user := args[0]
devices, err = ctx.providers.StorageProvider.LoadWebauthnDevicesByUsername(ctx, "", user)
devices, err = ctx.providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, "", user)
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)
case err != nil:
return fmt.Errorf("can't list devices for user '%s': %w", user, err)
default:
fmt.Printf("Webauthn Devices for user '%s':\n\n", user)
fmt.Printf("WebAuthn Devices for user '%s':\n\n", user)
fmt.Printf("ID\tKID\tDescription\n")
for _, device := range devices {
@ -569,8 +569,8 @@ func (ctx *CmdCtx) StorageUserWebauthnListRunE(cmd *cobra.Command, args []string
return nil
}
// StorageUserWebauthnListAllRunE is the RunE for the authelia storage user webauthn list command when no args are specified.
func (ctx *CmdCtx) StorageUserWebauthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
// StorageUserWebAuthnListAllRunE is the RunE for the authelia storage user webauthn list command when no args are specified.
func (ctx *CmdCtx) StorageUserWebAuthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
@ -579,14 +579,14 @@ func (ctx *CmdCtx) StorageUserWebauthnListAllRunE(_ *cobra.Command, _ []string)
return storageWrapCheckSchemaErr(err)
}
var devices []model.WebauthnDevice
var devices []model.WebAuthnDevice
limit := 10
output := strings.Builder{}
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)
}
@ -603,14 +603,14 @@ func (ctx *CmdCtx) StorageUserWebauthnListAllRunE(_ *cobra.Command, _ []string)
}
}
fmt.Printf("Webauthn Devices:\n\nID\tKID\tDescription\tUsername\n")
fmt.Printf("WebAuthn Devices:\n\nID\tKID\tDescription\tUsername\n")
fmt.Println(output.String())
return nil
}
// StorageUserWebauthnDeleteRunE is the RunE for the authelia storage user webauthn delete command.
func (ctx *CmdCtx) StorageUserWebauthnDeleteRunE(cmd *cobra.Command, args []string) (err error) {
// StorageUserWebAuthnDeleteRunE is the RunE for the authelia storage user webauthn delete command.
func (ctx *CmdCtx) StorageUserWebAuthnDeleteRunE(cmd *cobra.Command, args []string) (err error) {
defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
@ -624,31 +624,31 @@ func (ctx *CmdCtx) StorageUserWebauthnDeleteRunE(cmd *cobra.Command, args []stri
description, kid, user string
)
if all, byKID, description, kid, user, err = storageWebauthnDeleteRunEOptsFromFlags(cmd.Flags(), args); err != nil {
if all, byKID, description, kid, user, err = storageWebAuthnDeleteRunEOptsFromFlags(cmd.Flags(), args); err != nil {
return err
}
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)
}
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 {
err = ctx.providers.StorageProvider.DeleteWebauthnDeviceByUsername(ctx, user, description)
err = ctx.providers.StorageProvider.DeleteWebAuthnDeviceByUsername(ctx, user, description)
if all {
if err != nil {
return fmt.Errorf("failed to delete all webauthn devices with username '%s': %w", user, err)
}
fmt.Printf("Successfully deleted all Webauthn devices for user '%s'\n", user)
fmt.Printf("Successfully deleted all WebAuthn devices for user '%s'\n", user)
} else {
if err != nil {
return fmt.Errorf("failed to delete webauthn device with username '%s' and description '%s': %w", user, description, err)
}
fmt.Printf("Successfully deleted Webauthn device with description '%s' for user '%s'\n", description, user)
fmt.Printf("Successfully deleted WebAuthn device with description '%s' for user '%s'\n", description, user)
}
}

View File

@ -21,7 +21,7 @@ type Configuration struct {
Notifier NotifierConfiguration `koanf:"notifier"`
Server ServerConfiguration `koanf:"server"`
Telemetry TelemetryConfig `koanf:"telemetry"`
Webauthn WebauthnConfiguration `koanf:"webauthn"`
WebAuthn WebAuthnConfiguration `koanf:"webauthn"`
PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
PrivacyPolicy PrivacyPolicy `koanf:"privacy_policy"`
}

View File

@ -6,8 +6,8 @@ import (
"github.com/go-webauthn/webauthn/protocol"
)
// WebauthnConfiguration represents the webauthn config.
type WebauthnConfiguration struct {
// WebAuthnConfiguration represents the webauthn config.
type WebAuthnConfiguration struct {
Disable bool `koanf:"disable"`
DisplayName string `koanf:"display_name"`
@ -17,8 +17,8 @@ type WebauthnConfiguration struct {
Timeout time.Duration `koanf:"timeout"`
}
// DefaultWebauthnConfiguration describes the default values for the WebauthnConfiguration.
var DefaultWebauthnConfiguration = WebauthnConfiguration{
// DefaultWebAuthnConfiguration describes the default values for the WebAuthnConfiguration.
var DefaultWebAuthnConfiguration = WebAuthnConfiguration{
DisplayName: "Authelia",
Timeout: time.Second * 60,

View File

@ -43,7 +43,7 @@ func ValidateConfiguration(config *schema.Configuration, validator *schema.Struc
ValidateTOTP(config, validator)
ValidateWebauthn(config, validator)
ValidateWebAuthn(config, validator)
ValidateAuthenticationBackend(&config.AuthenticationBackend, validator)
@ -89,7 +89,7 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St
enabledMethods = append(enabledMethods, "totp")
}
if !config.Webauthn.Disable {
if !config.WebAuthn.Disable {
enabledMethods = append(enabledMethods, "webauthn")
}

View File

@ -188,7 +188,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
},
},
{
desc: "ShouldAllowConfiguredMethodWebauthn",
desc: "ShouldAllowConfiguredMethodWebAuthn",
have: &schema.Configuration{
Default2FAMethod: "webauthn",
DuoAPI: schema.DuoAPIConfiguration{
@ -225,7 +225,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
},
},
{
desc: "ShouldNotAllowDisabledMethodWebauthn",
desc: "ShouldNotAllowDisabledMethodWebAuthn",
have: &schema.Configuration{
Default2FAMethod: "webauthn",
DuoAPI: schema.DuoAPIConfiguration{
@ -233,7 +233,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
IntegrationKey: "another key",
Hostname: "none",
},
Webauthn: schema.WebauthnConfiguration{Disable: true},
WebAuthn: schema.WebAuthnConfiguration{Disable: true},
},
expectedErrs: []string{
"option 'default_2fa_method' is configured as 'webauthn' but must be one of the following enabled method values: 'totp', 'mobile_push'",

View File

@ -191,10 +191,10 @@ const (
"configured to an unsafe value, it should be above 8 but it's configured to %d"
)
// Webauthn Error constants.
// WebAuthn Error constants.
const (
errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of '%s' but it is configured as '%s'"
errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as '%s'"
errFmtWebAuthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of '%s' but it is configured as '%s'"
errFmtWebAuthnUserVerification = "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as '%s'"
)
// Access Control error constants.
@ -375,8 +375,8 @@ var (
validThemeNames = []string{"light", "dark", "grey", "auto"}
validSessionSameSiteValues = []string{"none", "lax", "strict"}
validLogLevels = []string{"trace", "debug", "info", "warn", "error"}
validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
validWebauthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
validWebAuthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
validWebAuthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"}
)
@ -401,7 +401,7 @@ var (
var (
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/\._-]*)([a-zA-Z]))?$`)
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`)
)
var replacedKeys = map[string]string{

View File

@ -841,7 +841,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
// Assert Clients[0] DisplayName is set to the Clients[0] ID, and Clients[1]'s DisplayName is not overridden.
assert.Equal(t, config.OIDC.Clients[0].ID, config.OIDC.Clients[0].Description)
assert.Equal(t, "Normal DisplayName", config.OIDC.Clients[1].Description)
assert.Equal(t, "Normal Description", config.OIDC.Clients[1].Description)
// Assert Clients[0] ends up configured with the default Scopes.
require.Len(t, config.OIDC.Clients[0].Scopes, 4)

View File

@ -8,27 +8,27 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
// ValidateWebauthn validates and update Webauthn configuration.
func ValidateWebauthn(config *schema.Configuration, validator *schema.StructValidator) {
if config.Webauthn.DisplayName == "" {
config.Webauthn.DisplayName = schema.DefaultWebauthnConfiguration.DisplayName
// ValidateWebAuthn validates and update WebAuthn configuration.
func ValidateWebAuthn(config *schema.Configuration, validator *schema.StructValidator) {
if config.WebAuthn.DisplayName == "" {
config.WebAuthn.DisplayName = schema.DefaultWebAuthnConfiguration.DisplayName
}
if config.Webauthn.Timeout <= 0 {
config.Webauthn.Timeout = schema.DefaultWebauthnConfiguration.Timeout
if config.WebAuthn.Timeout <= 0 {
config.WebAuthn.Timeout = schema.DefaultWebAuthnConfiguration.Timeout
}
switch {
case config.Webauthn.ConveyancePreference == "":
config.Webauthn.ConveyancePreference = schema.DefaultWebauthnConfiguration.ConveyancePreference
case !utils.IsStringInSlice(string(config.Webauthn.ConveyancePreference), validWebauthnConveyancePreferences):
validator.Push(fmt.Errorf(errFmtWebauthnConveyancePreference, strings.Join(validWebauthnConveyancePreferences, "', '"), config.Webauthn.ConveyancePreference))
case config.WebAuthn.ConveyancePreference == "":
config.WebAuthn.ConveyancePreference = schema.DefaultWebAuthnConfiguration.ConveyancePreference
case !utils.IsStringInSlice(string(config.WebAuthn.ConveyancePreference), validWebAuthnConveyancePreferences):
validator.Push(fmt.Errorf(errFmtWebAuthnConveyancePreference, strings.Join(validWebAuthnConveyancePreferences, "', '"), config.WebAuthn.ConveyancePreference))
}
switch {
case config.Webauthn.UserVerification == "":
config.Webauthn.UserVerification = schema.DefaultWebauthnConfiguration.UserVerification
case !utils.IsStringInSlice(string(config.Webauthn.UserVerification), validWebauthnUserVerificationRequirement):
validator.Push(fmt.Errorf(errFmtWebauthnUserVerification, config.Webauthn.UserVerification))
case config.WebAuthn.UserVerification == "":
config.WebAuthn.UserVerification = schema.DefaultWebAuthnConfiguration.UserVerification
case !utils.IsStringInSlice(string(config.WebAuthn.UserVerification), validWebAuthnUserVerificationRequirement):
validator.Push(fmt.Errorf(errFmtWebAuthnUserVerification, config.WebAuthn.UserVerification))
}
}

View File

@ -11,39 +11,39 @@ import (
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func TestWebauthnShouldSetDefaultValues(t *testing.T) {
func TestWebAuthnShouldSetDefaultValues(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.Configuration{
Webauthn: schema.WebauthnConfiguration{},
WebAuthn: schema.WebAuthnConfiguration{},
}
ValidateWebauthn(config, validator)
ValidateWebAuthn(config, validator)
require.Len(t, validator.Errors(), 0)
assert.Equal(t, schema.DefaultWebauthnConfiguration.DisplayName, config.Webauthn.DisplayName)
assert.Equal(t, schema.DefaultWebauthnConfiguration.Timeout, config.Webauthn.Timeout)
assert.Equal(t, schema.DefaultWebauthnConfiguration.ConveyancePreference, config.Webauthn.ConveyancePreference)
assert.Equal(t, schema.DefaultWebauthnConfiguration.UserVerification, config.Webauthn.UserVerification)
assert.Equal(t, schema.DefaultWebAuthnConfiguration.DisplayName, config.WebAuthn.DisplayName)
assert.Equal(t, schema.DefaultWebAuthnConfiguration.Timeout, config.WebAuthn.Timeout)
assert.Equal(t, schema.DefaultWebAuthnConfiguration.ConveyancePreference, config.WebAuthn.ConveyancePreference)
assert.Equal(t, schema.DefaultWebAuthnConfiguration.UserVerification, config.WebAuthn.UserVerification)
}
func TestWebauthnShouldSetDefaultTimeoutWhenNegative(t *testing.T) {
func TestWebAuthnShouldSetDefaultTimeoutWhenNegative(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.Configuration{
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
Timeout: -1,
},
}
ValidateWebauthn(config, validator)
ValidateWebAuthn(config, validator)
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()
config := &schema.Configuration{
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
DisplayName: "Test",
Timeout: time.Second * 50,
ConveyancePreference: protocol.PreferNoAttestation,
@ -51,37 +51,37 @@ func TestWebauthnShouldNotSetDefaultValuesWhenConfigured(t *testing.T) {
},
}
ValidateWebauthn(config, validator)
ValidateWebAuthn(config, validator)
require.Len(t, validator.Errors(), 0)
assert.Equal(t, "Test", config.Webauthn.DisplayName)
assert.Equal(t, time.Second*50, config.Webauthn.Timeout)
assert.Equal(t, protocol.PreferNoAttestation, config.Webauthn.ConveyancePreference)
assert.Equal(t, protocol.VerificationDiscouraged, config.Webauthn.UserVerification)
assert.Equal(t, "Test", config.WebAuthn.DisplayName)
assert.Equal(t, time.Second*50, config.WebAuthn.Timeout)
assert.Equal(t, protocol.PreferNoAttestation, config.WebAuthn.ConveyancePreference)
assert.Equal(t, protocol.VerificationDiscouraged, config.WebAuthn.UserVerification)
config.Webauthn.ConveyancePreference = protocol.PreferIndirectAttestation
config.Webauthn.UserVerification = protocol.VerificationPreferred
config.WebAuthn.ConveyancePreference = protocol.PreferIndirectAttestation
config.WebAuthn.UserVerification = protocol.VerificationPreferred
ValidateWebauthn(config, validator)
ValidateWebAuthn(config, validator)
require.Len(t, validator.Errors(), 0)
assert.Equal(t, protocol.PreferIndirectAttestation, config.Webauthn.ConveyancePreference)
assert.Equal(t, protocol.VerificationPreferred, config.Webauthn.UserVerification)
assert.Equal(t, protocol.PreferIndirectAttestation, config.WebAuthn.ConveyancePreference)
assert.Equal(t, protocol.VerificationPreferred, config.WebAuthn.UserVerification)
config.Webauthn.ConveyancePreference = protocol.PreferDirectAttestation
config.Webauthn.UserVerification = protocol.VerificationRequired
config.WebAuthn.ConveyancePreference = protocol.PreferDirectAttestation
config.WebAuthn.UserVerification = protocol.VerificationRequired
ValidateWebauthn(config, validator)
ValidateWebAuthn(config, validator)
require.Len(t, validator.Errors(), 0)
assert.Equal(t, protocol.PreferDirectAttestation, config.Webauthn.ConveyancePreference)
assert.Equal(t, protocol.VerificationRequired, config.Webauthn.UserVerification)
assert.Equal(t, protocol.PreferDirectAttestation, config.WebAuthn.ConveyancePreference)
assert.Equal(t, protocol.VerificationRequired, config.WebAuthn.UserVerification)
}
func TestWebauthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) {
func TestWebAuthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.Configuration{
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
DisplayName: "Test",
Timeout: time.Second * 50,
ConveyancePreference: "no",
@ -89,7 +89,7 @@ func TestWebauthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) {
},
}
ValidateWebauthn(config, validator)
ValidateWebAuthn(config, validator)
require.Len(t, validator.Errors(), 2)

View File

@ -8,8 +8,8 @@ const (
// ActionTOTPRegistration is the string representation of the action for which the token has been produced.
ActionTOTPRegistration = "RegisterTOTPDevice"
// ActionWebauthnRegistration is the string representation of the action for which the token has been produced.
ActionWebauthnRegistration = "RegisterWebauthnDevice"
// ActionWebAuthnRegistration is the string representation of the action for which the token has been produced.
ActionWebAuthnRegistration = "RegisterWebAuthnDevice"
// ActionResetPassword is the string representation of the action for which the token has been produced.
ActionResetPassword = "ResetPassword"

View File

@ -36,7 +36,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldHaveAllConfiguredMethods
TOTP: schema.TOTPConfiguration{
Disable: false,
},
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
Disable: false,
},
AccessControl: schema.AccessControlConfiguration{
@ -66,7 +66,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveTOTPFromAvailableM
TOTP: schema.TOTPConfiguration{
Disable: true,
},
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
Disable: false,
},
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{
DuoAPI: schema.DuoAPIConfiguration{
Disable: false,
@ -96,7 +96,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveWebauthnFromAvaila
TOTP: schema.TOTPConfiguration{
Disable: false,
},
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
Disable: true,
},
AccessControl: schema.AccessControlConfiguration{
@ -126,7 +126,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveDuoFromAvailableMe
TOTP: schema.TOTPConfiguration{
Disable: false,
},
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
Disable: false,
},
AccessControl: schema.AccessControlConfiguration{
@ -156,7 +156,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveAllMethodsWhenNoTw
TOTP: schema.TOTPConfiguration{
Disable: false,
},
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
Disable: false,
},
AccessControl: schema.AccessControlConfiguration{
@ -186,7 +186,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveAllMethodsWhenAllD
TOTP: schema.TOTPConfiguration{
Disable: true,
},
Webauthn: schema.WebauthnConfiguration{
WebAuthn: schema.WebAuthnConfiguration{
Disable: true,
},
AccessControl: schema.AccessControlConfiguration{

View File

@ -16,26 +16,26 @@ import (
"github.com/authelia/authelia/v4/internal/storage"
)
// WebauthnRegistrationPUT returns the attestation challenge from the server.
func WebauthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
// WebAuthnRegistrationPUT returns the attestation challenge from the server.
func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
var (
w *webauthn.WebAuthn
user *model.WebauthnUser
user *model.WebAuthnUser
userSession session.UserSession
bodyJSON bodyRegisterWebauthnPUTRequest
bodyJSON bodyRegisterWebAuthnPUTRequest
err error
)
if userSession, err = ctx.GetSession(); err != nil {
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s registration challenge", regulation.AuthTypeWebauthn)
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s registration challenge", regulation.AuthTypeWebAuthn)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
if w, err = newWebauthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to create provider to generate %s registration challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if w, err = newWebAuthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to create provider to generate %s registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -43,7 +43,7 @@ func WebauthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
}
if err = json.Unmarshal(ctx.PostBody(), &bodyJSON); err != nil {
ctx.Logger.Errorf("Unable to parse %s registration request PUT data for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf("Unable to parse %s registration request PUT data for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -51,16 +51,16 @@ func WebauthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
}
if length := len(bodyJSON.Description); length == 0 || length > 64 {
ctx.Logger.Errorf("Failed to validate the user chosen display name for during %s registration for user '%s': the value has a length of %d but must be between 1 and 64", regulation.AuthTypeWebauthn, userSession.Username, length)
ctx.Logger.Errorf("Failed to validate the user chosen display name for during %s registration for user '%s': the value has a length of %d but must be between 1 and 64", regulation.AuthTypeWebAuthn, userSession.Username, length)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
devices, err := ctx.Providers.StorageProvider.LoadWebauthnDevicesByUsername(ctx, w.Config.RPID, userSession.Username)
if err != nil && err != storage.ErrNoWebauthnDevice {
ctx.Logger.Errorf("Unable to load existing %s devices for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
devices, err := ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, w.Config.RPID, userSession.Username)
if err != nil && err != storage.ErrNoWebAuthnDevice {
ctx.Logger.Errorf("Unable to load existing %s devices for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -69,7 +69,7 @@ func WebauthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
for _, device := range devices {
if strings.EqualFold(device.Description, bodyJSON.Description) {
ctx.Logger.Errorf("Unable to generate %s registration challenge: device for for user '%s' with display name '%s' already exists", regulation.AuthTypeWebauthn, userSession.Username, bodyJSON.Description)
ctx.Logger.Errorf("Unable to generate %s registration challenge: device for for user '%s' with display name '%s' already exists", regulation.AuthTypeWebAuthn, userSession.Username, bodyJSON.Description)
ctx.SetStatusCode(fasthttp.StatusConflict)
ctx.SetJSONError(messageSecurityKeyDuplicateName)
@ -78,8 +78,8 @@ func WebauthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
}
}
if user, err = getWebauthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for registration challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -96,22 +96,22 @@ func WebauthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
webauthn.WithResidentKeyRequirement(protocol.ResidentKeyRequirementDiscouraged),
}
data := session.Webauthn{
data := session.WebAuthn{
Description: bodyJSON.Description,
}
if creation, data.SessionData, err = w.BeginRegistration(user, opts...); err != nil {
ctx.Logger.Errorf("Unable to create %s registration challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf("Unable to create %s registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
userSession.Webauthn = &data
userSession.WebAuthn = &data
if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "registration challenge", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf(logFmtErrSessionSave, "registration challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -119,7 +119,7 @@ func WebauthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
}
if err = ctx.SetJSONBody(creation); err != nil {
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -127,12 +127,12 @@ func WebauthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
}
}
// WebauthnRegistrationPOST processes the attestation challenge response from the client.
func WebauthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
// WebAuthnRegistrationPOST processes the attestation challenge response from the client.
func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
var (
err error
w *webauthn.WebAuthn
user *model.WebauthnUser
user *model.WebAuthnUser
userSession session.UserSession
@ -142,23 +142,23 @@ func WebauthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
)
if userSession, err = ctx.GetSession(); err != nil {
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s registration response", regulation.AuthTypeWebauthn)
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s registration response", regulation.AuthTypeWebAuthn)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
if userSession.Webauthn == nil || userSession.Webauthn.SessionData == nil {
ctx.Logger.Errorf("Webauthn session data is not present in order to handle %s registration 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.", regulation.AuthTypeWebauthn, userSession.Username)
if userSession.WebAuthn == nil || userSession.WebAuthn.SessionData == nil {
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle %s registration 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.", regulation.AuthTypeWebAuthn, userSession.Username)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
if w, err = newWebauthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during registration for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if w, err = newWebAuthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -168,9 +168,9 @@ func WebauthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
if response, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
switch e := err.(type) {
case *protocol.Error:
ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v (%s)", regulation.AuthTypeWebauthn, userSession.Username, err, e.DevInfo)
ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo)
default:
ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
}
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -178,20 +178,20 @@ func WebauthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
return
}
if user, err = getWebauthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s user details for registration for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s user details for registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
if credential, err = w.CreateCredential(user, *userSession.Webauthn.SessionData, response); err != nil {
if credential, err = w.CreateCredential(user, *userSession.WebAuthn.SessionData, response); err != nil {
switch e := err.(type) {
case *protocol.Error:
ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v (%s)", regulation.AuthTypeWebauthn, userSession.Username, err, e.DevInfo)
ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo)
default:
ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
}
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -199,26 +199,26 @@ func WebauthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
return
}
device := model.NewWebauthnDeviceFromCredential(w.Config.RPID, userSession.Username, userSession.Webauthn.Description, credential)
device := model.NewWebAuthnDeviceFromCredential(w.Config.RPID, userSession.Username, userSession.WebAuthn.Description, credential)
device.Discoverable = webauthnCredentialCreationIsDiscoverable(ctx, response)
if err = ctx.Providers.StorageProvider.SaveWebauthnDevice(ctx, device); err != nil {
ctx.Logger.Errorf("Unable to save %s device registration for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if err = ctx.Providers.StorageProvider.SaveWebAuthnDevice(ctx, device); err != nil {
ctx.Logger.Errorf("Unable to save %s device registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
userSession.Webauthn = nil
userSession.WebAuthn = nil
if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the registration challenge", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the registration challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
}
ctx.ReplyOK()
ctx.SetStatusCode(fasthttp.StatusCreated)
ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "Webauthn Credential", "Credential Description": device.Description})
ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "WebAuthn Credential", "Credential Description": device.Description})
}

View File

@ -12,11 +12,11 @@ import (
"github.com/authelia/authelia/v4/internal/session"
)
// WebauthnAssertionGET handler starts the assertion ceremony.
func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
// WebAuthnAssertionGET handler starts the assertion ceremony.
func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {
var (
w *webauthn.WebAuthn
user *model.WebauthnUser
user *model.WebAuthnUser
userSession session.UserSession
err error
)
@ -29,16 +29,16 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
return
}
if w, err = newWebauthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if w, err = newWebAuthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
if user, err = getWebauthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s user details during authentication challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s user details during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
@ -61,21 +61,21 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
var (
assertion *protocol.CredentialAssertion
data session.Webauthn
data session.WebAuthn
)
if assertion, data.SessionData, err = w.BeginLogin(user, opts...); err != nil {
ctx.Logger.Errorf("Unable to create %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf("Unable to create %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
userSession.Webauthn = &data
userSession.WebAuthn = &data
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)
@ -83,7 +83,7 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
}
if err = ctx.SetJSONBody(assertion); err != nil {
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
@ -91,21 +91,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
func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
var (
userSession session.UserSession
err error
w *webauthn.WebAuthn
bodyJSON bodySignWebauthnRequest
bodyJSON bodySignWebAuthnRequest
)
if err = ctx.ParseBody(&bodyJSON); err != nil {
ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeWebauthn, err)
ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeWebAuthn, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
@ -120,16 +120,16 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
return
}
if userSession.Webauthn == nil || userSession.Webauthn.SessionData == nil {
ctx.Logger.Errorf("Webauthn session data is not present in order to handle authentication challenge 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)
if userSession.WebAuthn == nil || userSession.WebAuthn.SessionData == nil {
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle authentication challenge 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)
return
}
if w, err = newWebauthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if w, err = newWebAuthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
@ -139,27 +139,27 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
var (
assertionResponse *protocol.ParsedCredentialAssertionData
credential *webauthn.Credential
user *model.WebauthnUser
user *model.WebAuthnUser
)
if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(bytes.NewReader(bodyJSON.Response)); err != nil {
ctx.Logger.Errorf("Unable to parse %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf("Unable to parse %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
if user, err = getWebauthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s credentials for authentication challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s credentials for authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
if credential, err = w.ValidateLogin(user, *userSession.Webauthn.SessionData, assertionResponse); err != nil {
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebauthn, err)
if credential, err = w.ValidateLogin(user, *userSession.WebAuthn.SessionData, assertionResponse); err != nil {
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebAuthn, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
@ -174,8 +174,8 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
found = true
if err = ctx.Providers.StorageProvider.UpdateWebauthnDeviceSignIn(ctx, device); err != nil {
ctx.Logger.Errorf("Unable to save %s device signin count for authentication challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceSignIn(ctx, device); err != nil {
ctx.Logger.Errorf("Unable to save %s device signin count for authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
@ -187,7 +187,7 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
}
if !found {
ctx.Logger.Errorf("Unable to save %s device signin count for authentication 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 authentication challenge for user '%s' device '%x' count '%d': unable to find device", regulation.AuthTypeWebAuthn, userSession.Username, credential.ID, credential.Authenticator.SignCount)
respondUnauthorized(ctx, messageMFAValidationFailed)
@ -195,25 +195,25 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
}
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)
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)
return
}
userSession.SetTwoFactorWebauthn(ctx.Clock.Now(),
userSession.SetTwoFactorWebAuthn(ctx.Clock.Now(),
assertionResponse.Response.AuthenticatorData.Flags.HasUserPresent(),
assertionResponse.Response.AuthenticatorData.Flags.HasUserVerified())
if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the authentiation challenge and authentication time", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the authentiation challenge and authentication time", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)

View File

@ -62,7 +62,7 @@ func TestUserInfoEndpoint_SetCorrectMethod(t *testing.T) {
{
db: model.UserInfo{
Method: "webauthn",
HasWebauthn: true,
HasWebAuthn: true,
HasTOTP: true,
},
err: nil,
@ -70,7 +70,7 @@ func TestUserInfoEndpoint_SetCorrectMethod(t *testing.T) {
{
db: model.UserInfo{
Method: "webauthn",
HasWebauthn: true,
HasWebAuthn: true,
HasTOTP: false,
},
err: nil,
@ -78,7 +78,7 @@ func TestUserInfoEndpoint_SetCorrectMethod(t *testing.T) {
{
db: model.UserInfo{
Method: "mobile_push",
HasWebauthn: false,
HasWebAuthn: false,
HasTOTP: false,
},
err: nil,
@ -128,7 +128,7 @@ func TestUserInfoEndpoint_SetCorrectMethod(t *testing.T) {
})
t.Run("registered webauthn", func(t *testing.T) {
assert.Equal(t, resp.api.HasWebauthn, actualPreferences.HasWebauthn)
assert.Equal(t, resp.api.HasWebAuthn, actualPreferences.HasWebAuthn)
})
t.Run("registered totp", func(t *testing.T) {
@ -160,13 +160,13 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
db: model.UserInfo{
Method: "",
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
HasDuo: false,
},
api: &model.UserInfo{
Method: "totp",
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
HasDuo: false,
},
config: &schema.Configuration{},
@ -178,13 +178,13 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
db: model.UserInfo{
Method: "",
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
HasDuo: true,
},
api: &model.UserInfo{
Method: "mobile_push",
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
HasDuo: true,
},
config: &schema.Configuration{},
@ -196,13 +196,13 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
db: model.UserInfo{
Method: "",
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
HasDuo: true,
},
api: &model.UserInfo{
Method: "totp",
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
HasDuo: true,
},
config: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{Disable: true}},
@ -214,13 +214,13 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
db: model.UserInfo{
Method: "",
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
HasDuo: true,
},
api: &model.UserInfo{
Method: "webauthn",
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
HasDuo: true,
},
config: &schema.Configuration{
@ -236,13 +236,13 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
db: model.UserInfo{
Method: "",
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
HasDuo: false,
},
api: &model.UserInfo{
Method: "totp",
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
HasDuo: true,
},
config: &schema.Configuration{},
@ -322,7 +322,7 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
})
t.Run("registered webauthn", func(t *testing.T) {
assert.Equal(t, resp.api.HasWebauthn, actualPreferences.HasWebauthn)
assert.Equal(t, resp.api.HasWebAuthn, actualPreferences.HasWebAuthn)
})
t.Run("registered totp", func(t *testing.T) {

View File

@ -16,7 +16,7 @@ import (
"github.com/authelia/authelia/v4/internal/storage"
)
func getWebauthnDeviceIDFromContext(ctx *middlewares.AutheliaCtx) (int, error) {
func getWebAuthnDeviceIDFromContext(ctx *middlewares.AutheliaCtx) (int, error) {
deviceIDStr, ok := ctx.UserValue("deviceID").(string)
if !ok {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
@ -34,8 +34,8 @@ func getWebauthnDeviceIDFromContext(ctx *middlewares.AutheliaCtx) (int, error) {
return deviceID, nil
}
// WebauthnDevicesGET returns all devices registered for the current user.
func WebauthnDevicesGET(ctx *middlewares.AutheliaCtx) {
// WebAuthnDevicesGET returns all devices registered for the current user.
func WebAuthnDevicesGET(ctx *middlewares.AutheliaCtx) {
var (
userSession session.UserSession
origin *url.URL
@ -58,9 +58,9 @@ func WebauthnDevicesGET(ctx *middlewares.AutheliaCtx) {
return
}
devices, err := ctx.Providers.StorageProvider.LoadWebauthnDevicesByUsername(ctx, origin.Hostname(), userSession.Username)
devices, err := ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, origin.Hostname(), userSession.Username)
if err != nil && err != storage.ErrNoWebauthnDevice {
if err != nil && err != storage.ErrNoWebAuthnDevice {
ctx.Error(err, messageOperationFailed)
return
}
@ -71,13 +71,13 @@ func WebauthnDevicesGET(ctx *middlewares.AutheliaCtx) {
}
}
// WebauthnDevicePUT updates the description for a specific device for the current user.
func WebauthnDevicePUT(ctx *middlewares.AutheliaCtx) {
// WebAuthnDevicePUT updates the description for a specific device for the current user.
func WebAuthnDevicePUT(ctx *middlewares.AutheliaCtx) {
var (
bodyJSON bodyEditWebauthnDeviceRequest
bodyJSON bodyEditWebAuthnDeviceRequest
id int
device *model.WebauthnDevice
device *model.WebAuthnDevice
userSession session.UserSession
err error
@ -92,7 +92,7 @@ func WebauthnDevicePUT(ctx *middlewares.AutheliaCtx) {
}
if err = json.Unmarshal(ctx.PostBody(), &bodyJSON); err != nil {
ctx.Logger.Errorf("Unable to parse %s update request data for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
ctx.Logger.Errorf("Unable to parse %s update request data for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.Error(err, messageOperationFailed)
@ -100,11 +100,11 @@ func WebauthnDevicePUT(ctx *middlewares.AutheliaCtx) {
return
}
if id, err = getWebauthnDeviceIDFromContext(ctx); err != nil {
if id, err = getWebAuthnDeviceIDFromContext(ctx); err != nil {
return
}
if device, err = ctx.Providers.StorageProvider.LoadWebauthnDeviceByID(ctx, id); err != nil {
if device, err = ctx.Providers.StorageProvider.LoadWebAuthnDeviceByID(ctx, id); err != nil {
ctx.Error(err, messageOperationFailed)
return
}
@ -114,26 +114,26 @@ func WebauthnDevicePUT(ctx *middlewares.AutheliaCtx) {
return
}
if err = ctx.Providers.StorageProvider.UpdateWebauthnDeviceDescription(ctx, userSession.Username, id, bodyJSON.Description); err != nil {
if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceDescription(ctx, userSession.Username, id, bodyJSON.Description); err != nil {
ctx.Error(err, messageOperationFailed)
return
}
}
// WebauthnDeviceDELETE deletes a specific device for the current user.
func WebauthnDeviceDELETE(ctx *middlewares.AutheliaCtx) {
// WebAuthnDeviceDELETE deletes a specific device for the current user.
func WebAuthnDeviceDELETE(ctx *middlewares.AutheliaCtx) {
var (
id int
device *model.WebauthnDevice
device *model.WebAuthnDevice
userSession session.UserSession
err error
)
if id, err = getWebauthnDeviceIDFromContext(ctx); err != nil {
if id, err = getWebAuthnDeviceIDFromContext(ctx); err != nil {
return
}
if device, err = ctx.Providers.StorageProvider.LoadWebauthnDeviceByID(ctx, id); err != nil {
if device, err = ctx.Providers.StorageProvider.LoadWebAuthnDeviceByID(ctx, id); err != nil {
ctx.Error(err, messageOperationFailed)
return
}
@ -151,7 +151,7 @@ func WebauthnDeviceDELETE(ctx *middlewares.AutheliaCtx) {
return
}
if err = ctx.Providers.StorageProvider.DeleteWebauthnDevice(ctx, device.KID.String()); err != nil {
if err = ctx.Providers.StorageProvider.DeleteWebAuthnDevice(ctx, device.KID.String()); err != nil {
ctx.Error(err, messageOperationFailed)
return
}

View File

@ -31,8 +31,8 @@ type bodySignTOTPRequest struct {
WorkflowID string `json:"workflowID"`
}
// bodySignWebauthnRequest is the model of the request body of WebAuthn 2FA authentication endpoint.
type bodySignWebauthnRequest struct {
// bodySignWebAuthnRequest is the model of the request body of WebAuthn 2FA authentication endpoint.
type bodySignWebAuthnRequest struct {
TargetURL string `json:"targetURL"`
Workflow string `json:"workflow"`
WorkflowID string `json:"workflowID"`
@ -40,11 +40,11 @@ type bodySignWebauthnRequest struct {
Response json.RawMessage `json:"response"`
}
type bodyRegisterWebauthnPUTRequest struct {
type bodyRegisterWebAuthnPUTRequest struct {
Description string `json:"description"`
}
type bodyEditWebauthnDeviceRequest struct {
type bodyEditWebAuthnDeviceRequest struct {
Description string `json:"description"`
}

View File

@ -12,20 +12,20 @@ import (
"github.com/authelia/authelia/v4/internal/random"
)
func getWebauthnUserByRPID(ctx *middlewares.AutheliaCtx, username, displayname string, rpid string) (user *model.WebauthnUser, err error) {
if user, err = ctx.Providers.StorageProvider.LoadWebauthnUser(ctx, rpid, username); err != nil {
func getWebAuthnUserByRPID(ctx *middlewares.AutheliaCtx, username, displayname string, rpid string) (user *model.WebAuthnUser, err error) {
if user, err = ctx.Providers.StorageProvider.LoadWebAuthnUser(ctx, rpid, username); err != nil {
return nil, err
}
if user == nil {
user = &model.WebauthnUser{
user = &model.WebAuthnUser{
RPID: rpid,
Username: username,
UserID: ctx.Providers.Random.StringCustom(64, random.CharSetASCII),
DisplayName: displayname,
}
if err = ctx.Providers.StorageProvider.SaveWebauthnUser(ctx, *user); err != nil {
if err = ctx.Providers.StorageProvider.SaveWebAuthnUser(ctx, *user); err != nil {
return nil, err
}
} else {
@ -36,14 +36,14 @@ func getWebauthnUserByRPID(ctx *middlewares.AutheliaCtx, username, displayname s
user.DisplayName = user.Username
}
if user.Devices, err = ctx.Providers.StorageProvider.LoadWebauthnDevicesByUsername(ctx, rpid, user.Username); err != nil {
if user.Devices, err = ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, rpid, user.Username); err != nil {
return nil, err
}
return user, nil
}
func newWebauthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error) {
func newWebAuthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error) {
var (
origin *url.URL
)
@ -54,32 +54,32 @@ func newWebauthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error)
config := &webauthn.Config{
RPID: origin.Hostname(),
RPDisplayName: ctx.Configuration.Webauthn.DisplayName,
RPDisplayName: ctx.Configuration.WebAuthn.DisplayName,
RPOrigins: []string{origin.String()},
AttestationPreference: ctx.Configuration.Webauthn.ConveyancePreference,
AttestationPreference: ctx.Configuration.WebAuthn.ConveyancePreference,
AuthenticatorSelection: protocol.AuthenticatorSelection{
AuthenticatorAttachment: protocol.CrossPlatform,
RequireResidentKey: protocol.ResidentKeyNotRequired(),
ResidentKey: protocol.ResidentKeyRequirementDiscouraged,
UserVerification: ctx.Configuration.Webauthn.UserVerification,
UserVerification: ctx.Configuration.WebAuthn.UserVerification,
},
Debug: false,
EncodeUserIDAsString: true,
Timeouts: webauthn.TimeoutsConfig{
Login: webauthn.TimeoutConfig{
Enforce: true,
Timeout: ctx.Configuration.Webauthn.Timeout,
TimeoutUVD: ctx.Configuration.Webauthn.Timeout,
Timeout: ctx.Configuration.WebAuthn.Timeout,
TimeoutUVD: ctx.Configuration.WebAuthn.Timeout,
},
Registration: webauthn.TimeoutConfig{
Enforce: true,
Timeout: ctx.Configuration.Webauthn.Timeout,
TimeoutUVD: ctx.Configuration.Webauthn.Timeout,
Timeout: ctx.Configuration.WebAuthn.Timeout,
TimeoutUVD: ctx.Configuration.WebAuthn.Timeout,
},
},
}
ctx.Logger.Tracef("Creating new Webauthn RP instance with ID %s and Origins %s", config.RPID, strings.Join(config.RPOrigins, ", "))
ctx.Logger.Tracef("Creating new WebAuthn RP instance with ID %s and Origins %s", config.RPID, strings.Join(config.RPOrigins, ", "))
return webauthn.New(config)
}

View File

@ -15,7 +15,7 @@ import (
"github.com/authelia/authelia/v4/internal/session"
)
func TestWebauthnGetUser(t *testing.T) {
func TestWebAuthnGetUser(t *testing.T) {
ctx := mocks.NewMockAutheliaCtx(t)
userSession := session.UserSession{
@ -24,10 +24,10 @@ func TestWebauthnGetUser(t *testing.T) {
}
ctx.StorageMock.EXPECT().
LoadWebauthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebauthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebauthnDevice{
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
{
ID: 1,
RPID: "example.com",
@ -53,7 +53,7 @@ func TestWebauthnGetUser(t *testing.T) {
},
}, nil)
user, err := getWebauthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
require.NoError(t, err)
require.NotNil(t, user)
@ -105,7 +105,7 @@ func TestWebauthnGetUser(t *testing.T) {
assert.Equal(t, protocol.AuthenticatorTransport("nfc"), descriptors[1].Transport[1])
}
func TestWebauthnGetNewUser(t *testing.T) {
func TestWebAuthnGetNewUser(t *testing.T) {
ctx := mocks.NewMockAutheliaCtx(t)
// Use the random mock.
@ -118,15 +118,15 @@ func TestWebauthnGetNewUser(t *testing.T) {
gomock.InOrder(
ctx.StorageMock.EXPECT().
LoadWebauthnUser(ctx.Ctx, "example.com", "john").
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
Return(nil, nil),
ctx.RandomMock.EXPECT().
StringCustom(64, random.CharSetASCII).
Return("=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"),
ctx.StorageMock.EXPECT().
SaveWebauthnUser(ctx.Ctx, model.WebauthnUser{RPID: "example.com", Username: "john", DisplayName: "John Smith", UserID: "=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"}).
SaveWebAuthnUser(ctx.Ctx, model.WebAuthnUser{RPID: "example.com", Username: "john", DisplayName: "John Smith", UserID: "=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"}).
Return(nil),
ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebauthnDevice{
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
{
ID: 1,
RPID: "example.com",
@ -153,7 +153,7 @@ func TestWebauthnGetNewUser(t *testing.T) {
}, nil),
)
user, err := getWebauthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
require.NoError(t, err)
require.NotNil(t, user)
@ -205,7 +205,7 @@ func TestWebauthnGetNewUser(t *testing.T) {
assert.Equal(t, protocol.AuthenticatorTransport("nfc"), descriptors[1].Transport[1])
}
func TestWebauthnGetUserWithoutDisplayName(t *testing.T) {
func TestWebAuthnGetUserWithoutDisplayName(t *testing.T) {
ctx := mocks.NewMockAutheliaCtx(t)
userSession := session.UserSession{
@ -213,10 +213,10 @@ func TestWebauthnGetUserWithoutDisplayName(t *testing.T) {
}
ctx.StorageMock.EXPECT().
LoadWebauthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebauthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
ctx.StorageMock.EXPECT().LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebauthnDevice{
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
{
ID: 1,
RPID: "example.com",
@ -230,7 +230,7 @@ func TestWebauthnGetUserWithoutDisplayName(t *testing.T) {
},
}, nil)
user, err := getWebauthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
require.NoError(t, err)
require.NotNil(t, user)
@ -239,7 +239,7 @@ func TestWebauthnGetUserWithoutDisplayName(t *testing.T) {
assert.Equal(t, "john", user.DisplayName)
}
func TestWebauthnGetUserWithErr(t *testing.T) {
func TestWebAuthnGetUserWithErr(t *testing.T) {
ctx := mocks.NewMockAutheliaCtx(t)
userSession := session.UserSession{
@ -247,37 +247,37 @@ func TestWebauthnGetUserWithErr(t *testing.T) {
}
ctx.StorageMock.EXPECT().
LoadWebauthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebauthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
ctx.StorageMock.EXPECT().
LoadWebauthnDevicesByUsername(ctx.Ctx, "example.com", "john").
LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").
Return(nil, errors.New("not found"))
user, err := getWebauthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
assert.EqualError(t, err, "not found")
assert.Nil(t, user)
}
func TestWebauthnNewWebauthnShouldReturnErrWhenHeadersNotAvailable(t *testing.T) {
func TestWebAuthnNewWebAuthnShouldReturnErrWhenHeadersNotAvailable(t *testing.T) {
ctx := mocks.NewMockAutheliaCtx(t)
ctx.Ctx.Request.Header.Del("X-Forwarded-Host")
w, err := newWebauthn(ctx.Ctx)
w, err := newWebAuthn(ctx.Ctx)
assert.Nil(t, w)
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.Ctx.Request.Header.Set("X-Forwarded-Host", "example.com")
ctx.Ctx.Request.Header.Set("X-Forwarded-URI", "/")
ctx.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
w, err := newWebauthn(ctx.Ctx)
w, err := newWebAuthn(ctx.Ctx)
assert.Nil(t, w)
assert.EqualError(t, err, "error occurred validating the configuration: the field 'RPDisplayName' must be configured but it is empty")

View File

@ -49,8 +49,8 @@ func (ctx *AutheliaCtx) AvailableSecondFactorMethods() (methods []string) {
methods = append(methods, model.SecondFactorMethodTOTP)
}
if !ctx.Configuration.Webauthn.Disable {
methods = append(methods, model.SecondFactorMethodWebauthn)
if !ctx.Configuration.WebAuthn.Disable {
methods = append(methods, model.SecondFactorMethodWebAuthn)
}
if !ctx.Configuration.DuoAPI.Disable {

View File

@ -235,17 +235,17 @@ func TestShouldReturnCorrectSecondFactorMethods(t *testing.T) {
mock.Ctx.Configuration.DuoAPI.Disable = true
assert.Equal(t, []string{model.SecondFactorMethodTOTP, model.SecondFactorMethodWebauthn}, mock.Ctx.AvailableSecondFactorMethods())
assert.Equal(t, []string{model.SecondFactorMethodTOTP, model.SecondFactorMethodWebAuthn}, mock.Ctx.AvailableSecondFactorMethods())
mock.Ctx.Configuration.DuoAPI.Disable = false
assert.Equal(t, []string{model.SecondFactorMethodTOTP, model.SecondFactorMethodWebauthn, model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
assert.Equal(t, []string{model.SecondFactorMethodTOTP, model.SecondFactorMethodWebAuthn, model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
mock.Ctx.Configuration.TOTP.Disable = true
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())

View File

@ -39,7 +39,6 @@ func (m *MockStorage) EXPECT() *MockStorageMockRecorder {
return m.recorder
}
// AppendAuthenticationLog mocks base method.
func (m *MockStorage) AppendAuthenticationLog(arg0 context.Context, arg1 model.AuthenticationAttempt) error {
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)
}
// DeleteWebauthnDevice mocks base method.
func (m *MockStorage) DeleteWebauthnDevice(arg0 context.Context, arg1 string) error {
// DeleteWebAuthnDevice mocks base method.
func (m *MockStorage) DeleteWebAuthnDevice(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteWebauthnDevice", arg0, arg1)
ret := m.ctrl.Call(m, "DeleteWebAuthnDevice", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteWebauthnDevice indicates an expected call of DeleteWebauthnDevice.
func (mr *MockStorageMockRecorder) DeleteWebauthnDevice(arg0, arg1 interface{}) *gomock.Call {
// DeleteWebAuthnDevice indicates an expected call of DeleteWebAuthnDevice.
func (mr *MockStorageMockRecorder) DeleteWebAuthnDevice(arg0, arg1 interface{}) *gomock.Call {
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.
func (m *MockStorage) DeleteWebauthnDeviceByUsername(arg0 context.Context, arg1, arg2 string) error {
// DeleteWebAuthnDeviceByUsername mocks base method.
func (m *MockStorage) DeleteWebAuthnDeviceByUsername(arg0 context.Context, arg1, arg2 string) error {
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)
return ret0
}
// DeleteWebauthnDeviceByUsername indicates an expected call of DeleteWebauthnDeviceByUsername.
func (mr *MockStorageMockRecorder) DeleteWebauthnDeviceByUsername(arg0, arg1, arg2 interface{}) *gomock.Call {
// DeleteWebAuthnDeviceByUsername indicates an expected call of DeleteWebAuthnDeviceByUsername.
func (mr *MockStorageMockRecorder) DeleteWebAuthnDeviceByUsername(arg0, arg1, arg2 interface{}) *gomock.Call {
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.
@ -420,64 +419,64 @@ func (mr *MockStorageMockRecorder) LoadUserOpaqueIdentifiers(arg0 interface{}) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadUserOpaqueIdentifiers", reflect.TypeOf((*MockStorage)(nil).LoadUserOpaqueIdentifiers), arg0)
}
// LoadWebauthnDeviceByID mocks base method.
func (m *MockStorage) LoadWebauthnDeviceByID(arg0 context.Context, arg1 int) (*model.WebauthnDevice, error) {
// LoadWebAuthnDeviceByID mocks base method.
func (m *MockStorage) LoadWebAuthnDeviceByID(arg0 context.Context, arg1 int) (*model.WebAuthnDevice, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadWebauthnDeviceByID", arg0, arg1)
ret0, _ := ret[0].(*model.WebauthnDevice)
ret := m.ctrl.Call(m, "LoadWebAuthnDeviceByID", arg0, arg1)
ret0, _ := ret[0].(*model.WebAuthnDevice)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadWebauthnDeviceByID indicates an expected call of LoadWebauthnDeviceByID.
func (mr *MockStorageMockRecorder) LoadWebauthnDeviceByID(arg0, arg1 interface{}) *gomock.Call {
// LoadWebAuthnDeviceByID indicates an expected call of LoadWebAuthnDeviceByID.
func (mr *MockStorageMockRecorder) LoadWebAuthnDeviceByID(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebauthnDeviceByID", reflect.TypeOf((*MockStorage)(nil).LoadWebauthnDeviceByID), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebAuthnDeviceByID", reflect.TypeOf((*MockStorage)(nil).LoadWebAuthnDeviceByID), arg0, arg1)
}
// LoadWebauthnDevices mocks base method.
func (m *MockStorage) LoadWebauthnDevices(arg0 context.Context, arg1, arg2 int) ([]model.WebauthnDevice, error) {
// LoadWebAuthnDevices mocks base method.
func (m *MockStorage) LoadWebAuthnDevices(arg0 context.Context, arg1, arg2 int) ([]model.WebAuthnDevice, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadWebauthnDevices", arg0, arg1, arg2)
ret0, _ := ret[0].([]model.WebauthnDevice)
ret := m.ctrl.Call(m, "LoadWebAuthnDevices", arg0, arg1, arg2)
ret0, _ := ret[0].([]model.WebAuthnDevice)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadWebauthnDevices indicates an expected call of LoadWebauthnDevices.
func (mr *MockStorageMockRecorder) LoadWebauthnDevices(arg0, arg1, arg2 interface{}) *gomock.Call {
// LoadWebAuthnDevices indicates an expected call of LoadWebAuthnDevices.
func (mr *MockStorageMockRecorder) LoadWebAuthnDevices(arg0, arg1, arg2 interface{}) *gomock.Call {
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.
func (m *MockStorage) LoadWebauthnDevicesByUsername(arg0 context.Context, arg1, arg2 string) ([]model.WebauthnDevice, error) {
// LoadWebAuthnDevicesByUsername mocks base method.
func (m *MockStorage) LoadWebAuthnDevicesByUsername(arg0 context.Context, arg1, arg2 string) ([]model.WebAuthnDevice, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadWebauthnDevicesByUsername", arg0, arg1, arg2)
ret0, _ := ret[0].([]model.WebauthnDevice)
ret := m.ctrl.Call(m, "LoadWebAuthnDevicesByUsername", arg0, arg1, arg2)
ret0, _ := ret[0].([]model.WebAuthnDevice)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadWebauthnDevicesByUsername indicates an expected call of LoadWebauthnDevicesByUsername.
func (mr *MockStorageMockRecorder) LoadWebauthnDevicesByUsername(arg0, arg1, arg2 interface{}) *gomock.Call {
// LoadWebAuthnDevicesByUsername indicates an expected call of LoadWebAuthnDevicesByUsername.
func (mr *MockStorageMockRecorder) LoadWebAuthnDevicesByUsername(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebauthnDevicesByUsername", reflect.TypeOf((*MockStorage)(nil).LoadWebauthnDevicesByUsername), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebAuthnDevicesByUsername", reflect.TypeOf((*MockStorage)(nil).LoadWebAuthnDevicesByUsername), arg0, arg1, arg2)
}
// LoadWebauthnUser mocks base method.
func (m *MockStorage) LoadWebauthnUser(arg0 context.Context, arg1, arg2 string) (*model.WebauthnUser, error) {
// LoadWebAuthnUser mocks base method.
func (m *MockStorage) LoadWebAuthnUser(arg0 context.Context, arg1, arg2 string) (*model.WebAuthnUser, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadWebauthnUser", arg0, arg1, arg2)
ret0, _ := ret[0].(*model.WebauthnUser)
ret := m.ctrl.Call(m, "LoadWebAuthnUser", arg0, arg1, arg2)
ret0, _ := ret[0].(*model.WebAuthnUser)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadWebauthnUser indicates an expected call of LoadWebauthnUser.
func (mr *MockStorageMockRecorder) LoadWebauthnUser(arg0, arg1, arg2 interface{}) *gomock.Call {
// LoadWebAuthnUser indicates an expected call of LoadWebAuthnUser.
func (mr *MockStorageMockRecorder) LoadWebAuthnUser(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebauthnUser", reflect.TypeOf((*MockStorage)(nil).LoadWebauthnUser), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebAuthnUser", reflect.TypeOf((*MockStorage)(nil).LoadWebAuthnUser), arg0, arg1, arg2)
}
// RevokeOAuth2PARContext mocks base method.
@ -719,32 +718,32 @@ func (mr *MockStorageMockRecorder) SaveUserOpaqueIdentifier(arg0, arg1 interface
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveUserOpaqueIdentifier", reflect.TypeOf((*MockStorage)(nil).SaveUserOpaqueIdentifier), arg0, arg1)
}
// SaveWebauthnDevice mocks base method.
func (m *MockStorage) SaveWebauthnDevice(arg0 context.Context, arg1 model.WebauthnDevice) error {
// SaveWebAuthnDevice mocks base method.
func (m *MockStorage) SaveWebAuthnDevice(arg0 context.Context, arg1 model.WebAuthnDevice) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaveWebauthnDevice", arg0, arg1)
ret := m.ctrl.Call(m, "SaveWebAuthnDevice", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// SaveWebauthnDevice indicates an expected call of SaveWebauthnDevice.
func (mr *MockStorageMockRecorder) SaveWebauthnDevice(arg0, arg1 interface{}) *gomock.Call {
// SaveWebAuthnDevice indicates an expected call of SaveWebAuthnDevice.
func (mr *MockStorageMockRecorder) SaveWebAuthnDevice(arg0, arg1 interface{}) *gomock.Call {
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)
}
// SaveWebauthnUser mocks base method.
func (m *MockStorage) SaveWebauthnUser(arg0 context.Context, arg1 model.WebauthnUser) error {
// SaveWebAuthnUser mocks base method.
func (m *MockStorage) SaveWebAuthnUser(arg0 context.Context, arg1 model.WebAuthnUser) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaveWebauthnUser", arg0, arg1)
ret := m.ctrl.Call(m, "SaveWebAuthnUser", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// SaveWebauthnUser indicates an expected call of SaveWebauthnUser.
func (mr *MockStorageMockRecorder) SaveWebauthnUser(arg0, arg1 interface{}) *gomock.Call {
// SaveWebAuthnUser indicates an expected call of SaveWebAuthnUser.
func (mr *MockStorageMockRecorder) SaveWebAuthnUser(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveWebauthnUser", reflect.TypeOf((*MockStorage)(nil).SaveWebauthnUser), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveWebAuthnUser", reflect.TypeOf((*MockStorage)(nil).SaveWebAuthnUser), arg0, arg1)
}
// SchemaEncryptionChangeKey mocks base method.
@ -908,30 +907,30 @@ func (mr *MockStorageMockRecorder) UpdateTOTPConfigurationSignIn(arg0, arg1, arg
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTOTPConfigurationSignIn", reflect.TypeOf((*MockStorage)(nil).UpdateTOTPConfigurationSignIn), arg0, arg1, arg2)
}
// UpdateWebauthnDeviceDescription mocks base method.
func (m *MockStorage) UpdateWebauthnDeviceDescription(arg0 context.Context, arg1 string, arg2 int, arg3 string) error {
// UpdateWebAuthnDeviceDescription mocks base method.
func (m *MockStorage) UpdateWebAuthnDeviceDescription(arg0 context.Context, arg1 string, arg2 int, arg3 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateWebauthnDeviceDescription", arg0, arg1, arg2, arg3)
ret := m.ctrl.Call(m, "UpdateWebAuthnDeviceDescription", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateWebauthnDeviceDescription indicates an expected call of UpdateWebauthnDeviceDescription.
func (mr *MockStorageMockRecorder) UpdateWebauthnDeviceDescription(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
// UpdateWebAuthnDeviceDescription indicates an expected call of UpdateWebAuthnDeviceDescription.
func (mr *MockStorageMockRecorder) UpdateWebAuthnDeviceDescription(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWebauthnDeviceDescription", reflect.TypeOf((*MockStorage)(nil).UpdateWebauthnDeviceDescription), arg0, arg1, arg2, arg3)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWebAuthnDeviceDescription", reflect.TypeOf((*MockStorage)(nil).UpdateWebAuthnDeviceDescription), arg0, arg1, arg2, arg3)
}
// UpdateWebauthnDeviceSignIn mocks base method.
func (m *MockStorage) UpdateWebauthnDeviceSignIn(arg0 context.Context, arg1 model.WebauthnDevice) error {
// UpdateWebAuthnDeviceSignIn mocks base method.
func (m *MockStorage) UpdateWebAuthnDeviceSignIn(arg0 context.Context, arg1 model.WebAuthnDevice) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateWebauthnDeviceSignIn", arg0, arg1)
ret := m.ctrl.Call(m, "UpdateWebAuthnDeviceSignIn", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateWebauthnDeviceSignIn indicates an expected call of UpdateWebauthnDeviceSignIn.
func (mr *MockStorageMockRecorder) UpdateWebauthnDeviceSignIn(arg0, arg1 interface{}) *gomock.Call {
// UpdateWebAuthnDeviceSignIn indicates an expected call of UpdateWebAuthnDeviceSignIn.
func (mr *MockStorageMockRecorder) UpdateWebAuthnDeviceSignIn(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWebauthnDeviceSignIn", reflect.TypeOf((*MockStorage)(nil).UpdateWebauthnDeviceSignIn), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWebAuthnDeviceSignIn", reflect.TypeOf((*MockStorage)(nil).UpdateWebAuthnDeviceSignIn), arg0, arg1)
}

View File

@ -15,8 +15,8 @@ const (
// SecondFactorMethodTOTP method using Time-Based One-Time Password applications like Google Authenticator.
SecondFactorMethodTOTP = "totp"
// SecondFactorMethodWebauthn method using Webauthn devices like YubiKey's.
SecondFactorMethodWebauthn = "webauthn"
// SecondFactorMethodWebAuthn method using WebAuthn devices like YubiKey's.
SecondFactorMethodWebAuthn = "webauthn"
// SecondFactorMethodDuo method using Duo application to receive push notifications.
SecondFactorMethodDuo = "mobile_push"

View File

@ -15,8 +15,8 @@ type UserInfo struct {
// True if a TOTP device has been registered.
HasTOTP bool `db:"has_totp" json:"has_totp" valid:"required"`
// True if a Webauthn device has been registered.
HasWebauthn bool `db:"has_webauthn" json:"has_webauthn" valid:"required"`
// True if a WebAuthn device has been registered.
HasWebAuthn bool `db:"has_webauthn" json:"has_webauthn" valid:"required"`
// True if a duo device has been configured as the preferred.
HasDuo bool `db:"has_duo" json:"has_duo" valid:"required"`
@ -31,7 +31,7 @@ func (i *UserInfo) SetDefaultPreferred2FAMethod(methods []string, fallback strin
before := i.Method
totp, webauthn, duo := utils.IsStringInSlice(SecondFactorMethodTOTP, methods), utils.IsStringInSlice(SecondFactorMethodWebauthn, methods), utils.IsStringInSlice(SecondFactorMethodDuo, methods)
totp, webauthn, duo := utils.IsStringInSlice(SecondFactorMethodTOTP, methods), utils.IsStringInSlice(SecondFactorMethodWebAuthn, methods), utils.IsStringInSlice(SecondFactorMethodDuo, methods)
if i.Method == "" && utils.IsStringInSlice(fallback, methods) {
i.Method = fallback
@ -50,8 +50,8 @@ func (i *UserInfo) setMethod(totp, webauthn, duo bool, methods []string, fallbac
switch {
case i.HasTOTP && totp:
i.Method = SecondFactorMethodTOTP
case i.HasWebauthn && webauthn:
i.Method = SecondFactorMethodWebauthn
case i.HasWebAuthn && webauthn:
i.Method = SecondFactorMethodWebAuthn
case i.HasDuo && duo:
i.Method = SecondFactorMethodDuo
case fallback != "" && utils.IsStringInSlice(fallback, methods):
@ -59,7 +59,7 @@ func (i *UserInfo) setMethod(totp, webauthn, duo bool, methods []string, fallbac
case totp:
i.Method = SecondFactorMethodTOTP
case webauthn:
i.Method = SecondFactorMethodWebauthn
i.Method = SecondFactorMethodWebAuthn
case duo:
i.Method = SecondFactorMethodDuo
}

View File

@ -20,7 +20,7 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
has := ""
if have.HasTOTP || have.HasDuo || have.HasWebauthn {
if have.HasTOTP || have.HasDuo || have.HasWebAuthn {
has += " has"
if have.HasTOTP {
@ -31,8 +31,8 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
has += " " + SecondFactorMethodDuo
}
if have.HasWebauthn {
has += " " + SecondFactorMethodWebauthn
if have.HasWebAuthn {
has += " " + SecondFactorMethodWebAuthn
}
}
@ -62,60 +62,60 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: SecondFactorMethodTOTP,
HasDuo: true,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
want: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: true,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
methods: []string{SecondFactorMethodWebauthn, SecondFactorMethodDuo},
methods: []string{SecondFactorMethodWebAuthn, SecondFactorMethodDuo},
changed: true,
},
{
have: UserInfo{
HasDuo: true,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
want: UserInfo{
Method: SecondFactorMethodTOTP,
HasDuo: true,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodWebauthn, SecondFactorMethodDuo},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodWebAuthn, SecondFactorMethodDuo},
changed: true,
},
{
have: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: true,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodTOTP,
HasDuo: true,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodTOTP},
changed: true,
},
{
have: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodTOTP,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodTOTP},
changed: true,
@ -125,15 +125,15 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: SecondFactorMethodTOTP,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodWebauthn},
methods: []string{SecondFactorMethodWebAuthn},
changed: true,
},
{
@ -141,31 +141,31 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: SecondFactorMethodTOTP,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodDuo,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodDuo},
changed: true,
},
{
have: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: false,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
want: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: false,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodWebauthn, SecondFactorMethodDuo},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodWebAuthn, SecondFactorMethodDuo},
changed: false,
},
{
@ -173,15 +173,15 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: "",
HasDuo: false,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
want: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: false,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
methods: []string{SecondFactorMethodWebauthn, SecondFactorMethodDuo},
methods: []string{SecondFactorMethodWebAuthn, SecondFactorMethodDuo},
changed: true,
},
{
@ -189,13 +189,13 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: "",
HasDuo: false,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
want: UserInfo{
Method: SecondFactorMethodDuo,
HasDuo: false,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
methods: []string{SecondFactorMethodDuo},
changed: true,
@ -205,13 +205,13 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: "",
HasDuo: false,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
want: UserInfo{
Method: "",
HasDuo: false,
HasTOTP: true,
HasWebauthn: true,
HasWebAuthn: true,
},
methods: nil,
changed: false,
@ -221,15 +221,15 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: "",
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodDuo,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodWebauthn, SecondFactorMethodDuo},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodWebAuthn, SecondFactorMethodDuo},
fallback: SecondFactorMethodDuo,
changed: true,
},
@ -238,15 +238,15 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: "",
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodTOTP,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodWebauthn},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodWebAuthn},
fallback: SecondFactorMethodDuo,
changed: true,
},
@ -255,15 +255,15 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: SecondFactorMethodTOTP,
HasDuo: true,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodDuo,
HasDuo: true,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodWebauthn, SecondFactorMethodDuo},
methods: []string{SecondFactorMethodWebAuthn, SecondFactorMethodDuo},
changed: true,
},
{
@ -271,30 +271,30 @@ func TestUserInfo_SetDefaultMethod(t *testing.T) {
Method: SecondFactorMethodTOTP,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodWebauthn, SecondFactorMethodDuo},
fallback: SecondFactorMethodWebauthn,
methods: []string{SecondFactorMethodWebAuthn, SecondFactorMethodDuo},
fallback: SecondFactorMethodWebAuthn,
changed: true,
},
{
have: UserInfo{
Method: SecondFactorMethodWebauthn,
Method: SecondFactorMethodWebAuthn,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
want: UserInfo{
Method: SecondFactorMethodDuo,
HasDuo: false,
HasTOTP: false,
HasWebauthn: false,
HasWebAuthn: false,
},
methods: []string{SecondFactorMethodTOTP, SecondFactorMethodDuo},
fallback: SecondFactorMethodDuo,

View File

@ -18,19 +18,19 @@ const (
attestationTypeFIDOU2F = "fido-u2f"
)
// WebauthnUser is an object to represent a user for the Webauthn lib.
type WebauthnUser struct {
// WebAuthnUser is an object to represent a user for the WebAuthn lib.
type WebAuthnUser struct {
ID int `db:"id"`
RPID string `db:"rpid"`
Username string `db:"username"`
UserID string `db:"userid"`
DisplayName string `db:"-"`
Devices []WebauthnDevice `db:"-"`
Devices []WebAuthnDevice `db:"-"`
}
// HasFIDOU2F returns true if the user has any attestation type `fido-u2f` devices.
func (w WebauthnUser) HasFIDOU2F() bool {
func (w WebAuthnUser) HasFIDOU2F() bool {
for _, c := range w.Devices {
if c.AttestationType == attestationTypeFIDOU2F {
return true
@ -41,27 +41,27 @@ func (w WebauthnUser) HasFIDOU2F() bool {
}
// WebAuthnID implements the webauthn.User interface.
func (w WebauthnUser) WebAuthnID() []byte {
func (w WebAuthnUser) WebAuthnID() []byte {
return []byte(w.UserID)
}
// WebAuthnName implements the webauthn.User interface.
func (w WebauthnUser) WebAuthnName() string {
func (w WebAuthnUser) WebAuthnName() string {
return w.Username
}
// WebAuthnDisplayName implements the webauthn.User interface.
func (w WebauthnUser) WebAuthnDisplayName() string {
func (w WebAuthnUser) WebAuthnDisplayName() string {
return w.DisplayName
}
// WebAuthnIcon implements the webauthn.User interface.
func (w WebauthnUser) WebAuthnIcon() string {
func (w WebAuthnUser) WebAuthnIcon() string {
return ""
}
// WebAuthnCredentials implements the webauthn.User interface.
func (w WebauthnUser) WebAuthnCredentials() (credentials []webauthn.Credential) {
func (w WebAuthnUser) WebAuthnCredentials() (credentials []webauthn.Credential) {
credentials = make([]webauthn.Credential, len(w.Devices))
var credential webauthn.Credential
@ -108,7 +108,7 @@ func (w WebauthnUser) WebAuthnCredentials() (credentials []webauthn.Credential)
}
// WebAuthnCredentialDescriptors decodes the users credentials into protocol.CredentialDescriptor's.
func (w WebauthnUser) WebAuthnCredentialDescriptors() (descriptors []protocol.CredentialDescriptor) {
func (w WebAuthnUser) WebAuthnCredentialDescriptors() (descriptors []protocol.CredentialDescriptor) {
credentials := w.WebAuthnCredentials()
descriptors = make([]protocol.CredentialDescriptor, len(credentials))
@ -120,15 +120,15 @@ func (w WebauthnUser) WebAuthnCredentialDescriptors() (descriptors []protocol.Cr
return descriptors
}
// NewWebauthnDeviceFromCredential creates a WebauthnDevice from a webauthn.Credential.
func NewWebauthnDeviceFromCredential(rpid, username, description string, credential *webauthn.Credential) (device WebauthnDevice) {
// NewWebAuthnDeviceFromCredential creates a WebAuthnDevice from a webauthn.Credential.
func NewWebAuthnDeviceFromCredential(rpid, username, description string, credential *webauthn.Credential) (device WebAuthnDevice) {
transport := make([]string, len(credential.Transport))
for i, t := range credential.Transport {
transport[i] = string(t)
}
device = WebauthnDevice{
device = WebAuthnDevice{
RPID: rpid,
Username: username,
CreatedAt: time.Now(),
@ -155,30 +155,8 @@ func NewWebauthnDeviceFromCredential(rpid, username, description string, credent
return device
}
// WebauthnDeviceJSON represents a Webauthn Device in the JSON format.
type WebauthnDeviceJSON struct {
ID int `json:"id"`
CreatedAt time.Time `json:"created_at"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
RPID string `json:"rpid"`
Description string `json:"description"`
KID []byte `json:"kid"`
AAGUID string `json:"aaguid,omitempty"`
Attachment string `json:"attachment"`
AttestationType string `json:"attestation_type"`
Transports []string `json:"transports"`
SignCount uint32 `json:"sign_count"`
CloneWarning bool `json:"clone_warning"`
Discoverable bool `json:"discoverable"`
Present bool `json:"present"`
Verified bool `json:"verified"`
BackupEligible bool `json:"backup_eligible"`
BackupState bool `json:"backup_state"`
PublicKey []byte `json:"public_key"`
}
// WebauthnDevice represents a Webauthn Device in the database storage.
type WebauthnDevice struct {
// WebAuthnDevice represents a WebAuthn Device in the database storage.
type WebAuthnDevice struct {
ID int `db:"id"`
CreatedAt time.Time `db:"created_at"`
LastUsedAt sql.NullTime `db:"last_used_at"`
@ -200,44 +178,8 @@ type WebauthnDevice struct {
PublicKey []byte `db:"public_key"`
}
// MarshalJSON returns the WebauthnDevice in a JSON friendly manner.
func (w *WebauthnDevice) MarshalJSON() (data []byte, err error) {
o := WebauthnDeviceJSON{
ID: w.ID,
CreatedAt: w.CreatedAt,
RPID: w.RPID,
Description: w.Description,
KID: w.KID.data,
AttestationType: w.AttestationType,
Attachment: w.Attachment,
Transports: []string{},
SignCount: w.SignCount,
CloneWarning: w.CloneWarning,
Discoverable: w.Discoverable,
Present: w.Present,
Verified: w.Verified,
BackupEligible: w.BackupEligible,
BackupState: w.BackupState,
PublicKey: w.PublicKey,
}
if w.AAGUID.Valid {
o.AAGUID = w.AAGUID.UUID.String()
}
if w.Transport != "" {
o.Transports = strings.Split(w.Transport, ",")
}
if w.LastUsedAt.Valid {
o.LastUsedAt = &w.LastUsedAt.Time
}
return json.Marshal(o)
}
// UpdateSignInInfo adjusts the values of the WebauthnDevice after a sign in.
func (d *WebauthnDevice) UpdateSignInInfo(config *webauthn.Config, now time.Time, signCount uint32) {
// UpdateSignInInfo adjusts the values of the WebAuthnDevice after a sign in.
func (d *WebAuthnDevice) UpdateSignInInfo(config *webauthn.Config, now time.Time, signCount uint32) {
d.LastUsedAt = sql.NullTime{Time: now, Valid: true}
d.SignCount = signCount
@ -254,7 +196,7 @@ func (d *WebauthnDevice) UpdateSignInInfo(config *webauthn.Config, now time.Time
}
}
func (d *WebauthnDevice) LastUsed() *time.Time {
func (d *WebAuthnDevice) DataValueLastUsedAt() *time.Time {
if d.LastUsedAt.Valid {
return &d.LastUsedAt.Time
}
@ -262,19 +204,28 @@ func (d *WebauthnDevice) LastUsed() *time.Time {
return nil
}
// MarshalYAML marshals this model into YAML.
func (d *WebauthnDevice) MarshalYAML() (any, error) {
o := WebauthnDeviceData{
func (d *WebAuthnDevice) DataValueAAGUID() *string {
if d.AAGUID.Valid {
value := d.AAGUID.UUID.String()
return &value
}
return nil
}
func (d *WebAuthnDevice) ToData() WebAuthnDeviceData {
o := WebAuthnDeviceData{
ID: d.ID,
CreatedAt: d.CreatedAt,
LastUsedAt: d.LastUsed(),
LastUsedAt: d.DataValueLastUsedAt(),
RPID: d.RPID,
Username: d.Username,
Description: d.Description,
KID: d.KID.String(),
AAGUID: d.AAGUID.UUID.String(),
AAGUID: d.DataValueAAGUID(),
AttestationType: d.AttestationType,
Attachment: d.Attachment,
Transport: d.Transport,
SignCount: d.SignCount,
CloneWarning: d.CloneWarning,
Present: d.Present,
@ -284,12 +235,26 @@ func (d *WebauthnDevice) MarshalYAML() (any, error) {
PublicKey: base64.StdEncoding.EncodeToString(d.PublicKey),
}
return yaml.Marshal(o)
if d.Transport != "" {
o.Transports = strings.Split(d.Transport, ",")
}
return o
}
// MarshalJSON returns the WebAuthnDevice in a JSON friendly manner.
func (d *WebAuthnDevice) MarshalJSON() (data []byte, err error) {
return json.Marshal(d.ToData())
}
// MarshalYAML marshals this model into YAML.
func (d *WebAuthnDevice) MarshalYAML() (any, error) {
return d.ToData(), nil
}
// UnmarshalYAML unmarshalls YAML into this model.
func (d *WebauthnDevice) UnmarshalYAML(value *yaml.Node) (err error) {
o := &WebauthnDeviceData{}
func (d *WebAuthnDevice) UnmarshalYAML(value *yaml.Node) (err error) {
o := &WebAuthnDeviceData{}
if err = value.Decode(o); err != nil {
return err
@ -301,12 +266,14 @@ func (d *WebauthnDevice) UnmarshalYAML(value *yaml.Node) (err error) {
var aaguid uuid.UUID
if aaguid, err = uuid.Parse(o.AAGUID); err != nil {
return err
}
if o.AAGUID != nil {
if aaguid, err = uuid.Parse(*o.AAGUID); err != nil {
return err
}
if aaguid.ID() != 0 {
d.AAGUID = uuid.NullUUID{Valid: true, UUID: aaguid}
if aaguid.ID() != 0 {
d.AAGUID = uuid.NullUUID{Valid: true, UUID: aaguid}
}
}
var kid []byte
@ -323,7 +290,7 @@ func (d *WebauthnDevice) UnmarshalYAML(value *yaml.Node) (err error) {
d.Description = o.Description
d.AttestationType = o.AttestationType
d.Attachment = o.Attachment
d.Transport = o.Transport
d.Transport = strings.Join(o.Transports, ",")
d.SignCount = o.SignCount
d.CloneWarning = o.CloneWarning
d.Discoverable = o.Discoverable
@ -339,29 +306,79 @@ func (d *WebauthnDevice) UnmarshalYAML(value *yaml.Node) (err error) {
return nil
}
// WebauthnDeviceData represents a Webauthn Device in the database storage.
type WebauthnDeviceData struct {
CreatedAt time.Time `yaml:"created_at"`
LastUsedAt *time.Time `yaml:"last_used_at"`
RPID string `yaml:"rpid"`
Username string `yaml:"username"`
Description string `yaml:"description"`
KID string `yaml:"kid"`
AAGUID string `yaml:"aaguid"`
AttestationType string `yaml:"attestation_type"`
Attachment string `yaml:"attachment"`
Transport string `yaml:"transport"`
SignCount uint32 `yaml:"sign_count"`
CloneWarning bool `yaml:"clone_warning"`
Discoverable bool `yaml:"discoverable"`
Present bool `yaml:"present"`
Verified bool `yaml:"verified"`
BackupEligible bool `yaml:"backup_eligible"`
BackupState bool `yaml:"backup_state"`
PublicKey string `yaml:"public_key"`
// WebAuthnDeviceData represents a WebAuthn Device in the database storage.
type WebAuthnDeviceData struct {
ID int `json:"id" yaml:"-"`
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
LastUsedAt *time.Time `json:"last_used_at,omitempty" yaml:"last_used_at,omitempty"`
RPID string `json:"rpid" yaml:"rpid"`
Username string `json:"-" yaml:"username"`
Description string `json:"description" yaml:"description"`
KID string `json:"kid" yaml:"kid"`
AAGUID *string `json:"aaguid,omitempty" yaml:"aaguid,omitempty"`
AttestationType string `json:"attestation_type" yaml:"attestation_type"`
Attachment string `json:"attachment" yaml:"attachment"`
Transports []string `json:"transports" yaml:"transports"`
SignCount uint32 `json:"sign_count" yaml:"sign_count"`
CloneWarning bool `json:"clone_warning" yaml:"clone_warning"`
Discoverable bool `json:"discoverable" yaml:"discoverable"`
Present bool `json:"present" yaml:"present"`
Verified bool `json:"verified" yaml:"verified"`
BackupEligible bool `json:"backup_eligible" yaml:"backup_eligible"`
BackupState bool `json:"backup_state" yaml:"backup_state"`
PublicKey string `json:"public_key" yaml:"public_key"`
}
// WebauthnDeviceExport represents a WebauthnDevice export file.
type WebauthnDeviceExport struct {
WebauthnDevices []WebauthnDevice `yaml:"webauthn_devices"`
func (d *WebAuthnDeviceData) ToDevice() (device *WebAuthnDevice, err error) {
device = &WebAuthnDevice{
CreatedAt: d.CreatedAt,
RPID: d.RPID,
Username: d.Username,
Description: d.Description,
AttestationType: d.AttestationType,
Attachment: d.Attachment,
Transport: strings.Join(d.Transports, ","),
SignCount: d.SignCount,
CloneWarning: d.CloneWarning,
Discoverable: d.Discoverable,
Present: d.Present,
Verified: d.Verified,
BackupEligible: d.BackupEligible,
BackupState: d.BackupState,
}
if device.PublicKey, err = base64.StdEncoding.DecodeString(d.PublicKey); err != nil {
return nil, err
}
var aaguid uuid.UUID
if d.AAGUID != nil {
if aaguid, err = uuid.Parse(*d.AAGUID); err != nil {
return nil, err
}
if aaguid.ID() != 0 {
device.AAGUID = uuid.NullUUID{Valid: true, UUID: aaguid}
}
}
var kid []byte
if kid, err = base64.StdEncoding.DecodeString(d.KID); err != nil {
return nil, err
}
device.KID = NewBase64(kid)
if d.LastUsedAt != nil {
device.LastUsedAt = sql.NullTime{Valid: true, Time: *d.LastUsedAt}
}
return device, nil
}
// WebAuthnDeviceExport represents a WebAuthnDevice export file.
type WebAuthnDeviceExport struct {
WebAuthnDevices []WebAuthnDevice `yaml:"webauthn_devices"`
}

View File

@ -5,9 +5,9 @@ type AuthenticationMethodsReferences struct {
UsernameAndPassword bool
TOTP bool
Duo bool
Webauthn bool
WebauthnUserPresence bool
WebauthnUserVerified bool
WebAuthn bool
WebAuthnUserPresence bool
WebAuthnUserVerified bool
}
// 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.
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.
@ -27,7 +27,7 @@ func (r AuthenticationMethodsReferences) MultiFactorAuthentication() bool {
// ChannelBrowser returns true if a browser was used to authenticate.
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.
@ -57,15 +57,15 @@ func (r AuthenticationMethodsReferences) MarshalRFC8176() []string {
amr = append(amr, AMRShortMessageService)
}
if r.Webauthn {
if r.WebAuthn {
amr = append(amr, AMRHardwareSecuredKey)
}
if r.WebauthnUserPresence {
if r.WebAuthnUserPresence {
amr = append(amr, AMRUserPresence)
}
if r.WebauthnUserVerified {
if r.WebAuthnUserVerified {
amr = append(amr, AMRPersonalIdentificationNumber)
}

View File

@ -49,9 +49,9 @@ func TestAuthenticationMethodsReferences(t *testing.T) {
},
},
{
desc: "Webauthn",
desc: "WebAuthn",
is: AuthenticationMethodsReferences{Webauthn: true},
is: AuthenticationMethodsReferences{WebAuthn: true},
want: testAMRWant{
FactorKnowledge: false,
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{
FactorKnowledge: 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{
FactorKnowledge: 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{
FactorKnowledge: false,
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{
FactorKnowledge: false,
FactorPossession: true,

View File

@ -190,7 +190,7 @@ const (
// 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.
//
// 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.
//
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
@ -203,7 +203,7 @@ const (
// 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.
//
// 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.
//
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
@ -239,7 +239,7 @@ const (
// AMRHardwareSecuredKey is an RFC8176 Authentication Method Reference Value that
// 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
AMRHardwareSecuredKey = "hwk"

View File

@ -12,8 +12,8 @@ const (
// AuthTypeTOTP is the string representing an auth log for second-factor authentication via TOTP.
AuthTypeTOTP = "TOTP"
// AuthTypeWebauthn is the string representing an auth log for second-factor authentication via FIDO2/CTAP2/WebAuthn.
AuthTypeWebauthn = "Webauthn"
// AuthTypeWebAuthn is the string representing an auth log for second-factor authentication via FIDO2/CTAP2/WebAuthn.
AuthTypeWebAuthn = "WebAuthn"
// AuthTypeDuo is the string representing an auth log for second-factor authentication via DUO.
AuthTypeDuo = "Duo"

View File

@ -260,16 +260,16 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
r.POST("/api/secondfactor/totp", middleware1FA(handlers.TimeBasedOneTimePasswordPOST))
}
if !config.Webauthn.Disable {
r.GET("/api/secondfactor/webauthn", middleware1FA(handlers.WebauthnAssertionGET))
r.POST("/api/secondfactor/webauthn", middleware1FA(handlers.WebauthnAssertionPOST))
if !config.WebAuthn.Disable {
r.GET("/api/secondfactor/webauthn", middleware1FA(handlers.WebAuthnAssertionGET))
r.POST("/api/secondfactor/webauthn", middleware1FA(handlers.WebAuthnAssertionPOST))
// Management of the webauthn devices.
r.GET("/api/secondfactor/webauthn/credentials", middleware1FA(handlers.WebauthnDevicesGET))
r.PUT("/api/secondfactor/webauthn/credential/register", middleware1FA(handlers.WebauthnRegistrationPUT))
r.POST("/api/secondfactor/webauthn/credential/register", middleware1FA(handlers.WebauthnRegistrationPOST))
r.PUT("/api/secondfactor/webauthn/credential/{deviceID}", middleware2FA(handlers.WebauthnDevicePUT))
r.DELETE("/api/secondfactor/webauthn/credential/{deviceID}", middleware2FA(handlers.WebauthnDeviceDELETE))
r.GET("/api/secondfactor/webauthn/credentials", middleware1FA(handlers.WebAuthnDevicesGET))
r.PUT("/api/secondfactor/webauthn/credential/register", middleware1FA(handlers.WebAuthnRegistrationPUT))
r.POST("/api/secondfactor/webauthn/credential/register", middleware1FA(handlers.WebAuthnRegistrationPOST))
r.PUT("/api/secondfactor/webauthn/credential/{deviceID}", middleware2FA(handlers.WebAuthnDevicePUT))
r.DELETE("/api/secondfactor/webauthn/credential/{deviceID}", middleware2FA(handlers.WebAuthnDeviceDELETE))
}
// Configure DUO api endpoint only if configuration exists.

View File

@ -272,7 +272,7 @@ func NewTemplatedFileOptions(config *schema.Configuration) (opts *TemplatedFileO
Theme: config.Theme,
EndpointsPasswordReset: !(config.AuthenticationBackend.PasswordReset.Disable || config.AuthenticationBackend.PasswordReset.CustomURL.String() != ""),
EndpointsWebauthn: !config.Webauthn.Disable,
EndpointsWebauthn: !config.WebAuthn.Disable,
EndpointsTOTP: !config.TOTP.Disable,
EndpointsDuo: !config.DuoAPI.Disable,
EndpointsOpenIDConnect: !(config.IdentityProviders.OIDC == nil),

View File

@ -179,7 +179,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
AuthenticationMethodRefs: oidc.AuthenticationMethodsReferences{UsernameAndPassword: true},
}, session)
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
session.SetTwoFactorWebAuthn(timeTwoFactor, false, false)
err = provider.SaveSession(ctx, session)
assert.NoError(t, err)
@ -187,7 +187,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
session, err = provider.GetSession(ctx)
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())
authAt, err = session.AuthenticatedTime(authorization.OneFactor)
@ -202,7 +202,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.EqualError(t, err, "invalid authorization level")
assert.Equal(t, timeZeroFactor, authAt)
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
session.SetTwoFactorWebAuthn(timeTwoFactor, false, false)
err = provider.SaveSession(ctx, session)
assert.NoError(t, err)
@ -211,10 +211,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t,
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true},
session.AuthenticationMethodRefs)
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
session.SetTwoFactorWebAuthn(timeTwoFactor, false, false)
err = provider.SaveSession(ctx, session)
assert.NoError(t, err)
@ -223,10 +223,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t,
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true},
session.AuthenticationMethodRefs)
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
session.SetTwoFactorWebAuthn(timeTwoFactor, true, false)
err = provider.SaveSession(ctx, session)
assert.NoError(t, err)
@ -235,10 +235,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t,
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true, WebAuthnUserPresence: true},
session.AuthenticationMethodRefs)
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
session.SetTwoFactorWebAuthn(timeTwoFactor, true, false)
err = provider.SaveSession(ctx, session)
assert.NoError(t, err)
@ -247,10 +247,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t,
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true, WebAuthnUserPresence: true},
session.AuthenticationMethodRefs)
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
session.SetTwoFactorWebAuthn(timeTwoFactor, false, true)
err = provider.SaveSession(ctx, session)
assert.NoError(t, err)
@ -259,10 +259,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t,
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true, WebAuthnUserVerified: true},
session.AuthenticationMethodRefs)
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
session.SetTwoFactorWebAuthn(timeTwoFactor, false, true)
err = provider.SaveSession(ctx, session)
assert.NoError(t, err)
@ -271,7 +271,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t,
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, WebAuthn: true, WebAuthnUserVerified: true},
session.AuthenticationMethodRefs)
session.SetTwoFactorTOTP(timeTwoFactor)
@ -283,7 +283,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.NoError(t, err)
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.SetTwoFactorTOTP(timeTwoFactor)
@ -295,7 +295,7 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
assert.NoError(t, err)
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)
}

View File

@ -35,8 +35,8 @@ type UserSession struct {
AuthenticationMethodRefs oidc.AuthenticationMethodsReferences
// Webauthn holds the session registration data for this session.
Webauthn *Webauthn
// WebAuthn holds the session registration data for this session.
WebAuthn *WebAuthn
// This boolean is set to true after identity verification and checked
// while doing the query actually updating the password.
@ -45,8 +45,8 @@ type UserSession struct {
RefreshTTL time.Time
}
// Webauthn holds the standard webauthn session data plus some extra.
type Webauthn struct {
// WebAuthn holds the standard webauthn session data plus some extra.
type WebAuthn struct {
*webauthn.SessionData
Description string
}

View File

@ -56,13 +56,13 @@ func (s *UserSession) SetTwoFactorDuo(now time.Time) {
s.AuthenticationMethodRefs.Duo = true
}
// SetTwoFactorWebauthn sets the relevant Webauthn AMR's and sets the factor to 2FA.
func (s *UserSession) SetTwoFactorWebauthn(now time.Time, userPresence, userVerified bool) {
// SetTwoFactorWebAuthn sets the relevant WebAuthn AMR's and sets the factor to 2FA.
func (s *UserSession) SetTwoFactorWebAuthn(now time.Time, userPresence, userVerified bool) {
s.setTwoFactor(now)
s.AuthenticationMethodRefs.Webauthn = true
s.AuthenticationMethodRefs.WebauthnUserPresence, s.AuthenticationMethodRefs.WebauthnUserVerified = userPresence, userVerified
s.AuthenticationMethodRefs.WebAuthn = true
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.

View File

@ -11,8 +11,8 @@ const (
tableTOTPConfigurations = "totp_configurations"
tableUserOpaqueIdentifier = "user_opaque_identifier"
tableUserPreferences = "user_preferences"
tableWebauthnDevices = "webauthn_devices"
tableWebauthnUsers = "webauthn_users"
tableWebAuthnDevices = "webauthn_devices"
tableWebAuthnUsers = "webauthn_users"
tableOAuth2BlacklistedJTI = "oauth2_blacklisted_jti"
tableOAuth2ConsentSession = "oauth2_consent_session"

View File

@ -11,8 +11,8 @@ var (
// ErrNoTOTPConfiguration error thrown when no TOTP configuration has been found in DB.
ErrNoTOTPConfiguration = errors.New("no TOTP configuration for user")
// ErrNoWebauthnDevice error thrown when no Webauthn device handle has been found in DB.
ErrNoWebauthnDevice = errors.New("no Webauthn device found")
// ErrNoWebAuthnDevice error thrown when no WebAuthn device handle has been found in DB.
ErrNoWebAuthnDevice = errors.New("no WebAuthn device found")
// ErrNoDuoDevice error thrown when no Duo device and method has been found in DB.
ErrNoDuoDevice = errors.New("no Duo device and method saved")

View File

@ -9,7 +9,7 @@ import (
const (
// This is the latest schema version for the purpose of tests.
LatestVersion = 9
LatestVersion = 10
)
func TestShouldObtainCorrectUpMigrations(t *testing.T) {

View File

@ -38,17 +38,17 @@ type Provider interface {
LoadTOTPConfiguration(ctx context.Context, username string) (config *model.TOTPConfiguration, err error)
LoadTOTPConfigurations(ctx context.Context, limit, page int) (configs []model.TOTPConfiguration, err error)
SaveWebauthnUser(ctx context.Context, user model.WebauthnUser) (err error)
LoadWebauthnUser(ctx context.Context, rpid, username string) (user *model.WebauthnUser, err error)
SaveWebAuthnUser(ctx context.Context, user model.WebAuthnUser) (err error)
LoadWebAuthnUser(ctx context.Context, rpid, username string) (user *model.WebAuthnUser, err error)
SaveWebauthnDevice(ctx context.Context, device model.WebauthnDevice) (err error)
UpdateWebauthnDeviceDescription(ctx context.Context, username string, deviceID int, description string) (err error)
UpdateWebauthnDeviceSignIn(ctx context.Context, device model.WebauthnDevice) (err error)
DeleteWebauthnDevice(ctx context.Context, kid string) (err error)
DeleteWebauthnDeviceByUsername(ctx context.Context, username, description string) (err error)
LoadWebauthnDevices(ctx context.Context, limit, page int) (devices []model.WebauthnDevice, err error)
LoadWebauthnDevicesByUsername(ctx context.Context, rpid, username string) (devices []model.WebauthnDevice, err error)
LoadWebauthnDeviceByID(ctx context.Context, id int) (device *model.WebauthnDevice, err error)
SaveWebAuthnDevice(ctx context.Context, device model.WebAuthnDevice) (err error)
UpdateWebAuthnDeviceDescription(ctx context.Context, username string, deviceID int, description string) (err error)
UpdateWebAuthnDeviceSignIn(ctx context.Context, device model.WebAuthnDevice) (err error)
DeleteWebAuthnDevice(ctx context.Context, kid string) (err error)
DeleteWebAuthnDeviceByUsername(ctx context.Context, username, description string) (err error)
LoadWebAuthnDevices(ctx context.Context, limit, page int) (devices []model.WebAuthnDevice, err error)
LoadWebAuthnDevicesByUsername(ctx context.Context, rpid, username string) (devices []model.WebAuthnDevice, err error)
LoadWebAuthnDeviceByID(ctx context.Context, id int) (device *model.WebAuthnDevice, err error)
SavePreferredDuoDevice(ctx context.Context, device model.DuoDevice) (err error)
DeletePreferredDuoDevice(ctx context.Context, username string) (err error)

View File

@ -46,19 +46,19 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlUpdateTOTPConfigRecordSignIn: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignIn, tableTOTPConfigurations),
sqlUpdateTOTPConfigRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateTOTPConfigRecordSignInByUsername, tableTOTPConfigurations),
sqlInsertWebauthnUser: fmt.Sprintf(queryFmtInsertWebauthnUser, tableWebauthnUsers),
sqlSelectWebauthnUser: fmt.Sprintf(queryFmtSelectWebauthnUser, tableWebauthnUsers),
sqlInsertWebAuthnUser: fmt.Sprintf(queryFmtInsertWebAuthnUser, tableWebAuthnUsers),
sqlSelectWebAuthnUser: fmt.Sprintf(queryFmtSelectWebAuthnUser, tableWebAuthnUsers),
sqlInsertWebauthnDevice: fmt.Sprintf(queryFmtInsertWebauthnDevice, tableWebauthnDevices),
sqlSelectWebauthnDevices: fmt.Sprintf(queryFmtSelectWebauthnDevices, tableWebauthnDevices),
sqlSelectWebauthnDevicesByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByUsername, tableWebauthnDevices),
sqlSelectWebauthnDevicesByRPIDByUsername: fmt.Sprintf(queryFmtSelectWebauthnDevicesByRPIDByUsername, tableWebauthnDevices),
sqlSelectWebauthnDeviceByID: fmt.Sprintf(queryFmtSelectWebauthnDeviceByID, tableWebauthnDevices),
sqlUpdateWebauthnDeviceDescriptionByUsernameAndID: fmt.Sprintf(queryFmtUpdateUpdateWebauthnDeviceDescriptionByUsernameAndID, tableWebauthnDevices),
sqlUpdateWebauthnDeviceRecordSignIn: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignIn, tableWebauthnDevices),
sqlDeleteWebauthnDevice: fmt.Sprintf(queryFmtDeleteWebauthnDevice, tableWebauthnDevices),
sqlDeleteWebauthnDeviceByUsername: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsername, tableWebauthnDevices),
sqlDeleteWebauthnDeviceByUsernameAndDisplayName: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsernameAndDescription, tableWebauthnDevices),
sqlInsertWebAuthnDevice: fmt.Sprintf(queryFmtInsertWebAuthnDevice, tableWebAuthnDevices),
sqlSelectWebAuthnDevices: fmt.Sprintf(queryFmtSelectWebAuthnDevices, tableWebAuthnDevices),
sqlSelectWebAuthnDevicesByUsername: fmt.Sprintf(queryFmtSelectWebAuthnDevicesByUsername, tableWebAuthnDevices),
sqlSelectWebAuthnDevicesByRPIDByUsername: fmt.Sprintf(queryFmtSelectWebAuthnDevicesByRPIDByUsername, tableWebAuthnDevices),
sqlSelectWebAuthnDeviceByID: fmt.Sprintf(queryFmtSelectWebAuthnDeviceByID, tableWebAuthnDevices),
sqlUpdateWebAuthnDeviceDescriptionByUsernameAndID: fmt.Sprintf(queryFmtUpdateUpdateWebAuthnDeviceDescriptionByUsernameAndID, tableWebAuthnDevices),
sqlUpdateWebAuthnDeviceRecordSignIn: fmt.Sprintf(queryFmtUpdateWebAuthnDeviceRecordSignIn, tableWebAuthnDevices),
sqlDeleteWebAuthnDevice: fmt.Sprintf(queryFmtDeleteWebAuthnDevice, tableWebAuthnDevices),
sqlDeleteWebAuthnDeviceByUsername: fmt.Sprintf(queryFmtDeleteWebAuthnDeviceByUsername, tableWebAuthnDevices),
sqlDeleteWebAuthnDeviceByUsernameAndDisplayName: fmt.Sprintf(queryFmtDeleteWebAuthnDeviceByUsernameAndDescription, tableWebAuthnDevices),
sqlUpsertDuoDevice: fmt.Sprintf(queryFmtUpsertDuoDevice, tableDuoDevices),
sqlDeleteDuoDevice: fmt.Sprintf(queryFmtDeleteDuoDevice, tableDuoDevices),
@ -66,7 +66,7 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlUpsertPreferred2FAMethod: fmt.Sprintf(queryFmtUpsertPreferred2FAMethod, 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),
sqlSelectUserOpaqueIdentifier: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifier, tableUserOpaqueIdentifier),
@ -168,22 +168,22 @@ type SQLProvider struct {
sqlUpdateTOTPConfigRecordSignInByUsername string
// Table: webauthn_users.
sqlInsertWebauthnUser string
sqlSelectWebauthnUser string
sqlInsertWebAuthnUser string
sqlSelectWebAuthnUser string
// Table: webauthn_devices.
sqlInsertWebauthnDevice string
sqlSelectWebauthnDevices string
sqlSelectWebauthnDevicesByUsername string
sqlSelectWebauthnDevicesByRPIDByUsername string
sqlSelectWebauthnDeviceByID string
sqlInsertWebAuthnDevice string
sqlSelectWebAuthnDevices string
sqlSelectWebAuthnDevicesByUsername string
sqlSelectWebAuthnDevicesByRPIDByUsername string
sqlSelectWebAuthnDeviceByID string
sqlUpdateWebauthnDeviceDescriptionByUsernameAndID string
sqlUpdateWebauthnDeviceRecordSignIn string
sqlUpdateWebAuthnDeviceDescriptionByUsernameAndID string
sqlUpdateWebAuthnDeviceRecordSignIn string
sqlDeleteWebauthnDevice string
sqlDeleteWebauthnDeviceByUsername string
sqlDeleteWebauthnDeviceByUsernameAndDisplayName string
sqlDeleteWebAuthnDevice string
sqlDeleteWebAuthnDeviceByUsername string
sqlDeleteWebAuthnDeviceByUsernameAndDisplayName string
// Table: duo_devices.
sqlUpsertDuoDevice string
@ -832,7 +832,7 @@ func (p *SQLProvider) SaveTOTPConfiguration(ctx context.Context, config model.TO
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) {
if _, err = p.db.ExecContext(ctx, p.sqlUpdateTOTPConfigRecordSignIn, lastUsedAt, id); err != nil {
return fmt.Errorf("error updating TOTP configuration id %d: %w", id, err)
@ -890,154 +890,154 @@ func (p *SQLProvider) LoadTOTPConfigurations(ctx context.Context, limit, page in
return configs, nil
}
// SaveWebauthnUser saves a registered Webauthn user.
func (p *SQLProvider) SaveWebauthnUser(ctx context.Context, user model.WebauthnUser) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlInsertWebauthnUser, user.RPID, user.Username, user.UserID); err != nil {
return fmt.Errorf("error inserting Webauthn user '%s' with relying party id '%s': %w", user.Username, user.RPID, err)
// SaveWebAuthnUser saves a registered WebAuthn user.
func (p *SQLProvider) SaveWebAuthnUser(ctx context.Context, user model.WebAuthnUser) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlInsertWebAuthnUser, user.RPID, user.Username, user.UserID); err != nil {
return fmt.Errorf("error inserting WebAuthn user '%s' with relying party id '%s': %w", user.Username, user.RPID, err)
}
return nil
}
// LoadWebauthnUser loads a registered Webauthn user.
func (p *SQLProvider) LoadWebauthnUser(ctx context.Context, rpid, username string) (user *model.WebauthnUser, err error) {
user = &model.WebauthnUser{}
// LoadWebAuthnUser loads a registered WebAuthn user.
func (p *SQLProvider) LoadWebAuthnUser(ctx context.Context, rpid, username string) (user *model.WebAuthnUser, err error) {
user = &model.WebAuthnUser{}
if err = p.db.GetContext(ctx, user, p.sqlSelectWebauthnUser, rpid, username); err != nil {
if err = p.db.GetContext(ctx, user, p.sqlSelectWebAuthnUser, rpid, username); err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, nil
default:
return nil, fmt.Errorf("error selecting Webauthn user '%s' with relying party id '%s': %w", user.Username, user.RPID, err)
return nil, fmt.Errorf("error selecting WebAuthn user '%s' with relying party id '%s': %w", user.Username, user.RPID, err)
}
}
return user, nil
}
// SaveWebauthnDevice saves a registered Webauthn device.
func (p *SQLProvider) SaveWebauthnDevice(ctx context.Context, device model.WebauthnDevice) (err error) {
// SaveWebAuthnDevice saves a registered WebAuthn device.
func (p *SQLProvider) SaveWebAuthnDevice(ctx context.Context, device model.WebAuthnDevice) (err error) {
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.sqlInsertWebauthnDevice,
if _, err = p.db.ExecContext(ctx, p.sqlInsertWebAuthnDevice,
device.CreatedAt, device.LastUsedAt, device.RPID, device.Username, device.Description,
device.KID, device.AAGUID, device.AttestationType, device.Attachment, device.Transport,
device.SignCount, device.CloneWarning, device.Discoverable, device.Present, device.Verified,
device.BackupEligible, device.BackupState, device.PublicKey,
); err != nil {
return fmt.Errorf("error inserting Webauthn device for user '%s' kid '%x': %w", device.Username, device.KID, err)
return fmt.Errorf("error inserting WebAuthn device for user '%s' kid '%x': %w", device.Username, device.KID, err)
}
return nil
}
// UpdateWebauthnDeviceDescription updates a registered Webauthn device's description.
func (p *SQLProvider) UpdateWebauthnDeviceDescription(ctx context.Context, username string, deviceID int, description string) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlUpdateWebauthnDeviceDescriptionByUsernameAndID, description, username, deviceID); err != nil {
return fmt.Errorf("error updating Webauthn device description to '%s' for device id '%d': %w", description, deviceID, err)
// UpdateWebAuthnDeviceDescription updates a registered WebAuthn device's description.
func (p *SQLProvider) UpdateWebAuthnDeviceDescription(ctx context.Context, username string, deviceID int, description string) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlUpdateWebAuthnDeviceDescriptionByUsernameAndID, description, username, deviceID); err != nil {
return fmt.Errorf("error updating WebAuthn device description to '%s' for device id '%d': %w", description, deviceID, err)
}
return nil
}
// UpdateWebauthnDeviceSignIn updates a registered Webauthn devices sign in information.
func (p *SQLProvider) UpdateWebauthnDeviceSignIn(ctx context.Context, device model.WebauthnDevice) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlUpdateWebauthnDeviceRecordSignIn,
// UpdateWebAuthnDeviceSignIn updates a registered WebAuthn devices sign in information.
func (p *SQLProvider) UpdateWebAuthnDeviceSignIn(ctx context.Context, device model.WebAuthnDevice) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlUpdateWebAuthnDeviceRecordSignIn,
device.RPID, device.LastUsedAt, device.SignCount, device.Discoverable, device.Present, device.Verified,
device.BackupEligible, device.BackupState, device.CloneWarning, device.ID,
); err != nil {
return fmt.Errorf("error updating Webauthn authentication metadata for id '%x': %w", device.ID, err)
return fmt.Errorf("error updating WebAuthn authentication metadata for id '%x': %w", device.ID, err)
}
return nil
}
// DeleteWebauthnDevice deletes a registered Webauthn device.
func (p *SQLProvider) DeleteWebauthnDevice(ctx context.Context, kid string) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDevice, kid); err != nil {
return fmt.Errorf("error deleting webauthn device with kid '%s': %w", kid, err)
// DeleteWebAuthnDevice deletes a registered WebAuthn device.
func (p *SQLProvider) DeleteWebAuthnDevice(ctx context.Context, kid string) (err error) {
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 nil
}
// DeleteWebauthnDeviceByUsername deletes registered Webauthn devices by username or username and description.
func (p *SQLProvider) DeleteWebauthnDeviceByUsername(ctx context.Context, username, displayname string) (err error) {
// DeleteWebAuthnDeviceByUsername deletes registered WebAuthn devices by username or username and description.
func (p *SQLProvider) DeleteWebAuthnDeviceByUsername(ctx context.Context, username, displayname string) (err error) {
if len(username) == 0 {
return fmt.Errorf("error deleting webauthn device with username '%s' and displayname '%s': username must not be empty", username, displayname)
return fmt.Errorf("error deleting WebAuthn device with username '%s' and displayname '%s': username must not be empty", username, displayname)
}
if len(displayname) == 0 {
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDeviceByUsername, username); err != nil {
return fmt.Errorf("error deleting webauthn devices for username '%s': %w", username, err)
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebAuthnDeviceByUsername, username); err != nil {
return fmt.Errorf("error deleting WebAuthn devices for username '%s': %w", username, err)
}
} else {
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDeviceByUsernameAndDisplayName, username, displayname); err != nil {
return fmt.Errorf("error deleting webauthn device with username '%s' and displayname '%s': %w", username, displayname, err)
if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebAuthnDeviceByUsernameAndDisplayName, username, displayname); err != nil {
return fmt.Errorf("error deleting WebAuthn device with username '%s' and displayname '%s': %w", username, displayname, err)
}
}
return nil
}
// LoadWebauthnDevices loads Webauthn device registrations.
func (p *SQLProvider) LoadWebauthnDevices(ctx context.Context, limit, page int) (devices []model.WebauthnDevice, err error) {
devices = make([]model.WebauthnDevice, 0, limit)
// LoadWebAuthnDevices loads WebAuthn device registrations.
func (p *SQLProvider) LoadWebAuthnDevices(ctx context.Context, limit, page int) (devices []model.WebAuthnDevice, err error) {
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) {
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 {
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
}
// LoadWebauthnDeviceByID loads a webauthn device registration for a given id.
func (p *SQLProvider) LoadWebauthnDeviceByID(ctx context.Context, id int) (device *model.WebauthnDevice, err error) {
device = &model.WebauthnDevice{}
// LoadWebAuthnDeviceByID loads a WebAuthn device registration for a given id.
func (p *SQLProvider) LoadWebAuthnDeviceByID(ctx context.Context, id int) (device *model.WebAuthnDevice, err error) {
device = &model.WebAuthnDevice{}
if err = p.db.GetContext(ctx, device, p.sqlSelectWebauthnDeviceByID, id); err != nil {
if err = p.db.GetContext(ctx, device, p.sqlSelectWebAuthnDeviceByID, id); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, sql.ErrNoRows
}
return nil, fmt.Errorf("error selecting Webauthn device with id '%d': %w", id, err)
return nil, fmt.Errorf("error selecting WebAuthn device with id '%d': %w", id, err)
}
return device, nil
}
// LoadWebauthnDevicesByUsername loads all webauthn devices registration for a given username.
func (p *SQLProvider) LoadWebauthnDevicesByUsername(ctx context.Context, rpid, username string) (devices []model.WebauthnDevice, err error) {
// LoadWebAuthnDevicesByUsername loads all WebAuthn devices registration for a given username.
func (p *SQLProvider) LoadWebAuthnDevicesByUsername(ctx context.Context, rpid, username string) (devices []model.WebAuthnDevice, err error) {
switch len(rpid) {
case 0:
err = p.db.SelectContext(ctx, &devices, p.sqlSelectWebauthnDevicesByUsername, username)
err = p.db.SelectContext(ctx, &devices, p.sqlSelectWebAuthnDevicesByUsername, username)
default:
err = p.db.SelectContext(ctx, &devices, p.sqlSelectWebauthnDevicesByRPIDByUsername, rpid, username)
err = p.db.SelectContext(ctx, &devices, p.sqlSelectWebAuthnDevicesByRPIDByUsername, rpid, username)
}
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return devices, ErrNoWebauthnDevice
return devices, 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 {
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)
}
}

View File

@ -58,19 +58,19 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo
provider.sqlDeleteTOTPConfig = provider.db.Rebind(provider.sqlDeleteTOTPConfig)
provider.sqlSelectTOTPConfigs = provider.db.Rebind(provider.sqlSelectTOTPConfigs)
provider.sqlInsertWebauthnUser = provider.db.Rebind(provider.sqlInsertWebauthnUser)
provider.sqlSelectWebauthnUser = provider.db.Rebind(provider.sqlSelectWebauthnUser)
provider.sqlInsertWebAuthnUser = provider.db.Rebind(provider.sqlInsertWebAuthnUser)
provider.sqlSelectWebAuthnUser = provider.db.Rebind(provider.sqlSelectWebAuthnUser)
provider.sqlInsertWebauthnDevice = provider.db.Rebind(provider.sqlInsertWebauthnDevice)
provider.sqlSelectWebauthnDevices = provider.db.Rebind(provider.sqlSelectWebauthnDevices)
provider.sqlSelectWebauthnDevicesByUsername = provider.db.Rebind(provider.sqlSelectWebauthnDevicesByUsername)
provider.sqlSelectWebauthnDevicesByRPIDByUsername = provider.db.Rebind(provider.sqlSelectWebauthnDevicesByRPIDByUsername)
provider.sqlSelectWebauthnDeviceByID = provider.db.Rebind(provider.sqlSelectWebauthnDeviceByID)
provider.sqlUpdateWebauthnDeviceDescriptionByUsernameAndID = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceDescriptionByUsernameAndID)
provider.sqlUpdateWebauthnDeviceRecordSignIn = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceRecordSignIn)
provider.sqlDeleteWebauthnDevice = provider.db.Rebind(provider.sqlDeleteWebauthnDevice)
provider.sqlDeleteWebauthnDeviceByUsername = provider.db.Rebind(provider.sqlDeleteWebauthnDeviceByUsername)
provider.sqlDeleteWebauthnDeviceByUsernameAndDisplayName = provider.db.Rebind(provider.sqlDeleteWebauthnDeviceByUsernameAndDisplayName)
provider.sqlInsertWebAuthnDevice = provider.db.Rebind(provider.sqlInsertWebAuthnDevice)
provider.sqlSelectWebAuthnDevices = provider.db.Rebind(provider.sqlSelectWebAuthnDevices)
provider.sqlSelectWebAuthnDevicesByUsername = provider.db.Rebind(provider.sqlSelectWebAuthnDevicesByUsername)
provider.sqlSelectWebAuthnDevicesByRPIDByUsername = provider.db.Rebind(provider.sqlSelectWebAuthnDevicesByRPIDByUsername)
provider.sqlSelectWebAuthnDeviceByID = provider.db.Rebind(provider.sqlSelectWebAuthnDeviceByID)
provider.sqlUpdateWebAuthnDeviceDescriptionByUsernameAndID = provider.db.Rebind(provider.sqlUpdateWebAuthnDeviceDescriptionByUsernameAndID)
provider.sqlUpdateWebAuthnDeviceRecordSignIn = provider.db.Rebind(provider.sqlUpdateWebAuthnDeviceRecordSignIn)
provider.sqlDeleteWebAuthnDevice = provider.db.Rebind(provider.sqlDeleteWebAuthnDevice)
provider.sqlDeleteWebAuthnDeviceByUsername = provider.db.Rebind(provider.sqlDeleteWebAuthnDeviceByUsername)
provider.sqlDeleteWebAuthnDeviceByUsernameAndDisplayName = provider.db.Rebind(provider.sqlDeleteWebAuthnDeviceByUsernameAndDisplayName)
provider.sqlSelectDuoDevice = provider.db.Rebind(provider.sqlSelectDuoDevice)
provider.sqlDeleteDuoDevice = provider.db.Rebind(provider.sqlDeleteDuoDevice)

View File

@ -34,7 +34,7 @@ func (p *SQLProvider) SchemaEncryptionChangeKey(ctx context.Context, key string)
encChangeFuncs := []EncryptionChangeKeyFunc{
schemaEncryptionChangeKeyTOTP,
schemaEncryptionChangeKeyWebauthn,
schemaEncryptionChangeKeyWebAuthn,
}
for i := 0; true; i++ {
@ -90,7 +90,7 @@ func (p *SQLProvider) SchemaEncryptionCheckKey(ctx context.Context, verbose bool
if verbose {
encCheckFuncs := []EncryptionCheckKeyFunc{
schemaEncryptionCheckKeyTOTP,
schemaEncryptionCheckKeyWebauthn,
schemaEncryptionCheckKeyWebAuthn,
}
for i := 0; true; i++ {
@ -153,10 +153,10 @@ func schemaEncryptionChangeKeyTOTP(ctx context.Context, provider *SQLProvider, t
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
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
}
@ -164,29 +164,29 @@ func schemaEncryptionChangeKeyWebauthn(ctx context.Context, provider *SQLProvide
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) {
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(queryFmtUpdateWebauthnDevicesEncryptedData, tableWebauthnDevices))
query := provider.db.Rebind(fmt.Sprintf(queryFmtUpdateWebAuthnDevicesEncryptedData, tableWebAuthnDevices))
for _, d := range devices {
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 {
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 {
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
}
func schemaEncryptionCheckKeyWebauthn(ctx context.Context, provider *SQLProvider) (table string, result EncryptionValidationTableResult) {
func schemaEncryptionCheckKeyWebAuthn(ctx context.Context, provider *SQLProvider) (table string, result EncryptionValidationTableResult) {
var (
rows *sqlx.Rows
err error
)
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)}
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)}
}
var device encWebauthnDevice
var device encWebAuthnDevice
for rows.Next() {
result.Total++
@ -280,7 +280,7 @@ func schemaEncryptionCheckKeyWebauthn(ctx context.Context, provider *SQLProvider
if err = rows.StructScan(&device); err != nil {
_ = 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 {
@ -290,7 +290,7 @@ func schemaEncryptionCheckKeyWebauthn(ctx context.Context, provider *SQLProvider
_ = rows.Close()
return tableWebauthnDevices, result
return tableWebAuthnDevices, result
}
func schemaEncryptionCheckKeyOpenIDConnect(typeOAuth2Session OAuth2SessionType) EncryptionCheckKeyFunc {

View File

@ -119,71 +119,71 @@ const (
)
const (
queryFmtSelectWebauthnDevices = `
queryFmtSelectWebAuthnDevices = `
SELECT id, created_at, last_used_at, rpid, username, description, kid, aaguid, attestation_type, attachment, transport, sign_count, clone_warning, discoverable, present, verified, backup_eligible, backup_state, public_key
FROM %s
LIMIT ?
OFFSET ?;`
queryFmtSelectWebauthnDevicesByUsername = `
queryFmtSelectWebAuthnDevicesByUsername = `
SELECT id, created_at, last_used_at, rpid, username, description, kid, aaguid, attestation_type, attachment, transport, sign_count, clone_warning, discoverable, present, verified, backup_eligible, backup_state, public_key
FROM %s
WHERE username = ?;`
queryFmtSelectWebauthnDevicesByRPIDByUsername = `
queryFmtSelectWebAuthnDevicesByRPIDByUsername = `
SELECT id, created_at, last_used_at, rpid, username, description, kid, aaguid, attestation_type, attachment, transport, sign_count, clone_warning, discoverable, present, verified, backup_eligible, backup_state, public_key
FROM %s
WHERE rpid = ? AND username = ?;`
queryFmtSelectWebauthnDeviceByID = `
queryFmtSelectWebAuthnDeviceByID = `
SELECT id, created_at, last_used_at, rpid, username, description, kid, aaguid, attestation_type, attachment, transport, sign_count, clone_warning, discoverable, present, verified, backup_eligible, backup_state, public_key
FROM %s
WHERE id = ?;`
queryFmtUpdateUpdateWebauthnDeviceDescriptionByUsernameAndID = `
queryFmtUpdateUpdateWebAuthnDeviceDescriptionByUsernameAndID = `
UPDATE %s
SET description = ?
WHERE username = ? AND id = ?;`
queryFmtUpdateWebauthnDeviceRecordSignIn = `
queryFmtUpdateWebAuthnDeviceRecordSignIn = `
UPDATE %s
SET
rpid = ?, last_used_at = ?, sign_count = ?, discoverable = ?, present = ?, verified = ?, backup_eligible = ?, backup_state = ?,
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
WHERE id = ?;`
queryFmtInsertWebauthnDevice = `
queryFmtInsertWebAuthnDevice = `
INSERT INTO %s (created_at, last_used_at, rpid, username, description, kid, aaguid, attestation_type, attachment, transport, sign_count, clone_warning, discoverable, present, verified, backup_eligible, backup_state, public_key)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
queryFmtDeleteWebauthnDevice = `
queryFmtDeleteWebAuthnDevice = `
DELETE FROM %s
WHERE kid = ?;`
queryFmtDeleteWebauthnDeviceByUsername = `
queryFmtDeleteWebAuthnDeviceByUsername = `
DELETE FROM %s
WHERE username = ?;`
queryFmtDeleteWebauthnDeviceByUsernameAndDescription = `
queryFmtDeleteWebAuthnDeviceByUsernameAndDescription = `
DELETE FROM %s
WHERE username = ? AND description = ?;`
queryFmtSelectWebauthnDevicesEncryptedData = `
queryFmtSelectWebAuthnDevicesEncryptedData = `
SELECT id, public_key
FROM %s;`
queryFmtUpdateWebauthnDevicesEncryptedData = `
queryFmtUpdateWebAuthnDevicesEncryptedData = `
UPDATE %s
SET public_key = ?
WHERE id = ?;`
)
const (
queryFmtInsertWebauthnUser = `
queryFmtInsertWebAuthnUser = `
INSERT INTO %s (rpid, username, userid)
VALUES (?, ?, ?);`
queryFmtSelectWebauthnUser = `
queryFmtSelectWebAuthnUser = `
SELECT id, rpid, username, userid
FROM %s
WHERE rpid = ? AND username = ?;`

View File

@ -32,7 +32,7 @@ type encOAuth2Session struct {
Session []byte `db:"session_data"`
}
type encWebauthnDevice struct {
type encWebAuthnDevice struct {
ID int `db:"id"`
PublicKey []byte `db:"public_key"`
}

1
web/.gitignore vendored
View File

@ -19,6 +19,7 @@
.env.test.local
.env.production.local
.eslintcache
.vitest-preview
npm-debug.log*
yarn-debug.log*

View File

@ -10,15 +10,8 @@
"peerDependencyRules": {
"allowedVersions": {
"@types/react": "18",
"react": "18",
"react-dom": "18"
},
"ignoreMissing": [
"@babel/core",
"@babel/plugin-syntax-flow",
"@babel/plugin-transform-react-jsx",
"prop-types"
]
"react": "18"
}
}
},
"dependencies": {
@ -55,83 +48,14 @@
"build": "vite build",
"coverage": "VITE_COVERAGE=true vite build",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"test": "jest --coverage --no-cache",
"test": "vitest run --coverage",
"test:watch": "vitest --coverage",
"test:preview": "vitest-preview",
"report": "nyc report -r clover -r json -r lcov -r text"
},
"eslintConfig": {
"extends": "react-app"
},
"jest": {
"roots": [
"<rootDir>/src"
],
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts"
],
"setupFilesAfterEnv": [
"<rootDir>/src/setupTests.js"
],
"testMatch": [
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
],
"testEnvironment": "jsdom",
"transform": {
"^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": [
"esbuild-jest",
{
"sourcemap": true
}
],
"^.+\\.(css|png|svg)$": "jest-transform-stub"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\](?!(\\.pnpm[/\\\\])?(@simplewebauthn[+/\\\\]browser)).+\\.(js|jsx|cjs|ts|tsx)$"
],
"moduleNameMapper": {
"^@root/(.*)$": [
"<rootDir>/src/$1"
],
"^@assets/(.*)$": [
"<rootDir>/src/assets/$1"
],
"^@components/(.*)$": [
"<rootDir>/src/components/$1"
],
"^@constants/(.*)$": [
"<rootDir>/src/constants/$1"
],
"^@hooks/(.*)$": [
"<rootDir>/src/hooks/$1"
],
"^@i18n/(.*)$": [
"<rootDir>/src/i18n/$1"
],
"^@layouts/(.*)$": [
"<rootDir>/src/layouts/$1"
],
"^@models/(.*)$": [
"<rootDir>/src/models/$1"
],
"^@services/(.*)$": [
"<rootDir>/src/services/$1"
],
"^@themes/(.*)$": [
"<rootDir>/src/themes/$1"
],
"^@utils/(.*)$": [
"<rootDir>/src/utils/$1"
],
"^@views/(.*)$": [
"<rootDir>/src/views/$1"
]
},
"watchPlugins": [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname"
],
"resetMocks": true
},
"browserslist": {
"production": [
">0.2%",
@ -153,16 +77,16 @@
"@limegrass/eslint-plugin-import-alias": "1.0.6",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "14.0.0",
"@types/jest": "29.5.0",
"@types/node": "18.15.11",
"@types/react": "18.0.33",
"@types/react": "18.0.34",
"@types/react-dom": "18.0.11",
"@types/testing-library__jest-dom": "5.14.5",
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.1",
"@typescript-eslint/eslint-plugin": "5.58.0",
"@typescript-eslint/parser": "5.58.0",
"@vitejs/plugin-react": "3.1.0",
"esbuild": "0.17.15",
"esbuild-jest": "0.5.0",
"@vitest/coverage-istanbul": "0.30.0",
"esbuild": "0.17.16",
"eslint": "8.38.0",
"eslint-config-prettier": "8.8.0",
"eslint-config-react-app": "7.0.1",
@ -173,11 +97,8 @@
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"happy-dom": "9.1.9",
"husky": "8.0.3",
"jest": "29.5.0",
"jest-environment-jsdom": "29.5.0",
"jest-transform-stub": "2.0.0",
"jest-watch-typeahead": "2.2.2",
"prettier": "2.8.7",
"react-test-renderer": "18.2.0",
"typescript": "5.0.4",
@ -185,6 +106,8 @@
"vite-plugin-eslint": "1.8.1",
"vite-plugin-istanbul": "4.0.1",
"vite-plugin-svgr": "2.4.0",
"vite-tsconfig-paths": "4.0.8"
"vite-tsconfig-paths": "4.1.0",
"vitest": "0.30.0",
"vitest-preview": "0.0.1"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,34 @@
import React from "react";
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import NotificationBar from "@components/NotificationBar";
import NotificationsContext from "@hooks/NotificationsContext";
import { Notification } from "@models/Notifications";
const testNotification: Notification = {
message: "Test notification",
level: "success",
timeout: 3,
};
it("renders without crashing", () => {
render(<NotificationBar onClose={() => {}} />);
});
it("displays notification message and level correctly", async () => {
render(
<NotificationsContext.Provider value={{ notification: testNotification, setNotification: () => {} }}>
<NotificationBar onClose={() => {}} />
</NotificationsContext.Provider>,
);
const alert = await screen.getByRole("alert");
const message = await screen.findByText(testNotification.message);
expect(alert).toHaveClass(
`MuiAlert-filled${testNotification.level.charAt(0).toUpperCase() + testNotification.level.substring(1)}`,
{ exact: false },
);
expect(message).toHaveTextContent(testNotification.message);
});

View File

@ -0,0 +1,61 @@
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach } from "vitest";
import PrivacyPolicyDrawer from "@components/PrivacyPolicyDrawer";
vi.mock("react-i18next", () => ({
withTranslation: () => (Component: any) => {
Component.defaultProps = { ...Component.defaultProps, t: (children: any) => children };
return Component;
},
Trans: ({ children }: any) => children,
useTranslation: () => {
return {
t: (str) => str,
i18n: {
changeLanguage: () => new Promise(() => {}),
},
};
},
}));
beforeEach(() => {
document.body.setAttribute("data-privacypolicyurl", "");
document.body.setAttribute("data-privacypolicyaccept", "false");
global.localStorage.clear();
});
it("renders privacy policy and accepts when Accept button is clicked", () => {
document.body.setAttribute("data-privacypolicyurl", "http://example.com/privacy-policy");
document.body.setAttribute("data-privacypolicyaccept", "true");
const { container } = render(<PrivacyPolicyDrawer />);
fireEvent.click(screen.getByText("Accept"));
expect(container).toBeEmptyDOMElement();
});
it("does not render when privacy policy is disabled", () => {
render(<PrivacyPolicyDrawer />);
expect(screen.queryByText("Privacy Policy")).toBeNull();
expect(screen.queryByText("You must view and accept the Privacy Policy before using")).toBeNull();
expect(screen.queryByText("Accept")).toBeNull();
});
it("does not render when acceptance is not required", () => {
document.body.setAttribute("data-privacypolicyurl", "http://example.com/privacy-policy");
render(<PrivacyPolicyDrawer />);
expect(screen.queryByText("Privacy Policy")).toBeNull();
expect(screen.queryByText("You must view and accept the Privacy Policy before using")).toBeNull();
expect(screen.queryByText("Accept")).toBeNull();
});
it("does not render when already accepted", () => {
global.localStorage.setItem("privacy-policy-accepted", "true");
const { container } = render(<PrivacyPolicyDrawer />);
expect(container).toBeEmptyDOMElement();
});

View File

@ -0,0 +1,30 @@
import React from "react";
import { render } from "@testing-library/react";
import PrivacyPolicyLink from "@components/PrivacyPolicyLink";
vi.mock("react-i18next", () => ({
withTranslation: () => (Component: any) => {
Component.defaultProps = { ...Component.defaultProps, t: (children: any) => children };
return Component;
},
Trans: ({ children }: any) => children,
useTranslation: () => {
return {
t: (str) => str,
i18n: {
changeLanguage: () => new Promise(() => {}),
},
};
},
}));
it("renders a link to the privacy policy with the correct text", () => {
document.body.setAttribute("data-privacypolicyurl", "http://example.com/privacy-policy");
const { getByRole } = render(<PrivacyPolicyLink />);
const link = getByRole("link");
expect(link).toHaveAttribute("href", "http://example.com/privacy-policy");
expect(link).toHaveTextContent("Privacy Policy");
});

View File

@ -1,9 +1,37 @@
import React from "react";
import { render } from "@testing-library/react";
import { act, render } from "@testing-library/react";
import TimerIcon from "@components/TimerIcon";
beforeEach(() => {
vi.useFakeTimers().setSystemTime(new Date(2023, 1, 1, 8));
});
afterEach(() => {
vi.useRealTimers();
});
it("renders without crashing", () => {
render(<TimerIcon width={32} height={32} period={30} />);
});
it("renders a timer icon with updating progress for a given period", async () => {
const { container } = render(<TimerIcon width={32} height={32} period={30} />);
const initialProgress =
container.firstElementChild!.firstElementChild!.nextElementSibling!.nextElementSibling!.getAttribute(
"stroke-dasharray",
);
expect(initialProgress).toBe("0 31.6");
act(() => {
vi.advanceTimersByTime(3000);
});
const updatedProgress =
container.firstElementChild!.firstElementChild!.nextElementSibling!.nextElementSibling!.getAttribute(
"stroke-dasharray",
);
expect(updatedProgress).toBe("3.16 31.6");
expect(Number(updatedProgress!.split(/\s(.+)/)[0])).toBeGreaterThan(Number(initialProgress!.split(/\s(.+)/)[0]));
});

View File

@ -2,12 +2,41 @@ import React from "react";
import { render } from "@testing-library/react";
import TypographyWithTooltip from "@components/TypographyWithTootip";
import TypographyWithTooltip, { Props } from "@components/TypographyWithTooltip";
const defaultProps: Props = {
variant: "h5",
value: "Example",
};
it("renders without crashing", () => {
render(<TypographyWithTooltip value={"Example"} variant={"h5"} />);
render(<TypographyWithTooltip {...defaultProps} />);
});
it("renders with tooltip without crashing", () => {
render(<TypographyWithTooltip value={"Example"} tooltip={"A tooltip"} variant={"h5"} />);
const props: Props = {
...defaultProps,
tooltip: "A tooltip",
};
render(<TypographyWithTooltip {...props} />);
});
it("renders the text correctly", () => {
const props: Props = {
...defaultProps,
value: "Test text",
};
const { getByText } = render(<TypographyWithTooltip {...props} />);
const element = getByText(props.value!);
expect(element).toBeInTheDocument();
});
it("renders the tooltip correctly", () => {
const props: Props = {
...defaultProps,
tooltip: "Test tooltip",
};
const { getByText } = render(<TypographyWithTooltip {...props} />);
const element = getByText(props.value!);
expect(element).toHaveAttribute("aria-label", props.tooltip);
});

View File

@ -9,7 +9,7 @@ import { useNavigate } from "react-router-dom";
import { ReactComponent as UserSvg } from "@assets/images/user.svg";
import Brand from "@components/Brand";
import PrivacyPolicyDrawer from "@components/PrivacyPolicyDrawer";
import TypographyWithTooltip from "@components/TypographyWithTootip";
import TypographyWithTooltip from "@components/TypographyWithTooltip";
import { SettingsRoute } from "@constants/Routes";
import { getLogoOverride } from "@utils/Configuration";

View File

@ -1,10 +0,0 @@
import "@testing-library/jest-dom";
document.body.setAttribute("data-basepath", "");
document.body.setAttribute("data-duoselfenrollment", "true");
document.body.setAttribute("data-rememberme", "true");
document.body.setAttribute("data-resetpassword", "true");
document.body.setAttribute("data-resetpasswordcustomurl", "");
document.body.setAttribute("data-privacypolicyurl", "");
document.body.setAttribute("data-privacypolicyaccept", "false");
document.body.setAttribute("data-theme", "light");

View File

@ -0,0 +1,46 @@
import matchers, { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";
declare global {
namespace Vi {
interface JestAssertion<T = any> extends jest.Matchers<void, T>, TestingLibraryMatchers<T, void> {}
}
}
expect.extend(matchers);
const localStorageMock = (function () {
let store = {};
return {
getItem(key) {
return store[key];
},
setItem(key, value) {
store[key] = value;
},
clear() {
store = {};
},
removeItem(key) {
delete store[key];
},
getAll() {
return store;
},
};
})();
Object.defineProperty(window, "localStorage", { value: localStorageMock });
document.body.setAttribute("data-basepath", "");
document.body.setAttribute("data-duoselfenrollment", "true");
document.body.setAttribute("data-rememberme", "true");
document.body.setAttribute("data-resetpassword", "true");
document.body.setAttribute("data-resetpasswordcustomurl", "");
document.body.setAttribute("data-privacypolicyurl", "");
document.body.setAttribute("data-privacypolicyaccept", "false");
document.body.setAttribute("data-theme", "light");

View File

@ -21,7 +21,7 @@
"dom.iterable",
"esnext"
],
"types": ["@types/jest", "vite/client", "vite-plugin-svgr/client"],
"types": ["vite/client", "vite-plugin-svgr/client", "vitest/globals"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,

View File

@ -5,18 +5,17 @@ import istanbul from "vite-plugin-istanbul";
import svgr from "vite-plugin-svgr";
import tsconfigPaths from "vite-tsconfig-paths";
// @ts-ignore
export default defineConfig(({ mode }) => {
const isCoverage = process.env.VITE_COVERAGE === "true";
const sourcemap = isCoverage ? "inline" : undefined;
const istanbulPlugin = isCoverage
? istanbul({
include: "src/*",
checkProd: false,
exclude: ["node_modules"],
extension: [".js", ".jsx", ".ts", ".tsx"],
checkProd: false,
forceBuildInstrument: true,
include: "src/*",
requireEnv: true,
})
: undefined;
@ -24,14 +23,11 @@ export default defineConfig(({ mode }) => {
return {
base: "./",
build: {
sourcemap,
outDir: "../internal/server/public_html",
emptyOutDir: true,
assetsDir: "static",
emptyOutDir: true,
outDir: "../internal/server/public_html",
rollupOptions: {
output: {
entryFileNames: `static/js/[name].[hash].js`,
chunkFileNames: `static/js/[name].[hash].js`,
assetFileNames: ({ name }) => {
if (name && name.endsWith(".css")) {
return "static/css/[name].[hash].[ext]";
@ -39,12 +35,26 @@ export default defineConfig(({ mode }) => {
return "static/media/[name].[hash].[ext]";
},
chunkFileNames: `static/js/[name].[hash].js`,
entryFileNames: `static/js/[name].[hash].js`,
},
},
sourcemap,
},
server: {
port: 3000,
open: false,
port: 3000,
},
test: {
coverage: {
provider: "istanbul",
},
environment: "happy-dom",
globals: true,
onConsoleLog(log) {
if (log.includes('No routes matched location "blank"')) return false;
},
setupFiles: ["src/setupTests.ts"],
},
plugins: [eslintPlugin({ cache: false }), istanbulPlugin, react(), svgr(), tsconfigPaths()],
};