cauthy-auth 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/cauthy-C5_lopMQ.d.ts +108 -0
- package/dist/cauthy-mNADGlNl.d.mts +108 -0
- package/dist/cli.js +202 -2
- package/dist/fastify.d.mts +3 -2
- package/dist/fastify.d.ts +3 -2
- package/dist/index.d.mts +48 -2
- package/dist/index.d.ts +48 -2
- package/dist/index.js +343 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +335 -13
- package/dist/index.mjs.map +1 -1
- package/dist/permissions-B-a3eN0h.d.mts +52 -0
- package/dist/permissions-B-a3eN0h.d.ts +52 -0
- package/dist/react.d.mts +4 -2
- package/dist/react.d.ts +4 -2
- package/dist/react.js +18 -0
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +17 -0
- package/dist/react.mjs.map +1 -1
- package/dist/system-BWLddIt2.d.mts +233 -0
- package/dist/system-BWLddIt2.d.ts +233 -0
- package/package.json +1 -1
- package/dist/cauthy-By2cryXN.d.ts +0 -56
- package/dist/cauthy-DsUNqjg6.d.mts +0 -56
- package/dist/types-BwCjBn-K.d.mts +0 -97
- package/dist/types-BwCjBn-K.d.ts +0 -97
package/dist/react.js.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,mBAA6G;AA2GpG;AA5ET,IAAM,UAAM,4BAAqC,IAAI;AAGrD,IAAM,WAAW;AAEV,SAAS,eAAe,EAAE,UAAU,WAAW,aAAa,eAAe,GAAwB;AACxG,QAAM,OAAO,SAAS,QAAQ,QAAQ,EAAE;AACxC,QAAM,CAAC,SAAS,UAAU,QAAI,uBAA+B,kBAAkB,IAAI;AACnF,QAAM,CAAC,QAAQ,SAAS,QAAI;AAAA,IAC1B,mBAAmB,SAAY,YAAY,mBAAmB,OAAO,oBAAoB;AAAA,EAC3F;AACA,QAAM,aAAS,qBAA6C,IAAI;AAEhE,QAAM,cAAU,0BAAY,MAAqC;AAC/D,WAAO,aAAa,YAAY;AAC9B,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,aAAa,eAAe,SAAS,EAAE,QAAQ,mBAAmB,EAAE,CAAC;AAClH,YAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,OAAO,IAAI,MAAM,CAAC;AAC/C,cAAM,OAAQ,MAAM,IAAI,KAAK;AAG7B,YAAI,KAAK,WAAW,WAAW;AAC7B,qBAAW,KAAK,OAAO;AACvB,oBAAU,KAAK,UAAU,kBAAkB,iBAAiB;AAAA,QAC9D;AACA,eAAO,KAAK;AAAA,MACd,QAAQ;AACN,kBAAU,CAAC,MAAO,MAAM,YAAY,oBAAoB,CAAE;AAC1D,eAAO;AAAA,MACT,UAAE;AACA,eAAO,UAAU;AAAA,MACnB;AAAA,IACF,GAAG;AACH,WAAO,OAAO;AAAA,EAChB,GAAG,CAAC,IAAI,CAAC;AAET,8BAAU,MAAM;AACd,QAAI,mBAAmB,OAAW,MAAK,QAAQ;AAAA,EACjD,GAAG,CAAC,gBAAgB,OAAO,CAAC;AAE5B,8BAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,KAAK,IAAI,IAAI,KAAK,QAAQ,SAAS,EAAE,QAAQ,IAAI,KAAK,IAAI,IAAI,UAAU,GAAK;AAC1F,UAAM,QAAQ,WAAW,MAAM,KAAK,QAAQ,GAAG,IAAI;AACnD,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,SAAS,OAAO,CAAC;AAErB,8BAAU,MAAM;AACd,UAAM,YAAY,MAAM;AACtB,UAAI,SAAS,oBAAoB,UAAW,MAAK,QAAQ;AAAA,IAC3D;AACA,aAAS,iBAAiB,oBAAoB,SAAS;AACvD,WAAO,MAAM,SAAS,oBAAoB,oBAAoB,SAAS;AAAA,EACzE,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,YAAQ;AAAA,IACZ,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,UAAU,CAAC,GAAG;AACnB,cAAM,IAAI,IAAI,gBAAgB,EAAE,WAAW,QAAQ,YAAY,OAAO,SAAS,WAAW,OAAO,SAAS,OAAO,CAAC;AAClH,YAAI,QAAQ,OAAQ,GAAE,IAAI,UAAU,QAAQ,MAAM;AAClD,YAAI,QAAQ,OAAQ,GAAE,IAAI,UAAU,QAAQ,MAAM;AAClD,eAAO,SAAS,OAAO,GAAG,IAAI,UAAU,CAAC,EAAE;AAAA,MAC7C;AAAA,MACA,UAAU;AACR,eAAO,SAAS,OAAO,GAAG,IAAI,SAAS;AAAA,MACzC;AAAA,MACA,WAAW,UAAU;AACnB,cAAM,OAAO,aAAa,OAAO,WAAW,cAAc,MAAM,OAAO,SAAS,WAAW,OAAO,SAAS;AAC3G,eAAO,GAAG,IAAI,sBAAsB,mBAAmB,IAAI,CAAC;AAAA,MAC9D;AAAA,IACF;AAAA,IACA,CAAC,SAAS,QAAQ,SAAS,IAAI;AAAA,EACjC;AACA,SAAO,4CAAC,IAAI,UAAJ,EAAa,OAAe,UAAS;AAC/C;AAEO,SAAS,aAA6B;AAC3C,QAAM,UAAM,yBAAW,GAAG;AAC1B,MAAI,QAAQ,KAAM,OAAM,IAAI,MAAM,yDAAyD;AAC3F,SAAO;AACT;","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":[]}
|
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
|
package/dist/react.mjs.map
CHANGED
|
@@ -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":[]}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { JWK } from 'jose';
|
|
2
|
+
|
|
3
|
+
/** How an application is set up to sign its people in with Cauthy. */
|
|
4
|
+
interface CauthyOptions {
|
|
5
|
+
/** Cauthy's public address, e.g. `https://id.example.com`. Default: `CAUTHY_ISSUER`. */
|
|
6
|
+
issuer?: string;
|
|
7
|
+
/** The application's OAuth client id. Default: `CAUTHY_CLIENT_ID`. */
|
|
8
|
+
clientId?: string;
|
|
9
|
+
/** Its secret, when the client is confidential. Default: `CAUTHY_CLIENT_SECRET`. */
|
|
10
|
+
clientSecret?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The key the session cookie is encrypted with: any string of 32 characters
|
|
13
|
+
* or more, kept on the server. Default: `CAUTHY_SECRET`.
|
|
14
|
+
*/
|
|
15
|
+
secret?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The system this application is, as its organizations are filed under in
|
|
18
|
+
* Cauthy (`"pondari"`). When set, a session's organizations are that
|
|
19
|
+
* system's. Default: `CAUTHY_SYSTEM`.
|
|
20
|
+
*/
|
|
21
|
+
system?: string;
|
|
22
|
+
/** Where the routes are mounted. Default: `/api/auth`. */
|
|
23
|
+
basePath?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The application's own public origin (`https://app.example.com`), for the
|
|
26
|
+
* redirect URI. Default: the origin of each request, which is right unless
|
|
27
|
+
* a proxy hides it.
|
|
28
|
+
*/
|
|
29
|
+
baseURL?: string;
|
|
30
|
+
/** Where a person lands after signing in, when no `return_to` was asked. Default: `/`. */
|
|
31
|
+
afterSignIn?: string;
|
|
32
|
+
/** Where a person lands after signing out. Default: `/`. */
|
|
33
|
+
afterSignOut?: string;
|
|
34
|
+
/** Name of the session cookie. Default: `cauthy_session`. */
|
|
35
|
+
cookieName?: string;
|
|
36
|
+
/**
|
|
37
|
+
* A key the application proves who it is with when it acts on its own
|
|
38
|
+
* (`auth.system`), instead of its secret. Default: `CAUTHY_SYSTEM_KEY` and
|
|
39
|
+
* `CAUTHY_SYSTEM_KEY_ID`.
|
|
40
|
+
*/
|
|
41
|
+
systemKey?: SystemKey;
|
|
42
|
+
/** `fetch` to reach Cauthy with. Default: the global one. */
|
|
43
|
+
fetch?: typeof fetch;
|
|
44
|
+
}
|
|
45
|
+
/** The person signed in. Every moment is UTC. */
|
|
46
|
+
interface SessionUser {
|
|
47
|
+
id: string;
|
|
48
|
+
email: string;
|
|
49
|
+
name: string;
|
|
50
|
+
emailVerified: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* True when the account ran out of time to confirm its address: let it read
|
|
53
|
+
* and refuse its writes.
|
|
54
|
+
*/
|
|
55
|
+
restricted: boolean;
|
|
56
|
+
/** When an unconfirmed address stops being enough (ISO 8601, UTC), if there is a deadline. */
|
|
57
|
+
emailVerifyBy?: string;
|
|
58
|
+
/** The language the person reads in ("es", "en-US"), when Cauthy knows it. */
|
|
59
|
+
locale?: string;
|
|
60
|
+
}
|
|
61
|
+
/** A value JSON can carry. What crosses from server to browser is typed as this, never `unknown`. */
|
|
62
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
63
|
+
[key: string]: JsonValue;
|
|
64
|
+
};
|
|
65
|
+
/** An organization the person belongs to, with their role in it. */
|
|
66
|
+
interface SessionOrganization {
|
|
67
|
+
id: string;
|
|
68
|
+
system: string;
|
|
69
|
+
name: string;
|
|
70
|
+
kind: string;
|
|
71
|
+
externalId?: string;
|
|
72
|
+
role: string;
|
|
73
|
+
/** What the system keeps about the organization, as it filed it in Cauthy. */
|
|
74
|
+
metadata: Record<string, JsonValue>;
|
|
75
|
+
/**
|
|
76
|
+
* What the person may do here, as "resource:action" entries ("*" is any):
|
|
77
|
+
* their role's permissions, what it inherits, and anything given to them
|
|
78
|
+
* apart from it. Absent when the system keeps no catalog of roles in Cauthy.
|
|
79
|
+
* Check with `can(organization, resource, action)`.
|
|
80
|
+
*/
|
|
81
|
+
permissions?: string[];
|
|
82
|
+
/** What the person is denied here, whatever else allows it. */
|
|
83
|
+
denied?: string[];
|
|
84
|
+
}
|
|
85
|
+
/** A signed-in person, as the server sees them. */
|
|
86
|
+
interface Session {
|
|
87
|
+
user: SessionUser;
|
|
88
|
+
/** The Cauthy tenant the account is of. */
|
|
89
|
+
tenantId: string;
|
|
90
|
+
/** The roles the account holds in that tenant. */
|
|
91
|
+
roles: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Cauthy's access token for this person: send it as `Authorization: Bearer`
|
|
94
|
+
* to another service that verifies Cauthy's tokens. Never give it to the browser.
|
|
95
|
+
*/
|
|
96
|
+
accessToken: string;
|
|
97
|
+
/** When the access token expires and the session is checked with Cauthy again (ISO 8601, UTC). */
|
|
98
|
+
expiresAt: string;
|
|
99
|
+
}
|
|
100
|
+
/** What the browser is told of a session: who, and their organizations — never a token. */
|
|
101
|
+
interface PublicSession {
|
|
102
|
+
user: SessionUser;
|
|
103
|
+
tenantId: string;
|
|
104
|
+
roles: string[];
|
|
105
|
+
organizations: SessionOrganization[];
|
|
106
|
+
expiresAt: string;
|
|
107
|
+
}
|
|
108
|
+
/** Why there is no session. */
|
|
109
|
+
type SessionStatus = 'authenticated' | 'unauthenticated' | 'expired';
|
|
110
|
+
declare class CauthyConfigError extends Error {
|
|
111
|
+
constructor(message: string);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The key an application signs its assertions with. */
|
|
115
|
+
interface SystemKey {
|
|
116
|
+
/** The private key: PKCS#8 PEM, or a JWK (as an object or its JSON). RSA, for RS256. */
|
|
117
|
+
privateKey: string | JWK;
|
|
118
|
+
/** The id the public key is published under (`kid`). */
|
|
119
|
+
keyId: string;
|
|
120
|
+
}
|
|
121
|
+
interface SystemOptions {
|
|
122
|
+
/** Cauthy's address. Default: `CAUTHY_ISSUER`. */
|
|
123
|
+
issuer?: string;
|
|
124
|
+
/** The application's client id. Default: `CAUTHY_CLIENT_ID`. */
|
|
125
|
+
clientId?: string;
|
|
126
|
+
/** Its secret. Default: `CAUTHY_CLIENT_SECRET`. Not needed with `key`. */
|
|
127
|
+
clientSecret?: string;
|
|
128
|
+
/**
|
|
129
|
+
* The key to prove who the application is with, instead of the secret.
|
|
130
|
+
* Default: `CAUTHY_SYSTEM_KEY` (the private key) and `CAUTHY_SYSTEM_KEY_ID`.
|
|
131
|
+
*/
|
|
132
|
+
key?: SystemKey;
|
|
133
|
+
fetch?: typeof fetch;
|
|
134
|
+
}
|
|
135
|
+
/** Cauthy refused a request of the application's own. */
|
|
136
|
+
declare class CauthyApiError extends Error {
|
|
137
|
+
readonly status: number;
|
|
138
|
+
/** Cauthy's code for what went wrong: `organization_not_found`, `other_system`, `outside_system_scope`… */
|
|
139
|
+
readonly code: string;
|
|
140
|
+
constructor(status: number,
|
|
141
|
+
/** Cauthy's code for what went wrong: `organization_not_found`, `other_system`, `outside_system_scope`… */
|
|
142
|
+
code: string, message: string);
|
|
143
|
+
}
|
|
144
|
+
/** An organization of the application's system. Every moment is UTC. */
|
|
145
|
+
interface Organization {
|
|
146
|
+
id: string;
|
|
147
|
+
system: string;
|
|
148
|
+
name: string;
|
|
149
|
+
kind: string;
|
|
150
|
+
/** The id the application knows it by. */
|
|
151
|
+
externalId?: string;
|
|
152
|
+
status: 'active' | 'archived';
|
|
153
|
+
metadata: Record<string, JsonValue>;
|
|
154
|
+
members: number;
|
|
155
|
+
createdAt: string;
|
|
156
|
+
updatedAt: string;
|
|
157
|
+
}
|
|
158
|
+
interface OrganizationMember {
|
|
159
|
+
userId: string;
|
|
160
|
+
email: string;
|
|
161
|
+
name: string;
|
|
162
|
+
/** The account's status: a disabled member is still listed. */
|
|
163
|
+
status: string;
|
|
164
|
+
role: string;
|
|
165
|
+
/** True when the address had no account: one was made and invited. */
|
|
166
|
+
invited?: boolean;
|
|
167
|
+
}
|
|
168
|
+
/** Who to add: an account by its id, or anybody by their address (invited when new). */
|
|
169
|
+
type NewMember = {
|
|
170
|
+
role: string;
|
|
171
|
+
name?: string;
|
|
172
|
+
locale?: string;
|
|
173
|
+
} & ({
|
|
174
|
+
userId: string;
|
|
175
|
+
email?: undefined;
|
|
176
|
+
} | {
|
|
177
|
+
email: string;
|
|
178
|
+
userId?: undefined;
|
|
179
|
+
});
|
|
180
|
+
interface CauthySystem {
|
|
181
|
+
/** The application's own token, asked for when needed and kept until it is about to run out. */
|
|
182
|
+
token(): Promise<string>;
|
|
183
|
+
/** Any route the application's token may call; answers what Cauthy put under `data`. */
|
|
184
|
+
request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
|
|
185
|
+
organizations: {
|
|
186
|
+
list(query?: {
|
|
187
|
+
q?: string;
|
|
188
|
+
status?: 'active' | 'archived';
|
|
189
|
+
limit?: number;
|
|
190
|
+
offset?: number;
|
|
191
|
+
}): Promise<{
|
|
192
|
+
items: Organization[];
|
|
193
|
+
total: number;
|
|
194
|
+
}>;
|
|
195
|
+
create(input: {
|
|
196
|
+
name: string;
|
|
197
|
+
kind?: string;
|
|
198
|
+
externalId?: string;
|
|
199
|
+
metadata?: Record<string, JsonValue>;
|
|
200
|
+
}): Promise<Organization>;
|
|
201
|
+
get(id: string): Promise<Organization>;
|
|
202
|
+
update(id: string, patch: {
|
|
203
|
+
name?: string;
|
|
204
|
+
kind?: string;
|
|
205
|
+
status?: 'active' | 'archived';
|
|
206
|
+
metadata?: Record<string, JsonValue>;
|
|
207
|
+
}): Promise<Organization>;
|
|
208
|
+
/** Removes the organization and every membership of it, for good. */
|
|
209
|
+
remove(id: string): Promise<void>;
|
|
210
|
+
};
|
|
211
|
+
members: {
|
|
212
|
+
list(organizationId: string): Promise<OrganizationMember[]>;
|
|
213
|
+
add(organizationId: string, member: NewMember): Promise<OrganizationMember>;
|
|
214
|
+
setRole(organizationId: string, userId: string, role: string): Promise<OrganizationMember>;
|
|
215
|
+
remove(organizationId: string, userId: string): Promise<void>;
|
|
216
|
+
};
|
|
217
|
+
/** Invites a person who has no account yet; answers the account made for them. */
|
|
218
|
+
invite(person: {
|
|
219
|
+
email: string;
|
|
220
|
+
name?: string;
|
|
221
|
+
locale?: string;
|
|
222
|
+
}): Promise<{
|
|
223
|
+
id: string;
|
|
224
|
+
email: string;
|
|
225
|
+
}>;
|
|
226
|
+
/** The public half of `key`, to serve at the address Cauthy was given (`jwks_uri`). Empty with a secret. */
|
|
227
|
+
publicJwks(): Promise<{
|
|
228
|
+
keys: JWK[];
|
|
229
|
+
}>;
|
|
230
|
+
}
|
|
231
|
+
declare function createSystem(given?: SystemOptions): CauthySystem;
|
|
232
|
+
|
|
233
|
+
export { CauthyApiError as C, type JsonValue as J, type NewMember as N, type Organization as O, type PublicSession as P, type Session as S, CauthyConfigError as a, type CauthyOptions as b, type CauthySystem as c, type OrganizationMember as d, type SessionOrganization as e, type SessionStatus as f, type SessionUser as g, type SystemKey as h, type SystemOptions as i, createSystem as j };
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { JWK } from 'jose';
|
|
2
|
+
|
|
3
|
+
/** How an application is set up to sign its people in with Cauthy. */
|
|
4
|
+
interface CauthyOptions {
|
|
5
|
+
/** Cauthy's public address, e.g. `https://id.example.com`. Default: `CAUTHY_ISSUER`. */
|
|
6
|
+
issuer?: string;
|
|
7
|
+
/** The application's OAuth client id. Default: `CAUTHY_CLIENT_ID`. */
|
|
8
|
+
clientId?: string;
|
|
9
|
+
/** Its secret, when the client is confidential. Default: `CAUTHY_CLIENT_SECRET`. */
|
|
10
|
+
clientSecret?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The key the session cookie is encrypted with: any string of 32 characters
|
|
13
|
+
* or more, kept on the server. Default: `CAUTHY_SECRET`.
|
|
14
|
+
*/
|
|
15
|
+
secret?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The system this application is, as its organizations are filed under in
|
|
18
|
+
* Cauthy (`"pondari"`). When set, a session's organizations are that
|
|
19
|
+
* system's. Default: `CAUTHY_SYSTEM`.
|
|
20
|
+
*/
|
|
21
|
+
system?: string;
|
|
22
|
+
/** Where the routes are mounted. Default: `/api/auth`. */
|
|
23
|
+
basePath?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The application's own public origin (`https://app.example.com`), for the
|
|
26
|
+
* redirect URI. Default: the origin of each request, which is right unless
|
|
27
|
+
* a proxy hides it.
|
|
28
|
+
*/
|
|
29
|
+
baseURL?: string;
|
|
30
|
+
/** Where a person lands after signing in, when no `return_to` was asked. Default: `/`. */
|
|
31
|
+
afterSignIn?: string;
|
|
32
|
+
/** Where a person lands after signing out. Default: `/`. */
|
|
33
|
+
afterSignOut?: string;
|
|
34
|
+
/** Name of the session cookie. Default: `cauthy_session`. */
|
|
35
|
+
cookieName?: string;
|
|
36
|
+
/**
|
|
37
|
+
* A key the application proves who it is with when it acts on its own
|
|
38
|
+
* (`auth.system`), instead of its secret. Default: `CAUTHY_SYSTEM_KEY` and
|
|
39
|
+
* `CAUTHY_SYSTEM_KEY_ID`.
|
|
40
|
+
*/
|
|
41
|
+
systemKey?: SystemKey;
|
|
42
|
+
/** `fetch` to reach Cauthy with. Default: the global one. */
|
|
43
|
+
fetch?: typeof fetch;
|
|
44
|
+
}
|
|
45
|
+
/** The person signed in. Every moment is UTC. */
|
|
46
|
+
interface SessionUser {
|
|
47
|
+
id: string;
|
|
48
|
+
email: string;
|
|
49
|
+
name: string;
|
|
50
|
+
emailVerified: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* True when the account ran out of time to confirm its address: let it read
|
|
53
|
+
* and refuse its writes.
|
|
54
|
+
*/
|
|
55
|
+
restricted: boolean;
|
|
56
|
+
/** When an unconfirmed address stops being enough (ISO 8601, UTC), if there is a deadline. */
|
|
57
|
+
emailVerifyBy?: string;
|
|
58
|
+
/** The language the person reads in ("es", "en-US"), when Cauthy knows it. */
|
|
59
|
+
locale?: string;
|
|
60
|
+
}
|
|
61
|
+
/** A value JSON can carry. What crosses from server to browser is typed as this, never `unknown`. */
|
|
62
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
63
|
+
[key: string]: JsonValue;
|
|
64
|
+
};
|
|
65
|
+
/** An organization the person belongs to, with their role in it. */
|
|
66
|
+
interface SessionOrganization {
|
|
67
|
+
id: string;
|
|
68
|
+
system: string;
|
|
69
|
+
name: string;
|
|
70
|
+
kind: string;
|
|
71
|
+
externalId?: string;
|
|
72
|
+
role: string;
|
|
73
|
+
/** What the system keeps about the organization, as it filed it in Cauthy. */
|
|
74
|
+
metadata: Record<string, JsonValue>;
|
|
75
|
+
/**
|
|
76
|
+
* What the person may do here, as "resource:action" entries ("*" is any):
|
|
77
|
+
* their role's permissions, what it inherits, and anything given to them
|
|
78
|
+
* apart from it. Absent when the system keeps no catalog of roles in Cauthy.
|
|
79
|
+
* Check with `can(organization, resource, action)`.
|
|
80
|
+
*/
|
|
81
|
+
permissions?: string[];
|
|
82
|
+
/** What the person is denied here, whatever else allows it. */
|
|
83
|
+
denied?: string[];
|
|
84
|
+
}
|
|
85
|
+
/** A signed-in person, as the server sees them. */
|
|
86
|
+
interface Session {
|
|
87
|
+
user: SessionUser;
|
|
88
|
+
/** The Cauthy tenant the account is of. */
|
|
89
|
+
tenantId: string;
|
|
90
|
+
/** The roles the account holds in that tenant. */
|
|
91
|
+
roles: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Cauthy's access token for this person: send it as `Authorization: Bearer`
|
|
94
|
+
* to another service that verifies Cauthy's tokens. Never give it to the browser.
|
|
95
|
+
*/
|
|
96
|
+
accessToken: string;
|
|
97
|
+
/** When the access token expires and the session is checked with Cauthy again (ISO 8601, UTC). */
|
|
98
|
+
expiresAt: string;
|
|
99
|
+
}
|
|
100
|
+
/** What the browser is told of a session: who, and their organizations — never a token. */
|
|
101
|
+
interface PublicSession {
|
|
102
|
+
user: SessionUser;
|
|
103
|
+
tenantId: string;
|
|
104
|
+
roles: string[];
|
|
105
|
+
organizations: SessionOrganization[];
|
|
106
|
+
expiresAt: string;
|
|
107
|
+
}
|
|
108
|
+
/** Why there is no session. */
|
|
109
|
+
type SessionStatus = 'authenticated' | 'unauthenticated' | 'expired';
|
|
110
|
+
declare class CauthyConfigError extends Error {
|
|
111
|
+
constructor(message: string);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The key an application signs its assertions with. */
|
|
115
|
+
interface SystemKey {
|
|
116
|
+
/** The private key: PKCS#8 PEM, or a JWK (as an object or its JSON). RSA, for RS256. */
|
|
117
|
+
privateKey: string | JWK;
|
|
118
|
+
/** The id the public key is published under (`kid`). */
|
|
119
|
+
keyId: string;
|
|
120
|
+
}
|
|
121
|
+
interface SystemOptions {
|
|
122
|
+
/** Cauthy's address. Default: `CAUTHY_ISSUER`. */
|
|
123
|
+
issuer?: string;
|
|
124
|
+
/** The application's client id. Default: `CAUTHY_CLIENT_ID`. */
|
|
125
|
+
clientId?: string;
|
|
126
|
+
/** Its secret. Default: `CAUTHY_CLIENT_SECRET`. Not needed with `key`. */
|
|
127
|
+
clientSecret?: string;
|
|
128
|
+
/**
|
|
129
|
+
* The key to prove who the application is with, instead of the secret.
|
|
130
|
+
* Default: `CAUTHY_SYSTEM_KEY` (the private key) and `CAUTHY_SYSTEM_KEY_ID`.
|
|
131
|
+
*/
|
|
132
|
+
key?: SystemKey;
|
|
133
|
+
fetch?: typeof fetch;
|
|
134
|
+
}
|
|
135
|
+
/** Cauthy refused a request of the application's own. */
|
|
136
|
+
declare class CauthyApiError extends Error {
|
|
137
|
+
readonly status: number;
|
|
138
|
+
/** Cauthy's code for what went wrong: `organization_not_found`, `other_system`, `outside_system_scope`… */
|
|
139
|
+
readonly code: string;
|
|
140
|
+
constructor(status: number,
|
|
141
|
+
/** Cauthy's code for what went wrong: `organization_not_found`, `other_system`, `outside_system_scope`… */
|
|
142
|
+
code: string, message: string);
|
|
143
|
+
}
|
|
144
|
+
/** An organization of the application's system. Every moment is UTC. */
|
|
145
|
+
interface Organization {
|
|
146
|
+
id: string;
|
|
147
|
+
system: string;
|
|
148
|
+
name: string;
|
|
149
|
+
kind: string;
|
|
150
|
+
/** The id the application knows it by. */
|
|
151
|
+
externalId?: string;
|
|
152
|
+
status: 'active' | 'archived';
|
|
153
|
+
metadata: Record<string, JsonValue>;
|
|
154
|
+
members: number;
|
|
155
|
+
createdAt: string;
|
|
156
|
+
updatedAt: string;
|
|
157
|
+
}
|
|
158
|
+
interface OrganizationMember {
|
|
159
|
+
userId: string;
|
|
160
|
+
email: string;
|
|
161
|
+
name: string;
|
|
162
|
+
/** The account's status: a disabled member is still listed. */
|
|
163
|
+
status: string;
|
|
164
|
+
role: string;
|
|
165
|
+
/** True when the address had no account: one was made and invited. */
|
|
166
|
+
invited?: boolean;
|
|
167
|
+
}
|
|
168
|
+
/** Who to add: an account by its id, or anybody by their address (invited when new). */
|
|
169
|
+
type NewMember = {
|
|
170
|
+
role: string;
|
|
171
|
+
name?: string;
|
|
172
|
+
locale?: string;
|
|
173
|
+
} & ({
|
|
174
|
+
userId: string;
|
|
175
|
+
email?: undefined;
|
|
176
|
+
} | {
|
|
177
|
+
email: string;
|
|
178
|
+
userId?: undefined;
|
|
179
|
+
});
|
|
180
|
+
interface CauthySystem {
|
|
181
|
+
/** The application's own token, asked for when needed and kept until it is about to run out. */
|
|
182
|
+
token(): Promise<string>;
|
|
183
|
+
/** Any route the application's token may call; answers what Cauthy put under `data`. */
|
|
184
|
+
request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
|
|
185
|
+
organizations: {
|
|
186
|
+
list(query?: {
|
|
187
|
+
q?: string;
|
|
188
|
+
status?: 'active' | 'archived';
|
|
189
|
+
limit?: number;
|
|
190
|
+
offset?: number;
|
|
191
|
+
}): Promise<{
|
|
192
|
+
items: Organization[];
|
|
193
|
+
total: number;
|
|
194
|
+
}>;
|
|
195
|
+
create(input: {
|
|
196
|
+
name: string;
|
|
197
|
+
kind?: string;
|
|
198
|
+
externalId?: string;
|
|
199
|
+
metadata?: Record<string, JsonValue>;
|
|
200
|
+
}): Promise<Organization>;
|
|
201
|
+
get(id: string): Promise<Organization>;
|
|
202
|
+
update(id: string, patch: {
|
|
203
|
+
name?: string;
|
|
204
|
+
kind?: string;
|
|
205
|
+
status?: 'active' | 'archived';
|
|
206
|
+
metadata?: Record<string, JsonValue>;
|
|
207
|
+
}): Promise<Organization>;
|
|
208
|
+
/** Removes the organization and every membership of it, for good. */
|
|
209
|
+
remove(id: string): Promise<void>;
|
|
210
|
+
};
|
|
211
|
+
members: {
|
|
212
|
+
list(organizationId: string): Promise<OrganizationMember[]>;
|
|
213
|
+
add(organizationId: string, member: NewMember): Promise<OrganizationMember>;
|
|
214
|
+
setRole(organizationId: string, userId: string, role: string): Promise<OrganizationMember>;
|
|
215
|
+
remove(organizationId: string, userId: string): Promise<void>;
|
|
216
|
+
};
|
|
217
|
+
/** Invites a person who has no account yet; answers the account made for them. */
|
|
218
|
+
invite(person: {
|
|
219
|
+
email: string;
|
|
220
|
+
name?: string;
|
|
221
|
+
locale?: string;
|
|
222
|
+
}): Promise<{
|
|
223
|
+
id: string;
|
|
224
|
+
email: string;
|
|
225
|
+
}>;
|
|
226
|
+
/** The public half of `key`, to serve at the address Cauthy was given (`jwks_uri`). Empty with a secret. */
|
|
227
|
+
publicJwks(): Promise<{
|
|
228
|
+
keys: JWK[];
|
|
229
|
+
}>;
|
|
230
|
+
}
|
|
231
|
+
declare function createSystem(given?: SystemOptions): CauthySystem;
|
|
232
|
+
|
|
233
|
+
export { CauthyApiError as C, type JsonValue as J, type NewMember as N, type Organization as O, type PublicSession as P, type Session as S, CauthyConfigError as a, type CauthyOptions as b, type CauthySystem as c, type OrganizationMember as d, type SessionOrganization as e, type SessionStatus as f, type SessionUser as g, type SystemKey as h, type SystemOptions as i, createSystem as j };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cauthy-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { S as Session, c as SessionStatus, b as SessionOrganization, P as PublicSession, a as CauthyOptions } from './types-BwCjBn-K.js';
|
|
2
|
-
|
|
3
|
-
/** What `resolve` answers: who is there, and the cookies that must go back. */
|
|
4
|
-
interface Resolved {
|
|
5
|
-
session: Session | null;
|
|
6
|
-
status: SessionStatus;
|
|
7
|
-
/** `Set-Cookie` values to send with the response. Empty when nothing changed. */
|
|
8
|
-
cookies: string[];
|
|
9
|
-
}
|
|
10
|
-
interface Cauthy {
|
|
11
|
-
/** Serves the routes under `basePath`: login, callback, session, refresh, logout, account. */
|
|
12
|
-
handler(request: Request): Promise<Response>;
|
|
13
|
-
/**
|
|
14
|
-
* Who is signed in, or null.
|
|
15
|
-
*
|
|
16
|
-
* Give it the response's headers and a session that needs renewing is
|
|
17
|
-
* renewed on the spot, its new cookie added to them. Without them an
|
|
18
|
-
* expired session reads as null (see `status`): renewing changes the
|
|
19
|
-
* cookie, and a cookie nobody sends back is a session lost.
|
|
20
|
-
*/
|
|
21
|
-
getSession(request: Request, responseHeaders?: Headers): Promise<Session | null>;
|
|
22
|
-
/** Whether somebody is signed in, nobody is, or the session must be renewed first. */
|
|
23
|
-
status(request: Request): Promise<SessionStatus>;
|
|
24
|
-
/**
|
|
25
|
-
* The session, renewed when it has to be, and the cookies to send back.
|
|
26
|
-
* The caller MUST send `cookies` with its response.
|
|
27
|
-
*/
|
|
28
|
-
resolve(request: Request): Promise<Resolved>;
|
|
29
|
-
/**
|
|
30
|
-
* For a route only a signed-in person may see: the session, or the response
|
|
31
|
-
* to give instead — a redirect to sign in (or to renew) for a page, a 401
|
|
32
|
-
* for anything else.
|
|
33
|
-
*/
|
|
34
|
-
protect(request: Request): Promise<{
|
|
35
|
-
session: Session;
|
|
36
|
-
response?: undefined;
|
|
37
|
-
} | {
|
|
38
|
-
session?: undefined;
|
|
39
|
-
response: Response;
|
|
40
|
-
}>;
|
|
41
|
-
/** The organizations the person belongs to (of `system`, when set), with their role in each. */
|
|
42
|
-
organizations(session: Session): Promise<SessionOrganization[]>;
|
|
43
|
-
/** What the browser may be told of a session. */
|
|
44
|
-
toPublic(session: Session): Promise<PublicSession>;
|
|
45
|
-
/** Paths of this application that start a sign-in, a sign-out, or open the account page. */
|
|
46
|
-
urls: {
|
|
47
|
-
signIn(returnTo?: string): string;
|
|
48
|
-
signOut(): string;
|
|
49
|
-
account(returnTo?: string): string;
|
|
50
|
-
session: string;
|
|
51
|
-
};
|
|
52
|
-
options: Readonly<Required<Pick<CauthyOptions, 'issuer' | 'clientId' | 'basePath' | 'afterSignIn' | 'afterSignOut' | 'cookieName'>>>;
|
|
53
|
-
}
|
|
54
|
-
declare function createCauthy(given?: CauthyOptions): Cauthy;
|
|
55
|
-
|
|
56
|
-
export { type Cauthy as C, type Resolved as R, createCauthy as c };
|