pgkiln 0.62.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/SECURITY.md +4 -0
  2. package/db/migrations/131_builder_sso.sql +69 -0
  3. package/db/migrations/132_app_user_mfa.sql +58 -0
  4. package/docs/apex-feature-parity.md +5 -2
  5. package/docs/guide/03-builder.md +46 -0
  6. package/docs/guide/08-security.md +48 -6
  7. package/docs/guide/09-reference.md +5 -1
  8. package/docs/guide/11-from-apex.md +2 -1
  9. package/docs/guide/12-development.md +9 -0
  10. package/docs/guide/18-cli.md +1 -0
  11. package/package.json +1 -1
  12. package/public/app.css +10 -0
  13. package/public/builder.css +4 -0
  14. package/src/account-mfa.ts +93 -0
  15. package/src/builder/advisor.ts +8 -0
  16. package/src/builder/builder-sso.ts +135 -0
  17. package/src/builder/i18n/de.ts +56 -0
  18. package/src/builder/i18n/en.ts +56 -0
  19. package/src/builder/i18n/es.ts +56 -0
  20. package/src/builder/i18n/fr.ts +56 -0
  21. package/src/builder/i18n/nl.ts +56 -0
  22. package/src/builder/i18n/pt.ts +56 -0
  23. package/src/builder/ldap.ts +2 -0
  24. package/src/builder/login-page.ts +26 -2
  25. package/src/builder/routes.ts +137 -21
  26. package/src/builder/security.ts +3 -1
  27. package/src/builder/ui.ts +2 -1
  28. package/src/builder/users.ts +36 -3
  29. package/src/builder/workspaces.ts +37 -3
  30. package/src/cli/main.ts +19 -5
  31. package/src/devsso.ts +149 -0
  32. package/src/i18n/ar.ts +34 -0
  33. package/src/i18n/cs.ts +34 -0
  34. package/src/i18n/da.ts +34 -0
  35. package/src/i18n/de.ts +34 -0
  36. package/src/i18n/el.ts +34 -0
  37. package/src/i18n/es.ts +34 -0
  38. package/src/i18n/fi.ts +34 -0
  39. package/src/i18n/fr.ts +34 -0
  40. package/src/i18n/he.ts +34 -0
  41. package/src/i18n/it.ts +34 -0
  42. package/src/i18n/ja.ts +34 -0
  43. package/src/i18n/ko.ts +34 -0
  44. package/src/i18n/nb.ts +34 -0
  45. package/src/i18n/pl.ts +34 -0
  46. package/src/i18n/pt.ts +34 -0
  47. package/src/i18n/ru.ts +34 -0
  48. package/src/i18n/sv.ts +34 -0
  49. package/src/i18n/tr.ts +34 -0
  50. package/src/i18n/uk.ts +34 -0
  51. package/src/i18n/zh.ts +34 -0
  52. package/src/i18n.ts +68 -0
  53. package/src/metadata.ts +4 -1
  54. package/src/mfa.ts +76 -52
  55. package/src/remember.ts +1 -1
  56. package/src/runtime/account.ts +4 -1
  57. package/src/runtime/routes.ts +24 -9
  58. package/src/runtime/two-step.ts +395 -0
  59. package/src/saml.ts +13 -7
  60. package/src/sharedlogin.ts +1 -1
  61. package/src/sso.ts +37 -7
