ugly-app 0.1.518 → 0.1.521
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pgdata/base/16384/pg_internal.init +0 -0
- package/.pgdata/global/pg_control +0 -0
- package/.pgdata/global/pg_internal.init +0 -0
- package/.pgdata/pg_wal/000000010000000000000004 +0 -0
- package/.pgdata/postmaster.pid +2 -2
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +0 -3
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +31 -66
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +8 -66
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +0 -4
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/schemaGen.d.ts.map +1 -1
- package/dist/cli/schemaGen.js +0 -4
- package/dist/cli/schemaGen.js.map +1 -1
- package/dist/cli/schemaStatus.d.ts.map +1 -1
- package/dist/cli/schemaStatus.js +0 -4
- package/dist/cli/schemaStatus.js.map +1 -1
- package/dist/cli/serverLogQueryApi.d.ts +5 -4
- package/dist/cli/serverLogQueryApi.d.ts.map +1 -1
- package/dist/cli/serverLogQueryApi.js +55 -115
- package/dist/cli/serverLogQueryApi.js.map +1 -1
- package/dist/cli/version.d.ts +1 -1
- package/dist/cli/version.js +1 -1
- package/dist/client/auth/MagicLinkCallback.d.ts.map +1 -1
- package/dist/client/auth/MagicLinkCallback.js +8 -2
- package/dist/client/auth/MagicLinkCallback.js.map +1 -1
- package/dist/client/auth/MagicLinkForm.d.ts.map +1 -1
- package/dist/client/auth/MagicLinkForm.js +6 -1
- package/dist/client/auth/MagicLinkForm.js.map +1 -1
- package/dist/server/App.d.ts.map +1 -1
- package/dist/server/App.js +1 -2
- package/dist/server/App.js.map +1 -1
- package/dist/server/Email.d.ts.map +1 -1
- package/dist/server/Email.js +7 -1
- package/dist/server/Email.js.map +1 -1
- package/dist/server/Logging.d.ts +5 -8
- package/dist/server/Logging.d.ts.map +1 -1
- package/dist/server/Logging.js +21 -97
- package/dist/server/Logging.js.map +1 -1
- package/dist/server/adapter/workers/cf-types.d.ts +7 -0
- package/dist/server/adapter/workers/cf-types.d.ts.map +1 -1
- package/dist/server/adapter/workers/createWorkersApp.js +1 -0
- package/dist/server/adapter/workers/createWorkersApp.js.map +1 -1
- package/dist/server/auth/magicLinkProvider.d.ts.map +1 -1
- package/dist/server/auth/magicLinkProvider.js +55 -6
- package/dist/server/auth/magicLinkProvider.js.map +1 -1
- package/dist/server/auth/workersMagicLink.d.ts.map +1 -1
- package/dist/server/auth/workersMagicLink.js +117 -13
- package/dist/server/auth/workersMagicLink.js.map +1 -1
- package/dist/server/bootstrap.d.ts +5 -21
- package/dist/server/bootstrap.d.ts.map +1 -1
- package/dist/server/bootstrap.js +5 -27
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/shared/DB.d.ts +0 -9
- package/dist/shared/DB.d.ts.map +1 -1
- package/dist/shared/DB.js +46 -10
- package/dist/shared/DB.js.map +1 -1
- package/dist/vite/index.d.ts +0 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +0 -1
- package/dist/vite/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/dev.ts +0 -4
- package/src/cli/doctor.ts +31 -63
- package/src/cli/index.ts +9 -81
- package/src/cli/migrate.ts +0 -4
- package/src/cli/schemaGen.ts +0 -4
- package/src/cli/schemaStatus.ts +0 -4
- package/src/cli/serverLogQueryApi.ts +53 -136
- package/src/cli/version.ts +1 -1
- package/src/client/auth/MagicLinkCallback.tsx +10 -2
- package/src/client/auth/MagicLinkForm.tsx +7 -1
- package/src/server/App.ts +1 -2
- package/src/server/Email.ts +7 -1
- package/src/server/Logging.ts +21 -97
- package/src/server/adapter/workers/cf-types.ts +8 -0
- package/src/server/adapter/workers/createWorkersApp.ts +1 -0
- package/src/server/auth/magicLinkProvider.ts +61 -6
- package/src/server/auth/workersMagicLink.ts +117 -13
- package/src/server/bootstrap.ts +5 -29
- package/src/shared/DB.ts +46 -17
- package/src/vite/index.ts +0 -1
- package/.pgdata/base/1/pg_internal.init +0 -0
- package/.pgdata/base/17702/pg_internal.init +0 -0
- package/.pgdata/base/5/pg_internal.init +0 -0
- package/dist/cli/devTunnel.d.ts +0 -37
- package/dist/cli/devTunnel.d.ts.map +0 -1
- package/dist/cli/devTunnel.js +0 -470
- package/dist/cli/devTunnel.js.map +0 -1
- package/dist/cli/ensureInfra.d.ts +0 -9
- package/dist/cli/ensureInfra.d.ts.map +0 -1
- package/dist/cli/ensureInfra.js +0 -86
- package/dist/cli/ensureInfra.js.map +0 -1
- package/dist/cli/sidecar.d.ts +0 -21
- package/dist/cli/sidecar.d.ts.map +0 -1
- package/dist/cli/sidecar.js +0 -294
- package/dist/cli/sidecar.js.map +0 -1
- package/dist/cli/sidecarHealth.d.ts +0 -13
- package/dist/cli/sidecarHealth.d.ts.map +0 -1
- package/dist/cli/sidecarHealth.js +0 -42
- package/dist/cli/sidecarHealth.js.map +0 -1
- package/dist/cli/sidecarProtocol.d.ts +0 -41
- package/dist/cli/sidecarProtocol.d.ts.map +0 -1
- package/dist/cli/sidecarProtocol.js +0 -61
- package/dist/cli/sidecarProtocol.js.map +0 -1
- package/dist/vite/tunnelPlugin.d.ts +0 -10
- package/dist/vite/tunnelPlugin.d.ts.map +0 -1
- package/dist/vite/tunnelPlugin.js +0 -30
- package/dist/vite/tunnelPlugin.js.map +0 -1
- package/src/cli/devTunnel.ts +0 -589
- package/src/cli/ensureInfra.ts +0 -90
- package/src/cli/sidecar.ts +0 -350
- package/src/cli/sidecarHealth.ts +0 -58
- package/src/cli/sidecarProtocol.ts +0 -68
- package/src/vite/tunnelPlugin.ts +0 -32
|
@@ -102,13 +102,37 @@ const MAX_REQUESTS_PER_EMAIL_PER_HOUR = 5;
|
|
|
102
102
|
|
|
103
103
|
const requestBodySchema = z.object({
|
|
104
104
|
email: z.string().email().max(254),
|
|
105
|
+
returnTo: z.string().max(2000).optional(),
|
|
105
106
|
});
|
|
106
107
|
|
|
107
108
|
const verifyBodySchema = z.object({
|
|
108
109
|
id: z.string().min(1).max(128),
|
|
109
110
|
sig: z.string().min(1).max(128),
|
|
111
|
+
returnTo: z.string().max(2000).optional(),
|
|
110
112
|
});
|
|
111
113
|
|
|
114
|
+
/** Same-origin redirect guard. See `workersMagicLink.ts` for rationale. */
|
|
115
|
+
function validateReturnTo(
|
|
116
|
+
returnTo: string | undefined,
|
|
117
|
+
requestHost: string,
|
|
118
|
+
): string {
|
|
119
|
+
if (!returnTo || returnTo.length > 2000) return '/';
|
|
120
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(returnTo)) {
|
|
121
|
+
try {
|
|
122
|
+
const u = new URL(returnTo);
|
|
123
|
+
if (u.protocol !== 'https:' && u.protocol !== 'http:') return '/';
|
|
124
|
+
if (u.host.toLowerCase() !== requestHost.toLowerCase()) return '/';
|
|
125
|
+
if (u.pathname.startsWith('/auth/magic-link/')) return '/';
|
|
126
|
+
return `${u.pathname}${u.search}${u.hash}`;
|
|
127
|
+
} catch {
|
|
128
|
+
return '/';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!returnTo.startsWith('/') || returnTo.startsWith('//')) return '/';
|
|
132
|
+
if (returnTo.startsWith('/auth/magic-link/')) return '/';
|
|
133
|
+
return returnTo;
|
|
134
|
+
}
|
|
135
|
+
|
|
112
136
|
/**
|
|
113
137
|
* Magic-link provider (Mode B default). Issues a single-use signed token
|
|
114
138
|
* over email; verification mints a local `auth_token` JWT cookie.
|
|
@@ -206,7 +230,21 @@ export function createMagicLinkProvider(
|
|
|
206
230
|
});
|
|
207
231
|
|
|
208
232
|
const base = getAppPublicUrl();
|
|
209
|
-
|
|
233
|
+
let baseHost = '';
|
|
234
|
+
try {
|
|
235
|
+
baseHost = new URL(base).host;
|
|
236
|
+
} catch {
|
|
237
|
+
baseHost = req.get('host') ?? '';
|
|
238
|
+
}
|
|
239
|
+
const safeReturnTo = validateReturnTo(
|
|
240
|
+
parsed.data.returnTo,
|
|
241
|
+
baseHost,
|
|
242
|
+
);
|
|
243
|
+
const returnToParam =
|
|
244
|
+
safeReturnTo === '/'
|
|
245
|
+
? ''
|
|
246
|
+
: `&returnTo=${encodeURIComponent(safeReturnTo)}`;
|
|
247
|
+
const url = `${base}/auth/magic-link/verify?id=${encodeURIComponent(tokenId)}&sig=${encodeURIComponent(sig)}${returnToParam}`;
|
|
210
248
|
|
|
211
249
|
const tpl = magicLinkEmail(url, appName, expiresMin);
|
|
212
250
|
await emailSend({
|
|
@@ -234,22 +272,35 @@ export function createMagicLinkProvider(
|
|
|
234
272
|
(req: Request, res: Response) => {
|
|
235
273
|
const id = String(req.query['id'] ?? '');
|
|
236
274
|
const sig = String(req.query['sig'] ?? '');
|
|
275
|
+
const safeReturnTo = validateReturnTo(
|
|
276
|
+
req.query['returnTo'] != null
|
|
277
|
+
? String(req.query['returnTo'])
|
|
278
|
+
: undefined,
|
|
279
|
+
req.get('host') ?? '',
|
|
280
|
+
);
|
|
237
281
|
// Inline JSON-encode to prevent <script>-context XSS; even though
|
|
238
282
|
// these values are signed/short, defence-in-depth.
|
|
239
283
|
const idJson = JSON.stringify(id);
|
|
240
284
|
const sigJson = JSON.stringify(sig);
|
|
285
|
+
const returnToJson = JSON.stringify(safeReturnTo);
|
|
241
286
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
242
287
|
res.send(`<!doctype html><html><head><meta charset="utf-8"><title>Signing in…</title></head><body style="font-family:system-ui,sans-serif;padding:24px;text-align:center"><p>Signing you in…</p><script>
|
|
243
288
|
(async function(){
|
|
289
|
+
var returnTo = ${returnToJson};
|
|
244
290
|
try {
|
|
245
|
-
|
|
291
|
+
var r = await fetch('/auth/magic-link/verify', {
|
|
246
292
|
method: 'POST',
|
|
247
293
|
headers: {'Content-Type': 'application/json'},
|
|
248
294
|
credentials: 'same-origin',
|
|
249
|
-
body: JSON.stringify({ id: ${idJson}, sig: ${sigJson} }),
|
|
295
|
+
body: JSON.stringify({ id: ${idJson}, sig: ${sigJson}, returnTo: returnTo }),
|
|
250
296
|
});
|
|
251
|
-
if (r.ok) {
|
|
252
|
-
|
|
297
|
+
if (r.ok) {
|
|
298
|
+
var data = await r.json().catch(function(){ return {}; });
|
|
299
|
+
var dest = (data && data.returnTo) || returnTo || '/';
|
|
300
|
+
window.location.replace(dest);
|
|
301
|
+
} else {
|
|
302
|
+
document.body.innerText = 'This sign-in link is invalid or has expired.';
|
|
303
|
+
}
|
|
253
304
|
} catch (e) { document.body.innerText = 'Sign-in failed. Please try again.'; }
|
|
254
305
|
})();
|
|
255
306
|
</script></body></html>`);
|
|
@@ -321,7 +372,11 @@ export function createMagicLinkProvider(
|
|
|
321
372
|
maxAge: COOKIE_MAX_AGE_SECONDS * 1000,
|
|
322
373
|
path: '/',
|
|
323
374
|
});
|
|
324
|
-
|
|
375
|
+
const safeReturnTo = validateReturnTo(
|
|
376
|
+
parsed.data.returnTo,
|
|
377
|
+
req.get('host') ?? '',
|
|
378
|
+
);
|
|
379
|
+
res.json({ ok: true, token, returnTo: safeReturnTo });
|
|
325
380
|
} catch (err) {
|
|
326
381
|
console.error('[Auth] magic-link verify failed', err);
|
|
327
382
|
res.status(401).json({ error: 'Invalid link' });
|
|
@@ -31,13 +31,53 @@ const MAX_REQUESTS_PER_EMAIL_PER_HOUR = 5;
|
|
|
31
31
|
|
|
32
32
|
const requestBodySchema = z.object({
|
|
33
33
|
email: z.string().email().max(254),
|
|
34
|
+
/**
|
|
35
|
+
* Optional URL the client wants to land on after sign-in. Must be
|
|
36
|
+
* same-origin — full URLs to other hosts are silently downgraded to
|
|
37
|
+
* `/` by `validateReturnTo`. Sent by `MagicLinkForm` as
|
|
38
|
+
* `window.location.href` so the user resumes wherever they were.
|
|
39
|
+
*/
|
|
40
|
+
returnTo: z.string().max(2000).optional(),
|
|
34
41
|
});
|
|
35
42
|
|
|
36
43
|
const verifyBodySchema = z.object({
|
|
37
44
|
id: z.string().min(1).max(128),
|
|
38
45
|
sig: z.string().min(1).max(128),
|
|
46
|
+
returnTo: z.string().max(2000).optional(),
|
|
39
47
|
});
|
|
40
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Same-origin redirect guard. Returns a safe path (always starts with
|
|
51
|
+
* `/`) — `/` when the input is missing, off-origin, or points back at
|
|
52
|
+
* `/auth/magic-link/*` (loop prevention). The verify URL inside the
|
|
53
|
+
* email is signed (HMAC binds id+email+expiresAt), so a tampered
|
|
54
|
+
* returnTo can't be used to bypass auth, but a same-origin guard still
|
|
55
|
+
* matters to prevent post-login open redirects.
|
|
56
|
+
*/
|
|
57
|
+
function validateReturnTo(
|
|
58
|
+
returnTo: string | undefined,
|
|
59
|
+
requestHost: string,
|
|
60
|
+
): string {
|
|
61
|
+
if (!returnTo || returnTo.length > 2000) return '/';
|
|
62
|
+
// Absolute URL: must be http(s) AND same host.
|
|
63
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(returnTo)) {
|
|
64
|
+
try {
|
|
65
|
+
const u = new URL(returnTo);
|
|
66
|
+
if (u.protocol !== 'https:' && u.protocol !== 'http:') return '/';
|
|
67
|
+
if (u.host.toLowerCase() !== requestHost.toLowerCase()) return '/';
|
|
68
|
+
if (u.pathname.startsWith('/auth/magic-link/')) return '/';
|
|
69
|
+
return `${u.pathname}${u.search}${u.hash}`;
|
|
70
|
+
} catch {
|
|
71
|
+
return '/';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Relative — accept only paths that start with `/` (block protocol-
|
|
75
|
+
// relative `//evil.com` URLs and bare path-traversal).
|
|
76
|
+
if (!returnTo.startsWith('/') || returnTo.startsWith('//')) return '/';
|
|
77
|
+
if (returnTo.startsWith('/auth/magic-link/')) return '/';
|
|
78
|
+
return returnTo;
|
|
79
|
+
}
|
|
80
|
+
|
|
41
81
|
export interface WorkersMagicLinkConfig {
|
|
42
82
|
/** Magic-link lifetime in minutes (1..1440). */
|
|
43
83
|
expiresMin: number;
|
|
@@ -223,16 +263,66 @@ export function registerWorkersMagicLinkRoutes(
|
|
|
223
263
|
});
|
|
224
264
|
|
|
225
265
|
const base = getAppPublicUrl(c, config.publicAppUrl);
|
|
226
|
-
|
|
266
|
+
// Validate returnTo against the public origin so a malicious
|
|
267
|
+
// `returnTo` (sent in the request body) can't ride along through
|
|
268
|
+
// the email and steal the post-login navigation. The validation
|
|
269
|
+
// strips off-origin URLs to `/` silently.
|
|
270
|
+
let baseHost = '';
|
|
271
|
+
try {
|
|
272
|
+
baseHost = new URL(base).host;
|
|
273
|
+
} catch {
|
|
274
|
+
baseHost = c.req.header('host') ?? '';
|
|
275
|
+
}
|
|
276
|
+
const safeReturnTo = validateReturnTo(parsed.data.returnTo, baseHost);
|
|
277
|
+
const returnToParam =
|
|
278
|
+
safeReturnTo === '/'
|
|
279
|
+
? ''
|
|
280
|
+
: `&returnTo=${encodeURIComponent(safeReturnTo)}`;
|
|
281
|
+
const url = `${base}/auth/magic-link/verify?id=${encodeURIComponent(tokenId)}&sig=${encodeURIComponent(sig)}${returnToParam}`;
|
|
282
|
+
|
|
283
|
+
const debugMode = process.env['UGLY_APP_MAGIC_LINK_DEBUG'] === '1';
|
|
284
|
+
// Debug mode: log the link to the worker console so it's grep-able
|
|
285
|
+
// from `wrangler tail`. Also returned in the response below.
|
|
286
|
+
// ALWAYS off in prod — gated on UGLY_APP_MAGIC_LINK_DEBUG and the
|
|
287
|
+
// self auth-mode binding, never on by default.
|
|
288
|
+
if (debugMode) {
|
|
289
|
+
// eslint-disable-next-line no-console
|
|
290
|
+
console.log(
|
|
291
|
+
`[Auth] DEBUG magic-link for ${email}: ${url}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
227
294
|
|
|
228
295
|
const tpl = magicLinkEmail(url, appName, expiresMin);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
296
|
+
let emailError: unknown = null;
|
|
297
|
+
try {
|
|
298
|
+
await emailSend({
|
|
299
|
+
to: email,
|
|
300
|
+
subject: tpl.subject,
|
|
301
|
+
html: tpl.html,
|
|
302
|
+
text: tpl.text,
|
|
303
|
+
id: 'magic-link',
|
|
304
|
+
});
|
|
305
|
+
} catch (err) {
|
|
306
|
+
emailError = err;
|
|
307
|
+
// eslint-disable-next-line no-console
|
|
308
|
+
console.error('[Auth] magic-link email send failed', err);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (debugMode) {
|
|
312
|
+
// Debug-only response shape — magic link surfaced directly so
|
|
313
|
+
// you can drive the verify step from a test harness without a
|
|
314
|
+
// working email proxy or sending domain. Anti-enum still
|
|
315
|
+
// covers the prod path; this branch only runs when the
|
|
316
|
+
// operator opts in.
|
|
317
|
+
return c.json({
|
|
318
|
+
ok: true,
|
|
319
|
+
debug: {
|
|
320
|
+
link: url,
|
|
321
|
+
emailDelivered: !emailError,
|
|
322
|
+
emailError: emailError ? String(emailError) : null,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
236
326
|
} catch (err) {
|
|
237
327
|
// eslint-disable-next-line no-console
|
|
238
328
|
console.error('[Auth] magic-link request failed', err);
|
|
@@ -246,19 +336,31 @@ export function registerWorkersMagicLinkRoutes(
|
|
|
246
336
|
}
|
|
247
337
|
const id = c.req.query('id') ?? '';
|
|
248
338
|
const sig = c.req.query('sig') ?? '';
|
|
339
|
+
// Re-validate the URL-borne returnTo at GET time too. The link can
|
|
340
|
+
// be tampered between email-render and click, so we don't trust
|
|
341
|
+
// whatever shows up here without re-checking origin.
|
|
342
|
+
const host = c.req.header('host') ?? '';
|
|
343
|
+
const safeReturnTo = validateReturnTo(c.req.query('returnTo'), host);
|
|
249
344
|
const idJson = JSON.stringify(id);
|
|
250
345
|
const sigJson = JSON.stringify(sig);
|
|
346
|
+
const returnToJson = JSON.stringify(safeReturnTo);
|
|
251
347
|
const html = `<!doctype html><html><head><meta charset="utf-8"><title>Signing in…</title></head><body style="font-family:system-ui,sans-serif;padding:24px;text-align:center"><p>Signing you in…</p><script>
|
|
252
348
|
(async function(){
|
|
349
|
+
var returnTo = ${returnToJson};
|
|
253
350
|
try {
|
|
254
|
-
|
|
351
|
+
var r = await fetch('/auth/magic-link/verify', {
|
|
255
352
|
method: 'POST',
|
|
256
353
|
headers: {'Content-Type': 'application/json'},
|
|
257
354
|
credentials: 'same-origin',
|
|
258
|
-
body: JSON.stringify({ id: ${idJson}, sig: ${sigJson} }),
|
|
355
|
+
body: JSON.stringify({ id: ${idJson}, sig: ${sigJson}, returnTo: returnTo }),
|
|
259
356
|
});
|
|
260
|
-
if (r.ok) {
|
|
261
|
-
|
|
357
|
+
if (r.ok) {
|
|
358
|
+
var data = await r.json().catch(function(){ return {}; });
|
|
359
|
+
var dest = (data && data.returnTo) || returnTo || '/';
|
|
360
|
+
window.location.replace(dest);
|
|
361
|
+
} else {
|
|
362
|
+
document.body.innerText = 'This sign-in link is invalid or has expired.';
|
|
363
|
+
}
|
|
262
364
|
} catch (e) { document.body.innerText = 'Sign-in failed. Please try again.'; }
|
|
263
365
|
})();
|
|
264
366
|
</script></body></html>`;
|
|
@@ -309,7 +411,9 @@ export function registerWorkersMagicLinkRoutes(
|
|
|
309
411
|
|
|
310
412
|
const token = await mintSessionJwt(userId, secret);
|
|
311
413
|
c.header('Set-Cookie', buildAuthCookie(token));
|
|
312
|
-
|
|
414
|
+
const host = c.req.header('host') ?? '';
|
|
415
|
+
const safeReturnTo = validateReturnTo(parsed.data.returnTo, host);
|
|
416
|
+
return c.json({ ok: true, token, returnTo: safeReturnTo });
|
|
313
417
|
} catch (err) {
|
|
314
418
|
// eslint-disable-next-line no-console
|
|
315
419
|
console.error('[Auth] magic-link verify failed', err);
|
package/src/server/bootstrap.ts
CHANGED
|
@@ -1,39 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* bootstrapInfra —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* bootstrapInfra — supported entrypoint for wiring up the Postgres pool
|
|
3
|
+
* from any script that imports `ugly-app/server`. Touches the singleton
|
|
4
|
+
* pg.Pool to surface config errors early. The pool itself is lazily
|
|
5
|
+
* created on first query.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
* DATABASE_URL / R2_* env vars for this process only. No `.env`
|
|
8
|
-
* file is ever touched.
|
|
9
|
-
* 2. `initPg()` — touches the singleton pg.Pool to surface config
|
|
10
|
-
* errors early. The pool itself is lazily created on first query.
|
|
11
|
-
*
|
|
12
|
-
* After this returns, `pgQuery`, `createTypedDB`, `storage`, etc. all
|
|
13
|
-
* work as if the script were running inside `ugly-app dev`.
|
|
14
|
-
*
|
|
15
|
-
* Idempotent: if the pool is already open (e.g. when a test or another
|
|
16
|
-
* script already bootstrapped the process), the call is a no-op. Safe
|
|
17
|
-
* to call at the top of every script that touches data.
|
|
7
|
+
* Idempotent: a no-op if the pool is already open.
|
|
18
8
|
*/
|
|
19
9
|
|
|
20
10
|
import { initPg, isPgConnected } from './NodePool.js';
|
|
21
11
|
|
|
22
|
-
/**
|
|
23
|
-
* Ensure Postgres infrastructure is initialised. Safe to call multiple
|
|
24
|
-
* times. Throws on unrecoverable config errors (no DATABASE_URL,
|
|
25
|
-
* login rejected, etc.). The thrown error comes straight from
|
|
26
|
-
* `ensureInfra` so the caller sees the real reason.
|
|
27
|
-
*/
|
|
28
12
|
export async function bootstrapInfra(): Promise<void> {
|
|
29
13
|
if (isPgConnected()) return;
|
|
30
|
-
|
|
31
|
-
// ensureInfra lives under ../cli/ so we pull it in via dynamic
|
|
32
|
-
// import. That keeps the static import graph of `ugly-app/server`
|
|
33
|
-
// clean — nothing in the server runtime has to depend on the CLI
|
|
34
|
-
// layer unless user code asks for it via bootstrapInfra.
|
|
35
|
-
const { ensureInfra } = await import('../cli/ensureInfra.js');
|
|
36
|
-
await ensureInfra();
|
|
37
|
-
|
|
38
14
|
await initPg();
|
|
39
15
|
}
|
package/src/shared/DB.ts
CHANGED
|
@@ -410,8 +410,6 @@ export const ErrorLogDocSchema = z.object({
|
|
|
410
410
|
sessionId: z.string().optional(),
|
|
411
411
|
buildId: z.string().optional(),
|
|
412
412
|
projectId: z.string().optional(),
|
|
413
|
-
/** Set on rows captured via a dev-tunnel data-proxy connection. Null/missing for deployed-server rows. */
|
|
414
|
-
devTunnelId: z.string().nullable().optional(),
|
|
415
413
|
}),
|
|
416
414
|
message: z.string(),
|
|
417
415
|
stack: z.string().nullable().optional(),
|
|
@@ -449,13 +447,6 @@ export interface ErrorLogListInput {
|
|
|
449
447
|
type?: ErrorLogType;
|
|
450
448
|
userId?: string;
|
|
451
449
|
projectId?: string;
|
|
452
|
-
/**
|
|
453
|
-
* Filter by dev-tunnel ownership.
|
|
454
|
-
* - String → only rows from that dev's tunnel sessions.
|
|
455
|
-
* - 'none' → only rows from deployed servers (devTunnelId IS NULL).
|
|
456
|
-
* - Omitted → no filter applied.
|
|
457
|
-
*/
|
|
458
|
-
devTunnelId?: string | 'none';
|
|
459
450
|
startDate?: number;
|
|
460
451
|
endDate?: number;
|
|
461
452
|
cursor?: number;
|
|
@@ -501,8 +492,6 @@ export const PerfLogSchema = z.object({
|
|
|
501
492
|
operation: z.string(),
|
|
502
493
|
durationMs: z.number(),
|
|
503
494
|
userId: z.string(),
|
|
504
|
-
/** Set on rows captured via a dev-tunnel data-proxy connection. Null/missing for deployed-server rows. */
|
|
505
|
-
devTunnelId: z.string().nullable().optional(),
|
|
506
495
|
});
|
|
507
496
|
export type PerfLog = InferDocType<typeof PerfLogSchema>;
|
|
508
497
|
|
|
@@ -627,13 +616,53 @@ export interface EventLog {
|
|
|
627
616
|
// Builtin collection defs (no user — apps define their own)
|
|
628
617
|
// ---------------------------------------------------------------------------
|
|
629
618
|
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
// `auth.mode === '
|
|
619
|
+
// Built-in collections owned by every ugly-app project:
|
|
620
|
+
// - errorLog / perfLog / feedbackReport: runtime diagnostics, written by
|
|
621
|
+
// Logging.ts and surfaced via the `ugly-app errors` / `perf` / `feedback`
|
|
622
|
+
// CLI commands and Studio panels.
|
|
623
|
+
// - authMagicLink / authIdentity: local back-store for the app-issued
|
|
624
|
+
// session flow when .uglyapp `auth.mode === 'self'`. With
|
|
625
|
+
// `auth.mode === 'uglybot'` (the default) these tables stay empty.
|
|
636
626
|
export const BUILTIN_DEFS = defineCollections({
|
|
627
|
+
errorLog: {
|
|
628
|
+
schema: ErrorLogDocSchema,
|
|
629
|
+
meta: {
|
|
630
|
+
cache: false,
|
|
631
|
+
trackable: false,
|
|
632
|
+
public: false,
|
|
633
|
+
cascadeFrom: null,
|
|
634
|
+
},
|
|
635
|
+
indexes: [
|
|
636
|
+
{ fields: { 'meta.level': 1 } },
|
|
637
|
+
{ fields: { 'meta.userId': 1 } },
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
perfLog: {
|
|
641
|
+
schema: PerfLogSchema,
|
|
642
|
+
meta: {
|
|
643
|
+
cache: false,
|
|
644
|
+
trackable: false,
|
|
645
|
+
public: false,
|
|
646
|
+
cascadeFrom: null,
|
|
647
|
+
},
|
|
648
|
+
indexes: [
|
|
649
|
+
{ fields: { operation: 1 } },
|
|
650
|
+
{ fields: { userId: 1 } },
|
|
651
|
+
],
|
|
652
|
+
},
|
|
653
|
+
feedbackReport: {
|
|
654
|
+
schema: FeedbackReportDocSchema,
|
|
655
|
+
meta: {
|
|
656
|
+
cache: false,
|
|
657
|
+
trackable: false,
|
|
658
|
+
public: false,
|
|
659
|
+
cascadeFrom: null,
|
|
660
|
+
},
|
|
661
|
+
indexes: [
|
|
662
|
+
{ fields: { userId: 1 } },
|
|
663
|
+
{ fields: { status: 1 } },
|
|
664
|
+
],
|
|
665
|
+
},
|
|
637
666
|
authMagicLink: {
|
|
638
667
|
schema: z.object({
|
|
639
668
|
email: z.string(),
|
package/src/vite/index.ts
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/cli/devTunnel.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* devTunnel.ts
|
|
3
|
-
*
|
|
4
|
-
* Combined dev tunnel client: multiplexes infrastructure TCP ports (Postgres,
|
|
5
|
-
* NATS, S3, Qdrant) over binary frames AND handles proxy tunnel HTTP/WS relay
|
|
6
|
-
* for public dev URLs — all on a single WebSocket to /dev-tunnel.
|
|
7
|
-
*
|
|
8
|
-
* Binary frames = infra tunnel (5-byte header + TCP payload)
|
|
9
|
-
* Text frames = proxy tunnel (JSON messages for HTTP/WS relay)
|
|
10
|
-
*/
|
|
11
|
-
import type { SidecarPorts } from './sidecarHealth.js';
|
|
12
|
-
export interface StartDevTunnelOpts {
|
|
13
|
-
projectId: string;
|
|
14
|
-
token: string;
|
|
15
|
-
/** Defaults to UGLY_BOT_WS_URL */
|
|
16
|
-
serverUrl?: string;
|
|
17
|
-
/** When set, enables proxy tunnel relay to this local port */
|
|
18
|
-
vitePort?: number;
|
|
19
|
-
/**
|
|
20
|
-
* Optional studio session id (`ws_x:sess_y`). When set, the server
|
|
21
|
-
* scopes the proxy subdomain to this session so two concurrent dev
|
|
22
|
-
* servers on the same project don't collide on the same registration.
|
|
23
|
-
*/
|
|
24
|
-
sessionId?: string;
|
|
25
|
-
}
|
|
26
|
-
export interface DevTunnelResult extends SidecarPorts {
|
|
27
|
-
/** Public subdomain if proxy tunnel registered (e.g. 'proxy-user-myapp') */
|
|
28
|
-
subdomain?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Start the dev tunnel: opens WSS connection and binds ephemeral TCP ports.
|
|
32
|
-
* If vitePort is provided, also registers a proxy tunnel for public URL access.
|
|
33
|
-
*/
|
|
34
|
-
export declare function startDevTunnel(opts: StartDevTunnelOpts): Promise<DevTunnelResult>;
|
|
35
|
-
/** Stop the dev tunnel: close WS, all TCP servers and sockets. */
|
|
36
|
-
export declare function stopDevTunnel(): void;
|
|
37
|
-
//# sourceMappingURL=devTunnel.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devTunnel.d.ts","sourceRoot":"","sources":["../../src/cli/devTunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUvD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8BD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CA0DvF;AAwaD,kEAAkE;AAClE,wBAAgB,aAAa,IAAI,IAAI,CA6BpC"}
|