cauthy-auth 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.mjs CHANGED
@@ -1,5 +1,21 @@
1
1
  // src/react.tsx
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
3
+
4
+ // src/permissions.ts
5
+ function matches(entry, resource, action) {
6
+ const i = entry.lastIndexOf(":");
7
+ if (i < 0) return false;
8
+ const r = entry.slice(0, i);
9
+ const a = entry.slice(i + 1);
10
+ return (r === "*" || r === resource) && (a === "*" || a === action);
11
+ }
12
+ function can(organization, resource, action) {
13
+ if (!organization?.permissions) return false;
14
+ if ((organization.denied ?? []).some((d) => matches(d, resource, action))) return false;
15
+ return organization.permissions.some((p) => matches(p, resource, action));
16
+ }
17
+
18
+ // src/react.tsx
3
19
  import { jsx } from "react/jsx-runtime";
4
20
  var Ctx = createContext(null);
5
21
  var AHEAD_MS = 6e4;
@@ -76,6 +92,7 @@ function useSession() {
76
92
  }
77
93
  export {
78
94
  CauthyProvider,
95
+ can,
79
96
  useSession
80
97
  };
81
98
  //# sourceMappingURL=react.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.tsx"],"sourcesContent":["// React: who is signed in, in the browser.\n//\n// <CauthyProvider> // once, at the root\n// <App />\n// </CauthyProvider>\n//\n// const { session, status, signIn, signOut } = useSession()\n//\n// The browser never holds a token: it asks the application's own server\n// (`/api/auth/session`), which answers who is signed in and their\n// organizations. The session is kept current by asking again a little before\n// it expires and when the window comes back into view.\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { PublicSession } from './types'\n\nexport type { JsonValue, PublicSession, SessionOrganization, SessionUser } from './types'\n\nexport interface SessionContext {\n /** The signed-in person and their organizations, or null. */\n session: PublicSession | null\n status: 'loading' | 'authenticated' | 'unauthenticated'\n /** Sends the browser to sign in, and back to `returnTo` (default: where it is now). */\n signIn(options?: { returnTo?: string; locale?: string; prompt?: 'login' }): void\n /** Signs out of this application and of Cauthy in this browser. */\n signOut(): void\n /** Asks the server again who is signed in. */\n refresh(): Promise<PublicSession | null>\n /** Where the person looks after their account: password, second step, passkeys. */\n accountUrl(returnTo?: string): string\n}\n\nexport interface CauthyProviderProps {\n children: ReactNode\n /** Where the server mounted the routes. Default: `/api/auth`. */\n basePath?: string\n /**\n * The session the server already read while rendering (SSR): the first\n * paint is then right, with no \"loading\". `null` means \"nobody\"; leave it\n * out when the server did not look.\n */\n initialSession?: PublicSession | null\n}\n\nconst Ctx = createContext<SessionContext | null>(null)\n\n/** Ask again this long before the session's token runs out. */\nconst AHEAD_MS = 60_000\n\nexport function CauthyProvider({ children, basePath = '/api/auth', initialSession }: CauthyProviderProps) {\n const base = basePath.replace(/\\/+$/, '')\n const [session, setSession] = useState<PublicSession | null>(initialSession ?? null)\n const [status, setStatus] = useState<SessionContext['status']>(\n initialSession === undefined ? 'loading' : initialSession === null ? 'unauthenticated' : 'authenticated',\n )\n const asking = useRef<Promise<PublicSession | null> | null>(null)\n\n const refresh = useCallback((): Promise<PublicSession | null> => {\n asking.current ??= (async () => {\n try {\n const res = await fetch(`${base}/session`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n if (!res.ok) throw new Error(String(res.status))\n const body = (await res.json()) as { session: PublicSession | null; status: string }\n // \"expired\" is the server unable to reach Cauthy: what is on screen\n // stays, and the next ask settles it.\n if (body.status !== 'expired') {\n setSession(body.session)\n setStatus(body.session ? 'authenticated' : 'unauthenticated')\n }\n return body.session\n } catch {\n setStatus((s) => (s === 'loading' ? 'unauthenticated' : s))\n return null\n } finally {\n asking.current = null\n }\n })()\n return asking.current\n }, [base])\n\n useEffect(() => {\n if (initialSession === undefined) void refresh()\n }, [initialSession, refresh])\n\n useEffect(() => {\n if (!session) return\n const wait = Math.max(new Date(session.expiresAt).getTime() - Date.now() - AHEAD_MS, 5_000)\n const timer = setTimeout(() => void refresh(), wait)\n return () => clearTimeout(timer)\n }, [session, refresh])\n\n useEffect(() => {\n const onVisible = () => {\n if (document.visibilityState === 'visible') void refresh()\n }\n document.addEventListener('visibilitychange', onVisible)\n return () => document.removeEventListener('visibilitychange', onVisible)\n }, [refresh])\n\n const value = useMemo<SessionContext>(\n () => ({\n session,\n status,\n refresh,\n signIn(options = {}) {\n const q = new URLSearchParams({ return_to: options.returnTo ?? window.location.pathname + window.location.search })\n if (options.locale) q.set('locale', options.locale)\n if (options.prompt) q.set('prompt', options.prompt)\n window.location.assign(`${base}/login?${q}`)\n },\n signOut() {\n window.location.assign(`${base}/logout`)\n },\n accountUrl(returnTo) {\n const back = returnTo ?? (typeof window === 'undefined' ? '/' : window.location.pathname + window.location.search)\n return `${base}/account?return_to=${encodeURIComponent(back)}`\n },\n }),\n [session, status, refresh, base],\n )\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>\n}\n\nexport function useSession(): SessionContext {\n const ctx = useContext(Ctx)\n if (ctx === null) throw new Error('cauthy-auth: useSession needs <CauthyProvider> above it')\n return ctx\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/react.tsx","../src/permissions.ts"],"sourcesContent":["// React: who is signed in, in the browser.\n//\n// <CauthyProvider> // once, at the root\n// <App />\n// </CauthyProvider>\n//\n// const { session, status, signIn, signOut } = useSession()\n//\n// The browser never holds a token: it asks the application's own server\n// (`/api/auth/session`), which answers who is signed in and their\n// organizations. The session is kept current by asking again a little before\n// it expires and when the window comes back into view.\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { PublicSession } from './types'\n\nexport type { JsonValue, PublicSession, SessionOrganization, SessionUser } from './types'\nexport { can } from './permissions'\n\nexport interface SessionContext {\n /** The signed-in person and their organizations, or null. */\n session: PublicSession | null\n status: 'loading' | 'authenticated' | 'unauthenticated'\n /** Sends the browser to sign in, and back to `returnTo` (default: where it is now). */\n signIn(options?: { returnTo?: string; locale?: string; prompt?: 'login' }): void\n /** Signs out of this application and of Cauthy in this browser. */\n signOut(): void\n /** Asks the server again who is signed in. */\n refresh(): Promise<PublicSession | null>\n /** Where the person looks after their account: password, second step, passkeys. */\n accountUrl(returnTo?: string): string\n}\n\nexport interface CauthyProviderProps {\n children: ReactNode\n /** Where the server mounted the routes. Default: `/api/auth`. */\n basePath?: string\n /**\n * The session the server already read while rendering (SSR): the first\n * paint is then right, with no \"loading\". `null` means \"nobody\"; leave it\n * out when the server did not look.\n */\n initialSession?: PublicSession | null\n}\n\nconst Ctx = createContext<SessionContext | null>(null)\n\n/** Ask again this long before the session's token runs out. */\nconst AHEAD_MS = 60_000\n\nexport function CauthyProvider({ children, basePath = '/api/auth', initialSession }: CauthyProviderProps) {\n const base = basePath.replace(/\\/+$/, '')\n const [session, setSession] = useState<PublicSession | null>(initialSession ?? null)\n const [status, setStatus] = useState<SessionContext['status']>(\n initialSession === undefined ? 'loading' : initialSession === null ? 'unauthenticated' : 'authenticated',\n )\n const asking = useRef<Promise<PublicSession | null> | null>(null)\n\n const refresh = useCallback((): Promise<PublicSession | null> => {\n asking.current ??= (async () => {\n try {\n const res = await fetch(`${base}/session`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n if (!res.ok) throw new Error(String(res.status))\n const body = (await res.json()) as { session: PublicSession | null; status: string }\n // \"expired\" is the server unable to reach Cauthy: what is on screen\n // stays, and the next ask settles it.\n if (body.status !== 'expired') {\n setSession(body.session)\n setStatus(body.session ? 'authenticated' : 'unauthenticated')\n }\n return body.session\n } catch {\n setStatus((s) => (s === 'loading' ? 'unauthenticated' : s))\n return null\n } finally {\n asking.current = null\n }\n })()\n return asking.current\n }, [base])\n\n useEffect(() => {\n if (initialSession === undefined) void refresh()\n }, [initialSession, refresh])\n\n useEffect(() => {\n if (!session) return\n const wait = Math.max(new Date(session.expiresAt).getTime() - Date.now() - AHEAD_MS, 5_000)\n const timer = setTimeout(() => void refresh(), wait)\n return () => clearTimeout(timer)\n }, [session, refresh])\n\n useEffect(() => {\n const onVisible = () => {\n if (document.visibilityState === 'visible') void refresh()\n }\n document.addEventListener('visibilitychange', onVisible)\n return () => document.removeEventListener('visibilitychange', onVisible)\n }, [refresh])\n\n const value = useMemo<SessionContext>(\n () => ({\n session,\n status,\n refresh,\n signIn(options = {}) {\n const q = new URLSearchParams({ return_to: options.returnTo ?? window.location.pathname + window.location.search })\n if (options.locale) q.set('locale', options.locale)\n if (options.prompt) q.set('prompt', options.prompt)\n window.location.assign(`${base}/login?${q}`)\n },\n signOut() {\n window.location.assign(`${base}/logout`)\n },\n accountUrl(returnTo) {\n const back = returnTo ?? (typeof window === 'undefined' ? '/' : window.location.pathname + window.location.search)\n return `${base}/account?return_to=${encodeURIComponent(back)}`\n },\n }),\n [session, status, refresh, base],\n )\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>\n}\n\nexport function useSession(): SessionContext {\n const ctx = useContext(Ctx)\n if (ctx === null) throw new Error('cauthy-auth: useSession needs <CauthyProvider> above it')\n return ctx\n}\n","// What a person may do in an organization.\n//\n// When a system keeps its catalog of roles in Cauthy, each of a person's\n// organizations arrives with what their role — and anything given or denied to\n// them apart from it — lets them do. Checking is a lookup in that list: no\n// call to Cauthy, on the server or in the browser.\n\n/** The part of an organization that says what its member may do. */\nexport interface Permitted {\n /** \"resource:action\" entries the person is allowed; \"*\" is any. Absent when the system keeps no catalog in Cauthy. */\n permissions?: string[]\n /** Entries the person is denied whatever else allows them. */\n denied?: string[]\n}\n\nfunction matches(entry: string, resource: string, action: string): boolean {\n // The resource may itself hold a colon: the action is what follows the last one.\n const i = entry.lastIndexOf(':')\n if (i < 0) return false\n const r = entry.slice(0, i)\n const a = entry.slice(i + 1)\n return (r === '*' || r === resource) && (a === '*' || a === action)\n}\n\n/**\n * Whether the person may do an action on a resource in an organization:\n * allowed by some entry, and denied by none. A denial wins.\n *\n * An organization of a system that keeps no catalog in Cauthy carries no\n * permissions, and the answer is `false`: decide by its `role` instead.\n *\n * if (!can(organization, 'invoicing', 'write')) return forbidden()\n */\nexport function can(organization: Permitted | null | undefined, resource: string, action: string): boolean {\n if (!organization?.permissions) return false\n if ((organization.denied ?? []).some((d) => matches(d, resource, action))) return false\n return organization.permissions.some((p) => matches(p, resource, action))\n}\n\n// ── A system's catalog of roles ────────────────────────────────────────────\n\n/** Something a role may do: an action on a resource, in organizations of a kind (\"*\", the default, is every kind). */\nexport interface PolicyPermission {\n kind?: string\n resource: string\n action: string\n}\n\nexport interface PolicyRole {\n role: string\n name?: string\n description?: string\n /** Roles whose permissions this one has as well. */\n inherits?: string[]\n permissions?: PolicyPermission[]\n}\n\nexport interface PushPolicyOptions {\n /** The system whose catalog this is. Default: `CAUTHY_SYSTEM`. */\n system?: string\n roles: PolicyRole[]\n /** Cauthy's address. Default: `CAUTHY_ISSUER`. */\n issuer?: string\n /** The workspace's slug. Default: `CAUTHY_TENANT`. */\n tenant?: string\n /** A personal access token with `organizations:manage`. Default: `CAUTHY_TOKEN`. */\n token?: string\n fetch?: typeof fetch\n}\n\n/**\n * Gives Cauthy a system's catalog of roles, replacing the one it has. It is\n * the way to load a first catalog from what a system already had; from then on\n * the catalog is Cauthy's, edited in its dashboard, and pushing again would\n * overwrite what was edited there.\n */\nexport async function pushPolicy(o: PushPolicyOptions): Promise<void> {\n const env = (n: string) => (typeof process !== 'undefined' ? process.env?.[n] : undefined)\n const issuer = (o.issuer ?? env('CAUTHY_ISSUER') ?? '').replace(/\\/+$/, '')\n const system = o.system ?? env('CAUTHY_SYSTEM') ?? ''\n const tenant = o.tenant ?? env('CAUTHY_TENANT') ?? ''\n const token = o.token ?? env('CAUTHY_TOKEN') ?? ''\n for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {\n if (v === '') throw new Error(`cauthy-auth: ${name} is required to push a policy`)\n }\n const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {\n method: 'PUT',\n headers: { authorization: `Bearer ${token}`, 'x-tenant-key': tenant, 'content-type': 'application/json' },\n body: JSON.stringify({ roles: o.roles }),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { errors?: { message?: string }[] } | null\n throw new Error(`cauthy-auth: Cauthy refused the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`)\n }\n}\n"],"mappings":"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","names":[]}
