@johnnyboy11234/esg-idea-contest 0.3.0 → 0.3.1

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 (96) hide show
  1. package/backend/api/openapi.yaml +83 -0
  2. package/backend/cmd/server/main.go +35 -2
  3. package/backend/db/migration/V005__idea.sql +49 -0
  4. package/backend/db/migration/V006__login_lock.sql +15 -0
  5. package/backend/db/migration/V007__login_attempt.sql +15 -0
  6. package/backend/db/migration/V008__pending_file_deletion.sql +27 -0
  7. package/backend/db/migration/V009__login_throttle_audit.sql +13 -0
  8. package/backend/go.mod +17 -3
  9. package/backend/go.sum +48 -7
  10. package/backend/internal/account/account_test.go +6 -6
  11. package/backend/internal/account/integration_test.go +45 -9
  12. package/backend/internal/account/keyboard.go +75 -0
  13. package/backend/internal/account/keyboard_test.go +43 -0
  14. package/backend/internal/account/logging_integration_test.go +1 -1
  15. package/backend/internal/account/password.go +125 -0
  16. package/backend/internal/account/validate.go +4 -21
  17. package/backend/internal/api/api.gen.go +224 -0
  18. package/backend/internal/auth/internal/store/login_attempt.go +53 -0
  19. package/backend/internal/auth/internal/store/login_lock.go +77 -0
  20. package/backend/internal/auth/internal/store/login_throttle_audit.go +42 -0
  21. package/backend/internal/auth/internal/store/session.go +15 -7
  22. package/backend/internal/auth/login.go +16 -4
  23. package/backend/internal/auth/login_integration_test.go +14 -1
  24. package/backend/internal/auth/login_ip_integration_test.go +153 -0
  25. package/backend/internal/auth/login_lock_integration_test.go +205 -0
  26. package/backend/internal/auth/login_test.go +15 -0
  27. package/backend/internal/auth/login_throttle_audit_integration_test.go +88 -0
  28. package/backend/internal/auth/purge.go +93 -0
  29. package/backend/internal/auth/purge_integration_test.go +220 -0
  30. package/backend/internal/auth/purge_test.go +51 -0
  31. package/backend/internal/auth/session.go +0 -6
  32. package/backend/internal/auth/session_integration_test.go +3 -3
  33. package/backend/internal/auth/throttle.go +122 -0
  34. package/backend/internal/deadline/gate.go +80 -0
  35. package/backend/internal/deadline/gate_integration_test.go +67 -0
  36. package/backend/internal/deadline/gate_test.go +61 -0
  37. package/backend/internal/idea/create.go +352 -0
  38. package/backend/internal/idea/create_integration_test.go +650 -0
  39. package/backend/internal/idea/handler.go +148 -0
  40. package/backend/internal/idea/handler_integration_test.go +107 -0
  41. package/backend/internal/idea/integration_test.go +149 -0
  42. package/backend/internal/idea/internal/store/list.go +146 -0
  43. package/backend/internal/idea/internal/store/list_test.go +33 -0
  44. package/backend/internal/idea/internal/store/store.go +166 -0
  45. package/backend/internal/idea/list.go +166 -0
  46. package/backend/internal/idea/list_integration_test.go +252 -0
  47. package/backend/internal/idea/list_test.go +96 -0
  48. package/backend/internal/idea/service.go +53 -0
  49. package/backend/internal/server/api.go +17 -0
  50. package/backend/internal/server/public.go +1 -0
  51. package/backend/internal/server/slides.go +21 -0
  52. package/backend/internal/shared/config/config.go +44 -1
  53. package/backend/internal/shared/config/config_test.go +21 -13
  54. package/backend/internal/shared/db/intx_integration_test.go +179 -0
  55. package/backend/internal/shared/dbtest/dbtest.go +21 -3
  56. package/backend/internal/shared/dbtest/faults.go +162 -0
  57. package/backend/internal/shared/job/job.go +54 -0
  58. package/backend/internal/shared/job/job_test.go +105 -0
  59. package/backend/internal/shared/web/multipart.go +175 -0
  60. package/backend/internal/shared/web/multipart_test.go +169 -0
  61. package/backend/internal/shared/web/problem.go +1 -0
  62. package/backend/internal/shared/web/request.go +42 -1
  63. package/backend/internal/shared/web/web_test.go +36 -0
  64. package/backend/internal/storage/expectedfilename.go +43 -0
  65. package/backend/internal/storage/expectedfilename_test.go +16 -0
  66. package/backend/internal/storage/internal/store/pending.go +41 -0
  67. package/backend/internal/storage/minio.go +139 -0
  68. package/backend/internal/storage/minio_integration_test.go +87 -0
  69. package/backend/internal/storage/minio_test.go +84 -0
  70. package/backend/internal/storage/pending.go +144 -0
  71. package/backend/internal/storage/pending_integration_test.go +324 -0
  72. package/backend/internal/storage/pending_test.go +23 -0
  73. package/backend/internal/storage/validator.go +100 -0
  74. package/backend/internal/storage/validator_test.go +95 -0
  75. package/backend/test/contract/expected_filename_test.go +60 -0
  76. package/backend/test/contract/password_test.go +46 -0
  77. package/backend/test/matrix/load_test.go +12 -0
  78. package/backend/test/matrix/matrix_integration_test.go +86 -5
  79. package/backend/test/migration/pending_file_deletion_test.go +46 -0
  80. package/docs/API/api-endpoints.md +1 -1
  81. package/docs/API/error-codes.md +1 -1
  82. package/frontend/src/app/App.tsx +11 -2
  83. package/frontend/src/features/auth/IdleWarning.test.tsx +82 -0
  84. package/frontend/src/features/auth/IdleWarning.tsx +66 -0
  85. package/frontend/src/shared/api/activity.ts +20 -0
  86. package/frontend/src/shared/api/http.ts +2 -0
  87. package/frontend/src/shared/api/index.ts +1 -0
  88. package/frontend/src/shared/i18n/locales/en.ts +1 -0
  89. package/frontend/src/shared/i18n/locales/zh-TW.ts +1 -0
  90. package/package.json +1 -1
  91. package/testdata/expected-filename.json +93 -0
  92. package/testdata/password.json +425 -0
  93. package/testdata/permission-matrix.json +48 -0
  94. package/testdata/schema/expected-filename.schema.json +32 -0
  95. package/testdata/schema/password.schema.json +59 -0
  96. package/testdata/schema/permission-matrix.schema.json +21 -0
