emailengine-app 2.65.0 → 2.67.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 (50) hide show
  1. package/.github/workflows/deploy.yml +8 -8
  2. package/.github/workflows/release.yaml +9 -9
  3. package/.github/workflows/test.yml +2 -2
  4. package/CHANGELOG.md +53 -0
  5. package/bin/emailengine.js +3 -0
  6. package/data/google-crawlers.json +7 -1
  7. package/lib/account.js +35 -29
  8. package/lib/consts.js +5 -0
  9. package/lib/email-client/gmail-client.js +23 -27
  10. package/lib/email-client/imap/mailbox.js +46 -19
  11. package/lib/email-client/imap/sync-operations.js +51 -19
  12. package/lib/email-client/imap-client.js +28 -5
  13. package/lib/email-client/outlook-client.js +155 -1
  14. package/lib/oauth/gmail.js +52 -1
  15. package/lib/passkeys.js +206 -0
  16. package/lib/routes-ui.js +522 -21
  17. package/lib/ui-routes/oauth-routes.js +6 -1
  18. package/package.json +13 -11
  19. package/sbom.json +1 -1
  20. package/static/js/login-passkey.js +75 -0
  21. package/static/js/passkey-register.js +107 -0
  22. package/static/licenses.html +238 -38
  23. package/static/vendor/handlebars/handlebars.min-v4.7.9.js +29 -0
  24. package/static/vendor/simplewebauthn/browser.min.js +2 -0
  25. package/translations/de.mo +0 -0
  26. package/translations/de.po +91 -53
  27. package/translations/en.mo +0 -0
  28. package/translations/en.po +84 -52
  29. package/translations/et.mo +0 -0
  30. package/translations/et.po +95 -60
  31. package/translations/fr.mo +0 -0
  32. package/translations/fr.po +102 -65
  33. package/translations/ja.mo +0 -0
  34. package/translations/ja.po +93 -57
  35. package/translations/messages.pot +101 -76
  36. package/translations/nl.mo +0 -0
  37. package/translations/nl.po +92 -56
  38. package/translations/pl.mo +0 -0
  39. package/translations/pl.po +106 -70
  40. package/views/account/login.hbs +35 -25
  41. package/views/account/password.hbs +4 -4
  42. package/views/account/security.hbs +101 -12
  43. package/views/account/totp.hbs +3 -3
  44. package/views/config/oauth/app.hbs +25 -0
  45. package/views/layout/app.hbs +2 -2
  46. package/views/layout/login.hbs +6 -1
  47. package/views/oauth-scope-error.hbs +29 -0
  48. package/workers/api.js +81 -3
  49. package/workers/imap.js +4 -0
  50. package/static/vendor/handlebars/handlebars.min-v4.7.7.js +0 -29
@@ -1,7 +1,7 @@
1
1
  msgid ""
2
2
  msgstr ""
3
3
  "Project-Id-Version: \n"
4
- "POT-Creation-Date: 2026-03-10 13:39+0000\n"
4
+ "POT-Creation-Date: 2026-03-31 08:47+0000\n"
5
5
  "PO-Revision-Date: \n"
6
6
  "Last-Translator: \n"
7
7
  "Language-Team: \n"
@@ -11,15 +11,15 @@ msgstr ""
11
11
  "Content-Transfer-Encoding: 8bit\n"
12
12
  "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 "
13
13
  "|| n%100>14) ? 1 : 2);\n"
14
- "X-Generator: Poedit 3.8\n"
14
+ "X-Generator: Poedit 3.9\n"
15
15
 
16
- #: views/error.hbs:4 workers/api.js:7043
16
+ #: views/error.hbs:4 workers/api.js:7103
17
17
  msgid "Something went wrong"
18
- msgstr "Ups... Coś poszlo nie tak"
18
+ msgstr "Ups... Coś poszło nie tak"
19
19
 
20
20
  #: views/error.hbs:6
21
21
  msgid "Error code: %s"
22
- msgstr "Kod bledu: %s"
22
+ msgstr "Kod błędu: %s"
23
23
 
24
24
  #: views/error.hbs:14
25
25
  msgid "Go back"
@@ -29,13 +29,17 @@ msgstr "Wstecz"
29
29
  msgid "Dashboard"
30
30
  msgstr "Pulpit"
31
31
 
32
- #: views/config/license.hbs:45
32
+ #: views/config/license.hbs:45 lib/routes-ui.js:2497
33
33
  msgid "%d day"
34
34
  msgid_plural "%d days"
35
35
  msgstr[0] "%d dzień"
36
36
  msgstr[1] "%d dni"
37
37
  msgstr[2] "%d dni"
38
38
 
