@nakedev/go-scaffold 0.3.3 → 0.4.3
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/README.md +288 -50
- package/dist/commands/auth.js +53 -22
- package/dist/commands/config.js +50 -0
- package/dist/commands/create.js +32 -2
- package/dist/commands/generate.js +25 -2
- package/dist/commands/method.js +22 -7
- package/dist/commands/migration.js +2 -2
- package/dist/commands/observability.js +3 -3
- package/dist/commands/rbac.js +3 -3
- package/dist/commands/undo.js +5 -0
- package/dist/commands/worker.js +1 -1
- package/dist/index.js +186 -59
- package/dist/prompts/auth-wizard.js +40 -6
- package/dist/prompts/create-wizard.js +43 -2
- package/dist/prompts/generate-wizard.js +89 -9
- package/dist/templates/auth-manifest.js +31 -1
- package/dist/templates/create-manifest.js +4 -0
- package/dist/templates/module-manifest.js +37 -1
- package/dist/templates/rbac-manifest.js +1 -0
- package/dist/types.js +6 -0
- package/dist/utils/auth-patcher.js +115 -24
- package/dist/utils/config.js +147 -3
- package/dist/utils/main-patcher.js +29 -27
- package/dist/utils/marker-patch.js +7 -1
- package/dist/utils/method-patcher.js +261 -81
- package/dist/utils/module-profile.js +32 -0
- package/dist/utils/observability-patcher.js +2 -2
- package/dist/utils/platform-patcher.js +29 -7
- package/dist/utils/rbac-patcher.js +97 -75
- package/package.json +7 -2
- package/templates/add/auth/cmd/seed/main.go.hbs +13 -3
- package/templates/add/auth/docs/login.yaml.hbs +11 -1
- package/templates/add/auth/docs/mfa-verify.yaml.hbs +19 -0
- package/templates/add/auth/docs/provider-exchange.yaml.hbs +40 -0
- package/templates/add/auth/docs/provider-login.yaml.hbs +31 -0
- package/templates/add/auth/docs/refresh.yaml.hbs +7 -0
- package/templates/add/auth/docs/register.yaml.hbs +7 -0
- package/templates/add/auth/docs/reset-password.yaml.hbs +1 -1
- package/templates/add/auth/docs/schemas.yaml.hbs +59 -1
- package/templates/add/auth/docs/users-me-mfa-confirm.yaml.hbs +19 -0
- package/templates/add/auth/docs/users-me-mfa-disable.yaml.hbs +15 -0
- package/templates/add/auth/docs/users-me-mfa-setup.yaml.hbs +14 -0
- package/templates/add/auth/docs/users-me-mfa.yaml.hbs +12 -0
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +132 -0
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +113 -0
- package/templates/add/auth/internal/app/user/browser_policy.go.hbs +98 -0
- package/templates/add/auth/internal/app/user/composition.go.hbs +165 -0
- package/templates/add/auth/internal/app/user/contracts.go.hbs +88 -0
- package/templates/add/auth/internal/app/user/dto.go.hbs +57 -0
- package/templates/add/auth/internal/app/user/errors.go.hbs +25 -0
- package/templates/add/auth/internal/app/user/external_login.go.hbs +208 -0
- package/templates/add/auth/internal/app/user/handler.go.hbs +60 -203
- package/templates/add/auth/internal/app/user/handler_local.go.hbs +75 -0
- package/templates/add/auth/internal/app/user/handler_mfa.go.hbs +83 -0
- package/templates/add/auth/internal/app/user/handler_oauth.go.hbs +70 -0
- package/templates/add/auth/internal/app/user/handler_recovery.go.hbs +49 -0
- package/templates/add/auth/internal/app/user/handler_test.go.hbs +290 -0
- package/templates/add/auth/internal/app/user/handler_user.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/jwt.go.hbs +6 -59
- package/templates/add/auth/internal/app/user/local_auth.go.hbs +98 -0
- package/templates/add/auth/internal/app/user/mfa_service.go.hbs +450 -0
- package/templates/add/auth/internal/app/user/mfa_service_test.go.hbs +199 -0
- package/templates/add/auth/internal/app/user/mfa_store.go.hbs +127 -0
- package/templates/add/auth/internal/app/user/mfa_store_test.go.hbs +174 -0
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +8 -2
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +4 -3
- package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +17 -0
- package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +17 -0
- package/templates/add/auth/internal/app/user/model/user.go.hbs +3 -2
- package/templates/add/auth/internal/app/user/provider_test.go.hbs +286 -0
- package/templates/add/auth/internal/app/user/recovery_service.go.hbs +114 -0
- package/templates/add/auth/internal/app/user/repository.go.hbs +2 -0
- package/templates/add/auth/internal/app/user/service.go.hbs +82 -478
- package/templates/add/auth/internal/app/user/service_test.go.hbs +601 -45
- package/templates/add/auth/internal/app/user/session_cookie.go.hbs +33 -0
- package/templates/add/auth/internal/app/user/sessions.go.hbs +99 -0
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +42 -14
- package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +105 -40
- package/templates/add/auth/internal/app/user/tokenstore_pg_test.go.hbs +96 -0
- package/templates/add/auth/internal/app/user/tokenstore_recovery.go.hbs +58 -0
- package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +144 -70
- package/templates/add/auth/internal/app/user/tokenstore_redis_test.go.hbs +185 -0
- package/templates/add/auth/internal/app/user/user_query.go.hbs +65 -0
- package/templates/add/auth/internal/platform/authprovider/google/google.go.hbs +389 -0
- package/templates/add/auth/internal/platform/authprovider/google/google_test.go.hbs +312 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +9 -4
- package/templates/add/auth/migrations/create_identities.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_mfa.down.sql.hbs +3 -0
- package/templates/add/auth/migrations/create_mfa.up.sql.hbs +29 -0
- package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -2
- package/templates/add/rbac/internal/app/role/composition.go.hbs +35 -0
- package/templates/add/rbac/internal/app/role/service.go.hbs +12 -12
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +340 -121
- package/templates/create/base/.env.example.hbs +0 -1
- package/templates/create/base/AGENTS.md.hbs +255 -67
- package/templates/create/base/Makefile.hbs +2 -1
- package/templates/create/base/README.md.hbs +45 -17
- package/templates/create/base/cmd/api/wiring.go.hbs +18 -25
- package/templates/create/base/internal/platform/database/database.go.hbs +3 -3
- package/templates/create/base/internal/shared/apperror/apperror.go.hbs +15 -2
- package/templates/create/base/internal/shared/config/config.go.hbs +0 -8
- package/templates/create/base/internal/shared/middleware/cors_test.go.hbs +40 -0
- package/templates/create/base/internal/shared/middleware/error.go.hbs +15 -5
- package/templates/create/features/docs/architecture.md.hbs +38 -16
- package/templates/create/features/docs/patterns.md.hbs +40 -21
- package/templates/create/features/docs/techstack.md.hbs +3 -3
- package/templates/generate/module/commands.go.hbs +95 -0
- package/templates/generate/module/composition.go.hbs +23 -0
- package/templates/generate/module/cqrs_test.go.hbs +7 -0
- package/templates/generate/module/handler.go.hbs +50 -5
- package/templates/generate/module/minimal/commands.go.hbs +34 -0
- package/templates/generate/module/minimal/handler.go.hbs +34 -0
- package/templates/generate/module/minimal/queries.go.hbs +45 -0
- package/templates/generate/module/minimal/service.go.hbs +27 -1
- package/templates/generate/module/queries.go.hbs +62 -0
- package/templates/generate/module/service.go.hbs +61 -5
- package/templates/add/auth/docs/google-callback.yaml.hbs +0 -22
- package/templates/add/auth/docs/google-login.yaml.hbs +0 -7
|
@@ -3,140 +3,359 @@ name: go-scaffold
|
|
|
3
3
|
description: >
|
|
4
4
|
Use whenever the user asks to add, create, scaffold, wire up, or generate a
|
|
5
5
|
new domain/feature/resource OR a new endpoint/method in THIS project —
|
|
6
|
-
including indirect
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
go-scaffold.config.json at its root.
|
|
6
|
+
including indirect requests such as "add products", "let admins approve
|
|
7
|
+
orders", "add an endpoint for X", or "look up a user by email". This project
|
|
8
|
+
was scaffolded by go-scaffold and has a go-scaffold.config.json at its root;
|
|
9
|
+
check for that file before assuming this applies. Use the generated
|
|
10
|
+
modular/Hexagonal boundaries and runtime safety contract described below.
|
|
11
|
+
Do NOT use for business logic inside an existing method, model/DTO field
|
|
12
|
+
edits, foreign keys/relations, bug fixes, refactors, or a project without
|
|
13
|
+
go-scaffold.config.json.
|
|
15
14
|
---
|
|
16
15
|
|
|
17
16
|
# go-scaffold
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
Use this skill for generated backend surface only. A new module or endpoint
|
|
19
|
+
must be created with the CLI so route registration, feature-local composition,
|
|
20
|
+
application seams, test stubs, OpenAPI TODOs, and migration markers remain
|
|
21
|
+
consistent. After generation, the product's business logic and domain rules
|
|
22
|
+
are still the engineer's responsibility.
|
|
23
|
+
|
|
24
|
+
This file is emitted at `.claude/skills/go-scaffold/SKILL.md` for Claude Code.
|
|
25
|
+
Agents that do not load `.claude/skills` should follow the root `AGENTS.md`,
|
|
26
|
+
which carries the same project-wide safety contract.
|
|
26
27
|
|
|
27
28
|
## When to use this skill
|
|
28
29
|
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
- A new feature, resource, or domain is requested, even if the user does not
|
|
31
|
+
say "module".
|
|
32
|
+
- A new endpoint or capability is requested on an existing feature, even if
|
|
33
|
+
the user does not say "method".
|
|
34
|
+
- The user explicitly asks to scaffold, generate, or use go-scaffold.
|
|
35
|
+
|
|
36
|
+
Examples that all use this skill:
|
|
37
|
+
|
|
38
|
+
- “add invoices” → a new module
|
|
39
|
+
- “let admins approve orders” → a new method on orders
|
|
40
|
+
- “list overdue invoices” → a GET-all method
|
|
41
|
+
- “look up a user by email” → a GET-one lookup with --field email
|
|
42
|
+
|
|
43
|
+
## When not to use it
|
|
44
|
+
|
|
45
|
+
- Filling in business logic in a method the CLI already generated
|
|
46
|
+
- Editing model fields, DTO validation, or existing method behavior
|
|
47
|
+
- Adding a foreign key or relation between domains
|
|
48
|
+
- Bug fixes, refactors, or architecture work unrelated to generated surface
|
|
49
|
+
- Any repository without go-scaffold.config.json at its root
|
|
50
|
+
|
|
51
|
+
Those tasks still follow AGENTS.md and docs/architect/; this skill does
|
|
52
|
+
not authorize schema, auth/security, dependency, architecture, or deployment
|
|
53
|
+
changes without the project's required owner approval.
|
|
35
54
|
|
|
36
|
-
##
|
|
55
|
+
## Before running the CLI
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
57
|
+
1. Read AGENTS.md, the relevant docs/architect/ notes, and the target
|
|
58
|
+
module's existing handler/service/repository.
|
|
59
|
+
2. Run git status --short --branch and confirm the work is on a dedicated,
|
|
60
|
+
non-protected branch. Preserve unrelated local changes.
|
|
61
|
+
3. Search for an existing module, endpoint, route, and migration before
|
|
62
|
+
generating anything. Do not generate a duplicate name.
|
|
63
|
+
4. Decide which module profile matches the domain. The interactive wizard
|
|
64
|
+
offers Lean (`minimal + service`), CRUD (`crud + service`), CQRS
|
|
65
|
+
(`minimal + cqrs`), and Advanced for an explicit custom combination. Lean
|
|
66
|
+
is the safe starting point when the product only needs specific endpoints;
|
|
67
|
+
CRUD is for an intentional CRUD starter, and CQRS is for genuinely
|
|
68
|
+
different command/query needs. A profile is a generator preset, not a
|
|
69
|
+
claim about the domain's DDD maturity.
|
|
43
70
|
|
|
44
|
-
##
|
|
71
|
+
## Commands
|
|
45
72
|
|
|
46
|
-
Run from the project root:
|
|
73
|
+
Run from the project root and pass every value as a flag:
|
|
47
74
|
|
|
48
|
-
|
|
49
|
-
go-scaffold generate module <name> --
|
|
75
|
+
~~~bash
|
|
76
|
+
go-scaffold generate module <name> [--profile <lean|crud|cqrs>] [--defaults] [--auth] [--permission <code>]
|
|
50
77
|
go-scaffold generate method <module> <name> --type <get|post|put|patch|delete> [--get-mode all|one] [--field <name>]
|
|
51
78
|
go-scaffold generate migration <name>
|
|
79
|
+
go-scaffold config
|
|
80
|
+
go-scaffold add auth --store <postgres|redis> --browser-topology <same-origin|same-site|cross-site> [--defaults] [--yes]
|
|
81
|
+
go-scaffold add worker --queue <postgres|redis> [--defaults]
|
|
52
82
|
go-scaffold undo module <name> -y
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
`
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
83
|
+
~~~
|
|
84
|
+
|
|
85
|
+
Omitted values prompt; in a non-interactive shell that exits 1 without
|
|
86
|
+
writing. For `generate module`, `--defaults` uses the recorded project
|
|
87
|
+
defaults and keeps the module public; fresh and legacy projects resolve to
|
|
88
|
+
Lean (`minimal + service`). `--profile` is the non-interactive equivalent of
|
|
89
|
+
choosing a named architecture preset. The older `--full` and `--cqrs` flags
|
|
90
|
+
remain supported for existing scripts, but cannot be combined with
|
|
91
|
+
`--profile`; `--full` means CRUD surface and `--cqrs` means separate command /
|
|
92
|
+
query handlers. Advanced is a wizard-only choice that asks the two axes
|
|
93
|
+
separately. Add `--auth` and `--permission <code>` only when they are wanted;
|
|
94
|
+
permission also requires `--auth`. `--get-mode` is required with `--type get`;
|
|
95
|
+
`--field` is required for GET-one and cannot be id. `undo` requires
|
|
96
|
+
`-y`. `add auth` accepts `--store postgres|redis` and
|
|
97
|
+
`--browser-topology same-origin|same-site|cross-site`; `--defaults` selects
|
|
98
|
+
Postgres and same-site. Postgres keeps refresh, recovery, and MFA state in
|
|
99
|
+
Postgres and uses an in-process rate limiter. Redis keeps refresh tokens and
|
|
100
|
+
rate-limit counters in Redis, while recovery and MFA state remain durable in
|
|
101
|
+
Postgres. Without `add worker`, auth mail is sent inline; with a worker it uses
|
|
102
|
+
the queue backend chosen by `add worker`.
|
|
103
|
+
|
|
104
|
+
Module names may be singular/plural, mixed case, hyphenated, or underscored.
|
|
105
|
+
The CLI normalizes the Go package, REST path, SQL table, and error-code prefix.
|
|
106
|
+
Reject names that would not compile, such as a leading digit, Go keyword, or
|
|
107
|
+
predeclared type.
|
|
108
|
+
|
|
109
|
+
## Wizard and profile contract
|
|
110
|
+
|
|
111
|
+
`go-scaffold generate module <name>` asks for the module profile even when the
|
|
112
|
+
name is already supplied. If the name is omitted, it asks for the name first.
|
|
113
|
+
Choosing Advanced then asks for module surface and application style
|
|
114
|
+
separately. Auth and permission prompts appear only when the project has the
|
|
115
|
+
corresponding features installed. There is no final confirmation for module
|
|
116
|
+
generation; review the profile and flags before running it.
|
|
117
|
+
|
|
118
|
+
`go-scaffold config` changes defaults for future modules only. Existing modules
|
|
119
|
+
are not rewritten. `config show` and `config validate` are intentionally
|
|
120
|
+
read-only and do not open a wizard. The bare `generate` command first asks
|
|
121
|
+
whether to generate a module, method, or migration; the bare `add` command
|
|
122
|
+
first asks which infrastructure feature to add.
|
|
123
|
+
|
|
124
|
+
## Generated module contract
|
|
125
|
+
|
|
126
|
+
`generate module <name> --defaults` creates a module using the resolved project
|
|
127
|
+
defaults. On a fresh project that is a Lean module with:
|
|
128
|
+
|
|
129
|
+
~~~text
|
|
130
|
+
internal/app/<pkg>/
|
|
131
|
+
model/model.go
|
|
132
|
+
dto.go
|
|
133
|
+
errors.go
|
|
134
|
+
repository.go
|
|
135
|
+
service.go
|
|
136
|
+
composition.go
|
|
137
|
+
handler.go
|
|
138
|
+
service_test.go
|
|
139
|
+
handler_test.go
|
|
140
|
+
repository_test.go
|
|
141
|
+
migrations/<version>_create_<plural>.{up,down}.sql
|
|
142
|
+
~~~
|
|
143
|
+
|
|
144
|
+
The Lean/minimal module has no endpoint yet, but includes the complete
|
|
145
|
+
data-access surface and test seams needed by generate method. The CRUD profile
|
|
146
|
+
adds a CRUD starter and routes list/get/create/update/delete. The CQRS profile
|
|
147
|
+
adds separate commands.go and queries.go application handlers; Advanced can
|
|
148
|
+
combine CRUD + CQRS, or use CQRS alone before adding methods one at a time.
|
|
149
|
+
With OpenAPI enabled it also creates the per-domain documents and updates
|
|
150
|
+
docs/openapi.yaml.
|
|
151
|
+
|
|
152
|
+
Feature-local composition.go constructs repository → application handlers →
|
|
153
|
+
handler. A module generated with `--cqrs` constructs separate command/query
|
|
154
|
+
handlers there; a regular module constructs its service there.
|
|
155
|
+
cmd/api/wiring.go is the process composition root: it selects shared
|
|
156
|
+
infrastructure, supplies explicit cross-feature ports/security dependencies,
|
|
157
|
+
and registers routes. Do not move business rules or feature-internal
|
|
158
|
+
constructors into wiring, or hand-edit root route markers for a new endpoint.
|
|
159
|
+
|
|
160
|
+
## Method shapes and stub safety
|
|
161
|
+
|
|
162
|
+
generate method patches the existing module without overwriting a same-named
|
|
163
|
+
method:
|
|
164
|
+
|
|
165
|
+
| Type | Shape | Generated behavior |
|
|
108
166
|
|---|---|---|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
167
|
+
| get --get-mode all | GET /<plural>/<name> | Reuses FindAll; add real filtering |
|
|
168
|
+
| get --get-mode one --field <f> | GET /<plural>/<f>/:<f> | Adds a repository lookup and test seam |
|
|
169
|
+
| post | POST /<plural>/<name> | Body DTO plus internal-error TODO |
|
|
170
|
+
| put / patch | <VERB> /<plural>/:id/<name> | Safe 501 Not Implemented; no repository read/write |
|
|
171
|
+
| delete | DELETE /<plural>/:id/<name> | Internal-error TODO until implemented |
|
|
172
|
+
|
|
173
|
+
The 501 contract is intentional: a generated PUT/PATCH action must not
|
|
174
|
+
pretend to update a record. Implement the use case, validation, authorization,
|
|
175
|
+
repository operation, tests, and OpenAPI response together before changing
|
|
176
|
+
that behavior. Replace all generated TODOs before treating an endpoint as
|
|
177
|
+
production-ready.
|
|
178
|
+
|
|
179
|
+
## Architecture guidance for generated code
|
|
180
|
+
|
|
181
|
+
This project is modular plus Hexagonal/DDD-friendly, with CQRS available via
|
|
182
|
+
the `cqrs` profile or the backwards-compatible
|
|
183
|
+
`go-scaffold generate module <name> --cqrs` flag:
|
|
184
|
+
|
|
185
|
+
- handlers are inbound adapters and know HTTP/Gin only;
|
|
186
|
+
- services or commands.go/queries.go (and, for complex flows,
|
|
187
|
+
internal/app/<feature>/application/) are application/use-case boundaries and
|
|
188
|
+
depend on narrow ports;
|
|
189
|
+
- models and domain invariants stay independent of Gin, GORM, Redis, and HTTP;
|
|
190
|
+
- repositories/token stores/mail/queue clients are outbound adapters;
|
|
191
|
+
- composition.go wires one feature locally, while cmd/api/wiring.go wires
|
|
192
|
+
the process.
|
|
193
|
+
|
|
194
|
+
DDD does not require every CRUD row to become a large aggregate. CQRS optional:
|
|
195
|
+
use the CQRS profile when command and query models, consistency, or
|
|
196
|
+
read scaling differ; it creates separate command/query handlers while keeping
|
|
197
|
+
one modular monolith and database by default. For simple CRUD, a single
|
|
198
|
+
service and repository port are clearer. Do not add a broker, a second
|
|
199
|
+
database, or empty command/query layers just for naming.
|
|
200
|
+
|
|
201
|
+
Never import another feature's private model, repository, or handler. For
|
|
202
|
+
cross-feature behavior, define a narrow public application port, use a
|
|
203
|
+
documented feature API, or publish an explicit event. Keep dependencies
|
|
204
|
+
flowing inward:
|
|
205
|
+
|
|
206
|
+
~~~text
|
|
207
|
+
handler -> application/use case -> domain + ports -> adapters
|
|
208
|
+
^ |
|
|
209
|
+
+--- composition -+
|
|
210
|
+
~~~
|
|
211
|
+
|
|
212
|
+
The generated auth facade is a pragmatic compatibility boundary: its service
|
|
213
|
+
contracts currently use the feature's `model` values and shared `apperror`,
|
|
214
|
+
while handlers own Gin and provider adapters own SDK configuration. Do not
|
|
215
|
+
broaden that coupling by importing Gin, provider SDKs, or HTTP handlers into
|
|
216
|
+
use cases. New complex flows should prefer transport-neutral DTOs and narrow
|
|
217
|
+
ports; a deeper separation of the existing auth facade is an explicit
|
|
218
|
+
architecture refactor and needs focused tests.
|
|
219
|
+
|
|
220
|
+
## Authentication and browser OAuth contract
|
|
221
|
+
|
|
222
|
+
`add auth` provides email/password authentication and a generic provider
|
|
223
|
+
boundary. For browser OAuth, preserve this client-owned callback contract:
|
|
224
|
+
|
|
225
|
+
- The browser frontend owns one provider callback route. The API exposes
|
|
226
|
+
`GET /auth/{provider}/login` to start authorization and
|
|
227
|
+
`POST /auth/{provider}/exchange` to finish it; do not add an API callback
|
|
228
|
+
route or a server-side browser handoff after the exchange.
|
|
229
|
+
- The frontend creates `state` and an S256 PKCE verifier/challenge. It sends
|
|
230
|
+
`state`, `code_challenge`, and `code_challenge_method=S256` to the login
|
|
231
|
+
endpoint, handles provider success/cancel/error in its callback route, then
|
|
232
|
+
sends `code`, `state`, and `code_verifier` to the exchange endpoint.
|
|
233
|
+
- Provider adapters own OAuth configuration, code exchange, and claims/userinfo
|
|
234
|
+
validation. The application service persists a one-time server-side
|
|
235
|
+
transaction binding provider, state, S256 challenge, and OIDC nonce, then
|
|
236
|
+
consumes it before completion. Application services depend on the
|
|
237
|
+
`LoginProvider` port, provider registry, and normalized identity DTOs; they
|
|
238
|
+
must not branch on Google or import a provider's `oauth2.Config`.
|
|
239
|
+
- The server uses the exact provider-registered redirect URI from environment
|
|
240
|
+
configuration (`GOOGLE_OAUTH_REDIRECT_URI` for the Google adapter), validates
|
|
241
|
+
the provider response plus state, PKCE, and nonce, resolves the local
|
|
242
|
+
identity, creates the local session, sets an HttpOnly refresh cookie, and
|
|
243
|
+
returns JSON. Google is OIDC: validate the signed ID token's issuer,
|
|
244
|
+
audience/authorized party, time claims, nonce, and subject, and reconcile its
|
|
245
|
+
subject with UserInfo. Token responses must use `Cache-Control: no-store` and
|
|
246
|
+
`Pragma: no-cache`.
|
|
247
|
+
Never accept a request-supplied redirect destination or place an access token,
|
|
248
|
+
refresh token, authorization code, or state in a URI.
|
|
249
|
+
- Map provider and validation failures to controlled public codes only:
|
|
250
|
+
`oauth_denied`, `oauth_state_invalid`, `oauth_provider_unavailable`, and
|
|
251
|
+
`oauth_failed`. Do not expose raw provider descriptions or technical causes.
|
|
252
|
+
- `AUTH_BROWSER_TOPOLOGY` is the cookie/CORS deployment policy, not the
|
|
253
|
+
provider redirect configuration. Keep `CORS_ALLOWED_ORIGINS` as an exact
|
|
254
|
+
origin allowlist. Cross-site browser deployments require HTTPS,
|
|
255
|
+
`COOKIE_SAMESITE=none`, and `COOKIE_SECURE=true`; production browser cookies
|
|
256
|
+
always require `COOKIE_SECURE=true`. Because CORS is not CSRF protection,
|
|
257
|
+
cookie-authenticated state-changing endpoints also require an exact allowed
|
|
258
|
+
`Origin` (or equivalent validated `Referer`) when SameSite=None is used.
|
|
259
|
+
- Native/mobile OAuth is out of scope for this scaffold phase. Do not infer a
|
|
260
|
+
native callback or token handoff contract from the browser flow.
|
|
261
|
+
|
|
262
|
+
### MFA contract
|
|
263
|
+
|
|
264
|
+
MFA has two independent switches: an operator capability and per-user
|
|
265
|
+
enrollment. Preserve both:
|
|
266
|
+
|
|
267
|
+
- `AUTH_MFA_ENABLED=false` is the default. When it is false, MFA endpoints
|
|
268
|
+
report unavailable and login does not require a challenge. Turning the
|
|
269
|
+
capability off does not delete stored enrollments; re-enabling it restores
|
|
270
|
+
the policy for users who had already enrolled.
|
|
271
|
+
- When the capability is enabled, the composition root must validate
|
|
272
|
+
`MFA_ENCRYPTION_KEY` as a base64-encoded 32-byte key before serving traffic.
|
|
273
|
+
Keep `MFA_ISSUER`, `MFA_CHALLENGE_TTL_MIN`, `MFA_TOTP_WINDOW`, and
|
|
274
|
+
`MFA_RECOVERY_CODE_COUNT` explicit in configuration; do not silently weaken
|
|
275
|
+
their safety limits.
|
|
276
|
+
- An authenticated user opts in with `GET /users/me/mfa`,
|
|
277
|
+
`POST /users/me/mfa/setup`, and `POST /users/me/mfa/confirm`; disabling it
|
|
278
|
+
requires the current TOTP code at `POST /users/me/mfa/disable`. Setup returns
|
|
279
|
+
the secret/otpauth URI only while pending, and confirmation returns recovery
|
|
280
|
+
codes only once.
|
|
281
|
+
- Password login and provider exchange may return
|
|
282
|
+
`{ "mfa_required": true, "challenge": "..." }` for an enrolled user. This
|
|
283
|
+
is a pre-session response: do not issue an access token or refresh cookie
|
|
284
|
+
until `POST /auth/mfa/verify` succeeds with a TOTP or recovery code.
|
|
285
|
+
- Store only an encrypted TOTP secret, hashed challenges, and hashed recovery
|
|
286
|
+
codes. Challenges are short-lived and one-use; a failed verification also
|
|
287
|
+
consumes its challenge. Recovery codes are atomic one-use login factors and
|
|
288
|
+
must not be accepted for disabling MFA. MFA state is durable in Postgres for
|
|
289
|
+
both refresh-token store choices, so `add auth`'s `create_mfa` migration must
|
|
290
|
+
be applied and included in real-store/concurrency tests.
|
|
291
|
+
|
|
292
|
+
Keep auth construction explicit: `NewService(deps Dependencies, cfg
|
|
293
|
+
AuthConfig)`. `Dependencies` owns narrow ports such as `MFA`, `Providers`,
|
|
294
|
+
`RefreshTokens`, `RecoveryTokens`, `Mailer`, `Roles`, and `Clock`; `AuthConfig`
|
|
295
|
+
owns JWT, OAuth, recovery, and `MFASettings` policy. Do not pass the generated
|
|
296
|
+
shared config package into the application service or replace these contracts
|
|
297
|
+
with a positional constructor.
|
|
298
|
+
|
|
299
|
+
When adding auth from a script, pass the choices explicitly, for example:
|
|
300
|
+
|
|
301
|
+
~~~bash
|
|
302
|
+
go-scaffold add auth --store postgres --browser-topology same-site --defaults --yes
|
|
303
|
+
~~~
|
|
304
|
+
|
|
305
|
+
## Runtime, migration, and security guardrails
|
|
306
|
+
|
|
307
|
+
- APP_ENV is the only environment gate and accepts development or production.
|
|
308
|
+
Do not reintroduce an AUTO_MIGRATE setting.
|
|
309
|
+
- Development may use the convenience db.AutoMigrate(...) bootstrap.
|
|
310
|
+
Production must never bootstrap or mutate schema at API startup. Apply
|
|
311
|
+
versioned SQL with make migrate-up; the startup version check must fail
|
|
312
|
+
fast for a behind or dirty database.
|
|
313
|
+
- Use generate migration for schema changes outside a new module, write and
|
|
314
|
+
review both migration directions, and test them against a disposable DB.
|
|
315
|
+
- Keep SQL, Redis, queue, mail, and other external resources under the
|
|
316
|
+
composition root and close them on every exit path. Preserve
|
|
317
|
+
SIGINT/SIGTERM graceful shutdown and do not call os.Exit from a goroutine.
|
|
318
|
+
- Never log passwords, raw tokens, JWT secrets, OAuth secrets, or auth
|
|
319
|
+
headers. Client responses must not expose technical error causes.
|
|
320
|
+
- Refresh-token rotation is an atomic consume: Postgres uses one SQL unit and
|
|
321
|
+
Redis uses one Lua operation. Preserve active-session cleanup and the
|
|
322
|
+
reuse-detection tombstone; never replace it with read-then-delete.
|
|
323
|
+
- Password reset and email verification consume one-time tokens in the same
|
|
324
|
+
retry-safe transaction as the identity/user update. Preserve recovery on a
|
|
325
|
+
post-commit session-revocation failure.
|
|
326
|
+
- Wrap technical causes with %w or apperror.NewInternal(cause). Middleware
|
|
327
|
+
logs the cause with request_id; production strips it from the response.
|
|
328
|
+
|
|
329
|
+
## Verification
|
|
330
|
+
|
|
331
|
+
After changing generated Go code or templates, run the relevant checks:
|
|
332
|
+
|
|
333
|
+
~~~bash
|
|
334
|
+
gofmt -w <changed-go-files>
|
|
335
|
+
go test ./...
|
|
336
|
+
go test -race ./... # required for concurrency/auth/token changes
|
|
337
|
+
go vet ./...
|
|
338
|
+
golangci-lint run
|
|
339
|
+
~~~
|
|
340
|
+
|
|
341
|
+
For repository, schema, or auth adapter changes, use a migrated disposable
|
|
342
|
+
PostgreSQL/Redis instance and set REQUIRE_TEST_DB=true or
|
|
343
|
+
REQUIRE_TEST_REDIS=true; a skipped integration test is not proof. MFA state
|
|
344
|
+
still requires PostgreSQL even when refresh tokens use Redis. When routes or
|
|
345
|
+
OpenAPI templates change, inspect the updated `docs/openapi.yaml` and run the
|
|
346
|
+
configured OpenAPI linter.
|
|
347
|
+
|
|
348
|
+
Only in the `go-scaffold` source repository, run `pnpm run verify` for CLI or
|
|
349
|
+
template changes, then generate a fresh sample project and inspect both its
|
|
350
|
+
source and guidance files. A generated Go project does not contain the
|
|
351
|
+
generator's `package.json` or pnpm scripts; from that project run the Go checks
|
|
352
|
+
above and use the installed/local CLI for generation checks.
|
|
353
|
+
Review git diff --check, git diff, and git status --short before handoff,
|
|
354
|
+
and report exact output plus known limitations.
|
|
355
|
+
|
|
356
|
+
## Migration and undo
|
|
357
|
+
|
|
358
|
+
generate migration <name> reserves a timestamped .up.sql/.down.sql pair; the
|
|
359
|
+
engineer writes the actual SQL. undo module <name> -y is only for an unshipped
|
|
360
|
+
generated module and refuses if its migration is committed or applied. Retire
|
|
361
|
+
a shipped domain with a reviewed drop migration and data plan, not undo.
|