package/src/devsso.ts ADDED
@@ -0,0 +1,149 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { owner } from './db.ts';
3
+ import { SsoError } from './sso.ts';
4
+
5
+ // (sprint 129, SEC-1) Developers sign in to the builder through an identity
6
+ // provider (OpenID Connect, SAML) or an LDAP directory that has "builder"
7
+ // turned on. The rules, as for application accounts (sso.ts, ldap.ts):
8
+ // * a developer is found by the provider's stable subject
9
+ // (meta.developer_identity), never by a name the provider sends alone;
10
+ // * an existing developer with the same username is linked on the first
11
+ // sign-in only when the source allows it (builder_link_existing), with a
12
+ // verified e-mail address when that is the username, and never a
13
+ // break-glass developer; otherwise the developer links the identity
14
+ // themselves on the Security page, signed in;
15
+ // * a new developer is created when the source allows it
16
+ // (builder_auto_create), for members of builder_group when one is set;
17
+ // * builder_admin_group, when set, decides on every sign-in who is an
18
+ // instance administrator (break-glass developers and the last
19
+ // administrator are never demoted).
20
+ // A workspace can require these sign-ins of its developers (sso_only);
21
+ // break-glass developers may still use the pgkiln password.
22
+
23
+ export interface BuilderSource {
24
+ kind: 'provider' | 'directory';
25
+ id: number;
26
+ name: string;
27
+ builder_auto_create: boolean;
28
+ builder_link_existing: boolean;
29
+ builder_group: string | null;
30
+ builder_admin_group: string | null;
31
+ }
32
+
33
+ export interface BuilderIdentity {
34
+ subject: string;
35
+ username: string;
36
+ name?: string | null;
37
+ email?: string | null;
38
+ /** false: the username is an unverified e-mail address and links nothing */
39
+ mayLink: boolean;
40
+ groups: string[];
41
+ }
42
+
43
+ const column = (k: BuilderSource['kind']) => (k === 'provider' ? 'provider_id' : 'directory_id');
44
+ const validEmail = (v: unknown): v is string => typeof v === 'string' && /^[^@\s<>"(),;:\\]+@[^@\s<>"(),;:\\]+\.[^@\s<>"(),;:\\]+$/.test(v) && v.length <= 254;
45
+
46
+ /** The providers and directories developers can sign in with. */
47
+ export async function builderSources() {
48
+ const providers = (await owner.query<{ name: string; display_name: string }>('select name, display_name from meta.auth_provider where enabled and builder order by display_name')).rows;
49
+ const directories = (await owner.query<{ name: string; display_name: string }>('select name, display_name from meta.ldap_directory where enabled and builder order by display_name')).rows;
50
+ return { providers, directories };
51
+ }
52
+
53
+ export async function builderProvider(name: string) {
54
+ return owner.one('select * from meta.auth_provider where enabled and builder and name = $1', [name]);
55
+ }
56
+ export async function builderDirectory(name: string) {
57
+ return owner.one('select * from meta.ldap_directory where enabled and builder and name = $1', [name]);
58
+ }
59
+
60
+ /** The developer for an identity (see the rules above); throws SsoError when there is none. */
61
+ export async function resolveDeveloper(src: BuilderSource, id: BuilderIdentity): Promise<string> {
62
+ const col = column(src.kind);
63
+ return owner.tx(async (c) => {
64
+ let username: string | undefined = (await c.query(`select username from meta.developer_identity where ${col} = $1 and subject = $2`, [src.id, id.subject])).rows[0]?.username;
65
+ if (!username) {
66
+ const existing = (
67
+ await c.query(
68
+ `select d.username, d.break_glass, exists (select 1 from meta.developer_identity i where i.username = d.username and i.${col} = $2) as linked
69
+ from meta.developer d where lower(d.username) = lower($1)`,
70
+ [id.username, src.id],
71
+ )
72
+ ).rows[0];
73
+ if (existing) {
74
+ if (existing.linked) throw new SsoError('This developer is already linked to another identity here.');
75
+ if (!src.builder_link_existing || !id.mayLink || existing.break_glass)
76
+ throw new SsoError(`A developer "${existing.username}" exists but is not linked to this identity. Sign in with the password and link it on the Security page.`);
77
+ username = existing.username as string;
78
+ } else {
79
+ const member = !src.builder_group || id.groups.includes(src.builder_group) || (!!src.builder_admin_group && id.groups.includes(src.builder_admin_group));
80
+ if (!src.builder_auto_create || !member) throw new SsoError(`There is no developer "${id.username}". Ask an administrator for access.`);
81
+ // a password nobody knows: the developer signs in through the provider (an administrator can set one)
82
+ username = (
83
+ await c.query(
84
+ `insert into meta.developer (username, password_hash, is_admin, email) values ($1, crypt($2, gen_salt('bf', 10)), false, $3) returning username`,
85
+ [id.username, randomBytes(32).toString('base64url'), validEmail(id.email) ? id.email : null],
86
+ )
87
+ ).rows[0].username;
88
+ }
89
+ await c.query(`insert into meta.developer_identity (${col}, subject, username) values ($1, $2, $3)`, [src.id, id.subject, username]);
90
+ }
91
+ if (src.builder_admin_group) {
92
+ const admin = id.groups.includes(src.builder_admin_group);
93
+ // never the last administrator either: the instance would have none
94
+ await c.query(
95
+ `update meta.developer set is_admin = $2 where username = $1 and is_admin <> $2
96
+ and ($2 or (not break_glass and (select count(*) from meta.developer where is_admin) > 1))`,
97
+ [username, admin],
98
+ );
99
+ }
100
+ return username!;
101
+ });
102
+ }
103
+
104
+ /** Link an identity to a signed-in developer (Security page). */
105
+ export async function linkDeveloper(src: Pick<BuilderSource, 'kind' | 'id'>, subject: string, username: string) {
106
+ const col = column(src.kind);
107
+ const other = await owner.one(`select username from meta.developer_identity where ${col} = $1 and (subject = $2 or username = $3)`, [src.id, subject, username]);
108
+ if (other) throw new SsoError(other.username === username ? 'You already linked an identity here; unlink it first.' : 'This identity is linked to another developer.');
109
+ await owner.query(`insert into meta.developer_identity (${col}, subject, username) values ($1, $2, $3)`, [src.id, subject, username]);
110
+ }
111
+
112
+ /** A developer's linked identities (Security page, provider pages). */
113
+ export async function identitiesOf(username: string) {
114
+ return (
115
+ await owner.query<{ id: number; source: string; display_name: string; kind: 'provider' | 'directory'; linked_at: string }>(
116
+ `select i.id, coalesce(p.name, d.name) as source, coalesce(p.display_name, d.display_name) as display_name,
117
+ case when i.provider_id is null then 'directory' else 'provider' end as kind, i.linked_at
118
+ from meta.developer_identity i
119
+ left join meta.auth_provider p on p.id = i.provider_id
120
+ left join meta.ldap_directory d on d.id = i.directory_id
121
+ where i.username = $1 order by 3`,
122
+ [username],
123
+ )
124
+ ).rows;
125
+ }
126
+
127
+ /**
128
+ * Must the developer sign in through an identity provider? A member of a workspace that requires it,
129
+ * unless break-glass.
130
+ */
131
+ export async function ssoRequired(username: string) {
132
+ const r = await owner.one(
133
+ `select not d.break_glass and exists (select 1 from meta.workspace_member m join meta.workspace w on w.id = m.workspace_id
134
+ where m.username = d.username and w.sso_only) as required
135
+ from meta.developer d where d.username = $1`,
136
+ [username],
137
+ );
138
+ return !!r?.required;
139
+ }
140
+
141
+ /** Instance administrators who may still sign in with the password. */
142
+ export async function breakGlassAdmins() {
143
+ return (await owner.query<{ username: string }>('select username from meta.developer where is_admin and break_glass order by username')).rows.map((r) => r.username);
144
+ }
145
+
146
+ /** `pgkiln admin builder-sign-in password`: every workspace allows the password again. */
147
+ export async function clearSsoOnly() {
148
+ return (await owner.query('update meta.workspace set sso_only = false where sso_only')).rowCount ?? 0;
149
+ }
package/src/i18n/ar.ts CHANGED
@@ -805,4 +805,38 @@ export const ar: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} لكل مجموعة",
806
806
  'ai_filter.needs_number': "تحتاج {fn} إلى عمود أرقام.",
807
807
  'ai_filter.invalid': "هذا التغيير غير ممكن: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "تسجيل الدخول بخطوتين",
