@things-factory/auth-base 10.1.232 → 10.1.241

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.232",
3
+ "version": "10.1.241",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -35,10 +35,10 @@
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.228",
38
+ "@things-factory/email-base": "^10.1.234",
39
39
  "@things-factory/env": "^10.1.149",
40
- "@things-factory/shell": "^10.1.228",
41
- "@things-factory/task-lease": "^10.1.228",
40
+ "@things-factory/shell": "^10.1.234",
41
+ "@things-factory/task-lease": "^10.1.234",
42
42
  "@things-factory/utils": "^10.1.179",
43
43
  "@types/webappsec-credential-management": "^0.6.9",
44
44
  "jsonwebtoken": "^9.0.0",
@@ -50,5 +50,5 @@
50
50
  "passport-jwt": "^4.0.0",
51
51
  "passport-local": "^1.0.0"
52
52
  },
53
- "gitHead": "9307e1db833f13018e3f0ca9b1050f947cbefd68"
53
+ "gitHead": "d7fe2e1a1eb7c02cb21b897da26d385df014f908"
54
54
  }
@@ -259,6 +259,15 @@ describe("the caller's own passkeys (decision 6)", () => {
259
259
  expect((await credentialOf({ id: credential.credentialId } as any)).name).toBe('Phone')
260
260
  })
261
261
 
