@johnnyboy11234/esg-idea-contest 0.3.0 → 0.3.2
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/backend/api/openapi.yaml +119 -0
- package/backend/cmd/server/main.go +36 -2
- package/backend/db/migration/V005__idea.sql +49 -0
- package/backend/db/migration/V006__login_lock.sql +15 -0
- package/backend/db/migration/V007__login_attempt.sql +15 -0
- package/backend/db/migration/V008__pending_file_deletion.sql +27 -0
- package/backend/db/migration/V009__login_throttle_audit.sql +13 -0
- package/backend/go.mod +17 -3
- package/backend/go.sum +48 -7
- package/backend/internal/account/account_test.go +6 -6
- package/backend/internal/account/integration_test.go +45 -9
- package/backend/internal/account/internal/passwords/NOTICE +36 -0
- package/backend/internal/account/internal/passwords/common-passwords.txt +10001 -0
- package/backend/internal/account/internal/passwords/passwords.go +37 -0
- package/backend/internal/account/internal/passwords/passwords_test.go +35 -0
- package/backend/internal/account/keyboard.go +75 -0
- package/backend/internal/account/keyboard_test.go +43 -0
- package/backend/internal/account/logging_integration_test.go +1 -1
- package/backend/internal/account/password.go +131 -0
- package/backend/internal/account/sequence.go +54 -0
- package/backend/internal/account/sequence_test.go +38 -0
- package/backend/internal/account/validate.go +4 -21
- package/backend/internal/api/api.gen.go +262 -0
- package/backend/internal/auth/internal/store/login_attempt.go +53 -0
- package/backend/internal/auth/internal/store/login_lock.go +77 -0
- package/backend/internal/auth/internal/store/login_throttle_audit.go +42 -0
- package/backend/internal/auth/internal/store/session.go +15 -7
- package/backend/internal/auth/login.go +16 -4
- package/backend/internal/auth/login_integration_test.go +14 -1
- package/backend/internal/auth/login_ip_integration_test.go +153 -0
- package/backend/internal/auth/login_lock_integration_test.go +205 -0
- package/backend/internal/auth/login_test.go +15 -0
- package/backend/internal/auth/login_throttle_audit_integration_test.go +88 -0
- package/backend/internal/auth/purge.go +93 -0
- package/backend/internal/auth/purge_integration_test.go +220 -0
- package/backend/internal/auth/purge_test.go +51 -0
- package/backend/internal/auth/session.go +0 -6
- package/backend/internal/auth/session_integration_test.go +3 -3
- package/backend/internal/auth/throttle.go +122 -0
- package/backend/internal/deadline/gate.go +80 -0
- package/backend/internal/deadline/gate_integration_test.go +67 -0
- package/backend/internal/deadline/gate_test.go +61 -0
- package/backend/internal/idea/create.go +356 -0
- package/backend/internal/idea/create_integration_test.go +680 -0
- package/backend/internal/idea/download.go +193 -0
- package/backend/internal/idea/download_integration_test.go +311 -0
- package/backend/internal/idea/download_test.go +25 -0
- package/backend/internal/idea/handler.go +151 -0
- package/backend/internal/idea/handler_integration_test.go +107 -0
- package/backend/internal/idea/integration_test.go +149 -0
- package/backend/internal/idea/internal/store/list.go +146 -0
- package/backend/internal/idea/internal/store/list_test.go +33 -0
- package/backend/internal/idea/internal/store/store.go +166 -0
- package/backend/internal/idea/list.go +166 -0
- package/backend/internal/idea/list_integration_test.go +252 -0
- package/backend/internal/idea/list_test.go +96 -0
- package/backend/internal/idea/service.go +54 -0
- package/backend/internal/server/api.go +22 -0
- package/backend/internal/server/binding.go +4 -0
- package/backend/internal/server/public.go +1 -0
- package/backend/internal/server/server.go +20 -1
- package/backend/internal/server/slides.go +21 -0
- package/backend/internal/server/slides_test.go +82 -0
- package/backend/internal/shared/config/config.go +44 -1
- package/backend/internal/shared/config/config_test.go +21 -13
- package/backend/internal/shared/db/intx_integration_test.go +179 -0
- package/backend/internal/shared/dbtest/dbtest.go +21 -3
- package/backend/internal/shared/dbtest/faults.go +162 -0
- package/backend/internal/shared/job/job.go +54 -0
- package/backend/internal/shared/job/job_test.go +105 -0
- package/backend/internal/shared/web/multipart.go +175 -0
- package/backend/internal/shared/web/multipart_test.go +169 -0
- package/backend/internal/shared/web/problem.go +1 -0
- package/backend/internal/shared/web/request.go +42 -1
- package/backend/internal/shared/web/transport.go +4 -3
- package/backend/internal/shared/web/web_test.go +36 -0
- package/backend/internal/storage/expectedfilename.go +43 -0
- package/backend/internal/storage/expectedfilename_test.go +16 -0
- package/backend/internal/storage/internal/store/pending.go +41 -0
- package/backend/internal/storage/minio.go +139 -0
- package/backend/internal/storage/minio_integration_test.go +87 -0
- package/backend/internal/storage/minio_test.go +84 -0
- package/backend/internal/storage/pending.go +144 -0
- package/backend/internal/storage/pending_integration_test.go +324 -0
- package/backend/internal/storage/pending_test.go +23 -0
- package/backend/internal/storage/validator.go +100 -0
- package/backend/internal/storage/validator_test.go +95 -0
- package/backend/test/arch/arch_test.go +5 -1
- package/backend/test/contract/expected_filename_test.go +60 -0
- package/backend/test/contract/password_test.go +46 -0
- package/backend/test/matrix/load_test.go +12 -0
- package/backend/test/matrix/matrix_integration_test.go +92 -5
- package/backend/test/migration/pending_file_deletion_test.go +46 -0
- package/docs/API/api-endpoints.md +1 -1
- package/docs/API/error-codes.md +1 -1
- package/docs/Architecture/Architecture.md +1 -1
- package/docs/Architecture/Backend.md +2 -1
- package/frontend/Dockerfile +2 -0
- package/frontend/package-lock.json +1 -0
- package/frontend/package.json +1 -0
- package/frontend/src/app/App.tsx +11 -2
- package/frontend/src/app/pages/ideaCreate.test.tsx +308 -0
- package/frontend/src/app/pages/ideas.tsx +23 -9
- package/frontend/src/app/pages/login.test.tsx +38 -1
- package/frontend/src/app/pages/register.test.tsx +32 -0
- package/frontend/src/features/auth/IdleWarning.test.tsx +82 -0
- package/frontend/src/features/auth/IdleWarning.tsx +66 -0
- package/frontend/src/features/auth/LoginForm.tsx +21 -3
- package/frontend/src/features/auth/PasswordChecklist.test.tsx +73 -0
- package/frontend/src/features/auth/PasswordChecklist.tsx +96 -0
- package/frontend/src/features/auth/RegisterForm.tsx +11 -2
- package/frontend/src/features/auth/common-passwords.ts +31 -0
- package/frontend/src/features/auth/password-policy.test.ts +64 -0
- package/frontend/src/features/auth/password-policy.ts +168 -0
- package/frontend/src/features/auth/useCountdown.test.tsx +33 -0
- package/frontend/src/features/auth/useCountdown.ts +28 -0
- package/frontend/src/features/idea/CreateIdeaForm.tsx +279 -0
- package/frontend/src/features/idea/create.ts +123 -0
- package/frontend/src/shared/api/activity.ts +20 -0
- package/frontend/src/shared/api/http.ts +2 -0
- package/frontend/src/shared/api/index.ts +1 -0
- package/frontend/src/shared/api/types.generated.ts +319 -1
- package/frontend/src/shared/i18n/locales/en.ts +46 -4
- package/frontend/src/shared/i18n/locales/zh-TW.ts +46 -4
- package/frontend/src/shared/slides/index.ts +74 -0
- package/frontend/src/shared/slides/slides.test.ts +51 -0
- package/frontend/src/shared/ui/copy-button.tsx +52 -0
- package/frontend/src/shared/ui/native-select.tsx +27 -0
- package/frontend/src/shared/ui/textarea.tsx +19 -0
- package/frontend/src/test-setup.ts +37 -2
- package/frontend/vite.config.ts +3 -1
- package/package.json +1 -1
- package/testdata/expected-filename.json +93 -0
- package/testdata/password.json +582 -0
- package/testdata/permission-matrix.json +71 -0
- package/testdata/schema/expected-filename.schema.json +32 -0
- package/testdata/schema/password.schema.json +59 -0
- package/testdata/schema/permission-matrix.schema.json +22 -1
package/backend/api/openapi.yaml
CHANGED
|
@@ -152,6 +152,39 @@ paths:
|
|
|
152
152
|
- name: size
|
|
153
153
|
in: query
|
|
154
154
|
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
|
|
155
|
+
- name: org
|
|
156
|
+
in: query
|
|
157
|
+
description: Closed set; an invalid value is 422 INVALID_ENUM (never ignored). Validated in the handler.
|
|
158
|
+
schema: { type: string }
|
|
159
|
+
- name: category
|
|
160
|
+
in: query
|
|
161
|
+
schema: { type: string }
|
|
162
|
+
- name: direction
|
|
163
|
+
in: query
|
|
164
|
+
schema: { type: string }
|
|
165
|
+
- name: leaderEmpNo
|
|
166
|
+
in: query
|
|
167
|
+
schema: { type: string }
|
|
168
|
+
- name: createdFrom
|
|
169
|
+
in: query
|
|
170
|
+
description: ISO 8601 instant with offset or Z; inclusive. Validated in the handler.
|
|
171
|
+
schema: { type: string }
|
|
172
|
+
- name: createdTo
|
|
173
|
+
in: query
|
|
174
|
+
description: ISO 8601 instant with offset or Z; exclusive. Validated in the handler.
|
|
175
|
+
schema: { type: string }
|
|
176
|
+
- name: q
|
|
177
|
+
in: query
|
|
178
|
+
description: Case-insensitive "name contains"; `%` and `_` are literal.
|
|
179
|
+
schema: { type: string }
|
|
180
|
+
- name: sort
|
|
181
|
+
in: query
|
|
182
|
+
description: createdAt (default) or name; anything else is 422 INVALID_ENUM. Validated in the handler.
|
|
183
|
+
schema: { type: string }
|
|
184
|
+
- name: order
|
|
185
|
+
in: query
|
|
186
|
+
description: asc or desc (default desc for createdAt, asc for name); anything else is 422 INVALID_ENUM. Validated in the handler.
|
|
187
|
+
schema: { type: string }
|
|
155
188
|
responses:
|
|
156
189
|
'200':
|
|
157
190
|
description: One page of IdeaSummary
|
|
@@ -161,6 +194,42 @@ paths:
|
|
|
161
194
|
'401': { $ref: '#/components/responses/Problem' }
|
|
162
195
|
'422': { $ref: '#/components/responses/Problem' }
|
|
163
196
|
'500': { $ref: '#/components/responses/Problem' }
|
|
197
|
+
post:
|
|
198
|
+
operationId: createIdea
|
|
199
|
+
tags: [ideas]
|
|
200
|
+
summary: Create an Idea with its slides (User only)
|
|
201
|
+
description: >
|
|
202
|
+
One atomic multipart request (PRD 6.6). Order: role 403, state 403, 400 (missing or repeated
|
|
203
|
+
part), 422 text fields (incl. FILENAME_TOO_LONG), file 413 / 422 EMPTY_FILE / 415 /
|
|
204
|
+
422 FILENAME_MISMATCH, 409 IDEA_DUPLICATE, then upload (503 STORAGE_UNAVAILABLE). A transaction
|
|
205
|
+
whose outcome is unknown answers 503 SERVICE_UNAVAILABLE (the Idea may exist). The Leader is
|
|
206
|
+
the caller; any leader value in the form is ignored. The body is read by the handler, so the
|
|
207
|
+
generated wrapper does not bind it.
|
|
208
|
+
parameters:
|
|
209
|
+
- $ref: '#/components/parameters/CsrfToken'
|
|
210
|
+
requestBody:
|
|
211
|
+
required: true
|
|
212
|
+
content:
|
|
213
|
+
multipart/form-data:
|
|
214
|
+
schema: { $ref: '#/components/schemas/CreateIdeaForm' }
|
|
215
|
+
responses:
|
|
216
|
+
'201':
|
|
217
|
+
description: Created; Location is /api/v1/ideas/{id}
|
|
218
|
+
headers:
|
|
219
|
+
Location:
|
|
220
|
+
schema: { type: string }
|
|
221
|
+
content:
|
|
222
|
+
application/json:
|
|
223
|
+
schema: { $ref: '#/components/schemas/IdeaDetail' }
|
|
224
|
+
'400': { $ref: '#/components/responses/Problem' }
|
|
225
|
+
'401': { $ref: '#/components/responses/Problem' }
|
|
226
|
+
'403': { $ref: '#/components/responses/Problem' }
|
|
227
|
+
'409': { $ref: '#/components/responses/Problem' }
|
|
228
|
+
'413': { $ref: '#/components/responses/Problem' }
|
|
229
|
+
'415': { $ref: '#/components/responses/Problem' }
|
|
230
|
+
'422': { $ref: '#/components/responses/Problem' }
|
|
231
|
+
'500': { $ref: '#/components/responses/Problem' }
|
|
232
|
+
'503': { $ref: '#/components/responses/Problem' }
|
|
164
233
|
/ideas/{id}:
|
|
165
234
|
get:
|
|
166
235
|
operationId: getIdea
|
|
@@ -177,6 +246,42 @@ paths:
|
|
|
177
246
|
'401': { $ref: '#/components/responses/Problem' }
|
|
178
247
|
'404': { $ref: '#/components/responses/Problem' }
|
|
179
248
|
'500': { $ref: '#/components/responses/Problem' }
|
|
249
|
+
/ideas/{id}/slides:
|
|
250
|
+
get:
|
|
251
|
+
operationId: downloadSlides
|
|
252
|
+
tags: [ideas]
|
|
253
|
+
summary: Download the Idea's slides (Leader, Member, Admin)
|
|
254
|
+
description: >
|
|
255
|
+
The SLIDES_DOWNLOAD audit row is committed before the first byte is sent (PRD 6.11). The file
|
|
256
|
+
is named by the current expected filename (PRD 6.6). Range is not supported; HEAD answers 405
|
|
257
|
+
(no audit row). The Accept header is not checked on this route. MinIO unavailable before the
|
|
258
|
+
first byte answers 503 STORAGE_UNAVAILABLE; a storage failure after the headers are sent aborts
|
|
259
|
+
the connection (the body is shorter than Content-Length). A transaction whose outcome is
|
|
260
|
+
unknown answers 503 SERVICE_UNAVAILABLE and serves nothing.
|
|
261
|
+
parameters:
|
|
262
|
+
- $ref: '#/components/parameters/IdeaId'
|
|
263
|
+
responses:
|
|
264
|
+
'200':
|
|
265
|
+
description: The stored file
|
|
266
|
+
headers:
|
|
267
|
+
Content-Disposition:
|
|
268
|
+
description: 'attachment; filename="<ASCII fallback>"; filename*=UTF-8''''<percent-encoded> (RFC 6266)'
|
|
269
|
+
schema: { type: string }
|
|
270
|
+
Content-Length:
|
|
271
|
+
schema: { type: integer }
|
|
272
|
+
Cache-Control:
|
|
273
|
+
schema: { type: string, enum: [no-store] }
|
|
274
|
+
X-Content-Type-Options:
|
|
275
|
+
schema: { type: string, enum: [nosniff] }
|
|
276
|
+
content:
|
|
277
|
+
application/pdf:
|
|
278
|
+
schema: { type: string, format: binary }
|
|
279
|
+
application/vnd.openxmlformats-officedocument.presentationml.presentation:
|
|
280
|
+
schema: { type: string, format: binary }
|
|
281
|
+
'401': { $ref: '#/components/responses/Problem' }
|
|
282
|
+
'404': { $ref: '#/components/responses/Problem' }
|
|
283
|
+
'500': { $ref: '#/components/responses/Problem' }
|
|
284
|
+
'503': { $ref: '#/components/responses/Problem' }
|
|
180
285
|
components:
|
|
181
286
|
securitySchemes:
|
|
182
287
|
sessionCookie:
|
|
@@ -259,6 +364,7 @@ components:
|
|
|
259
364
|
field: { type: string }
|
|
260
365
|
code: { type: string }
|
|
261
366
|
expected: { type: string }
|
|
367
|
+
rule: { type: string, description: 'PASSWORD_POLICY only, the violated rule id (PRD 6.4)' }
|
|
262
368
|
Deadline:
|
|
263
369
|
type: object
|
|
264
370
|
required: [deadline, state, serverTime]
|
|
@@ -272,6 +378,19 @@ components:
|
|
|
272
378
|
properties:
|
|
273
379
|
empNo: { type: string }
|
|
274
380
|
name: { type: string }
|
|
381
|
+
CreateIdeaForm:
|
|
382
|
+
type: object
|
|
383
|
+
required: [category, org, direction, name, summary, slides]
|
|
384
|
+
properties:
|
|
385
|
+
category: { type: string, description: 'COOL_IDEA or OUTSTANDING_IMPACT' }
|
|
386
|
+
org: { type: string, description: 'AAID, BSID, ICSD, TSID, PLED or DEID' }
|
|
387
|
+
direction: { type: string, description: One of the five ESG directions (PRD 6.6) }
|
|
388
|
+
name: { type: string, description: '1-75 code points after clean-up; no / \ : * ? " < > |' }
|
|
389
|
+
summary: { type: string, description: 1-300 code points after clean-up }
|
|
390
|
+
slides:
|
|
391
|
+
type: string
|
|
392
|
+
format: binary
|
|
393
|
+
description: 'One PDF or PPTX, at most 5 MiB, named {I|O}_{org}_{leaderName}_{leaderEmpNo}_{name}.{pdf|pptx}'
|
|
275
394
|
IdeaSummary:
|
|
276
395
|
type: object
|
|
277
396
|
required: [id, name, category, org, direction, leader, memberCount, myRelation, createdAt, slidesUpdatedAt]
|
|
@@ -8,6 +8,7 @@ import (
|
|
|
8
8
|
"fmt"
|
|
9
9
|
"os"
|
|
10
10
|
"os/signal"
|
|
11
|
+
"sync"
|
|
11
12
|
"sync/atomic"
|
|
12
13
|
"syscall"
|
|
13
14
|
|
|
@@ -17,9 +18,12 @@ import (
|
|
|
17
18
|
"github.com/brian-huanggg/AwardRegistration/backend/internal/audit"
|
|
18
19
|
"github.com/brian-huanggg/AwardRegistration/backend/internal/auth"
|
|
19
20
|
"github.com/brian-huanggg/AwardRegistration/backend/internal/deadline"
|
|
21
|
+
"github.com/brian-huanggg/AwardRegistration/backend/internal/idea"
|
|
20
22
|
"github.com/brian-huanggg/AwardRegistration/backend/internal/server"
|
|
21
23
|
"github.com/brian-huanggg/AwardRegistration/backend/internal/shared/config"
|
|
24
|
+
"github.com/brian-huanggg/AwardRegistration/backend/internal/shared/job"
|
|
22
25
|
"github.com/brian-huanggg/AwardRegistration/backend/internal/shared/web"
|
|
26
|
+
"github.com/brian-huanggg/AwardRegistration/backend/internal/storage"
|
|
23
27
|
)
|
|
24
28
|
|
|
25
29
|
func main() {
|
|
@@ -52,7 +56,14 @@ func run() error {
|
|
|
52
56
|
return err
|
|
53
57
|
}
|
|
54
58
|
sessions := auth.New(db)
|
|
55
|
-
logins, err := auth.NewLogins(sessions, auditSvc, auth.
|
|
59
|
+
logins, err := auth.NewLogins(sessions, auditSvc, auth.DBThrottle{DB: db})
|
|
60
|
+
if err != nil {
|
|
61
|
+
return err
|
|
62
|
+
}
|
|
63
|
+
slides, err := storage.NewMinIO(storage.MinIOConfig{
|
|
64
|
+
Endpoint: cfg.MinIO.Endpoint, AccessKey: cfg.MinIO.AccessKey, SecretKey: cfg.MinIO.SecretKey,
|
|
65
|
+
Bucket: cfg.MinIO.Bucket, Secure: cfg.MinIO.Secure,
|
|
66
|
+
})
|
|
56
67
|
if err != nil {
|
|
57
68
|
return err
|
|
58
69
|
}
|
|
@@ -62,14 +73,37 @@ func run() error {
|
|
|
62
73
|
Logouts: auth.LogoutHandler{Logouts: &auth.Logouts{Sessions: sessions, Audit: auditSvc}, Fail: errs.Write},
|
|
63
74
|
Me: auth.MeHandler{Sessions: sessions, Fail: errs.Write},
|
|
64
75
|
Account: account.Handler{
|
|
65
|
-
Service: &account.Service{DB: db, Audit: auditSvc, Policy: account.
|
|
76
|
+
Service: &account.Service{DB: db, Audit: auditSvc, Policy: account.DefaultPolicy()},
|
|
66
77
|
Fail: errs.Write,
|
|
67
78
|
},
|
|
68
79
|
Deadline: deadline.Handler{Store: deadline.SQLStore{DB: db}},
|
|
80
|
+
Ideas: idea.Handler{
|
|
81
|
+
Service: &idea.Service{
|
|
82
|
+
DB: db, Audit: auditSvc,
|
|
83
|
+
Gate: deadline.Gate{Store: deadline.SQLStore{DB: db}},
|
|
84
|
+
Slides: server.IdeaSlideRules(),
|
|
85
|
+
Objects: slides,
|
|
86
|
+
Aborter: storage.NewPendingDeletion(db, slides, log),
|
|
87
|
+
},
|
|
88
|
+
ExpectedFilename: storage.ExpectedFilenameBase,
|
|
89
|
+
Fail: errs.Write,
|
|
90
|
+
Log: log,
|
|
91
|
+
},
|
|
69
92
|
}
|
|
70
93
|
|
|
94
|
+
// Background jobs stop when the root context is cancelled, or when Run
|
|
95
|
+
// returns for another reason, and are awaited before the pool closes.
|
|
96
|
+
jobCtx, stopJobs := context.WithCancel(ctx)
|
|
97
|
+
var jobs sync.WaitGroup
|
|
98
|
+
defer func() { stopJobs(); jobs.Wait() }()
|
|
99
|
+
jobs.Go(func() {
|
|
100
|
+
job.Run(jobCtx, log, job.Spec{Name: "auth-purge", Interval: auth.PurgeInterval, Jitter: auth.PurgeJitter},
|
|
101
|
+
auth.Purger{DB: db, Log: log}.Purge)
|
|
102
|
+
})
|
|
103
|
+
|
|
71
104
|
var ready atomic.Bool
|
|
72
105
|
return server.Run(ctx, cfg, log, server.NewPublic(log, server.Mount(si),
|
|
106
|
+
web.TrustedProxies(cfg.ProxyHops),
|
|
73
107
|
auth.Middleware(sessions, server.IsPublic, log),
|
|
74
108
|
csrf.Middleware(errs.Write, server.ActionFor)), &ready, server.DBCheck(db))
|
|
75
109
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- V005 (B3-01): idea, one row per team (PRD 6.6). There is no Idea without slides, so the slide metadata
|
|
2
|
+
-- lives on this row as NOT NULL columns (D22). Members are added by V006 (B5-01, idea_member).
|
|
3
|
+
-- category / org / direction closed value sets (VARCHAR + CHECK, never ENUM)
|
|
4
|
+
-- name 1-75 code points, utf8mb4_bin: case-sensitive, unique per leader (D20), not globally
|
|
5
|
+
-- summary 1-300 code points
|
|
6
|
+
-- slides_object_key slides/{ideaId}/{uuid}.{ext} (D50); the clean-up job asks "does any idea reference this key?"
|
|
7
|
+
-- slides_size 1..5 MiB (5,242,880 bytes, PRD 6.8)
|
|
8
|
+
-- version optimistic-locking counter (PRD 9 #3); every successful UPDATE of the row adds 1
|
|
9
|
+
-- The text clean-up (NFC, whitespace) is done by shared/text before the INSERT; the CHECKs are the second line.
|
|
10
|
+
|
|
11
|
+
CREATE TABLE idea (
|
|
12
|
+
id UUID NOT NULL DEFAULT UUID(),
|
|
13
|
+
leader_id UUID NOT NULL,
|
|
14
|
+
category VARCHAR(24) NOT NULL,
|
|
15
|
+
org VARCHAR(4) NOT NULL,
|
|
16
|
+
direction VARCHAR(24) NOT NULL,
|
|
17
|
+
name VARCHAR(75) NOT NULL,
|
|
18
|
+
summary VARCHAR(300) NOT NULL,
|
|
19
|
+
slides_object_key VARCHAR(128) NOT NULL,
|
|
20
|
+
slides_ext VARCHAR(4) NOT NULL,
|
|
21
|
+
slides_size INT UNSIGNED NOT NULL,
|
|
22
|
+
slides_sha256 CHAR(64) NOT NULL,
|
|
23
|
+
slides_updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
|
24
|
+
version INT UNSIGNED NOT NULL DEFAULT 0,
|
|
25
|
+
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
|
26
|
+
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
|
27
|
+
PRIMARY KEY (id),
|
|
28
|
+
-- A join request addresses an Idea by (leader employee ID, name) (PRD 6.6); this is also the IDEA_DUPLICATE guard.
|
|
29
|
+
UNIQUE KEY uq_idea_leader_name (leader_id, name),
|
|
30
|
+
-- Deleting a User is not a supported operation while they lead an Idea.
|
|
31
|
+
CONSTRAINT fk_idea_leader FOREIGN KEY (leader_id) REFERENCES app_user (id),
|
|
32
|
+
CONSTRAINT ck_idea_category CHECK (category IN ('COOL_IDEA', 'OUTSTANDING_IMPACT')),
|
|
33
|
+
CONSTRAINT ck_idea_org CHECK (org IN ('AAID', 'BSID', 'ICSD', 'TSID', 'PLED', 'DEID')),
|
|
34
|
+
CONSTRAINT ck_idea_direction CHECK (direction IN ('GREEN_MANUFACTURING', 'RESPONSIBLE_SUPPLY_CHAIN',
|
|
35
|
+
'INCLUSIVE_WORKPLACE', 'TALENT_DEVELOPMENT', 'CARING_FOR_DISADVANTAGED')),
|
|
36
|
+
CONSTRAINT ck_idea_name CHECK (CHAR_LENGTH(name) BETWEEN 1 AND 75
|
|
37
|
+
AND name NOT REGEXP '[[:cntrl:]]' AND name NOT REGEXP '[/\\\\:*?"<>|]'),
|
|
38
|
+
CONSTRAINT ck_idea_summary CHECK (CHAR_LENGTH(summary) BETWEEN 1 AND 300),
|
|
39
|
+
CONSTRAINT ck_idea_slides_ext CHECK (slides_ext IN ('pdf', 'pptx')),
|
|
40
|
+
CONSTRAINT ck_idea_slides_size CHECK (slides_size BETWEEN 1 AND 5242880),
|
|
41
|
+
CONSTRAINT ck_idea_slides_sha256 CHECK (slides_sha256 REGEXP '^[0-9a-f]{64}$'),
|
|
42
|
+
-- "My Ideas" and the Leader filter; the visibility query starts from here for Users.
|
|
43
|
+
KEY ix_idea_leader (leader_id, created_at, id),
|
|
44
|
+
-- Default list order (createdAt, id as final tie-breaker) and the name order.
|
|
45
|
+
KEY ix_idea_created (created_at, id),
|
|
46
|
+
KEY ix_idea_name (name, id),
|
|
47
|
+
-- Orphan-file check: "no idea row has slides_object_key = ?"
|
|
48
|
+
KEY ix_idea_slides_key (slides_object_key)
|
|
49
|
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- V006 (B1b-04): per-key login lock (PRD 6.5, OI-12). One row per submitted employee-ID string (control
|
|
2
|
+
-- characters stripped, cut to 32 chars), also for accounts that do not exist. All times are database time.
|
|
3
|
+
-- fail_count consecutive failures, each within 15 min of the previous one
|
|
4
|
+
-- locked_until set by the 5th failure to NOW + 15 min (fixed; later failures never extend it)
|
|
5
|
+
-- The IP sliding window (login_attempt) arrives with B1b-05.
|
|
6
|
+
|
|
7
|
+
CREATE TABLE login_lock (
|
|
8
|
+
lock_key VARCHAR(32) NOT NULL,
|
|
9
|
+
fail_count INT NOT NULL,
|
|
10
|
+
last_fail_at DATETIME(6) NOT NULL,
|
|
11
|
+
locked_until DATETIME(6) NULL,
|
|
12
|
+
PRIMARY KEY (lock_key),
|
|
13
|
+
-- Purge scans by age.
|
|
14
|
+
KEY ix_login_lock_last_fail (last_fail_at)
|
|
15
|
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- V007 (B1b-05): per-IP login failure events (PRD 6.5). One row per failed login attempt; the IP limit counts
|
|
2
|
+
-- the rows of one address inside a sliding 15-minute window (30 failures -> 429). Throttled attempts are not
|
|
3
|
+
-- recorded. All times are database time. Rows older than the window are dead weight; the purge job (B1b-07)
|
|
4
|
+
-- deletes them by attempted_at.
|
|
5
|
+
|
|
6
|
+
CREATE TABLE login_attempt (
|
|
7
|
+
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
8
|
+
ip INET6 NOT NULL,
|
|
9
|
+
attempted_at DATETIME(6) NOT NULL,
|
|
10
|
+
PRIMARY KEY (id),
|
|
11
|
+
-- Window count and "the 30th newest" lookup per address.
|
|
12
|
+
KEY ix_login_attempt_ip_time (ip, attempted_at),
|
|
13
|
+
-- Purge scans by age.
|
|
14
|
+
KEY ix_login_attempt_time (attempted_at)
|
|
15
|
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- V008 (B3-06): pending_file_deletion, MinIO object keys that should be deleted (PRD 6.8, Sequence.md 2, D25, D36).
|
|
2
|
+
-- Written in v1; the clean-up job that drains it is the next phase (PRD 9 #7). All times are database time.
|
|
3
|
+
-- object_key slides/{ideaId}/{uuid}.{ext}; no FK to idea: the row must outlive the idea it came from
|
|
4
|
+
-- reason REPLACED old key, inserted in the transaction that switched the pointer
|
|
5
|
+
-- DELETED key of a deleted Idea, inserted in the delete transaction
|
|
6
|
+
-- UPLOAD_ABORTED new key whose transaction rolled back (delete failed) or ended unknown;
|
|
7
|
+
-- the job deletes it only after next_attempt_at AND if no idea row references it
|
|
8
|
+
-- next_attempt_at earliest time the job may act: NOW for REPLACED / DELETED / known rollback,
|
|
9
|
+
-- NOW + grace period for an unknown outcome (OI-19, proposed 10 min)
|
|
10
|
+
-- attempts failed job attempts so far (the job backs off with next_attempt_at)
|
|
11
|
+
-- A key may appear more than once (UPLOAD_ABORTED after a lost COMMIT ack, later REPLACED), so it is not unique.
|
|
12
|
+
-- A row is removed when the object was deleted, or when the job finds an UPLOAD_ABORTED key still referenced.
|
|
13
|
+
|
|
14
|
+
CREATE TABLE pending_file_deletion (
|
|
15
|
+
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
16
|
+
object_key VARCHAR(128) NOT NULL,
|
|
17
|
+
reason VARCHAR(16) NOT NULL,
|
|
18
|
+
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
|
19
|
+
next_attempt_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
|
20
|
+
attempts INT UNSIGNED NOT NULL DEFAULT 0,
|
|
21
|
+
PRIMARY KEY (id),
|
|
22
|
+
CONSTRAINT ck_pending_file_deletion_reason CHECK (reason IN ('REPLACED', 'DELETED', 'UPLOAD_ABORTED')),
|
|
23
|
+
CONSTRAINT ck_pending_file_deletion_key CHECK (CHAR_LENGTH(object_key) BETWEEN 1 AND 128),
|
|
24
|
+
-- The job claims due rows: WHERE next_attempt_at <= NOW(6) ORDER BY next_attempt_at, id FOR UPDATE SKIP LOCKED.
|
|
25
|
+
KEY ix_pending_file_deletion_due (next_attempt_at, id),
|
|
26
|
+
KEY ix_pending_file_deletion_key (object_key)
|
|
27
|
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- V009 (B1b-06): "a throttled key is audited once per window" (PRD 6.11). One row per throttled subject records
|
|
2
|
+
-- until when its THROTTLED audit row has been written; a throttled attempt writes a new audit row only after it
|
|
3
|
+
-- wins the claim (expired row updated, or row inserted), so replicas agree. All times are database time.
|
|
4
|
+
-- scope 'key' (login_lock key) or 'ip' (source address)
|
|
5
|
+
-- Rows past audited_until are dead weight; the purge job (B1b-07) deletes them.
|
|
6
|
+
|
|
7
|
+
CREATE TABLE login_throttle_audit (
|
|
8
|
+
scope VARCHAR(8) NOT NULL,
|
|
9
|
+
subject VARCHAR(45) NOT NULL,
|
|
10
|
+
audited_until DATETIME(6) NOT NULL,
|
|
11
|
+
PRIMARY KEY (scope, subject),
|
|
12
|
+
KEY ix_login_throttle_audit_until (audited_until)
|
|
13
|
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin;
|
package/backend/go.mod
CHANGED
|
@@ -9,9 +9,13 @@ require (
|
|
|
9
9
|
github.com/go-chi/chi/v5 v5.2.3
|
|
10
10
|
github.com/go-sql-driver/mysql v1.9.3
|
|
11
11
|
github.com/google/uuid v1.6.0
|
|
12
|
+
github.com/minio/minio-go/v7 v7.4.1
|
|
12
13
|
github.com/oapi-codegen/runtime v1.8.0
|
|
13
14
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3
|
|
15
|
+
github.com/testcontainers/testcontainers-go v0.45.0
|
|
14
16
|
github.com/testcontainers/testcontainers-go/modules/mariadb v0.45.0
|
|
17
|
+
github.com/testcontainers/testcontainers-go/modules/minio v0.45.0
|
|
18
|
+
github.com/testcontainers/testcontainers-go/modules/toxiproxy v0.45.0
|
|
15
19
|
golang.org/x/crypto v0.58.0
|
|
16
20
|
golang.org/x/text v0.43.0
|
|
17
21
|
)
|
|
@@ -33,6 +37,7 @@ require (
|
|
|
33
37
|
github.com/docker/go-connections v0.8.2 // indirect
|
|
34
38
|
github.com/docker/go-units v0.5.0 // indirect
|
|
35
39
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
|
40
|
+
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
36
41
|
github.com/ebitengine/purego v0.10.1 // indirect
|
|
37
42
|
github.com/felixge/httpsnoop v1.1.0 // indirect
|
|
38
43
|
github.com/go-logr/logr v1.4.3 // indirect
|
|
@@ -40,9 +45,13 @@ require (
|
|
|
40
45
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
|
41
46
|
github.com/go-openapi/jsonpointer v0.23.1 // indirect
|
|
42
47
|
github.com/go-openapi/swag/jsonname v0.26.0 // indirect
|
|
43
|
-
github.com/klauspost/compress v1.
|
|
48
|
+
github.com/klauspost/compress v1.19.2 // indirect
|
|
49
|
+
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
|
50
|
+
github.com/klauspost/crc32 v1.3.0 // indirect
|
|
44
51
|
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
|
45
52
|
github.com/magiconair/properties v1.18.12 // indirect
|
|
53
|
+
github.com/minio/crc64nvme v1.1.1 // indirect
|
|
54
|
+
github.com/minio/md5-simd v1.1.2 // indirect
|
|
46
55
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
|
47
56
|
github.com/moby/go-archive v0.3.3 // indirect
|
|
48
57
|
github.com/moby/moby/api v1.56.1 // indirect
|
|
@@ -57,17 +66,20 @@ require (
|
|
|
57
66
|
github.com/oasdiff/yaml3 v0.0.14 // indirect
|
|
58
67
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
|
59
68
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
|
69
|
+
github.com/philhofer/fwd v1.2.0 // indirect
|
|
60
70
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
|
71
|
+
github.com/rs/xid v1.6.0 // indirect
|
|
61
72
|
github.com/shirou/gopsutil/v4 v4.26.6 // indirect
|
|
62
|
-
github.com/sirupsen/logrus v1.
|
|
73
|
+
github.com/sirupsen/logrus v1.10.2 // indirect
|
|
63
74
|
github.com/speakeasy-api/jsonpath v0.6.3 // indirect
|
|
64
75
|
github.com/speakeasy-api/openapi v1.24.0 // indirect
|
|
65
76
|
github.com/stretchr/testify v1.12.1 // indirect
|
|
66
|
-
github.com/
|
|
77
|
+
github.com/tinylib/msgp v1.6.4 // indirect
|
|
67
78
|
github.com/tklauser/go-sysconf v0.4.0 // indirect
|
|
68
79
|
github.com/tklauser/numcpus v0.12.0 // indirect
|
|
69
80
|
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
|
|
70
81
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
|
82
|
+
github.com/zeebo/xxh3 v1.1.0 // indirect
|
|
71
83
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
|
72
84
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
|
|
73
85
|
go.opentelemetry.io/otel v1.44.0 // indirect
|
|
@@ -75,9 +87,11 @@ require (
|
|
|
75
87
|
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
|
76
88
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
|
77
89
|
golang.org/x/mod v0.41.0 // indirect
|
|
90
|
+
golang.org/x/net v0.60.0 // indirect
|
|
78
91
|
golang.org/x/sync v0.24.0 // indirect
|
|
79
92
|
golang.org/x/sys v0.49.0 // indirect
|
|
80
93
|
golang.org/x/tools v0.51.0 // indirect
|
|
94
|
+
gopkg.in/ini.v1 v1.67.3 // indirect
|
|
81
95
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
82
96
|
)
|
|
83
97
|
|
package/backend/go.sum
CHANGED
|
@@ -44,13 +44,16 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
|
|
44
44
|
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
|
|
45
45
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
|
|
46
46
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
|
|
47
|
+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
48
|
+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
47
49
|
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
|
|
48
50
|
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
|
49
51
|
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
|
|
50
52
|
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
|
|
51
53
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
52
|
-
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
|
53
54
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
55
|
+
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
|
56
|
+
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
|
54
57
|
github.com/getkin/kin-openapi v0.142.0 h1:izj0vBdFprMhitfzaX8sTqztsEQyvwhssBoB6n8NO7w=
|
|
55
58
|
github.com/getkin/kin-openapi v0.142.0/go.mod h1:3BH9M9XDe/y9M5DSvEocVYAYq1w0qrhJHjC/vZi0AaY=
|
|
56
59
|
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
|
@@ -95,8 +98,13 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
|
|
|
95
98
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
96
99
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
97
100
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
|
98
|
-
github.com/klauspost/compress v1.
|
|
99
|
-
github.com/klauspost/compress v1.
|
|
101
|
+
github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
|
|
102
|
+
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
|
103
|
+
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
104
|
+
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
|
105
|
+
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
|
106
|
+
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
|
107
|
+
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
|
100
108
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
101
109
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
102
110
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
@@ -108,6 +116,14 @@ github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRt
|
|
|
108
116
|
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
|
109
117
|
github.com/magiconair/properties v1.18.12 h1:sT9zQpvTB3B4gzrX0tmZNTEaGyg8Zw55MFYRE32Mr9I=
|
|
110
118
|
github.com/magiconair/properties v1.18.12/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
|
119
|
+
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
|
|
120
|
+
github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
|
|
121
|
+
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
|
122
|
+
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
|
123
|
+
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
|
124
|
+
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
|
125
|
+
github.com/minio/minio-go/v7 v7.4.1 h1:PtU/zIOfQRWZ+QydXAZysc+xZYHdG+BBR4Z6TUvy73w=
|
|
126
|
+
github.com/minio/minio-go/v7 v7.4.1/go.mod h1:nQnQi7zTfXR4lREtYXZQWqo5aw0Rqv+ZqCGrTORygRk=
|
|
111
127
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
|
112
128
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
|
113
129
|
github.com/moby/go-archive v0.3.3 h1:OxxR9paxsluYi+zDUEXTTaIxtkK3viymW+Ka7vRhhME=
|
|
@@ -159,36 +175,55 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
|
|
159
175
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
|
160
176
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
|
161
177
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
|
178
|
+
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
|
179
|
+
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
|
162
180
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
163
181
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
|
164
182
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
|
165
|
-
github.com/rogpeppe/go-internal v1.
|
|
166
|
-
github.com/rogpeppe/go-internal v1.
|
|
183
|
+
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
|
|
184
|
+
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
|
185
|
+
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
|
186
|
+
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
|
167
187
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
|
|
168
188
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
|
169
189
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
|
170
190
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
171
191
|
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
|
|
172
192
|
github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
|
173
|
-
github.com/sirupsen/logrus v1.
|
|
174
|
-
github.com/sirupsen/logrus v1.
|
|
193
|
+
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
|
194
|
+
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
|
|
175
195
|
github.com/speakeasy-api/jsonpath v0.6.3 h1:c+QPwzAOdrWvzycuc9HFsIZcxKIaWcNpC+xhOW9rJxU=
|
|
176
196
|
github.com/speakeasy-api/jsonpath v0.6.3/go.mod h1:2cXloNuQ+RSXi5HTRaeBh7JEmjRXTiaKpFTdZiL7URI=
|
|
177
197
|
github.com/speakeasy-api/openapi v1.24.0 h1:opoD27rupX7zBVPq1HkIGLeMOzNNA7JalhYP8q34i04=
|
|
178
198
|
github.com/speakeasy-api/openapi v1.24.0/go.mod h1:g3+dIMe0AYgbbGvnlQZqesmjAVWSm9BmsjLevnefQrg=
|
|
179
199
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
|
180
200
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
201
|
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
202
|
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
203
|
+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
181
204
|
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
|
182
205
|
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
|
183
206
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
184
207
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
185
208
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
209
|
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
210
|
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
211
|
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
212
|
+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
186
213
|
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
|
187
214
|
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
|
188
215
|
github.com/testcontainers/testcontainers-go v0.45.0 h1:ss9Stm9KDX6JLgGL4jr2fC5dwpabTbCBsRcGadBPR9k=
|
|
189
216
|
github.com/testcontainers/testcontainers-go v0.45.0/go.mod h1:VMJZ7M+qhgfEHWNbtjm596OyEOraULSTDaus/h0hmJI=
|
|
190
217
|
github.com/testcontainers/testcontainers-go/modules/mariadb v0.45.0 h1:oN0eXjn9Ae43kJThdWosEiq5mbU7RvkuRIfOZnO8Awg=
|
|
191
218
|
github.com/testcontainers/testcontainers-go/modules/mariadb v0.45.0/go.mod h1:z5rjEcunHetNxfFAIwKp2uO9RA91aWIC5wvMZyPKZG0=
|
|
219
|
+
github.com/testcontainers/testcontainers-go/modules/minio v0.45.0 h1:y/Nj/4ZkI220EJM6L45dntmNh73qcH7wLEuAgc9pyrE=
|
|
220
|
+
github.com/testcontainers/testcontainers-go/modules/minio v0.45.0/go.mod h1:C07UqpIojab780lr5P9tKaYIIz2sPZWOUjp2E12pmmQ=
|
|
221
|
+
github.com/testcontainers/testcontainers-go/modules/redis v0.45.0 h1:U7GClvzAxLc7pjDfFxw3K6+odTa8NbxEa31svO5gl/Q=
|
|
222
|
+
github.com/testcontainers/testcontainers-go/modules/redis v0.45.0/go.mod h1:06G3YVRf2RHAFNKwtFYhVd4CYgnXLWXmckaJNWI4fGY=
|
|
223
|
+
github.com/testcontainers/testcontainers-go/modules/toxiproxy v0.45.0 h1:bzp1LLWbgE5cWpfuR5lW6PuVddRoz7yzgK2zkwXYMQg=
|
|
224
|
+
github.com/testcontainers/testcontainers-go/modules/toxiproxy v0.45.0/go.mod h1:CWGzEsj3+TX1D21EcpY1SLSuNroXiXfnZUlUY9w71Hw=
|
|
225
|
+
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
|
226
|
+
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
|
192
227
|
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
|
|
193
228
|
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
|
|
194
229
|
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
|
|
@@ -198,6 +233,10 @@ github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN
|
|
|
198
233
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
199
234
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
|
200
235
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
236
|
+
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
|
237
|
+
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
|
238
|
+
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
|
239
|
+
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
|
201
240
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
|
202
241
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
|
203
242
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
|
|
@@ -287,6 +326,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
|
|
287
326
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
288
327
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
289
328
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
329
|
+
gopkg.in/ini.v1 v1.67.3 h1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=
|
|
330
|
+
gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
|
290
331
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
291
332
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
292
333
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@@ -15,7 +15,7 @@ import (
|
|
|
15
15
|
// A Service with no database: every case here must be rejected before any I/O.
|
|
16
16
|
func validate(t *testing.T, cmd account.RegisterCommand) *web.Error {
|
|
17
17
|
t.Helper()
|
|
18
|
-
svc := &account.Service{Policy: account.
|
|
18
|
+
svc := &account.Service{Policy: account.DefaultPolicy()}
|
|
19
19
|
_, err := svc.Register(context.Background(), cmd, account.Request{})
|
|
20
20
|
var e *web.Error
|
|
21
21
|
if !errors.As(err, &e) {
|
|
@@ -25,7 +25,7 @@ func validate(t *testing.T, cmd account.RegisterCommand) *web.Error {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
func good() account.RegisterCommand {
|
|
28
|
-
return account.RegisterCommand{EmpNo: "089290", Name: "王小明", Email: "a@b.c", Password: "
|
|
28
|
+
return account.RegisterCommand{EmpNo: "089290", Name: "王小明", Email: "a@b.c", Password: "Correct-Horse-1"}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
func codes(e *web.Error) []string {
|
|
@@ -61,11 +61,11 @@ func TestRegisterValidation(t *testing.T) {
|
|
|
61
61
|
{"email non-ASCII", func(c *account.RegisterCommand) { c.Email = "é@x.com" }, web.CodeValidationFailed, "email:INVALID_FORMAT"},
|
|
62
62
|
{"email empty", func(c *account.RegisterCommand) { c.Email = "" }, web.CodeValidationFailed, "email:REQUIRED"},
|
|
63
63
|
{"email 255 chars", func(c *account.RegisterCommand) { c.Email = strings.Repeat("a", 253) + "@b" }, web.CodeValidationFailed, "email:OUT_OF_RANGE"},
|
|
64
|
-
{"password empty", func(c *account.RegisterCommand) { c.Password = "" }, web.
|
|
65
|
-
{"password beyond bcrypt", func(c *account.RegisterCommand) { c.Password = strings.Repeat("
|
|
64
|
+
{"password empty", func(c *account.RegisterCommand) { c.Password = "" }, web.CodePasswordPolicy, "password:PASSWORD_POLICY password:PASSWORD_POLICY"},
|
|
65
|
+
{"password beyond bcrypt", func(c *account.RegisterCommand) { c.Password = strings.Repeat("Pp1", 25) }, web.CodePasswordPolicy, "password:PASSWORD_POLICY"},
|
|
66
66
|
{"all fields in form order", func(c *account.RegisterCommand) {
|
|
67
67
|
c.EmpNo, c.Name, c.Email, c.Password = "x", "a:b", "nope", ""
|
|
68
|
-
}, web.CodeValidationFailed, "empNo:INVALID_FORMAT name:INVALID_NAME_CHARS email:INVALID_FORMAT password:
|
|
68
|
+
}, web.CodeValidationFailed, "empNo:INVALID_FORMAT name:INVALID_NAME_CHARS email:INVALID_FORMAT password:PASSWORD_POLICY password:PASSWORD_POLICY"},
|
|
69
69
|
}
|
|
70
70
|
for _, tc := range cases {
|
|
71
71
|
t.Run(tc.name, func(t *testing.T) {
|
|
@@ -143,7 +143,7 @@ func TestTopCodeIsPasswordPolicyWhenFirst(t *testing.T) {
|
|
|
143
143
|
|
|
144
144
|
func handlerFor(got *error) http.Handler {
|
|
145
145
|
h := account.Handler{
|
|
146
|
-
Service: &account.Service{Policy: account.
|
|
146
|
+
Service: &account.Service{Policy: account.DefaultPolicy()},
|
|
147
147
|
Fail: func(_ http.ResponseWriter, _ *http.Request, err error) { *got = err },
|
|
148
148
|
}
|
|
149
149
|
return http.HandlerFunc(h.Register)
|