Files
Oxicloud/src/application/dtos/user_dto.rs
T

204 lines
7.0 KiB
Rust
Raw Normal View History

2025-03-26 18:33:22 +01:00
use crate::domain::entities::user::User;
2026-02-14 01:29:34 +01:00
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
2025-03-20 09:22:31 +01:00
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2025-03-20 09:22:31 +01:00
pub struct UserDto {
pub id: String,
/// Optional handle. `None` for users who have not claimed one
/// (externals, fresh email-only signups). Frontend display callers
/// should walk `username → given/family → email` as their fallback
/// chain. Omitted from JSON when None (consistent with the existing
/// given_name / family_name fields).
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
2025-03-20 09:22:31 +01:00
pub email: String,
pub role: String,
pub storage_quota_bytes: i64,
pub storage_used_bytes: i64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub last_login_at: Option<DateTime<Utc>>,
pub active: bool,
pub auth_provider: String,
pub image: Option<String>,
pub can_edit_image: bool,
/// `true` for grant-only external recipients (magic-link, OIDC-only,
/// future OCM federated). External users have no home folder and
/// can't own storage; their quota is always 0. Internal users
/// default to `false`.
pub is_external: bool,
/// Optional first/given name. Populated from the OIDC `given_name`
/// claim at JIT provisioning, or via a profile-edit endpoint.
/// `None` until explicitly set — `skip_serializing_if = "Option::is_none"`
/// keeps the wire format compact for the common case.
#[serde(skip_serializing_if = "Option::is_none")]
pub given_name: Option<String>,
/// Optional last/family name. Same provenance + serde rules as
/// `given_name`.
#[serde(skip_serializing_if = "Option::is_none")]
pub family_name: Option<String>,
2025-03-20 09:22:31 +01:00
}
impl From<User> for UserDto {
fn from(user: User) -> Self {
Self {
id: user.id().to_string(),
username: user.username().map(str::to_string),
2025-03-20 09:22:31 +01:00
email: user.email().to_string(),
role: format!("{}", user.role()),
storage_quota_bytes: user.storage_quota_bytes(),
storage_used_bytes: user.storage_used_bytes(),
created_at: user.created_at(),
updated_at: user.updated_at(),
last_login_at: user.last_login_at(),
active: user.is_active(),
2026-02-21 20:33:54 +01:00
auth_provider: user.oidc_provider().unwrap_or("local").to_string(),
image: user.image().map(|s| s.to_string()),
can_edit_image: !user.is_oidc_user(),
is_external: user.is_external(),
given_name: user.given_name().map(str::to_string),
family_name: user.family_name().map(str::to_string),
2025-03-20 09:22:31 +01:00
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
2025-03-20 09:22:31 +01:00
pub struct LoginDto {
/// Identifier the user typed. Accepts BOTH a username (no `@`) and
/// an email address (`@` present). The server dispatches on
/// `@`-in-input: with `@` it looks up by email; without, by
/// username. The two namespaces are provably disjoint (PR 16
/// forbids `@` in usernames), so a single field handles both
/// without ambiguity. The frontend submits whatever the user
/// typed in the "Username or email" field as-is.
2025-03-20 09:22:31 +01:00
pub username: String,
pub password: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
2025-03-20 09:22:31 +01:00
pub struct RegisterDto {
/// Optional handle (2-64 chars, no `@`). When omitted, the user can
/// claim one later via the profile-edit endpoint. Users without a
/// username cannot use NextCloud clients or create app passwords
/// (Basic-Auth resolves users by username); web UI / native API
/// works fine without one.
#[serde(default)]
pub username: Option<String>,
2025-03-20 09:22:31 +01:00
pub email: String,
/// Optional password (≥8 chars when present). When omitted, a
/// welcome magic-link is mailed to `email` for first-session
/// bootstrap. The user can later set a password via the
/// change-password endpoint to switch to classic username/email +
/// password login.
#[serde(default)]
pub password: Option<String>,
}
/// DTO for the one-time initial admin setup endpoint (`/api/setup`).
/// Available only when the system is not yet initialized (no admin exists).
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct SetupAdminDto {
pub username: String,
pub email: String,
pub password: String,
2025-03-20 09:22:31 +01:00
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2025-03-20 09:22:31 +01:00
pub struct AuthResponseDto {
pub user: UserDto,
pub access_token: String,
pub refresh_token: String,
pub token_type: String,
pub expires_in: i64,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
2025-03-20 09:22:31 +01:00
pub struct ChangePasswordDto {
pub current_password: String,
pub new_password: String,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
2025-03-20 09:22:31 +01:00
pub struct RefreshTokenDto {
pub refresh_token: String,
}
/// Authenticated current user data (for use in application services)
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct CurrentUser {
pub id: Uuid,
pub username: String,
pub email: String,
pub role: String,
}
// ============================================================================
// App Password DTOs
// ============================================================================
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct CreateAppPasswordDto {
pub label: String,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct AppPasswordCreatedDto {
pub id: String,
pub label: String,
pub password: String,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct AppPasswordDto {
pub id: String,
pub label: String,
pub created_at: DateTime<Utc>,
pub last_used_at: Option<DateTime<Utc>>,
}
// ============================================================================
// OIDC DTOs
// ============================================================================
/// Response with the OIDC authorization URL for client redirect
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct OidcAuthorizeResponseDto {
pub authorize_url: String,
pub state: String,
}
/// Query parameters received on the OIDC callback
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct OidcCallbackQueryDto {
pub code: String,
pub state: String,
}
/// Request body for the OIDC one-time code exchange endpoint
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct OidcExchangeDto {
pub code: String,
}
/// Information about available OIDC providers
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct OidcProviderInfoDto {
pub enabled: bool,
pub provider_name: String,
pub authorize_endpoint: String,
pub password_login_enabled: bool,
}
/// Claims extracted from the validated OIDC ID token
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct OidcUserInfoDto {
pub sub: String,
pub preferred_username: Option<String>,
pub email: Option<String>,
pub name: Option<String>,
pub groups: Vec<String>,
2026-02-14 01:29:34 +01:00
}