feat(platform-service): add plan field to auth UserDoc + auth responses

- Added plan to auth UserDoc model and token payload typing
- Register flow initializes user.plan from product default plan
- Login/Register/Me responses now include user.plan
- Access tokens now include optional plan claim
- Verified: tsc --noEmit clean, 19 test files / 178 tests passing
This commit is contained in:
saravanakumardb1 2026-02-15 14:39:42 -08:00
parent 0fee7e9ee7
commit a9ac953ed1
3 changed files with 32 additions and 3 deletions

View File

@ -16,6 +16,7 @@ export async function createAccessToken(payload: {
email: string;
role: string;
productId: string;
plan?: 'free' | 'pro' | 'enterprise';
}): Promise<string> {
return new SignJWT({ ...payload, type: 'access' })
.setProtectedHeader({ alg: 'HS256' })
@ -42,6 +43,7 @@ export async function verifyToken(token: string): Promise<{
email?: string;
role?: string;
productId?: string;
plan?: 'free' | 'pro' | 'enterprise';
type?: string;
}> {
const { payload } = await jwtVerify(token, getSecret(), {
@ -52,6 +54,7 @@ export async function verifyToken(token: string): Promise<{
email?: string;
role?: string;
productId?: string;
plan?: 'free' | 'pro' | 'enterprise';
type?: string;
};
}

View File

@ -9,6 +9,7 @@
*/
import type { FastifyInstance } from 'fastify';
import { getRequestProductConfig } from '../../lib/request-context.js';
import { BadRequestError, UnauthorizedError } from '../../lib/errors.js';
import * as repo from './repository.js';
import * as jwt from './jwt.js';
@ -36,13 +37,20 @@ export async function authRoutes(app: FastifyInstance) {
email: user.email,
role: user.role,
productId,
plan: user.plan,
});
const refreshToken = await jwt.createRefreshToken({ sub: user.id, productId });
return {
accessToken,
refreshToken,
user: { id: user.id, email: user.email, role: user.role, displayName: user.displayName },
user: {
id: user.id,
email: user.email,
role: user.role,
plan: user.plan,
displayName: user.displayName,
},
};
});
@ -53,6 +61,7 @@ export async function authRoutes(app: FastifyInstance) {
throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
}
const { email, password, displayName, role, productId } = parsed.data;
const product = getRequestProductConfig(req);
const existing = await repo.getByEmail(email, productId);
if (existing) throw new BadRequestError('Email already registered');
@ -63,6 +72,7 @@ export async function authRoutes(app: FastifyInstance) {
productId,
email: email.toLowerCase(),
passwordHash: await repo.hashPassword(password),
plan: product.defaultPlan,
role,
displayName,
status: 'active',
@ -77,6 +87,7 @@ export async function authRoutes(app: FastifyInstance) {
email: user.email,
role: user.role,
productId,
plan: user.plan,
});
const refreshToken = await jwt.createRefreshToken({ sub: user.id, productId });
@ -84,7 +95,13 @@ export async function authRoutes(app: FastifyInstance) {
return {
accessToken,
refreshToken,
user: { id: user.id, email: user.email, role: user.role, displayName: user.displayName },
user: {
id: user.id,
email: user.email,
role: user.role,
plan: user.plan,
displayName: user.displayName,
},
};
});
@ -106,6 +123,7 @@ export async function authRoutes(app: FastifyInstance) {
email: user.email,
role: user.role,
productId: user.productId,
plan: user.plan,
});
return { accessToken };
} catch {
@ -123,7 +141,13 @@ export async function authRoutes(app: FastifyInstance) {
const payload = await jwt.verifyToken(token);
const user = await repo.getById(payload.sub);
if (!user) throw new UnauthorizedError('User not found');
return { id: user.id, email: user.email, role: user.role, displayName: user.displayName };
return {
id: user.id,
email: user.email,
role: user.role,
plan: user.plan,
displayName: user.displayName,
};
} catch {
throw new UnauthorizedError('Invalid or expired token');
}

View File

@ -9,6 +9,7 @@ export interface UserDoc {
productId: string;
email: string;
passwordHash: string;
plan: 'free' | 'pro' | 'enterprise';
role: 'super_admin' | 'admin' | 'viewer' | 'user';
displayName: string;
status: 'active' | 'disabled';
@ -22,6 +23,7 @@ export interface TokenPayload {
email: string;
role: string;
productId: string;
plan?: 'free' | 'pro' | 'enterprise';
iat: number;
exp: number;
}