Skip to content

Commit

Permalink
Retry with differen rp id (#2753)
Browse files Browse the repository at this point in the history
* Retry with differen rp id

* Improve tests
  • Loading branch information
lmuntaner authored Dec 18, 2024
1 parent 2705d64 commit e3e48d0
Show file tree
Hide file tree
Showing 4 changed files with 390 additions and 56 deletions.
106 changes: 106 additions & 0 deletions src/frontend/src/utils/findWebAuthnRpId.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CredentialData } from "./credential-devices";
import {
BETA_DOMAINS,
PROD_DOMAINS,
excludeCredentialsFromOrigins,
findWebAuthnRpId,
} from "./findWebAuthnRpId";

Expand Down Expand Up @@ -165,3 +166,108 @@ describe("findWebAuthnRpId", () => {
);
});
});

describe("excludeCredentialsFromOrigins", () => {
const mockDeviceData = (origin?: string): CredentialData => ({
origin,
credentialId: new ArrayBuffer(1),
pubkey: new ArrayBuffer(1),
});

test("excludes credentials from specified origins", () => {
const credentials = [
mockDeviceData("https://identity.ic0.app"),
mockDeviceData("https://identity.internetcomputer.org"),
mockDeviceData("https://identity.icp0.io"),
];
const originsToExclude = new Set(["https://identity.ic0.app"]);
const currentOrigin = "https://identity.internetcomputer.org";

const result = excludeCredentialsFromOrigins(
credentials,
originsToExclude,
currentOrigin
);

expect(result).toHaveLength(2);
expect(result).toEqual([
mockDeviceData("https://identity.internetcomputer.org"),
mockDeviceData("https://identity.icp0.io"),
]);
});

test("treats undefined credential origins as DEFAULT_DOMAIN", () => {
const credentials = [
mockDeviceData(undefined), // Should be treated as DEFAULT_DOMAIN
mockDeviceData("https://identity.internetcomputer.org"),
];
const originsToExclude = new Set(["https://identity.ic0.app"]); // Should match DEFAULT_DOMAIN
const currentOrigin = "https://identity.internetcomputer.org";

const result = excludeCredentialsFromOrigins(
credentials,
originsToExclude,
currentOrigin
);

expect(result).toHaveLength(1);
expect(result).toEqual([
mockDeviceData("https://identity.internetcomputer.org"),
]);
});

test("treats undefined origins in exclusion set as currentOrigin", () => {
const credentials = [
mockDeviceData("https://identity.ic0.app"),
mockDeviceData("https://identity.internetcomputer.org"),
];
const originsToExclude = new Set([undefined]); // Should be treated as currentOrigin
const currentOrigin = "https://identity.internetcomputer.org";

const result = excludeCredentialsFromOrigins(
credentials,
originsToExclude,
currentOrigin
);

expect(result).toHaveLength(1);
expect(result).toEqual([mockDeviceData("https://identity.ic0.app")]);
});

test("returns empty array when all credentials are excluded", () => {
const credentials = [
mockDeviceData("https://identity.ic0.app"),
mockDeviceData("https://identity.internetcomputer.org"),
];
const originsToExclude = new Set([
"https://identity.ic0.app",
"https://identity.internetcomputer.org",
]);
const currentOrigin = "https://identity.ic0.app";

const result = excludeCredentialsFromOrigins(
credentials,
originsToExclude,
currentOrigin
);

expect(result).toHaveLength(0);
});

test("returns all credentials when no origins to exclude", () => {
const credentials = [
mockDeviceData("https://identity.ic0.app"),
mockDeviceData("https://identity.internetcomputer.org"),
];
const originsToExclude = new Set<string>();
const currentOrigin = "https://identity.ic0.app";

const result = excludeCredentialsFromOrigins(
credentials,
originsToExclude,
currentOrigin
);

expect(result).toEqual(credentials);
});
});
37 changes: 37 additions & 0 deletions src/frontend/src/utils/findWebAuthnRpId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,43 @@ export const relatedDomains = (): string[] => {
return [];
};

export const hasCredentialsFromMultipleOrigins = (
credentials: CredentialData[]
): boolean =>
new Set(credentials.map(({ origin }) => origin ?? DEFAULT_DOMAIN)).size > 1;

/**
* Filters out credentials from specific origins.
*
* This function takes a list of credentials and removes any that match the provided origins.
* If a credential has no origin (undefined), it is treated as if it had the `DEFAULT_DOMAIN`.
* Two origins match if they have the same hostname (domain).
*
* @param credentials - List of credential devices to filter
* @param origins - Set of origins to exclude (undefined values are treated as `currentOrigin`)
* @param currentOrigin - The current origin to use when comparing against undefined origins
* @returns Filtered list of credentials, excluding those from the specified origins
*/
export const excludeCredentialsFromOrigins = (
credentials: CredentialData[],
origins: Set<string | undefined>,
currentOrigin: string
): CredentialData[] => {
if (origins.size === 0) {
return credentials;
}
// Change `undefined` to the current origin.
const originsToExclude = Array.from(origins).map(
(origin) => origin ?? currentOrigin
);
return credentials.filter(
(credential) =>
originsToExclude.filter((originToExclude) =>
sameDomain(credential.origin ?? DEFAULT_DOMAIN, originToExclude)
).length === 0
);
};

const sameDomain = (url1: string, url2: string): boolean =>
new URL(url1).hostname === new URL(url2).hostname;

Expand Down
Loading

0 comments on commit e3e48d0

Please sign in to comment.