Skip to content

Commit

Permalink
Secure payment confirmation, #15, #19
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiancook committed Jun 20, 2023
1 parent 068709d commit ebcda22
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 14 deletions.
14 changes: 7 additions & 7 deletions src/package.readonly.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// File generated by scripts/pre-build.js

export const commit = "4665f8a86648ea0ce875395a8c3c209955fc696e";
export const commitShort = "4665f8a";
export const commit = "068709d140f087482398c4e091b5316c4a56d461";
export const commitShort = "068709d";
export const commitAuthor = "Fabian Cook";
export const commitEmail = "[email protected]";
export const commitMessage = "Create payment method, #15";
export const commitAt = "2023-06-20T03:27:43.000Z";
export const secondsBetweenCommitAndBuild = 3188.88;
export const minutesBetweenCommitAndBuild = 53.15;
export const timeBetweenCommitAndBuild = "53 minutes and 8 seconds";
export const commitMessage = "Secure payment confirmation, #15, #19";
export const commitAt = "2023-06-20T04:39:17.000Z";
export const secondsBetweenCommitAndBuild = 1034.86;
export const minutesBetweenCommitAndBuild = 17.25;
export const timeBetweenCommitAndBuild = "17 minutes and 14 seconds";
// Variables to be replaced after tests
export const secondsBetweenCommitAndTestCompletion = "";
export const minutesBetweenCommitAndTestCompletion = "";
Expand Down
118 changes: 111 additions & 7 deletions src/react/client/authsignal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import {Authsignal} from "@authsignal/browser";
import {Authsignal, AuthsignalOptions} from "@authsignal/browser";
import {ok} from "./utils";
import type {Passkey} from "@authsignal/browser/dist/passkey";
import type {PasskeyApiClient} from "@authsignal/browser/dist/api";
import {startAuthentication, startRegistration} from "@simplewebauthn/browser";
import {AuthenticationExtensionsClientInputs} from "@simplewebauthn/typescript-types/dist/dom";
import {PublicKeyCredentialRequestOptionsJSON} from "@simplewebauthn/typescript-types";

interface AuthsignalMeta {
tenantId: string;
Expand All @@ -23,13 +28,13 @@ export function getAuthsignalMeta(element = document.body): AuthsignalMeta {
};
}

const clients = new WeakMap<AuthsignalMeta, Authsignal>();
const clients = new WeakMap<AuthsignalMeta, AuthsignalCustomClient>();

export function getAuthsignalClient(meta = getAuthsignalMeta()) {
export function getAuthsignalClient(meta = getAuthsignalMeta()): AuthsignalCustomClient {
const existing = clients.get(meta);
if (existing) return existing;
const { tenantId, baseUrl } = meta;
const client = new Authsignal({ tenantId, baseUrl });
const client = new AuthsignalCustomClient({ tenantId, baseUrl });
clients.set(meta, client);
return client;
}
Expand Down Expand Up @@ -71,13 +76,23 @@ export async function passkey(email: string, meta: AuthsignalMeta = getAuthsigna
const authsignal = getAuthsignalClient(meta);

if (isPasskeyEnrolled) {
return await authsignal.passkey.signIn({
return await authsignal.extendedPasskey.signIn({
token
})
} else {
return await authsignal.passkey.signUp({
return await authsignal.extendedPasskey.signUp({
userName: email,
token
token,
authenticatorSelection: {
userVerification: "required",
residentKey: "required",
authenticatorAttachment: "platform",
},
extensions: {
"payment": {
isPayment: true,
}
}
})
}

Expand Down Expand Up @@ -117,4 +132,93 @@ export async function passkey(email: string, meta: AuthsignalMeta = getAuthsigna
const found = authenticators.find(authenticator => authenticator.verificationMethod === "PASSKEY");
return found?.webauthnCredential
}
}

class AuthsignalCustomPasskey {

base: Passkey;

constructor(base: Passkey) {
this.base = base;
}

private get api(): PasskeyApiClient {
const base: unknown = this.base;
ok<{ api: PasskeyApiClient }>(base);
return base.api;
};

signIn(params?: { token: string, extensions?: Record<string, unknown> }): Promise<string | undefined>;
signIn(params?: { autofill: boolean, extensions?: Record<string, unknown> }): Promise<string | undefined>;
async signIn(params?: {token?: string; autofill?: boolean, extensions?: Record<string, unknown> } | undefined) {
if (params?.token && params.autofill) {
throw new Error("Autofill is not supported when providing a token");
}

const optionsResponse = await this.api.authenticationOptions({token: params?.token});

try {
const givenOptions: PublicKeyCredentialRequestOptionsJSON = optionsResponse.options;
const options: PublicKeyCredentialRequestOptionsJSON = {
...givenOptions,
extensions: {
...givenOptions.extensions,
...params.extensions
}
}
const authenticationResponse = await startAuthentication(options, params?.autofill);

const verifyResponse = await this.api.verify({
challengeId: optionsResponse.challengeId,
authenticationCredential: authenticationResponse,
token: params?.token,
});

return verifyResponse?.accessToken;
} catch (error) {
console.error(error);
}
}

async signUp({userName, token, extensions, authenticatorSelection}: { userName: string, token: string, extensions?: Record<string, unknown>, authenticatorSelection?: Record<string, unknown> }): Promise<string | undefined> {
const optionsResponse = await this.api.registrationOptions({userName, token});
try {
const options = {
...optionsResponse.options,
authenticatorSelection: {
...optionsResponse.options.authenticatorSelection,
...authenticatorSelection
},
extensions: {
...optionsResponse.options.extensions,
...extensions
}
};
console.log(options);
const registrationResponse = await startRegistration(options);

const addAuthenticatorResponse = await this.api.addAuthenticator({
challengeId: optionsResponse.challengeId,
registrationCredential: registrationResponse,
token,
});
return addAuthenticatorResponse?.accessToken;
} catch (error) {
console.error(error);
}
}
}

class AuthsignalCustomClient extends Authsignal {

extendedPasskey: AuthsignalCustomPasskey;

constructor(options: AuthsignalOptions) {
super(options);
this.extendedPasskey = new AuthsignalCustomPasskey(this.passkey);
const passkey: unknown = this.extendedPasskey;
ok<Passkey>(passkey);
this.passkey = passkey;
}

}
2 changes: 2 additions & 0 deletions src/react/client/secure-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export interface PaymentMethodInfo {
}

export async function authenticatePaymentMethod({ credentialIds, challenge, paymentMethodName, payeeOrigin }: PaymentMethodInfo) {
console.log(`${location.hostname}`)
const request = new PaymentRequest([{
// Specify `secure-payment-confirmation` as payment method.
supportedMethods: "secure-payment-confirmation",
Expand Down Expand Up @@ -120,6 +121,7 @@ export async function authenticatePaymentMethod({ credentialIds, challenge, paym
// } else {
// await response.complete('fail');
// }

} catch (err) {
// SPC cannot be used; merchant should fallback to traditional flows
console.error(err);
Expand Down

0 comments on commit ebcda22

Please sign in to comment.