810
+ "mfa.step_intro": "أدخل الرمز المكوّن من 6 أرقام من تطبيق المصادقة، أو أحد رموز الاسترداد.",
811
+ "mfa.code": "الرمز",
812
+ "mfa.verify": "تحقق",
813
+ "mfa.wrong": "الرمز غير صحيح أو سبق استخدامه.",
814
+ "mfa.expired": "استغرق تسجيل الدخول وقتًا طويلًا. يُرجى تسجيل الدخول مرة أخرى.",
815
+ "mfa.enrol_title": "إعداد تسجيل الدخول بخطوتين",
816
+ "mfa.enrol_intro": "يطلب هذا التطبيق رمزًا من تطبيق مصادقة عند تسجيل الدخول. أضف تطبيقًا الآن للمتابعة.",
817
+ "mfa.scan": "امسح رمز QR باستخدام تطبيق مصادقة، مثل Google Authenticator أو Microsoft Authenticator أو 1Password.",
818
+ "mfa.or_enter": "أو أدخل هذا المفتاح:",
819
+ "mfa.enter_code": "أدخل الرمز المكوّن من 6 أرقام الذي يعرضه التطبيق.",
820
+ "mfa.qr_label": "رمز QR لتطبيق المصادقة",
821
+ "mfa.codes_title": "رموز الاسترداد",
822
+ "mfa.codes_once": "احتفظ بهذه الرموز في مكان آمن. يتيح لك كل رمز تسجيل الدخول مرة واحدة عندما لا يكون هاتفك معك. لن تظهر إلا الآن.",
823
+ "mfa.codes_saved": "لقد حفظت هذه الرموز",
824
+ "mfa.continue": "متابعة",
825
+ "mfa.no_key": "لم يتم إعداد تسجيل الدخول بخطوتين على هذا الخادم بعد. يُرجى التواصل مع المسؤول.",
826
+ "mfa.app_name": "الاسم",
827
+ "mfa.app_default": "تطبيق المصادقة",
828
+ "mfa.on": "عند تسجيل الدخول، تُدخل أيضًا رمزًا من تطبيق المصادقة.",
829
+ "mfa.off": "أضف تطبيق مصادقة لحماية حسابك بخطوة ثانية عند تسجيل الدخول.",
830
+ "mfa.required_you": "يتطلب هذا التطبيق منك تسجيل الدخول بخطوتين: أضف تطبيق مصادقة.",
831
+ "mfa.add_app": "إضافة تطبيق مصادقة",
832
+ "mfa.confirm_app": "إضافة التطبيق",
833
+ "mfa.cancel": "إلغاء",
834
+ "mfa.remove": "إزالة",
835
+ "mfa.remove_code": "رمز حالي أو رمز استرداد",
836
+ "mfa.removed": "تمت إزالة تطبيق المصادقة.",
837
+ "mfa.keep_one": "يتطلب هذا التطبيق منك تسجيل الدخول بخطوتين: أضف تطبيقًا آخر قبل إزالة هذا التطبيق.",
838
+ "mfa.added": "تمت إضافة تطبيق المصادقة.",
839
+ "mfa.codes_left": "رموز الاسترداد المتبقية: {n}",
840
+ "mfa.codes_new": "رموز استرداد جديدة",
841
+ "mfa.recovery_used": "سجّلت الدخول باستخدام رمز استرداد. رموز الاسترداد المتبقية: {n}.",
808
842
  };
package/src/i18n/cs.ts CHANGED
@@ -805,4 +805,38 @@ export const cs: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} na skupinu",
806
806
  'ai_filter.needs_number': "{fn} potřebuje sloupec s čísly.",
807
807
  'ai_filter.invalid': "Tuto změnu nelze provést: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "Dvoufázové přihlášení",
810
+ "mfa.step_intro": "Zadejte šestimístný kód z ověřovací aplikace nebo jeden ze záložních kódů.",
811
+ "mfa.code": "Kód",
812
+ "mfa.verify": "Ověřit",
813
+ "mfa.wrong": "Kód není správný nebo už byl použit.",
814
+ "mfa.expired": "Přihlášení trvalo příliš dlouho. Přihlaste se znovu.",
815
+ "mfa.enrol_title": "Nastavení dvoufázového přihlášení",
816
+ "mfa.enrol_intro": "Tato aplikace při přihlášení vyžaduje kód z ověřovací aplikace. Pokračujte jejím přidáním.",
817
+ "mfa.scan": "Naskenujte QR kód ověřovací aplikací, například Google Authenticator, Microsoft Authenticator nebo 1Password.",
818
+ "mfa.or_enter": "Nebo zadejte tento klíč:",
819
+ "mfa.enter_code": "Zadejte šestimístný kód, který aplikace zobrazuje.",
820
+ "mfa.qr_label": "QR kód pro ověřovací aplikaci",
821
+ "mfa.codes_title": "Záložní kódy",
822
+ "mfa.codes_once": "Uložte si tyto kódy na bezpečné místo. Každý vás jednou přihlásí, když nemáte telefon. Zobrazují se pouze nyní.",
823
+ "mfa.codes_saved": "Kódy jsem si uložil",
824
+ "mfa.continue": "Pokračovat",
825
+ "mfa.no_key": "Dvoufázové přihlášení zatím na tomto serveru není nastaveno. Obraťte se na správce.",
826
+ "mfa.app_name": "Název",
827
+ "mfa.app_default": "Ověřovací aplikace",
828
+ "mfa.on": "Při přihlášení zadáváte také kód z ověřovací aplikace.",
829
+ "mfa.off": "Přidejte ověřovací aplikaci a chraňte svůj účet při přihlášení druhým krokem.",
830
+ "mfa.required_you": "Tato aplikace od vás vyžaduje dvoufázové přihlášení: přidejte ověřovací aplikaci.",
831
+ "mfa.add_app": "Přidat ověřovací aplikaci",
832
+ "mfa.confirm_app": "Přidat aplikaci",
833
+ "mfa.cancel": "Zrušit",
834
+ "mfa.remove": "Odebrat",
835
+ "mfa.remove_code": "Aktuální kód nebo záložní kód",
836
+ "mfa.removed": "Ověřovací aplikace byla odebrána.",
837
+ "mfa.keep_one": "Tato aplikace od vás vyžaduje dvoufázové přihlášení: než tuto aplikaci odeberete, přidejte jinou.",
838
+ "mfa.added": "Ověřovací aplikace byla přidána.",
839
+ "mfa.codes_left": "Zbývající záložní kódy: {n}",
840
+ "mfa.codes_new": "Nové záložní kódy",
841
+ "mfa.recovery_used": "Přihlásili jste se záložním kódem. Zbývající záložní kódy: {n}.",
808
842
  };
package/src/i18n/da.ts CHANGED
@@ -805,4 +805,38 @@ export const da: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} pr. gruppe",
806
806
  'ai_filter.needs_number': "{fn} kræver en kolonne med tal.",