@@ -0,0 +1,425 @@
1
+ {
2
+ "description": "Shared by backend (Go) and frontend (TS). password and empNo are raw strings; empNo is \"\" when the submitted employee ID is malformed (CONTAINS_EMP_NO is then skipped). wantRules lists every violated rule id in PRD 6.4 table order ([] = valid). Rules not implemented yet (SEQUENCE, COMMON_PASSWORD: B1b-03 and later) have no cases here; those cases are added with their rules, and these inputs avoid triggering them. PRD 6.4.",
3
+ "cases": [
4
+ {
5
+ "id": "valid-typical",
6
+ "password": "Correct-Horse-1",
7
+ "empNo": "089290",
8
+ "wantRules": []
9
+ },
10
+ {
11
+ "id": "valid-min-length-12",
12
+ "password": "Abcdefgh1!xY",
13
+ "empNo": "",
14
+ "wantRules": []
15
+ },
16
+ {
17
+ "id": "valid-max-length-64",
18
+ "password": "Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!",
19
+ "empNo": "",
20
+ "wantRules": []
21
+ },
22
+ {
23
+ "id": "valid-space-is-symbol",
24
+ "password": "Correct Horse1",
25
+ "empNo": "",
26
+ "wantRules": []
27
+ },
28
+ {
29
+ "id": "valid-three-classes-no-symbol",
30
+ "password": "CorrectHorse12",
31
+ "empNo": "",
32
+ "wantRules": []
33
+ },
34
+ {
35
+ "id": "valid-three-classes-no-digit",
36
+ "password": "Correct-Horse-x",
37
+ "empNo": "",
38
+ "wantRules": []
39
+ },
40
+ {
41
+ "id": "valid-three-classes-no-upper",
42
+ "password": "correct-horse-1",
43
+ "empNo": "",
44
+ "wantRules": []
45
+ },
46
+ {
47
+ "id": "valid-three-classes-no-lower",
48
+ "password": "CORRECT-HORSE-1",
49
+ "empNo": "",
50
+ "wantRules": []
51
+ },
52
+ {
53
+ "id": "length-11",
54
+ "password": "Abcdefgh1!x",
55
+ "empNo": "",
56
+ "wantRules": [
57
+ "LENGTH"
58
+ ]
59
+ },
60
+ {
61
+ "id": "length-65",
62
+ "password": "Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!Ab1!x",
63
+ "empNo": "",
64
+ "wantRules": [
65
+ "LENGTH"
66
+ ]
67
+ },
68
+ {
69
+ "id": "length-empty",
70
+ "password": "",
71
+ "empNo": "",
72
+ "wantRules": [
73
+ "LENGTH",
74
+ "COMPLEXITY"
75
+ ]
76
+ },
77
+ {
78
+ "id": "length-counts-code-points-not-bytes",
79
+ "password": "Aa1!Aa1!Aa1é",
80
+ "empNo": "",
81
+ "wantRules": [
82
+ "CHARSET"
83
+ ]
84
+ },
85
+ {
86
+ "id": "charset-non-ascii",
87
+ "password": "Correct-Horse-1é",
88
+ "empNo": "",
89
+ "wantRules": [
90
+ "CHARSET"
91
+ ]
92
+ },
93
+ {
94
+ "id": "charset-cjk-only-12",
95
+ "password": "密碼密碼密碼密碼密碼密碼",
96
+ "empNo": "",
97
+ "wantRules": [
98
+ "CHARSET",
99
+ "COMPLEXITY"
100
+ ]
101
+ },
102
+ {
103
+ "id": "charset-tab",
104
+ "password": "Correct\tHorse-1",
105
+ "empNo": "",
106
+ "wantRules": [
107
+ "CHARSET"
108
+ ]
109
+ },
110
+ {
111
+ "id": "charset-del",
112
+ "password": "Correct-Horse-1",
113
+ "empNo": "",
114
+ "wantRules": [
115
+ "CHARSET"
116
+ ]
117
+ },
118
+ {
119
+ "id": "charset-emoji-counts-as-one-code-point",
120
+ "password": "Correct-Hor1😀",
121
+ "empNo": "",
122
+ "wantRules": [
123
+ "CHARSET"
124
+ ]
125
+ },
126
+ {
127
+ "id": "complexity-two-classes-lower-digit",
128
+ "password": "abcdefghij12",
129
+ "empNo": "",
130
+ "wantRules": [
131
+ "COMPLEXITY"
132
+ ]
133
+ },
134
+ {
135
+ "id": "complexity-two-classes-lower-symbol",
136
+ "password": "whatever-goes",
137
+ "empNo": "",
138
+ "wantRules": [
139
+ "COMPLEXITY"
140
+ ]
141
+ },
142
+ {
143
+ "id": "complexity-two-classes-upper-lower",
144
+ "password": "CorrectHorseXy",
145
+ "empNo": "",
146
+ "wantRules": [
147
+ "COMPLEXITY"
148
+ ]
149
+ },
150
+ {
151
+ "id": "complexity-non-ascii-is-no-class",
152
+ "password": "abcdefghijk1é",
153
+ "empNo": "",
154
+ "wantRules": [
155
+ "CHARSET",
156
+ "COMPLEXITY"
157
+ ]
158
+ },
159
+ {
160
+ "id": "repeat-three-lower",
161
+ "password": "Correct-Hoooorse1",
162
+ "empNo": "",
163
+ "wantRules": [
164
+ "REPEAT"
165
+ ]
166
+ },
167
+ {
168
+ "id": "repeat-three-digit",
169
+ "password": "Correct-Horse111",
170
+ "empNo": "",
171
+ "wantRules": [
172
+ "REPEAT"
173
+ ]
174
+ },
175
+ {
176
+ "id": "repeat-exactly-two-ok",
177
+ "password": "Correct-Hoorse-11",
178
+ "empNo": "",
179
+ "wantRules": []
180
+ },
181
+ {
182
+ "id": "repeat-case-sensitive",
183
+ "password": "Correct-HoOorse-1",
184
+ "empNo": "",
185
+ "wantRules": []
186
+ },
187
+ {
188
+ "id": "repeat-symbols",
189
+ "password": "Correct-Horse1!!!",
190
+ "empNo": "",
191
+ "wantRules": [
192
+ "REPEAT"
193
+ ]
194
+ },
195
+ {
196
+ "id": "emp-no-contained",
197
+ "password": "Correct-089290-Ab",
198
+ "empNo": "089290",
199
+ "wantRules": [
200
+ "CONTAINS_EMP_NO"
201
+ ]
202
+ },
203
+ {
204
+ "id": "emp-no-contained-at-start",
205
+ "password": "089290-Correct-Ab",
206
+ "empNo": "089290",
207
+ "wantRules": [
208
+ "CONTAINS_EMP_NO"
209
+ ]
210
+ },
211
+ {
212
+ "id": "emp-no-skipped-when-malformed",
213
+ "password": "Correct-089290-Ab",
214
+ "empNo": "",
215
+ "wantRules": []
216
+ },
217
+ {
218
+ "id": "emp-no-other-number-ok",
219
+ "password": "Correct-089291-Ab",
220
+ "empNo": "089290",
221
+ "wantRules": []
222
+ },
223
+ {
224
+ "id": "table-order-all-violated",
225
+ "password": "aaaé",
226
+ "empNo": "123456",
227
+ "wantRules": [
228
+ "LENGTH",
229
+ "CHARSET",
230
+ "COMPLEXITY",
231
+ "REPEAT"
232
+ ]
233
+ },
234
+ {
235
+ "id": "table-order-with-emp-no",
236
+ "password": "aaa123456",
237
+ "empNo": "123456",
238
+ "wantRules": [
239
+ "LENGTH",
240
+ "COMPLEXITY",
241
+ "KEYBOARD_RUN",
242
+ "REPEAT",
243
+ "CONTAINS_EMP_NO"
244
+ ]
245
+ },
246
+ {
247
+ "id": "table-order-full-set",
248
+ "password": "aaaaaaaaaaaaé123456",
249
+ "empNo": "123456",
250
+ "wantRules": [
251
+ "CHARSET",
252
+ "COMPLEXITY",
253
+ "KEYBOARD_RUN",
254
+ "REPEAT",
255
+ "CONTAINS_EMP_NO"
256
+ ]
257
+ },
258
+ {
259
+ "id": "keyboard-row-digits",
260
+ "password": "Xk9-1234-Pmzj",
261
+ "empNo": "",
262
+ "wantRules": [
263
+ "KEYBOARD_RUN"
264
+ ]
265
+ },
266
+ {
267
+ "id": "keyboard-row-qwer",
268
+ "password": "Zq-qwer-Mj81",
269
+ "empNo": "",
270
+ "wantRules": [
271
+ "KEYBOARD_RUN"
272
+ ]
273
+ },
274
+ {
275
+ "id": "keyboard-row-asdf",
276
+ "password": "Zq-Asdf-Mj81",
277
+ "empNo": "",
278
+ "wantRules": [
279
+ "KEYBOARD_RUN"
280
+ ]
281
+ },
282
+ {
283
+ "id": "keyboard-row-bottom",
284
+ "password": "Zq-xcvb-Mj81",
285
+ "empNo": "",
286
+ "wantRules": [
287
+ "KEYBOARD_RUN"
288
+ ]
289
+ },
290
+ {
291
+ "id": "keyboard-reverse",
292
+ "password": "Zq-rewq-Mj81",
293
+ "empNo": "",
294
+ "wantRules": [
295
+ "KEYBOARD_RUN"
296
+ ]
297
+ },
298
+ {
299
+ "id": "keyboard-reverse-digits",
300
+ "password": "Zq-4321-Mj8P",
301
+ "empNo": "",
302
+ "wantRules": [
303
+ "KEYBOARD_RUN"
304
+ ]
305
+ },
306
+ {
307
+ "id": "keyboard-case-insensitive",
308
+ "password": "Zq-qWeR-Mj81",
309
+ "empNo": "",
310
+ "wantRules": [
311
+ "KEYBOARD_RUN"
312
+ ]
313
+ },
314
+ {
315
+ "id": "keyboard-shifted-symbols",
316
+ "password": "Zq-!@#$-Mjkp",
317
+ "empNo": "",
318
+ "wantRules": [
319
+ "KEYBOARD_RUN"
320
+ ]
321
+ },
322
+ {
323
+ "id": "keyboard-mixed-shift-and-plain",
324
+ "password": "Zq-1@3$-Mjkp",
325
+ "empNo": "",
326
+ "wantRules": [
327
+ "KEYBOARD_RUN"
328
+ ]
329
+ },
330
+ {
331
+ "id": "keyboard-longer-run-reported-once",
332
+ "password": "Zq-qwertyu-M1",
333
+ "empNo": "",
334
+ "wantRules": [
335
+ "KEYBOARD_RUN"
336
+ ]
337
+ },
338
+ {
339
+ "id": "keyboard-run-at-start",
340
+ "password": "qwerZk-Mj81xP",
341
+ "empNo": "",
342
+ "wantRules": [
343
+ "KEYBOARD_RUN"
344
+ ]
345
+ },
346
+ {
347
+ "id": "keyboard-run-at-end",
348
+ "password": "Zk-Mj81xP-poiu",
349
+ "empNo": "",
350
+ "wantRules": [
351
+ "KEYBOARD_RUN"
352
+ ]
353
+ },
354
+ {
355
+ "id": "keyboard-punctuation-row",
356
+ "password": "Zk-Mj81xP;lkj",
357
+ "empNo": "",
358
+ "wantRules": [
359
+ "KEYBOARD_RUN"
360
+ ]
361
+ },
362
+ {
363
+ "id": "keyboard-three-keys-ok",
364
+ "password": "Zq-qwe-Mj81xP",
365
+ "empNo": "",
366
+ "wantRules": []
367
+ },
368
+ {
369
+ "id": "keyboard-row-end-does-not-wrap-to-next-row",
370
+ "password": "Zq-90-qw-Mj81",
371
+ "empNo": "",
372
+ "wantRules": []
373
+ },
374
+ {
375
+ "id": "keyboard-rows-do-not-join",
376
+ "password": "Zq-pasdMj81xP",
377
+ "empNo": "",
378
+ "wantRules": []
379
+ },
380
+ {
381
+ "id": "keyboard-direction-change-ok",
382
+ "password": "Zq-qwewq-Mj81",
383
+ "empNo": "",
384
+ "wantRules": []
385
+ },
386
+ {
387
+ "id": "keyboard-gap-ok",
388
+ "password": "Zq-qwdf-Mj81xP",
389
+ "empNo": "",
390
+ "wantRules": []
391
+ },
392
+ {
393
+ "id": "keyboard-non-row-glyph-breaks-run",
394
+ "password": "Zq-qw~er-Mj81",
395
+ "empNo": "",
396
+ "wantRules": []
397
+ },
398
+ {
399
+ "id": "keyboard-repeat-is-not-a-run",
400
+ "password": "Zq-qqqq-Mj81xP",
401
+ "empNo": "",
402
+ "wantRules": [
403
+ "REPEAT"
404
+ ]
405
+ },
406
+ {
407
+ "id": "keyboard-and-complexity",
408
+ "password": "qwertyuiopas",
409
+ "empNo": "",
410
+ "wantRules": [
411
+ "COMPLEXITY",
412
+ "KEYBOARD_RUN"
413
+ ]
414
+ },
415
+ {
416
+ "id": "keyboard-before-repeat-in-table-order",
417
+ "password": "Zq-1234-aaa-Mj",
418
+ "empNo": "",
419
+ "wantRules": [
420
+ "KEYBOARD_RUN",
421
+ "REPEAT"
422
+ ]
423
+ }
424
+ ]
425
+ }
@@ -68,6 +68,54 @@
68
68
  "admin": { "status": 200 }