39
+ #: views/redirect.hbs:1
40
+ msgid "Click <a href=\"%s\">here</a> to continue&mldr;"
41
+ msgstr "Kliknij <a href=\"%s\">tutaj</a>, aby kontynuować&mldr;"
42
+
39
43
  #: views/unsubscribe.hbs:3 views/unsubscribe.hbs:62 views/unsubscribe.hbs:85
40
44
  msgid "Unsubscribe"
41
45
  msgstr "Wypisz się"
@@ -81,9 +85,33 @@ msgstr "Adres e-mail"
81
85
  msgid "Enter your email address"
82
86
  msgstr "Wprowadź swój adres e-mail"
83
87
 
84
- #: views/redirect.hbs:1
85
- msgid "Click <a href=\"%s\">here</a> to continue&mldr;"
86
- msgstr "Kliknij <a href=\"%s\">tutaj</a>, aby kontynuować&mldr;"
88
+ #: views/oauth-scope-error.hbs:2
89
+ msgid "Insufficient Permissions"
90
+ msgstr "Niewystarczające uprawnienia"
91
+
92
+ #: views/oauth-scope-error.hbs:8
93
+ msgid ""
94
+ "All requested permissions are required for this service to function properly. "
95
+ "Some required permissions were not granted during sign-in."
96
+ msgstr ""
97
+ "Wszystkie wymagane uprawnienia są niezbędne do prawidłowego działania tej "
98
+ "usługi. Niektóre z nich nie zostały przyznane podczas logowania."
99
+
100
+ #: views/oauth-scope-error.hbs:12
101
+ msgid "The following permissions were not granted:"
102
+ msgstr "Nie przyznano następujących uprawnień:"
103
+
104
+ #: views/oauth-scope-error.hbs:21
105
+ msgid ""
106
+ "Please try again and make sure all permission checkboxes are selected on the "
107
+ "Google consent screen."
108
+ msgstr ""
109
+ "Spróbuj ponownie i upewnij się, że na ekranie zgody Google zaznaczone są "
110
+ "wszystkie pola wyboru dotyczące uprawnień."
111
+
112
+ #: views/oauth-scope-error.hbs:27
113
+ msgid "Try Again"
114
+ msgstr "Spróbuj ponownie"
87
115
 
88
116
  #: views/accounts/register/imap.hbs:11
89
117
  msgid "Your name"
@@ -107,6 +135,14 @@ msgstr "Wprowadź hasło do konta"
107
135
  msgid "Continue"
108
136
  msgstr "Kontynuuj"
109
137
 
138
+ #: views/accounts/register/index.hbs:2
139
+ msgid "Choose your email account provider"
140
+ msgstr "Wybierz dostawcę konta e-mail"
141
+
142
+ #: views/accounts/register/index.hbs:15
143
+ msgid "Standard IMAP"
144
+ msgstr "Standardowy IMAP"
145
+
110
146
  #: views/accounts/register/imap-server.hbs:19
111
147
  msgid "IMAP"
112
148
  msgstr "IMAP"
@@ -154,8 +190,8 @@ msgstr "TLS dla IMAP"
154
190
 
155
191
  #: views/accounts/register/imap-server.hbs:100
156
192
  msgid ""
157
- "TLS (also known as SSL) is usually only needed when using port 993. For "
158
- "other ports EmailEngine falls back to using STARTTLS based encryption."
193
+ "TLS (also known as SSL) is usually only needed when using port 993. For other "
194
+ "ports EmailEngine falls back to using STARTTLS based encryption."
159
195
  msgstr ""
160
196
  "TLS (znany również jako SSL) jest zwykle potrzebny tylko w przypadku "
161
197
  "korzystania z portu 993. W przypadku innych portów EmailEngine powraca do "
@@ -176,8 +212,8 @@ msgstr "TLS dla SMTP"
176
212
 
177
213
  #: views/accounts/register/imap-server.hbs:191
178
214
  msgid ""
179
- "TLS (also known as SSL) is usually only needed when using port 465. For "
180
- "other ports EmailEngine falls back to using STARTTLS based encryption."
215
+ "TLS (also known as SSL) is usually only needed when using port 465. For other "
216
+ "ports EmailEngine falls back to using STARTTLS based encryption."
181
217
  msgstr ""
182
218
  "TLS (znany również jako SSL) jest zwykle potrzebny tylko w przypadku "
183
219
  "korzystania z portu 465. W przypadku innych portów EmailEngine powraca do "
@@ -185,7 +221,7 @@ msgstr ""
185
221
 
186
222
  #: views/accounts/register/imap-server.hbs:209
187
223
  msgid "Verify connection"
188
- msgstr "Zweryfikuj polaczenie"
224
+ msgstr "Zweryfikuj połączenie"
189
225
 
