Skip to content

Commit

Permalink
Push newAnchor into LoginData (#2278)
Browse files Browse the repository at this point in the history
The authentication flow returns whether the user authenticated with a
new or existing anchor. This data is used tailor subsequent flows to new
vs returning users.

Originally this information was added to all return types (success &
error alike) for simplicity, although it only ever makes sense to return
it upon successful authentication (until now we just defaulted to "not a
new anchor" upon failures).

As we start passing more data through the flow (like whether the user
used PIN or not) this shortcut became a bit problematic, so these
changes push the `newAnchor` information deeper into the success state.
  • Loading branch information
nmattia authored Feb 16, 2024
1 parent 9e90921 commit 9f645c2
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 24 deletions.
44 changes: 26 additions & 18 deletions src/frontend/src/components/authenticateBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ export const authenticateBox = async ({

// Retry until user has successfully authenticated
for (;;) {
const { newAnchor, ...result } = await promptAuth();
const result = await promptAuth();
const loginData = await handleLoginFlowResult(result);

if (nonNullish(loginData)) {
return { ...loginData, newAnchor };
return loginData;
}
}
};
Expand Down Expand Up @@ -163,7 +163,7 @@ export const authenticateBoxFlow = async <T, I>({
pinIdentityMaterial: I;
}) => Promise<"valid" | "expired">;
registerFlowOpts: Parameters<typeof registerFlow<T>>[0];
}): Promise<LoginFlowResult<T> & { newAnchor: boolean }> => {
}): Promise<LoginFlowResult<T, { newAnchor: boolean }>> => {
const pages = authnScreens(i18n, { ...templates });

// The registration flow for a new identity
Expand Down Expand Up @@ -197,7 +197,7 @@ export const authenticateBoxFlow = async <T, I>({

// Prompt for an identity number
const doPrompt = async (): Promise<
LoginFlowResult<T> & { newAnchor: boolean }
LoginFlowResult<T, { newAnchor: boolean }>
> => {
const result = await pages.useExisting();
if (result.tag === "submit") {
Expand All @@ -219,11 +219,17 @@ export const authenticateBoxFlow = async <T, I>({
}

result satisfies { tag: "recover" };
const result2 = await recover();
return {
newAnchor: false,
...result2,
};

const recoverResult = await recover();
if (recoverResult.tag === "ok") {
// If an anchor was recovered, then it's _not_ a new anchor
return {
newAnchor: false,
...recoverResult,
};
} else {
return recoverResult;
}
};

// If there _are_ some anchors, then we show the "pick" screen, otherwise
Expand All @@ -250,9 +256,9 @@ export const authenticateBoxFlow = async <T, I>({
}
};

export const handleLoginFlowResult = async <T>(
result: LoginFlowResult<T>
): Promise<LoginData<T> | undefined> => {
export const handleLoginFlowResult = async <T, E>(
result: LoginFlowResult<T, E>
): Promise<LoginData<T, E> | undefined> => {
switch (result.tag) {
case "ok":
await setAnchorUsed(result.userNumber);
Expand Down Expand Up @@ -619,7 +625,7 @@ const useIdentityFlow = async <T, I>({
pin: string;
pinIdentityMaterial: I;
}) => Promise<LoginFlowResult<T> | { tag: "err"; message: string }>;
}): Promise<LoginFlowResult<T> & { newAnchor: boolean }> => {
}): Promise<LoginFlowResult<T, { newAnchor: boolean }>> => {
const pinIdentityMaterial: I | undefined = await withLoader(() =>
retrievePinIdentityMaterial({
userNumber,
Expand Down Expand Up @@ -669,10 +675,7 @@ const useIdentityFlow = async <T, I>({
});

if (result.kind === "canceled") {
return {
newAnchor: false,
tag: "canceled",
} as const;
return { tag: "canceled" } as const;
}

if (result.kind === "passkey") {
Expand All @@ -683,7 +686,12 @@ const useIdentityFlow = async <T, I>({
result satisfies { kind: "pin" };
const { result: pinResult } = result;

return { newAnchor: false, ...pinResult };
if (pinResult.tag === "ok") {
// We log in with an existing PIN anchor, meaning it is _not_ a new anchor
return { newAnchor: false, ...pinResult };
} else {
return pinResult;
}
};

// Use a passkey, with concrete impl.
Expand Down
12 changes: 6 additions & 6 deletions src/frontend/src/utils/flowResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import { DynamicKey } from "$src/utils/i18n";
import { ApiResult, AuthenticatedConnection } from "./iiConnection";
import { webAuthnErrorCopy } from "./webAuthnErrorUtils";

export type LoginFlowResult<T = AuthenticatedConnection> =
| LoginFlowSuccess<T>
export type LoginFlowResult<T = AuthenticatedConnection, E = object> =
| LoginFlowSuccess<T, E>
| LoginFlowError
| LoginFlowCanceled;

export type LoginFlowSuccess<T = AuthenticatedConnection> = {
export type LoginFlowSuccess<T = AuthenticatedConnection, E = object> = {
tag: "ok";
} & LoginData<T>;
} & LoginData<T, E>;

export type LoginData<T = AuthenticatedConnection> = {
export type LoginData<T = AuthenticatedConnection, E = object> = {
userNumber: bigint;
connection: T;
};
} & E;

export type LoginFlowError = {
tag: "err";
Expand Down

0 comments on commit 9f645c2

Please sign in to comment.