@nakedev/go-scaffold 0.5.2 → 0.5.4
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 +4 -0
- package/dist/commands/auth.js +25 -13
- package/dist/commands/method.js +2 -0
- package/dist/templates/auth-manifest.js +3 -0
- package/dist/utils/auth-patcher.js +10 -1
- package/dist/utils/hexagonal-method-patcher.js +12 -1
- package/package.json +1 -1
- package/templates/add/auth/docs/schemas.yaml.hbs +45 -3
- package/templates/add/auth/docs/users-me-identities.yaml.hbs +13 -0
- package/templates/add/auth/docs/users-me-identity-link-exchange.yaml.hbs +26 -0
- package/templates/add/auth/docs/users-me-identity-link.yaml.hbs +25 -0
- package/templates/add/auth/docs/users-me-identity-local-link.yaml.hbs +16 -0
- package/templates/add/auth/docs/users-me-identity.yaml.hbs +15 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +45 -3
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler.go.hbs +7 -1
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_identity.go.hbs +107 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_test.go.hbs +21 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/mfa_store_test.go.hbs +1 -1
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +40 -17
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +253 -53
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository_test.go.hbs +11 -8
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg.go.hbs +4 -2
- package/templates/add/auth/internal/app/user/application/dto.go.hbs +14 -0
- package/templates/add/auth/internal/app/user/application/errors.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/application/external_login.go.hbs +47 -18
- package/templates/add/auth/internal/app/user/application/identities.go.hbs +135 -0
- package/templates/add/auth/internal/app/user/application/identities_test.go.hbs +102 -0
- package/templates/add/auth/internal/app/user/application/local_auth.go.hbs +3 -0
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +1 -0
- package/templates/add/auth/internal/app/user/application/provider_test.go.hbs +2 -0
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +3 -0
- package/templates/add/auth/internal/app/user/application/service.go.hbs +16 -0
- package/templates/add/auth/internal/app/user/application/service_test.go.hbs +75 -4
- package/templates/add/auth/internal/app/user/application/user_query.go.hbs +3 -0
- package/templates/add/auth/internal/app/user/domain/entity.go.hbs +1 -0
- package/templates/add/auth/internal/app/user/domain/errors.go.hbs +4 -1
- package/templates/add/auth/internal/app/user/ports/repository.go.hbs +4 -1
- package/templates/add/auth/internal/platform/authprovider/google/google.go.hbs +1 -0
- package/templates/add/auth/internal/shared/middleware/ratelimit_redis.go.hbs +7 -1
- package/templates/add/auth/migrations/create_external_identities.down.sql.hbs +1 -0
- package/templates/add/auth/migrations/create_external_identities.up.sql.hbs +12 -0
- package/templates/add/auth/migrations/create_password_credentials.down.sql.hbs +1 -0
- package/templates/add/auth/migrations/create_password_credentials.up.sql.hbs +11 -0
- package/templates/add/auth/migrations/create_user_emails.down.sql.hbs +1 -0
- package/templates/add/auth/migrations/create_user_emails.up.sql.hbs +14 -0
- package/templates/add/auth/migrations/create_users.up.sql.hbs +0 -11
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +9 -0
- package/templates/create/base/AGENTS.md.hbs +14 -0
- package/templates/create/base/README.md.hbs +7 -0
- package/templates/create/features/docs/architecture.md.hbs +5 -5
- package/templates/create/features/docs/techstack.md.hbs +1 -1
- package/templates/add/auth/migrations/create_identities.down.sql.hbs +0 -1
- package/templates/add/auth/migrations/create_identities.up.sql.hbs +0 -15
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
CREATE TABLE user_svc.password_credentials (
|
|
2
|
+
id UUID PRIMARY KEY,
|
|
3
|
+
user_id UUID NOT NULL REFERENCES user_svc.users(id) ON DELETE CASCADE,
|
|
4
|
+
password_hash TEXT NOT NULL,
|
|
5
|
+
hash_algorithm VARCHAR(32) NOT NULL DEFAULT 'bcrypt',
|
|
6
|
+
password_changed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
7
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
8
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
CREATE UNIQUE INDEX idx_password_credentials_user ON user_svc.password_credentials (user_id);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DROP TABLE IF EXISTS user_svc.user_emails;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
CREATE TABLE user_svc.user_emails (
|
|
2
|
+
id UUID PRIMARY KEY,
|
|
3
|
+
user_id UUID NOT NULL REFERENCES user_svc.users(id) ON DELETE CASCADE,
|
|
4
|
+
email VARCHAR(255) NOT NULL,
|
|
5
|
+
email_normalized VARCHAR(255) NOT NULL,
|
|
6
|
+
is_primary BOOLEAN NOT NULL DEFAULT true,
|
|
7
|
+
verified_at TIMESTAMPTZ,
|
|
8
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
9
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
CREATE UNIQUE INDEX idx_user_emails_normalized ON user_svc.user_emails (email_normalized);
|
|
13
|
+
CREATE UNIQUE INDEX idx_user_emails_primary ON user_svc.user_emails (user_id) WHERE is_primary;
|
|
14
|
+
CREATE INDEX idx_user_emails_user ON user_svc.user_emails (user_id);
|
|
@@ -2,20 +2,9 @@ CREATE SCHEMA IF NOT EXISTS user_svc;
|
|
|
2
2
|
|
|
3
3
|
CREATE TABLE user_svc.users (
|
|
4
4
|
id UUID PRIMARY KEY,
|
|
5
|
-
email VARCHAR(255) NOT NULL,
|
|
6
5
|
name VARCHAR(255) NOT NULL DEFAULT '',
|
|
7
6
|
avatar_url TEXT NOT NULL DEFAULT '',
|
|
8
|
-
email_verified BOOLEAN NOT NULL DEFAULT false,
|
|
9
7
|
role VARCHAR(20) NOT NULL DEFAULT 'staff',
|
|
10
8
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
11
9
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
12
10
|
);
|
|
13
|
-
|
|
14
|
-
-- A unique INDEX, not an inline UNIQUE constraint, and named to match the
|
|
15
|
-
-- `uniqueIndex:idx_users_email` tag on the outbound persistence model exactly. Postgres names an
|
|
16
|
-
-- inline UNIQUE "users_email_key"; the development bootstrap then sees
|
|
17
|
-
-- uniqueness it didn't create and tries to drop it under its own
|
|
18
|
-
-- name, which fails the boot with
|
|
19
|
-
-- constraint "uni_users_email" of relation "users" does not exist
|
|
20
|
-
-- for anyone who ran `make migrate-up` first — which `add rbac` tells you to.
|
|
21
|
-
CREATE UNIQUE INDEX idx_users_email ON user_svc.users (email);
|
|
@@ -260,6 +260,15 @@ boundary. For browser OAuth, preserve this client-owned callback contract:
|
|
|
260
260
|
`Pragma: no-cache`.
|
|
261
261
|
Never accept a request-supplied redirect destination or place an access token,
|
|
262
262
|
refresh token, authorization code, or state in a URI.
|
|
263
|
+
- Authenticated identity management is a separate account flow, not another
|
|
264
|
+
login endpoint: list safe metadata at `GET /users/me/identities`, start and
|
|
265
|
+
finish linking at `POST /users/me/identities/:provider/link` and
|
|
266
|
+
`POST /users/me/identities/:provider/link/exchange`, and unlink with
|
|
267
|
+
`DELETE /users/me/identities/:provider`. Bind the server-side transaction to
|
|
268
|
+
the caller's user ID, never issue or replace a session during link exchange,
|
|
269
|
+
keep one identity per provider per user, and reject unlinking the last login
|
|
270
|
+
method. Provider subjects and password hashes must never cross the HTTP
|
|
271
|
+
boundary.
|
|
263
272
|
- Map provider and validation failures to controlled public codes only:
|
|
264
273
|
`oauth_denied`, `oauth_state_invalid`, `oauth_provider_unavailable`, and
|
|
265
274
|
`oauth_failed`. Do not expose raw provider descriptions or technical causes.
|
|
@@ -186,6 +186,20 @@ approval before changing their contract.
|
|
|
186
186
|
rotation; responses expose only User-Agent and lifecycle timestamps, never
|
|
187
187
|
refresh tokens or token hashes. The current access token carries the session
|
|
188
188
|
ID as `sid` so the adapter can mark the current device.
|
|
189
|
+
- Authenticated users can manage login identities through
|
|
190
|
+
`GET /users/me/identities`, `POST /users/me/identities/local`, and
|
|
191
|
+
`POST /users/me/identities/:provider/link`,
|
|
192
|
+
`POST /users/me/identities/:provider/link/exchange`, and
|
|
193
|
+
`DELETE /users/me/identities/:provider`. The link transaction is bound to
|
|
194
|
+
the current user as well as provider/state/PKCE/nonce; it can never create a
|
|
195
|
+
session or silently move a provider account between users. Return only safe
|
|
196
|
+
provider metadata, allow at most one identity per provider per user, and
|
|
197
|
+
refuse to unlink the last remaining login method.
|
|
198
|
+
- Auth persistence keeps account profile, email addresses, password
|
|
199
|
+
credentials, and external provider identities in separate tables. Resolve an
|
|
200
|
+
external login by `(issuer, subject)`; never use an email address as the
|
|
201
|
+
provider identity key. Adding a password to a Google/OIDC account must write
|
|
202
|
+
a password credential for that existing user.
|
|
189
203
|
- Password reset and email verification consume a one-time token in the same
|
|
190
204
|
retry-safe transaction as the user/identity update. A failed post-commit
|
|
191
205
|
session revocation must not make a successful reset impossible to retry.
|
|
@@ -198,6 +198,13 @@ Authenticated users can list active device sessions with
|
|
|
198
198
|
`GET /users/me/sessions` and revoke an individual session with
|
|
199
199
|
`DELETE /users/me/sessions/:id`; only User-Agent and session lifecycle
|
|
200
200
|
timestamps are returned.
|
|
201
|
+
They can also list safe login-provider metadata with
|
|
202
|
+
`GET /users/me/identities`, link a provider through the authenticated
|
|
203
|
+
`/users/me/identities/:provider/link` start/exchange flow, and unlink a
|
|
204
|
+
provider with `DELETE /users/me/identities/:provider`. For a Google/OIDC-only
|
|
205
|
+
account, `POST /users/me/identities/local` adds an email/password credential to
|
|
206
|
+
the same account; it does not create a second user. The last login method
|
|
207
|
+
cannot be removed.
|
|
201
208
|
For `cross-site`, configure HTTPS origins, `COOKIE_SAMESITE=none`,
|
|
202
209
|
`COOKIE_SECURE=true`, and an exact allowed Origin; the API applies an Origin
|
|
203
210
|
guard independently of CORS.
|
|
@@ -60,7 +60,7 @@ internal/
|
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Installed process and platform additions:
|
|
63
|
-
{{#if auth}}- `cmd/seed` and `platform/authprovider/google/` for user bootstrap
|
|
63
|
+
{{#if auth}}- `cmd/seed` and `platform/authprovider/google/` for user bootstrap, Google OAuth/OIDC login, and authenticated identity linking
|
|
64
64
|
{{/if}}{{#if worker}}- `cmd/worker` and `platform/{mail,queue}/` for queue/SMTP jobs{{#if (eq queue "asynq")}}, plus `platform/cache/` for Redis{{/if}}
|
|
65
65
|
{{/if}}{{#if observability}}- `platform/telemetry/` for the OTLP exporter
|
|
66
66
|
{{/if}}{{#if rbac}}- `shared/middleware/authz.go` and `app/role/` for RBAC policy
|
|
@@ -73,10 +73,10 @@ a real external system.
|
|
|
73
73
|
add a `shared/utils` or similarly generic package — name it after what it
|
|
74
74
|
actually does.
|
|
75
75
|
|
|
76
|
-
Optional feature boundaries are explicit: auth owns user authentication
|
|
77
|
-
provider ports; RBAC owns role policy and authorization;
|
|
78
|
-
mail adapters; observability owns metrics/tracing setup.
|
|
79
|
-
second domain architecture.
|
|
76
|
+
Optional feature boundaries are explicit: auth owns user authentication,
|
|
77
|
+
identity linking, and provider ports; RBAC owns role policy and authorization;
|
|
78
|
+
worker owns queue and mail adapters; observability owns metrics/tracing setup.
|
|
79
|
+
They do not create a second domain architecture.
|
|
80
80
|
|
|
81
81
|
## 3. API Style
|
|
82
82
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
| Logging | `log/slog`, JSON handler |
|
|
16
16
|
| Migrations | [golang-migrate](https://github.com/golang-migrate/migrate) |
|
|
17
17
|
| Testing | stdlib `testing`, real Postgres for integration tests |
|
|
18
|
-
| Authentication | {{#if auth}}JWT access/refresh sessions, password recovery, email verification, provider login, and MFA{{else}}optional via `go-scaffold add auth`{{/if}} |
|
|
18
|
+
| Authentication | {{#if auth}}JWT access/refresh sessions, password recovery, email verification, provider login/linking, and MFA{{else}}optional via `go-scaffold add auth`{{/if}} |
|
|
19
19
|
| Authorization | {{#if rbac}}RBAC role/permission middleware{{else}}optional RBAC via `go-scaffold add rbac`{{/if}} |
|
|
20
20
|
| Background jobs | {{#if worker}}{{queue}} queue backend with SMTP mail and `cmd/worker`{{else}}optional via `go-scaffold add worker`{{/if}} |
|
|
21
21
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
DROP TABLE IF EXISTS user_svc.identities;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
CREATE TABLE user_svc.identities (
|
|
2
|
-
id UUID PRIMARY KEY,
|
|
3
|
-
user_id UUID NOT NULL REFERENCES user_svc.users(id) ON DELETE CASCADE,
|
|
4
|
-
provider VARCHAR(20) NOT NULL,
|
|
5
|
-
password_hash TEXT,
|
|
6
|
-
provider_uid TEXT,
|
|
7
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
8
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
-- Named unique indexes matching model.Identity's own
|
|
12
|
-
-- `uniqueIndex:idx_identities_*` tags — see create_users.up.sql for why an
|
|
13
|
-
-- anonymous UNIQUE constraint breaks schema parity.
|
|
14
|
-
CREATE UNIQUE INDEX idx_identities_user_provider ON user_svc.identities (user_id, provider);
|
|
15
|
-
CREATE UNIQUE INDEX idx_identities_provider_uid ON user_svc.identities (provider, provider_uid);
|