@johnnyboy11234/esg-idea-contest 0.2.4 → 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 (111) 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/file_test.go +52 -0
  78. package/backend/test/matrix/load_test.go +62 -0
  79. package/backend/test/matrix/matrix_integration_test.go +291 -0
  80. package/backend/test/migration/pending_file_deletion_test.go +46 -0
  81. package/docs/API/api-endpoints.md +1 -1
  82. package/docs/API/error-codes.md +1 -1
  83. package/frontend/public/auth-hero-login.jpg +0 -0
  84. package/frontend/public/auth-hero-register.jpg +0 -0
  85. package/frontend/src/app/App.test.tsx +2 -42
  86. package/frontend/src/app/App.tsx +11 -2
  87. package/frontend/src/app/index.css +4 -4
  88. package/frontend/src/app/layouts.tsx +7 -135
  89. package/frontend/src/app/pages/JoinRequests.tsx +2 -2
  90. package/frontend/src/app/pages/NotFound.tsx +1 -1
  91. package/frontend/src/app/pages/admin.tsx +1 -1
  92. package/frontend/src/app/pages/auth.tsx +53 -25
  93. package/frontend/src/app/pages/ideas.tsx +7 -7
  94. package/frontend/src/app/pages/login.test.tsx +3 -54
  95. package/frontend/src/app/pages/register.test.tsx +1 -1
  96. package/frontend/src/app/router.tsx +2 -6
  97. package/frontend/src/features/auth/IdleWarning.test.tsx +82 -0
  98. package/frontend/src/features/auth/IdleWarning.tsx +66 -0
  99. package/frontend/src/features/auth/LoginForm.tsx +8 -1
  100. package/frontend/src/shared/api/activity.ts +20 -0
  101. package/frontend/src/shared/api/http.ts +2 -0
  102. package/frontend/src/shared/api/index.ts +1 -0
  103. package/frontend/src/shared/i18n/locales/en.ts +1 -0
  104. package/frontend/src/shared/i18n/locales/zh-TW.ts +1 -0
  105. package/package.json +1 -1
  106. package/testdata/expected-filename.json +93 -0
  107. package/testdata/password.json +425 -0
  108. package/testdata/permission-matrix.json +157 -0
  109. package/testdata/schema/expected-filename.schema.json +32 -0
  110. package/testdata/schema/password.schema.json +59 -0
  111. package/testdata/schema/permission-matrix.schema.json +73 -0
