UNPKG

@auth/core

Version:

Authentication for the Web.

150 lines (149 loc) 5.98 kB
import { merge } from "./merge.js"; import { customFetch } from "../symbols.js"; /** * Adds `signinUrl` and `callbackUrl` to each provider * and deep merge user-defined options. */ export default function parseProviders(params) { const { providerId, config } = params; const url = new URL(config.basePath ?? "/auth", params.url.origin); const providers = config.providers.map((p) => { const provider = typeof p === "function" ? p() : p; const { options: userOptions, ...defaults } = provider; const id = (userOptions?.id ?? defaults.id); // TODO: Support if properties have different types, e.g. authorization: string or object const merged = merge(defaults, userOptions, { signinUrl: `${url}/signin/${id}`, callbackUrl: `${url}/callback/${id}`, }); if (provider.type === "oauth" || provider.type === "oidc") { merged.redirectProxyUrl ?? (merged.redirectProxyUrl = userOptions?.redirectProxyUrl ?? config.redirectProxyUrl); const normalized = normalizeOAuth(merged); // We currently don't support redirect proxies for response_mode=form_post if (normalized.authorization?.url.searchParams.get("response_mode") === "form_post") { delete normalized.redirectProxyUrl; } // @ts-expect-error Symbols don't get merged by the `merge` function // so we need to do it manually. normalized[customFetch] ?? (normalized[customFetch] = userOptions?.[customFetch]); return normalized; } return merged; }); const provider = providers.find(({ id }) => id === providerId); if (providerId && !provider) { const availableProviders = providers.map((p) => p.id).join(", "); throw new Error(`Provider with id "${providerId}" not found. Available providers: [${availableProviders}].`); } return { providers, provider }; } // TODO: Also add discovery here, if some endpoints/config are missing. // We should return both a client and authorization server config. function normalizeOAuth(c) { if (c.issuer) c.wellKnown ?? (c.wellKnown = `${c.issuer}/.well-known/openid-configuration`); const authorization = normalizeEndpoint(c.authorization, c.issuer); if (authorization && !authorization.url?.searchParams.has("scope")) { authorization.url.searchParams.set("scope", "openid profile email"); } const token = normalizeEndpoint(c.token, c.issuer); const userinfo = normalizeEndpoint(c.userinfo, c.issuer); const checks = c.checks ?? ["pkce"]; if (c.redirectProxyUrl) { if (!checks.includes("state")) checks.push("state"); c.redirectProxyUrl = `${c.redirectProxyUrl}/callback/${c.id}`; } return { ...c, authorization, token, checks, userinfo, profile: c.profile ?? defaultProfile, account: c.account ?? defaultAccount, }; } /** * Returns basic user profile from the userinfo response/`id_token` claims. * The returned `id` will become the `account.providerAccountId`. `user.id` * and `account.id` are auto-generated UUID's. * * The result if this function is used to create the `User` in the database. * @see https://authjs.dev/reference/core/adapters#user * @see https://openid.net/specs/openid-connect-core-1_0.html#IDToken * @see https://openid.net/specs/openid-connect-core-1_0.html# */ const defaultProfile = (profile) => { return stripUndefined({ id: profile.sub ?? profile.id ?? crypto.randomUUID(), name: profile.name ?? profile.nickname ?? profile.preferred_username, email: profile.email, image: profile.picture, }); }; /** * Returns basic OAuth/OIDC values from the token response. * @see https://www.ietf.org/rfc/rfc6749.html#section-5.1 * @see https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse * @see https://authjs.dev/reference/core/adapters#account */ const defaultAccount = (account) => { return stripUndefined({ access_token: account.access_token, id_token: account.id_token, refresh_token: account.refresh_token, expires_at: account.expires_at, scope: account.scope, token_type: account.token_type, session_state: account.session_state, }); }; function stripUndefined(o) { const result = {}; for (const [k, v] of Object.entries(o)) { if (v !== undefined) result[k] = v; } return result; } function normalizeEndpoint(e, issuer) { if (!e && issuer) return; if (typeof e === "string") { return { url: new URL(e) }; } // If e.url is undefined, it's because the provider config // assumes that we will use the issuer endpoint. // The existence of either e.url or provider.issuer is checked in // assert.ts. We fallback to "https://authjs.dev" to be able to pass around // a valid URL even if the user only provided params. // NOTE: This need to be checked when constructing the URL // for the authorization, token and userinfo endpoints. const url = new URL(e?.url ?? "https://authjs.dev"); if (e?.params != null) { for (let [key, value] of Object.entries(e.params)) { if (key === "claims") { value = JSON.stringify(value); } url.searchParams.set(key, String(value)); } } return { url, request: e?.request, conform: e?.conform, ...(e?.clientPrivateKey ? { clientPrivateKey: e?.clientPrivateKey } : null), }; } export function isOIDCProvider(provider) { return provider.type === "oidc"; } export function isOAuth2Provider(provider) { return provider.type === "oauth"; } /** Either OAuth 2 or OIDC */ export function isOAuthProvider(provider) { return provider.type === "oauth" || provider.type === "oidc"; }