feat(auth): Add role-based access management from OpenID Connect (#535)

* feat(auth): Add role-based access management from OpenID Connect

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>

* Apply suggestions from code review

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>

---------

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>
This commit is contained in:
Marvin A. Ruder
2024-07-17 23:25:42 +02:00
committed by GitHub
parent e5a0c649e3
commit 70fd2d94be
33 changed files with 160 additions and 38 deletions

View File

@@ -19,6 +19,7 @@
"@nestjs/swagger": "^7.3.1", "@nestjs/swagger": "^7.3.1",
"@nestjs/throttler": "^5.2.0", "@nestjs/throttler": "^5.2.0",
"@prisma/client": "^5.16.1", "@prisma/client": "^5.16.1",
"@types/jmespath": "^0.15.2",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"argon2": "^0.40.3", "argon2": "^0.40.3",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
@@ -28,6 +29,7 @@
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"content-disposition": "^0.5.4", "content-disposition": "^0.5.4",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"jmespath": "^0.16.0",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"moment": "^2.30.1", "moment": "^2.30.1",
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
@@ -1994,6 +1996,12 @@
"@types/range-parser": "*" "@types/range-parser": "*"
} }
}, },
"node_modules/@types/jmespath": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz",
"integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==",
"license": "MIT"
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.12", "version": "7.0.12",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
@@ -5523,6 +5531,15 @@
"url": "https://github.com/chalk/supports-color?sponsor=1" "url": "https://github.com/chalk/supports-color?sponsor=1"
} }
}, },
"node_modules/jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
"license": "Apache-2.0",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/joi": { "node_modules/joi": {
"version": "17.11.0", "version": "17.11.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz",

View File

@@ -24,6 +24,7 @@
"@nestjs/swagger": "^7.3.1", "@nestjs/swagger": "^7.3.1",
"@nestjs/throttler": "^5.2.0", "@nestjs/throttler": "^5.2.0",
"@prisma/client": "^5.16.1", "@prisma/client": "^5.16.1",
"@types/jmespath": "^0.15.2",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"argon2": "^0.40.3", "argon2": "^0.40.3",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
@@ -33,6 +34,7 @@
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"content-disposition": "^0.5.4", "content-disposition": "^0.5.4",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"jmespath": "^0.16.0",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"moment": "^2.30.1", "moment": "^2.30.1",
"nanoid": "^3.3.7", "nanoid": "^3.3.7",

View File

@@ -230,6 +230,18 @@ const configVariables: ConfigVariables = {
type: "string", type: "string",
defaultValue: "", defaultValue: "",
}, },
"oidc-rolePath": {
type: "string",
defaultValue: "",
},
"oidc-roleGeneralAccess": {
type: "string",
defaultValue: "",
},
"oidc-roleAdminAccess": {
type: "string",
defaultValue: "",
},
"oidc-clientId": { "oidc-clientId": {
type: "string", type: "string",
defaultValue: "", defaultValue: "",

View File

@@ -27,7 +27,7 @@ export class AuthService {
) {} ) {}
private readonly logger = new Logger(AuthService.name); private readonly logger = new Logger(AuthService.name);
async signUp(dto: AuthRegisterDTO, ip: string) { async signUp(dto: AuthRegisterDTO, ip: string, isAdmin?: boolean) {
const isFirstUser = (await this.prisma.user.count()) == 0; const isFirstUser = (await this.prisma.user.count()) == 0;
const hash = dto.password ? await argon.hash(dto.password) : null; const hash = dto.password ? await argon.hash(dto.password) : null;
@@ -37,7 +37,7 @@ export class AuthService {
email: dto.email, email: dto.email,
username: dto.username, username: dto.username,
password: hash, password: hash,
isAdmin: isFirstUser, isAdmin: isAdmin ?? isFirstUser,
}, },
}); });
@@ -80,7 +80,7 @@ export class AuthService {
throw new UnauthorizedException("Wrong email or password"); throw new UnauthorizedException("Wrong email or password");
} }
this.logger.log(`Successful login for user ${dto.email} from IP ${ip}`); this.logger.log(`Successful login for user ${user.email} from IP ${ip}`);
return this.generateToken(user); return this.generateToken(user);
} }

View File

@@ -3,4 +3,5 @@ export interface OAuthSignInDto {
providerId: string; providerId: string;
providerUsername: string; providerUsername: string;
email: string; email: string;
isAdmin?: boolean;
} }

View File

