59 lines
2.4 KiB
JavaScript
59 lines
2.4 KiB
JavaScript
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);
|
|
}
|