auth-core-session.ts163 lines · main
1/**
2 * Briven Auth Core — session admin/verify endpoints (Phase 2).
3 *
4 * GET /v1/auth-core/session/me — verify current session (cookie/header)
5 * POST /v1/auth-core/session/revoke — revoke by handle or all for user
6 * GET /v1/auth-core/session/list — list handles for userId (query)
7 */
8
9import { Hono } from 'hono';
10
11import { requireAuthCoreDashboard } from '../middleware/auth-core-guard.js';
12import {
13 listSessionsForUser,
14 revokeAllSessionsForUser,
15 revokeSession,
16 verifyAuthCoreSession,
17} from '../services/auth-core/session.js';
18import { listRecentEngineSessions } from '../services/auth-core/native-session.js';
19import {
20 BRIVEN_ENGINE_ID,
21 isAuthCoreInitialized,
22} from '../services/auth-core/engine.js';
23import type { AppEnv } from '../types/app-env.js';
24
25export const authCoreSessionRouter = new Hono<AppEnv>();
26
27// me = self-check via cookie (public to holders of session cookie)
28// list/revoke/recent = dashboard only
29authCoreSessionRouter.use('/v1/auth-core/session/list', requireAuthCoreDashboard());
30authCoreSessionRouter.use('/v1/auth-core/session/revoke', requireAuthCoreDashboard());
31authCoreSessionRouter.use('/v1/auth-core/session/recent', requireAuthCoreDashboard());
32
33authCoreSessionRouter.get('/v1/auth-core/session/me', async (c) => {
34 if (!isAuthCoreInitialized()) {
35 return c.json({ code: 'auth_core_sdk_not_ready' }, 503);
36 }
37
38 const result = await verifyAuthCoreSession({
39 url: c.req.url,
40 method: c.req.method,
41 headers: c.req.raw.headers,
42 cookieHeader: c.req.header('cookie'),
43 });
44
45 if (!result.ok) {
46 return c.json(
47 { authenticated: false, reason: result.reason },
48 (result.status as 401) ?? 401,
49 );
50 }
51
52 const session = result.session;
53 const userId = session.getUserId();
54 // Load email for app session mint (Konnos etc.) — apps expect user.email.
55 let email: string | null = null;
56 let name: string | null = null;
57 try {
58 const { getEnginePool } = await import('../services/auth-core/db.js');
59 const pool = getEnginePool();
60 const u = await pool.query(
61 `SELECT email, metadata_json FROM be_users WHERE id = $1 LIMIT 1`,
62 [userId],
63 );
64 const row = u.rows[0] as
65 | { email?: string | null; metadata_json?: string | null }
66 | undefined;
67 if (row?.email) email = row.email;
68 if (row?.metadata_json) {
69 try {
70 const meta = JSON.parse(row.metadata_json) as { name?: string };
71 if (meta.name) name = meta.name;
72 } catch {
73 /* ignore */
74 }
75 }
76 } catch {
77 /* email optional */
78 }
79 return c.json({
80 authenticated: true,
81 userId,
82 sessionHandle: session.getHandle(),
83 accessTokenPayload: session.getAccessTokenPayload(),
84 // Better Auth–shaped fields for app mint routes
85 user: { id: userId, email, name },
86 });
87});
88
89authCoreSessionRouter.get('/v1/auth-core/session/list', async (c) => {
90 if (!isAuthCoreInitialized()) {
91 return c.json({ code: 'auth_core_sdk_not_ready' }, 503);
92 }
93 const userId = c.req.query('userId');
94 if (!userId) {
95 return c.json({ code: 'userId_required' }, 400);
96 }
97 const handles = await listSessionsForUser(userId);
98 return c.json({ userId, handles, count: handles.length });
99});
100
101/** Yellow dashboard: recent active sessions across tenants. */
102authCoreSessionRouter.get('/v1/auth-core/session/recent', async (c) => {
103 if (!isAuthCoreInitialized()) {
104 return c.json(
105 {
106 engine: BRIVEN_ENGINE_ID,
107 code: 'auth_core_sdk_not_ready',
108 sessions: [],
109 },
110 503,
111 );
112 }
113 const limit = Number(c.req.query('limit') ?? '50');
114 const projectId = c.req.query('projectId') ?? undefined;
115 let tenantId = c.req.query('tenantId') ?? undefined;
116 if (!tenantId && projectId) {
117 try {
118 const { projectIdToTenantId } = await import(
119 '../services/auth-core/project-map.js'
120 );
121 tenantId = projectIdToTenantId(projectId);
122 } catch {
123 tenantId = undefined;
124 }
125 }
126 const sessions = await listRecentEngineSessions(
127 Number.isFinite(limit) ? limit : 50,
128 tenantId ? { tenantId } : undefined,
129 );
130 return c.json({
131 engine: BRIVEN_ENGINE_ID,
132 storage: 'doltgres',
133 projectId: projectId ?? null,
134 tenantId: tenantId ?? null,
135 sessions,
136 count: sessions.length,
137 });
138});
139
140authCoreSessionRouter.post('/v1/auth-core/session/revoke', async (c) => {
141 if (!isAuthCoreInitialized()) {
142 return c.json({ code: 'auth_core_sdk_not_ready' }, 503);
143 }
144 let body: { sessionHandle?: string; userId?: string; all?: boolean } = {};
145 try {
146 body = await c.req.json();
147 } catch {
148 body = {};
149 }
150
151 if (body.all && body.userId) {
152 const n = await revokeAllSessionsForUser(body.userId);
153 return c.json({ revoked: n, userId: body.userId });
154 }
155 if (body.sessionHandle) {
156 const ok = await revokeSession(body.sessionHandle);
157 return c.json({ revoked: ok ? 1 : 0, sessionHandle: body.sessionHandle });
158 }
159 return c.json(
160 { code: 'bad_request', message: 'Provide sessionHandle, or userId+all' },
161 400,
162 );
163});