feat(auth): include subscription tier in token and session payload
- Adds subscription tier info to access/refresh tokens. - Stores tier details in session Redis record. - Returns tier data in login/register response. - Uses default tier when user has none.
This commit is contained in:
1 parent
7f42def478
commit
d27526e026
20 files changed
+2133
-40
No files matched your search
+76
-15
@@ -8,6 +8,13 @@ import { cookies } from "next/headers";
|
||||
import { verifyAccessToken } from "./jwt";
|
||||
import { getSession } from "@/lib/redis/session";
|
||||
|
||||
export interface AuthenticatedUser {
|
||||
userId: number;
|
||||
username: string;
|
||||
tierSlug?: string;
|
||||
tierLevel?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai token do header Authorization
|
||||
* @param request - Request do Next.js
|
||||
@@ -24,18 +31,23 @@ function extractToken(request: NextRequest): string | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware que requer autenticação
|
||||
* Retorna dados do usuário ou NextResponse com erro
|
||||
* Suporta tanto Bearer token quanto cookie sessionId
|
||||
* @param request - Request do Next.js
|
||||
* @returns Dados do usuário autenticado ou NextResponse com erro
|
||||
*/
|
||||
function toAuthenticatedUser(payload: {
|
||||
userId: number;
|
||||
username: string;
|
||||
tierSlug?: string;
|
||||
tierLevel?: number;
|
||||
}): AuthenticatedUser {
|
||||
return {
|
||||
userId: payload.userId,
|
||||
username: payload.username,
|
||||
tierSlug: payload.tierSlug,
|
||||
tierLevel: payload.tierLevel,
|
||||
};
|
||||
}
|
||||
|
||||
export async function requireAuth(
|
||||
request: NextRequest
|
||||
): Promise<
|
||||
| { userId: number; username: string }
|
||||
| NextResponse<{ success: false; error: string }>
|
||||
> {
|
||||
): Promise<AuthenticatedUser | NextResponse<{ success: false; error: string }>> {
|
||||
// Tentar primeiro com Bearer token
|
||||
const token = extractToken(request);
|
||||
|
||||
@@ -43,7 +55,7 @@ export async function requireAuth(
|
||||
const payload = verifyAccessToken(token);
|
||||
|
||||
if (payload) {
|
||||
return payload;
|
||||
return toAuthenticatedUser(payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +71,7 @@ export async function requireAuth(
|
||||
const tokenPayload = verifyAccessToken(session.accessToken);
|
||||
|
||||
if (tokenPayload) {
|
||||
return tokenPayload;
|
||||
return toAuthenticatedUser(tokenPayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,14 +97,14 @@ export async function requireAuth(
|
||||
*/
|
||||
export async function optionalAuth(
|
||||
request: NextRequest
|
||||
): Promise<{ userId: number; username: string } | null> {
|
||||
): Promise<AuthenticatedUser | null> {
|
||||
// Tentar primeiro com Bearer token
|
||||
const token = extractToken(request);
|
||||
|
||||
if (token) {
|
||||
const payload = verifyAccessToken(token);
|
||||
if (payload) {
|
||||
return payload;
|
||||
return toAuthenticatedUser(payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +118,9 @@ export async function optionalAuth(
|
||||
|
||||
if (session) {
|
||||
const tokenPayload = verifyAccessToken(session.accessToken);
|
||||
return tokenPayload;
|
||||
if (tokenPayload) {
|
||||
return toAuthenticatedUser(tokenPayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -115,3 +129,50 @@ export async function optionalAuth(
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function requireAdminUser(
|
||||
request: NextRequest
|
||||
): Promise<AuthenticatedUser | NextResponse<{ success: false; error: string }>> {
|
||||
const auth = await requireAuth(request);
|
||||
|
||||
if (auth instanceof NextResponse) {
|
||||
return auth;
|
||||
}
|
||||
|
||||
if (auth.userId !== 1) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Acesso restrito ao administrador.",
|
||||
},
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
export async function requireTierLevel(
|
||||
request: NextRequest,
|
||||
minimumLevel: number
|
||||
): Promise<AuthenticatedUser | NextResponse<{ success: false; error: string }>> {
|
||||
const auth = await requireAuth(request);
|
||||
|
||||
if (auth instanceof NextResponse) {
|
||||
return auth;
|
||||
}
|
||||
|
||||
const tierLevel = auth.tierLevel ?? 0;
|
||||
|
||||
if (tierLevel < minimumLevel) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Plano atual não possui acesso a este recurso.",
|
||||
},
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
return auth;
|
||||
}
|
||||
Reference in new issue
Block a user