262
+ it('★ a name over the bound is refused with the bound, not cut', async () => {
263
+ const credential = await credentialOf(await registered(kim))
264
+ await mutation.updateMyWebAuthCredential(credential.id, 'Phone', context(kim))
265
+
266
+ await expect(mutation.updateMyWebAuthCredential(credential.id, '가'.repeat(65), context(kim))).rejects.toMatchObject({ code: 'passkey-name-too-long', params: { max: 64, length: 65 } })
267
+ expect((await credentialOf({ id: credential.credentialId } as any)).name).toBe('Phone')
268
+ expect((await mutation.updateMyWebAuthCredential(credential.id, '가'.repeat(64), context(kim))).name).toBe('가'.repeat(64))
269
+ })
270
+
262
271
  it('removes the caller’s own - it signs nobody in afterwards and the removal is audited', async () => {
263
272
  const authenticator = await registered(kim)
264
273
  const credential = await credentialOf(authenticator)
@@ -0,0 +1,24 @@
1
+ /*
2
+ * Every refusal this package throws has its Korean sentence - a missing one is read in the server's English fallback
3
+ * (inline help audit 2026-10-30: passkey-not-found, and the check-in's `error.domain not specified` printed as its key).
4
+ */
5
+ import { readFileSync, readdirSync } from 'fs'
6
+ import { join } from 'path'
7
+
8
+ const ROOT = join(__dirname, '..')
9
+ const ko: Record<string, string> = JSON.parse(readFileSync(join(ROOT, 'translations', 'ko.json'), 'utf8'))
10
+
11
+ const sources = (dir: string): string[] =>
12
+ readdirSync(dir, { withFileTypes: true }).flatMap(entry => (entry.isDirectory() ? sources(join(dir, entry.name)) : /\.ts$/.test(entry.name) && !/\.test\.ts$/.test(entry.name) ? [join(dir, entry.name)] : []))
13
+ const text = sources(join(ROOT, 'server')).map(file => readFileSync(file, 'utf8')).join('\n')
14
+
15
+ test('★ every Refusal code has error.<code> in Korean', () => {
16
+ const codes = [...new Set([...text.matchAll(/new Refusal\(\s*'([a-z0-9-]+)'/g)].map(match => match[1]))]
17
+ expect(codes.length).toBeGreaterThanOrEqual(10)
18
+ expect(codes.filter(code => !(`error.${code}` in ko))).toEqual([])
19
+ })
20
+
21
+ test('★ the check-in refusal has its Korean sentence', () => {
22
+ expect(text).toMatch(/t\('error\.domain not specified'/)
23
+ expect(ko['error.domain not specified']).toBeTruthy()
24
+ })
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Signing up refuses a username already taken, and a username that is another account's email.
3
+ *
4
+ * The email lookup was assigned to `user` instead of `duplicated`, so an email clash was never refused and a second
5
+ * account with the same address was made. And a username clash on the profile was named an email clash: the two
6
+ * codes shared one value.
7
+ */
8
+
9
+ import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
10
+ import { loadCompiled } from './compiled'
11
+
12
+ const { User } = loadCompiled('service/user/user')
13
+ const { signup } = loadCompiled('controllers/signup')
14
+ const ERROR_CODES = loadCompiled('constants/error-code')
15
+
16
+ const PASSWORD = 'N3w!Passw0rd'
17
+
18
+ let here: any
19
+
20
+ function signUp(username: string) {
21
+ return signup({ name: 'Lee', username, email: username, password: PASSWORD, domain: here, context: { lng: 'en' } })
22
+ }
23
+
24
+ beforeAll(async () => {
25
+ await openAuthDatabase()
26
+ })
27
+
28
+ afterAll(async () => {
29
+ await closeAuthDatabase()
30
+ })
31
+
32
+ beforeEach(async () => {
33
+ await resetAuthDatabase()
34
+ here = await getRepository(Domain).save({ name: 'Plant A', subdomain: 'plant-a' })
35
+ const salt = User.generateSalt()
36
+ await getRepository(User).save({
37
+ username: 'kim',
38
+ email: 'kim@example.com',
39
+ name: 'Kim',
40
+ salt,
41
+ password: User.encode(PASSWORD, salt),
42
+ status: 'activated',
43
+ domains: [here]
44
+ })
45
+ })
46
+
47
+ describe('signing up', () => {
48
+ it('★ refuses a username that is another account email, in any case', async () => {
49
+ await expect(signUp('KIM@example.com')).rejects.toMatchObject({ errorCode: ERROR_CODES.USER_DUPLICATED })
50
+ expect(await getRepository(User).countBy({ name: 'Lee' })).toBe(0)
51
+ })
52
+
53
+ it('refuses a username already taken', async () => {
54
+ await expect(signUp('kim')).rejects.toMatchObject({ errorCode: ERROR_CODES.USER_DUPLICATED })
55
+ })
56
+
57
+ it('makes an account for a username nobody has', async () => {
58
+ await signUp('lee@example.com')
59
+ expect(await getRepository(User).countBy({ username: 'lee@example.com' })).toBe(1)
60
+ })
61
+ })
62
+
63
+ describe('a username clash', () => {
64
+ it('★ is named a username clash, not an email clash', () => {
65
+ expect(ERROR_CODES.USERNAME_ALREADY_EXISTS).toBe('username already exists')
66
+ expect(ERROR_CODES.USERNAME_ALREADY_EXISTS).not.toBe(ERROR_CODES.EMAIL_ALREADY_EXISTS)
67
+ })
68
+ })
@@ -222,7 +222,7 @@
222
222
  "text.pattern_atleast_1_special": "at least 1 special character(!@#$%^&*())",
223
223
  "text.pattern_atleast_1_uppercase": "at least 1 uppercase character",
224
224
  "text.pattern_minimum_charaters": "minimum {length} charaters",
225
- "text.pattern_not_allowed": "not allowed repeated charater",
225
+ "text.pattern_not_allowed": "no character 3 times in a row",
226
226
  "text.profile changed successfully": "profile changed successfully",
227
227
  "text.result": "result",
228
228
  "text.signout successfully": "signout successfully",
@@ -268,5 +268,8 @@
268
268
  "task.auth-base.audit-reading.name": "Reading the audit log",
269
269
  "task.auth-base.audit-reading.description": "Reads everyone's sign-ins, privilege use and refusals, and role changes in the domain.",
270
270
  "error.access-request-task-mismatch": "the work you picked does not include the refused privilege.",
271
- "error.identity confirmation required": "confirm it is you to see this"
271
+ "error.identity confirmation required": "confirm it is you to see this",
272
+ "error.passkey-not-found": "There is no passkey of yours with this id. Reload the list.",
273
+ "error.passkey-name-too-long": "A passkey name takes up to {max} characters; this one has {length}.",
274
+ "error.domain not specified": "This account cannot enter the $t(term.domain) {subdomain}."
272
275
  }
@@ -222,7 +222,7 @@
222
222
  "text.pattern_atleast_1_special": "al menos 1 carácter especial(!@#$%^&*())",
223
223
  "text.pattern_atleast_1_uppercase": "al menos 1 letra mayúscula",
224
224
  "text.pattern_minimum_charaters": "mínimo {length} caracteres",
225
- "text.pattern_not_allowed": "no se permiten caracteres repetidos",
225
+ "text.pattern_not_allowed": "no se permite el mismo carácter 3 veces seguidas",
226
226
  "text.profile changed successfully": "perfil cambiado correctamente",
227
227
  "text.result": "resultado",
228
228
  "text.signout successfully": "sesión cerrada correctamente",
@@ -268,5 +268,8 @@
268
268
  "task.auth-base.audit-reading.name": "Consultar el registro de auditoría",
269
269
  "task.auth-base.audit-reading.description": "Lee los inicios de sesión, el uso y rechazo de privilegios y los cambios de rol de todos en el dominio.",
270
270
  "error.access-request-task-mismatch": "la tarea elegida no incluye el privilegio rechazado.",
271
- "error.identity confirmation required": "confirme que es usted para ver esto"
271
+ "error.identity confirmation required": "confirme que es usted para ver esto",
272
+ "error.passkey-not-found": "No tiene ninguna llave de acceso con este id. Vuelva a cargar la lista.",
273
+ "error.passkey-name-too-long": "El nombre de una llave de acceso admite hasta {max} caracteres; este tiene {length}.",
274
+ "error.domain not specified": "Esta cuenta no puede entrar en el $t(term.domain) {subdomain}."
272
275
  }
@@ -62,7 +62,7 @@
62
62
  "text.pattern_atleast_1_special": "少なくとも1つの特殊文字(!@#$%^&*())",
63
63
  "text.pattern_atleast_1_uppercase": "少なくとも1つの大文字",
64
64
  "text.pattern_minimum_charaters": "少なくとも{length}文字",
65
- "text.pattern_not_allowed": "文字の繰り返し使用不可",
65
+ "text.pattern_not_allowed": "同じ文字を3回連続で使用不可",
66
66
  "text.profile changed successfully": "プロフィールが正常に変更されました.",
67
67
  "text.result": "結果",
68
68
  "text.signout successfully": "ログアウトに成功しました.",
@@ -268,5 +268,8 @@
268
268
  "task.auth-base.audit-reading.name": "監査ログの閲覧",
269
269
  "task.auth-base.audit-reading.description": "全ユーザーのログイン、権限の使用と拒否、ロール変更の記録を読む。",
270
270
  "error.access-request-task-mismatch": "選んだ業務に拒否された権限が含まれていません。",
271
- "error.identity confirmation required": "本人確認が必要です。"
271
+ "error.identity confirmation required": "本人確認が必要です。",
272
+ "error.passkey-not-found": "あなたのパスキーにこのパスキーはありません。一覧を再読み込みしてください。",
273
+ "error.passkey-name-too-long": "パスキー名は{max}文字までです。今は{length}文字です。",
274
+ "error.domain not specified": "このアカウントで入れる$t(term.domain)ではありません: {subdomain}"
272
275
  }
@@ -222,7 +222,7 @@
222
222
  "text.pattern_atleast_1_special": "최소 1개 특수문자(!@#$%^&*())",
223
223
  "text.pattern_atleast_1_uppercase": "최소 1개 대문자",
224
224
  "text.pattern_minimum_charaters": "최소 {length} 글자",
225
- "text.pattern_not_allowed": "문자 반복 사용 불가",
225
+ "text.pattern_not_allowed": "같은 문자 3번 연속 사용 불가",
226
226
  "text.profile changed successfully": "프로필이 성공적으로 변경되었습니다.",
227
227
  "text.result": "결과",
228
228
  "text.signout successfully": "성공적으로 로그아웃 하였습니다.",
@@ -268,5 +268,8 @@
268
268
  "task.auth-base.audit-reading.name": "감사 기록 열람",
269
269
  "task.auth-base.audit-reading.description": "모든 사용자의 로그인, 권한 사용과 거절, 역할 변경 기록을 읽는다.",
270
270
  "error.access-request-task-mismatch": "선택한 업무에 접근이 거부된 권한이 포함되어 있지 않습니다.",
271
- "error.identity confirmation required": "본인 확인이 필요합니다."
271
+ "error.identity confirmation required": "본인 확인이 필요합니다.",
272
+ "error.passkey-not-found": "내 패스키 가운데 이 패스키가 없습니다. 목록을 새로 불러오세요.",
273
+ "error.passkey-name-too-long": "패스키 이름은 {max}자까지입니다. 지금 {length}자입니다.",
274
+ "error.domain not specified": "이 계정으로 들어갈 수 있는 $t(term.domain)이 아닙니다: {subdomain}"
272
275
  }
