mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 12:50:09 +00:00
Harden admin token creation to enforce target user role
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user