auth-core.ts216 lines · main
1/**
2 * Briven Auth Core public routes (briven-engine on Doltgres).
3 *
4 * - GET /v1/auth-core/info — engine status (no secrets)
5 * - GET /v1/auth-core/ready — health
6 * - GET /v1/auth-core/map/:projectId — project → tenant map
7 * - Legacy multi-tenant Better Auth product paths → 410 Gone
8 * - Enable Auth is bridged to briven-engine (not 410)
9 *
10 * Platform operator login (/v1/auth/* Better Auth for briven.tech) is NOT here.
11 */
12
13import { Hono } from 'hono';
14
15import { requireAuth } from '../middleware/session.js';
16import {
17 requireProjectAuth,
18 requireProjectRole,
19} from '../middleware/project-auth.js';
20import { BUILD_AT, BUILD_SHA } from './health.js';
21import { mapProjectToAuthCore } from '../services/auth-core/project-map.js';
22import {
23 BRIVEN_ENGINE_ID,
24 probeBrivenEngine,
25} from '../services/auth-core/engine.js';
26import {
27 disableBrivenEngineAuth,
28 enableBrivenEngineAuth,
29 listBrivenEngineWorkspace,
30} from '../services/auth-core/workspace.js';
31import type { AppEnv } from '../types/app-env.js';
32
33export const authCoreRouter = new Hono<AppEnv>();
34
35const GONE = {
36 code: 'auth_product_retired',
37 message:
38 'That Auth path was retired. Use Briven Auth (briven-engine) under /v1/auth-core/*.',
39 engine: BRIVEN_ENGINE_ID,
40} as const;
41
42authCoreRouter.get('/v1/auth-core/info', async (c) => {
43 const core = await probeBrivenEngine();
44 return c.json({
45 service: 'briven-auth-core',
46 product: 'Briven Auth',
47 engine: BRIVEN_ENGINE_ID,
48 productStatus: 'live-on-briven-engine',
49 buildSha: BUILD_SHA,
50 buildAt: BUILD_AT,
51 ...core,
52 });
53});
54
55authCoreRouter.get('/v1/auth-core/ready', async (c) => {
56 const core = await probeBrivenEngine();
57 if (!core.ok) {
58 return c.json({ status: 'not_ready', ...core }, 503);
59 }
60 return c.json({ status: 'ready', engine: BRIVEN_ENGINE_ID, ...core });
61});
62
63/** Phase 1.4 — projectId → briven-engine appId/tenantId (rule-based until Multitenancy). */
64authCoreRouter.get('/v1/auth-core/map/:projectId', (c) => {
65 const projectId = c.req.param('projectId');
66 try {
67 return c.json(mapProjectToAuthCore(projectId));
68 } catch (err) {
69 return c.json(
70 {
71 code: 'invalid_project_id',
72 message: err instanceof Error ? err.message : String(err),
73 },
74 400,
75 );
76 }
77});
78
79/**
80 * Dashboard workspace — projects + Auth on/off (Doltgres tenants).
81 * Replaces legacy /v1/auth-v2/workspace.
82 */
83authCoreRouter.get(
84 '/v1/auth-core/workspace',
85 requireAuth(),
86 async (c) => {
87 const user = c.get('user');
88 if (!user?.id) {
89 return c.json(
90 { code: 'unauthorized', message: 'sign in required', engine: BRIVEN_ENGINE_ID },
91 401,
92 );
93 }
94 const data = await listBrivenEngineWorkspace(user.id);
95 return c.json({ ok: true, ...data });
96 },
97);
98
99/**
100 * Enable Auth for a project — creates briven-engine tenant on Doltgres.
101 * Also available as POST /v1/projects/:id/auth/enable (bridge below).
102 */
103authCoreRouter.post(
104 '/v1/auth-core/projects/:projectId/enable',
105 ...[requireProjectAuth('projectId'), requireProjectRole('admin')],
106 async (c) => {
107 const projectId = c.req.param('projectId');
108 const result = await enableBrivenEngineAuth(projectId);
109 return c.json(result, result.ok ? 200 : 503);
110 },
111);
112
113/**
114 * Disable Auth for a project (soft). User data stays; enable again anytime.
115 */
116authCoreRouter.post(
117 '/v1/auth-core/projects/:projectId/disable',
118 ...[requireProjectAuth('projectId'), requireProjectRole('admin')],
119 async (c) => {
120 const projectId = c.req.param('projectId');
121 const result = await disableBrivenEngineAuth(projectId);
122 return c.json(result, result.ok ? 200 : 503);
123 },
124);
125
126/**
127 * Bridge: dashboard "enable Auth" buttons still call the old path.
128 * Do NOT return 410 — wire to briven-engine instead.
129 */
130authCoreRouter.post(
131 '/v1/projects/:id/auth/enable',
132 ...[requireProjectAuth('id'), requireProjectRole('admin')],
133 async (c) => {
134 const projectId = c.req.param('id');
135 const result = await enableBrivenEngineAuth(projectId);
136 if (!result.ok) {
137 return c.json(
138 {
139 code: 'auth_enable_failed',
140 message: result.message ?? 'could not enable Auth on briven-engine',
141 engine: BRIVEN_ENGINE_ID,
142 },
143 503,
144 );
145 }
146 return c.json({
147 ok: true,
148 engine: BRIVEN_ENGINE_ID,
149 projectId: result.projectId,
150 tenantId: result.tenantId,
151 authEnabled: true,
152 created: result.created,
153 message: result.created
154 ? 'Auth enabled for this project'
155 : 'Auth already on for this project',
156 storage: 'doltgres',
157 });
158 },
159);
160
161authCoreRouter.post(
162 '/v1/projects/:id/auth/disable',
163 ...[requireProjectAuth('id'), requireProjectRole('admin')],
164 async (c) => {
165 const projectId = c.req.param('id');
166 const result = await disableBrivenEngineAuth(projectId);
167 if (!result.ok) {
168 return c.json(
169 {
170 code: 'auth_disable_failed',
171 message: result.message ?? 'could not disable Auth',
172 engine: BRIVEN_ENGINE_ID,
173 },
174 503,
175 );
176 }
177 return c.json({
178 ok: true,
179 engine: BRIVEN_ENGINE_ID,
180 projectId: result.projectId,
181 tenantId: result.tenantId,
182 authEnabled: false,
183 message: result.message ?? 'Auth disabled for this project',
184 storage: 'doltgres',
185 });
186 },
187);
188
189/**
190 * Bridge: workspace list for older UI that still hits auth-v2.
191 */
192authCoreRouter.get('/v1/auth-v2/workspace', requireAuth(), async (c) => {
193 const user = c.get('user');
194 if (!user?.id) {
195 return c.json({ code: 'unauthorized' }, 401);
196 }
197 const data = await listBrivenEngineWorkspace(user.id);
198 return c.json({ ok: true, engine: 'briven-engine', projects: data.projects });
199});
200
201/** Old multi-tenant Better Auth product — gone (except enable + workspace bridges above). */
202authCoreRouter.all('/v1/auth-tenant/*', (c) => c.json(GONE, 410));
203// Note: GET /v1/auth-v2/workspace is registered above; other auth-v2 paths stay retired.
204authCoreRouter.all('/v1/auth-v2/*', (c) => c.json(GONE, 410));
205// Note: POST /v1/projects/:id/auth/enable is registered above; other project auth paths retired.
206authCoreRouter.all('/v1/projects/:id/auth/*', (c) =>
207 c.json(
208 {
209 ...GONE,
210 message:
211 'Use Briven Auth under /dashboard/auth and /v1/auth-core/* for this action.',
212 },
213 410,
214 ),
215);
216authCoreRouter.all('/v1/projects/:id/scim/*', (c) => c.json(GONE, 410));