@cancia/astro 0.11.0 → 0.12.0

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 (35) hide show
  1. package/dist/chunk-4PYVIIOO.js +25 -0
  2. package/dist/{chunk-GD37XVES.js → chunk-5R3I5RLT.js} +6 -8
  3. package/dist/chunk-6GUOGDWO.js +172 -0
  4. package/dist/chunk-DEI3FBNY.js +65 -0
  5. package/dist/{chunk-T2GDBQJ7.js → chunk-DOFLZ7KG.js} +5 -6
  6. package/dist/{chunk-GJIX7MWC.js → chunk-ECPYHIAW.js} +9 -1
  7. package/dist/{chunk-TUZNTXZR.js → chunk-R7VA3Z5N.js} +8 -2
  8. package/dist/{chunk-ZPXMPLQU.js → chunk-VF3EXXEM.js} +130 -7
  9. package/dist/chunk-XZCXWILA.js +145 -0
  10. package/dist/{upload-DwCGjXbz.d.ts → email-BRK4WAO2.d.ts} +9 -1
  11. package/dist/endpoints/auth-callback.d.ts +1 -0
  12. package/dist/endpoints/auth-callback.js +9 -0
  13. package/dist/endpoints/auth-logout.d.ts +1 -0
  14. package/dist/endpoints/auth-logout.js +9 -0
  15. package/dist/endpoints/auth-request.d.ts +18 -0
  16. package/dist/endpoints/auth-request.js +9 -0
  17. package/dist/endpoints/auth-session.d.ts +1 -0
  18. package/dist/endpoints/auth-session.js +9 -0
  19. package/dist/endpoints/auth.js +10 -2
  20. package/dist/endpoints/content.js +12 -12
  21. package/dist/endpoints/lists.js +4 -1
  22. package/dist/endpoints/publish.js +8 -6
  23. package/dist/endpoints/schemas.js +5 -2
  24. package/dist/endpoints/upload.js +7 -6
  25. package/dist/{git-backed-DtiH52EI.d.ts → git-backed-CwTFfUS4.d.ts} +2 -2
  26. package/dist/{github-client-Db0pIrqE.d.ts → github-client-Db5EPcSL.d.ts} +1 -1
  27. package/dist/index.d.ts +32 -6
  28. package/dist/index.js +117 -27
  29. package/dist/loader/index.d.ts +12 -2
  30. package/dist/loader/index.js +1 -1
  31. package/dist/runtime.d.ts +69 -3
  32. package/dist/runtime.js +3 -1
  33. package/dist/storage/index.d.ts +4 -4
  34. package/dist/{types-BMlLS-OS.d.ts → types-CKzfz5iu.d.ts} +1 -1
  35. package/package.json +1 -1
@@ -10,4 +10,12 @@ declare function makeLocalUploadHandler(opts: {
10
10
  maxMB?: number;
11
11
  }): UploadHandler;
12
12
 
