Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions app/javascript/app/README.md

This file was deleted.

4 changes: 2 additions & 2 deletions app/javascript/packages/webauthn/enroll-webauthn-device.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { arrayBufferToBase64 } from './converters';

interface EnnrollOptions {
interface EnrollOptions {
user: PublicKeyCredentialUserEntity;

challenge: BufferSource;
Expand All @@ -25,7 +25,7 @@ async function enrollWebauthnDevice({
challenge,
excludeCredentials,
authenticatorAttachment,
}: EnnrollOptions): Promise<EnrollResult> {
}: EnrollOptions): Promise<EnrollResult> {
const credential = (await navigator.credentials.create({
publicKey: {
challenge,
Expand Down
1 change: 1 addition & 0 deletions app/javascript/packages/webauthn/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as isWebauthnSupported } from './is-webauthn-supported';
export { default as enrollWebauthnDevice } from './enroll-webauthn-device';
export { default as extractCredentials } from './extract-credentials';
export { default as verifyWebauthnDevice } from './verify-webauthn-device';
export * from './converters';
90 changes: 90 additions & 0 deletions app/javascript/packages/webauthn/verify-webauthn-device.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { TextEncoder } from 'util';
import { useSandbox, useDefineProperty } from '@18f/identity-test-helpers';
import verifyWebauthnDevice from './verify-webauthn-device';

describe('verifyWebauthnDevice', () => {
const sandbox = useSandbox();
const defineProperty = useDefineProperty();

const userChallenge = '[1, 2, 3, 4, 5, 6, 7, 8]';
const credentialIds = [btoa('credential123'), btoa('credential456')].join(',');

context('webauthn api resolves credential', () => {
beforeEach(() => {
defineProperty(navigator, 'credentials', {
configurable: true,
value: {
get: sandbox.stub().resolves({
rawId: Buffer.from('123', 'utf-8'),
response: {
authenticatorData: Buffer.from('auth', 'utf-8'),
clientDataJSON: Buffer.from('json', 'utf-8'),
signature: Buffer.from('sig', 'utf-8'),
},
}),
},
});
});

it('resolves to credential', async () => {
const expectedGetOptions = {
publicKey: {
challenge: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
rpId: 'example.test',
allowCredentials: [
{
id: new TextEncoder().encode('credential123').buffer,
type: 'public-key',
},
{
id: new TextEncoder().encode('credential456').buffer,
type: 'public-key',
},
],
timeout: 800000,
},
};

const result = await verifyWebauthnDevice({
userChallenge,
credentialIds,
});

expect(navigator.credentials.get).to.have.been.calledWith(expectedGetOptions);
expect(result).to.deep.equal({
credentialId: btoa('123'),
authenticatorData: btoa('auth'),
clientDataJSON: btoa('json'),
signature: btoa('sig'),
});
});
});

context('webauthn rejects with an error', () => {
const authError = new Error();

beforeEach(() => {
defineProperty(navigator, 'credentials', {
configurable: true,
value: {
get: sandbox.stub().rejects(authError),
},
});
});

it('forwards errors', async () => {
let didCatch;
try {
await verifyWebauthnDevice({
userChallenge,
credentialIds,
});
} catch (error) {
expect(error).to.equal(error);
didCatch = true;
}

expect(didCatch).to.be.true();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { extractCredentials, arrayBufferToBase64 } from '@18f/identity-webauthn';
import { arrayBufferToBase64 } from './converters';
import extractCredentials from './extract-credentials';

interface VerifyOptions {
userChallenge: string;

credentialIds: string;
}

interface VerifyResult {
credentialId: string;
Expand All @@ -13,29 +20,24 @@ interface VerifyResult {
async function verifyWebauthnDevice({
userChallenge,
credentialIds,
}: {
userChallenge: string;
credentialIds: string;
}): Promise<VerifyResult> {
const getOptions = {
}: VerifyOptions): Promise<VerifyResult> {
const credential = (await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array(JSON.parse(userChallenge)),
rpId: window.location.hostname,
allowCredentials: extractCredentials(credentialIds.split(',').filter(Boolean)),
timeout: 800000,
},
};

const newCred = (await navigator.credentials.get(getOptions)) as PublicKeyCredential;
})) as PublicKeyCredential;

const response = newCred.response as AuthenticatorAssertionResponse;
const response = credential.response as AuthenticatorAssertionResponse;

return {
credentialId: arrayBufferToBase64(newCred.rawId),
credentialId: arrayBufferToBase64(credential.rawId),
authenticatorData: arrayBufferToBase64(response.authenticatorData),
clientDataJSON: arrayBufferToBase64(response.clientDataJSON),
signature: arrayBufferToBase64(response.signature),
};
}

export { verifyWebauthnDevice };
export default verifyWebauthnDevice;
5 changes: 2 additions & 3 deletions app/javascript/packs/webauthn-authenticate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { isWebauthnSupported } from '@18f/identity-webauthn';
import * as WebAuthn from '../app/webauthn';
import { isWebauthnSupported, verifyWebauthnDevice } from '@18f/identity-webauthn';

function webauthn() {
const webauthnInProgressContainer = document.getElementById('webauthn-auth-in-progress')!;
Expand All @@ -23,7 +22,7 @@ function webauthn() {
window.location.href = href;
} else {
// if platform auth is not supported on device, we should take user to the error screen if theres no additional methods.
WebAuthn.verifyWebauthnDevice({
verifyWebauthnDevice({
userChallenge: (document.getElementById('user_challenge') as HTMLInputElement).value,
credentialIds: (document.getElementById('credential_ids') as HTMLInputElement).value,
})
Expand Down
1 change: 0 additions & 1 deletion docs/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,5 @@ The application should support:
You can find additional frontend documentation in relevant places throughout the code:

- [`app/components/README.md`](../app/components/README.md)
- [`app/javascript/app/README.md`](../app/javascript/app/README.md)
- [`app/javascript/packages/README.md`](../app/javascript/packages/README.md)
- [`app/javascript/packs/README.md`](../app/javascript/packs/README.md)
93 changes: 0 additions & 93 deletions spec/javascript/app/webauthn_spec.js

This file was deleted.

1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
},
"include": [
"app/components",
"app/javascript/app",
"app/javascript/packages",
"app/javascript/packs",
"spec/javascript/spec_helper.d.ts",
Expand Down