807
807
  'ai_filter.invalid': "Den ændring er ikke mulig: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "Totrinslogin",
810
+ "mfa.step_intro": "Indtast den 6-cifrede kode fra din godkendelsesapp eller en af dine gendannelseskoder.",
811
+ "mfa.code": "Kode",
812
+ "mfa.verify": "Bekræft",
813
+ "mfa.wrong": "Koden er forkert eller er allerede brugt.",
814
+ "mfa.expired": "Login tog for lang tid. Log ind igen.",
815
+ "mfa.enrol_title": "Konfigurer totrinslogin",
816
+ "mfa.enrol_intro": "Denne applikation beder om en kode fra en godkendelsesapp, når du logger ind. Tilføj en nu for at fortsætte.",
817
+ "mfa.scan": "Scan QR-koden med en godkendelsesapp, f.eks. Google Authenticator, Microsoft Authenticator eller 1Password.",
818
+ "mfa.or_enter": "Eller indtast denne nøgle:",
819
+ "mfa.enter_code": "Indtast den 6-cifrede kode, som appen viser.",
820
+ "mfa.qr_label": "QR-kode til en godkendelsesapp",
821
+ "mfa.codes_title": "Gendannelseskoder",
822
+ "mfa.codes_once": "Opbevar koderne et sikkert sted. Hver kode logger dig ind én gang, når du ikke har din telefon. De vises kun nu.",
823
+ "mfa.codes_saved": "Jeg har gemt koderne",
824
+ "mfa.continue": "Fortsæt",
825
+ "mfa.no_key": "Totrinslogin er endnu ikke konfigureret på denne server. Kontakt administratoren.",
826
+ "mfa.app_name": "Navn",
827
+ "mfa.app_default": "Godkendelsesapp",
828
+ "mfa.on": "Når du logger ind, angiver du også en kode fra din godkendelsesapp.",
829
+ "mfa.off": "Tilføj en godkendelsesapp for at beskytte din konto med et ekstra trin, når du logger ind.",
830
+ "mfa.required_you": "Denne applikation kræver totrinslogin for dig: tilføj en godkendelsesapp.",
831
+ "mfa.add_app": "Tilføj en godkendelsesapp",
832
+ "mfa.confirm_app": "Tilføj app",
833
+ "mfa.cancel": "Annuller",
834
+ "mfa.remove": "Fjern",
835
+ "mfa.remove_code": "En aktuel kode eller en gendannelseskode",
836
+ "mfa.removed": "Godkendelsesappen blev fjernet.",
837
+ "mfa.keep_one": "Denne applikation kræver totrinslogin for dig: tilføj en anden app, før du fjerner denne.",
838
+ "mfa.added": "Godkendelsesappen blev tilføjet.",
839
+ "mfa.codes_left": "Resterende gendannelseskoder: {n}",
840
+ "mfa.codes_new": "Nye gendannelseskoder",
841
+ "mfa.recovery_used": "Du loggede ind med en gendannelseskode. Resterende gendannelseskoder: {n}.",
808
842
  };
package/src/i18n/de.ts CHANGED
@@ -805,4 +805,38 @@ export const de: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} pro Gruppe",
806
806
  'ai_filter.needs_number': "{fn} braucht eine Spalte mit Zahlen.",
807
807
  'ai_filter.invalid': "Diese Änderung ist nicht möglich: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "Anmeldung in zwei Schritten",
810
+ "mfa.step_intro": "Geben Sie den 6-stelligen Code aus Ihrer Authenticator-App oder einen Ihrer Wiederherstellungscodes ein.",
811
+ "mfa.code": "Code",
812
+ "mfa.verify": "Bestätigen",
813
+ "mfa.wrong": "Dieser Code ist falsch oder wurde bereits verwendet.",
814
+ "mfa.expired": "Die Anmeldung hat zu lange gedauert. Bitte melden Sie sich erneut an.",
815
+ "mfa.enrol_title": "Anmeldung in zwei Schritten einrichten",
816
+ "mfa.enrol_intro": "Diese Anwendung verlangt bei der Anmeldung einen Code aus einer Authenticator-App. Fügen Sie jetzt eine hinzu, um fortzufahren.",
817
+ "mfa.scan": "Scannen Sie den QR-Code mit einer Authenticator-App wie Google Authenticator, Microsoft Authenticator oder 1Password.",
818
+ "mfa.or_enter": "Oder geben Sie diesen Schlüssel ein:",
819
+ "mfa.enter_code": "Geben Sie den 6-stelligen Code ein, den die App anzeigt.",
820
+ "mfa.qr_label": "QR-Code für eine Authenticator-App",
821
+ "mfa.codes_title": "Wiederherstellungscodes",
822
+ "mfa.codes_once": "Bewahren Sie diese Codes sicher auf. Jeder Code meldet Sie einmal an, wenn Sie Ihr Telefon nicht haben. Sie werden nur jetzt angezeigt.",
823
+ "mfa.codes_saved": "Ich habe diese Codes gespeichert",
824
+ "mfa.continue": "Weiter",
825
+ "mfa.no_key": "Die Anmeldung in zwei Schritten ist auf diesem Server noch nicht eingerichtet. Wenden Sie sich an den Administrator.",
826
+ "mfa.app_name": "Name",
827
+ "mfa.app_default": "Authenticator-App",
828
+ "mfa.on": "Bei der Anmeldung geben Sie zusätzlich einen Code aus Ihrer Authenticator-App ein.",
829
+ "mfa.off": "Fügen Sie eine Authenticator-App hinzu, um Ihr Konto bei der Anmeldung mit einem zweiten Schritt zu schützen.",
830
+ "mfa.required_you": "Diese Anwendung verlangt von Ihnen die Anmeldung in zwei Schritten: Fügen Sie eine Authenticator-App hinzu.",
831
+ "mfa.add_app": "Authenticator-App hinzufügen",
832
+ "mfa.confirm_app": "App hinzufügen",
833
+ "mfa.cancel": "Abbrechen",
834
+ "mfa.remove": "Entfernen",
835
+ "mfa.remove_code": "Ein aktueller Code oder ein Wiederherstellungscode",
836
+ "mfa.removed": "Die Authenticator-App wurde entfernt.",
837
+ "mfa.keep_one": "Diese Anwendung verlangt von Ihnen die Anmeldung in zwei Schritten: Fügen Sie zuerst eine andere App hinzu, bevor Sie diese entfernen.",
838
+ "mfa.added": "Die Authenticator-App wurde hinzugefügt.",
839
+ "mfa.codes_left": "Verbleibende Wiederherstellungscodes: {n}",
840
+ "mfa.codes_new": "Neue Wiederherstellungscodes",
841
+ "mfa.recovery_used": "Sie haben sich mit einem Wiederherstellungscode angemeldet. Verbleibende Wiederherstellungscodes: {n}.",
808
842
  };
package/src/i18n/el.ts CHANGED
@@ -805,4 +805,38 @@ export const el: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} ανά ομάδα",
806
806
  'ai_filter.needs_number': "Η συνάρτηση {fn} χρειάζεται στήλη με αριθμούς.",
807
807
  'ai_filter.invalid': "Αυτή η αλλαγή δεν είναι δυνατή: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "Σύνδεση δύο βημάτων",
810
+ "mfa.step_intro": "Πληκτρολογήστε τον 6ψήφιο κωδικό από την εφαρμογή ελέγχου ταυτότητας ή έναν από τους κωδικούς ανάκτησης.",
811
+ "mfa.code": "Κωδικός",
812
+ "mfa.verify": "Επαλήθευση",
813
+ "mfa.wrong": "Ο κωδικός δεν είναι σωστός ή έχει ήδη χρησιμοποιηθεί.",
814
+ "mfa.expired": "Η σύνδεση άργησε πολύ. Συνδεθείτε ξανά.",
815
+ "mfa.enrol_title": "Ρύθμιση σύνδεσης δύο βημάτων",
816
+ "mfa.enrol_intro": "Αυτή η εφαρμογή ζητά κωδικό από εφαρμογή ελέγχου ταυτότητας κατά τη σύνδεση. Προσθέστε μία τώρα για να συνεχίσετε.",
817
+ "mfa.scan": "Σαρώστε τον κωδικό QR με μια εφαρμογή ελέγχου ταυτότητας, όπως Google Authenticator, Microsoft Authenticator ή 1Password.",
818
+ "mfa.or_enter": "Ή πληκτρολογήστε αυτό το κλειδί:",
819
+ "mfa.enter_code": "Πληκτρολογήστε τον 6ψήφιο κωδικό που εμφανίζει η εφαρμογή.",
820
+ "mfa.qr_label": "Κωδικός QR για εφαρμογή ελέγχου ταυτότητας",
821
+ "mfa.codes_title": "Κωδικοί ανάκτησης",
822
+ "mfa.codes_once": "Φυλάξτε αυτούς τους κωδικούς σε ασφαλές μέρος. Ο καθένας σας συνδέει μία φορά όταν δεν έχετε το τηλέφωνό σας. Εμφανίζονται μόνο τώρα.",
823
+ "mfa.codes_saved": "Αποθήκευσα αυτούς τους κωδικούς",
824
+ "mfa.continue": "Συνέχεια",
825
+ "mfa.no_key": "Η σύνδεση δύο βημάτων δεν έχει ρυθμιστεί ακόμη σε αυτόν τον διακομιστή. Επικοινωνήστε με τον διαχειριστή.",
826
+ "mfa.app_name": "Όνομα",
827
+ "mfa.app_default": "Εφαρμογή ελέγχου ταυτότητας",
828
+ "mfa.on": "Κατά τη σύνδεση δίνετε επίσης έναν κωδικό από την εφαρμογή ελέγχου ταυτότητας.",
829
+ "mfa.off": "Προσθέστε μια εφαρμογή ελέγχου ταυτότητας για να προστατεύσετε τον λογαριασμό σας με δεύτερο βήμα κατά τη σύνδεση.",
830
+ "mfa.required_you": "Αυτή η εφαρμογή απαιτεί από εσάς σύνδεση δύο βημάτων: προσθέστε μια εφαρμογή ελέγχου ταυτότητας.",
831
+ "mfa.add_app": "Προσθήκη εφαρμογής ελέγχου ταυτότητας",
832
+ "mfa.confirm_app": "Προσθήκη εφαρμογής",
833
+ "mfa.cancel": "Ακύρωση",
834
+ "mfa.remove": "Κατάργηση",
835
+ "mfa.remove_code": "Ένας τρέχων κωδικός ή ένας κωδικός ανάκτησης",
836
+ "mfa.removed": "Η εφαρμογή ελέγχου ταυτότητας καταργήθηκε.",
837
+ "mfa.keep_one": "Αυτή η εφαρμογή απαιτεί από εσάς σύνδεση δύο βημάτων: προσθέστε άλλη εφαρμογή πριν καταργήσετε αυτήν.",
838
+ "mfa.added": "Η εφαρμογή ελέγχου ταυτότητας προστέθηκε.",
839
+ "mfa.codes_left": "Κωδικοί ανάκτησης που απομένουν: {n}",
840
+ "mfa.codes_new": "Νέοι κωδικοί ανάκτησης",
841
+ "mfa.recovery_used": "Συνδεθήκατε με κωδικό ανάκτησης. Κωδικοί ανάκτησης που απομένουν: {n}.",
808
842
  };
package/src/i18n/es.ts CHANGED
@@ -805,4 +805,38 @@ export const es: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} por grupo",
806
806
  'ai_filter.needs_number': "{fn} necesita una columna con números.",
807
807
  'ai_filter.invalid': "Ese cambio no es posible: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "Inicio de sesión en dos pasos",
