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 { Playlist } from "../entities/Playlist";
import { PlaylistShare } from "../entities/PlaylistShare";
import { User } from "../entities/User";
import { requireAuth, AuthenticatedRequest } from "../middleware/auth.middleware";
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
* Playlists publiques + playlists partagées avec l'utilisateur connecté.
*/
router.get("/", requireAuth, async (req: Request, res: Response): Promise<void> => {
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 {
const owned = await AppDataSource.getRepository(Playlist).find({
where: { ownerId: user.id },
where: { ownerId: dbUserId },
order: { createdAt: "DESC" },
});
const shared = await AppDataSource.getRepository(PlaylistShare).find({
where: { userId: user.id, status: "active" },
where: { userId: dbUserId, status: "active" },
relations: ["playlist"],
});
@@ -53,10 +62,13 @@ router.post("/", requireAuth, async (req: Request, res: Response): Promise<void>
return;
}
const dbUserId = await resolveDbUserId(user.id);
if (!dbUserId) { res.status(401).json({ success: false, error: "USER_NOT_FOUND" }); return; }
try {
const playlist = AppDataSource.getRepository(Playlist).create({
id: require("crypto").randomUUID(),
ownerId: user.id,
ownerId: dbUserId,
title: title.trim(),
description: description ?? null,
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> => {
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 {
const playlist = await AppDataSource.getRepository(Playlist).findOne({
@@ -88,8 +102,8 @@ router.get("/:id", requireAuth, async (req: Request, res: Response): Promise<voi
return;
}
const isOwner = playlist.ownerId === user.id;
const share = playlist.shares.find((s) => s.userId === user.id && s.status === "active");
const isOwner = playlist.ownerId === dbUserId;
const share = playlist.shares.find((s) => s.userId === dbUserId && s.status === "active");
const isPublic = playlist.visibility === "public";
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> => {
const { user } = req as AuthenticatedRequest;
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 {
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;
}
if (playlist.ownerId !== user.id) {
if (playlist.ownerId !== dbUserId) {
res.status(403).json({ success: false, error: "FORBIDDEN" });
return;
}
@@ -165,11 +181,13 @@ router.patch("/:id/share/:shareId", requireAuth, async (req: Request, res: Respo
permission?: "view" | "edit";
status?: "active" | "revoked";
};
const dbUserId = await resolveDbUserId(user.id);
if (!dbUserId) { res.status(401).json({ success: false, error: "USER_NOT_FOUND" }); return; }
try {
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" });
return;
}

View File

@@ -1,15 +1,24 @@
import { Router, Request, Response } from "express";
import { AppDataSource } from "../config/data-source";
import { Video } from "../entities/Video";
import { User } from "../entities/User";
import { requireAuth, AuthenticatedRequest, AuthenticatedUser } from "../middleware/auth.middleware";
import { UserSubscription } from "../entities/UserSubscription";
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) */
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({
where: { userId, status: "active" },
where: { userId: dbUserId, status: "active" },
relations: ["plan"],
order: { startsAt: "DESC" },
});