adding features

This commit is contained in:
DioCrafts
2025-04-12 18:58:21 +02:00
parent 090ab07423
commit f2ecbc1a39
7 changed files with 394 additions and 61 deletions
+71 -57
View File
@@ -1,89 +1,103 @@
# Solución para el issue #45: [BUG] Admin already exists?
# Solución para el error "El usuario 'admin' ya existe"
## Descripción del problema
## Problema
Al intentar configurar un usuario administrador, algunos usuarios reciben el error:
Cuando se intenta crear un usuario administrador en una instalación nueva de OxiCloud, aparece el siguiente error:
```
2025-04-09T14:01:26.733566Z ERROR oxicloud::interfaces::api::handlers::auth_handler: Registration failed for user admin: Already Exists: El usuario 'admin' ya existe
oxicloud-1 | 2025-04-12T10:47:26.643669Z ERROR oxicloud::interfaces::api::handlers::auth_handler: Registration failed for user admin: Already Exists: El usuario 'admin' ya existe
```
## Causa raíz
Este problema ocurre debido a cómo está implementada la migración de datos inicial. En el archivo `migrations/20250408000001_default_users.sql`, el sistema intenta crear un usuario admin por defecto durante la migración inicial, pero:
1. Si luego el usuario intenta crear manualmente otro usuario con nombre "admin", el sistema detecta el conflicto.
2. La cláusula `ON CONFLICT (id) DO NOTHING` solo previene conflictos en el ID, no en el nombre de usuario.
Este error ocurre porque las migraciones de la base de datos ya crean un usuario administrador por defecto como parte del proceso de inicialización.
## Solución implementada
Hemos realizado los siguientes cambios:
Hemos mejorado el sistema para que maneje mejor el registro de usuarios administradores:
1. Modificado `migrations/20250408000001_default_users.sql` para comprobar primero si el usuario "admin" ya existe:
1. **En una instalación nueva**:
- Si registras cualquier usuario como administrador (sea cual sea su nombre), el sistema detectará que es una instalación nueva y eliminará automáticamente el usuario admin predeterminado.
- Esto te permite crear tu propio usuario administrador con el nombre que prefieras desde el principio.
```sql
-- Check if admin user already exists before creating it
DO $$$
BEGIN
IF NOT EXISTS (SELECT 1 FROM auth.users WHERE username = 'admin') THEN
-- Create admin user (password: Admin123!)
INSERT INTO auth.users (
id,
username,
...
) VALUES (...);
END IF;
END;
$$$;
```
2. **En un sistema en uso**:
- No se permite crear nuevos usuarios administradores desde la página de registro una vez que ya existe un administrador en el sistema
- Esto previene la creación no autorizada de usuarios con permisos elevados
- El sistema tampoco permite tener múltiples usuarios con el mismo nombre (incluido "admin")
2. Creado un script `scripts/reset_admin.sql` que puede ejecutarse para eliminar el usuario admin existente:
### Detalles técnicos
```sql
-- Set the correct schema
SET search_path TO auth;
La solución implementa:
-- Delete the admin user if it exists
DELETE FROM auth.users WHERE username = 'admin';
1. Detección inteligente de instalaciones nuevas basada en:
- Verificación del número total de usuarios en el sistema
- Verificación del número de usuarios administradores
2. Reconocimiento de usuarios administradores:
- Un usuario es administrador si su nombre es "admin"
- Un usuario es administrador si se proporciona un rol "admin" explícitamente
- Los administradores reciben automáticamente una cuota de 100GB
3. Eliminación segura del usuario admin predeterminado:
- Se detecta al inicio del registro si es una instalación nueva
- Se elimina el admin predeterminado antes de continuar con el registro
4. Prevención de creación de múltiples administradores:
- Una vez que existe un usuario administrador en el sistema, no se permite crear más administradores desde la página de registro
- Solo se puede crear un administrador desde la página de registro durante la instalación inicial
- Esto protege el sistema contra la creación no autorizada de usuarios con privilegios elevados
-- Output remaining users for verification
SELECT username, email, role FROM auth.users ORDER BY role, username;
```
## Cómo usar esta funcionalidad
3. Actualizado la documentación en `doc/DATABASE-MIGRATIONS.md` con instrucciones detalladas para resolver este problema.
### En una instalación nueva:
## Instrucciones para usuarios afectados
1. Inicia OxiCloud por primera vez (las migraciones crearán automáticamente un usuario admin predeterminado)
2. Ve a la pantalla de registro y crea un usuario con:
- **Nombre de usuario**: Cualquier nombre que prefieras (por ejemplo, "torrefacto")
- **Contraseña**: La que tú quieras
- **Email**: Tu correo electrónico
3. El sistema detectará automáticamente que se trata de una instalación nueva
4. Si es un usuario administrador (porque el nombre es "admin" o porque explícitamente quieres que sea admin), el sistema eliminará el admin predeterminado antes de continuar
5. Tu nuevo usuario se creará y podrás iniciar sesión con él
Si encuentras el error "Admin already exists", tienes dos opciones:
### Si necesitas restablecer el usuario administrador:
### Opción 1: Usar el script proporcionado
Si ya tienes un sistema en uso y necesitas restablecer el usuario administrador:
#### Opción 1: Usar el script proporcionado
```bash
cat scripts/reset_admin.sql | docker exec -i oxicloud-postgres-1 psql -U postgres -d oxicloud
```
### Opción 2: Hacerlo manualmente
1. Conéctate al contenedor de PostgreSQL:
#### Opción 2: Hacerlo manualmente
```bash
# Encuentra el contenedor
docker ps
# Ejemplo: oxicloud-postgres-1
docker exec -it oxicloud-postgres-1 bash
docker exec -it oxicloud-postgres-1 psql -U postgres -d oxicloud
```
2. Conéctate a la base de datos:
```bash
psql -U postgres -d oxicloud
```
3. Borra el usuario admin existente:
```sql
SET search_path TO auth;
DELETE FROM auth.users WHERE username = 'admin';
```
4. Verifica que se eliminó correctamente:
```sql
SELECT username, email, role FROM auth.users;
```
5. Sal y registra un nuevo usuario admin a través de la interfaz web.
Luego registra un nuevo usuario admin a través de la interfaz web.
## Nota técnica
El usuario administrador predeterminado se crea durante las migraciones con estos valores:
```sql
INSERT INTO auth.users (
id,
username,
email,
password_hash,
role,
storage_quota_bytes
) VALUES (
'00000000-0000-0000-0000-000000000000',
'admin',
'admin@oxicloud.local',
'$argon2id$v=19$m=65536,t=3,p=4$c2FsdHNhbHRzYWx0c2FsdA$H3VxE8LL2qPT31DM3loTg6D+O4MSc2sD7GjlQ5h7Jkw', -- Admin123!
'admin',
107374182400 -- 100GB for admin
);
```
+1
View File
@@ -44,6 +44,7 @@ pub struct RegisterDto {
pub username: String,
pub email: String,
pub password: String,
pub role: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
+6
View File
@@ -26,6 +26,12 @@ pub trait UserStoragePort: Send + Sync + 'static {
/// Lista usuarios con paginación
async fn list_users(&self, limit: i64, offset: i64) -> Result<Vec<User>, DomainError>;
/// Lista usuarios por rol (por ejemplo, "admin" o "user")
async fn list_users_by_role(&self, role: &str) -> Result<Vec<User>, DomainError>;
/// Elimina un usuario por su ID
async fn delete_user(&self, user_id: &str) -> Result<(), DomainError>;
/// Cambia la contraseña de un usuario
async fn change_password(&self, user_id: &str, password_hash: &str) -> Result<(), DomainError>;
}
@@ -53,16 +53,87 @@ impl AuthApplicationService {
));
}
// Cuota predeterminada: 1GB (ajustable según plan)
let default_quota = 1024 * 1024 * 1024; // 1GB
// Verificar si el usuario quiere crear un admin
let is_admin_request = dto.username.to_lowercase() == "admin" ||
(dto.role.is_some() && dto.role.as_ref().unwrap().to_lowercase() == "admin");
// Si está intentando crear un admin, verificar si ya existen admins en el sistema
if is_admin_request {
match self.count_admin_users().await {
Ok(admin_count) => {
// Si ya hay admins en el sistema y no estamos en instalación limpia,
// no permitimos crear otro admin desde el registro
if admin_count > 0 {
// Verificar si es una instalación limpia (solo el admin predeterminado)
match self.count_all_users().await {
Ok(user_count) => {
// Si hay más de 2 usuarios (admin + test), no es instalación limpia
if user_count > 2 {
tracing::warn!("Intento de crear admin adicional rechazado: ya existe al menos un admin");
return Err(DomainError::new(
ErrorKind::AccessDenied,
"User",
"No se permite crear usuarios admin adicionales desde la página de registro"
));
}
// En caso contrario, es instalación limpia y se permite el primer admin
tracing::info!("Permitiendo creación de admin en instalación limpia");
},
Err(e) => {
tracing::error!("Error al contar usuarios: {}", e);
// Por seguridad, si no podemos verificar, rechazamos la creación de admin
return Err(DomainError::new(
ErrorKind::AccessDenied,
"User",
"No se permite crear usuarios admin adicionales"
));
}
}
}
},
Err(e) => {
tracing::error!("Error al contar usuarios admin: {}", e);
// Por seguridad, si no podemos verificar, rechazamos la creación de admin
return Err(DomainError::new(
ErrorKind::AccessDenied,
"User",
"No se permite crear usuarios admin adicionales"
));
}
}
}
// Determinar rol y cuota según el tipo de usuario
// Si se proporciona un rol explícito de "admin", usar rol de administrador
let role = if let Some(role_str) = &dto.role {
if role_str.to_lowercase() == "admin" {
UserRole::Admin
} else {
UserRole::User
}
} else {
// Caso especial: si el nombre es "admin", asignar rol de admin aunque no se especifique
if dto.username.to_lowercase() == "admin" {
UserRole::Admin
} else {
UserRole::User
}
};
// Cuota según el rol: 100GB para admin, 1GB para usuarios normales
let quota = if role == UserRole::Admin {
107374182400 // 100GB para admin
} else {
1024 * 1024 * 1024 // 1GB para usuarios normales
};
// Crear usuario
let user = User::new(
dto.username.clone(),
dto.email,
dto.password,
UserRole::User, // Por defecto: usuario normal
default_quota,
role,
quota,
).map_err(|e| DomainError::new(
ErrorKind::InvalidInput,
"User",
@@ -314,6 +385,129 @@ impl AuthApplicationService {
self.get_user(user_id).await
}
// New method to get user by username - needed for admin user handling
pub async fn get_user_by_username(&self, username: &str) -> Result<UserDto, DomainError> {
let user = self.user_storage.get_user_by_username(username).await?;
Ok(UserDto::from(user))
}
// Method to count how many admin users exist in the system
// Used to determine if we have multiple admins or just the default one
pub async fn count_admin_users(&self) -> Result<i64, DomainError> {
// Use the list_users_by_role method or similar from user_storage port
// For now, we'll use a basic implementation that counts all users with role = "admin"
let admin_users = self.user_storage.list_users_by_role("admin").await
.map_err(|e| DomainError::new(
ErrorKind::InternalError,
"User",
format!("Error al contar usuarios administradores: {}", e)
))?;
Ok(admin_users.len() as i64)
}
// Method to count all users in the system
// Used to determine if this is a fresh install
pub async fn count_all_users(&self) -> Result<i64, DomainError> {
// Get all users with large limit and 0 offset
let all_users = self.user_storage.list_users(1000, 0).await
.map_err(|e| DomainError::new(
ErrorKind::InternalError,
"User",
format!("Error al contar usuarios: {}", e)
))?;
Ok(all_users.len() as i64)
}
// Method to delete the default admin user created by migrations
// Used in fresh installations before creating a custom admin
pub async fn delete_default_admin(&self) -> Result<(), DomainError> {
// Find the default admin user (created by migrations)
match self.get_user_by_username("admin").await {
Ok(default_admin) => {
// Delete the default admin user
self.user_storage.delete_user(&default_admin.id).await
.map_err(|e| DomainError::new(
ErrorKind::InternalError,
"User",
format!("Error al eliminar usuario admin predeterminado: {}", e)
))
},
Err(_) => {
// Admin user doesn't exist, nothing to do
tracing::info!("Default admin user not found, nothing to delete");
Ok(())
}
}
}
// Method to replace the default admin user with a custom one
// Used in fresh installations to allow users to set their own admin credentials
pub async fn replace_default_admin(&self, dto: &RegisterDto) -> Result<UserDto, DomainError> {
// 1. Get the default admin user
let default_admin = self.get_user_by_username("admin").await?;
// 2. Delete the default admin user
self.user_storage.delete_user(&default_admin.id).await
.map_err(|e| DomainError::new(
ErrorKind::InternalError,
"User",
format!("Error al eliminar usuario admin predeterminado: {}", e)
))?;
// 3. Create new admin user with the provided credentials but admin role
let admin_role = UserRole::Admin;
// Use 100GB for admin quota
let admin_quota = 107374182400;
// Create the new admin user
let user = User::new(
dto.username.clone(),
dto.email.clone(),
dto.password.clone(),
admin_role,
admin_quota,
).map_err(|e| DomainError::new(
ErrorKind::InvalidInput,
"User",
format!("Error al crear usuario admin: {}", e)
))?;
// 4. Save the new admin user
let created_user = self.user_storage.create_user(user).await?;
// 5. Create personal folder for the new admin if folder service is available
if let Some(folder_service) = &self.folder_service {
let folder_name = format!("Mi Carpeta - {}", dto.username);
match folder_service.create_folder(CreateFolderDto {
name: folder_name,
parent_id: None,
}).await {
Ok(folder) => {
tracing::info!(
"Carpeta personal creada para el admin {}: {} (ID: {})",
created_user.id(),
folder.name,
folder.id
);
},
Err(e) => {
tracing::error!(
"No se pudo crear la carpeta personal para el admin {}: {}",
created_user.id(),
e
);
}
}
}
tracing::info!("Admin personalizado creado: {}", created_user.id());
Ok(UserDto::from(created_user))
}
pub async fn list_users(&self, limit: i64, offset: i64) -> Result<Vec<UserDto>, DomainError> {
let users = self.user_storage.list_users(limit, offset).await?;
Ok(users.into_iter().map(UserDto::from).collect())
@@ -86,6 +86,9 @@ pub trait UserRepository: Send + Sync + 'static {
/// Cambia el rol de un usuario
async fn change_role(&self, user_id: &str, role: UserRole) -> UserRepositoryResult<()>;
/// Lista usuarios por rol (admin o user)
async fn list_users_by_role(&self, role: &str) -> UserRepositoryResult<Vec<User>>;
/// Elimina un usuario
async fn delete_user(&self, user_id: &str) -> UserRepositoryResult<()>;
}
@@ -425,6 +425,52 @@ impl UserRepository for UserPgRepository {
Ok(())
}
/// Lista usuarios por rol
async fn list_users_by_role(&self, role: &str) -> UserRepositoryResult<Vec<User>> {
let rows = sqlx::query(
r#"
SELECT
id, username, email, password_hash, role::text as role_text,
storage_quota_bytes, storage_used_bytes,
created_at, updated_at, last_login_at, active
FROM auth.users
WHERE role::text = $1
ORDER BY created_at DESC
"#
)
.bind(role)
.fetch_all(&*self.pool)
.await
.map_err(Self::map_sqlx_error)?;
let users = rows.into_iter()
.map(|row| {
// Convert role string to UserRole enum for each row
let role_str: Option<String> = row.try_get("role_text").unwrap_or(None);
let role = match role_str.as_deref() {
Some("admin") => UserRole::Admin,
_ => UserRole::User,
};
User::from_data(
row.get("id"),
row.get("username"),
row.get("email"),
row.get("password_hash"),
role,
row.get("storage_quota_bytes"),
row.get("storage_used_bytes"),
row.get("created_at"),
row.get("updated_at"),
row.get("last_login_at"),
row.get("active"),
)
})
.collect();
Ok(users)
}
/// Elimina un usuario
async fn delete_user(&self, user_id: &str) -> UserRepositoryResult<()> {
sqlx::query(
@@ -475,6 +521,16 @@ impl UserStoragePort for UserPgRepository {
UserRepository::list_users(self, limit, offset).await.map_err(DomainError::from)
}
async fn list_users_by_role(&self, role: &str) -> Result<Vec<User>, DomainError> {
UserRepository::list_users_by_role(self, role).await.map_err(DomainError::from)
}
async fn delete_user(&self, user_id: &str) -> Result<(), DomainError> {
UserRepository::delete_user(self, user_id)
.await
.map_err(DomainError::from)
}
async fn change_password(&self, user_id: &str, password_hash: &str) -> Result<(), DomainError> {
UserRepository::change_password(self, user_id, password_hash)
.await
@@ -66,6 +66,65 @@ async fn register(
return Ok((StatusCode::CREATED, Json(mock_user)));
}
// Check if this is a fresh install
tracing::info!("New user registration detected, checking if it's a fresh install");
// Detect if we're in a fresh install with just the default admin user
match auth_service.auth_application_service.count_admin_users().await {
Ok(admin_count) => {
// If we have exactly one admin user (the default one from migrations)
if admin_count == 1 {
tracing::info!("Found one admin user - checking if it's the default admin");
// Verify it's truly a fresh install by counting all users
match auth_service.auth_application_service.count_all_users().await {
Ok(user_count) => {
// In a fresh install with only the default admin (and possibly test user)
if user_count <= 2 { // Allow for admin + test user from migrations
tracing::info!("This appears to be a fresh install with just default users");
// Check if the user is trying to create an admin user (via role field or username)
let is_admin_registration =
dto.username.to_lowercase() == "admin" ||
(dto.role.is_some() && dto.role.as_ref().unwrap().to_lowercase() == "admin");
// If we're registering an admin user in a fresh install
if is_admin_registration {
tracing::info!("Admin user registration detected in fresh install");
// Remove the default admin user and create the new customized one
match auth_service.auth_application_service.delete_default_admin().await {
Ok(_) => {
tracing::info!("Successfully deleted default admin");
// Proceed with normal registration (now that default admin is removed)
// Normal registration will continue below
},
Err(err) => {
tracing::error!("Failed to delete default admin: {}", err);
// Continue anyway - worst case we'll get an error during registration
// if there's a username conflict
}
}
} else {
// Non-admin user registration in fresh install, proceed normally
tracing::info!("Regular user registration in fresh install, proceeding normally");
}
}
},
Err(err) => {
tracing::error!("Error counting users: {}", err);
// Not critical, continue with registration
}
}
}
},
Err(err) => {
tracing::error!("Error counting admin users: {}", err);
// Not critical, continue with registration
}
}
// Try the normal registration process
match auth_service.auth_application_service.register(dto.clone()).await {
Ok(user) => {