Skip to content

Commit 359f407

Browse files
authored
Merge pull request #8 from datum-cloud/fix/github-idp-registration
Fix GitHub IdP mapping for names and private email
2 parents 538d4c4 + aa296a7 commit 359f407

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

  • apps/login/src/app/(main)/(boxed)/idp/[provider]/success

apps/login/src/app/(main)/(boxed)/idp/[provider]/success/page.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ConnectError, create } from "@zitadel/client";
2222
import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb";
2323
import { OrganizationSchema } from "@zitadel/proto/zitadel/object/v2/object_pb";
2424
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
25+
import { SetHumanEmailSchema } from "@zitadel/proto/zitadel/user/v2/email_pb";
2526
import {
2627
AddHumanUserRequest,
2728
AddHumanUserRequestSchema,
@@ -66,6 +67,47 @@ async function resolveOrganizationForUser({
6667
return undefined;
6768
}
6869

70+
type GithubEmailInfo = { email: string; verified: boolean } | undefined;
71+
72+
// getGithunPrimaryEmail fetches the primary email, even if the user has hidden it
73+
// user:email scope is required for private emails
74+
async function getGithubPrimaryEmail(token: string): Promise<GithubEmailInfo> {
75+
const fetchEmails = async () => {
76+
return fetch("https://api.github.com/user/emails", {
77+
headers: {
78+
Accept: "application/vnd.github+json",
79+
Authorization: `Bearer ${token}`,
80+
"X-GitHub-Api-Version": "2022-11-28",
81+
},
82+
cache: "no-store",
83+
});
84+
};
85+
86+
try {
87+
let res = await fetchEmails();
88+
if (!res.ok) {
89+
console.warn("GitHub email fetch failed", res.status);
90+
return undefined;
91+
}
92+
93+
const data: Array<{ email: string; primary: boolean; verified: boolean }> =
94+
await res.json();
95+
96+
const primary =
97+
data.find((e) => e.primary && e.verified) ||
98+
data.find((e) => e.primary) ||
99+
data[0];
100+
if (primary) {
101+
return { email: primary.email, verified: primary.verified };
102+
}
103+
104+
return undefined;
105+
} catch (err) {
106+
console.warn("GitHub email fetch error", err);
107+
return undefined;
108+
}
109+
}
110+
69111
export const metadata = generateRouteMetadata("idp");
70112
export default async function Page(props: {
71113
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
@@ -101,6 +143,57 @@ export default async function Page(props: {
101143
const { idpInformation, userId } = intent;
102144
let { addHumanUser } = intent;
103145

146+
// ensure mandatory profile fields are populated to satisfy ZITADEL validation, as GitHub does not provide givenName
147+
// and familyName
148+
if (addHumanUser) {
149+
const profile: any = addHumanUser.profile ?? {};
150+
let { givenName, familyName } = profile;
151+
152+
const fallback = idpInformation?.userName ?? "user";
153+
154+
if (!givenName || givenName.trim().length === 0) {
155+
givenName = fallback.slice(0, 200);
156+
}
157+
158+
if (!familyName || familyName.trim().length === 0) {
159+
familyName = fallback.slice(0, 200);
160+
}
161+
162+
addHumanUser = {
163+
...addHumanUser,
164+
profile: {
165+
...profile,
166+
givenName,
167+
familyName,
168+
},
169+
} as typeof addHumanUser;
170+
171+
// ensure email exists as well (GitHub may not return an address if user hides it)
172+
if (
173+
!addHumanUser.email?.email ||
174+
addHumanUser.email.email.trim().length === 0
175+
) {
176+
let newEmailInfo: GithubEmailInfo;
177+
const githubAccessToken: string | undefined = (idpInformation as any)
178+
?.access?.value?.accessToken;
179+
if (provider === "github") {
180+
if (githubAccessToken) {
181+
newEmailInfo = await getGithubPrimaryEmail(githubAccessToken);
182+
}
183+
}
184+
let finalEmail = newEmailInfo?.email ?? `${fallback}@idp.local`;
185+
const isVerified = newEmailInfo?.verified ?? false;
186+
187+
addHumanUser = {
188+
...addHumanUser,
189+
email: create(SetHumanEmailSchema, {
190+
email: finalEmail.slice(0, 200),
191+
verification: { case: "isVerified", value: isVerified },
192+
}),
193+
};
194+
}
195+
}
196+
104197
if (!idpInformation) {
105198
return loginFailed("IDP information missing");
106199
}

0 commit comments

Comments
 (0)