190
226
  #: views/accounts/register/imap-server.hbs:218
191
227
  msgid "Save and continue"
@@ -193,58 +229,50 @@ msgstr "Zapisz i kontynuuj"
193
229
 
194
230
  #: views/accounts/register/imap-server.hbs:226
195
231
  msgid "Skip verification"
196
- msgstr "Pomin weryfikacje"
232
+ msgstr "Pomiń weryfikację"
197
233
 
198
234
  #: views/accounts/register/imap-server.hbs:241
199
235
  msgid "Connection test failed"
200
- msgstr "Test polaczenia nie powiodl sie"
236
+ msgstr "Test połączenia nie powiódł się"
201
237
 
202
238
  #: views/accounts/register/imap-server.hbs:247
203
239
  msgid "We couldn't connect with these settings. Check the errors below."
204
- msgstr "Nie udalo sie polaczyc z tymi ustawieniami. Sprawdz bledy ponizej."
240
+ msgstr "Nie udało się połączyć z tymi ustawieniami. Sprawdź błędy poniżej."
205
241
 
206
242
  #: views/accounts/register/imap-server.hbs:318
207
243
  msgid "Error"
208
- msgstr "Blad"
244
+ msgstr "Błąd"
209
245
 
210
246
  #: views/accounts/register/imap-server.hbs:320
211
247
  msgid "Invalid settings"
212
- msgstr "Nieprawidlowe ustawienia"
248
+ msgstr "Nieprawidłowe ustawienia"
213
249
 
214
250
  #: views/accounts/register/imap-server.hbs:326
215
251
  msgid "Couldn't connect to IMAP server"
216
- msgstr "Nie udalo sie polaczyc z serwerem IMAP"
252
+ msgstr "Nie udało się połączyć z serwerem IMAP"
217
253
 
218
254
  #: views/accounts/register/imap-server.hbs:328
219
255
  #: views/accounts/register/imap-server.hbs:337
220
256
  msgid "Server response:"
221
- msgstr "Odpowiedz serwera:"
257
+ msgstr "Odpowiedź serwera:"
222
258
 
223
259
  #: views/accounts/register/imap-server.hbs:335
224
260
  msgid "Couldn't connect to SMTP server"
225
- msgstr "Nie udalo sie polaczyc z serwerem SMTP"
261
+ msgstr "Nie udało się połączyć z serwerem SMTP"
226
262
 
227
263
  #: views/accounts/register/imap-server.hbs:395
228
264
  msgid "Request failed."
229
- msgstr "Zadanie nie powiodlo sie."
230
-
231
- #: views/accounts/register/index.hbs:2
232
- msgid "Choose your email account provider"
233
- msgstr "Wybierz dostawcę konta e-mail"
234
-
235
- #: views/accounts/register/index.hbs:15
236
- msgid "Standard IMAP"
237
- msgstr "Standardowy IMAP"
265
+ msgstr "Żądanie nie powiodło się."
238
266
 
239
- #: lib/routes-ui.js:523 lib/ui-routes/account-routes.js:60
267
+ #: lib/routes-ui.js:384 lib/ui-routes/account-routes.js:60
240
268
  msgid "Delegated"
241
269
  msgstr "Delegowany"
242
270
 
243
- #: lib/routes-ui.js:524 lib/ui-routes/account-routes.js:61
271
+ #: lib/routes-ui.js:385 lib/ui-routes/account-routes.js:61
244
272
  msgid "Using credentials from \"%s\""
245
273
  msgstr "Używanie poświadczeń z \"%s\""
246
274
 
247
- #: lib/routes-ui.js:574 lib/ui-routes/account-routes.js:111
275
+ #: lib/routes-ui.js:435 lib/ui-routes/account-routes.js:111
248
276
  msgid ""
249
277
  "Connection timed out. This usually occurs if you are behind a firewall or "
250
278
  "connecting to the wrong port."
@@ -252,98 +280,106 @@ msgstr ""
252
280
  "Przekroczono limit czasu połączenia. Zwykle dzieje się tak, gdy użytkownik "
253
281
  "znajduje się za zaporą sieciową lub łączy się z niewłaściwym portem."
254
282
 
255
- #: lib/routes-ui.js:577 lib/ui-routes/account-routes.js:114
283
+ #: lib/routes-ui.js:438 lib/ui-routes/account-routes.js:114
256
284
  msgid "The server unexpectedly closed the connection."
257
285
  msgstr "Serwer nieoczekiwanie zamknął połączenie."
258
286
 
259
- #: lib/routes-ui.js:580 lib/ui-routes/account-routes.js:117
287
+ #: lib/routes-ui.js:441 lib/ui-routes/account-routes.js:117
260
288
  msgid ""
261
289
  "The server unexpectedly closed the connection. This usually happens when "
262
290
  "attempting to connect to a TLS port without TLS enabled."
263
291
  msgstr ""
264
- "Serwer nieoczekiwanie zamknął połączenie. Zwykle dzieje się tak podczas "
265
- "próby połączenia z portem TLS bez włączonego protokołu TLS."
292
+ "Serwer nieoczekiwanie zamknął połączenie. Zwykle dzieje się tak podczas próby "
293
+ "połączenia z portem TLS bez włączonego protokołu TLS."
266
294
 
267
- #: lib/routes-ui.js:585 lib/ui-routes/account-routes.js:122
295
+ #: lib/routes-ui.js:446 lib/ui-routes/account-routes.js:122
268
296
  msgid ""
269
- "The server refused the connection. This typically occurs if the server is "
270
- "not running, is overloaded, or you are connecting to the wrong host or port."
297
+ "The server refused the connection. This typically occurs if the server is not "
298
+ "running, is overloaded, or you are connecting to the wrong host or port."
271
299
  msgstr ""
272
- "Serwer odmówił połączenia. Zwykle dzieje się tak, gdy serwer nie działa, "
273
- "jest przeciążony lub łączysz się z niewłaściwym hostem lub portem."
300
+ "Serwer odmówił połączenia. Zwykle dzieje się tak, gdy serwer nie działa, jest "
301
+ "przeciążony lub łączysz się z niewłaściwym hostem lub portem."
274
302
 
275
- #: lib/routes-ui.js:712
303
+ #: lib/routes-ui.js:573
276
304
  msgid "Invalid API key for OpenAI"
277
305
  msgstr "Nieprawidłowy klucz API dla OpenAI"
278
306
 
279
- #: lib/routes-ui.js:4754 lib/routes-ui.js:4789 lib/routes-ui.js:4904
280
- #: lib/routes-ui.js:4951 lib/routes-ui.js:5198 lib/routes-ui.js:5234
281
- #: workers/api.js:2557 lib/ui-routes/account-routes.js:549
282
- #: lib/ui-routes/account-routes.js:585 lib/ui-routes/account-routes.js:702
283
- #: lib/ui-routes/account-routes.js:749 lib/ui-routes/account-routes.js:998
284
- #: lib/ui-routes/account-routes.js:1034
307
+ #: lib/routes-ui.js:2490
308
+ msgid "Unknown"
309
+ msgstr "Nieznany"
310
+
311
+ #: lib/routes-ui.js:2499
312
+ msgid "Indefinite"
313
+ msgstr "Na czas nieokreślony"
314
+
315
+ #: lib/routes-ui.js:5133 lib/routes-ui.js:5168 lib/routes-ui.js:5283
316
+ #: lib/routes-ui.js:5330 lib/routes-ui.js:5577 lib/routes-ui.js:5613
317
+ #: workers/api.js:2279 workers/api.js:2607 lib/ui-routes/account-routes.js:554
318
+ #: lib/ui-routes/account-routes.js:590 lib/ui-routes/account-routes.js:707
319
+ #: lib/ui-routes/account-routes.js:754 lib/ui-routes/account-routes.js:1003
320
+ #: lib/ui-routes/account-routes.js:1039
285
321
  msgid "Email Account Setup"
286
322
  msgstr "Konfiguracja konta e-mail"
287
323
 
288
- #: lib/routes-ui.js:4814 lib/routes-ui.js:4847 lib/routes-ui.js:7807
289
- #: lib/ui-routes/account-routes.js:610 lib/ui-routes/account-routes.js:644
324
+ #: lib/routes-ui.js:5193 lib/routes-ui.js:5226 lib/routes-ui.js:8186
325
+ #: lib/ui-routes/account-routes.js:615 lib/ui-routes/account-routes.js:649
290
326
  msgid "Invalid request. Check your input and try again."
291
327
  msgstr "Nie udało się zweryfikować argumentów żądania."
292
328
 
293
- #: lib/routes-ui.js:5007 lib/routes-ui.js:5018
294
- #: lib/ui-routes/account-routes.js:806 lib/ui-routes/account-routes.js:817
329
+ #: lib/routes-ui.js:5386 lib/routes-ui.js:5397
330
+ #: lib/ui-routes/account-routes.js:811 lib/ui-routes/account-routes.js:822
295
331
  #: lib/ui-routes/admin-entities-routes.js:2020
296
332
  msgid "Server hostname was not found"
297
333
  msgstr "Nie znaleziono nazwy hosta serwera"
298
334
 
