Addresses data exposure, access control, input validation, infrastructure hardening, and supply chain security issues identified during audit. Key changes: - Strip internal fields from public campaign/profile/comment endpoints - Restrict docs routes to CONTENT_ROLES, provisioning to SUPER_ADMIN - Add SSE connection limits, social middleware fail-closed behavior - Bind all non-nginx ports to 127.0.0.1, pin container image versions - Add CSP header, conditional HSTS, token redaction in nginx logs - Validate nav URLs, calendar schemas, video tracking batch events - Reject default admin password placeholder, add SSRF protocol checks - Exclude .env from Code Server, enforce RC admin password in compose - Add Zod validation for achievement grant/revoke, webhook secret header - Fix path traversal prefix attack, add calendar token expiry Bunker Admin
97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
import { Router } from 'express';
|
|
import type { Request, Response } from 'express';
|
|
import { checkSocialEnabled } from './social.middleware';
|
|
import { groupService } from './group.service';
|
|
import { generateModeratorToken } from '../jitsi/jitsi.utils';
|
|
import { prisma } from '../../config/database';
|
|
|
|
const router = Router();
|
|
|
|
router.use(checkSocialEnabled);
|
|
|
|
/** GET /api/social/groups — list my groups */
|
|
router.get('/', async (req: Request, res: Response) => {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const page = Math.max(1, parseInt(req.query.page as string) || 1);
|
|
const limit = Math.min(50, Math.max(1, parseInt(req.query.limit as string) || 20));
|
|
const result = await groupService.listMyGroups(userId, page, limit);
|
|
res.json(result);
|
|
} catch (err: any) {
|
|
res.status(err.statusCode || 500).json({ error: { message: err.message } });
|
|
}
|
|
});
|
|
|
|
/** GET /api/social/groups/:id — group detail with members (membership required) */
|
|
router.get('/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const groupId = req.params.id as string;
|
|
const userId = req.user!.id;
|
|
const result = await groupService.getGroupDetail(groupId);
|
|
|
|
// Verify the requesting user is a member of this group
|
|
const isMember = result.members.some((m: any) => m.userId === userId);
|
|
if (!isMember) {
|
|
res.status(404).json({ error: { message: 'Group not found', code: 'NOT_FOUND' } });
|
|
return;
|
|
}
|
|
|
|
res.json(result);
|
|
} catch (err: any) {
|
|
res.status(err.statusCode || 500).json({ error: { message: err.message } });
|
|
}
|
|
});
|
|
|
|
/** POST /api/social/groups/:id/call/start — start group call */
|
|
router.post('/:id/call/start', async (req: Request, res: Response) => {
|
|
try {
|
|
// Check enableMeet
|
|
const settings = await prisma.siteSettings.findFirst({ select: { enableMeet: true } });
|
|
if (!settings?.enableMeet) {
|
|
res.status(404).json({ error: { message: 'Video meetings are not enabled' } });
|
|
return;
|
|
}
|
|
|
|
const groupId = req.params.id as string;
|
|
const result = await groupService.startGroupCall(groupId, req.user!.id);
|
|
res.status(201).json(result);
|
|
} catch (err: any) {
|
|
res.status(err.statusCode || 500).json({ error: { message: err.message } });
|
|
}
|
|
});
|
|
|
|
/** POST /api/social/groups/:id/call/end — end group call */
|
|
router.post('/:id/call/end', async (req: Request, res: Response) => {
|
|
try {
|
|
const groupId = req.params.id as string;
|
|
const result = await groupService.endGroupCall(groupId, req.user!.id);
|
|
res.json(result);
|
|
} catch (err: any) {
|
|
res.status(err.statusCode || 500).json({ error: { message: err.message } });
|
|
}
|
|
});
|
|
|
|
/** POST /api/social/groups/:id/call/token — get moderator JWT for group call */
|
|
router.post('/:id/call/token', async (req: Request, res: Response) => {
|
|
try {
|
|
const groupId = req.params.id as string;
|
|
const meeting = await groupService.getCallMeeting(groupId, req.user!.id);
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: req.user!.id },
|
|
select: { id: true, email: true, name: true },
|
|
});
|
|
if (!user) {
|
|
res.status(404).json({ error: { message: 'User not found' } });
|
|
return;
|
|
}
|
|
|
|
const token = generateModeratorToken(user, meeting.jitsiRoom);
|
|
res.json({ token, room: meeting.jitsiRoom, slug: meeting.slug });
|
|
} catch (err: any) {
|
|
res.status(err.statusCode || 500).json({ error: { message: err.message } });
|
|
}
|
|
});
|
|
|
|
export { router as groupRouter };
|