@glw907/cairn-cms 0.3.1 → 0.4.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/README.md +14 -6
- package/dist/auth/admins.d.ts +33 -0
- package/dist/auth/admins.d.ts.map +1 -0
- package/dist/auth/admins.js +90 -0
- package/dist/auth/config.d.ts +2097 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/auth/config.js +78 -0
- package/dist/auth/guard.d.ts +34 -0
- package/dist/auth/guard.d.ts.map +1 -0
- package/dist/auth/guard.js +47 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/schema.d.ts +750 -0
- package/dist/auth/schema.d.ts.map +1 -0
- package/dist/auth/schema.js +93 -0
- package/dist/components/AdminLayout.svelte +6 -6
- package/dist/components/AdminLayout.svelte.d.ts +2 -2
- package/dist/components/AdminLayout.svelte.d.ts.map +1 -1
- package/dist/components/ConfirmPage.svelte +31 -0
- package/dist/components/ConfirmPage.svelte.d.ts +11 -0
- package/dist/components/ConfirmPage.svelte.d.ts.map +1 -0
- package/dist/components/LoginPage.svelte +35 -18
- package/dist/components/LoginPage.svelte.d.ts +0 -2
- package/dist/components/LoginPage.svelte.d.ts.map +1 -1
- package/dist/components/ManageAdmins.svelte +1 -1
- package/dist/components/ManageAdmins.svelte.d.ts +1 -1
- package/dist/components/ManageAdmins.svelte.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/email.d.ts.map +1 -1
- package/dist/email.js +15 -7
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/sveltekit/index.d.ts +5 -58
- package/dist/sveltekit/index.d.ts.map +1 -1
- package/dist/sveltekit/index.js +9 -153
- package/package.json +34 -4
- package/src/lib/auth/admins.ts +106 -0
- package/src/lib/auth/config.ts +108 -0
- package/src/lib/auth/guard.ts +60 -0
- package/src/lib/auth/index.ts +6 -0
- package/src/lib/auth/schema.ts +112 -0
- package/src/lib/components/AdminLayout.svelte +6 -6
- package/src/lib/components/ConfirmPage.svelte +31 -0
- package/src/lib/components/LoginPage.svelte +35 -18
- package/src/lib/components/ManageAdmins.svelte +1 -1
- package/src/lib/components/index.ts +1 -0
- package/src/lib/email.ts +14 -7
- package/src/lib/index.ts +2 -2
- package/src/lib/sveltekit/index.ts +15 -224
- package/dist/auth.d.ts +0 -25
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -132
- package/src/lib/auth.ts +0 -185
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/lib/auth/schema.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBf,CAAC;AAEH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBnB,CAAC;AAEF,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BnB,CAAC;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBxB,CAAC;AAEF,eAAO,MAAM,aAAa;;;EAGvB,CAAC;AAEJ,eAAO,MAAM,gBAAgB;;EAK1B,CAAC;AAEJ,eAAO,MAAM,gBAAgB;;EAK1B,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { relations, sql } from "drizzle-orm";
|
|
2
|
+
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
|
|
3
|
+
export const user = sqliteTable("user", {
|
|
4
|
+
id: text("id").primaryKey(),
|
|
5
|
+
name: text("name").notNull(),
|
|
6
|
+
email: text("email").notNull().unique(),
|
|
7
|
+
emailVerified: integer("email_verified", { mode: "boolean" })
|
|
8
|
+
.default(false)
|
|
9
|
+
.notNull(),
|
|
10
|
+
image: text("image"),
|
|
11
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
12
|
+
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
13
|
+
.notNull(),
|
|
14
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
15
|
+
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
16
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
17
|
+
.notNull(),
|
|
18
|
+
role: text("role"),
|
|
19
|
+
banned: integer("banned", { mode: "boolean" }).default(false),
|
|
20
|
+
banReason: text("ban_reason"),
|
|
21
|
+
banExpires: integer("ban_expires", { mode: "timestamp_ms" }),
|
|
22
|
+
});
|
|
23
|
+
export const session = sqliteTable("session", {
|
|
24
|
+
id: text("id").primaryKey(),
|
|
25
|
+
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
|
26
|
+
token: text("token").notNull().unique(),
|
|
27
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
28
|
+
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
29
|
+
.notNull(),
|
|
30
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
31
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
32
|
+
.notNull(),
|
|
33
|
+
ipAddress: text("ip_address"),
|
|
34
|
+
userAgent: text("user_agent"),
|
|
35
|
+
userId: text("user_id")
|
|
36
|
+
.notNull()
|
|
37
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
38
|
+
impersonatedBy: text("impersonated_by"),
|
|
39
|
+
}, (table) => [index("session_userId_idx").on(table.userId)]);
|
|
40
|
+
export const account = sqliteTable("account", {
|
|
41
|
+
id: text("id").primaryKey(),
|
|
42
|
+
accountId: text("account_id").notNull(),
|
|
43
|
+
providerId: text("provider_id").notNull(),
|
|
44
|
+
userId: text("user_id")
|
|
45
|
+
.notNull()
|
|
46
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
47
|
+
accessToken: text("access_token"),
|
|
48
|
+
refreshToken: text("refresh_token"),
|
|
49
|
+
idToken: text("id_token"),
|
|
50
|
+
accessTokenExpiresAt: integer("access_token_expires_at", {
|
|
51
|
+
mode: "timestamp_ms",
|
|
52
|
+
}),
|
|
53
|
+
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
|
|
54
|
+
mode: "timestamp_ms",
|
|
55
|
+
}),
|
|
56
|
+
scope: text("scope"),
|
|
57
|
+
password: text("password"),
|
|
58
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
59
|
+
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
60
|
+
.notNull(),
|
|
61
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
62
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
63
|
+
.notNull(),
|
|
64
|
+
}, (table) => [index("account_userId_idx").on(table.userId)]);
|
|
65
|
+
export const verification = sqliteTable("verification", {
|
|
66
|
+
id: text("id").primaryKey(),
|
|
67
|
+
identifier: text("identifier").notNull(),
|
|
68
|
+
value: text("value").notNull(),
|
|
69
|
+
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
|
70
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
71
|
+
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
72
|
+
.notNull(),
|
|
73
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
74
|
+
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
75
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
76
|
+
.notNull(),
|
|
77
|
+
}, (table) => [index("verification_identifier_idx").on(table.identifier)]);
|
|
78
|
+
export const userRelations = relations(user, ({ many }) => ({
|
|
79
|
+
sessions: many(session),
|
|
80
|
+
accounts: many(account),
|
|
81
|
+
}));
|
|
82
|
+
export const sessionRelations = relations(session, ({ one }) => ({
|
|
83
|
+
user: one(user, {
|
|
84
|
+
fields: [session.userId],
|
|
85
|
+
references: [user.id],
|
|
86
|
+
}),
|
|
87
|
+
}));
|
|
88
|
+
export const accountRelations = relations(account, ({ one }) => ({
|
|
89
|
+
user: one(user, {
|
|
90
|
+
fields: [account.userId],
|
|
91
|
+
references: [user.id],
|
|
92
|
+
}),
|
|
93
|
+
}));
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
// (the login page lives under this layout) it falls back to a minimal centered shell.
|
|
8
8
|
// Each site's `admin/+layout.svelte` is a one-line shim that forwards `data` + `children`.
|
|
9
9
|
import type { Snippet } from 'svelte';
|
|
10
|
-
import type {
|
|
10
|
+
import type { CairnUser } from '../auth';
|
|
11
11
|
|
|
12
12
|
let {
|
|
13
13
|
data,
|
|
14
14
|
children,
|
|
15
15
|
}: {
|
|
16
|
-
data: { siteName: string;
|
|
16
|
+
data: { siteName: string; user: CairnUser | null; pathname: string };
|
|
17
17
|
children: Snippet;
|
|
18
18
|
} = $props();
|
|
19
19
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
active: data.pathname.startsWith('/admin/admins'),
|
|
42
42
|
},
|
|
43
43
|
]);
|
|
44
|
-
const visibleNav = $derived(nav.filter((item) => !item.owner || data.
|
|
44
|
+
const visibleNav = $derived(nav.filter((item) => !item.owner || data.user?.role === 'owner'));
|
|
45
45
|
|
|
46
46
|
// Close the slide-over after a nav tap on mobile (no-op on desktop where it's pinned open).
|
|
47
47
|
function closeDrawer(): void {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
<meta name="robots" content="noindex, nofollow" />
|
|
69
69
|
</svelte:head>
|
|
70
70
|
|
|
71
|
-
{#if data.
|
|
71
|
+
{#if data.user}
|
|
72
72
|
<div class="drawer min-h-screen bg-base-200 lg:drawer-open" data-pagefind-ignore>
|
|
73
73
|
<input id="admin-drawer" type="checkbox" class="drawer-toggle" />
|
|
74
74
|
|
|
@@ -111,8 +111,8 @@
|
|
|
111
111
|
</ul>
|
|
112
112
|
|
|
113
113
|
<div class="border-t border-base-300 p-4">
|
|
114
|
-
<p class="text-sm font-medium">{data.
|
|
115
|
-
<p class="text-xs opacity-60">{data.
|
|
114
|
+
<p class="text-sm font-medium">{data.user.name}</p>
|
|
115
|
+
<p class="text-xs opacity-60">{data.user.email}</p>
|
|
116
116
|
<form method="POST" action="/admin/auth/logout" class="mt-3">
|
|
117
117
|
<button type="submit" class="btn btn-ghost btn-sm btn-block justify-start">Sign out</button>
|
|
118
118
|
</form>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type {
|
|
2
|
+
import type { CairnUser } from '../auth';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
data: {
|
|
5
5
|
siteName: string;
|
|
6
|
-
|
|
6
|
+
user: CairnUser | null;
|
|
7
7
|
pathname: string;
|
|
8
8
|
};
|
|
9
9
|
children: Snippet;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminLayout.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminLayout.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"AdminLayout.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminLayout.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAWxC,KAAK,gBAAgB,GAAI;IACtB,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAyHJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The scanner-safe confirm surface (C2). A GET renders this static page — nothing is consumed.
|
|
3
|
+
// The token rides in a hidden field; only the explicit form POST (the route's default action →
|
|
4
|
+
// confirmSignIn) verifies it. Mail scanners GET URLs but don't submit forms, so prefetch can't
|
|
5
|
+
// burn the link. JS-free by design.
|
|
6
|
+
interface Props {
|
|
7
|
+
data: { token: string; siteName: string; error: string | null };
|
|
8
|
+
}
|
|
9
|
+
let { data }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<svelte:head>
|
|
13
|
+
<title>Confirm sign-in · {data.siteName} CMS</title>
|
|
14
|
+
</svelte:head>
|
|
15
|
+
|
|
16
|
+
<div class="mx-auto mt-16 max-w-md rounded-box border border-base-300 bg-base-100 p-8">
|
|
17
|
+
<h1 class="text-2xl font-bold">Confirm sign-in</h1>
|
|
18
|
+
<p class="mt-1 text-sm opacity-70">to {data.siteName} CMS</p>
|
|
19
|
+
|
|
20
|
+
{#if data.error || !data.token}
|
|
21
|
+
<div class="alert alert-error mt-6">
|
|
22
|
+
<span>This sign-in link is invalid or expired. Request a new one.</span>
|
|
23
|
+
</div>
|
|
24
|
+
<a href="/admin/login" class="btn btn-primary mt-6">Back to sign-in</a>
|
|
25
|
+
{:else}
|
|
26
|
+
<form method="POST" class="mt-6 flex flex-col gap-3">
|
|
27
|
+
<input type="hidden" name="token" value={data.token} />
|
|
28
|
+
<button type="submit" class="btn btn-primary">Confirm sign-in</button>
|
|
29
|
+
</form>
|
|
30
|
+
{/if}
|
|
31
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
data: {
|
|
3
|
+
token: string;
|
|
4
|
+
siteName: string;
|
|
5
|
+
error: string | null;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
declare const ConfirmPage: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type ConfirmPage = ReturnType<typeof ConfirmPage>;
|
|
10
|
+
export default ConfirmPage;
|
|
11
|
+
//# sourceMappingURL=ConfirmPage.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmPage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ConfirmPage.svelte.ts"],"names":[],"mappings":"AAOE,UAAU,KAAK;IACb,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACjE;AA6BH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
// The magic-link sign-in page.
|
|
3
|
-
//
|
|
2
|
+
// The magic-link sign-in page. Requests a link via the better-auth client (client-side, same
|
|
3
|
+
// origin). To avoid enumeration the UI shows the SAME neutral copy whether or not the email is
|
|
4
|
+
// on the allowlist — the server only emails actual editors (see auth/config.ts send gate).
|
|
5
|
+
import { createAuthClient } from 'better-auth/svelte';
|
|
6
|
+
import { magicLinkClient } from 'better-auth/client/plugins';
|
|
7
|
+
|
|
8
|
+
// The browser client lives in the one component that needs it (requesting a link). Sign-out
|
|
9
|
+
// and editor management go through server endpoints, so no shared client module is needed —
|
|
10
|
+
// and a component-local const keeps better-auth's deep client types out of the packaged .d.ts.
|
|
11
|
+
const authClient = createAuthClient({ plugins: [magicLinkClient()] });
|
|
12
|
+
|
|
4
13
|
interface Props {
|
|
5
|
-
data: { siteName: string
|
|
14
|
+
data: { siteName: string };
|
|
6
15
|
}
|
|
7
16
|
let { data }: Props = $props();
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
let email = $state('');
|
|
19
|
+
let requested = $state(false);
|
|
20
|
+
let busy = $state(false);
|
|
21
|
+
|
|
22
|
+
async function request(event: SubmitEvent) {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
busy = true;
|
|
25
|
+
// The magic-link email points at our /admin/auth/confirm page (built in config.ts), not a
|
|
26
|
+
// GET-verify URL — so the result is the same regardless of allowlist membership.
|
|
27
|
+
await authClient.signIn.magicLink({ email });
|
|
28
|
+
busy = false;
|
|
29
|
+
requested = true;
|
|
30
|
+
}
|
|
15
31
|
</script>
|
|
16
32
|
|
|
17
33
|
<svelte:head>
|
|
@@ -22,26 +38,27 @@
|
|
|
22
38
|
<h1 class="text-2xl font-bold">{data.siteName} CMS</h1>
|
|
23
39
|
<p class="mt-1 text-sm opacity-70">Sign in with your editor email.</p>
|
|
24
40
|
|
|
25
|
-
{#if
|
|
41
|
+
{#if requested}
|
|
26
42
|
<div class="alert alert-success mt-6">
|
|
27
|
-
<span>
|
|
43
|
+
<span>
|
|
44
|
+
If that address is on the editor list, a sign-in link is on its way. It expires in 10
|
|
45
|
+
minutes.
|
|
46
|
+
</span>
|
|
28
47
|
</div>
|
|
29
48
|
{:else}
|
|
30
|
-
{
|
|
31
|
-
<div class="alert alert-error mt-6">
|
|
32
|
-
<span>{errorMessages[data.error] ?? 'Something went wrong. Try again.'}</span>
|
|
33
|
-
</div>
|
|
34
|
-
{/if}
|
|
35
|
-
<form method="POST" action="/admin/auth/request" class="mt-6 flex flex-col gap-3">
|
|
49
|
+
<form onsubmit={request} class="mt-6 flex flex-col gap-3">
|
|
36
50
|
<input
|
|
37
51
|
type="email"
|
|
38
52
|
name="email"
|
|
53
|
+
bind:value={email}
|
|
39
54
|
required
|
|
40
55
|
autocomplete="email"
|
|
41
56
|
placeholder="you@example.com"
|
|
42
57
|
class="input w-full"
|
|
43
58
|
/>
|
|
44
|
-
<button type="submit" class="btn btn-primary"
|
|
59
|
+
<button type="submit" class="btn btn-primary" disabled={busy}>
|
|
60
|
+
{busy ? 'Sending…' : 'Email me a sign-in link'}
|
|
61
|
+
</button>
|
|
45
62
|
</form>
|
|
46
63
|
{/if}
|
|
47
64
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoginPage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/LoginPage.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LoginPage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/LoginPage.svelte.ts"],"names":[],"mappings":"AAUE,UAAU,KAAK;IACb,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5B;AAyDH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ones. Reuses the same neutral DaisyUI chrome as the rest of the admin (panels, alerts,
|
|
4
4
|
// table, buttons). Data comes from `adminsLoad` merged with `adminLayoutLoad` (siteName);
|
|
5
5
|
// mutations post to the page's named form actions (`?/add`, `?/remove`, `?/setRole`).
|
|
6
|
-
import type { AdminsData } from '../
|
|
6
|
+
import type { AdminsData } from '../auth';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
data: AdminsData & { siteName: string };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManageAdmins.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ManageAdmins.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ManageAdmins.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ManageAdmins.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGxC,UAAU,KAAK;IACb,IAAI,EAAE,UAAU,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AAmFH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { default as AdminLayout } from './AdminLayout.svelte';
|
|
2
2
|
export { default as AdminList } from './AdminList.svelte';
|
|
3
3
|
export { default as LoginPage } from './LoginPage.svelte';
|
|
4
|
+
export { default as ConfirmPage } from './ConfirmPage.svelte';
|
|
4
5
|
export { default as EditPage } from './EditPage.svelte';
|
|
5
6
|
export { default as ManageAdmins } from './ManageAdmins.svelte';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/components/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/components/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/components/index.js
CHANGED
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
export { default as AdminLayout } from './AdminLayout.svelte';
|
|
4
4
|
export { default as AdminList } from './AdminList.svelte';
|
|
5
5
|
export { default as LoginPage } from './LoginPage.svelte';
|
|
6
|
+
export { default as ConfirmPage } from './ConfirmPage.svelte';
|
|
6
7
|
export { default as EditPage } from './EditPage.svelte';
|
|
7
8
|
export { default as ManageAdmins } from './ManageAdmins.svelte';
|
package/dist/email.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/lib/email.ts"],"names":[],"mappings":"AAQA,4FAA4F;AAC5F,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,OAAO,EAAE;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpC;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/lib/email.ts"],"names":[],"mappings":"AAQA,4FAA4F;AAC5F,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,OAAO,EAAE;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpC;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAgBf"}
|
package/dist/email.js
CHANGED
|
@@ -7,11 +7,19 @@
|
|
|
7
7
|
// Resend can slot in behind the same `sendMagicLink` signature if needed.
|
|
8
8
|
export async function sendMagicLink(sender, to, link, siteName, from) {
|
|
9
9
|
const expiry = "This link expires in 10 minutes and works only once. If you didn't request it, ignore this email.";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
try {
|
|
11
|
+
await sender.send({
|
|
12
|
+
to,
|
|
13
|
+
from,
|
|
14
|
+
subject: `Your ${siteName} sign-in link`,
|
|
15
|
+
text: `Sign in to ${siteName}:\n\n${link}\n\n${expiry}`,
|
|
16
|
+
html: `<p>Sign in to ${siteName}:</p><p><a href="${link}">Confirm sign-in</a></p><p style="color:#666;font-size:0.9em">${expiry}</p>`,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
// H6: Email Sending is beta + the sole auth channel. Surface + audit; a Resend fallback
|
|
21
|
+
// can slot in behind this same signature if Sending proves unreliable.
|
|
22
|
+
console.error(`magic-link email send failed for ${to}:`, err);
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
17
25
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAEA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// cairn-cms public API. Consumers import
|
|
2
|
-
|
|
1
|
+
// cairn-cms public API. Consumers import content/email/github/adapter from 'cairn-cms';
|
|
2
|
+
// auth (better-auth factory, guards, manage-editors) lives at the 'cairn-cms/auth' subpath.
|
|
3
3
|
export * from './email';
|
|
4
4
|
export * from './github';
|
|
5
5
|
export * from './carta';
|
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { KVNamespace } from '@cloudflare/workers-types';
|
|
3
|
-
import { type Editor } from '../auth';
|
|
4
|
-
import { type EmailSender } from '../email';
|
|
1
|
+
import type { CairnUser } from '../auth/guard';
|
|
5
2
|
import { type RepoFile } from '../github';
|
|
6
3
|
import { type CairnAdapter, type CairnField } from '../adapter';
|
|
7
|
-
/** The `platform.env` bindings the
|
|
4
|
+
/** The `platform.env` bindings the content routes read. All optional — the handlers guard. */
|
|
8
5
|
export interface AdminEnv {
|
|
9
|
-
AUTH_KV?: KVNamespace;
|
|
10
|
-
MAGIC_LINK_SECRET?: string;
|
|
11
|
-
SESSION_SECRET?: string;
|
|
12
|
-
EMAIL?: EmailSender;
|
|
13
|
-
/** Overrides `url.origin` for the magic-link base (set in dev, unset in prod). */
|
|
14
|
-
PUBLIC_ORIGIN?: string;
|
|
15
6
|
GITHUB_APP_ID?: string;
|
|
16
7
|
GITHUB_APP_INSTALLATION_ID?: string;
|
|
17
8
|
GITHUB_APP_PRIVATE_KEY_B64?: string;
|
|
@@ -22,7 +13,7 @@ interface PlatformEvent {
|
|
|
22
13
|
};
|
|
23
14
|
}
|
|
24
15
|
export interface AdminLayoutData {
|
|
25
|
-
|
|
16
|
+
user: CairnUser | null;
|
|
26
17
|
siteName: string;
|
|
27
18
|
pathname: string;
|
|
28
19
|
}
|
|
@@ -35,7 +26,7 @@ export interface AdminLayoutData {
|
|
|
35
26
|
*/
|
|
36
27
|
export declare function adminLayoutLoad(event: {
|
|
37
28
|
locals: {
|
|
38
|
-
|
|
29
|
+
user: CairnUser | null;
|
|
39
30
|
};
|
|
40
31
|
url: URL;
|
|
41
32
|
}, adapter: CairnAdapter): AdminLayoutData;
|
|
@@ -49,13 +40,6 @@ export interface AdminCollectionList {
|
|
|
49
40
|
export declare function adminListLoad(event: PlatformEvent, adapter: CairnAdapter): Promise<{
|
|
50
41
|
collections: AdminCollectionList[];
|
|
51
42
|
}>;
|
|
52
|
-
export interface LoginData {
|
|
53
|
-
sent: boolean;
|
|
54
|
-
error: string | null;
|
|
55
|
-
}
|
|
56
|
-
export declare function loginLoad(event: {
|
|
57
|
-
url: URL;
|
|
58
|
-
}): LoginData;
|
|
59
43
|
export interface EditData {
|
|
60
44
|
type: string;
|
|
61
45
|
id: string;
|
|
@@ -75,48 +59,11 @@ export declare function editLoad(event: PlatformEvent & {
|
|
|
75
59
|
};
|
|
76
60
|
url: URL;
|
|
77
61
|
}, adapter: CairnAdapter): Promise<EditData>;
|
|
78
|
-
export declare function authRequest(event: PlatformEvent & {
|
|
79
|
-
request: Request;
|
|
80
|
-
url: URL;
|
|
81
|
-
}, adapter: CairnAdapter): Promise<never>;
|
|
82
|
-
export declare function authCallback(event: PlatformEvent & {
|
|
83
|
-
url: URL;
|
|
84
|
-
cookies: Cookies;
|
|
85
|
-
}): Promise<never>;
|
|
86
|
-
export declare function logout(event: {
|
|
87
|
-
cookies: Cookies;
|
|
88
|
-
}): never;
|
|
89
62
|
export declare function saveCommit(event: PlatformEvent & {
|
|
90
63
|
request: Request;
|
|
91
64
|
locals: {
|
|
92
|
-
|
|
65
|
+
user: CairnUser | null;
|
|
93
66
|
};
|
|
94
67
|
}, adapter: CairnAdapter): Promise<never>;
|
|
95
|
-
export interface AdminsData {
|
|
96
|
-
admins: Editor[];
|
|
97
|
-
/** Acting owner's email, so the UI can disable self-targeted remove/demote. */
|
|
98
|
-
self: string;
|
|
99
|
-
saved: boolean;
|
|
100
|
-
error: string | null;
|
|
101
|
-
}
|
|
102
|
-
/** List the allowlist for the manage-admins page. Owner-only. */
|
|
103
|
-
export declare function adminsLoad(event: PlatformEvent & {
|
|
104
|
-
locals: {
|
|
105
|
-
editor: Editor | null;
|
|
106
|
-
};
|
|
107
|
-
url: URL;
|
|
108
|
-
}): Promise<AdminsData>;
|
|
109
|
-
type AdminsActionEvent = PlatformEvent & {
|
|
110
|
-
request: Request;
|
|
111
|
-
locals: {
|
|
112
|
-
editor: Editor | null;
|
|
113
|
-
};
|
|
114
|
-
};
|
|
115
|
-
/** Add (or update) an allowlist entry. Owner-only. */
|
|
116
|
-
export declare function addAdmin(event: AdminsActionEvent): Promise<never>;
|
|
117
|
-
/** Remove an allowlist entry. Owner-only; owners can't remove themselves (anti-lockout). */
|
|
118
|
-
export declare function removeAdmin(event: AdminsActionEvent): Promise<never>;
|
|
119
|
-
/** Change an editor's role. Owner-only; owners can't demote themselves (anti-lockout). */
|
|
120
|
-
export declare function setAdminRole(event: AdminsActionEvent): Promise<never>;
|
|
121
68
|
export {};
|
|
122
69
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAwD,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEhG,OAAO,EAAuC,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAErG,8FAA8F;AAC9F,MAAM,WAAW,QAAQ;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,QAAQ,CAAA;KAAE,CAAC;CAC/B;AA2BD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,EACvD,OAAO,EAAE,YAAY,GACpB,eAAe,CAEjB;AAID,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4FAA4F;AAC5F,wBAAsB,aAAa,CACjC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,WAAW,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC,CAajD;AAID,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,aAAa,GAAG;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,EACzE,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,QAAQ,CAAC,CAyBnB;AAID,wBAAsB,UAAU,CAC9B,KAAK,EAAE,aAAa,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,EAC/E,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,KAAK,CAAC,CA0ChB"}
|