@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
|
@@ -32,7 +32,7 @@ func newEnv(t *testing.T) env {
|
|
|
32
32
|
auditSvc := audit.New(db, nil)
|
|
33
33
|
errs := server.ErrorAdapter{Audit: auditSvc}
|
|
34
34
|
api := server.API{Account: account.Handler{
|
|
35
|
-
Service: &account.Service{DB: db, Audit: auditSvc, Policy: account.
|
|
35
|
+
Service: &account.Service{DB: db, Audit: auditSvc, Policy: account.DefaultPolicy()},
|
|
36
36
|
Fail: errs.Write,
|
|
37
37
|
}}
|
|
38
38
|
h := apispec.Load(t).Validate(t, server.NewPublic(slog.New(slog.DiscardHandler), server.Mount(api)))
|
|
@@ -49,6 +49,8 @@ func (e env) register(body string) *httptest.ResponseRecorder {
|
|
|
49
49
|
return rec
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
const goodPW = "Correct-Horse-1"
|
|
53
|
+
|
|
52
54
|
func reg(empNo, name, email, pw string) string {
|
|
53
55
|
b, _ := json.Marshal(map[string]string{"empNo": empNo, "name": name, "email": email, "password": pw})
|
|
54
56
|
return string(b)
|
|
@@ -150,16 +152,16 @@ func TestRegisterSuccess(t *testing.T) {
|
|
|
150
152
|
|
|
151
153
|
func TestRegisterConflicts(t *testing.T) {
|
|
152
154
|
e := newEnv(t)
|
|
153
|
-
if rec := e.register(reg("089290", "A", "a@x.com",
|
|
155
|
+
if rec := e.register(reg("089290", "A", "a@x.com", goodPW)); rec.Code != 201 {
|
|
154
156
|
t.Fatal(rec.Body)
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
cases := []struct {
|
|
158
160
|
name, body, code, reason string
|
|
159
161
|
}{
|
|
160
|
-
{"same employee ID", reg("089290", "B", "b@x.com",
|
|
161
|
-
{"same email, any case", reg("089291", "B", " A@X.COM ",
|
|
162
|
-
{"both taken reports the employee ID", reg("089290", "B", "a@x.com",
|
|
162
|
+
{"same employee ID", reg("089290", "B", "b@x.com", goodPW), "EMP_NO_TAKEN", "emp_no_taken"},
|
|
163
|
+
{"same email, any case", reg("089291", "B", " A@X.COM ", goodPW), "EMAIL_TAKEN", "email_taken"},
|
|
164
|
+
{"both taken reports the employee ID", reg("089290", "B", "a@x.com", goodPW), "EMP_NO_TAKEN", "emp_no_taken"},
|
|
163
165
|
}
|
|
164
166
|
for _, tc := range cases {
|
|
165
167
|
rec := e.register(tc.body)
|
|
@@ -189,14 +191,14 @@ func TestRegisterConflicts(t *testing.T) {
|
|
|
189
191
|
|
|
190
192
|
func TestRegisterValidationBeatsConflictAndIsAudited(t *testing.T) {
|
|
191
193
|
e := newEnv(t)
|
|
192
|
-
e.register(reg("089290", "A", "a@x.com",
|
|
194
|
+
e.register(reg("089290", "A", "a@x.com", goodPW))
|
|
193
195
|
|
|
194
196
|
// The employee ID is taken, but the email is malformed: 422, never 409.
|
|
195
|
-
rec := e.register(reg("089290", "B", "a b@x.com",
|
|
197
|
+
rec := e.register(reg("089290", "B", "a b@x.com", goodPW))
|
|
196
198
|
if rec.Code != 422 || problemCode(t, rec) != "VALIDATION_FAILED" {
|
|
197
199
|
t.Fatalf("%d %s", rec.Code, rec.Body)
|
|
198
200
|
}
|
|
199
|
-
rec = e.register(reg("12 4", "B/C", "ok@x.com",
|
|
201
|
+
rec = e.register(reg("12 4", "B/C", "ok@x.com", goodPW))
|
|
200
202
|
if rec.Code != 422 {
|
|
201
203
|
t.Fatalf("%d %s", rec.Code, rec.Body)
|
|
202
204
|
}
|
|
@@ -248,7 +250,7 @@ func TestRegisterRaceHasOneWinner(t *testing.T) {
|
|
|
248
250
|
wg.Add(1)
|
|
249
251
|
go func() {
|
|
250
252
|
defer wg.Done()
|
|
251
|
-
codes[i] = e.register(reg("089290", "A", "race"+string(rune('a'+i))+"@x.com",
|
|
253
|
+
codes[i] = e.register(reg("089290", "A", "race"+string(rune('a'+i))+"@x.com", goodPW)).Code
|
|
252
254
|
}()
|
|
253
255
|
}
|
|
254
256
|
wg.Wait()
|
|
@@ -266,3 +268,37 @@ func TestRegisterRaceHasOneWinner(t *testing.T) {
|
|
|
266
268
|
t.Fatalf("codes = %v, users = %d", codes, e.users(t))
|
|
267
269
|
}
|
|
268
270
|
}
|
|
271
|
+
|
|
272
|
+
func TestRegisterWeakPasswordListsEveryRuleInTableOrder(t *testing.T) {
|
|
273
|
+
e := newEnv(t)
|
|
274
|
+
// Format and policy errors come back together; the password contains the
|
|
275
|
+
// employee ID, repeats a character and has only two classes.
|
|
276
|
+
rec := e.register(reg("089290", "A", "nope", "aaa089290"))
|
|
277
|
+
if rec.Code != 422 || problemCode(t, rec) != "VALIDATION_FAILED" {
|
|
278
|
+
t.Fatalf("%d %s", rec.Code, rec.Body)
|
|
279
|
+
}
|
|
280
|
+
var body struct {
|
|
281
|
+
Errors []struct{ Field, Code, Rule string }
|
|
282
|
+
}
|
|
283
|
+
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
284
|
+
t.Fatal(err)
|
|
285
|
+
}
|
|
286
|
+
var got []string
|
|
287
|
+
for _, f := range body.Errors {
|
|
288
|
+
got = append(got, f.Field+":"+f.Code+":"+f.Rule)
|
|
289
|
+
}
|
|
290
|
+
want := "email:INVALID_FORMAT: password:PASSWORD_POLICY:LENGTH password:PASSWORD_POLICY:COMPLEXITY " +
|
|
291
|
+
"password:PASSWORD_POLICY:REPEAT password:PASSWORD_POLICY:CONTAINS_EMP_NO"
|
|
292
|
+
if strings.Join(got, " ") != want {
|
|
293
|
+
t.Errorf("errors = %v\nwant %s", got, want)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Alone, a weak password makes PASSWORD_POLICY the top-level code.
|
|
297
|
+
rec = e.register(reg("089290", "A", "a@x.com", "short"))
|
|
298
|
+
if rec.Code != 422 || problemCode(t, rec) != "PASSWORD_POLICY" {
|
|
299
|
+
t.Fatalf("%d %s", rec.Code, rec.Body)
|
|
300
|
+
}
|
|
301
|
+
if e.users(t) != 0 {
|
|
302
|
+
t.Error("a rejected registration created a user")
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
common-passwords.txt
|
|
2
|
+
====================
|
|
3
|
+
|
|
4
|
+
Source: SecLists, Passwords/Common-Credentials/10k-most-common.txt
|
|
5
|
+
https://github.com/danielmiessler/SecLists
|
|
6
|
+
Commit: a1a06ab57c3f4efc450d80738a72c11597aac81a
|
|
7
|
+
SHA-256: 68782d6a4a19a4768d5f15dd66bd534e7a33055cc755411e33f16d18c50fdcce
|
|
8
|
+
Entries: 10,001 lines, printable ASCII, no case-folded duplicates
|
|
9
|
+
Changes: none (byte-for-byte copy)
|
|
10
|
+
|
|
11
|
+
Used by: backend rule COMMON_PASSWORD (PRD §6.4, embedded with go:embed) and
|
|
12
|
+
the frontend live password checklist (lazy-loaded chunk).
|
|
13
|
+
|
|
14
|
+
Licence of the source file:
|
|
15
|
+
|
|
16
|
+
MIT License
|
|
17
|
+
|
|
18
|
+
Copyright (c) 2018 Daniel Miessler
|
|
19
|
+
|
|
20
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
21
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
22
|
+
in the Software without restriction, including without limitation the rights
|
|
23
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
24
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
25
|
+
furnished to do so, subject to the following conditions:
|
|
26
|
+
|
|
27
|
+
The above copyright notice and this permission notice shall be included in all
|
|
28
|
+
copies or substantial portions of the Software.
|
|
29
|
+
|
|
30
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
31
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
32
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
33
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
34
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
35
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
36
|
+
SOFTWARE.
|