13
- export { type UploadHandler as U, makeLocalUploadHandler as m };
13
+ interface LoginEmailPayload {
14
+ to: string;
15
+ /** The full, ready-to-click URL. */
16
+ url: string;
17
+ site: string;
18
+ }
19
+ type SendLoginEmail = (payload: LoginEmailPayload) => Promise<void>;
20
+
21
+ export { type SendLoginEmail as S, type UploadHandler as U, makeLocalUploadHandler as m };
@@ -0,0 +1 @@
1
+ export { c as POST } from './auth-request.js';
@@ -0,0 +1,9 @@
1
+ import {
2
+ callback
3
+ } from "../chunk-DEI3FBNY.js";
4
+ import "../chunk-XZCXWILA.js";
5
+ import "../chunk-ECPYHIAW.js";
6
+ import "../chunk-6GUOGDWO.js";
7
+ export {
8
+ callback as POST
9
+ };
@@ -0,0 +1 @@
1
+ export { l as POST } from './auth-request.js';
@@ -0,0 +1,9 @@
1
+ import {
2
+ logout
3
+ } from "../chunk-DEI3FBNY.js";
4
+ import "../chunk-XZCXWILA.js";
5
+ import "../chunk-ECPYHIAW.js";
6
+ import "../chunk-6GUOGDWO.js";
7
+ export {
8
+ logout as POST
9
+ };
@@ -0,0 +1,18 @@
1
+ /** POST /api/cancia/auth/request — { email } */
2
+ declare function requestLink({ request }: {
3
+ request: Request;
4
+ }): Promise<Response>;
5
+ /** POST /api/cancia/auth/callback — { token } */
6
+ declare function callback({ request }: {
7
+ request: Request;
8
+ }): Promise<Response>;
9
+ /** POST /api/cancia/auth/logout */
10
+ declare function logout({ request }: {
11
+ request: Request;
12
+ }): Promise<Response>;
13
+ /** GET /api/cancia/auth/session — "am I signed in?" */
14
+ declare function session({ request }: {
15
+ request: Request;
16
+ }): Promise<Response>;
17
+
18
+ export { requestLink as POST, callback as c, logout as l, session as s };
@@ -0,0 +1,9 @@
1
+ import {
2
+ requestLink
3
+ } from "../chunk-DEI3FBNY.js";
4
+ import "../chunk-XZCXWILA.js";
5
+ import "../chunk-ECPYHIAW.js";
6
+ import "../chunk-6GUOGDWO.js";
7
+ export {
8
+ requestLink as POST
9
+ };
@@ -0,0 +1 @@
1
+ export { s as GET } from './auth-request.js';
@@ -0,0 +1,9 @@
1
+ import {
2
+ session
3
+ } from "../chunk-DEI3FBNY.js";
4
+ import "../chunk-XZCXWILA.js";
5
+ import "../chunk-ECPYHIAW.js";
6
+ import "../chunk-6GUOGDWO.js";
7
+ export {
8
+ session as GET
9
+ };
@@ -1,13 +1,21 @@
1
1
  import {
2
2
  createRateLimiter,
3
3
  runAuth
4
- } from "../chunk-GJIX7MWC.js";
4
+ } from "../chunk-ECPYHIAW.js";
5
5
 
6
6
  // src/endpoints/auth.ts
7
7
  import { getCanciaRuntime } from "virtual:cancia/runtime";
8
8
  var limiter = createRateLimiter();
9
9
  async function POST({ request }) {
10
- const { secret } = getCanciaRuntime();
10
+ const { secret, auth } = getCanciaRuntime();
11
+ if (auth) {
12
+ return new Response(
13
+ JSON.stringify({
14
+ error: "This site uses email login. Open the site and request a link instead."
15
+ }),
16
+ { status: 403, headers: { "Content-Type": "application/json" } }
17
+ );
18
+ }
11
19
  return runAuth({ request, secret, limiter });
12
20
  }
13
21
  export {
@@ -2,20 +2,20 @@ import {
2
2
  extractRoute,
3
3
  invalidateOnSave
4
4
  } from "../chunk-VGRG5DN7.js";
5
+ import {
6
+ guardRequest
7
+ } from "../chunk-4PYVIIOO.js";
8
+ import "../chunk-6GUOGDWO.js";
5
9
  import "../chunk-X6ZFFGIA.js";
6
10
 
7
11
  // src/endpoints/content.ts
8
12
  import { getCanciaRuntime } from "virtual:cancia/runtime";
9
- function checkAuth(req, secret) {
10
- if (!secret) return null;
11
- const token = req.headers.get("Authorization")?.replace("Bearer ", "").trim();
12
- if (token !== secret)
13
- return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
14
- return null;
13
+ async function checkAuth(req, secret, auth) {
14
+ return (await guardRequest(req, { secret, auth })).deny;
15
15
  }
16
16
  async function GET({ request }) {
17
- const { storage, secret } = getCanciaRuntime();
18
- const deny = checkAuth(request, secret);
17
+ const { storage, secret, auth } = getCanciaRuntime();
18
+ const deny = await checkAuth(request, secret, auth);
19
19
  if (deny) return deny;
20
20
  const site = new URL(request.url).searchParams.get("site");
21
21
  if (!site)
@@ -27,8 +27,8 @@ async function GET({ request }) {
27
27
  }
28
28
  async function POST(context) {
29
29
  const { request } = context;
30
- const { storage, secret, publishMode } = getCanciaRuntime();
31
- const deny = checkAuth(request, secret);
30
+ const { storage, secret, publishMode, auth } = getCanciaRuntime();
31
+ const deny = await checkAuth(request, secret, auth);
32
32
  if (deny) return deny;
33
33
  const body = await request.json().catch(() => null);
34
34
  if (!body?.site || !body?.key || !body?.lang || body?.value === void 0)
@@ -43,8 +43,8 @@ async function POST(context) {
43
43
  });
44
44
  }
45
45
  async function DELETE({ request }) {
46
- const { storage, secret } = getCanciaRuntime();
47
- const deny = checkAuth(request, secret);
46
+ const { storage, secret, auth } = getCanciaRuntime();
47
+ const deny = await checkAuth(request, secret, auth);
48
48
  if (deny) return deny;
49
49
  const body = await request.json().catch(() => null);
50
50
  if (!body?.site || !body?.key || !body?.lang)
@@ -4,7 +4,9 @@ import {
4
4
  } from "../chunk-VGRG5DN7.js";
5
5
  import {
6
6
  makeListsRoutes
7
- } from "../chunk-GD37XVES.js";
7
+ } from "../chunk-5R3I5RLT.js";
8
+ import "../chunk-4PYVIIOO.js";
9
+ import "../chunk-6GUOGDWO.js";
8
10
  import "../chunk-X6ZFFGIA.js";
9
11
  import "../chunk-VL6FO446.js";
10
12
  import "../chunk-7IA5B5CF.js";
@@ -18,6 +20,7 @@ function getRoutes() {
18
20
  projectRoot: rt.projectRoot,
19
21
  schemasPath: rt.schemasPath,
20
22
  secret: rt.secret,
23
+ auth: rt.auth,
21
24
  defaultLocale: rt.defaultLocale
22
25
  });
23
26
  }
