Files
Homarr/packages/auth/providers/oidc/oidc-provider.ts

81 lines
3.0 KiB
TypeScript

import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
import type { OIDCConfig } from "@auth/core/providers";
import type { Profile } from "@auth/core/types";
import { customFetch } from "next-auth";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { env } from "../../env";
import { createRedirectUri } from "../../redirect";
export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig<Profile> => ({
id: "oidc",
name: env.AUTH_OIDC_CLIENT_NAME,
type: "oidc",
clientId: env.AUTH_OIDC_CLIENT_ID,
clientSecret: env.AUTH_OIDC_CLIENT_SECRET,
issuer: env.AUTH_OIDC_ISSUER,
allowDangerousEmailAccountLinking: env.AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING,
authorization: {
params: {
scope: env.AUTH_OIDC_SCOPE_OVERWRITE,
// We fallback to https as generally oidc providers require https
redirect_uri: createRedirectUri(headers, "/api/auth/callback/oidc", "https"),
},
},
token: {
// Providers like fusionauth may return www-authenticate which results in an error
// https://github.com/nextauthjs/next-auth/issues/8745
// https://github.com/homarr-labs/homarr/issues/2690
conform: (response: Response) => {
if (response.status === 401) return response;
const newHeaders = Array.from(response.headers.entries())
.filter(([key]) => key.toLowerCase() !== "www-authenticate")
.reduce((headers, [key, value]) => {
headers.append(key, value);
return headers;
}, new Headers());
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
},
},
// idToken false forces the use of the userinfo endpoint
// Userinfo endpoint is required for authelia since v4.39
// See https://github.com/homarr-labs/homarr/issues/2635
idToken: !env.AUTH_OIDC_FORCE_USERINFO,
profile(profile) {
if (!profile.sub) {
throw new Error(`OIDC provider did not return a sub property='${Object.keys(profile).join(",")}'`);
}
const name = extractProfileName(profile);
if (!name) {
throw new Error(`OIDC provider did not return a name properties='${Object.keys(profile).join(",")}'`);
}
return {
id: profile.sub,
name,
email: profile.email,
provider: "oidc",
};
},
// The type for fetch is not identical, but for what we need it it's okay to not be an 1:1 match
// See documentation https://authjs.dev/guides/corporate-proxy?framework=next-js
// @ts-expect-error `undici` has a `duplex` option
[customFetch]: fetchWithTrustedCertificatesAsync,
});
export const extractProfileName = (profile: Profile) => {
if (!env.AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE) {
// Use the name as the username if the preferred_username is an email address
return profile.preferred_username?.includes("@") ? profile.name : profile.preferred_username;
}
return profile[env.AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE as keyof typeof profile] as string;
};