diff --git a/package.json b/package.json index bb595442cf..87dd45436d 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "private": true, "license": "SEE LICENSE IN LICENSE.md", "scripts": { - "dev": "II_FETCH_ROOT_KEY=1 II_DUMMY_CAPTCHA=1 vite", - "host": "II_FETCH_ROOT_KEY=1 II_DUMMY_CAPTCHA=1 vite --host", + "dev": "II_FETCH_ROOT_KEY=1 II_DUMMY_CAPTCHA=1 II_OPENID_GOOGLE_CLIENT_ID=45431994619-cbbfgtn7o0pp0dpfcg2l66bc4rcg7qbu.apps.googleusercontent.com vite", + "host": "II_FETCH_ROOT_KEY=1 II_DUMMY_CAPTCHA=1 II_OPENID_GOOGLE_CLIENT_ID=45431994619-cbbfgtn7o0pp0dpfcg2l66bc4rcg7qbu.apps.googleusercontent.com vite --host", "showcase": "astro dev --root ./src/showcase", "build": "tsc --noEmit && vite build", "check": "tsc --project ./tsconfig.all.json --noEmit", diff --git a/src/canister_tests/src/framework.rs b/src/canister_tests/src/framework.rs index 0bc05dc661..e980a61d2a 100644 --- a/src/canister_tests/src/framework.rs +++ b/src/canister_tests/src/framework.rs @@ -433,7 +433,7 @@ xr-spatial-tracking=()", let rgx = Regex::new( "^default-src 'none';\ connect-src 'self' https:;\ -img-src 'self' data:;\ +img-src 'self' data: https://\\*.googleusercontent.com;\ script-src 'strict-dynamic' ('[^']+' )*'unsafe-inline' 'unsafe-eval' https:;\ base-uri 'none';\ form-action 'none';\ diff --git a/src/frontend/src/components/icons.ts b/src/frontend/src/components/icons.ts index f82f1829d6..0c8be576f7 100644 --- a/src/frontend/src/components/icons.ts +++ b/src/frontend/src/components/icons.ts @@ -445,3 +445,29 @@ export const cypherIcon = html` /> `; + +export const googleIcon = html` + + + + + + +`; diff --git a/src/frontend/src/environment.ts b/src/frontend/src/environment.ts index ed36419d65..1b98538d67 100644 --- a/src/frontend/src/environment.ts +++ b/src/frontend/src/environment.ts @@ -5,3 +5,5 @@ export const VERSION = import.meta.env.II_VERSION ?? ""; export const FETCH_ROOT_KEY = import.meta.env.II_FETCH_ROOT_KEY === "1"; export const DUMMY_AUTH = import.meta.env.II_DUMMY_AUTH === "1"; export const DUMMY_CAPTCHA = import.meta.env.II_DUMMY_CAPTCHA === "1"; +export const II_OPENID_GOOGLE_CLIENT_ID = import.meta.env + .II_OPENID_GOOGLE_CLIENT_ID; diff --git a/src/frontend/src/featureFlags/index.ts b/src/frontend/src/featureFlags/index.ts index 0b0c8d8aa4..14cd4c9b91 100644 --- a/src/frontend/src/featureFlags/index.ts +++ b/src/frontend/src/featureFlags/index.ts @@ -1,6 +1,7 @@ // Feature flags with default values const FEATURE_FLAGS_WITH_DEFAULTS = { DOMAIN_COMPATIBILITY: false, + OPENID_AUTHENTICATION: false, } as const satisfies Record; const LOCALSTORAGE_FEATURE_FLAGS_PREFIX = "ii-localstorage-feature-flags__"; @@ -63,4 +64,5 @@ const initializedFeatureFlags = Object.fromEntries( window.__featureFlags = initializedFeatureFlags; // Export initialized feature flags as named exports -export const { DOMAIN_COMPATIBILITY } = initializedFeatureFlags; +export const { DOMAIN_COMPATIBILITY, OPENID_AUTHENTICATION } = + initializedFeatureFlags; diff --git a/src/frontend/src/flows/manage/index.ts b/src/frontend/src/flows/manage/index.ts index c05f94d57e..e647d662d7 100644 --- a/src/frontend/src/flows/manage/index.ts +++ b/src/frontend/src/flows/manage/index.ts @@ -15,10 +15,13 @@ import { logoutSection } from "$src/components/logout"; import { mainWindow } from "$src/components/mainWindow"; import { toast } from "$src/components/toast"; import { ENABLE_PIN_QUERY_PARAM_KEY, LEGACY_II_URL } from "$src/config"; +import { OPENID_AUTHENTICATION } from "$src/featureFlags"; import { addDevice } from "$src/flows/addDevice/manage/addDevice"; import { dappsExplorer } from "$src/flows/dappsExplorer"; import { KnownDapp, getDapps } from "$src/flows/dappsExplorer/dapps"; import { dappsHeader, dappsTeaser } from "$src/flows/dappsExplorer/teaser"; +import { linkedAccountsSection } from "$src/flows/manage/linkedAccountsSection"; +import copyJson from "$src/flows/manage/linkedAccountsSection.json"; import { TempKeyWarningAction, tempKeyWarningBox, @@ -29,6 +32,14 @@ import { setupKey, setupPhrase } from "$src/flows/recovery/setupRecovery"; import { I18n } from "$src/i18n"; import { AuthenticatedConnection, Connection } from "$src/utils/iiConnection"; import { TemplateElement, renderPage } from "$src/utils/lit-html"; +import { OpenIDCredential } from "$src/utils/mockOpenID"; +import { + GOOGLE_REQUEST_CONFIG, + createAnonymousNonce, + decodeJWT, + isPermissionError, + requestJWT, +} from "$src/utils/openID"; import { PreLoadImage } from "$src/utils/preLoadImage"; import { isProtected, @@ -147,6 +158,9 @@ const displayManageTemplate = ({ onAddDevice, addRecoveryPhrase, addRecoveryKey, + credentials, + onLinkAccount, + onUnlinkAccount, dapps, exploreDapps, identityBackground, @@ -157,6 +171,9 @@ const displayManageTemplate = ({ onAddDevice: () => void; addRecoveryPhrase: () => void; addRecoveryKey: () => void; + credentials: OpenIDCredential[]; + onLinkAccount: () => void; + onUnlinkAccount: (credential: OpenIDCredential) => void; dapps: KnownDapp[]; exploreDapps: () => void; identityBackground: PreLoadImage; @@ -182,6 +199,14 @@ const displayManageTemplate = ({ onAddDevice, warnNoPasskeys, })} + ${OPENID_AUTHENTICATION.isEnabled() + ? linkedAccountsSection({ + credentials, + onLinkAccount, + onUnlinkAccount, + hasOtherAuthMethods: authenticators.length > 0, + }) + : ""} ${recoveryMethodsSection({ recoveries, addRecoveryPhrase, addRecoveryKey })}