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/throttler": "^5.2.0",
"@prisma/client": "^5.16.1",
"@types/jmespath": "^0.15.2",
"archiver": "^7.0.1",
"argon2": "^0.40.3",
"body-parser": "^1.20.2",
@@ -28,6 +29,7 @@
"class-validator": "^0.14.1",
"content-disposition": "^0.5.4",
"cookie-parser": "^1.4.6",
"jmespath": "^0.16.0",
"mime-types": "^2.1.35",
"moment": "^2.30.1",
"nanoid": "^3.3.7",
@@ -1994,6 +1996,12 @@
"@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": {
"version": "7.0.12",
"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"
}
},
"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": {
"version": "17.11.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz",

View File

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

View File

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

View File

@@ -27,7 +27,7 @@ export class AuthService {
) {}
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 hash = dto.password ? await argon.hash(dto.password) : null;
@@ -37,7 +37,7 @@ export class AuthService {
email: dto.email,
username: dto.username,
password: hash,
isAdmin: isFirstUser,
isAdmin: isAdmin ?? isFirstUser,
},
});
@@ -80,7 +80,7 @@ export class AuthService {
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);
}

View File

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

View File

@@ -46,13 +46,16 @@ export class OAuthService {
provider: user.provider,
providerUserId: user.providerId,
},
include: {
user: true,
},
});
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}`);
return this.auth.generateToken(oauthUser.user, true);
return this.auth.generateToken(updatedUser, true);
}
return this.signUp(user, ip);
@@ -150,6 +153,7 @@ export class OAuthService {
userId: existingUser.id,
},
});
await this.updateIsAdmin(user);
return this.auth.generateToken(existingUser, true);
}
@@ -160,6 +164,7 @@ export class OAuthService {
password: null,
},
ip,
user.isAdmin,
);
await this.prisma.oAuthUser.create({
@@ -173,4 +178,16 @@ export class OAuthService {
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[];
if (!guilds.some((guild) => guild.id === guildId)) {
throw new ErrorPageException("discord_guild_permission_denied");
throw new ErrorPageException("user_not_allowed");
}
} 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 { JwtService } from "@nestjs/jwt";
import { Cache } from "cache-manager";
import * as jmespath from "jmespath";
import { nanoid } from "nanoid";
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
@@ -108,6 +109,11 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
token: OAuthToken<OidcToken>,
query: OAuthCallbackDto,
claim?: string,
roleConfig?: {
path?: string;
generalAccess?: string;
adminAccess?: string;
},
): Promise<OAuthSignInDto> {
const idTokenData = this.decodeIdToken(token.idToken);
// maybe it's not necessary to verify the id token since it's directly obtained from the provider
@@ -127,6 +133,39 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
: idTokenData.preferred_username ||
idTokenData.name ||
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) {
this.logger.error(
@@ -146,6 +185,7 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
email: idTokenData.email,
providerId: idTokenData.sub,
providerUsername: username,
...(isAdmin !== undefined && { isAdmin }),
};
}

View File

@@ -34,6 +34,13 @@ export class OidcProvider extends GenericOidcProvider {
_?: string,
): Promise<OAuthSignInDto> {
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.unverified_account":
"لم يتم التحقق من حساب {0} هذا، يرجى المحاولة مرة أخرى بعد التحقق.",
"error.msg.discord_guild_permission_denied": "غير مسموح لك بتسجيل الدخول.",
"error.msg.user_not_allowed": "غير مسموح لك بتسجيل الدخول.",
"error.msg.cannot_get_user_info":
"فشلت عملية جلب معلومات المستخدم الخاصة بك من حساب {0} هذا.",
"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.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.old": "Altes 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.card.oauth.title": "Anmeldung über soziale Netzwerke",
"account.card.oauth.github": "GitHub",
@@ -85,7 +85,7 @@ export default {
"account.card.oauth.unlink": "Verknüpfung aufheben",
"account.card.oauth.unlinked": "Verknüpfung aufgehoben",
"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.card.security.title": "Sicherheit",
"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.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.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.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",
@@ -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.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.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.description": "Host des SMTP-Servers",
"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-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-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.description": "Client-ID der OpenID Connect OAuth-App",
"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.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.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.param.provider_github": "GitHub",
"error.param.provider_google": "Google",

View File

@@ -518,7 +518,7 @@ export default {
"Αυτός ο λογαριασμός {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.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.description":
"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.description":
"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.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.",
"error.msg.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.",
"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.",
"error.msg.discord_guild_permission_denied": "Nem jelentkezhet be.",
"error.msg.user_not_allowed": "Nem jelentkezhet be.",
"error.msg.cannot_get_user_info":
"Nem nyerhető ki felhasználói információ ebből a(z) {0} fiókból.",
"error.param.provider_github": "GitHub",

View File

@@ -512,7 +512,7 @@ export default {
"Questo account {0} non è ancora collegato ad alcun account.",
"error.msg.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"Non è possibile ottenere le informazioni utente da questo account {0}.",

View File

@@ -495,7 +495,7 @@ export default {
"この{0} アカウントはどのアカウントにもリンクされていません。",
"error.msg.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.",

View File

@@ -484,7 +484,7 @@ export default {
"이 {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.param.provider_github": "깃허브",

View File

@@ -504,7 +504,7 @@ export default {
"Dit {0} account is nog aan geen enkel account gekoppeld.",
"error.msg.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.",
"error.msg.unverified_account":
"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":
"Nie można uzyskać informacji o użytkowniku z tego konta {0}.",
"error.param.provider_github": "GitHub",

View File

@@ -516,7 +516,7 @@ export default {
"Esta conta {0} ainda não foi vinculada a nenhuma conta.",
"error.msg.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"Não é possível obter suas informações de usuário desta conta {0}.",

View File

@@ -500,7 +500,7 @@ export default {
"Эта учетная запись {0} ещё не привязана ни к одному аккаунту.",
"error.msg.unverified_account":
"Эта учетная запись {0} не подтверждена, повторите попытку после подтверждения.",
"error.msg.discord_guild_permission_denied": "У вас нет разрешения на вход.",
"error.msg.user_not_allowed": "У вас нет разрешения на вход.",
"error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.",
"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.unverified_account":
"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":
"Ne moremo najti uporabniških informacij za račun {0}.",
"error.param.provider_github": "GitHub",

View File

@@ -495,7 +495,7 @@ export default {
"Овај {0} налог још увек није повезан ни са једним налогом.",
"error.msg.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.",
"error.msg.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"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.unverified_account":
"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":
"Bu {0} hesabından kullanıcı bilgilerinizi alamıyorum.",
"error.param.provider_github": "GitHub",

View File

@@ -506,7 +506,7 @@ export default {
"Цей обліковий запис {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.param.provider_github": "GitHub",

View File

@@ -458,7 +458,7 @@ export default {
"error.msg.already_linked": "{0} 这个账户已经关联到另一个账号。",
"error.msg.not_linked": "{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.param.provider_github": "GitHub",
"error.param.provider_google": "谷歌",

View File

@@ -461,7 +461,7 @@ export default {
"error.msg.not_linked": "此 {0} 帳號尚未關聯到任何帳號。",
"error.msg.unverified_account":
"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.",
"error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.",