Skip to content

Commit

Permalink
First stab at friendly storage interface
Browse files Browse the repository at this point in the history
This patch attempts to formalize the storage interface to make it easier
to integrate with existing databases.

See issue strangerlabs#27
  • Loading branch information
nsatragno committed Jan 30, 2020
1 parent fdeb3ef commit 0f3077c
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 61 deletions.
68 changes: 52 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ const webauthn = new WebAuthn({
// store: {
// put: async (id, value) => {/* return <void> */},
// get: async (id) => {/* return User */},
// search: async (search) => {/* return { [username]: User } */},
// delete: async (id) => {/* return boolean */},
// },
rpName: 'Stranger Labs, Inc.',
Expand Down Expand Up @@ -157,28 +156,65 @@ redirected to the supplied URL.
### Storage Adapater

Storage adapters provide an interface to the WebAuthn RP to store and retrieve
data necessary for authentication, such as authenticator public keys. Storage
adapters must implement the following interface:

**`async get (id)`**
data necessary for authentication, such as authenticator public keys. You can
use the provided LevelDB adapter or write your own to interface with your
database.

The WebAuthnUser object interface is as follows:

```ts
interface Credential {
// The credential format (for now this is the string "fido-u2f").
fmt: string,

// base64url encoded credential ID returned by the authenticator on
// registration.
credID: string,

// PKCS#8-encoded encoded public key as a base64url string.
publicKey: string,

// Signature counter (https://w3c.github.io/webauthn/#signature-counter) for
// the credential.
counter: number,

// Credential transport
// (https://w3c.github.io/webauthn/#dom-publickeycredentialdescriptor-transports).
transports: string[],
};

interface WebAuthnUser {
// base64url-encoded sequence of 32 random bytes that identifies the user
// uniquely, generated by WebAuthn. Avoid replacing this ID with something
// else, user IDs in WebAuthn must be random.
id: string,

// List of credentials associated to the user.
credentials: Credential[],
};
```

Retrieves and returns the previously stored object with the provided `id`.
Additionally, `WebAuthnUser` will have any attributes declared on
`options.userFields` set.

**`async put (id, value)`**
Storage adapters must implement the following interface:

Stores an object so that it may be retrieved with the provided `id`. Returns
nothing.
```ts
async function get (id: string): WebAuthnUser
```

**`async search (startsWith, [options])`**
Retrieves and returns the previously stored WebAuthnUser with the provided `id`.

Returns a mapping of objects where the `id` of the objects return starts with
the provided query value. Available options include:
```ts
async function put (id: string, webAuthnUser: WebAuthnUser): void
```

- `limit`: Return the first N results.
- `reverse`: Return results in reverse lexicographical order. If used in
conjunction with limit then the _last_ N results are returned.
Stores a WebAuthnUser object so that it may be retrieved with the provided `id`.
Returns nothing.

**`async delete (id)`**
```ts
async function delete (id: string): boolean
```

Delete a previously stored object. Returns a boolean indicating success.

Expand Down
4 changes: 4 additions & 0 deletions client/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ class Client {
console.log('REGISTER CREDENTIAL', credential)

const credentialResponse = Client.publicKeyCredentialToJSON(credential)
if (credential.response.getTransports)
credentialResponse.response.transports = credential.response.getTransports()
else
credentialResponse.response.transports = []
console.log('REGISTER RESPONSE', credentialResponse)

return await this.sendWebAuthnResponse(credentialResponse)
Expand Down
6 changes: 0 additions & 6 deletions example/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const webauthn = new Webauthn({
// store: {
// put: async (id, value) => {/* return <void> */},
// get: async (id) => {/* return User */},
// search: async (search) => {/* return { [username]: User } */},
// delete: async (id) => {/* return boolean */},
// },
rpName: 'Stranger Labs, Inc.',
Expand All @@ -66,11 +65,6 @@ app.get('/credentials', webauthn.authenticate(), async (req, res) => {
res.status(200).json((await webauthn.store.get(req.session.username)).credentials)
})

// Debug
app.get('/db', async (req, res) => {
res.status(200).json(await webauthn.store.search())
})

// Debug
app.get('/session', (req, res) => {
res.status(200).json(req.session)
Expand Down
6 changes: 2 additions & 4 deletions example/src/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ function Login (props) {
webauthn.register({ name, username }).then(response => {
console.log('Register response: ', response)
setSuccess('Registration successful. Try logging in.')
}).catch(error => {
setError(error.message)
})
});
}

function onLogin () {
Expand All @@ -47,7 +45,7 @@ function Login (props) {
props.onLogin({
username,
});
}).catch(error => setError(error.message))
});
}

return (
Expand Down
2 changes: 1 addition & 1 deletion src/AttestationChallengeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class AttestationChallengeBuilder {
|| !Object.values(PublicKeyCredentialType).includes(excluded.type)
|| !Array.isArray(excluded.transports)
) {
throw new Error('Invalid PublicKeyCredentialDescriptor. See https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor')
throw new Error('Invalid PublicKeyCredentialDescriptor:', excluded, '. See https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor')
}
excludeCredentials.push(excluded)
});
Expand Down
24 changes: 0 additions & 24 deletions src/LevelAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,6 @@ class LevelAdapter {
throw err
}
}

async search (startsWith = '', options = {}) {
if (typeof startsWith === 'object') {
options = startsWith
startsWith = options.startsWith || options.gte || options.search
}

const { limit = -1, reverse = false, lte } = options

return await new Promise((resolve, reject) => {
const data = {}

// Get all values that start with `startsWith`
this.db.createReadStream({
gte: startsWith,
lte: lte ? lte : `${startsWith}\uffff`,
limit,
reverse,
})
.on('data', item => data[item.key] = item.value)
.on('end', () => resolve(data))
.on('error', err => reject(err))
})
}
}

/**
Expand Down
9 changes: 0 additions & 9 deletions src/MemoryAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,6 @@ class MemoryAdapter {

return false
}

async search (id = '') {
return Object.entries(this.db)
.filter(([key]) => key.startsWith(id))
.reduce((state, [key, value]) => {
state[key] = value
return state
}, {})
}
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Webauthn.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,8 @@ class Webauthn {
fmt: 'fido-u2f',
publicKey: base64url.encode(publicKey),
counter: authrDataStruct.counter,
credID: base64url.encode(authrDataStruct.credID)
credID: base64url.encode(authrDataStruct.credID),
transports: webauthnResponse.transports,
}
if (this.config.enableLogging) console.log('RESPONSE', response)
} else if (this.config.enableLogging) {
Expand Down

0 comments on commit 0f3077c

Please sign in to comment.