mirror of
https://github.com/recklessop/zroc.git
synced 2026-07-02 21:13:15 -04:00
5a617fd550
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
149 lines
4.4 KiB
JavaScript
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,
|
|
};
|