Harden admin token creation to enforce target user role

This commit is contained in:
Michael Borohovski
2026-03-04 16:27:52 -08:00
parent 95917715b1
commit a5e7af1a53
+16 -8
View File
@@ -18,7 +18,7 @@ const CreateTokenSchema = z.object({
name: z.string().min(1).max(100), name: z.string().min(1).max(100),
expiresAt: z.string().datetime().nullable().optional(), expiresAt: z.string().datetime().nullable().optional(),
userId: z.string().uuid().optional(), // Admin can specify which user the token acts as userId: z.string().uuid().optional(), // Admin can specify which user the token acts as
role: z.enum(['admin', 'user']).optional(), // Admin can override the token role role: z.enum(['admin', 'user']).optional(), // Accepted for compatibility, but cannot differ from target user role
}); });
/** /**
@@ -66,7 +66,8 @@ export async function GET(request: NextRequest) {
/** /**
* POST /api/admin/api-tokens * POST /api/admin/api-tokens
* Create a new API token. Admin can optionally specify userId and role. * Create a new API token. Admin can optionally specify userId.
* Token role is always derived from the target user's current role.
* Returns the full token ONCE. * Returns the full token ONCE.
*/ */
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
@@ -120,19 +121,26 @@ export async function POST(request: NextRequest) {
); );
} }
// Determine token role (defaults to target user's role) // Security guard: token role must always match the target user's persisted role.
const tokenRole = role || targetUser.role; // This avoids role/identity mismatch (for example: acting as user A with admin role).
// Log when admin explicitly overrides role to differ from user's actual role
if (role && role !== targetUser.role) { if (role && role !== targetUser.role) {
logger.warn('Admin creating token with role different from user actual role', { logger.warn('Admin attempted token role override that differs from target user role', {
tokenRole: role, requestedRole: role,
userActualRole: targetUser.role, userActualRole: targetUser.role,
targetUser: targetUser.plexUsername, targetUser: targetUser.plexUsername,
createdBy: req.user!.username, createdBy: req.user!.username,
}); });
return NextResponse.json(
{
error: `Token role must match target user's role (${targetUser.role}).`,
},
{ status: 400 }
);
} }
const tokenRole = targetUser.role;
// Generate the token // Generate the token
const { fullToken, tokenHash, tokenPrefix } = generateApiToken(); const { fullToken, tokenHash, tokenPrefix } = generateApiToken();