barycenter/src/user_sync.rs

181 lines
5.6 KiB
Rust
Raw Normal View History

feat: add admin GraphQL API, background jobs, and user sync CLI Major Features: - Admin GraphQL API with dual endpoints (Seaography + custom) - Background job scheduler with execution tracking - Idempotent user sync CLI for Kubernetes deployments - Secure PUT /properties endpoint with Bearer token auth Admin GraphQL API: - Entity CRUD via Seaography at /admin/graphql - Custom job management API at /admin/jobs - Mutations: triggerJob - Queries: jobLogs, availableJobs - GraphiQL playgrounds for both endpoints Background Jobs: - tokio-cron-scheduler integration - Automated cleanup of expired sessions (hourly) - Automated cleanup of expired refresh tokens (hourly) - Job execution tracking in database - Manual job triggering via GraphQL User Sync CLI: - Command: barycenter sync-users --file users.json - Idempotent user synchronization from JSON - Creates new users with hashed passwords - Updates existing users (enabled, email_verified, email) - Syncs custom properties per user - Perfect for Kubernetes init containers Security Enhancements: - PUT /properties endpoint requires Bearer token - Users can only modify their own properties - Public registration disabled by default - Admin API on separate port for network isolation Database: - New job_executions table for job tracking - User update functions (update_user, update_user_email) - PostgreSQL + SQLite support maintained Configuration: - allow_public_registration setting (default: false) - admin_port setting (default: main port + 1) Documentation: - Comprehensive Kubernetes deployment guide - User sync JSON schema and examples - Init container and CronJob examples - Production deployment patterns Files Added: - src/admin_graphql.rs - GraphQL schema builders - src/admin_mutations.rs - Custom mutations and queries - src/jobs.rs - Job scheduler and tracking - src/user_sync.rs - User sync logic - src/entities/ - SeaORM entities (8 entities) - docs/kubernetes-deployment.md - K8s deployment guide - users.json.example - User sync example Dependencies: - tokio-cron-scheduler 0.13 - seaography 1.1.4 - async-graphql 7.0 - async-graphql-axum 7.0 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 18:06:50 +01:00
use crate::errors::CrabError;
use crate::storage;
use miette::{IntoDiagnostic, Result};
use sea_orm::DatabaseConnection;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
/// User definition from JSON file
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserDefinition {
/// Username (unique identifier)
pub username: String,
/// User email
#[serde(default)]
pub email: Option<String>,
/// Plain text password (will be hashed)
pub password: String,
/// Whether the user account is enabled
#[serde(default = "default_true")]
pub enabled: bool,
/// Whether the email is verified
#[serde(default)]
pub email_verified: bool,
/// Custom properties to attach to the user
#[serde(default)]
pub properties: HashMap<String, Value>,
}
fn default_true() -> bool {
true
}
/// Root structure of the users JSON file
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsersFile {
pub users: Vec<UserDefinition>,
}
/// Sync users from a JSON file to the database (idempotent)
pub async fn sync_users_from_file(db: &DatabaseConnection, file_path: &str) -> Result<()> {
tracing::info!("Loading users from {}", file_path);
// Read and parse JSON file
let content = fs::read_to_string(file_path)
.into_diagnostic()
.map_err(|e| miette::miette!("Failed to read users file at '{}': {}", file_path, e))?;
feat: add admin GraphQL API, background jobs, and user sync CLI Major Features: - Admin GraphQL API with dual endpoints (Seaography + custom) - Background job scheduler with execution tracking - Idempotent user sync CLI for Kubernetes deployments - Secure PUT /properties endpoint with Bearer token auth Admin GraphQL API: - Entity CRUD via Seaography at /admin/graphql - Custom job management API at /admin/jobs - Mutations: triggerJob - Queries: jobLogs, availableJobs - GraphiQL playgrounds for both endpoints Background Jobs: - tokio-cron-scheduler integration - Automated cleanup of expired sessions (hourly) - Automated cleanup of expired refresh tokens (hourly) - Job execution tracking in database - Manual job triggering via GraphQL User Sync CLI: - Command: barycenter sync-users --file users.json - Idempotent user synchronization from JSON - Creates new users with hashed passwords - Updates existing users (enabled, email_verified, email) - Syncs custom properties per user - Perfect for Kubernetes init containers Security Enhancements: - PUT /properties endpoint requires Bearer token - Users can only modify their own properties - Public registration disabled by default - Admin API on separate port for network isolation Database: - New job_executions table for job tracking - User update functions (update_user, update_user_email) - PostgreSQL + SQLite support maintained Configuration: - allow_public_registration setting (default: false) - admin_port setting (default: main port + 1) Documentation: - Comprehensive Kubernetes deployment guide - User sync JSON schema and examples - Init container and CronJob examples - Production deployment patterns Files Added: - src/admin_graphql.rs - GraphQL schema builders - src/admin_mutations.rs - Custom mutations and queries - src/jobs.rs - Job scheduler and tracking - src/user_sync.rs - User sync logic - src/entities/ - SeaORM entities (8 entities) - docs/kubernetes-deployment.md - K8s deployment guide - users.json.example - User sync example Dependencies: - tokio-cron-scheduler 0.13 - seaography 1.1.4 - async-graphql 7.0 - async-graphql-axum 7.0 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 18:06:50 +01:00
let users_file: UsersFile = serde_json::from_str(&content)
.into_diagnostic()
.map_err(|e| {
miette::miette!(
"Failed to parse users JSON file: {}\n\nExpected format:\n{{\n \"users\": [\n {{\n \"username\": \"alice\",\n \"email\": \"alice@example.com\",\n \"password\": \"secure-password\",\n \"enabled\": true,\n \"email_verified\": false,\n \"properties\": {{\n \"department\": \"Engineering\"\n }}\n }}\n ]\n}}",
e
)
})?;
tracing::info!("Found {} user(s) in file", users_file.users.len());
let mut created = 0;
let mut updated = 0;
let mut unchanged = 0;
for user_def in users_file.users {
match sync_user(db, &user_def).await? {
SyncResult::Created => created += 1,
SyncResult::Updated => updated += 1,
SyncResult::Unchanged => unchanged += 1,
}
}
tracing::info!(
"User sync complete: {} created, {} updated, {} unchanged",
created,
updated,
unchanged
);
Ok(())
}
#[derive(Debug)]
enum SyncResult {
Created,
Updated,
Unchanged,
}
/// Sync a single user (idempotent)
async fn sync_user(db: &DatabaseConnection, user_def: &UserDefinition) -> Result<SyncResult> {
// Check if user exists
let existing = storage::get_user_by_username(db, &user_def.username)
.await
.into_diagnostic()?;
let result = match existing {
None => {
// Create new user
tracing::info!("Creating user: {}", user_def.username);
storage::create_user(
db,
&user_def.username,
&user_def.password,
user_def.email.clone(),
)
.await
.into_diagnostic()?;
// Update enabled and email_verified flags if needed
if !user_def.enabled || user_def.email_verified {
storage::update_user(
db,
&user_def.username,
user_def.enabled,
user_def.email_verified,
)
.await
.into_diagnostic()?;
}
SyncResult::Created
}
Some(existing_user) => {
// User exists - check if update is needed
let enabled_matches = (existing_user.enabled == 1) == user_def.enabled;
let email_verified_matches =
(existing_user.email_verified == 1) == user_def.email_verified;
let email_matches = existing_user.email == user_def.email;
if !enabled_matches || !email_verified_matches || !email_matches {
tracing::info!("Updating user: {}", user_def.username);
storage::update_user(
db,
&user_def.username,
user_def.enabled,
user_def.email_verified,
)
.await
.into_diagnostic()?;
// Update email if it changed
if !email_matches {
storage::update_user_email(db, &user_def.username, user_def.email.clone())
.await
.into_diagnostic()?;
}
SyncResult::Updated
} else {
SyncResult::Unchanged
}
}
};
// Sync properties
for (key, value) in &user_def.properties {
// Get the user's subject to use as owner for properties
let user = storage::get_user_by_username(db, &user_def.username)
.await
.into_diagnostic()?
.ok_or_else(|| {
miette::miette!("User not found after creation: {}", user_def.username)
})?;
feat: add admin GraphQL API, background jobs, and user sync CLI Major Features: - Admin GraphQL API with dual endpoints (Seaography + custom) - Background job scheduler with execution tracking - Idempotent user sync CLI for Kubernetes deployments - Secure PUT /properties endpoint with Bearer token auth Admin GraphQL API: - Entity CRUD via Seaography at /admin/graphql - Custom job management API at /admin/jobs - Mutations: triggerJob - Queries: jobLogs, availableJobs - GraphiQL playgrounds for both endpoints Background Jobs: - tokio-cron-scheduler integration - Automated cleanup of expired sessions (hourly) - Automated cleanup of expired refresh tokens (hourly) - Job execution tracking in database - Manual job triggering via GraphQL User Sync CLI: - Command: barycenter sync-users --file users.json - Idempotent user synchronization from JSON - Creates new users with hashed passwords - Updates existing users (enabled, email_verified, email) - Syncs custom properties per user - Perfect for Kubernetes init containers Security Enhancements: - PUT /properties endpoint requires Bearer token - Users can only modify their own properties - Public registration disabled by default - Admin API on separate port for network isolation Database: - New job_executions table for job tracking - User update functions (update_user, update_user_email) - PostgreSQL + SQLite support maintained Configuration: - allow_public_registration setting (default: false) - admin_port setting (default: main port + 1) Documentation: - Comprehensive Kubernetes deployment guide - User sync JSON schema and examples - Init container and CronJob examples - Production deployment patterns Files Added: - src/admin_graphql.rs - GraphQL schema builders - src/admin_mutations.rs - Custom mutations and queries - src/jobs.rs - Job scheduler and tracking - src/user_sync.rs - User sync logic - src/entities/ - SeaORM entities (8 entities) - docs/kubernetes-deployment.md - K8s deployment guide - users.json.example - User sync example Dependencies: - tokio-cron-scheduler 0.13 - seaography 1.1.4 - async-graphql 7.0 - async-graphql-axum 7.0 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 18:06:50 +01:00
storage::set_property(db, &user.subject, key, value)
.await
.into_diagnostic()?;
}
if !user_def.properties.is_empty() {
tracing::debug!(
"Synced {} properties for user {}",
user_def.properties.len(),
user_def.username
);
}
Ok(result)
}