810
+ "mfa.step_intro": "Introduzca el código de 6 dígitos de su aplicación de autenticación, o uno de sus códigos de recuperación.",
811
+ "mfa.code": "Código",
812
+ "mfa.verify": "Verificar",
813
+ "mfa.wrong": "Ese código no es correcto o ya se ha utilizado.",
814
+ "mfa.expired": "El inicio de sesión tardó demasiado. Vuelva a iniciar sesión.",
815
+ "mfa.enrol_title": "Configurar el inicio de sesión en dos pasos",
816
+ "mfa.enrol_intro": "Esta aplicación pide un código de una aplicación de autenticación al iniciar sesión. Añada una ahora para continuar.",
817
+ "mfa.scan": "Escanee el código QR con una aplicación de autenticación, como Google Authenticator, Microsoft Authenticator o 1Password.",
818
+ "mfa.or_enter": "O introduzca esta clave:",
819
+ "mfa.enter_code": "Introduzca el código de 6 dígitos que muestra la aplicación.",
820
+ "mfa.qr_label": "Código QR para una aplicación de autenticación",
821
+ "mfa.codes_title": "Códigos de recuperación",
822
+ "mfa.codes_once": "Guarde estos códigos en un lugar seguro. Cada uno le permite iniciar sesión una vez si no tiene su teléfono. Solo se muestran ahora.",
823
+ "mfa.codes_saved": "He guardado estos códigos",
824
+ "mfa.continue": "Continuar",
825
+ "mfa.no_key": "El inicio de sesión en dos pasos aún no está configurado en este servidor. Consulte al administrador.",
826
+ "mfa.app_name": "Nombre",
827
+ "mfa.app_default": "Aplicación de autenticación",
828
+ "mfa.on": "Al iniciar sesión, también introduce un código de su aplicación de autenticación.",
829
+ "mfa.off": "Añada una aplicación de autenticación para proteger su cuenta con un segundo paso al iniciar sesión.",
830
+ "mfa.required_you": "Esta aplicación le exige el inicio de sesión en dos pasos: añada una aplicación de autenticación.",
831
+ "mfa.add_app": "Añadir una aplicación de autenticación",
832
+ "mfa.confirm_app": "Añadir aplicación",
833
+ "mfa.cancel": "Cancelar",
834
+ "mfa.remove": "Quitar",
835
+ "mfa.remove_code": "Un código actual o un código de recuperación",
836
+ "mfa.removed": "Se quitó la aplicación de autenticación.",
837
+ "mfa.keep_one": "Esta aplicación le exige el inicio de sesión en dos pasos: añada otra aplicación antes de quitar esta.",
838
+ "mfa.added": "Se añadió la aplicación de autenticación.",
839
+ "mfa.codes_left": "Códigos de recuperación restantes: {n}",
840
+ "mfa.codes_new": "Nuevos códigos de recuperación",
841
+ "mfa.recovery_used": "Ha iniciado sesión con un código de recuperación. Códigos de recuperación restantes: {n}.",
808
842
  };
package/src/i18n/fi.ts CHANGED
@@ -805,4 +805,38 @@ export const fi: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} ryhmää kohden",
806
806
  'ai_filter.needs_number': "{fn} vaatii numerosarakkeen.",
807
807
  'ai_filter.invalid': "Muutos ei ole mahdollinen: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "Kaksivaiheinen kirjautuminen",
810
+ "mfa.step_intro": "Anna todennussovelluksen 6-numeroinen koodi tai jokin palautuskoodeistasi.",
811
+ "mfa.code": "Koodi",
812
+ "mfa.verify": "Vahvista",
813
+ "mfa.wrong": "Koodi on väärä tai se on jo käytetty.",
814
+ "mfa.expired": "Kirjautuminen kesti liian kauan. Kirjaudu uudelleen.",
815
+ "mfa.enrol_title": "Ota kaksivaiheinen kirjautuminen käyttöön",
816
+ "mfa.enrol_intro": "Tämä sovellus pyytää kirjautuessa koodia todennussovelluksesta. Lisää sellainen nyt jatkaaksesi.",
817
+ "mfa.scan": "Skannaa QR-koodi todennussovelluksella, kuten Google Authenticator, Microsoft Authenticator tai 1Password.",
818
+ "mfa.or_enter": "Tai anna tämä avain:",
819
+ "mfa.enter_code": "Anna sovelluksen näyttämä 6-numeroinen koodi.",
820
+ "mfa.qr_label": "QR-koodi todennussovellukselle",
821
+ "mfa.codes_title": "Palautuskoodit",
822
+ "mfa.codes_once": "Säilytä koodit turvallisessa paikassa. Kukin kirjaa sinut sisään kerran, kun puhelin ei ole käsillä. Ne näytetään vain nyt.",
823
+ "mfa.codes_saved": "Olen tallentanut koodit",
824
+ "mfa.continue": "Jatka",
825
+ "mfa.no_key": "Kaksivaiheista kirjautumista ei ole vielä otettu käyttöön tällä palvelimella. Ota yhteyttä ylläpitäjään.",
826
+ "mfa.app_name": "Nimi",
827
+ "mfa.app_default": "Todennussovellus",
828
+ "mfa.on": "Kirjautuessa annat myös koodin todennussovelluksesta.",
829
+ "mfa.off": "Lisää todennussovellus suojataksesi tilisi kirjautumisen toisella vaiheella.",
830
+ "mfa.required_you": "Tämä sovellus edellyttää sinulta kaksivaiheista kirjautumista: lisää todennussovellus.",
831
+ "mfa.add_app": "Lisää todennussovellus",
832
+ "mfa.confirm_app": "Lisää sovellus",
833
+ "mfa.cancel": "Peruuta",
834
+ "mfa.remove": "Poista",
835
+ "mfa.remove_code": "Voimassa oleva koodi tai palautuskoodi",
836
+ "mfa.removed": "Todennussovellus poistettiin.",
837
+ "mfa.keep_one": "Tämä sovellus edellyttää sinulta kaksivaiheista kirjautumista: lisää toinen sovellus ennen tämän poistamista.",
838
+ "mfa.added": "Todennussovellus lisättiin.",
839
+ "mfa.codes_left": "Palautuskoodeja jäljellä: {n}",
840
+ "mfa.codes_new": "Uudet palautuskoodit",
841
+ "mfa.recovery_used": "Kirjauduit palautuskoodilla. Palautuskoodeja jäljellä: {n}.",
808
842
  };
package/src/i18n/fr.ts CHANGED
@@ -805,4 +805,38 @@ export const fr: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} par groupe",
806
806
  'ai_filter.needs_number': "{fn} demande une colonne de nombres.",
807
807
  'ai_filter.invalid': "Cette modification n'est pas possible : {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "Connexion en deux étapes",
