Skip to content

Commit b73281f

Browse files
committed
refactor(web): extract getEffectiveProviderId helper
Shared between signIn callback and getIssuerUrlForAccount. The latter previously read only the top-level provider id, which silently returned undefined for factory-based providers with an `id:` override (Bitbucket Cloud, GitHub, GitLab, Google, Okta, Keycloak, Entra, Authentik), meaning the stored issuerUrl was never resolved correctly for those accounts.
1 parent 85af3fe commit b73281f

1 file changed

Lines changed: 22 additions & 29 deletions

File tree

packages/web/src/auth.ts

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export const getProviders = () => {
129129
} else {
130130
if (!user.hashedPassword) {
131131
return null;
132-
}
132+
}
133133

134134
if (!bcrypt.compareSync(password, user.hashedPassword)) {
135135
return null;
@@ -241,24 +241,7 @@ const nextAuthResult = NextAuth({
241241
callbacks: {
242242
async signIn({ account }) {
243243
const matchingProvider = account
244-
? getProviders().find((p) => {
245-
// NextAuth/Auth.js provider factories (e.g. Bitbucket,
246-
// GitHub, GitLab) hardcode a default `id` at the top of
247-
// the returned object and nest the caller's options
248-
// (including any `id` override) under `.options`. At
249-
// runtime the framework merges options over the
250-
// top-level defaults, so the effective provider id can
251-
// live under either field depending on whether the
252-
// caller passed an override. Read `.options.id` first
253-
// and fall back to the top-level `id`.
254-
const config = (
255-
typeof p.provider === 'function'
256-
? (p.provider as unknown as () => unknown)()
257-
: p.provider
258-
) as { id?: string; options?: { id?: string } };
259-
const providerId = config.options?.id ?? config.id;
260-
return providerId === account.provider;
261-
})
244+
? getProviders().find((p) => getEffectiveProviderId(p.provider) === account.provider)
262245
: undefined;
263246

264247
// Refuse OAuth signin for providers configured purely for account
@@ -282,7 +265,6 @@ const nextAuthResult = NextAuth({
282265
// new orphan identity with no UserToOrg row.
283266
const isAccountLinkingAttempt = matchingProvider?.purpose === 'account_linking';
284267
const session = await auth();
285-
286268
if (isAccountLinkingAttempt && session === null) {
287269
return false;
288270
}
@@ -417,18 +399,29 @@ export const auth = cache(async (): Promise<Session | null> => {
417399
return nextAuthResult.auth();
418400
});
419401

402+
// NextAuth/Auth.js provider factories (e.g. Bitbucket, GitHub, GitLab) hardcode
403+
// a default `id` at the top of the returned object and nest the caller's
404+
// options (including any `id` override) under `.options`. At runtime the
405+
// framework merges options over the top-level defaults, so the effective
406+
// provider id can live under either field depending on whether the caller
407+
// passed an override. Read `.options.id` first and fall back to the top-level
408+
// `id`. The function form of `Provider` is part of the NextAuth type union but
409+
// unused in this codebase; we handle it for type completeness.
410+
const getEffectiveProviderId = (provider: Provider): string | undefined => {
411+
const config = (
412+
typeof provider === 'function'
413+
? (provider as unknown as () => unknown)()
414+
: provider
415+
) as { id?: string; options?: { id?: string } };
416+
return config.options?.id ?? config.id;
417+
}
418+
420419
/**
421420
* Returns the issuer URL for a given auth.js account
422421
*/
423422
const getIssuerUrlForAccount = async (account: { provider: string; }) => {
424-
const providers = getProviders();
425-
const matchingProvider = providers.find((provider) => {
426-
if (typeof provider.provider === "function") {
427-
const providerInfo = provider.provider();
428-
return providerInfo.id === account.provider;
429-
} else {
430-
return provider.provider.id === account.provider;
431-
}
432-
});
423+
const matchingProvider = getProviders().find(
424+
(p) => getEffectiveProviderId(p.provider) === account.provider
425+
);
433426
return matchingProvider?.issuerUrl;
434427
}

0 commit comments

Comments
 (0)