ugly-app 0.1.519 → 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.
Files changed (110) hide show
  1. package/.pgdata/base/16384/pg_internal.init +0 -0
  2. package/.pgdata/global/pg_control +0 -0
  3. package/.pgdata/global/pg_internal.init +0 -0
  4. package/.pgdata/pg_wal/000000010000000000000004 +0 -0
  5. package/.pgdata/postmaster.pid +2 -2
  6. package/dist/cli/dev.d.ts.map +1 -1
  7. package/dist/cli/dev.js +0 -3
  8. package/dist/cli/dev.js.map +1 -1
  9. package/dist/cli/doctor.d.ts.map +1 -1
  10. package/dist/cli/doctor.js +31 -66
  11. package/dist/cli/doctor.js.map +1 -1
  12. package/dist/cli/index.js +8 -66
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/migrate.d.ts.map +1 -1
  15. package/dist/cli/migrate.js +0 -4
  16. package/dist/cli/migrate.js.map +1 -1
  17. package/dist/cli/schemaGen.d.ts.map +1 -1
  18. package/dist/cli/schemaGen.js +0 -4
  19. package/dist/cli/schemaGen.js.map +1 -1
  20. package/dist/cli/schemaStatus.d.ts.map +1 -1
  21. package/dist/cli/schemaStatus.js +0 -4
  22. package/dist/cli/schemaStatus.js.map +1 -1
  23. package/dist/cli/serverLogQueryApi.d.ts +5 -4
  24. package/dist/cli/serverLogQueryApi.d.ts.map +1 -1
  25. package/dist/cli/serverLogQueryApi.js +55 -115
  26. package/dist/cli/serverLogQueryApi.js.map +1 -1
  27. package/dist/cli/version.d.ts +1 -1
  28. package/dist/cli/version.js +1 -1
  29. package/dist/client/auth/MagicLinkCallback.d.ts.map +1 -1
  30. package/dist/client/auth/MagicLinkCallback.js +8 -2
  31. package/dist/client/auth/MagicLinkCallback.js.map +1 -1
  32. package/dist/client/auth/MagicLinkForm.d.ts.map +1 -1
  33. package/dist/client/auth/MagicLinkForm.js +6 -1
  34. package/dist/client/auth/MagicLinkForm.js.map +1 -1
  35. package/dist/server/App.d.ts.map +1 -1
  36. package/dist/server/App.js +1 -2
  37. package/dist/server/App.js.map +1 -1
  38. package/dist/server/Logging.d.ts +5 -8
  39. package/dist/server/Logging.d.ts.map +1 -1
  40. package/dist/server/Logging.js +21 -97
  41. package/dist/server/Logging.js.map +1 -1
  42. package/dist/server/auth/magicLinkProvider.d.ts.map +1 -1
  43. package/dist/server/auth/magicLinkProvider.js +55 -6
  44. package/dist/server/auth/magicLinkProvider.js.map +1 -1
  45. package/dist/server/auth/workersMagicLink.d.ts.map +1 -1
  46. package/dist/server/auth/workersMagicLink.js +78 -6
  47. package/dist/server/auth/workersMagicLink.js.map +1 -1
  48. package/dist/server/bootstrap.d.ts +5 -21
  49. package/dist/server/bootstrap.d.ts.map +1 -1
  50. package/dist/server/bootstrap.js +5 -27
  51. package/dist/server/bootstrap.js.map +1 -1
  52. package/dist/shared/DB.d.ts +0 -9
  53. package/dist/shared/DB.d.ts.map +1 -1
  54. package/dist/shared/DB.js +46 -10
  55. package/dist/shared/DB.js.map +1 -1
  56. package/dist/vite/index.d.ts +0 -1
  57. package/dist/vite/index.d.ts.map +1 -1
  58. package/dist/vite/index.js +0 -1
  59. package/dist/vite/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/cli/dev.ts +0 -4
  62. package/src/cli/doctor.ts +31 -63
  63. package/src/cli/index.ts +9 -81
  64. package/src/cli/migrate.ts +0 -4
  65. package/src/cli/schemaGen.ts +0 -4
  66. package/src/cli/schemaStatus.ts +0 -4
  67. package/src/cli/serverLogQueryApi.ts +53 -136
  68. package/src/cli/version.ts +1 -1
  69. package/src/client/auth/MagicLinkCallback.tsx +10 -2
  70. package/src/client/auth/MagicLinkForm.tsx +7 -1
  71. package/src/server/App.ts +1 -2
  72. package/src/server/Logging.ts +21 -97
  73. package/src/server/auth/magicLinkProvider.ts +61 -6
  74. package/src/server/auth/workersMagicLink.ts +75 -6
  75. package/src/server/bootstrap.ts +5 -29
  76. package/src/shared/DB.ts +46 -17
  77. package/src/vite/index.ts +0 -1
  78. package/.pgdata/base/1/pg_internal.init +0 -0
  79. package/.pgdata/base/17702/pg_internal.init +0 -0
  80. package/.pgdata/base/5/pg_internal.init +0 -0
  81. package/dist/cli/devTunnel.d.ts +0 -37
  82. package/dist/cli/devTunnel.d.ts.map +0 -1
  83. package/dist/cli/devTunnel.js +0 -470
  84. package/dist/cli/devTunnel.js.map +0 -1
  85. package/dist/cli/ensureInfra.d.ts +0 -9
  86. package/dist/cli/ensureInfra.d.ts.map +0 -1
  87. package/dist/cli/ensureInfra.js +0 -86
  88. package/dist/cli/ensureInfra.js.map +0 -1
  89. package/dist/cli/sidecar.d.ts +0 -21
  90. package/dist/cli/sidecar.d.ts.map +0 -1
  91. package/dist/cli/sidecar.js +0 -294
  92. package/dist/cli/sidecar.js.map +0 -1
  93. package/dist/cli/sidecarHealth.d.ts +0 -13
  94. package/dist/cli/sidecarHealth.d.ts.map +0 -1
  95. package/dist/cli/sidecarHealth.js +0 -42
  96. package/dist/cli/sidecarHealth.js.map +0 -1
  97. package/dist/cli/sidecarProtocol.d.ts +0 -41
  98. package/dist/cli/sidecarProtocol.d.ts.map +0 -1
  99. package/dist/cli/sidecarProtocol.js +0 -61
  100. package/dist/cli/sidecarProtocol.js.map +0 -1
  101. package/dist/vite/tunnelPlugin.d.ts +0 -10
  102. package/dist/vite/tunnelPlugin.d.ts.map +0 -1
  103. package/dist/vite/tunnelPlugin.js +0 -30
  104. package/dist/vite/tunnelPlugin.js.map +0 -1
  105. package/src/cli/devTunnel.ts +0 -589
  106. package/src/cli/ensureInfra.ts +0 -90
  107. package/src/cli/sidecar.ts +0 -350
  108. package/src/cli/sidecarHealth.ts +0 -58
  109. package/src/cli/sidecarProtocol.ts +0 -68
  110. package/src/vite/tunnelPlugin.ts +0 -32
