@guard-rail/core 0.8.0 → 0.9.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 +62 -102
- package/dist/adapters/supabase/auth.d.ts +9 -15
- package/dist/adapters/supabase/auth.js +33 -42
- package/dist/adapters/supabase/auth.js.map +1 -1
- package/dist/adapters/supabase/confirm.js +2 -2
- package/dist/adapters/supabase/confirm.js.map +1 -1
- package/dist/adapters/supabase/members.js +13 -2
- package/dist/adapters/supabase/members.js.map +1 -1
- package/dist/core/adapter-types.d.ts +60 -28
- package/dist/core/config.d.ts +2 -1
- package/dist/core/config.js +4 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/defs.d.ts +11 -1
- package/dist/core/defs.js +11 -1
- package/dist/core/defs.js.map +1 -1
- package/dist/core/types.d.ts +6 -0
- package/dist/email/resend.d.ts +39 -10
- package/dist/email/resend.js +92 -49
- package/dist/email/resend.js.map +1 -1
- package/dist/index.client.d.ts +5 -4
- package/dist/index.client.js +9 -0
- package/dist/index.client.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/react/Admin.js +105 -107
- package/dist/react/Admin.js.map +1 -1
- package/dist/react/AuthDrawer.d.ts +23 -0
- package/dist/react/AuthDrawer.js +165 -0
- package/dist/react/AuthDrawer.js.map +1 -0
- package/dist/react/AuthRedirectSync.js +1 -1
- package/dist/react/AuthRedirectSync.js.map +1 -1
- package/dist/react/Icons.d.ts +2 -1
- package/dist/react/Icons.js +2 -0
- package/dist/react/Icons.js.map +1 -1
- package/dist/react/Team.d.ts +7 -1
- package/dist/react/Team.js +23 -3
- package/dist/react/Team.js.map +1 -1
- package/dist/server/access.d.ts +3 -1
- package/dist/server/access.js +5 -3
- package/dist/server/access.js.map +1 -1
- package/dist/server/actions.d.ts +25 -4
- package/dist/server/actions.js +115 -39
- package/dist/server/actions.js.map +1 -1
- package/dist/server/wrapper.js +2 -1
- package/dist/server/wrapper.js.map +1 -1
- package/package.json +8 -10
package/dist/server/actions.js
CHANGED
|
@@ -6,13 +6,18 @@ import {
|
|
|
6
6
|
getDB,
|
|
7
7
|
getInviteHandler,
|
|
8
8
|
getMembers,
|
|
9
|
+
getPasswordResetHandler,
|
|
9
10
|
getStorage,
|
|
10
11
|
resolveSiteId
|
|
11
12
|
} from "../core/config";
|
|
12
13
|
import { getCachedViewer } from "./session";
|
|
13
14
|
import { requireRole } from "./access";
|
|
14
15
|
import { rethrowIfBailout } from "../core/bailout";
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
GR_AUTH_ERROR_TTL_SECONDS,
|
|
18
|
+
GR_COOKIES,
|
|
19
|
+
GR_NEEDS_PASSWORD_TTL_SECONDS
|
|
20
|
+
} from "../core/defs";
|
|
16
21
|
const DENIED = { success: false, error: "Unauthorized." };
|
|
17
22
|
async function saveOverride(row) {
|
|
18
23
|
const viewer = await requireRole(["admin", "editor"]);
|
|
@@ -111,59 +116,42 @@ async function inviteMember(email, role) {
|
|
|
111
116
|
}
|
|
112
117
|
const auth = getAuth();
|
|
113
118
|
const onInvite = getInviteHandler();
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (!made.success) {
|
|
118
|
-
revalidatePath("/", "layout");
|
|
119
|
-
return {
|
|
120
|
-
success: false,
|
|
121
|
-
error: `Invite saved, but the sign-in link couldn't be created: ${made.error}`
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
await onInvite({
|
|
126
|
-
email: normalized,
|
|
127
|
-
role: existing?.role ?? role,
|
|
128
|
-
siteId,
|
|
129
|
-
invitedBy,
|
|
130
|
-
link: made.link,
|
|
131
|
-
isNewUser: made.isNewUser
|
|
132
|
-
});
|
|
133
|
-
} catch (e) {
|
|
134
|
-
console.error("Guardrail: onInvite handler threw", e);
|
|
135
|
-
revalidatePath("/", "layout");
|
|
136
|
-
return {
|
|
137
|
-
success: false,
|
|
138
|
-
error: `Invite saved, but the email failed: ${e instanceof Error ? e.message : "unknown error"}`
|
|
139
|
-
};
|
|
140
|
-
}
|
|
119
|
+
const origin = await requestOrigin();
|
|
120
|
+
const made = auth.createInviteLink ? await auth.createInviteLink(normalized, { origin }) : null;
|
|
121
|
+
if (made && !made.success) {
|
|
141
122
|
revalidatePath("/", "layout");
|
|
142
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: `Invite saved, but the sign-in link couldn't be created: ${made.error}`
|
|
126
|
+
};
|
|
143
127
|
}
|
|
144
|
-
if (
|
|
145
|
-
const sent = await auth.inviteUser(normalized, {
|
|
146
|
-
redirectTo: `${await requestOrigin()}/?edit=true`
|
|
147
|
-
});
|
|
128
|
+
if (!onInvite) {
|
|
148
129
|
revalidatePath("/", "layout");
|
|
149
|
-
return
|
|
130
|
+
return {
|
|
150
131
|
success: false,
|
|
151
|
-
error:
|
|
132
|
+
error: "Invite saved, but this site has nothing set up to email it. Add resendEmails() (or your own onInvite) to withGuardrail() \u2014 see the README."
|
|
152
133
|
};
|
|
153
134
|
}
|
|
154
135
|
try {
|
|
155
|
-
await onInvite
|
|
136
|
+
await onInvite({
|
|
156
137
|
email: normalized,
|
|
157
138
|
role: existing?.role ?? role,
|
|
158
139
|
siteId,
|
|
159
|
-
invitedBy
|
|
140
|
+
invitedBy: viewer.email ?? viewer.userId,
|
|
141
|
+
...made ? { link: made.link, isNewUser: made.isNewUser } : {}
|
|
160
142
|
});
|
|
161
143
|
} catch (e) {
|
|
162
144
|
console.error("Guardrail: onInvite handler threw", e);
|
|
145
|
+
revalidatePath("/", "layout");
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
error: `Invite saved, but the email failed: ${e instanceof Error ? e.message : "unknown error"}`
|
|
149
|
+
};
|
|
163
150
|
}
|
|
164
151
|
revalidatePath("/", "layout");
|
|
165
|
-
return { success: true };
|
|
152
|
+
return made && !made.isNewUser ? { success: true, notice: ALREADY_HAS_ACCOUNT(normalized) } : { success: true };
|
|
166
153
|
}
|
|
154
|
+
const ALREADY_HAS_ACCOUNT = (email) => `${email} already has an account, so they were told they've been added rather than sent a link \u2014 they sign in with their existing password.`;
|
|
167
155
|
async function updateMemberRole(email, role) {
|
|
168
156
|
const viewer = await requireRole(["admin"]);
|
|
169
157
|
if (!viewer) return DENIED;
|
|
@@ -198,6 +186,74 @@ async function removeMember(email) {
|
|
|
198
186
|
if (res.success) revalidatePath("/", "layout");
|
|
199
187
|
return res;
|
|
200
188
|
}
|
|
189
|
+
const RESET_COOLDOWN_MS = 6e4;
|
|
190
|
+
const lastResetAt = /* @__PURE__ */ new Map();
|
|
191
|
+
const RESET_MIN_DURATION_MS = 600;
|
|
192
|
+
const EMAIL_PATTERN = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
|
|
193
|
+
async function dispatchPasswordReset(email, requestedBy) {
|
|
194
|
+
const auth = getAuth();
|
|
195
|
+
const handler = getPasswordResetHandler();
|
|
196
|
+
if (!auth.createPasswordResetLink) {
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: "this site's auth setup can't create reset links \u2014 give the Supabase adapter a serviceRoleKey."
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (!handler) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: "this site has no onPasswordReset handler \u2014 add resendEmails() (or your own) to withGuardrail()."
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const made = await auth.createPasswordResetLink(email, { origin: await requestOrigin() });
|
|
209
|
+
if (!made.success) return { success: false, error: made.error };
|
|
210
|
+
try {
|
|
211
|
+
await handler({ email, siteId: await resolveSiteId(), link: made.link, requestedBy });
|
|
212
|
+
return { success: true };
|
|
213
|
+
} catch (e) {
|
|
214
|
+
console.error("Guardrail: onPasswordReset handler threw", e);
|
|
215
|
+
return { success: false, error: e instanceof Error ? e.message : "unknown error" };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function forgotPasswordAction(email) {
|
|
219
|
+
const started = Date.now();
|
|
220
|
+
const normalized = String(email ?? "").toLowerCase().trim();
|
|
221
|
+
if (!EMAIL_PATTERN.test(normalized)) {
|
|
222
|
+
return { success: false, error: "Enter a valid email address." };
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const siteId = await resolveSiteId();
|
|
226
|
+
const key = `${siteId}:${normalized}`;
|
|
227
|
+
const last = lastResetAt.get(key);
|
|
228
|
+
if (!last || started - last >= RESET_COOLDOWN_MS) {
|
|
229
|
+
lastResetAt.set(key, started);
|
|
230
|
+
if (lastResetAt.size > 500) {
|
|
231
|
+
for (const [k, t] of lastResetAt) if (started - t >= RESET_COOLDOWN_MS) lastResetAt.delete(k);
|
|
232
|
+
}
|
|
233
|
+
const members = getMembers();
|
|
234
|
+
const known = members.emailHasAccess ? await members.emailHasAccess(siteId, normalized) : true;
|
|
235
|
+
if (known) {
|
|
236
|
+
const res = await dispatchPasswordReset(normalized, "self");
|
|
237
|
+
if (!res.success) console.error("Guardrail: password reset failed:", res.error);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch (e) {
|
|
241
|
+
rethrowIfBailout(e);
|
|
242
|
+
console.error("Guardrail: password reset failed", e);
|
|
243
|
+
}
|
|
244
|
+
const wait = RESET_MIN_DURATION_MS - (Date.now() - started);
|
|
245
|
+
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
246
|
+
return { success: true };
|
|
247
|
+
}
|
|
248
|
+
async function sendPasswordResetAction(email) {
|
|
249
|
+
const viewer = await requireRole(["admin"]);
|
|
250
|
+
if (!viewer) return DENIED;
|
|
251
|
+
const normalized = String(email ?? "").toLowerCase().trim();
|
|
252
|
+
const target = await getMembers().getMember(await resolveSiteId(), { email: normalized });
|
|
253
|
+
if (!target) return { success: false, error: "No such member." };
|
|
254
|
+
const res = await dispatchPasswordReset(normalized, viewer.email ?? viewer.userId);
|
|
255
|
+
return res.success ? { success: true, notice: `Password reset email sent to ${normalized}.` } : { success: false, error: `The reset email couldn't be sent: ${res.error}` };
|
|
256
|
+
}
|
|
201
257
|
async function requestOrigin() {
|
|
202
258
|
const h = await headers();
|
|
203
259
|
const origin = h.get("origin");
|
|
@@ -241,6 +297,8 @@ async function logoutAction() {
|
|
|
241
297
|
await auth.logout();
|
|
242
298
|
const cookieStore = await cookies();
|
|
243
299
|
cookieStore.delete(GR_COOKIES.EDIT_MODE);
|
|
300
|
+
cookieStore.delete(GR_COOKIES.NEEDS_PASSWORD);
|
|
301
|
+
cookieStore.delete(GR_COOKIES.AUTH_ERROR);
|
|
244
302
|
revalidatePath("/", "layout");
|
|
245
303
|
return { success: true };
|
|
246
304
|
}
|
|
@@ -248,15 +306,24 @@ async function completeAuthRedirectAction(params) {
|
|
|
248
306
|
const auth = getAuth();
|
|
249
307
|
if (!auth.completeAuthRedirect) return { success: false };
|
|
250
308
|
const res = await auth.completeAuthRedirect(params);
|
|
309
|
+
const cookieStore = await cookies();
|
|
251
310
|
if (res.success) {
|
|
311
|
+
cookieStore.delete(GR_COOKIES.AUTH_ERROR);
|
|
252
312
|
if (res.needsPassword) {
|
|
253
|
-
const cookieStore = await cookies();
|
|
254
313
|
cookieStore.set(GR_COOKIES.NEEDS_PASSWORD, "true", {
|
|
255
314
|
path: "/",
|
|
256
315
|
maxAge: GR_NEEDS_PASSWORD_TTL_SECONDS
|
|
257
316
|
});
|
|
258
317
|
}
|
|
259
318
|
revalidatePath("/", "layout");
|
|
319
|
+
} else {
|
|
320
|
+
console.error("Guardrail: couldn't complete an emailed link:", res.error);
|
|
321
|
+
cookieStore.set(
|
|
322
|
+
GR_COOKIES.AUTH_ERROR,
|
|
323
|
+
/expired|invalid|already|used|not found/i.test(res.error ?? "") ? "expired" : "failed",
|
|
324
|
+
{ path: "/", maxAge: GR_AUTH_ERROR_TTL_SECONDS }
|
|
325
|
+
);
|
|
326
|
+
revalidatePath("/", "layout");
|
|
260
327
|
}
|
|
261
328
|
return res;
|
|
262
329
|
}
|
|
@@ -268,6 +335,13 @@ async function setPasswordAction(_prev, formData) {
|
|
|
268
335
|
return { success: false, error: "This site's auth provider does not support setting a password." };
|
|
269
336
|
}
|
|
270
337
|
const password = String(formData.get("password") ?? "");
|
|
338
|
+
const confirm = formData.get("confirm");
|
|
339
|
+
if (password.length < 8) {
|
|
340
|
+
return { success: false, error: "Use at least 8 characters." };
|
|
341
|
+
}
|
|
342
|
+
if (confirm !== null && String(confirm) !== password) {
|
|
343
|
+
return { success: false, error: "The two passwords don't match." };
|
|
344
|
+
}
|
|
271
345
|
const res = await auth.setPassword(password);
|
|
272
346
|
if (res.success) {
|
|
273
347
|
const cookieStore = await cookies();
|
|
@@ -285,6 +359,7 @@ async function skipSetPasswordAction() {
|
|
|
285
359
|
export {
|
|
286
360
|
completeAuthRedirectAction,
|
|
287
361
|
createAssetUpload,
|
|
362
|
+
forgotPasswordAction,
|
|
288
363
|
inviteMember,
|
|
289
364
|
listAssets,
|
|
290
365
|
listTeam,
|
|
@@ -292,6 +367,7 @@ export {
|
|
|
292
367
|
logoutAction,
|
|
293
368
|
removeMember,
|
|
294
369
|
saveOverride,
|
|
370
|
+
sendPasswordResetAction,
|
|
295
371
|
setFieldValue,
|
|
296
372
|
setPasswordAction,
|
|
297
373
|
skipSetPasswordAction,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/actions.ts"],"sourcesContent":["\"use server\";\n\nimport { revalidatePath } from \"next/cache\";\nimport { cookies, headers } from \"next/headers\";\nimport {\n getAuth,\n getDB,\n getInviteHandler,\n getMembers,\n getStorage,\n resolveSiteId,\n} from \"../core/config\";\nimport { getCachedViewer } from \"./session\";\nimport { requireRole } from \"./access\";\nimport { rethrowIfBailout } from \"../core/bailout\";\nimport { GR_COOKIES, GR_NEEDS_PASSWORD_TTL_SECONDS } from \"../core/defs\";\nimport type {\n ActionState,\n GRAsset,\n GRMember,\n GRRole,\n OverrideRow,\n OverrideType,\n OverrideValue,\n SetPasswordState,\n} from \"../core/types\";\nimport type {\n AuthRedirectParams,\n GRStorageRow,\n GRUploadTarget,\n} from \"../core/adapter-types\";\n\ntype Result = { success: boolean; error?: string };\n\nconst DENIED: Result = { success: false, error: \"Unauthorized.\" };\n\n// Every mutation below re-resolves the viewer server-side through\n// requireRole() (see access.ts) — the client's copy of the role decides what\n// to *show*, never what is *allowed*.\n\nexport async function saveOverride(row: OverrideRow): Promise<Result> {\n const viewer = await requireRole([\"admin\", \"editor\"]);\n if (!viewer) return DENIED;\n return writeOverride(row.pathKey, row.value);\n}\n\nconst OVERRIDE_TYPES: readonly OverrideType[] = [\"text\", \"link\", \"image\", \"button\"];\n\n/**\n * Writes one field's value by id, with no <GRail> involved — the setter that\n * pairs with getFieldValue(). It exists for fields with nothing on the page to\n * click: an SEO title or meta description, an og:image, a product's stock\n * note. Build a drawer or form of your own, and have it (or a Server Action of\n * yours) call this.\n *\n * A bare string is shorthand for a text field. Pass an OverrideValue for the\n * other types ({ type: \"image\", src, alt }, a link, a button).\n *\n * Does its own role check — an editor or admin — so it is safe to call\n * directly from a Client Component, the same as saveOverride. If your own\n * action does other work first, check isEditor() there too.\n *\n * Writes the same row a <GRail> with this id would, so the two stay\n * interchangeable: a field first written here can later be given a visible\n * <GRail>, and vice versa.\n */\nexport async function setFieldValue(\n id: string,\n value: string | OverrideValue,\n): Promise<Result> {\n const viewer = await requireRole([\"admin\", \"editor\"]);\n if (!viewer) return DENIED;\n\n const pathKey = id.trim();\n if (!pathKey) return { success: false, error: \"A field id is required.\" };\n\n const normalized: OverrideValue =\n typeof value === \"string\" ? { type: \"text\", text: value } : value;\n if (!OVERRIDE_TYPES.includes(normalized.type)) {\n return { success: false, error: `Unknown field type \"${normalized.type}\".` };\n }\n\n return writeOverride(pathKey, normalized);\n}\n\n/** The write shared by saveOverride and setFieldValue; the caller has already authorized. */\nasync function writeOverride(\n pathKey: string,\n value: OverrideValue,\n): Promise<Result> {\n const rowToSend: GRStorageRow = {\n pathKey,\n siteId: await resolveSiteId(),\n type: value.type,\n value,\n };\n\n try {\n const db = getDB();\n const res = await db.setOverride(rowToSend);\n if (!res.success) return res;\n\n // TODO: too broad — purges every cached route on any single-field save.\n revalidatePath(\"/\", \"layout\");\n return { success: true };\n } catch (e) {\n rethrowIfBailout(e);\n console.error(\"Guardrail: save failed\", e);\n return { success: false, error: \"Database write failed.\" };\n }\n}\n\n/* --------------------------------------------------------------- media --- */\n\n// SVG is excluded on purpose: it can carry script, and next/image refuses it\n// by default anyway.\nconst IMAGE_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\", \"image/avif\"];\nconst MAX_UPLOAD_BYTES = 10 * 1024 * 1024;\n\n/**\n * Authorizes an upload and returns where the browser should send the bytes.\n * Only metadata comes through here — the file itself goes straight to storage.\n */\nexport async function createAssetUpload(file: {\n name: string;\n contentType: string;\n size: number;\n}): Promise<\n { success: true; target: GRUploadTarget } | { success: false; error: string }\n> {\n const viewer = await requireRole([\"admin\", \"editor\"]);\n if (!viewer) return { success: false, error: \"Unauthorized.\" };\n\n const storage = getStorage();\n if (!storage) {\n return { success: false, error: \"No storage adapter is configured.\" };\n }\n if (!IMAGE_TYPES.includes(file.contentType)) {\n return { success: false, error: \"Upload a PNG, JPEG, WebP, GIF, or AVIF image.\" };\n }\n if (!(file.size > 0) || file.size > MAX_UPLOAD_BYTES) {\n return { success: false, error: \"Images must be under 10 MB.\" };\n }\n\n try {\n const target = await storage.createUpload(await resolveSiteId(), file);\n return { success: true, target };\n } catch (e) {\n rethrowIfBailout(e);\n console.error(\"Guardrail: createUpload failed\", e);\n // Failures here are almost always setup (missing SQL, bucket, or policy),\n // so show the cause while developing. Production keeps it generic rather\n // than exposing backend detail to editors.\n const detail =\n process.env.NODE_ENV !== \"production\" && e instanceof Error\n ? ` ${e.message}`\n : \"\";\n return { success: false, error: `Could not start the upload.${detail}` };\n }\n}\n\nexport async function listAssets(): Promise<GRAsset[]> {\n const viewer = await requireRole([\"admin\", \"editor\"]);\n const storage = getStorage();\n if (!viewer || !storage) return [];\n return storage.listAssets(await resolveSiteId());\n}\n\n/* ---------------------------------------------------------------- team --- */\n\nexport async function listTeam(): Promise<GRMember[]> {\n // Any member may see who else is on the site; only admins may change it.\n const viewer = await requireRole([\"admin\", \"editor\"]);\n if (!viewer) return [];\n\n const members = getMembers();\n return members.listMembers(await resolveSiteId());\n}\n\nexport async function inviteMember(\n email: string,\n role: GRRole,\n): Promise<Result> {\n const viewer = await requireRole([\"admin\"]);\n if (!viewer) return DENIED;\n\n const normalized = email.toLowerCase().trim();\n if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(normalized)) {\n return { success: false, error: \"Enter a valid email address.\" };\n }\n\n const siteId = await resolveSiteId();\n const members = getMembers();\n\n const existing = await members.getMember(siteId, { email: normalized });\n if (existing?.status === \"active\") {\n return { success: false, error: \"That address is already on the team.\" };\n }\n\n // A pending invite is re-sent rather than rejected: provider email is\n // rate-limited and unreliable enough that resending has to be possible.\n if (!existing) {\n const res = await members.upsertMember(siteId, {\n email: normalized,\n role,\n userId: null,\n status: \"pending\",\n });\n if (!res.success) return res;\n }\n\n const auth = getAuth();\n const onInvite = getInviteHandler();\n const invitedBy = viewer.email ?? viewer.userId;\n\n // The app owns delivery: the adapter creates the account and mints a link,\n // and the app's own handler sends it in its own email. Opt-in on both sides\n // — an adapter that can mint links AND an onInvite handler — so a site that\n // configured neither keeps getting the provider's email exactly as before.\n if (auth.createInviteLink && onInvite) {\n // As elsewhere, the membership is already written; a failure here must not undo it.\n const made = await auth.createInviteLink(normalized, { origin: await requestOrigin() });\n if (!made.success) {\n revalidatePath(\"/\", \"layout\");\n return {\n success: false,\n error: `Invite saved, but the sign-in link couldn't be created: ${made.error}`,\n };\n }\n try {\n await onInvite({\n email: normalized,\n role: existing?.role ?? role,\n siteId,\n invitedBy,\n link: made.link,\n isNewUser: made.isNewUser,\n });\n } catch (e) {\n console.error(\"Guardrail: onInvite handler threw\", e);\n revalidatePath(\"/\", \"layout\");\n return {\n success: false,\n error: `Invite saved, but the email failed: ${e instanceof Error ? e.message : \"unknown error\"}`,\n };\n }\n revalidatePath(\"/\", \"layout\");\n return { success: true };\n }\n\n if (auth.inviteUser) {\n // The membership is already written; a failed send must not undo it.\n const sent = await auth.inviteUser(normalized, {\n redirectTo: `${await requestOrigin()}/?edit=true`,\n });\n revalidatePath(\"/\", \"layout\");\n return sent.success\n ? { success: true }\n : {\n success: false,\n error: `Invite saved, but the email failed: ${sent.error}`,\n };\n }\n\n // No provider-side delivery, so hand off to the app.\n try {\n await onInvite?.({\n email: normalized,\n role: existing?.role ?? role,\n siteId,\n invitedBy,\n });\n } catch (e) {\n console.error(\"Guardrail: onInvite handler threw\", e);\n }\n\n revalidatePath(\"/\", \"layout\");\n return { success: true };\n}\n\nexport async function updateMemberRole(\n email: string,\n role: GRRole,\n): Promise<Result> {\n const viewer = await requireRole([\"admin\"]);\n if (!viewer) return DENIED;\n\n const siteId = await resolveSiteId();\n const members = getMembers();\n\n const target = await members.getMember(siteId, {\n email: email.toLowerCase().trim(),\n });\n if (!target) return { success: false, error: \"No such member.\" };\n\n if (target.role === \"admin\" && role !== \"admin\") {\n const guard = await wouldStrandSite(siteId, target.email);\n if (guard) return guard;\n }\n\n const res = await members.upsertMember(siteId, {\n email: target.email,\n role,\n userId: target.userId,\n status: target.status,\n });\n if (res.success) revalidatePath(\"/\", \"layout\");\n return res;\n}\n\nexport async function removeMember(email: string): Promise<Result> {\n const viewer = await requireRole([\"admin\"]);\n if (!viewer) return DENIED;\n\n const siteId = await resolveSiteId();\n const members = getMembers();\n const normalized = email.toLowerCase().trim();\n\n const guard = await wouldStrandSite(siteId, normalized);\n if (guard) return guard;\n\n const res = await members.removeMember(siteId, normalized);\n if (res.success) revalidatePath(\"/\", \"layout\");\n return res;\n}\n\nasync function requestOrigin(): Promise<string> {\n const h = await headers();\n const origin = h.get(\"origin\");\n if (origin) return origin;\n const host = h.get(\"x-forwarded-host\") ?? h.get(\"host\") ?? \"localhost\";\n const proto = h.get(\"x-forwarded-proto\") ?? (host.startsWith(\"localhost\") ? \"http\" : \"https\");\n return `${proto}://${host}`;\n}\n\n/**\n * Refuses changes that would leave a site with no admin, which would lock\n * everyone out of team management with no in-app way to recover.\n */\nasync function wouldStrandSite(\n siteId: string,\n email: string,\n): Promise<Result | null> {\n const members = getMembers();\n const all = await members.listMembers(siteId);\n const admins = all.filter((m) => m.role === \"admin\");\n\n if (admins.length <= 1 && admins.some((m) => m.email === email)) {\n return {\n success: false,\n error: \"A site must keep at least one admin.\",\n };\n }\n return null;\n}\n\n/* ---------------------------------------------------------------- auth --- */\n\nexport async function loginAction(\n _prev: ActionState,\n formData: FormData,\n): Promise<ActionState> {\n const email = String(formData.get(\"email\") ?? \"\");\n const password = String(formData.get(\"password\") ?? \"\");\n\n const auth = getAuth();\n if (!auth.authenticate) {\n return {\n success: false,\n error: \"This site's auth provider does not accept passwords.\",\n email,\n };\n }\n\n const result = await auth.authenticate(email, password);\n if (!result.success) {\n return { success: false, error: result.error, email };\n }\n\n revalidatePath(\"/\", \"layout\");\n return { success: true, email };\n}\n\nexport async function logoutAction(): Promise<{ success: boolean }> {\n const auth = getAuth();\n await auth.logout();\n // Cleared here, not by the client afterward: a Server Action's own\n // revalidatePath() below triggers Next's router to refresh the current\n // route automatically, which can race a client-side navigation attempted\n // right after this resolves — confirmed live, where that automatic\n // refresh (for the pre-logout URL, ?edit=true) won the race and silently\n // undid a client-side `window.location.href` to the plain path. Deleting\n // the cookie here instead makes it part of the one server round-trip that\n // revalidatePath already accounts for, with nothing left to race.\n const cookieStore = await cookies();\n cookieStore.delete(GR_COOKIES.EDIT_MODE);\n revalidatePath(\"/\", \"layout\");\n return { success: true };\n}\n\n/* ---------------------------------------------------------- redirects --- */\n\n/**\n * Completes a session that arrived via an invite, magic-link, or\n * password-recovery redirect. Called by AuthRedirectSync (react/), the\n * generic client component that notices this kind of link and hands off\n * whatever it found in the URL — the actual token exchange is entirely the\n * auth adapter's job (see GRAuthAdapter.completeAuthRedirect); core only\n * orchestrates the result.\n *\n * A no-op {success:false}, not an error, when the adapter doesn't implement\n * this — most page loads carry no redirect data at all, and an adapter with\n * no such concept should not make every one of them fail.\n */\nexport async function completeAuthRedirectAction(\n params: AuthRedirectParams,\n): Promise<{ success: boolean; needsPassword?: boolean; error?: string }> {\n const auth = getAuth();\n if (!auth.completeAuthRedirect) return { success: false };\n\n const res = await auth.completeAuthRedirect(params);\n if (res.success) {\n if (res.needsPassword) {\n const cookieStore = await cookies();\n cookieStore.set(GR_COOKIES.NEEDS_PASSWORD, \"true\", {\n path: \"/\",\n maxAge: GR_NEEDS_PASSWORD_TTL_SECONDS,\n });\n }\n revalidatePath(\"/\", \"layout\");\n }\n return res;\n}\n\n/**\n * Sets the current session's password — the prompt completeAuthRedirectAction\n * triggers via needsPassword. Requires only a valid session, not a role: an\n * invited person's role is claimed as a side effect of getCachedViewer()\n * resolving them, which already happens independently of this.\n */\nexport async function setPasswordAction(\n _prev: SetPasswordState,\n formData: FormData,\n): Promise<SetPasswordState> {\n const viewer = await getCachedViewer();\n if (!viewer) return { success: false, error: \"Not signed in.\" };\n\n const auth = getAuth();\n if (!auth.setPassword) {\n return { success: false, error: \"This site's auth provider does not support setting a password.\" };\n }\n\n const password = String(formData.get(\"password\") ?? \"\");\n const res = await auth.setPassword(password);\n if (res.success) {\n const cookieStore = await cookies();\n cookieStore.delete(GR_COOKIES.NEEDS_PASSWORD);\n revalidatePath(\"/\", \"layout\");\n }\n return res;\n}\n\n/** Dismisses the set-password prompt without setting one — see Admin.tsx. */\nexport async function skipSetPasswordAction(): Promise<{ success: boolean }> {\n const cookieStore = await cookies();\n cookieStore.delete(GR_COOKIES.NEEDS_PASSWORD);\n revalidatePath(\"/\", \"layout\");\n return { success: true };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/actions.ts"],"sourcesContent":["\"use server\";\n\nimport { revalidatePath } from \"next/cache\";\nimport { cookies, headers } from \"next/headers\";\nimport {\n getAuth,\n getDB,\n getInviteHandler,\n getMembers,\n getPasswordResetHandler,\n getStorage,\n resolveSiteId,\n} from \"../core/config\";\nimport { getCachedViewer } from \"./session\";\nimport { requireRole } from \"./access\";\nimport { rethrowIfBailout } from \"../core/bailout\";\nimport {\n GR_AUTH_ERROR_TTL_SECONDS,\n GR_COOKIES,\n GR_NEEDS_PASSWORD_TTL_SECONDS,\n} from \"../core/defs\";\nimport type {\n ActionState,\n GRAsset,\n GRMember,\n GRRole,\n OverrideRow,\n OverrideType,\n OverrideValue,\n SetPasswordState,\n} from \"../core/types\";\nimport type {\n AuthRedirectParams,\n GRStorageRow,\n GRUploadTarget,\n} from \"../core/adapter-types\";\n\n/** `notice` is for a success the admin should still read, e.g. \"no email was sent because...\". */\ntype Result = { success: boolean; error?: string; notice?: string };\n\nconst DENIED: Result = { success: false, error: \"Unauthorized.\" };\n\n// Every mutation below re-resolves the viewer server-side through\n// requireRole() (see access.ts) — the client's copy of the role decides what\n// to *show*, never what is *allowed*.\n\nexport async function saveOverride(row: OverrideRow): Promise<Result> {\n const viewer = await requireRole([\"admin\", \"editor\"]);\n if (!viewer) return DENIED;\n return writeOverride(row.pathKey, row.value);\n}\n\nconst OVERRIDE_TYPES: readonly OverrideType[] = [\"text\", \"link\", \"image\", \"button\"];\n\n/**\n * Writes one field's value by id, with no <GRail> involved — the setter that\n * pairs with getFieldValue(). It exists for fields with nothing on the page to\n * click: an SEO title or meta description, an og:image, a product's stock\n * note. Build a drawer or form of your own, and have it (or a Server Action of\n * yours) call this.\n *\n * A bare string is shorthand for a text field. Pass an OverrideValue for the\n * other types ({ type: \"image\", src, alt }, a link, a button).\n *\n * Does its own role check — an editor or admin — so it is safe to call\n * directly from a Client Component, the same as saveOverride. If your own\n * action does other work first, check isEditor() there too.\n *\n * Writes the same row a <GRail> with this id would, so the two stay\n * interchangeable: a field first written here can later be given a visible\n * <GRail>, and vice versa.\n */\nexport async function setFieldValue(\n id: string,\n value: string | OverrideValue,\n): Promise<Result> {\n const viewer = await requireRole([\"admin\", \"editor\"]);\n if (!viewer) return DENIED;\n\n const pathKey = id.trim();\n if (!pathKey) return { success: false, error: \"A field id is required.\" };\n\n const normalized: OverrideValue =\n typeof value === \"string\" ? { type: \"text\", text: value } : value;\n if (!OVERRIDE_TYPES.includes(normalized.type)) {\n return { success: false, error: `Unknown field type \"${normalized.type}\".` };\n }\n\n return writeOverride(pathKey, normalized);\n}\n\n/** The write shared by saveOverride and setFieldValue; the caller has already authorized. */\nasync function writeOverride(\n pathKey: string,\n value: OverrideValue,\n): Promise<Result> {\n const rowToSend: GRStorageRow = {\n pathKey,\n siteId: await resolveSiteId(),\n type: value.type,\n value,\n };\n\n try {\n const db = getDB();\n const res = await db.setOverride(rowToSend);\n if (!res.success) return res;\n\n // TODO: too broad — purges every cached route on any single-field save.\n revalidatePath(\"/\", \"layout\");\n return { success: true };\n } catch (e) {\n rethrowIfBailout(e);\n console.error(\"Guardrail: save failed\", e);\n return { success: false, error: \"Database write failed.\" };\n }\n}\n\n/* --------------------------------------------------------------- media --- */\n\n// SVG is excluded on purpose: it can carry script, and next/image refuses it\n// by default anyway.\nconst IMAGE_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\", \"image/avif\"];\nconst MAX_UPLOAD_BYTES = 10 * 1024 * 1024;\n\n/**\n * Authorizes an upload and returns where the browser should send the bytes.\n * Only metadata comes through here — the file itself goes straight to storage.\n */\nexport async function createAssetUpload(file: {\n name: string;\n contentType: string;\n size: number;\n}): Promise<\n { success: true; target: GRUploadTarget } | { success: false; error: string }\n> {\n const viewer = await requireRole([\"admin\", \"editor\"]);\n if (!viewer) return { success: false, error: \"Unauthorized.\" };\n\n const storage = getStorage();\n if (!storage) {\n return { success: false, error: \"No storage adapter is configured.\" };\n }\n if (!IMAGE_TYPES.includes(file.contentType)) {\n return { success: false, error: \"Upload a PNG, JPEG, WebP, GIF, or AVIF image.\" };\n }\n if (!(file.size > 0) || file.size > MAX_UPLOAD_BYTES) {\n return { success: false, error: \"Images must be under 10 MB.\" };\n }\n\n try {\n const target = await storage.createUpload(await resolveSiteId(), file);\n return { success: true, target };\n } catch (e) {\n rethrowIfBailout(e);\n console.error(\"Guardrail: createUpload failed\", e);\n // Failures here are almost always setup (missing SQL, bucket, or policy),\n // so show the cause while developing. Production keeps it generic rather\n // than exposing backend detail to editors.\n const detail =\n process.env.NODE_ENV !== \"production\" && e instanceof Error\n ? ` ${e.message}`\n : \"\";\n return { success: false, error: `Could not start the upload.${detail}` };\n }\n}\n\nexport async function listAssets(): Promise<GRAsset[]> {\n const viewer = await requireRole([\"admin\", \"editor\"]);\n const storage = getStorage();\n if (!viewer || !storage) return [];\n return storage.listAssets(await resolveSiteId());\n}\n\n/* ---------------------------------------------------------------- team --- */\n\nexport async function listTeam(): Promise<GRMember[]> {\n // Any member may see who else is on the site; only admins may change it.\n const viewer = await requireRole([\"admin\", \"editor\"]);\n if (!viewer) return [];\n\n const members = getMembers();\n return members.listMembers(await resolveSiteId());\n}\n\nexport async function inviteMember(\n email: string,\n role: GRRole,\n): Promise<Result> {\n const viewer = await requireRole([\"admin\"]);\n if (!viewer) return DENIED;\n\n const normalized = email.toLowerCase().trim();\n if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(normalized)) {\n return { success: false, error: \"Enter a valid email address.\" };\n }\n\n const siteId = await resolveSiteId();\n const members = getMembers();\n\n const existing = await members.getMember(siteId, { email: normalized });\n if (existing?.status === \"active\") {\n return { success: false, error: \"That address is already on the team.\" };\n }\n\n // A pending invite is re-sent rather than rejected: provider email is\n // rate-limited and unreliable enough that resending has to be possible.\n if (!existing) {\n const res = await members.upsertMember(siteId, {\n email: normalized,\n role,\n userId: null,\n status: \"pending\",\n });\n if (!res.success) return res;\n }\n\n const auth = getAuth();\n const onInvite = getInviteHandler();\n const origin = await requestOrigin();\n\n // The membership is saved either way, so none of what follows can undo it;\n // each failure just says what to fix. Delivery has one shape: the adapter\n // creates the account and a link (if it can), the app's handler emails it.\n const made = auth.createInviteLink\n ? await auth.createInviteLink(normalized, { origin })\n : null;\n if (made && !made.success) {\n revalidatePath(\"/\", \"layout\");\n return {\n success: false,\n error: `Invite saved, but the sign-in link couldn't be created: ${made.error}`,\n };\n }\n if (!onInvite) {\n revalidatePath(\"/\", \"layout\");\n return {\n success: false,\n error:\n \"Invite saved, but this site has nothing set up to email it. Add resendEmails() (or your own onInvite) to withGuardrail() — see the README.\",\n };\n }\n\n try {\n await onInvite({\n email: normalized,\n role: existing?.role ?? role,\n siteId,\n invitedBy: viewer.email ?? viewer.userId,\n ...(made ? { link: made.link, isNewUser: made.isNewUser } : {}),\n });\n } catch (e) {\n console.error(\"Guardrail: onInvite handler threw\", e);\n revalidatePath(\"/\", \"layout\");\n return {\n success: false,\n error: `Invite saved, but the email failed: ${e instanceof Error ? e.message : \"unknown error\"}`,\n };\n }\n\n revalidatePath(\"/\", \"layout\");\n return made && !made.isNewUser\n ? { success: true, notice: ALREADY_HAS_ACCOUNT(normalized) }\n : { success: true };\n}\n\nconst ALREADY_HAS_ACCOUNT = (email: string) =>\n `${email} already has an account, so they were told they've been added rather than sent a link — they sign in with their existing password.`;\n\nexport async function updateMemberRole(\n email: string,\n role: GRRole,\n): Promise<Result> {\n const viewer = await requireRole([\"admin\"]);\n if (!viewer) return DENIED;\n\n const siteId = await resolveSiteId();\n const members = getMembers();\n\n const target = await members.getMember(siteId, {\n email: email.toLowerCase().trim(),\n });\n if (!target) return { success: false, error: \"No such member.\" };\n\n if (target.role === \"admin\" && role !== \"admin\") {\n const guard = await wouldStrandSite(siteId, target.email);\n if (guard) return guard;\n }\n\n const res = await members.upsertMember(siteId, {\n email: target.email,\n role,\n userId: target.userId,\n status: target.status,\n });\n if (res.success) revalidatePath(\"/\", \"layout\");\n return res;\n}\n\nexport async function removeMember(email: string): Promise<Result> {\n const viewer = await requireRole([\"admin\"]);\n if (!viewer) return DENIED;\n\n const siteId = await resolveSiteId();\n const members = getMembers();\n const normalized = email.toLowerCase().trim();\n\n const guard = await wouldStrandSite(siteId, normalized);\n if (guard) return guard;\n\n const res = await members.removeMember(siteId, normalized);\n if (res.success) revalidatePath(\"/\", \"layout\");\n return res;\n}\n\n/* ------------------------------------------------------ password reset --- */\n\n/** One reset email per address per site per minute, so the form can't be used to spam someone. */\nconst RESET_COOLDOWN_MS = 60_000;\nconst lastResetAt = new Map<string, number>();\n\n/** Floor for a self-service request, so how long it takes doesn't reveal whether the address is a member. */\nconst RESET_MIN_DURATION_MS = 600;\n\nconst EMAIL_PATTERN = /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/;\n\n/**\n * Sends one password-reset email: the adapter mints a one-time link and the\n * app's onPasswordReset handler emails it. If either is missing, says which —\n * there is no quieter fallback to hide behind.\n */\nasync function dispatchPasswordReset(email: string, requestedBy: string): Promise<Result> {\n const auth = getAuth();\n const handler = getPasswordResetHandler();\n\n if (!auth.createPasswordResetLink) {\n return {\n success: false,\n error: \"this site's auth setup can't create reset links — give the Supabase adapter a serviceRoleKey.\",\n };\n }\n if (!handler) {\n return {\n success: false,\n error: \"this site has no onPasswordReset handler — add resendEmails() (or your own) to withGuardrail().\",\n };\n }\n\n const made = await auth.createPasswordResetLink(email, { origin: await requestOrigin() });\n if (!made.success) return { success: false, error: made.error };\n try {\n await handler({ email, siteId: await resolveSiteId(), link: made.link, requestedBy });\n return { success: true };\n } catch (e) {\n console.error(\"Guardrail: onPasswordReset handler threw\", e);\n return { success: false, error: e instanceof Error ? e.message : \"unknown error\" };\n }\n}\n\n/**\n * \"Forgot password?\" from the sign-in bar. Called with no session, so it says\n * the same thing whether or not the address belongs to anyone: a member gets an\n * email, a stranger gets nothing, and neither can tell which happened from the\n * response or how long it took. Only a malformed address is rejected, since\n * that says nothing about anyone.\n */\nexport async function forgotPasswordAction(email: string): Promise<Result> {\n const started = Date.now();\n const normalized = String(email ?? \"\").toLowerCase().trim();\n if (!EMAIL_PATTERN.test(normalized)) {\n return { success: false, error: \"Enter a valid email address.\" };\n }\n\n try {\n const siteId = await resolveSiteId();\n const key = `${siteId}:${normalized}`;\n const last = lastResetAt.get(key);\n if (!last || started - last >= RESET_COOLDOWN_MS) {\n lastResetAt.set(key, started);\n if (lastResetAt.size > 500) {\n for (const [k, t] of lastResetAt) if (started - t >= RESET_COOLDOWN_MS) lastResetAt.delete(k);\n }\n\n // With a privileged membership check, only members are sent anything.\n // Without one (an adapter that can mint reset links but not look members\n // up) any address that asks is — which is why the Supabase adapter offers\n // both together.\n const members = getMembers();\n const known = members.emailHasAccess ? await members.emailHasAccess(siteId, normalized) : true;\n if (known) {\n const res = await dispatchPasswordReset(normalized, \"self\");\n if (!res.success) console.error(\"Guardrail: password reset failed:\", res.error);\n }\n }\n } catch (e) {\n rethrowIfBailout(e);\n console.error(\"Guardrail: password reset failed\", e);\n }\n\n const wait = RESET_MIN_DURATION_MS - (Date.now() - started);\n if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));\n return { success: true };\n}\n\n/**\n * An admin sending a password reset to someone on the team, from the Team\n * drawer. Unlike the self-service request this reports what happened — an admin\n * is allowed to know, and needs to when it didn't work.\n */\nexport async function sendPasswordResetAction(email: string): Promise<Result> {\n const viewer = await requireRole([\"admin\"]);\n if (!viewer) return DENIED;\n\n const normalized = String(email ?? \"\").toLowerCase().trim();\n const target = await getMembers().getMember(await resolveSiteId(), { email: normalized });\n if (!target) return { success: false, error: \"No such member.\" };\n\n const res = await dispatchPasswordReset(normalized, viewer.email ?? viewer.userId);\n return res.success\n ? { success: true, notice: `Password reset email sent to ${normalized}.` }\n : { success: false, error: `The reset email couldn't be sent: ${res.error}` };\n}\n\nasync function requestOrigin(): Promise<string> {\n const h = await headers();\n const origin = h.get(\"origin\");\n if (origin) return origin;\n const host = h.get(\"x-forwarded-host\") ?? h.get(\"host\") ?? \"localhost\";\n const proto = h.get(\"x-forwarded-proto\") ?? (host.startsWith(\"localhost\") ? \"http\" : \"https\");\n return `${proto}://${host}`;\n}\n\n/**\n * Refuses changes that would leave a site with no admin, which would lock\n * everyone out of team management with no in-app way to recover.\n */\nasync function wouldStrandSite(\n siteId: string,\n email: string,\n): Promise<Result | null> {\n const members = getMembers();\n const all = await members.listMembers(siteId);\n const admins = all.filter((m) => m.role === \"admin\");\n\n if (admins.length <= 1 && admins.some((m) => m.email === email)) {\n return {\n success: false,\n error: \"A site must keep at least one admin.\",\n };\n }\n return null;\n}\n\n/* ---------------------------------------------------------------- auth --- */\n\nexport async function loginAction(\n _prev: ActionState,\n formData: FormData,\n): Promise<ActionState> {\n const email = String(formData.get(\"email\") ?? \"\");\n const password = String(formData.get(\"password\") ?? \"\");\n\n const auth = getAuth();\n if (!auth.authenticate) {\n return {\n success: false,\n error: \"This site's auth provider does not accept passwords.\",\n email,\n };\n }\n\n const result = await auth.authenticate(email, password);\n if (!result.success) {\n return { success: false, error: result.error, email };\n }\n\n revalidatePath(\"/\", \"layout\");\n return { success: true, email };\n}\n\nexport async function logoutAction(): Promise<{ success: boolean }> {\n const auth = getAuth();\n await auth.logout();\n // Cleared here, not by the client afterward: a Server Action's own\n // revalidatePath() below triggers Next's router to refresh the current\n // route automatically, which can race a client-side navigation attempted\n // right after this resolves — confirmed live, where that automatic\n // refresh (for the pre-logout URL, ?edit=true) won the race and silently\n // undid a client-side `window.location.href` to the plain path. Deleting\n // the cookie here instead makes it part of the one server round-trip that\n // revalidatePath already accounts for, with nothing left to race.\n const cookieStore = await cookies();\n cookieStore.delete(GR_COOKIES.EDIT_MODE);\n // Signing out ends whatever was pending for that session too. A leftover\n // \"needs a password\" flag used to survive this and keep the sign-in bar\n // pinned on screen.\n cookieStore.delete(GR_COOKIES.NEEDS_PASSWORD);\n cookieStore.delete(GR_COOKIES.AUTH_ERROR);\n revalidatePath(\"/\", \"layout\");\n return { success: true };\n}\n\n/* ---------------------------------------------------------- redirects --- */\n\n/**\n * Completes a session that arrived via an invite or password-recovery link. Called by AuthRedirectSync (react/), the\n * generic client component that notices this kind of link and hands off\n * whatever it found in the URL — the actual token exchange is entirely the\n * auth adapter's job (see GRAuthAdapter.completeAuthRedirect); core only\n * orchestrates the result.\n *\n * A no-op {success:false}, not an error, when the adapter doesn't implement\n * this — most page loads carry no redirect data at all, and an adapter with\n * no such concept should not make every one of them fail.\n */\nexport async function completeAuthRedirectAction(\n params: AuthRedirectParams,\n): Promise<{ success: boolean; needsPassword?: boolean; error?: string }> {\n const auth = getAuth();\n if (!auth.completeAuthRedirect) return { success: false };\n\n const res = await auth.completeAuthRedirect(params);\n const cookieStore = await cookies();\n if (res.success) {\n cookieStore.delete(GR_COOKIES.AUTH_ERROR);\n if (res.needsPassword) {\n cookieStore.set(GR_COOKIES.NEEDS_PASSWORD, \"true\", {\n path: \"/\",\n maxAge: GR_NEEDS_PASSWORD_TTL_SECONDS,\n });\n }\n revalidatePath(\"/\", \"layout\");\n } else {\n // Say so, rather than leaving the person on a plain page with nothing\n // visibly different. The provider's own wording stays in the server log; the\n // cookie carries only a code, which the chrome turns into plain English.\n console.error(\"Guardrail: couldn't complete an emailed link:\", res.error);\n cookieStore.set(\n GR_COOKIES.AUTH_ERROR,\n /expired|invalid|already|used|not found/i.test(res.error ?? \"\") ? \"expired\" : \"failed\",\n { path: \"/\", maxAge: GR_AUTH_ERROR_TTL_SECONDS },\n );\n revalidatePath(\"/\", \"layout\");\n }\n return res;\n}\n\n/**\n * Sets the current session's password — the prompt completeAuthRedirectAction\n * triggers via needsPassword. Requires only a valid session, not a role: an\n * invited person's role is claimed as a side effect of getCachedViewer()\n * resolving them, which already happens independently of this.\n */\nexport async function setPasswordAction(\n _prev: SetPasswordState,\n formData: FormData,\n): Promise<SetPasswordState> {\n const viewer = await getCachedViewer();\n if (!viewer) return { success: false, error: \"Not signed in.\" };\n\n const auth = getAuth();\n if (!auth.setPassword) {\n return { success: false, error: \"This site's auth provider does not support setting a password.\" };\n }\n\n const password = String(formData.get(\"password\") ?? \"\");\n // `confirm` is absent from older custom forms, so only checked when sent.\n const confirm = formData.get(\"confirm\");\n if (password.length < 8) {\n return { success: false, error: \"Use at least 8 characters.\" };\n }\n if (confirm !== null && String(confirm) !== password) {\n return { success: false, error: \"The two passwords don't match.\" };\n }\n const res = await auth.setPassword(password);\n if (res.success) {\n const cookieStore = await cookies();\n cookieStore.delete(GR_COOKIES.NEEDS_PASSWORD);\n revalidatePath(\"/\", \"layout\");\n }\n return res;\n}\n\n/**\n * Dismisses the set-password step without setting a password.\n *\n * @deprecated Guardrail's own UI no longer offers this: passwords are the only\n * way back in, so the step can't be skipped. Kept so an app's own UI built on\n * it keeps working.\n */\nexport async function skipSetPasswordAction(): Promise<{ success: boolean }> {\n const cookieStore = await cookies();\n cookieStore.delete(GR_COOKIES.NEEDS_PASSWORD);\n revalidatePath(\"/\", \"layout\");\n return { success: true };\n}\n"],"mappings":"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","names":[]}
|
package/dist/server/wrapper.js
CHANGED
|
@@ -23,7 +23,7 @@ function withGuardrail(LayoutComponent, config) {
|
|
|
23
23
|
]);
|
|
24
24
|
const auth = getAuth();
|
|
25
25
|
const isAuth = viewer !== null;
|
|
26
|
-
const { isEditMode, needsPassword } = resolveEditMode(viewer, cookieStore);
|
|
26
|
+
const { isEditMode, needsPassword, authError } = resolveEditMode(viewer, cookieStore);
|
|
27
27
|
const role = viewer?.role ?? null;
|
|
28
28
|
const state = {
|
|
29
29
|
storage: getStorage() !== void 0,
|
|
@@ -34,6 +34,7 @@ function withGuardrail(LayoutComponent, config) {
|
|
|
34
34
|
isAuth,
|
|
35
35
|
isEditMode,
|
|
36
36
|
needsPassword,
|
|
37
|
+
...authError ? { authError } : {},
|
|
37
38
|
siteId: await resolveSiteId(),
|
|
38
39
|
role,
|
|
39
40
|
canEdit: isEditMode && role !== null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/wrapper.tsx"],"sourcesContent":["import React, { Suspense } from \"react\";\nimport { cookies } from \"next/headers\";\nimport {\n configureGuardrail,\n getAuth,\n getStorage,\n resolveSiteId,\n} from \"../core/config\";\nimport { getCachedViewer } from \"./session\";\nimport { resolveEditMode } from \"./access\";\nimport { loadPreloadedContent } from \"./loader\";\nimport type { GRConfig } from \"../core/adapter-types\";\nimport { GRailProvider } from \"../react/Provider\";\nimport { GRailAdmin } from \"../react/Admin\";\nimport type { GRState } from \"../core/types\";\n\n/**\n * Wraps a root layout so every route carries Guardrail's edit state and admin\n * chrome. Stays a Server Component: the provider below is the client boundary.\n */\nexport function withGuardrail<P extends object>(\n LayoutComponent: React.ComponentType<P>,\n config: GRConfig,\n) {\n // Applied at module evaluation so the config is present as soon as anything\n // imports the layout, and again per render because Next.js does not\n // guarantee one shared module instance across its server contexts.\n configureGuardrail(config);\n\n return async function GuardrailLayout(props: P) {\n configureGuardrail(config);\n\n const cookieStore = await cookies();\n\n // Verified against the auth provider, not inferred from a cookie. Loaded\n // alongside the content preload: independent round trips, and the preload\n // is shared with the page's own fields, so it costs them nothing extra.\n const [viewer, content] = await Promise.all([\n getCachedViewer(),\n loadPreloadedContent(),\n ]);\n\n const auth = getAuth();\n\n // The rule for what counts as \"in edit mode\" lives in resolveEditMode —\n // shared with canEdit() so the layout and the server helper can't disagree.\n const isAuth = viewer !== null;\n const { isEditMode, needsPassword } = resolveEditMode(viewer, cookieStore);\n const role = viewer?.role ?? null;\n\n const state: GRState = {\n storage: getStorage() !== undefined,\n auth: {\n password: typeof auth.authenticate === \"function\",\n ...(auth.signInUrl ? { signInUrl: auth.signInUrl } : {}),\n },\n isAuth,\n isEditMode,\n needsPassword,\n siteId: await resolveSiteId(),\n role,\n canEdit: isEditMode && role !== null,\n ...(viewer?.email ? { email: viewer.email } : {}),\n };\n\n return (\n <GRailProvider state={state} content={content}>\n <LayoutComponent {...props} />\n <Suspense fallback={null}>\n <GRailAdmin toolbarExtra={config.toolbarExtra} />\n </Suspense>\n </GRailProvider>\n );\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/server/wrapper.tsx"],"sourcesContent":["import React, { Suspense } from \"react\";\nimport { cookies } from \"next/headers\";\nimport {\n configureGuardrail,\n getAuth,\n getStorage,\n resolveSiteId,\n} from \"../core/config\";\nimport { getCachedViewer } from \"./session\";\nimport { resolveEditMode } from \"./access\";\nimport { loadPreloadedContent } from \"./loader\";\nimport type { GRConfig } from \"../core/adapter-types\";\nimport { GRailProvider } from \"../react/Provider\";\nimport { GRailAdmin } from \"../react/Admin\";\nimport type { GRState } from \"../core/types\";\n\n/**\n * Wraps a root layout so every route carries Guardrail's edit state and admin\n * chrome. Stays a Server Component: the provider below is the client boundary.\n */\nexport function withGuardrail<P extends object>(\n LayoutComponent: React.ComponentType<P>,\n config: GRConfig,\n) {\n // Applied at module evaluation so the config is present as soon as anything\n // imports the layout, and again per render because Next.js does not\n // guarantee one shared module instance across its server contexts.\n configureGuardrail(config);\n\n return async function GuardrailLayout(props: P) {\n configureGuardrail(config);\n\n const cookieStore = await cookies();\n\n // Verified against the auth provider, not inferred from a cookie. Loaded\n // alongside the content preload: independent round trips, and the preload\n // is shared with the page's own fields, so it costs them nothing extra.\n const [viewer, content] = await Promise.all([\n getCachedViewer(),\n loadPreloadedContent(),\n ]);\n\n const auth = getAuth();\n\n // The rule for what counts as \"in edit mode\" lives in resolveEditMode —\n // shared with canEdit() so the layout and the server helper can't disagree.\n const isAuth = viewer !== null;\n const { isEditMode, needsPassword, authError } = resolveEditMode(viewer, cookieStore);\n const role = viewer?.role ?? null;\n\n const state: GRState = {\n storage: getStorage() !== undefined,\n auth: {\n password: typeof auth.authenticate === \"function\",\n ...(auth.signInUrl ? { signInUrl: auth.signInUrl } : {}),\n },\n isAuth,\n isEditMode,\n needsPassword,\n ...(authError ? { authError } : {}),\n siteId: await resolveSiteId(),\n role,\n canEdit: isEditMode && role !== null,\n ...(viewer?.email ? { email: viewer.email } : {}),\n };\n\n return (\n <GRailProvider state={state} content={content}>\n <LayoutComponent {...props} />\n <Suspense fallback={null}>\n <GRailAdmin toolbarExtra={config.toolbarExtra} />\n </Suspense>\n </GRailProvider>\n );\n };\n}\n"],"mappings":"AAmEM,SACE,KADF;AAnEN,OAAO,SAAS,gBAAgB;AAChC,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,4BAA4B;AAErC,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAOpB,SAAS,cACd,iBACA,QACA;AAIA,qBAAmB,MAAM;AAEzB,SAAO,eAAe,gBAAgB,OAAU;AAC9C,uBAAmB,MAAM;AAEzB,UAAM,cAAc,MAAM,QAAQ;AAKlC,UAAM,CAAC,QAAQ,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC1C,gBAAgB;AAAA,MAChB,qBAAqB;AAAA,IACvB,CAAC;AAED,UAAM,OAAO,QAAQ;AAIrB,UAAM,SAAS,WAAW;AAC1B,UAAM,EAAE,YAAY,eAAe,UAAU,IAAI,gBAAgB,QAAQ,WAAW;AACpF,UAAM,OAAO,QAAQ,QAAQ;AAE7B,UAAM,QAAiB;AAAA,MACrB,SAAS,WAAW,MAAM;AAAA,MAC1B,MAAM;AAAA,QACJ,UAAU,OAAO,KAAK,iBAAiB;AAAA,QACvC,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,MACxD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,MACjC,QAAQ,MAAM,cAAc;AAAA,MAC5B;AAAA,MACA,SAAS,cAAc,SAAS;AAAA,MAChC,GAAI,QAAQ,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;AAAA,IACjD;AAEA,WACE,qBAAC,iBAAc,OAAc,SAC3B;AAAA,0BAAC,mBAAiB,GAAG,OAAO;AAAA,MAC5B,oBAAC,YAAS,UAAU,MAClB,8BAAC,cAAW,cAAc,OAAO,cAAc,GACjD;AAAA,OACF;AAAA,EAEJ;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guard-rail/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Lightweight inline content editing for developer-owned Next.js sites.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"type": "module",
|
|
@@ -38,14 +38,6 @@
|
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "tsup",
|
|
43
|
-
"dev": "tsup --watch",
|
|
44
|
-
"typecheck": "tsc --noEmit",
|
|
45
|
-
"prepare": "npm run build",
|
|
46
|
-
"prepublishOnly": "npm run build",
|
|
47
|
-
"postinstall": "node scripts/postinstall.mjs"
|
|
48
|
-
},
|
|
49
41
|
"peerDependencies": {
|
|
50
42
|
"@supabase/ssr": "^0.8.0",
|
|
51
43
|
"@supabase/supabase-js": "^2.0.0",
|
|
@@ -70,5 +62,11 @@
|
|
|
70
62
|
"react": "^19.2.4",
|
|
71
63
|
"tsup": "^8.5.1",
|
|
72
64
|
"typescript": "^5.9.3"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "tsup",
|
|
68
|
+
"dev": "tsup --watch",
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
73
71
|
}
|
|
74
|
-
}
|
|
72
|
+
}
|