Files
Masterluke77andGitHub 40071f718e Vendoo 1.43.0 – Production Update
Manifestbasierter Production-Updater 3.3, Modulnavigation, optionales Produktbild Studio, Listing-Studio-Aktionsleiste sowie plattformübergreifende Windows-/Linux-Release-Gates.
2026-07-10 13:16:21 +02:00

107 lines
8.0 KiB
JavaScript

import { objectSchema } from '../../kernel/input-schema.mjs';
import { getUsersService } from './index.mjs';
const emailRule = { type: 'string', required: true, minLength: 3, maxLength: 254, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ };
const roles = ['admin','manager','editor','publisher','viewer'];
export function registerUsersRoutes({ app, routes, deps }) {
routes.register(app, {
id: 'users.me.read', method: 'GET', path: '/api/me', owner: 'vendoo.users', policy: 'users.self', csrf: false, stability: 'stable',
handler: async (req, res) => res.json(getUsersService().currentIdentity(req.user.id, req.cookies?.csrf_token)),
});
routes.register(app, {
id: 'users.me.update', method: 'PUT', path: '/api/me', owner: 'vendoo.users', policy: 'users.self', csrf: true, stability: 'stable',
input: objectSchema({ name: { type: 'string', required: true, minLength: 1, maxLength: 120 } }),
handler: async (req, res) => {
const user = getUsersService().updateProfile(req.user.id, req.validatedBody);
deps.auditLog(req.user.id, req.user.email, 'user.profile_updated', 'user', String(req.user.id), null, deps.getClientIp(req));
res.json(user);
},
});
routes.register(app, {
id: 'users.admin.list', method: 'GET', path: '/api/admin/users', owner: 'vendoo.users', policy: 'users.manage', csrf: false, stability: 'stable',
handler: async (_req, res) => res.json(getUsersService().list()),
});
routes.register(app, {
id: 'users.admin.invite', method: 'POST', path: '/api/admin/users/invite', owner: 'vendoo.users', policy: 'users.manage', csrf: true, stability: 'stable',
input: objectSchema({ email: emailRule, name: { type: 'string', maxLength: 120 }, role: { type: 'string', enum: roles }, method: { type: 'string', enum: ['magic_link','auto_password'] } }),
handler: async (req, res) => {
const prepared = getUsersService().prepareInvite({ ...req.validatedBody, createdBy: req.user.id });
const baseUrl = `${req.protocol}://${req.get('host')}`;
if (prepared.mode === 'auto_password') {
const mailResult = await deps.sendCredentials(prepared.user.email, prepared.password, prepared.user.name || '', baseUrl);
deps.auditLog(req.user.id, req.user.email, 'user.invited', 'user', prepared.user.email, `Rolle: ${prepared.role}, Auto-Passwort`, deps.getClientIp(req));
return res.json({ ok: true, emailSent: mailResult.sent, consoleFallback: mailResult.consoleFallback || false, password: prepared.password });
}
const result = await deps.sendMagicLink(prepared.email, prepared.invite.token, baseUrl, 'invite');
deps.auditLog(req.user.id, req.user.email, 'user.invited', 'user', prepared.email, `Rolle: ${prepared.role}`, deps.getClientIp(req));
res.json({ ok: true, emailSent: result.sent, consoleFallback: result.consoleFallback || false });
},
});
routes.register(app, {
id: 'users.admin.invite-resend', method: 'POST', path: '/api/admin/users/resend-invite', owner: 'vendoo.users', policy: 'users.manage', csrf: true, stability: 'stable',
input: objectSchema({ user_id: { type: 'integer', required: true, min: 1 } }),
handler: async (req, res) => {
const prepared = getUsersService().resendInvite({ userId: req.validatedBody.user_id, createdBy: req.user.id });
const result = await deps.sendMagicLink(prepared.user.email, prepared.invite.token, `${req.protocol}://${req.get('host')}`, 'invite');
deps.auditLog(req.user.id, req.user.email, 'user.invite_resent', 'user', String(prepared.user.id), null, deps.getClientIp(req));
res.json({ ok: true, emailSent: result.sent, consoleFallback: result.consoleFallback || false });
},
});
routes.register(app, {
id: 'users.admin.invitations.list', method: 'GET', path: '/api/admin/users/invitations', owner: 'vendoo.users', policy: 'users.manage', csrf: false, stability: 'stable',
handler: async (req, res) => res.json({ invitations: getUsersService().invitations(Number(req.query.limit) || 100) }),
});
routes.register(app, {
id: 'users.admin.invitations.resend', method: 'POST', path: '/api/admin/users/invitations/:id/resend', owner: 'vendoo.users', policy: 'users.manage', csrf: true, stability: 'stable',
handler: async (req, res) => {
const prepared = getUsersService().resendPendingInvitation({ invitationId: Number(req.params.id), createdBy: req.user.id });
const result = await deps.sendMagicLink(prepared.invitation.email, prepared.invite.token, `${req.protocol}://${req.get('host')}`, 'invite');
deps.auditLog(req.user.id, req.user.email, 'user.invitation_resent', 'invitation', String(req.params.id), JSON.stringify({ email: prepared.invitation.email, role: prepared.role, expires_at: prepared.invite.expiresAt }), deps.getClientIp(req));
res.json({ ok: true, emailSent: result.sent, consoleFallback: result.consoleFallback || false, expiresAt: prepared.invite.expiresAt });
},
});
routes.register(app, {
id: 'users.admin.invitations.revoke', method: 'DELETE', path: '/api/admin/users/invitations/:id', owner: 'vendoo.users', policy: 'users.manage', csrf: true, stability: 'stable',
handler: async (req, res) => {
const invitation = getUsersService().revokePendingInvitation(Number(req.params.id));
deps.auditLog(req.user.id, req.user.email, 'user.invitation_revoked', 'invitation', String(req.params.id), JSON.stringify({ email: invitation.email, role: invitation.role }), deps.getClientIp(req));
res.json({ ok: true, invitation });
},
});
routes.register(app, {
id: 'users.admin.update', method: 'PUT', path: '/api/admin/users/:id', owner: 'vendoo.users', policy: 'users.manage', csrf: true, stability: 'stable',
input: objectSchema({ name: { type: 'string', maxLength: 120 }, role: { type: 'string', enum: roles }, active: { type: 'integer', min: 0, max: 1 }, avatar_color: { type: 'string', maxLength: 32 } }),
handler: async (req, res) => {
const updated = getUsersService().updateManagedUser({ actor: req.user, userId: Number(req.params.id), patch: req.validatedBody });
deps.auditLog(req.user.id, req.user.email, 'user.updated', 'user', String(req.params.id), JSON.stringify(req.validatedBody), deps.getClientIp(req));
res.json(updated);
},
});
routes.register(app, {
id: 'users.admin.delete', method: 'DELETE', path: '/api/admin/users/:id', owner: 'vendoo.users', policy: 'users.manage', csrf: true, stability: 'stable',
handler: async (req, res) => {
const deleted = getUsersService().deleteManagedUser({ actor: req.user, userId: Number(req.params.id) });
deps.auditLog(req.user.id, req.user.email, 'user.deleted', 'user', String(req.params.id), deleted.email, deps.getClientIp(req));
res.json({ ok: true });
},
});
routes.register(app, {
id: 'users.admin.password-reset', method: 'POST', path: '/api/admin/users/:id/reset-password', owner: 'vendoo.users', policy: 'users.manage', csrf: true, stability: 'stable',
input: objectSchema({ password: { type: 'string', required: true, maxLength: 512, trim: false } }),
handler: async (req, res) => {
const user = getUsersService().resetPassword({ userId: Number(req.params.id), password: req.validatedBody.password });
deps.auditLog(req.user.id, req.user.email, 'user.password_reset', 'user', String(req.params.id), `für ${user.email}`, deps.getClientIp(req));
res.json({ ok: true, message: `Passwort für ${user.email} zurückgesetzt` });
},
});
routes.register(app, {
id: 'users.admin.login-history', method: 'GET', path: '/api/admin/users/:id/login-history', owner: 'vendoo.users', policy: 'users.manage', csrf: false, stability: 'stable',
handler: async (req, res) => res.json(getUsersService().loginHistory(Number(req.params.id), 30)),
});
routes.register(app, {
id: 'users.admin.login-history-compat', method: 'GET', path: '/api/admin/login-history/:userId', owner: 'vendoo.users', policy: 'users.manage', csrf: false, stability: 'deprecated',
handler: async (req, res) => res.json(getUsersService().loginHistory(Number(req.params.userId), 20)),
});
}