Skip to content

Commit

Permalink
Update based on feedback.
Browse files Browse the repository at this point in the history
  • Loading branch information
sea-snake committed Feb 5, 2025
1 parent d04d141 commit cc6a659
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 25 deletions.
8 changes: 6 additions & 2 deletions src/frontend/src/flows/authorize/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
"validating_auth_data": "Hang tight, validating the dapp's information",
"finalizing_auth": "Hang tight, starting your session",

"invalid_data": "Invalid Data",
"no_auth_data": "It looks like you were sent here for authentication, but no service requested authentication.",
"invalid_request": "Invalid request",
"invalid_authentication_request_received": "It seems like an invalid authentication request was received.",
"connection_closed": "Connection closed",
"connection_could_not_be_established": "It seems like the connection with the service could not be established.",
"wrong_place": "Wrong place",
"no_request_received": "It looks like you arrived here for authentication, but no service has requested authentication.",
"go_home": "Home",

"auth_failed": "Something went wrong during authentication. Authenticating service was notified and you may close this page.",
Expand Down
24 changes: 22 additions & 2 deletions src/frontend/src/flows/authorize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,28 @@ export const authFlowAuthorize = async (

if (result === "orphan") {
await displayError({
title: copy.invalid_data,
message: copy.no_auth_data,
title: copy.wrong_place,
message: copy.no_request_received,
primaryButton: copy.go_home,
});

location.hash = "";
return window.location.reload() as never;
}
if (result === "closed") {
await displayError({
title: copy.connection_closed,
message: copy.connection_could_not_be_established,
primaryButton: copy.go_home,
});

location.hash = "";
return window.location.reload() as never;
}
if (result === "invalid") {
await displayError({
title: copy.invalid_request,
message: copy.invalid_authentication_request_received,
primaryButton: copy.go_home,
});

Expand Down
78 changes: 57 additions & 21 deletions src/frontend/src/flows/authorize/postMessageInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export const AuthReady = {
kind: "authorize-ready",
};

// If the relying party hasn't sent a request in 10 seconds, we should assume
// something went wrong and that we're likely aren't going to receive any.
const TIMEOUT_WAIT_FOR_REQUEST = 10000;

/**
* All information required to process an authentication request received from
* a client application.
Expand Down Expand Up @@ -92,11 +96,15 @@ export async function authenticationProtocol({
>;
/* Progress update messages to let the user know what's happening. */
onProgress: (state: "waiting" | "validating") => void;
}): Promise<"orphan" | "success" | "failure"> {
}): Promise<"orphan" | "closed" | "invalid" | "success" | "failure"> {
if (window.opener === null) {
// If there's no `window.opener` a user has manually navigated to "/#authorize".
// Signal that there will never be an authentication request incoming.
return "orphan";
if (window.history.length > 1) {
// If there's no `window.opener` and a user has manually navigated to "/#authorize".
// Signal that there will never be an authentication request incoming.
return "orphan";
}
// Else signal that the connection has been unexpectedly closed.
return "closed";
}

// Send a message to indicate we're ready.
Expand All @@ -107,29 +115,39 @@ export async function authenticationProtocol({

onProgress("waiting");

const { origin, request } = await waitForRequest();
const authContext = { authRequest: request, requestOrigin: origin };
const requestResult = await waitForRequest();
if (requestResult.kind === "timeout") {
return "closed";
}
if (requestResult.kind === "invalid") {
return "invalid";
}
void (requestResult.kind satisfies "received");

const authContext = {
authRequest: requestResult.request,
requestOrigin: requestResult.origin,
};

onProgress("validating");

const result = await authenticate(authContext);
const authenticateResult = await authenticate(authContext);

if (result.kind === "failure") {
if (authenticateResult.kind === "failure") {
window.opener.postMessage({
kind: "authorize-client-failure",
text: result.text,
text: authenticateResult.text,
} satisfies AuthResponse);
return "failure";
}

result.kind satisfies "success";
void (authenticateResult.kind satisfies "success");

window.opener.postMessage(
{
kind: "authorize-client-success",
delegations: result.delegations,
userPublicKey: result.userPublicKey,
authnMethod: result.authnMethod,
delegations: authenticateResult.delegations,
userPublicKey: authenticateResult.userPublicKey,
authnMethod: authenticateResult.authnMethod,
} satisfies AuthResponse,
authContext.requestOrigin
);
Expand All @@ -138,11 +156,20 @@ export async function authenticationProtocol({
}

// Wait for a request to kickstart the flow
const waitForRequest = (): Promise<{
request: AuthRequest;
origin: string;
}> => {
const waitForRequest = (): Promise<
| {
kind: "received";
request: AuthRequest;
origin: string;
}
| { kind: "timeout" }
| { kind: "invalid" }
> => {
return new Promise((resolve) => {
const timeout = setTimeout(
() => resolve({ kind: "timeout" }),
TIMEOUT_WAIT_FOR_REQUEST
);
const messageEventHandler = (evnt: MessageEvent) => {
if (evnt.origin === window.location.origin) {
// Ignore messages from own origin (e.g. from browser extensions)
Expand All @@ -155,14 +182,23 @@ const waitForRequest = (): Promise<{
if (!result.success) {
const message = `Unexpected error: flow request ` + result.error;
console.error(message);
// XXX: here we just wait further assuming the opener might recover
// and send a correct request

// The relying party is not made aware that the request is invalid,
// so we should assume the relying party will wait forever.
//
// Let's at least indicate to the user that the request was invalid,
// so they can communicate this with the relying party developer.
clearTimeout(timeout);
window.removeEventListener("message", messageEventHandler);

resolve({ kind: "invalid" });
return;
}

clearTimeout(timeout);
window.removeEventListener("message", messageEventHandler);

resolve({ request: result.data, origin: evnt.origin });
resolve({ kind: "received", request: result.data, origin: evnt.origin });
};

// Set up an event listener for receiving messages from the client.
Expand Down

0 comments on commit cc6a659

Please sign in to comment.