fix(routes): resolve superOAuthId → DB userId — critical auth bug
Some checks failed
CI/CD — Build & Deploy / Build (push) Failing after 35s
CI/CD — Build & Deploy / Deploy to VPS (push) Has been skipped

req.user.id = SuperOAuth UUID, pas l'UUID TypeORM en DB.
Sans ce fix : getUserPlanLevel retourne toujours 0, ownerId ne matche jamais.

- video.routes: resolveDbUserId avant getUserPlanLevel
- playlist.routes: resolveDbUserId sur toutes les opérations owner/member
This commit is contained in:
2026-03-14 08:12:11 +01:00
parent 87d076313c
commit 11d9432218
2 changed files with 36 additions and 9 deletions

View File

@@ -2,25 +2,34 @@ import { Router, Request, Response } from "express";
import { AppDataSource } from "../config/data-source"; import { AppDataSource } from "../config/data-source";
import { Playlist } from "../entities/Playlist"; import { Playlist } from "../entities/Playlist";
import { PlaylistShare } from "../entities/PlaylistShare"; import { PlaylistShare } from "../entities/PlaylistShare";
import { User } from "../entities/User";
import { requireAuth, AuthenticatedRequest } from "../middleware/auth.middleware"; import { requireAuth, AuthenticatedRequest } from "../middleware/auth.middleware";
const router = Router(); const router = Router();
/** Résout le superOAuthId vers l'UUID DB, crée le user si inexistant */
async function resolveDbUserId(superOAuthId: string): Promise<string | null> {
const user = await AppDataSource.getRepository(User).findOne({ where: { superOAuthId } });
return user?.id ?? null;
}
/** /**
* GET /api/playlists * GET /api/playlists
* Playlists publiques + playlists partagées avec l'utilisateur connecté. * Playlists publiques + playlists partagées avec l'utilisateur connecté.
*/ */
router.get("/", requireAuth, async (req: Request, res: Response): Promise<void> => { router.get("/", requireAuth, async (req: Request, res: Response): Promise<void> => {
const { user } = req as AuthenticatedRequest; const { user } = req as AuthenticatedRequest;
const dbUserId = await resolveDbUserId(user.id);
if (!dbUserId) { res.status(401).json({ success: false, error: "USER_NOT_FOUND" }); return; }
try { try {
const owned = await AppDataSource.getRepository(Playlist).find({ const owned = await AppDataSource.getRepository(Playlist).find({
where: { ownerId: user.id }, where: { ownerId: dbUserId },
order: { createdAt: "DESC" }, order: { createdAt: "DESC" },
}); });
const shared = await AppDataSource.getRepository(PlaylistShare).find({ const shared = await AppDataSource.getRepository(PlaylistShare).find({
where: { userId: user.id, status: "active" }, where: { userId: dbUserId, status: "active" },
relations: ["playlist"], relations: ["playlist"],
}); });
@@ -53,10 +62,13 @@ router.post("/", requireAuth, async (req: Request, res: Response): Promise<void>
return; return;
} }
const dbUserId = await resolveDbUserId(user.id);
if (!dbUserId) { res.status(401).json({ success: false, error: "USER_NOT_FOUND" }); return; }
try { try {
const playlist = AppDataSource.getRepository(Playlist).create({ const playlist = AppDataSource.getRepository(Playlist).create({
id: require("crypto").randomUUID(), id: require("crypto").randomUUID(),
ownerId: user.id, ownerId: dbUserId,
title: title.trim(), title: title.trim(),
description: description ?? null, description: description ?? null,
visibility: visibility ?? "private", visibility: visibility ?? "private",
@@ -76,6 +88,8 @@ router.post("/", requireAuth, async (req: Request, res: Response): Promise<void>
*/ */
router.get("/:id", requireAuth, async (req: Request, res: Response): Promise<void> => { router.get("/:id", requireAuth, async (req: Request, res: Response): Promise<void> => {
const { user } = req as AuthenticatedRequest; const { user } = req as AuthenticatedRequest;
const dbUserId = await resolveDbUserId(user.id);
if (!dbUserId) { res.status(401).json({ success: false, error: "USER_NOT_FOUND" }); return; }
try { try {
const playlist = await AppDataSource.getRepository(Playlist).findOne({ const playlist = await AppDataSource.getRepository(Playlist).findOne({
@@ -88,8 +102,8 @@ router.get("/:id", requireAuth, async (req: Request, res: Response): Promise<voi
return; return;
} }
const isOwner = playlist.ownerId === user.id; const isOwner = playlist.ownerId === dbUserId;
const share = playlist.shares.find((s) => s.userId === user.id && s.status === "active"); const share = playlist.shares.find((s) => s.userId === dbUserId && s.status === "active");
const isPublic = playlist.visibility === "public"; const isPublic = playlist.visibility === "public";
if (!isOwner && !share && !isPublic) { if (!isOwner && !share && !isPublic) {
@@ -121,6 +135,8 @@ router.get("/:id", requireAuth, async (req: Request, res: Response): Promise<voi
router.post("/:id/share", requireAuth, async (req: Request, res: Response): Promise<void> => { router.post("/:id/share", requireAuth, async (req: Request, res: Response): Promise<void> => {
const { user } = req as AuthenticatedRequest; const { user } = req as AuthenticatedRequest;
const { userId, permission } = req.body as { userId?: string; permission?: "view" | "edit" }; const { userId, permission } = req.body as { userId?: string; permission?: "view" | "edit" };
const dbUserId = await resolveDbUserId(user.id);
if (!dbUserId) { res.status(401).json({ success: false, error: "USER_NOT_FOUND" }); return; }
try { try {
const playlist = await AppDataSource.getRepository(Playlist).findOneBy({ id: req.params.id }); const playlist = await AppDataSource.getRepository(Playlist).findOneBy({ id: req.params.id });
@@ -130,7 +146,7 @@ router.post("/:id/share", requireAuth, async (req: Request, res: Response): Prom
return; return;
} }
if (playlist.ownerId !== user.id) { if (playlist.ownerId !== dbUserId) {
res.status(403).json({ success: false, error: "FORBIDDEN" }); res.status(403).json({ success: false, error: "FORBIDDEN" });
return; return;
} }
@@ -165,11 +181,13 @@ router.patch("/:id/share/:shareId", requireAuth, async (req: Request, res: Respo
permission?: "view" | "edit"; permission?: "view" | "edit";
status?: "active" | "revoked"; status?: "active" | "revoked";
}; };
const dbUserId = await resolveDbUserId(user.id);
if (!dbUserId) { res.status(401).json({ success: false, error: "USER_NOT_FOUND" }); return; }
try { try {
const playlist = await AppDataSource.getRepository(Playlist).findOneBy({ id: req.params.id }); const playlist = await AppDataSource.getRepository(Playlist).findOneBy({ id: req.params.id });
if (!playlist || playlist.ownerId !== user.id) { if (!playlist || playlist.ownerId !== dbUserId) {
res.status(403).json({ success: false, error: "FORBIDDEN" }); res.status(403).json({ success: false, error: "FORBIDDEN" });
return; return;
} }

View File

@@ -1,15 +1,24 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { AppDataSource } from "../config/data-source"; import { AppDataSource } from "../config/data-source";
import { Video } from "../entities/Video"; import { Video } from "../entities/Video";
import { User } from "../entities/User";
import { requireAuth, AuthenticatedRequest, AuthenticatedUser } from "../middleware/auth.middleware"; import { requireAuth, AuthenticatedRequest, AuthenticatedUser } from "../middleware/auth.middleware";
import { UserSubscription } from "../entities/UserSubscription"; import { UserSubscription } from "../entities/UserSubscription";
const router = Router(); const router = Router();
/** Résout le superOAuthId vers l'UUID DB, retourne null si user inconnu */
async function resolveDbUserId(superOAuthId: string): Promise<string | null> {
const user = await AppDataSource.getRepository(User).findOne({ where: { superOAuthId } });
return user?.id ?? null;
}
/** Récupère le niveau de plan actif d'un user (0 = free si aucun abonnement actif) */ /** Récupère le niveau de plan actif d'un user (0 = free si aucun abonnement actif) */
async function getUserPlanLevel(userId: string): Promise<number> { async function getUserPlanLevel(superOAuthId: string): Promise<number> {
const dbUserId = await resolveDbUserId(superOAuthId);
if (!dbUserId) return 0;
const sub = await AppDataSource.getRepository(UserSubscription).findOne({ const sub = await AppDataSource.getRepository(UserSubscription).findOne({
where: { userId, status: "active" }, where: { userId: dbUserId, status: "active" },
relations: ["plan"], relations: ["plan"],
order: { startsAt: "DESC" }, order: { startsAt: "DESC" },
}); });