Add local user authentication and admin capabilities
Implement local user authentication with password hashing, add admin roles for user management and audit log viewing, and introduce audit logging for critical actions. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 776963d0-f75d-42e2-a57b-cc36bdff8495 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 832a44ff-12ae-4096-8a0d-666ec083d536 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0683fb79-a27c-485c-9333-5f4b288c4567/776963d0-f75d-42e2-a57b-cc36bdff8495/1p7jhzu Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -1,11 +1,23 @@
|
||||
import { Router, type IRouter, type Request } from "express";
|
||||
import { Issuer, generators, type Client } from "openid-client";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db, usersTable } from "@workspace/db";
|
||||
import { logger } from "../lib/logger";
|
||||
|
||||
const router: IRouter = Router();
|
||||
|
||||
let cachedClient: Client | null = null;
|
||||
|
||||
function isOidcConfigured(): boolean {
|
||||
return !!(
|
||||
process.env.KEYCLOAK_URL &&
|
||||
process.env.KEYCLOAK_REALM &&
|
||||
process.env.KEYCLOAK_CLIENT_ID &&
|
||||
process.env.KEYCLOAK_CLIENT_SECRET
|
||||
);
|
||||
}
|
||||
|
||||
function getBaseUrl(req: Request): string {
|
||||
if (process.env.APP_URL) return process.env.APP_URL;
|
||||
const host = req.get("x-forwarded-host") || req.get("host") || "localhost";
|
||||
@@ -40,10 +52,62 @@ async function getClient(): Promise<Client | null> {
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/auth/mode", (_req, res): void => {
|
||||
res.json({ mode: isOidcConfigured() ? "oidc" : "local" });
|
||||
});
|
||||
|
||||
router.post("/auth/login", async (req, res): Promise<void> => {
|
||||
if (isOidcConfigured()) {
|
||||
res.status(400).json({ error: "Use OIDC login when Keycloak is configured." });
|
||||
return;
|
||||
}
|
||||
|
||||
const { username, password } = req.body;
|
||||
if (!username || !password) {
|
||||
res.status(400).json({ error: "username and password are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const [user] = await db
|
||||
.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.username, String(username)))
|
||||
.limit(1);
|
||||
|
||||
if (!user) {
|
||||
res.status(401).json({ error: "Invalid username or password" });
|
||||
return;
|
||||
}
|
||||
|
||||
const valid = await bcrypt.compare(String(password), user.passwordHash);
|
||||
if (!valid) {
|
||||
res.status(401).json({ error: "Invalid username or password" });
|
||||
return;
|
||||
}
|
||||
|
||||
req.session.user = {
|
||||
sub: String(user.id),
|
||||
name: user.username,
|
||||
preferred_username: user.username,
|
||||
email: user.email ?? undefined,
|
||||
role: (user.role as "admin" | "user") ?? "user",
|
||||
isLocal: true,
|
||||
};
|
||||
|
||||
res.json({
|
||||
sub: String(user.id),
|
||||
email: user.email ?? null,
|
||||
name: user.username,
|
||||
preferredUsername: user.username,
|
||||
role: user.role,
|
||||
isLocal: true,
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/auth/login", async (req, res): Promise<void> => {
|
||||
const client = await getClient();
|
||||
if (!client) {
|
||||
res.status(503).json({ error: "Keycloak is not configured. Set KEYCLOAK_URL, KEYCLOAK_REALM, KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRET." });
|
||||
res.status(503).json({ error: "Keycloak is not configured." });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,6 +158,8 @@ router.get("/auth/callback", async (req, res): Promise<void> => {
|
||||
email: typeof userinfo.email === "string" ? userinfo.email : undefined,
|
||||
name: typeof userinfo.name === "string" ? userinfo.name : undefined,
|
||||
preferred_username: typeof userinfo.preferred_username === "string" ? userinfo.preferred_username : undefined,
|
||||
role: "user",
|
||||
isLocal: false,
|
||||
};
|
||||
delete req.session.codeVerifier;
|
||||
|
||||
@@ -108,7 +174,6 @@ router.get("/auth/callback", async (req, res): Promise<void> => {
|
||||
});
|
||||
|
||||
router.get("/auth/logout", async (req, res): Promise<void> => {
|
||||
const user = req.session.user;
|
||||
req.session.destroy(() => {});
|
||||
|
||||
const client = await getClient();
|
||||
@@ -132,6 +197,8 @@ router.get("/auth/me", async (req, res): Promise<void> => {
|
||||
email: u.email ?? null,
|
||||
name: u.name ?? null,
|
||||
preferredUsername: u.preferred_username ?? null,
|
||||
role: u.role ?? "user",
|
||||
isLocal: u.isLocal ?? false,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user