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.tsxfeat-otp-verification
commit
7fdcc351d4
|
@ -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] [Webauthn] with devices like a [YubiKey].
|
||||
[FIDO2] [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/
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
package cmd
|
||||
|
||||
const (
|
||||
versionSwaggerUI = "4.18.1"
|
||||
versionSwaggerUI = "4.18.2"
|
||||
)
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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: []
|
||||
---
|
||||
|
|
|
@ -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: []
|
||||
---
|
||||
|
|
|
@ -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: []
|
||||
---
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
2197
docs/pnpm-lock.yaml
2197
docs/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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'",
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = ?;`
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
.env.test.local
|
||||
.env.production.local
|
||||
.eslintcache
|
||||
.vitest-preview
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
|
|
107
web/package.json
107
web/package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
6107
web/pnpm-lock.yaml
6107
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -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");
|
||||
});
|
|
@ -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]));
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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");
|
|
@ -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");
|
|
@ -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,
|
||||
|
|
|
@ -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()],
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue