@things-factory/auth-base 10.1.183 → 10.1.192

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/auth-base",
3
- "version": "10.1.183",
3
+ "version": "10.1.192",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -35,9 +35,9 @@
35
35
  "@reduxjs/toolkit": "^2.2.5",
36
36
  "@simplewebauthn/browser": "^13.0.0",
37
37
  "@simplewebauthn/server": "^13.0.0",
38
- "@things-factory/email-base": "^10.1.179",
38
+ "@things-factory/email-base": "^10.1.188",
39
39
  "@things-factory/env": "^10.1.149",
40
- "@things-factory/shell": "^10.1.179",
40
+ "@things-factory/shell": "^10.1.188",
41
41
  "@things-factory/utils": "^10.1.179",
42
42
  "@types/webappsec-credential-management": "^0.6.9",
43
43
  "jsonwebtoken": "^9.0.0",
@@ -49,5 +49,5 @@
49
49
  "passport-jwt": "^4.0.0",
50
50
  "passport-local": "^1.0.0"
51
51
  },
52
- "gitHead": "c36684e0db8353f5f4523ed55111df3154bae19e"
52
+ "gitHead": "c44fe3ae4646dc4ffad7947785322acdb84c7374"
53
53
  }
@@ -0,0 +1,53 @@
1
+ import { loadCompiled } from './compiled'
2
+
3
+ const { authenticate401Middleware } = loadCompiled('middlewares/authenticate-401-middleware')
4
+ const { AuthError } = loadCompiled('errors/auth-error')
5
+ const CODES = loadCompiled('constants/error-code')
6
+
7
+ /**
8
+ * One page, `auth-activate`, answers a locked account and one not yet confirmed. It said "your
9
+ * account is not activated" to both: the reason was in `message`, and the page never read it. A
10
+ * locked person was not told why, nor that the unlock mail had already gone.
11
+ *
12
+ * The page tells the two apart by `errorCode`, so that is what this holds - for both cases.
13
+ */
14
+
15
+ async function pageFor(errorCode: string) {
16
+ let rendered: any
17
+
18
+ const context: any = {
19
+ t: (key: string) => key,
20
+ method: 'GET',
21
+ header: { accept: 'text/html' },
22
+ path: '/',
23
+ originalUrl: '/',
24
+ query: {},
25
+ request: { body: {} },
26
+ render: async (view: string, options: any) => {
27
+ rendered = { view, ...options }
28
+ }
29
+ }
30
+
31
+ await authenticate401Middleware(context, async () => {
32
+ throw new AuthError({ errorCode, detail: { username: 'kim', email: 'kim@example.com' } })
33
+ })
34
+
35
+ return rendered
36
+ }
37
+
38
+ describe.each([CODES.USER_LOCKED, CODES.USER_NOT_ACTIVATED])('%s', errorCode => {
39
+ it('goes to the activate page with its code', async () => {
40
+ const page = await pageFor(errorCode)
41
+
42
+ expect(page.pageElement).toBe('auth-activate')
43
+ expect(page.data.errorCode).toBe(errorCode)
44
+ })
45
+ })
46
+
47
+ it('the page reads the locked code by the spelling the server sends', () => {
48
+ const { readFileSync } = require('fs')
49
+ const { join } = require('path')
50
+ const page = readFileSync(join(__dirname, '..', '..', 'auth-ui', 'client', 'entries', 'auth', 'activate.ts'), 'utf-8')
51
+
52
+ expect(page).toContain(`this.data?.errorCode === '${CODES.USER_LOCKED}'`)
53
+ })
@@ -0,0 +1,139 @@
1
+ const mockSentMail: any[] = []
2
+ let mockMailFails = false
3
+
4
+ jest.mock('@things-factory/email-base', () => ({
5
+ sendEmail: async (mail: any) => {
6
+ if (mockMailFails) throw new Error('smtp down')
7
+ mockSentMail.push(mail)
8
+ }
9
+ }))
10
+
11
+ import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
12
+ import { loadCompiled } from './compiled'
13
+
14
+ const { User } = loadCompiled('service/user/user')
15
+ const { authPublicProcessRouter } = loadCompiled('router/auth-public-process-router')
16
+
17
+ /**
18
+ * What the two "mail me" posts answer - forgot-password and resend-verification-email.
19
+ *
20
+ * Both set nothing when there was no account to mail, or when the mail did not go, so koa answered
21
+ * a bare 404 and the page showed "Not Found" - the same words for "no such address" and "the mail
22
+ * server is down". Now they give one answer whether or not the address has an account (another
23
+ * would tell anyone which addresses are registered), and fail only when a mail was due and did not
24
+ * go.
25
+ *
26
+ * The handler is taken from the router's own table, so the path is the one the page posts to.
27
+ */
28
+
29
+ function handler(path: string) {
30
+ const layer = authPublicProcessRouter.stack.find((layer: any) => layer.path === path && layer.methods.includes('POST'))
31
+
32
+ return layer.stack[layer.stack.length - 1]
33
+ }
34
+
35
+ async function post(path: string, body: any) {
36
+ const context: any = {
37
+ t: (key: string) => key,
38
+ lng: 'en-US',
39
+ headers: {},
40
+ header: {},
41
+ request: { protocol: 'https', host: 'acme.example.com', body }
42
+ }
43
+
44
+ await handler(path)(context, async () => {})
45
+
46
+ return { status: context.status, body: context.body }
47
+ }
48
+
49
+ let domain: any
50
+
51
+ async function person(email: string, status: string) {
52
+ const salt = User.generateSalt()
53
+
54
+ return await getRepository(User).save({
55
+ username: email.split('@')[0],
56
+ email,
57
+ name: 'Kim',
58
+ salt,
59
+ password: User.encode('Passw0rd!', salt),
60
+ status,
61
+ domains: [domain]
62
+ })
63
+ }
64
+
65
+ beforeAll(async () => {
66
+ await openAuthDatabase()
67
+ })
68
+
69
+ beforeEach(async () => {
70
+ await resetAuthDatabase()
71
+ domain = await getRepository(Domain).save({ name: 'plant-a', subdomain: 'plant-a' })
72
+ mockSentMail.length = 0
73
+ mockMailFails = false
74
+ })
75
+
76
+ afterAll(async () => {
77
+ await closeAuthDatabase()
78
+ })
79
+
80
+ describe('forgot-password', () => {
81
+ const PATH = '/auth/forgot-password'
82
+
83
+ it('mails a registered address', async () => {
84
+ await person('kim@example.com', 'activated')
85
+
86
+ expect(await post(PATH, { email: 'kim@example.com' })).toEqual({ status: 200, body: 'text.password reset email sent' })
87
+ expect(mockSentMail.map(mail => mail.receiver)).toEqual(['kim@example.com'])
88
+ })
89
+
90
+ it('answers an unknown address the same way, and mails nobody', async () => {
91
+ await person('kim@example.com', 'activated')
92
+
93
+ expect(await post(PATH, { email: 'nobody@example.com' })).toEqual({ status: 200, body: 'text.password reset email sent' })
94
+ expect(mockSentMail).toEqual([])
95
+ })
96
+
97
+ it('says so when the mail does not go', async () => {
98
+ await person('kim@example.com', 'activated')
99
+ mockMailFails = true
100
+
101
+ expect(await post(PATH, { email: 'kim@example.com' })).toEqual({ status: 503, body: 'error.mail could not be sent' })
102
+ })
103
+ })
104
+
105
+ describe('resend-verification-email', () => {
106
+ const PATH = '/auth/resend-verification-email'
107
+
108
+ it('mails an account waiting for confirmation', async () => {
109
+ await person('kim@example.com', 'inactive')
110
+
111
+ expect(await post(PATH, { email: 'kim@example.com' })).toEqual({ status: 200, body: 'text.verification email sent' })
112
+ expect(mockSentMail.map(mail => mail.receiver)).toEqual(['kim@example.com'])
113
+ })
114
+
115
+ it.each([
116
+ ['an unknown address', 'nobody@example.com'],
117
+ ['an account already confirmed', 'lee@example.com']
118
+ ])('answers %s the same way, and mails nobody', async (_, email) => {
119
+ await person('lee@example.com', 'activated')
120
+
121
+ expect(await post(PATH, { email })).toEqual({ status: 200, body: 'text.verification email sent' })
122
+ expect(mockSentMail).toEqual([])
123
+ })
124
+
125
+ it('mails nobody for a post without an address', async () => {
126
+ /* ⚠ TypeORM drops an undefined condition - this matched the first account and mailed it. */
127
+ await person('kim@example.com', 'inactive')
128
+
129
+ expect(await post(PATH, {})).toEqual({ status: 200, body: 'text.verification email sent' })
130
+ expect(mockSentMail).toEqual([])
131
+ })
132
+
133
+ it('says so when the mail does not go', async () => {
134
+ await person('kim@example.com', 'inactive')
135
+ mockMailFails = true
136
+
137
+ expect(await post(PATH, { email: 'kim@example.com' })).toEqual({ status: 503, body: 'error.mail could not be sent' })
138
+ })
139
+ })
@@ -7,6 +7,7 @@
7
7
  "error.email already exists": "email already used by another user",
8
8
  "error.failed to find x": "failed to find {x}",
9
9
  "error.invalid username": "invalid username: {username}",
10
+ "error.mail could not be sent": "the email could not be sent. try again in a moment.",
10
11
  "error.ownership required": "unauthorized! domain or system ownership required",
11
12
  "error.password should be supported": "initial password or default password should be supported",
12
13
  "error.password should match the rule": "password should match following rule. ${rule}",
@@ -211,7 +212,7 @@
211
212
  "text.invalid verification token": "invalid verification token",
212
213
  "text.invitation email sent": "invitation email sent",
213
214
  "text.password changed successfully": "password changed successfully",
214
- "text.password reset email sent": "password reset email sent",
215
+ "text.password reset email sent": "if an account uses this address, a password reset email is on its way.",
215
216
  "text.password reset succeed": "password reset succeed",
216
217
  "text.pattern_atleast_1_digit": "at least 1 digit character",
217
218
  "text.pattern_atleast_1_lowercase": "at least 1 lowercase character",
@@ -226,7 +227,7 @@
226
227
  "text.user credential registered successfully": "device registration has been successfully completed. You can now use biometric authentication.",
227
228
  "text.user inactivated successfully": "user inactivated successfully",
228
229
  "text.user registered successfully": "user registered successfully. find your email to activate account",
229
- "text.verification email sent": "verification email sent",
230
+ "text.verification email sent": "if an account at this address is waiting for confirmation, a verification email is on its way.",
230
231
  "privilege.description": "{name} {category}",
231
232
  "error.access-request-names-nothing": "an access request names a category and a privilege.",
232
233
  "error.access-request-unknown-privilege": "there is no privilege {privilege}.",
@@ -7,6 +7,7 @@
7
7
  "error.email already exists": "el correo electrónico ya lo usa otro usuario",
8
8
  "error.failed to find x": "no se encontró {x}",
9
9
  "error.invalid username": "nombre de usuario no válido: {username}",
10
+ "error.mail could not be sent": "no se pudo enviar el correo. vuelva a intentarlo en un momento.",
10
11
  "error.ownership required": "¡no autorizado! se requiere la propiedad del dominio o del sistema",
11
12
  "error.password should be supported": "se debe proporcionar una contraseña inicial o una contraseña predeterminada",
12
13
  "error.password should match the rule": "la contraseña debe cumplir la siguiente regla. ${rule}",
@@ -211,7 +212,7 @@
211
212
  "text.invalid verification token": "token de verificación no válido",
212
213
  "text.invitation email sent": "correo de invitación enviado",
213
214
  "text.password changed successfully": "contraseña cambiada correctamente",
214
- "text.password reset email sent": "correo de restablecimiento de contraseña enviado",
215
+ "text.password reset email sent": "si hay una cuenta con esta dirección, le enviamos un correo de restablecimiento de contraseña.",
215
216
  "text.password reset succeed": "contraseña restablecida correctamente",
216
217
  "text.pattern_atleast_1_digit": "al menos 1 dígito",
217
218
  "text.pattern_atleast_1_lowercase": "al menos 1 letra minúscula",
@@ -226,7 +227,7 @@
226
227
  "text.user credential registered successfully": "el registro del dispositivo se ha completado correctamente. Ya puede usar la autenticación biométrica.",
227
228
  "text.user inactivated successfully": "usuario desactivado correctamente",
228
229
  "text.user registered successfully": "usuario registrado correctamente. busque el correo electrónico para activar la cuenta",
229
- "text.verification email sent": "correo de verificación enviado",
230
+ "text.verification email sent": "si una cuenta con esta dirección espera confirmación, le enviamos un correo de verificación.",
230
231
  "privilege.description": "{name} de {category}",
231
232
  "error.access-request-names-nothing": "una solicitud de acceso indica una categoría y un privilegio.",
232
233
  "error.access-request-unknown-privilege": "no existe el privilegio {privilege}.",
@@ -7,6 +7,7 @@
7
7
  "error.email already exists": "メールはすでに他のユーザーによって使用されています.",
8
8
  "error.failed to find x": "{x}が見つかりません.",
9
9
  "error.invalid username": "無効なユーザー名: {username}",