@@ -64,6 +64,15 @@ interface SessionOrganization {
64
64
  role: string;
65
65
  /** What the system keeps about the organization, as it filed it in Cauthy. */
66
66
  metadata: Record<string, JsonValue>;
67
+ /**
68
+ * What the person may do here, as "resource:action" entries ("*" is any):
69
+ * their role's permissions, what it inherits, and anything given to them
70
+ * apart from it. Absent when the system keeps no catalog of roles in Cauthy.
71
+ * Check with `can(organization, resource, action)`.
72
+ */
73
+ permissions?: string[];
74
+ /** What the person is denied here, whatever else allows it. */
75
+ denied?: string[];
67
76
  }
68
77
  /** A signed-in person, as the server sees them. */
69
78
  interface Session {
@@ -64,6 +64,15 @@ interface SessionOrganization {
64
64
  role: string;
65
65
  /** What the system keeps about the organization, as it filed it in Cauthy. */
66
66
  metadata: Record<string, JsonValue>;
67
+ /**
68
+ * What the person may do here, as "resource:action" entries ("*" is any):
69
+ * their role's permissions, what it inherits, and anything given to them
70
+ * apart from it. Absent when the system keeps no catalog of roles in Cauthy.
71
+ * Check with `can(organization, resource, action)`.
72
+ */
73
+ permissions?: string[];
74
+ /** What the person is denied here, whatever else allows it. */
75
+ denied?: string[];
67
76
  }
68
77
  /** A signed-in person, as the server sees them. */
69
78
  interface Session {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cauthy-auth",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Sign in with Cauthy: one route to mount, getSession on the server, useSession in the browser. No database, no screens to build.",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",