@nakedev/go-scaffold 0.4.0 → 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 +42 -1
- 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/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 +42 -14
- package/templates/create/base/cmd/api/wiring.go.hbs +11 -8
- 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 +35 -13
- package/templates/create/features/docs/patterns.md.hbs +40 -21
- package/templates/create/features/docs/techstack.md.hbs +2 -2
- 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
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ A CLI that scaffolds a Gin + GORM + PostgreSQL Go backend, then keeps
|
|
|
4
4
|
generating consistent domain modules into that project as it grows — the Go
|
|
5
5
|
counterpart to nest-scaffold.
|
|
6
6
|
|
|
7
|
-
You don't hand-wire a new domain
|
|
7
|
+
You don't hand-wire a new domain's repository/service/handler composition into
|
|
8
|
+
`cmd/api/wiring.go`, write the
|
|
8
9
|
handler/service/repository boilerplate, or decide error-handling conventions
|
|
9
10
|
each time — the CLI does that, and every module it generates follows the
|
|
10
11
|
same shape as the last one.
|
|
@@ -33,6 +34,84 @@ node bin/go-scaffold.js create my-api --defaults
|
|
|
33
34
|
depending on your machine's npm/pnpm global-bin config — running
|
|
34
35
|
`node bin/go-scaffold.js ...` directly sidesteps that.
|
|
35
36
|
|
|
37
|
+
## Release: npm is locked to Git
|
|
38
|
+
|
|
39
|
+
Git is the release source of truth. The package version, annotated tag, and
|
|
40
|
+
tagged commit must agree before npm can publish:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
package.json 0.4.3
|
|
44
|
+
│
|
|
45
|
+
└── annotated tag v0.4.3 ──> the exact release commit
|
|
46
|
+
│
|
|
47
|
+
└── npm @nakedev/go-scaffold@0.4.3
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`npm publish` runs `release:check` first, so publishing from an untagged,
|
|
51
|
+
lightweight-tagged, dirty, or mismatched checkout fails before anything is
|
|
52
|
+
sent to npm. The GitHub Actions workflow at
|
|
53
|
+
`.github/workflows/release.yml` repeats that check, runs the full verification
|
|
54
|
+
gate, publishes from the tag, and creates the GitHub Release.
|
|
55
|
+
|
|
56
|
+
### One-time npm setup
|
|
57
|
+
|
|
58
|
+
The preferred setup is npm Trusted Publishing with GitHub Actions (OIDC):
|
|
59
|
+
|
|
60
|
+
1. In npm package settings for `@nakedev/go-scaffold`, add a GitHub Actions
|
|
61
|
+
trusted publisher for `NakePranob/go-scaffold`.
|
|
62
|
+
2. Set the workflow filename to `release.yml` and environment to
|
|
63
|
+
`npm-release`.
|
|
64
|
+
3. Allow `npm publish`. The workflow already requests the required OIDC
|
|
65
|
+
permission and uses Node 24.
|
|
66
|
+
|
|
67
|
+
If Trusted Publishing is not used, add an npm granular token as the GitHub
|
|
68
|
+
repository/environment secret `NPM_TOKEN`. Never commit a token or put it in
|
|
69
|
+
`.npmrc`.
|
|
70
|
+
|
|
71
|
+
### Branch policy
|
|
72
|
+
|
|
73
|
+
`develop` is the integration branch. A PR into `main` must come from
|
|
74
|
+
`develop` or `release/*`, and must bump `package.json` to a semver greater than
|
|
75
|
+
the version on `main`. PRs into `develop` do not need a version bump.
|
|
76
|
+
|
|
77
|
+
The `main-merge-policy` workflow checks this automatically. In GitHub branch
|
|
78
|
+
rules, protect `main`, require a pull request, require the
|
|
79
|
+
`main-merge-policy`, `verify`, and `packaged-artifact` checks, and disable
|
|
80
|
+
force-pushes. Protect the `v*` tag pattern from updates and deletion as well.
|
|
81
|
+
|
|
82
|
+
### Release flow
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git switch develop
|
|
86
|
+
git pull --ff-only origin develop
|
|
87
|
+
git switch -c release/v0.4.3
|
|
88
|
+
npm version 0.4.3 --no-git-tag-version
|
|
89
|
+
pnpm run verify
|
|
90
|
+
git add package.json
|
|
91
|
+
git commit -m "chore: release v0.4.3"
|
|
92
|
+
git push -u origin release/v0.4.3
|
|
93
|
+
# open a PR from release/v0.4.3 into main
|
|
94
|
+
|
|
95
|
+
# after the PR is merged, tag the merge commit on main
|
|
96
|
+
git switch main
|
|
97
|
+
git pull --ff-only origin main
|
|
98
|
+
git tag -a v0.4.3 -m "v0.4.3"
|
|
99
|
+
git push origin v0.4.3
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Pushing the tag starts the release workflow. To retry a failed publish for a
|
|
103
|
+
tag created after this workflow landed, use GitHub Actions' **Run workflow**
|
|
104
|
+
with that tag; do not create a second tag for the same package version. A tag
|
|
105
|
+
created before this guard existed should not be moved after it has been
|
|
106
|
+
pushed — use the next patch version instead.
|
|
107
|
+
|
|
108
|
+
## Requirements
|
|
109
|
+
|
|
110
|
+
- Node.js `>=22.13` to run the CLI
|
|
111
|
+
- Go `>=1.25` for the generated project
|
|
112
|
+
- A running PostgreSQL instance; Docker is optional, but either `psql` or a
|
|
113
|
+
Docker container is needed by `make db-create`
|
|
114
|
+
|
|
36
115
|
## Quick start
|
|
37
116
|
|
|
38
117
|
```bash
|
|
@@ -51,12 +130,60 @@ go-scaffold generate module orders
|
|
|
51
130
|
go-scaffold generate method orders approve --type patch
|
|
52
131
|
```
|
|
53
132
|
|
|
133
|
+
## The 30-second flow
|
|
134
|
+
|
|
135
|
+
The CLI has two layers of interaction: the command you choose, then only the
|
|
136
|
+
questions that command still needs. Flags are answers, not requests to ask the
|
|
137
|
+
same question again.
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
go-scaffold create my-api
|
|
141
|
+
1. Docker + PostgreSQL?
|
|
142
|
+
2. OpenAPI files?
|
|
143
|
+
3. Metrics + tracing?
|
|
144
|
+
4. API route prefix?
|
|
145
|
+
5. Default module profile: Lean / CRUD / CQRS / Advanced?
|
|
146
|
+
6. Summary confirmation
|
|
147
|
+
|
|
148
|
+
cd my-api
|
|
149
|
+
go-scaffold generate module users
|
|
150
|
+
1. Module profile: Lean / CRUD / CQRS / Advanced?
|
|
151
|
+
2. Require an access token? # only when auth is installed
|
|
152
|
+
3. Permission code? # only when auth + RBAC are installed
|
|
153
|
+
-> writes internal/app/user/, its migration, wiring, and config metadata
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`Advanced` is the only path that asks the two lower-level architecture
|
|
157
|
+
questions separately. `--profile` and `--defaults` are the scripted forms; the
|
|
158
|
+
latter uses the project defaults recorded in `go-scaffold.config.json`.
|
|
159
|
+
|
|
54
160
|
## Commands
|
|
55
161
|
|
|
56
162
|
Every `add` command shows what it's about to do and asks before writing;
|
|
57
163
|
`-y/--yes` skips that (and `--defaults` implies it) for CI and scripts.
|
|
58
164
|
Running `go-scaffold` with no arguments picks the command from a menu.
|
|
59
165
|
|
|
166
|
+
### Wizard coverage
|
|
167
|
+
|
|
168
|
+
The interactive path is deliberately available from both the bare command and
|
|
169
|
+
the direct command form:
|
|
170
|
+
|
|
171
|
+
| Command | Wizard coverage |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `create [name]` | asks for the project name and settings that were not passed as flags |
|
|
174
|
+
| `generate` / `generate module [name]` | chooses a target, module name, and module profile; `Advanced` asks the two underlying architecture questions |
|
|
175
|
+
| `generate method [module] [name]` | asks for the existing module, method name, HTTP verb, GET mode, and lookup field when needed |
|
|
176
|
+
| `generate migration [name]` | asks for the migration name when omitted |
|
|
177
|
+
| `config` | edits future module defaults; existing modules are unchanged |
|
|
178
|
+
| `config show` / `config validate` | intentionally no wizard: read-only print/validation commands |
|
|
179
|
+
| `add` / `add worker` / `add auth` | chooses the feature, backend/topology, and confirmation where applicable |
|
|
180
|
+
| `add rbac` / `add observability` | no parameter choice is needed; the direct command confirms, while bare `add` selects the target |
|
|
181
|
+
| `undo` / `undo module [name]` | asks for the generated module and confirmation when omitted |
|
|
182
|
+
|
|
183
|
+
Run any command with `--help` for the non-interactive equivalent. If a value is
|
|
184
|
+
omitted in a non-TTY shell, the CLI exits before writing and tells you which
|
|
185
|
+
flag or `--defaults` is required.
|
|
186
|
+
|
|
60
187
|
|
|
61
188
|
### `create <name>` — scaffold a new project
|
|
62
189
|
|
|
@@ -74,15 +201,21 @@ with `generate module`.
|
|
|
74
201
|
|
|
75
202
|
| Option | Effect |
|
|
76
203
|
|---|---|
|
|
77
|
-
| `--defaults` | Skip
|
|
78
|
-
| `--no-docker` |
|
|
79
|
-
| `--no-openapi-docs` |
|
|
80
|
-
| `--observability` | Prometheus `/metrics` + OpenTelemetry tracing
|
|
81
|
-
| `--api-prefix <prefix>` |
|
|
82
|
-
|
|
83
|
-
|
|
204
|
+
| `--defaults` | Skip settings prompts; use Docker/OpenAPI on, no prefix, and Lean (`minimal + service`) defaults for future modules |
|
|
205
|
+
| `--no-docker` | Do not create `docker-compose.yml` or include a local Postgres service |
|
|
206
|
+
| `--no-openapi-docs` | Do not create `docs/openapi.yaml` or per-module OpenAPI files |
|
|
207
|
+
| `--observability` | Include Prometheus `/metrics` + OpenTelemetry tracing; off unless passed |
|
|
208
|
+
| `--api-prefix <prefix>` | Group every API route under a prefix such as `v1` or `api/v1`; omit for no prefix |
|
|
209
|
+
| `--module-profile <lean\|crud\|cqrs>` | Default profile for future modules; replaces the two architecture questions |
|
|
210
|
+
| `--module-surface <minimal\|crud>` | Legacy axis flag for future modules; use `--module-profile` for a clearer preset |
|
|
211
|
+
| `--application-style <service\|cqrs>` | Legacy axis flag for future modules; use `--module-profile` for a clearer preset |
|
|
212
|
+
|
|
213
|
+
Without `--defaults`, an interactive wizard asks the project questions —
|
|
84
214
|
skipping any a flag already answered, so `create my-api --no-docker` never asks
|
|
85
|
-
about Docker and never scaffolds it.
|
|
215
|
+
about Docker and never scaffolds it. It also asks for the default module
|
|
216
|
+
profile so future `generate module` commands start with the project's
|
|
217
|
+
conventions. Choose `Advanced` when you intentionally want the less common
|
|
218
|
+
CRUD + CQRS combination.
|
|
86
219
|
The prefix is a single project-wide choice made once at `create` time —
|
|
87
220
|
there's no per-domain versioning (a domain that needs a real breaking change
|
|
88
221
|
gets a new domain package or a new DTO field, not a duplicated model pointed
|
|
@@ -91,18 +224,72 @@ below).
|
|
|
91
224
|
|
|
92
225
|
**Config file** — every `create` writes `go-scaffold.config.json` to the
|
|
93
226
|
project root; `generate` reads it back (or auto-detects from `go.mod` /
|
|
94
|
-
directory layout if missing).
|
|
227
|
+
directory layout if missing). It records project defaults and the resolved
|
|
228
|
+
surface/application style of each generated module:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"schemaVersion": 1,
|
|
233
|
+
"architecture": {
|
|
234
|
+
"style": "modular-monolith",
|
|
235
|
+
"defaultModuleSurface": "minimal",
|
|
236
|
+
"defaultApplicationStyle": "service"
|
|
237
|
+
},
|
|
238
|
+
"modules": {
|
|
239
|
+
"order": { "surface": "crud", "applicationStyle": "cqrs" }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Run these from the generated project root. Use the wizard again later without
|
|
245
|
+
recreating the project:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
go-scaffold config # interactive project-default wizard
|
|
249
|
+
go-scaffold config show # print the resolved config
|
|
250
|
+
go-scaffold config validate # validate without changing anything
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Module profiles
|
|
254
|
+
|
|
255
|
+
The wizard asks for one useful profile instead of forcing everyone to reason
|
|
256
|
+
about two implementation axes up front:
|
|
257
|
+
|
|
258
|
+
| Profile | Resolves to | Use it when |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| `lean` | minimal surface + one service | the domain should start small and grow endpoint by endpoint |
|
|
261
|
+
| `crud` | CRUD surface + one service | the domain genuinely needs the standard list/get/create/update/delete starter |
|
|
262
|
+
| `cqrs` | minimal surface + command/query handlers | reads and writes have different business models, invariants, or scaling pressure |
|
|
263
|
+
| `Advanced` (wizard only) | choose both axes separately | you deliberately want a custom mix, including CRUD + CQRS |
|
|
264
|
+
|
|
265
|
+
The scaffold is DDD-shaped rather than a complete tactical DDD implementation:
|
|
266
|
+
it gives each domain a package boundary, repository port, application boundary,
|
|
267
|
+
feature-local composition, and shared error conventions. It does not invent
|
|
268
|
+
aggregates, value objects, domain events, or business invariants for you; those
|
|
269
|
+
belong to the domain team.
|
|
270
|
+
|
|
271
|
+
`minimal` means the generator does not invent five endpoints before the domain
|
|
272
|
+
has real requirements. `CQRS` does not mean a second database, broker, or event
|
|
273
|
+
bus here. It only separates command and query application handlers inside the
|
|
274
|
+
same modular monolith.
|
|
95
275
|
|
|
96
276
|
### `generate module <name>` (alias `m`) — add a domain module
|
|
97
277
|
|
|
98
278
|
```bash
|
|
99
|
-
go-scaffold generate module orders # asks for
|
|
100
|
-
go-scaffold generate module orders --
|
|
101
|
-
go-scaffold generate module orders --
|
|
279
|
+
go-scaffold generate module orders # asks for profile (and auth, if installed)
|
|
280
|
+
go-scaffold generate module orders --profile lean # explicit Lean profile, no architecture prompt
|
|
281
|
+
go-scaffold generate module orders --profile crud # explicit CRUD profile, no architecture prompt
|
|
282
|
+
go-scaffold generate module orders --profile cqrs # explicit CQRS profile, no architecture prompt
|
|
283
|
+
go-scaffold generate module orders --full --cqrs # legacy flags: CRUD + separate command/query handlers
|
|
284
|
+
go-scaffold generate module orders --defaults # use project defaults, no prompt (CI/scripting)
|
|
102
285
|
```
|
|
103
286
|
|
|
104
|
-
Anything you don't pass as a flag is asked for;
|
|
105
|
-
|
|
287
|
+
Anything you don't pass as a flag is asked for; the prompt starts with the
|
|
288
|
+
project defaults. `--defaults` uses those defaults without asking anything
|
|
289
|
+
(fresh and legacy projects default to Lean, with no auth). `--profile` is the
|
|
290
|
+
non-interactive equivalent of choosing a named profile for this module. The
|
|
291
|
+
older `--full` and `--cqrs` flags remain supported for existing scripts; do not
|
|
292
|
+
combine them with `--profile`.
|
|
106
293
|
|
|
107
294
|
`--full` scaffolds:
|
|
108
295
|
|
|
@@ -113,12 +300,30 @@ internal/app/order/
|
|
|
113
300
|
├── errors.go # ORDER_NOT_FOUND / ORDER_CONFLICT / ORDER_HAS_REFERENCES / ORDER_STALE
|
|
114
301
|
├── repository.go # GORM data access
|
|
115
302
|
├── service.go # business logic + repository interface (mockable)
|
|
303
|
+
├── composition.go # feature-local repository → service → handler wiring
|
|
116
304
|
├── handler.go # Gin routes, registered under the project's API prefix
|
|
117
305
|
├── service_test.go # unit test, function-backed repository stub
|
|
118
306
|
├── handler_test.go # HTTP unit test, service stub, no DB
|
|
119
307
|
└── repository_test.go # Postgres integration test against migrated schema
|
|
120
308
|
```
|
|
121
309
|
|
|
310
|
+
With `--cqrs`, the module also adds separate command/query application files:
|
|
311
|
+
|
|
312
|
+
```text
|
|
313
|
+
internal/app/order/
|
|
314
|
+
├── commands.go # command port + state-changing application handlers
|
|
315
|
+
├── queries.go # query port + read-only application handlers
|
|
316
|
+
├── service.go # compatibility facade; new wiring uses both handlers
|
|
317
|
+
├── composition.go # constructs command/query handlers separately
|
|
318
|
+
└── cqrs_test.go # command/query boundary tests
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`--cqrs` works with both minimal and `--full` modules. It keeps one modular
|
|
322
|
+
monolith and one database by default; CQRS here means separate application
|
|
323
|
+
paths, not mandatory separate databases, brokers, or event sourcing. The
|
|
324
|
+
default remains the simpler layered module because an empty command/query
|
|
325
|
+
split adds ceremony without a business reason.
|
|
326
|
+
|
|
122
327
|
The default minimal mode scaffolds the same `model`/`errors`/`repository` (so `generate
|
|
123
328
|
method` always has a full data-access surface to call), but `dto`/`service`/
|
|
124
329
|
`handler` start empty — no default CRUD, no routes, just the plumbing
|
|
@@ -128,13 +333,15 @@ surface, or you'd rather add endpoints one at a time.
|
|
|
128
333
|
|
|
129
334
|
Both modes also:
|
|
130
335
|
|
|
131
|
-
- Register the module
|
|
132
|
-
|
|
133
|
-
wires an empty route group
|
|
336
|
+
- Register the module through its feature-local composition and the root
|
|
337
|
+
registration markers in `cmd/api/wiring.go` — full wires an actual route,
|
|
338
|
+
minimal wires an empty route group
|
|
134
339
|
- Create the module's own Postgres schema (`<module>_svc`, e.g. `order_svc`)
|
|
135
|
-
and add the model to the
|
|
340
|
+
and add the model to the development schema bootstrap
|
|
136
341
|
- Append `migrations/<timestamp>_create_<plural>.{up,down}.sql`, which creates that
|
|
137
|
-
same schema for
|
|
342
|
+
same schema for production
|
|
343
|
+
- Record the resolved `minimal|crud` and `service|cqrs` choices in
|
|
344
|
+
`go-scaffold.config.json`; changing project defaults does not rewrite existing modules
|
|
138
345
|
|
|
139
346
|
What it does **not** do: invent your fields or wire foreign keys between
|
|
140
347
|
domains — see `docs/architect/patterns.md` in the generated project for the
|
|
@@ -148,9 +355,12 @@ go-scaffold generate method orders findByStatus --type get --get-mode one --fiel
|
|
|
148
355
|
go-scaffold g me orders findOverdue --type get --get-mode all
|
|
149
356
|
```
|
|
150
357
|
|
|
151
|
-
Patches an *existing* module's `handler.go`/`service.go` in place
|
|
152
|
-
|
|
153
|
-
|
|
358
|
+
Patches an *existing* module's `handler.go`/`service.go` in place at the
|
|
359
|
+
`// go-scaffold:*` markers — never a whole new module. Never overwrites a method
|
|
360
|
+
with the same name; pick a different one or the command errors.
|
|
361
|
+
For a module generated with `--cqrs`, it also patches `commands.go` for
|
|
362
|
+
state-changing endpoints and `queries.go` for read endpoints, while keeping
|
|
363
|
+
the compatibility facade in sync.
|
|
154
364
|
|
|
155
365
|
| Option | Effect |
|
|
156
366
|
|---|---|
|
|
@@ -185,8 +395,8 @@ the generated code doesn't compile, but this project was fine a moment ago.
|
|
|
185
395
|
The most likely cause is drift: this project's internal/shared layer has been edited
|
|
186
396
|
since it was scaffolded, so the templates this CLI emits no longer match it.
|
|
187
397
|
|
|
188
|
-
scaffolded with: go-scaffold
|
|
189
|
-
this CLI: go-scaffold
|
|
398
|
+
scaffolded with: go-scaffold <project-version>
|
|
399
|
+
this CLI: go-scaffold <cli-version>
|
|
190
400
|
```
|
|
191
401
|
|
|
192
402
|
That happens because `generate`'s templates are written against the `shared/`
|
|
@@ -224,7 +434,6 @@ async email delivery, and `cmd/worker`.
|
|
|
224
434
|
| Extra service to run | none | Redis |
|
|
225
435
|
| Needed by `add auth` | no | no — `add auth --store` decides that separately |
|
|
226
436
|
| Enqueue joins your DB transaction | yes | **no** — needs an outbox |
|
|
227
|
-
| Throughput | thousands/sec | tens of thousands/sec |
|
|
228
437
|
| Inspect pending jobs | plain SQL | asynqmon |
|
|
229
438
|
|
|
230
439
|
The default is Postgres because a job enqueued inside `tx.Do` is then only
|
|
@@ -251,26 +460,51 @@ tx.Do(ctx, db, func(ctx context.Context) error {
|
|
|
251
460
|
### `add auth` — add email/password authentication
|
|
252
461
|
|
|
253
462
|
```bash
|
|
254
|
-
go-scaffold add auth # asks
|
|
463
|
+
go-scaffold add auth # asks token store + browser topology, then confirms
|
|
255
464
|
go-scaffold add auth --store postgres # tokens in Postgres, no extra service
|
|
256
465
|
go-scaffold add auth --store redis # tokens in Redis, exact across replicas
|
|
257
|
-
go-scaffold add auth --defaults # Postgres
|
|
466
|
+
go-scaffold add auth --defaults # Postgres + local same-site topology (CI/scripting)
|
|
467
|
+
go-scaffold add auth --browser-topology cross-site --yes
|
|
258
468
|
```
|
|
259
469
|
|
|
260
470
|
Adds JWT access tokens, refresh-token rotation with reuse detection,
|
|
261
471
|
registration/login/logout, password reset, email verification, failed-login
|
|
262
|
-
lockout, and
|
|
263
|
-
|
|
264
|
-
`migrate up
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
472
|
+
lockout, and generic provider OAuth routes (Google is the first adapter). Apply
|
|
473
|
+
the generated migrations. Development may bootstrap tables for convenience;
|
|
474
|
+
production must run `migrate up` first.
|
|
475
|
+
|
|
476
|
+
The browser frontend owns its single provider callback route. It generates
|
|
477
|
+
`state` and an S256 PKCE verifier/challenge, starts
|
|
478
|
+
`GET /auth/{provider}/login`, handles both success and provider-cancel/error
|
|
479
|
+
responses in that route, then sends `code`, `state`, and `code_verifier` to
|
|
480
|
+
`POST /auth/{provider}/exchange`. The API uses the exact
|
|
481
|
+
`GOOGLE_OAUTH_REDIRECT_URI` registered with the provider, creates the local
|
|
482
|
+
session, sets the HttpOnly refresh cookie, and returns JSON. The backend also
|
|
483
|
+
consumes a one-time transaction binding provider, state, S256 challenge, and
|
|
484
|
+
OIDC nonce before completing the exchange. It never accepts a
|
|
485
|
+
request-supplied `redirect_uri`/`return_to`, redirects to a configured frontend
|
|
486
|
+
URL, or places tokens/code/state in a URI. Native/mobile flow is out of scope
|
|
487
|
+
for this scaffold phase.
|
|
488
|
+
|
|
489
|
+
`AUTH_BROWSER_TOPOLOGY` is only the cookie/CORS deployment policy, separate
|
|
490
|
+
from the provider redirect URI. For a genuinely cross-site frontend use
|
|
491
|
+
`--browser-topology cross-site`, deploy over HTTPS, set
|
|
492
|
+
`COOKIE_SAMESITE=none` and `COOKIE_SECURE=true`, and add the exact frontend
|
|
493
|
+
origin to `CORS_ALLOWED_ORIGINS` separately. SameSite=None requests also pass
|
|
494
|
+
an exact Origin guard because CORS alone is not CSRF protection. Token
|
|
495
|
+
responses use `Cache-Control: no-store` and `Pragma: no-cache`; configure
|
|
496
|
+
`JWT_REFRESH_MAX_TTL_MIN` so refresh rotation cannot extend beyond its absolute
|
|
497
|
+
lifetime.
|
|
498
|
+
|
|
499
|
+
No prerequisites. On a project with no worker, the registration-verification,
|
|
500
|
+
resend-verification, and password-reset mail flows are sent inline, and `add
|
|
501
|
+
worker` later moves them onto the queue for you. Until then, those auth flows
|
|
502
|
+
block on SMTP when a mail server is configured.
|
|
503
|
+
|
|
504
|
+
| `--store` | Refresh/recovery tokens and rate-limit counters | Extra service |
|
|
271
505
|
|---|---|---|
|
|
272
506
|
| `postgres` (default) | `user_svc.auth_tokens`, counters in-process | none |
|
|
273
|
-
| `redis` | Redis | Redis |
|
|
507
|
+
| `redis` | refresh + rate-limit counters in Redis; recovery in `user_svc.auth_tokens` | Redis |
|
|
274
508
|
|
|
275
509
|
The rate limiter follows the store rather than being chosen separately, because
|
|
276
510
|
"I want this exact across replicas" is one decision. With `postgres` the per-IP
|
|
@@ -286,8 +520,8 @@ go-scaffold generate module secrets --auth --permission secret:manage
|
|
|
286
520
|
|
|
287
521
|
Requires `add auth`. Adds role/permission administration, cached authorization
|
|
288
522
|
middleware, and role assignment. Its migration seeds the default roles and
|
|
289
|
-
permissions, so apply it with `migrate up
|
|
290
|
-
|
|
523
|
+
permissions, so apply it with `migrate up`; table creation does not run SQL
|
|
524
|
+
seed statements.
|
|
291
525
|
|
|
292
526
|
### `add observability` — add metrics + tracing
|
|
293
527
|
|
|
@@ -313,8 +547,8 @@ go-scaffold undo m orders --yes # skip the confirm
|
|
|
313
547
|
The inverse of `generate module`, for the case it's actually the inverse of:
|
|
314
548
|
a module you didn't mean to generate — a typo'd name, a domain you decided
|
|
315
549
|
against. It deletes `internal/app/<name>/`, the per-module docs folder, **and
|
|
316
|
-
the module's migration files**, and reverses the import/
|
|
317
|
-
`
|
|
550
|
+
the module's migration files**, and reverses the import/bootstrap/route in
|
|
551
|
+
`cmd/api/wiring.go` plus the paths/schemas in `docs/openapi.yaml`. Restores the
|
|
318
552
|
`_ = api` placeholder if it was the last module, so the project still builds.
|
|
319
553
|
|
|
320
554
|
Deleting the migrations is the point. `migrations/embed.go` is a `//go:embed
|
|
@@ -331,9 +565,10 @@ working tree, so `undo` proves it first and refuses loudly otherwise:
|
|
|
331
565
|
`migrate ... down` first, then try again.
|
|
332
566
|
|
|
333
567
|
The table itself is never dropped either way — `undo` only reverses what the
|
|
334
|
-
CLI wrote. Prefer it to hand-deleting the folder: it also un-wires
|
|
335
|
-
`.golangci.yml` and the OpenAPI index, and it refuses when
|
|
336
|
-
still imports this one rather than leaving you an un-compilable
|
|
568
|
+
CLI wrote. Prefer it to hand-deleting the folder: it also un-wires
|
|
569
|
+
`cmd/api/wiring.go`, `.golangci.yml` and the OpenAPI index, and it refuses when
|
|
570
|
+
another domain still imports this one rather than leaving you an un-compilable
|
|
571
|
+
project.
|
|
337
572
|
|
|
338
573
|
## Why no per-domain versioning
|
|
339
574
|
|
|
@@ -343,8 +578,8 @@ twice with different behavior. It was cut: the migration (and usually the
|
|
|
343
578
|
DB table) is shared between "versions" of the same domain, but each version
|
|
344
579
|
got its own physically-copied `model.go` — nothing stopped the two structs
|
|
345
580
|
from drifting apart. Verified against a real Postgres instance:
|
|
346
|
-
|
|
347
|
-
and `float64` in the other for the *same* column, converging it to
|
|
581
|
+
development schema bootstrapping silently accepted a column typed `int` in
|
|
582
|
+
one version's model and `float64` in the other for the *same* column, converging it to
|
|
348
583
|
`numeric` with no error — the two versions would then read/write the same
|
|
349
584
|
data with different, silently incompatible interpretations.
|
|
350
585
|
|
|
@@ -357,7 +592,9 @@ out of sync with.
|
|
|
357
592
|
## Project structure produced by `create`
|
|
358
593
|
|
|
359
594
|
```text
|
|
360
|
-
cmd/api/
|
|
595
|
+
cmd/api/
|
|
596
|
+
├── main.go # process entry point and exit handling
|
|
597
|
+
└── wiring.go # composition root: infrastructure + domain registration
|
|
361
598
|
internal/
|
|
362
599
|
├── platform/database/
|
|
363
600
|
├── shared/{config,apperror,dberr,httpx,id,middleware,pagination,tx}/
|
|
@@ -383,8 +620,9 @@ go-scaffold.config.json
|
|
|
383
620
|
|
|
384
621
|
## Supported stack
|
|
385
622
|
|
|
386
|
-
Pinned in the generated `go.mod` — this table mirrors
|
|
387
|
-
`templates/create/base/go.mod.hbs`, which is the source of truth.
|
|
623
|
+
Pinned base dependencies in the generated `go.mod` — this table mirrors
|
|
624
|
+
`templates/create/base/go.mod.hbs`, which is the source of truth. `add auth`,
|
|
625
|
+
`add worker`, and `add observability` append their optional dependencies.
|
|
388
626
|
|
|
389
627
|
| Package | Version |
|
|
390
628
|
|---|---|
|
package/dist/commands/auth.js
CHANGED
|
@@ -18,6 +18,7 @@ const migrations_1 = require("../utils/migrations");
|
|
|
18
18
|
const openapi_patcher_1 = require("../utils/openapi-patcher");
|
|
19
19
|
const gocheck_1 = require("../utils/gocheck");
|
|
20
20
|
const gomod_patcher_1 = require("../utils/gomod-patcher");
|
|
21
|
+
const auth_wizard_1 = require("../prompts/auth-wizard");
|
|
21
22
|
// URL (relative to the api prefix) -> docs file (relative to docs/) for every
|
|
22
23
|
// route `add auth` registers — kept next to AUTH_FILES's route list so the
|
|
23
24
|
// two are easy to eyeball together when a route changes.
|
|
@@ -29,20 +30,26 @@ const AUTH_OPENAPI_PATHS = [
|
|
|
29
30
|
{ urlPath: "/auth/forgot-password", file: "./auth/forgot-password.yaml" },
|
|
30
31
|
{ urlPath: "/auth/reset-password", file: "./auth/reset-password.yaml" },
|
|
31
32
|
{ urlPath: "/auth/verify-email", file: "./auth/verify-email.yaml" },
|
|
32
|
-
{ urlPath: "/auth/
|
|
33
|
-
{ urlPath: "/auth/
|
|
33
|
+
{ urlPath: "/auth/{provider}/login", file: "./auth/provider-login.yaml" },
|
|
34
|
+
{ urlPath: "/auth/{provider}/exchange", file: "./auth/provider-exchange.yaml" },
|
|
34
35
|
{ urlPath: "/users/me", file: "./auth/users-me.yaml" },
|
|
35
36
|
{ urlPath: "/users/me/resend-verification", file: "./auth/users-me-resend-verification.yaml" },
|
|
36
37
|
{ urlPath: "/users/me/logout-all", file: "./auth/users-me-logout-all.yaml" },
|
|
38
|
+
{ urlPath: "/users/me/mfa", file: "./auth/users-me-mfa.yaml" },
|
|
39
|
+
{ urlPath: "/users/me/mfa/setup", file: "./auth/users-me-mfa-setup.yaml" },
|
|
40
|
+
{ urlPath: "/users/me/mfa/confirm", file: "./auth/users-me-mfa-confirm.yaml" },
|
|
41
|
+
{ urlPath: "/users/me/mfa/disable", file: "./auth/users-me-mfa-disable.yaml" },
|
|
42
|
+
{ urlPath: "/auth/mfa/verify", file: "./auth/mfa-verify.yaml" },
|
|
37
43
|
];
|
|
38
44
|
// addAuth scaffolds email/password authentication: a users+identities model
|
|
39
|
-
// pair, JWT access tokens, a
|
|
45
|
+
// pair, JWT access tokens, a selectable refresh token store with
|
|
40
46
|
// rotation + reuse detection, and register/login/refresh/logout/me. No RBAC
|
|
41
47
|
// (no roles/permissions) — that's a separate opt-in on top of this, since
|
|
42
48
|
// most projects need "is this caller logged in" long before they need "can
|
|
43
49
|
// this caller do X".
|
|
44
|
-
async function addAuth(store = "postgres", projectDir = process.cwd()) {
|
|
50
|
+
async function addAuth(store = "postgres", projectDir = process.cwd(), browserTopology = auth_wizard_1.DEFAULT_BROWSER_TOPOLOGY) {
|
|
45
51
|
const config = (0, config_1.readConfig)(projectDir);
|
|
52
|
+
const browser = (0, auth_wizard_1.validateBrowserTopology)(browserTopology);
|
|
46
53
|
// No longer a prerequisite. Without a worker the verification and reset mail
|
|
47
54
|
// goes out inline instead of through a queue — a real trade (those two
|
|
48
55
|
// endpoints then block on SMTP), but not one worth forcing a second binary
|
|
@@ -63,7 +70,11 @@ async function addAuth(store = "postgres", projectDir = process.cwd()) {
|
|
|
63
70
|
(0, platform_patcher_1.patchConfigForSMTP)(path_1.default.join(projectDir, "internal", "shared", "config", "config.go"));
|
|
64
71
|
patchEnvExampleForSMTP(path_1.default.join(projectDir, ".env.example"));
|
|
65
72
|
}
|
|
66
|
-
await (0, template_renderer_1.applyTemplateEntries)(projectDir, (0, auth_manifest_1.authFiles)(store), {
|
|
73
|
+
await (0, template_renderer_1.applyTemplateEntries)(projectDir, (0, auth_manifest_1.authFiles)(store), {
|
|
74
|
+
goModule: config.goModule,
|
|
75
|
+
redis: store === "redis",
|
|
76
|
+
worker,
|
|
77
|
+
});
|
|
67
78
|
const migrationsDir = path_1.default.join(projectDir, "migrations");
|
|
68
79
|
fs_extra_1.default.ensureDirSync(migrationsDir);
|
|
69
80
|
const usersVersion = (0, migrations_1.newMigrationVersion)(migrationsDir);
|
|
@@ -71,6 +82,11 @@ async function addAuth(store = "postgres", projectDir = process.cwd()) {
|
|
|
71
82
|
{ template: "add/auth/migrations/create_users.up.sql.hbs", output: path_1.default.join("migrations", `${usersVersion}_create_users.up.sql`) },
|
|
72
83
|
{ template: "add/auth/migrations/create_users.down.sql.hbs", output: path_1.default.join("migrations", `${usersVersion}_create_users.down.sql`) },
|
|
73
84
|
], {});
|
|
85
|
+
const mfaVersion = (0, migrations_1.newMigrationVersion)(migrationsDir);
|
|
86
|
+
await (0, template_renderer_1.applyTemplateEntries)(projectDir, [
|
|
87
|
+
{ template: "add/auth/migrations/create_mfa.up.sql.hbs", output: path_1.default.join("migrations", `${mfaVersion}_create_mfa.up.sql`) },
|
|
88
|
+
{ template: "add/auth/migrations/create_mfa.down.sql.hbs", output: path_1.default.join("migrations", `${mfaVersion}_create_mfa.down.sql`) },
|
|
89
|
+
], {});
|
|
74
90
|
// identities references users(id) — must apply strictly after it. A
|
|
75
91
|
// second newMigrationVersion() call, scanning the dir again now that the
|
|
76
92
|
// users pair is already written, guarantees a later (or same-second,
|
|
@@ -87,13 +103,13 @@ async function addAuth(store = "postgres", projectDir = process.cwd()) {
|
|
|
87
103
|
{ template: "add/auth/migrations/create_login_throttle.up.sql.hbs", output: path_1.default.join("migrations", `${throttleVersion}_create_login_throttle.up.sql`) },
|
|
88
104
|
{ template: "add/auth/migrations/create_login_throttle.down.sql.hbs", output: path_1.default.join("migrations", `${throttleVersion}_create_login_throttle.down.sql`) },
|
|
89
105
|
], {});
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
106
|
+
// Recovery tokens are always stored in Postgres so consumption and the user
|
|
107
|
+
// update can share one transaction, even when refresh rotation uses Redis.
|
|
108
|
+
const authTokensVersion = (0, migrations_1.newMigrationVersion)(migrationsDir);
|
|
109
|
+
await (0, template_renderer_1.applyTemplateEntries)(projectDir, [
|
|
110
|
+
{ template: "add/auth/migrations/create_auth_tokens.up.sql.hbs", output: path_1.default.join("migrations", `${authTokensVersion}_create_auth_tokens.up.sql`) },
|
|
111
|
+
{ template: "add/auth/migrations/create_auth_tokens.down.sql.hbs", output: path_1.default.join("migrations", `${authTokensVersion}_create_auth_tokens.down.sql`) },
|
|
112
|
+
], {});
|
|
97
113
|
// Only meaningful when there is a worker; readConfig fills this from the
|
|
98
114
|
// adapter file on disk, so the only way it is still unknown is a project
|
|
99
115
|
// that has internal/platform/queue with neither adapter in it. Guessing
|
|
@@ -120,7 +136,7 @@ async function addAuth(store = "postgres", projectDir = process.cwd()) {
|
|
|
120
136
|
// go-redis only when something in this project actually constructs a client
|
|
121
137
|
...(store === "redis" ? ["github.com/redis/go-redis/v9 v9.22.0"] : []),
|
|
122
138
|
]);
|
|
123
|
-
patchEnvExample(path_1.default.join(projectDir, ".env.example"));
|
|
139
|
+
patchEnvExample(path_1.default.join(projectDir, ".env.example"), browser);
|
|
124
140
|
patchMakefile(path_1.default.join(projectDir, "Makefile"));
|
|
125
141
|
let docsMessage = "";
|
|
126
142
|
const openapiPath = path_1.default.join(projectDir, "docs", "openapi.yaml");
|
|
@@ -146,13 +162,14 @@ async function addAuth(store = "postgres", projectDir = process.cwd()) {
|
|
|
146
162
|
? "verification + password-reset mail goes through the queue"
|
|
147
163
|
: "verification + password-reset mail is sent inline (no worker) — run `add worker` later to move it onto the queue");
|
|
148
164
|
console.log(store === "postgres"
|
|
149
|
-
? "
|
|
150
|
-
: "tokens + rate-limit counters: Redis");
|
|
165
|
+
? "refresh + recovery tokens: Postgres (user_svc.auth_tokens), rate-limit counters in-process — no Redis"
|
|
166
|
+
: "refresh tokens + rate-limit counters: Redis; recovery tokens: Postgres (user_svc.auth_tokens)");
|
|
151
167
|
console.log("registered POST /auth/{register,login,refresh,logout,forgot-password,reset-password,verify-email}, " +
|
|
152
|
-
"GET /auth/
|
|
153
|
-
"POST /users/me/{resend-verification,logout-all}
|
|
168
|
+
"GET /auth/{provider}/login, POST /auth/{provider}/exchange, GET /users/me, and " +
|
|
169
|
+
"POST /users/me/{resend-verification,logout-all,mfa/setup,mfa/confirm,mfa/disable}, " +
|
|
170
|
+
"GET /users/me/mfa, and POST /auth/mfa/verify in cmd/api/wiring.go" +
|
|
154
171
|
docsMessage);
|
|
155
|
-
console.log(picocolors_1.default.dim("\nnext: go mod tidy, then apply the new migrations
|
|
172
|
+
console.log(picocolors_1.default.dim("\nnext: go mod tidy, then apply the new migrations with `make migrate-up` before production\n" +
|
|
156
173
|
"seed an admin: SEED_ADMIN_EMAIL=... SEED_ADMIN_PASSWORD=... make seed"));
|
|
157
174
|
}
|
|
158
175
|
function patchMakefile(makefilePath) {
|
|
@@ -183,7 +200,7 @@ function patchMakefile(makefilePath) {
|
|
|
183
200
|
content = content.replace(/\nbuild:/, () => `${target}\nbuild:`);
|
|
184
201
|
fs_extra_1.default.writeFileSync(makefilePath, content);
|
|
185
202
|
}
|
|
186
|
-
function patchEnvExample(envExamplePath) {
|
|
203
|
+
function patchEnvExample(envExamplePath, browserTopology) {
|
|
187
204
|
if (!fs_extra_1.default.existsSync(envExamplePath))
|
|
188
205
|
return;
|
|
189
206
|
let content = fs_extra_1.default.readFileSync(envExamplePath, "utf8");
|
|
@@ -195,13 +212,15 @@ function patchEnvExample(envExamplePath) {
|
|
|
195
212
|
"JWT_SECRET=dev-secret-change-me\n" +
|
|
196
213
|
"JWT_ACCESS_TTL_MIN=15\n" +
|
|
197
214
|
"JWT_REFRESH_TTL_MIN=43200\n" +
|
|
198
|
-
"
|
|
215
|
+
"JWT_REFRESH_MAX_TTL_MIN=43200\n" +
|
|
216
|
+
"OAUTH_STATE_TTL_MIN=10\n" +
|
|
217
|
+
`COOKIE_SECURE=${browserTopology === "cross-site" ? "true" : "false"}\n` +
|
|
199
218
|
"# strict | lax | none — the refresh cookie's SameSite. Keep strict while the\n" +
|
|
200
219
|
"# frontend is the same site as this API (localhost:3000 -> localhost:8080 is,\n" +
|
|
201
220
|
"# and so is app.example.com -> api.example.com). A frontend on a different\n" +
|
|
202
221
|
"# site entirely needs none, together with COOKIE_SECURE=true, or the browser\n" +
|
|
203
222
|
"# never sends the cookie to /auth/refresh and sessions die at every expiry.\n" +
|
|
204
|
-
"
|
|
223
|
+
`COOKIE_SAMESITE=${browserTopology === "cross-site" ? "none" : "strict"}\n` +
|
|
205
224
|
"\nPASSWORD_RESET_TTL_MIN=30\n" +
|
|
206
225
|
"PASSWORD_RESET_URL=http://localhost:3000/reset-password\n" +
|
|
207
226
|
"\nEMAIL_VERIFY_TTL_MIN=1440\n" +
|
|
@@ -209,7 +228,19 @@ function patchEnvExample(envExamplePath) {
|
|
|
209
228
|
"\n# leave the Google vars unset to disable Google login (register/login/refresh still work)\n" +
|
|
210
229
|
"GOOGLE_CLIENT_ID=\n" +
|
|
211
230
|
"GOOGLE_CLIENT_SECRET=\n" +
|
|
212
|
-
"
|
|
231
|
+
"# exact browser callback URI registered with the provider (frontend-owned route)\n" +
|
|
232
|
+
"GOOGLE_OAUTH_REDIRECT_URI=\n" +
|
|
233
|
+
"\n# cookie/CORS deployment topology; the frontend owns its provider callback route\n" +
|
|
234
|
+
`AUTH_BROWSER_TOPOLOGY=${browserTopology}\n` +
|
|
235
|
+
"\n# MFA is globally off by default. When enabled, set a base64-encoded 32-byte\n" +
|
|
236
|
+
"# AES-256 key (for example: openssl rand -base64 32). Users still opt in\n" +
|
|
237
|
+
"# individually through /users/me/mfa/setup and /users/me/mfa/confirm.\n" +
|
|
238
|
+
"AUTH_MFA_ENABLED=false\n" +
|
|
239
|
+
"MFA_ISSUER=go-scaffold\n" +
|
|
240
|
+
"MFA_ENCRYPTION_KEY=\n" +
|
|
241
|
+
"MFA_CHALLENGE_TTL_MIN=5\n" +
|
|
242
|
+
"MFA_TOTP_WINDOW=1\n" +
|
|
243
|
+
"MFA_RECOVERY_CODE_COUNT=10\n";
|
|
213
244
|
fs_extra_1.default.writeFileSync(envExamplePath, content);
|
|
214
245
|
}
|
|
215
246
|
// ensureRedis adds internal/platform/cache + its config/env/main.go wiring
|