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 @@