diff --git a/backend/prisma/seed/config.seed.ts b/backend/prisma/seed/config.seed.ts index 18730637..8216329a 100644 --- a/backend/prisma/seed/config.seed.ts +++ b/backend/prisma/seed/config.seed.ts @@ -201,6 +201,10 @@ const configVariables: ConfigVariables = { type: "string", defaultValue: "", }, + "oidc-usernameClaim": { + type: "string", + defaultValue: "", + }, "oidc-clientId": { type: "string", defaultValue: "", diff --git a/backend/src/oauth/provider/genericOidc.provider.ts b/backend/src/oauth/provider/genericOidc.provider.ts index 8bcd4c3e..977a9e42 100644 --- a/backend/src/oauth/provider/genericOidc.provider.ts +++ b/backend/src/oauth/provider/genericOidc.provider.ts @@ -108,6 +108,7 @@ export abstract class GenericOidcProvider implements OAuthProvider { async getUserInfo( token: OAuthToken, query: OAuthCallbackDto, + claim?: string ): Promise { const idTokenData = this.decodeIdToken(token.idToken); // maybe it's not necessary to verify the id token since it's directly obtained from the provider @@ -122,11 +123,30 @@ export abstract class GenericOidcProvider implements OAuthProvider { throw new ErrorPageException("invalid_token"); } + const username = claim + ? idTokenData[claim] + : idTokenData.name || + idTokenData.nickname || + idTokenData.preferred_username; + + if (!username) { + this.logger.error( + `Can not get username from ID Token ${JSON.stringify( + idTokenData, + undefined, + 2, + )}`, + ); + throw new ErrorPageException("cannot_get_user_info", undefined, [ + `provider_${this.name}`, + ]); + } + return { provider: this.name as any, email: idTokenData.email, providerId: idTokenData.sub, - providerUsername: idTokenData.name, + providerUsername: username, }; } @@ -211,5 +231,7 @@ export interface OidcIdToken { iat: number; email: string; name: string; + nickname: string; + preferred_username: string; nonce: string; } diff --git a/backend/src/oauth/provider/oidc.provider.ts b/backend/src/oauth/provider/oidc.provider.ts index 8a123381..984290da 100644 --- a/backend/src/oauth/provider/oidc.provider.ts +++ b/backend/src/oauth/provider/oidc.provider.ts @@ -1,9 +1,12 @@ -import { GenericOidcProvider } from "./genericOidc.provider"; +import { GenericOidcProvider, OidcToken } from "./genericOidc.provider"; import { Inject, Injectable } from "@nestjs/common"; import { ConfigService } from "../../config/config.service"; import { JwtService } from "@nestjs/jwt"; import { CACHE_MANAGER } from "@nestjs/cache-manager"; import { Cache } from "cache-manager"; +import { OAuthCallbackDto } from "../dto/oauthCallback.dto"; +import { OAuthSignInDto } from "../dto/oauthSignIn.dto"; +import { OAuthToken } from "./oauthProvider.interface"; @Injectable() export class OidcProvider extends GenericOidcProvider { @@ -24,4 +27,13 @@ export class OidcProvider extends GenericOidcProvider { protected getDiscoveryUri(): string { return this.config.get("oauth.oidc-discoveryUri"); } + + getUserInfo( + token: OAuthToken, + query: OAuthCallbackDto, + _?: string, + ): Promise { + const claim = this.config.get("oauth.oidc-usernameClaim") || undefined; + return super.getUserInfo(token, query, claim); + } } diff --git a/frontend/src/i18n/translations/en-US.ts b/frontend/src/i18n/translations/en-US.ts index c56cd2ba..2b010a43 100644 --- a/frontend/src/i18n/translations/en-US.ts +++ b/frontend/src/i18n/translations/en-US.ts @@ -478,14 +478,16 @@ export default { "admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app", "admin.config.oauth.discord-client-secret": "Discord Client secret", "admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app", - "admin.config.oauth.oidc-enabled": "OpenID", - "admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled", - "admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI", - "admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app", - "admin.config.oauth.oidc-client-id": "OpenID Client ID", - "admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app", - "admin.config.oauth.oidc-client-secret": "OpenID Client secret", - "admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app", + "admin.config.oauth.oidc-enabled": "OpenID Connect", + "admin.config.oauth.oidc-enabled.description": "Whether OpenID Connect login is enabled", + "admin.config.oauth.oidc-discovery-uri": "OpenID Connect Discovery URI", + "admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID Connect OAuth app", + "admin.config.oauth.oidc-username-claim": "OpenID Connect username claim", + "admin.config.oauth.oidc-username-claim.description": "Username claim in OpenID Connect ID token. Leave it blank if you don't know what this config is.", + "admin.config.oauth.oidc-client-id": "OpenID Connect Client ID", + "admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID Connect OAuth app", + "admin.config.oauth.oidc-client-secret": "OpenID Connect Client secret", + "admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID Connect OAuth app", // 404 "404.description": "Oops this page doesn't exist.", @@ -505,11 +507,12 @@ export default { "error.msg.not_linked": "This {0} account haven't linked to any account yet.", "error.msg.unverified_account": "This {0} account is unverified, please try again after verification.", "error.msg.discord_guild_permission_denied": "You are not allowed to sign in.", + "error.msg.cannot_get_user_info": "Can not get your user info from this {0} account.", "error.param.provider_github": "GitHub", "error.param.provider_google": "Google", "error.param.provider_microsoft": "Microsoft", "error.param.provider_discord": "Discord", - "error.param.provider_oidc": "OpenID", + "error.param.provider_oidc": "OpenID Connect", // Common translations "common.button.save": "Save",