@@ -46,13 +46,16 @@ export class OAuthService {
provider: user.provider, provider: user.provider,
providerUserId: user.providerId, providerUserId: user.providerId,
}, },
include: {
user: true,
},
}); });
if (oauthUser) { if (oauthUser) {
await this.updateIsAdmin(user);
const updatedUser = await this.prisma.user.findFirst({
where: {
email: user.email,
},
});
this.logger.log(`Successful login for user ${user.email} from IP ${ip}`); this.logger.log(`Successful login for user ${user.email} from IP ${ip}`);
return this.auth.generateToken(oauthUser.user, true); return this.auth.generateToken(updatedUser, true);
} }
return this.signUp(user, ip); return this.signUp(user, ip);
@@ -150,6 +153,7 @@ export class OAuthService {
userId: existingUser.id, userId: existingUser.id,
}, },
}); });
await this.updateIsAdmin(user);
return this.auth.generateToken(existingUser, true); return this.auth.generateToken(existingUser, true);
} }
@@ -160,6 +164,7 @@ export class OAuthService {
password: null, password: null,
}, },
ip, ip,
user.isAdmin,
); );
await this.prisma.oAuthUser.create({ await this.prisma.oAuthUser.create({
@@ -173,4 +178,16 @@ export class OAuthService {
return result; return result;
} }
private async updateIsAdmin(user: OAuthSignInDto) {
if ("isAdmin" in user)
await this.prisma.user.update({
where: {
email: user.email,
},
data: {
isAdmin: user.isAdmin,
},
});
}
} }

View File

@@ -101,10 +101,10 @@ export class DiscordProvider implements OAuthProvider<DiscordToken> {
}); });
const guilds = (await res.json()) as DiscordPartialGuild[]; const guilds = (await res.json()) as DiscordPartialGuild[];
if (!guilds.some((guild) => guild.id === guildId)) { if (!guilds.some((guild) => guild.id === guildId)) {
throw new ErrorPageException("discord_guild_permission_denied"); throw new ErrorPageException("user_not_allowed");
} }
} catch { } catch {
throw new ErrorPageException("discord_guild_permission_denied"); throw new ErrorPageException("user_not_allowed");
} }
} }
} }

View File

@@ -2,6 +2,7 @@ import { Logger } from "@nestjs/common";
import { ConfigService } from "../../config/config.service"; import { ConfigService } from "../../config/config.service";
import { JwtService } from "@nestjs/jwt"; import { JwtService } from "@nestjs/jwt";
import { Cache } from "cache-manager"; import { Cache } from "cache-manager";
import * as jmespath from "jmespath";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { OAuthCallbackDto } from "../dto/oauthCallback.dto"; import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface"; import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
@@ -108,6 +109,11 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
token: OAuthToken<OidcToken>, token: OAuthToken<OidcToken>,
query: OAuthCallbackDto, query: OAuthCallbackDto,
claim?: string, claim?: string,
roleConfig?: {
path?: string;
generalAccess?: string;
adminAccess?: string;
},
): Promise<OAuthSignInDto> { ): Promise<OAuthSignInDto> {
const idTokenData = this.decodeIdToken(token.idToken); const idTokenData = this.decodeIdToken(token.idToken);
// maybe it's not necessary to verify the id token since it's directly obtained from the provider // maybe it's not necessary to verify the id token since it's directly obtained from the provider
@@ -128,6 +134,39 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
idTokenData.name || idTokenData.name ||
idTokenData.nickname; idTokenData.nickname;
let isAdmin: boolean;
if (roleConfig?.path) {
// A path to read roles from the token is configured
let roles: string[] | null;
try {
roles = jmespath.search(idTokenData, roleConfig.path);
} catch (e) {
roles = null;
}
if (Array.isArray(roles)) {
// Roles are found in the token
if (roleConfig.generalAccess && !roles.includes(roleConfig.generalAccess)) {
// Role for general access is configured and the user does not have it
this.logger.error(`User roles ${roles} do not include ${roleConfig.generalAccess}`);
throw new ErrorPageException("user_not_allowed");
}
if (roleConfig.adminAccess) {
// Role for admin access is configured
isAdmin = roles.includes(roleConfig.adminAccess);
}
} else {
this.logger.error(
`Roles not found at path ${roleConfig.path} in ID Token ${JSON.stringify(
idTokenData,
undefined,
2,
)}`,
);
throw new ErrorPageException("user_not_allowed");
}
}
if (!username) { if (!username) {
this.logger.error( this.logger.error(
`Can not get username from ID Token ${JSON.stringify( `Can not get username from ID Token ${JSON.stringify(
@@ -146,6 +185,7 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
email: idTokenData.email, email: idTokenData.email,
providerId: idTokenData.sub, providerId: idTokenData.sub,
providerUsername: username, providerUsername: username,
...(isAdmin !== undefined && { isAdmin }),
}; };
} }

View File

@@ -34,6 +34,13 @@ export class OidcProvider extends GenericOidcProvider {
_?: string, _?: string,
): Promise<OAuthSignInDto> { ): Promise<OAuthSignInDto> {
const claim = this.config.get("oauth.oidc-usernameClaim") || undefined; const claim = this.config.get("oauth.oidc-usernameClaim") || undefined;
return super.getUserInfo(token, query, claim); const rolePath = this.config.get("oauth.oidc-rolePath") || undefined;
const roleGeneralAccess = this.config.get("oauth.oidc-roleGeneralAccess") || undefined;
const roleAdminAccess = this.config.get("oauth.oidc-roleAdminAccess") || undefined;
return super.getUserInfo(token, query, claim, {
path: rolePath,
generalAccess: roleGeneralAccess,
adminAccess: roleAdminAccess,
});
} }
} }