10
+ "error.mail could not be sent": "メールを送れませんでした. しばらくしてからもう一度お試しください.",
10
11
  "error.ownership required": "権限がありません。$t(term.domain)またはシステムの所有者権限が必要です。",
11
12
  "error.password should be supported": "初期パスワードまたはデフォルトパスワードがサポートされるべきです",
12
13
  "error.password should match the rule": "パスワードは次の規則を守らなければなりません. {rule}",
@@ -51,7 +52,7 @@
51
52
  "text.invalid verification token": "確認トークンが無効です.",
52
53
  "text.invitation email sent": "招待メールを送りました.",
53
54
  "text.password changed successfully": "パスワードが正常に変更されました.",
54
- "text.password reset email sent": "パスワード リセットメールを送りました.",
55
+ "text.password reset email sent": "登録済みのアドレスであれば、パスワード リセットメールを送りました.",
55
56
  "text.password reset succeed": "パスワードが正常にリセットされました.",
56
57
  "text.pattern_atleast_1_digit": "少なくとも1つの数字",
57
58
  "text.pattern_atleast_1_lowercase": "少なくとも1つの小文字",
@@ -66,7 +67,7 @@
66
67
  "text.user credential registered successfully": "デバイスの登録が正常に完了しました。今後は生体認証を使用できます。",
67
68
  "text.user inactivated successfully": "ユーザーが正常に非アクティブ化されました.",
68
69
  "text.user registered successfully": "ユーザーが正常に登録されました. 確認メールでアカウントを有効にしてください.",
69
- "text.verification email sent": "確認メールを送りました.",
70
+ "text.verification email sent": "確認待ちのアカウントのアドレスであれば、確認メールを送りました.",
70
71
  "privilege.allows.accounting.mutation": "伝票を起票・修正・締めできます。",
71
72
  "privilege.allows.accounting.query": "伝票と勘定残高を閲覧できます。",
72
73
  "privilege.allows.activity-instance.mutation": "業務を開始・引き継ぎ・完了・中断できます。",
@@ -7,6 +7,7 @@
7
7
  "error.email already exists": "이메일이 이미 사용되고 있습니다.",
8
8
  "error.failed to find x": "{x}을(를) 찾을 수 없습니다.",
9
9
  "error.invalid username": "유효하지 않은 사용자 이름: {username}",
10
+ "error.mail could not be sent": "메일을 보내지 못했습니다. 잠시 후 다시 시도하십시오.",
10
11
  "error.ownership required": "권한이 없습니다. $t(term.domain) 또는 시스템 소유자 권한이 필요합니다.",
11
12
  "error.password should be supported": "초기 비밀번호나 디폴트 비밀번호가 제공되어야 합니다.",
12
13
  "error.password should match the rule": "비밀번호는 다음 규칙을 지켜야 합니다. {rule}",
@@ -211,7 +212,7 @@
211
212
  "text.invalid verification token": "확인 토큰이 유효하지 않습니다.",
212
213
  "text.invitation email sent": "초대 이메일을 보냈습니다.",
213
214
  "text.password changed successfully": "비밀번호가 성공적으로 변경되었습니다.",
214
- "text.password reset email sent": "비밀번호 재설정 이메일을 보냈습니다.",
215
+ "text.password reset email sent": "가입된 주소라면 비밀번호 재설정 메일을 보냈습니다.",
215
216
  "text.password reset succeed": "비밀번호 성공적으로 재설정되었습니다.",
216
217
  "text.pattern_atleast_1_digit": "최소 1개 숫자",
217
218
  "text.pattern_atleast_1_lowercase": "최소 1개 소문자",
@@ -226,7 +227,7 @@
226
227
  "text.user credential registered successfully": "기기 등록이 성공적으로 완료되었습니다. 이제 바이오메트릭 인증을 사용할 수 있습니다.",
227
228
  "text.user inactivated successfully": "사용자가 성공적으로 비활성화되었습니다.",
228
229
  "text.user registered successfully": "사용자가 성공적으로 등록되었습니다. 확인 이메일을 통해서 계정을 활성화하시기 바랍니다.",
229
- "text.verification email sent": "확인 이메일을 보냈습니다.",
230
+ "text.verification email sent": "확인을 기다리는 계정의 주소라면 확인 메일을 보냈습니다.",
230
231
  "privilege.description": "{category} {name}",
231
232
  "error.access-request-names-nothing": "권한 요청에는 범주와 권한이 있어야 합니다.",
232
233
  "error.access-request-unknown-privilege": "{privilege} 권한이 없습니다.",