@@ -0,0 +1,157 @@
1
+ {
2
+ "rows": [
3
+ {
4
+ "id": "csrf",
5
+ "operation": "csrf",
6
+ "method": "GET",
7
+ "path": "/api/v1/auth/csrf",
8
+ "expect": {
9
+ "visitor": { "status": 200 },
10
+ "user": { "status": 200 },
11
+ "admin": { "status": 200 }
12
+ }
13
+ },
14
+ {
15
+ "id": "deadline",
16
+ "operation": "deadline",
17
+ "method": "GET",
18
+ "path": "/api/v1/deadline",
19
+ "expect": {
20
+ "visitor": { "status": 200 },
21
+ "user": { "status": 200 },
22
+ "admin": { "status": 200 }
23
+ }
24
+ },
25
+ {
26
+ "id": "register",
27
+ "operation": "register",
28
+ "method": "POST",
29
+ "path": "/api/v1/auth/register",
30
+ "body": { "empNo": "${newEmpNo}", "name": "Matrix Person", "email": "${newEmpNo}@example.com", "password": "${password}" },
31
+ "expect": {
32
+ "visitor": { "status": 201 },
33
+ "user": { "status": 201 },
34
+ "admin": { "status": 201 }
35
+ }
36
+ },
37
+ {
38
+ "id": "login",
39
+ "operation": "login",
40
+ "method": "POST",
41
+ "path": "/api/v1/auth/login",
42
+ "body": { "empNo": "${actorEmpNo}", "password": "${password}" },
43
+ "expect": {
44
+ "visitor": { "status": 401, "code": "INVALID_CREDENTIALS" },
45
+ "user": { "status": 200 },
46
+ "admin": { "status": 200 }
47
+ }
48
+ },
49
+ {
50
+ "id": "logout",
51
+ "operation": "logout",
52
+ "method": "POST",
53
+ "path": "/api/v1/auth/logout",
54
+ "expect": {
55
+ "visitor": { "status": 204 },
56
+ "user": { "status": 204 },
57
+ "admin": { "status": 204 }
58
+ }
59
+ },
60
+ {
61
+ "id": "me",
62
+ "operation": "me",
63
+ "method": "GET",
64
+ "path": "/api/v1/auth/me",
65
+ "expect": {
66
+ "visitor": { "status": 401, "code": "UNAUTHENTICATED" },
67
+ "user": { "status": 200 },
68
+ "admin": { "status": 200 }
69
+ }
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
+ },
119
+ {
120
+ "id": "order-logout-no-csrf",
121
+ "operation": "check order: a write without a CSRF token",
122
+ "method": "POST",
123
+ "path": "/api/v1/auth/logout",
124
+ "csrf": "none",
125
+ "expect": {
126
+ "visitor": { "status": 403, "code": "CSRF_INVALID" },
127
+ "user": { "status": 403, "code": "CSRF_INVALID" },
128
+ "admin": { "status": 403, "code": "CSRF_INVALID" }
129
+ }
130
+ },
131
+ {
132
+ "id": "order-unknown-write-no-csrf",
133
+ "operation": "check order: 401 before CSRF on a path outside the contract",
134
+ "method": "POST",
135
+ "path": "/api/v1/matrix-unknown",
136
+ "csrf": "none",
137
+ "outsideSpec": true,
138
+ "expect": {
139
+ "visitor": { "status": 401, "code": "UNAUTHENTICATED" },
140
+ "user": { "status": 403, "code": "CSRF_INVALID" },
141
+ "admin": { "status": 403, "code": "CSRF_INVALID" }
142
+ }
143
+ },
144
+ {
145
+ "id": "order-unknown-write-csrf",
146
+ "operation": "check order: CSRF before 404 on a path outside the contract",
147
+ "method": "POST",
148
+ "path": "/api/v1/matrix-unknown",
149
+ "outsideSpec": true,
150
+ "expect": {
151
+ "visitor": { "status": 401, "code": "UNAUTHENTICATED" },
152
+ "user": { "status": 404, "code": "NOT_FOUND" },
153
+ "admin": { "status": 404, "code": "NOT_FOUND" }
154
+ }
155
+ }
156
+ ]
157
+ }
@@ -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
+ }
@@ -0,0 +1,73 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "permission-matrix.schema.json",
4
+ "title": "Permission matrix",
5
+ "description": "PRD §7 as data: one row per operation (or check-order case), one expected cell per actor. Read by backend/test/matrix. Later PRs add their rows and actors.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["rows"],
9
+ "properties": {
10
+ "rows": {
11
+ "type": "array",
12
+ "minItems": 1,
13
+ "items": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["id", "operation", "method", "path", "expect"],
17
+ "not": { "required": ["body", "multipart"] },
18
+ "properties": {
19
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
20
+ "operation": { "type": "string", "minLength": 1 },
21
+ "method": { "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] },
22
+ "path": { "type": "string", "pattern": "^/api/v1/" },
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
+ },
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." },
45
+ "outsideSpec": { "type": "boolean", "default": false },
46
+ "expect": {
47
+ "type": "object",
48
+ "additionalProperties": false,
49
+ "minProperties": 1,
50
+ "properties": {
51
+ "visitor": { "$ref": "#/$defs/cell" },
52
+ "user": { "$ref": "#/$defs/cell" },
53
+ "member": { "$ref": "#/$defs/cell" },
54
+ "leader": { "$ref": "#/$defs/cell" },
55
+ "admin": { "$ref": "#/$defs/cell" }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ },
62
+ "$defs": {
63
+ "cell": {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": ["status"],
67
+ "properties": {
68
+ "status": { "type": "integer", "minimum": 200, "maximum": 599 },
69
+ "code": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$" }
70
+ }
71
+ }
72
+ }
73
+ }