@@ -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,7 +263,22 @@ export function registerWorkersMagicLinkRoutes(
223
263
  });
224
264
 
225
265
  const base = getAppPublicUrl(c, config.publicAppUrl);
226
- const url = `${base}/auth/magic-link/verify?id=${encodeURIComponent(tokenId)}&sig=${encodeURIComponent(sig)}`;
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}`;
227
282
 
228
283
  const debugMode = process.env['UGLY_APP_MAGIC_LINK_DEBUG'] === '1';
229
284
  // Debug mode: log the link to the worker console so it's grep-able
@@ -281,19 +336,31 @@ export function registerWorkersMagicLinkRoutes(
281
336
  }
282
337
  const id = c.req.query('id') ?? '';
283
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);
284
344
  const idJson = JSON.stringify(id);
285
345
  const sigJson = JSON.stringify(sig);
346
+ const returnToJson = JSON.stringify(safeReturnTo);
286
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>
287
348
  (async function(){
349
+ var returnTo = ${returnToJson};
288
350
  try {
289
- const r = await fetch('/auth/magic-link/verify', {
351
+ var r = await fetch('/auth/magic-link/verify', {
290
352
  method: 'POST',
291
353
  headers: {'Content-Type': 'application/json'},
292
354
  credentials: 'same-origin',
293
- body: JSON.stringify({ id: ${idJson}, sig: ${sigJson} }),
355
+ body: JSON.stringify({ id: ${idJson}, sig: ${sigJson}, returnTo: returnTo }),
294
356
  });
295
- if (r.ok) { window.location.replace('/'); }
296
- else { document.body.innerText = 'This sign-in link is invalid or has expired.'; }
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
+ }
297
364
  } catch (e) { document.body.innerText = 'Sign-in failed. Please try again.'; }
298
365
  })();
299
366
  </script></body></html>`;
@@ -344,7 +411,9 @@ export function registerWorkersMagicLinkRoutes(
344
411
 
345
412
  const token = await mintSessionJwt(userId, secret);
346
413
  c.header('Set-Cookie', buildAuthCookie(token));
347
- return c.json({ ok: true, token });
414
+ const host = c.req.header('host') ?? '';
415
+ const safeReturnTo = validateReturnTo(parsed.data.returnTo, host);
416
+ return c.json({ ok: true, token, returnTo: safeReturnTo });
348
417
  } catch (err) {
349
418
  // eslint-disable-next-line no-console
350
419
  console.error('[Auth] magic-link verify failed', err);
@@ -1,39 +1,15 @@
1
1
  /**
2
- * bootstrapInfra — the supported way to wire up the Postgres + Storage
3
- * connections from any script that imports `ugly-app/server`. Wraps
4
- * the two-step dance every seed / migration / import script needs:
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
- * 1. `ensureInfra()` reads `.uglyapp`, logs in if needed, sets
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
- // All logging collections (errorLog, perfLog, eventLog, eventCounter) are
631
- // proxied to ugly.bot no local tables needed.
632
- //
633
- // Auth collections (authMagicLink, authIdentity) are local: they back the
634
- // app-issued session flow when .uglyapp `auth.mode === 'self'`. With
635
- // `auth.mode === 'uglybot'` (the default) these tables stay empty.
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
@@ -1,2 +1 @@
1
1
  export { dataSourcePlugin } from './dataSourcePlugin.js';
2
- export { tunnelPlugin } from './tunnelPlugin.js';
Binary file
Binary file
Binary file
@@ -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"}