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
|
import type {
AuthenticationExtensionsClientInputs,
AuthenticatorTransportFuture,
Base64URLString,
PublicKeyCredentialRequestOptionsJSON,
UserVerificationRequirement,
} from '../deps.ts';
import { isoBase64URL, isoUint8Array } from '../helpers/iso/index.ts';
import { generateChallenge } from '../helpers/generateChallenge.ts';
export type GenerateAuthenticationOptionsOpts = {
rpID: string;
allowCredentials?: {
id: Base64URLString;
transports?: AuthenticatorTransportFuture[];
}[];
challenge?: string | Uint8Array;
timeout?: number;
userVerification?: UserVerificationRequirement;
extensions?: AuthenticationExtensionsClientInputs;
};
/**
* Prepare a value to pass into navigator.credentials.get(...) for authenticator authentication
*
* **Options:**
*
* @param rpID - Valid domain name (after `https://`)
* @param allowCredentials **(Optional)** - Authenticators previously registered by the user, if any. If undefined the client will ask the user which credential they want to use
* @param challenge **(Optional)** - Random value the authenticator needs to sign and pass back user for authentication. Defaults to generating a random value
* @param timeout **(Optional)** - How long (in ms) the user can take to complete authentication. Defaults to `60000`
* @param userVerification **(Optional)** - Set to `'discouraged'` when asserting as part of a 2FA flow, otherwise set to `'preferred'` or `'required'` as desired. Defaults to `"preferred"`
* @param extensions **(Optional)** - Additional plugins the authenticator or browser should use during authentication
*/
export async function generateAuthenticationOptions(
options: GenerateAuthenticationOptionsOpts,
): Promise<PublicKeyCredentialRequestOptionsJSON> {
const {
allowCredentials,
challenge = await generateChallenge(),
timeout = 60000,
userVerification = 'preferred',
extensions,
rpID,
} = options;
/**
* Preserve ability to specify `string` values for challenges
*/
let _challenge = challenge;
if (typeof _challenge === 'string') {
_challenge = isoUint8Array.fromUTF8String(_challenge);
}
return {
rpId: rpID,
challenge: isoBase64URL.fromBuffer(_challenge),
allowCredentials: allowCredentials?.map((cred) => {
if (!isoBase64URL.isBase64URL(cred.id)) {
throw new Error(`excludeCredential id "${cred.id}" is not a valid base64url string`);
}
return {
...cred,
id: isoBase64URL.trimPadding(cred.id),
type: 'public-key',
};
}),
timeout,
userVerification,
extensions,
};
}
|