Skip to content

Commit

Permalink
Merge branch 'main' into andri/jwt-delegations
Browse files Browse the repository at this point in the history
  • Loading branch information
LXIF authored Feb 11, 2025
2 parents 56ebb64 + fe2dc02 commit bc2c015
Show file tree
Hide file tree
Showing 22 changed files with 444 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/canister-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ jobs:
# (i.e. capitalize the first letter)
captcha_variant="Captcha$(tr '[:lower:]' '[:upper:]' <<< ${captcha_flag:0:1})${captcha_flag:1}"
# NOTE: dfx install will run the postinstall scripts from dfx.json
dfx canister install internet_identity --wasm internet_identity_test.wasm.gz --argument "(opt record { captcha_config = opt record { max_unsolved_captchas= 50:nat64; captcha_trigger = variant {Static = variant { $captcha_variant }}}})"
dfx canister install internet_identity --wasm internet_identity_test.wasm.gz --argument "(opt record { captcha_config = opt record { max_unsolved_captchas= 50:nat64; captcha_trigger = variant {Static = variant { $captcha_variant }}}; related_origins = opt vec { \"https://identity.internetcomputer.org\"; \"https://identity.ic0.app\" }})"
dfx canister install test_app --wasm demos/test-app/test_app.wasm
dfx canister install issuer --wasm demos/vc_issuer/vc_demo_issuer.wasm.gz
Expand Down
2 changes: 1 addition & 1 deletion HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ We have a set of Selenium tests that run through the various flows. To set up a

1. Start a local replica with `dfx start`
1. Deploy II and the other test canisters with `dfx deploy --no-wallet`
1. Start the vite dev server with TLS enabled: `TLS_DEV_SERVER=1 npm run dev`
1. Start the vite dev server with TLS enabled and hot reloading disabled: `TLS_DEV_SERVER=1 NO_HOT_RELOAD=1 npm run dev`

To watch the tests run in the browser remove the `headless` option from `src/frontend/src/test-e2e/util.ts`.

