Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): add env variable for oidc-name-attribute-overwrite #1850

Merged
merged 2 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/auth/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const env = createEnv({
AUTH_OIDC_AUTO_LOGIN: booleanSchema,
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_NAME_ATTRIBUTE_OVERWRITE: z.string().optional(),
}
: {}),
...(authProviders.includes("ldap")
Expand Down Expand Up @@ -117,6 +118,7 @@ export const env = createEnv({
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_OIDC_AUTO_LOGIN: process.env.AUTH_OIDC_AUTO_LOGIN,
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE: process.env.AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE,
},
skipValidation,
});
19 changes: 13 additions & 6 deletions packages/auth/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
import { logger } from "@homarr/log";

import { env } from "./env.mjs";
import { extractProfileName } from "./providers/oidc/oidc-provider";

export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["events"], undefined>["signIn"] => {
return async ({ user, profile }) => {
Expand Down Expand Up @@ -43,12 +44,18 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
);
}

const profileUsername = profile?.preferred_username?.includes("@") ? profile.name : profile?.preferred_username;
if (profileUsername && 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}'`,
);
if (profile) {
const profileUsername = extractProfileName(profile);
if (!profileUsername) {
throw new Error(`OIDC provider did not return a name properties='${Object.keys(profile).join(",")}'`);
}

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()}`);
Expand Down
32 changes: 20 additions & 12 deletions packages/auth/providers/oidc/oidc-provider.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
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 { 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> => ({
id: "oidc",
name: env.AUTH_OIDC_CLIENT_NAME,
Expand All @@ -28,12 +20,28 @@ export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig<Profil
},
},
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,
// Use the name as the username if the preferred_username is an email address
name: profile.preferred_username.includes("@") ? profile.name : profile.preferred_username,
name,
email: profile.email,
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;
};
1 change: 1 addition & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"AUTH_OIDC_ISSUER",
"AUTH_OIDC_SCOPE_OVERWRITE",
"AUTH_OIDC_GROUPS_ATTRIBUTE",
"AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE",
"AUTH_LDAP_USERNAME_ATTRIBUTE",
"AUTH_LDAP_USER_MAIL_ATTRIBUTE",
"AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG",
Expand Down
Loading