From 634f2bfe2daca2b26948da36f84bf8dcb956f8d9 Mon Sep 17 00:00:00 2001 From: Hassan Ben Jobrane Date: Tue, 28 Nov 2023 11:26:20 +0100 Subject: [PATCH] feat: add AUTH_WEBAUTHN_RP_ID environment variable (#446) * feat: add AUTH_WEBAUTHN_RP_ID environment variable * test(webauthn): add test when rpId set in environment variables * chore: add changeset * chore: update AUTH_WEBAUTHN_RP_ID description * fix: typo in `AUTH_WEBAUTHN_RP_ID` description --- .changeset/soft-brooms-sit.md | 5 +++++ .env.example | 1 + docs/environment-variables.md | 1 + src/utils/env.ts | 3 +++ src/utils/webauthn.ts | 9 ++++++-- test/routes/signin/webauthn.test.ts | 34 +++++++++++++++++++++++++++++ 6 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 .changeset/soft-brooms-sit.md diff --git a/.changeset/soft-brooms-sit.md b/.changeset/soft-brooms-sit.md new file mode 100644 index 000000000..9c4879f78 --- /dev/null +++ b/.changeset/soft-brooms-sit.md @@ -0,0 +1,5 @@ +--- +'hasura-auth': minor +--- + +feat: add `AUTH_WEBAUTHN_RP_ID` environment variable diff --git a/.env.example b/.env.example index c2080e10e..60f61d4be 100644 --- a/.env.example +++ b/.env.example @@ -78,6 +78,7 @@ AUTH_PROVIDER_STRAVA_CLIENT_SECRET= # WEBAUTHN AUTH_WEBAUTHN_ENABLED= AUTH_WEBAUTHN_RP_NAME='Nhost App' +AUTH_WEBAUTHN_RP_ID='nhost.io' AUTH_WEBAUTHN_RP_ORIGINS= # LOGS diff --git a/docs/environment-variables.md b/docs/environment-variables.md index df56381f7..c40cbc7c8 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -53,6 +53,7 @@ | AUTH_JWT_CUSTOM_CLAIMS | | | | AUTH_WEBAUTHN_ENABLED | When enabled, passwordless Webauthn authentication can be done via device supported strong authenticators like fingerprint, Face ID, etc. | false | | AUTH_WEBAUTHN_RP_NAME | Relying party name. Friendly name visual to the user informing who requires the authentication. Probably your app's name. | | +| AUTH_WEBAUTHN_RP_ID | Relying party id. If not set `AUTH_CLIENT_URL` will be used as a default. | | | AUTH_WEBAUTHN_RP_ORIGINS | Array of URLs where the registration is permitted and should have occurred on. `AUTH_CLIENT_URL` will be automatically added to the list of origins if is set. | | | AUTH_WEBAUTHN_ATTESTATION_TIMEOUT | How long (in ms) the user can take to complete authentication. | `60000` (1 minute) | diff --git a/src/utils/env.ts b/src/utils/env.ts index 4fb5677a7..9f76627a9 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -107,6 +107,9 @@ export const ENV = { get AUTH_WEBAUTHN_RP_NAME() { return castStringEnv('AUTH_WEBAUTHN_RP_NAME', ''); }, + get AUTH_WEBAUTHN_RP_ID() { + return castStringEnv('AUTH_WEBAUTHN_RP_ID', ''); + }, get AUTH_WEBAUTHN_RP_ORIGINS() { const origins = castStringArrayEnv('AUTH_WEBAUTHN_RP_ORIGINS', []); const clientUrl = ENV.AUTH_CLIENT_URL; diff --git a/src/utils/webauthn.ts b/src/utils/webauthn.ts index b209cf070..7f26d263b 100644 --- a/src/utils/webauthn.ts +++ b/src/utils/webauthn.ts @@ -9,8 +9,13 @@ import { ENV } from './env'; import { gqlSdk } from './gql-sdk'; import { AuthUserSecurityKeys_Insert_Input } from './__generated__/graphql-request'; -export const getWebAuthnRelyingParty = () => - ENV.AUTH_CLIENT_URL && new URL(ENV.AUTH_CLIENT_URL).hostname; +export const getWebAuthnRelyingParty = () => { + if (ENV.AUTH_WEBAUTHN_RP_ID) { + return ENV.AUTH_WEBAUTHN_RP_ID; + } + + return ENV.AUTH_CLIENT_URL && new URL(ENV.AUTH_CLIENT_URL).hostname; +}; export const getCurrentChallenge = async (id: string) => { const { user } = await gqlSdk.getUserChallenge({ id }); diff --git a/test/routes/signin/webauthn.test.ts b/test/routes/signin/webauthn.test.ts index 7dc7fb11e..071fc11c2 100644 --- a/test/routes/signin/webauthn.test.ts +++ b/test/routes/signin/webauthn.test.ts @@ -119,6 +119,40 @@ describe('webauthn', () => { }); }); + it('should return authentication options with rpId set in the environement variables', async () => { + const email = faker.internet.email(); + const password = faker.internet.password(); + + const record = await insertDbUser(client, email, password, true, false); + expect(record.rowCount).toEqual(1); + + const userRecord = await client.query(`SELECT id FROM auth.users LIMIT 1;`); + expect(userRecord.rows).toBeArrayOfSize(1); + expect(userRecord.rows[0]).toHaveProperty('id'); + + const rpId = 'example.io'; + + await request.post('/change-env').send({ + AUTH_WEBAUTHN_RP_ID: rpId, + }); + + const { body } = await request + .post('/signin/webauthn') + .send({ email }) + .expect(StatusCodes.OK); + + // checking its persist and remove it as cannot compare + expect(body).toHaveProperty('challenge'); + delete body.challenge; + + expect(body).toEqual({ + allowCredentials: [], + rpId, + timeout: 60000, + userVerification: 'preferred', + }); + }); + it('should fail verify user is webauth is not enabled', async () => { const email = faker.internet.email();