@@ -1,6 +1,10 @@
1
1
  import {
2
2
  runPublish
3
3
  } from "../chunk-5RCLBWRH.js";
4
+ import {
5
+ guardRequest
6
+ } from "../chunk-4PYVIIOO.js";
7
+ import "../chunk-6GUOGDWO.js";
4
8
  import "../chunk-U7V53JX7.js";
5
9
 
6
10
  // src/endpoints/publish.ts
@@ -14,13 +18,11 @@ async function POST({ request }) {
14
18
  publishRepo,
15
19
  publishGithubToken,
16
20
  publishMode,
17
- secret
21
+ secret,
22
+ auth
18
23
  } = getCanciaRuntime();
19
- if (secret) {
20
- const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
21
- if (token !== secret)
22
- return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
23
- }
24
+ const deny = (await guardRequest(request, { secret, auth })).deny;
25
+ if (deny) return deny;
24
26
  const hook = deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
25
27
  const ghToken = publishGithubToken ?? process.env.CANCIA_GITHUB_TOKEN?.trim();
26
28
  return runPublish({
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  makeSchemasRoute
3
- } from "../chunk-T2GDBQJ7.js";
3
+ } from "../chunk-DOFLZ7KG.js";
4
+ import "../chunk-4PYVIIOO.js";
5
+ import "../chunk-6GUOGDWO.js";
4
6
  import "../chunk-VL6FO446.js";
5
7
  import "../chunk-QQEUCOXQ.js";
6
8
  import "../chunk-KHCFVVYV.js";
@@ -12,7 +14,8 @@ async function GET({ request }) {
12
14
  const route = makeSchemasRoute({
13
15
  projectRoot: rt.projectRoot,
14
16
  schemasPath: rt.schemasPath,
15
- secret: rt.secret
17
+ secret: rt.secret,
18
+ auth: rt.auth
16
19
  });
17
20
  return route(request);
18
21
  }
@@ -2,16 +2,17 @@ import {
2
2
  detectUploadType,
3
3
  isValidSite
4
4
  } from "../chunk-RHFTQW4W.js";
5
+ import {
6
+ guardRequest
7
+ } from "../chunk-4PYVIIOO.js";
8
+ import "../chunk-6GUOGDWO.js";
5
9
 
6
10
  // src/endpoints/upload.ts
7
11
  import { getCanciaRuntime } from "virtual:cancia/runtime";
