Skip to content

Commit

Permalink
feat(WebAuthn): multiple authenticators implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Mik13 committed Sep 1, 2019
1 parent 0896449 commit 9bf709f
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ endpoint.
endpoint.
- `[logoutEndpoint = '/logout']` - the path of the logout endpoint.
- `[attestationType = 'direct']` - either direct, indirect or none
- `[maxAuthenticators = 1]` - allowed authenticators per user

**`webauthn.initialize()`**

Expand Down
7 changes: 7 additions & 0 deletions client/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ class Client {
static preformatMakeCredReq (makeCredReq) {
makeCredReq.challenge = base64url.decode(makeCredReq.challenge)
makeCredReq.user.id = base64url.decode(makeCredReq.user.id)

if (makeCredReq.excludeCredentials) {
for (const exclude of makeCredReq.excludeCredentials) {
exclude.id = base64url.decode(exclude.id)
}
}

return makeCredReq
}

Expand Down
11 changes: 11 additions & 0 deletions src/AttestationChallengeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,17 @@ class AttestationChallengeBuilder {
return this
}

setExcludeCredentials (excludeCredentials) {
if (excludeCredentials) {
this.result.excludeCredentials = excludeCredentials.map((credential) => ({
type: 'public-key',
id: credential.credID,
}));
}

return this
}

build (override = {}) {
const challenge = base64url(crypto.randomBytes(32))
const { rp, user, attestation, pubKeyCredParams } = this.result
Expand Down
40 changes: 33 additions & 7 deletions src/Webauthn.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Webauthn {
challengeEndpoint: '/response',
logoutEndpoint: '/logout',
attestationType: Dictionaries.AttestationConveyancePreference.DIRECT,
maxAuthenticators: 1,
}, options)

// Map object for field names from req param to db name.
Expand Down Expand Up @@ -99,10 +100,16 @@ class Webauthn {
})

const existing = await this.store.get(username)
if (existing && existing.authenticator) {
let existingAuthenticators;

if (existing) {
existingAuthenticators = existing.authenticator ? [existing.authenticator] : existing.authenticators
}

if (existingAuthenticators && existingAuthenticators.length >= this.config.maxAuthenticators) {
return res.status(403).json({
'status': 'failed',
'message': `${usernameField} ${username} already exists`,
'message': `${usernameField} ${username} already has ${this.config.maxAuthenticators} device(s) registered`,
})
}

Expand All @@ -116,6 +123,7 @@ class Webauthn {
// .setAuthenticator() // Forces TPM
.setAttestationType(attestationType)
.setRelyingPartyInfo({ name: this.config.rpName || options.rpName })
.setExcludeCredentials(existingAuthenticators)
.build({ status: 'ok' })

req.session.challenge = attestation.challenge
Expand Down Expand Up @@ -154,8 +162,12 @@ class Webauthn {
})
}

if (user.authenticator) {
user.authenticators = [user.authenticator];
}

const assertion = new AssertionChallengeBuilder(this)
.addAllowedCredential({ id: user.authenticator.credID })
.addAllowedCredential(user.authenticators.map((authenticator) => ({ id: authenticator.credID })))
.build({ status: 'ok' })

req.session.challenge = assertion.challenge
Expand Down Expand Up @@ -247,18 +259,32 @@ class Webauthn {
)

if (result.verified) {
user.authenticator = result.authrInfo
if (!user.authenticators) {
user.authenticators = []

if (user.authenticator) {
user.authenticators.push(user.authenticator)

delete user.authenticator
}
}

user.authenticators.push(result.authrInfo)
await this.store.put(username, user)
}

} else if (response.authenticatorData !== undefined) {
result = Webauthn.verifyAuthenticatorAssertionResponse(response, user.authenticator)
const authenticators = user.authenticator ? [user.authenticator] : user.authenticators
const results = authenticators.map((authenticator) => ( {authenticator, result: Webauthn.verifyAuthenticatorAssertionResponse(response, authenticator) }))
const verified = results.find(({result}) => result.verified)

result = (verified || results[0]).result

if (result.verified) {
if (result.counter <= user.authenticator.counter)
if (result.counter <= verified.authenticator.counter)
throw new Error('Authr counter did not increase!')

user.authenticator.counter = result.counter
verified.authenticator.counter = result.counter
await this.store.put(username, user)
}

Expand Down

0 comments on commit 9bf709f

Please sign in to comment.