diff --git a/src/channels/web/handlers/tokens.rs b/src/channels/web/handlers/tokens.rs index 097f87bf..ad41c291 100644 --- a/src/channels/web/handlers/tokens.rs +++ b/src/channels/web/handlers/tokens.rs @@ -53,8 +53,15 @@ pub async fn tokens_create_handler( // First 8 chars of the hex token as a prefix for identification. let token_prefix = &plaintext_token[..8]; + // Admin users can create tokens for other users via optional "user_id" field. + let target_user = body + .get("user_id") + .and_then(|v| v.as_str()) + .filter(|_| user.role == "admin") + .unwrap_or(&user.user_id); + let record = store - .create_api_token(&user.user_id, &name, &hash, token_prefix, expires_at) + .create_api_token(target_user, &name, &hash, token_prefix, expires_at) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; diff --git a/src/channels/web/handlers/users.rs b/src/channels/web/handlers/users.rs index 177cd247..55feed3b 100644 --- a/src/channels/web/handlers/users.rs +++ b/src/channels/web/handlers/users.rs @@ -7,6 +7,9 @@ use axum::{ extract::{Path, State}, http::StatusCode, }; +use rand::RngCore; +use rand::rngs::OsRng; +use sha2::{Digest, Sha256}; use uuid::Uuid; use crate::channels::web::auth::{AdminUser, AuthenticatedUser}; @@ -67,12 +70,27 @@ pub async fn users_create_handler( .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + // Generate a first API token so the new user can authenticate immediately. + let mut token_bytes = [0u8; 32]; + OsRng.fill_bytes(&mut token_bytes); + let plaintext_token = hex::encode(token_bytes); + let mut hasher = Sha256::new(); + hasher.update(token_bytes); + let token_hash: [u8; 32] = hasher.finalize().into(); + let token_prefix = &plaintext_token[..8]; + + let _token_record = store + .create_api_token(&user_id, "initial", &token_hash, token_prefix, None) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + Ok(Json(serde_json::json!({ "id": user_record.id, "email": user_record.email, "display_name": user_record.display_name, "status": user_record.status, "role": user_record.role, + "token": plaintext_token, "created_at": user_record.created_at.to_rfc3339(), "created_by": user_record.created_by, }))) diff --git a/src/channels/web/static/app.js b/src/channels/web/static/app.js index 9683bcce..ad148143 100644 --- a/src/channels/web/static/app.js +++ b/src/channels/web/static/app.js @@ -4408,14 +4408,28 @@ function createTokenForUser(userId, displayName) { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: tokenName, user_id: userId }), }).then(function(data) { - var banner = document.getElementById('users-token-result'); - banner.style.display = 'block'; - banner.innerHTML = 'Token created! Copy this now — it won\'t be shown again:
' - + '' + escapeHtml(data.token) + '' - + ''; + showTokenBanner(data.token); }).catch(function(e) { alert('Failed to create token: ' + e.message); }); } +function showTokenBanner(tokenValue) { + var banner = document.getElementById('users-token-result'); + if (!banner) return; + banner.style.display = 'block'; + banner.innerHTML = 'Token created! Copy this now — it won\'t be shown again:
' + + '' + escapeHtml(tokenValue) + '' + + ''; + document.getElementById('token-copy-btn').addEventListener('click', function() { + navigator.clipboard.writeText(tokenValue); + this.textContent = 'Copied!'; + }); +} + +// Make functions accessible for delegated event handlers +window.suspendUser = suspendUser; +window.activateUser = activateUser; +window.createTokenForUser = createTokenForUser; + // Wire up Users tab create form document.getElementById('users-create-btn')?.addEventListener('click', function() { document.getElementById('users-create-form').style.display = 'flex'; @@ -4441,10 +4455,13 @@ document.getElementById('users-create-submit')?.addEventListener('click', functi email: email || undefined, role: role, }), - }).then(function() { + }).then(function(data) { document.getElementById('users-create-form').style.display = 'none'; document.getElementById('user-display-name').value = ''; document.getElementById('user-email').value = ''; + if (data.token) { + showTokenBanner(data.token); + } loadUsers(); }).catch(function(e) { alert('Failed to create user: ' + e.message); }); }); diff --git a/src/channels/web/static/index.html b/src/channels/web/static/index.html index 3a1370cb..c05c6912 100644 --- a/src/channels/web/static/index.html +++ b/src/channels/web/static/index.html @@ -397,9 +397,9 @@

User Management

-