Skip to content

Commit 0539714

Browse files
author
Llorenç Muntaner
authored
User can link generic openID providers (#3304)
* Link open id providers * Pending commit * Show multiple buttons one line and remove logs * Improve loading * Rename prop * Change state for authenticating provider id * CR changes * Add authScope
1 parent e928a35 commit 0539714

File tree

4 files changed

+128
-24
lines changed

4 files changed

+128
-24
lines changed

src/frontend/src/lib/components/views/AccessMethodsPanel.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
isSameAccessMethod(removableOpenIdCredential, lastUsedAccessMethod),
7272
);
7373
74-
const handleGoogleLinked = (credential: OpenIdCredential) => {
74+
const handleOpenIDLinked = (credential: OpenIdCredential) => {
7575
openIdCredentials.push(credential);
7676
invalidateAll();
7777
};
@@ -279,7 +279,7 @@
279279
{#if isAddAccessMethodWizardOpen}
280280
{#if $ADD_ACCESS_METHOD}
281281
<AddAccessMethodWizard
282-
onGoogleLinked={handleGoogleLinked}
282+
onOpenIDLinked={handleOpenIDLinked}
283283
onPasskeyRegistered={handlePasskeyRegistered}
284284
onOtherDeviceRegistered={handleOtherDeviceRegistered}
285285
onClose={() => (isAddAccessMethodWizardOpen = false)}

src/frontend/src/lib/components/wizards/addAccessMethod/AddAccessMethodWizard.svelte

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
import type {
77
AuthnMethodData,
88
OpenIdCredential,
9+
OpenIdConfig,
910
} from "$lib/generated/internet_identity_types";
1011
import AddAccessMethod from "$lib/components/wizards/addAccessMethod/views/AddAccessMethod.svelte";
1112
import AddPasskey from "$lib/components/wizards/addAccessMethod/views/AddPasskey.svelte";
1213
import { ConfirmAccessMethodWizard } from "$lib/components/wizards/confirmAccessMethod";
1314
1415
interface Props {
15-
onGoogleLinked: (credential: OpenIdCredential) => void;
16+
onOpenIDLinked: (credential: OpenIdCredential) => void;
1617
onPasskeyRegistered: (credential: AuthnMethodData) => void;
1718
onOtherDeviceRegistered: () => void;
1819
onClose: () => void;
@@ -22,7 +23,7 @@
2223
}
2324
2425
const {
25-
onGoogleLinked,
26+
onOpenIDLinked,
2627
onPasskeyRegistered,
2728
onOtherDeviceRegistered,
2829
onClose,
@@ -42,7 +43,16 @@
4243
4344
const handleContinueWithGoogle = async () => {
4445
try {
45-
onGoogleLinked(await addAccessMethodFlow.linkGoogleAccount());
46+
onOpenIDLinked(await addAccessMethodFlow.linkGoogleAccount());
47+
onClose();
48+
} catch (error) {
49+
onError(error);
50+
}
51+
};
52+
53+
const handleContinueWithOpenId = async (config: OpenIdConfig) => {
54+
try {
55+
onOpenIDLinked(await addAccessMethodFlow.linkOpenIdAccount(config));
4656
onClose();
4757
} catch (error) {
4858
onError(error);
@@ -72,6 +82,7 @@
7282
<AddAccessMethod
7383
continueWithPasskey={addAccessMethodFlow.continueWithPasskey}
7484
linkGoogleAccount={handleContinueWithGoogle}
85+
linkOpenIdAccount={handleContinueWithOpenId}
7586
/>
7687
{:else if addAccessMethodFlow.view === "addPasskey"}
7788
<AddPasskey

src/frontend/src/lib/components/wizards/addAccessMethod/views/AddAccessMethod.svelte

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,48 @@
66
import Button from "$lib/components/ui/Button.svelte";
77
import GoogleIcon from "$lib/components/icons/GoogleIcon.svelte";
88
import { nonNullish } from "@dfinity/utils";
9+
import { canisterConfig } from "$lib/globals";
10+
import { ENABLE_GENERIC_OPEN_ID } from "$lib/state/featureFlags";
11+
import type { OpenIdConfig } from "$lib/generated/internet_identity_types";
912
1013
interface Props {
1114
linkGoogleAccount: () => Promise<void>;
15+
linkOpenIdAccount: (config: OpenIdConfig) => Promise<void>;
1216
continueWithPasskey: () => void;
1317
}
1418
15-
const { linkGoogleAccount, continueWithPasskey }: Props = $props();
19+
const { linkGoogleAccount, linkOpenIdAccount, continueWithPasskey }: Props =
20+
$props();
1621
17-
let authenticating = $state(false);
22+
let authenticatingGoogle = $state(false);
23+
let authenticatingProviderId = $state<string | null>();
24+
let authenticating = $derived(
25+
nonNullish(authenticatingProviderId) || authenticatingGoogle,
26+
);
1827
1928
const isPasskeySupported = nonNullish(window.PublicKeyCredential);
2029
2130
const handleContinueWithGoogle = async () => {
22-
authenticating = true;
31+
authenticatingGoogle = true;
2332
try {
2433
await linkGoogleAccount();
2534
} finally {
26-
authenticating = false;
35+
authenticatingGoogle = false;
2736
}
2837
};
38+
39+
const handleContinueWithOpenId = async (config: OpenIdConfig) => {
40+
authenticatingProviderId = config.client_id;
41+
try {
42+
await linkOpenIdAccount(config);
43+
} finally {
44+
authenticatingProviderId = null;
45+
}
46+
};
47+
48+
const showGoogleButton =
49+
canisterConfig.openid_google?.[0]?.[0] && !$ENABLE_GENERIC_OPEN_ID;
50+
const openIdProviders = canisterConfig.openid_configs?.[0] ?? [];
2951
</script>
3052

3153
<div class="mt-4 mb-6 flex flex-col">
@@ -36,7 +58,7 @@
3658
Add access method
3759
</h1>
3860
<p class="text-md text-text-tertiary font-medium text-balance sm:text-center">
39-
Add another way to sign in with a passkey or Google account for secure
61+
Add another way to sign in with a passkey or third-party account for secure
4062
access.
4163
</p>
4264
</div>
@@ -58,20 +80,42 @@
5880
<PasskeyIcon />
5981
Continue with Passkey
6082
</Button>
61-
<Button
62-
onclick={handleContinueWithGoogle}
63-
variant="secondary"
64-
disabled={authenticating}
65-
size="xl"
66-
>
67-
{#if authenticating}
68-
<ProgressRing />
69-
<span>Authenticating with Google...</span>
70-
{:else}
71-
<GoogleIcon />
72-
<span>Continue with Google</span>
73-
{/if}
74-
</Button>
83+
{#if $ENABLE_GENERIC_OPEN_ID}
84+
<div class="flex flex-row flex-nowrap justify-stretch gap-3">
85+
{#each openIdProviders as provider}
86+
<Button
87+
onclick={() => handleContinueWithOpenId(provider)}
88+
variant="secondary"
89+
disabled={authenticating}
90+
size="xl"
91+
class="flex-1"
92+
>
93+
{#if authenticatingProviderId === provider.client_id}
94+
<ProgressRing />
95+
{:else if provider.logo}
96+
<div class="size-6">
97+
{@html provider.logo}
98+
</div>
99+
{/if}
100+
</Button>
101+
{/each}
102+
</div>
103+
{:else if showGoogleButton}
104+
<Button
105+
onclick={handleContinueWithGoogle}
106+
variant="secondary"
107+
disabled={authenticating}
108+
size="xl"
109+
>
110+
{#if authenticatingGoogle}
111+
<ProgressRing />
112+
<span>Authenticating with Google...</span>
113+
{:else}
114+
<GoogleIcon />
115+
<span>Continue with Google</span>
116+
{/if}
117+
</Button>
118+
{/if}
75119
</div>
76120
</div>
77121

src/frontend/src/lib/flows/addAccessMethodFlow.svelte.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { throwCanisterError } from "$lib/utils/utils";
1212
import type {
1313
AuthnMethodData,
1414
OpenIdCredential,
15+
OpenIdConfig,
16+
MetadataMapV2,
1517
} from "$lib/generated/internet_identity_types";
1618
import { features } from "$lib/legacy/features";
1719
import { DiscoverableDummyIdentity } from "$lib/utils/discoverableDummyIdentity";
@@ -42,6 +44,53 @@ export class AddAccessMethodFlow {
4244
}
4345
}
4446

47+
linkOpenIdAccount = async (
48+
config: OpenIdConfig,
49+
): Promise<OpenIdCredential> => {
50+
const { identity, actor, identityNumber } = get(authenticatedStore);
51+
52+
try {
53+
this.#isSystemOverlayVisible = true;
54+
const { nonce, salt } = await createAnonymousNonce(
55+
identity.getPrincipal(),
56+
);
57+
const jwt = await requestJWT(
58+
{
59+
clientId: config.client_id,
60+
authURL: config.auth_uri,
61+
configURL: config.fedcm_uri?.[0],
62+
authScope: config.auth_scope.join(" "),
63+
},
64+
{
65+
nonce,
66+
mediation: "required",
67+
},
68+
);
69+
const { iss, sub, aud, name, email } = decodeJWTWithNameAndEmail(jwt);
70+
this.#isSystemOverlayVisible = false;
71+
await actor
72+
.openid_credential_add(identityNumber, jwt, salt)
73+
.then(throwCanisterError);
74+
75+
const metadata: MetadataMapV2 = [];
76+
if (nonNullish(name)) {
77+
metadata.push(["name", { String: name }]);
78+
}
79+
if (nonNullish(email)) {
80+
metadata.push(["email", { String: email }]);
81+
}
82+
return {
83+
aud,
84+
iss,
85+
sub,
86+
metadata,
87+
last_usage_timestamp: [],
88+
};
89+
} finally {
90+
this.#isSystemOverlayVisible = false;
91+
}
92+
};
93+
4594
linkGoogleAccount = async (): Promise<OpenIdCredential> => {
4695
const { identity, actor, identityNumber } = get(authenticatedStore);
4796

0 commit comments

Comments
 (0)