@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.
- package/dist/chunk-4PYVIIOO.js +25 -0
- package/dist/{chunk-GD37XVES.js → chunk-5R3I5RLT.js} +6 -8
- package/dist/chunk-6GUOGDWO.js +172 -0
- package/dist/chunk-DEI3FBNY.js +65 -0
- package/dist/{chunk-T2GDBQJ7.js → chunk-DOFLZ7KG.js} +5 -6
- package/dist/{chunk-GJIX7MWC.js → chunk-ECPYHIAW.js} +9 -1
- package/dist/{chunk-TUZNTXZR.js → chunk-R7VA3Z5N.js} +8 -2
- package/dist/{chunk-ZPXMPLQU.js → chunk-VF3EXXEM.js} +130 -7
- package/dist/chunk-XZCXWILA.js +145 -0
- package/dist/{upload-DwCGjXbz.d.ts → email-BRK4WAO2.d.ts} +9 -1
- package/dist/endpoints/auth-callback.d.ts +1 -0
- package/dist/endpoints/auth-callback.js +9 -0
- package/dist/endpoints/auth-logout.d.ts +1 -0
- package/dist/endpoints/auth-logout.js +9 -0
- package/dist/endpoints/auth-request.d.ts +18 -0
- package/dist/endpoints/auth-request.js +9 -0
- package/dist/endpoints/auth-session.d.ts +1 -0
- package/dist/endpoints/auth-session.js +9 -0
- package/dist/endpoints/auth.js +10 -2
- package/dist/endpoints/content.js +12 -12
- package/dist/endpoints/lists.js +4 -1
- package/dist/endpoints/publish.js +8 -6
- package/dist/endpoints/schemas.js +5 -2
- package/dist/endpoints/upload.js +7 -6
- package/dist/{git-backed-DtiH52EI.d.ts → git-backed-CwTFfUS4.d.ts} +2 -2
- package/dist/{github-client-Db0pIrqE.d.ts → github-client-Db5EPcSL.d.ts} +1 -1
- package/dist/index.d.ts +32 -6
- package/dist/index.js +117 -27
- package/dist/loader/index.d.ts +12 -2
- package/dist/loader/index.js +1 -1
- package/dist/runtime.d.ts +69 -3
- package/dist/runtime.js +3 -1
- package/dist/storage/index.d.ts +4 -4
- package/dist/{types-BMlLS-OS.d.ts → types-CKzfz5iu.d.ts} +1 -1
- package/package.json +1 -1
|
@@ -10,4 +10,12 @@ declare function makeLocalUploadHandler(opts: {
|
|
|
10
10
|
maxMB?: number;
|
|
11
11
|
}): UploadHandler;
|
|
12
12
|
|
|
13
|
-
|
|
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 @@
|
|
|
1
|
+
export { l as POST } from './auth-request.js';
|
|
@@ -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 @@
|
|
|
1
|
+
export { s as GET } from './auth-request.js';
|
package/dist/endpoints/auth.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createRateLimiter,
|
|
3
3
|
runAuth
|
|
4
|
-
} from "../chunk-
|
|
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
|
-
|
|
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)
|
package/dist/endpoints/lists.js
CHANGED
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
} from "../chunk-VGRG5DN7.js";
|
|
5
5
|
import {
|
|
6
6
|
makeListsRoutes
|
|
7
|
-
} from "../chunk-
|
|
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
|
-
|
|
20
|
-
|
|
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-
|
|
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
|
}
|
package/dist/endpoints/upload.js
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
|
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 {
|
|
3
|
-
export {
|
|
4
|
-
import { U as UploadHandler } from './
|
|
5
|
-
export { m as makeLocalUploadHandler } from './
|
|
6
|
-
import {
|
|
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-
|
|
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-
|
|
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-
|
|
17
|
+
} from "./chunk-ECPYHIAW.js";
|
|
11
18
|
import {
|
|
12
19
|
makeListsRoutes
|
|
13
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
|
631
|
-
const
|
|
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 =
|
|
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,
|
package/dist/loader/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Loader } from 'astro/loaders';
|
|
2
|
-
import {
|
|
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
|
|
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
|