Add login-required user management (admin/user roles, Config->Benutzer)
No self-signup: admins create every account in Config -> Benutzer with a typed start password; a forced password change gates first use. Session- cookie auth protects all /api/* routes; /api/users additionally requires the admin role. See docs/authentication.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
b50c00c0ac
commit
c56cfb30ad
36 changed files with 4928 additions and 38 deletions
|
|
@ -4,7 +4,12 @@ import { projectDeviceRouter } from "./routes/project-device.routes.js";
|
|||
import { documentBrandingRouter } from "./routes/document-branding.routes.js";
|
||||
import { projectRouter } from "./routes/project.routes.js";
|
||||
import { cableSizingRouter } from "./routes/cable-sizing.routes.js";
|
||||
import { authRouter } from "./routes/auth.routes.js";
|
||||
import { userRouter } from "./routes/user.routes.js";
|
||||
import { errorMiddleware } from "./middleware/error.middleware.js";
|
||||
import { createAuthGuard, requirePasswordChanged } from "./auth/auth.middleware.js";
|
||||
import { authConfig, sessionStore, userService } from "./composition/auth.js";
|
||||
import { userRepository } from "./composition/application-repositories.js";
|
||||
import { createLogger, toErrorMeta } from "../shared/logging/logger.js";
|
||||
import { closeDefaultDatabase } from "../db/client.js";
|
||||
|
||||
|
|
@ -50,6 +55,15 @@ app.get("/health", (_req, res) => {
|
|||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// Everything below /api requires a signed-in, active user, except
|
||||
// /auth/login, /auth/status and /auth/logout (see createAuthGuard's
|
||||
// openPaths). authRouter is mounted AFTER the guard (not before) so that
|
||||
// /auth/me and /auth/change-password still get req.authUser populated by it.
|
||||
app.use("/api", createAuthGuard(authConfig, sessionStore, userRepository));
|
||||
app.use("/api", requirePasswordChanged());
|
||||
|
||||
app.use("/api/auth", authRouter);
|
||||
app.use("/api/users", userRouter);
|
||||
app.use("/api/cable-sizing", cableSizingRouter);
|
||||
app.use("/api/projects", projectRouter);
|
||||
app.use("/api/global-devices", globalDeviceRouter);
|
||||
|
|
@ -77,40 +91,48 @@ setInterval(() => {
|
|||
});
|
||||
}, heartbeatIntervalMs).unref();
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
logger.info("server started", { port });
|
||||
});
|
||||
|
||||
let shuttingDown = false;
|
||||
|
||||
function shutdown(signal: NodeJS.Signals): void {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
logger.info("shutting down", { signal });
|
||||
|
||||
// Falls ein Request haengt oder ein Socket nicht freigegeben wird, soll der
|
||||
// Prozess trotzdem enden, statt auf das SIGKILL des Containers zu warten.
|
||||
const forceExit = setTimeout(() => {
|
||||
logger.error("shutdown timed out, forcing exit");
|
||||
userService
|
||||
.ensureBootstrapAdmin()
|
||||
.catch((error) => {
|
||||
logger.error("bootstrap admin setup failed", toErrorMeta(error));
|
||||
process.exit(1);
|
||||
}, shutdownTimeoutMs);
|
||||
forceExit.unref();
|
||||
})
|
||||
.then(() => {
|
||||
const server = app.listen(port, () => {
|
||||
logger.info("server started", { port });
|
||||
});
|
||||
|
||||
server.close((error) => {
|
||||
if (error) logger.error("http server close failed", toErrorMeta(error));
|
||||
try {
|
||||
closeDefaultDatabase();
|
||||
} catch (closeError) {
|
||||
logger.error("database close failed", toErrorMeta(closeError));
|
||||
let shuttingDown = false;
|
||||
|
||||
function shutdown(signal: NodeJS.Signals): void {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
logger.info("shutting down", { signal });
|
||||
|
||||
// Falls ein Request haengt oder ein Socket nicht freigegeben wird, soll der
|
||||
// Prozess trotzdem enden, statt auf das SIGKILL des Containers zu warten.
|
||||
const forceExit = setTimeout(() => {
|
||||
logger.error("shutdown timed out, forcing exit");
|
||||
process.exit(1);
|
||||
}, shutdownTimeoutMs);
|
||||
forceExit.unref();
|
||||
|
||||
server.close((error) => {
|
||||
if (error) logger.error("http server close failed", toErrorMeta(error));
|
||||
try {
|
||||
closeDefaultDatabase();
|
||||
} catch (closeError) {
|
||||
logger.error("database close failed", toErrorMeta(closeError));
|
||||
}
|
||||
logger.info("shutdown complete");
|
||||
process.exit(error ? 1 : 0);
|
||||
});
|
||||
|
||||
// Ohne das warten offene Keep-Alive-Verbindungen (z. B. die des
|
||||
// Next.js-Servers) den Timeout oben aus; laufende Requests laufen weiter.
|
||||
server.closeIdleConnections();
|
||||
}
|
||||
logger.info("shutdown complete");
|
||||
process.exit(error ? 1 : 0);
|
||||
|
||||
process.on("SIGTERM", shutdown);
|
||||
process.on("SIGINT", shutdown);
|
||||
});
|
||||
|
||||
// Ohne das warten offene Keep-Alive-Verbindungen (z. B. die des
|
||||
// Next.js-Servers) den Timeout oben aus; laufende Requests laufen weiter.
|
||||
server.closeIdleConnections();
|
||||
}
|
||||
|
||||
process.on("SIGTERM", shutdown);
|
||||
process.on("SIGINT", shutdown);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue