feat: auth foundation, similar tools, costs, redundancy, anonymous voting

This commit is contained in:
root
2026-07-29 21:59:29 +02:00
parent 8b3b7c9955
commit 59badeaa48
22 changed files with 793 additions and 15 deletions
+48 -3
View File
@@ -52,6 +52,46 @@ async function getClient(): Promise<Client | null> {
}
}
async function upsertUserFromOidc(userinfo: Record<string, unknown>): Promise<{ id: number; tier: string; role: string }> {
const sub = String(userinfo.sub ?? "");
if (!sub) throw new Error("Missing sub claim");
const email = typeof userinfo.email === "string" ? userinfo.email : undefined;
const preferredUsername = typeof userinfo.preferred_username === "string" ? userinfo.preferred_username : undefined;
const name = typeof userinfo.name === "string" ? userinfo.name : preferredUsername;
const username = preferredUsername ?? email ?? `oidc-${sub.slice(0, 8)}`;
const [existing] = await db
.select({ id: usersTable.id, tier: usersTable.tier, role: usersTable.role })
.from(usersTable)
.where(eq(usersTable.authProviderId, sub))
.limit(1);
if (existing) {
await db
.update(usersTable)
.set({ email, displayName: name })
.where(eq(usersTable.id, existing.id));
return existing;
}
const [created] = await db
.insert(usersTable)
.values({
username,
email,
displayName: name,
role: "user",
tier: "free",
authProvider: "oidc",
authProviderId: sub,
})
.returning({ id: usersTable.id, tier: usersTable.tier, role: usersTable.role });
logger.info({ username, sub }, "OIDC user created");
return created;
}
router.get("/auth/mode", (_req, res): void => {
res.json({ mode: isOidcConfigured() ? "oidc" : "local" });
});
@@ -74,7 +114,7 @@ router.post("/auth/login", async (req, res): Promise<void> => {
.where(eq(usersTable.username, String(username)))
.limit(1);
if (!user) {
if (!user || !user.passwordHash) {
res.status(401).json({ error: "Invalid username or password" });
return;
}
@@ -91,6 +131,7 @@ router.post("/auth/login", async (req, res): Promise<void> => {
preferred_username: user.username,
email: user.email ?? undefined,
role: (user.role as "admin" | "user") ?? "user",
tier: (user.tier as "free" | "premium" | "enterprise") ?? "free",
isLocal: true,
};
@@ -100,6 +141,7 @@ router.post("/auth/login", async (req, res): Promise<void> => {
name: user.username,
preferredUsername: user.username,
role: user.role,
tier: user.tier,
isLocal: true,
});
});
@@ -152,13 +194,15 @@ router.get("/auth/callback", async (req, res): Promise<void> => {
});
const userinfo = await client.userinfo(tokenSet.access_token!);
const dbUser = await upsertUserFromOidc(userinfo);
req.session.user = {
sub: userinfo.sub,
sub: dbUser.id.toString(),
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",
role: (dbUser.role as "admin" | "user") ?? "user",
tier: (dbUser.tier as "free" | "premium" | "enterprise") ?? "free",
isLocal: false,
};
delete req.session.codeVerifier;
@@ -198,6 +242,7 @@ router.get("/auth/me", async (req, res): Promise<void> => {
name: u.name ?? null,
preferredUsername: u.preferred_username ?? null,
role: u.role ?? "user",
tier: u.tier ?? "free",
isLocal: u.isLocal ?? false,
});
});