diff --git a/DESIGN.md b/DESIGN.md index 79fe35e..5035e0a 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -159,7 +159,8 @@ - Bootstrap 规则: - 启动时若已有已验证用户但没有任何 `owner` 用户,系统自动将最早完成验证的用户加入 `owner` 角色。 - 若系统还没有 `owner` 用户,首个完成邮箱验证的用户自动加入 `owner` 角色。 - - 系统初始化只补齐默认角色、默认权限和 Owner 关联;不覆盖管理员对默认角色/权限元数据或角色权限分配的配置。 + - 已完成邮箱验证且没有任何角色的用户默认加入 `editor` 角色;已有角色关系的用户不被覆盖。 + - 系统初始化只补齐默认角色、默认权限、Owner 关联和无角色已验证用户的默认 Editor 关联;不覆盖管理员对默认角色/权限元数据或角色权限分配的配置。 - 新建权限会自动关联到 `owner` 角色,确保 Owner 始终拥有可用权限全集;`owner` 角色的权限分配不能在管理端被手动删改。 - 系统必须始终至少保留一个拥有 `admin.permissions.update` 且可管理权限的有效用户;核心 RBAC 管理权限(`admin.access`、`admin.users.*`、`admin.roles.*`、`admin.permissions.*`)不能被禁用或删除;不能删除最后一个 Owner,不能移除最后一个 Owner 的关键权限能力。 - 权限管理能力本身也通过权限控制;只有拥有相应管理权限的用户可以查看、新增、编辑、删除权限、角色和用户角色关系。 diff --git a/backend/db/schema.sql b/backend/db/schema.sql index d05712f..c3859aa 100644 --- a/backend/db/schema.sql +++ b/backend/db/schema.sql @@ -368,6 +368,19 @@ CROSS JOIN roles r WHERE r.key = 'owner' ON CONFLICT DO NOTHING; +INSERT INTO user_roles (user_id, role_id) +SELECT u.id, r.id +FROM users u +CROSS JOIN roles r +WHERE u.email_verified_at IS NOT NULL + AND r.key = 'editor' + AND NOT EXISTS ( + SELECT 1 + FROM user_roles ur + WHERE ur.user_id = u.id + ) +ON CONFLICT DO NOTHING; + CREATE TABLE IF NOT EXISTS system_wording_keys ( key text PRIMARY KEY, module text NOT NULL, diff --git a/backend/src/auth.ts b/backend/src/auth.ts index 4ee9ec8..bf7b699 100644 --- a/backend/src/auth.ts +++ b/backend/src/auth.ts @@ -422,6 +422,24 @@ async function ensureOwnerRoleForUser(client: DbClient, userId: number): Promise ); } +async function ensureDefaultEditorRoleForUser(client: DbClient, userId: number): Promise { + await client.query( + ` + INSERT INTO user_roles (user_id, role_id) + SELECT $1, r.id + FROM roles r + WHERE r.key = 'editor' + AND NOT EXISTS ( + SELECT 1 + FROM user_roles ur + WHERE ur.user_id = $1 + ) + ON CONFLICT DO NOTHING + `, + [userId] + ); +} + function toRoleSummary(row: RoleRow): RoleSummary { return { id: row.id, @@ -832,6 +850,7 @@ export async function verifyEmail(payload: Record, locale = def user.id ]); await ensureOwnerRoleForUser(client, user.id); + await ensureDefaultEditorRoleForUser(client, user.id); const publicUser = await publicUserById(user.id, client); return { message: await authMessage(locale, 'emailVerified'), user: publicUser ?? toPublicUser(user) };