@marktiderman/genesis-supabase 0.2.0 → 0.3.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.
Files changed (39) hide show
  1. package/README.md +377 -23
  2. package/dist/blocks.d.ts +19 -0
  3. package/dist/blocks.js +3 -0
  4. package/dist/blocks.js.map +1 -0
  5. package/dist/chunk-FAA2FTF4.js +110 -0
  6. package/dist/chunk-FAA2FTF4.js.map +1 -0
  7. package/dist/chunk-T2HGFC4C.js +37 -0
  8. package/dist/chunk-T2HGFC4C.js.map +1 -0
  9. package/dist/index-BSGz3RGk.d.ts +317 -0
  10. package/dist/index.d.ts +4 -103
  11. package/dist/index.js +60 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/native.d.ts +90 -6
  14. package/dist/native.js +70 -2
  15. package/dist/native.js.map +1 -1
  16. package/functions/genesis-apple-revoke/apple.ts +237 -0
  17. package/functions/genesis-apple-revoke/handler.ts +227 -0
  18. package/functions/genesis-apple-revoke/index.ts +55 -0
  19. package/functions/genesis-push-send/expo.ts +117 -0
  20. package/functions/genesis-push-send/handler.ts +462 -0
  21. package/functions/genesis-push-send/index.ts +54 -0
  22. package/functions/genesis-support-cleanup/handler.ts +80 -0
  23. package/functions/genesis-support-cleanup/index.ts +39 -0
  24. package/functions/genesis-support-intake/contract.ts +193 -0
  25. package/functions/genesis-support-intake/handler.ts +170 -0
  26. package/functions/genesis-support-intake/index.ts +47 -0
  27. package/functions/genesis-support-notify/handler.ts +167 -0
  28. package/functions/genesis-support-notify/index.ts +15 -0
  29. package/functions/tests/genesis-apple-revoke_test.ts +81 -0
  30. package/functions/tests/genesis-push-send_test.ts +53 -0
  31. package/migrations/0003_genesis_apple_token_revocation.sql +205 -0
  32. package/migrations/0004_genesis_tenant_features.sql +333 -0
  33. package/migrations/0005_genesis_push.sql +524 -0
  34. package/migrations/0006_genesis_support.sql +846 -0
  35. package/migrations/tests/0003_genesis_apple_token_revocation.test.sql +230 -0
  36. package/migrations/tests/0004_genesis_tenant_features.test.sql +418 -0
  37. package/migrations/tests/0005_genesis_push.test.sql +407 -0
  38. package/migrations/tests/0006_genesis_support.test.sql +552 -0
  39. package/package.json +91 -4
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-crypto.ts"],"names":["error"],"mappings":"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","file":"chunk-FAA2FTF4.js","sourcesContent":["/**\n * The two WebCrypto calls supabase-js's PKCE flow needs, for runtimes that\n * lack them (Hermes in React Native has no `crypto.subtle`).\n *\n * supabase-js makes the PKCE code verifier with `crypto.getRandomValues` and\n * the S256 code challenge with `crypto.subtle.digest(\"SHA-256\", …)`. When\n * `crypto.subtle` is missing it warns and sends the verifier itself as a\n * \"plain\" challenge; when `crypto` is missing altogether it also makes the\n * verifier with `Math.random`. Either way an intercepted redirect is enough to\n * finish someone else's sign-in. This installs both calls on `globalThis.crypto`\n * from `expo-crypto`, which hashes and draws random bytes natively.\n *\n * Platform-neutral: nothing here imports Expo. Pass the module in.\n */\n\n/** The parts of `expo-crypto` the polyfill uses: `import * as ExpoCrypto from \"expo-crypto\"`. */\nexport interface ExpoCryptoLike {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getRandomValues(typedArray: any): any;\n /** `algorithm` is `CryptoDigestAlgorithm`, whose SHA-256 member is the string `\"SHA-256\"`. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n digest(algorithm: any, data: BufferSource): Promise<ArrayBuffer>;\n}\n\n/** What {@link installWebCrypto} found: real WebCrypto left alone, or the polyfill in place. */\nexport type WebCryptoSource = \"native\" | \"polyfill\";\n\ninterface CryptoLike {\n getRandomValues?: (array: ArrayBufferView) => ArrayBufferView;\n subtle?: { digest?: (algorithm: AlgorithmIdentifier, data: BufferSource) => Promise<ArrayBuffer> };\n}\n\nconst POLYFILL = Symbol.for(\"genesis.webCryptoPolyfill\");\n\n/** WebCrypto's quota for one `getRandomValues` call. */\nconst MAX_RANDOM_BYTES = 65536;\n\nfunction algorithmName(algorithm: AlgorithmIdentifier): string {\n const name = typeof algorithm === \"string\" ? algorithm : (algorithm as { name?: unknown } | null)?.name;\n return typeof name === \"string\" ? name.toUpperCase() : \"\";\n}\n\nconst INTEGER_ARRAYS = [\"Int8Array\", \"Uint8Array\", \"Uint8ClampedArray\", \"Int16Array\", \"Uint16Array\", \"Int32Array\", \"Uint32Array\", \"BigInt64Array\", \"BigUint64Array\"];\n\nfunction isIntegerArray(value: unknown): value is ArrayBufferView {\n return ArrayBuffer.isView(value) && INTEGER_ARRAYS.includes(Object.prototype.toString.call(value).slice(8, -1));\n}\n\nfunction cryptoOf(target: object): CryptoLike | undefined {\n return (target as { crypto?: CryptoLike }).crypto;\n}\n\n/**\n * True when `target` has everything supabase-js needs for an S256 PKCE\n * challenge made from secure random bytes.\n *\n * @stability Beta\n */\nexport function hasPkceCrypto(target: object = globalThis): boolean {\n const g = target as { TextEncoder?: unknown; btoa?: unknown };\n const crypto = cryptoOf(target);\n return (\n typeof crypto?.getRandomValues === \"function\" &&\n typeof crypto?.subtle?.digest === \"function\" &&\n typeof g.TextEncoder === \"function\" &&\n typeof g.btoa === \"function\"\n );\n}\n\n/**\n * Give `globalThis.crypto` a `getRandomValues` and a SHA-256\n * `subtle.digest`, backed by `expo-crypto`, when the runtime has none. Real\n * WebCrypto is never replaced: a `crypto` that has one of the two keeps it.\n * Only SHA-256 is offered: `subtle.digest` rejects any other algorithm, and no\n * other `subtle` method is added, so a library that needs more fails instead\n * of getting a stand-in. Safe to call more than once.\n *\n * `createGenesisSupabase({ platform: \"native\", crypto })` calls this for you.\n *\n * @stability Beta\n */\nexport function installWebCrypto(expoCrypto: ExpoCryptoLike, target: object = globalThis): WebCryptoSource {\n if (typeof expoCrypto?.getRandomValues !== \"function\" || typeof expoCrypto?.digest !== \"function\") {\n throw new TypeError('installWebCrypto needs expo-crypto: pass `import * as ExpoCrypto from \"expo-crypto\"`.');\n }\n const existing = cryptoOf(target);\n const realRandom = existing?.getRandomValues;\n const realSubtle = existing?.subtle;\n if (typeof realRandom === \"function\" && typeof realSubtle?.digest === \"function\") {\n return (existing as Record<symbol, unknown>)[POLYFILL] ? \"polyfill\" : \"native\";\n }\n\n const getRandomValues =\n typeof realRandom === \"function\"\n ? realRandom.bind(existing)\n : <T extends ArrayBufferView>(array: T): T => {\n if (!isIntegerArray(array)) throw new TypeError(\"crypto.getRandomValues: expected an integer TypedArray.\");\n if (array.byteLength > MAX_RANDOM_BYTES) {\n throw new RangeError(`crypto.getRandomValues: ${array.byteLength} bytes is over the ${MAX_RANDOM_BYTES}-byte quota.`);\n }\n // Through a byte view, so every element of any TypedArray gets random bits in every byte.\n const bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);\n const filled: unknown = expoCrypto.getRandomValues(bytes);\n if (filled !== bytes) {\n if (!(filled instanceof Uint8Array) || filled.byteLength !== bytes.byteLength) {\n throw new Error(\"crypto.getRandomValues: expo-crypto returned no random bytes.\");\n }\n bytes.set(filled);\n }\n return array;\n };\n\n const digest = async (algorithm: AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer> => {\n const name = algorithmName(algorithm);\n if (name !== \"SHA-256\") throw new Error(`crypto.subtle.digest: only SHA-256 is polyfilled, not ${name || String(algorithm)}.`);\n let bytes: Uint8Array;\n if (ArrayBuffer.isView(data)) bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n else if (data instanceof ArrayBuffer) bytes = new Uint8Array(data);\n else throw new TypeError(\"crypto.subtle.digest: expected an ArrayBuffer or a TypedArray.\");\n // A copy, so the hash reads these bytes even if the caller reuses its buffer.\n return expoCrypto.digest(\"SHA-256\", bytes.slice());\n };\n // A runtime's own `subtle` (without digest) stays the prototype, so none of its methods are lost.\n const subtle = typeof realSubtle?.digest === \"function\" ? realSubtle : Object.assign(Object.create(realSubtle ?? Object.prototype) as object, { digest });\n\n const crypto = Object.assign(Object.create(existing ?? Object.prototype) as object, { getRandomValues, subtle, [POLYFILL]: true });\n Object.defineProperty(target, \"crypto\", { value: crypto, configurable: true, enumerable: false, writable: true });\n return \"polyfill\";\n}\n\n/**\n * True when `globalThis.crypto.subtle` is this polyfill's digest-only stand-in\n * (no `importKey` or `verify`), which supabase-js's `getClaims` can't use.\n */\nexport function isDigestOnlyPolyfill(target: object = globalThis): boolean {\n const crypto = cryptoOf(target) as (CryptoLike & Record<symbol, unknown>) | undefined;\n return Boolean(crypto?.[POLYFILL]) && typeof (crypto?.subtle as { importKey?: unknown } | undefined)?.importKey !== \"function\";\n}\n\n/** The parts of supabase-js's auth client that {@link claimsViaServer} uses. */\nexport interface ClaimsAuthClient {\n getSession(): Promise<{ data: { session: { access_token: string } | null }; error: unknown }>;\n getUser(jwt?: string): Promise<{ error: unknown }>;\n}\n\n/** A JWT's three parts, decoded (header and payload as JSON, the signature as bytes). */\nfunction decodeJwt(token: string): { header: Record<string, unknown>; payload: Record<string, unknown>; signature: Uint8Array } | null {\n const parts = token.split(\".\");\n if (parts.length !== 3 || parts.some((p) => !/^[A-Za-z0-9_-]*$/.test(p))) return null;\n const bytes = (part: string) => {\n const binary = atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((part.length + 3) % 4));\n return Uint8Array.from(binary, (c) => c.charCodeAt(0));\n };\n try {\n const json = (part: string) => JSON.parse(new TextDecoder().decode(bytes(part))) as Record<string, unknown>;\n return { header: json(parts[0]!), payload: json(parts[1]!), signature: bytes(parts[2]!) };\n } catch {\n return null;\n }\n}\n\n/**\n * `auth.getClaims` for a runtime whose `crypto.subtle` is the digest-only\n * polyfill. supabase-js verifies an asymmetric JWT locally whenever\n * `crypto.subtle` exists, and would throw on the missing `importKey`; this\n * always takes supabase-js's own fallback instead: the server checks the\n * token (`getUser`), and the claims are returned only if it accepts it.\n * Same result shape as `getClaims`.\n */\nexport function claimsViaServer(auth: ClaimsAuthClient, invalidJwt: (message: string) => Error) {\n return async (jwt?: string, options: { allowExpired?: boolean } = {}) => {\n let token = jwt;\n if (!token) {\n const { data, error } = await auth.getSession();\n if (error || !data.session) return { data: null, error: error ?? null };\n token = data.session.access_token;\n }\n const decoded = decodeJwt(token);\n if (!decoded) return { data: null, error: invalidJwt(\"Invalid JWT structure\") };\n if (!options.allowExpired) {\n const exp = decoded.payload.exp;\n if (typeof exp !== \"number\") return { data: null, error: invalidJwt(\"Missing exp claim\") };\n if (exp <= Math.floor(Date.now() / 1000)) return { data: null, error: invalidJwt(\"JWT has expired\") };\n }\n const { error } = await auth.getUser(token);\n if (error) return { data: null, error };\n return { data: { claims: decoded.payload, header: decoded.header, signature: decoded.signature }, error: null };\n };\n}\n\ndeclare const __DEV__: boolean | undefined;\n\nfunction isDev(): boolean {\n if (typeof __DEV__ !== \"undefined\") return Boolean(__DEV__);\n return typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\";\n}\n\n/**\n * Fail loudly when PKCE would fall back to \"plain\": throws in development,\n * logs an error in a release build (a crash at launch helps no one there).\n *\n * @stability Beta\n */\nexport function assertPkceCrypto(target: object = globalThis): void {\n if (hasPkceCrypto(target)) return;\n const message =\n 'Genesis Supabase: this runtime has no WebCrypto (crypto.getRandomValues and crypto.subtle.digest), so supabase-js would send PKCE challenges as \"plain\". ' +\n 'Pass expo-crypto: createGenesisSupabase({ platform: \"native\", crypto: ExpoCrypto, … }) with `import * as ExpoCrypto from \"expo-crypto\"`.';\n if (isDev()) throw new Error(message);\n console.error(message);\n}\n"]}
@@ -0,0 +1,37 @@
1
+ import { defineBlock } from '@marktiderman/genesis-features/blocks';
2
+
3
+ // src/blocks.ts
4
+ var PACKAGE = "@marktiderman/genesis-supabase";
5
+ var accountBlock = defineBlock({
6
+ id: "account",
7
+ platforms: ["web", "native"],
8
+ contributes: {
9
+ migrations: [
10
+ { package: PACKAGE, file: "migrations/0001_genesis_account_deletion.sql", test: "migrations/tests/0001_genesis_account_deletion.test.sql" },
11
+ // With Sign in with Apple, with the genesis-apple-revoke function beside it.
12
+ { package: PACKAGE, file: "migrations/0003_genesis_apple_token_revocation.sql", test: "migrations/tests/0003_genesis_apple_token_revocation.test.sql" }
13
+ ],
14
+ privacy: {
15
+ collects: [
16
+ { type: "email address", purposes: ["app functionality"], linked: true, tracking: false },
17
+ { type: "user id", purposes: ["app functionality"], linked: true, tracking: false },
18
+ { type: "name", purposes: ["app functionality"], linked: true, tracking: false }
19
+ ],
20
+ notes: [
21
+ "People delete their account from inside the app; their login and the rows that cascade from it go with it.",
22
+ "With Sign in with Apple, the person's Apple tokens are revoked when the account is deleted."
23
+ ]
24
+ }
25
+ }
26
+ });
27
+ var tenantFeaturesBlock = defineBlock({
28
+ id: "tenant-features",
29
+ contributes: {
30
+ migrations: [{ package: PACKAGE, file: "migrations/0004_genesis_tenant_features.sql", test: "migrations/tests/0004_genesis_tenant_features.test.sql" }],
31
+ privacy: { notes: ["Keeps who changed a tenant's switches; a deleted person's changes stay, without the person."] }
32
+ }
33
+ });
34
+
35
+ export { accountBlock, tenantFeaturesBlock };
36
+ //# sourceMappingURL=chunk-T2HGFC4C.js.map
37
+ //# sourceMappingURL=chunk-T2HGFC4C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/blocks.ts"],"names":[],"mappings":";;;AAQA,IAAM,OAAA,GAAU,gCAAA;AAST,IAAM,eAAe,WAAA,CAAY;AAAA,EACtC,EAAA,EAAI,SAAA;AAAA,EACJ,SAAA,EAAW,CAAC,KAAA,EAAO,QAAQ,CAAA;AAAA,EAC3B,WAAA,EAAa;AAAA,IACX,UAAA,EAAY;AAAA,MACV,EAAE,OAAA,EAAS,OAAA,EAAS,IAAA,EAAM,8CAAA,EAAgD,MAAM,yDAAA,EAA0D;AAAA;AAAA,MAE1I,EAAE,OAAA,EAAS,OAAA,EAAS,IAAA,EAAM,oDAAA,EAAsD,MAAM,+DAAA;AAAgE,KACxJ;AAAA,IACA,OAAA,EAAS;AAAA,MACP,QAAA,EAAU;AAAA,QACR,EAAE,IAAA,EAAM,eAAA,EAAiB,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAAA,QACxF,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAAA,QAClF,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA;AAAM,OACjF;AAAA,MACA,KAAA,EAAO;AAAA,QACL,4GAAA;AAAA,QACA;AAAA;AACF;AACF;AAEJ,CAAC;AAQM,IAAM,sBAAsB,WAAA,CAAY;AAAA,EAC7C,EAAA,EAAI,iBAAA;AAAA,EACJ,WAAA,EAAa;AAAA,IACX,UAAA,EAAY,CAAC,EAAE,OAAA,EAAS,SAAS,IAAA,EAAM,6CAAA,EAA+C,IAAA,EAAM,wDAAA,EAA0D,CAAA;AAAA,IACtJ,OAAA,EAAS,EAAE,KAAA,EAAO,CAAC,6FAA6F,CAAA;AAAE;AAEtH,CAAC","file":"chunk-T2HGFC4C.js","sourcesContent":["/**\n * The blocks this package carries the server half of, as `defineBlock`\n * declarations: what each needs in the database, and what it means for the\n * stores' privacy forms. The functions themselves (`deleteMyAccount`,\n * `tenantFeatures`, …) don't change.\n */\nimport { defineBlock } from \"@marktiderman/genesis-features/blocks\";\n\nconst PACKAGE = \"@marktiderman/genesis-supabase\";\n\n/**\n * The account block: sign-in, profile and in-app account deletion. Its\n * screens are the app's (or genesis-ui-native's `AuthScreen`); this names its\n * migrations and its data.\n *\n * @stability Beta\n */\nexport const accountBlock = defineBlock({\n id: \"account\",\n platforms: [\"web\", \"native\"],\n contributes: {\n migrations: [\n { package: PACKAGE, file: \"migrations/0001_genesis_account_deletion.sql\", test: \"migrations/tests/0001_genesis_account_deletion.test.sql\" },\n // With Sign in with Apple, with the genesis-apple-revoke function beside it.\n { package: PACKAGE, file: \"migrations/0003_genesis_apple_token_revocation.sql\", test: \"migrations/tests/0003_genesis_apple_token_revocation.test.sql\" },\n ],\n privacy: {\n collects: [\n { type: \"email address\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"user id\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"name\", purposes: [\"app functionality\"], linked: true, tracking: false },\n ],\n notes: [\n \"People delete their account from inside the app; their login and the rows that cascade from it go with it.\",\n \"With Sign in with Apple, the person's Apple tokens are revoked when the account is deleted.\",\n ],\n },\n },\n});\n\n/**\n * The tenant features block: per-tenant on/off switches for shipped\n * features, set by the tenant's admins with a reason.\n *\n * @stability Beta\n */\nexport const tenantFeaturesBlock = defineBlock({\n id: \"tenant-features\",\n contributes: {\n migrations: [{ package: PACKAGE, file: \"migrations/0004_genesis_tenant_features.sql\", test: \"migrations/tests/0004_genesis_tenant_features.test.sql\" }],\n privacy: { notes: [\"Keeps who changed a tenant's switches; a deleted person's changes stay, without the person.\"] },\n },\n});\n"]}
@@ -0,0 +1,317 @@
1
+ import { SupabaseClientOptions, SupabaseClient } from '@supabase/supabase-js';
2
+ import './blocks.js';
3
+
4
+ /**
5
+ * The app's side of Sign in with Apple token revocation: hand the sign-in's
6
+ * authorization code to the `genesis-apple-revoke` Edge Function (it keeps
7
+ * Apple's refresh token server-side), and ask it to revoke after the account
8
+ * is deleted. The function and its migration ship in this package; see the
9
+ * README ("Revoking Apple's tokens").
10
+ */
11
+ /** The Edge Function's default name, as shipped in `functions/genesis-apple-revoke`. */
12
+ declare const APPLE_REVOKE_FUNCTION = "genesis-apple-revoke";
13
+ /** The parts of a Supabase client these helpers use. A `SupabaseClient` fits. */
14
+ interface FunctionsClient {
15
+ functions: {
16
+ invoke: (name: string, options?: any) => PromiseLike<{
17
+ data: unknown;
18
+ error: unknown;
19
+ }>;
20
+ };
21
+ }
22
+ /** @stability Beta */
23
+ interface StoreAppleTokenOptions {
24
+ /** `authorizationCode` from `signInWithApple`'s result. Single-use, valid for five minutes: send it straight after sign-in. */
25
+ authorizationCode: string;
26
+ /** The client id the code was issued for: your bundle id on iOS. Must be in the function's APPLE_CLIENT_IDS; default its first. */
27
+ clientId?: string;
28
+ /** The Edge Function's name. Default `genesis-apple-revoke`. */
29
+ functionName?: string;
30
+ }
31
+ /** @stability Beta */
32
+ type StoreAppleTokenResult = {
33
+ ok: true;
34
+ } | {
35
+ ok: false;
36
+ error: Error;
37
+ };
38
+ /**
39
+ * After a Sign in with Apple, sends the authorization code to the Edge
40
+ * Function, which exchanges it for Apple's refresh token and keeps it where
41
+ * only the server reads it, so deleting the account can revoke it. Call it
42
+ * with the session from that sign-in. Never throws: the person is signed in
43
+ * either way, so log a failure rather than showing it.
44
+ *
45
+ * @stability Beta
46
+ */
47
+ declare function storeAppleToken(client: FunctionsClient, { authorizationCode, clientId, functionName }: StoreAppleTokenOptions): Promise<StoreAppleTokenResult>;
48
+ /**
49
+ * Asks the Edge Function to revoke the tokens of deleted accounts now, rather
50
+ * than at its next scheduled run. Never throws and never fails the caller:
51
+ * the account is already gone and the queue keeps the token until a run
52
+ * succeeds.
53
+ */
54
+ declare function requestAppleRevocation(client: FunctionsClient, functionName?: string): Promise<boolean>;
55
+
56
+ /**
57
+ * The two WebCrypto calls supabase-js's PKCE flow needs, for runtimes that
58
+ * lack them (Hermes in React Native has no `crypto.subtle`).
59
+ *
60
+ * supabase-js makes the PKCE code verifier with `crypto.getRandomValues` and
61
+ * the S256 code challenge with `crypto.subtle.digest("SHA-256", …)`. When
62
+ * `crypto.subtle` is missing it warns and sends the verifier itself as a
63
+ * "plain" challenge; when `crypto` is missing altogether it also makes the
64
+ * verifier with `Math.random`. Either way an intercepted redirect is enough to
65
+ * finish someone else's sign-in. This installs both calls on `globalThis.crypto`
66
+ * from `expo-crypto`, which hashes and draws random bytes natively.
67
+ *
68
+ * Platform-neutral: nothing here imports Expo. Pass the module in.
69
+ */
70
+ /** The parts of `expo-crypto` the polyfill uses: `import * as ExpoCrypto from "expo-crypto"`. */
71
+ interface ExpoCryptoLike {
72
+ getRandomValues(typedArray: any): any;
73
+ /** `algorithm` is `CryptoDigestAlgorithm`, whose SHA-256 member is the string `"SHA-256"`. */
74
+ digest(algorithm: any, data: BufferSource): Promise<ArrayBuffer>;
75
+ }
76
+ /** What {@link installWebCrypto} found: real WebCrypto left alone, or the polyfill in place. */
77
+ type WebCryptoSource = "native" | "polyfill";
78
+ /**
79
+ * True when `target` has everything supabase-js needs for an S256 PKCE
80
+ * challenge made from secure random bytes.
81
+ *
82
+ * @stability Beta
83
+ */
84
+ declare function hasPkceCrypto(target?: object): boolean;
85
+ /**
86
+ * Give `globalThis.crypto` a `getRandomValues` and a SHA-256
87
+ * `subtle.digest`, backed by `expo-crypto`, when the runtime has none. Real
88
+ * WebCrypto is never replaced: a `crypto` that has one of the two keeps it.
89
+ * Only SHA-256 is offered: `subtle.digest` rejects any other algorithm, and no
90
+ * other `subtle` method is added, so a library that needs more fails instead
91
+ * of getting a stand-in. Safe to call more than once.
92
+ *
93
+ * `createGenesisSupabase({ platform: "native", crypto })` calls this for you.
94
+ *
95
+ * @stability Beta
96
+ */
97
+ declare function installWebCrypto(expoCrypto: ExpoCryptoLike, target?: object): WebCryptoSource;
98
+ /**
99
+ * True when `globalThis.crypto.subtle` is this polyfill's digest-only stand-in
100
+ * (no `importKey` or `verify`), which supabase-js's `getClaims` can't use.
101
+ */
102
+ declare function isDigestOnlyPolyfill(target?: object): boolean;
103
+ /**
104
+ * Fail loudly when PKCE would fall back to "plain": throws in development,
105
+ * logs an error in a release build (a crash at launch helps no one there).
106
+ *
107
+ * @stability Beta
108
+ */
109
+ declare function assertPkceCrypto(target?: object): void;
110
+
111
+ /**
112
+ * Tenant features: the client half of `migrations/0004_genesis_tenant_features.sql`.
113
+ * A tenant's admins turn a shipped feature off (or back on) for their tenant,
114
+ * with a reason; its members read which ids are off and hide them.
115
+ *
116
+ * Exposure only: a switch hides a feature, it never grants or denies access
117
+ * to data (that is RLS) and never sells it (that is an entitlement).
118
+ */
119
+
120
+ /** The parts of a Supabase client these helpers use. A `SupabaseClient` fits. */
121
+ interface TenantFeaturesClient {
122
+ rpc: (fn: any, ...rest: any[]) => PromiseLike<{
123
+ data: unknown;
124
+ error: PostgrestErrorLike | null;
125
+ }>;
126
+ }
127
+ /** Which features a tenant has turned off. A feature not in `off` is on. @stability Beta */
128
+ interface TenantFeatures {
129
+ readonly tenantId: string;
130
+ readonly off: ReadonlySet<string>;
131
+ isOn(featureId: string): boolean;
132
+ }
133
+ /**
134
+ * The features `tenantId` has turned off, for one of its members (or
135
+ * admins): `tenant_features_off`. Throws a `SupabaseQueryError` (`42501`)
136
+ * for anyone else, so a refusal is never mistaken for "everything on".
137
+ *
138
+ * ```ts
139
+ * const features = await tenantFeatures(supabase, teamId);
140
+ * if (features.isOn("chat")) showChatTab();
141
+ * ```
142
+ *
143
+ * @stability Beta
144
+ */
145
+ declare function tenantFeatures(client: TenantFeaturesClient, tenantId: string): Promise<TenantFeatures>;
146
+ /** @stability Beta */
147
+ interface SetTenantFeatureInput {
148
+ tenantId: string;
149
+ featureId: string;
150
+ enabled: boolean;
151
+ /** Why, in at most 500 characters. Required: it goes in the history. */
152
+ reason: string;
153
+ }
154
+ /**
155
+ * Turn a feature on or off for a tenant: `set_tenant_feature`. Only the
156
+ * tenant's admins (the app's `private.genesis_tenant_admin` hook); throws a
157
+ * `SupabaseQueryError` otherwise (`42501`), for a blank reason (`22023`) or
158
+ * a malformed feature id (`23514`).
159
+ *
160
+ * @stability Beta
161
+ */
162
+ declare function setTenantFeature(client: TenantFeaturesClient, input: SetTenantFeatureInput): Promise<void>;
163
+ /** One switch a tenant has set. @stability Beta */
164
+ interface TenantFeatureSetting {
165
+ feature_id: string;
166
+ enabled: boolean;
167
+ reason: string;
168
+ updated_by: string | null;
169
+ updated_at: string;
170
+ }
171
+ /** Every switch a tenant has set, with its reason, for its admins: `tenant_feature_settings`. @stability Beta */
172
+ declare function tenantFeatureSettings(client: TenantFeaturesClient, tenantId: string): Promise<TenantFeatureSetting[]>;
173
+ /** One change to a tenant's switches. `enabled` is null when a switch row was deleted (back to on). @stability Beta */
174
+ interface TenantFeatureChange {
175
+ feature_id: string;
176
+ enabled: boolean | null;
177
+ reason: string | null;
178
+ /** The person, or null for the service role, a migration, or a person since deleted. */
179
+ changed_by: string | null;
180
+ changed_role: string;
181
+ changed_at: string;
182
+ }
183
+ /** A tenant's history of changes, newest first (at most `maxRows`, 1 to 1000, default 100), for its admins. @stability Beta */
184
+ declare function tenantFeatureHistory(client: TenantFeaturesClient, tenantId: string, options?: {
185
+ maxRows?: number;
186
+ }): Promise<TenantFeatureChange[]>;
187
+
188
+ /**
189
+ * Supabase for Genesis apps. Platform-neutral: web, React Native and the
190
+ * server import it. Native-only helpers (Keychain storage, foreground
191
+ * refresh, reinstall wipe) are on `@marktiderman/genesis-supabase/native`.
192
+ */
193
+
194
+ /** The async key-value store Supabase auth keeps its session in. */
195
+ interface SessionStorage {
196
+ getItem(key: string): Promise<string | null>;
197
+ setItem(key: string, value: string): Promise<void>;
198
+ removeItem(key: string): Promise<void>;
199
+ }
200
+ /** @stability Beta */
201
+ interface GenesisSupabaseOptions {
202
+ url: string;
203
+ /** The publishable (anon) key. Never the secret key in an app. */
204
+ publishableKey: string;
205
+ /** Where the session lives. Native: `keychainStorage(SecureStore)`. Web: leave out for supabase-js's own default. */
206
+ storage?: SessionStorage;
207
+ /** `"native"` sets the React Native defaults (no session from the URL). Default `"web"`. */
208
+ platform?: "web" | "native";
209
+ /**
210
+ * Read a session from the URL after an OAuth redirect. Default: `true` on
211
+ * web, `false` on native, where the app hands the redirect URL to
212
+ * `exchangeCodeForSession` itself.
213
+ */
214
+ detectSessionInUrl?: boolean;
215
+ /**
216
+ * `expo-crypto` (`import * as ExpoCrypto from "expo-crypto"`), on native.
217
+ * Hermes has no `crypto.subtle`, and without it supabase-js sends the PKCE
218
+ * challenge as "plain"; this installs `crypto.getRandomValues` and a SHA-256
219
+ * `crypto.subtle.digest` from it (real WebCrypto is kept), so the challenge
220
+ * is S256. A PKCE client made where there is neither throws in development
221
+ * and logs an error in a release build. With the polyfill in place,
222
+ * `auth.getClaims` always has the server check the token (`getUser`), as
223
+ * supabase-js does where WebCrypto is missing: the polyfill can hash but
224
+ * not verify a signature.
225
+ */
226
+ crypto?: ExpoCryptoLike;
227
+ /** Anything else for supabase-js, merged last. */
228
+ options?: SupabaseClientOptions<"public">;
229
+ }
230
+ /**
231
+ * A Supabase client with Genesis's defaults: PKCE sign-in (the code flow, not
232
+ * tokens in a redirect URL, with an S256 challenge: pass `crypto` on native),
233
+ * a persisted session that refreshes itself, and
234
+ * the session storage you pass.
235
+ *
236
+ * @stability Beta
237
+ */
238
+ declare function createGenesisSupabase<Database = unknown>({ url, publishableKey, storage, platform, detectSessionInUrl, crypto, options, }: GenesisSupabaseOptions): SupabaseClient<Database>;
239
+ /** What a Supabase or PostgREST error carries. */
240
+ interface PostgrestErrorLike {
241
+ message: string;
242
+ code?: string;
243
+ details?: string | null;
244
+ hint?: string | null;
245
+ }
246
+ /**
247
+ * A failed query, with the database's `code` (`23505` unique violation,
248
+ * `42501` permission denied, `PGRST116` no rows…), `details` and `hint`, so a
249
+ * caller can tell an RLS refusal from a conflict. The original error is the
250
+ * `cause`.
251
+ *
252
+ * @stability Beta
253
+ */
254
+ declare class SupabaseQueryError extends Error {
255
+ name: string;
256
+ readonly code?: string;
257
+ readonly details?: string | null;
258
+ readonly hint?: string | null;
259
+ constructor(error: PostgrestErrorLike);
260
+ }
261
+ /**
262
+ * The data, or a thrown {@link SupabaseQueryError}: for query results where an
263
+ * error should fail the caller (a TanStack Query `queryFn`). The return type
264
+ * keeps `null` when the query can return no row (`.maybeSingle()`).
265
+ *
266
+ * @stability Beta
267
+ */
268
+ declare function must<T>(result: {
269
+ data: T;
270
+ error: null;
271
+ } | {
272
+ data: null;
273
+ error: PostgrestErrorLike;
274
+ }): T;
275
+ /** The parts of a Supabase client {@link deleteMyAccount} uses. A `SupabaseClient` fits. */
276
+ interface DeleteAccountClient {
277
+ rpc: (fn: any, ...rest: any[]) => PromiseLike<{
278
+ data: unknown;
279
+ error: PostgrestErrorLike | null;
280
+ }>;
281
+ auth: {
282
+ signOut(options?: {
283
+ scope?: "global" | "local" | "others";
284
+ }): Promise<unknown>;
285
+ };
286
+ /** Needed only with `revokeAppleTokens`. */
287
+ functions?: FunctionsClient["functions"];
288
+ }
289
+ /** @stability Beta */
290
+ interface DeleteMyAccountOptions {
291
+ /**
292
+ * After the delete, ask the `genesis-apple-revoke` Edge Function to revoke
293
+ * the person's Sign in with Apple tokens now (`true`, or the function's
294
+ * name). Best effort: the delete has already queued them and the
295
+ * function's schedule retries, so a failure here never fails the call.
296
+ */
297
+ revokeAppleTokens?: boolean | string;
298
+ }
299
+ /**
300
+ * In-app account deletion (App Store Review Guideline 5.1.1(v)), on web and
301
+ * native: calls the `delete_my_account` RPC (this package's
302
+ * `migrations/0001_genesis_account_deletion.sql`), which deletes the login
303
+ * and, through the app's `on delete cascade` foreign keys, its data; then
304
+ * clears the session on this device. Throws a {@link SupabaseQueryError} when
305
+ * the database refuses (`42501`: not signed in) and keeps the session then.
306
+ *
307
+ * With Sign in with Apple, ship the `genesis-apple-revoke` function and
308
+ * migration 0003 and pass `{ revokeAppleTokens: true }`: App Review requires
309
+ * the person's Apple tokens to be revoked.
310
+ *
311
+ * Put it behind a confirmation; see the README.
312
+ *
313
+ * @stability Beta
314
+ */
315
+ declare function deleteMyAccount(client: DeleteAccountClient, options?: DeleteMyAccountOptions): Promise<void>;
316
+
317
+ export { APPLE_REVOKE_FUNCTION as A, type DeleteAccountClient as D, type ExpoCryptoLike as E, type FunctionsClient as F, type GenesisSupabaseOptions as G, type PostgrestErrorLike as P, type SessionStorage as S, type TenantFeatureChange as T, type WebCryptoSource as W, assertPkceCrypto as a, isDigestOnlyPolyfill as b, type DeleteMyAccountOptions as c, type SetTenantFeatureInput as d, type StoreAppleTokenOptions as e, type StoreAppleTokenResult as f, SupabaseQueryError as g, hasPkceCrypto as h, installWebCrypto as i, type TenantFeatureSetting as j, type TenantFeatures as k, type TenantFeaturesClient as l, createGenesisSupabase as m, deleteMyAccount as n, must as o, storeAppleToken as p, tenantFeatureSettings as q, requestAppleRevocation as r, setTenantFeature as s, tenantFeatureHistory as t, tenantFeatures as u };
package/dist/index.d.ts CHANGED
@@ -1,103 +1,4 @@
1
- import { SupabaseClientOptions, SupabaseClient } from '@supabase/supabase-js';
2
-
3
- /**
4
- * Supabase for Genesis apps. Platform-neutral: web, React Native and the
5
- * server import it. Native-only helpers (Keychain storage, foreground
6
- * refresh, reinstall wipe) are on `@marktiderman/genesis-supabase/native`.
7
- */
8
-
9
- /** The async key-value store Supabase auth keeps its session in. */
10
- interface SessionStorage {
11
- getItem(key: string): Promise<string | null>;
12
- setItem(key: string, value: string): Promise<void>;
13
- removeItem(key: string): Promise<void>;
14
- }
15
- /** @stability Beta */
16
- interface GenesisSupabaseOptions {
17
- url: string;
18
- /** The publishable (anon) key. Never the secret key in an app. */
19
- publishableKey: string;
20
- /** Where the session lives. Native: `keychainStorage(SecureStore)`. Web: leave out for supabase-js's own default. */
21
- storage?: SessionStorage;
22
- /** `"native"` sets the React Native defaults (no session from the URL). Default `"web"`. */
23
- platform?: "web" | "native";
24
- /**
25
- * Read a session from the URL after an OAuth redirect. Default: `true` on
26
- * web, `false` on native, where the app hands the redirect URL to
27
- * `exchangeCodeForSession` itself.
28
- */
29
- detectSessionInUrl?: boolean;
30
- /** Anything else for supabase-js, merged last. */
31
- options?: SupabaseClientOptions<"public">;
32
- }
33
- /**
34
- * A Supabase client with Genesis's defaults: PKCE sign-in (the code flow, not
35
- * tokens in a redirect URL), a persisted session that refreshes itself, and
36
- * the session storage you pass.
37
- *
38
- * @stability Beta
39
- */
40
- declare function createGenesisSupabase<Database = unknown>({ url, publishableKey, storage, platform, detectSessionInUrl, options, }: GenesisSupabaseOptions): SupabaseClient<Database>;
41
- /** What a Supabase or PostgREST error carries. */
42
- interface PostgrestErrorLike {
43
- message: string;
44
- code?: string;
45
- details?: string | null;
46
- hint?: string | null;
47
- }
48
- /**
49
- * A failed query, with the database's `code` (`23505` unique violation,
50
- * `42501` permission denied, `PGRST116` no rows…), `details` and `hint`, so a
51
- * caller can tell an RLS refusal from a conflict. The original error is the
52
- * `cause`.
53
- *
54
- * @stability Beta
55
- */
56
- declare class SupabaseQueryError extends Error {
57
- name: string;
58
- readonly code?: string;
59
- readonly details?: string | null;
60
- readonly hint?: string | null;
61
- constructor(error: PostgrestErrorLike);
62
- }
63
- /**
64
- * The data, or a thrown {@link SupabaseQueryError}: for query results where an
65
- * error should fail the caller (a TanStack Query `queryFn`). The return type
66
- * keeps `null` when the query can return no row (`.maybeSingle()`).
67
- *
68
- * @stability Beta
69
- */
70
- declare function must<T>(result: {
71
- data: T;
72
- error: null;
73
- } | {
74
- data: null;
75
- error: PostgrestErrorLike;
76
- }): T;
77
- /** The parts of a Supabase client {@link deleteMyAccount} uses. A `SupabaseClient` fits. */
78
- interface DeleteAccountClient {
79
- rpc: (fn: any, ...rest: any[]) => PromiseLike<{
80
- data: unknown;
81
- error: PostgrestErrorLike | null;
82
- }>;
83
- auth: {
84
- signOut(options?: {
85
- scope?: "global" | "local" | "others";
86
- }): Promise<unknown>;
87
- };
88
- }
89
- /**
90
- * In-app account deletion (App Store Review Guideline 5.1.1(v)), on web and
91
- * native: calls the `delete_my_account` RPC (this package's
92
- * `migrations/0001_genesis_account_deletion.sql`), which deletes the login
93
- * and, through the app's `on delete cascade` foreign keys, its data; then
94
- * clears the session on this device. Throws a {@link SupabaseQueryError} when
95
- * the database refuses (`42501`: not signed in) and keeps the session then.
96
- *
97
- * Put it behind a confirmation; see the README.
98
- *
99
- * @stability Beta
100
- */
101
- declare function deleteMyAccount(client: DeleteAccountClient): Promise<void>;
102
-
103
- export { type DeleteAccountClient, type GenesisSupabaseOptions, type PostgrestErrorLike, type SessionStorage, SupabaseQueryError, createGenesisSupabase, deleteMyAccount, must };
1
+ import '@supabase/supabase-js';
2
+ export { A as APPLE_REVOKE_FUNCTION, D as DeleteAccountClient, c as DeleteMyAccountOptions, E as ExpoCryptoLike, F as FunctionsClient, G as GenesisSupabaseOptions, P as PostgrestErrorLike, S as SessionStorage, d as SetTenantFeatureInput, e as StoreAppleTokenOptions, f as StoreAppleTokenResult, g as SupabaseQueryError, T as TenantFeatureChange, j as TenantFeatureSetting, k as TenantFeatures, l as TenantFeaturesClient, m as createGenesisSupabase, n as deleteMyAccount, o as must, r as requestAppleRevocation, s as setTenantFeature, p as storeAppleToken, t as tenantFeatureHistory, q as tenantFeatureSettings, u as tenantFeatures } from './index-BSGz3RGk.js';
3
+ export { accountBlock, tenantFeaturesBlock } from './blocks.js';
4
+ import '@marktiderman/genesis-features/blocks';
package/dist/index.js CHANGED
@@ -1,4 +1,52 @@
1
- import { createClient } from '@supabase/supabase-js';
1
+ import { installWebCrypto, assertPkceCrypto, isDigestOnlyPolyfill, claimsViaServer } from './chunk-FAA2FTF4.js';
2
+ export { accountBlock, tenantFeaturesBlock } from './chunk-T2HGFC4C.js';
3
+ import { createClient, AuthInvalidJwtError } from '@supabase/supabase-js';
4
+
5
+ // src/apple-tokens.ts
6
+ var APPLE_REVOKE_FUNCTION = "genesis-apple-revoke";
7
+ var STORE_TIMEOUT_MS = 2e4;
8
+ var REVOKE_TIMEOUT_MS = 5e3;
9
+ async function storeAppleToken(client, { authorizationCode, clientId, functionName = APPLE_REVOKE_FUNCTION }) {
10
+ try {
11
+ const { error } = await client.functions.invoke(functionName, { body: { action: "store", code: authorizationCode, ...clientId ? { clientId } : {} }, timeout: STORE_TIMEOUT_MS });
12
+ if (error) return { ok: false, error: error instanceof Error ? error : new Error(String(error.message ?? error)) };
13
+ return { ok: true };
14
+ } catch (error) {
15
+ return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
16
+ }
17
+ }
18
+ async function requestAppleRevocation(client, functionName = APPLE_REVOKE_FUNCTION) {
19
+ try {
20
+ const { error } = await client.functions.invoke(functionName, { body: { action: "revoke" }, timeout: REVOKE_TIMEOUT_MS });
21
+ return !error;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ // src/tenant-features.ts
28
+ async function tenantFeatures(client, tenantId) {
29
+ const data = must(await client.rpc("tenant_features_off", { tenant: tenantId }));
30
+ const off = new Set(Array.isArray(data) ? data.filter((id) => typeof id === "string") : []);
31
+ return Object.freeze({ tenantId, off, isOn: (featureId) => !off.has(featureId) });
32
+ }
33
+ async function setTenantFeature(client, input) {
34
+ must(
35
+ await client.rpc("set_tenant_feature", {
36
+ tenant: input.tenantId,
37
+ feature: input.featureId,
38
+ enabled: input.enabled,
39
+ reason: input.reason
40
+ })
41
+ );
42
+ }
43
+ async function tenantFeatureSettings(client, tenantId) {
44
+ return must(await client.rpc("tenant_feature_settings", { tenant: tenantId })) ?? [];
45
+ }
46
+ async function tenantFeatureHistory(client, tenantId, options = {}) {
47
+ const args = options.maxRows === void 0 ? { tenant: tenantId } : { tenant: tenantId, max_rows: options.maxRows };
48
+ return must(await client.rpc("tenant_feature_history", args)) ?? [];
49
+ }
2
50
 
3
51
  // src/index.ts
4
52
  function createGenesisSupabase({
@@ -7,8 +55,11 @@ function createGenesisSupabase({
7
55
  storage,
8
56
  platform = "web",
9
57
  detectSessionInUrl = platform === "web",
58
+ crypto,
10
59
  options = {}
11
60
  }) {
61
+ if (crypto) installWebCrypto(crypto);
62
+ if ((options.auth?.flowType ?? "pkce") === "pkce") assertPkceCrypto();
12
63
  const client = createClient(url, publishableKey, {
13
64
  ...options,
14
65
  auth: {
@@ -20,6 +71,10 @@ function createGenesisSupabase({
20
71
  ...options.auth
21
72
  }
22
73
  });
74
+ if (isDigestOnlyPolyfill()) {
75
+ const auth = client.auth;
76
+ auth.getClaims = claimsViaServer(auth, (message) => new AuthInvalidJwtError(message));
77
+ }
23
78
  return client;
24
79
  }
25
80
  var SupabaseQueryError = class extends Error {
@@ -38,11 +93,13 @@ function must(result) {
38
93
  if (result.error) throw new SupabaseQueryError(result.error);
39
94
  return result.data;
40
95
  }
41
- async function deleteMyAccount(client) {
96
+ async function deleteMyAccount(client, options = {}) {
42
97
  must(await client.rpc("delete_my_account"));
98
+ const revoke = options.revokeAppleTokens;
99
+ if (revoke && client.functions) await requestAppleRevocation({ functions: client.functions }, typeof revoke === "string" ? revoke : APPLE_REVOKE_FUNCTION);
43
100
  await client.auth.signOut({ scope: "local" });
44
101
  }
45
102
 
46
- export { SupabaseQueryError, createGenesisSupabase, deleteMyAccount, must };
103
+ export { APPLE_REVOKE_FUNCTION, SupabaseQueryError, createGenesisSupabase, deleteMyAccount, must, requestAppleRevocation, setTenantFeature, storeAppleToken, tenantFeatureHistory, tenantFeatureSettings, tenantFeatures };
47
104
  //# sourceMappingURL=index.js.map
48
105
  //# sourceMappingURL=index.js.map