feat(auth): add env variable for oidc-name-attribute-overwrite (#1850)
This commit is contained in:
@@ -74,6 +74,7 @@ export const env = createEnv({
|
|||||||
AUTH_OIDC_AUTO_LOGIN: booleanSchema,
|
AUTH_OIDC_AUTO_LOGIN: booleanSchema,
|
||||||
AUTH_OIDC_SCOPE_OVERWRITE: z.string().min(1).default("openid email profile groups"),
|
AUTH_OIDC_SCOPE_OVERWRITE: z.string().min(1).default("openid email profile groups"),
|
||||||
AUTH_OIDC_GROUPS_ATTRIBUTE: z.string().default("groups"), // Is used in the signIn event to assign the correct groups, key is from object of decoded id_token
|
AUTH_OIDC_GROUPS_ATTRIBUTE: z.string().default("groups"), // Is used in the signIn event to assign the correct groups, key is from object of decoded id_token
|
||||||
|
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE: z.string().optional(),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(authProviders.includes("ldap")
|
...(authProviders.includes("ldap")
|
||||||
@@ -117,6 +118,7 @@ export const env = createEnv({
|
|||||||
AUTH_LDAP_USER_MAIL_ATTRIBUTE: process.env.AUTH_LDAP_USER_MAIL_ATTRIBUTE,
|
AUTH_LDAP_USER_MAIL_ATTRIBUTE: process.env.AUTH_LDAP_USER_MAIL_ATTRIBUTE,
|
||||||
AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG: process.env.AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG,
|
AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG: process.env.AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG,
|
||||||
AUTH_OIDC_AUTO_LOGIN: process.env.AUTH_OIDC_AUTO_LOGIN,
|
AUTH_OIDC_AUTO_LOGIN: process.env.AUTH_OIDC_AUTO_LOGIN,
|
||||||
|
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE: process.env.AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE,
|
||||||
},
|
},
|
||||||
skipValidation,
|
skipValidation,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
|
|||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
|
|
||||||
import { env } from "./env.mjs";
|
import { env } from "./env.mjs";
|
||||||
|
import { extractProfileName } from "./providers/oidc/oidc-provider";
|
||||||
|
|
||||||
export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["events"], undefined>["signIn"] => {
|
export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["events"], undefined>["signIn"] => {
|
||||||
return async ({ user, profile }) => {
|
return async ({ user, profile }) => {
|
||||||
@@ -43,12 +44,18 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileUsername = profile?.preferred_username?.includes("@") ? profile.name : profile?.preferred_username;
|
if (profile) {
|
||||||
if (profileUsername && dbUser.name !== profileUsername) {
|
const profileUsername = extractProfileName(profile);
|
||||||
await db.update(users).set({ name: profileUsername }).where(eq(users.id, user.id));
|
if (!profileUsername) {
|
||||||
logger.info(
|
throw new Error(`OIDC provider did not return a name properties='${Object.keys(profile).join(",")}'`);
|
||||||
`Username for user of oidc provider has changed. user=${user.id} old='${dbUser.name}' new='${profileUsername}'`,
|
}
|
||||||
);
|
|
||||||
|
if (dbUser.name !== profileUsername) {
|
||||||
|
await db.update(users).set({ name: profileUsername }).where(eq(users.id, user.id));
|
||||||
|
logger.info(
|
||||||
|
`Username for user of oidc provider has changed. user=${user.id} old='${dbUser.name}' new='${profileUsername}'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`User '${dbUser.name}' logged in at ${dayjs().format()}`);
|
logger.info(`User '${dbUser.name}' logged in at ${dayjs().format()}`);
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
|
import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
|
||||||
import type { OIDCConfig } from "next-auth/providers";
|
import type { OIDCConfig } from "@auth/core/providers";
|
||||||
|
import type { Profile } from "@auth/core/types";
|
||||||
|
|
||||||
import { env } from "../../env.mjs";
|
import { env } from "../../env.mjs";
|
||||||
import { createRedirectUri } from "../../redirect";
|
import { createRedirectUri } from "../../redirect";
|
||||||
|
|
||||||
interface Profile {
|
|
||||||
sub: string;
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
groups: string[];
|
|
||||||
preferred_username: string;
|
|
||||||
email_verified: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig<Profile> => ({
|
export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig<Profile> => ({
|
||||||
id: "oidc",
|
id: "oidc",
|
||||||
name: env.AUTH_OIDC_CLIENT_NAME,
|
name: env.AUTH_OIDC_CLIENT_NAME,
|
||||||
@@ -28,12 +20,28 @@ export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig<Profil
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
profile(profile) {
|
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 {
|
return {
|
||||||
id: profile.sub,
|
id: profile.sub,
|
||||||
// Use the name as the username if the preferred_username is an email address
|
name,
|
||||||
name: profile.preferred_username.includes("@") ? profile.name : profile.preferred_username,
|
|
||||||
email: profile.email,
|
email: profile.email,
|
||||||
provider: "oidc",
|
provider: "oidc",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"AUTH_OIDC_ISSUER",
|
"AUTH_OIDC_ISSUER",
|
||||||
"AUTH_OIDC_SCOPE_OVERWRITE",
|
"AUTH_OIDC_SCOPE_OVERWRITE",
|
||||||
"AUTH_OIDC_GROUPS_ATTRIBUTE",
|
"AUTH_OIDC_GROUPS_ATTRIBUTE",
|
||||||
|
"AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE",
|
||||||
"AUTH_LDAP_USERNAME_ATTRIBUTE",
|
"AUTH_LDAP_USERNAME_ATTRIBUTE",
|
||||||
"AUTH_LDAP_USER_MAIL_ATTRIBUTE",
|
"AUTH_LDAP_USER_MAIL_ATTRIBUTE",
|
||||||
"AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG",
|
"AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG",
|
||||||
|
|||||||
Reference in New Issue
Block a user