299
- #: lib/routes-ui.js:5010 lib/routes-ui.js:5021
300
- #: lib/ui-routes/account-routes.js:809 lib/ui-routes/account-routes.js:820
335
+ #: lib/routes-ui.js:5389 lib/routes-ui.js:5400
336
+ #: lib/ui-routes/account-routes.js:814 lib/ui-routes/account-routes.js:825
301
337
  #: lib/ui-routes/admin-entities-routes.js:2023
302
338
  msgid "Invalid username or password"
303
339
  msgstr "Nieprawidłowa nazwa użytkownika lub hasło"
304
340
 
305
- #: lib/routes-ui.js:5024 lib/ui-routes/account-routes.js:823
341
+ #: lib/routes-ui.js:5403 lib/ui-routes/account-routes.js:828
306
342
  #: lib/ui-routes/admin-entities-routes.js:2026
307
343
  msgid "Authentication credentials were not provided"
308
344
  msgstr "Nie podano danych uwierzytelniających"
309
345
 
310
- #: lib/routes-ui.js:5027 lib/ui-routes/account-routes.js:826
346
+ #: lib/routes-ui.js:5406 lib/ui-routes/account-routes.js:831
311
347
  #: lib/ui-routes/admin-entities-routes.js:2029
312
348
  msgid "OAuth2 authentication failed"
313
349
  msgstr "Uwierzytelnianie OAuth2 nie powiodło się"
314
350
 
315
- #: lib/routes-ui.js:5030 lib/routes-ui.js:5034
316
- #: lib/ui-routes/account-routes.js:829 lib/ui-routes/account-routes.js:833
351
+ #: lib/routes-ui.js:5409 lib/routes-ui.js:5413
352
+ #: lib/ui-routes/account-routes.js:834 lib/ui-routes/account-routes.js:838
317
353
  #: lib/ui-routes/admin-entities-routes.js:2032
318
354
  #: lib/ui-routes/admin-entities-routes.js:2036
319
355
  msgid "TLS protocol error"
320
356
  msgstr "Błąd protokołu TLS"
321
357
 
322
- #: lib/routes-ui.js:5038 lib/ui-routes/account-routes.js:837
358
+ #: lib/routes-ui.js:5417 lib/ui-routes/account-routes.js:842
323
359
  #: lib/ui-routes/admin-entities-routes.js:2040
324
360
  msgid "Connection timed out"
325
361
  msgstr "Przekroczono limit czasu połączenia"
326
362
 
327
- #: lib/routes-ui.js:5041 lib/ui-routes/account-routes.js:840
363
+ #: lib/routes-ui.js:5420 lib/ui-routes/account-routes.js:845
328
364
  #: lib/ui-routes/admin-entities-routes.js:2043
329
365
  msgid "Could not connect to server"
330
366
  msgstr "Nie można połączyć się z serwerem"
331
367
 
332
- #: lib/routes-ui.js:5044 lib/ui-routes/account-routes.js:843
368
+ #: lib/routes-ui.js:5423 lib/ui-routes/account-routes.js:848
333
369
  #: lib/ui-routes/admin-entities-routes.js:2046
334
370
  msgid "Unexpected server response"
335
371
  msgstr "Nieoczekiwana odpowiedź serwera"
336
372
 
337
- #: lib/routes-ui.js:7770 lib/tools.js:950
373
+ #: lib/routes-ui.js:8149 lib/tools.js:950
338
374
  msgid "Invalid input"
339
375
  msgstr "Nieprawidłowe dane wejściowe"
340
376
 
341
- #: lib/routes-ui.js:7780 lib/routes-ui.js:7898 lib/routes-ui.js:7915
342
- #: lib/routes-ui.js:7951
377
+ #: lib/routes-ui.js:8159 lib/routes-ui.js:8277 lib/routes-ui.js:8294
378
+ #: lib/routes-ui.js:8330
343
379
  msgid "Subscription Management"
344
380
  msgstr "Zarządzanie subskrypcjami"
345
381
 
346
- #: workers/api.js:7042 workers/api.js:7159
382
+ #: workers/api.js:7102 workers/api.js:7219
347
383
  msgid "Requested page not found"
348
384
  msgstr "Nie znaleziono żądanej strony"
349
385
 
@@ -364,10 +400,10 @@ msgid ""
364
400
  msgstr ""
365
401
  "Firma Microsoft wyłączyła logowanie oparte na hasłach (w tym hasłach "
366
402
  "aplikacji) dla kont e-mail w usługach Outlook.com, Hotmail.com i Microsoft "
367
- "365. Aby kontynuować, użyj przycisku \"Zaloguj się za pomocą Microsoft\", "
368
- "aby bezpiecznie połączyć swoje konto."
403
+ "365. Aby kontynuować, użyj przycisku \"Zaloguj się za pomocą Microsoft\", aby "
404
+ "bezpiecznie połączyć swoje konto."
369
405
 