@@ -7,6 +7,7 @@
7
7
  "error.email already exists": "Emel telah digunakan oleh pengguna lain",
8
8
  "error.failed to find x": "Gagal mencari {x}",
9
9
  "error.invalid username": "Nama pengguna tidak sah: {username}",
10
+ "error.mail could not be sent": "E-mel tidak dapat dihantar. Cuba lagi sebentar lagi.",
10
11
  "error.ownership required": "tidak dibenarkan! kebenaran pemilik domain atau sistem diperlukan",
11
12
  "error.password should be supported": "kata laluan awal atau kata laluan lalai harus disokong",
12
13
  "error.password should match the rule": "Kata laluan harus mematuhi peraturan berikut. ${rule}",
@@ -51,7 +52,7 @@
51
52
  "text.invalid verification token": "Token pengesahan tidak sah",
52
53
  "text.invitation email sent": "E-mel jemputan telah dihantar",
53
54
  "text.password changed successfully": "Kata laluan berjaya diubah",
54
- "text.password reset email sent": "E-mel reset kata laluan telah dihantar",
55
+ "text.password reset email sent": "Jika alamat ini didaftarkan, e-mel reset kata laluan telah dihantar",
55
56
  "text.password reset succeed": "Reset kata laluan berjaya",
56
57
  "text.pattern_atleast_1_digit": "Sekurang-kurangnya 1 aksara digit",
57
58
  "text.pattern_atleast_1_lowercase": "Sekurang-kurangnya 1 aksara kecil",
@@ -66,7 +67,7 @@
66
67
  "text.user credential registered successfully": "pendaftaran peranti berjaya diselesaikan. Kini anda boleh menggunakan pengesahan biometrik.",
67
68
  "text.user inactivated successfully": "Pengguna tidak aktif dengan berjaya",
68
69
  "text.user registered successfully": "Pengguna berjaya didaftarkan. Cari e-mel anda untuk mengaktifkan akaun",
69
- "text.verification email sent": "E-mel pengesahan telah dihantar",
70
+ "text.verification email sent": "Jika akaun dengan alamat ini menunggu pengesahan, e-mel pengesahan telah dihantar",
70
71
  "privilege.allows.accounting.mutation": "boleh mengepos, membetulkan dan menutup baucar.",
71
72
  "privilege.allows.accounting.query": "boleh melihat baucar dan baki akaun.",
72
73
  "privilege.allows.activity-instance.mutation": "boleh memulakan, menyerahkan, dan menamatkan atau membatalkan kerja.",
@@ -7,6 +7,7 @@
7
7
  "error.email already exists": "电子邮件已被其他用户使用!",
8
8
  "error.failed to find x": "查询{x}失败!",
9
9
  "error.invalid username": "无效的用户名: {username}",
10
+ "error.mail could not be sent": "邮件未能发送。请稍后再试。",
10
11
  "error.ownership required": "没有权限。需要$t(term.domain)或系统所有者权限。",
11
12
  "error.password should be supported": "应支持初始密码或默认密码",
12
13
  "error.password should match the rule": "密码应符合以下规则。${rule}",
@@ -51,7 +52,7 @@
51
52
  "text.invalid verification token": "令牌无效。",
52
53
  "text.invitation email sent": "成功发送邀请电子邮件。",
53
54
  "text.password changed successfully": "密码更改成功。",
54
- "text.password reset email sent": "已发送密码重置邮件。",
55
+ "text.password reset email sent": "如果该地址已注册,我们已发送密码重置邮件。",
55
56
  "text.password reset succeed": "密码重置成功。",
56
57
  "text.pattern_atleast_1_digit": "至少1位数字字符",
57
58
  "text.pattern_atleast_1_lowercase": "至少1个小写字母",
@@ -66,7 +67,7 @@
66
67
  "text.user credential registered successfully": "设备注册已成功完成。现在可以使用生物识别认证。",
67
68
  "text.user inactivated successfully": "用户已成功停用",
68
69
  "text.user registered successfully": "用户注册成功。 请查看电子邮件以激活帐户。",
69
- "text.verification email sent": "验证邮件已发送",
70
+ "text.verification email sent": "如果该地址的账号正在等待验证,我们已发送验证邮件。",
70
71
  "privilege.allows.accounting.mutation": "可以过账、更正和结账凭证。",
71
72
  "privilege.allows.accounting.query": "可以查看凭证和科目余额。",
72
73
  "privilege.allows.activity-instance.mutation": "可以开始、移交、完成或中止工作。",