diff --git a/server.mjs b/server.mjs index 3d33471..73dae2a 100644 --- a/server.mjs +++ b/server.mjs @@ -635,6 +635,7 @@ attachPortalCoreAuthRoutes({ resolveSkillRuntimeForClient, resolveAgentCodeRunForClient, resolveGoalRunForClient, + getSubscriptionService: () => subscriptionService, getPlazaSeo: () => plazaSeo, plazaClientIp, logger: console, diff --git a/server/portal-core-auth-routes.mjs b/server/portal-core-auth-routes.mjs index a28e786..5dc6a0f 100644 --- a/server/portal-core-auth-routes.mjs +++ b/server/portal-core-auth-routes.mjs @@ -21,6 +21,7 @@ export function attachPortalCoreAuthRoutes({ resolveGoalRunForClient = async (userId) => ({ enabled: isGoalRunEnabledForUser(userId, process.env), }), + getSubscriptionService = () => null, getPlazaSeo = () => null, plazaClientIp = (req) => req.ip, logger = console, @@ -64,9 +65,16 @@ export function attachPortalCoreAuthRoutes({ const agentCodeRun = await resolveAgentCodeRunForClient(me.id); const goalRun = await resolveGoalRunForClient(me.id); + const subscriptionService = getSubscriptionService(); + const subscription = subscriptionService + ? await subscriptionService.getActiveSubscription(me.id) + : null; return res.json({ authenticated: true, - user: me, + user: { + ...me, + subscription, + }, mode: 'user', capabilities: capabilityState.capabilities, grantedSkills: diff --git a/server/portal-core-auth-routes.test.mjs b/server/portal-core-auth-routes.test.mjs index eebbdad..62d5e92 100644 --- a/server/portal-core-auth-routes.test.mjs +++ b/server/portal-core-auth-routes.test.mjs @@ -209,7 +209,7 @@ test('returns multi-user status and preserves capability projection', async () = assert.equal(res.statusCode, 200); assert.deepEqual(res.body, { authenticated: true, - user: { id: 'user-1', username: 'john' }, + user: { id: 'user-1', username: 'john', subscription: null }, mode: 'user', capabilities: ['chat'], grantedSkills: ['web'], @@ -220,6 +220,24 @@ test('returns multi-user status and preserves capability projection', async () = }); }); +test('includes active subscription in auth status user payload', async () => { + const setup = createSetup({ + getSubscriptionService: () => ({ + async getActiveSubscription(userId) { + assert.equal(userId, 'user-1'); + return { + planType: 'pro', + periodTokensLimit: 100_000_000, + periodTokensUsed: 0, + }; + }, + }), + }); + const res = await invoke(setup, 'get', '/auth/status'); + assert.equal(res.statusCode, 200); + assert.equal(res.body.user.subscription?.planType, 'pro'); +}); + test('preserves unavailable, legacy, and disabled status modes', async () => { const unavailable = createSetup({ isDatabaseConfiguredFn: () => true, diff --git a/src/api/client.ts b/src/api/client.ts index 9c0989d..01019b6 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -331,17 +331,24 @@ export async function checkAuth(): Promise { } if (status.authenticated) resetUnauthorizedGuard(); applyAgentCodeRunClientPolicy(status.agentCodeRun ?? null); - if (status.authenticated && status.mode === 'user' && !status.capabilities) { - try { - const me = await getMe(); - return { - ...status, - capabilities: me.capabilities, - grantedSkills: me.grantedSkills, - user: me.user, - }; - } catch { - return status; + if (status.authenticated && status.mode === 'user') { + const needsMeHydration = + !status.capabilities + || !status.user + || !Object.prototype.hasOwnProperty.call(status.user, 'subscription'); + if (needsMeHydration) { + try { + const me = await getMe(); + return { + ...status, + capabilities: me.capabilities ?? status.capabilities, + grantedSkills: me.grantedSkills ?? status.grantedSkills, + unrestricted: me.unrestricted ?? status.unrestricted, + user: me.user, + }; + } catch { + return status; + } } } return status;