import bcrypt from 'bcryptjs'; import { db, id, now } from '../db.js'; import { publicUser } from '../auth.js'; export function listUsers() { return db.prepare('SELECT * FROM users ORDER BY display_name').all().map((row) => ({ ...publicUser(row), groups: db.prepare(` SELECT g.id, g.name FROM groups g INNER JOIN user_groups ug ON ug.group_id = g.id WHERE ug.user_id = ? ORDER BY g.name `).all(row.id) })); } // Provision (or refresh) a user that authenticated through Microsoft Entra. // Matches on email: an existing account is marked Entra-backed in place so the // operator keeps their resources, and a brand-new account is created with no // local password. export function findOrProvisionEntraUser({ email, displayName }) { const normalized = String(email || '').toLowerCase(); const existing = db.prepare('SELECT * FROM users WHERE email = ?').get(normalized); if (existing) { db.prepare('UPDATE users SET display_name = ?, auth_provider = ?, updated_at = ? WHERE id = ?') .run(displayName || existing.display_name, 'entra', now(), existing.id); return db.prepare('SELECT * FROM users WHERE id = ?').get(existing.id); } const userId = id('usr'); const timestamp = now(); // First user on a fresh install becomes admin; subsequent Entra users are standard. const isFirstUser = db.prepare('SELECT COUNT(*) AS count FROM users').get().count === 0; db.prepare(` INSERT INTO users (id, email, display_name, password_hash, auth_provider, role, created_at, updated_at) VALUES (?, ?, ?, NULL, 'entra', ?, ?, ?) `).run(userId, normalized, displayName || normalized, isFirstUser ? 'admin' : 'user', timestamp, timestamp); return db.prepare('SELECT * FROM users WHERE id = ?').get(userId); } export function createUser(payload) { const userId = id('usr'); const timestamp = now(); db.prepare(` INSERT INTO users (id, email, display_name, password_hash, auth_provider, role, created_at) VALUES (?, ?, ?, ?, 'local', ?, ?) `).run( userId, payload.email.toLowerCase(), payload.displayName, payload.password ? bcrypt.hashSync(payload.password, 12) : null, payload.role, timestamp ); const groupStmt = db.prepare('INSERT OR IGNORE INTO user_groups (user_id, group_id) VALUES (?, ?)'); for (const groupId of payload.groupIds || []) groupStmt.run(userId, groupId); return listUsers().find((user) => user.id === userId); }