370
- #: lib/ui-routes/account-routes.js:742 lib/ui-routes/account-routes.js:1027
406
+ #: lib/ui-routes/account-routes.js:747 lib/ui-routes/account-routes.js:1032
371
407
  msgid "Couldn't set up account. Try again."
372
408
  msgstr "Nie udało się założyć konta. Spróbuj ponownie."
373
409
 
@@ -1,13 +1,13 @@
1
1
  <div class="text-center">
2
- <h1 class="h4 text-gray-900 mb-4">Sign In to EmailEngine</h1>
2
+ <h1 class="h4 text-gray-900 mb-4">Sign in to EmailEngine</h1>
3
3
  </div>
4
- <form class="user" method="post" action="/admin/login" style="min-height: 14rem;">
4
+ <form class="user" method="post" action="/admin/login">
5
5
  <input type="hidden" id="crumb" name="crumb" value="{{crumb}}" />
6
6
  <input type="hidden" name="next" value="{{values.next}}" />
7
7
 
8
8
  <div class="form-group">
9
9
  <input type="text" class="form-control form-control-user {{#if errors.username}}is-invalid{{/if}}"
10
- id="exampleInputEmail" name="username" aria-describedby="emailHelp" placeholder="Enter your username"
10
+ id="loginUsername" name="username" placeholder="Username"
11
11
  value="{{values.username}}">
