mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat(session): guest login endpoint, guestAllowed, guest /me payload
This commit is contained in:
1 parent
271504eec1
commit
8fbc522d06
3 files changed
+109
-8
No files matched your search
@@ -8,6 +8,8 @@ import { createUserStore, type UserStore } from "../../data/users.js";
|
|||||||
import { createSessionStore, type SessionStore } from "../../data/sessions.js";
|
import { createSessionStore, type SessionStore } from "../../data/sessions.js";
|
||||||
import { createAuditStore } from "../../data/audit.js";
|
import { createAuditStore } from "../../data/audit.js";
|
||||||
import { createPermissionStore } from "../../data/permissions.js";
|
import { createPermissionStore } from "../../data/permissions.js";
|
||||||
|
import { getDefaultConfig, type GuestModeConfig } from "../../data/config.js";
|
||||||
|
import type { GuestPermissions, BotAccess } from "../../data/permissions.js";
|
||||||
import { createSessionRouter } from "./session.js";
|
import { createSessionRouter } from "./session.js";
|
||||||
import { SESSION_COOKIE_NAME } from "../auth/validateSession.js";
|
import { SESSION_COOKIE_NAME } from "../auth/validateSession.js";
|
||||||
|
|
||||||
@@ -17,7 +19,17 @@ function makeApp(botDb: BotDatabase, users: UserStore, sessions: SessionStore) {
|
|||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
const audit = createAuditStore(botDb.db);
|
const audit = createAuditStore(botDb.db);
|
||||||
const permissions = createPermissionStore(botDb.db);
|
const permissions = createPermissionStore(botDb.db);
|
||||||
app.use("/api/session", createSessionRouter(users, sessions, audit, pino({ level: "silent" }), permissions));
|
app.use(
|
||||||
|
"/api/session",
|
||||||
|
createSessionRouter(
|
||||||
|
users,
|
||||||
|
sessions,
|
||||||
|
audit,
|
||||||
|
pino({ level: "silent" }),
|
||||||
|
permissions,
|
||||||
|
() => getDefaultConfig().guestMode
|
||||||
|
)
|
||||||
|
);
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +59,7 @@ describe("session router", () => {
|
|||||||
it("GET /needs-setup returns true on an empty db", async () => {
|
it("GET /needs-setup returns true on an empty db", async () => {
|
||||||
const res = await request(app).get("/api/session/needs-setup");
|
const res = await request(app).get("/api/session/needs-setup");
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
expect(res.body).toEqual({ needsSetup: true });
|
expect(res.body).toEqual({ needsSetup: true, guestAllowed: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("POST /setup creates the first admin, logs them in, and returns false from /needs-setup afterwards", async () => {
|
it("POST /setup creates the first admin, logs them in, and returns false from /needs-setup afterwards", async () => {
|
||||||
@@ -59,7 +71,7 @@ describe("session router", () => {
|
|||||||
extractCookie(setupRes);
|
extractCookie(setupRes);
|
||||||
|
|
||||||
const needs = await request(app).get("/api/session/needs-setup");
|
const needs = await request(app).get("/api/session/needs-setup");
|
||||||
expect(needs.body).toEqual({ needsSetup: false });
|
expect(needs.body).toEqual({ needsSetup: false, guestAllowed: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("POST /setup returns 409 once a user already exists", async () => {
|
it("POST /setup returns 409 once a user already exists", async () => {
|
||||||
@@ -157,3 +169,71 @@ describe("session router", () => {
|
|||||||
expect(u.id).toBe(meA.body.id);
|
expect(u.id).toBe(meA.body.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("session router — guest mode", () => {
|
||||||
|
let botDb: BotDatabase;
|
||||||
|
|
||||||
|
afterEach(() => botDb.close());
|
||||||
|
|
||||||
|
function makeApp(opts: {
|
||||||
|
guestEnabled: boolean;
|
||||||
|
guestPermissions?: GuestPermissions;
|
||||||
|
guestBots?: BotAccess;
|
||||||
|
}) {
|
||||||
|
botDb = createDatabase(":memory:");
|
||||||
|
const users = createUserStore(botDb.db);
|
||||||
|
const sessions = createSessionStore(botDb.db);
|
||||||
|
const audit = createAuditStore(botDb.db);
|
||||||
|
const permissions = createPermissionStore(botDb.db);
|
||||||
|
const guestCfg: GuestModeConfig = {
|
||||||
|
enabled: opts.guestEnabled,
|
||||||
|
bots: opts.guestBots ?? getDefaultConfig().guestMode.bots,
|
||||||
|
permissions: opts.guestPermissions ?? getDefaultConfig().guestMode.permissions,
|
||||||
|
};
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(
|
||||||
|
"/api/session",
|
||||||
|
createSessionRouter(users, sessions, audit, pino({ level: "silent" }), permissions, () => guestCfg)
|
||||||
|
);
|
||||||
|
return { app, users, sessions };
|
||||||
|
}
|
||||||
|
|
||||||
|
it("POST /guest is 403 when guest mode disabled", async () => {
|
||||||
|
const { app } = makeApp({ guestEnabled: false });
|
||||||
|
const res = await request(app).post("/api/session/guest");
|
||||||
|
expect(res.status).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("POST /guest mints a guest session when enabled, and /me reports role guest + flags", async () => {
|
||||||
|
const { app } = makeApp({
|
||||||
|
guestEnabled: true,
|
||||||
|
guestPermissions: {
|
||||||
|
addToQueue: true,
|
||||||
|
playNext: true,
|
||||||
|
playNow: false,
|
||||||
|
skip: false,
|
||||||
|
transport: false,
|
||||||
|
removeClear: false,
|
||||||
|
playMode: false,
|
||||||
|
},
|
||||||
|
guestBots: "all",
|
||||||
|
});
|
||||||
|
const login = await request(app).post("/api/session/guest");
|
||||||
|
expect(login.status).toBe(200);
|
||||||
|
expect(login.body.role).toBe("guest");
|
||||||
|
const cookie = login.headers["set-cookie"];
|
||||||
|
const me = await request(app).get("/api/session/me").set("Cookie", cookie);
|
||||||
|
expect(me.body.role).toBe("guest");
|
||||||
|
expect(me.body.guest.addToQueue).toBe(true);
|
||||||
|
expect(me.body.guest.playNext).toBe(true);
|
||||||
|
expect(me.body.capabilities).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("GET /needs-setup exposes guestAllowed", async () => {
|
||||||
|
const { app } = makeApp({ guestEnabled: true });
|
||||||
|
const res = await request(app).get("/api/session/needs-setup");
|
||||||
|
expect(res.body.guestAllowed).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
+25
-4
@@ -5,7 +5,9 @@ import type { UserStore } from "../../data/users.js";
|
|||||||
import type { SessionStore } from "../../data/sessions.js";
|
import type { SessionStore } from "../../data/sessions.js";
|
||||||
import type { AuditStore } from "../../data/audit.js";
|
import type { AuditStore } from "../../data/audit.js";
|
||||||
import { resolvePermissionContext, type PermissionStore } from "../../data/permissions.js";
|
import { resolvePermissionContext, type PermissionStore } from "../../data/permissions.js";
|
||||||
import { SESSION_TTL_MS } from "../../data/sessions.js";
|
import { SESSION_TTL_MS, GUEST_SESSION_TTL_MS } from "../../data/sessions.js";
|
||||||
|
import { GUEST_USER_ID, GUEST_USERNAME } from "../../data/users.js";
|
||||||
|
import type { GuestModeConfig } from "../../data/config.js";
|
||||||
import { SESSION_COOKIE_NAME, validateSessionFromHeaders, extractSessionToken } from "../auth/validateSession.js";
|
import { SESSION_COOKIE_NAME, validateSessionFromHeaders, extractSessionToken } from "../auth/validateSession.js";
|
||||||
|
|
||||||
const FAILED_LOGIN_DELAY_MS = 250;
|
const FAILED_LOGIN_DELAY_MS = 250;
|
||||||
@@ -51,7 +53,8 @@ export function createSessionRouter(
|
|||||||
sessions: SessionStore,
|
sessions: SessionStore,
|
||||||
audit: AuditStore,
|
audit: AuditStore,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
permissions: PermissionStore
|
permissions: PermissionStore,
|
||||||
|
getGuestConfig: () => GuestModeConfig
|
||||||
): Router {
|
): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -69,7 +72,7 @@ export function createSessionRouter(
|
|||||||
};
|
};
|
||||||
|
|
||||||
router.get("/needs-setup", (_req, res) => {
|
router.get("/needs-setup", (_req, res) => {
|
||||||
res.json({ needsSetup: users.countUsers() === 0 });
|
res.json({ needsSetup: users.countUsers() === 0, guestAllowed: getGuestConfig().enabled });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/setup", async (req, res) => {
|
router.post("/setup", async (req, res) => {
|
||||||
@@ -125,6 +128,17 @@ export function createSessionRouter(
|
|||||||
res.json({ id: user.id, username: user.username, role: user.role });
|
res.json({ id: user.id, username: user.username, role: user.role });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/guest", (_req, res) => {
|
||||||
|
const cfg = getGuestConfig();
|
||||||
|
if (!cfg.enabled) {
|
||||||
|
res.status(403).json({ error: "guest mode disabled" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { token } = sessions.createSession(GUEST_USER_ID, { ttlMs: GUEST_SESSION_TTL_MS, skipCap: true });
|
||||||
|
setSessionCookie(res, token);
|
||||||
|
res.json({ id: GUEST_USER_ID, username: GUEST_USERNAME, role: "guest" });
|
||||||
|
});
|
||||||
|
|
||||||
router.post("/logout", (req, res) => {
|
router.post("/logout", (req, res) => {
|
||||||
const token = parseTokenFromCookie(req.headers.cookie);
|
const token = parseTokenFromCookie(req.headers.cookie);
|
||||||
if (token) {
|
if (token) {
|
||||||
@@ -136,13 +150,20 @@ export function createSessionRouter(
|
|||||||
|
|
||||||
router.get("/me", requireAuthInline, (req, res) => {
|
router.get("/me", requireAuthInline, (req, res) => {
|
||||||
const user = req.user!;
|
const user = req.user!;
|
||||||
const ctx = resolvePermissionContext(user.role, user.id, permissions);
|
const cfg = getGuestConfig();
|
||||||
|
const ctx = resolvePermissionContext(
|
||||||
|
user.role,
|
||||||
|
user.id,
|
||||||
|
permissions,
|
||||||
|
user.role === "guest" ? { bots: cfg.bots, permissions: cfg.permissions } : undefined
|
||||||
|
);
|
||||||
res.json({
|
res.json({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
capabilities: [...ctx.capabilities],
|
capabilities: [...ctx.capabilities],
|
||||||
bots: ctx.bots === "all" ? "all" : [...ctx.bots],
|
bots: ctx.bots === "all" ? "all" : [...ctx.bots],
|
||||||
|
guest: ctx.guest ?? null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ export function createWebServer(options: WebServerOptions): WebServer {
|
|||||||
app.use("/api/session/login", loginLimit);
|
app.use("/api/session/login", loginLimit);
|
||||||
app.use("/api/session/setup", setupLimit);
|
app.use("/api/session/setup", setupLimit);
|
||||||
|
|
||||||
app.use("/api/session", createSessionRouter(users, sessions, audit, logger, permissions));
|
app.use("/api/session", createSessionRouter(users, sessions, audit, logger, permissions, () => options.config.guestMode));
|
||||||
|
|
||||||
// ─── Gates for everything else under /api ───────────────────────────────
|
// ─── Gates for everything else under /api ───────────────────────────────
|
||||||
const requireAuth = createRequireAuth(sessions, permissions, () => options.config.guestMode);
|
const requireAuth = createRequireAuth(sessions, permissions, () => options.config.guestMode);
|
||||||
|
|||||||
Reference in new issue
Block a user