Skip to content

Commit

Permalink
Propagate server validation errors (#58)
Browse files Browse the repository at this point in the history
* wip

* wip

* wip
  • Loading branch information
hwhmeikle committed Jun 17, 2024
1 parent fdd30f7 commit 79df47d
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 17 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.2",
"version": "0.4.3",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
Expand Down
13 changes: 7 additions & 6 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,
AuthsignalResponse,
ChallengeResponse,
PasskeyAuthenticatorResponse,
RegistrationOptsRequest,
Expand All @@ -29,7 +30,7 @@ export class PasskeyApiClient {
token,
username,
authenticatorAttachment,
}: {token: string} & RegistrationOptsRequest): Promise<RegistrationOptsResponse> {
}: {token: string} & RegistrationOptsRequest): Promise<AuthsignalResponse<RegistrationOptsResponse>> {
const body: RegistrationOptsRequest = Boolean(authenticatorAttachment)
? {username, authenticatorAttachment}
: {username};
Expand All @@ -46,7 +47,7 @@ export class PasskeyApiClient {
async authenticationOptions({
token,
challengeId,
}: {token?: string} & AuthenticationOptsRequest): Promise<AuthenticationOptsResponse> {
}: {token?: string} & AuthenticationOptsRequest): Promise<AuthsignalResponse<AuthenticationOptsResponse>> {
const body: AuthenticationOptsRequest = {challengeId};

const response = fetch(`${this.baseUrl}/client/user-authenticators/passkey/authentication-options`, {
Expand All @@ -62,7 +63,7 @@ export class PasskeyApiClient {
token,
challengeId,
registrationCredential,
}: {token: string} & AddAuthenticatorRequest): Promise<AddAuthenticatorResponse> {
}: {token: string} & AddAuthenticatorRequest): Promise<AuthsignalResponse<AddAuthenticatorResponse>> {
const body: AddAuthenticatorRequest = {
challengeId,
registrationCredential,
Expand All @@ -82,7 +83,7 @@ export class PasskeyApiClient {
challengeId,
authenticationCredential,
deviceId,
}: {token?: string} & VerifyRequest): Promise<VerifyResponse> {
}: {token?: string} & VerifyRequest): Promise<AuthsignalResponse<VerifyResponse>> {
const body: VerifyRequest = {challengeId, authenticationCredential, deviceId};

const response = fetch(`${this.baseUrl}/client/verify/passkey`, {
Expand All @@ -94,7 +95,7 @@ export class PasskeyApiClient {
return (await response).json();
}

async getPasskeyAuthenticator(credentialId: string): Promise<PasskeyAuthenticatorResponse> {
async getPasskeyAuthenticator(credentialId: string): Promise<AuthsignalResponse<PasskeyAuthenticatorResponse>> {
const response = await fetch(`${this.baseUrl}/client/user-authenticators/passkey?credentialId=${credentialId}`, {
method: "GET",
headers: this.buildHeaders(),
Expand All @@ -107,7 +108,7 @@ export class PasskeyApiClient {
return response.json();
}

async challenge(action: string): Promise<ChallengeResponse> {
async challenge(action: string): Promise<AuthsignalResponse<ChallengeResponse>> {
const response = fetch(`${this.baseUrl}/client/challenge`, {
method: "POST",
headers: this.buildHeaders(),
Expand Down
7 changes: 7 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,10 @@ export type PasskeyAuthenticatorResponse = {
export type ChallengeResponse = {
challengeId: string;
};

export type ErrorResponse = {
error: string;
errorDescription?: string;
};

export type AuthsignalResponse<T> = T | ErrorResponse;
18 changes: 12 additions & 6 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {AuthsignalResponse, ErrorResponse} from "./api/types";

type CookieOptions = {
name: string;
value: string;
Expand All @@ -6,7 +8,7 @@ type CookieOptions = {
secure: boolean;
};

export const setCookie = ({name, value, expire, domain, secure}: CookieOptions): void => {
export function setCookie({name, value, expire, domain, secure}: CookieOptions) {
const expireString = expire === Infinity ? " expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + expire;
document.cookie =
encodeURIComponent(name) +
Expand All @@ -16,13 +18,13 @@ export const setCookie = ({name, value, expire, domain, secure}: CookieOptions):
expireString +
(domain ? "; domain=" + domain : "") +
(secure ? "; secure" : "");
};
}

export const getCookieDomain = (): string => {
export function getCookieDomain() {
return document.location.hostname.replace("www.", "");
};
}

export const getCookie = (name: string) => {
export function getCookie(name: string) {
if (!name) {
return null;
}
Expand All @@ -36,4 +38,8 @@ export const getCookie = (name: string) => {
)
) || null
);
};
}

export function logErrorResponse(errorResponse: ErrorResponse) {
console.error(errorResponse.errorDescription ?? errorResponse.error);
}
34 changes: 30 additions & 4 deletions src/passkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {startAuthentication, startRegistration} from "@simplewebauthn/browser";

import {PasskeyApiClient} from "./api";
import {AuthenticationResponseJSON, RegistrationResponseJSON, AuthenticatorAttachment} from "@simplewebauthn/types";
import {logErrorResponse} from "./helpers";

type PasskeyOptions = {
baseUrl: string;
Expand All @@ -28,6 +29,11 @@ export class Passkey {
async signUp({userName, token, authenticatorAttachment = "platform"}: SignUpParams) {
const optionsResponse = await this.api.registrationOptions({username: userName, token, authenticatorAttachment});

if ("error" in optionsResponse) {
logErrorResponse(optionsResponse);
return;
}

const registrationResponse = await startRegistration(optionsResponse.options);

const addAuthenticatorResponse = await this.api.addAuthenticator({
Expand All @@ -36,11 +42,16 @@ export class Passkey {
token,
});

if (addAuthenticatorResponse?.isVerified) {
if ("error" in addAuthenticatorResponse) {
logErrorResponse(addAuthenticatorResponse);
return;
}

if (addAuthenticatorResponse.isVerified) {
this.storeCredentialAgainstDevice(registrationResponse);
}

return addAuthenticatorResponse?.accessToken;
return addAuthenticatorResponse.accessToken;
}

async signIn(): Promise<string | undefined>;
Expand All @@ -58,11 +69,21 @@ export class Passkey {

const challengeResponse = params?.action ? await this.api.challenge(params.action) : null;

if (challengeResponse && "error" in challengeResponse) {
logErrorResponse(challengeResponse);
return;
}

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

if ("error" in optionsResponse) {
logErrorResponse(optionsResponse);
return;
}

const authenticationResponse = await startAuthentication(optionsResponse.options, params?.autofill);

const verifyResponse = await this.api.verify({
Expand All @@ -72,11 +93,16 @@ export class Passkey {
deviceId: this.anonymousId,
});

if (verifyResponse?.isVerified) {
if ("error" in verifyResponse) {
logErrorResponse(verifyResponse);
return;
}

if (verifyResponse.isVerified) {
this.storeCredentialAgainstDevice(authenticationResponse);
}

return verifyResponse?.accessToken;
return verifyResponse.accessToken;
}

async isAvailableOnDevice() {
Expand Down

0 comments on commit 79df47d

Please sign in to comment.