69
69
  }
70
70
  },
71
+ {
72
+ "id": "create-idea",
73
+ "operation": "create Idea",
74
+ "method": "POST",
75
+ "path": "/api/v1/ideas",
76
+ "multipart": {
77
+ "fields": { "category": "COOL_IDEA", "org": "BSID", "direction": "TALENT_DEVELOPMENT", "name": "Matrix Idea", "summary": "Matrix summary" },
78
+ "file": { "field": "slides", "filename": "I_BSID_${actorName}_${actorEmpNo}_Matrix Idea.pdf", "content": "pdf" }
79
+ },
80
+ "state": "OPEN",
81
+ "expect": {
82
+ "visitor": { "status": 401, "code": "UNAUTHENTICATED" },
83
+ "user": { "status": 201 },
84
+ "admin": { "status": 403, "code": "FORBIDDEN" }
85
+ }
86
+ },
87
+ {
88
+ "id": "create-idea-not-open",
89
+ "operation": "create Idea before the contest opens",
90
+ "method": "POST",
91
+ "path": "/api/v1/ideas",
92
+ "multipart": {
93
+ "fields": { "category": "COOL_IDEA", "org": "BSID", "direction": "TALENT_DEVELOPMENT", "name": "Matrix Idea", "summary": "Matrix summary" },
94
+ "file": { "field": "slides", "filename": "I_BSID_${actorName}_${actorEmpNo}_Matrix Idea.pdf", "content": "pdf" }
95
+ },
96
+ "state": "NOT_OPEN",
97
+ "expect": {
98
+ "visitor": { "status": 401, "code": "UNAUTHENTICATED" },
99
+ "user": { "status": 403, "code": "SUBMISSION_NOT_OPEN" },
100
+ "admin": { "status": 403, "code": "FORBIDDEN" }
101
+ }
102
+ },
103
+ {
104
+ "id": "create-idea-closed",
105
+ "operation": "create Idea after the deadline",
106
+ "method": "POST",
107
+ "path": "/api/v1/ideas",
108
+ "multipart": {
109
+ "fields": { "category": "COOL_IDEA", "org": "BSID", "direction": "TALENT_DEVELOPMENT", "name": "Matrix Idea", "summary": "Matrix summary" },
110
+ "file": { "field": "slides", "filename": "I_BSID_${actorName}_${actorEmpNo}_Matrix Idea.pdf", "content": "pdf" }
111
+ },
112
+ "state": "CLOSED",
113
+ "expect": {
114
+ "visitor": { "status": 401, "code": "UNAUTHENTICATED" },
115
+ "user": { "status": 403, "code": "SUBMISSION_CLOSED" },
116
+ "admin": { "status": 403, "code": "FORBIDDEN" }
117
+ }
118
+ },
71
119
  {
72
120
  "id": "order-logout-no-csrf",
73
121
  "operation": "check order: a write without a CSRF token",
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "expected-filename.schema.json",
4
+ "title": "Expected filename cases",
5
+ "description": "Shared front-end/back-end cases for PRD 6.8 (filename base) and PRD 6.2 (255 UTF-8 byte limit measured with .pptx).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["cases"],
9
+ "properties": {
10
+ "description": { "type": "string" },
11
+ "cases": {
12
+ "type": "array",
13
+ "minItems": 1,
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["id", "category", "org", "leaderName", "leaderEmpNo", "ideaName", "wantBase", "wantBytes", "wantTooLong"],
18
+ "properties": {
19
+ "id": { "type": "string", "minLength": 1 },
20
+ "category": { "enum": ["COOL_IDEA", "OUTSTANDING_IMPACT"] },
21
+ "org": { "enum": ["AAID", "BSID", "ICSD", "TSID", "PLED", "DEID"] },
22
+ "leaderName": { "type": "string" },
23
+ "leaderEmpNo": { "type": "string" },
24
+ "ideaName": { "type": "string" },
25
+ "wantBase": { "type": "string" },
26
+ "wantBytes": { "type": "integer", "minimum": 1 },
27
+ "wantTooLong": { "type": "boolean" }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "password.schema.json",
4
+ "title": "Password policy cases",
5
+ "description": "Shared front-end/back-end cases for PRD 6.4: every violated rule id, in table order.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "cases"
10
+ ],
11
+ "properties": {
12
+ "description": {
13
+ "type": "string"
14
+ },
15
+ "cases": {
16
+ "type": "array",
17
+ "minItems": 1,
18
+ "items": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "required": [
22
+ "id",
23
+ "password",
24
+ "empNo",
25
+ "wantRules"
26
+ ],
27
+ "properties": {
28
+ "id": {
29
+ "type": "string",
30
+ "minLength": 1
31
+ },
32
+ "password": {
33
+ "type": "string"
34
+ },
35
+ "empNo": {
36
+ "type": "string",
37
+ "pattern": "^([0-9]{6})?$"
38
+ },
39
+ "wantRules": {
40
+ "type": "array",
41
+ "uniqueItems": true,
42
+ "items": {
43
+ "enum": [
44
+ "LENGTH",
45
+ "CHARSET",
46
+ "COMPLEXITY",
47
+ "COMMON_PASSWORD",
48
+ "KEYBOARD_RUN",
49
+ "SEQUENCE",
50
+ "REPEAT",
51
+ "CONTAINS_EMP_NO"
52
+ ]
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
@@ -14,13 +14,34 @@
14
14
  "type": "object",
15
15
  "additionalProperties": false,
16
16
  "required": ["id", "operation", "method", "path", "expect"],
17
+ "not": { "required": ["body", "multipart"] },
17
18
  "properties": {
18
19
  "id": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
19
20
  "operation": { "type": "string", "minLength": 1 },
20
21
  "method": { "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] },
21
22
  "path": { "type": "string", "pattern": "^/api/v1/" },
22
23
  "body": { "type": "object" },
24
+ "multipart": {
25
+ "description": "multipart/form-data body instead of JSON. Text values and the filename may use ${actorEmpNo} and ${actorName}.",
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": ["fields", "file"],
29
+ "properties": {
30
+ "fields": { "type": "object", "additionalProperties": { "type": "string" } },
31
+ "file": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "required": ["field", "filename", "content"],
35
+ "properties": {
36
+ "field": { "type": "string" },
37
+ "filename": { "type": "string" },
38
+ "content": { "enum": ["pdf"] }
39
+ }
40
+ }
41
+ }
42
+ },
23
43
  "csrf": { "enum": ["valid", "none"], "default": "valid" },
44
+ "state": { "enum": ["NOT_OPEN", "OPEN", "CLOSED"], "description": "Submission state set before the row runs (PRD §5); omitted = unchanged." },
24
45
  "outsideSpec": { "type": "boolean", "default": false },
25
46
  "expect": {
26
47
  "type": "object",