@open-mercato/shared 0.4.11-develop.1561.c31ede76ba → 0.4.11-develop.1570.dcc5b7c59a

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.
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.4.11-develop.1561.c31ede76ba";
1
+ const APP_VERSION = "0.4.11-develop.1570.dcc5b7c59a";
2
2
  const appVersion = APP_VERSION;
3
3
  export {
4
4
  APP_VERSION,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/version.ts"],
4
- "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.11-develop.1561.c31ede76ba'\nexport const appVersion = APP_VERSION\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.11-develop.1570.dcc5b7c59a'\nexport const appVersion = APP_VERSION\n"],
5
5
  "mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/shared",
3
- "version": "0.4.11-develop.1561.c31ede76ba",
3
+ "version": "0.4.11-develop.1570.dcc5b7c59a",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -0,0 +1,162 @@
1
+ import {
2
+ buildPasswordSchema,
3
+ formatPasswordRequirements,
4
+ getPasswordPolicy,
5
+ getPasswordRequirements,
6
+ validatePassword,
7
+ } from '../passwordPolicy'
8
+
9
+ describe('passwordPolicy', () => {
10
+ it('returns the default policy when no env overrides are present', () => {
11
+ expect(getPasswordPolicy({} as NodeJS.ProcessEnv)).toEqual({
12
+ minLength: 6,
13
+ requireDigit: true,
14
+ requireUppercase: true,
15
+ requireSpecial: true,
16
+ })
17
+ })
18
+
19
+ it('prefers server env values, falls back to NEXT_PUBLIC values, and clamps min length', () => {
20
+ const env = {
21
+ OM_PASSWORD_MIN_LENGTH: '0',
22
+ OM_PASSWORD_REQUIRE_DIGIT: 'disabled',
23
+ OM_PASSWORD_REQUIRE_SPECIAL: ' ',
24
+ NEXT_PUBLIC_OM_PASSWORD_MIN_LENGTH: '12',
25
+ NEXT_PUBLIC_OM_PASSWORD_REQUIRE_DIGIT: 'enabled',
26
+ NEXT_PUBLIC_OM_PASSWORD_REQUIRE_UPPERCASE: 'false',
27
+ NEXT_PUBLIC_OM_PASSWORD_REQUIRE_SPECIAL: 'off',
28
+ } as NodeJS.ProcessEnv
29
+
30
+ expect(getPasswordPolicy(env)).toEqual({
31
+ minLength: 1,
32
+ requireDigit: false,
33
+ requireUppercase: false,
34
+ requireSpecial: false,
35
+ })
36
+ })
37
+
38
+ it('falls back to defaults when env overrides are invalid', () => {
39
+ const env = {
40
+ OM_PASSWORD_MIN_LENGTH: 'not-a-number',
41
+ OM_PASSWORD_REQUIRE_DIGIT: 'maybe',
42
+ OM_PASSWORD_REQUIRE_UPPERCASE: 'sometimes',
43
+ OM_PASSWORD_REQUIRE_SPECIAL: 'unknown',
44
+ } as NodeJS.ProcessEnv
45
+
46
+ expect(getPasswordPolicy(env)).toEqual({
47
+ minLength: 6,
48
+ requireDigit: true,
49
+ requireUppercase: true,
50
+ requireSpecial: true,
51
+ })
52
+ })
53
+
54
+ it('returns enabled requirements in policy order', () => {
55
+ expect(
56
+ getPasswordRequirements({
57
+ minLength: 10,
58
+ requireDigit: false,
59
+ requireUppercase: true,
60
+ requireSpecial: true,
61
+ }),
62
+ ).toEqual([
63
+ { id: 'minLength', value: 10 },
64
+ { id: 'uppercase' },
65
+ { id: 'special' },
66
+ ])
67
+ })
68
+
69
+ it('formats requirement text with translation keys, params, and custom separators', () => {
70
+ const translate = jest.fn(
71
+ (key: string, fallback: string, params?: Record<string, string | number>) => {
72
+ switch (key) {
73
+ case 'custom.password.minLength':
74
+ return `min:${String(params?.min)}`
75
+ case 'custom.password.uppercase':
76
+ return 'uppercase'
77
+ case 'custom.password.special':
78
+ return ' '
79
+ case 'custom.password.separator':
80
+ return ' | '
81
+ default:
82
+ return fallback
83
+ }
84
+ },
85
+ )
86
+
87
+ expect(
88
+ formatPasswordRequirements(
89
+ {
90
+ minLength: 12,
91
+ requireDigit: false,
92
+ requireUppercase: true,
93
+ requireSpecial: true,
94
+ },
95
+ translate,
96
+ 'custom.password',
97
+ ),
98
+ ).toBe('min:12 | uppercase')
99
+
100
+ expect(translate).toHaveBeenCalledWith(
101
+ 'custom.password.minLength',
102
+ 'At least {min} characters',
103
+ { min: 12 },
104
+ )
105
+ expect(translate).toHaveBeenCalledWith('custom.password.separator', ', ')
106
+ })
107
+
108
+ it('returns ordered violations for passwords that fail enabled checks', () => {
109
+ expect(
110
+ validatePassword('abc', {
111
+ minLength: 6,
112
+ requireDigit: true,
113
+ requireUppercase: true,
114
+ requireSpecial: true,
115
+ }),
116
+ ).toEqual({
117
+ ok: false,
118
+ violations: ['minLength', 'digit', 'uppercase', 'special'],
119
+ })
120
+
121
+ expect(
122
+ validatePassword('Password1', {
123
+ minLength: 8,
124
+ requireDigit: true,
125
+ requireUppercase: true,
126
+ requireSpecial: false,
127
+ }),
128
+ ).toEqual({
129
+ ok: true,
130
+ violations: [],
131
+ })
132
+ })
133
+
134
+ it('builds a zod schema that enforces both policy and max length', () => {
135
+ const schema = buildPasswordSchema({
136
+ policy: {
137
+ minLength: 8,
138
+ requireDigit: true,
139
+ requireUppercase: true,
140
+ requireSpecial: false,
141
+ },
142
+ maxLength: 10,
143
+ message: 'Weak password',
144
+ })
145
+
146
+ expect(schema.safeParse('Password1').success).toBe(true)
147
+
148
+ const weakResult = schema.safeParse('password1')
149
+ expect(weakResult.success).toBe(false)
150
+ if (weakResult.success) throw new Error('Expected lowercase password to fail schema validation')
151
+ expect(weakResult.error.issues).toEqual(
152
+ expect.arrayContaining([expect.objectContaining({ message: 'Weak password' })]),
153
+ )
154
+
155
+ const tooLongResult = schema.safeParse('Password1234')
156
+ expect(tooLongResult.success).toBe(false)
157
+ if (tooLongResult.success) throw new Error('Expected overlong password to fail schema validation')
158
+ expect(tooLongResult.error.issues).toEqual(
159
+ expect.arrayContaining([expect.objectContaining({ message: 'Weak password' })]),
160
+ )
161
+ })
162
+ })