View File

@@ -485,7 +485,7 @@ export default {
"error.msg.not_linked": "لم يتم ربط حساب {0} هذا بأي حساب حتى الآن.", "error.msg.not_linked": "لم يتم ربط حساب {0} هذا بأي حساب حتى الآن.",
"error.msg.unverified_account": "error.msg.unverified_account":
"لم يتم التحقق من حساب {0} هذا، يرجى المحاولة مرة أخرى بعد التحقق.", "لم يتم التحقق من حساب {0} هذا، يرجى المحاولة مرة أخرى بعد التحقق.",
"error.msg.discord_guild_permission_denied": "غير مسموح لك بتسجيل الدخول.", "error.msg.user_not_allowed": "غير مسموح لك بتسجيل الدخول.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"فشلت عملية جلب معلومات المستخدم الخاصة بك من حساب {0} هذا.", "فشلت عملية جلب معلومات المستخدم الخاصة بك من حساب {0} هذا.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",

View File

@@ -494,7 +494,7 @@ export default {
"error.msg.not_linked": "This {0} account haven't linked to any account yet.", "error.msg.not_linked": "This {0} account haven't linked to any account yet.",
"error.msg.unverified_account": "error.msg.unverified_account":
"This {0} account is unverified, please try again after verification.", "This {0} account is unverified, please try again after verification.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"Du har ikke tilladelse til at logge ind.", "Du har ikke tilladelse til at logge ind.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.", "Can not get your user info from this {0} account.",

View File

@@ -73,7 +73,7 @@ export default {
"account.card.password.title": "Passwort", "account.card.password.title": "Passwort",
"account.card.password.old": "Altes Passwort", "account.card.password.old": "Altes Passwort",
"account.card.password.new": "Neues Passwort", "account.card.password.new": "Neues Passwort",
"account.card.password.noPasswordSet": "Du hast kein Passwort erstellt. Wenn Du Dich mit E-Mail und Passwort anmelden möchtest, musst Du ein Passwort festlegen.", "account.card.password.noPasswordSet": "Du hast kein Passwort erstellt. Wenn du dich mit E-Mail und Passwort anmelden möchtest, musst du ein Passwort festlegen.",
"account.notify.password.success": "Passwort erfolgreich geändert", "account.notify.password.success": "Passwort erfolgreich geändert",
"account.card.oauth.title": "Anmeldung über soziale Netzwerke", "account.card.oauth.title": "Anmeldung über soziale Netzwerke",
"account.card.oauth.github": "GitHub", "account.card.oauth.github": "GitHub",
@@ -85,7 +85,7 @@ export default {
"account.card.oauth.unlink": "Verknüpfung aufheben", "account.card.oauth.unlink": "Verknüpfung aufheben",
"account.card.oauth.unlinked": "Verknüpfung aufgehoben", "account.card.oauth.unlinked": "Verknüpfung aufgehoben",
"account.modal.unlink.title": "Kontoverknüpfung aufheben", "account.modal.unlink.title": "Kontoverknüpfung aufheben",
"account.modal.unlink.description": "Das Entfernen der Verknüpfung mit Deinem sozialen Konten kann dazu führen, dass Du Dein Konto verlierst, wenn Du Dich nicht an Deinen Benutzernamen und Dein Passwort erinnerst.", "account.modal.unlink.description": "Das Entfernen der Verknüpfung mit deinem sozialen Konten kann dazu führen, dass du dein Konto verlierst, wenn du dich nicht an deinen Benutzernamen und dein Passwort erinnerst.",
"account.notify.oauth.unlinked.success": "Verknüpfung erfolgreich aufgehoben", "account.notify.oauth.unlinked.success": "Verknüpfung erfolgreich aufgehoben",
"account.card.security.title": "Sicherheit", "account.card.security.title": "Sicherheit",
"account.card.security.totp.enable.description": "Gib dein aktuelles Passwort ein, um TOTP zu aktivieren", "account.card.security.totp.enable.description": "Gib dein aktuelles Passwort ein, um TOTP zu aktivieren",
@@ -301,7 +301,7 @@ export default {
"admin.config.general.logo.description": "Ändere dein Logo durch Hochladen eines Bildes. Das Bild muss im PNG-Format vorliegen und sollte mit Seitenverhältnis 1:1 sein.", "admin.config.general.logo.description": "Ändere dein Logo durch Hochladen eines Bildes. Das Bild muss im PNG-Format vorliegen und sollte mit Seitenverhältnis 1:1 sein.",
"admin.config.general.logo.placeholder": "Bild auswählen", "admin.config.general.logo.placeholder": "Bild auswählen",
"admin.config.email.enable-share-email-recipients": "Erlaube das Teilen der Freigabe via Email", "admin.config.email.enable-share-email-recipients": "Erlaube das Teilen der Freigabe via Email",
"admin.config.email.enable-share-email-recipients.description": "Gibt an, ob Emails an Freigabe-Empfänger ermöglicht werden sollen. Aktiviere dies nur, wenn Du SMTP aktivierst hast.", "admin.config.email.enable-share-email-recipients.description": "Gibt an, ob Emails an Freigabe-Empfänger ermöglicht werden sollen. Aktiviere dies nur, wenn du SMTP aktivierst hast.",
"admin.config.email.share-recipients-subject": "Betreff für Freigabe-Empfänger", "admin.config.email.share-recipients-subject": "Betreff für Freigabe-Empfänger",
"admin.config.email.share-recipients-subject.description": "Betreff der E-Mail, die an die Freigabe-Empfänger gesendet wird.", "admin.config.email.share-recipients-subject.description": "Betreff der E-Mail, die an die Freigabe-Empfänger gesendet wird.",
"admin.config.email.share-recipients-message": "Nachricht für Freigabe-Empfänger", "admin.config.email.share-recipients-message": "Nachricht für Freigabe-Empfänger",
@@ -333,7 +333,7 @@ export default {
"admin.config.share.auto-open-share-modal": "Auto open create share modal", "admin.config.share.auto-open-share-modal": "Auto open create share modal",
"admin.config.share.auto-open-share-modal.description": "The share creation modal automatically appears when a user selects files, eliminating the need to manually click the button.", "admin.config.share.auto-open-share-modal.description": "The share creation modal automatically appears when a user selects files, eliminating the need to manually click the button.",
"admin.config.smtp.enabled": "Aktiviert", "admin.config.smtp.enabled": "Aktiviert",
"admin.config.smtp.enabled.description": "Gibt an, ob SMTP aktiviert ist. Aktiviere dies nur, wenn Du den Host, den Port, die Email, den Benutzernamen und das Passwort deines SMTP-Servers eingegeben hast.", "admin.config.smtp.enabled.description": "Gibt an, ob SMTP aktiviert ist. Aktiviere dies nur, wenn du den Host, den Port, die Email, den Benutzernamen und das Passwort deines SMTP-Servers eingegeben hast.",
"admin.config.smtp.host": "Host", "admin.config.smtp.host": "Host",
"admin.config.smtp.host.description": "Host des SMTP-Servers", "admin.config.smtp.host.description": "Host des SMTP-Servers",
"admin.config.smtp.port": "Port", "admin.config.smtp.port": "Port",
@@ -387,6 +387,19 @@ export default {
"admin.config.oauth.oidc-discovery-uri.description": "Discovery-URL der OpenID OAuth App", "admin.config.oauth.oidc-discovery-uri.description": "Discovery-URL der OpenID OAuth App",
"admin.config.oauth.oidc-username-claim": "OpenID Connect Benutzername anfordern", "admin.config.oauth.oidc-username-claim": "OpenID Connect Benutzername anfordern",
"admin.config.oauth.oidc-username-claim.description": "Benutzername im OpenID Token. Leer lassen, wenn du nicht weißt, was diese Konfiguration bedeutet.", "admin.config.oauth.oidc-username-claim.description": "Benutzername im OpenID Token. Leer lassen, wenn du nicht weißt, was diese Konfiguration bedeutet.",
"admin.config.oauth.oidc-role-path": "Path to roles in OpenID Connect token",
"admin.config.oauth.oidc-role-path.description":
"Muss ein valider JMES-Pfad sein, der zu einem Array an Rollen führt. " +
"Die Zugangsverwaltung über Rollen in OpenID Connect ist nur empfohlen, wenn kein anderer Identitätsprovider konfiguriert und die Anmeldung per Password deaktiviert ist. " +
"Leer lassen, wenn du nicht weißt, was diese Konfiguration bedeutet.",
"admin.config.oauth.oidc-role-general-access": "OpenID Connect role for general access",
"admin.config.oauth.oidc-role-general-access.description":
"Rolle für generellen Zugriff. Muss Teil der Rollen eines Benutzers sein, damit dieser sich anmelden kann. " +
"Leer lassen, wenn du nicht weißt, was diese Konfiguration bedeutet.",
"admin.config.oauth.oidc-role-admin-access": "OpenID Connect role for admin access",
"admin.config.oauth.oidc-role-admin-access.description":
"Rolle für administrativen Zugriff. Muss Teil der Rollen eines Benutzers sein, damit dieser auf das Administrator-Panel zugreifen kann. " +
"Leer lassen, wenn du nicht weißt, was diese Konfiguration bedeutet.",
"admin.config.oauth.oidc-client-id": "OpenID Connect Client-ID", "admin.config.oauth.oidc-client-id": "OpenID Connect Client-ID",
"admin.config.oauth.oidc-client-id.description": "Client-ID der OpenID Connect OAuth-App", "admin.config.oauth.oidc-client-id.description": "Client-ID der OpenID Connect OAuth-App",
"admin.config.oauth.oidc-client-secret": "OpenID Connect Client-Secret", "admin.config.oauth.oidc-client-secret": "OpenID Connect Client-Secret",
@@ -407,7 +420,7 @@ export default {
"error.msg.already_linked": "Das Konto {0} ist bereits mit einem anderen Konto verknüpft.", "error.msg.already_linked": "Das Konto {0} ist bereits mit einem anderen Konto verknüpft.",
"error.msg.not_linked": "Das Konto {0} wurde noch nicht mit einem Konto verknüpft.", "error.msg.not_linked": "Das Konto {0} wurde noch nicht mit einem Konto verknüpft.",
"error.msg.unverified_account": "Dieses Konto {0} wurde noch nicht verifiziert, bitte versuche es nach der Verifikation erneut.", "error.msg.unverified_account": "Dieses Konto {0} wurde noch nicht verifiziert, bitte versuche es nach der Verifikation erneut.",
"error.msg.discord_guild_permission_denied": "Du bist nicht berechtigt, Dich anzumelden.", "error.msg.user_not_allowed": "Du bist nicht berechtigt, dich anzumelden.",
"error.msg.cannot_get_user_info": "Deine Benutzerinformationen können nicht von diesem Konto {0} abgerufen werden.", "error.msg.cannot_get_user_info": "Deine Benutzerinformationen können nicht von diesem Konto {0} abgerufen werden.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",
"error.param.provider_google": "Google", "error.param.provider_google": "Google",

View File

@@ -518,7 +518,7 @@ export default {
"Αυτός ο λογαριασμός {0} δεν έχει συνδεθεί με κανένα λογαριασμό ακόμα.", "Αυτός ο λογαριασμός {0} δεν έχει συνδεθεί με κανένα λογαριασμό ακόμα.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Αυτός ο λογαριασμός {0} δεν έχει επαληθευτεί, παρακαλώ προσπαθήστε ξανά μετά την επαλήθευση.", "Αυτός ο λογαριασμός {0} δεν έχει επαληθευτεί, παρακαλώ προσπαθήστε ξανά μετά την επαλήθευση.",
"error.msg.discord_guild_permission_denied": "Δεν σας επιτρέπεται η σύνδεση.", "error.msg.user_not_allowed": "Δεν σας επιτρέπεται η σύνδεση.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Δεν είναι δυνατή η λήψη των πληροφοριών χρήστη από αυτόν τον λογαριασμό {0}.", "Δεν είναι δυνατή η λήψη των πληροφοριών χρήστη από αυτόν τον λογαριασμό {0}.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",

View File

@@ -539,6 +539,19 @@ export default {
"admin.config.oauth.oidc-username-claim": "OpenID Connect username claim", "admin.config.oauth.oidc-username-claim": "OpenID Connect username claim",
"admin.config.oauth.oidc-username-claim.description": "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.", "Username claim in OpenID Connect ID token. Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-path": "Path to roles in OpenID Connect token",
"admin.config.oauth.oidc-role-path.description":
"Must be a valid JMES path referencing an array of roles. " +
"Managing access rights using OpenID Connect roles is only recommended if no other identity provider is configured and password login is disabled. " +
"Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-general-access": "OpenID Connect role for general access",
"admin.config.oauth.oidc-role-general-access.description":
"Role required for general access. Must be present in a users roles for them to log in. " +
"Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-admin-access": "OpenID Connect role for admin access",
"admin.config.oauth.oidc-role-admin-access.description":
"Role required for administrative access. Must be present in a users roles for them to access the admin panel. " +
"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": "OpenID Connect Client ID",
"admin.config.oauth.oidc-client-id.description": "admin.config.oauth.oidc-client-id.description":
"Client ID of the OpenID Connect OAuth app", "Client ID of the OpenID Connect OAuth app",
@@ -567,7 +580,7 @@ export default {
"error.msg.not_linked": "This {0} account haven't linked to any account yet.", "error.msg.not_linked": "This {0} account haven't linked to any account yet.",
"error.msg.unverified_account": "error.msg.unverified_account":
"This {0} account is unverified, please try again after verification.", "This {0} account is unverified, please try again after verification.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"You are not allowed to sign in.", "You are not allowed to sign in.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.", "Can not get your user info from this {0} account.",

View File

@@ -509,7 +509,7 @@ export default {
"Esta cuenta {0} aún no ha sido vinculada a ninguna cuenta.", "Esta cuenta {0} aún no ha sido vinculada a ninguna cuenta.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Esta cuenta {0} no está verificada, por favor inténtalo de nuevo después de la verificación.", "Esta cuenta {0} no está verificada, por favor inténtalo de nuevo después de la verificación.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"No tienes permitido iniciar sesion.", "No tienes permitido iniciar sesion.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"No se puede obtener la información de usuario de la cuenta {0}.", "No se puede obtener la información de usuario de la cuenta {0}.",

View File

@@ -497,7 +497,7 @@ export default {
"error.msg.not_linked": "This {0} account haven't linked to any account yet.", "error.msg.not_linked": "This {0} account haven't linked to any account yet.",
"error.msg.unverified_account": "error.msg.unverified_account":
"This {0} account is unverified, please try again after verification.", "This {0} account is unverified, please try again after verification.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"You are not allowed to sign in.", "You are not allowed to sign in.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.", "Can not get your user info from this {0} account.",

View File

@@ -504,7 +504,7 @@ export default {
"error.msg.not_linked": "Le compte {0} nest pas encore associé à compte.", "error.msg.not_linked": "Le compte {0} nest pas encore associé à compte.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Le compte {0} n'est pas vérifié, veuillez réessayer après vérification.", "Le compte {0} n'est pas vérifié, veuillez réessayer après vérification.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"Vous nêtes pas autorisé à vous authentifier.", "Vous nêtes pas autorisé à vous authentifier.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Impossible dobtenir vos informations utilisateur à partir du compte {0}.", "Impossible dobtenir vos informations utilisateur à partir du compte {0}.",

View File

@@ -503,7 +503,7 @@ export default {
"Ez a(z){0} fiók még nem kapcsolódott egyetlen fiókhoz sem.", "Ez a(z){0} fiók még nem kapcsolódott egyetlen fiókhoz sem.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Ezt a(z) {0} fiókot még nem igazolták vissza, kérem próbálja újra a megerősítés után.", "Ezt a(z) {0} fiókot még nem igazolták vissza, kérem próbálja újra a megerősítés után.",
"error.msg.discord_guild_permission_denied": "Nem jelentkezhet be.", "error.msg.user_not_allowed": "Nem jelentkezhet be.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Nem nyerhető ki felhasználói információ ebből a(z) {0} fiókból.", "Nem nyerhető ki felhasználói információ ebből a(z) {0} fiókból.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",

View File

@@ -512,7 +512,7 @@ export default {
"Questo account {0} non è ancora collegato ad alcun account.", "Questo account {0} non è ancora collegato ad alcun account.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Questo account {0} non è verificato, per favore riprova dopo la verifica.", "Questo account {0} non è verificato, per favore riprova dopo la verifica.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"Non sei autorizzato ad accedere.", "Non sei autorizzato ad accedere.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Non è possibile ottenere le informazioni utente da questo account {0}.", "Non è possibile ottenere le informazioni utente da questo account {0}.",

View File

@@ -495,7 +495,7 @@ export default {
"この{0} アカウントはどのアカウントにもリンクされていません。", "この{0} アカウントはどのアカウントにもリンクされていません。",
"error.msg.unverified_account": "error.msg.unverified_account":
"This {0} account is unverified, please try again after verification.", "This {0} account is unverified, please try again after verification.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"You are not allowed to sign in.", "You are not allowed to sign in.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.", "Can not get your user info from this {0} account.",

View File

@@ -484,7 +484,7 @@ export default {
"이 {0} 계정은 아직 어떤 계정에도 연결되지 않았습니다.", "이 {0} 계정은 아직 어떤 계정에도 연결되지 않았습니다.",
"error.msg.unverified_account": "error.msg.unverified_account":
"이 {0} 계정은 확인되지 않았습니다. 확인 후 다시 시도하십시오.", "이 {0} 계정은 확인되지 않았습니다. 확인 후 다시 시도하십시오.",
"error.msg.discord_guild_permission_denied": "로그인할 수 없습니다.", "error.msg.user_not_allowed": "로그인할 수 없습니다.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"이 {0} 계정에서 사용자 정보를 가져올 수 없습니다", "이 {0} 계정에서 사용자 정보를 가져올 수 없습니다",
"error.param.provider_github": "깃허브", "error.param.provider_github": "깃허브",

View File

@@ -504,7 +504,7 @@ export default {
"Dit {0} account is nog aan geen enkel account gekoppeld.", "Dit {0} account is nog aan geen enkel account gekoppeld.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Dit {0} account is nog niet geverifieerd, probeer het opnieuw na de verificatie.", "Dit {0} account is nog niet geverifieerd, probeer het opnieuw na de verificatie.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"U heeft geen toestemming om in te loggen.", "U heeft geen toestemming om in te loggen.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Kan uw gebruikersgegevens van dit {0} account niet ophalen.", "Kan uw gebruikersgegevens van dit {0} account niet ophalen.",

View File

@@ -508,7 +508,7 @@ export default {
"To konto {0} nie zostało jeszcze połączone z żadnym kontem.", "To konto {0} nie zostało jeszcze połączone z żadnym kontem.",
"error.msg.unverified_account": "error.msg.unverified_account":
"To konto {0} nie zostało zweryfikowane, spróbuj ponownie po weryfikacji.", "To konto {0} nie zostało zweryfikowane, spróbuj ponownie po weryfikacji.",
"error.msg.discord_guild_permission_denied": "Nie możesz się zalogować.", "error.msg.user_not_allowed": "Nie możesz się zalogować.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Nie można uzyskać informacji o użytkowniku z tego konta {0}.", "Nie można uzyskać informacji o użytkowniku z tego konta {0}.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",

View File

@@ -516,7 +516,7 @@ export default {
"Esta conta {0} ainda não foi vinculada a nenhuma conta.", "Esta conta {0} ainda não foi vinculada a nenhuma conta.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Esta conta {0} não foi verificada, tente novamente após a verificação.", "Esta conta {0} não foi verificada, tente novamente após a verificação.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"Você não tem permissão para acessar.", "Você não tem permissão para acessar.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Não é possível obter suas informações de usuário desta conta {0}.", "Não é possível obter suas informações de usuário desta conta {0}.",

View File

@@ -500,7 +500,7 @@ export default {
"Эта учетная запись {0} ещё не привязана ни к одному аккаунту.", "Эта учетная запись {0} ещё не привязана ни к одному аккаунту.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Эта учетная запись {0} не подтверждена, повторите попытку после подтверждения.", "Эта учетная запись {0} не подтверждена, повторите попытку после подтверждения.",
"error.msg.discord_guild_permission_denied": "У вас нет разрешения на вход.", "error.msg.user_not_allowed": "У вас нет разрешения на вход.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.", "Can not get your user info from this {0} account.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",

View File

@@ -498,7 +498,7 @@ export default {
"error.msg.not_linked": "Račun {0} še ni povezan z nobenim računom.", "error.msg.not_linked": "Račun {0} še ni povezan z nobenim računom.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Račun {0} je nepreverjen, prosimo poskusite ponovno po preverjanju.", "Račun {0} je nepreverjen, prosimo poskusite ponovno po preverjanju.",
"error.msg.discord_guild_permission_denied": "Nimate dovoljenja za prijavo.", "error.msg.user_not_allowed": "Nimate dovoljenja za prijavo.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Ne moremo najti uporabniških informacij za račun {0}.", "Ne moremo najti uporabniških informacij za račun {0}.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",

View File

@@ -495,7 +495,7 @@ export default {
"Овај {0} налог још увек није повезан ни са једним налогом.", "Овај {0} налог још увек није повезан ни са једним налогом.",
"error.msg.unverified_account": "error.msg.unverified_account":
"This {0} account is unverified, please try again after verification.", "This {0} account is unverified, please try again after verification.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"You are not allowed to sign in.", "You are not allowed to sign in.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.", "Can not get your user info from this {0} account.",

View File

@@ -497,7 +497,7 @@ export default {
"Detta {0} konto har ännu inte länkat till något konto.", "Detta {0} konto har ännu inte länkat till något konto.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Detta {0} -konto är overifierat, försök igen efter verifiering.", "Detta {0} -konto är overifierat, försök igen efter verifiering.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"Du är inte tillåten att logga in.", "Du är inte tillåten att logga in.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Kan inte hämta din användarinformation från detta {0} konto.", "Kan inte hämta din användarinformation från detta {0} konto.",

View File

@@ -489,7 +489,7 @@ export default {
"error.msg.not_linked": "This {0} account haven't linked to any account yet.", "error.msg.not_linked": "This {0} account haven't linked to any account yet.",
"error.msg.unverified_account": "error.msg.unverified_account":
"This {0} account is unverified, please try again after verification.", "This {0} account is unverified, please try again after verification.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"You are not allowed to sign in.", "You are not allowed to sign in.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.", "Can not get your user info from this {0} account.",

View File

@@ -493,7 +493,7 @@ export default {
"error.msg.not_linked": "Bu {0} hesabı henüz bir hesaba bağlı değil.", "error.msg.not_linked": "Bu {0} hesabı henüz bir hesaba bağlı değil.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Bu {0} hesabı doğrulanmamış, lütfen doğruladıktan sonra yeniden dene.", "Bu {0} hesabı doğrulanmamış, lütfen doğruladıktan sonra yeniden dene.",
"error.msg.discord_guild_permission_denied": "Giriş yapmana izin verilmiyor.", "error.msg.user_not_allowed": "Giriş yapmana izin verilmiyor.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Bu {0} hesabından kullanıcı bilgilerinizi alamıyorum.", "Bu {0} hesabından kullanıcı bilgilerinizi alamıyorum.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",

View File

@@ -506,7 +506,7 @@ export default {
"Цей обліковий запис {0} ще не прив'язаний до жодного акаунту.", "Цей обліковий запис {0} ще не прив'язаний до жодного акаунту.",
"error.msg.unverified_account": "error.msg.unverified_account":
"Цей обліковий запис {0} не підтверджено, повторіть спробу після підтвердження.", "Цей обліковий запис {0} не підтверджено, повторіть спробу після підтвердження.",
"error.msg.discord_guild_permission_denied": "У вас немає дозволу на вхід.", "error.msg.user_not_allowed": "У вас немає дозволу на вхід.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Не вдається отримати інфу про користувача з цього {0} облікового запису.", "Не вдається отримати інфу про користувача з цього {0} облікового запису.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",

View File

@@ -458,7 +458,7 @@ export default {
"error.msg.already_linked": "{0} 这个账户已经关联到另一个账号。", "error.msg.already_linked": "{0} 这个账户已经关联到另一个账号。",
"error.msg.not_linked": "{0} 这个账户尚未关联到任何账号。", "error.msg.not_linked": "{0} 这个账户尚未关联到任何账号。",
"error.msg.unverified_account": "{0} 这个账户尚未验证,请在验证后重试。", "error.msg.unverified_account": "{0} 这个账户尚未验证,请在验证后重试。",
"error.msg.discord_guild_permission_denied": "您无权登录。", "error.msg.user_not_allowed": "您无权登录。",
"error.msg.cannot_get_user_info": "无法从 {0} 这个账户获取您的用户信息。", "error.msg.cannot_get_user_info": "无法从 {0} 这个账户获取您的用户信息。",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",
"error.param.provider_google": "谷歌", "error.param.provider_google": "谷歌",

View File

@@ -461,7 +461,7 @@ export default {
"error.msg.not_linked": "此 {0} 帳號尚未關聯到任何帳號。", "error.msg.not_linked": "此 {0} 帳號尚未關聯到任何帳號。",
"error.msg.unverified_account": "error.msg.unverified_account":
"This {0} account is unverified, please try again after verification.", "This {0} account is unverified, please try again after verification.",
"error.msg.discord_guild_permission_denied": "error.msg.user_not_allowed":
"You are not allowed to sign in.", "You are not allowed to sign in.",
"error.msg.cannot_get_user_info": "error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.", "Can not get your user info from this {0} account.",