@nakedev/go-scaffold 0.4.3 → 0.5.1
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 +567 -513
- package/dist/commands/auth.js +12 -1
- package/dist/commands/check.js +281 -0
- package/dist/commands/create.js +2 -1
- package/dist/commands/generate.js +5 -2
- package/dist/commands/method.js +66 -70
- package/dist/commands/observability.js +4 -53
- package/dist/commands/rbac.js +19 -8
- package/dist/commands/undo.js +6 -3
- package/dist/commands/worker.js +14 -4
- package/dist/index.js +13 -1
- package/dist/templates/auth-manifest.js +46 -45
- package/dist/templates/create-manifest.js +4 -0
- package/dist/templates/module-manifest.js +82 -60
- package/dist/templates/rbac-manifest.js +15 -11
- package/dist/templates/worker-manifest.js +4 -1
- package/dist/types.js +2 -0
- package/dist/utils/auth-patcher.js +22 -22
- package/dist/utils/config.js +24 -5
- package/dist/utils/docs-patcher.js +68 -0
- package/dist/utils/hexagonal-method-patcher.js +334 -0
- package/dist/utils/main-patcher.js +3 -3
- package/dist/utils/module-location.js +17 -11
- package/dist/utils/platform-patcher.js +27 -0
- package/dist/utils/rbac-patcher.js +73 -216
- package/package.json +1 -1
- package/templates/add/auth/cmd/seed/main.go.hbs +2 -0
- package/templates/add/auth/internal/app/user/{browser_policy.go.hbs → adapters/inbound/http/browser_policy.go.hbs} +3 -3
- package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
- package/templates/add/auth/internal/app/user/{handler.go.hbs → adapters/inbound/http/handler.go.hbs} +103 -8
- package/templates/add/auth/internal/app/user/{handler_local.go.hbs → adapters/inbound/http/handler_local.go.hbs} +8 -7
- package/templates/add/auth/internal/app/user/{handler_mfa.go.hbs → adapters/inbound/http/handler_mfa.go.hbs} +6 -6
- package/templates/add/auth/internal/app/user/{handler_oauth.go.hbs → adapters/inbound/http/handler_oauth.go.hbs} +17 -17
- package/templates/add/auth/internal/app/user/{handler_recovery.go.hbs → adapters/inbound/http/handler_recovery.go.hbs} +4 -4
- package/templates/add/auth/internal/app/user/{handler_test.go.hbs → adapters/inbound/http/handler_test.go.hbs} +52 -31
- package/templates/add/auth/internal/app/user/{handler_user.go.hbs → adapters/inbound/http/handler_user.go.hbs} +4 -4
- package/templates/add/auth/internal/app/user/{session_cookie.go.hbs → adapters/inbound/http/session_cookie.go.hbs} +5 -3
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/{mfa_store.go.hbs → adapters/outbound/postgres/mfa_store.go.hbs} +22 -20
- package/templates/add/auth/internal/app/user/{mfa_store_test.go.hbs → adapters/outbound/postgres/mfa_store_test.go.hbs} +7 -7
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
- package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
- package/templates/add/auth/internal/app/user/{tokenstore_pg.go.hbs → adapters/outbound/postgres/tokenstore_pg.go.hbs} +36 -32
- package/templates/add/auth/internal/app/user/{tokenstore_pg_test.go.hbs → adapters/outbound/postgres/tokenstore_pg_test.go.hbs} +10 -3
- package/templates/add/auth/internal/app/user/{tokenstore_recovery.go.hbs → adapters/outbound/postgres/tokenstore_recovery.go.hbs} +29 -3
- package/templates/add/auth/internal/app/user/{tokenstore_redis.go.hbs → adapters/outbound/redis/tokenstore.go.hbs} +43 -36
- package/templates/add/auth/internal/app/user/{tokenstore_redis_test.go.hbs → adapters/outbound/redis/tokenstore_test.go.hbs} +14 -3
- package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
- package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
- package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
- package/templates/add/auth/internal/app/user/{external_login.go.hbs → application/external_login.go.hbs} +30 -40
- package/templates/add/auth/internal/app/user/{jwt.go.hbs → application/jwt.go.hbs} +6 -3
- package/templates/add/auth/internal/app/user/{local_auth.go.hbs → application/local_auth.go.hbs} +17 -19
- package/templates/add/auth/internal/app/user/{mfa_service.go.hbs → application/mfa_service.go.hbs} +27 -28
- package/templates/add/auth/internal/app/user/{mfa_service_test.go.hbs → application/mfa_service_test.go.hbs} +12 -11
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +3 -3
- package/templates/add/auth/internal/app/user/{provider_test.go.hbs → application/provider_test.go.hbs} +55 -56
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +16 -47
- package/templates/add/auth/internal/app/user/{recovery_service.go.hbs → application/recovery_service.go.hbs} +13 -15
- package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
- package/templates/add/auth/internal/app/user/{service_test.go.hbs → application/service_test.go.hbs} +94 -75
- package/templates/add/auth/internal/app/user/{sessions.go.hbs → application/sessions.go.hbs} +14 -14
- package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
- package/templates/add/auth/internal/app/user/{user_query.go.hbs → application/user_query.go.hbs} +16 -16
- package/templates/add/auth/internal/app/user/composition.go.hbs +89 -86
- package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
- package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
- package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -1
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
- package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
- package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
- package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
- package/templates/add/rbac/internal/app/role/composition.go.hbs +23 -10
- package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
- package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
- package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
- package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
- package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
- package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +54 -36
- package/templates/create/base/.golangci.yml.hbs +2 -2
- package/templates/create/base/AGENTS.md.hbs +55 -31
- package/templates/create/base/README.md.hbs +77 -22
- package/templates/create/base/cmd/api/wiring.go.hbs +3 -2
- package/templates/create/base/internal/composition/doc.go.hbs +7 -0
- package/templates/create/features/docs/architecture.md.hbs +64 -26
- package/templates/create/features/docs/patterns.md.hbs +117 -90
- package/templates/create/features/docs/techstack.md.hbs +17 -2
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
- package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
- package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
- package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
- package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
- package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
- package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
- package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
- package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
- package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
- package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
- package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
- package/templates/generate/module/migration.up.sql.hbs +1 -1
- package/dist/utils/method-patcher.js +0 -537
- package/templates/add/auth/internal/app/user/contracts.go.hbs +0 -88
- package/templates/add/auth/internal/app/user/dto.go.hbs +0 -134
- package/templates/add/auth/internal/app/user/errors.go.hbs +0 -68
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -45
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -32
- package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
- package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +0 -17
- package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +0 -20
- package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +0 -17
- package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -31
- package/templates/add/auth/internal/app/user/repository.go.hbs +0 -139
- package/templates/add/auth/internal/app/user/service.go.hbs +0 -135
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -58
- package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
- package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
- package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
- package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
- package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
- package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
- package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
- package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
- package/templates/generate/module/commands.go.hbs +0 -95
- package/templates/generate/module/composition.go.hbs +0 -23
- package/templates/generate/module/cqrs_test.go.hbs +0 -7
- package/templates/generate/module/dto.go.hbs +0 -36
- package/templates/generate/module/errors.go.hbs +0 -33
- package/templates/generate/module/handler.go.hbs +0 -179
- package/templates/generate/module/handler_test.go.hbs +0 -174
- package/templates/generate/module/minimal/commands.go.hbs +0 -34
- package/templates/generate/module/minimal/dto.go.hbs +0 -28
- package/templates/generate/module/minimal/handler.go.hbs +0 -82
- package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
- package/templates/generate/module/minimal/queries.go.hbs +0 -45
- package/templates/generate/module/minimal/service.go.hbs +0 -71
- package/templates/generate/module/minimal/service_test.go.hbs +0 -77
- package/templates/generate/module/model/model.go.hbs +0 -36
- package/templates/generate/module/queries.go.hbs +0 -62
- package/templates/generate/module/repository.go.hbs +0 -103
- package/templates/generate/module/service.go.hbs +0 -164
- package/templates/generate/module/service_test.go.hbs +0 -161
|
@@ -13,7 +13,6 @@ exports.patchUserServiceTestForRbac = patchUserServiceTestForRbac;
|
|
|
13
13
|
exports.patchUserDTOForRbac = patchUserDTOForRbac;
|
|
14
14
|
exports.patchUserHandlerForRbac = patchUserHandlerForRbac;
|
|
15
15
|
exports.patchUserErrorsForRbac = patchUserErrorsForRbac;
|
|
16
|
-
exports.userSvcLineFor = userSvcLineFor;
|
|
17
16
|
exports.assertRbacPatchable = assertRbacPatchable;
|
|
18
17
|
exports.patchMainGoForRbac = patchMainGoForRbac;
|
|
19
18
|
exports.patchCmdSeedForRbac = patchCmdSeedForRbac;
|
|
@@ -23,13 +22,12 @@ const marker_patch_1 = require("./marker-patch");
|
|
|
23
22
|
const IMPORT_MARKER = "// go-scaffold:imports";
|
|
24
23
|
const SCHEMA_MARKER = "// go-scaffold:schemas";
|
|
25
24
|
const MODEL_MARKER = "// go-scaffold:models";
|
|
26
|
-
const ROUTE_MARKER = "// go-scaffold:routes";
|
|
27
25
|
const CONFIG_FIELDS_MARKER = "// go-scaffold:config-fields";
|
|
28
26
|
const CONFIG_LOAD_MARKER = "// go-scaffold:config-load";
|
|
29
27
|
// patchAuthDocsForRbac adds `role` to the hand-written MeResponse schema in
|
|
30
28
|
// docs/auth/schemas.yaml — same marker convention as the Go dto.go patch
|
|
31
29
|
// (patchUserDTOForRbac), since GET/PATCH /users(/me) all serialize the same
|
|
32
|
-
//
|
|
30
|
+
// user response whether or not RBAC is installed.
|
|
33
31
|
function patchAuthDocsForRbac(authSchemasYamlPath) {
|
|
34
32
|
if (!fs_extra_1.default.existsSync(authSchemasYamlPath))
|
|
35
33
|
return; // openapi docs feature disabled
|
|
@@ -46,15 +44,16 @@ function patchConfigForRbac(configGoPath) {
|
|
|
46
44
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, CONFIG_LOAD_MARKER, 'AuthzCacheTTL: time.Duration(envInt("AUTHZ_CACHE_TTL_MIN", 1)) * time.Minute,', "AuthzCacheTTL: time.Duration");
|
|
47
45
|
fs_extra_1.default.writeFileSync(configGoPath, content);
|
|
48
46
|
}
|
|
49
|
-
// patchUserModelForRbac
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
47
|
+
// patchUserModelForRbac verifies the auth template exposes the optional role
|
|
48
|
+
// field in its outbound persistence model. Auth owns the stable user shape;
|
|
49
|
+
// RBAC adds the role catalog and authorization policy on top of it. Keeping
|
|
50
|
+
// this check idempotent lets the command work across scaffold versions
|
|
51
|
+
// without recreating a second model package.
|
|
53
52
|
function patchUserModelForRbac(userModelPath) {
|
|
54
53
|
let content = fs_extra_1.default.readFileSync(userModelPath, "utf8");
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
if (/\bRole\s+string\b/.test(content))
|
|
55
|
+
return;
|
|
56
|
+
throw new Error(`${userModelPath} does not expose User.Role — the auth module must be regenerated or migrated before adding RBAC`);
|
|
58
57
|
}
|
|
59
58
|
// patchMiddlewareAuthForRbac adds RoleKey + the Role claim to the shared
|
|
60
59
|
// middleware's own accessClaims copy (see auth.go's own comment on why it's
|
|
@@ -78,201 +77,78 @@ function patchUserJWTForRbac(jwtGoPath) {
|
|
|
78
77
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:jwt-claims-values", "Role: role,", "Role: role,");
|
|
79
78
|
fs_extra_1.default.writeFileSync(jwtGoPath, content);
|
|
80
79
|
}
|
|
81
|
-
// patchUserServiceForRbac
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
function patchUserServiceForRbac(serviceGoPath,
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"\tCodeExists(ctx context.Context, code string) (bool, error)",
|
|
91
|
-
"}",
|
|
92
|
-
].join("\n");
|
|
93
|
-
if (!content.includes("type RoleChecker interface") && !content.includes("type roleChecker interface")) {
|
|
94
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-interfaces", roleCheckerInterface, "type RoleChecker interface");
|
|
95
|
-
}
|
|
96
|
-
// issueTokens moved out of service.go with auth's use-case split. Keep the
|
|
97
|
-
// patch compatible with both shapes: new projects patch sessions.go, while
|
|
98
|
-
// older generated projects still have the marker in service.go.
|
|
99
|
-
const tokenIssuePath = sessionsGoPath && fs_extra_1.default.existsSync(sessionsGoPath) ? sessionsGoPath : serviceGoPath;
|
|
100
|
-
let tokenIssueContent = tokenIssuePath === serviceGoPath ? content : fs_extra_1.default.readFileSync(tokenIssuePath, "utf8");
|
|
101
|
-
tokenIssueContent = (0, marker_patch_1.insertBeforeMarkerOnce)(tokenIssueContent, "// go-scaffold:issue-access-token-args", "u.Role,", "u.Role,");
|
|
102
|
-
if (tokenIssuePath === serviceGoPath) {
|
|
103
|
-
content = tokenIssueContent;
|
|
80
|
+
// patchUserServiceForRbac verifies the auth application already exposes the
|
|
81
|
+
// optional role capability. The canonical auth template owns this shared user
|
|
82
|
+
// behavior; `add rbac` only supplies the role catalog and authorizer.
|
|
83
|
+
function patchUserServiceForRbac(serviceGoPath, sessionsGoPath) {
|
|
84
|
+
const content = fs_extra_1.default.readFileSync(serviceGoPath, "utf8");
|
|
85
|
+
for (const required of ["roles ports.RoleChecker", "func (s *Service) SetRole(", "// go-scaffold:service-interface"]) {
|
|
86
|
+
if (!content.includes(required)) {
|
|
87
|
+
throw new Error(`${serviceGoPath} is missing the canonical auth role capability (${required}); regenerate auth before adding RBAC`);
|
|
88
|
+
}
|
|
104
89
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (goModule) {
|
|
111
|
-
content = (0, marker_patch_1.ensureImport)(content, "errors");
|
|
112
|
-
content = (0, marker_patch_1.ensureImport)(content, `${goModule}/internal/shared/apperror`);
|
|
113
|
-
content = (0, marker_patch_1.ensureImport)(content, "gorm.io/gorm");
|
|
90
|
+
if (sessionsGoPath && fs_extra_1.default.existsSync(sessionsGoPath)) {
|
|
91
|
+
const sessions = fs_extra_1.default.readFileSync(sessionsGoPath, "utf8");
|
|
92
|
+
if (!sessions.includes("u.Role,")) {
|
|
93
|
+
throw new Error(`${sessionsGoPath} does not include the user's role in access-token issuance; regenerate auth before adding RBAC`);
|
|
94
|
+
}
|
|
114
95
|
}
|
|
115
|
-
const setRoleMethod = [
|
|
116
|
-
"// SetRole assigns userID a new role — validated against the role catalog",
|
|
117
|
-
"// (not just the DB's FK) so a typo'd code fails with a clear 422 here",
|
|
118
|
-
"// instead of surfacing as an opaque FK-violation, and so AutoMigrate-only",
|
|
119
|
-
"// test schemas (which never create the FK) still reject it correctly.",
|
|
120
|
-
"func (s *Service) SetRole(ctx context.Context, userID uuid.UUID, roleCode string) (*model.User, error) {",
|
|
121
|
-
"\tu, err := s.repo.FindByID(ctx, userID)",
|
|
122
|
-
"\tif err != nil {",
|
|
123
|
-
"\t\tif errors.Is(err, gorm.ErrRecordNotFound) {",
|
|
124
|
-
"\t\t\treturn nil, errNotFound()",
|
|
125
|
-
"\t\t}",
|
|
126
|
-
"\t\treturn nil, apperror.NewInternal(err)",
|
|
127
|
-
"\t}",
|
|
128
|
-
"\texists, err := s.roles.CodeExists(ctx, roleCode)",
|
|
129
|
-
"\tif err != nil {",
|
|
130
|
-
"\t\treturn nil, apperror.NewInternal(err)",
|
|
131
|
-
"\t}",
|
|
132
|
-
"\tif !exists {",
|
|
133
|
-
"\t\treturn nil, errUnknownRole()",
|
|
134
|
-
"\t}",
|
|
135
|
-
"\tu.Role = roleCode",
|
|
136
|
-
"\tif err := s.repo.UpdateUser(ctx, u); err != nil {",
|
|
137
|
-
"\t\treturn nil, apperror.NewInternal(err)",
|
|
138
|
-
"\t}",
|
|
139
|
-
"\treturn u, nil",
|
|
140
|
-
"}",
|
|
141
|
-
].join("\n");
|
|
142
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-service-methods", setRoleMethod, "func (s *Service) SetRole(");
|
|
143
|
-
fs_extra_1.default.writeFileSync(serviceGoPath, content);
|
|
144
|
-
if (tokenIssuePath !== serviceGoPath)
|
|
145
|
-
fs_extra_1.default.writeFileSync(tokenIssuePath, tokenIssueContent);
|
|
146
96
|
}
|
|
147
|
-
// patchUserServiceTestForRbac
|
|
148
|
-
//
|
|
149
|
-
//
|
|
97
|
+
// patchUserServiceTestForRbac verifies the canonical auth test seam already
|
|
98
|
+
// supplies the optional role capability. It is part of the auth contract so
|
|
99
|
+
// adding RBAC does not rewrite every test call site.
|
|
150
100
|
function patchUserServiceTestForRbac(serviceTestGoPath) {
|
|
151
|
-
|
|
152
|
-
const fakeRoles
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
].join("\n");
|
|
158
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-service-test-types", fakeRoles, "type fakeRoles struct{}");
|
|
159
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-service-test-deps", "Roles: fakeRoles{},", "Roles: fakeRoles{},");
|
|
160
|
-
fs_extra_1.default.writeFileSync(serviceTestGoPath, content);
|
|
101
|
+
const content = fs_extra_1.default.readFileSync(serviceTestGoPath, "utf8");
|
|
102
|
+
for (const required of ["type fakeRoles struct{}", "Roles: fakeRoles{},", "// go-scaffold:user-service-test-deps"]) {
|
|
103
|
+
if (!content.includes(required)) {
|
|
104
|
+
throw new Error(`${serviceTestGoPath} is missing the canonical auth role test seam (${required}); regenerate auth before adding RBAC`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
161
107
|
}
|
|
162
|
-
// patchUserDTOForRbac
|
|
163
|
-
//
|
|
164
|
-
// model.User has no Role field at all without it.
|
|
108
|
+
// patchUserDTOForRbac verifies the role DTOs owned by the canonical auth
|
|
109
|
+
// inbound adapter. The routes remain inactive until an Authz is composed.
|
|
165
110
|
function patchUserDTOForRbac(dtoGoPath) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const setRoleInput = ["type setRoleInput struct {", '\tRole string `json:"role" binding:"required"`', "}"].join("\n");
|
|
170
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-dto", setRoleInput, "type setRoleInput struct");
|
|
171
|
-
fs_extra_1.default.writeFileSync(dtoGoPath, content);
|
|
172
|
-
}
|
|
173
|
-
// patchUserHandlerForRbac wires an *middleware.Authz into Handler and adds
|
|
174
|
-
// the admin routes this PR ships: listing/viewing other users (PermUserRead)
|
|
175
|
-
// and changing a user's role (PermUserManageRole). Suspending a user is a
|
|
176
|
-
// separate concern, not RBAC's — see the role domain's own /roles,
|
|
177
|
-
// /permissions for the actual role/permission management API.
|
|
178
|
-
function patchUserHandlerForRbac(handlerGoPath, goModule) {
|
|
179
|
-
let content = fs_extra_1.default.readFileSync(handlerGoPath, "utf8");
|
|
180
|
-
// The base auth handler is intentionally only the route/composition surface;
|
|
181
|
-
// its endpoint implementations now live in sibling files. RBAC still
|
|
182
|
-
// patches its admin handlers into handler.go for marker compatibility, so
|
|
183
|
-
// make the imports required by those injected handlers explicit here rather
|
|
184
|
-
// than relying on imports that used to come from the old monolithic file.
|
|
185
|
-
content = (0, marker_patch_1.ensureImport)(content, "net/http");
|
|
186
|
-
content = (0, marker_patch_1.ensureImport)(content, `${goModule}/internal/shared/httpx`);
|
|
187
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-handler-consts", 'const PermUserManageRole = "user:manage-role"', "PermUserManageRole");
|
|
188
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-handler-consts", 'const PermUserRead = "user:read"', "PermUserRead");
|
|
189
|
-
const paginationImport = `"${goModule}/internal/shared/pagination"`;
|
|
190
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-handler-imports", paginationImport, "internal/shared/pagination");
|
|
191
|
-
if (!/\bauthz\s+(?:\*middleware\.Authz|authorizer)\b/.test(content)) {
|
|
192
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-handler-fields", "authz *middleware.Authz", "authz *middleware.Authz");
|
|
111
|
+
const content = fs_extra_1.default.readFileSync(dtoGoPath, "utf8");
|
|
112
|
+
if (!/\bRole\s+string\s+`json:"role"`/.test(content)) {
|
|
113
|
+
throw new Error(`${dtoGoPath} is missing the canonical auth role DTO; regenerate auth before adding RBAC`);
|
|
193
114
|
}
|
|
194
|
-
if (
|
|
195
|
-
|
|
115
|
+
if (!/Role:\s+user\.Role,/.test(content)) {
|
|
116
|
+
throw new Error(`${dtoGoPath} is missing the canonical auth role DTO mapping; regenerate auth before adding RBAC`);
|
|
196
117
|
}
|
|
197
|
-
if (
|
|
198
|
-
|
|
118
|
+
if (!content.includes("type setRoleInput struct")) {
|
|
119
|
+
throw new Error(`${dtoGoPath} is missing the canonical auth role DTO (type setRoleInput struct); regenerate auth before adding RBAC`);
|
|
199
120
|
}
|
|
200
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-routes", 'usersGroup.GET("", h.authz.Require(PermUserRead), h.adminListUsers)', 'usersGroup.GET("", h.authz.Require(PermUserRead)');
|
|
201
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-routes", 'usersGroup.GET("/:id", h.authz.Require(PermUserRead), h.adminGetUser)', 'usersGroup.GET("/:id", h.authz.Require(PermUserRead)');
|
|
202
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-routes", 'usersGroup.PATCH("/:id/set-role", h.authz.Require(PermUserManageRole), h.setRole)', 'usersGroup.PATCH("/:id/set-role"');
|
|
203
|
-
const adminListHandler = [
|
|
204
|
-
"func (h *Handler) adminListUsers(c *gin.Context) {",
|
|
205
|
-
"\tp := pagination.Parse(c)",
|
|
206
|
-
"\titems, err := h.svc.List(c.Request.Context(), p.Limit, p.Offset)",
|
|
207
|
-
"\tif err != nil {",
|
|
208
|
-
"\t\tc.Error(err)",
|
|
209
|
-
"\t\treturn",
|
|
210
|
-
"\t}",
|
|
211
|
-
"\tout := make([]meResponse, len(items))",
|
|
212
|
-
"\tfor i := range items {",
|
|
213
|
-
"\t\tout[i] = toMeResponse(&items[i])",
|
|
214
|
-
"\t}",
|
|
215
|
-
"\tc.JSON(http.StatusOK, p.Response(out))",
|
|
216
|
-
"}",
|
|
217
|
-
].join("\n");
|
|
218
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-handler-funcs", adminListHandler, "func (h *Handler) adminListUsers(");
|
|
219
|
-
const adminGetHandler = [
|
|
220
|
-
"func (h *Handler) adminGetUser(c *gin.Context) {",
|
|
221
|
-
"\tid, ok := httpx.ParseID(c)",
|
|
222
|
-
"\tif !ok {",
|
|
223
|
-
"\t\treturn",
|
|
224
|
-
"\t}",
|
|
225
|
-
"\tu, err := h.svc.Get(c.Request.Context(), id)",
|
|
226
|
-
"\tif err != nil {",
|
|
227
|
-
"\t\tc.Error(err)",
|
|
228
|
-
"\t\treturn",
|
|
229
|
-
"\t}",
|
|
230
|
-
"\tc.JSON(http.StatusOK, toMeResponse(u))",
|
|
231
|
-
"}",
|
|
232
|
-
].join("\n");
|
|
233
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-handler-funcs", adminGetHandler, "func (h *Handler) adminGetUser(");
|
|
234
|
-
const setRoleHandler = [
|
|
235
|
-
"func (h *Handler) setRole(c *gin.Context) {",
|
|
236
|
-
"\tid, ok := httpx.ParseID(c)",
|
|
237
|
-
"\tif !ok {",
|
|
238
|
-
"\t\treturn",
|
|
239
|
-
"\t}",
|
|
240
|
-
"\tvar in setRoleInput",
|
|
241
|
-
"\tif err := c.ShouldBindJSON(&in); err != nil {",
|
|
242
|
-
"\t\tc.Error(httpx.BindErr(err))",
|
|
243
|
-
"\t\treturn",
|
|
244
|
-
"\t}",
|
|
245
|
-
"\tu, err := h.svc.SetRole(c.Request.Context(), id, in.Role)",
|
|
246
|
-
"\tif err != nil {",
|
|
247
|
-
"\t\tc.Error(err)",
|
|
248
|
-
"\t\treturn",
|
|
249
|
-
"\t}",
|
|
250
|
-
"\tc.JSON(http.StatusOK, toMeResponse(u))",
|
|
251
|
-
"}",
|
|
252
|
-
].join("\n");
|
|
253
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, "// go-scaffold:user-handler-funcs", setRoleHandler, "func (h *Handler) setRole(");
|
|
254
|
-
fs_extra_1.default.writeFileSync(handlerGoPath, content);
|
|
255
121
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
fs_extra_1.default.
|
|
122
|
+
// patchUserHandlerForRbac verifies that auth owns the role-aware admin route
|
|
123
|
+
// surface. The routes are guarded by the optional authorizer, so adding RBAC
|
|
124
|
+
// composes the guard; it does not copy or rewrite handler code.
|
|
125
|
+
function patchUserHandlerForRbac(handlerGoPath) {
|
|
126
|
+
const content = fs_extra_1.default.readFileSync(handlerGoPath, "utf8");
|
|
127
|
+
for (const required of [
|
|
128
|
+
"authz authorizer",
|
|
129
|
+
"func (h *Handler) adminListUsers(",
|
|
130
|
+
"func (h *Handler) adminGetUser(",
|
|
131
|
+
"func (h *Handler) setRole(",
|
|
132
|
+
'usersGroup.GET("", h.authz.Require(PermUserRead), h.adminListUsers)',
|
|
133
|
+
'usersGroup.GET("/:id", h.authz.Require(PermUserRead), h.adminGetUser)',
|
|
134
|
+
'usersGroup.PATCH("/:id/set-role", h.authz.Require(PermUserManageRole), h.setRole)',
|
|
135
|
+
]) {
|
|
136
|
+
if (!content.includes(required)) {
|
|
137
|
+
throw new Error(`${handlerGoPath} is missing the canonical auth role route (${required}); regenerate auth before adding RBAC`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
261
140
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const { tokenStore, mailer } = (0, auth_patcher_1.authWiringLines)({ goModule, queueBackend: "river", store, worker });
|
|
268
|
-
return `userSvc := user.NewService(user.NewRepository(db), ${tokenStore}, ${mailer}, cfg, application.NewProviderRegistry())`;
|
|
141
|
+
function patchUserErrorsForRbac(errorsGoPath) {
|
|
142
|
+
const content = fs_extra_1.default.readFileSync(errorsGoPath, "utf8");
|
|
143
|
+
if (!content.includes("func errUnknownRole(")) {
|
|
144
|
+
throw new Error(`${errorsGoPath} is missing the canonical unknown-role error; regenerate auth before adding RBAC`);
|
|
145
|
+
}
|
|
269
146
|
}
|
|
270
147
|
function assertRbacPatchable(mainGoPath, goModule, store, worker) {
|
|
271
148
|
const wiring = { goModule, queueBackend: "river", store, worker };
|
|
272
149
|
const content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
|
|
273
150
|
const expected = (0, auth_patcher_1.authHandlerLineFor)(wiring);
|
|
274
|
-
|
|
275
|
-
if (content.includes(expected) || content.includes(userSvcLineFor(goModule, store, worker)) || content.includes(legacyRoute))
|
|
151
|
+
if (content.includes(expected))
|
|
276
152
|
return;
|
|
277
153
|
throw new Error("cmd/api/wiring.go's auth route doesn't match what `add auth` wrote, so `add rbac` can't extend it.\n" +
|
|
278
154
|
`Expected to find:\n ${expected}\n\n` +
|
|
@@ -282,7 +158,7 @@ function patchMainGoForRbac(mainGoPath, goModule, store, worker) {
|
|
|
282
158
|
let content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
|
|
283
159
|
const importLine = `"${goModule}/internal/app/role"`;
|
|
284
160
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, importLine, importLine);
|
|
285
|
-
const modelImportLine = `
|
|
161
|
+
const modelImportLine = `rolepostgres "${goModule}/internal/app/role/adapters/outbound/postgres"`;
|
|
286
162
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, modelImportLine, modelImportLine);
|
|
287
163
|
const schemaBlock = [
|
|
288
164
|
'if err := db.Exec("CREATE SCHEMA IF NOT EXISTS role_svc").Error; err != nil {',
|
|
@@ -290,7 +166,7 @@ function patchMainGoForRbac(mainGoPath, goModule, store, worker) {
|
|
|
290
166
|
"}",
|
|
291
167
|
].join("\n");
|
|
292
168
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, SCHEMA_MARKER, schemaBlock, "CREATE SCHEMA IF NOT EXISTS role_svc");
|
|
293
|
-
const migrateLines = ["&
|
|
169
|
+
const migrateLines = ["&rolepostgres.Role{},", "&rolepostgres.Permission{},", "&rolepostgres.RolePermission{},"];
|
|
294
170
|
for (const line of migrateLines) {
|
|
295
171
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, MODEL_MARKER, line, line);
|
|
296
172
|
}
|
|
@@ -298,32 +174,13 @@ function patchMainGoForRbac(mainGoPath, goModule, store, worker) {
|
|
|
298
174
|
const authRouteLine = (0, auth_patcher_1.authHandlerLineFor)(wiring);
|
|
299
175
|
const roleCompositionLine = "roleComposition := role.NewCompositionFromDB(db, cfg.JWTSecret, cfg.AuthzCacheTTL)";
|
|
300
176
|
const roleHandlerLine = "roleComposition.Handler.Register(api)";
|
|
301
|
-
if (content.includes(authRouteLine)) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
else {
|
|
306
|
-
// Upgrade projects generated before auth's local composition root. The
|
|
307
|
-
// role feature is still constructed locally; only the legacy user service
|
|
308
|
-
// call needs an adapter until that project is regenerated.
|
|
309
|
-
const applicationImportLine = `"${goModule}/internal/app/user/application"`;
|
|
310
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, applicationImportLine, applicationImportLine);
|
|
311
|
-
const userSvcLine = userSvcLineFor(goModule, store, worker);
|
|
312
|
-
if (!content.includes(userSvcLine)) {
|
|
313
|
-
throw new Error(`cmd/api/wiring.go's auth route doesn't match what \`add auth\` wrote, so \`add rbac\` can't extend it.\n` +
|
|
314
|
-
`Expected to find:\n ${authRouteLine}\n\n` +
|
|
315
|
-
"It was probably hand-edited. Restore that route and re-run — nothing has been changed yet.");
|
|
316
|
-
}
|
|
317
|
-
content = content.replace(userSvcLine, [roleCompositionLine, `${userSvcLine.slice(0, -1)}, roleComposition.Service)`].join("\n"));
|
|
318
|
-
const limiter = (0, auth_patcher_1.authWiringLines)(wiring).limiter;
|
|
319
|
-
const legacyRoute = `user.NewHandler(userSvc, cfg.JWTSecret, cfg.JWTRefreshTTL, cfg.CookieSecure, cfg.CookieSameSite, ${limiter}).Register(api)`;
|
|
320
|
-
const tail = ").Register(api)";
|
|
321
|
-
if (!content.includes(legacyRoute)) {
|
|
322
|
-
throw new Error("cmd/api/wiring.go is missing auth's legacy handler route while adding RBAC");
|
|
323
|
-
}
|
|
324
|
-
content = content.replace(legacyRoute, `${legacyRoute.slice(0, -tail.length)}, roleComposition.Authz${tail}`);
|
|
325
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, ROUTE_MARKER, roleHandlerLine, roleHandlerLine);
|
|
177
|
+
if (!content.includes(authRouteLine)) {
|
|
178
|
+
throw new Error(`cmd/api/wiring.go's auth route doesn't match what \`add auth\` wrote, so \`add rbac\` can't extend it.\n` +
|
|
179
|
+
`Expected to find:\n ${authRouteLine}\n\n` +
|
|
180
|
+
"It was probably hand-edited. Restore that route and re-run — nothing has been changed yet.");
|
|
326
181
|
}
|
|
182
|
+
const authWithRoleLine = (0, auth_patcher_1.authHandlerLineFor)(wiring, ["roleComposition.Service", "roleComposition.Authz"]);
|
|
183
|
+
content = content.replace(authRouteLine, [roleCompositionLine, authWithRoleLine, roleHandlerLine].join("\n"));
|
|
327
184
|
fs_extra_1.default.writeFileSync(mainGoPath, content);
|
|
328
185
|
}
|
|
329
186
|
// patchCmdSeedForRbac makes the seeded admin actually an admin: wires a
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import (
|
|
|
7
7
|
"os"
|
|
8
8
|
|
|
9
9
|
"{{goModule}}/internal/app/user"
|
|
10
|
+
userpassword "{{goModule}}/internal/app/user/adapters/outbound/password"
|
|
10
11
|
"{{goModule}}/internal/platform/database"
|
|
11
12
|
"{{goModule}}/internal/shared/config"
|
|
12
13
|
// go-scaffold:seed-imports
|
|
@@ -32,6 +33,7 @@ func main() {
|
|
|
32
33
|
// go-scaffold:seed-services
|
|
33
34
|
svc := user.NewService(user.Dependencies{
|
|
34
35
|
Repository: user.NewRepository(db),
|
|
36
|
+
Passwords: userpassword.NewBCryptHasher(),
|
|
35
37
|
// go-scaffold:seed-user-service-args
|
|
36
38
|
}, user.AuthConfig{
|
|
37
39
|
JWTSecret: cfg.JWTSecret,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package httpadapter
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"fmt"
|
|
@@ -61,10 +61,10 @@ func canonicalBrowserOrigin(value string) (string, bool) {
|
|
|
61
61
|
return parsed.Scheme + "://" + parsed.Host, true
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
//
|
|
64
|
+
// ValidateBrowserCookiePolicy ties deployment topology to the attributes used
|
|
65
65
|
// by /auth/refresh. Same-site is the safe local default; cross-site must opt
|
|
66
66
|
// into None + Secure, and production always requires Secure.
|
|
67
|
-
func
|
|
67
|
+
func ValidateBrowserCookiePolicy(topology, sameSite string, secure, production bool) error {
|
|
68
68
|
if production && !secure {
|
|
69
69
|
return fmt.Errorf("production browser cookies require COOKIE_SECURE=true")
|
|
70
70
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
package httpadapter
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"time"
|
|
5
|
+
|
|
6
|
+
userapp "{{goModule}}/internal/app/user/application"
|
|
7
|
+
userdomain "{{goModule}}/internal/app/user/domain"
|
|
8
|
+
|
|
9
|
+
"github.com/google/uuid"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// Request DTOs belong to the HTTP adapter. The application receives the
|
|
13
|
+
// transport-neutral inputs from handler mapping code.
|
|
14
|
+
type registerInput struct {
|
|
15
|
+
Email string `json:"email" binding:"required,email"`
|
|
16
|
+
Password string `json:"password" binding:"required,min=8"`
|
|
17
|
+
Name string `json:"name" binding:"required"`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type loginInput struct {
|
|
21
|
+
Email string `json:"email" binding:"required,email"`
|
|
22
|
+
Password string `json:"password" binding:"required"`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type loginExchangeInput struct {
|
|
26
|
+
Code string `json:"code"`
|
|
27
|
+
State string `json:"state"`
|
|
28
|
+
CodeVerifier string `json:"code_verifier"`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type loginStartInput struct {
|
|
32
|
+
State string
|
|
33
|
+
CodeChallenge string
|
|
34
|
+
CodeChallengeMethod string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func toRegisterInput(in registerInput) userapp.RegisterInput {
|
|
38
|
+
return userapp.RegisterInput{Email: in.Email, Password: in.Password, Name: in.Name}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func toLoginInput(in loginInput) userapp.LoginInput {
|
|
42
|
+
return userapp.LoginInput{Email: in.Email, Password: in.Password}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func toLoginStartInput(in loginStartInput) userapp.LoginStartInput {
|
|
46
|
+
return userapp.LoginStartInput{
|
|
47
|
+
State: in.State, CodeChallenge: in.CodeChallenge, CodeChallengeMethod: in.CodeChallengeMethod,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func toLoginExchangeInput(in loginExchangeInput) userapp.LoginExchangeInput {
|
|
52
|
+
return userapp.LoginExchangeInput{Code: in.Code, State: in.State, CodeVerifier: in.CodeVerifier}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type forgotPasswordInput struct {
|
|
56
|
+
Email string `json:"email" binding:"required,email"`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type resetPasswordInput struct {
|
|
60
|
+
Token string `json:"token" binding:"required"`
|
|
61
|
+
NewPassword string `json:"new_password" binding:"required,min=8"`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type verifyEmailInput struct {
|
|
65
|
+
Token string `json:"token" binding:"required"`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type mfaCodeInput struct {
|
|
69
|
+
Code string `json:"code" binding:"required"`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type mfaChallengeInput struct {
|
|
73
|
+
Challenge string `json:"challenge" binding:"required"`
|
|
74
|
+
Code string `json:"code" binding:"required"`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type authCookieResponse struct {
|
|
78
|
+
AccessToken string `json:"access_token"`
|
|
79
|
+
TokenType string `json:"token_type"`
|
|
80
|
+
ExpiresIn int `json:"expires_in"`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type mfaChallengeResponse struct {
|
|
84
|
+
MFARequired bool `json:"mfa_required"`
|
|
85
|
+
Challenge string `json:"challenge"`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type mfaStatusResponse struct {
|
|
89
|
+
Available bool `json:"available"`
|
|
90
|
+
Enabled bool `json:"enabled"`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type mfaSetupResponse struct {
|
|
94
|
+
Secret string `json:"secret"`
|
|
95
|
+
OTPAuthURI string `json:"otpauth_uri"`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
type mfaConfirmResponse struct {
|
|
99
|
+
RecoveryCodes []string `json:"recovery_codes"`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func toCookieResponse(auth *userapp.AuthResponse) authCookieResponse {
|
|
103
|
+
return authCookieResponse{AccessToken: auth.AccessToken, TokenType: auth.TokenType, ExpiresIn: auth.ExpiresIn}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type meResponse struct {
|
|
107
|
+
ID uuid.UUID `json:"id"`
|
|
108
|
+
Email string `json:"email"`
|
|
109
|
+
Name string `json:"name"`
|
|
110
|
+
AvatarURL string `json:"avatar_url"`
|
|
111
|
+
EmailVerified bool `json:"email_verified"`
|
|
112
|
+
Role string `json:"role"`
|
|
113
|
+
// go-scaffold:user-me-fields
|
|
114
|
+
CreatedAt time.Time `json:"created_at"`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func toMeResponse(user *userdomain.User) meResponse {
|
|
118
|
+
return meResponse{
|
|
119
|
+
ID: user.ID,
|
|
120
|
+
Email: user.Email,
|
|
121
|
+
Name: user.Name,
|
|
122
|
+
AvatarURL: user.AvatarURL,
|
|
123
|
+
EmailVerified: user.EmailVerified,
|
|
124
|
+
CreatedAt: user.CreatedAt,
|
|
125
|
+
Role: user.Role,
|
|
126
|
+
// go-scaffold:user-me-values
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// response is the HTTP representation used by generated methods. It is kept
|
|
131
|
+
// separate from application.Response so JSON naming remains an adapter detail.
|
|
132
|
+
//
|
|
133
|
+
//nolint:unused
|
|
134
|
+
type response struct {
|
|
135
|
+
ID uuid.UUID `json:"id"`
|
|
136
|
+
Email string `json:"email"`
|
|
137
|
+
Name string `json:"name"`
|
|
138
|
+
AvatarURL string `json:"avatar_url"`
|
|
139
|
+
EmailVerified bool `json:"email_verified"`
|
|
140
|
+
Role string `json:"role"`
|
|
141
|
+
CreatedAt time.Time `json:"created_at"`
|
|
142
|
+
UpdatedAt time.Time `json:"updated_at"`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//nolint:unused
|
|
146
|
+
func toResponse(out userapp.Response) response {
|
|
147
|
+
return response{
|
|
148
|
+
ID: out.ID, Email: out.Email, Name: out.Name, AvatarURL: out.AvatarURL,
|
|
149
|
+
EmailVerified: out.EmailVerified, Role: out.Role, CreatedAt: out.CreatedAt,
|
|
150
|
+
UpdatedAt: out.UpdatedAt,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type setRoleInput struct {
|
|
155
|
+
Role string `json:"role" binding:"required"`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// go-scaffold:dto
|
|
159
|
+
// go-scaffold:user-dto
|