Skip to content

Commit

Permalink
Merge pull request #44 from emlun/transports
Browse files Browse the repository at this point in the history
Pass through AuthenticatorAttestationResponse.getTransports()
  • Loading branch information
lgarron authored Jan 11, 2022
2 parents 66322fc + 0c3523b commit 1a10638
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 30 deletions.
9 changes: 2 additions & 7 deletions src/basic/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
CredentialRequestOptionsJSON,
PublicKeyCredentialWithAssertionJSON,
PublicKeyCredentialWithAttestationJSON,
PublicKeyCredentialWithClientExtensionResults,
} from "./json";
import {
credentialCreationOptions,
Expand All @@ -23,12 +22,10 @@ export function createRequestFromJSON(
export function createResponseToJSON(
credential: PublicKeyCredential,
): PublicKeyCredentialWithAttestationJSON {
const credentialWithClientExtensionResults = credential as PublicKeyCredentialWithClientExtensionResults;
credentialWithClientExtensionResults.clientExtensionResults = credential.getClientExtensionResults();
return convert(
bufferToBase64url,
publicKeyCredentialWithAttestation,
credentialWithClientExtensionResults,
credential,
);
}

Expand All @@ -50,12 +47,10 @@ export function getRequestFromJSON(
export function getResponseToJSON(
credential: PublicKeyCredential,
): PublicKeyCredentialWithAssertionJSON {
const credentialWithClientExtensionResults = credential as PublicKeyCredentialWithClientExtensionResults;
credentialWithClientExtensionResults.clientExtensionResults = credential.getClientExtensionResults();
return convert(
bufferToBase64url,
publicKeyCredentialWithAssertion,
credentialWithClientExtensionResults,
credential,
);
}

Expand Down
1 change: 1 addition & 0 deletions src/basic/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface CredentialCreationOptionsJSON {
export interface AuthenticatorAttestationResponseJSON {
clientDataJSON: Base64urlString;
attestationObject: Base64urlString;
transports: string[];
}

export interface PublicKeyCredentialWithAttestationJSON {
Expand Down
15 changes: 13 additions & 2 deletions src/basic/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Schema } from "../schema-format";
import {
convertValue as convert,
copyValue as copy,
derived,
optional,
required,
} from "../convert";
Expand Down Expand Up @@ -58,8 +59,15 @@ export const publicKeyCredentialWithAttestation: Schema = {
response: required({
clientDataJSON: required(convert),
attestationObject: required(convert),
transports: derived(
copy,
(response: any) => response.getTransports?.() || [],
),
}),
clientExtensionResults: required(simplifiedClientExtensionResultsSchema),
clientExtensionResults: derived(
simplifiedClientExtensionResultsSchema,
(pkc: PublicKeyCredential) => pkc.getClientExtensionResults(),
),
};

// `navigator.get()` request
Expand Down Expand Up @@ -89,7 +97,10 @@ export const publicKeyCredentialWithAssertion: Schema = {
signature: required(convert),
userHandle: required(convert),
}),
clientExtensionResults: required(simplifiedClientExtensionResultsSchema),
clientExtensionResults: derived(
simplifiedClientExtensionResultsSchema,
(pkc: PublicKeyCredential) => pkc.getClientExtensionResults(),
),
};

export const schema: { [s: string]: Schema } = {
Expand Down
24 changes: 21 additions & 3 deletions src/convert.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// We export these values in order so that they can be used to deduplicate
// schema definitions in minified JS code.

import { Schema } from "./schema-format";
import { Schema, SchemaProperty } from "./schema-format";

// TODO: Parcel isn't deduplicating these values.
export const copyValue = "copy";
Expand All @@ -24,6 +24,13 @@ export function convert<From, To>(
if (schema instanceof Object) {
const output: any = {};
for (const [key, schemaField] of Object.entries(schema)) {
if (schemaField.deriveFn) {
const v = schemaField.deriveFn(input);
if (v !== undefined) {
input[key] = v;
}
}

if (!(key in input)) {
if (schemaField.required) {
throw new Error(`Missing key: ${key}`);
Expand All @@ -47,14 +54,25 @@ export function convert<From, To>(
}
}

export function required(schema: Schema): any {
export function derived(
schema: Schema,
deriveFn: (v: any) => any,
): SchemaProperty {
return {
required: true,
schema,
deriveFn,
};
}

export function required(schema: Schema): SchemaProperty {
return {
required: true,
schema,
};
}

export function optional(schema: Schema): any {
export function optional(schema: Schema): SchemaProperty {
return {
required: false,
schema,
Expand Down
13 changes: 3 additions & 10 deletions src/extended/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import {
Base64urlString,
} from "../base64url";
import { convert } from "../convert";
import {
PublicKeyCredentialWithClientExtensionResults,
AuthenticatorAttestationResponseJSON,
} from "../basic/json";
import { AuthenticatorAttestationResponseJSON } from "../basic/json";
import {
CredentialCreationOptionsExtendedJSON,
CredentialRequestOptionsExtendedJSON,
Expand Down Expand Up @@ -38,12 +35,10 @@ export function createExtendedRequestFromJSON(
export function createExtendedResponseToJSON(
credential: PublicKeyCredential,
): PublicKeyCredentialWithAttestationExtendedResultsJSON {
const credentialWithClientExtensionResults = credential as PublicKeyCredentialWithClientExtensionResults;
credentialWithClientExtensionResults.clientExtensionResults = credential.getClientExtensionResults();
return convert(
bufferToBase64url,
publicKeyCredentialWithAttestationExtended,
credentialWithClientExtensionResults,
credential,
);
}

Expand Down Expand Up @@ -119,12 +114,10 @@ export function getExtendedRequestFromJSON(
export function getExtendedResponseToJSON(
credential: PublicKeyCredential,
): PublicKeyCredentialWithAssertionExtendedResultsJSON {
const credentialWithClientExtensionResults = credential as PublicKeyCredentialWithClientExtensionResults;
credentialWithClientExtensionResults.clientExtensionResults = credential.getClientExtensionResults();
return convert(
bufferToBase64url,
publicKeyCredentialWithAssertionExtended,
credentialWithClientExtensionResults,
credential,
);
}

Expand Down
9 changes: 6 additions & 3 deletions src/extended/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
publicKeyCredentialWithAssertion,
publicKeyCredentialWithAttestation,
} from "../basic/schema";
import { convertValue, copyValue, optional, required } from "../convert";
import { convertValue, copyValue, derived, optional } from "../convert";
import { Schema } from "../schema-format";

// shared
Expand Down Expand Up @@ -45,9 +45,11 @@ export const credentialCreationOptionsExtended: Schema = JSON.parse(
export const publicKeyCredentialWithAttestationExtended: Schema = JSON.parse(
JSON.stringify(publicKeyCredentialWithAttestation),
);
(publicKeyCredentialWithAttestationExtended as any).clientExtensionResults = required(
(publicKeyCredentialWithAttestationExtended as any).clientExtensionResults = derived(
authenticationExtensionsClientOutputsSchema,
(publicKeyCredentialWithAttestation as any).clientExtensionResults.deriveFn,
);
(publicKeyCredentialWithAttestationExtended as any).response.schema.transports = (publicKeyCredentialWithAttestation as any).response.schema.transports;
// get

export const credentialRequestOptionsExtended: Schema = JSON.parse(
Expand All @@ -60,6 +62,7 @@ export const credentialRequestOptionsExtended: Schema = JSON.parse(
export const publicKeyCredentialWithAssertionExtended: Schema = JSON.parse(
JSON.stringify(publicKeyCredentialWithAssertion),
);
(publicKeyCredentialWithAssertionExtended as any).clientExtensionResults = required(
(publicKeyCredentialWithAssertionExtended as any).clientExtensionResults = derived(
authenticationExtensionsClientOutputsSchema,
(publicKeyCredentialWithAssertion as any).clientExtensionResults.deriveFn,
);
7 changes: 6 additions & 1 deletion src/schema-format.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
type SchemaLeaf = "copy" | "convert";
export interface SchemaProperty {
required: boolean;
schema: Schema;
deriveFn?(v: any): any;
}
interface SchemaObject {
[property: string]: { required: boolean; schema: Schema };
[property: string]: SchemaProperty;
}
type SchemaArray = [SchemaObject] | [SchemaLeaf];

Expand Down
83 changes: 83 additions & 0 deletions test/extended.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { base64urlToBuffer } from "../src/base64url";
import { CredentialCreationOptionsExtendedJSON } from "../src/extended/json";
import { PublicKeyCredentialWithClientExtensionResults } from "../src/basic/json";
import { credentialCreationOptionsExtended } from "../src/extended/schema";
import {
createExtendedResponseToJSON,
getExtendedResponseToJSON,
} from "../src/extended/api";
import { convert } from "../src/convert";
import "./arraybuffer";

Expand Down Expand Up @@ -75,4 +80,82 @@ describe("extended schema", () => {
]),
);
});

test("converts PublicKeyCredentialWithClientExtensionResults with attestation", () => {
const pkcwa: PublicKeyCredentialWithClientExtensionResults = {
type: "public-key",
id:
"URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENT",
rawId: new Uint8Array([1, 2, 3, 4]),
response: {
clientDataJSON: new Uint8Array([9, 10, 11, 12]),
attestationObject: new Uint8Array([13, 14, 15, 16]),
getTransports: () => ["usb"],
} as AuthenticatorAttestationResponse,
getClientExtensionResults: () =>
({
appidExclude: true,
largeBlob: {
supported: true,
},
} as AuthenticationExtensionsClientOutputs),
};
const converted = createExtendedResponseToJSON(pkcwa);
expect(converted).toEqual({
type: "public-key",
id:
"URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENT",
rawId: "AQIDBA",
response: {
attestationObject: "DQ4PEA",
clientDataJSON: "CQoLDA",
transports: ["usb"],
},
clientExtensionResults: {
appidExclude: true,
largeBlob: {
supported: true,
},
},
});
});

test("converts PublicKeyCredentialWithClientExtensionResults with assertion", () => {
const pkcwa: PublicKeyCredentialWithClientExtensionResults = {
type: "public-key",
id:
"URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENT",
rawId: new Uint8Array([1, 2, 3, 4]),
response: {
authenticatorData: new Uint8Array([5, 6, 7, 8]),
clientDataJSON: new Uint8Array([9, 10, 11, 12]),
signature: new Uint8Array([13, 14, 15, 16]),
userHandle: null,
} as AuthenticatorAssertionResponse,
getClientExtensionResults: () =>
({
largeBlob: {
written: true,
},
} as AuthenticationExtensionsClientOutputs),
};
const converted = getExtendedResponseToJSON(pkcwa);
expect(converted).toEqual({
type: "public-key",
id:
"URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENT",
rawId: "AQIDBA",
response: {
authenticatorData: "BQYHCA",
clientDataJSON: "CQoLDA",
signature: "DQ4PEA",
userHandle: null,
},
clientExtensionResults: {
largeBlob: {
written: true,
},
},
});
});
});
51 changes: 47 additions & 4 deletions test/webauthn-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,57 @@ describe("webauthn schema", () => {
response: {
clientDataJSON: new Uint8Array([9, 10, 11, 12]),
attestationObject: new Uint8Array([13, 14, 15, 16]),
getTransports: () => ["usb"],
} as AuthenticatorAttestationResponse,
getClientExtensionResults: () => ({}),
getClientExtensionResults: () =>
({
appidExclude: true,
credProps: {
rk: true,
},
} as AuthenticationExtensionsClientOutputs),
};
const converted = convert(
bufferToBase64url,
publicKeyCredentialWithAttestation,
pkcwa,
);
expect(converted).toEqual({
type: "public-key",
id:
"URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENT",
rawId: "AQIDBA",
response: {
attestationObject: "DQ4PEA",
clientDataJSON: "CQoLDA",
transports: ["usb"],
},
clientExtensionResults: {
appidExclude: true,
credProps: {
rk: true,
},
},
});
});

test("converts PublicKeyCredentialWithAttestationJSON in browsers without getTransports()", () => {
const pkcwa: PublicKeyCredentialWithClientExtensionResults = {
type: "public-key",
id:
"URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENTIAL_ID-URL_SAFE_BASE_64_CREDENT",
rawId: new Uint8Array([1, 2, 3, 4]),
response: {
clientDataJSON: new Uint8Array([9, 10, 11, 12]),
attestationObject: new Uint8Array([13, 14, 15, 16]),
} as AuthenticatorAttestationResponse,
getClientExtensionResults: () =>
({
appidExclude: true,
credProps: {
rk: true,
},
} as AuthenticationExtensionsClientOutputs),
};
const converted = convert(
bufferToBase64url,
Expand All @@ -115,6 +158,7 @@ describe("webauthn schema", () => {
response: {
attestationObject: "DQ4PEA",
clientDataJSON: "CQoLDA",
transports: [],
},
clientExtensionResults: {
appidExclude: true,
Expand Down Expand Up @@ -232,10 +276,9 @@ describe("webauthn schema", () => {
signature: new Uint8Array([13, 14, 15, 16]),
userHandle: null,
} as AuthenticatorAssertionResponse,
getClientExtensionResults: () => ({}),
clientExtensionResults: {
getClientExtensionResults: () => ({
appid: true,
},
}),
};
const converted = convert(
bufferToBase64url,
Expand Down

0 comments on commit 1a10638

Please sign in to comment.