@owlmeans/web-auth-email 0.0.0-stage → 0.1.19
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.
- package/README.md +116 -2
- package/THIRD-PARTY-NOTICES.txt +24 -0
- package/agent-meta/manifest.json +16 -0
- package/agent-meta/skills/web-auth-email/SKILL.md +119 -0
- package/build/@/components/ui/alert.d.ts +10 -0
- package/build/@/components/ui/alert.d.ts.map +1 -0
- package/build/@/components/ui/alert.js +26 -0
- package/build/@/components/ui/alert.js.map +1 -0
- package/build/@/components/ui/button.d.ts +11 -0
- package/build/@/components/ui/button.d.ts.map +1 -0
- package/build/@/components/ui/button.js +32 -0
- package/build/@/components/ui/button.js.map +1 -0
- package/build/@/components/ui/card.d.ts +10 -0
- package/build/@/components/ui/card.d.ts.map +1 -0
- package/build/@/components/ui/card.js +25 -0
- package/build/@/components/ui/card.js.map +1 -0
- package/build/@/components/ui/input.d.ts +4 -0
- package/build/@/components/ui/input.d.ts.map +1 -0
- package/build/@/components/ui/input.js +7 -0
- package/build/@/components/ui/input.js.map +1 -0
- package/build/@/components/ui/label.d.ts +5 -0
- package/build/@/components/ui/label.d.ts.map +1 -0
- package/build/@/components/ui/label.js +8 -0
- package/build/@/components/ui/label.js.map +1 -0
- package/build/@/lib/utils.d.ts +3 -0
- package/build/@/lib/utils.d.ts.map +1 -0
- package/build/@/lib/utils.js +6 -0
- package/build/@/lib/utils.js.map +1 -0
- package/build/append.d.ts +23 -0
- package/build/append.d.ts.map +1 -0
- package/build/append.js +49 -0
- package/build/append.js.map +1 -0
- package/build/components/connected.d.ts +8 -0
- package/build/components/connected.d.ts.map +1 -0
- package/build/components/connected.js +16 -0
- package/build/components/connected.js.map +1 -0
- package/build/components/credit.d.ts +5 -0
- package/build/components/credit.d.ts.map +1 -0
- package/build/components/credit.js +16 -0
- package/build/components/credit.js.map +1 -0
- package/build/components/fields.d.ts +23 -0
- package/build/components/fields.d.ts.map +1 -0
- package/build/components/fields.js +39 -0
- package/build/components/fields.js.map +1 -0
- package/build/components/index.d.ts +9 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +8 -0
- package/build/components/index.js.map +1 -0
- package/build/components/offer.d.ts +9 -0
- package/build/components/offer.d.ts.map +1 -0
- package/build/components/offer.js +99 -0
- package/build/components/offer.js.map +1 -0
- package/build/components/panel.d.ts +12 -0
- package/build/components/panel.d.ts.map +1 -0
- package/build/components/panel.js +129 -0
- package/build/components/panel.js.map +1 -0
- package/build/components/route.d.ts +9 -0
- package/build/components/route.d.ts.map +1 -0
- package/build/components/route.js +17 -0
- package/build/components/route.js.map +1 -0
- package/build/components/screen.d.ts +17 -0
- package/build/components/screen.d.ts.map +1 -0
- package/build/components/screen.js +79 -0
- package/build/components/screen.js.map +1 -0
- package/build/components/types.d.ts +81 -0
- package/build/components/types.d.ts.map +1 -0
- package/build/components/types.js +2 -0
- package/build/components/types.js.map +1 -0
- package/build/consts.d.ts +37 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +43 -0
- package/build/consts.js.map +1 -0
- package/build/entrypoints.d.ts +10 -0
- package/build/entrypoints.d.ts.map +1 -0
- package/build/entrypoints.js +14 -0
- package/build/entrypoints.js.map +1 -0
- package/build/hooks/consts.local.d.ts +4 -0
- package/build/hooks/consts.local.d.ts.map +1 -0
- package/build/hooks/consts.local.js +9 -0
- package/build/hooks/consts.local.js.map +1 -0
- package/build/hooks/types.d.ts +78 -0
- package/build/hooks/types.d.ts.map +1 -0
- package/build/hooks/types.js +2 -0
- package/build/hooks/types.js.map +1 -0
- package/build/hooks/use-email-login.d.ts +13 -0
- package/build/hooks/use-email-login.d.ts.map +1 -0
- package/build/hooks/use-email-login.js +208 -0
- package/build/hooks/use-email-login.js.map +1 -0
- package/build/hooks/use-password.d.ts +13 -0
- package/build/hooks/use-password.d.ts.map +1 -0
- package/build/hooks/use-password.js +118 -0
- package/build/hooks/use-password.js.map +1 -0
- package/build/hooks/use-translate.d.ts +7 -0
- package/build/hooks/use-translate.d.ts.map +1 -0
- package/build/hooks/use-translate.js +12 -0
- package/build/hooks/use-translate.js.map +1 -0
- package/build/i18n/auth/be.json +7 -0
- package/build/i18n/auth/de.json +7 -0
- package/build/i18n/auth/en.json +7 -0
- package/build/i18n/auth/es.json +7 -0
- package/build/i18n/auth/fr.json +7 -0
- package/build/i18n/auth/pl.json +7 -0
- package/build/i18n/auth/ru.json +7 -0
- package/build/i18n/auth/uk.json +7 -0
- package/build/i18n/be.json +100 -0
- package/build/i18n/de.json +100 -0
- package/build/i18n/en.json +100 -0
- package/build/i18n/es.json +100 -0
- package/build/i18n/fr.json +100 -0
- package/build/i18n/pl.json +100 -0
- package/build/i18n/ru.json +100 -0
- package/build/i18n/uk.json +100 -0
- package/build/i18n.d.ts +2 -0
- package/build/i18n.d.ts.map +1 -0
- package/build/i18n.js +40 -0
- package/build/i18n.js.map +1 -0
- package/build/index.d.ts +23 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +17 -0
- package/build/index.js.map +1 -0
- package/build/password/types.d.ts +39 -0
- package/build/password/types.d.ts.map +1 -0
- package/build/password/types.js +2 -0
- package/build/password/types.js.map +1 -0
- package/build/password.d.ts +4 -0
- package/build/password.d.ts.map +1 -0
- package/build/password.js +45 -0
- package/build/password.js.map +1 -0
- package/build/plugin.d.ts +10 -0
- package/build/plugin.d.ts.map +1 -0
- package/build/plugin.js +21 -0
- package/build/plugin.js.map +1 -0
- package/build/refusal/consts.local.d.ts +3 -0
- package/build/refusal/consts.local.d.ts.map +1 -0
- package/build/refusal/consts.local.js +3 -0
- package/build/refusal/consts.local.js.map +1 -0
- package/build/refusal/types.d.ts +38 -0
- package/build/refusal/types.d.ts.map +1 -0
- package/build/refusal/types.js +2 -0
- package/build/refusal/types.js.map +1 -0
- package/build/refusal.d.ts +4 -0
- package/build/refusal.d.ts.map +1 -0
- package/build/refusal.js +131 -0
- package/build/refusal.js.map +1 -0
- package/build/service.d.ts +12 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +66 -0
- package/build/service.js.map +1 -0
- package/build/state/types.d.ts +90 -0
- package/build/state/types.d.ts.map +1 -0
- package/build/state/types.js +2 -0
- package/build/state/types.js.map +1 -0
- package/build/state.d.ts +4 -0
- package/build/state.d.ts.map +1 -0
- package/build/state.js +98 -0
- package/build/state.js.map +1 -0
- package/build/step.d.ts +16 -0
- package/build/step.d.ts.map +1 -0
- package/build/step.js +31 -0
- package/build/step.js.map +1 -0
- package/build/storage/types.d.ts +42 -0
- package/build/storage/types.d.ts.map +1 -0
- package/build/storage/types.js +2 -0
- package/build/storage/types.js.map +1 -0
- package/build/storage.d.ts +4 -0
- package/build/storage.d.ts.map +1 -0
- package/build/storage.js +100 -0
- package/build/storage.js.map +1 -0
- package/build/types.d.ts +102 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/account.d.ts +4 -0
- package/build/utils/account.d.ts.map +1 -0
- package/build/utils/account.js +11 -0
- package/build/utils/account.js.map +1 -0
- package/build/utils/locale.d.ts +6 -0
- package/build/utils/locale.d.ts.map +1 -0
- package/build/utils/locale.js +10 -0
- package/build/utils/locale.js.map +1 -0
- package/build/utils/text/types.d.ts +23 -0
- package/build/utils/text/types.d.ts.map +1 -0
- package/build/utils/text/types.js +2 -0
- package/build/utils/text/types.js.map +1 -0
- package/build/utils/text.d.ts +4 -0
- package/build/utils/text.d.ts.map +1 -0
- package/build/utils/text.js +26 -0
- package/build/utils/text.js.map +1 -0
- package/build/utils.d.ts +8 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +9 -0
- package/build/utils.js.map +1 -0
- package/components.json +21 -0
- package/package.json +71 -4
- package/src/@/components/ui/alert.tsx +70 -0
- package/src/@/components/ui/button.tsx +60 -0
- package/src/@/components/ui/card.tsx +93 -0
- package/src/@/components/ui/input.tsx +22 -0
- package/src/@/components/ui/label.tsx +23 -0
- package/src/@/globals.css +57 -0
- package/src/@/lib/utils.ts +6 -0
- package/src/append.ts +65 -0
- package/src/components/connected.tsx +36 -0
- package/src/components/credit.tsx +30 -0
- package/src/components/fields.tsx +112 -0
- package/src/components/index.ts +8 -0
- package/src/components/offer.tsx +197 -0
- package/src/components/panel.tsx +299 -0
- package/src/components/route.tsx +20 -0
- package/src/components/screen.tsx +253 -0
- package/src/components/types.ts +85 -0
- package/src/consts.ts +53 -0
- package/src/entrypoints.ts +15 -0
- package/src/hooks/consts.local.ts +12 -0
- package/src/hooks/types.ts +79 -0
- package/src/hooks/use-email-login.ts +234 -0
- package/src/hooks/use-password.ts +137 -0
- package/src/hooks/use-translate.ts +14 -0
- package/src/i18n/auth/be.json +7 -0
- package/src/i18n/auth/de.json +7 -0
- package/src/i18n/auth/en.json +7 -0
- package/src/i18n/auth/es.json +7 -0
- package/src/i18n/auth/fr.json +7 -0
- package/src/i18n/auth/pl.json +7 -0
- package/src/i18n/auth/ru.json +7 -0
- package/src/i18n/auth/uk.json +7 -0
- package/src/i18n/be.json +100 -0
- package/src/i18n/de.json +100 -0
- package/src/i18n/en.json +100 -0
- package/src/i18n/es.json +100 -0
- package/src/i18n/fr.json +100 -0
- package/src/i18n/pl.json +100 -0
- package/src/i18n/ru.json +100 -0
- package/src/i18n/uk.json +100 -0
- package/src/i18n.ts +43 -0
- package/src/index.ts +22 -0
- package/src/password/types.ts +43 -0
- package/src/password.ts +65 -0
- package/src/plugin.ts +22 -0
- package/src/refusal/consts.local.ts +2 -0
- package/src/refusal/types.ts +64 -0
- package/src/refusal.ts +145 -0
- package/src/service.ts +95 -0
- package/src/state/types.ts +72 -0
- package/src/state.ts +115 -0
- package/src/step.ts +38 -0
- package/src/storage/types.ts +43 -0
- package/src/storage.ts +113 -0
- package/src/types.ts +111 -0
- package/src/utils/account.ts +12 -0
- package/src/utils/locale.ts +11 -0
- package/src/utils/text/types.ts +24 -0
- package/src/utils/text.ts +34 -0
- package/src/utils.ts +9 -0
- package/tests/helpers.ts +22 -0
- package/tests/i18n.spec.ts +94 -0
- package/tests/package-boundary.spec.ts +35 -0
- package/tests/password.spec.ts +56 -0
- package/tests/refusal.spec.ts +91 -0
- package/tests/state.spec.ts +99 -0
- package/tests/step.spec.ts +82 -0
- package/tests/storage.spec.ts +83 -0
- package/tsconfig.json +21 -0
package/src/i18n/ru.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"captcha": {
|
|
3
|
+
"notice": "Этот сайт защищён reCAPTCHA, и к нему применяются {{privacy}} и {{terms}} Google.",
|
|
4
|
+
"privacy": "Политика конфиденциальности",
|
|
5
|
+
"terms": "Условия использования"
|
|
6
|
+
},
|
|
7
|
+
"error": {
|
|
8
|
+
"captcha": "Не удалось подтвердить, что вы не робот. Попробуйте ещё раз.",
|
|
9
|
+
"captcha-unavailable": "Не удалось загрузить проверку безопасности. Проверьте подключение и попробуйте ещё раз.",
|
|
10
|
+
"code": "Код неверный или устарел. Проверьте его или запросите новый.",
|
|
11
|
+
"credentials": "Неверный адрес электронной почты или пароль.",
|
|
12
|
+
"email": "Введите правильный адрес электронной почты.",
|
|
13
|
+
"failed": "Что-то пошло не так. Попробуйте ещё раз.",
|
|
14
|
+
"mismatch": "Пароли не совпадают.",
|
|
15
|
+
"offer-expired": "Срок действия предложения истёк. Пароль можно задать позже в настройках аккаунта.",
|
|
16
|
+
"password-disabled": "Вход по паролю здесь недоступен. Войдите с кодом.",
|
|
17
|
+
"password-empty": "Введите пароль.",
|
|
18
|
+
"pin-format": "Введите код из письма: {{length}} цифр.",
|
|
19
|
+
"throttled": "Слишком много попыток. Подождите немного и попробуйте снова.",
|
|
20
|
+
"throttled-in": "Слишком много попыток. Попробуйте снова {{when}}.",
|
|
21
|
+
"unavailable": "Вход временно недоступен. Попробуйте позже.",
|
|
22
|
+
"weak": "Используйте от {{min}} до {{max}} символов и не используйте свой адрес электронной почты."
|
|
23
|
+
},
|
|
24
|
+
"login": {
|
|
25
|
+
"change-email": "Изменить адрес",
|
|
26
|
+
"device": "Мы не узнаём это устройство. Введите код, который мы отправили вам на почту.",
|
|
27
|
+
"done": "Выполняем вход…",
|
|
28
|
+
"email-label": "Электронная почта",
|
|
29
|
+
"email-placeholder": "you@example.com",
|
|
30
|
+
"hide-password": "Скрыть пароль",
|
|
31
|
+
"options-failed": "Не удалось загрузить настройки входа.",
|
|
32
|
+
"other": "Другие способы входа",
|
|
33
|
+
"password-label": "Пароль",
|
|
34
|
+
"password-submit": "Войти",
|
|
35
|
+
"password-subtitle": "Войдите с помощью электронной почты и пароля.",
|
|
36
|
+
"pin-label": "Код из письма",
|
|
37
|
+
"pin-subtitle": "Код для входа ждёт вас в почте.",
|
|
38
|
+
"powered": "Работает на OwlMeans",
|
|
39
|
+
"resend": "Отправить новый код",
|
|
40
|
+
"retry": "Повторить",
|
|
41
|
+
"send": "Получить PIN-код для входа",
|
|
42
|
+
"sending": "Отправляем…",
|
|
43
|
+
"sent": "Мы отправили код для входа на {{email}}.",
|
|
44
|
+
"show-password": "Показать пароль",
|
|
45
|
+
"subtitle": "Мы пришлём код для входа на вашу почту. Впервые здесь? Тот же код создаст ваш аккаунт.",
|
|
46
|
+
"title": "Вход по электронной почте",
|
|
47
|
+
"use-password": "Уже задали пароль? Войдите с ним",
|
|
48
|
+
"use-pin": "Войти с кодом",
|
|
49
|
+
"verify": "Войти",
|
|
50
|
+
"verifying": "Выполняем вход…"
|
|
51
|
+
},
|
|
52
|
+
"offer": {
|
|
53
|
+
"description": "В следующий раз входите по почте и паролю, не дожидаясь кода.",
|
|
54
|
+
"hint": "От {{min}} до {{max}} символов.",
|
|
55
|
+
"later": "Пароль всегда можно задать позже в настройках аккаунта.",
|
|
56
|
+
"password-label": "Новый пароль",
|
|
57
|
+
"repeat-label": "Повторите пароль",
|
|
58
|
+
"save": "Сохранить пароль",
|
|
59
|
+
"saving": "Сохраняем…",
|
|
60
|
+
"skip": "Не сейчас",
|
|
61
|
+
"title": "Задайте пароль для быстрого входа"
|
|
62
|
+
},
|
|
63
|
+
"password": {
|
|
64
|
+
"action": {
|
|
65
|
+
"change": "Изменить пароль",
|
|
66
|
+
"remove": "Удалить пароль",
|
|
67
|
+
"set": "Задать пароль"
|
|
68
|
+
},
|
|
69
|
+
"cancel": "Отмена",
|
|
70
|
+
"confirm": "Подтвердить",
|
|
71
|
+
"confirming": "Сохраняем…",
|
|
72
|
+
"description": "Входите по электронной почте и паролю вместо кода.",
|
|
73
|
+
"devices": "На других устройствах при следующем входе понадобится код.",
|
|
74
|
+
"done": {
|
|
75
|
+
"change": "Пароль изменён.",
|
|
76
|
+
"remove": "Пароль удалён.",
|
|
77
|
+
"set": "Пароль задан."
|
|
78
|
+
},
|
|
79
|
+
"explain": {
|
|
80
|
+
"change": "Чтобы изменить пароль, подтвердите, что это вы, кодом, который мы пришлём на почту.",
|
|
81
|
+
"remove": "Без пароля вы входите с кодом, который приходит на почту. Мы пришлём код для подтверждения.",
|
|
82
|
+
"set": "Чтобы задать пароль, подтвердите, что это вы, кодом, который мы пришлём на почту."
|
|
83
|
+
},
|
|
84
|
+
"loading": "Загрузка…",
|
|
85
|
+
"new-label": "Новый пароль",
|
|
86
|
+
"off": "Вход по паролю здесь недоступен. Вы входите с кодом, который приходит на почту.",
|
|
87
|
+
"pin-label": "Код из письма",
|
|
88
|
+
"repeat-label": "Повторите пароль",
|
|
89
|
+
"resend": "Отправить новый код",
|
|
90
|
+
"retry": "Повторить",
|
|
91
|
+
"send": "Отправить код",
|
|
92
|
+
"sending": "Отправляем…",
|
|
93
|
+
"sent": "Мы отправили код на вашу почту.",
|
|
94
|
+
"set": "У вас есть пароль.",
|
|
95
|
+
"title": "Пароль",
|
|
96
|
+
"unreadable": "Не удалось загрузить настройки пароля.",
|
|
97
|
+
"unset": "У вас пока нет пароля. Вы входите с кодом, который приходит на почту.",
|
|
98
|
+
"updated": "Последнее изменение: {{date}}."
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/i18n/uk.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"captcha": {
|
|
3
|
+
"notice": "Цей сайт захищено reCAPTCHA, і до нього застосовуються {{privacy}} та {{terms}} Google.",
|
|
4
|
+
"privacy": "Політика конфіденційності",
|
|
5
|
+
"terms": "Умови використання"
|
|
6
|
+
},
|
|
7
|
+
"error": {
|
|
8
|
+
"captcha": "Не вдалося підтвердити, що ви не робот. Спробуйте ще раз.",
|
|
9
|
+
"captcha-unavailable": "Не вдалося завантажити перевірку безпеки. Перевірте з'єднання та спробуйте ще раз.",
|
|
10
|
+
"code": "Код неправильний або застарів. Перевірте його або запросіть новий.",
|
|
11
|
+
"credentials": "Неправильна адреса електронної пошти або пароль.",
|
|
12
|
+
"email": "Введіть правильну адресу електронної пошти.",
|
|
13
|
+
"failed": "Щось пішло не так. Спробуйте ще раз.",
|
|
14
|
+
"mismatch": "Паролі не збігаються.",
|
|
15
|
+
"offer-expired": "Термін дії пропозиції минув. Пароль можна встановити пізніше в налаштуваннях облікового запису.",
|
|
16
|
+
"password-disabled": "Вхід за паролем тут недоступний. Увійдіть за кодом.",
|
|
17
|
+
"password-empty": "Введіть пароль.",
|
|
18
|
+
"pin-format": "Введіть код із листа: {{length}} цифр.",
|
|
19
|
+
"throttled": "Забагато спроб. Зачекайте трохи й спробуйте знову.",
|
|
20
|
+
"throttled-in": "Забагато спроб. Спробуйте знову {{when}}.",
|
|
21
|
+
"unavailable": "Вхід тимчасово недоступний. Спробуйте пізніше.",
|
|
22
|
+
"weak": "Використовуйте від {{min}} до {{max}} символів і не використовуйте свою адресу електронної пошти."
|
|
23
|
+
},
|
|
24
|
+
"login": {
|
|
25
|
+
"change-email": "Змінити адресу",
|
|
26
|
+
"device": "Ми не впізнаємо цей пристрій. Введіть код, який ми надіслали вам на пошту.",
|
|
27
|
+
"done": "Виконуємо вхід…",
|
|
28
|
+
"email-label": "Електронна пошта",
|
|
29
|
+
"email-placeholder": "you@example.com",
|
|
30
|
+
"hide-password": "Сховати пароль",
|
|
31
|
+
"options-failed": "Не вдалося завантажити налаштування входу.",
|
|
32
|
+
"other": "Інші способи входу",
|
|
33
|
+
"password-label": "Пароль",
|
|
34
|
+
"password-submit": "Увійти",
|
|
35
|
+
"password-subtitle": "Увійдіть за допомогою електронної пошти та пароля.",
|
|
36
|
+
"pin-label": "Код із листа",
|
|
37
|
+
"pin-subtitle": "Код для входу чекає на вас у пошті.",
|
|
38
|
+
"powered": "Працює на OwlMeans",
|
|
39
|
+
"resend": "Надіслати новий код",
|
|
40
|
+
"retry": "Повторити",
|
|
41
|
+
"send": "Отримати PIN-код для входу",
|
|
42
|
+
"sending": "Надсилаємо…",
|
|
43
|
+
"sent": "Ми надіслали код для входу на {{email}}.",
|
|
44
|
+
"show-password": "Показати пароль",
|
|
45
|
+
"subtitle": "Ми надішлемо код для входу на вашу пошту. Вперше тут? Той самий код створить ваш обліковий запис.",
|
|
46
|
+
"title": "Вхід за електронною поштою",
|
|
47
|
+
"use-password": "Уже встановили пароль? Увійдіть із ним",
|
|
48
|
+
"use-pin": "Увійти за кодом",
|
|
49
|
+
"verify": "Увійти",
|
|
50
|
+
"verifying": "Виконуємо вхід…"
|
|
51
|
+
},
|
|
52
|
+
"offer": {
|
|
53
|
+
"description": "Наступного разу входьте за поштою й паролем, не чекаючи на код.",
|
|
54
|
+
"hint": "Від {{min}} до {{max}} символів.",
|
|
55
|
+
"later": "Пароль завжди можна встановити пізніше в налаштуваннях облікового запису.",
|
|
56
|
+
"password-label": "Новий пароль",
|
|
57
|
+
"repeat-label": "Повторіть пароль",
|
|
58
|
+
"save": "Зберегти пароль",
|
|
59
|
+
"saving": "Зберігаємо…",
|
|
60
|
+
"skip": "Не зараз",
|
|
61
|
+
"title": "Встановіть пароль для швидкого входу"
|
|
62
|
+
},
|
|
63
|
+
"password": {
|
|
64
|
+
"action": {
|
|
65
|
+
"change": "Змінити пароль",
|
|
66
|
+
"remove": "Видалити пароль",
|
|
67
|
+
"set": "Встановити пароль"
|
|
68
|
+
},
|
|
69
|
+
"cancel": "Скасувати",
|
|
70
|
+
"confirm": "Підтвердити",
|
|
71
|
+
"confirming": "Зберігаємо…",
|
|
72
|
+
"description": "Входьте за електронною поштою та паролем замість коду.",
|
|
73
|
+
"devices": "На інших пристроях під час наступного входу знадобиться код.",
|
|
74
|
+
"done": {
|
|
75
|
+
"change": "Пароль змінено.",
|
|
76
|
+
"remove": "Пароль видалено.",
|
|
77
|
+
"set": "Пароль встановлено."
|
|
78
|
+
},
|
|
79
|
+
"explain": {
|
|
80
|
+
"change": "Щоб змінити пароль, підтвердьте, що це ви, кодом, який ми надішлемо на пошту.",
|
|
81
|
+
"remove": "Без пароля ви входите за кодом, який приходить на пошту. Ми надішлемо код для підтвердження.",
|
|
82
|
+
"set": "Щоб встановити пароль, підтвердьте, що це ви, кодом, який ми надішлемо на пошту."
|
|
83
|
+
},
|
|
84
|
+
"loading": "Завантаження…",
|
|
85
|
+
"new-label": "Новий пароль",
|
|
86
|
+
"off": "Вхід за паролем тут недоступний. Ви входите за кодом, який приходить на пошту.",
|
|
87
|
+
"pin-label": "Код із листа",
|
|
88
|
+
"repeat-label": "Повторіть пароль",
|
|
89
|
+
"resend": "Надіслати новий код",
|
|
90
|
+
"retry": "Повторити",
|
|
91
|
+
"send": "Надіслати код",
|
|
92
|
+
"sending": "Надсилаємо…",
|
|
93
|
+
"sent": "Ми надіслали код на вашу пошту.",
|
|
94
|
+
"set": "У вас є пароль.",
|
|
95
|
+
"title": "Пароль",
|
|
96
|
+
"unreadable": "Не вдалося завантажити налаштування пароля.",
|
|
97
|
+
"unset": "У вас ще немає пароля. Ви входите за кодом, який приходить на пошту.",
|
|
98
|
+
"updated": "Остання зміна: {{date}}."
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { i18nHelper } from '@owlmeans/i18n'
|
|
2
|
+
import { AUTH_I18N, EMAIL_AUTH_I18N } from './consts.js'
|
|
3
|
+
|
|
4
|
+
import en from './i18n/en.json' with { type: 'json' }
|
|
5
|
+
import pl from './i18n/pl.json' with { type: 'json' }
|
|
6
|
+
import ru from './i18n/ru.json' with { type: 'json' }
|
|
7
|
+
import be from './i18n/be.json' with { type: 'json' }
|
|
8
|
+
import uk from './i18n/uk.json' with { type: 'json' }
|
|
9
|
+
import es from './i18n/es.json' with { type: 'json' }
|
|
10
|
+
import de from './i18n/de.json' with { type: 'json' }
|
|
11
|
+
import fr from './i18n/fr.json' with { type: 'json' }
|
|
12
|
+
|
|
13
|
+
import authEn from './i18n/auth/en.json' with { type: 'json' }
|
|
14
|
+
import authPl from './i18n/auth/pl.json' with { type: 'json' }
|
|
15
|
+
import authRu from './i18n/auth/ru.json' with { type: 'json' }
|
|
16
|
+
import authBe from './i18n/auth/be.json' with { type: 'json' }
|
|
17
|
+
import authUk from './i18n/auth/uk.json' with { type: 'json' }
|
|
18
|
+
import authEs from './i18n/auth/es.json' with { type: 'json' }
|
|
19
|
+
import authDe from './i18n/auth/de.json' with { type: 'json' }
|
|
20
|
+
import authFr from './i18n/auth/fr.json' with { type: 'json' }
|
|
21
|
+
|
|
22
|
+
i18nHelper.addI18nLib('en', EMAIL_AUTH_I18N, en)
|
|
23
|
+
i18nHelper.addI18nLib('pl', EMAIL_AUTH_I18N, pl)
|
|
24
|
+
i18nHelper.addI18nLib('ru', EMAIL_AUTH_I18N, ru)
|
|
25
|
+
i18nHelper.addI18nLib('be', EMAIL_AUTH_I18N, be)
|
|
26
|
+
i18nHelper.addI18nLib('uk', EMAIL_AUTH_I18N, uk)
|
|
27
|
+
i18nHelper.addI18nLib('es', EMAIL_AUTH_I18N, es)
|
|
28
|
+
i18nHelper.addI18nLib('de', EMAIL_AUTH_I18N, de)
|
|
29
|
+
i18nHelper.addI18nLib('fr', EMAIL_AUTH_I18N, fr)
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The chooser button's label, `login.method.email`, pushed into the `auth` library resource that
|
|
33
|
+
* `@owlmeans/client-auth/login` fills with the rest of the sign-in screen — the bundles merge by
|
|
34
|
+
* tier and priority, exactly as `@owlmeans/web-panel` adds `login.provider.*`.
|
|
35
|
+
*/
|
|
36
|
+
i18nHelper.addI18nLib('en', AUTH_I18N, authEn)
|
|
37
|
+
i18nHelper.addI18nLib('pl', AUTH_I18N, authPl)
|
|
38
|
+
i18nHelper.addI18nLib('ru', AUTH_I18N, authRu)
|
|
39
|
+
i18nHelper.addI18nLib('be', AUTH_I18N, authBe)
|
|
40
|
+
i18nHelper.addI18nLib('uk', AUTH_I18N, authUk)
|
|
41
|
+
i18nHelper.addI18nLib('es', AUTH_I18N, authEs)
|
|
42
|
+
i18nHelper.addI18nLib('de', AUTH_I18N, authDe)
|
|
43
|
+
i18nHelper.addI18nLib('fr', AUTH_I18N, authFr)
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export * from './consts.js'
|
|
2
|
+
export type * from './types.js'
|
|
3
|
+
export * from './utils.js'
|
|
4
|
+
export * from './state.js'
|
|
5
|
+
export type * from './state/types.js'
|
|
6
|
+
export * from './refusal.js'
|
|
7
|
+
export type * from './refusal/types.js'
|
|
8
|
+
export * from './storage.js'
|
|
9
|
+
export type * from './storage/types.js'
|
|
10
|
+
export * from './password.js'
|
|
11
|
+
export type * from './password/types.js'
|
|
12
|
+
export * from './service.js'
|
|
13
|
+
export * from './step.js'
|
|
14
|
+
export * from './plugin.js'
|
|
15
|
+
export * from './append.js'
|
|
16
|
+
export * from './entrypoints.js'
|
|
17
|
+
export * from './hooks/use-email-login.js'
|
|
18
|
+
export * from './hooks/use-password.js'
|
|
19
|
+
export * from './hooks/use-translate.js'
|
|
20
|
+
export type * from './hooks/types.js'
|
|
21
|
+
export * from './components/index.js'
|
|
22
|
+
export * from './i18n.js'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { EmailPasswordPurpose, EmailPasswordStatusView } from '@owlmeans/auth-email'
|
|
2
|
+
import type { EmailRefusal } from '../refusal/types.js'
|
|
3
|
+
|
|
4
|
+
/** What a person can do with their password from the panel. */
|
|
5
|
+
export type EmailPasswordAction = 'set' | 'change' | 'remove'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What the panel shows: the status is still loading, could not be read, passwords are off on this
|
|
9
|
+
* deployment, or the account has no password / has one.
|
|
10
|
+
*/
|
|
11
|
+
export type PasswordPanelState = 'loading' | 'unreadable' | 'off' | 'unset' | 'set'
|
|
12
|
+
|
|
13
|
+
/** The panel's own fields while an action is open. */
|
|
14
|
+
export interface PasswordPanelForm {
|
|
15
|
+
pin: string
|
|
16
|
+
password: string
|
|
17
|
+
repeat: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The policy a new password is checked against before it is sent. */
|
|
21
|
+
export interface PasswordPolicy {
|
|
22
|
+
minLength?: number
|
|
23
|
+
maxLength?: number
|
|
24
|
+
pinLength?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The password panel and the offer step as pure decisions — no React, no I/O. */
|
|
28
|
+
export interface PasswordPanelHelper {
|
|
29
|
+
stateOf: (status: EmailPasswordStatusView | null, loading: boolean) => PasswordPanelState
|
|
30
|
+
/** The actions offered for a status: `set` without a password, `change` + `remove` with one. */
|
|
31
|
+
actionsOf: (status: EmailPasswordStatusView | null) => EmailPasswordAction[]
|
|
32
|
+
/** The confirmation PIN's purpose for an action — what `password/pin` is asked for. */
|
|
33
|
+
purposeOf: (action: EmailPasswordAction) => EmailPasswordPurpose
|
|
34
|
+
/** Whether the action asks for a new password (set, change) or only a PIN (remove). */
|
|
35
|
+
needsPassword: (action: EmailPasswordAction) => boolean
|
|
36
|
+
/**
|
|
37
|
+
* What stops a new password from being sent: empty, outside the length bounds (NFKC, as the
|
|
38
|
+
* server counts), or not repeated exactly. Null when it may go.
|
|
39
|
+
*/
|
|
40
|
+
passwordBlock: (password: string, repeat: string, policy?: PasswordPolicy) => EmailRefusal | null
|
|
41
|
+
/** What stops "Confirm" for an action: the PIN's format first, then the new password. */
|
|
42
|
+
confirmBlock: (action: EmailPasswordAction, form: PasswordPanelForm, policy?: PasswordPolicy) => EmailRefusal | null
|
|
43
|
+
}
|
package/src/password.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EMAIL_PASSWORD_MAX_LENGTH, EMAIL_PASSWORD_MIN_LENGTH, EMAIL_PIN_LENGTH, EmailPasswordPurpose,
|
|
3
|
+
} from '@owlmeans/auth-email'
|
|
4
|
+
import type { EmailPasswordStatusView } from '@owlmeans/auth-email'
|
|
5
|
+
import type { EmailRefusal } from './refusal/types.js'
|
|
6
|
+
import type {
|
|
7
|
+
EmailPasswordAction, PasswordPanelForm, PasswordPanelHelper, PasswordPanelState, PasswordPolicy,
|
|
8
|
+
} from './password/types.js'
|
|
9
|
+
|
|
10
|
+
export const createPasswordPanelHelper = (): PasswordPanelHelper => {
|
|
11
|
+
const stateOf = (status: EmailPasswordStatusView | null, loading: boolean): PasswordPanelState => {
|
|
12
|
+
if (status == null) {
|
|
13
|
+
return loading ? 'loading' : 'unreadable'
|
|
14
|
+
}
|
|
15
|
+
if (!status.enabled) {
|
|
16
|
+
return 'off'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return status.set ? 'set' : 'unset'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const actionsOf = (status: EmailPasswordStatusView | null): EmailPasswordAction[] => {
|
|
23
|
+
switch (stateOf(status, false)) {
|
|
24
|
+
case 'set':
|
|
25
|
+
return ['change', 'remove']
|
|
26
|
+
case 'unset':
|
|
27
|
+
return ['set']
|
|
28
|
+
default:
|
|
29
|
+
return []
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const purposeOf = (action: EmailPasswordAction): EmailPasswordPurpose =>
|
|
34
|
+
action === 'set' ? EmailPasswordPurpose.Set
|
|
35
|
+
: action === 'change' ? EmailPasswordPurpose.Change : EmailPasswordPurpose.Remove
|
|
36
|
+
|
|
37
|
+
const needsPassword = (action: EmailPasswordAction): boolean => action !== 'remove'
|
|
38
|
+
|
|
39
|
+
const passwordBlock = (password: string, repeat: string, policy?: PasswordPolicy): EmailRefusal | null => {
|
|
40
|
+
if (password === '') {
|
|
41
|
+
return { kind: 'password-empty' }
|
|
42
|
+
}
|
|
43
|
+
const length = password.normalize('NFKC').length
|
|
44
|
+
if (length < (policy?.minLength ?? EMAIL_PASSWORD_MIN_LENGTH)
|
|
45
|
+
|| length > (policy?.maxLength ?? EMAIL_PASSWORD_MAX_LENGTH)) {
|
|
46
|
+
return { kind: 'weak' }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return password === repeat ? null : { kind: 'mismatch' }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const confirmBlock = (
|
|
53
|
+
action: EmailPasswordAction, form: PasswordPanelForm, policy?: PasswordPolicy
|
|
54
|
+
): EmailRefusal | null => {
|
|
55
|
+
if (!new RegExp(`^[0-9]{${policy?.pinLength ?? EMAIL_PIN_LENGTH}}$`).test(form.pin)) {
|
|
56
|
+
return { kind: 'pin-format' }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return needsPassword(action) ? passwordBlock(form.password, form.repeat, policy) : null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { stateOf, actionsOf, purposeOf, needsPassword, passwordBlock, confirmBlock }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const passwordPanelHelper = createPasswordPanelHelper()
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AuthenticationPlugin, AuthMethodMeta } from '@owlmeans/client-auth/manager/plugins'
|
|
2
|
+
import { EMAIL_LOGIN_METHOD, EMAIL_LOGIN_SERVICE } from '@owlmeans/auth-email'
|
|
3
|
+
import { EMAIL_LOGIN_METHOD_META } from './consts.js'
|
|
4
|
+
import { EmailLoginRoute } from './components/route.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `email` authentication plugin: a self-contained method whose route
|
|
8
|
+
* (`/authentication/login/email`) renders the e-mail card and completes the sign-in itself.
|
|
9
|
+
*
|
|
10
|
+
* It needs no renderer, and it is offered only where `appendEmailLogin` registered the client
|
|
11
|
+
* service — so a bundle that merely contains this package never grows a button that cannot work.
|
|
12
|
+
*/
|
|
13
|
+
export const makeEmailLoginPlugin = (method?: Partial<AuthMethodMeta>): AuthenticationPlugin => ({
|
|
14
|
+
type: EMAIL_LOGIN_METHOD,
|
|
15
|
+
requiresRenderer: false,
|
|
16
|
+
method: {
|
|
17
|
+
...EMAIL_LOGIN_METHOD_META,
|
|
18
|
+
...method,
|
|
19
|
+
available: ctx => ctx.context.hasService(EMAIL_LOGIN_SERVICE),
|
|
20
|
+
},
|
|
21
|
+
Implementation: () => EmailLoginRoute,
|
|
22
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Translate } from '../types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why an e-mail sign-in or password action did not go through, as the person is told.
|
|
5
|
+
*
|
|
6
|
+
* Server refusals (`throttled`, `code`, `credentials`, `captcha`, `captcha-unavailable`,
|
|
7
|
+
* `unavailable`, `weak`, `offer-expired`, `password-disabled`, `failed`) are read from the error a
|
|
8
|
+
* call rejected with; the rest (`email`, `pin-format`, `password-empty`, `mismatch`, and `weak` for a
|
|
9
|
+
* length the policy refuses anyway) are found before anything is sent.
|
|
10
|
+
*/
|
|
11
|
+
export type EmailRefusalKind =
|
|
12
|
+
| 'throttled'
|
|
13
|
+
| 'code'
|
|
14
|
+
| 'credentials'
|
|
15
|
+
| 'captcha'
|
|
16
|
+
| 'captcha-unavailable'
|
|
17
|
+
| 'unavailable'
|
|
18
|
+
| 'email'
|
|
19
|
+
| 'pin-format'
|
|
20
|
+
| 'password-empty'
|
|
21
|
+
| 'mismatch'
|
|
22
|
+
| 'weak'
|
|
23
|
+
| 'offer-expired'
|
|
24
|
+
| 'password-disabled'
|
|
25
|
+
| 'failed'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Which call was refused — the same status means different things on different calls: a 401 is a
|
|
29
|
+
* wrong code after a PIN and a wrong password after a password sign-in.
|
|
30
|
+
*/
|
|
31
|
+
export type EmailRefusalContext =
|
|
32
|
+
| 'options'
|
|
33
|
+
| 'send'
|
|
34
|
+
| 'pin'
|
|
35
|
+
| 'password'
|
|
36
|
+
| 'status'
|
|
37
|
+
| 'confirm'
|
|
38
|
+
| 'set'
|
|
39
|
+
| 'remove'
|
|
40
|
+
| 'offer'
|
|
41
|
+
|
|
42
|
+
export interface EmailRefusal {
|
|
43
|
+
kind: EmailRefusalKind
|
|
44
|
+
/** Seconds until a throttled request may be repeated, when the refusal said so. */
|
|
45
|
+
retryAfter?: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Reads a failed call as a refusal and phrases it. */
|
|
49
|
+
export interface EmailRefusalHelper {
|
|
50
|
+
/**
|
|
51
|
+
* The refusal an error stands for.
|
|
52
|
+
*
|
|
53
|
+
* A development server sends the typed error (`AuthenFailed('email:password')`, `OtpThrottled`,
|
|
54
|
+
* `EmailPasswordWeak`, `AuthenFailed('recaptcha:score')`…); a production one only the HTTP status
|
|
55
|
+
* and an incident id. Both are read — the type first, then the status in the light of `context` —
|
|
56
|
+
* and anything unrecognised is `failed`, never a guess.
|
|
57
|
+
*/
|
|
58
|
+
refusalOf: (error: unknown, context: EmailRefusalContext) => EmailRefusal
|
|
59
|
+
/**
|
|
60
|
+
* The sentence for a refusal in the reader's language. `locale` formats a known waiting time
|
|
61
|
+
* ("in 45 seconds") with `Intl.RelativeTimeFormat`, so no plural rule lives in a translation.
|
|
62
|
+
*/
|
|
63
|
+
messageOf: (refusal: EmailRefusal, t: Translate, locale?: string) => string
|
|
64
|
+
}
|
package/src/refusal.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { apiStatusHelper } from '@owlmeans/api'
|
|
2
|
+
import {
|
|
3
|
+
EMAIL_PASSWORD_MAX_LENGTH, EMAIL_PASSWORD_MIN_LENGTH, EMAIL_PIN_LENGTH, EmailAuthUnavailable,
|
|
4
|
+
EmailOfferExpired, EmailPasswordDisabled, EmailPasswordWeak,
|
|
5
|
+
} from '@owlmeans/auth-email'
|
|
6
|
+
import { CaptchaLoadError } from '@owlmeans/web-captcha'
|
|
7
|
+
import type { Translate } from './types.js'
|
|
8
|
+
import type { EmailRefusal, EmailRefusalContext, EmailRefusalHelper } from './refusal/types.js'
|
|
9
|
+
import { textUtils } from './utils/text.js'
|
|
10
|
+
import { CAPTCHA_LOAD_ERROR } from './refusal/consts.local.js'
|
|
11
|
+
|
|
12
|
+
export const createEmailRefusalHelper = (): EmailRefusalHelper => {
|
|
13
|
+
/** Everything an error says about itself: its registered type and its message. */
|
|
14
|
+
const textOf = (error: unknown): string => {
|
|
15
|
+
if (error == null || typeof error !== 'object') {
|
|
16
|
+
return typeof error === 'string' ? error : ''
|
|
17
|
+
}
|
|
18
|
+
const { type, message, name } = error as { type?: unknown, message?: unknown, name?: unknown }
|
|
19
|
+
|
|
20
|
+
return [type, name, message].filter(value => typeof value === 'string').join(' ')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Seconds to wait, when the error carries them. An `OtpThrottled` rebuilt from a development
|
|
25
|
+
* body states 1 whatever the server said (the number is not on the wire), so only a longer wait
|
|
26
|
+
* is believed; otherwise the sentence names no time at all.
|
|
27
|
+
*/
|
|
28
|
+
const retryAfterOf = (error: unknown): number | undefined => {
|
|
29
|
+
const value = error != null && typeof error === 'object'
|
|
30
|
+
? (error as { retryAfter?: unknown }).retryAfter : undefined
|
|
31
|
+
|
|
32
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 1 ? Math.ceil(value) : undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const refusalOf = (error: unknown, context: EmailRefusalContext): EmailRefusal => {
|
|
36
|
+
const text = textOf(error)
|
|
37
|
+
const status = apiStatusHelper.httpStatusOf(error)
|
|
38
|
+
|
|
39
|
+
if (error instanceof CaptchaLoadError || text.includes(CAPTCHA_LOAD_ERROR)) {
|
|
40
|
+
// The browser could not get a token at all — the script did not load or Google gave none.
|
|
41
|
+
return { kind: 'captcha-unavailable' }
|
|
42
|
+
}
|
|
43
|
+
if (status === 429 || /Throttled|:throttled/.test(text)) {
|
|
44
|
+
const retryAfter = retryAfterOf(error)
|
|
45
|
+
return retryAfter != null ? { kind: 'throttled', retryAfter } : { kind: 'throttled' }
|
|
46
|
+
}
|
|
47
|
+
if (text.includes('recaptcha')) {
|
|
48
|
+
return { kind: status === 503 || text.includes('Unavailable') ? 'captcha-unavailable' : 'captcha' }
|
|
49
|
+
}
|
|
50
|
+
if (text.includes(EmailPasswordWeak.typeName) || status === 422) {
|
|
51
|
+
return { kind: 'weak' }
|
|
52
|
+
}
|
|
53
|
+
if (text.includes(EmailOfferExpired.typeName) || status === 409) {
|
|
54
|
+
return { kind: 'offer-expired' }
|
|
55
|
+
}
|
|
56
|
+
if (text.includes(EmailPasswordDisabled.typeName)) {
|
|
57
|
+
return { kind: 'password-disabled' }
|
|
58
|
+
}
|
|
59
|
+
if (text.includes('email:password')) {
|
|
60
|
+
return { kind: 'credentials' }
|
|
61
|
+
}
|
|
62
|
+
if (/otp:(code|email)|authen:challenge|email:confirm/.test(text)) {
|
|
63
|
+
return { kind: 'code' }
|
|
64
|
+
}
|
|
65
|
+
if (status === 503 || text.includes(EmailAuthUnavailable.typeName) || text.includes('Unavailable')) {
|
|
66
|
+
return { kind: 'unavailable' }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// A production body carries nothing but the status (and the server's own refusal classes are
|
|
70
|
+
// not registered here, so a development one often arrives the same way): read it in the light
|
|
71
|
+
// of the call. A credential refused on a guest route is 401; a captcha or a confirmation PIN
|
|
72
|
+
// refused is 403 — never 401 on a signed-in route, which would drop the session.
|
|
73
|
+
switch (status) {
|
|
74
|
+
case 403:
|
|
75
|
+
return { kind: context === 'send' || context === 'password' ? 'captcha'
|
|
76
|
+
: context === 'pin' || context === 'confirm' || context === 'set' || context === 'remove' ? 'code' : 'failed' }
|
|
77
|
+
case 401:
|
|
78
|
+
return { kind: context === 'password' ? 'credentials' : context === 'pin' || context === 'confirm'
|
|
79
|
+
|| context === 'set' || context === 'remove' ? 'code' : 'failed' }
|
|
80
|
+
case 404:
|
|
81
|
+
return { kind: context === 'password' || context === 'status' || context === 'confirm'
|
|
82
|
+
|| context === 'set' || context === 'remove' || context === 'offer' ? 'password-disabled' : 'failed' }
|
|
83
|
+
case 400:
|
|
84
|
+
return { kind: context === 'send' ? 'email' : 'failed' }
|
|
85
|
+
default:
|
|
86
|
+
return { kind: 'failed' }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** "in 45 seconds", "через 2 минуты" — the language's own plural rules, never a translation's. */
|
|
91
|
+
const waitOf = (seconds: number, locale?: string): string => {
|
|
92
|
+
const minutes = seconds >= 90
|
|
93
|
+
const value = minutes ? Math.ceil(seconds / 60) : seconds
|
|
94
|
+
try {
|
|
95
|
+
return new Intl.RelativeTimeFormat(locale, { numeric: 'always' }).format(value, minutes ? 'minute' : 'second')
|
|
96
|
+
} catch {
|
|
97
|
+
return `${value} ${minutes ? 'min' : 's'}`
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const messageOf = (refusal: EmailRefusal, t: Translate, locale?: string): string => {
|
|
102
|
+
switch (refusal.kind) {
|
|
103
|
+
case 'throttled':
|
|
104
|
+
return refusal.retryAfter != null
|
|
105
|
+
? textUtils.fill(t('error.throttled-in', 'Too many attempts. Please try again {{when}}.'), {
|
|
106
|
+
when: waitOf(refusal.retryAfter, locale),
|
|
107
|
+
})
|
|
108
|
+
: t('error.throttled', 'Too many attempts. Please wait a little and try again.')
|
|
109
|
+
case 'code':
|
|
110
|
+
return t('error.code', 'This code is wrong or has expired. Check it or ask for a new one.')
|
|
111
|
+
case 'credentials':
|
|
112
|
+
return t('error.credentials', 'Wrong email or password.')
|
|
113
|
+
case 'captcha':
|
|
114
|
+
return t('error.captcha', "We couldn't confirm that you're not a robot. Please try again.")
|
|
115
|
+
case 'captcha-unavailable':
|
|
116
|
+
return t('error.captcha-unavailable', "The security check couldn't load. Check your connection and try again.")
|
|
117
|
+
case 'unavailable':
|
|
118
|
+
return t('error.unavailable', 'Sign-in is temporarily unavailable. Please try again later.')
|
|
119
|
+
case 'email':
|
|
120
|
+
return t('error.email', 'Enter a valid email address.')
|
|
121
|
+
case 'pin-format':
|
|
122
|
+
return textUtils.fill(t('error.pin-format', 'Enter the code from the email: {{length}} digits.'), {
|
|
123
|
+
length: EMAIL_PIN_LENGTH,
|
|
124
|
+
})
|
|
125
|
+
case 'password-empty':
|
|
126
|
+
return t('error.password-empty', 'Enter your password.')
|
|
127
|
+
case 'mismatch':
|
|
128
|
+
return t('error.mismatch', "The passwords don't match.")
|
|
129
|
+
case 'weak':
|
|
130
|
+
return textUtils.fill(t('error.weak', 'Use {{min}} to {{max}} characters, and not your email address.'), {
|
|
131
|
+
min: EMAIL_PASSWORD_MIN_LENGTH, max: EMAIL_PASSWORD_MAX_LENGTH,
|
|
132
|
+
})
|
|
133
|
+
case 'offer-expired':
|
|
134
|
+
return t('error.offer-expired', 'This offer has expired. You can set a password later in your account settings.')
|
|
135
|
+
case 'password-disabled':
|
|
136
|
+
return t('error.password-disabled', 'Password sign-in is not available here. Use a code instead.')
|
|
137
|
+
default:
|
|
138
|
+
return t('error.failed', 'Something went wrong. Please try again.')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { refusalOf, messageOf }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const emailRefusalHelper = createEmailRefusalHelper()
|