Skip to content

Commit 044c5b8

Browse files
atergasea-snake
andauthored
feat: Enable new identity attribute verified_email (#3640)
# Motivation Verified emails help app developers by informing then whether particular emails have actually been verified. # Changes <!-- List the changes that have been developed --> # Tests Added "with verified_email attribute" e2e test for the happy scenario. Edge cases specific to verified emails were already tested in Rust. --------- Co-authored-by: sea-snake <sea-snake@outlook.com>
1 parent 4bba927 commit 044c5b8

File tree

11 files changed

+70
-4
lines changed

11 files changed

+70
-4
lines changed

.github/workflows/canister-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ jobs:
518518
svg='<svg viewBox=\\\"0 0 24 24\\\"><path d=\\\"m14 2.8-2.6 1.8v16.6l2.6-1.4zm-3.3 5.4C1.9 9 2 14.7 2 14.7c0 5.6 8.7 6.5 8.7 6.5v-1.9c-6.3-1-5.5-4.5-5.5-4.5.3-4 5.5-4.3 5.5-4.3Zm4 0v2.1s1.6 0 3 1.2l-1.5.6 5.8 1.4V9l-2 1.1s-1.7-1.7-5.3-1.9z\\\" style=\\\"fill: currentColor;\\\"></path></svg>'
519519
configs=()
520520
for port in ${{ env.openid_providers }}; do
521-
configs+=("record { name = \\\"Test OpenID $port\\\"; logo = \\\"$svg\\\"; issuer = \\\"http://localhost:$port\\\"; client_id = \\\"internet_identity\\\"; jwks_uri = \\\"http://localhost:$port/jwks\\\"; auth_uri = \\\"http://localhost:$port/auth\\\"; auth_scope = vec { \\\"openid\\\"; \\\"profile\\\"; \\\"email\\\" }; fedcm_uri = opt \\\"\\\"; }")
521+
configs+=("record { name = \\\"Test OpenID $port\\\"; logo = \\\"$svg\\\"; issuer = \\\"http://localhost:$port\\\"; client_id = \\\"internet_identity\\\"; jwks_uri = \\\"http://localhost:$port/jwks\\\"; auth_uri = \\\"http://localhost:$port/auth\\\"; auth_scope = vec { \\\"openid\\\"; \\\"profile\\\"; \\\"email\\\" }; fedcm_uri = opt \\\"\\\"; email_verification = opt variant { Google }; }")
522522
done
523523
openid_configs="$(IFS='; '; echo "${configs[*]}")"
524524
echo "OPENID_CONFIGS=$openid_configs" >> "$GITHUB_OUTPUT"

src/frontend/src/lib/generated/internet_identity_idl.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ export const idlFactory = ({ IDL }) => {
1515
'api_host' : IDL.Opt(IDL.Text),
1616
}),
1717
});
18+
const OpenIdEmailVerification = IDL.Variant({
19+
'Google' : IDL.Null,
20+
'Unknown' : IDL.Null,
21+
'Microsoft' : IDL.Null,
22+
});
1823
const OpenIdConfig = IDL.Record({
1924
'auth_uri' : IDL.Text,
2025
'jwks_uri' : IDL.Text,
2126
'logo' : IDL.Text,
2227
'name' : IDL.Text,
2328
'fedcm_uri' : IDL.Opt(IDL.Text),
29+
'email_verification' : IDL.Opt(OpenIdEmailVerification),
2430
'issuer' : IDL.Text,
2531
'auth_scope' : IDL.Vec(IDL.Text),
2632
'client_id' : IDL.Text,
@@ -899,12 +905,18 @@ export const init = ({ IDL }) => {
899905
'api_host' : IDL.Opt(IDL.Text),
900906
}),
901907
});
908+
const OpenIdEmailVerification = IDL.Variant({
909+
'Google' : IDL.Null,
910+
'Unknown' : IDL.Null,
911+
'Microsoft' : IDL.Null,
912+
});
902913
const OpenIdConfig = IDL.Record({
903914
'auth_uri' : IDL.Text,
904915
'jwks_uri' : IDL.Text,
905916
'logo' : IDL.Text,
906917
'name' : IDL.Text,
907918
'fedcm_uri' : IDL.Opt(IDL.Text),
919+
'email_verification' : IDL.Opt(OpenIdEmailVerification),
908920
'issuer' : IDL.Text,
909921
'auth_scope' : IDL.Vec(IDL.Text),
910922
'client_id' : IDL.Text,

src/frontend/src/lib/generated/internet_identity_types.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,7 @@ export interface OpenIdConfig {
812812
'logo' : string,
813813
'name' : string,
814814
'fedcm_uri' : [] | [string],
815+
'email_verification' : [] | [OpenIdEmailVerification],
815816
'issuer' : string,
816817
'auth_scope' : Array<string>,
817818
'client_id' : string,
@@ -838,6 +839,9 @@ export type OpenIdDelegationError = { 'NoSuchDelegation' : null } |
838839
{ 'NoSuchAnchor' : null } |
839840
{ 'JwtExpired' : null } |
840841
{ 'JwtVerificationFailed' : null };
842+
export type OpenIdEmailVerification = { 'Google' : null } |
843+
{ 'Unknown' : null } |
844+
{ 'Microsoft' : null };
841845
export interface OpenIdPrepareDelegationResponse {
842846
'user_key' : UserKey,
843847
'expiration' : Timestamp,

src/frontend/src/lib/utils/lastUsedIdentity.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ describe("lastUsedIdentityTypeName", () => {
5757
logo: "https://example.com/logo.png",
5858
fedcm_uri: [],
5959
auth_scope: ["openid"],
60+
email_verification: [],
6061
},
6162
],
6263
],
@@ -88,6 +89,7 @@ describe("lastUsedIdentityTypeName", () => {
8889
logo: "https://other.example/logo.png",
8990
fedcm_uri: [],
9091
auth_scope: ["openid"],
92+
email_verification: [],
9193
},
9294
],
9395
],

src/frontend/src/lib/utils/openID.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ const createOpenIDConfig = (issuer: string): OpenIdConfig => ({
177177
issuer,
178178
auth_scope: ["test"],
179179
client_id: "test",
180+
email_verification: [],
180181
});
181182

182183
describe("findConfig", () => {

src/frontend/src/routes/(new-styling)/(resuming-channel)/resume-openid-authorize/+page.svelte

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
const implicitConsentAttributeKeys = paramsResult.data.attributes.filter(
5454
(attribute) =>
5555
attribute === `openid:${issuer}:name` ||
56-
attribute === `openid:${issuer}:email`,
56+
attribute === `openid:${issuer}:email` ||
57+
attribute === `openid:${issuer}:verified_email`,
5758
);
5859
try {
5960
const { attributes, issued_at_timestamp_ns } =
@@ -111,6 +112,7 @@
111112
112113
onMount(async () => {
113114
const searchParams = new URLSearchParams(window.location.hash.slice(1));
115+
console.log("Search params:", window.location.hash);
114116
window.history.replaceState(
115117
undefined,
116118
"",
@@ -143,8 +145,12 @@
143145
});
144146
directOpenIdFunnel.trigger(DirectOpenIdEvents.CallbackFromOpenId);
145147
const authFlowResult = await authFlow.continueWithOpenId(config, jwt);
148+
const { name, email } = decodeJWT(jwt);
146149
if (authFlowResult.type === "signUp") {
147-
await authFlow.completeOpenIdRegistration(authFlowResult.name!);
150+
await authFlow.completeOpenIdRegistration(
151+
// Prefer name, then email prefix, then fallback to a generic name
152+
name ?? email?.split("@")[0] ?? $t`${config.name} user`,
153+
);
148154
}
149155
if (
150156
dapp?.certifiedAttributes === true ||

src/frontend/tests/e2e-playwright/authorize/openid.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,42 @@ test.describe("Authorize with direct OpenID", () => {
143143
await signInWithOpenId(authorizePage.page, openIdUsers[0].id);
144144
});
145145
});
146+
147+
test.describe("with verified_email attribute", () => {
148+
const email = "john.doe@example.com";
149+
150+
test.use({
151+
openIdConfig: {
152+
defaultPort: DEFAULT_OPENID_PORT,
153+
createUsers: [
154+
{
155+
claims: { email, email_verified: "true" },
156+
},
157+
],
158+
},
159+
authorizeConfig: {
160+
protocol: "icrc25",
161+
openid: `http://localhost:${DEFAULT_OPENID_PORT}`,
162+
attributes: [
163+
`openid:http://localhost:${DEFAULT_OPENID_PORT}:verified_email`,
164+
],
165+
},
166+
});
167+
168+
test.afterEach(({ authorizedPrincipal, authorizedAttributes }) => {
169+
expect(authorizedPrincipal?.isAnonymous()).toBe(false);
170+
expect(authorizedAttributes).toEqual({
171+
[`openid:http://localhost:${DEFAULT_OPENID_PORT}:verified_email`]:
172+
email,
173+
});
174+
});
175+
176+
test("should return verified email", async ({
177+
authorizePage,
178+
signInWithOpenId,
179+
openIdUsers,
180+
}) => {
181+
await signInWithOpenId(authorizePage.page, openIdUsers[0].id);
182+
});
183+
});
146184
});

src/internet_identity/internet_identity.did

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ type JWT = text;
367367
type Salt = blob;
368368

369369
type OpenIdEmailVerification = variant {
370+
Unknown;
370371
Google;
371372
Microsoft;
372373
};

src/internet_identity/src/openid.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ impl OpenIdCredential {
222222
let verification_scheme = provider.email_verification_scheme()?;
223223

224224
match verification_scheme {
225+
Unknown => None,
225226
Google => self.get_google_verified_email(),
226227
Microsoft => self.get_microsoft_verified_email(),
227228
}

src/internet_identity_interface/src/internet_identity/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ pub enum DeployArchiveResult {
393393

394394
#[derive(Clone, Copy, Debug, CandidType, Serialize, Deserialize, Eq, PartialEq)]
395395
pub enum OpenIdEmailVerificationScheme {
396+
Unknown,
396397
Google,
397398
Microsoft,
398399
}

0 commit comments

Comments
 (0)