@realiizlabs/admin 0.10.0 → 0.10.1
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/auth/index.cjs +5 -2
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +4 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.js +5 -2
- package/dist/auth/index.js.map +1 -1
- package/dist/shell/index.cjs +9 -9
- package/dist/shell/index.cjs.map +1 -1
- package/dist/shell/index.js +9 -9
- package/dist/shell/index.js.map +1 -1
- package/package.json +1 -1
package/dist/auth/index.cjs
CHANGED
|
@@ -248,13 +248,16 @@ var MIN_PASSWORD_LENGTH = 10;
|
|
|
248
248
|
var PASSWORD_TOO_SHORT = "Use at least 10 characters.";
|
|
249
249
|
function profileOf(user) {
|
|
250
250
|
const m = user?.user_metadata ?? {};
|
|
251
|
-
const
|
|
251
|
+
const str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
252
|
+
const name = str(m.name);
|
|
253
|
+
const displayName = str(m.display_name) ?? name;
|
|
252
254
|
const avatarUrl = typeof m.avatar_url === "string" && m.avatar_url ? m.avatar_url : null;
|
|
253
|
-
return { name, avatarUrl };
|
|
255
|
+
return { name, displayName, avatarUrl };
|
|
254
256
|
}
|
|
255
257
|
async function updateProfile(client, patch) {
|
|
256
258
|
const data = {};
|
|
257
259
|
if (patch.name !== void 0) data.name = (patch.name ?? "").trim().slice(0, 80);
|
|
260
|
+
if (patch.displayName !== void 0) data.display_name = (patch.displayName ?? "").trim().slice(0, 80);
|
|
258
261
|
if (patch.avatarUrl !== void 0) data.avatar_url = patch.avatarUrl ?? "";
|
|
259
262
|
const { data: res, error } = await client.auth.updateUser({ data });
|
|
260
263
|
if (error) throw new IdentityError(error.message);
|
package/dist/auth/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/client.ts","../../src/auth/session.ts","../../src/auth/errors.ts","../../src/auth/types.ts","../../src/auth/roles.ts","../../src/auth/callback.ts","../../src/auth/signout.ts","../../src/auth/service.ts","../../src/auth/branding.ts","../../src/auth/members.ts","../../src/auth/profile.ts"],"names":["createServerClient","createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * createIdentityClient — a Supabase client bound to the request's cookies.\n *\n * Built per request from arguments. A Next 16 route does:\n *\n * const store = await cookies();\n * const client = createIdentityClient({ url, anonKey, cookies: store });\n *\n * No `next` import here. The CookieStore interface accepts either shape a\n * host might hand us: @supabase/ssr's { getAll, setAll }, or Next's\n * cookies() store, which has { getAll, set } — the session cookies are\n * written through whichever exists. (Observed 2026-09-08: with only setAll\n * supported, Next's store silently wrote nothing and every sign-in bounced.)\n */\n\nimport { createServerClient } from \"@supabase/ssr\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { IdentityContext } from \"./types\";\n\nexport type IdentityClient = SupabaseClient;\n\ntype CookieToSet = { name: string; value: string; options?: Record<string, unknown> };\n\nexport function createIdentityClient(ctx: IdentityContext): IdentityClient {\n const store = ctx.cookies;\n return createServerClient(ctx.url, ctx.anonKey, {\n cookies: {\n getAll: () => store.getAll(),\n setAll: (list: CookieToSet[]) => {\n // Server Components can't set cookies; @supabase/ssr documents that\n // swallowing the failure there is correct — middleware / route\n // handlers refresh the session instead.\n try {\n if (typeof store.setAll === \"function\") {\n void store.setAll(list);\n } else if (typeof store.set === \"function\") {\n for (const c of list) store.set(c.name, c.value, c.options);\n }\n } catch {\n /* read-only cookie store */\n }\n },\n },\n });\n}\n","/**\n * getCurrentUser — who is signed in, verified against the auth server.\n *\n * `getUser()` round-trips to Supabase and validates the JWT; `getSession()`\n * only decodes the cookie and is not trustworthy on the server. Always this.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport type { IdentityUser } from \"./types\";\n\nexport async function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null> {\n const { data, error } = await client.auth.getUser();\n if (error || !data.user) return null;\n return { id: data.user.id, email: data.user.email ?? null };\n}\n","/** Typed errors so a route can map them to 401 / 403 without string matching. */\n\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"IdentityError\";\n }\n}\n\n/** No session — send them to sign in. Maps to 401. */\nexport class UnauthenticatedError extends IdentityError {\n constructor() {\n super(\"Not signed in\");\n this.name = \"UnauthenticatedError\";\n }\n}\n\n/** Signed in, but not allowed to do this here. Maps to 403. */\nexport class ForbiddenError extends IdentityError {\n readonly siteId: string;\n readonly required: string;\n readonly actual: string | null;\n\n constructor(siteId: string, required: string, actual: string | null) {\n super(`Requires ${required} on site \"${siteId}\" (you are ${actual ?? \"not a member\"})`);\n this.name = \"ForbiddenError\";\n this.siteId = siteId;\n this.required = required;\n this.actual = actual;\n }\n}\n","/**\n * Types for the identity layer. All config arrives as arguments — never env.\n */\n\n/**\n * Either cookie-store shape a host can hand us:\n * - @supabase/ssr's CookieMethodsServer: { getAll, setAll }\n * - Next's `await cookies()` store: { getAll, set }\n * `setAll` is preferred when present; otherwise each cookie goes through `set`.\n */\nexport interface CookieStore {\n getAll(): { name: string; value: string }[] | Promise<{ name: string; value: string }[]>;\n setAll?(cookies: { name: string; value: string; options?: Record<string, unknown> }[]): void | Promise<void>;\n set?(name: string, value: string, options?: Record<string, unknown>): unknown;\n}\n\n/** Enough to read the signed-in user with the anon key + their cookies. */\nexport interface IdentityContext {\n url: string;\n anonKey: string;\n cookies: CookieStore;\n}\n\n/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */\nexport interface ServiceContext {\n url: string;\n serviceRoleKey: string;\n}\n\nexport type SiteRole = \"staff\" | \"owner\" | \"editor\";\n\nexport const ROLE_RANK: Record<SiteRole, number> = { editor: 1, owner: 2, staff: 3 };\n\nexport interface IdentityUser {\n id: string;\n email: string | null;\n}\n\nexport interface SiteMembership {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n}\n","/**\n * Role resolution and the request gate.\n *\n * Two selects on the caller's OWN rows — both permitted by RLS with the anon\n * key, so this never needs the service role. Staff wins over any site role.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError, UnauthenticatedError } from \"./errors\";\nimport { getCurrentUser } from \"./session\";\nimport { ROLE_RANK, type IdentityUser, type SiteRole } from \"./types\";\n\nexport async function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null> {\n const uid = userId ?? (await getCurrentUser(client))?.id;\n if (!uid) return null;\n\n const [staff, member] = await Promise.all([\n client.from(\"staff\").select(\"user_id\").eq(\"user_id\", uid).maybeSingle(),\n client.from(\"site_users\").select(\"role\").eq(\"user_id\", uid).eq(\"site_id\", siteId).maybeSingle(),\n ]);\n\n if (staff.error) throw staff.error;\n if (member.error) throw member.error;\n\n if (staff.data) return \"staff\";\n const role = (member.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nexport function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean {\n return actual !== null && ROLE_RANK[actual] >= ROLE_RANK[minimum];\n}\n\nexport interface RequireOptions {\n /** Default \"editor\" — any member of the site. */\n minimumRole?: SiteRole;\n}\n\n/**\n * The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);\n * otherwise returns the user and their effective role for this site.\n */\nexport async function requireSiteUser(\n client: IdentityClient,\n siteId: string,\n opts: RequireOptions = {},\n): Promise<{ user: IdentityUser; role: SiteRole }> {\n const user = await getCurrentUser(client);\n if (!user) throw new UnauthenticatedError();\n\n const minimum = opts.minimumRole ?? \"editor\";\n const role = await getSiteRole(client, siteId, user.id);\n if (!roleAtLeast(role, minimum)) throw new ForbiddenError(siteId, minimum, role);\n\n return { user, role: role as SiteRole };\n}\n","/**\n * handleAuthCallback — the emailed magic link lands here with ?code=…&next=…\n *\n * Exchanges the PKCE code for a session (cookies are written through the\n * adapter the client was built with) and says where to send the user. The\n * `next` parameter is only honoured when it is a plain same-origin path —\n * \"//evil.example\" and \"https://…\" fall back, which closes the open-redirect\n * a naive startsWith(\"/\") check leaves open.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport type CallbackResult =\n | { ok: true; redirectTo: string }\n | { ok: false; reason: \"missing_code\" | \"exchange_failed\"; message?: string };\n\nexport interface CallbackOptions {\n /** Where to go when `next` is absent or unsafe. Default \"/admin\". */\n fallbackPath?: string;\n}\n\nexport function safeNextPath(next: string | null | undefined, fallback: string): string {\n if (!next) return fallback;\n // Exactly one leading slash, then not another slash or backslash.\n if (!/^\\/(?![/\\\\])/.test(next)) return fallback;\n // No scheme smuggling or CR/LF.\n if (/[\\r\\n]/.test(next) || /^\\/[^?#]*:\\/\\//.test(next)) return fallback;\n return next;\n}\n\nexport async function handleAuthCallback(\n client: IdentityClient,\n requestUrl: string | URL,\n opts: CallbackOptions = {},\n): Promise<CallbackResult> {\n const fallback = opts.fallbackPath ?? \"/admin\";\n const url = typeof requestUrl === \"string\" ? new URL(requestUrl, \"http://placeholder.invalid\") : requestUrl;\n const code = url.searchParams.get(\"code\");\n if (!code) return { ok: false, reason: \"missing_code\" };\n\n const { error } = await client.auth.exchangeCodeForSession(code);\n if (error) return { ok: false, reason: \"exchange_failed\", message: error.message };\n\n return { ok: true, redirectTo: safeNextPath(url.searchParams.get(\"next\"), fallback) };\n}\n","/** signOut — clears this browser's session. The caller redirects afterwards. */\n\nimport type { IdentityClient } from \"./client\";\n\nexport async function signOut(client: IdentityClient): Promise<void> {\n const { error } = await client.auth.signOut({ scope: \"local\" });\n if (error) throw error;\n}\n","/**\n * The only file that touches the service-role key. Invite and remove.\n *\n * Authorization is checked HERE, in code, before the privileged client does\n * anything — RLS doesn't apply to the service role, so this is the guard:\n * staff → may grant owner or editor on any site\n * owner → may grant editor on their own site only\n * editor → may not invite\n *\n * `actor` is the caller's already-resolved identity (from requireSiteUser on\n * the anon client). Its role is re-resolved with the service client so a\n * caller can't lie about it.\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { ForbiddenError, IdentityError } from \"./errors\";\nimport type { ServiceContext, SiteRole } from \"./types\";\n\nexport type ServiceClient = SupabaseClient;\n\nexport function createServiceClient(ctx: ServiceContext): ServiceClient {\n return createClient(ctx.url, ctx.serviceRoleKey, {\n auth: { persistSession: false, autoRefreshToken: false },\n });\n}\n\nexport interface InviteInput {\n email: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** The signed-in user performing the invite. */\n actor: { id: string };\n /** Where the invite email's link lands — the site's /admin/auth/callback. */\n redirectTo: string;\n}\n\nexport interface InviteResult {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** false when the auth user already existed and only the membership was added. */\n invitedByEmail: boolean;\n}\n\nasync function resolveActorRole(svc: ServiceClient, actorId: string, siteId: string): Promise<SiteRole | null> {\n const staff = await svc.from(\"staff\").select(\"user_id\").eq(\"user_id\", actorId).maybeSingle();\n if (staff.error) throw staff.error;\n if (staff.data) return \"staff\";\n const m = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", actorId).eq(\"site_id\", siteId).maybeSingle();\n if (m.error) throw m.error;\n const role = (m.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nfunction assertMayGrant(actorRole: SiteRole | null, siteId: string, granting: \"owner\" | \"editor\"): void {\n if (actorRole === \"staff\") return;\n if (actorRole === \"owner\" && granting === \"editor\") return;\n throw new ForbiddenError(siteId, granting === \"owner\" ? \"staff\" : \"owner\", actorRole);\n}\n\nexport async function inviteUser(ctx: ServiceContext, input: InviteInput): Promise<InviteResult> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const email = input.email.trim().toLowerCase();\n let userId: string | undefined;\n let invitedByEmail = false;\n\n const invite = await svc.auth.admin.inviteUserByEmail(email, {\n redirectTo: input.redirectTo,\n data: { site_id: input.siteId, role: input.role },\n });\n\n if (!invite.error) {\n userId = invite.data.user.id;\n invitedByEmail = true;\n } else if (/already|exists|registered/i.test(invite.error.message)) {\n // Existing auth user (works for another site, or is staff): just add the row.\n userId = await findUserIdByEmail(svc, email);\n if (!userId) throw new IdentityError(`Could not find existing user ${email}`);\n } else {\n throw new IdentityError(`Invite failed: ${invite.error.message}`);\n }\n\n const upsert = await svc\n .from(\"site_users\")\n .upsert({ user_id: userId, site_id: input.siteId, role: input.role, invited_by: input.actor.id }, { onConflict: \"user_id,site_id\" });\n if (upsert.error) throw upsert.error;\n\n return { userId, siteId: input.siteId, role: input.role, invitedByEmail };\n}\n\nasync function findUserIdByEmail(svc: ServiceClient, email: string): Promise<string | undefined> {\n // listUsers is paged; identity holds tens of users, not thousands.\n for (let page = 1; page <= 20; page++) {\n const { data, error } = await svc.auth.admin.listUsers({ page, perPage: 200 });\n if (error) throw error;\n const hit = data.users.find((u) => u.email?.toLowerCase() === email);\n if (hit) return hit.id;\n if (data.users.length < 200) break;\n }\n return undefined;\n}\n\nexport interface RemoveInput {\n userId: string;\n siteId: string;\n actor: { id: string };\n}\n\n/** Removes the membership only. The auth user survives (they may belong to other sites). */\nexport async function removeSiteUser(ctx: ServiceContext, input: RemoveInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const targetRole = ((target.data as { role?: string } | null)?.role ?? \"editor\") as \"owner\" | \"editor\";\n assertMayGrant(actorRole, input.siteId, targetRole);\n if (targetRole === \"owner\") await assertNotLastOwner(svc, input.siteId);\n\n const del = await svc.from(\"site_users\").delete().eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (del.error) throw del.error;\n}\n\nexport interface RoleInput {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n actor: { id: string };\n}\n\n/**\n * Change a member's role. Same grant rules as invite (staff anything; an owner\n * may only make people editors on their own site), plus: the last owner of a\n * site can never be demoted, otherwise nobody could manage it.\n */\nexport async function updateSiteRole(ctx: ServiceContext, input: RoleInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const current = (target.data as { role?: string } | null)?.role;\n if (!current) throw new IdentityError(\"That person isn't a member of this site.\");\n // Demoting an owner is an owner-level act — an owner may not do it to a peer.\n if (current === \"owner\") assertMayGrant(actorRole, input.siteId, \"owner\");\n if (current === \"owner\" && input.role === \"editor\") await assertNotLastOwner(svc, input.siteId);\n\n const upd = await svc.from(\"site_users\").update({ role: input.role }).eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (upd.error) throw upd.error;\n}\n\nexport async function assertNotLastOwner(svc: ServiceClient, siteId: string): Promise<void> {\n const owners = await svc.from(\"site_users\").select(\"user_id\").eq(\"site_id\", siteId).eq(\"role\", \"owner\");\n if (owners.error) throw owners.error;\n if ((owners.data ?? []).length <= 1) throw new IdentityError(\"A site needs at least one owner — make someone else an owner first.\");\n}\n\nexport interface ResendInput {\n email: string;\n siteId: string;\n actor: { id: string };\n redirectTo: string;\n}\n\n/** Send the invite email again. An already-active user gets no email — they can just sign in. */\nexport async function resendInvite(ctx: ServiceContext, input: ResendInput): Promise<{ resent: boolean; message: string }> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, \"editor\");\n\n const email = input.email.trim().toLowerCase();\n const again = await svc.auth.admin.inviteUserByEmail(email, { redirectTo: input.redirectTo, data: { site_id: input.siteId } });\n if (!again.error) return { resent: true, message: `Invitation sent again to ${email}.` };\n if (/already|exists|registered|confirmed/i.test(again.error.message)) {\n return { resent: false, message: `${email} has already accepted — they can sign in with an email link any time.` };\n }\n throw new IdentityError(`Resend failed: ${again.error.message}`);\n}\n","/**\n * Per-site branding — the Business tab. Reads and writes public.sites through\n * the signed-in user's client, so RLS decides who may (members read; owners\n * and staff write). Nothing here needs the service role.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport interface SiteBranding {\n name: string;\n logoUrl: string | null;\n faviconUrl: string | null;\n businessEmail: string | null;\n businessPhone: string | null;\n}\n\nexport type BrandingPatch = Partial<SiteBranding> & { name: string };\n\ninterface Row {\n name: string;\n logo_url: string | null;\n favicon_url: string | null;\n business_email: string | null;\n business_phone: string | null;\n}\n\nexport async function getSiteBranding(client: IdentityClient, siteId: string): Promise<SiteBranding | null> {\n const { data, error } = await client.from(\"sites\").select(\"name, logo_url, favicon_url, business_email, business_phone\").eq(\"site_id\", siteId).maybeSingle();\n if (error) throw error;\n if (!data) return null;\n const r = data as Row;\n return { name: r.name, logoUrl: r.logo_url, faviconUrl: r.favicon_url, businessEmail: r.business_email, businessPhone: r.business_phone };\n}\n\n/** Upsert the row. RLS refuses anyone but the site's owners or staff. */\nexport async function updateSiteBranding(client: IdentityClient, siteId: string, patch: BrandingPatch): Promise<void> {\n const row = {\n site_id: siteId,\n name: patch.name.trim(),\n logo_url: patch.logoUrl ?? null,\n favicon_url: patch.faviconUrl ?? null,\n business_email: emptyToNull(patch.businessEmail),\n business_phone: emptyToNull(patch.businessPhone),\n };\n const { error } = await client.from(\"sites\").upsert(row, { onConflict: \"site_id\" });\n if (error) throw error;\n}\n\nfunction emptyToNull(v: string | null | undefined): string | null {\n const s = (v ?? \"\").trim();\n return s === \"\" ? null : s;\n}\n","/**\n * The Team roster — public.site_members(site_id), a SECURITY DEFINER function\n * that returns rows only to that site's owners or staff (editors get a 42501).\n * Read with the signed-in user's client; writes are in service.ts.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError } from \"./errors\";\n\nexport interface SiteMember {\n userId: string;\n email: string;\n name: string | null;\n avatarUrl: string | null;\n role: \"owner\" | \"editor\";\n /** \"invited\" until the person has signed in once. */\n status: \"invited\" | \"active\";\n invitedAt: string;\n}\n\ninterface Row {\n user_id: string;\n email: string;\n name: string | null;\n avatar_url: string | null;\n role: string;\n status: string;\n invited_at: string;\n}\n\nexport async function listSiteMembers(client: IdentityClient, siteId: string): Promise<SiteMember[]> {\n const { data, error } = await client.rpc(\"site_members\", { p_site_id: siteId });\n if (error) {\n if (error.code === \"42501\" || /not allowed/i.test(error.message)) throw new ForbiddenError(siteId, \"owner\", null);\n throw error;\n }\n return ((data ?? []) as Row[]).map((r) => ({\n userId: r.user_id,\n email: r.email,\n name: r.name,\n avatarUrl: r.avatar_url,\n role: r.role === \"owner\" ? \"owner\" : \"editor\",\n status: r.status === \"active\" ? \"active\" : \"invited\",\n invitedAt: r.invited_at,\n }));\n}\n","/**\n * My Profile and Change Password — the signed-in user editing themselves,\n * through their own client (auth.updateUser). Name and photo live in user\n * metadata; the photo file itself is in the avatars bucket (uploaded by the\n * host, path avatars/{uid}/…).\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { IdentityError } from \"./errors\";\n\nexport const MIN_PASSWORD_LENGTH = 10;\nexport const PASSWORD_TOO_SHORT = \"Use at least 10 characters.\";\n\nexport interface ProfilePatch {\n name?: string | null;\n avatarUrl?: string | null;\n}\n\nexport interface Profile {\n name: string | null;\n avatarUrl: string | null;\n}\n\nexport function profileOf(user: { user_metadata?: Record<string, unknown> | null } | null | undefined): Profile {\n const m = user?.user_metadata ?? {};\n const name = typeof m.name === \"string\" && m.name.trim() ? m.name.trim() : null;\n const avatarUrl = typeof m.avatar_url === \"string\" && m.avatar_url ? m.avatar_url : null;\n return { name, avatarUrl };\n}\n\nexport async function updateProfile(client: IdentityClient, patch: ProfilePatch): Promise<Profile> {\n const data: Record<string, unknown> = {};\n if (patch.name !== undefined) data.name = (patch.name ?? \"\").trim().slice(0, 80);\n if (patch.avatarUrl !== undefined) data.avatar_url = patch.avatarUrl ?? \"\";\n const { data: res, error } = await client.auth.updateUser({ data });\n if (error) throw new IdentityError(error.message);\n return profileOf(res.user);\n}\n\n/** Sets (or replaces) a password. Magic links keep working alongside it. */\nexport async function setPassword(client: IdentityClient, password: string): Promise<void> {\n if (typeof password !== \"string\" || password.length < MIN_PASSWORD_LENGTH) throw new IdentityError(PASSWORD_TOO_SHORT);\n const { error } = await client.auth.updateUser({ password });\n if (error) throw new IdentityError(error.message);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/client.ts","../../src/auth/session.ts","../../src/auth/errors.ts","../../src/auth/types.ts","../../src/auth/roles.ts","../../src/auth/callback.ts","../../src/auth/signout.ts","../../src/auth/service.ts","../../src/auth/branding.ts","../../src/auth/members.ts","../../src/auth/profile.ts"],"names":["createServerClient","createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * createIdentityClient — a Supabase client bound to the request's cookies.\n *\n * Built per request from arguments. A Next 16 route does:\n *\n * const store = await cookies();\n * const client = createIdentityClient({ url, anonKey, cookies: store });\n *\n * No `next` import here. The CookieStore interface accepts either shape a\n * host might hand us: @supabase/ssr's { getAll, setAll }, or Next's\n * cookies() store, which has { getAll, set } — the session cookies are\n * written through whichever exists. (Observed 2026-09-08: with only setAll\n * supported, Next's store silently wrote nothing and every sign-in bounced.)\n */\n\nimport { createServerClient } from \"@supabase/ssr\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { IdentityContext } from \"./types\";\n\nexport type IdentityClient = SupabaseClient;\n\ntype CookieToSet = { name: string; value: string; options?: Record<string, unknown> };\n\nexport function createIdentityClient(ctx: IdentityContext): IdentityClient {\n const store = ctx.cookies;\n return createServerClient(ctx.url, ctx.anonKey, {\n cookies: {\n getAll: () => store.getAll(),\n setAll: (list: CookieToSet[]) => {\n // Server Components can't set cookies; @supabase/ssr documents that\n // swallowing the failure there is correct — middleware / route\n // handlers refresh the session instead.\n try {\n if (typeof store.setAll === \"function\") {\n void store.setAll(list);\n } else if (typeof store.set === \"function\") {\n for (const c of list) store.set(c.name, c.value, c.options);\n }\n } catch {\n /* read-only cookie store */\n }\n },\n },\n });\n}\n","/**\n * getCurrentUser — who is signed in, verified against the auth server.\n *\n * `getUser()` round-trips to Supabase and validates the JWT; `getSession()`\n * only decodes the cookie and is not trustworthy on the server. Always this.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport type { IdentityUser } from \"./types\";\n\nexport async function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null> {\n const { data, error } = await client.auth.getUser();\n if (error || !data.user) return null;\n return { id: data.user.id, email: data.user.email ?? null };\n}\n","/** Typed errors so a route can map them to 401 / 403 without string matching. */\n\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"IdentityError\";\n }\n}\n\n/** No session — send them to sign in. Maps to 401. */\nexport class UnauthenticatedError extends IdentityError {\n constructor() {\n super(\"Not signed in\");\n this.name = \"UnauthenticatedError\";\n }\n}\n\n/** Signed in, but not allowed to do this here. Maps to 403. */\nexport class ForbiddenError extends IdentityError {\n readonly siteId: string;\n readonly required: string;\n readonly actual: string | null;\n\n constructor(siteId: string, required: string, actual: string | null) {\n super(`Requires ${required} on site \"${siteId}\" (you are ${actual ?? \"not a member\"})`);\n this.name = \"ForbiddenError\";\n this.siteId = siteId;\n this.required = required;\n this.actual = actual;\n }\n}\n","/**\n * Types for the identity layer. All config arrives as arguments — never env.\n */\n\n/**\n * Either cookie-store shape a host can hand us:\n * - @supabase/ssr's CookieMethodsServer: { getAll, setAll }\n * - Next's `await cookies()` store: { getAll, set }\n * `setAll` is preferred when present; otherwise each cookie goes through `set`.\n */\nexport interface CookieStore {\n getAll(): { name: string; value: string }[] | Promise<{ name: string; value: string }[]>;\n setAll?(cookies: { name: string; value: string; options?: Record<string, unknown> }[]): void | Promise<void>;\n set?(name: string, value: string, options?: Record<string, unknown>): unknown;\n}\n\n/** Enough to read the signed-in user with the anon key + their cookies. */\nexport interface IdentityContext {\n url: string;\n anonKey: string;\n cookies: CookieStore;\n}\n\n/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */\nexport interface ServiceContext {\n url: string;\n serviceRoleKey: string;\n}\n\nexport type SiteRole = \"staff\" | \"owner\" | \"editor\";\n\nexport const ROLE_RANK: Record<SiteRole, number> = { editor: 1, owner: 2, staff: 3 };\n\nexport interface IdentityUser {\n id: string;\n email: string | null;\n}\n\nexport interface SiteMembership {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n}\n","/**\n * Role resolution and the request gate.\n *\n * Two selects on the caller's OWN rows — both permitted by RLS with the anon\n * key, so this never needs the service role. Staff wins over any site role.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError, UnauthenticatedError } from \"./errors\";\nimport { getCurrentUser } from \"./session\";\nimport { ROLE_RANK, type IdentityUser, type SiteRole } from \"./types\";\n\nexport async function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null> {\n const uid = userId ?? (await getCurrentUser(client))?.id;\n if (!uid) return null;\n\n const [staff, member] = await Promise.all([\n client.from(\"staff\").select(\"user_id\").eq(\"user_id\", uid).maybeSingle(),\n client.from(\"site_users\").select(\"role\").eq(\"user_id\", uid).eq(\"site_id\", siteId).maybeSingle(),\n ]);\n\n if (staff.error) throw staff.error;\n if (member.error) throw member.error;\n\n if (staff.data) return \"staff\";\n const role = (member.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nexport function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean {\n return actual !== null && ROLE_RANK[actual] >= ROLE_RANK[minimum];\n}\n\nexport interface RequireOptions {\n /** Default \"editor\" — any member of the site. */\n minimumRole?: SiteRole;\n}\n\n/**\n * The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);\n * otherwise returns the user and their effective role for this site.\n */\nexport async function requireSiteUser(\n client: IdentityClient,\n siteId: string,\n opts: RequireOptions = {},\n): Promise<{ user: IdentityUser; role: SiteRole }> {\n const user = await getCurrentUser(client);\n if (!user) throw new UnauthenticatedError();\n\n const minimum = opts.minimumRole ?? \"editor\";\n const role = await getSiteRole(client, siteId, user.id);\n if (!roleAtLeast(role, minimum)) throw new ForbiddenError(siteId, minimum, role);\n\n return { user, role: role as SiteRole };\n}\n","/**\n * handleAuthCallback — the emailed magic link lands here with ?code=…&next=…\n *\n * Exchanges the PKCE code for a session (cookies are written through the\n * adapter the client was built with) and says where to send the user. The\n * `next` parameter is only honoured when it is a plain same-origin path —\n * \"//evil.example\" and \"https://…\" fall back, which closes the open-redirect\n * a naive startsWith(\"/\") check leaves open.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport type CallbackResult =\n | { ok: true; redirectTo: string }\n | { ok: false; reason: \"missing_code\" | \"exchange_failed\"; message?: string };\n\nexport interface CallbackOptions {\n /** Where to go when `next` is absent or unsafe. Default \"/admin\". */\n fallbackPath?: string;\n}\n\nexport function safeNextPath(next: string | null | undefined, fallback: string): string {\n if (!next) return fallback;\n // Exactly one leading slash, then not another slash or backslash.\n if (!/^\\/(?![/\\\\])/.test(next)) return fallback;\n // No scheme smuggling or CR/LF.\n if (/[\\r\\n]/.test(next) || /^\\/[^?#]*:\\/\\//.test(next)) return fallback;\n return next;\n}\n\nexport async function handleAuthCallback(\n client: IdentityClient,\n requestUrl: string | URL,\n opts: CallbackOptions = {},\n): Promise<CallbackResult> {\n const fallback = opts.fallbackPath ?? \"/admin\";\n const url = typeof requestUrl === \"string\" ? new URL(requestUrl, \"http://placeholder.invalid\") : requestUrl;\n const code = url.searchParams.get(\"code\");\n if (!code) return { ok: false, reason: \"missing_code\" };\n\n const { error } = await client.auth.exchangeCodeForSession(code);\n if (error) return { ok: false, reason: \"exchange_failed\", message: error.message };\n\n return { ok: true, redirectTo: safeNextPath(url.searchParams.get(\"next\"), fallback) };\n}\n","/** signOut — clears this browser's session. The caller redirects afterwards. */\n\nimport type { IdentityClient } from \"./client\";\n\nexport async function signOut(client: IdentityClient): Promise<void> {\n const { error } = await client.auth.signOut({ scope: \"local\" });\n if (error) throw error;\n}\n","/**\n * The only file that touches the service-role key. Invite and remove.\n *\n * Authorization is checked HERE, in code, before the privileged client does\n * anything — RLS doesn't apply to the service role, so this is the guard:\n * staff → may grant owner or editor on any site\n * owner → may grant editor on their own site only\n * editor → may not invite\n *\n * `actor` is the caller's already-resolved identity (from requireSiteUser on\n * the anon client). Its role is re-resolved with the service client so a\n * caller can't lie about it.\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { ForbiddenError, IdentityError } from \"./errors\";\nimport type { ServiceContext, SiteRole } from \"./types\";\n\nexport type ServiceClient = SupabaseClient;\n\nexport function createServiceClient(ctx: ServiceContext): ServiceClient {\n return createClient(ctx.url, ctx.serviceRoleKey, {\n auth: { persistSession: false, autoRefreshToken: false },\n });\n}\n\nexport interface InviteInput {\n email: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** The signed-in user performing the invite. */\n actor: { id: string };\n /** Where the invite email's link lands — the site's /admin/auth/callback. */\n redirectTo: string;\n}\n\nexport interface InviteResult {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** false when the auth user already existed and only the membership was added. */\n invitedByEmail: boolean;\n}\n\nasync function resolveActorRole(svc: ServiceClient, actorId: string, siteId: string): Promise<SiteRole | null> {\n const staff = await svc.from(\"staff\").select(\"user_id\").eq(\"user_id\", actorId).maybeSingle();\n if (staff.error) throw staff.error;\n if (staff.data) return \"staff\";\n const m = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", actorId).eq(\"site_id\", siteId).maybeSingle();\n if (m.error) throw m.error;\n const role = (m.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nfunction assertMayGrant(actorRole: SiteRole | null, siteId: string, granting: \"owner\" | \"editor\"): void {\n if (actorRole === \"staff\") return;\n if (actorRole === \"owner\" && granting === \"editor\") return;\n throw new ForbiddenError(siteId, granting === \"owner\" ? \"staff\" : \"owner\", actorRole);\n}\n\nexport async function inviteUser(ctx: ServiceContext, input: InviteInput): Promise<InviteResult> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const email = input.email.trim().toLowerCase();\n let userId: string | undefined;\n let invitedByEmail = false;\n\n const invite = await svc.auth.admin.inviteUserByEmail(email, {\n redirectTo: input.redirectTo,\n data: { site_id: input.siteId, role: input.role },\n });\n\n if (!invite.error) {\n userId = invite.data.user.id;\n invitedByEmail = true;\n } else if (/already|exists|registered/i.test(invite.error.message)) {\n // Existing auth user (works for another site, or is staff): just add the row.\n userId = await findUserIdByEmail(svc, email);\n if (!userId) throw new IdentityError(`Could not find existing user ${email}`);\n } else {\n throw new IdentityError(`Invite failed: ${invite.error.message}`);\n }\n\n const upsert = await svc\n .from(\"site_users\")\n .upsert({ user_id: userId, site_id: input.siteId, role: input.role, invited_by: input.actor.id }, { onConflict: \"user_id,site_id\" });\n if (upsert.error) throw upsert.error;\n\n return { userId, siteId: input.siteId, role: input.role, invitedByEmail };\n}\n\nasync function findUserIdByEmail(svc: ServiceClient, email: string): Promise<string | undefined> {\n // listUsers is paged; identity holds tens of users, not thousands.\n for (let page = 1; page <= 20; page++) {\n const { data, error } = await svc.auth.admin.listUsers({ page, perPage: 200 });\n if (error) throw error;\n const hit = data.users.find((u) => u.email?.toLowerCase() === email);\n if (hit) return hit.id;\n if (data.users.length < 200) break;\n }\n return undefined;\n}\n\nexport interface RemoveInput {\n userId: string;\n siteId: string;\n actor: { id: string };\n}\n\n/** Removes the membership only. The auth user survives (they may belong to other sites). */\nexport async function removeSiteUser(ctx: ServiceContext, input: RemoveInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const targetRole = ((target.data as { role?: string } | null)?.role ?? \"editor\") as \"owner\" | \"editor\";\n assertMayGrant(actorRole, input.siteId, targetRole);\n if (targetRole === \"owner\") await assertNotLastOwner(svc, input.siteId);\n\n const del = await svc.from(\"site_users\").delete().eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (del.error) throw del.error;\n}\n\nexport interface RoleInput {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n actor: { id: string };\n}\n\n/**\n * Change a member's role. Same grant rules as invite (staff anything; an owner\n * may only make people editors on their own site), plus: the last owner of a\n * site can never be demoted, otherwise nobody could manage it.\n */\nexport async function updateSiteRole(ctx: ServiceContext, input: RoleInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const current = (target.data as { role?: string } | null)?.role;\n if (!current) throw new IdentityError(\"That person isn't a member of this site.\");\n // Demoting an owner is an owner-level act — an owner may not do it to a peer.\n if (current === \"owner\") assertMayGrant(actorRole, input.siteId, \"owner\");\n if (current === \"owner\" && input.role === \"editor\") await assertNotLastOwner(svc, input.siteId);\n\n const upd = await svc.from(\"site_users\").update({ role: input.role }).eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (upd.error) throw upd.error;\n}\n\nexport async function assertNotLastOwner(svc: ServiceClient, siteId: string): Promise<void> {\n const owners = await svc.from(\"site_users\").select(\"user_id\").eq(\"site_id\", siteId).eq(\"role\", \"owner\");\n if (owners.error) throw owners.error;\n if ((owners.data ?? []).length <= 1) throw new IdentityError(\"A site needs at least one owner — make someone else an owner first.\");\n}\n\nexport interface ResendInput {\n email: string;\n siteId: string;\n actor: { id: string };\n redirectTo: string;\n}\n\n/** Send the invite email again. An already-active user gets no email — they can just sign in. */\nexport async function resendInvite(ctx: ServiceContext, input: ResendInput): Promise<{ resent: boolean; message: string }> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, \"editor\");\n\n const email = input.email.trim().toLowerCase();\n const again = await svc.auth.admin.inviteUserByEmail(email, { redirectTo: input.redirectTo, data: { site_id: input.siteId } });\n if (!again.error) return { resent: true, message: `Invitation sent again to ${email}.` };\n if (/already|exists|registered|confirmed/i.test(again.error.message)) {\n return { resent: false, message: `${email} has already accepted — they can sign in with an email link any time.` };\n }\n throw new IdentityError(`Resend failed: ${again.error.message}`);\n}\n","/**\n * Per-site branding — the Business tab. Reads and writes public.sites through\n * the signed-in user's client, so RLS decides who may (members read; owners\n * and staff write). Nothing here needs the service role.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport interface SiteBranding {\n name: string;\n logoUrl: string | null;\n faviconUrl: string | null;\n businessEmail: string | null;\n businessPhone: string | null;\n}\n\nexport type BrandingPatch = Partial<SiteBranding> & { name: string };\n\ninterface Row {\n name: string;\n logo_url: string | null;\n favicon_url: string | null;\n business_email: string | null;\n business_phone: string | null;\n}\n\nexport async function getSiteBranding(client: IdentityClient, siteId: string): Promise<SiteBranding | null> {\n const { data, error } = await client.from(\"sites\").select(\"name, logo_url, favicon_url, business_email, business_phone\").eq(\"site_id\", siteId).maybeSingle();\n if (error) throw error;\n if (!data) return null;\n const r = data as Row;\n return { name: r.name, logoUrl: r.logo_url, faviconUrl: r.favicon_url, businessEmail: r.business_email, businessPhone: r.business_phone };\n}\n\n/** Upsert the row. RLS refuses anyone but the site's owners or staff. */\nexport async function updateSiteBranding(client: IdentityClient, siteId: string, patch: BrandingPatch): Promise<void> {\n const row = {\n site_id: siteId,\n name: patch.name.trim(),\n logo_url: patch.logoUrl ?? null,\n favicon_url: patch.faviconUrl ?? null,\n business_email: emptyToNull(patch.businessEmail),\n business_phone: emptyToNull(patch.businessPhone),\n };\n const { error } = await client.from(\"sites\").upsert(row, { onConflict: \"site_id\" });\n if (error) throw error;\n}\n\nfunction emptyToNull(v: string | null | undefined): string | null {\n const s = (v ?? \"\").trim();\n return s === \"\" ? null : s;\n}\n","/**\n * The Team roster — public.site_members(site_id), a SECURITY DEFINER function\n * that returns rows only to that site's owners or staff (editors get a 42501).\n * Read with the signed-in user's client; writes are in service.ts.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError } from \"./errors\";\n\nexport interface SiteMember {\n userId: string;\n email: string;\n name: string | null;\n avatarUrl: string | null;\n role: \"owner\" | \"editor\";\n /** \"invited\" until the person has signed in once. */\n status: \"invited\" | \"active\";\n invitedAt: string;\n}\n\ninterface Row {\n user_id: string;\n email: string;\n name: string | null;\n avatar_url: string | null;\n role: string;\n status: string;\n invited_at: string;\n}\n\nexport async function listSiteMembers(client: IdentityClient, siteId: string): Promise<SiteMember[]> {\n const { data, error } = await client.rpc(\"site_members\", { p_site_id: siteId });\n if (error) {\n if (error.code === \"42501\" || /not allowed/i.test(error.message)) throw new ForbiddenError(siteId, \"owner\", null);\n throw error;\n }\n return ((data ?? []) as Row[]).map((r) => ({\n userId: r.user_id,\n email: r.email,\n name: r.name,\n avatarUrl: r.avatar_url,\n role: r.role === \"owner\" ? \"owner\" : \"editor\",\n status: r.status === \"active\" ? \"active\" : \"invited\",\n invitedAt: r.invited_at,\n }));\n}\n","/**\n * My Profile and Change Password — the signed-in user editing themselves,\n * through their own client (auth.updateUser). Name and photo live in user\n * metadata; the photo file itself is in the avatars bucket (uploaded by the\n * host, path avatars/{uid}/…).\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { IdentityError } from \"./errors\";\n\nexport const MIN_PASSWORD_LENGTH = 10;\nexport const PASSWORD_TOO_SHORT = \"Use at least 10 characters.\";\n\nexport interface ProfilePatch {\n name?: string | null;\n /** The byline — how the name appears on published posts. Blank means \"same as name\". */\n displayName?: string | null;\n avatarUrl?: string | null;\n}\n\nexport interface Profile {\n name: string | null;\n /** Resolved byline: display_name if set, else name. */\n displayName: string | null;\n avatarUrl: string | null;\n}\n\nexport function profileOf(user: { user_metadata?: Record<string, unknown> | null } | null | undefined): Profile {\n const m = user?.user_metadata ?? {};\n const str = (v: unknown) => (typeof v === \"string\" && v.trim() ? v.trim() : null);\n const name = str(m.name);\n const displayName = str(m.display_name) ?? name;\n const avatarUrl = typeof m.avatar_url === \"string\" && m.avatar_url ? m.avatar_url : null;\n return { name, displayName, avatarUrl };\n}\n\nexport async function updateProfile(client: IdentityClient, patch: ProfilePatch): Promise<Profile> {\n const data: Record<string, unknown> = {};\n if (patch.name !== undefined) data.name = (patch.name ?? \"\").trim().slice(0, 80);\n if (patch.displayName !== undefined) data.display_name = (patch.displayName ?? \"\").trim().slice(0, 80);\n if (patch.avatarUrl !== undefined) data.avatar_url = patch.avatarUrl ?? \"\";\n const { data: res, error } = await client.auth.updateUser({ data });\n if (error) throw new IdentityError(error.message);\n return profileOf(res.user);\n}\n\n/** Sets (or replaces) a password. Magic links keep working alongside it. */\nexport async function setPassword(client: IdentityClient, password: string): Promise<void> {\n if (typeof password !== \"string\" || password.length < MIN_PASSWORD_LENGTH) throw new IdentityError(PASSWORD_TOO_SHORT);\n const { error } = await client.auth.updateUser({ password });\n if (error) throw new IdentityError(error.message);\n}\n"]}
|
package/dist/auth/index.d.cts
CHANGED
|
@@ -244,10 +244,14 @@ declare const MIN_PASSWORD_LENGTH = 10;
|
|
|
244
244
|
declare const PASSWORD_TOO_SHORT = "Use at least 10 characters.";
|
|
245
245
|
interface ProfilePatch {
|
|
246
246
|
name?: string | null;
|
|
247
|
+
/** The byline — how the name appears on published posts. Blank means "same as name". */
|
|
248
|
+
displayName?: string | null;
|
|
247
249
|
avatarUrl?: string | null;
|
|
248
250
|
}
|
|
249
251
|
interface Profile {
|
|
250
252
|
name: string | null;
|
|
253
|
+
/** Resolved byline: display_name if set, else name. */
|
|
254
|
+
displayName: string | null;
|
|
251
255
|
avatarUrl: string | null;
|
|
252
256
|
}
|
|
253
257
|
declare function profileOf(user: {
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -244,10 +244,14 @@ declare const MIN_PASSWORD_LENGTH = 10;
|
|
|
244
244
|
declare const PASSWORD_TOO_SHORT = "Use at least 10 characters.";
|
|
245
245
|
interface ProfilePatch {
|
|
246
246
|
name?: string | null;
|
|
247
|
+
/** The byline — how the name appears on published posts. Blank means "same as name". */
|
|
248
|
+
displayName?: string | null;
|
|
247
249
|
avatarUrl?: string | null;
|
|
248
250
|
}
|
|
249
251
|
interface Profile {
|
|
250
252
|
name: string | null;
|
|
253
|
+
/** Resolved byline: display_name if set, else name. */
|
|
254
|
+
displayName: string | null;
|
|
251
255
|
avatarUrl: string | null;
|
|
252
256
|
}
|
|
253
257
|
declare function profileOf(user: {
|
package/dist/auth/index.js
CHANGED
|
@@ -246,13 +246,16 @@ var MIN_PASSWORD_LENGTH = 10;
|
|
|
246
246
|
var PASSWORD_TOO_SHORT = "Use at least 10 characters.";
|
|
247
247
|
function profileOf(user) {
|
|
248
248
|
const m = user?.user_metadata ?? {};
|
|
249
|
-
const
|
|
249
|
+
const str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
250
|
+
const name = str(m.name);
|
|
251
|
+
const displayName = str(m.display_name) ?? name;
|
|
250
252
|
const avatarUrl = typeof m.avatar_url === "string" && m.avatar_url ? m.avatar_url : null;
|
|
251
|
-
return { name, avatarUrl };
|
|
253
|
+
return { name, displayName, avatarUrl };
|
|
252
254
|
}
|
|
253
255
|
async function updateProfile(client, patch) {
|
|
254
256
|
const data = {};
|
|
255
257
|
if (patch.name !== void 0) data.name = (patch.name ?? "").trim().slice(0, 80);
|
|
258
|
+
if (patch.displayName !== void 0) data.display_name = (patch.displayName ?? "").trim().slice(0, 80);
|
|
256
259
|
if (patch.avatarUrl !== void 0) data.avatar_url = patch.avatarUrl ?? "";
|
|
257
260
|
const { data: res, error } = await client.auth.updateUser({ data });
|
|
258
261
|
if (error) throw new IdentityError(error.message);
|
package/dist/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/client.ts","../../src/auth/session.ts","../../src/auth/errors.ts","../../src/auth/types.ts","../../src/auth/roles.ts","../../src/auth/callback.ts","../../src/auth/signout.ts","../../src/auth/service.ts","../../src/auth/branding.ts","../../src/auth/members.ts","../../src/auth/profile.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * createIdentityClient — a Supabase client bound to the request's cookies.\n *\n * Built per request from arguments. A Next 16 route does:\n *\n * const store = await cookies();\n * const client = createIdentityClient({ url, anonKey, cookies: store });\n *\n * No `next` import here. The CookieStore interface accepts either shape a\n * host might hand us: @supabase/ssr's { getAll, setAll }, or Next's\n * cookies() store, which has { getAll, set } — the session cookies are\n * written through whichever exists. (Observed 2026-09-08: with only setAll\n * supported, Next's store silently wrote nothing and every sign-in bounced.)\n */\n\nimport { createServerClient } from \"@supabase/ssr\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { IdentityContext } from \"./types\";\n\nexport type IdentityClient = SupabaseClient;\n\ntype CookieToSet = { name: string; value: string; options?: Record<string, unknown> };\n\nexport function createIdentityClient(ctx: IdentityContext): IdentityClient {\n const store = ctx.cookies;\n return createServerClient(ctx.url, ctx.anonKey, {\n cookies: {\n getAll: () => store.getAll(),\n setAll: (list: CookieToSet[]) => {\n // Server Components can't set cookies; @supabase/ssr documents that\n // swallowing the failure there is correct — middleware / route\n // handlers refresh the session instead.\n try {\n if (typeof store.setAll === \"function\") {\n void store.setAll(list);\n } else if (typeof store.set === \"function\") {\n for (const c of list) store.set(c.name, c.value, c.options);\n }\n } catch {\n /* read-only cookie store */\n }\n },\n },\n });\n}\n","/**\n * getCurrentUser — who is signed in, verified against the auth server.\n *\n * `getUser()` round-trips to Supabase and validates the JWT; `getSession()`\n * only decodes the cookie and is not trustworthy on the server. Always this.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport type { IdentityUser } from \"./types\";\n\nexport async function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null> {\n const { data, error } = await client.auth.getUser();\n if (error || !data.user) return null;\n return { id: data.user.id, email: data.user.email ?? null };\n}\n","/** Typed errors so a route can map them to 401 / 403 without string matching. */\n\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"IdentityError\";\n }\n}\n\n/** No session — send them to sign in. Maps to 401. */\nexport class UnauthenticatedError extends IdentityError {\n constructor() {\n super(\"Not signed in\");\n this.name = \"UnauthenticatedError\";\n }\n}\n\n/** Signed in, but not allowed to do this here. Maps to 403. */\nexport class ForbiddenError extends IdentityError {\n readonly siteId: string;\n readonly required: string;\n readonly actual: string | null;\n\n constructor(siteId: string, required: string, actual: string | null) {\n super(`Requires ${required} on site \"${siteId}\" (you are ${actual ?? \"not a member\"})`);\n this.name = \"ForbiddenError\";\n this.siteId = siteId;\n this.required = required;\n this.actual = actual;\n }\n}\n","/**\n * Types for the identity layer. All config arrives as arguments — never env.\n */\n\n/**\n * Either cookie-store shape a host can hand us:\n * - @supabase/ssr's CookieMethodsServer: { getAll, setAll }\n * - Next's `await cookies()` store: { getAll, set }\n * `setAll` is preferred when present; otherwise each cookie goes through `set`.\n */\nexport interface CookieStore {\n getAll(): { name: string; value: string }[] | Promise<{ name: string; value: string }[]>;\n setAll?(cookies: { name: string; value: string; options?: Record<string, unknown> }[]): void | Promise<void>;\n set?(name: string, value: string, options?: Record<string, unknown>): unknown;\n}\n\n/** Enough to read the signed-in user with the anon key + their cookies. */\nexport interface IdentityContext {\n url: string;\n anonKey: string;\n cookies: CookieStore;\n}\n\n/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */\nexport interface ServiceContext {\n url: string;\n serviceRoleKey: string;\n}\n\nexport type SiteRole = \"staff\" | \"owner\" | \"editor\";\n\nexport const ROLE_RANK: Record<SiteRole, number> = { editor: 1, owner: 2, staff: 3 };\n\nexport interface IdentityUser {\n id: string;\n email: string | null;\n}\n\nexport interface SiteMembership {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n}\n","/**\n * Role resolution and the request gate.\n *\n * Two selects on the caller's OWN rows — both permitted by RLS with the anon\n * key, so this never needs the service role. Staff wins over any site role.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError, UnauthenticatedError } from \"./errors\";\nimport { getCurrentUser } from \"./session\";\nimport { ROLE_RANK, type IdentityUser, type SiteRole } from \"./types\";\n\nexport async function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null> {\n const uid = userId ?? (await getCurrentUser(client))?.id;\n if (!uid) return null;\n\n const [staff, member] = await Promise.all([\n client.from(\"staff\").select(\"user_id\").eq(\"user_id\", uid).maybeSingle(),\n client.from(\"site_users\").select(\"role\").eq(\"user_id\", uid).eq(\"site_id\", siteId).maybeSingle(),\n ]);\n\n if (staff.error) throw staff.error;\n if (member.error) throw member.error;\n\n if (staff.data) return \"staff\";\n const role = (member.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nexport function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean {\n return actual !== null && ROLE_RANK[actual] >= ROLE_RANK[minimum];\n}\n\nexport interface RequireOptions {\n /** Default \"editor\" — any member of the site. */\n minimumRole?: SiteRole;\n}\n\n/**\n * The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);\n * otherwise returns the user and their effective role for this site.\n */\nexport async function requireSiteUser(\n client: IdentityClient,\n siteId: string,\n opts: RequireOptions = {},\n): Promise<{ user: IdentityUser; role: SiteRole }> {\n const user = await getCurrentUser(client);\n if (!user) throw new UnauthenticatedError();\n\n const minimum = opts.minimumRole ?? \"editor\";\n const role = await getSiteRole(client, siteId, user.id);\n if (!roleAtLeast(role, minimum)) throw new ForbiddenError(siteId, minimum, role);\n\n return { user, role: role as SiteRole };\n}\n","/**\n * handleAuthCallback — the emailed magic link lands here with ?code=…&next=…\n *\n * Exchanges the PKCE code for a session (cookies are written through the\n * adapter the client was built with) and says where to send the user. The\n * `next` parameter is only honoured when it is a plain same-origin path —\n * \"//evil.example\" and \"https://…\" fall back, which closes the open-redirect\n * a naive startsWith(\"/\") check leaves open.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport type CallbackResult =\n | { ok: true; redirectTo: string }\n | { ok: false; reason: \"missing_code\" | \"exchange_failed\"; message?: string };\n\nexport interface CallbackOptions {\n /** Where to go when `next` is absent or unsafe. Default \"/admin\". */\n fallbackPath?: string;\n}\n\nexport function safeNextPath(next: string | null | undefined, fallback: string): string {\n if (!next) return fallback;\n // Exactly one leading slash, then not another slash or backslash.\n if (!/^\\/(?![/\\\\])/.test(next)) return fallback;\n // No scheme smuggling or CR/LF.\n if (/[\\r\\n]/.test(next) || /^\\/[^?#]*:\\/\\//.test(next)) return fallback;\n return next;\n}\n\nexport async function handleAuthCallback(\n client: IdentityClient,\n requestUrl: string | URL,\n opts: CallbackOptions = {},\n): Promise<CallbackResult> {\n const fallback = opts.fallbackPath ?? \"/admin\";\n const url = typeof requestUrl === \"string\" ? new URL(requestUrl, \"http://placeholder.invalid\") : requestUrl;\n const code = url.searchParams.get(\"code\");\n if (!code) return { ok: false, reason: \"missing_code\" };\n\n const { error } = await client.auth.exchangeCodeForSession(code);\n if (error) return { ok: false, reason: \"exchange_failed\", message: error.message };\n\n return { ok: true, redirectTo: safeNextPath(url.searchParams.get(\"next\"), fallback) };\n}\n","/** signOut — clears this browser's session. The caller redirects afterwards. */\n\nimport type { IdentityClient } from \"./client\";\n\nexport async function signOut(client: IdentityClient): Promise<void> {\n const { error } = await client.auth.signOut({ scope: \"local\" });\n if (error) throw error;\n}\n","/**\n * The only file that touches the service-role key. Invite and remove.\n *\n * Authorization is checked HERE, in code, before the privileged client does\n * anything — RLS doesn't apply to the service role, so this is the guard:\n * staff → may grant owner or editor on any site\n * owner → may grant editor on their own site only\n * editor → may not invite\n *\n * `actor` is the caller's already-resolved identity (from requireSiteUser on\n * the anon client). Its role is re-resolved with the service client so a\n * caller can't lie about it.\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { ForbiddenError, IdentityError } from \"./errors\";\nimport type { ServiceContext, SiteRole } from \"./types\";\n\nexport type ServiceClient = SupabaseClient;\n\nexport function createServiceClient(ctx: ServiceContext): ServiceClient {\n return createClient(ctx.url, ctx.serviceRoleKey, {\n auth: { persistSession: false, autoRefreshToken: false },\n });\n}\n\nexport interface InviteInput {\n email: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** The signed-in user performing the invite. */\n actor: { id: string };\n /** Where the invite email's link lands — the site's /admin/auth/callback. */\n redirectTo: string;\n}\n\nexport interface InviteResult {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** false when the auth user already existed and only the membership was added. */\n invitedByEmail: boolean;\n}\n\nasync function resolveActorRole(svc: ServiceClient, actorId: string, siteId: string): Promise<SiteRole | null> {\n const staff = await svc.from(\"staff\").select(\"user_id\").eq(\"user_id\", actorId).maybeSingle();\n if (staff.error) throw staff.error;\n if (staff.data) return \"staff\";\n const m = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", actorId).eq(\"site_id\", siteId).maybeSingle();\n if (m.error) throw m.error;\n const role = (m.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nfunction assertMayGrant(actorRole: SiteRole | null, siteId: string, granting: \"owner\" | \"editor\"): void {\n if (actorRole === \"staff\") return;\n if (actorRole === \"owner\" && granting === \"editor\") return;\n throw new ForbiddenError(siteId, granting === \"owner\" ? \"staff\" : \"owner\", actorRole);\n}\n\nexport async function inviteUser(ctx: ServiceContext, input: InviteInput): Promise<InviteResult> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const email = input.email.trim().toLowerCase();\n let userId: string | undefined;\n let invitedByEmail = false;\n\n const invite = await svc.auth.admin.inviteUserByEmail(email, {\n redirectTo: input.redirectTo,\n data: { site_id: input.siteId, role: input.role },\n });\n\n if (!invite.error) {\n userId = invite.data.user.id;\n invitedByEmail = true;\n } else if (/already|exists|registered/i.test(invite.error.message)) {\n // Existing auth user (works for another site, or is staff): just add the row.\n userId = await findUserIdByEmail(svc, email);\n if (!userId) throw new IdentityError(`Could not find existing user ${email}`);\n } else {\n throw new IdentityError(`Invite failed: ${invite.error.message}`);\n }\n\n const upsert = await svc\n .from(\"site_users\")\n .upsert({ user_id: userId, site_id: input.siteId, role: input.role, invited_by: input.actor.id }, { onConflict: \"user_id,site_id\" });\n if (upsert.error) throw upsert.error;\n\n return { userId, siteId: input.siteId, role: input.role, invitedByEmail };\n}\n\nasync function findUserIdByEmail(svc: ServiceClient, email: string): Promise<string | undefined> {\n // listUsers is paged; identity holds tens of users, not thousands.\n for (let page = 1; page <= 20; page++) {\n const { data, error } = await svc.auth.admin.listUsers({ page, perPage: 200 });\n if (error) throw error;\n const hit = data.users.find((u) => u.email?.toLowerCase() === email);\n if (hit) return hit.id;\n if (data.users.length < 200) break;\n }\n return undefined;\n}\n\nexport interface RemoveInput {\n userId: string;\n siteId: string;\n actor: { id: string };\n}\n\n/** Removes the membership only. The auth user survives (they may belong to other sites). */\nexport async function removeSiteUser(ctx: ServiceContext, input: RemoveInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const targetRole = ((target.data as { role?: string } | null)?.role ?? \"editor\") as \"owner\" | \"editor\";\n assertMayGrant(actorRole, input.siteId, targetRole);\n if (targetRole === \"owner\") await assertNotLastOwner(svc, input.siteId);\n\n const del = await svc.from(\"site_users\").delete().eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (del.error) throw del.error;\n}\n\nexport interface RoleInput {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n actor: { id: string };\n}\n\n/**\n * Change a member's role. Same grant rules as invite (staff anything; an owner\n * may only make people editors on their own site), plus: the last owner of a\n * site can never be demoted, otherwise nobody could manage it.\n */\nexport async function updateSiteRole(ctx: ServiceContext, input: RoleInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const current = (target.data as { role?: string } | null)?.role;\n if (!current) throw new IdentityError(\"That person isn't a member of this site.\");\n // Demoting an owner is an owner-level act — an owner may not do it to a peer.\n if (current === \"owner\") assertMayGrant(actorRole, input.siteId, \"owner\");\n if (current === \"owner\" && input.role === \"editor\") await assertNotLastOwner(svc, input.siteId);\n\n const upd = await svc.from(\"site_users\").update({ role: input.role }).eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (upd.error) throw upd.error;\n}\n\nexport async function assertNotLastOwner(svc: ServiceClient, siteId: string): Promise<void> {\n const owners = await svc.from(\"site_users\").select(\"user_id\").eq(\"site_id\", siteId).eq(\"role\", \"owner\");\n if (owners.error) throw owners.error;\n if ((owners.data ?? []).length <= 1) throw new IdentityError(\"A site needs at least one owner — make someone else an owner first.\");\n}\n\nexport interface ResendInput {\n email: string;\n siteId: string;\n actor: { id: string };\n redirectTo: string;\n}\n\n/** Send the invite email again. An already-active user gets no email — they can just sign in. */\nexport async function resendInvite(ctx: ServiceContext, input: ResendInput): Promise<{ resent: boolean; message: string }> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, \"editor\");\n\n const email = input.email.trim().toLowerCase();\n const again = await svc.auth.admin.inviteUserByEmail(email, { redirectTo: input.redirectTo, data: { site_id: input.siteId } });\n if (!again.error) return { resent: true, message: `Invitation sent again to ${email}.` };\n if (/already|exists|registered|confirmed/i.test(again.error.message)) {\n return { resent: false, message: `${email} has already accepted — they can sign in with an email link any time.` };\n }\n throw new IdentityError(`Resend failed: ${again.error.message}`);\n}\n","/**\n * Per-site branding — the Business tab. Reads and writes public.sites through\n * the signed-in user's client, so RLS decides who may (members read; owners\n * and staff write). Nothing here needs the service role.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport interface SiteBranding {\n name: string;\n logoUrl: string | null;\n faviconUrl: string | null;\n businessEmail: string | null;\n businessPhone: string | null;\n}\n\nexport type BrandingPatch = Partial<SiteBranding> & { name: string };\n\ninterface Row {\n name: string;\n logo_url: string | null;\n favicon_url: string | null;\n business_email: string | null;\n business_phone: string | null;\n}\n\nexport async function getSiteBranding(client: IdentityClient, siteId: string): Promise<SiteBranding | null> {\n const { data, error } = await client.from(\"sites\").select(\"name, logo_url, favicon_url, business_email, business_phone\").eq(\"site_id\", siteId).maybeSingle();\n if (error) throw error;\n if (!data) return null;\n const r = data as Row;\n return { name: r.name, logoUrl: r.logo_url, faviconUrl: r.favicon_url, businessEmail: r.business_email, businessPhone: r.business_phone };\n}\n\n/** Upsert the row. RLS refuses anyone but the site's owners or staff. */\nexport async function updateSiteBranding(client: IdentityClient, siteId: string, patch: BrandingPatch): Promise<void> {\n const row = {\n site_id: siteId,\n name: patch.name.trim(),\n logo_url: patch.logoUrl ?? null,\n favicon_url: patch.faviconUrl ?? null,\n business_email: emptyToNull(patch.businessEmail),\n business_phone: emptyToNull(patch.businessPhone),\n };\n const { error } = await client.from(\"sites\").upsert(row, { onConflict: \"site_id\" });\n if (error) throw error;\n}\n\nfunction emptyToNull(v: string | null | undefined): string | null {\n const s = (v ?? \"\").trim();\n return s === \"\" ? null : s;\n}\n","/**\n * The Team roster — public.site_members(site_id), a SECURITY DEFINER function\n * that returns rows only to that site's owners or staff (editors get a 42501).\n * Read with the signed-in user's client; writes are in service.ts.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError } from \"./errors\";\n\nexport interface SiteMember {\n userId: string;\n email: string;\n name: string | null;\n avatarUrl: string | null;\n role: \"owner\" | \"editor\";\n /** \"invited\" until the person has signed in once. */\n status: \"invited\" | \"active\";\n invitedAt: string;\n}\n\ninterface Row {\n user_id: string;\n email: string;\n name: string | null;\n avatar_url: string | null;\n role: string;\n status: string;\n invited_at: string;\n}\n\nexport async function listSiteMembers(client: IdentityClient, siteId: string): Promise<SiteMember[]> {\n const { data, error } = await client.rpc(\"site_members\", { p_site_id: siteId });\n if (error) {\n if (error.code === \"42501\" || /not allowed/i.test(error.message)) throw new ForbiddenError(siteId, \"owner\", null);\n throw error;\n }\n return ((data ?? []) as Row[]).map((r) => ({\n userId: r.user_id,\n email: r.email,\n name: r.name,\n avatarUrl: r.avatar_url,\n role: r.role === \"owner\" ? \"owner\" : \"editor\",\n status: r.status === \"active\" ? \"active\" : \"invited\",\n invitedAt: r.invited_at,\n }));\n}\n","/**\n * My Profile and Change Password — the signed-in user editing themselves,\n * through their own client (auth.updateUser). Name and photo live in user\n * metadata; the photo file itself is in the avatars bucket (uploaded by the\n * host, path avatars/{uid}/…).\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { IdentityError } from \"./errors\";\n\nexport const MIN_PASSWORD_LENGTH = 10;\nexport const PASSWORD_TOO_SHORT = \"Use at least 10 characters.\";\n\nexport interface ProfilePatch {\n name?: string | null;\n avatarUrl?: string | null;\n}\n\nexport interface Profile {\n name: string | null;\n avatarUrl: string | null;\n}\n\nexport function profileOf(user: { user_metadata?: Record<string, unknown> | null } | null | undefined): Profile {\n const m = user?.user_metadata ?? {};\n const name = typeof m.name === \"string\" && m.name.trim() ? m.name.trim() : null;\n const avatarUrl = typeof m.avatar_url === \"string\" && m.avatar_url ? m.avatar_url : null;\n return { name, avatarUrl };\n}\n\nexport async function updateProfile(client: IdentityClient, patch: ProfilePatch): Promise<Profile> {\n const data: Record<string, unknown> = {};\n if (patch.name !== undefined) data.name = (patch.name ?? \"\").trim().slice(0, 80);\n if (patch.avatarUrl !== undefined) data.avatar_url = patch.avatarUrl ?? \"\";\n const { data: res, error } = await client.auth.updateUser({ data });\n if (error) throw new IdentityError(error.message);\n return profileOf(res.user);\n}\n\n/** Sets (or replaces) a password. Magic links keep working alongside it. */\nexport async function setPassword(client: IdentityClient, password: string): Promise<void> {\n if (typeof password !== \"string\" || password.length < MIN_PASSWORD_LENGTH) throw new IdentityError(PASSWORD_TOO_SHORT);\n const { error } = await client.auth.updateUser({ password });\n if (error) throw new IdentityError(error.message);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/client.ts","../../src/auth/session.ts","../../src/auth/errors.ts","../../src/auth/types.ts","../../src/auth/roles.ts","../../src/auth/callback.ts","../../src/auth/signout.ts","../../src/auth/service.ts","../../src/auth/branding.ts","../../src/auth/members.ts","../../src/auth/profile.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * createIdentityClient — a Supabase client bound to the request's cookies.\n *\n * Built per request from arguments. A Next 16 route does:\n *\n * const store = await cookies();\n * const client = createIdentityClient({ url, anonKey, cookies: store });\n *\n * No `next` import here. The CookieStore interface accepts either shape a\n * host might hand us: @supabase/ssr's { getAll, setAll }, or Next's\n * cookies() store, which has { getAll, set } — the session cookies are\n * written through whichever exists. (Observed 2026-09-08: with only setAll\n * supported, Next's store silently wrote nothing and every sign-in bounced.)\n */\n\nimport { createServerClient } from \"@supabase/ssr\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { IdentityContext } from \"./types\";\n\nexport type IdentityClient = SupabaseClient;\n\ntype CookieToSet = { name: string; value: string; options?: Record<string, unknown> };\n\nexport function createIdentityClient(ctx: IdentityContext): IdentityClient {\n const store = ctx.cookies;\n return createServerClient(ctx.url, ctx.anonKey, {\n cookies: {\n getAll: () => store.getAll(),\n setAll: (list: CookieToSet[]) => {\n // Server Components can't set cookies; @supabase/ssr documents that\n // swallowing the failure there is correct — middleware / route\n // handlers refresh the session instead.\n try {\n if (typeof store.setAll === \"function\") {\n void store.setAll(list);\n } else if (typeof store.set === \"function\") {\n for (const c of list) store.set(c.name, c.value, c.options);\n }\n } catch {\n /* read-only cookie store */\n }\n },\n },\n });\n}\n","/**\n * getCurrentUser — who is signed in, verified against the auth server.\n *\n * `getUser()` round-trips to Supabase and validates the JWT; `getSession()`\n * only decodes the cookie and is not trustworthy on the server. Always this.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport type { IdentityUser } from \"./types\";\n\nexport async function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null> {\n const { data, error } = await client.auth.getUser();\n if (error || !data.user) return null;\n return { id: data.user.id, email: data.user.email ?? null };\n}\n","/** Typed errors so a route can map them to 401 / 403 without string matching. */\n\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"IdentityError\";\n }\n}\n\n/** No session — send them to sign in. Maps to 401. */\nexport class UnauthenticatedError extends IdentityError {\n constructor() {\n super(\"Not signed in\");\n this.name = \"UnauthenticatedError\";\n }\n}\n\n/** Signed in, but not allowed to do this here. Maps to 403. */\nexport class ForbiddenError extends IdentityError {\n readonly siteId: string;\n readonly required: string;\n readonly actual: string | null;\n\n constructor(siteId: string, required: string, actual: string | null) {\n super(`Requires ${required} on site \"${siteId}\" (you are ${actual ?? \"not a member\"})`);\n this.name = \"ForbiddenError\";\n this.siteId = siteId;\n this.required = required;\n this.actual = actual;\n }\n}\n","/**\n * Types for the identity layer. All config arrives as arguments — never env.\n */\n\n/**\n * Either cookie-store shape a host can hand us:\n * - @supabase/ssr's CookieMethodsServer: { getAll, setAll }\n * - Next's `await cookies()` store: { getAll, set }\n * `setAll` is preferred when present; otherwise each cookie goes through `set`.\n */\nexport interface CookieStore {\n getAll(): { name: string; value: string }[] | Promise<{ name: string; value: string }[]>;\n setAll?(cookies: { name: string; value: string; options?: Record<string, unknown> }[]): void | Promise<void>;\n set?(name: string, value: string, options?: Record<string, unknown>): unknown;\n}\n\n/** Enough to read the signed-in user with the anon key + their cookies. */\nexport interface IdentityContext {\n url: string;\n anonKey: string;\n cookies: CookieStore;\n}\n\n/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */\nexport interface ServiceContext {\n url: string;\n serviceRoleKey: string;\n}\n\nexport type SiteRole = \"staff\" | \"owner\" | \"editor\";\n\nexport const ROLE_RANK: Record<SiteRole, number> = { editor: 1, owner: 2, staff: 3 };\n\nexport interface IdentityUser {\n id: string;\n email: string | null;\n}\n\nexport interface SiteMembership {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n}\n","/**\n * Role resolution and the request gate.\n *\n * Two selects on the caller's OWN rows — both permitted by RLS with the anon\n * key, so this never needs the service role. Staff wins over any site role.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError, UnauthenticatedError } from \"./errors\";\nimport { getCurrentUser } from \"./session\";\nimport { ROLE_RANK, type IdentityUser, type SiteRole } from \"./types\";\n\nexport async function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null> {\n const uid = userId ?? (await getCurrentUser(client))?.id;\n if (!uid) return null;\n\n const [staff, member] = await Promise.all([\n client.from(\"staff\").select(\"user_id\").eq(\"user_id\", uid).maybeSingle(),\n client.from(\"site_users\").select(\"role\").eq(\"user_id\", uid).eq(\"site_id\", siteId).maybeSingle(),\n ]);\n\n if (staff.error) throw staff.error;\n if (member.error) throw member.error;\n\n if (staff.data) return \"staff\";\n const role = (member.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nexport function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean {\n return actual !== null && ROLE_RANK[actual] >= ROLE_RANK[minimum];\n}\n\nexport interface RequireOptions {\n /** Default \"editor\" — any member of the site. */\n minimumRole?: SiteRole;\n}\n\n/**\n * The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);\n * otherwise returns the user and their effective role for this site.\n */\nexport async function requireSiteUser(\n client: IdentityClient,\n siteId: string,\n opts: RequireOptions = {},\n): Promise<{ user: IdentityUser; role: SiteRole }> {\n const user = await getCurrentUser(client);\n if (!user) throw new UnauthenticatedError();\n\n const minimum = opts.minimumRole ?? \"editor\";\n const role = await getSiteRole(client, siteId, user.id);\n if (!roleAtLeast(role, minimum)) throw new ForbiddenError(siteId, minimum, role);\n\n return { user, role: role as SiteRole };\n}\n","/**\n * handleAuthCallback — the emailed magic link lands here with ?code=…&next=…\n *\n * Exchanges the PKCE code for a session (cookies are written through the\n * adapter the client was built with) and says where to send the user. The\n * `next` parameter is only honoured when it is a plain same-origin path —\n * \"//evil.example\" and \"https://…\" fall back, which closes the open-redirect\n * a naive startsWith(\"/\") check leaves open.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport type CallbackResult =\n | { ok: true; redirectTo: string }\n | { ok: false; reason: \"missing_code\" | \"exchange_failed\"; message?: string };\n\nexport interface CallbackOptions {\n /** Where to go when `next` is absent or unsafe. Default \"/admin\". */\n fallbackPath?: string;\n}\n\nexport function safeNextPath(next: string | null | undefined, fallback: string): string {\n if (!next) return fallback;\n // Exactly one leading slash, then not another slash or backslash.\n if (!/^\\/(?![/\\\\])/.test(next)) return fallback;\n // No scheme smuggling or CR/LF.\n if (/[\\r\\n]/.test(next) || /^\\/[^?#]*:\\/\\//.test(next)) return fallback;\n return next;\n}\n\nexport async function handleAuthCallback(\n client: IdentityClient,\n requestUrl: string | URL,\n opts: CallbackOptions = {},\n): Promise<CallbackResult> {\n const fallback = opts.fallbackPath ?? \"/admin\";\n const url = typeof requestUrl === \"string\" ? new URL(requestUrl, \"http://placeholder.invalid\") : requestUrl;\n const code = url.searchParams.get(\"code\");\n if (!code) return { ok: false, reason: \"missing_code\" };\n\n const { error } = await client.auth.exchangeCodeForSession(code);\n if (error) return { ok: false, reason: \"exchange_failed\", message: error.message };\n\n return { ok: true, redirectTo: safeNextPath(url.searchParams.get(\"next\"), fallback) };\n}\n","/** signOut — clears this browser's session. The caller redirects afterwards. */\n\nimport type { IdentityClient } from \"./client\";\n\nexport async function signOut(client: IdentityClient): Promise<void> {\n const { error } = await client.auth.signOut({ scope: \"local\" });\n if (error) throw error;\n}\n","/**\n * The only file that touches the service-role key. Invite and remove.\n *\n * Authorization is checked HERE, in code, before the privileged client does\n * anything — RLS doesn't apply to the service role, so this is the guard:\n * staff → may grant owner or editor on any site\n * owner → may grant editor on their own site only\n * editor → may not invite\n *\n * `actor` is the caller's already-resolved identity (from requireSiteUser on\n * the anon client). Its role is re-resolved with the service client so a\n * caller can't lie about it.\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { ForbiddenError, IdentityError } from \"./errors\";\nimport type { ServiceContext, SiteRole } from \"./types\";\n\nexport type ServiceClient = SupabaseClient;\n\nexport function createServiceClient(ctx: ServiceContext): ServiceClient {\n return createClient(ctx.url, ctx.serviceRoleKey, {\n auth: { persistSession: false, autoRefreshToken: false },\n });\n}\n\nexport interface InviteInput {\n email: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** The signed-in user performing the invite. */\n actor: { id: string };\n /** Where the invite email's link lands — the site's /admin/auth/callback. */\n redirectTo: string;\n}\n\nexport interface InviteResult {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** false when the auth user already existed and only the membership was added. */\n invitedByEmail: boolean;\n}\n\nasync function resolveActorRole(svc: ServiceClient, actorId: string, siteId: string): Promise<SiteRole | null> {\n const staff = await svc.from(\"staff\").select(\"user_id\").eq(\"user_id\", actorId).maybeSingle();\n if (staff.error) throw staff.error;\n if (staff.data) return \"staff\";\n const m = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", actorId).eq(\"site_id\", siteId).maybeSingle();\n if (m.error) throw m.error;\n const role = (m.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nfunction assertMayGrant(actorRole: SiteRole | null, siteId: string, granting: \"owner\" | \"editor\"): void {\n if (actorRole === \"staff\") return;\n if (actorRole === \"owner\" && granting === \"editor\") return;\n throw new ForbiddenError(siteId, granting === \"owner\" ? \"staff\" : \"owner\", actorRole);\n}\n\nexport async function inviteUser(ctx: ServiceContext, input: InviteInput): Promise<InviteResult> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const email = input.email.trim().toLowerCase();\n let userId: string | undefined;\n let invitedByEmail = false;\n\n const invite = await svc.auth.admin.inviteUserByEmail(email, {\n redirectTo: input.redirectTo,\n data: { site_id: input.siteId, role: input.role },\n });\n\n if (!invite.error) {\n userId = invite.data.user.id;\n invitedByEmail = true;\n } else if (/already|exists|registered/i.test(invite.error.message)) {\n // Existing auth user (works for another site, or is staff): just add the row.\n userId = await findUserIdByEmail(svc, email);\n if (!userId) throw new IdentityError(`Could not find existing user ${email}`);\n } else {\n throw new IdentityError(`Invite failed: ${invite.error.message}`);\n }\n\n const upsert = await svc\n .from(\"site_users\")\n .upsert({ user_id: userId, site_id: input.siteId, role: input.role, invited_by: input.actor.id }, { onConflict: \"user_id,site_id\" });\n if (upsert.error) throw upsert.error;\n\n return { userId, siteId: input.siteId, role: input.role, invitedByEmail };\n}\n\nasync function findUserIdByEmail(svc: ServiceClient, email: string): Promise<string | undefined> {\n // listUsers is paged; identity holds tens of users, not thousands.\n for (let page = 1; page <= 20; page++) {\n const { data, error } = await svc.auth.admin.listUsers({ page, perPage: 200 });\n if (error) throw error;\n const hit = data.users.find((u) => u.email?.toLowerCase() === email);\n if (hit) return hit.id;\n if (data.users.length < 200) break;\n }\n return undefined;\n}\n\nexport interface RemoveInput {\n userId: string;\n siteId: string;\n actor: { id: string };\n}\n\n/** Removes the membership only. The auth user survives (they may belong to other sites). */\nexport async function removeSiteUser(ctx: ServiceContext, input: RemoveInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const targetRole = ((target.data as { role?: string } | null)?.role ?? \"editor\") as \"owner\" | \"editor\";\n assertMayGrant(actorRole, input.siteId, targetRole);\n if (targetRole === \"owner\") await assertNotLastOwner(svc, input.siteId);\n\n const del = await svc.from(\"site_users\").delete().eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (del.error) throw del.error;\n}\n\nexport interface RoleInput {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n actor: { id: string };\n}\n\n/**\n * Change a member's role. Same grant rules as invite (staff anything; an owner\n * may only make people editors on their own site), plus: the last owner of a\n * site can never be demoted, otherwise nobody could manage it.\n */\nexport async function updateSiteRole(ctx: ServiceContext, input: RoleInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const current = (target.data as { role?: string } | null)?.role;\n if (!current) throw new IdentityError(\"That person isn't a member of this site.\");\n // Demoting an owner is an owner-level act — an owner may not do it to a peer.\n if (current === \"owner\") assertMayGrant(actorRole, input.siteId, \"owner\");\n if (current === \"owner\" && input.role === \"editor\") await assertNotLastOwner(svc, input.siteId);\n\n const upd = await svc.from(\"site_users\").update({ role: input.role }).eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (upd.error) throw upd.error;\n}\n\nexport async function assertNotLastOwner(svc: ServiceClient, siteId: string): Promise<void> {\n const owners = await svc.from(\"site_users\").select(\"user_id\").eq(\"site_id\", siteId).eq(\"role\", \"owner\");\n if (owners.error) throw owners.error;\n if ((owners.data ?? []).length <= 1) throw new IdentityError(\"A site needs at least one owner — make someone else an owner first.\");\n}\n\nexport interface ResendInput {\n email: string;\n siteId: string;\n actor: { id: string };\n redirectTo: string;\n}\n\n/** Send the invite email again. An already-active user gets no email — they can just sign in. */\nexport async function resendInvite(ctx: ServiceContext, input: ResendInput): Promise<{ resent: boolean; message: string }> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, \"editor\");\n\n const email = input.email.trim().toLowerCase();\n const again = await svc.auth.admin.inviteUserByEmail(email, { redirectTo: input.redirectTo, data: { site_id: input.siteId } });\n if (!again.error) return { resent: true, message: `Invitation sent again to ${email}.` };\n if (/already|exists|registered|confirmed/i.test(again.error.message)) {\n return { resent: false, message: `${email} has already accepted — they can sign in with an email link any time.` };\n }\n throw new IdentityError(`Resend failed: ${again.error.message}`);\n}\n","/**\n * Per-site branding — the Business tab. Reads and writes public.sites through\n * the signed-in user's client, so RLS decides who may (members read; owners\n * and staff write). Nothing here needs the service role.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport interface SiteBranding {\n name: string;\n logoUrl: string | null;\n faviconUrl: string | null;\n businessEmail: string | null;\n businessPhone: string | null;\n}\n\nexport type BrandingPatch = Partial<SiteBranding> & { name: string };\n\ninterface Row {\n name: string;\n logo_url: string | null;\n favicon_url: string | null;\n business_email: string | null;\n business_phone: string | null;\n}\n\nexport async function getSiteBranding(client: IdentityClient, siteId: string): Promise<SiteBranding | null> {\n const { data, error } = await client.from(\"sites\").select(\"name, logo_url, favicon_url, business_email, business_phone\").eq(\"site_id\", siteId).maybeSingle();\n if (error) throw error;\n if (!data) return null;\n const r = data as Row;\n return { name: r.name, logoUrl: r.logo_url, faviconUrl: r.favicon_url, businessEmail: r.business_email, businessPhone: r.business_phone };\n}\n\n/** Upsert the row. RLS refuses anyone but the site's owners or staff. */\nexport async function updateSiteBranding(client: IdentityClient, siteId: string, patch: BrandingPatch): Promise<void> {\n const row = {\n site_id: siteId,\n name: patch.name.trim(),\n logo_url: patch.logoUrl ?? null,\n favicon_url: patch.faviconUrl ?? null,\n business_email: emptyToNull(patch.businessEmail),\n business_phone: emptyToNull(patch.businessPhone),\n };\n const { error } = await client.from(\"sites\").upsert(row, { onConflict: \"site_id\" });\n if (error) throw error;\n}\n\nfunction emptyToNull(v: string | null | undefined): string | null {\n const s = (v ?? \"\").trim();\n return s === \"\" ? null : s;\n}\n","/**\n * The Team roster — public.site_members(site_id), a SECURITY DEFINER function\n * that returns rows only to that site's owners or staff (editors get a 42501).\n * Read with the signed-in user's client; writes are in service.ts.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError } from \"./errors\";\n\nexport interface SiteMember {\n userId: string;\n email: string;\n name: string | null;\n avatarUrl: string | null;\n role: \"owner\" | \"editor\";\n /** \"invited\" until the person has signed in once. */\n status: \"invited\" | \"active\";\n invitedAt: string;\n}\n\ninterface Row {\n user_id: string;\n email: string;\n name: string | null;\n avatar_url: string | null;\n role: string;\n status: string;\n invited_at: string;\n}\n\nexport async function listSiteMembers(client: IdentityClient, siteId: string): Promise<SiteMember[]> {\n const { data, error } = await client.rpc(\"site_members\", { p_site_id: siteId });\n if (error) {\n if (error.code === \"42501\" || /not allowed/i.test(error.message)) throw new ForbiddenError(siteId, \"owner\", null);\n throw error;\n }\n return ((data ?? []) as Row[]).map((r) => ({\n userId: r.user_id,\n email: r.email,\n name: r.name,\n avatarUrl: r.avatar_url,\n role: r.role === \"owner\" ? \"owner\" : \"editor\",\n status: r.status === \"active\" ? \"active\" : \"invited\",\n invitedAt: r.invited_at,\n }));\n}\n","/**\n * My Profile and Change Password — the signed-in user editing themselves,\n * through their own client (auth.updateUser). Name and photo live in user\n * metadata; the photo file itself is in the avatars bucket (uploaded by the\n * host, path avatars/{uid}/…).\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { IdentityError } from \"./errors\";\n\nexport const MIN_PASSWORD_LENGTH = 10;\nexport const PASSWORD_TOO_SHORT = \"Use at least 10 characters.\";\n\nexport interface ProfilePatch {\n name?: string | null;\n /** The byline — how the name appears on published posts. Blank means \"same as name\". */\n displayName?: string | null;\n avatarUrl?: string | null;\n}\n\nexport interface Profile {\n name: string | null;\n /** Resolved byline: display_name if set, else name. */\n displayName: string | null;\n avatarUrl: string | null;\n}\n\nexport function profileOf(user: { user_metadata?: Record<string, unknown> | null } | null | undefined): Profile {\n const m = user?.user_metadata ?? {};\n const str = (v: unknown) => (typeof v === \"string\" && v.trim() ? v.trim() : null);\n const name = str(m.name);\n const displayName = str(m.display_name) ?? name;\n const avatarUrl = typeof m.avatar_url === \"string\" && m.avatar_url ? m.avatar_url : null;\n return { name, displayName, avatarUrl };\n}\n\nexport async function updateProfile(client: IdentityClient, patch: ProfilePatch): Promise<Profile> {\n const data: Record<string, unknown> = {};\n if (patch.name !== undefined) data.name = (patch.name ?? \"\").trim().slice(0, 80);\n if (patch.displayName !== undefined) data.display_name = (patch.displayName ?? \"\").trim().slice(0, 80);\n if (patch.avatarUrl !== undefined) data.avatar_url = patch.avatarUrl ?? \"\";\n const { data: res, error } = await client.auth.updateUser({ data });\n if (error) throw new IdentityError(error.message);\n return profileOf(res.user);\n}\n\n/** Sets (or replaces) a password. Magic links keep working alongside it. */\nexport async function setPassword(client: IdentityClient, password: string): Promise<void> {\n if (typeof password !== \"string\" || password.length < MIN_PASSWORD_LENGTH) throw new IdentityError(PASSWORD_TOO_SHORT);\n const { error } = await client.auth.updateUser({ password });\n if (error) throw new IdentityError(error.message);\n}\n"]}
|
package/dist/shell/index.cjs
CHANGED
|
@@ -214,15 +214,6 @@ function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, hel
|
|
|
214
214
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-menu__name", children: user.name?.trim() || user.email }),
|
|
215
215
|
user.name?.trim() && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-menu__email", children: user.email })
|
|
216
216
|
] }),
|
|
217
|
-
accountHref && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: accountHref, role: "menuitem", className: "rz-menu__item", children: [
|
|
218
|
-
/* @__PURE__ */ jsxRuntime.jsx(UserIcon, {}),
|
|
219
|
-
"Account"
|
|
220
|
-
] }),
|
|
221
|
-
helpHref && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: helpHref, role: "menuitem", className: "rz-menu__item", children: [
|
|
222
|
-
/* @__PURE__ */ jsxRuntime.jsx(HelpIcon, {}),
|
|
223
|
-
"Help"
|
|
224
|
-
] }),
|
|
225
|
-
(accountHref || helpHref) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-menu__sep" }),
|
|
226
217
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rz-menu__item rz-menu__item--static", role: "menuitem", "aria-disabled": "true", children: [
|
|
227
218
|
/* @__PURE__ */ jsxRuntime.jsx(ShieldIcon, {}),
|
|
228
219
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Role" }),
|
|
@@ -233,6 +224,15 @@ function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, hel
|
|
|
233
224
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Theme" }),
|
|
234
225
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "rz-menu__end", children: /* @__PURE__ */ jsxRuntime.jsx(ThemeToggle, { theme, onChange: onTheme }) })
|
|
235
226
|
] }),
|
|
227
|
+
(accountHref || helpHref) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-menu__sep" }),
|
|
228
|
+
accountHref && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: accountHref, role: "menuitem", className: "rz-menu__item", children: [
|
|
229
|
+
/* @__PURE__ */ jsxRuntime.jsx(UserIcon, {}),
|
|
230
|
+
"Account"
|
|
231
|
+
] }),
|
|
232
|
+
helpHref && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: helpHref, role: "menuitem", className: "rz-menu__item", children: [
|
|
233
|
+
/* @__PURE__ */ jsxRuntime.jsx(HelpIcon, {}),
|
|
234
|
+
"Help"
|
|
235
|
+
] }),
|
|
236
236
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-menu__sep" }),
|
|
237
237
|
/* @__PURE__ */ jsxRuntime.jsx("form", { method: "post", action: signOutPath, className: "rz-menu__signout", children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "submit", role: "menuitem", className: "rz-menu__item", children: [
|
|
238
238
|
/* @__PURE__ */ jsxRuntime.jsx(OutIcon, {}),
|