Skip to content

Commit

Permalink
Implement canister config in html.
Browse files Browse the repository at this point in the history
  • Loading branch information
sea-snake committed Feb 11, 2025
1 parent fe2dc02 commit 73bf41f
Show file tree
Hide file tree
Showing 14 changed files with 203 additions and 71 deletions.
14 changes: 10 additions & 4 deletions src/asset_util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use ic_certification::{
use ic_representation_independent_hash::{representation_independent_hash, Value};
use include_dir::Dir;
use internet_identity_interface::http_gateway::HeaderField;
use internet_identity_interface::internet_identity::types::InternetIdentityInit;
use lazy_static::lazy_static;
use serde::Serialize;
use sha2::Digest;
Expand Down Expand Up @@ -416,7 +417,11 @@ fn response_hash(status_code: u16, headers: &[HeaderField], body_hash: &Hash) ->

/// Collects all assets from the given directory, recursing into subdirectories.
/// Optionally, a transformer function can be provided to transform the HTML files.
pub fn collect_assets(dir: &Dir, html_transformer: Option<fn(&str) -> String>) -> Vec<Asset> {
pub fn collect_assets(
dir: &Dir,
html_transformer: Option<fn(&str, &InternetIdentityInit) -> String>,
config: &InternetIdentityInit,
) -> Vec<Asset> {
let mut assets = vec![];

// Collect all assets, recursively
Expand All @@ -426,9 +431,10 @@ pub fn collect_assets(dir: &Dir, html_transformer: Option<fn(&str) -> String>) -
if let Some(html_transformer) = html_transformer {
for asset in &mut assets {
if let ContentType::HTML = asset.content_type {
asset.content = html_transformer(std::str::from_utf8(&asset.content).unwrap())
.as_bytes()
.to_vec();
asset.content =
html_transformer(std::str::from_utf8(&asset.content).unwrap(), config)
.as_bytes()
.to_vec();
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/frontend/src/flows/iframeWebAuthn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,14 @@ export const webAuthnInIframeFlow = async (
connection: Connection
): Promise<never> => {
// Establish cross-origin connection with parent window
const config = await connection.getConfig();
const targetOrigin = await waitForWindowReadyRequest(
window.parent,
// We only establish a connection for the related origins in the II config,
// incoming requests from other origins are not listed here and ignored.
//
// Additionally, the CSP configuration will block any attempt to render II
// inside an iframe from domains that are not related origins.
config.related_origins[0] ?? []
connection.canisterConfig.related_origins[0] ?? []
);

// Get credential and send to parent window
Expand Down
9 changes: 2 additions & 7 deletions src/frontend/src/flows/manage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
DeviceData,
DeviceWithUsage,
IdentityAnchorInfo,
InternetIdentityInit,
OpenIdCredential,
OpenIdCredentialAddError,
OpenIdCredentialRemoveError,
Expand Down Expand Up @@ -355,11 +354,8 @@ export const displayManage = async (
connection.identity.getPrincipal()
);

// Get Google client id from config in background
const configRef: { current?: InternetIdentityInit } = {};
void connection.getConfig().then((config) => (configRef.current = config));
const getGoogleClientId = () =>
configRef.current?.openid_google[0]?.[0]?.client_id;
const googleClientId =
connection.canisterConfig.openid_google[0]?.[0]?.client_id;

return new Promise((resolve) => {
const devices = devicesFromDevicesWithUsage({
Expand Down Expand Up @@ -417,7 +413,6 @@ export const displayManage = async (
};

const onLinkAccount = async () => {
const googleClientId = getGoogleClientId();
if (isNullish(googleClientId)) {
toast.error(copy.linking_google_accounts_is_unavailable);
return;
Expand Down
43 changes: 42 additions & 1 deletion src/frontend/src/spa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import { version } from "./version";
import { isNullish, nonNullish } from "@dfinity/utils";

// Polyfill Buffer globally for the browser
import { init } from "$generated/internet_identity_idl";
import { InternetIdentityInit } from "$generated/internet_identity_types";
import { fromBase64 } from "$src/utils/utils";
import { IDL } from "@dfinity/candid";
import { Buffer } from "buffer";

globalThis.Buffer = Buffer;

/** Reads the canister ID from the <script> tag.
Expand All @@ -38,6 +43,42 @@ const readCanisterId = (): string => {
return setupJs.dataset.canisterId;
};

const readCanisterConfig = (): InternetIdentityInit => {
// The backend uses a known element ID so that we can pick up the value from here
const setupJs = document.querySelector(
"[data-canister-config]"
) as HTMLElement | null;
if (isNullish(setupJs) || isNullish(setupJs.dataset.canisterConfig)) {
void displayError({
title: "Canister config not set",
message:
"There was a problem contacting the IC. The host serving this page did not give us the canister config. Try reloading the page and contact support if the problem persists.",
primaryButton: "Reload",
}).then(() => {
window.location.reload();
});
throw new Error("canister config is undefined"); // abort further execution of this script
}

try {
const [jsonValue] = IDL.decode(
[init({ IDL })[0]._type],
fromBase64(setupJs.dataset.canisterConfig)
);
return jsonValue as unknown as InternetIdentityInit;
} catch (e) {
void displayError({
title: "Canister config not valid",
message:
"There was a problem contacting the IC. The host serving this page did not give us a valid canister config. Try reloading the page and contact support if the problem persists.",
primaryButton: "Reload",
}).then(() => {
window.location.reload();
});
throw new Error("canister config is invalid"); // abort further execution of this script
}
};

// Show version information for the curious programmer
const printDevMessage = () => {
console.log("Welcome to Internet Identity!");
Expand Down Expand Up @@ -96,7 +137,7 @@ export const createSpa = (app: (connection: Connection) => Promise<never>) => {
}

// Prepare the actor/connection to talk to the canister
const connection = new Connection(readCanisterId());
const connection = new Connection(readCanisterId(), readCanisterConfig());

return app(connection);
};
73 changes: 62 additions & 11 deletions src/frontend/src/utils/iiConnection.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
DeviceData,
InternetIdentityInit,
MetadataMapV2,
_SERVICE,
} from "$generated/internet_identity_types";
Expand Down Expand Up @@ -84,6 +85,7 @@ beforeEach(async () => {
test("initializes identity metadata repository", async () => {
const connection = new AuthenticatedConnection(
"12345",
{} as InternetIdentityInit,
MultiWebAuthnIdentity.fromCredentials([], undefined, undefined),
mockDelegationIdentity,
BigInt(1234),
Expand All @@ -99,6 +101,7 @@ test("commits changes on identity metadata", async () => {
const userNumber = BigInt(1234);
const connection = new AuthenticatedConnection(
"12345",
{} as InternetIdentityInit,
MultiWebAuthnIdentity.fromCredentials([], undefined, undefined),
mockDelegationIdentity,
userNumber,
Expand Down Expand Up @@ -178,7 +181,11 @@ describe("Connection.login", () => {
});

it("login returns authenticated connection with expected rpID", async () => {
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

const loginResult = await connection.login(BigInt(12345));

Expand Down Expand Up @@ -207,7 +214,11 @@ describe("Connection.login", () => {
identity_info: vi.fn().mockResolvedValue({ Ok: { metadata: [] } }),
lookup: vi.fn().mockResolvedValue([currentOriginDevice, currentDevice]),
} as unknown as ActorSubclass<_SERVICE>;
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

failSign = true;
const firstLoginResult = await connection.login(BigInt(12345));
Expand Down Expand Up @@ -260,7 +271,11 @@ describe("Connection.login", () => {
.fn()
.mockResolvedValue([currentOriginDevice, currentOriginDevice2]),
} as unknown as ActorSubclass<_SERVICE>;
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

failSign = true;
const firstLoginResult = await connection.login(BigInt(12345));
Expand Down Expand Up @@ -310,7 +325,11 @@ describe("Connection.login", () => {
});

it("login returns authenticated connection with expected rpID", async () => {
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

const loginResult = await connection.login(BigInt(12345));

Expand Down Expand Up @@ -339,7 +358,11 @@ describe("Connection.login", () => {
});

it("login returns authenticated connection without rpID if flag is not enabled", async () => {
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

const loginResult = await connection.login(BigInt(12345));

Expand Down Expand Up @@ -367,7 +390,11 @@ describe("Connection.login", () => {
identity_info: vi.fn().mockResolvedValue({ Ok: { metadata: [] } }),
lookup: vi.fn().mockResolvedValue([currentOriginDevice, currentDevice]),
} as unknown as ActorSubclass<_SERVICE>;
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

failSign = true;
const firstLoginResult = await connection.login(BigInt(12345));
Expand Down Expand Up @@ -417,7 +444,11 @@ describe("Connection.login", () => {
});

it("login returns authenticated connection without rpID if flag is not enabled", async () => {
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

const loginResult = await connection.login(BigInt(12345));

Expand Down Expand Up @@ -445,7 +476,11 @@ describe("Connection.login", () => {
identity_info: vi.fn().mockResolvedValue({ Ok: { metadata: [] } }),
lookup: vi.fn().mockResolvedValue([currentOriginDevice, currentDevice]),
} as unknown as ActorSubclass<_SERVICE>;
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

failSign = true;
const firstLoginResult = await connection.login(BigInt(12345));
Expand Down Expand Up @@ -498,7 +533,11 @@ describe("Connection.login", () => {
deviceWithoutCredentialId,
]),
} as unknown as ActorSubclass<_SERVICE>;
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);
await connection.login(BigInt(12345));
expect(MultiWebAuthnIdentity.fromCredentials).toHaveBeenCalledTimes(1);
expect(MultiWebAuthnIdentity.fromCredentials).toHaveBeenCalledWith(
Expand All @@ -523,7 +562,11 @@ describe("Connection.login", () => {
deviceInvalidCredentialId,
]),
} as unknown as ActorSubclass<_SERVICE>;
const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);
await connection.login(BigInt(12345));
expect(MultiWebAuthnIdentity.fromCredentials).toHaveBeenCalledTimes(1);
expect(MultiWebAuthnIdentity.fromCredentials).toHaveBeenCalledWith(
Expand Down Expand Up @@ -556,7 +599,11 @@ describe("Connection.login", () => {
lookup: vi.fn().mockResolvedValue([pinDevice]),
} as unknown as ActorSubclass<_SERVICE>;

const connection = new Connection("aaaaa-aa", mockActor);
const connection = new Connection(
"aaaaa-aa",
{} as InternetIdentityInit,
mockActor
);

const loginResult = await connection.login(BigInt(12345));

Expand All @@ -579,6 +626,7 @@ describe("Connection.login", () => {
const userNumber = BigInt(12345);
const connection = new AuthenticatedConnection(
"aaaaa-aa",
{} as InternetIdentityInit,
MultiWebAuthnIdentity.fromCredentials([], undefined, undefined),
mockDelegationIdentity,
userNumber,
Expand Down Expand Up @@ -616,6 +664,7 @@ describe("Connection.login", () => {
const userNumber = BigInt(12345);
const connection = new AuthenticatedConnection(
"aaaaa-aa",
{} as InternetIdentityInit,
MultiWebAuthnIdentity.fromCredentials([], undefined, undefined),
mockDelegationIdentity,
userNumber,
Expand Down Expand Up @@ -653,6 +702,7 @@ describe("Connection.login", () => {
const userNumber = BigInt(12345);
const connection = new AuthenticatedConnection(
"aaaaa-aa",
{} as InternetIdentityInit,
MultiWebAuthnIdentity.fromCredentials([], undefined, undefined),
mockDelegationIdentity,
userNumber,
Expand Down Expand Up @@ -689,6 +739,7 @@ describe("Connection.login", () => {
const userNumber = BigInt(12345);
const connection = new AuthenticatedConnection(
"aaaaa-aa",
{} as InternetIdentityInit,
MultiWebAuthnIdentity.fromCredentials([], undefined, undefined),
mockDelegationIdentity,
userNumber,
Expand Down
Loading

0 comments on commit 73bf41f

Please sign in to comment.