@@ -62,7 +62,7 @@
62
62
  "text.pattern_atleast_1_special": "Sekurang-kurangnya 1 aksara istimewa (!@#$%^&*())",
63
63
  "text.pattern_atleast_1_uppercase": "Sekurang-kurangnya 1 aksara besar",
64
64
  "text.pattern_minimum_charaters": "Minimum {length} aksara",
65
- "text.pattern_not_allowed": "Tidak dibenarkan aksara berulang",
65
+ "text.pattern_not_allowed": "Aksara yang sama tidak boleh 3 kali berturut-turut",
66
66
  "text.profile changed successfully": "Profil berjaya diubah",
67
67
  "text.result": "Hasil",
68
68
  "text.signout successfully": "Berjaya keluar",
@@ -268,5 +268,8 @@
268
268
  "task.auth-base.audit-reading.name": "Membaca log audit",
269
269
  "task.auth-base.audit-reading.description": "Membaca log masuk, penggunaan dan penolakan keistimewaan, serta perubahan peranan semua orang dalam domain.",
270
270
  "error.access-request-task-mismatch": "tugas yang dipilih tidak termasuk keistimewaan yang ditolak.",
271
- "error.identity confirmation required": "sahkan ini anda untuk melihat ini"
271
+ "error.identity confirmation required": "sahkan ini anda untuk melihat ini",
272
+ "error.passkey-not-found": "Tiada kunci laluan anda dengan id ini. Muat semula senarai.",
273
+ "error.passkey-name-too-long": "Nama kunci laluan boleh sehingga {max} aksara; nama ini ada {length}.",
274
+ "error.domain not specified": "Akaun ini tidak boleh memasuki $t(term.domain) {subdomain}."
272
275
  }
