Files
2026-04-12 17:12:19 -04:00

149 lines
4.4 KiB
JavaScript

// backend/authentik.js
'use strict';
const axios = require('axios');
const QRCode = require('qrcode');
const config = require('./config');
const logger = require('./logger');
const api = axios.create({
baseURL: `${config.authentik_url}/api/v3`,
headers: {
Authorization: `Bearer ${config.authentik_admin_token}`,
'Content-Type': 'application/json',
},
timeout: 10_000,
});
api.interceptors.response.use(
(res) => res,
(err) => {
const status = err.response?.status;
const detail = err.response?.data?.detail || err.message;
logger.error(`[Authentik API] ${err.config?.method?.toUpperCase()} ${err.config?.url}${status}: ${detail}`);
return Promise.reject(err);
}
);
async function listUsers({ search = '', page = 1, pageSize = 50 } = {}) {
const params = { page, page_size: pageSize };
if (search) params.search = search;
const { data } = await api.get('/core/users/', { params });
const totpDevices = await listAllTotpDevices();
const totpByUser = new Map();
for (const d of totpDevices) {
totpByUser.set(d.user, true);
}
const users = data.results.map((u) => ({
id: u.pk,
username: u.username,
name: u.name,
email: u.email,
isActive: u.is_active,
isSuperuser: u.is_superuser,
groups: u.groups_obj?.map((g) => ({ id: g.pk, name: g.name })) ?? [],
avatar: u.avatar,
lastLogin: u.last_login,
dateJoined: u.date_joined,
totpEnrolled: totpByUser.has(u.pk),
type: u.type,
}));
return { users, count: data.count, page, pageSize };
}
async function getUser(userId) {
const { data: u } = await api.get(`/core/users/${userId}/`);
return {
id: u.pk,
username: u.username,
name: u.name,
email: u.email,
isActive: u.is_active,
isSuperuser: u.is_superuser,
groups: u.groups_obj?.map((g) => ({ id: g.pk, name: g.name })) ?? [],
avatar: u.avatar,
lastLogin: u.last_login,
dateJoined: u.date_joined,
type: u.type,
};
}
async function createUser({ username, name, email, isActive = true, groups = [], password }) {
const payload = {
username, name, email, is_active: isActive, groups, type: 'internal',
};
const { data: u } = await api.post('/core/users/', payload);
if (password) { await setPassword(u.pk, password); }
return getUser(u.pk);
}
async function updateUser(userId, { name, email, isActive, groups }) {
const payload = {};
if (name !== undefined) payload.name = name;
if (email !== undefined) payload.email = email;
if (isActive !== undefined) payload.is_active = isActive;
if (groups !== undefined) payload.groups = groups;
await api.patch(`/core/users/${userId}/`, payload);
return getUser(userId);
}
async function deleteUser(userId) {
await api.delete(`/core/users/${userId}/`);
}
async function setPassword(userId, password) {
await api.post(`/core/users/${userId}/set_password/`, { password });
}
async function listGroups({ search = '' } = {}) {
const params = { page_size: 100 };
if (search) params.search = search;
const { data } = await api.get('/core/groups/', { params });
return data.results.map((g) => ({
id: g.pk, name: g.name, userCount: g.num_pk ?? 0,
}));
}
async function listAllTotpDevices() {
const { data } = await api.get('/authenticators/totp/', { params: { page_size: 1000 } });
return data.results;
}
async function revokeTotpForUser(userId) {
const { data } = await api.get('/authenticators/totp/', {
params: { user: userId, page_size: 100 },
});
await Promise.all(data.results.map((d) => api.delete(`/authenticators/totp/${d.pk}/`)));
return data.results.length;
}
async function generateTwoFactorSetupLink(userId) {
await revokeTotpForUser(userId);
const { data } = await api.post(`/core/users/${userId}/recovery/`);
const setupUrl = data.link;
const qrDataUrl = await QRCode.toDataURL(setupUrl, {
width: 280, margin: 2,
color: { dark: '#0ea5e9', light: '#0a0f1e' },
errorCorrectionLevel: 'M',
});
return { setupUrl, qrDataUrl };
}
async function validateAdminToken() {
try {
await api.get('/core/users/me/');
return true;
} catch {
return false;
}
}
module.exports = {
listUsers, getUser, createUser, updateUser, deleteUser, setPassword,
listGroups, revokeTotpForUser, generateTwoFactorSetupLink, validateAdminToken,
};