810
+ "mfa.step_intro": "Saisissez le code à 6 chiffres de votre application d'authentification, ou l'un de vos codes de récupération.",
811
+ "mfa.code": "Code",
812
+ "mfa.verify": "Vérifier",
813
+ "mfa.wrong": "Ce code est incorrect ou a déjà été utilisé.",
814
+ "mfa.expired": "La connexion a pris trop de temps. Veuillez vous reconnecter.",
815
+ "mfa.enrol_title": "Configurer la connexion en deux étapes",
816
+ "mfa.enrol_intro": "Cette application demande un code d'une application d'authentification à la connexion. Ajoutez-en une maintenant pour continuer.",
817
+ "mfa.scan": "Scannez le code QR avec une application d'authentification, comme Google Authenticator, Microsoft Authenticator ou 1Password.",
818
+ "mfa.or_enter": "Ou saisissez cette clé :",
819
+ "mfa.enter_code": "Saisissez le code à 6 chiffres affiché par l'application.",
820
+ "mfa.qr_label": "Code QR pour une application d'authentification",
821
+ "mfa.codes_title": "Codes de récupération",
822
+ "mfa.codes_once": "Conservez ces codes en lieu sûr. Chacun vous connecte une fois si vous n'avez pas votre téléphone. Ils ne sont affichés que maintenant.",
823
+ "mfa.codes_saved": "J'ai enregistré ces codes",
824
+ "mfa.continue": "Continuer",
825
+ "mfa.no_key": "La connexion en deux étapes n'est pas encore configurée sur ce serveur. Contactez l'administrateur.",
826
+ "mfa.app_name": "Nom",
827
+ "mfa.app_default": "Application d'authentification",
828
+ "mfa.on": "À la connexion, vous saisissez aussi un code de votre application d'authentification.",
829
+ "mfa.off": "Ajoutez une application d'authentification pour protéger votre compte par une deuxième étape à la connexion.",
830
+ "mfa.required_you": "Cette application exige pour vous la connexion en deux étapes : ajoutez une application d'authentification.",
831
+ "mfa.add_app": "Ajouter une application d'authentification",
832
+ "mfa.confirm_app": "Ajouter l'application",
833
+ "mfa.cancel": "Annuler",
834
+ "mfa.remove": "Supprimer",
835
+ "mfa.remove_code": "Un code actuel ou un code de récupération",
836
+ "mfa.removed": "L'application d'authentification a été supprimée.",
837
+ "mfa.keep_one": "Cette application exige pour vous la connexion en deux étapes : ajoutez une autre application avant de supprimer celle-ci.",
838
+ "mfa.added": "L'application d'authentification a été ajoutée.",
839
+ "mfa.codes_left": "Codes de récupération restants : {n}",
840
+ "mfa.codes_new": "Nouveaux codes de récupération",
841
+ "mfa.recovery_used": "Vous vous êtes connecté avec un code de récupération. Codes de récupération restants : {n}.",
808
842
  };
package/src/i18n/he.ts CHANGED
@@ -805,4 +805,38 @@ export const he: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} לכל קבוצה",
806
806
  'ai_filter.needs_number': "{fn} דורש עמודה של מספרים.",
807
807
  'ai_filter.invalid': "שינוי זה אינו אפשרי: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "כניסה דו-שלבית",
810
+ "mfa.step_intro": "הזינו את הקוד בן 6 הספרות מאפליקציית האימות, או אחד מקודי השחזור.",
811
+ "mfa.code": "קוד",
812
+ "mfa.verify": "אימות",
813
+ "mfa.wrong": "הקוד שגוי או שכבר נעשה בו שימוש.",
814
+ "mfa.expired": "הכניסה ארכה זמן רב מדי. יש להיכנס שוב.",
815
+ "mfa.enrol_title": "הגדרת כניסה דו-שלבית",
816
+ "mfa.enrol_intro": "אפליקציה זו מבקשת קוד מאפליקציית אימות בעת הכניסה. הוסיפו אחת עכשיו כדי להמשיך.",
817
+ "mfa.scan": "סרקו את קוד ה-QR באפליקציית אימות, כמו Google Authenticator,‏ Microsoft Authenticator או 1Password.",
818
+ "mfa.or_enter": "או הזינו את המפתח הזה:",
819
+ "mfa.enter_code": "הזינו את הקוד בן 6 הספרות שהאפליקציה מציגה.",
820
+ "mfa.qr_label": "קוד QR לאפליקציית אימות",
821
+ "mfa.codes_title": "קודי שחזור",
822
+ "mfa.codes_once": "שמרו את הקודים האלה במקום בטוח. כל קוד מאפשר כניסה אחת כשהטלפון לא בהישג יד. הם מוצגים רק עכשיו.",
823
+ "mfa.codes_saved": "שמרתי את הקודים",
824
+ "mfa.continue": "המשך",
825
+ "mfa.no_key": "כניסה דו-שלבית עדיין לא הוגדרה בשרת זה. פנו למנהל המערכת.",
826
+ "mfa.app_name": "שם",
827
+ "mfa.app_default": "אפליקציית אימות",
828
+ "mfa.on": "בעת הכניסה מזינים גם קוד מאפליקציית האימות.",
829
+ "mfa.off": "הוסיפו אפליקציית אימות כדי להגן על החשבון בשלב נוסף בעת הכניסה.",
830
+ "mfa.required_you": "אפליקציה זו מחייבת אתכם בכניסה דו-שלבית: הוסיפו אפליקציית אימות.",
831
+ "mfa.add_app": "הוספת אפליקציית אימות",
832
+ "mfa.confirm_app": "הוספת האפליקציה",
833
+ "mfa.cancel": "ביטול",
834
+ "mfa.remove": "הסרה",
835
+ "mfa.remove_code": "קוד נוכחי או קוד שחזור",
836
+ "mfa.removed": "אפליקציית האימות הוסרה.",
837
+ "mfa.keep_one": "אפליקציה זו מחייבת אתכם בכניסה דו-שלבית: הוסיפו אפליקציה אחרת לפני הסרת זו.",
838
+ "mfa.added": "אפליקציית האימות נוספה.",
839
+ "mfa.codes_left": "קודי שחזור שנותרו: {n}",
840
+ "mfa.codes_new": "קודי שחזור חדשים",
841
+ "mfa.recovery_used": "נכנסתם עם קוד שחזור. קודי שחזור שנותרו: {n}.",
808
842
  };
package/src/i18n/it.ts CHANGED
@@ -805,4 +805,38 @@ export const it: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "{what} per gruppo",
806
806
  'ai_filter.needs_number': "{fn} richiede una colonna di numeri.",
