mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(enterprise): add SAML 2.0 SSO with SP-initiated login
Implements SAML SSO using @node-saml/node-saml, gated behind SAML_ENABLED env var and the saml_sso enterprise license feature. - SAML env vars (entity ID, callback URL, IdP SSO URL, IdP cert, auto-create/auto-link users, default role, provider name, username/email attribute mapping) with validation in superRefine - SAML plugin with three routes: metadata (GET), login (GET), and ACS callback (POST with form-urlencoded content type parser) - Callback uses the shared external-auth resolver for user resolution (same pattern as OIDC: match/link/create/deny) - Auth config endpoint exposes samlEnabled and samlProviderName - Session loginMethod detection updated for SAML auth provider - Frontend login page shows SAML SSO button when enabled - i18n strings for SAML error messages across all 21 locales
This commit is contained in:
+22
-1
@@ -30,6 +30,7 @@ import {
|
|||||||
ensureDefaultAdmin,
|
ensureDefaultAdmin,
|
||||||
} from "./plugins/auth.js";
|
} from "./plugins/auth.js";
|
||||||
import { oidcRoutes } from "./plugins/oidc.js";
|
import { oidcRoutes } from "./plugins/oidc.js";
|
||||||
|
import { registerSaml } from "./plugins/saml.js";
|
||||||
import { registerStatic } from "./plugins/static.js";
|
import { registerStatic } from "./plugins/static.js";
|
||||||
import { registerUpload } from "./plugins/upload.js";
|
import { registerUpload } from "./plugins/upload.js";
|
||||||
import { adminOpsRoutes } from "./routes/admin-ops.js";
|
import { adminOpsRoutes } from "./routes/admin-ops.js";
|
||||||
@@ -39,6 +40,7 @@ import { auditLogRoutes } from "./routes/audit-log.js";
|
|||||||
import { registerBatchRoutes } from "./routes/batch.js";
|
import { registerBatchRoutes } from "./routes/batch.js";
|
||||||
import { configRoutes } from "./routes/config.js";
|
import { configRoutes } from "./routes/config.js";
|
||||||
import { docsRoutes } from "./routes/docs.js";
|
import { docsRoutes } from "./routes/docs.js";
|
||||||
|
import { registerEnterpriseRoutes } from "./routes/enterprise/index.js";
|
||||||
import { registerFeatureRoutes } from "./routes/features.js";
|
import { registerFeatureRoutes } from "./routes/features.js";
|
||||||
import { registerFetchUrlsRoute } from "./routes/fetch-urls.js";
|
import { registerFetchUrlsRoute } from "./routes/fetch-urls.js";
|
||||||
import { fileRoutes } from "./routes/files.js";
|
import { fileRoutes } from "./routes/files.js";
|
||||||
@@ -50,7 +52,6 @@ import { settingsRoutes } from "./routes/settings.js";
|
|||||||
import { teamsRoutes } from "./routes/teams.js";
|
import { teamsRoutes } from "./routes/teams.js";
|
||||||
import { registerToolRoutes } from "./routes/tools/index.js";
|
import { registerToolRoutes } from "./routes/tools/index.js";
|
||||||
import { userFileRoutes } from "./routes/user-files.js";
|
import { userFileRoutes } from "./routes/user-files.js";
|
||||||
import { registerEnterpriseRoutes } from "./routes/enterprise/index.js";
|
|
||||||
|
|
||||||
// Run before anything else
|
// Run before anything else
|
||||||
try {
|
try {
|
||||||
@@ -302,6 +303,9 @@ await authRoutes(app);
|
|||||||
// OIDC routes
|
// OIDC routes
|
||||||
await oidcRoutes(app);
|
await oidcRoutes(app);
|
||||||
|
|
||||||
|
// SAML routes
|
||||||
|
await registerSaml(app);
|
||||||
|
|
||||||
// File upload/download routes
|
// File upload/download routes
|
||||||
await fileRoutes(app);
|
await fileRoutes(app);
|
||||||
|
|
||||||
@@ -417,6 +421,23 @@ app.get("/api/v1/config/auth", async () => {
|
|||||||
config.oidcProviderName = env.OIDC_PROVIDER_NAME || null;
|
config.oidcProviderName = env.OIDC_PROVIDER_NAME || null;
|
||||||
config.oidcLoginUrl = "/api/auth/oidc/login";
|
config.oidcLoginUrl = "/api/auth/oidc/login";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAML SSO requires both env flag and enterprise license
|
||||||
|
let samlLicensed = false;
|
||||||
|
if (env.SAML_ENABLED) {
|
||||||
|
try {
|
||||||
|
const { isFeatureEnabled } = await import("@snapotter/enterprise");
|
||||||
|
samlLicensed = isFeatureEnabled("saml_sso");
|
||||||
|
} catch {
|
||||||
|
// Enterprise package not available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (env.SAML_ENABLED && samlLicensed) {
|
||||||
|
config.samlEnabled = true;
|
||||||
|
config.samlProviderName = env.SAML_PROVIDER_NAME || "SSO";
|
||||||
|
config.samlLoginUrl = "/api/auth/saml/login";
|
||||||
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,26 @@ const envSchema = z
|
|||||||
OIDC_PROVIDER_NAME: z.string().default(""),
|
OIDC_PROVIDER_NAME: z.string().default(""),
|
||||||
OIDC_CLOCK_TOLERANCE: z.coerce.number().min(0).max(300).default(30),
|
OIDC_CLOCK_TOLERANCE: z.coerce.number().min(0).max(300).default(30),
|
||||||
OIDC_USERNAME_CLAIM: z.string().default("preferred_username"),
|
OIDC_USERNAME_CLAIM: z.string().default("preferred_username"),
|
||||||
|
SAML_ENABLED: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((v) => v === "true"),
|
||||||
|
SAML_ENTITY_ID: z.string().default(""),
|
||||||
|
SAML_CALLBACK_URL: z.string().default(""),
|
||||||
|
SAML_IDP_SSO_URL: z.string().default(""),
|
||||||
|
SAML_IDP_CERTIFICATE: z.string().default(""),
|
||||||
|
SAML_AUTO_CREATE_USERS: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("true")
|
||||||
|
.transform((v) => v === "true"),
|
||||||
|
SAML_AUTO_LINK_USERS: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((v) => v === "true"),
|
||||||
|
SAML_DEFAULT_ROLE: z.string().default("user"),
|
||||||
|
SAML_PROVIDER_NAME: z.string().default(""),
|
||||||
|
SAML_USERNAME_ATTRIBUTE: z.string().default(""),
|
||||||
|
SAML_EMAIL_ATTRIBUTE: z.string().default("email"),
|
||||||
EXTERNAL_URL: z.string().default(""),
|
EXTERNAL_URL: z.string().default(""),
|
||||||
COOKIE_SECRET: z.string().default(""),
|
COOKIE_SECRET: z.string().default(""),
|
||||||
REDIS_URL: z.string().default("redis://localhost:6379"),
|
REDIS_URL: z.string().default("redis://localhost:6379"),
|
||||||
@@ -154,6 +174,29 @@ const envSchema = z
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (data.SAML_ENABLED) {
|
||||||
|
if (!data.SAML_IDP_SSO_URL) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "SAML_IDP_SSO_URL is required when SAML_ENABLED=true",
|
||||||
|
path: ["SAML_IDP_SSO_URL"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!data.SAML_IDP_CERTIFICATE) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "SAML_IDP_CERTIFICATE is required when SAML_ENABLED=true",
|
||||||
|
path: ["SAML_IDP_CERTIFICATE"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!data.EXTERNAL_URL) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "EXTERNAL_URL is required when SAML_ENABLED=true",
|
||||||
|
path: ["EXTERNAL_URL"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Env = z.infer<typeof envSchema>;
|
export type Env = z.infer<typeof envSchema>;
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
mustChangePassword: env.SKIP_MUST_CHANGE_PASSWORD ? false : user.mustChangePassword,
|
mustChangePassword: env.SKIP_MUST_CHANGE_PASSWORD ? false : user.mustChangePassword,
|
||||||
permissions: await getPermissions(user.role),
|
permissions: await getPermissions(user.role),
|
||||||
authProvider: user.authProvider ?? "local",
|
authProvider: user.authProvider ?? "local",
|
||||||
loginMethod: session.idToken ? "oidc" : "local",
|
loginMethod: session.idToken ? "oidc" : user.authProvider === "saml" ? "saml" : "local",
|
||||||
email: user.email ?? null,
|
email: user.email ?? null,
|
||||||
hasLocalPassword: !!user.passwordHash,
|
hasLocalPassword: !!user.passwordHash,
|
||||||
hasOidcLink: !!user.externalId,
|
hasOidcLink: !!user.externalId,
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import { parse as parseQs } from "node:querystring";
|
||||||
|
import type {} from "@fastify/cookie";
|
||||||
|
import { SAML } from "@node-saml/node-saml";
|
||||||
|
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||||
|
import { env } from "../config.js";
|
||||||
|
import { db, schema } from "../db/index.js";
|
||||||
|
import { auditFromRequest } from "../lib/audit.js";
|
||||||
|
import {
|
||||||
|
findUniqueUsername,
|
||||||
|
resolveExternalUser,
|
||||||
|
sanitizeUsername,
|
||||||
|
} from "../lib/external-auth-resolver.js";
|
||||||
|
import { createSessionToken } from "./auth.js";
|
||||||
|
|
||||||
|
// -- SAML instance factory ----------------------------------------------------
|
||||||
|
|
||||||
|
function getSamlInstance(): SAML {
|
||||||
|
return new SAML({
|
||||||
|
callbackUrl: env.SAML_CALLBACK_URL || `${env.EXTERNAL_URL}/api/auth/saml/callback`,
|
||||||
|
entryPoint: env.SAML_IDP_SSO_URL,
|
||||||
|
issuer: env.SAML_ENTITY_ID || `${env.EXTERNAL_URL}/api/auth/saml/metadata`,
|
||||||
|
cert: env.SAML_IDP_CERTIFICATE,
|
||||||
|
wantAuthnResponseSigned: true,
|
||||||
|
wantAssertionsSigned: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Helpers ------------------------------------------------------------------
|
||||||
|
|
||||||
|
function isSecure(): boolean {
|
||||||
|
return env.EXTERNAL_URL.startsWith("https");
|
||||||
|
}
|
||||||
|
|
||||||
|
const SESSION_DURATION_MS = env.SESSION_DURATION_HOURS * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
function redirectToLogin(reply: FastifyReply, errorCode: string): void {
|
||||||
|
reply.redirect(`/login?error=${errorCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Plugin registration ------------------------------------------------------
|
||||||
|
|
||||||
|
export async function registerSaml(app: FastifyInstance): Promise<void> {
|
||||||
|
if (!env.SAML_ENABLED) return;
|
||||||
|
|
||||||
|
let isEnabled = false;
|
||||||
|
try {
|
||||||
|
const { isFeatureEnabled } = await import("@snapotter/enterprise");
|
||||||
|
isEnabled = isFeatureEnabled("saml_sso");
|
||||||
|
} catch {
|
||||||
|
// Enterprise package not available
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEnabled) {
|
||||||
|
app.log.warn("SAML is enabled via env but saml_sso enterprise feature is not licensed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register form-urlencoded content type parser for the SAML callback.
|
||||||
|
// The IdP POSTs the SAML response as application/x-www-form-urlencoded.
|
||||||
|
app.addContentTypeParser(
|
||||||
|
"application/x-www-form-urlencoded",
|
||||||
|
{ parseAs: "string" },
|
||||||
|
(_request, body, done) => {
|
||||||
|
try {
|
||||||
|
const str = typeof body === "string" ? body : (body as Buffer).toString();
|
||||||
|
done(null, parseQs(str));
|
||||||
|
} catch (err) {
|
||||||
|
done(err as Error, undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// GET /api/auth/saml/metadata -- SP metadata XML
|
||||||
|
app.get("/api/auth/saml/metadata", async (_request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
const saml = getSamlInstance();
|
||||||
|
const metadata = saml.generateServiceProviderMetadata(null, null);
|
||||||
|
return reply.type("application/xml").send(metadata);
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/auth/saml/login -- SP-initiated login redirect
|
||||||
|
app.get("/api/auth/saml/login", async (_request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
try {
|
||||||
|
const saml = getSamlInstance();
|
||||||
|
const loginUrl = await saml.getAuthorizeUrlAsync("", undefined, {});
|
||||||
|
return reply.redirect(loginUrl);
|
||||||
|
} catch (err) {
|
||||||
|
_request.log.error({ err }, "SAML login redirect failed");
|
||||||
|
return redirectToLogin(reply, "saml_auth_failed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /api/auth/saml/callback -- Assertion Consumer Service (ACS)
|
||||||
|
app.post("/api/auth/saml/callback", async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
const saml = getSamlInstance();
|
||||||
|
const audit = auditFromRequest(request);
|
||||||
|
|
||||||
|
let profile;
|
||||||
|
try {
|
||||||
|
const result = await saml.validatePostResponseAsync(request.body as Record<string, string>);
|
||||||
|
profile = result.profile;
|
||||||
|
} catch (err) {
|
||||||
|
request.log.error({ err }, "SAML assertion validation failed");
|
||||||
|
await audit("SAML_LOGIN_FAILED", {
|
||||||
|
error: err instanceof Error ? err.message : "Unknown error",
|
||||||
|
});
|
||||||
|
return redirectToLogin(reply, "saml_auth_failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!profile || !profile.nameID) {
|
||||||
|
request.log.warn("SAML callback: no profile or nameID in assertion");
|
||||||
|
await audit("SAML_LOGIN_FAILED", { reason: "missing_profile" });
|
||||||
|
return redirectToLogin(reply, "saml_auth_failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract claims from SAML assertion
|
||||||
|
const externalId = profile.nameID;
|
||||||
|
const email = profile[env.SAML_EMAIL_ATTRIBUTE] as string | undefined;
|
||||||
|
const usernameAttr = env.SAML_USERNAME_ATTRIBUTE
|
||||||
|
? (profile[env.SAML_USERNAME_ATTRIBUTE] as string | undefined)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// Derive a username from available claims
|
||||||
|
const rawUsername = usernameAttr || email?.split("@")[0] || profile.nameID;
|
||||||
|
let username = sanitizeUsername(rawUsername);
|
||||||
|
username = await findUniqueUsername(username);
|
||||||
|
|
||||||
|
// Resolve user via shared external-auth resolver
|
||||||
|
const result = await resolveExternalUser({
|
||||||
|
provider: "saml",
|
||||||
|
externalId,
|
||||||
|
email,
|
||||||
|
emailVerified: true, // SAML assertions from a trusted IdP are considered verified
|
||||||
|
username,
|
||||||
|
autoCreate: env.SAML_AUTO_CREATE_USERS,
|
||||||
|
autoLink: env.SAML_AUTO_LINK_USERS,
|
||||||
|
defaultRole: env.SAML_DEFAULT_ROLE,
|
||||||
|
logger: request.log,
|
||||||
|
ip: request.ip,
|
||||||
|
requestId: request.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.action === "denied" || !result.user) {
|
||||||
|
const errorParam =
|
||||||
|
result.deniedReason === "user_limit_reached"
|
||||||
|
? "saml_user_limit_reached"
|
||||||
|
: "saml_user_not_authorized";
|
||||||
|
return redirectToLogin(reply, errorParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedUser = result.user;
|
||||||
|
|
||||||
|
// Create session (same pattern as OIDC)
|
||||||
|
const token = createSessionToken();
|
||||||
|
const expiresAt = new Date(Date.now() + SESSION_DURATION_MS);
|
||||||
|
|
||||||
|
await db.insert(schema.sessions).values({
|
||||||
|
id: token,
|
||||||
|
userId: resolvedUser.id,
|
||||||
|
expiresAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
await audit("SAML_LOGIN_SUCCESS", {
|
||||||
|
userId: resolvedUser.id,
|
||||||
|
username: resolvedUser.username,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set session cookie and redirect to app
|
||||||
|
reply.setCookie("snapotter-session", token, {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: isSecure(),
|
||||||
|
path: "/",
|
||||||
|
maxAge: env.SESSION_DURATION_HOURS * 3600,
|
||||||
|
});
|
||||||
|
|
||||||
|
return reply.redirect("/");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ interface AuthState {
|
|||||||
analyticsConsentRemindAt: number | null;
|
analyticsConsentRemindAt: number | null;
|
||||||
oidcEnabled: boolean;
|
oidcEnabled: boolean;
|
||||||
oidcProviderName: string | null;
|
oidcProviderName: string | null;
|
||||||
|
samlEnabled: boolean;
|
||||||
|
samlProviderName: string | null;
|
||||||
loginMethod: string | null;
|
loginMethod: string | null;
|
||||||
hasLocalPassword: boolean;
|
hasLocalPassword: boolean;
|
||||||
}
|
}
|
||||||
@@ -48,6 +50,8 @@ export function useAuth() {
|
|||||||
analyticsConsentRemindAt: null,
|
analyticsConsentRemindAt: null,
|
||||||
oidcEnabled: false,
|
oidcEnabled: false,
|
||||||
oidcProviderName: null,
|
oidcProviderName: null,
|
||||||
|
samlEnabled: false,
|
||||||
|
samlProviderName: null,
|
||||||
loginMethod: null,
|
loginMethod: null,
|
||||||
hasLocalPassword: false,
|
hasLocalPassword: false,
|
||||||
});
|
});
|
||||||
@@ -74,6 +78,8 @@ export function useAuth() {
|
|||||||
analyticsConsentRemindAt: null,
|
analyticsConsentRemindAt: null,
|
||||||
oidcEnabled: false,
|
oidcEnabled: false,
|
||||||
oidcProviderName: null,
|
oidcProviderName: null,
|
||||||
|
samlEnabled: false,
|
||||||
|
samlProviderName: null,
|
||||||
loginMethod: null,
|
loginMethod: null,
|
||||||
hasLocalPassword: false,
|
hasLocalPassword: false,
|
||||||
});
|
});
|
||||||
@@ -102,6 +108,8 @@ export function useAuth() {
|
|||||||
analyticsConsentRemindAt: session.user?.analyticsConsentRemindAt ?? null,
|
analyticsConsentRemindAt: session.user?.analyticsConsentRemindAt ?? null,
|
||||||
oidcEnabled: config.oidcEnabled ?? false,
|
oidcEnabled: config.oidcEnabled ?? false,
|
||||||
oidcProviderName: config.oidcProviderName ?? null,
|
oidcProviderName: config.oidcProviderName ?? null,
|
||||||
|
samlEnabled: config.samlEnabled ?? false,
|
||||||
|
samlProviderName: config.samlProviderName ?? null,
|
||||||
loginMethod: session.user?.loginMethod ?? null,
|
loginMethod: session.user?.loginMethod ?? null,
|
||||||
hasLocalPassword: session.user?.hasLocalPassword ?? false,
|
hasLocalPassword: session.user?.hasLocalPassword ?? false,
|
||||||
});
|
});
|
||||||
@@ -120,6 +128,8 @@ export function useAuth() {
|
|||||||
analyticsConsentRemindAt: null,
|
analyticsConsentRemindAt: null,
|
||||||
oidcEnabled: config.oidcEnabled ?? false,
|
oidcEnabled: config.oidcEnabled ?? false,
|
||||||
oidcProviderName: config.oidcProviderName ?? null,
|
oidcProviderName: config.oidcProviderName ?? null,
|
||||||
|
samlEnabled: config.samlEnabled ?? false,
|
||||||
|
samlProviderName: config.samlProviderName ?? null,
|
||||||
loginMethod: null,
|
loginMethod: null,
|
||||||
hasLocalPassword: false,
|
hasLocalPassword: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ function LanguageSelector() {
|
|||||||
|
|
||||||
export function LoginPage() {
|
export function LoginPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { oidcEnabled, oidcProviderName } = useAuth();
|
const { oidcEnabled, oidcProviderName, samlEnabled, samlProviderName } = useAuth();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@@ -135,16 +135,19 @@ export function LoginPage() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const oidcError = searchParams.get("error");
|
const authError = searchParams.get("error");
|
||||||
if (oidcError) {
|
if (authError) {
|
||||||
const errorMessages: Record<string, string> = {
|
const errorMessages: Record<string, string> = {
|
||||||
oidc_auth_failed: t.auth.oidcAuthFailed,
|
oidc_auth_failed: t.auth.oidcAuthFailed,
|
||||||
oidc_provider_unreachable: t.auth.oidcProviderUnreachable,
|
oidc_provider_unreachable: t.auth.oidcProviderUnreachable,
|
||||||
oidc_session_expired: t.auth.oidcSessionExpired,
|
oidc_session_expired: t.auth.oidcSessionExpired,
|
||||||
oidc_user_not_authorized: t.auth.oidcUserNotAuthorized,
|
oidc_user_not_authorized: t.auth.oidcUserNotAuthorized,
|
||||||
oidc_user_limit_reached: t.auth.oidcUserLimitReached,
|
oidc_user_limit_reached: t.auth.oidcUserLimitReached,
|
||||||
|
saml_auth_failed: t.auth.samlAuthFailed,
|
||||||
|
saml_user_not_authorized: t.auth.samlUserNotAuthorized,
|
||||||
|
saml_user_limit_reached: t.auth.samlUserLimitReached,
|
||||||
};
|
};
|
||||||
setError(errorMessages[oidcError] || t.auth.oidcGenericError);
|
setError(errorMessages[authError] || t.auth.oidcGenericError);
|
||||||
}
|
}
|
||||||
}, [searchParams, t]);
|
}, [searchParams, t]);
|
||||||
|
|
||||||
@@ -230,19 +233,29 @@ export function LoginPage() {
|
|||||||
{loading ? t.auth.loggingIn : t.auth.loginButton}
|
{loading ? t.auth.loggingIn : t.auth.loginButton}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{oidcEnabled && (
|
{(oidcEnabled || samlEnabled) && (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-3 my-4">
|
<div className="flex items-center gap-3 my-4">
|
||||||
<div className="flex-1 border-t border-border" />
|
<div className="flex-1 border-t border-border" />
|
||||||
<span className="text-sm text-muted-foreground">{t.auth.or}</span>
|
<span className="text-sm text-muted-foreground">{t.auth.or}</span>
|
||||||
<div className="flex-1 border-t border-border" />
|
<div className="flex-1 border-t border-border" />
|
||||||
</div>
|
</div>
|
||||||
|
{oidcEnabled && (
|
||||||
<a
|
<a
|
||||||
href="/api/auth/oidc/login"
|
href="/api/auth/oidc/login"
|
||||||
className="w-full py-3 rounded-lg bg-secondary text-secondary-foreground font-medium hover:bg-secondary/80 transition-colors flex items-center justify-center gap-2"
|
className="w-full py-3 rounded-lg bg-secondary text-secondary-foreground font-medium hover:bg-secondary/80 transition-colors flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
{format(t.auth.signInWith, { provider: oidcProviderName || "SSO" })}
|
{format(t.auth.signInWith, { provider: oidcProviderName || "SSO" })}
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
|
{samlEnabled && (
|
||||||
|
<a
|
||||||
|
href="/api/auth/saml/login"
|
||||||
|
className="w-full py-3 rounded-lg bg-secondary text-secondary-foreground font-medium hover:bg-secondary/80 transition-colors flex items-center justify-center gap-2 mt-2"
|
||||||
|
>
|
||||||
|
{format(t.auth.signInWith, { provider: samlProviderName || "SSO" })}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
|
|||||||
@@ -2997,6 +2997,11 @@ export const ar: TranslationKeys = {
|
|||||||
oidcUserNotAuthorized: "حسابك غير مصرح له بالوصول إلى هذا التطبيق. تواصل مع المسؤول.",
|
oidcUserNotAuthorized: "حسابك غير مصرح له بالوصول إلى هذا التطبيق. تواصل مع المسؤول.",
|
||||||
oidcUserLimitReached: "تم الوصول لحد المستخدمين. تواصل مع المسؤول.",
|
oidcUserLimitReached: "تم الوصول لحد المستخدمين. تواصل مع المسؤول.",
|
||||||
oidcGenericError: "خطأ في المصادقة. يرجى المحاولة مرة أخرى.",
|
oidcGenericError: "خطأ في المصادقة. يرجى المحاولة مرة أخرى.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "يتم إدارة تغيير كلمة المرور بواسطة مزود الهوية الخاص بك.",
|
passwordManagedByProvider: "يتم إدارة تغيير كلمة المرور بواسطة مزود الهوية الخاص بك.",
|
||||||
enterUsername: "أدخل اسم المستخدم",
|
enterUsername: "أدخل اسم المستخدم",
|
||||||
enterPassword: "أدخل كلمة المرور",
|
enterPassword: "أدخل كلمة المرور",
|
||||||
|
|||||||
@@ -3024,6 +3024,11 @@ export const de: TranslationKeys = {
|
|||||||
"Ihr Konto ist nicht fuer den Zugriff auf diese Anwendung autorisiert. Kontaktieren Sie Ihren Administrator.",
|
"Ihr Konto ist nicht fuer den Zugriff auf diese Anwendung autorisiert. Kontaktieren Sie Ihren Administrator.",
|
||||||
oidcUserLimitReached: "Benutzerlimit erreicht. Kontaktieren Sie Ihren Administrator.",
|
oidcUserLimitReached: "Benutzerlimit erreicht. Kontaktieren Sie Ihren Administrator.",
|
||||||
oidcGenericError: "Authentifizierungsfehler. Bitte versuchen Sie es erneut.",
|
oidcGenericError: "Authentifizierungsfehler. Bitte versuchen Sie es erneut.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider:
|
passwordManagedByProvider:
|
||||||
"Passwortaenderungen werden von Ihrem Identitaetsanbieter verwaltet.",
|
"Passwortaenderungen werden von Ihrem Identitaetsanbieter verwaltet.",
|
||||||
enterUsername: "Benutzernamen eingeben",
|
enterUsername: "Benutzernamen eingeben",
|
||||||
|
|||||||
@@ -2963,6 +2963,11 @@ export const en = {
|
|||||||
"Your account is not authorized to access this application. Contact your administrator.",
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
oidcUserLimitReached: "User limit reached. Contact your administrator.",
|
oidcUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
oidcGenericError: "Authentication error. Please try again.",
|
oidcGenericError: "Authentication error. Please try again.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "Password changes are managed by your identity provider.",
|
passwordManagedByProvider: "Password changes are managed by your identity provider.",
|
||||||
enterUsername: "Enter username",
|
enterUsername: "Enter username",
|
||||||
enterPassword: "Enter your password",
|
enterPassword: "Enter your password",
|
||||||
|
|||||||
@@ -3002,6 +3002,11 @@ export const es: TranslationKeys = {
|
|||||||
"Tu cuenta no esta autorizada para acceder a esta aplicacion. Contacta a tu administrador.",
|
"Tu cuenta no esta autorizada para acceder a esta aplicacion. Contacta a tu administrador.",
|
||||||
oidcUserLimitReached: "Limite de usuarios alcanzado. Contacta a tu administrador.",
|
oidcUserLimitReached: "Limite de usuarios alcanzado. Contacta a tu administrador.",
|
||||||
oidcGenericError: "Error de autenticacion. Intenta de nuevo.",
|
oidcGenericError: "Error de autenticacion. Intenta de nuevo.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider:
|
passwordManagedByProvider:
|
||||||
"Los cambios de contrasena son administrados por tu proveedor de identidad.",
|
"Los cambios de contrasena son administrados por tu proveedor de identidad.",
|
||||||
enterUsername: "Ingresa tu nombre de usuario",
|
enterUsername: "Ingresa tu nombre de usuario",
|
||||||
|
|||||||
@@ -3022,6 +3022,11 @@ export const fr: TranslationKeys = {
|
|||||||
"Votre compte n'est pas autorise a acceder a cette application. Contactez votre administrateur.",
|
"Votre compte n'est pas autorise a acceder a cette application. Contactez votre administrateur.",
|
||||||
oidcUserLimitReached: "Limite d'utilisateurs atteinte. Contactez votre administrateur.",
|
oidcUserLimitReached: "Limite d'utilisateurs atteinte. Contactez votre administrateur.",
|
||||||
oidcGenericError: "Erreur d'authentification. Veuillez reessayer.",
|
oidcGenericError: "Erreur d'authentification. Veuillez reessayer.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider:
|
passwordManagedByProvider:
|
||||||
"Les modifications de mot de passe sont gerees par votre fournisseur d'identite.",
|
"Les modifications de mot de passe sont gerees par votre fournisseur d'identite.",
|
||||||
enterUsername: "Saisissez votre nom d'utilisateur",
|
enterUsername: "Saisissez votre nom d'utilisateur",
|
||||||
|
|||||||
@@ -2994,6 +2994,11 @@ export const hi: TranslationKeys = {
|
|||||||
"आपके अकाउंट को इस एप्लिकेशन तक पहुंचने की अनुमति नहीं है। अपने एडमिनिस्ट्रेटर से संपर्क करें।",
|
"आपके अकाउंट को इस एप्लिकेशन तक पहुंचने की अनुमति नहीं है। अपने एडमिनिस्ट्रेटर से संपर्क करें।",
|
||||||
oidcUserLimitReached: "उपयोगकर्ता सीमा पहुंच गई। अपने एडमिनिस्ट्रेटर से संपर्क करें।",
|
oidcUserLimitReached: "उपयोगकर्ता सीमा पहुंच गई। अपने एडमिनिस्ट्रेटर से संपर्क करें।",
|
||||||
oidcGenericError: "ऑथेंटिकेशन त्रुटि। कृपया पुनः प्रयास करें।",
|
oidcGenericError: "ऑथेंटिकेशन त्रुटि। कृपया पुनः प्रयास करें।",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "पासवर्ड बदलाव आपके आइडेंटिटी प्रोवाइडर द्वारा प्रबंधित किए जाते हैं।",
|
passwordManagedByProvider: "पासवर्ड बदलाव आपके आइडेंटिटी प्रोवाइडर द्वारा प्रबंधित किए जाते हैं।",
|
||||||
enterUsername: "यूज़रनेम दर्ज करें",
|
enterUsername: "यूज़रनेम दर्ज करें",
|
||||||
enterPassword: "अपना पासवर्ड दर्ज करें",
|
enterPassword: "अपना पासवर्ड दर्ज करें",
|
||||||
|
|||||||
@@ -3010,6 +3010,11 @@ export const id: TranslationKeys = {
|
|||||||
"Akun Anda tidak diizinkan mengakses aplikasi ini. Hubungi administrator Anda.",
|
"Akun Anda tidak diizinkan mengakses aplikasi ini. Hubungi administrator Anda.",
|
||||||
oidcUserLimitReached: "Batas pengguna tercapai. Hubungi administrator Anda.",
|
oidcUserLimitReached: "Batas pengguna tercapai. Hubungi administrator Anda.",
|
||||||
oidcGenericError: "Kesalahan autentikasi. Silakan coba lagi.",
|
oidcGenericError: "Kesalahan autentikasi. Silakan coba lagi.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "Perubahan kata sandi dikelola oleh penyedia identitas Anda.",
|
passwordManagedByProvider: "Perubahan kata sandi dikelola oleh penyedia identitas Anda.",
|
||||||
enterUsername: "Masukkan nama pengguna",
|
enterUsername: "Masukkan nama pengguna",
|
||||||
enterPassword: "Masukkan kata sandi Anda",
|
enterPassword: "Masukkan kata sandi Anda",
|
||||||
|
|||||||
@@ -3016,6 +3016,11 @@ export const it: TranslationKeys = {
|
|||||||
"Il tuo account non e autorizzato ad accedere a questa applicazione. Contatta l'amministratore.",
|
"Il tuo account non e autorizzato ad accedere a questa applicazione. Contatta l'amministratore.",
|
||||||
oidcUserLimitReached: "Limite utenti raggiunto. Contatta l'amministratore.",
|
oidcUserLimitReached: "Limite utenti raggiunto. Contatta l'amministratore.",
|
||||||
oidcGenericError: "Errore di autenticazione. Riprova.",
|
oidcGenericError: "Errore di autenticazione. Riprova.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider:
|
passwordManagedByProvider:
|
||||||
"Le modifiche alla password sono gestite dal tuo provider di identita.",
|
"Le modifiche alla password sono gestite dal tuo provider di identita.",
|
||||||
enterUsername: "Inserisci il nome utente",
|
enterUsername: "Inserisci il nome utente",
|
||||||
|
|||||||
@@ -2967,6 +2967,11 @@ export const ja: TranslationKeys = {
|
|||||||
"お使いのアカウントにはこのアプリケーションへのアクセス権がありません。管理者にお問い合わせください。",
|
"お使いのアカウントにはこのアプリケーションへのアクセス権がありません。管理者にお問い合わせください。",
|
||||||
oidcUserLimitReached: "ユーザー上限に達しました。管理者にお問い合わせください。",
|
oidcUserLimitReached: "ユーザー上限に達しました。管理者にお問い合わせください。",
|
||||||
oidcGenericError: "認証エラー。もう一度お試しください。",
|
oidcGenericError: "認証エラー。もう一度お試しください。",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "パスワードはIDプロバイダーで管理されています。",
|
passwordManagedByProvider: "パスワードはIDプロバイダーで管理されています。",
|
||||||
enterUsername: "ユーザー名を入力",
|
enterUsername: "ユーザー名を入力",
|
||||||
enterPassword: "パスワードを入力",
|
enterPassword: "パスワードを入力",
|
||||||
|
|||||||
@@ -2952,6 +2952,11 @@ export const ko: TranslationKeys = {
|
|||||||
"귀하의 계정은 이 애플리케이션에 접근할 권한이 없습니다. 관리자에게 문의하세요.",
|
"귀하의 계정은 이 애플리케이션에 접근할 권한이 없습니다. 관리자에게 문의하세요.",
|
||||||
oidcUserLimitReached: "사용자 한도에 도달했습니다. 관리자에게 문의하세요.",
|
oidcUserLimitReached: "사용자 한도에 도달했습니다. 관리자에게 문의하세요.",
|
||||||
oidcGenericError: "인증 오류. 다시 시도해 주세요.",
|
oidcGenericError: "인증 오류. 다시 시도해 주세요.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "비밀번호는 ID 제공자에서 관리됩니다.",
|
passwordManagedByProvider: "비밀번호는 ID 제공자에서 관리됩니다.",
|
||||||
enterUsername: "사용자명 입력",
|
enterUsername: "사용자명 입력",
|
||||||
enterPassword: "비밀번호 입력",
|
enterPassword: "비밀번호 입력",
|
||||||
|
|||||||
@@ -3013,6 +3013,11 @@ export const nl: TranslationKeys = {
|
|||||||
"Je account heeft geen toegang tot deze applicatie. Neem contact op met je beheerder.",
|
"Je account heeft geen toegang tot deze applicatie. Neem contact op met je beheerder.",
|
||||||
oidcUserLimitReached: "Gebruikerslimiet bereikt. Neem contact op met je beheerder.",
|
oidcUserLimitReached: "Gebruikerslimiet bereikt. Neem contact op met je beheerder.",
|
||||||
oidcGenericError: "Authenticatiefout. Probeer het opnieuw.",
|
oidcGenericError: "Authenticatiefout. Probeer het opnieuw.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "Wachtwoordwijzigingen worden beheerd door je identiteitsprovider.",
|
passwordManagedByProvider: "Wachtwoordwijzigingen worden beheerd door je identiteitsprovider.",
|
||||||
enterUsername: "Voer gebruikersnaam in",
|
enterUsername: "Voer gebruikersnaam in",
|
||||||
enterPassword: "Voer je wachtwoord in",
|
enterPassword: "Voer je wachtwoord in",
|
||||||
|
|||||||
@@ -3020,6 +3020,11 @@ export const pl: TranslationKeys = {
|
|||||||
"Państwa konto nie ma uprawnień do korzystania z tej aplikacji. Skontaktuj się z administratorem.",
|
"Państwa konto nie ma uprawnień do korzystania z tej aplikacji. Skontaktuj się z administratorem.",
|
||||||
oidcUserLimitReached: "Osiągnięto limit użytkowników. Skontaktuj się z administratorem.",
|
oidcUserLimitReached: "Osiągnięto limit użytkowników. Skontaktuj się z administratorem.",
|
||||||
oidcGenericError: "Błąd uwierzytelniania. Spróbuj ponownie.",
|
oidcGenericError: "Błąd uwierzytelniania. Spróbuj ponownie.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "Zarządzanie hasłem odbywa się przez dostawcę tożsamości.",
|
passwordManagedByProvider: "Zarządzanie hasłem odbywa się przez dostawcę tożsamości.",
|
||||||
enterUsername: "Wprowadź nazwę użytkownika",
|
enterUsername: "Wprowadź nazwę użytkownika",
|
||||||
enterPassword: "Wprowadź hasło",
|
enterPassword: "Wprowadź hasło",
|
||||||
|
|||||||
@@ -3013,6 +3013,11 @@ export const ptBR: TranslationKeys = {
|
|||||||
"Sua conta nao esta autorizada a acessar este aplicativo. Entre em contato com o administrador.",
|
"Sua conta nao esta autorizada a acessar este aplicativo. Entre em contato com o administrador.",
|
||||||
oidcUserLimitReached: "Limite de usuarios atingido. Entre em contato com o administrador.",
|
oidcUserLimitReached: "Limite de usuarios atingido. Entre em contato com o administrador.",
|
||||||
oidcGenericError: "Erro de autenticacao. Tente novamente.",
|
oidcGenericError: "Erro de autenticacao. Tente novamente.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider:
|
passwordManagedByProvider:
|
||||||
"Alteracoes de senha sao gerenciadas pelo seu provedor de identidade.",
|
"Alteracoes de senha sao gerenciadas pelo seu provedor de identidade.",
|
||||||
enterUsername: "Digite seu nome de usuario",
|
enterUsername: "Digite seu nome de usuario",
|
||||||
|
|||||||
@@ -3012,6 +3012,11 @@ export const ru: TranslationKeys = {
|
|||||||
"Вашей учётной записи не предоставлен доступ к этому приложению. Обратитесь к администратору.",
|
"Вашей учётной записи не предоставлен доступ к этому приложению. Обратитесь к администратору.",
|
||||||
oidcUserLimitReached: "Достигнут лимит пользователей. Обратитесь к администратору.",
|
oidcUserLimitReached: "Достигнут лимит пользователей. Обратитесь к администратору.",
|
||||||
oidcGenericError: "Ошибка аутентификации. Попробуйте снова.",
|
oidcGenericError: "Ошибка аутентификации. Попробуйте снова.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "Управление паролем осуществляется Вашим провайдером идентификации.",
|
passwordManagedByProvider: "Управление паролем осуществляется Вашим провайдером идентификации.",
|
||||||
enterUsername: "Введите имя пользователя",
|
enterUsername: "Введите имя пользователя",
|
||||||
enterPassword: "Введите пароль",
|
enterPassword: "Введите пароль",
|
||||||
|
|||||||
@@ -3008,6 +3008,11 @@ export const sv: TranslationKeys = {
|
|||||||
"Ditt konto har inte behorighet till denna applikation. Kontakta din administrator.",
|
"Ditt konto har inte behorighet till denna applikation. Kontakta din administrator.",
|
||||||
oidcUserLimitReached: "Anvandargrans nadd. Kontakta din administrator.",
|
oidcUserLimitReached: "Anvandargrans nadd. Kontakta din administrator.",
|
||||||
oidcGenericError: "Autentiseringsfel. Forsok igen.",
|
oidcGenericError: "Autentiseringsfel. Forsok igen.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "Losenordsandringar hanteras av din identitetsleverantor.",
|
passwordManagedByProvider: "Losenordsandringar hanteras av din identitetsleverantor.",
|
||||||
enterUsername: "Ange anvandarnamn",
|
enterUsername: "Ange anvandarnamn",
|
||||||
enterPassword: "Ange ditt losenord",
|
enterPassword: "Ange ditt losenord",
|
||||||
|
|||||||
@@ -2985,6 +2985,11 @@ export const th: TranslationKeys = {
|
|||||||
oidcUserNotAuthorized: "บัญชีของคุณไม่ได้รับอนุญาตให้เข้าถึงแอปพลิเคชันนี้ กรุณาติดต่อผู้ดูแลระบบ",
|
oidcUserNotAuthorized: "บัญชีของคุณไม่ได้รับอนุญาตให้เข้าถึงแอปพลิเคชันนี้ กรุณาติดต่อผู้ดูแลระบบ",
|
||||||
oidcUserLimitReached: "ถึงจำนวนผู้ใช้สูงสุดแล้ว กรุณาติดต่อผู้ดูแลระบบ",
|
oidcUserLimitReached: "ถึงจำนวนผู้ใช้สูงสุดแล้ว กรุณาติดต่อผู้ดูแลระบบ",
|
||||||
oidcGenericError: "ข้อผิดพลาดในการยืนยันตัวตน กรุณาลองอีกครั้ง",
|
oidcGenericError: "ข้อผิดพลาดในการยืนยันตัวตน กรุณาลองอีกครั้ง",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "การเปลี่ยนรหัสผ่านจัดการโดยผู้ให้บริการยืนยันตัวตนของคุณ",
|
passwordManagedByProvider: "การเปลี่ยนรหัสผ่านจัดการโดยผู้ให้บริการยืนยันตัวตนของคุณ",
|
||||||
enterUsername: "กรอกชื่อผู้ใช้",
|
enterUsername: "กรอกชื่อผู้ใช้",
|
||||||
enterPassword: "กรอกรหัสผ่านของคุณ",
|
enterPassword: "กรอกรหัสผ่านของคุณ",
|
||||||
|
|||||||
@@ -3016,6 +3016,11 @@ export const tr: TranslationKeys = {
|
|||||||
"Hesabınız bu uygulamaya erişim yetkisine sahip değil. Yöneticinizle iletişime geçin.",
|
"Hesabınız bu uygulamaya erişim yetkisine sahip değil. Yöneticinizle iletişime geçin.",
|
||||||
oidcUserLimitReached: "Kullanıcı limitine ulaşıldı. Yöneticinizle iletişime geçin.",
|
oidcUserLimitReached: "Kullanıcı limitine ulaşıldı. Yöneticinizle iletişime geçin.",
|
||||||
oidcGenericError: "Kimlik doğrulama hatası. Lütfen tekrar deneyin.",
|
oidcGenericError: "Kimlik doğrulama hatası. Lütfen tekrar deneyin.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider:
|
passwordManagedByProvider:
|
||||||
"Parola değişiklikleri kimlik sağlayıcınız tarafından yönetilmektedir.",
|
"Parola değişiklikleri kimlik sağlayıcınız tarafından yönetilmektedir.",
|
||||||
enterUsername: "Kullanıcı adını girin",
|
enterUsername: "Kullanıcı adını girin",
|
||||||
|
|||||||
@@ -3013,6 +3013,11 @@ export const uk: TranslationKeys = {
|
|||||||
"Ваш обліковий запис не має доступу до цього застосунку. Зверніться до адміністратора.",
|
"Ваш обліковий запис не має доступу до цього застосунку. Зверніться до адміністратора.",
|
||||||
oidcUserLimitReached: "Досягнуто ліміту користувачів. Зверніться до адміністратора.",
|
oidcUserLimitReached: "Досягнуто ліміту користувачів. Зверніться до адміністратора.",
|
||||||
oidcGenericError: "Помилка автентифікації. Спробуйте знову.",
|
oidcGenericError: "Помилка автентифікації. Спробуйте знову.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "Керування паролем здійснюється Вашим постачальником ідентифікації.",
|
passwordManagedByProvider: "Керування паролем здійснюється Вашим постачальником ідентифікації.",
|
||||||
enterUsername: "Введіть ім'я користувача",
|
enterUsername: "Введіть ім'я користувача",
|
||||||
enterPassword: "Введіть пароль",
|
enterPassword: "Введіть пароль",
|
||||||
|
|||||||
@@ -3008,6 +3008,11 @@ export const vi: TranslationKeys = {
|
|||||||
"Tài khoản của bạn không được phép truy cập ứng dụng này. Liên hệ quản trị viên.",
|
"Tài khoản của bạn không được phép truy cập ứng dụng này. Liên hệ quản trị viên.",
|
||||||
oidcUserLimitReached: "Đã đạt giới hạn người dùng. Liên hệ quản trị viên.",
|
oidcUserLimitReached: "Đã đạt giới hạn người dùng. Liên hệ quản trị viên.",
|
||||||
oidcGenericError: "Lỗi xác thực. Vui lòng thử lại.",
|
oidcGenericError: "Lỗi xác thực. Vui lòng thử lại.",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "Việc đổi mật khẩu được quản lý bởi nhà cung cấp danh tính của bạn.",
|
passwordManagedByProvider: "Việc đổi mật khẩu được quản lý bởi nhà cung cấp danh tính của bạn.",
|
||||||
enterUsername: "Nhập tên đăng nhập",
|
enterUsername: "Nhập tên đăng nhập",
|
||||||
enterPassword: "Nhập mật khẩu của bạn",
|
enterPassword: "Nhập mật khẩu của bạn",
|
||||||
|
|||||||
@@ -2937,6 +2937,11 @@ export const zhCN: TranslationKeys = {
|
|||||||
oidcUserNotAuthorized: "您的账号无权访问此应用。请联系管理员。",
|
oidcUserNotAuthorized: "您的账号无权访问此应用。请联系管理员。",
|
||||||
oidcUserLimitReached: "用户数量已达上限。请联系管理员。",
|
oidcUserLimitReached: "用户数量已达上限。请联系管理员。",
|
||||||
oidcGenericError: "认证错误。请重试。",
|
oidcGenericError: "认证错误。请重试。",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "密码修改由您的身份提供商管理。",
|
passwordManagedByProvider: "密码修改由您的身份提供商管理。",
|
||||||
enterUsername: "输入用户名",
|
enterUsername: "输入用户名",
|
||||||
enterPassword: "输入密码",
|
enterPassword: "输入密码",
|
||||||
|
|||||||
@@ -2935,6 +2935,11 @@ export const zhTW: TranslationKeys = {
|
|||||||
oidcUserNotAuthorized: "您的帳戶無權存取此應用程式。請聯絡管理員。",
|
oidcUserNotAuthorized: "您的帳戶無權存取此應用程式。請聯絡管理員。",
|
||||||
oidcUserLimitReached: "已達使用者上限,請聯絡管理員。",
|
oidcUserLimitReached: "已達使用者上限,請聯絡管理員。",
|
||||||
oidcGenericError: "驗證錯誤,請重試。",
|
oidcGenericError: "驗證錯誤,請重試。",
|
||||||
|
samlAuthFailed: "SAML authentication failed. Please try again.",
|
||||||
|
samlUserNotAuthorized:
|
||||||
|
"Your account is not authorized to access this application. Contact your administrator.",
|
||||||
|
samlUserLimitReached: "User limit reached. Contact your administrator.",
|
||||||
|
methodSaml: "SAML",
|
||||||
passwordManagedByProvider: "密碼由您的身分提供者管理。",
|
passwordManagedByProvider: "密碼由您的身分提供者管理。",
|
||||||
enterUsername: "輸入使用者名稱",
|
enterUsername: "輸入使用者名稱",
|
||||||
enterPassword: "輸入密碼",
|
enterPassword: "輸入密碼",
|
||||||
|
|||||||
Reference in New Issue
Block a user