summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2020-12-08 10:11:33 -0800
committerGitHub <noreply@github.com>2020-12-08 10:11:33 -0800
commit58369389fa1de782d7e3764a9844124c9fcf12eb (patch)
tree6b0cddef05f798366b7154caa8d24a00e3a8c230
parent4f1a0ab1225a34788d3c1f9bf4a9c0b7cc31b17b (diff)
parentf5a8b982440774710ee8e2ac87d7b782ba98f4f6 (diff)
Merge pull request #79 from Moumouls/moumouls/allowCredentials
Optional Allow Credential
-rw-r--r--README.md1
-rw-r--r--package.json4
-rw-r--r--packages/browser/src/methods/startAssertion.test.ts46
-rw-r--r--packages/browser/src/methods/startAssertion.ts9
-rw-r--r--packages/server/src/assertion/generateAssertionOptions.test.ts19
-rw-r--r--packages/server/src/assertion/generateAssertionOptions.ts6
-rw-r--r--packages/typescript-types/src/index.ts2
7 files changed, 81 insertions, 6 deletions
diff --git a/README.md b/README.md
index b476f8d..6a15e79 100644
--- a/README.md
+++ b/README.md
@@ -49,3 +49,4 @@ Running Jest in watch mode for a specific project requires the use of `lerna exe
```sh
$> npx lerna exec npm run test:watch --scope=@simplewebauthn/server
```
+
diff --git a/package.json b/package.json
index c3618f1..cb57499 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,9 @@
"test": "lerna run test",
"build:types": "lerna bootstrap --scope=@simplewebauthn/typescript-types",
"build:browser": "lerna bootstrap --scope=@simplewebauthn/browser",
- "build:server": "lerna bootstrap --scope=@simplewebauthn/server"
+ "build:server": "lerna bootstrap --scope=@simplewebauthn/server",
+ "dev:server": "lerna exec npm run test:watch --scope=@simplewebauthn/server",
+ "dev:browser": "lerna exec npm run test:watch --scope=@simplewebauthn/browser"
},
"devDependencies": {
"@simplewebauthn/typescript-types": "^0.10.0",
diff --git a/packages/browser/src/methods/startAssertion.test.ts b/packages/browser/src/methods/startAssertion.test.ts
index 996f66a..c74b88f 100644
--- a/packages/browser/src/methods/startAssertion.test.ts
+++ b/packages/browser/src/methods/startAssertion.test.ts
@@ -72,6 +72,52 @@ test('should convert options before passing to navigator.credentials.get(...)',
done();
});
+test('should support optional allowCredential', async () => {
+ mockSupportsWebauthn.mockReturnValue(true);
+
+ // Stub out a response so the method won't throw
+ mockNavigatorGet.mockImplementation(
+ (): Promise<any> => {
+ return new Promise(resolve => {
+ resolve({
+ response: {},
+ getClientExtensionResults: () => ({}),
+ });
+ });
+ },
+ );
+
+ await startAssertion({
+ challenge: bufferToBase64URLString(toUint8Array('fizz')),
+ timeout: 1,
+ });
+
+ expect(mockNavigatorGet.mock.calls[0][0].allowCredentials).toEqual(undefined);
+});
+
+test('should convert allow allowCredential to undefined when empty', async () => {
+ mockSupportsWebauthn.mockReturnValue(true);
+
+ // Stub out a response so the method won't throw
+ mockNavigatorGet.mockImplementation(
+ (): Promise<any> => {
+ return new Promise(resolve => {
+ resolve({
+ response: {},
+ getClientExtensionResults: () => ({}),
+ });
+ });
+ },
+ );
+
+ await startAssertion({
+ challenge: bufferToBase64URLString(toUint8Array('fizz')),
+ timeout: 1,
+ allowCredentials: [],
+ });
+ expect(mockNavigatorGet.mock.calls[0][0].allowCredentials).toEqual(undefined);
+});
+
test('should return base64url-encoded response values', async done => {
mockSupportsWebauthn.mockReturnValue(true);
diff --git a/packages/browser/src/methods/startAssertion.ts b/packages/browser/src/methods/startAssertion.ts
index 09e416f..e02e577 100644
--- a/packages/browser/src/methods/startAssertion.ts
+++ b/packages/browser/src/methods/startAssertion.ts
@@ -21,11 +21,18 @@ export default async function startAssertion(
throw new Error('WebAuthn is not supported in this browser');
}
+ // We need to avoid passing empty array to avoid blocking retrieval
+ // of public key
+ let allowCredentials;
+ if (requestOptionsJSON.allowCredentials?.length !== 0) {
+ allowCredentials = requestOptionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
+ }
+
// We need to convert some values to Uint8Arrays before passing the credentials to the navigator
const publicKey: PublicKeyCredentialRequestOptions = {
...requestOptionsJSON,
challenge: base64URLStringToBuffer(requestOptionsJSON.challenge),
- allowCredentials: requestOptionsJSON.allowCredentials.map(toPublicKeyCredentialDescriptor),
+ allowCredentials,
};
// Wait for the user to complete assertion
diff --git a/packages/server/src/assertion/generateAssertionOptions.test.ts b/packages/server/src/assertion/generateAssertionOptions.test.ts
index 70a72db..24253bf 100644
--- a/packages/server/src/assertion/generateAssertionOptions.test.ts
+++ b/packages/server/src/assertion/generateAssertionOptions.test.ts
@@ -65,6 +65,25 @@ test('should not set userVerification if not specified', () => {
expect(options.userVerification).toEqual(undefined);
});
+test('should not set allowCredentials if not specified', () => {
+ const options = generateAssertionOptions({ rpID: 'test' });
+
+ expect(options.allowCredentials).toEqual(undefined);
+});
+
+test('should generate without params', () => {
+ const options = generateAssertionOptions();
+ const { challenge, ...otherFields } = options;
+ expect(otherFields).toEqual({
+ allowCredentials: undefined,
+ extensions: undefined,
+ rpId: undefined,
+ timeout: 60000,
+ userVerification: undefined,
+ });
+ expect(typeof challenge).toEqual('string');
+});
+
test('should set userVerification if specified', () => {
const options = generateAssertionOptions({
challenge: 'totallyrandomvalue',
diff --git a/packages/server/src/assertion/generateAssertionOptions.ts b/packages/server/src/assertion/generateAssertionOptions.ts
index d05dc00..0353666 100644
--- a/packages/server/src/assertion/generateAssertionOptions.ts
+++ b/packages/server/src/assertion/generateAssertionOptions.ts
@@ -9,7 +9,7 @@ import base64url from 'base64url';
import generateChallenge from '../helpers/generateChallenge';
type Options = {
- allowCredentials: PublicKeyCredentialDescriptorJSON[];
+ allowCredentials?: PublicKeyCredentialDescriptorJSON[];
challenge?: string | Buffer;
timeout?: number;
userVerification?: UserVerificationRequirement;
@@ -20,7 +20,7 @@ type Options = {
/**
* Prepare a value to pass into navigator.credentials.get(...) for authenticator "login"
*
- * @param allowCredentials Authenticators previously registered by the user
+ * @param allowCredentials Authenticators previously registered by the user, if any. If undefined the client will ask the user which credential they want to use
* @param challenge Random value the authenticator needs to sign and pass back
* user for assertion
* @param timeout How long (in ms) the user can take to complete assertion
@@ -30,7 +30,7 @@ type Options = {
* @param rpID Valid domain name (after `https://`)
*/
export default function generateAssertionOptions(
- options: Options,
+ options: Options = {},
): PublicKeyCredentialRequestOptionsJSON {
const {
allowCredentials,
diff --git a/packages/typescript-types/src/index.ts b/packages/typescript-types/src/index.ts
index 954615b..436f7ff 100644
--- a/packages/typescript-types/src/index.ts
+++ b/packages/typescript-types/src/index.ts
@@ -35,7 +35,7 @@ export interface PublicKeyCredentialCreationOptionsJSON
export interface PublicKeyCredentialRequestOptionsJSON
extends Omit<PublicKeyCredentialRequestOptions, 'challenge' | 'allowCredentials'> {
challenge: Base64URLString;
- allowCredentials: PublicKeyCredentialDescriptorJSON[];
+ allowCredentials?: PublicKeyCredentialDescriptorJSON[];
}
export interface PublicKeyCredentialDescriptorJSON