807
807
  'ai_filter.invalid': "Questa modifica non è possibile: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "Accesso in due passaggi",
810
+ "mfa.step_intro": "Inserisci il codice di 6 cifre dell'app di autenticazione, oppure uno dei tuoi codici di recupero.",
811
+ "mfa.code": "Codice",
812
+ "mfa.verify": "Verifica",
813
+ "mfa.wrong": "Il codice non è corretto oppure è già stato usato.",
814
+ "mfa.expired": "L'accesso ha richiesto troppo tempo. Accedi di nuovo.",
815
+ "mfa.enrol_title": "Configura l'accesso in due passaggi",
816
+ "mfa.enrol_intro": "Questa applicazione chiede un codice di un'app di autenticazione all'accesso. Aggiungine una ora per continuare.",
817
+ "mfa.scan": "Scansiona il codice QR con un'app di autenticazione, come Google Authenticator, Microsoft Authenticator o 1Password.",
818
+ "mfa.or_enter": "Oppure inserisci questa chiave:",
819
+ "mfa.enter_code": "Inserisci il codice di 6 cifre mostrato dall'app.",
820
+ "mfa.qr_label": "Codice QR per un'app di autenticazione",
821
+ "mfa.codes_title": "Codici di recupero",
822
+ "mfa.codes_once": "Conserva questi codici in un luogo sicuro. Ognuno ti fa accedere una volta quando non hai il telefono. Vengono mostrati solo ora.",
823
+ "mfa.codes_saved": "Ho salvato questi codici",
824
+ "mfa.continue": "Continua",
825
+ "mfa.no_key": "L'accesso in due passaggi non è ancora configurato su questo server. Rivolgiti all'amministratore.",
826
+ "mfa.app_name": "Nome",
827
+ "mfa.app_default": "App di autenticazione",
828
+ "mfa.on": "All'accesso inserisci anche un codice dell'app di autenticazione.",
829
+ "mfa.off": "Aggiungi un'app di autenticazione per proteggere il tuo account con un secondo passaggio all'accesso.",
830
+ "mfa.required_you": "Questa applicazione richiede per te l'accesso in due passaggi: aggiungi un'app di autenticazione.",
831
+ "mfa.add_app": "Aggiungi un'app di autenticazione",
832
+ "mfa.confirm_app": "Aggiungi app",
833
+ "mfa.cancel": "Annulla",
834
+ "mfa.remove": "Rimuovi",
835
+ "mfa.remove_code": "Un codice attuale o un codice di recupero",
836
+ "mfa.removed": "L'app di autenticazione è stata rimossa.",
837
+ "mfa.keep_one": "Questa applicazione richiede per te l'accesso in due passaggi: aggiungi un'altra app prima di rimuovere questa.",
838
+ "mfa.added": "L'app di autenticazione è stata aggiunta.",
839
+ "mfa.codes_left": "Codici di recupero rimasti: {n}",
840
+ "mfa.codes_new": "Nuovi codici di recupero",
841
+ "mfa.recovery_used": "Hai effettuato l'accesso con un codice di recupero. Codici di recupero rimasti: {n}.",
808
842
  };
package/src/i18n/ja.ts CHANGED
@@ -805,4 +805,38 @@ export const ja: Record<MessageKey, string> = {
805
805
  'ai_filter.per_group': "グループごとの {what}",
806
806
  'ai_filter.needs_number': "{fn} には数値の列が必要です。",
807
807
  'ai_filter.invalid': "この変更はできません: {why}",
808
+ // (sprint 130) two-step sign-in
809
+ "mfa.step_title": "2 段階サインイン",
810
+ "mfa.step_intro": "認証アプリの 6 桁のコード、または回復コードのいずれかを入力してください。",
811
+ "mfa.code": "コード",
812
+ "mfa.verify": "確認",
813
+ "mfa.wrong": "コードが正しくないか、すでに使用されています。",
814
+ "mfa.expired": "サインインに時間がかかりすぎました。もう一度サインインしてください。",
815
+ "mfa.enrol_title": "2 段階サインインの設定",
816
+ "mfa.enrol_intro": "このアプリケーションではサインイン時に認証アプリのコードが必要です。続行するには今すぐ追加してください。",
817
+ "mfa.scan": "Google Authenticator、Microsoft Authenticator、1Password などの認証アプリで QR コードを読み取ってください。",
818
+ "mfa.or_enter": "または次のキーを入力します:",
819
+ "mfa.enter_code": "アプリに表示される 6 桁のコードを入力します。",
820
+ "mfa.qr_label": "認証アプリ用の QR コード",
821
+ "mfa.codes_title": "回復コード",
822
+ "mfa.codes_once": "これらのコードは安全な場所に保管してください。電話がないときに、それぞれ 1 回だけサインインに使えます。表示されるのは今回だけです。",
823
+ "mfa.codes_saved": "これらのコードを保存しました",
824
+ "mfa.continue": "続行",
825
+ "mfa.no_key": "このサーバーでは 2 段階サインインがまだ設定されていません。管理者に問い合わせてください。",
826
+ "mfa.app_name": "名前",
827
+ "mfa.app_default": "認証アプリ",
828
+ "mfa.on": "サインイン時に認証アプリのコードも入力します。",
829
+ "mfa.off": "認証アプリを追加すると、サインイン時の 2 つ目の手順でアカウントを保護できます。",
830
+ "mfa.required_you": "このアプリケーションでは 2 段階サインインが必要です。認証アプリを追加してください。",
831
+ "mfa.add_app": "認証アプリを追加",
832
+ "mfa.confirm_app": "アプリを追加",
833
+ "mfa.cancel": "キャンセル",
834
+ "mfa.remove": "削除",
835
+ "mfa.remove_code": "現在のコードまたは回復コード",
836
+ "mfa.removed": "認証アプリを削除しました。",
837
+ "mfa.keep_one": "このアプリケーションでは 2 段階サインインが必要です。このアプリを削除する前に別のアプリを追加してください。",
838
+ "mfa.added": "認証アプリを追加しました。",
839
+ "mfa.codes_left": "残りの回復コード: {n}",
840
+ "mfa.codes_new": "新しい回復コード",
841
+ "mfa.recovery_used": "回復コードでサインインしました。残りの回復コード: {n}。",
808
842
  };