mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
feat(admin): remove the SSO link of an account whose provider changed its subject
A sign-in with another subject of the same provider is refused, so an account whose provider re-issued the subject (a migrated or rebuilt IdP, a changed sub format) was locked out: editing the email, as the wiki advised, never touched the stored link. The edit dialog now offers "Remove SSO link" for other accounts while SSO is set up (DELETE /api/admin/users/:id/sso, audit admin.user_sso_unlink). The next SSO sign-in links again under the rules of a first one, so it still needs a verified, not self-set email. The log line names the remedy, and the wiki tells the two refusals apart. Fixes #3065
This commit is contained in:
@@ -565,6 +565,7 @@ export const adminApi = {
|
||||
deleteUser: (id: number) => apiClient.delete(`/admin/users/${id}`).then(r => r.data),
|
||||
resetUserPasskeys: (id: number) => apiClient.delete(`/admin/users/${id}/passkeys`).then(r => r.data),
|
||||
resetUserMfa: (id: number) => apiClient.delete(`/admin/users/${id}/mfa`).then(r => r.data),
|
||||
unlinkUserSso: (id: number) => apiClient.delete(`/admin/users/${id}/sso`).then(r => r.data),
|
||||
stats: () => apiClient.get('/admin/stats').then(r => r.data),
|
||||
saveDemoBaseline: () => apiClient.post('/admin/save-demo-baseline').then(r => r.data),
|
||||
getOidc: () => apiClient.get('/admin/oidc').then(r => r.data),
|
||||
|
||||
@@ -3,9 +3,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { useAdminUserActions } from './useAdminUserActions';
|
||||
|
||||
// FE-HOOK-ADMUSERACT-001 to FE-HOOK-ADMUSERACT-007
|
||||
// FE-HOOK-ADMUSERACT-001 to FE-HOOK-ADMUSERACT-009
|
||||
|
||||
const api = vi.hoisted(() => ({ rotateJwtSecret: vi.fn(), resetUserPasskeys: vi.fn(), resetUserMfa: vi.fn() }));
|
||||
const api = vi.hoisted(() => ({
|
||||
rotateJwtSecret: vi.fn(),
|
||||
resetUserPasskeys: vi.fn(),
|
||||
resetUserMfa: vi.fn(),
|
||||
unlinkUserSso: vi.fn(),
|
||||
}));
|
||||
vi.mock('../../api/client', () => ({ adminApi: api }));
|
||||
|
||||
const t = (key: string, params?: Record<string, unknown>) => (params ? `${key} ${JSON.stringify(params)}` : key);
|
||||
@@ -28,6 +33,7 @@ beforeEach(() => {
|
||||
api.rotateJwtSecret.mockReset().mockResolvedValue({});
|
||||
api.resetUserPasskeys.mockReset().mockResolvedValue({ deleted: 2 });
|
||||
api.resetUserMfa.mockReset().mockResolvedValue({ success: true });
|
||||
api.unlinkUserSso.mockReset().mockResolvedValue({ success: true, unlinked: true });
|
||||
});
|
||||
|
||||
describe('useAdminUserActions', () => {
|
||||
@@ -101,4 +107,26 @@ describe('useAdminUserActions', () => {
|
||||
expect(admin.toast.error).toHaveBeenCalledWith('common.error');
|
||||
expect(ok).toBe(false);
|
||||
});
|
||||
|
||||
it('FE-HOOK-ADMUSERACT-008: removing the SSO link calls the API, toasts and reports success', async () => {
|
||||
const { admin, actions } = mount();
|
||||
let ok: boolean | undefined;
|
||||
await act(async () => {
|
||||
ok = await actions.unlinkSso({ id: 11 });
|
||||
});
|
||||
expect(api.unlinkUserSso).toHaveBeenCalledWith(11);
|
||||
expect(admin.toast.success).toHaveBeenCalledWith('admin.sso.unlinkDone');
|
||||
expect(ok).toBe(true);
|
||||
});
|
||||
|
||||
it('FE-HOOK-ADMUSERACT-009: a failed SSO unlink toasts and reports failure', async () => {
|
||||
api.unlinkUserSso.mockRejectedValue(new Error('x'));
|
||||
const { admin, actions } = mount();
|
||||
let ok: boolean | undefined;
|
||||
await act(async () => {
|
||||
ok = await actions.unlinkSso({ id: 11 });
|
||||
});
|
||||
expect(admin.toast.error).toHaveBeenCalledWith('common.error');
|
||||
expect(ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,8 +12,8 @@ interface UserActionsHost {
|
||||
/**
|
||||
* The irreversible user actions behind the admin dialogs and their phone sheets:
|
||||
* rotating the JWT secret, which signs everyone out including the admin, removing a
|
||||
* user's passkeys and switching off a user's two-factor login. Each shell keeps its own
|
||||
* confirm step and busy state around them.
|
||||
* user's passkeys, switching off a user's two-factor login and removing a user's SSO
|
||||
* link. Each shell keeps its own confirm step and busy state around them.
|
||||
*/
|
||||
export function useAdminUserActions(admin: UserActionsHost, t: TranslationFn) {
|
||||
const { toast, setRotatingJwt, setShowRotateJwtModal, logout, navigate } = admin;
|
||||
@@ -55,5 +55,17 @@ export function useAdminUserActions(admin: UserActionsHost, t: TranslationFn) {
|
||||
}
|
||||
};
|
||||
|
||||
return { rotateJwt, resetPasskeys, resetMfa };
|
||||
/** Forgets the user's SSO identity so the next SSO sign-in links again (#3065); true when it worked. */
|
||||
const unlinkSso = async (user: { id: number }): Promise<boolean> => {
|
||||
try {
|
||||
await adminApi.unlinkUserSso(user.id);
|
||||
toast.success(t('admin.sso.unlinkDone'));
|
||||
return true;
|
||||
} catch {
|
||||
toast.error(t('common.error'));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return { rotateJwt, resetPasskeys, resetMfa, unlinkSso };
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Fingerprint,
|
||||
RefreshCw,
|
||||
ShieldOff,
|
||||
Unlink,
|
||||
UserCog,
|
||||
UserPlus,
|
||||
} from 'lucide-react';
|
||||
@@ -114,14 +115,17 @@ export default function AdminUserModals({ admin, t }: AdminUserModalsProps): Rea
|
||||
rotatingJwt,
|
||||
handleCreateUser,
|
||||
handleSaveUser,
|
||||
oidcConfigured,
|
||||
} = admin;
|
||||
const { rotateJwt, resetPasskeys, resetMfa } = useAdminUserActions(admin, t);
|
||||
const { rotateJwt, resetPasskeys, resetMfa, unlinkSso } = useAdminUserActions(admin, t);
|
||||
const [showCreatePw, setShowCreatePw] = React.useState(false);
|
||||
const [showEditPw, setShowEditPw] = React.useState(false);
|
||||
// The user whose passkeys wait for the admin's answer in the confirm dialog.
|
||||
const [passkeyResetUser, setPasskeyResetUser] = React.useState<typeof editingUser>(null);
|
||||
// Same for the two-factor reset; an admin turns off their own 2FA in Settings, not here.
|
||||
const [mfaResetUser, setMfaResetUser] = React.useState<typeof editingUser>(null);
|
||||
// Same for removing the SSO link, offered only while SSO is set up (#3065).
|
||||
const [ssoUnlinkUser, setSsoUnlinkUser] = React.useState<typeof editingUser>(null);
|
||||
const uid = useId();
|
||||
const createId = `${uid}-create`;
|
||||
const editId = `${uid}-edit`;
|
||||
@@ -318,6 +322,23 @@ export default function AdminUserModals({ admin, t }: AdminUserModalsProps): Rea
|
||||
</div>
|
||||
</DialogSection>
|
||||
)}
|
||||
{oidcConfigured && editingUser && editingUser.id !== currentUser?.id && (
|
||||
<DialogSection label={t('admin.sso.unlink')}>
|
||||
<div className="flex flex-wrap items-center gap-3 rounded-[12px] border border-edge-faint bg-surface-secondary px-3.5 py-3">
|
||||
<p className="m-0 min-w-0 flex-1 basis-52 leading-snug text-content-muted" style={fs(12, 'body')}>
|
||||
{t('admin.sso.unlinkHint')}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSsoUnlinkUser(editingUser)}
|
||||
className={`${SETTINGS_BUTTON_DANGER} flex-none`}
|
||||
style={fs(12.5, 'body')}
|
||||
>
|
||||
<Unlink size={14} /> {t('admin.sso.unlink')}
|
||||
</button>
|
||||
</div>
|
||||
</DialogSection>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DialogShell>
|
||||
@@ -480,6 +501,19 @@ docker run -d --name trek \\
|
||||
confirmLabel={t('admin.mfa.reset')}
|
||||
danger
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={ssoUnlinkUser !== null}
|
||||
onClose={() => setSsoUnlinkUser(null)}
|
||||
onConfirm={async () => {
|
||||
if (!ssoUnlinkUser) return;
|
||||
await unlinkSso(ssoUnlinkUser);
|
||||
}}
|
||||
title={t('admin.sso.unlink')}
|
||||
message={ssoUnlinkUser ? t('admin.sso.unlinkConfirm', { name: ssoUnlinkUser.username }) : ''}
|
||||
confirmLabel={t('admin.sso.unlink')}
|
||||
danger
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -823,6 +823,14 @@ export class UsersRepository extends TrekRepository<Users> {
|
||||
await this.nativeUpdate({ id }, { oidc_sub: sub, oidc_issuer: issuer });
|
||||
}
|
||||
|
||||
/**
|
||||
* `UPDATE users SET oidc_sub = NULL, oidc_issuer = NULL WHERE id = ?`: the
|
||||
* admin's "Remove SSO link" (#3065). No `updated_at`, like the link it undoes.
|
||||
*/
|
||||
async clearOidcIdentity(id: number): Promise<void> {
|
||||
await this.nativeUpdate({ id }, { oidc_sub: null, oidc_issuer: null });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// O8 — claims-based role change
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@@ -156,6 +156,22 @@ export class AdminController {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Delete('users/:id/sso')
|
||||
async unlinkUserSso(@CurrentUser() user: User, @Param('id') id: string, @Req() req: Request) {
|
||||
const result = await this.admin.unlinkUserSso(id, user.id);
|
||||
if (result.unlinked) {
|
||||
await this.audit.writeAudit({
|
||||
userId: user.id,
|
||||
action: 'admin.user_sso_unlink',
|
||||
resource: String(id),
|
||||
ip: getClientIp(req),
|
||||
details: { targetUser: result.email },
|
||||
});
|
||||
logInfo(`Admin ${user.email} removed the SSO link of user ${result.email}`);
|
||||
}
|
||||
return { success: true, unlinked: result.unlinked };
|
||||
}
|
||||
|
||||
// ── Stats / permissions / audit ──
|
||||
@Get('stats')
|
||||
stats() {
|
||||
|
||||
@@ -398,6 +398,35 @@ export class AdminService {
|
||||
return { success: true, email: target.email };
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the SSO identity stored on another account (#3065).
|
||||
*
|
||||
* A sign-in that arrives with a different subject of the same provider is
|
||||
* refused, because that is another person at the provider as far as TREK can
|
||||
* tell. When the provider itself re-issued the subject (a migrated or rebuilt
|
||||
* IdP, a changed `sub` format), the account owner is locked out and editing the
|
||||
* email does not help. Clearing the link hands the decision to the admin: the
|
||||
* next SSO sign-in links again under the same rules as a first one, so it still
|
||||
* needs a verified email and an address the account did not set itself.
|
||||
*
|
||||
* Never for the caller themselves, for the same reason as resetUserMfa: a
|
||||
* stolen admin session must not be able to loosen the login of the account it
|
||||
* came from.
|
||||
*/
|
||||
async unlinkUserSso(id: string, actingUserId: number): Promise<{ email: string; unlinked: boolean }> {
|
||||
const targetId = Number(id);
|
||||
// SECURITY: plain JS comparison, kept here like resetUserMfa's (R4).
|
||||
if (targetId === actingUserId) {
|
||||
throw new DomainError(400, 'You cannot remove the SSO link of your own account');
|
||||
}
|
||||
const target = await this.users.findById(targetId);
|
||||
if (!target || target.is_guest) throw new DomainError(404, 'User not found');
|
||||
if (!target.oidc_sub && !target.oidc_issuer) return { email: target.email, unlinked: false };
|
||||
|
||||
await this.users.clearOidcIdentity(targetId);
|
||||
return { email: target.email, unlinked: true };
|
||||
}
|
||||
|
||||
// ── Stats ──────────────────────────────────────────────────────────────────
|
||||
|
||||
async getStats() {
|
||||
|
||||
@@ -686,7 +686,7 @@ export class OidcService implements OnModuleDestroy {
|
||||
// provider, never this account under a new name.
|
||||
if (user.oidc_sub && user.oidc_issuer === config.issuer) {
|
||||
logWarn(
|
||||
`[OIDC] Refused to link a sign-in to user ${user.id} by email: it is linked to another subject of this provider.`,
|
||||
`[OIDC] Refused to link a sign-in to user ${user.id} by email: it is linked to another subject of this provider. If the provider changed the subject of the same person, an admin can remove the SSO link of this user in the admin panel.`,
|
||||
);
|
||||
return { error: 'email_link_blocked' };
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import { NotificationsModule } from '../../src/nest/notifications/notifications.
|
||||
import { OidcModule } from '../../src/nest/oidc/oidc.module';
|
||||
import { RealtimeModule } from '../../src/nest/realtime/realtime.module';
|
||||
import { SettingsModule } from '../../src/nest/settings/settings.module';
|
||||
import { countRows, findRow, insertRow } from '../helpers/factories/rows';
|
||||
import { countRows, findRow, insertRow, updateRows } from '../helpers/factories/rows';
|
||||
import { readAppSetting } from '../helpers/factories/settings';
|
||||
import { makeTrip } from '../helpers/factories/trips';
|
||||
import { makeAdmin, makeUser } from '../helpers/factories/users';
|
||||
@@ -262,6 +262,27 @@ describe('Admin e2e (real auth + admin guard + temp SQLite)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE /users/:id/sso clears the stored SSO identity, audits it, and is admin-only (#3065)', async () => {
|
||||
const { user } = await makeUser(orm, { email: 'sso-moved@example.test', username: 'sso-moved' });
|
||||
await updateRows(orm, Users, { id: user.id }, { oidc_sub: 'sub-old', oidc_issuer: 'https://idp.example.test' });
|
||||
|
||||
const denied = await request(server).delete(`/api/admin/users/${user.id}/sso`).set('Cookie', sessionCookie(2));
|
||||
expect(denied.status).toBe(403);
|
||||
|
||||
const res = await request(server).delete(`/api/admin/users/${user.id}/sso`).set('Cookie', sessionCookie(1));
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toEqual({ success: true, unlinked: true });
|
||||
const row = await findRow(orm, Users, { id: user.id });
|
||||
expect(row && { oidc_sub: row.oidc_sub ?? null, oidc_issuer: row.oidc_issuer ?? null }).toEqual({
|
||||
oidc_sub: null,
|
||||
oidc_issuer: null,
|
||||
});
|
||||
expect(await findRow(orm, AuditLog, { action: 'admin.user_sso_unlink', resource: String(user.id) })).not.toBeNull();
|
||||
|
||||
const self = await request(server).delete('/api/admin/users/1/sso').set('Cookie', sessionCookie(1));
|
||||
expect(self.status).toBe(400);
|
||||
});
|
||||
|
||||
it('404 on the dev-only test-notification outside development', async () => {
|
||||
const res = await request(server).post('/api/admin/dev/test-notification').set('Cookie', sessionCookie(1)).send({});
|
||||
expect(res.status).toBe(404);
|
||||
|
||||
@@ -669,6 +669,25 @@ describe('UsersRepository', () => {
|
||||
expect(row.updated_at).toBe('2020-01-01 00:00:00');
|
||||
});
|
||||
|
||||
it('USERSREPO-088: clearOidcIdentity nulls sub+issuer WITHOUT touching updated_at (#3065)', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
await updateRows(
|
||||
t,
|
||||
Users,
|
||||
{ id: user.id },
|
||||
{ oidc_sub: 'sub-1', oidc_issuer: 'https://idp.example.com', updated_at: '2020-01-01 00:00:00' },
|
||||
);
|
||||
await users.clearOidcIdentity(user.id);
|
||||
const row = (await findRow(t, Users, { id: user.id })) as {
|
||||
oidc_sub: string | null;
|
||||
oidc_issuer: string | null;
|
||||
updated_at: string;
|
||||
};
|
||||
expect(row.oidc_sub).toBeNull();
|
||||
expect(row.oidc_issuer).toBeNull();
|
||||
expect(row.updated_at).toBe('2020-01-01 00:00:00');
|
||||
});
|
||||
|
||||
it('USERSREPO-042: setRole (O8) writes role WITHOUT touching updated_at', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
await updateRows(t, Users, { id: user.id }, { updated_at: '2020-01-01 00:00:00' });
|
||||
|
||||
@@ -357,6 +357,42 @@ describe('AdminController addons + sessions + jwt + defaults', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('AdminController SSO unlink (#3065)', () => {
|
||||
it('ADMIN-CTRL-SSO-001: audits a removed link and answers unlinked: true', async () => {
|
||||
writeAudit.mockClear();
|
||||
const unlinkUserSso = vi.fn().mockResolvedValue({ email: 'a@b.c', unlinked: true });
|
||||
expect(await adminCtl(svc({ unlinkUserSso } as Partial<AdminService>)).unlinkUserSso(user, '4', req)).toEqual({
|
||||
success: true,
|
||||
unlinked: true,
|
||||
});
|
||||
expect(unlinkUserSso).toHaveBeenCalledWith('4', user.id);
|
||||
expect(writeAudit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ action: 'admin.user_sso_unlink', resource: '4', details: { targetUser: 'a@b.c' } }),
|
||||
);
|
||||
});
|
||||
|
||||
it('ADMIN-CTRL-SSO-002: writes no audit row when there was no link, and maps the self refusal', async () => {
|
||||
writeAudit.mockClear();
|
||||
expect(
|
||||
await adminCtl(
|
||||
svc({ unlinkUserSso: vi.fn().mockResolvedValue({ email: 'a@b.c', unlinked: false }) } as Partial<AdminService>),
|
||||
).unlinkUserSso(user, '4', req),
|
||||
).toEqual({ success: true, unlinked: false });
|
||||
expect(writeAudit).not.toHaveBeenCalled();
|
||||
expect(
|
||||
await thrown(() =>
|
||||
adminCtl(
|
||||
svc({
|
||||
unlinkUserSso: vi
|
||||
.fn()
|
||||
.mockRejectedValue(new DomainError(400, 'You cannot remove the SSO link of your own account')),
|
||||
} as Partial<AdminService>),
|
||||
).unlinkUserSso(user, '1', req),
|
||||
),
|
||||
).toEqual({ status: 400, body: { error: 'You cannot remove the SSO link of your own account' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('AdminController read-only getters', () => {
|
||||
it('return service values verbatim', async () => {
|
||||
expect(
|
||||
|
||||
@@ -998,6 +998,61 @@ describe('resetUserMfa', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// #3065: the provider re-issued a user's subject, so every sign-in hits
|
||||
// "linked to another subject of this provider" and editing the email cannot help.
|
||||
describe('unlinkUserSso', () => {
|
||||
it('ADMIN-SVC-097 — clears sub and issuer so the next SSO sign-in links like a first one', async () => {
|
||||
const admin = createAdmin(testDb);
|
||||
const { user } = createUser(testDb);
|
||||
await updateRows(orm, Users, { id: user.id }, { oidc_sub: 'sub-old', oidc_issuer: 'https://idp.example.com' });
|
||||
|
||||
const result = await svc.unlinkUserSso(String(user.id), admin.user.id);
|
||||
|
||||
expect(result).toEqual({ email: user.email, unlinked: true });
|
||||
const row = await readUser(orm, user.id);
|
||||
expect({ oidc_sub: row.oidc_sub, oidc_issuer: row.oidc_issuer }).toEqual({ oidc_sub: null, oidc_issuer: null });
|
||||
});
|
||||
|
||||
it('ADMIN-SVC-098 — an account without an SSO link answers unlinked: false and stays as it is', async () => {
|
||||
const admin = createAdmin(testDb);
|
||||
const { user } = createUser(testDb);
|
||||
|
||||
expect(await svc.unlinkUserSso(String(user.id), admin.user.id)).toEqual({ email: user.email, unlinked: false });
|
||||
});
|
||||
|
||||
it('ADMIN-SVC-099 — refuses the callers own account and keeps its link', async () => {
|
||||
const admin = createAdmin(testDb);
|
||||
await updateRows(
|
||||
orm,
|
||||
Users,
|
||||
{ id: admin.user.id },
|
||||
{ oidc_sub: 'sub-admin', oidc_issuer: 'https://idp.example.com' },
|
||||
);
|
||||
|
||||
const result = (await asLegacyResult(svc.unlinkUserSso(String(admin.user.id), admin.user.id))) as {
|
||||
error?: string;
|
||||
status?: number;
|
||||
};
|
||||
|
||||
expect(result.status).toBe(400);
|
||||
expect(result.error).toMatch(/your own/i);
|
||||
expect((await readUser(orm, admin.user.id)).oidc_sub).toBe('sub-admin');
|
||||
});
|
||||
|
||||
it('ADMIN-SVC-100 — 404 for a user that is not there, and for a guest', async () => {
|
||||
const admin = createAdmin(testDb);
|
||||
const { user: guest } = createUser(testDb);
|
||||
await updateRows(orm, Users, { id: guest.id }, { is_guest: 1 });
|
||||
|
||||
expect((await asLegacyResult(svc.unlinkUserSso('99999', admin.user.id))) as { status?: number }).toMatchObject({
|
||||
status: 404,
|
||||
});
|
||||
expect(
|
||||
(await asLegacyResult(svc.unlinkUserSso(String(guest.id), admin.user.id))) as { status?: number },
|
||||
).toMatchObject({ status: 404 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkVersion on a centrally administered install', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
|
||||
@@ -1052,6 +1052,11 @@ const admin: TranslationStrings = {
|
||||
'يعطّل المصادقة الثنائية لهذا المستخدم، مثلاً بعد فقدان هاتفه. يتم تسجيل خروجه من كل مكان، ويمكنه إعدادها من جديد بعد تسجيل الدخول بكلمة مروره.',
|
||||
'admin.mfa.resetConfirm': 'إيقاف المصادقة الثنائية لـ {name}؟',
|
||||
'admin.mfa.resetDone': 'تمت إعادة تعيين المصادقة الثنائية',
|
||||
'admin.sso.unlink': 'إزالة ربط SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'ينسى أي تسجيل دخول لدى مزوّد SSO ينتمي إلى هذا الحساب، مثلاً بعد أن غيّر المزوّد معرّفات المستخدمين. أول تسجيل دخول عبر SSO بعنوان بريد مؤكَّد يربط الحساب من جديد.',
|
||||
'admin.sso.unlinkConfirm': 'إزالة ربط SSO لـ {name}؟',
|
||||
'admin.sso.unlinkDone': 'تمت إزالة ربط SSO',
|
||||
'admin.danger.title': 'منطقة الخطر',
|
||||
'admin.rotateJwt.title': 'تدوير سر JWT',
|
||||
'admin.rotateJwt.hint': 'أنشئ سرًّا جديدًا لتوقيع JWT. ستُلغى جميع الجلسات النشطة فورًا.',
|
||||
|
||||
@@ -1030,6 +1030,11 @@ const admin: TranslationStrings = {
|
||||
'Bu istifadəçi üçün iki faktorlu doğrulamanı söndürür, məsələn telefon itirildikdən sonra. İstifadəçi hər yerdən çıxarılır və parolu ilə daxil olduqdan sonra onu yenidən quraşdıra bilər.',
|
||||
'admin.mfa.resetConfirm': '{name} üçün iki faktorlu doğrulama söndürülsün?',
|
||||
'admin.mfa.resetDone': 'İki faktorlu doğrulama sıfırlandı',
|
||||
'admin.sso.unlink': 'SSO bağlantısını sil',
|
||||
'admin.sso.unlinkHint':
|
||||
'SSO provayderindəki hansı girişin bu hesaba aid olduğunu unudur, məsələn provayder istifadəçi ID-lərini dəyişdikdən sonra. Təsdiqlənmiş e-poçt ünvanı ilə növbəti SSO girişi hesabı yenidən bağlayır.',
|
||||
'admin.sso.unlinkConfirm': '{name} üçün SSO bağlantısı silinsin?',
|
||||
'admin.sso.unlinkDone': 'SSO bağlantısı silindi',
|
||||
'admin.danger.title': 'Təhlükəli zona',
|
||||
'admin.rotateJwt.title': 'JWT sirrini yenilə',
|
||||
'admin.rotateJwt.hint': 'Yeni JWT imza sirri yaradın. Bütün aktiv sessiyalar dərhal etibarsız olacaq.',
|
||||
|
||||
@@ -1004,6 +1004,11 @@ const admin: TranslationStrings = {
|
||||
'Desativa a autenticação em dois fatores deste usuário, por exemplo após a perda do celular. Ele é desconectado de todos os lugares e pode configurá-la novamente depois de entrar com a senha.',
|
||||
'admin.mfa.resetConfirm': 'Desativar a autenticação em dois fatores de {name}?',
|
||||
'admin.mfa.resetDone': 'Autenticação em dois fatores redefinida',
|
||||
'admin.sso.unlink': 'Remover vínculo SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Esquece qual login no seu provedor SSO pertence a esta conta, por exemplo depois que o provedor mudou os IDs de usuário. O próximo login via SSO com um e-mail confirmado vincula a conta novamente.',
|
||||
'admin.sso.unlinkConfirm': 'Remover o vínculo SSO de {name}?',
|
||||
'admin.sso.unlinkDone': 'Vínculo SSO removido',
|
||||
'admin.danger.title': 'Zona de perigo',
|
||||
'admin.rotateJwt.title': 'Rotacionar segredo JWT',
|
||||
'admin.rotateJwt.hint':
|
||||
|
||||
@@ -1018,6 +1018,11 @@ const admin: TranslationStrings = {
|
||||
"Desactiva l'autenticació de dos factors d'aquest usuari, per exemple després de perdre el telèfon. Es tanca la seva sessió a tot arreu i podrà tornar-la a configurar després d'iniciar sessió amb la contrasenya.",
|
||||
'admin.mfa.resetConfirm': "Vols desactivar l'autenticació de dos factors de {name}?",
|
||||
'admin.mfa.resetDone': 'Autenticació de dos factors restablerta',
|
||||
'admin.sso.unlink': "Elimina l'enllaç SSO",
|
||||
'admin.sso.unlinkHint':
|
||||
"Oblida quin inici de sessió del teu proveïdor SSO pertany a aquest compte, per exemple després que el proveïdor hagi canviat els identificadors d'usuari. El següent inici de sessió SSO amb una adreça confirmada torna a enllaçar el compte.",
|
||||
'admin.sso.unlinkConfirm': "Vols eliminar l'enllaç SSO de {name}?",
|
||||
'admin.sso.unlinkDone': 'Enllaç SSO eliminat',
|
||||
'admin.danger.title': 'Zona de perill',
|
||||
'admin.rotateJwt.title': 'Rota el secret JWT',
|
||||
'admin.rotateJwt.hint':
|
||||
|
||||
@@ -1012,6 +1012,11 @@ const admin: TranslationStrings = {
|
||||
'Vypne pro tohoto uživatele dvoufázové ověření, například po ztrátě telefonu. Bude odhlášen všude a po přihlášení heslem si ho může nastavit znovu.',
|
||||
'admin.mfa.resetConfirm': 'Vypnout dvoufázové ověření uživatele {name}?',
|
||||
'admin.mfa.resetDone': 'Dvoufázové ověření bylo resetováno',
|
||||
'admin.sso.unlink': 'Odebrat propojení SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Zapomene, které přihlášení u vašeho poskytovatele SSO patří k tomuto účtu, například poté, co poskytovatel změnil ID uživatelů. Další přihlášení přes SSO s potvrzenou e-mailovou adresou účet znovu propojí.',
|
||||
'admin.sso.unlinkConfirm': 'Odebrat propojení SSO uživatele {name}?',
|
||||
'admin.sso.unlinkDone': 'Propojení SSO bylo odebráno',
|
||||
'admin.danger.title': 'Nebezpečná zóna',
|
||||
'admin.rotateJwt.title': 'Obnovit tajný klíč JWT',
|
||||
'admin.rotateJwt.hint': 'Vygeneruje nový podpisový klíč JWT. Všechny aktivní relace budou okamžitě zneplatněny.',
|
||||
|
||||
@@ -1014,6 +1014,11 @@ const admin: TranslationStrings = {
|
||||
'Schaltet die Zwei-Faktor-Authentifizierung für diesen Benutzer ab, zum Beispiel nach dem Verlust des Handys. Die Person wird überall abgemeldet und kann sie nach der Anmeldung mit dem Passwort neu einrichten.',
|
||||
'admin.mfa.resetConfirm': 'Zwei-Faktor-Authentifizierung für {name} abschalten?',
|
||||
'admin.mfa.resetDone': 'Zwei-Faktor-Authentifizierung zurückgesetzt',
|
||||
'admin.sso.unlink': 'SSO-Verknüpfung entfernen',
|
||||
'admin.sso.unlinkHint':
|
||||
'Vergisst, welche Anmeldung bei deinem SSO-Anbieter zu diesem Konto gehört, zum Beispiel nachdem der Anbieter seine Benutzer-IDs geändert hat. Die nächste SSO-Anmeldung mit bestätigter E-Mail-Adresse verknüpft das Konto wieder.',
|
||||
'admin.sso.unlinkConfirm': 'SSO-Verknüpfung von {name} entfernen?',
|
||||
'admin.sso.unlinkDone': 'SSO-Verknüpfung entfernt',
|
||||
'admin.danger.title': 'Gefahrenzone',
|
||||
'admin.rotateJwt.title': 'JWT-Secret erneuern',
|
||||
'admin.rotateJwt.hint': 'Erzeugt ein neues JWT-Signatur-Secret. Alle aktiven Sitzungen werden sofort ungültig.',
|
||||
|
||||
@@ -982,6 +982,11 @@ const admin: TranslationStrings = {
|
||||
'Switches off two-factor authentication for this user, for example after a lost phone. They are signed out everywhere and can set it up again after logging in with their password.',
|
||||
'admin.mfa.resetConfirm': 'Turn off two-factor authentication for {name}?',
|
||||
'admin.mfa.resetDone': 'Two-factor authentication reset',
|
||||
'admin.sso.unlink': 'Remove SSO link',
|
||||
'admin.sso.unlinkHint':
|
||||
'Forgets which sign-in at your SSO provider belongs to this account, for example after the provider changed its user IDs. The next SSO sign-in with a confirmed email address links the account again.',
|
||||
'admin.sso.unlinkConfirm': 'Remove the SSO link of {name}?',
|
||||
'admin.sso.unlinkDone': 'SSO link removed',
|
||||
'admin.danger.title': 'Danger Zone',
|
||||
'admin.rotateJwt.title': 'Rotate JWT Secret',
|
||||
'admin.rotateJwt.hint': 'Generate a new JWT signing secret. All active sessions will be invalidated immediately.',
|
||||
|
||||
@@ -1022,6 +1022,11 @@ const admin: TranslationStrings = {
|
||||
'Desactiva la autenticación en dos factores de este usuario, por ejemplo tras perder el móvil. Se cierra su sesión en todas partes y podrá volver a configurarla después de iniciar sesión con su contraseña.',
|
||||
'admin.mfa.resetConfirm': '¿Desactivar la autenticación en dos factores de {name}?',
|
||||
'admin.mfa.resetDone': 'Autenticación en dos factores restablecida',
|
||||
'admin.sso.unlink': 'Quitar vínculo SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Olvida qué inicio de sesión de tu proveedor SSO pertenece a esta cuenta, por ejemplo después de que el proveedor cambiara los ID de usuario. El siguiente inicio de sesión SSO con un correo confirmado vuelve a vincular la cuenta.',
|
||||
'admin.sso.unlinkConfirm': '¿Quitar el vínculo SSO de {name}?',
|
||||
'admin.sso.unlinkDone': 'Vínculo SSO quitado',
|
||||
'admin.danger.title': 'Zona de peligro',
|
||||
'admin.rotateJwt.title': 'Rotar secreto JWT',
|
||||
'admin.rotateJwt.hint':
|
||||
|
||||
@@ -1004,6 +1004,11 @@ const admin: TranslationStrings = {
|
||||
'Lülitab selle kasutaja kaheastmelise autentimise välja, näiteks pärast telefoni kaotamist. Kasutaja logitakse kõikjalt välja ja ta saab selle pärast parooliga sisselogimist uuesti seadistada.',
|
||||
'admin.mfa.resetConfirm': 'Kas lülitada kasutaja {name} kaheastmeline autentimine välja?',
|
||||
'admin.mfa.resetDone': 'Kaheastmeline autentimine lähtestatud',
|
||||
'admin.sso.unlink': 'Eemalda SSO-seos',
|
||||
'admin.sso.unlinkHint':
|
||||
'Unustab, milline sisselogimine teie SSO-teenusepakkuja juures sellele kontole kuulub, näiteks pärast seda, kui teenusepakkuja muutis kasutajate ID-sid. Järgmine kinnitatud e-posti aadressiga SSO-sisselogimine seob konto uuesti.',
|
||||
'admin.sso.unlinkConfirm': 'Kas eemaldada kasutaja {name} SSO-seos?',
|
||||
'admin.sso.unlinkDone': 'SSO-seos eemaldatud',
|
||||
'admin.danger.title': 'Ohutsoon',
|
||||
'admin.rotateJwt.title': 'Roteeri JWT saladus',
|
||||
'admin.rotateJwt.hint': 'Loo uus JWT allkirjastamise saladus. Kõik aktiivsed seansid muutuvad kohe kehtetuks.',
|
||||
|
||||
@@ -1023,6 +1023,11 @@ const admin: TranslationStrings = {
|
||||
"Désactive l'authentification à deux facteurs de cet utilisateur, par exemple après la perte d'un téléphone. Il est déconnecté partout et pourra la reconfigurer après s'être connecté avec son mot de passe.",
|
||||
'admin.mfa.resetConfirm': "Désactiver l'authentification à deux facteurs de {name} ?",
|
||||
'admin.mfa.resetDone': 'Authentification à deux facteurs réinitialisée',
|
||||
'admin.sso.unlink': 'Supprimer la liaison SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Oublie quelle connexion chez votre fournisseur SSO appartient à ce compte, par exemple après un changement des identifiants utilisateur chez le fournisseur. La prochaine connexion SSO avec une adresse e-mail confirmée relie à nouveau le compte.',
|
||||
'admin.sso.unlinkConfirm': 'Supprimer la liaison SSO de {name} ?',
|
||||
'admin.sso.unlinkDone': 'Liaison SSO supprimée',
|
||||
'admin.danger.title': 'Zone de danger',
|
||||
'admin.rotateJwt.title': 'Renouveler le secret JWT',
|
||||
'admin.rotateJwt.hint':
|
||||
|
||||
@@ -1032,6 +1032,11 @@ const admin: TranslationStrings = {
|
||||
'Απενεργοποιεί τον έλεγχο ταυτότητας δύο παραγόντων για αυτόν τον χρήστη, για παράδειγμα μετά από απώλεια του τηλεφώνου. Αποσυνδέεται παντού και μπορεί να τον ρυθμίσει ξανά αφού συνδεθεί με τον κωδικό του.',
|
||||
'admin.mfa.resetConfirm': 'Απενεργοποίηση του ελέγχου ταυτότητας δύο παραγόντων για τον/την {name};',
|
||||
'admin.mfa.resetDone': 'Ο έλεγχος ταυτότητας δύο παραγόντων επαναφέρθηκε',
|
||||
'admin.sso.unlink': 'Αφαίρεση σύνδεσης SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Ξεχνά ποια σύνδεση στον πάροχο SSO ανήκει σε αυτόν τον λογαριασμό, για παράδειγμα αφού ο πάροχος άλλαξε τα αναγνωριστικά χρηστών. Η επόμενη σύνδεση SSO με επιβεβαιωμένο email συνδέει ξανά τον λογαριασμό.',
|
||||
'admin.sso.unlinkConfirm': 'Αφαίρεση της σύνδεσης SSO του/της {name};',
|
||||
'admin.sso.unlinkDone': 'Η σύνδεση SSO αφαιρέθηκε',
|
||||
'admin.danger.title': 'Επικίνδυνη ζώνη',
|
||||
'admin.rotateJwt.title': 'Εναλλαγή μυστικού JWT',
|
||||
'admin.rotateJwt.hint': 'Δημιουργεί νέο μυστικό υπογραφής JWT. Όλες οι ενεργές συνεδρίες θα ακυρωθούν αμέσως.',
|
||||
|
||||
@@ -970,6 +970,11 @@ const admin: TranslationStrings = {
|
||||
'מכבה את האימות הדו-שלבי של המשתמש הזה, למשל אחרי שאבד לו הטלפון. הוא ינותק מכל המכשירים ויוכל להגדיר אותו מחדש אחרי התחברות עם הסיסמה.',
|
||||
'admin.mfa.resetConfirm': 'לכבות את האימות הדו-שלבי של {name}?',
|
||||
'admin.mfa.resetDone': 'האימות הדו-שלבי אופס',
|
||||
'admin.sso.unlink': 'הסרת קישור SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'שוכח איזו כניסה אצל ספק ה-SSO שייכת לחשבון הזה, למשל אחרי שהספק שינה את מזהי המשתמשים. הכניסה הבאה דרך SSO עם כתובת דוא״ל מאומתת מקשרת את החשבון מחדש.',
|
||||
'admin.sso.unlinkConfirm': 'להסיר את קישור ה-SSO של {name}?',
|
||||
'admin.sso.unlinkDone': 'קישור ה-SSO הוסר',
|
||||
'admin.danger.title': 'אזור מסוכן',
|
||||
'admin.rotateJwt.title': 'החלפת סוד ה-JWT',
|
||||
'admin.rotateJwt.hint': 'יצירת סוד חתימה חדש ל-JWT. כל ההפעלות הפעילות יבוטלו מיד.',
|
||||
|
||||
@@ -1021,6 +1021,11 @@ const admin: TranslationStrings = {
|
||||
'Kikapcsolja a felhasználó kétlépcsős hitelesítését, például elveszett telefon esetén. Mindenhonnan kijelentkezik, és a jelszavával való belépés után újra beállíthatja.',
|
||||
'admin.mfa.resetConfirm': 'Kikapcsolod {name} kétlépcsős hitelesítését?',
|
||||
'admin.mfa.resetDone': 'A kétlépcsős hitelesítés visszaállítva',
|
||||
'admin.sso.unlink': 'SSO-kapcsolat eltávolítása',
|
||||
'admin.sso.unlinkHint':
|
||||
'Elfelejti, hogy az SSO-szolgáltatónál melyik bejelentkezés tartozik ehhez a fiókhoz, például miután a szolgáltató megváltoztatta a felhasználói azonosítókat. A következő SSO-bejelentkezés megerősített e-mail-címmel újra összekapcsolja a fiókot.',
|
||||
'admin.sso.unlinkConfirm': 'Eltávolítod {name} SSO-kapcsolatát?',
|
||||
'admin.sso.unlinkDone': 'Az SSO-kapcsolat eltávolítva',
|
||||
'admin.danger.title': 'Veszélyzóna',
|
||||
'admin.rotateJwt.title': 'JWT-titok cseréje',
|
||||
'admin.rotateJwt.hint': 'Új JWT-aláírási titkot hoz létre. Az összes aktív munkamenet azonnal érvénytelenné válik.',
|
||||
|
||||
@@ -992,6 +992,11 @@ const admin: TranslationStrings = {
|
||||
'Menonaktifkan autentikasi dua faktor untuk pengguna ini, misalnya setelah ponsel hilang. Pengguna dikeluarkan dari semua sesi dan dapat mengaturnya lagi setelah masuk dengan kata sandinya.',
|
||||
'admin.mfa.resetConfirm': 'Nonaktifkan autentikasi dua faktor untuk {name}?',
|
||||
'admin.mfa.resetDone': 'Autentikasi dua faktor telah diatur ulang',
|
||||
'admin.sso.unlink': 'Hapus tautan SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Melupakan login mana di penyedia SSO yang dimiliki akun ini, misalnya setelah penyedia mengubah ID pengguna. Login SSO berikutnya dengan alamat email yang terkonfirmasi menautkan akun ini lagi.',
|
||||
'admin.sso.unlinkConfirm': 'Hapus tautan SSO untuk {name}?',
|
||||
'admin.sso.unlinkDone': 'Tautan SSO dihapus',
|
||||
'admin.danger.title': 'Zona Berbahaya',
|
||||
'admin.rotateJwt.title': 'Putar Secret JWT',
|
||||
'admin.rotateJwt.hint': 'Buat secret penandatanganan JWT baru. Semua sesi aktif akan langsung tidak berlaku.',
|
||||
|
||||
@@ -1015,6 +1015,11 @@ const admin: TranslationStrings = {
|
||||
"Disattiva l'autenticazione a due fattori per questo utente, ad esempio dopo la perdita del telefono. L'utente viene disconnesso ovunque e può configurarla di nuovo dopo aver effettuato l'accesso con la password.",
|
||||
'admin.mfa.resetConfirm': "Disattivare l'autenticazione a due fattori per {name}?",
|
||||
'admin.mfa.resetDone': 'Autenticazione a due fattori reimpostata',
|
||||
'admin.sso.unlink': 'Rimuovi collegamento SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
"Dimentica quale accesso presso il provider SSO appartiene a questo account, ad esempio dopo che il provider ha cambiato gli ID utente. Il prossimo accesso SSO con un indirizzo email confermato collega di nuovo l'account.",
|
||||
'admin.sso.unlinkConfirm': 'Rimuovere il collegamento SSO di {name}?',
|
||||
'admin.sso.unlinkDone': 'Collegamento SSO rimosso',
|
||||
'admin.danger.title': 'Zona pericolosa',
|
||||
'admin.rotateJwt.title': 'Ruota il secret JWT',
|
||||
'admin.rotateJwt.hint':
|
||||
|
||||
@@ -951,6 +951,11 @@ const admin: TranslationStrings = {
|
||||
'このユーザーの二要素認証をオフにします(スマートフォンを紛失した場合など)。すべての場所からサインアウトされ、パスワードでサインインした後に再設定できます。',
|
||||
'admin.mfa.resetConfirm': '{name} の二要素認証をオフにしますか?',
|
||||
'admin.mfa.resetDone': '二要素認証をリセットしました',
|
||||
'admin.sso.unlink': 'SSO連携を解除',
|
||||
'admin.sso.unlinkHint':
|
||||
'SSOプロバイダーのどのサインインがこのアカウントに属するかを忘れます(プロバイダーがユーザーIDを変更した場合など)。確認済みのメールアドレスで次にSSOサインインすると、アカウントが再び連携されます。',
|
||||
'admin.sso.unlinkConfirm': '{name} のSSO連携を解除しますか?',
|
||||
'admin.sso.unlinkDone': 'SSO連携を解除しました',
|
||||
'admin.danger.title': '危険ゾーン',
|
||||
'admin.rotateJwt.title': 'JWTシークレットをローテーション',
|
||||
'admin.rotateJwt.hint':
|
||||
|
||||
@@ -943,6 +943,11 @@ const admin: TranslationStrings = {
|
||||
'이 사용자의 2단계 인증을 해제합니다. 예를 들어 휴대폰을 분실한 경우에 사용합니다. 모든 기기에서 로그아웃되며, 비밀번호로 로그인한 뒤 다시 설정할 수 있습니다.',
|
||||
'admin.mfa.resetConfirm': '{name}의 2단계 인증을 해제할까요?',
|
||||
'admin.mfa.resetDone': '2단계 인증이 초기화되었습니다',
|
||||
'admin.sso.unlink': 'SSO 연결 해제',
|
||||
'admin.sso.unlinkHint':
|
||||
'SSO 공급자의 어떤 로그인이 이 계정에 속하는지 잊습니다. 예를 들어 공급자가 사용자 ID를 변경한 경우에 사용합니다. 확인된 이메일 주소로 다음에 SSO 로그인하면 계정이 다시 연결됩니다.',
|
||||
'admin.sso.unlinkConfirm': '{name}의 SSO 연결을 해제할까요?',
|
||||
'admin.sso.unlinkDone': 'SSO 연결이 해제되었습니다',
|
||||
'admin.danger.title': '위험 구역',
|
||||
'admin.rotateJwt.title': 'JWT 시크릿 교체',
|
||||
'admin.rotateJwt.hint': '새 JWT 서명 시크릿을 생성합니다. 활성 세션이 모두 즉시 무효화됩니다.',
|
||||
|
||||
@@ -1013,6 +1013,11 @@ const admin: TranslationStrings = {
|
||||
'Schakelt tweestapsverificatie voor deze gebruiker uit, bijvoorbeeld na verlies van de telefoon. De gebruiker wordt overal afgemeld en kan het na inloggen met het wachtwoord opnieuw instellen.',
|
||||
'admin.mfa.resetConfirm': 'Tweestapsverificatie voor {name} uitschakelen?',
|
||||
'admin.mfa.resetDone': 'Tweestapsverificatie is gereset',
|
||||
'admin.sso.unlink': 'SSO-koppeling verwijderen',
|
||||
'admin.sso.unlinkHint':
|
||||
'Vergeet welke aanmelding bij je SSO-provider bij dit account hoort, bijvoorbeeld nadat de provider zijn gebruikers-ID’s heeft gewijzigd. De volgende SSO-aanmelding met een bevestigd e-mailadres koppelt het account opnieuw.',
|
||||
'admin.sso.unlinkConfirm': 'SSO-koppeling van {name} verwijderen?',
|
||||
'admin.sso.unlinkDone': 'SSO-koppeling verwijderd',
|
||||
'admin.danger.title': 'Gevarenzone',
|
||||
'admin.rotateJwt.title': 'JWT-geheim roteren',
|
||||
'admin.rotateJwt.hint': 'Genereert een nieuw JWT-ondertekeningsgeheim. Alle actieve sessies worden direct ongeldig.',
|
||||
|
||||
@@ -1059,6 +1059,11 @@ const admin: TranslationStrings = {
|
||||
'Wyłącza uwierzytelnianie dwuskładnikowe tego użytkownika, na przykład po zgubieniu telefonu. Użytkownik zostaje wylogowany ze wszystkich urządzeń i po zalogowaniu hasłem może skonfigurować je ponownie.',
|
||||
'admin.mfa.resetConfirm': 'Wyłączyć uwierzytelnianie dwuskładnikowe dla {name}?',
|
||||
'admin.mfa.resetDone': 'Uwierzytelnianie dwuskładnikowe zostało zresetowane',
|
||||
'admin.sso.unlink': 'Usuń powiązanie SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Zapomina, które logowanie u dostawcy SSO należy do tego konta, na przykład po zmianie identyfikatorów użytkowników przez dostawcę. Następne logowanie przez SSO z potwierdzonym adresem e-mail ponownie powiąże konto.',
|
||||
'admin.sso.unlinkConfirm': 'Usunąć powiązanie SSO dla {name}?',
|
||||
'admin.sso.unlinkDone': 'Powiązanie SSO zostało usunięte',
|
||||
'admin.danger.title': 'Strefa zagrożenia',
|
||||
'admin.rotateJwt.title': 'Zmień sekret JWT',
|
||||
'admin.rotateJwt.hint': 'Generuje nowy sekret podpisu JWT. Wszystkie aktywne sesje zostaną natychmiast unieważnione.',
|
||||
|
||||
@@ -1050,6 +1050,11 @@ const admin: TranslationStrings = {
|
||||
'Отключает двухфакторную аутентификацию пользователя, например после потери телефона. Он выйдет из аккаунта везде и сможет настроить её заново после входа с паролем.',
|
||||
'admin.mfa.resetConfirm': 'Отключить двухфакторную аутентификацию для {name}?',
|
||||
'admin.mfa.resetDone': 'Двухфакторная аутентификация сброшена',
|
||||
'admin.sso.unlink': 'Удалить привязку SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Забывает, какой вход у провайдера SSO принадлежит этому аккаунту, например после того как провайдер сменил идентификаторы пользователей. Следующий вход через SSO с подтверждённым адресом почты снова привяжет аккаунт.',
|
||||
'admin.sso.unlinkConfirm': 'Удалить привязку SSO для {name}?',
|
||||
'admin.sso.unlinkDone': 'Привязка SSO удалена',
|
||||
'admin.danger.title': 'Опасная зона',
|
||||
'admin.rotateJwt.title': 'Сменить секрет JWT',
|
||||
'admin.rotateJwt.hint': 'Создаёт новый секрет подписи JWT. Все активные сеансы будут немедленно аннулированы.',
|
||||
|
||||
@@ -1022,6 +1022,11 @@ const admin: TranslationStrings = {
|
||||
'Vypne pre tohto používateľa dvojfaktorové overenie, napríklad po strate telefónu. Bude odhlásený všade a po prihlásení heslom si ho môže nastaviť znova.',
|
||||
'admin.mfa.resetConfirm': 'Vypnúť dvojfaktorové overenie pre používateľa {name}?',
|
||||
'admin.mfa.resetDone': 'Dvojfaktorové overenie bolo resetované',
|
||||
'admin.sso.unlink': 'Odstrániť prepojenie SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Zabudne, ktoré prihlásenie u poskytovateľa SSO patrí k tomuto účtu, napríklad po tom, ako poskytovateľ zmenil ID používateľov. Ďalšie prihlásenie cez SSO s potvrdenou e-mailovou adresou účet znova prepojí.',
|
||||
'admin.sso.unlinkConfirm': 'Odstrániť prepojenie SSO používateľa {name}?',
|
||||
'admin.sso.unlinkDone': 'Prepojenie SSO bolo odstránené',
|
||||
'admin.danger.title': 'Nebezpečná zóna',
|
||||
'admin.rotateJwt.title': 'Obnoviť tajný kľúč JWT',
|
||||
'admin.rotateJwt.hint': 'Vygeneruje nový podpisový kľúč JWT. Všetky aktívne relácie budú okamžite zneplatnené.',
|
||||
|
||||
@@ -1007,6 +1007,11 @@ const admin: TranslationStrings = {
|
||||
'Stänger av tvåfaktorsautentiseringen för den här användaren, till exempel efter en förlorad telefon. Användaren loggas ut överallt och kan ställa in den igen efter inloggning med lösenordet.',
|
||||
'admin.mfa.resetConfirm': 'Stänga av tvåfaktorsautentiseringen för {name}?',
|
||||
'admin.mfa.resetDone': 'Tvåfaktorsautentiseringen har återställts',
|
||||
'admin.sso.unlink': 'Ta bort SSO-koppling',
|
||||
'admin.sso.unlinkHint':
|
||||
'Glömmer vilken inloggning hos din SSO-leverantör som hör till det här kontot, till exempel efter att leverantören bytt användar-ID:n. Nästa SSO-inloggning med en bekräftad e-postadress kopplar kontot igen.',
|
||||
'admin.sso.unlinkConfirm': 'Ta bort SSO-kopplingen för {name}?',
|
||||
'admin.sso.unlinkDone': 'SSO-kopplingen har tagits bort',
|
||||
'admin.danger.title': 'Farozon',
|
||||
'admin.rotateJwt.title': 'Rotera JWT-hemlighet',
|
||||
'admin.rotateJwt.hint': 'Skapar en ny JWT-signeringshemlighet. Alla aktiva sessioner ogiltigförklaras omedelbart.',
|
||||
|
||||
@@ -953,6 +953,11 @@ const admin: TranslationStrings = {
|
||||
'ปิดการรับรองความถูกต้องด้วยสองปัจจัยของผู้ใช้รายนี้ เช่น หลังโทรศัพท์หาย ผู้ใช้จะถูกออกจากระบบทุกที่ และตั้งค่าใหม่ได้หลังเข้าสู่ระบบด้วยรหัสผ่าน',
|
||||
'admin.mfa.resetConfirm': 'ปิดการรับรองความถูกต้องด้วยสองปัจจัยของ {name} หรือไม่',
|
||||
'admin.mfa.resetDone': 'รีเซ็ตการรับรองความถูกต้องด้วยสองปัจจัยแล้ว',
|
||||
'admin.sso.unlink': 'ยกเลิกการเชื่อมโยง SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'ล้างข้อมูลว่าการเข้าสู่ระบบใดที่ผู้ให้บริการ SSO เป็นของบัญชีนี้ เช่น หลังผู้ให้บริการเปลี่ยนรหัสผู้ใช้ การเข้าสู่ระบบ SSO ครั้งถัดไปด้วยอีเมลที่ยืนยันแล้วจะเชื่อมโยงบัญชีอีกครั้ง',
|
||||
'admin.sso.unlinkConfirm': 'ยกเลิกการเชื่อมโยง SSO ของ {name} หรือไม่',
|
||||
'admin.sso.unlinkDone': 'ยกเลิกการเชื่อมโยง SSO แล้ว',
|
||||
'admin.danger.title': 'โซนอันตราย',
|
||||
'admin.rotateJwt.title': 'หมุนเวียนรหัสลับ JWT',
|
||||
'admin.rotateJwt.hint': 'สร้างรหัสลับสำหรับลงนาม JWT ใหม่ เซสชันที่ใช้งานอยู่ทั้งหมดจะถูกยกเลิกทันที',
|
||||
|
||||
@@ -1013,6 +1013,11 @@ const admin: TranslationStrings = {
|
||||
'Bu kullanıcının iki faktörlü kimlik doğrulamasını kapatır, örneğin telefon kaybedildiğinde. Kullanıcının tüm oturumları kapatılır ve parolasıyla giriş yaptıktan sonra yeniden kurabilir.',
|
||||
'admin.mfa.resetConfirm': '{name} için iki faktörlü kimlik doğrulama kapatılsın mı?',
|
||||
'admin.mfa.resetDone': 'İki faktörlü kimlik doğrulama sıfırlandı',
|
||||
'admin.sso.unlink': 'SSO bağlantısını kaldır',
|
||||
'admin.sso.unlinkHint':
|
||||
'SSO sağlayıcınızdaki hangi girişin bu hesaba ait olduğunu unutur, örneğin sağlayıcı kullanıcı kimliklerini değiştirdiğinde. Onaylanmış bir e-posta adresiyle yapılan sonraki SSO girişi hesabı yeniden bağlar.',
|
||||
'admin.sso.unlinkConfirm': '{name} için SSO bağlantısı kaldırılsın mı?',
|
||||
'admin.sso.unlinkDone': 'SSO bağlantısı kaldırıldı',
|
||||
'admin.danger.title': 'Tehlikeli Bölge',
|
||||
'admin.rotateJwt.title': 'JWT sırrını yenile',
|
||||
'admin.rotateJwt.hint': 'Yeni bir JWT imzalama sırrı oluşturur. Tüm etkin oturumlar hemen geçersiz kılınır.',
|
||||
|
||||
@@ -1046,6 +1046,11 @@ const admin: TranslationStrings = {
|
||||
'Вимикає двофакторну автентифікацію користувача, наприклад після втрати телефона. Його буде виведено з акаунта всюди, а після входу з паролем він зможе налаштувати її знову.',
|
||||
'admin.mfa.resetConfirm': 'Вимкнути двофакторну автентифікацію для {name}?',
|
||||
'admin.mfa.resetDone': 'Двофакторну автентифікацію скинуто',
|
||||
'admin.sso.unlink': 'Видалити прив’язку SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Забуває, який вхід у провайдера SSO належить цьому акаунту, наприклад після того як провайдер змінив ідентифікатори користувачів. Наступний вхід через SSO з підтвердженою адресою пошти знову прив’яже акаунт.',
|
||||
'admin.sso.unlinkConfirm': 'Видалити прив’язку SSO для {name}?',
|
||||
'admin.sso.unlinkDone': 'Прив’язку SSO видалено',
|
||||
'admin.danger.title': 'Небезпечна зона',
|
||||
'admin.rotateJwt.title': 'Змінити секрет JWT',
|
||||
'admin.rotateJwt.hint': 'Створює новий секрет підпису JWT. Усі активні сеанси буде негайно анульовано.',
|
||||
|
||||
@@ -991,6 +991,11 @@ const admin: TranslationStrings = {
|
||||
'Tắt xác thực hai yếu tố của người dùng này, ví dụ khi mất điện thoại. Họ bị đăng xuất ở mọi nơi và có thể thiết lập lại sau khi đăng nhập bằng mật khẩu.',
|
||||
'admin.mfa.resetConfirm': 'Tắt xác thực hai yếu tố của {name}?',
|
||||
'admin.mfa.resetDone': 'Đã đặt lại xác thực hai yếu tố',
|
||||
'admin.sso.unlink': 'Gỡ liên kết SSO',
|
||||
'admin.sso.unlinkHint':
|
||||
'Quên lần đăng nhập nào ở nhà cung cấp SSO thuộc về tài khoản này, ví dụ sau khi nhà cung cấp thay đổi ID người dùng. Lần đăng nhập SSO tiếp theo bằng địa chỉ email đã xác nhận sẽ liên kết lại tài khoản.',
|
||||
'admin.sso.unlinkConfirm': 'Gỡ liên kết SSO của {name}?',
|
||||
'admin.sso.unlinkDone': 'Đã gỡ liên kết SSO',
|
||||
'admin.danger.title': 'Vùng nguy hiểm',
|
||||
'admin.rotateJwt.title': 'Xoay vòng khóa bí mật JWT',
|
||||
'admin.rotateJwt.hint': 'Tạo khóa bí mật ký JWT mới. Tất cả phiên đang hoạt động sẽ bị vô hiệu hóa ngay lập tức.',
|
||||
|
||||
@@ -907,6 +907,11 @@ const admin: TranslationStrings = {
|
||||
'為該使用者關閉雙因素驗證,例如手機遺失後。該使用者會在所有裝置上登出,使用密碼登入後可重新設定。',
|
||||
'admin.mfa.resetConfirm': '要關閉 {name} 的雙因素驗證嗎?',
|
||||
'admin.mfa.resetDone': '雙因素驗證已重設',
|
||||
'admin.sso.unlink': '解除 SSO 連結',
|
||||
'admin.sso.unlinkHint':
|
||||
'忘記 SSO 提供者中哪個登入屬於該帳號,例如提供者變更了使用者 ID 之後。下一次使用已驗證電子郵件地址的 SSO 登入會重新連結該帳號。',
|
||||
'admin.sso.unlinkConfirm': '要解除 {name} 的 SSO 連結嗎?',
|
||||
'admin.sso.unlinkDone': 'SSO 連結已解除',
|
||||
'admin.danger.title': '危險區域',
|
||||
'admin.rotateJwt.title': '輪替 JWT 密鑰',
|
||||
'admin.rotateJwt.hint': '產生新的 JWT 簽署密鑰。所有進行中的工作階段將立即失效。',
|
||||
|
||||
@@ -906,6 +906,11 @@ const admin: TranslationStrings = {
|
||||
'为该用户关闭双因素验证,例如手机丢失后。该用户会在所有设备上退出登录,使用密码登录后可重新设置。',
|
||||
'admin.mfa.resetConfirm': '要关闭 {name} 的双因素验证吗?',
|
||||
'admin.mfa.resetDone': '双因素验证已重置',
|
||||
'admin.sso.unlink': '解除 SSO 关联',
|
||||
'admin.sso.unlinkHint':
|
||||
'忘记 SSO 提供商中哪个登录属于该账户,例如提供商更改了用户 ID 之后。下一次使用已验证邮箱地址的 SSO 登录会重新关联该账户。',
|
||||
'admin.sso.unlinkConfirm': '要解除 {name} 的 SSO 关联吗?',
|
||||
'admin.sso.unlinkDone': 'SSO 关联已解除',
|
||||
'admin.danger.title': '危险区域',
|
||||
'admin.rotateJwt.title': '轮换 JWT 密钥',
|
||||
'admin.rotateJwt.hint': '生成新的 JWT 签名密钥。所有活动会话将立即失效。',
|
||||
|
||||
@@ -42,6 +42,8 @@ Below the fields sits **Reset passkeys**, which removes every passkey that user
|
||||
|
||||
For every other account the dialog also has **Reset 2FA**, for example after a lost phone. After you confirm (**Turn off two-factor authentication for {name}?**) it switches 2FA off for that user and signs them out everywhere; they can set it up again after logging in with their password. It is not offered for your own account, and it is recorded in the [Audit-Log](Audit-Log). See [Two-Factor-Authentication](Two-Factor-Authentication).
|
||||
|
||||
While SSO is set up, the dialog of every other account also has **Remove SSO link**. It forgets which sign-in at the provider belongs to the account, for when the provider changed the user's subject and their login now ends with *"This sign-in could not be linked to the existing account with this email"*. After you confirm, the next SSO sign-in with a verified email links the account again. It takes effect immediately, is not offered for your own account and is recorded in the [Audit-Log](Audit-Log). See [OIDC-SSO](OIDC-SSO#how-an-existing-account-gets-linked).
|
||||
|
||||
### Delete a user
|
||||
|
||||
Click the trash icon and confirm in the **Delete user** dialog (**Delete user "name"? All trips will be permanently deleted.**). Deletion is permanent. The user's account is removed from the database along with their data, including the trips they own (cascade behavior is enforced at the database level).
|
||||
|
||||
@@ -80,6 +80,7 @@ Logging out writes no row, and neither do the sessions a password change, a rese
|
||||
| `admin.user_delete` | User deleted by admin |
|
||||
| `admin.user_mfa_reset` | A user's MFA reset by admin |
|
||||
| `admin.user_passkeys_reset` | All of a user's passkeys removed by admin |
|
||||
| `admin.user_sso_unlink` | A user's SSO link removed by admin |
|
||||
| `admin.invite_create` | Invite link created |
|
||||
| `admin.invite_delete` | Invite link deleted |
|
||||
| `admin.permissions_update` | Instance permissions updated |
|
||||
|
||||
+5
-1
@@ -106,7 +106,11 @@ An SSO login that finds no account by its subject falls back to the email addres
|
||||
- **The address did not come from the account itself.** An address typed into the signup form or changed under **Settings** proves nothing about who owns it, so a login with that address ends with `email_link_blocked` and the user sees a message to ask the admin. Addresses an admin entered (an account created in **Admin → Users**, or an email changed there), the first account of the instance and accounts the provider created are linked as before.
|
||||
- **The account is not linked to another subject of the same provider.** The same issuer with another `sub` is another person at that provider, so this also ends with `email_link_blocked`. Pointing the instance at a different provider (a new issuer) still moves an account over on its first verified login there.
|
||||
|
||||
To let a blocked account link, confirm its address as admin: in **Admin → Users**, edit the user, change the email to any other address and save, then set it back to the real address and save again. Changing only upper and lower case does not count. If the account is not needed, deleting it lets the SSO login create a fresh one instead. The server log names the account a login was refused for (`[OIDC] Refused to link a sign-in to user <id> by email`), and every email change a user makes in their own settings appears in the audit log as `user.email_change`.
|
||||
The server log says which of the two blocks a login hit. `[OIDC] Refused to link a sign-in to user <id> by email: the account set that address itself` is the second one, `… it is linked to another subject of this provider` the third.
|
||||
|
||||
When the account is linked to another subject of the same provider and you know it is the same person (the provider was migrated or rebuilt, or it changed how it forms the `sub`), remove the stored link: in **Admin → Users**, edit the user and use **Remove SSO link**. The next SSO sign-in then links the account like a first one, under the rules above. Changing the email does not help in this case. The action is not offered for your own account and is recorded in the [Audit-Log](Audit-Log) as `admin.user_sso_unlink`. Only remove a link when you are sure: if the address now belongs to somebody else at the provider, that person receives the account.
|
||||
|
||||
When the account set its address itself, confirm the address as admin: in **Admin → Users**, edit the user, change the email to any other address and save, then set it back to the real address and save again. Changing only upper and lower case does not count. If the account is not needed, deleting it lets the SSO login create a fresh one instead. The server log names the account a login was refused for (`[OIDC] Refused to link a sign-in to user <id> by email`) and the reason, and every email change a user makes in their own settings appears in the audit log as `user.email_change`.
|
||||
|
||||
Accounts that existed before this check keep linking as they did, except those whose address provably changed since they registered without an admin editing it.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user