-
Notifications
You must be signed in to change notification settings - Fork 2
/
passkey.ts
108 lines (84 loc) · 3.29 KB
/
passkey.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import {startAuthentication, startRegistration} from "@simplewebauthn/browser";
import {PasskeyApiClient} from "./api";
import {AuthenticationResponseJSON, RegistrationResponseJSON, AuthenticatorAttachment} from "@simplewebauthn/types";
type PasskeyOptions = {
baseUrl: string;
tenantId: string;
anonymousId: string;
};
type SignUpParams = {
userName?: string;
token: string;
authenticatorAttachment?: AuthenticatorAttachment | null;
};
export class Passkey {
public api: PasskeyApiClient;
private passkeyLocalStorageKey = "as_passkey_credential_id";
private anonymousId: string;
constructor({baseUrl, tenantId, anonymousId}: PasskeyOptions) {
this.api = new PasskeyApiClient({baseUrl, tenantId});
this.anonymousId = anonymousId;
}
async signUp({userName, token, authenticatorAttachment = "platform"}: SignUpParams) {
const optionsResponse = await this.api.registrationOptions({username: userName, token, authenticatorAttachment});
const registrationResponse = await startRegistration(optionsResponse.options);
const addAuthenticatorResponse = await this.api.addAuthenticator({
challengeId: optionsResponse.challengeId,
registrationCredential: registrationResponse,
token,
});
if (addAuthenticatorResponse?.isVerified) {
this.storeCredentialAgainstDevice(registrationResponse);
}
return addAuthenticatorResponse?.accessToken;
}
async signIn(): Promise<string | undefined>;
async signIn(params?: {action: string; autofill?: boolean}): Promise<string | undefined>;
async signIn(params?: {token: string}): Promise<string | undefined>;
async signIn(params?: {autofill: boolean}): Promise<string | undefined>;
async signIn(params?: {token?: string; autofill?: boolean; action?: string} | undefined) {
if (params?.token && params.autofill) {
throw new Error("autofill is not supported when providing a token");
}
if (params?.action && params.token) {
throw new Error("action is not supported when providing a token");
}
const challengeResponse = params?.action ? await this.api.challenge(params.action) : null;
const optionsResponse = await this.api.authenticationOptions({
token: params?.token,
challengeId: challengeResponse?.challengeId,
});
const authenticationResponse = await startAuthentication(optionsResponse.options, params?.autofill);
const verifyResponse = await this.api.verify({
challengeId: optionsResponse.challengeId,
authenticationCredential: authenticationResponse,
token: params?.token,
deviceId: this.anonymousId,
});
if (verifyResponse?.isVerified) {
this.storeCredentialAgainstDevice(authenticationResponse);
}
return verifyResponse?.accessToken;
}
async isAvailableOnDevice() {
const credentialId = localStorage.getItem(this.passkeyLocalStorageKey);
if (!credentialId) {
return false;
}
try {
await this.api.getPasskeyAuthenticator(credentialId);
return true;
} catch {
return false;
}
}
private storeCredentialAgainstDevice({
id,
authenticatorAttachment,
}: AuthenticationResponseJSON | RegistrationResponseJSON) {
if (authenticatorAttachment === "cross-platform") {
return;
}
localStorage.setItem(this.passkeyLocalStorageKey, id);
}
}