8
12
  async function POST({ request }) {
9
- const { uploadHandler, secret, maxUploadMB } = getCanciaRuntime();
10
- if (secret) {
11
- const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
12
- if (token !== secret)
13
- return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
14
- }
13
+ const { uploadHandler, secret, maxUploadMB, auth } = getCanciaRuntime();
14
+ const deny = (await guardRequest(request, { secret, auth })).deny;
15
+ if (deny) return deny;
15
16
  const form = await request.formData().catch(() => null);
16
17
  const file = form?.get("file");
17
18
  const site = form?.get("site");
@@ -1,5 +1,5 @@
1
- import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
2
- import { G as GitHubCommitter, a as GitHubClient, F as FetchLike } from './github-client-Db0pIrqE.js';
1
+ import { d as CanciaStorage, C as CanciaStorageV2 } from './types-CKzfz5iu.js';
2
+ import { b as GitHubCommitter, G as GitHubClient, F as FetchLike } from './github-client-Db5EPcSL.js';
3
3
 
4
4
  declare function createJsonFileAdapter(filePath?: string): CanciaStorage;
5
5
 
@@ -54,4 +54,4 @@ interface GitHubClient {
54
54
  }
55
55
  declare function createGitHubClient(opts: GitHubClientOptions): GitHubClient;
56
56
 
57
- export { type CommitFile as C, type FetchLike as F, type GitHubCommitter as G, type GitHubClient as a, type GitHubClientOptions as b, createGitHubClient as c };
57
+ export { type CommitFile as C, type FetchLike as F, type GitHubClient as G, type GitHubClientOptions as a, type GitHubCommitter as b, createGitHubClient as c };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { AstroIntegration } from 'astro';
2
- import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
3
- export { b as CanciaKVStore, c as CanciaListStore, d as CanciaPageStore, L as ListEntry, P as PageMeta, e as PageRecord, f as PageSEO, R as Rev, g as RevConflictError } from './types-BMlLS-OS.js';
4
- import { U as UploadHandler } from './upload-DwCGjXbz.js';
5
- export { m as makeLocalUploadHandler } from './upload-DwCGjXbz.js';
6
- import { G as GitHubCommitter } from './github-client-Db0pIrqE.js';
2
+ import { d as CanciaStorage, C as CanciaStorageV2 } from './types-CKzfz5iu.js';
3
+ export { a as CanciaKVStore, b as CanciaListStore, c as CanciaPageStore, L as ListEntry, P as PageMeta, e as PageRecord, f as PageSEO, R as Rev, g as RevConflictError } from './types-CKzfz5iu.js';
4
+ import { U as UploadHandler, S as SendLoginEmail } from './email-BRK4WAO2.js';
5
+ export { m as makeLocalUploadHandler } from './email-BRK4WAO2.js';
6
+ import { b as GitHubCommitter } from './github-client-Db5EPcSL.js';
7
7
  export { CanciaLoaderOptions, canciaLoader } from './loader/index.js';
8
8
  export { FieldDescription, FieldMeta, FieldMetaBase, FieldWidget, ListDescription, ListSchema, SchemasModule, defineField, defineList, describeList } from './schema/index.js';
9
- export { G as GitBackedContentPaths, a as GitBackedControls, b as GitBackedOptions, c as GitBackedStorage, S as SqliteV2Options, d as closeSqliteAdapterV2, e as createGitBackedAdapter, f as createJsonFileAdapter, g as createJsonFileAdapterV2, h as createSQLiteAdapter, i as createSqliteAdapterV2 } from './git-backed-DtiH52EI.js';
9
+ export { G as GitBackedContentPaths, a as GitBackedControls, b as GitBackedOptions, c as GitBackedStorage, S as SqliteV2Options, d as closeSqliteAdapterV2, e as createGitBackedAdapter, f as createJsonFileAdapter, g as createJsonFileAdapterV2, h as createSQLiteAdapter, i as createSqliteAdapterV2 } from './git-backed-CwTFfUS4.js';
10
10
  export { z } from 'zod';
11
11
  import 'astro/loaders';
12
12
  import './portable-text-D74aIuHn.js';
@@ -139,6 +139,32 @@ interface CanciaIntegrationOptions {
139
139
  * Use for public demos only — anyone can edit if this is enabled.
140
140
  */
141
141
  public?: boolean;
142
+ /**
143
+ * Email addresses allowed to sign in (042).
144
+ *
145
+ * Setting this switches the site to magic-link login: the client asks for a
146
+ * link, proves they own the address, and gets a session cookie. It also
147
+ * TURNS OFF the legacy `?cancia=<token>` URL login for humans — that token
148
+ * is a permanent password in a URL, and once a real login exists there is no
149
+ * reason to keep accepting it from a person. `CANCIA_TOKEN` stays valid as a
150
+ * machine credential for the build-time content fetch.
151
+ *
152
+ * Leaving it unset preserves the pre-042 behaviour exactly, so upgrading the
153
+ * package never changes how an existing deployment authenticates.
154
+ *
155
+ * cancia({ editors: ["client@theirsite.com"] })
156
+ *
157
+ * Adding someone is a config change plus a redeploy — the right friction for
158
+ * granting write access to a live site.
159
+ */
160
+ editors?: string[];
161
+ /**
162
+ * Send the login email. Defaults to Loops when LOOPS_API_KEY and
163
+ * CANCIA_LOGIN_TID are set, and otherwise logs the link to the server
164
+ * console (which is also the dev behaviour — no email vendor is needed to
165
+ * work on the CMS locally).
166
+ */
167
+ sendLoginEmail?: SendLoginEmail;
142
168
  /**
143
169
  * v2 storage adapter (KV + pages + lists). When omitted, Cancia builds a
144
170
  * JSON-file v2 adapter automatically at <projectRoot>/.cancia/. List/page
package/dist/index.js CHANGED
@@ -1,23 +1,37 @@
1
1
  import {
2
2
  makeSchemasRoute
3
- } from "./chunk-T2GDBQJ7.js";
3
+ } from "./chunk-DOFLZ7KG.js";
4
+ import {
5
+ handleLoginCallback,
6
+ handleLoginRequest,
7
+ handleLogout,
8
+ handleSessionCheck,
9
+ resolveLoginEmailSender
10
+ } from "./chunk-XZCXWILA.js";
4
11
  import {
5
12
  runPublish
6
13
  } from "./chunk-5RCLBWRH.js";
7
14
  import {
8
15
  createRateLimiter,
9
16
  runAuth
10
- } from "./chunk-GJIX7MWC.js";
17
+ } from "./chunk-ECPYHIAW.js";
11
18
  import {
12
19
  makeListsRoutes
13
- } from "./chunk-GD37XVES.js";
20
+ } from "./chunk-5R3I5RLT.js";
14
21
  import {
22
+ createSqliteAuthStore,
15
23
  makeLocalUploadHandler,
16
24
  makeR2UploadHandler,
17
25
  makeUploadRoute,
18
26
  setCanciaRuntime
19
- } from "./chunk-ZPXMPLQU.js";
27
+ } from "./chunk-VF3EXXEM.js";
20
28
  import "./chunk-RHFTQW4W.js";
29
+ import {
30
+ guardRequest
31
+ } from "./chunk-4PYVIIOO.js";
32
+ import {
33
+ authorizeRequest
34
+ } from "./chunk-6GUOGDWO.js";
21
35
  import {
22
36
  createSQLiteAdapter
23
37
  } from "./chunk-CJDIVWO3.js";
@@ -28,7 +42,7 @@ import "./chunk-U7V53JX7.js";
28
42
  import "./chunk-VL6FO446.js";
29
43
  import {
30
44
  canciaLoader
31
- } from "./chunk-TUZNTXZR.js";
45
+ } from "./chunk-R7VA3Z5N.js";
32
46
  import {
33
47
  closeSqliteAdapterV2,
34
48
  createJsonFileAdapter,
@@ -53,16 +67,13 @@ import { readFile } from "fs/promises";
53
67
  import { isAbsolute, join as join2 } from "path";
54
68
 
55
69
  // src/routes/content.ts
56
- function makeContentRoute(storage, secret) {
57
- function checkAuth(req) {
58
- if (!secret) return null;
59
- const token = req.headers.get("Authorization")?.replace("Bearer ", "").trim();
60
- if (token !== secret) return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
61
- return null;
70
+ function makeContentRoute(storage, secret, auth) {
71
+ async function checkAuth(req) {
72
+ return (await guardRequest(req, { secret, auth })).deny;
62
73
  }
63
74
  return {
64
75
  GET: async ({ request }) => {
65
- const deny = checkAuth(request);
76
+ const deny = await checkAuth(request);
66
77
  if (deny) return deny;
67
78
  const site = new URL(request.url).searchParams.get("site");
68
79
  if (!site) return new Response(JSON.stringify({ error: "Missing ?site=" }), { status: 400 });
@@ -72,7 +83,7 @@ function makeContentRoute(storage, secret) {
72
83
  });
73
84
  },
74
85
  POST: async ({ request }) => {
75
- const deny = checkAuth(request);
86
+ const deny = await checkAuth(request);
76
87
  if (deny) return deny;
77
88
  const body = await request.json().catch(() => null);
78
89
  if (!body?.site || !body?.key || !body?.lang || body?.value === void 0) {
@@ -84,7 +95,7 @@ function makeContentRoute(storage, secret) {
84
95
  });
85
96
  },
86
97
  DELETE: async ({ request }) => {
87
- const deny = checkAuth(request);
98
+ const deny = await checkAuth(request);
88
99
  if (deny) return deny;
89
100
  const body = await request.json().catch(() => null);
90
101
  if (!body?.site || !body?.key || !body?.lang) {
@@ -99,14 +110,10 @@ function makeContentRoute(storage, secret) {
99
110
  }
100
111
 
101
112
  // src/routes/publish.ts
102
- function makePublishRoute(deployHook, secret, hookOptions, dispatch, mode) {
113
+ function makePublishRoute(deployHook, secret, hookOptions, dispatch, mode, auth) {
103
114
  return async ({ request }) => {
104
- if (secret) {
105
- const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
106
- if (token !== secret) {
107
- return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
108
- }
109
- }
115
+ const deny = (await guardRequest(request, { secret, auth })).deny;
116
+ if (deny) return deny;
110
117
  const hook = deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
111
118
  return runPublish({
112
119
  mode,
@@ -357,6 +364,34 @@ ${code}`;
357
364
  ${code.slice(at)}`;
358
365
  }
359
366
 
367
+ // src/routes/auth-magic.ts
368
+ function makeMagicAuthRoutes(ctx) {
369
+ const limiter = createRateLimiter();
370
+ const shared = { store: ctx.store, site: ctx.site, sendEmail: ctx.sendEmail, limiter };
371
+ return {
372
+ async request(req) {
373
+ return handleLoginRequest(req, shared, new URL(req.url).origin);
374
+ },
375
+ async callback(req) {
376
+ return handleLoginCallback(req, shared);
377
+ },
378
+ async logout(req) {
379
+ return handleLogout(req, shared);
380
+ },
381
+ async session(req) {
382
+ return handleSessionCheck(
383
+ req,
384
+ shared,
385
+ () => authorizeRequest({
386
+ request: req,
387
+ secret: ctx.secret,
388
+ auth: { store: ctx.store, site: ctx.site }
389
+ })
390
+ );
391
+ }
392
+ };
393
+ }
394
+
360
395
  // src/integration.ts
361
396
  function resolvePublish(opts, hasDeployHookEnv) {
362
397
  const publish = opts.publish;
@@ -386,6 +421,15 @@ function buildDefaultV2Store(opts, projectRoot) {
386
421
  }
387
422
  return createJsonFileAdapterV2({ projectRoot });
388
423
  }
424
+ function buildAuthContext(opts, projectRoot) {
425
+ const editors = opts.editors?.map((e) => e.trim().toLowerCase()).filter(Boolean) ?? [];
426
+ if (editors.length === 0) return void 0;
427
+ const dbPath = opts.db?.kind === "sqlite" && opts.db.path ? isAbsolute(opts.db.path) ? opts.db.path : join2(projectRoot, opts.db.path) : join2(projectRoot, "cancia.db");
428
+ const store = createSqliteAuthStore({ dbPath });
429
+ const site = opts.site ?? "default";
430
+ void store.seedEditors(site, editors);
431
+ return { store, site };
432
+ }
389
433
  function canciaIntegration(opts = {}) {
390
434
  let storage;
391
435
  let resolvedToken = "";
@@ -459,8 +503,12 @@ function canciaIntegration(opts = {}) {
459
503
  prefix: r2Opts.prefix
460
504
  };
461
505
  }
506
+ const bakedEditors = opts.editors?.map((e) => e.trim().toLowerCase()).filter(Boolean) ?? [];
462
507
  bakedConfig = {
463
508
  projectRoot: resolvedRootPath,
509
+ site,
510
+ editors: bakedEditors,
511
+ authDbPath: opts.db?.kind === "sqlite" && opts.db.path ? opts.db.path : "cancia.db",
464
512
  schemasPath: opts.schemasPath,
465
513
  defaultLocale: resolvedLocales[0],
466
514
  locales: resolvedLocales,
@@ -485,7 +533,12 @@ function canciaIntegration(opts = {}) {
485
533
  languages,
486
534
  page: "unknown",
487
535
  public: opts.public ?? false,
488
- canPublish
536
+ canPublish,
537
+ // Whether to offer the email login box. The ADDRESSES are never
538
+ // sent to the browser — publishing a client's staff list on every
539
+ // page would be a disclosure, and the server checks the allow-list
540
+ // anyway. The toolbar only needs to know the flow exists.
541
+ magicLink: bakedEditors.length > 0
489
542
  })};`
490
543
  );
491
544
  injectScript("page", `import "@cancia/toolbar";`);
@@ -497,6 +550,10 @@ function canciaIntegration(opts = {}) {
497
550
  injectRoute({ pattern: "/api/cancia/publish", entrypoint: endpointPath("publish"), prerender: false });
498
551
  injectRoute({ pattern: "/api/cancia/auth", entrypoint: endpointPath("auth"), prerender: false });
499
552
  injectRoute({ pattern: "/api/cancia/health", entrypoint: endpointPath("health"), prerender: false });
553
+ injectRoute({ pattern: "/api/cancia/auth/request", entrypoint: endpointPath("auth-request"), prerender: false });
554
+ injectRoute({ pattern: "/api/cancia/auth/callback", entrypoint: endpointPath("auth-callback"), prerender: false });
555
+ injectRoute({ pattern: "/api/cancia/auth/logout", entrypoint: endpointPath("auth-logout"), prerender: false });
556
+ injectRoute({ pattern: "/api/cancia/auth/session", entrypoint: endpointPath("auth-session"), prerender: false });
500
557
  injectRoute({ pattern: "/api/cancia/schemas", entrypoint: endpointPath("schemas"), prerender: false });
501
558
  injectRoute({ pattern: "/api/cancia/lists/[listName]", entrypoint: endpointPath("lists"), prerender: false });
502
559
  injectRoute({ pattern: "/api/cancia/lists/[listName]/[id]", entrypoint: endpointPath("lists"), prerender: false });
@@ -616,6 +673,9 @@ function canciaIntegration(opts = {}) {
616
673
  defaultLocale: resolvedLocales[0],
617
674
  locales: resolvedLocales,
618
675
  secret: routeSecret,
676
+ site: opts.site ?? "default",
677
+ auth: buildAuthContext(opts, resolvedRootPath),
678
+ sendLoginEmail: resolveLoginEmailSender(opts.sendLoginEmail),
619
679
  uploadHandler,
620
680
  deployHook,
621
681
  deployHookToken,
@@ -627,8 +687,9 @@ function canciaIntegration(opts = {}) {
627
687
  redeployHook: resolvedRedeployHook,
628
688
  maxUploadMB: opts.maxUploadMB ?? 10
629
689
  });
630
- const contentRoutes = makeContentRoute(storage, routeSecret);
631
- const uploadRoute = makeUploadRoute(uploadHandler, routeSecret, opts.maxUploadMB);
690
+ const devAuthCtx = buildAuthContext(opts, resolvedRootPath);
691
+ const contentRoutes = makeContentRoute(storage, routeSecret, devAuthCtx);
692
+ const uploadRoute = makeUploadRoute(uploadHandler, routeSecret, opts.maxUploadMB, devAuthCtx);
632
693
  const publishRoute = makePublishRoute(
633
694
  deployHook,
634
695
  routeSecret,
@@ -638,20 +699,33 @@ function canciaIntegration(opts = {}) {
638
699
  headers: opts.deployHookHeaders
639
700
  },
640
701
  { repo: resolvedPublishRepo, token: publishGithubToken },
641
- resolvedPublishMode
702
+ resolvedPublishMode,
703
+ devAuthCtx
642
704
  );
643
705
  const authRoute = makeAuthRoute(secret);
706
+ const magicRoutes = devAuthCtx ? makeMagicAuthRoutes({
707
+ store: devAuthCtx.store,
708
+ site: devAuthCtx.site,
709
+ sendEmail: resolveLoginEmailSender(opts.sendLoginEmail),
710
+ secret: routeSecret
711
+ }) : void 0;
712
+ const magicNotConfigured = () => new Response(
713
+ JSON.stringify({ error: "Magic-link login is not configured for this site." }),
714
+ { status: 404, headers: { "Content-Type": "application/json" } }
715
+ );
644
716
  const listsRoutes = makeListsRoutes({
645
717
  storageV2,
646
718
  projectRoot: resolvedRootPath,
647
719
  schemasPath: opts.schemasPath,
648
720
  secret: routeSecret,
721
+ auth: devAuthCtx,
649
722
  defaultLocale: resolvedLocales[0]
650
723
  });
651
724
  const schemasRoute = makeSchemasRoute({
652
725
  projectRoot: resolvedRootPath,
653
726
  schemasPath: opts.schemasPath,
654
- secret: routeSecret
727
+ secret: routeSecret,
728
+ auth: devAuthCtx
655
729
  });
656
730
  server.middlewares.use(async (req, res, next) => {
657
731
  const url = req.url ?? "";
@@ -716,8 +790,21 @@ function canciaIntegration(opts = {}) {
716
790
  response = await publishRoute(ctx);
717
791
  } else if (url === "/api/cancia/health") {
718
792
  response = new Response(JSON.stringify({ ok: true }), { status: 200 });
793
+ } else if (url === "/api/cancia/auth/request" && method === "POST") {
794
+ response = magicRoutes ? await magicRoutes.request(webReq) : magicNotConfigured();
795
+ } else if (url === "/api/cancia/auth/callback" && method === "POST") {
796
+ response = magicRoutes ? await magicRoutes.callback(webReq) : magicNotConfigured();
797
+ } else if (url === "/api/cancia/auth/logout" && method === "POST") {
798
+ response = magicRoutes ? await magicRoutes.logout(webReq) : magicNotConfigured();
799
+ } else if (url === "/api/cancia/auth/session" && method === "GET") {
800
+ response = magicRoutes ? await magicRoutes.session(webReq) : magicNotConfigured();
719
801
  } else if (url === "/api/cancia/auth" && method === "POST") {
720
- response = await authRoute(webReq);
802
+ response = magicRoutes ? new Response(
803
+ JSON.stringify({
804
+ error: "This site uses email login. Open the site and request a link instead."
805
+ }),
806
+ { status: 403, headers: { "Content-Type": "application/json" } }
807
+ ) : await authRoute(webReq);
721
808
  }
722
809
  if (!response) return next();
723
810
  res.statusCode = response.status;
@@ -782,6 +869,9 @@ function canciaIntegration(opts = {}) {
782
869
  defaultLocale: resolvedLocales[0],
783
870
  locales: resolvedLocales,
784
871
  secret: routeSecret,
872
+ site: opts.site ?? "default",
873
+ auth: buildAuthContext(opts, resolvedRootPath),
874
+ sendLoginEmail: resolveLoginEmailSender(opts.sendLoginEmail),
785
875
  uploadHandler,
786
876
  deployHook,
787
877
  deployHookToken,
@@ -1,5 +1,5 @@
1
1
  import { Loader } from 'astro/loaders';
2
- import { a as CanciaStorageV2 } from '../types-BMlLS-OS.js';
2
+ import { C as CanciaStorageV2 } from '../types-CKzfz5iu.js';
3
3
  import { ListSchema } from '../schema/index.js';
4
4
  import 'zod';
5
5
  import '../portable-text-D74aIuHn.js';
@@ -13,7 +13,17 @@ interface CanciaLoaderOptions {
13
13
  */
14
14
  site: string;
15
15
  /**
16
- * Where this site's content lives — MIRROR THE INTEGRATION'S `db` OPTION.
16
+ * Where this site's content lives.
17
+ *
18
+ * USUALLY UNNECESSARY. A `cancia.db` at the project root — where the
19
+ * integration's `db: { kind: "sqlite" }` puts it — is detected automatically,
20
+ * so a fresh project needs nothing here.
21
+ *
22
+ * Set it when the site uses a CUSTOM `db.path` (autodetection deliberately
23
+ * checks only the default location and never searches), or to force the
24
+ * JSON-file backend on a project that also has a stray .db.
25
+ *
26
+ * When set, MIRROR THE INTEGRATION'S `db` OPTION.
17
27
  *
18
28
  * cancia({ db: { kind: "sqlite" } }) // astro.config.mjs
19
29
  * canciaLoader({ …, db: { kind: "sqlite" } }) // content.config.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  canciaLoader
3
- } from "../chunk-TUZNTXZR.js";
3
+ } from "../chunk-R7VA3Z5N.js";
4
4
  import "../chunk-S5YLB6P3.js";
5
5
  import "../chunk-7IA5B5CF.js";
6
6
  import "../chunk-QQEUCOXQ.js";