Expand Down
2 changes: 1 addition & 1 deletion dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"candid": "src/internet_identity/internet_identity.did",
"wasm": "internet_identity.wasm.gz",
"build": "bash -c 'II_DEV_CSP=1 II_FETCH_ROOT_KEY=1 II_DUMMY_CAPTCHA=${II_DUMMY_CAPTCHA:-1} scripts/build'",
"init_arg": "(opt record { captcha_config = opt record { max_unsolved_captchas= 50:nat64; captcha_trigger = variant {Static = variant {CaptchaDisabled}}}; openid_google = opt opt record { client_id = \"45431994619-cbbfgtn7o0pp0dpfcg2l66bc4rcg7qbu.apps.googleusercontent.com\" }})",
"init_arg": "(opt record { captcha_config = opt record { max_unsolved_captchas= 50:nat64; captcha_trigger = variant {Static = variant {CaptchaDisabled}}}; openid_google = opt opt record { client_id = \"45431994619-cbbfgtn7o0pp0dpfcg2l66bc4rcg7qbu.apps.googleusercontent.com\" }; related_origins = opt vec { \"https://identity.internetcomputer.org\"; \"https://identity.ic0.app\" } })",
"shrink": false
},
"test_app": {
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/components/infoToast/copy.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"title_possibly_wrong_web_authn_flow": "Please try again",
"message_possibly_wrong_web_authn_flow_1": "The wrong domain was set for the passkey and the browser couldn't find it.",
"title_pin_another_domain": "Pin identity in another domain",
"title_trying_again": "Trying again",
"message_pin_another_domain_1": "You seem to be using a PIN identity created in another domain.",
"message_pin_another_domain_2": "To access this domain you need to go to the other domain and add a passkey.",
"sign_in_with_google_accounts_is_unavailable": "Google login is unavailable. Please try again later.",
Expand Down
6 changes: 3 additions & 3 deletions src/frontend/src/components/landingPage.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"en": {
"title_1": "Decentralized.",
"title_2": "Private. Identity.",
"subtitle": "Since 2021, Internet Identity has combined secure passkey technology with the Internet Computer to keep you safe."
"title_1": "Safe. Private.",
"title_2": "Decentralized.",
"subtitle": "Pioneering passkey based identity since May 2021"
}
}
28 changes: 23 additions & 5 deletions src/frontend/src/flows/iframeWebAuthn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ interface CredentialResponse {
};
};
}
// We will create a DOMException from this error object
| {
error: string;
error: {
name: string;
message: string;
};
}
);
}
Expand Down Expand Up @@ -61,8 +65,8 @@ const requestCredential = (
getClientExtensionResults: () => ({}),
} as PublicKeyCredential);
}
if ("err" in event.data.ii_credential_response) {
reject(event.data.ii_credential_response.err);
if ("error" in event.data.ii_credential_response) {
reject(event.data.ii_credential_response.error);
}
window.removeEventListener("message", listener);
};
Expand Down Expand Up @@ -117,11 +121,15 @@ const handleCredentialRequest = (
},
};
window.parent.postMessage(response, targetOrigin);
} catch (error) {
} catch (error: unknown) {
const response: CredentialResponse = {
ii_credential_response: {
id: event.data.ii_credential_request.id,
error: String(error),
// We need to manually copy the error values here since the error instance will be lost in the postMessage
error: {
name: "NotAllowedError",
message: error instanceof Error ? error.message : "Unknown error",
},
},
};
window.parent.postMessage(response, targetOrigin);
Expand Down Expand Up @@ -187,6 +195,16 @@ export const webAuthnInIframe = async (

// Request credential from iframe
return await requestCredential(options, iframe.contentWindow, targetOrigin);
} catch (e: unknown) {
// We need to throw a `DOMException` so that it is properly caught by the caller of this function.
if (typeof e === "object" && e !== null && "name" in e && "message" in e) {
const message: string =
typeof e.message === "string" ? e.message : "Unknown error";
const name: string =
typeof e.name === "string" ? e.name : "NotAllowedError";
throw new DOMException(message, name);
}
throw new DOMException("NotAllowedError", "Unknown error");
} finally {
iframe.remove();
}
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/flows/manage/authenticatorsSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ const itemWarning = ({
const itemInfo = (msg: TemplateResult): TemplateResult => html`<div
class="c-action-list__action"
>
<span class="c-tooltip c-icon" tabindex="0"
<span class="c-tooltip c-icon" tabindex="0" data-icon="info"
>${infoIcon}<span class="c-tooltip__message c-card c-card--tight"
>${msg}</span
></span
Expand Down
8 changes: 2 additions & 6 deletions src/frontend/src/flows/manage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import {
isRecoveryDevice,
isRecoveryPhrase,
} from "$src/utils/recoveryDevice";
import { supportsWebauthRoR } from "$src/utils/userAgent";
import { userSupportsWebauthRoR } from "$src/utils/rorSupport";
import {
OmitParams,
isCanisterError,
Expand Down Expand Up @@ -383,11 +383,9 @@ export const displayManage = async (

const onAddDevice = async () => {
const newDeviveOrigin =
supportsWebauthRoR(window.navigator.userAgent) &&
DOMAIN_COMPATIBILITY.isEnabled()
userSupportsWebauthRoR() && DOMAIN_COMPATIBILITY.isEnabled()
? getCredentialsOrigin({
credentials: devices_,
userAgent: navigator.userAgent,
})
: undefined;
await addDevice({
Expand All @@ -408,7 +406,6 @@ export const displayManage = async (
const newDeviceOrigin = DOMAIN_COMPATIBILITY.isEnabled()
? getCredentialsOrigin({
credentials: devices_,
userAgent: window.navigator.userAgent,
})
: undefined;
await setupPhrase(
Expand Down Expand Up @@ -734,7 +731,6 @@ const domainInfo = (
}
const commonOrigin = getCredentialsOrigin({
credentials: allDevices,
userAgent: window.navigator.userAgent,
});
if (nonNullish(commonOrigin)) {
return undefined;
Expand Down
6 changes: 2 additions & 4 deletions src/frontend/src/flows/recovery/recoveryWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { infoScreenTemplate } from "$src/components/infoScreen";
import { DOMAIN_COMPATIBILITY } from "$src/featureFlags";
import { IdentityMetadata } from "$src/repositories/identityMetadata";
import { getCredentialsOrigin } from "$src/utils/credential-devices";
import { supportsWebauthRoR } from "$src/utils/userAgent";
import { userSupportsWebauthRoR } from "$src/utils/rorSupport";
import { isNullish } from "@dfinity/utils";
import { addDevice } from "../addDevice/manage/addDevice";
import {
Expand Down Expand Up @@ -241,11 +241,9 @@ export const recoveryWizard = async (
});

const originNewDevice =
supportsWebauthRoR(window.navigator.userAgent) &&
DOMAIN_COMPATIBILITY.isEnabled()
userSupportsWebauthRoR() && DOMAIN_COMPATIBILITY.isEnabled()
? getCredentialsOrigin({
credentials,
userAgent: navigator.userAgent,
})
: undefined;

Expand Down
6 changes: 2 additions & 4 deletions src/frontend/src/flows/recovery/setupRecovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
creationOptions,
IC_DERIVATION_PATH,
} from "$src/utils/iiConnection";
import { supportsWebauthRoR } from "$src/utils/userAgent";
import { userSupportsWebauthRoR } from "$src/utils/rorSupport";
import { unreachable, unreachableLax } from "$src/utils/utils";
import { WebAuthnIdentity } from "$src/utils/webAuthnIdentity";
import { DerEncodedPublicKey, SignIdentity } from "@dfinity/agent";
Expand All @@ -34,11 +34,9 @@ export const setupKey = async ({
const devices =
devices_ ?? (await connection.lookupAll(connection.userNumber));
const newDeviceOrigin =
supportsWebauthRoR(window.navigator.userAgent) &&
DOMAIN_COMPATIBILITY.isEnabled()
userSupportsWebauthRoR() && DOMAIN_COMPATIBILITY.isEnabled()
? getCredentialsOrigin({
credentials: devices,
userAgent: window.navigator.userAgent,
})
: undefined;
const rpId = nonNullish(newDeviceOrigin)
Expand Down
6 changes: 2 additions & 4 deletions src/frontend/src/flows/recovery/useRecovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
IIWebAuthnIdentity,
LoginSuccess,
} from "$src/utils/iiConnection";
import { supportsWebauthRoR } from "$src/utils/userAgent";
import { userSupportsWebauthRoR } from "$src/utils/rorSupport";
import { unknownToString, unreachableLax } from "$src/utils/utils";
import { constructIdentity } from "$src/utils/webAuthn";
import {
Expand Down Expand Up @@ -132,11 +132,9 @@ const enrollAuthenticator = async ({
const newDeviceData = await withLoader(async () => {
const devices = (await connection.getAnchorInfo()).devices;
const newDeviceOrigin =
supportsWebauthRoR(window.navigator.userAgent) &&
DOMAIN_COMPATIBILITY.isEnabled()
userSupportsWebauthRoR() && DOMAIN_COMPATIBILITY.isEnabled()
? getCredentialsOrigin({
credentials: devices,
userAgent: window.navigator.userAgent,
})
: undefined;
const rpId = nonNullish(newDeviceOrigin)
Expand Down
38 changes: 37 additions & 1 deletion src/frontend/src/flows/verifiableCredentials/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { SignedIdAlias } from "$generated/internet_identity_types";
import { useIdentity } from "$src/components/authenticateBox";
import { infoToastTemplate } from "$src/components/infoToast";
import { withLoader } from "$src/components/loader";
import { showMessage } from "$src/components/message";
import { showSpinner } from "$src/components/spinner";
import { toast } from "$src/components/toast";
import { fetchDelegation } from "$src/flows/authorize/fetchDelegation";
import { I18n } from "$src/i18n";
import { getAnchorByPrincipal } from "$src/storage";
import { AuthenticatedConnection, Connection } from "$src/utils/iiConnection";
import { validateDerivationOrigin } from "$src/utils/validateDerivationOrigin";
Expand All @@ -18,6 +21,7 @@ import {
IssuedCredentialData,
} from "@dfinity/internet-identity-vc-api";
import { Principal } from "@dfinity/principal";
import infoToastCopy from "../../components/infoToast/copy.json";
import { abortedCredentials } from "./abortedCredentials";
import { allowCredentials } from "./allowCredentials";
import { VcVerifiablePresentation, vcProtocol } from "./postMessageInterface";
Expand Down Expand Up @@ -129,7 +133,7 @@ const verifyCredentials = async ({
const userNumber = allowed.userNumber;

// For the rest of the flow we need to be authenticated, so authenticate
const authResult = await useIdentity({
let authResult = await useIdentity({
userNumber,
connection,
allowPinLogin: true,
Expand All @@ -142,6 +146,38 @@ const verifyCredentials = async ({

authResult satisfies { kind: unknown };

// There are three supported origins. I wanted to give the user a chance to cancel once the correct one and maybe still make it work.
// Therefore, the max retries should allow to iterate through all the 3 origins twice.
const MAX_RETRIES = 5;
let currentRetry = 0;
// This is ugly, but I couldn't find a better way to handle the retry without disrupting the whole flow.
while (
authResult.kind === "possiblyWrongWebAuthnFlow" &&
currentRetry < MAX_RETRIES
) {
currentRetry++;
const i18n = new I18n();
const copy = i18n.i18n(infoToastCopy);
toast.info(
infoToastTemplate({
title: copy.title_trying_again,
messages: [copy.message_possibly_wrong_web_authn_flow_1],
})
);
authResult = await useIdentity({
userNumber,
connection,
allowPinLogin: true,
});

if ("tag" in authResult) {
authResult satisfies { tag: "canceled" };
return "aborted";
}

authResult satisfies { kind: unknown };
}

if (authResult.kind !== "loginSuccess") {
return abortedCredentials({ reason: "auth_failed_ii" });
}
Expand Down
8 changes: 8 additions & 0 deletions src/frontend/src/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@

--rs-marketing-block-stack: calc(var(--vs-stack) * 5);

--rs-toasts-brezel: var(--vs-bezel);
--rs-toast-body-stack: calc(var(--vs-stack) * 0.5);
--rs-toast-stack: var(--rs-toast-body-stack);

Expand Down Expand Up @@ -1419,6 +1420,13 @@ by all browsers (FF is missing) */
margin: 0 auto calc(var(--rs-footer-height) + 0.5rem);
}

@media screen and (max-width: 512px) {
.c-toasts {
min-width: unset;
padding: 0 var(--rs-toasts-brezel);
}
}

.c-toast + .c-toast {
margin-top: var(--rs-toast-stack);
}
Expand Down
11 changes: 11 additions & 0 deletions src/frontend/src/test-e2e/flows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ export const FLOWS = {
const mainView = new MainView(browser);
await mainView.waitForDeviceDisplay(deviceName);
},
loginExistingAuthenticateView: async (
userNumber: string,
deviceName: string,
browser: WebdriverIO.Browser
): Promise<void> => {
const authenticateView = new AuthenticateView(browser);
await authenticateView.waitForDisplay();
await authenticateView.continueWithAnchor(userNumber);
const mainView = new MainView(browser);
await mainView.waitForDeviceDisplay(deviceName);
},
loginPinAuthenticateView: async (
userNumber: string,
pin: string,
Expand Down
Loading

0 comments on commit bc2c015

Please sign in to comment.