@@ -62,7 +62,7 @@
62
62
  "text.pattern_atleast_1_special": "至少1个特殊字符(!@#$%^&*())",
63
63
  "text.pattern_atleast_1_uppercase": "至少1个大写字母",
64
64
  "text.pattern_minimum_charaters": "最少未字符长度为{length}",
65
- "text.pattern_not_allowed": "不允许重复字符",
65
+ "text.pattern_not_allowed": "同一字符不可连续出现3次",
66
66
  "text.profile changed successfully": "个人资料更改成功。",
67
67
  "text.result": "结果",
68
68
  "text.signout successfully": "登出成功。",
@@ -268,5 +268,8 @@
268
268
  "task.auth-base.audit-reading.name": "查看审计日志",
269
269
  "task.auth-base.audit-reading.description": "读取所有用户的登录、权限使用与拒绝以及角色变更记录。",
270
270
  "error.access-request-task-mismatch": "所选业务不包含被拒绝的权限。",
271
- "error.identity confirmation required": "需要确认身份。"
271
+ "error.identity confirmation required": "需要确认身份。",
272
+ "error.passkey-not-found": "您的通行密钥中没有这一个。请重新加载列表。",
273
+ "error.passkey-name-too-long": "通行密钥名称最多 {max} 个字符,现在是 {length} 个。",
274
+ "error.domain not specified": "此账户无法进入该$t(term.domain): {subdomain}"
272
275
  }