Skip to content

Commit

Permalink
Allow passing action to passkey.signIn() (#56)
Browse files Browse the repository at this point in the history
* wip

* Fix type
  • Loading branch information
hwhmeikle committed Apr 12, 2024
1 parent 13b8f15 commit c6c46ae
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 24 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@authsignal/browser",
"version": "0.4.1",
"version": "0.4.2",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
Expand Down
54 changes: 43 additions & 11 deletions src/api/passkey-api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AddAuthenticatorResponse,
AuthenticationOptsRequest,
AuthenticationOptsResponse,
ChallengeResponse,
PasskeyAuthenticatorResponse,
RegistrationOptsRequest,
RegistrationOptsResponse,
Expand All @@ -26,12 +27,12 @@ export class PasskeyApiClient {

async registrationOptions({
token,
userName,
username,
authenticatorAttachment,
}: RegistrationOptsRequest): Promise<RegistrationOptsResponse> {
const body = Boolean(authenticatorAttachment)
? {username: userName, authenticatorAttachment}
: {username: userName};
}: {token: string} & RegistrationOptsRequest): Promise<RegistrationOptsResponse> {
const body: RegistrationOptsRequest = Boolean(authenticatorAttachment)
? {username, authenticatorAttachment}
: {username};

const response = fetch(`${this.baseUrl}/client/user-authenticators/passkey/registration-options`, {
method: "POST",
Expand All @@ -42,31 +43,52 @@ export class PasskeyApiClient {
return (await response).json();
}

async authenticationOptions({token}: AuthenticationOptsRequest): Promise<AuthenticationOptsResponse> {
async authenticationOptions({
token,
challengeId,
}: {token?: string} & AuthenticationOptsRequest): Promise<AuthenticationOptsResponse> {
const body: AuthenticationOptsRequest = {challengeId};

const response = fetch(`${this.baseUrl}/client/user-authenticators/passkey/authentication-options`, {
method: "POST",
headers: this.buildHeaders(token),
body: JSON.stringify({}),
body: JSON.stringify(body),
});

return (await response).json();
}

async addAuthenticator({token, ...rest}: AddAuthenticatorRequest): Promise<AddAuthenticatorResponse> {
async addAuthenticator({
token,
challengeId,
registrationCredential,
}: {token: string} & AddAuthenticatorRequest): Promise<AddAuthenticatorResponse> {
const body: AddAuthenticatorRequest = {
challengeId,
registrationCredential,
};

const response = fetch(`${this.baseUrl}/client/user-authenticators/passkey`, {
method: "POST",
headers: this.buildHeaders(token),
body: JSON.stringify(rest),
body: JSON.stringify(body),
});

return (await response).json();
}

async verify({token, ...rest}: VerifyRequest): Promise<VerifyResponse> {
async verify({
token,
challengeId,
authenticationCredential,
deviceId,
}: {token?: string} & VerifyRequest): Promise<VerifyResponse> {
const body: VerifyRequest = {challengeId, authenticationCredential, deviceId};

const response = fetch(`${this.baseUrl}/client/verify/passkey`, {
method: "POST",
headers: this.buildHeaders(token),
body: JSON.stringify(rest),
body: JSON.stringify(body),
});

return (await response).json();
Expand All @@ -85,6 +107,16 @@ export class PasskeyApiClient {
return response.json();
}

async challenge(action: string): Promise<ChallengeResponse> {
const response = fetch(`${this.baseUrl}/client/challenge`, {
method: "POST",
headers: this.buildHeaders(),
body: JSON.stringify({action}),
});

return (await response).json();
}

private buildHeaders(token?: string) {
const authorizationHeader = token ? `Bearer ${token}` : `Basic ${window.btoa(encodeURIComponent(this.tenantId))}`;

Expand Down
12 changes: 7 additions & 5 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import {
} from "@simplewebauthn/types";

export type RegistrationOptsRequest = {
userName?: string;
token: string;
username?: string;
authenticatorAttachment?: AuthenticatorAttachment | null;
};

Expand All @@ -17,7 +16,7 @@ export type RegistrationOptsResponse = {
};

export type AuthenticationOptsRequest = {
token?: string;
challengeId?: string;
};

export type AuthenticationOptsResponse = {
Expand All @@ -26,7 +25,6 @@ export type AuthenticationOptsResponse = {
};

export type AddAuthenticatorRequest = {
token: string;
challengeId: string;
registrationCredential: RegistrationResponseJSON;
};
Expand All @@ -38,9 +36,9 @@ export type AddAuthenticatorResponse = {
};

export type VerifyRequest = {
token?: string;
challengeId: string;
authenticationCredential: AuthenticationResponseJSON;
deviceId?: string;
};

export type VerifyResponse = {
Expand All @@ -52,3 +50,7 @@ export type PasskeyAuthenticatorResponse = {
credentialId: string;
verifiedAt: string;
};

export type ChallengeResponse = {
challengeId: string;
};
4 changes: 2 additions & 2 deletions src/authsignal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ export class Authsignal {
throw new Error("tenantId is required");
}

this.passkey = new Passkey({tenantId, baseUrl});

const idCookie = getCookie(this.anonymousIdCookieName);

if (idCookie) {
Expand All @@ -59,6 +57,8 @@ export class Authsignal {
secure: document.location.protocol !== "http:",
});
}

this.passkey = new Passkey({tenantId, baseUrl, anonymousId: this.anonymousId});
}

launch(url: string, options?: {mode?: "redirect"} & LaunchOptions): undefined;
Expand Down
24 changes: 19 additions & 5 deletions src/passkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {AuthenticationResponseJSON, RegistrationResponseJSON, AuthenticatorAttac
type PasskeyOptions = {
baseUrl: string;
tenantId: string;
anonymousId: string;
};

type SignUpParams = {
Expand All @@ -17,13 +18,15 @@ type SignUpParams = {
export class Passkey {
public api: PasskeyApiClient;
private passkeyLocalStorageKey = "as_passkey_credential_id";
private anonymousId: string;

constructor({baseUrl, tenantId}: PasskeyOptions) {
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, token, authenticatorAttachment});
const optionsResponse = await this.api.registrationOptions({username: userName, token, authenticatorAttachment});

const registrationResponse = await startRegistration(optionsResponse.options);

Expand All @@ -41,21 +44,32 @@ export class Passkey {
}

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} | 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");
throw new Error("autofill is not supported when providing a token");
}

const optionsResponse = await this.api.authenticationOptions({token: params?.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) {
Expand Down

0 comments on commit c6c46ae

Please sign in to comment.