From f5540a4c124fd616146cc213916be9f9611042ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Tue, 25 Jan 2022 18:00:08 +0000 Subject: [PATCH] fix: add domain passthrough to webauthn credential creation functions --- .../modules/authentication/FIDO2FAStrategy.js | 23 +++++++---------- .../FIDOPasswordlessStrategy.js | 17 +++++-------- .../FIDOUsernamelessStrategy.js | 25 ++++++++----------- .../graphql/resolvers/users.resolver.js | 24 +++++++++--------- lib/new-admin/graphql/types/users.type.js | 24 +++++++++--------- lib/new-admin/middlewares/session.js | 1 - .../pages/Authentication/InputFIDOState.js | 18 ++++++++----- .../src/pages/Authentication/LoginState.js | 18 ++++++++----- .../pages/UserManagement/UserManagement.js | 14 +++++++---- 9 files changed, 82 insertions(+), 82 deletions(-) diff --git a/lib/new-admin/graphql/modules/authentication/FIDO2FAStrategy.js b/lib/new-admin/graphql/modules/authentication/FIDO2FAStrategy.js index 8b399e44..b8ab9d74 100644 --- a/lib/new-admin/graphql/modules/authentication/FIDO2FAStrategy.js +++ b/lib/new-admin/graphql/modules/authentication/FIDO2FAStrategy.js @@ -4,25 +4,20 @@ const _ = require('lodash/fp') const userManagement = require('../userManagement') const credentials = require('../../../../hardware-credentials') -const options = require('../../../../options') const T = require('../../../../time') const users = require('../../../../users') -const domain = options.hostname const devMode = require('minimist')(process.argv.slice(2)).dev const REMEMBER_ME_AGE = 90 * T.day -const rpID = domain -const expectedOrigin = `https://${rpID}${devMode ? `:3001` : ``}` - const generateAttestationOptions = (session, options) => { return users.getUserById(options.userId).then(user => { return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user]) }).then(([userDevices, user]) => { - const options = simpleWebauthn.generateAttestationOptions({ + const opts = simpleWebauthn.generateAttestationOptions({ rpName: 'Lamassu', - rpID, + rpID: options.domain, userName: user.username, userID: user.id, timeout: 60000, @@ -40,11 +35,11 @@ const generateAttestationOptions = (session, options) => { session.webauthn = { attestation: { - challenge: options.challenge + challenge: opts.challenge } } - return options + return opts }) } @@ -59,7 +54,7 @@ const generateAssertionOptions = (session, options) => { transports: ['usb', 'ble', 'nfc', 'internal'] })), userVerification: 'discouraged', - rpID + rpID: options.domain }) session.webauthn = { @@ -82,8 +77,8 @@ const validateAttestation = (session, options) => { simpleWebauthn.verifyAttestationResponse({ credential: options.attestationResponse, expectedChallenge: `${expectedChallenge}`, - expectedOrigin, - expectedRPID: rpID + expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`, + expectedRPID: options.domain }) ]) .then(([user, verification]) => { @@ -142,8 +137,8 @@ const validateAssertion = (session, options) => { verification = simpleWebauthn.verifyAssertionResponse({ credential: options.assertionResponse, expectedChallenge: `${expectedChallenge}`, - expectedOrigin, - expectedRPID: rpID, + expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`, + expectedRPID: options.domain, authenticator: convertedAuthenticator }) } catch (err) { diff --git a/lib/new-admin/graphql/modules/authentication/FIDOPasswordlessStrategy.js b/lib/new-admin/graphql/modules/authentication/FIDOPasswordlessStrategy.js index b31d3277..26091bde 100644 --- a/lib/new-admin/graphql/modules/authentication/FIDOPasswordlessStrategy.js +++ b/lib/new-admin/graphql/modules/authentication/FIDOPasswordlessStrategy.js @@ -3,25 +3,20 @@ const base64url = require('base64url') const _ = require('lodash/fp') const credentials = require('../../../../hardware-credentials') -const options = require('../../../../options') const T = require('../../../../time') const users = require('../../../../users') -const domain = options.hostname const devMode = require('minimist')(process.argv.slice(2)).dev const REMEMBER_ME_AGE = 90 * T.day -const rpID = domain -const expectedOrigin = `https://${rpID}${devMode ? `:3001` : ``}` - const generateAttestationOptions = (session, options) => { return users.getUserById(options.userId).then(user => { return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user]) }).then(([userDevices, user]) => { const opts = simpleWebauthn.generateAttestationOptions({ rpName: 'Lamassu', - rpID, + rpID: options.domain, userName: user.username, userID: user.id, timeout: 60000, @@ -58,7 +53,7 @@ const generateAssertionOptions = (session, options) => { transports: ['usb', 'ble', 'nfc', 'internal'] })), userVerification: 'discouraged', - rpID + rpID: options.domain }) session.webauthn = { @@ -81,8 +76,8 @@ const validateAttestation = (session, options) => { simpleWebauthn.verifyAttestationResponse({ credential: options.attestationResponse, expectedChallenge: `${expectedChallenge}`, - expectedOrigin, - expectedRPID: rpID + expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`, + expectedRPID: options.domain }) ]) .then(([user, verification]) => { @@ -141,8 +136,8 @@ const validateAssertion = (session, options) => { verification = simpleWebauthn.verifyAssertionResponse({ credential: options.assertionResponse, expectedChallenge: `${expectedChallenge}`, - expectedOrigin, - expectedRPID: rpID, + expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`, + expectedRPID: options.domain, authenticator: convertedAuthenticator }) } catch (err) { diff --git a/lib/new-admin/graphql/modules/authentication/FIDOUsernamelessStrategy.js b/lib/new-admin/graphql/modules/authentication/FIDOUsernamelessStrategy.js index 8d1ae4ec..b547d4f5 100644 --- a/lib/new-admin/graphql/modules/authentication/FIDOUsernamelessStrategy.js +++ b/lib/new-admin/graphql/modules/authentication/FIDOUsernamelessStrategy.js @@ -3,23 +3,18 @@ const base64url = require('base64url') const _ = require('lodash/fp') const credentials = require('../../../../hardware-credentials') -const options = require('../../../../options') const T = require('../../../../time') const users = require('../../../../users') -const domain = options.hostname const devMode = require('minimist')(process.argv.slice(2)).dev const REMEMBER_ME_AGE = 90 * T.day -const rpID = domain -const expectedOrigin = `https://${rpID}${devMode ? `:3001` : ``}` - const generateAttestationOptions = (session, options) => { return credentials.getHardwareCredentials().then(devices => { const opts = simpleWebauthn.generateAttestationOptions({ rpName: 'Lamassu', - rpID, + rpID: options.domain, userName: `Usernameless user created at ${new Date().toISOString()}`, userID: options.userId, timeout: 60000, @@ -46,9 +41,9 @@ const generateAttestationOptions = (session, options) => { }) } -const generateAssertionOptions = session => { +const generateAssertionOptions = (session, options) => { return credentials.getHardwareCredentials().then(devices => { - const options = simpleWebauthn.generateAssertionOptions({ + const opts = simpleWebauthn.generateAssertionOptions({ timeout: 60000, allowCredentials: devices.map(dev => ({ id: dev.data.credentialID, @@ -56,15 +51,15 @@ const generateAssertionOptions = session => { transports: ['usb', 'ble', 'nfc', 'internal'] })), userVerification: 'discouraged', - rpID + rpID: options.domain }) session.webauthn = { assertion: { - challenge: options.challenge + challenge: opts.challenge } } - return options + return opts }) } @@ -77,8 +72,8 @@ const validateAttestation = (session, options) => { simpleWebauthn.verifyAttestationResponse({ credential: options.attestationResponse, expectedChallenge: `${expectedChallenge}`, - expectedOrigin, - expectedRPID: rpID + expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`, + expectedRPID: options.domain }) ]) .then(([user, verification]) => { @@ -146,8 +141,8 @@ const validateAssertion = (session, options) => { verification = simpleWebauthn.verifyAssertionResponse({ credential: options.assertionResponse, expectedChallenge: `${expectedChallenge}`, - expectedOrigin, - expectedRPID: rpID, + expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`, + expectedRPID: options.domain, authenticator: convertedAuthenticator }) } catch (err) { diff --git a/lib/new-admin/graphql/resolvers/users.resolver.js b/lib/new-admin/graphql/resolvers/users.resolver.js index 6f5f8b3d..c62bc8e0 100644 --- a/lib/new-admin/graphql/resolvers/users.resolver.js +++ b/lib/new-admin/graphql/resolvers/users.resolver.js @@ -6,11 +6,11 @@ const sessionManager = require('../../../session-manager') const getAttestationQueryOptions = variables => { switch (authentication.CHOSEN_STRATEGY) { case 'FIDO2FA': - return { userId: variables.userID } + return { userId: variables.userID, domain: variables.domain } case 'FIDOPasswordless': - return { userId: variables.userID } + return { userId: variables.userID, domain: variables.domain } case 'FIDOUsernameless': - return { userId: variables.userID } + return { userId: variables.userID, domain: variables.domain } default: return {} } @@ -19,11 +19,11 @@ const getAttestationQueryOptions = variables => { const getAssertionQueryOptions = variables => { switch (authentication.CHOSEN_STRATEGY) { case 'FIDO2FA': - return { username: variables.username, password: variables.password } + return { username: variables.username, password: variables.password, domain: variables.domain } case 'FIDOPasswordless': - return { username: variables.username } + return { username: variables.username, domain: variables.domain } case 'FIDOUsernameless': - return {} + return { domain: variables.domain } default: return {} } @@ -32,11 +32,11 @@ const getAssertionQueryOptions = variables => { const getAttestationMutationOptions = variables => { switch (authentication.CHOSEN_STRATEGY) { case 'FIDO2FA': - return { userId: variables.userID, attestationResponse: variables.attestationResponse } + return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain } case 'FIDOPasswordless': - return { userId: variables.userID, attestationResponse: variables.attestationResponse } + return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain } case 'FIDOUsernameless': - return { userId: variables.userID, attestationResponse: variables.attestationResponse } + return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain } default: return {} } @@ -45,11 +45,11 @@ const getAttestationMutationOptions = variables => { const getAssertionMutationOptions = variables => { switch (authentication.CHOSEN_STRATEGY) { case 'FIDO2FA': - return { username: variables.username, password: variables.password, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse } + return { username: variables.username, password: variables.password, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse, domain: variables.domain } case 'FIDOPasswordless': - return { username: variables.username, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse } + return { username: variables.username, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse, domain: variables.domain } case 'FIDOUsernameless': - return { assertionResponse: variables.assertionResponse } + return { assertionResponse: variables.assertionResponse, domain: variables.domain } default: return {} } diff --git a/lib/new-admin/graphql/types/users.type.js b/lib/new-admin/graphql/types/users.type.js index a1a63394..ceb8a659 100644 --- a/lib/new-admin/graphql/types/users.type.js +++ b/lib/new-admin/graphql/types/users.type.js @@ -3,14 +3,14 @@ const authentication = require('../modules/authentication') const getFIDOStrategyQueryTypes = () => { switch (authentication.CHOSEN_STRATEGY) { case 'FIDO2FA': - return `generateAttestationOptions(userID: ID!): JSONObject - generateAssertionOptions(username: String!, password: String!): JSONObject` + return `generateAttestationOptions(userID: ID!, domain: String!): JSONObject + generateAssertionOptions(username: String!, password: String!, domain: String!): JSONObject` case 'FIDOPasswordless': - return `generateAttestationOptions(userID: ID!): JSONObject - generateAssertionOptions(username: String!): JSONObject` + return `generateAttestationOptions(userID: ID!, domain: String!): JSONObject + generateAssertionOptions(username: String!, domain: String!): JSONObject` case 'FIDOUsernameless': - return `generateAttestationOptions(userID: ID!): JSONObject - generateAssertionOptions: JSONObject` + return `generateAttestationOptions(userID: ID!, domain: String!): JSONObject + generateAssertionOptions(domain: String!): JSONObject` default: return `` } @@ -19,14 +19,14 @@ const getFIDOStrategyQueryTypes = () => { const getFIDOStrategyMutationsTypes = () => { switch (authentication.CHOSEN_STRATEGY) { case 'FIDO2FA': - return `validateAttestation(userID: ID!, attestationResponse: JSONObject!): Boolean - validateAssertion(username: String!, password: String!, rememberMe: Boolean!, assertionResponse: JSONObject!): Boolean` + return `validateAttestation(userID: ID!, attestationResponse: JSONObject!, domain: String!): Boolean + validateAssertion(username: String!, password: String!, rememberMe: Boolean!, assertionResponse: JSONObject!, domain: String!): Boolean` case 'FIDOPasswordless': - return `validateAttestation(userID: ID!, attestationResponse: JSONObject!): Boolean - validateAssertion(username: String!, rememberMe: Boolean!, assertionResponse: JSONObject!): Boolean` + return `validateAttestation(userID: ID!, attestationResponse: JSONObject!, domain: String!): Boolean + validateAssertion(username: String!, rememberMe: Boolean!, assertionResponse: JSONObject!, domain: String!): Boolean` case 'FIDOUsernameless': - return `validateAttestation(userID: ID!, attestationResponse: JSONObject!): Boolean - validateAssertion(assertionResponse: JSONObject!): Boolean` + return `validateAttestation(userID: ID!, attestationResponse: JSONObject!, domain: String!): Boolean + validateAssertion(assertionResponse: JSONObject!, domain: String!): Boolean` default: return `` } diff --git a/lib/new-admin/middlewares/session.js b/lib/new-admin/middlewares/session.js index 41fc31a0..338e4b11 100644 --- a/lib/new-admin/middlewares/session.js +++ b/lib/new-admin/middlewares/session.js @@ -21,7 +21,6 @@ router.use('*', async (req, res, next) => getOperatorId('authentication').then(( cookie: { httpOnly: true, secure: true, - domain: hostname, sameSite: true, maxAge: 60 * 10 * 1000 // 10 minutes } diff --git a/new-lamassu-admin/src/pages/Authentication/InputFIDOState.js b/new-lamassu-admin/src/pages/Authentication/InputFIDOState.js index 69ac1588..4352a492 100644 --- a/new-lamassu-admin/src/pages/Authentication/InputFIDOState.js +++ b/new-lamassu-admin/src/pages/Authentication/InputFIDOState.js @@ -42,10 +42,10 @@ const InputFIDOState = ({ state, strategy }) => { const GENERATE_ASSERTION = gql` query generateAssertionOptions($username: String!${ strategy === 'FIDO2FA' ? `, $password: String!` : `` - }) { + }, $domain: String!) { generateAssertionOptions(username: $username${ strategy === 'FIDO2FA' ? `, password: $password` : `` - }) + }, domain: $domain) } ` @@ -55,12 +55,14 @@ const InputFIDOState = ({ state, strategy }) => { ${strategy === 'FIDO2FA' ? `, $password: String!` : ``} $rememberMe: Boolean! $assertionResponse: JSONObject! + $domain: String! ) { validateAssertion( username: $username ${strategy === 'FIDO2FA' ? `password: $password` : ``} rememberMe: $rememberMe assertionResponse: $assertionResponse + domain: $domain ) } ` @@ -90,10 +92,12 @@ const InputFIDOState = ({ state, strategy }) => { strategy === 'FIDO2FA' ? { username: state.clientField, - password: state.passwordField + password: state.passwordField, + domain: window.location.hostname } : { - username: localClientField + username: localClientField, + domain: window.location.hostname }, onCompleted: ({ generateAssertionOptions: options }) => { startAssertion(options) @@ -104,12 +108,14 @@ const InputFIDOState = ({ state, strategy }) => { username: state.clientField, password: state.passwordField, rememberMe: state.rememberMeField, - assertionResponse: res + assertionResponse: res, + domain: window.location.hostname } : { username: localClientField, rememberMe: localRememberMeField, - assertionResponse: res + assertionResponse: res, + domain: window.location.hostname } validateAssertion({ variables diff --git a/new-lamassu-admin/src/pages/Authentication/LoginState.js b/new-lamassu-admin/src/pages/Authentication/LoginState.js index 4cc4444b..1716919b 100644 --- a/new-lamassu-admin/src/pages/Authentication/LoginState.js +++ b/new-lamassu-admin/src/pages/Authentication/LoginState.js @@ -24,14 +24,17 @@ const LOGIN = gql` ` const GENERATE_ASSERTION = gql` - query generateAssertionOptions { - generateAssertionOptions + query generateAssertionOptions($domain: String!) { + generateAssertionOptions(domain: $domain) } ` const VALIDATE_ASSERTION = gql` - mutation validateAssertion($assertionResponse: JSONObject!) { - validateAssertion(assertionResponse: $assertionResponse) + mutation validateAssertion( + $assertionResponse: JSONObject! + $domain: String! + ) { + validateAssertion(assertionResponse: $assertionResponse, domain: $domain) } ` @@ -117,7 +120,8 @@ const LoginState = ({ state, dispatch, strategy }) => { .then(res => { validateAssertion({ variables: { - assertionResponse: res + assertionResponse: res, + domain: window.location.hostname } }) }) @@ -212,7 +216,9 @@ const LoginState = ({ state, dispatch, strategy }) => { type="button" onClick={() => { return strategy === 'FIDOUsernameless' - ? assertionOptions() + ? assertionOptions({ + variables: { domain: window.location.hostname } + }) : dispatch({ type: 'FIDO', payload: {} diff --git a/new-lamassu-admin/src/pages/UserManagement/UserManagement.js b/new-lamassu-admin/src/pages/UserManagement/UserManagement.js index 8b069d3a..0215e3bc 100644 --- a/new-lamassu-admin/src/pages/UserManagement/UserManagement.js +++ b/new-lamassu-admin/src/pages/UserManagement/UserManagement.js @@ -41,8 +41,8 @@ const GET_USERS = gql` ` const GENERATE_ATTESTATION = gql` - query generateAttestationOptions($userID: ID!) { - generateAttestationOptions(userID: $userID) + query generateAttestationOptions($userID: ID!, $domain: String!) { + generateAttestationOptions(userID: $userID, domain: $domain) } ` @@ -50,10 +50,12 @@ const VALIDATE_ATTESTATION = gql` mutation validateAttestation( $userID: ID! $attestationResponse: JSONObject! + $domain: String! ) { validateAttestation( userID: $userID attestationResponse: $attestationResponse + domain: $domain ) } ` @@ -100,11 +102,12 @@ const Users = () => { const [generateAttestationOptions] = useLazyQuery(GENERATE_ATTESTATION, { onCompleted: ({ generateAttestationOptions: options }) => { - startAttestation(options).then(res => { + return startAttestation(options).then(res => { validateAttestation({ variables: { userID: userInfo.id, - attestationResponse: res + attestationResponse: res, + domain: window.location.hostname } }) }) @@ -194,7 +197,8 @@ const Users = () => { setUserInfo(u) generateAttestationOptions({ variables: { - userID: u.id + userID: u.id, + domain: window.location.hostname } }) }}>