12
12
  {{#if errors.username}}
13
13
  <span class="invalid-feedback">{{errors.username}}</span>
@@ -15,40 +15,50 @@
15
15
  </div>
16
16
  <div class="form-group">
17
17
  <input type="password" class="form-control form-control-user {{#if errors.password}}is-invalid{{/if}}"
18
- id="exampleInputPassword" name="password" placeholder="Enter your password">
18
+ id="loginPassword" name="password" placeholder="Password">
19
19
  {{#if errors.password}}
20
20
  <span class="invalid-feedback">{{errors.password}}</span>
21
21
  {{/if}}
22
22
  </div>
23
23
 
24
- <div class="form-group">
25
- <div class="custom-control custom-checkbox small">
26
- <input type="checkbox" class="custom-control-input {{#if errors.remember}}is-invalid{{/if}}"
27
- id="customCheck" name="remember" {{#if values.remember}}checked{{/if}}>
28
- <label class="custom-control-label" for="customCheck">Keep me signed in</label>
29
- {{#if errors.remember}}
30
- <span class="invalid-feedback">{{errors.remember}}</span>
31
- {{/if}}
32
- </div>
33
- </div>
24
+ <!-- Always enable persistent sessions; no UI toggle for now -->
25
+ <input type="hidden" name="remember" value="Y" />
34
26
  <div>
35
27
  <button type="submit" class="btn btn-primary btn-user btn-block">
36
- Sign In
28
+ Sign in
37
29
  </button>
38
30
  </div>
31
+ </form>
39
32
 
40
- {{#if providers.okta}}
41
- <hr>
42
- <div>
43
- <a href="/admin/login/okta" class="btn btn-primary btn-user btn-block">
44
- Sign in with Okta SSO
45
- </a>
46
- </div>
47
- {{/if}}
33
+ {{#if passkeysAvailable}}
34
+ <div class="d-flex align-items-center my-3">
35
+ <hr class="flex-grow-1">
36
+ <span class="px-3 text-muted small">or</span>
37
+ <hr class="flex-grow-1">
38
+ </div>
39
+ <div>
40
+ <button type="button" class="btn btn-primary btn-user btn-block" id="passkey-login-btn">
41
+ <i class="fas fa-fingerprint fa-sm fa-fw mr-2"></i>Sign in with a passkey
42
+ </button>
43
+ <div id="passkey-error" class="text-danger small mt-2 text-center" style="display:none;"></div>
44
+ </div>
45
+ {{/if}}
46
+
47
+ {{#if providers.okta}}
48
+ <div class="d-flex align-items-center my-3">
49
+ <hr class="flex-grow-1">
50
+ <span class="px-3 text-muted small">or</span>
51
+ <hr class="flex-grow-1">
52
+ </div>
53
+ <div class="mt-2">
54
+ <a href="/admin/login/okta" class="btn btn-primary btn-user btn-block">
55
+ <i class="fas fa-sign-in-alt fa-sm fa-fw mr-2"></i>Sign in with Okta SSO
56
+ </a>
57
+ </div>
58
+ {{/if}}
48
59
 
49
- </form>
50
60
  <hr>
51
61
  <div class="text-center">
52
62
  <a class="small" href="https://emailengine.app/reset-password" target="_blank" rel="noopener noreferrer"
53
63
  referrerpolicy="no-referrer">Forgot your password?</a>
54
- </div>
64
+ </div>
@@ -16,9 +16,9 @@
16
16
  <h6 class="m-0 font-weight-bold text-primary">
17
17
 
18
18
  {{#if authData.enabled}}
19
- Change Your Password
19
+ Change password
20
20
  {{else}}
21
- Create Your Password
21
+ Create password
22
22
  {{/if}}
23
23
 
24
24
  </h6>
@@ -78,9 +78,9 @@
78
78
  </span>
79
79
  <span class="text">
80
80
  {{#if authData.enabled}}
81
- Update Password
81
+ Update password
82
82
  {{else}}
83
- Create Password
83
+ Create password
84
84
  {{/if}}
85
85
  </span>
86
86
  </button>
@@ -17,7 +17,7 @@
17
17
  <div class="col-4 text-right">
18
18
  {{#if authData.enabled}}
19
19
  <button type="button" class="btn btn-outline-primary" data-toggle="modal"
20
- data-target="#logoutAllModal" title="Log out all sessions" id="logout-all-btn"
20
+ data-target="#logoutAllModal" title="Sign out all sessions" id="logout-all-btn"
21
21
  data-placement="top">
22
22
  Sign out everywhere
23
23
  </button>
@@ -33,11 +33,11 @@
33
33
  {{#if authData.enabled}} ******** {{else}}Not configured{{/if}}
34
34
  </div>
35
35
  <div class="col-4 text-right">
36
- <a href="/admin/account/password" class="btn btn-outline-primary bn-xs">
36
+ <a href="/admin/account/password" class="btn btn-outline-primary btn-xs">
37
37
  {{#if authData.enabled}}
38
- Change Password
38
+ Change password
39
39
  {{else}}
40
- Create Password
40
+ Create password
41
41
  {{/if}}
42
42
  </a>
43
43
  </div>
@@ -58,13 +58,13 @@
58
58
  <div class="col-4 text-right">
59
59
  {{#if totp.enabled}}
60
60
  <button type="button" class="btn btn-outline-danger" data-toggle="modal"
61
- data-target="#disableTfaModal" title="Disable Two-factor authentication"
61
+ data-target="#disableTfaModal" title="Disable two-factor authentication"
62
62
  id="disable-tfa-btn" data-placement="top">
63
63
  Disable 2FA
64
64
  </button>
65
65
  {{else}}
66
66
  <button type="button" class="btn btn-outline-primary" data-toggle="modal"
67
- data-target="#enableTfaModal" title="Enable Two-factor authentication" id="disable-tfa-btn"
67
+ data-target="#enableTfaModal" title="Enable two-factor authentication" id="enable-tfa-btn"
68
68
  data-placement="top">
69
69
  Enable 2FA
70
70
  </button>
@@ -73,6 +73,51 @@
73
73
  </div>
74
74
  </li>
75
75
 
76
+ <li class="list-group-item">
77
+ <div class="row" style="align-items: flex-start;">
78
+ <div class="col-4" style="padding-top: 0.4rem;">Passkeys
79
+ <br><small class="text-muted">Sign in with just a passkey - no password or additional verification needed.</small>
80
+ </div>
81
+ <div class="col-4">
82
+ {{#if passkeys.length}}
83
+ <ul class="list-unstyled mb-0">
84
+ {{#each passkeys}}
85
+ <li class="mb-2">
86
+ <strong>{{this.name}}</strong><br>
87
+ <small class="text-muted">Added {{this.createdAtFormatted}}</small>
88
+ <form method="post" action="/admin/account/passkeys/delete" style="display:inline;"
89
+ class="ml-2">
90
+ <input type="hidden" name="crumb" value="{{../crumb}}" />
91
+ <input type="hidden" name="credentialId" value="{{this.id}}" />
92
+ <button type="submit" class="btn btn-sm btn-outline-danger"
93
+ title="Remove passkey"
94
+ onclick="return confirm('Remove this passkey?');">
95
+ Remove
96
+ </button>
97
+ </form>
98
+ </li>
99
+ {{/each}}
100
+ </ul>
101
+ {{else}}
102
+ <span class="badge badge-light">None registered</span>
103
+ {{/if}}
104
+ </div>
105
+ <div class="col-4 text-right">
106
+ {{#if serviceUrl}}
107
+ <button type="button" class="btn btn-outline-primary" id="register-passkey-btn">
108
+ <i class="fas fa-fingerprint fa-sm fa-fw mr-1"></i>Add passkey
109
+ </button>
110
+ {{else}}
111
+ <button type="button" class="btn btn-outline-secondary" disabled
112
+ title="Set a Service URL in configuration before registering passkeys">
113
+ Add passkey
114
+ </button>
115
+ <br><small class="text-muted">Requires Service URL</small>
116
+ {{/if}}
117
+ </div>
118
+ </div>
119
+ </li>
120
+
76
121
  <li class="list-group-item">
77
122
  <div class="row" style="justify-content: center; align-items: center;">
78
123
  <div class="col-4">Single Sign-On (SSO)</div>
@@ -87,7 +132,7 @@
87
132
  <button type="button" class="btn btn-outline-primary" data-toggle="modal"
88
133
  data-target="#ssoInstructionsModal" title="Setting up SSO" id="sso-instructions"
89
134
  data-placement="top">
90
- Setup Guide
135
+ Setup guide
91
136
  </button>
92
137
  </div>
93
138
  </div>
@@ -101,7 +146,7 @@
101
146
  <div class="modal-dialog">
102
147
  <div class="modal-content">
103
148
  <div class="modal-header">
104
- <h5 class="modal-title" id="disableTfaModalLabel">Disable Two-Factor Authentication</h5>
149
+ <h5 class="modal-title" id="disableTfaModalLabel">Disable two-factor authentication</h5>
105
150
  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
106
151
  <span aria-hidden="true">&times;</span>
107
152
  </button>
@@ -126,7 +171,7 @@
126
171
  <form method="post" action="/admin/account/tfa/enable">
127
172
  <div class="modal-content">
128
173
  <div class="modal-header">
129
- <h5 class="modal-title" id="enableTfaModalLabel">Enable Two-Factor Authentication</h5>
174
+ <h5 class="modal-title" id="enableTfaModalLabel">Enable two-factor authentication</h5>
130
175
  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
131
176
  <span aria-hidden="true">&times;</span>
132
177
  </button>
@@ -164,7 +209,7 @@
164
209
  <form method="post" action="/admin/account/logout-all">
165
210
  <div class="modal-content">
166
211
  <div class="modal-header">
167
- <h5 class="modal-title" id="logoutAllModalLabel">Sign Out from All Devices</h5>
212
+ <h5 class="modal-title" id="logoutAllModalLabel">Sign out from all devices</h5>
168
213
  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
169
214
  <span aria-hidden="true">&times;</span>
170
215
  </button>
@@ -179,7 +224,7 @@
179
224
  <input type="hidden" name="crumb" value="{{crumb}}" />
180
225
  <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
181
226
  <button type="submit" class="btn btn-primary"><i
182
- class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i> Sign Out Everywhere</button>
227
+ class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i> Sign out everywhere</button>
183
228
  </div>
184
229
  </div>
185
230
  </form>
@@ -266,4 +311,48 @@
266
311
  </div>
267
312
 
268
313
  </div>
269
- </div>
314
+ </div>
315
+
316
+ <div class="modal fade" id="registerPasskeyModal" tabindex="-1" aria-labelledby="registerPasskeyModalLabel"
317
+ aria-hidden="true">
318
+ <div class="modal-dialog">
319
+ <div class="modal-content">
320
+ <div class="modal-header">
321
+ <h5 class="modal-title" id="registerPasskeyModalLabel">Register a new passkey</h5>
322
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
323
+ <span aria-hidden="true">&times;</span>
324
+ </button>
325
+ </div>
326
+ <div class="modal-body">
327
+ {{#if authData.enabled}}
328
+ <div class="form-group">
329
+ <label for="passkey-current-password">Current password</label>
330
+ <input type="password" class="form-control" id="passkey-current-password"
331
+ placeholder="Enter your current password" maxlength="256" autocomplete="current-password" />
332
+ <small class="form-text text-muted">Confirm your identity before registering a new passkey.</small>
333
+ </div>
334
+ {{/if}}
335
+ <div class="form-group">
336
+ <label for="passkey-name">Passkey name</label>
337
+ <input type="text" class="form-control" id="passkey-name" placeholder="e.g., MacBook Touch ID"
338
+ maxlength="100" />
339
+ <small class="form-text text-muted">A friendly name to help you identify this passkey.</small>
340
+ </div>
341
+ <div id="passkey-register-error" class="text-danger small" style="display:none;"></div>
342
+ <div id="passkey-register-success" class="text-success small" style="display:none;">
343
+ Passkey registered successfully.
344
+ </div>
345
+ </div>
346
+ <div class="modal-footer">
347
+ <input type="hidden" id="security-crumb" value="{{crumb}}" />
348
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
349
+ <button type="button" class="btn btn-primary" id="passkey-register-confirm-btn">
350
+ Register passkey
351
+ </button>
352
+ </div>
353
+ </div>
354
+ </div>
355
+ </div>
356
+
357
+ <script src="/static/vendor/simplewebauthn/browser.min.js"></script>
358
+ <script src="/static/js/passkey-register.js"></script>