feat(auth): add tenant seed data and bootstrap logic

- seed.rs: creates 21 permissions, admin+viewer roles, admin user with Argon2 password
- AuthConfig added to server config with default password Admin@2026
- Server startup: auto-creates default tenant and seeds auth data if not exists
- Idempotent: checks for existing tenant before seeding
This commit is contained in:
iven
2026-04-11 03:28:19 +08:00
parent 3afd732de8
commit a7cdf67d17
5 changed files with 328 additions and 0 deletions

View File

@@ -1,4 +1,5 @@
pub mod auth_service;
pub mod password;
pub mod seed;
pub mod token_service;
pub mod user_service;

View File

@@ -0,0 +1,265 @@
use sea_orm::{ActiveModelTrait, Set};
use uuid::Uuid;
use crate::entity::{permission, role, role_permission, user, user_credential, user_role};
use crate::error::AuthError;
use super::password;
/// Permission definitions to seed for every new tenant.
/// Each tuple is: (code, name, resource, action, description)
const DEFAULT_PERMISSIONS: &[(&str, &str, &str, &str, &str)] = &[
("user:create", "创建用户", "user", "create", "创建新用户"),
("user:read", "查看用户", "user", "read", "查看用户信息"),
("user:update", "编辑用户", "user", "update", "编辑用户信息"),
("user:delete", "删除用户", "user", "delete", "软删除用户"),
("role:create", "创建角色", "role", "create", "创建新角色"),
("role:read", "查看角色", "role", "read", "查看角色信息"),
("role:update", "编辑角色", "role", "update", "编辑角色"),
("role:delete", "删除角色", "role", "delete", "删除角色"),
(
"permission:read",
"查看权限",
"permission",
"read",
"查看权限列表",
),
(
"organization:create",
"创建组织",
"organization",
"create",
"创建组织",
),
(
"organization:read",
"查看组织",
"organization",
"read",
"查看组织架构",
),
(
"organization:update",
"编辑组织",
"organization",
"update",
"编辑组织",
),
(
"organization:delete",
"删除组织",
"organization",
"delete",
"删除组织",
),
(
"department:create",
"创建部门",
"department",
"create",
"创建部门",
),
(
"department:read",
"查看部门",
"department",
"read",
"查看部门",
),
(
"department:update",
"编辑部门",
"department",
"update",
"编辑部门",
),
(
"department:delete",
"删除部门",
"department",
"delete",
"删除部门",
),
("position:create", "创建岗位", "position", "create", "创建岗位"),
("position:read", "查看岗位", "position", "read", "查看岗位"),
("position:update", "编辑岗位", "position", "update", "编辑岗位"),
("position:delete", "删除岗位", "position", "delete", "删除岗位"),
];
/// Indices of read-only permissions within DEFAULT_PERMISSIONS.
const READ_PERM_INDICES: &[usize] = &[1, 5, 9, 11, 15, 19];
/// Seed default auth data for a new tenant.
///
/// Creates:
/// - 21 permissions covering user/role/permission/organization/department/position CRUD
/// - An "admin" system role with all permissions
/// - A "viewer" system role with read-only permissions
/// - A super-admin user with the admin role and a password credential
pub async fn seed_tenant_auth(
db: &sea_orm::DatabaseConnection,
tenant_id: Uuid,
super_admin_password: &str,
) -> Result<(), AuthError> {
let now = chrono::Utc::now();
let system_user_id = Uuid::nil();
// 1. Create permissions
let mut perm_ids: Vec<Uuid> = Vec::with_capacity(DEFAULT_PERMISSIONS.len());
for (code, name, resource, action, desc) in DEFAULT_PERMISSIONS {
let perm_id = Uuid::now_v7();
perm_ids.push(perm_id);
let perm = permission::ActiveModel {
id: Set(perm_id),
tenant_id: Set(tenant_id),
code: Set(code.to_string()),
name: Set(name.to_string()),
resource: Set(resource.to_string()),
action: Set(action.to_string()),
description: Set(Some(desc.to_string())),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(system_user_id),
updated_by: Set(system_user_id),
deleted_at: Set(None),
version: Set(1),
};
perm.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
}
// 2. Create "admin" role with all permissions
let admin_role_id = Uuid::now_v7();
let admin_role = role::ActiveModel {
id: Set(admin_role_id),
tenant_id: Set(tenant_id),
name: Set("管理员".to_string()),
code: Set("admin".to_string()),
description: Set(Some("系统管理员,拥有所有权限".to_string())),
is_system: Set(true),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(system_user_id),
updated_by: Set(system_user_id),
deleted_at: Set(None),
version: Set(1),
};
admin_role.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
// Assign all permissions to admin role
// role_permission uses composite PK (role_id, permission_id) -- no separate id column
for perm_id in &perm_ids {
let rp = role_permission::ActiveModel {
role_id: Set(admin_role_id),
permission_id: Set(*perm_id),
tenant_id: Set(tenant_id),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(system_user_id),
updated_by: Set(system_user_id),
deleted_at: Set(None),
version: Set(1),
};
rp.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
}
// 3. Create "viewer" role with read-only permissions
let viewer_role_id = Uuid::now_v7();
let viewer_role = role::ActiveModel {
id: Set(viewer_role_id),
tenant_id: Set(tenant_id),
name: Set("查看者".to_string()),
code: Set("viewer".to_string()),
description: Set(Some("只读用户,可查看所有数据".to_string())),
is_system: Set(true),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(system_user_id),
updated_by: Set(system_user_id),
deleted_at: Set(None),
version: Set(1),
};
viewer_role.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
// Assign read permissions to viewer role
for idx in READ_PERM_INDICES {
if *idx < perm_ids.len() {
let rp = role_permission::ActiveModel {
role_id: Set(viewer_role_id),
permission_id: Set(perm_ids[*idx]),
tenant_id: Set(tenant_id),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(system_user_id),
updated_by: Set(system_user_id),
deleted_at: Set(None),
version: Set(1),
};
rp.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
}
}
// 4. Create super admin user
let admin_user_id = Uuid::now_v7();
let password_hash = password::hash_password(super_admin_password)?;
let admin_user = user::ActiveModel {
id: Set(admin_user_id),
tenant_id: Set(tenant_id),
username: Set("admin".to_string()),
email: Set(None),
phone: Set(None),
display_name: Set(Some("系统管理员".to_string())),
avatar_url: Set(None),
status: Set("active".to_string()),
last_login_at: Set(None),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(system_user_id),
updated_by: Set(system_user_id),
deleted_at: Set(None),
version: Set(1),
};
admin_user.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
// Create password credential for admin user
let cred = user_credential::ActiveModel {
id: Set(Uuid::now_v7()),
tenant_id: Set(tenant_id),
user_id: Set(admin_user_id),
credential_type: Set("password".to_string()),
credential_data: Set(Some(serde_json::json!({ "hash": password_hash }))),
verified: Set(true),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(system_user_id),
updated_by: Set(system_user_id),
deleted_at: Set(None),
version: Set(1),
};
cred.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
// 5. Assign admin role to admin user
// user_role uses composite PK (user_id, role_id) -- no separate id column
let user_role_assignment = user_role::ActiveModel {
user_id: Set(admin_user_id),
role_id: Set(admin_role_id),
tenant_id: Set(tenant_id),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(system_user_id),
updated_by: Set(system_user_id),
deleted_at: Set(None),
version: Set(1),
};
user_role_assignment.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
tracing::info!(
tenant_id = %tenant_id,
admin_user_id = %admin_user_id,
"Seeded tenant auth: admin user, 2 roles, {} permissions",
DEFAULT_PERMISSIONS.len()
);
Ok(())
}