@sudobility/shapeshyft_service 1.0.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.
Files changed (127) hide show
  1. package/CLAUDE.md +35 -0
  2. package/README.md +17 -0
  3. package/dist/context.d.ts +62 -0
  4. package/dist/context.d.ts.map +1 -0
  5. package/dist/context.js +55 -0
  6. package/dist/context.js.map +1 -0
  7. package/dist/contracts.d.ts +110 -0
  8. package/dist/contracts.d.ts.map +1 -0
  9. package/dist/contracts.js +7 -0
  10. package/dist/contracts.js.map +1 -0
  11. package/dist/index.d.ts +17 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +16 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/lib/api-key.d.ts +22 -0
  16. package/dist/lib/api-key.d.ts.map +1 -0
  17. package/dist/lib/api-key.js +87 -0
  18. package/dist/lib/api-key.js.map +1 -0
  19. package/dist/lib/encryption.d.ts +22 -0
  20. package/dist/lib/encryption.d.ts.map +1 -0
  21. package/dist/lib/encryption.js +41 -0
  22. package/dist/lib/encryption.js.map +1 -0
  23. package/dist/lib/entity-api-key.d.ts +21 -0
  24. package/dist/lib/entity-api-key.d.ts.map +1 -0
  25. package/dist/lib/entity-api-key.js +54 -0
  26. package/dist/lib/entity-api-key.js.map +1 -0
  27. package/dist/lib/entity-helpers.d.ts +90 -0
  28. package/dist/lib/entity-helpers.d.ts.map +1 -0
  29. package/dist/lib/entity-helpers.js +136 -0
  30. package/dist/lib/entity-helpers.js.map +1 -0
  31. package/dist/lib/money.d.ts +7 -0
  32. package/dist/lib/money.d.ts.map +1 -0
  33. package/dist/lib/money.js +9 -0
  34. package/dist/lib/money.js.map +1 -0
  35. package/dist/lib/public-project.d.ts +14 -0
  36. package/dist/lib/public-project.d.ts.map +1 -0
  37. package/dist/lib/public-project.js +17 -0
  38. package/dist/lib/public-project.js.map +1 -0
  39. package/dist/lib/user-api-key-cache.d.ts +35 -0
  40. package/dist/lib/user-api-key-cache.d.ts.map +1 -0
  41. package/dist/lib/user-api-key-cache.js +99 -0
  42. package/dist/lib/user-api-key-cache.js.map +1 -0
  43. package/dist/lib/user-api-key.d.ts +46 -0
  44. package/dist/lib/user-api-key.d.ts.map +1 -0
  45. package/dist/lib/user-api-key.js +102 -0
  46. package/dist/lib/user-api-key.js.map +1 -0
  47. package/dist/middleware/firebaseAuth.d.ts +34 -0
  48. package/dist/middleware/firebaseAuth.d.ts.map +1 -0
  49. package/dist/middleware/firebaseAuth.js +133 -0
  50. package/dist/middleware/firebaseAuth.js.map +1 -0
  51. package/dist/middleware/rateLimit.d.ts +29 -0
  52. package/dist/middleware/rateLimit.d.ts.map +1 -0
  53. package/dist/middleware/rateLimit.js +85 -0
  54. package/dist/middleware/rateLimit.js.map +1 -0
  55. package/dist/middleware/subscription.d.ts +8 -0
  56. package/dist/middleware/subscription.d.ts.map +1 -0
  57. package/dist/middleware/subscription.js +19 -0
  58. package/dist/middleware/subscription.js.map +1 -0
  59. package/dist/routes/ai.d.ts +10 -0
  60. package/dist/routes/ai.d.ts.map +1 -0
  61. package/dist/routes/ai.js +698 -0
  62. package/dist/routes/ai.js.map +1 -0
  63. package/dist/routes/analytics.d.ts +9 -0
  64. package/dist/routes/analytics.d.ts.map +1 -0
  65. package/dist/routes/analytics.js +192 -0
  66. package/dist/routes/analytics.js.map +1 -0
  67. package/dist/routes/endpoints.d.ts +10 -0
  68. package/dist/routes/endpoints.d.ts.map +1 -0
  69. package/dist/routes/endpoints.js +255 -0
  70. package/dist/routes/endpoints.js.map +1 -0
  71. package/dist/routes/entities.d.ts +15 -0
  72. package/dist/routes/entities.d.ts.map +1 -0
  73. package/dist/routes/entities.js +332 -0
  74. package/dist/routes/entities.js.map +1 -0
  75. package/dist/routes/entity-api-keys.d.ts +17 -0
  76. package/dist/routes/entity-api-keys.d.ts.map +1 -0
  77. package/dist/routes/entity-api-keys.js +142 -0
  78. package/dist/routes/entity-api-keys.js.map +1 -0
  79. package/dist/routes/invitations.d.ts +15 -0
  80. package/dist/routes/invitations.d.ts.map +1 -0
  81. package/dist/routes/invitations.js +58 -0
  82. package/dist/routes/invitations.js.map +1 -0
  83. package/dist/routes/projects.d.ts +9 -0
  84. package/dist/routes/projects.d.ts.map +1 -0
  85. package/dist/routes/projects.js +241 -0
  86. package/dist/routes/projects.js.map +1 -0
  87. package/dist/routes/providers.d.ts +9 -0
  88. package/dist/routes/providers.d.ts.map +1 -0
  89. package/dist/routes/providers.js +89 -0
  90. package/dist/routes/providers.js.map +1 -0
  91. package/dist/routes/ratelimits.d.ts +9 -0
  92. package/dist/routes/ratelimits.d.ts.map +1 -0
  93. package/dist/routes/ratelimits.js +171 -0
  94. package/dist/routes/ratelimits.js.map +1 -0
  95. package/dist/routes/settings.d.ts +9 -0
  96. package/dist/routes/settings.d.ts.map +1 -0
  97. package/dist/routes/settings.js +138 -0
  98. package/dist/routes/settings.js.map +1 -0
  99. package/dist/routes/storage.d.ts +9 -0
  100. package/dist/routes/storage.d.ts.map +1 -0
  101. package/dist/routes/storage.js +180 -0
  102. package/dist/routes/storage.js.map +1 -0
  103. package/dist/routes/user-api-keys.d.ts +14 -0
  104. package/dist/routes/user-api-keys.d.ts.map +1 -0
  105. package/dist/routes/user-api-keys.js +237 -0
  106. package/dist/routes/user-api-keys.js.map +1 -0
  107. package/dist/routes/users.d.ts +7 -0
  108. package/dist/routes/users.d.ts.map +1 -0
  109. package/dist/routes/users.js +102 -0
  110. package/dist/routes/users.js.map +1 -0
  111. package/dist/schema/init.d.ts +12 -0
  112. package/dist/schema/init.d.ts.map +1 -0
  113. package/dist/schema/init.js +404 -0
  114. package/dist/schema/init.js.map +1 -0
  115. package/dist/schema/tables.d.ts +2110 -0
  116. package/dist/schema/tables.d.ts.map +1 -0
  117. package/dist/schema/tables.js +251 -0
  118. package/dist/schema/tables.js.map +1 -0
  119. package/dist/schemas/index.d.ts +671 -0
  120. package/dist/schemas/index.d.ts.map +1 -0
  121. package/dist/schemas/index.js +299 -0
  122. package/dist/schemas/index.js.map +1 -0
  123. package/dist/service.d.ts +41 -0
  124. package/dist/service.d.ts.map +1 -0
  125. package/dist/service.js +70 -0
  126. package/dist/service.js.map +1 -0
  127. package/package.json +85 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,35 @@
1
+ # shapeshyft_service
2
+
3
+ > **Git policy — never auto-commit or auto-push.** Run `git commit`, `git push`,
4
+ > or publish only when the user explicitly asks in that turn.
5
+
6
+ Shared backend for `shapeshyft_api` and `shaperouter_api`: Drizzle table
7
+ factories, idempotent DDL, Hono routers, auth and rate-limit middleware.
8
+ Stateless LLM work (adapters, prompts, media, provider catalog) lives in
9
+ `@sudobility/shapeshyft_engine`.
10
+
11
+ ## The seam
12
+
13
+ The service never knows where a provider API key lives. The app passes a
14
+ `ProviderCredentialResolver`:
15
+
16
+ - `bindEndpoint` — endpoint create/update; returns the `provider` and
17
+ `llmKeyId` to persist, or a `{ status, message }` failure.
18
+ - `resolve` — invoke and prompt-preview time; returns `provider`, `apiKey`,
19
+ `endpointUrl`, `timeoutMs`, or a failure.
20
+
21
+ Optional `InvokeHooks`: `beforeInvoke` (after rate limiting; return a Response to
22
+ stop) and `afterInvoke` (inside the usage_analytics transaction).
23
+
24
+ ## Rules
25
+
26
+ - No `process.env`. Add a field to `ShapeshyftServiceConfig` instead.
27
+ - No reference to `llm_api_keys`. The binding column is `llm_key_id`, nullable,
28
+ no FK here; apps add their own FK.
29
+ - Relative imports carry `.js`.
30
+ - Handler error messages and status codes are part of two products' public APIs.
31
+
32
+ ## Tests
33
+
34
+ bun run test # unit, no DB
35
+ TEST_DATABASE_URL=postgresql://localhost:5432/shapeshyft_test bun run test:db
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @sudobility/shapeshyft_service
2
+
3
+ Shared tables, routes and middleware for ShapeShyft-style structured-output APIs.
4
+
5
+ ```ts
6
+ const service = createShapeshyftService({
7
+ db,
8
+ tables,
9
+ keyPrefixes: { user: "shyft_", entity: "shyftent" },
10
+ encryption,
11
+ auth,
12
+ email,
13
+ credentials, // ProviderCredentialResolver: where provider API keys come from
14
+ });
15
+
16
+ app.route("/api/v1", service.buildRoutes({ mountAdmin }));
17
+ ```
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @fileoverview Service configuration and the context every router closes over
3
+ */
4
+ import type { RateLimitsConfig } from "@sudobility/ratelimit_service";
5
+ import { createLLMProvider } from "@sudobility/shapeshyft_engine";
6
+ import type { AuthAdapter, EmailSender, InvokeHooks, Logger, ProviderCredentialResolver, ServiceDb } from "./contracts.js";
7
+ import type { Encryption } from "./lib/encryption.js";
8
+ import { type ProjectApiKeys } from "./lib/api-key.js";
9
+ import { type UserApiKeys } from "./lib/user-api-key.js";
10
+ import { type EntityApiKeyFormat } from "./lib/entity-api-key.js";
11
+ import { type UserApiKeyCache } from "./lib/user-api-key-cache.js";
12
+ import { type EntityAccess } from "./lib/entity-helpers.js";
13
+ import { type SubscriptionAccess } from "./middleware/subscription.js";
14
+ import { type RateLimiting } from "./middleware/rateLimit.js";
15
+ import { type EndpointBindingShapes, type EndpointSchemas } from "./schemas/index.js";
16
+ import type { ServiceTables } from "./schema/tables.js";
17
+ export interface ShapeshyftServiceConfig {
18
+ db: ServiceDb;
19
+ tables: ServiceTables;
20
+ /** e.g. `{ user: "shyft_", entity: "shyftent" }` -- entity has no trailing underscore */
21
+ keyPrefixes: {
22
+ user: string;
23
+ entity: string;
24
+ };
25
+ encryption: Encryption;
26
+ auth: AuthAdapter;
27
+ email: EmailSender;
28
+ credentials: ProviderCredentialResolver;
29
+ /** Extra zod fields validated on endpoint create/update. */
30
+ endpointBinding?: EndpointBindingShapes;
31
+ hooks?: InvokeHooks;
32
+ /** Absent: subscription lookups and invoke rate limiting are skipped. */
33
+ revenueCatApiKey?: string;
34
+ rateLimits?: RateLimitsConfig;
35
+ entitlementDisplayNames?: Record<string, string>;
36
+ /** Default: `console`. */
37
+ logger?: Logger;
38
+ /** Default: the engine's `createLLMProvider`. Tests inject a fake. */
39
+ createProvider?: typeof createLLMProvider;
40
+ }
41
+ export interface ServiceContext {
42
+ db: ServiceDb;
43
+ tables: ServiceTables;
44
+ auth: AuthAdapter;
45
+ email: EmailSender;
46
+ credentials: ProviderCredentialResolver;
47
+ hooks: InvokeHooks;
48
+ logger: Logger;
49
+ createProvider: typeof createLLMProvider;
50
+ revenueCatApiKey: string | undefined;
51
+ encryption: Encryption;
52
+ projectApiKeys: ProjectApiKeys;
53
+ userApiKeys: UserApiKeys;
54
+ userApiKeyCache: UserApiKeyCache;
55
+ entityApiKeyFormat: EntityApiKeyFormat;
56
+ entityAccess: EntityAccess;
57
+ subscription: SubscriptionAccess;
58
+ rateLimiting: RateLimiting;
59
+ schemas: EndpointSchemas;
60
+ }
61
+ export declare function buildContext(config: ShapeshyftServiceConfig): ServiceContext;
62
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,MAAM,EACN,0BAA0B,EAC1B,SAAS,EACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAChF,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;IACtB,yFAAyF;IACzF,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,WAAW,EAAE,0BAA0B,CAAC;IACxC,4DAA4D;IAC5D,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,cAAc,CAAC,EAAE,OAAO,iBAAiB,CAAC;CAC3C;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,WAAW,EAAE,0BAA0B,CAAC;IACxC,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,iBAAiB,CAAC;IACzC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,UAAU,EAAE,UAAU,CAAC;IACvB,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,EAAE,kBAAkB,CAAC;IACjC,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,uBAAuB,GAAG,cAAc,CA2C5E"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Service configuration and the context every router closes over
3
+ */
4
+ import { createLLMProvider } from "@sudobility/shapeshyft_engine";
5
+ import { createProjectApiKeys } from "./lib/api-key.js";
6
+ import { createUserApiKeys } from "./lib/user-api-key.js";
7
+ import { createEntityApiKeyFormat, } from "./lib/entity-api-key.js";
8
+ import { createUserApiKeyCache, } from "./lib/user-api-key-cache.js";
9
+ import { createEntityAccess } from "./lib/entity-helpers.js";
10
+ import { createSubscriptionAccess, } from "./middleware/subscription.js";
11
+ import { createRateLimiting, DEFAULT_ENTITLEMENT_DISPLAY_NAMES, DEFAULT_RATE_LIMITS_CONFIG, } from "./middleware/rateLimit.js";
12
+ import { createEndpointSchemas, } from "./schemas/index.js";
13
+ export function buildContext(config) {
14
+ const logger = config.logger ?? console;
15
+ const userApiKeys = createUserApiKeys({
16
+ prefix: config.keyPrefixes.user,
17
+ encryption: config.encryption,
18
+ });
19
+ return {
20
+ db: config.db,
21
+ tables: config.tables,
22
+ auth: config.auth,
23
+ email: config.email,
24
+ credentials: config.credentials,
25
+ hooks: config.hooks ?? {},
26
+ logger,
27
+ createProvider: config.createProvider ?? createLLMProvider,
28
+ revenueCatApiKey: config.revenueCatApiKey,
29
+ encryption: config.encryption,
30
+ projectApiKeys: createProjectApiKeys(config.encryption),
31
+ userApiKeys,
32
+ userApiKeyCache: createUserApiKeyCache({
33
+ db: config.db,
34
+ tables: config.tables,
35
+ hashUserApiKey: userApiKeys.hashUserApiKey,
36
+ logger,
37
+ }),
38
+ entityApiKeyFormat: createEntityApiKeyFormat(config.keyPrefixes.entity),
39
+ entityAccess: createEntityAccess({
40
+ db: config.db,
41
+ tables: config.tables,
42
+ entityKeyPrefix: config.keyPrefixes.entity,
43
+ }),
44
+ subscription: createSubscriptionAccess(config.revenueCatApiKey),
45
+ rateLimiting: createRateLimiting({
46
+ db: config.db,
47
+ rateLimitCounters: config.tables.rateLimitCounters,
48
+ revenueCatApiKey: config.revenueCatApiKey,
49
+ rateLimitsConfig: config.rateLimits ?? DEFAULT_RATE_LIMITS_CONFIG,
50
+ entitlementDisplayNames: config.entitlementDisplayNames ?? DEFAULT_ENTITLEMENT_DISPLAY_NAMES,
51
+ }),
52
+ schemas: createEndpointSchemas(config.endpointBinding),
53
+ };
54
+ }
55
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAUlE,OAAO,EAAE,oBAAoB,EAAuB,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAoB,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EACL,wBAAwB,GAEzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,qBAAqB,GAEtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAqB,MAAM,yBAAyB,CAAC;AAChF,OAAO,EACL,wBAAwB,GAEzB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,kBAAkB,EAClB,iCAAiC,EACjC,0BAA0B,GAE3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,qBAAqB,GAGtB,MAAM,oBAAoB,CAAC;AA8C5B,MAAM,UAAU,YAAY,CAAC,MAA+B;IAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC;IACxC,MAAM,WAAW,GAAG,iBAAiB,CAAC;QACpC,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC,CAAC;IAEH,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE;QACzB,MAAM;QACN,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,iBAAiB;QAC1D,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,cAAc,EAAE,oBAAoB,CAAC,MAAM,CAAC,UAAU,CAAC;QACvD,WAAW;QACX,eAAe,EAAE,qBAAqB,CAAC;YACrC,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,cAAc,EAAE,WAAW,CAAC,cAAc;YAC1C,MAAM;SACP,CAAC;QACF,kBAAkB,EAAE,wBAAwB,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;QACvE,YAAY,EAAE,kBAAkB,CAAC;YAC/B,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,eAAe,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM;SAC3C,CAAC;QACF,YAAY,EAAE,wBAAwB,CAAC,MAAM,CAAC,gBAAgB,CAAC;QAC/D,YAAY,EAAE,kBAAkB,CAAC;YAC/B,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,iBAAiB;YAClD,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;YACzC,gBAAgB,EAAE,MAAM,CAAC,UAAU,IAAI,0BAA0B;YACjE,uBAAuB,EACrB,MAAM,CAAC,uBAAuB,IAAI,iCAAiC;SACtE,CAAC;QACF,OAAO,EAAE,qBAAqB,CAAC,MAAM,CAAC,eAAe,CAAC;KACvD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @fileoverview What an app supplies to shapeshyft_service
3
+ * @description The provider-credential seam, invoke hooks, and the adapters that
4
+ * replace module-level singletons (auth, email, logging).
5
+ */
6
+ import type { Context } from "hono";
7
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
8
+ import type { DecodedIdToken } from "firebase-admin/auth";
9
+ import type { getUserInfo, isAnonymousUser, isSiteAdmin } from "@sudobility/auth_service";
10
+ import type { EndpointBase, LlmProvider } from "@sudobility/shapeshyft_engine/types";
11
+ import type { ServiceTables } from "./schema/tables.js";
12
+ /** The Drizzle database the app connects. Tables are passed separately. */
13
+ export type ServiceDb = PostgresJsDatabase<any>;
14
+ /** The `tx` handed to a `db.transaction` callback. */
15
+ export type DbTransaction = Parameters<Parameters<ServiceDb["transaction"]>[0]>[0];
16
+ export type EndpointRow = ServiceTables["endpoints"]["$inferSelect"];
17
+ export type EntityRow = ServiceTables["entities"]["$inferSelect"];
18
+ export type ProjectRow = ServiceTables["projects"]["$inferSelect"];
19
+ /** An endpoint as the service's routes return it. */
20
+ export interface EndpointRecord extends EndpointBase {
21
+ llm_key_id: string | null;
22
+ provider: LlmProvider | null;
23
+ }
24
+ /** A refusal. `message` is returned to the caller verbatim as `errorResponse(message)`. */
25
+ export interface ResolverFailure {
26
+ ok: false;
27
+ status: 400 | 404 | 500 | 503;
28
+ message: string;
29
+ }
30
+ /** What to persist on an endpoint for its provider binding. */
31
+ export interface EndpointBinding {
32
+ ok: true;
33
+ provider: LlmProvider;
34
+ /** ShapeShyft: the LlmApiKey UUID. Products without per-entity keys: null. */
35
+ llmKeyId: string | null;
36
+ }
37
+ /** A live credential for one call. `provider` is authoritative for the call. */
38
+ export interface ResolvedCredential {
39
+ ok: true;
40
+ provider: LlmProvider;
41
+ apiKey?: string;
42
+ endpointUrl?: string;
43
+ timeoutMs?: number;
44
+ }
45
+ export interface ProviderCredentialResolver {
46
+ /**
47
+ * Endpoint create (no `current`) or update (`current` set). `body` is the
48
+ * validated request body, including the app's `endpointBinding` fields.
49
+ */
50
+ bindEndpoint(ctx: {
51
+ entityId: string;
52
+ body: Record<string, unknown>;
53
+ current?: EndpointRow;
54
+ }): Promise<EndpointBinding | ResolverFailure>;
55
+ /** Invoke and prompt-preview time. */
56
+ resolve(ctx: {
57
+ entityId: string;
58
+ endpoint: EndpointRow;
59
+ }): Promise<ResolvedCredential | ResolverFailure>;
60
+ }
61
+ export interface InvokeHooks {
62
+ /**
63
+ * After rate limiting, before the provider call. Return a Response to stop
64
+ * the request with it (e.g. 402 insufficient credit). Not run for /prompt.
65
+ */
66
+ beforeInvoke?(ctx: {
67
+ c: Context;
68
+ entity: EntityRow;
69
+ project: ProjectRow;
70
+ endpoint: EndpointRow;
71
+ provider: LlmProvider;
72
+ }): Promise<Response | void>;
73
+ /**
74
+ * After a successful provider call, inside the transaction that inserts the
75
+ * usage_analytics row. Throwing rolls that row back and fails the request 500.
76
+ */
77
+ afterInvoke?(ctx: {
78
+ tx: DbTransaction;
79
+ entity: EntityRow;
80
+ endpoint: EndpointRow;
81
+ usageAnalyticsId: string;
82
+ provider: LlmProvider;
83
+ model: string;
84
+ usage: {
85
+ promptTokens: number;
86
+ completionTokens: number;
87
+ };
88
+ providerCostMicroCents: bigint;
89
+ }): Promise<void>;
90
+ }
91
+ /** Auth functions the app initialises (auth_service is process-global). */
92
+ export interface AuthAdapter {
93
+ verifyIdToken(token: string): Promise<DecodedIdToken>;
94
+ isSiteAdmin: typeof isSiteAdmin;
95
+ isAnonymousUser: typeof isAnonymousUser;
96
+ getUserInfo: typeof getUserInfo;
97
+ }
98
+ export interface EmailSender {
99
+ sendInvitationEmail(params: {
100
+ recipientEmail: string;
101
+ entityName: string;
102
+ }): Promise<void>;
103
+ }
104
+ /** `console` satisfies this; it is the default. */
105
+ export interface Logger {
106
+ log(...args: unknown[]): void;
107
+ warn(...args: unknown[]): void;
108
+ error(...args: unknown[]): void;
109
+ }
110
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview What an app supplies to shapeshyft_service
3
+ * @description The provider-credential seam, invoke hooks, and the adapters that
4
+ * replace module-level singletons (auth, email, logging).
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @fileoverview @sudobility/shapeshyft_service
3
+ * @description Tables, routes and middleware shared by ShapeShyft and
4
+ * ShapeRouter. Provider credentials come from the app via
5
+ * ProviderCredentialResolver; billing seams via InvokeHooks.
6
+ */
7
+ export { createShapeshyftService, type BuildRoutesOptions, type ShapeshyftService, } from "./service.js";
8
+ export { buildContext, type ServiceContext, type ShapeshyftServiceConfig, } from "./context.js";
9
+ export type { AuthAdapter, DbTransaction, EmailSender, EndpointBinding, EndpointRecord, EndpointRow, EntityRow, InvokeHooks, Logger, ProjectRow, ProviderCredentialResolver, ResolvedCredential, ResolverFailure, ServiceDb, } from "./contracts.js";
10
+ export { createServiceTables, LLM_PROVIDER_VALUES, type ServiceTables, } from "./schema/tables.js";
11
+ export { initServiceTables } from "./schema/init.js";
12
+ export { createEncryption, generateEncryptionKey, type Encryption, } from "./lib/encryption.js";
13
+ export { toMicroCents } from "./lib/money.js";
14
+ export { getActor, getPermissionErrorStatus, userActor, ENTITY_API_KEY_PERMISSIONS, type EntityAccess, type EntityActor, type EntityPermissionResult, } from "./lib/entity-helpers.js";
15
+ export { DEFAULT_RATE_LIMITS_CONFIG, DEFAULT_ENTITLEMENT_DISPLAY_NAMES, } from "./middleware/rateLimit.js";
16
+ export * from "./schemas/index.js";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,uBAAuB,GAC7B,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,eAAe,EACf,cAAc,EACd,WAAW,EACX,SAAS,EACT,WAAW,EACX,MAAM,EACN,UAAU,EACV,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,QAAQ,EACR,wBAAwB,EACxB,SAAS,EACT,0BAA0B,EAC1B,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,sBAAsB,GAC5B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,0BAA0B,EAC1B,iCAAiC,GAClC,MAAM,2BAA2B,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @fileoverview @sudobility/shapeshyft_service
3
+ * @description Tables, routes and middleware shared by ShapeShyft and
4
+ * ShapeRouter. Provider credentials come from the app via
5
+ * ProviderCredentialResolver; billing seams via InvokeHooks.
6
+ */
7
+ export { createShapeshyftService, } from "./service.js";
8
+ export { buildContext, } from "./context.js";
9
+ export { createServiceTables, LLM_PROVIDER_VALUES, } from "./schema/tables.js";
10
+ export { initServiceTables } from "./schema/init.js";
11
+ export { createEncryption, generateEncryptionKey, } from "./lib/encryption.js";
12
+ export { toMicroCents } from "./lib/money.js";
13
+ export { getActor, getPermissionErrorStatus, userActor, ENTITY_API_KEY_PERMISSIONS, } from "./lib/entity-helpers.js";
14
+ export { DEFAULT_RATE_LIMITS_CONFIG, DEFAULT_ENTITLEMENT_DISPLAY_NAMES, } from "./middleware/rateLimit.js";
15
+ export * from "./schemas/index.js";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,uBAAuB,GAGxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,GAGb,MAAM,cAAc,CAAC;AAiBtB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,GAEpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACL,gBAAgB,EAChB,qBAAqB,GAEtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,QAAQ,EACR,wBAAwB,EACxB,SAAS,EACT,0BAA0B,GAI3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,0BAA0B,EAC1B,iCAAiC,GAClC,MAAM,2BAA2B,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Project API key management
3
+ * @description Generates, encrypts, decrypts, and validates project API keys.
4
+ * Keys use the format `sk_live_<base64url-random>` and are validated
5
+ * using timing-safe comparison to prevent timing attacks.
6
+ */
7
+ import type { Encryption } from "./encryption.js";
8
+ export declare function createProjectApiKeys(encryption: Encryption): {
9
+ generateProjectApiKey: () => {
10
+ key: string;
11
+ prefix: string;
12
+ };
13
+ encryptProjectApiKey: (key: string) => {
14
+ encrypted: string;
15
+ iv: string;
16
+ };
17
+ decryptProjectApiKey: (encrypted: string, iv: string) => string;
18
+ validateProjectApiKey: (providedKey: string, encryptedKey: string, iv: string) => boolean;
19
+ isValidApiKeyFormat: (key: string) => boolean;
20
+ };
21
+ export type ProjectApiKeys = ReturnType<typeof createProjectApiKeys>;
22
+ //# sourceMappingURL=api-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.d.ts","sourceRoot":"","sources":["../../src/lib/api-key.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAQlD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU;iCAQvB;QAChC,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;KAChB;gCAoBkC,MAAM,KAAG;QAC1C,SAAS,EAAE,MAAM,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;KACZ;sCAUwC,MAAM,MAAM,MAAM,KAAG,MAAM;yCAarD,MAAM,gBACL,MAAM,MAChB,MAAM,KACT,OAAO;+BAuBwB,MAAM,KAAG,OAAO;EAWnD;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @fileoverview Project API key management
3
+ * @description Generates, encrypts, decrypts, and validates project API keys.
4
+ * Keys use the format `sk_live_<base64url-random>` and are validated
5
+ * using timing-safe comparison to prevent timing attacks.
6
+ */
7
+ import { randomBytes, timingSafeEqual } from "crypto";
8
+ // API Key prefix for identification
9
+ const API_KEY_PREFIX = "sk_live_";
10
+ // Random bytes of entropy (32 bytes = 256 bits)
11
+ const KEY_BYTES_LENGTH = 32;
12
+ export function createProjectApiKeys(encryption) {
13
+ const { encryptApiKey, decryptApiKey } = encryption;
14
+ /**
15
+ * Generate a new API key with prefix
16
+ * Format: sk_live_<base64url-encoded-random-bytes>
17
+ * @returns Object with full key and prefix for display
18
+ */
19
+ function generateProjectApiKey() {
20
+ const randomPart = randomBytes(KEY_BYTES_LENGTH)
21
+ .toString("base64url")
22
+ .replace(/[=]/g, ""); // Remove padding
23
+ const fullKey = `${API_KEY_PREFIX}${randomPart}`;
24
+ // Prefix shows first 12 characters (sk_live_ + first 4 random chars)
25
+ const prefix = fullKey.substring(0, 12);
26
+ return {
27
+ key: fullKey,
28
+ prefix,
29
+ };
30
+ }
31
+ /**
32
+ * Encrypt a project API key for storage
33
+ * @param key The plain text API key
34
+ * @returns Object with encrypted value and IV
35
+ */
36
+ function encryptProjectApiKey(key) {
37
+ return encryptApiKey(key);
38
+ }
39
+ /**
40
+ * Decrypt a stored project API key
41
+ * @param encrypted The encrypted API key (hex string)
42
+ * @param iv The initialization vector (hex string)
43
+ * @returns The decrypted plain text API key
44
+ */
45
+ function decryptProjectApiKey(encrypted, iv) {
46
+ return decryptApiKey(encrypted, iv);
47
+ }
48
+ /**
49
+ * Validate a provided API key against an encrypted stored key
50
+ * Uses timing-safe comparison to prevent timing attacks
51
+ * @param providedKey The API key provided by the caller
52
+ * @param encryptedKey The encrypted stored API key
53
+ * @param iv The IV used for encryption
54
+ * @returns true if keys match, false otherwise
55
+ */
56
+ function validateProjectApiKey(providedKey, encryptedKey, iv) {
57
+ try {
58
+ const storedKey = decryptProjectApiKey(encryptedKey, iv);
59
+ // Use timing-safe comparison
60
+ const providedBuffer = Buffer.from(providedKey, "utf8");
61
+ const storedBuffer = Buffer.from(storedKey, "utf8");
62
+ if (providedBuffer.length !== storedBuffer.length) {
63
+ return false;
64
+ }
65
+ return timingSafeEqual(providedBuffer, storedBuffer);
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ /**
72
+ * Check if a string looks like a valid API key format
73
+ * @param key The string to check
74
+ * @returns true if format is valid
75
+ */
76
+ function isValidApiKeyFormat(key) {
77
+ return key.startsWith(API_KEY_PREFIX) && key.length > API_KEY_PREFIX.length;
78
+ }
79
+ return {
80
+ generateProjectApiKey,
81
+ encryptProjectApiKey,
82
+ decryptProjectApiKey,
83
+ validateProjectApiKey,
84
+ isValidApiKeyFormat,
85
+ };
86
+ }
87
+ //# sourceMappingURL=api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.js","sourceRoot":"","sources":["../../src/lib/api-key.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAGtD,oCAAoC;AACpC,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC,gDAAgD;AAChD,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,MAAM,UAAU,oBAAoB,CAAC,UAAsB;IACzD,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,UAAU,CAAC;IAEpD;;;;OAIG;IACH,SAAS,qBAAqB;QAI5B,MAAM,UAAU,GAAG,WAAW,CAAC,gBAAgB,CAAC;aAC7C,QAAQ,CAAC,WAAW,CAAC;aACrB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB;QAEzC,MAAM,OAAO,GAAG,GAAG,cAAc,GAAG,UAAU,EAAE,CAAC;QACjD,qEAAqE;QACrE,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAExC,OAAO;YACL,GAAG,EAAE,OAAO;YACZ,MAAM;SACP,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,SAAS,oBAAoB,CAAC,GAAW;QAIvC,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;;;;OAKG;IACH,SAAS,oBAAoB,CAAC,SAAiB,EAAE,EAAU;QACzD,OAAO,aAAa,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC;IAED;;;;;;;OAOG;IACH,SAAS,qBAAqB,CAC5B,WAAmB,EACnB,YAAoB,EACpB,EAAU;QAEV,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,oBAAoB,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;YAEzD,6BAA6B;YAC7B,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YACxD,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAEpD,IAAI,cAAc,CAAC,MAAM,KAAK,YAAY,CAAC,MAAM,EAAE,CAAC;gBAClD,OAAO,KAAK,CAAC;YACf,CAAC;YAED,OAAO,eAAe,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,SAAS,mBAAmB,CAAC,GAAW;QACtC,OAAO,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC;IAC9E,CAAC;IAED,OAAO;QACL,qBAAqB;QACrB,oBAAoB;QACpB,oBAAoB;QACpB,qBAAqB;QACrB,mBAAmB;KACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview AES-256-CBC encryption for credentials at rest
3
+ * @description The key is read through a getter on every call, so an app that
4
+ * starts without ENCRYPTION_KEY fails on first use -- the behaviour
5
+ * shapeshyft_api had when this read the environment directly.
6
+ */
7
+ export interface Encryption {
8
+ /** Encrypt plain text; returns hex ciphertext and hex IV. */
9
+ encryptApiKey(plainText: string): {
10
+ encrypted: string;
11
+ iv: string;
12
+ };
13
+ /** Decrypt hex ciphertext with its hex IV. */
14
+ decryptApiKey(encrypted: string, ivHex: string): string;
15
+ }
16
+ /**
17
+ * @param getKeyHex Returns the 64-hex-character (32-byte) key. Called per operation.
18
+ */
19
+ export declare function createEncryption(getKeyHex: () => string): Encryption;
20
+ /** A new random key suitable for ENCRYPTION_KEY. */
21
+ export declare function generateEncryptionKey(): string;
22
+ //# sourceMappingURL=encryption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/lib/encryption.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,WAAW,UAAU;IACzB,6DAA6D;IAC7D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,8CAA8C;IAC9C,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,MAAM,GAAG,UAAU,CA4BpE;AAED,oDAAoD;AACpD,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @fileoverview AES-256-CBC encryption for credentials at rest
3
+ * @description The key is read through a getter on every call, so an app that
4
+ * starts without ENCRYPTION_KEY fails on first use -- the behaviour
5
+ * shapeshyft_api had when this read the environment directly.
6
+ */
7
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
8
+ const ALGORITHM = "aes-256-cbc";
9
+ const IV_LENGTH = 16; // AES block size
10
+ /**
11
+ * @param getKeyHex Returns the 64-hex-character (32-byte) key. Called per operation.
12
+ */
13
+ export function createEncryption(getKeyHex) {
14
+ function key() {
15
+ const keyHex = getKeyHex();
16
+ if (keyHex.length !== 64) {
17
+ throw new Error("ENCRYPTION_KEY must be 64 hex characters (32 bytes)");
18
+ }
19
+ return Buffer.from(keyHex, "hex");
20
+ }
21
+ return {
22
+ encryptApiKey(plainText) {
23
+ const iv = randomBytes(IV_LENGTH);
24
+ const cipher = createCipheriv(ALGORITHM, key(), iv);
25
+ let encrypted = cipher.update(plainText, "utf8", "hex");
26
+ encrypted += cipher.final("hex");
27
+ return { encrypted, iv: iv.toString("hex") };
28
+ },
29
+ decryptApiKey(encrypted, ivHex) {
30
+ const decipher = createDecipheriv(ALGORITHM, key(), Buffer.from(ivHex, "hex"));
31
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
32
+ decrypted += decipher.final("utf8");
33
+ return decrypted;
34
+ },
35
+ };
36
+ }
37
+ /** A new random key suitable for ENCRYPTION_KEY. */
38
+ export function generateEncryptionKey() {
39
+ return randomBytes(32).toString("hex");
40
+ }
41
+ //# sourceMappingURL=encryption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../src/lib/encryption.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAEvE,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC,iBAAiB;AASvC;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAuB;IACtD,SAAS,GAAG;QACV,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,OAAO;QACL,aAAa,CAAC,SAAS;YACrB,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;YAClC,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;YACpD,IAAI,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YACxD,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACjC,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,CAAC;QACD,aAAa,CAAC,SAAS,EAAE,KAAK;YAC5B,MAAM,QAAQ,GAAG,gBAAgB,CAC/B,SAAS,EACT,GAAG,EAAE,EACL,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAC1B,CAAC;YACF,IAAI,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;YAC1D,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACpC,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,qBAAqB;IACnC,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @fileoverview Entity API key header conventions
3
+ * @description An entity key (`<prefix>_...`) authenticates a caller as the
4
+ * *entity* rather than as a person: CI jobs, deployment scripts, and MCP clients
5
+ * that must keep working when the member who created them leaves. Storage,
6
+ * generation, and verification live in `@sudobility/entity_service`; this
7
+ * module owns only the product's prefix and the header rules.
8
+ *
9
+ * Three other credentials exist and must not be confused with this one:
10
+ * - personal key (user prefix), authenticates a *user* (see user-api-key.ts)
11
+ * - `sk_live_...` project key, authenticates callers of an AI endpoint
12
+ * - Firebase token browser session credential
13
+ */
14
+ export declare function createEntityApiKeyFormat(prefix: string): {
15
+ prefix: string;
16
+ prefixWithSeparator: string;
17
+ isEntityApiKeyFormat: (value: string) => boolean;
18
+ extractEntityApiKeyFromHeaders: (getHeader: (name: string) => string | undefined) => string | null;
19
+ };
20
+ export type EntityApiKeyFormat = ReturnType<typeof createEntityApiKeyFormat>;
21
+ //# sourceMappingURL=entity-api-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-api-key.d.ts","sourceRoot":"","sources":["../../src/lib/entity-api-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM;;;kCAOhB,MAAM,KAAG,OAAO;gDAkBxC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,KAC9C,MAAM,GAAG,IAAI;EAqBjB;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @fileoverview Entity API key header conventions
3
+ * @description An entity key (`<prefix>_...`) authenticates a caller as the
4
+ * *entity* rather than as a person: CI jobs, deployment scripts, and MCP clients
5
+ * that must keep working when the member who created them leaves. Storage,
6
+ * generation, and verification live in `@sudobility/entity_service`; this
7
+ * module owns only the product's prefix and the header rules.
8
+ *
9
+ * Three other credentials exist and must not be confused with this one:
10
+ * - personal key (user prefix), authenticates a *user* (see user-api-key.ts)
11
+ * - `sk_live_...` project key, authenticates callers of an AI endpoint
12
+ * - Firebase token browser session credential
13
+ */
14
+ export function createEntityApiKeyFormat(prefix) {
15
+ const prefixWithSeparator = `${prefix}_`;
16
+ /**
17
+ * Check whether a string looks like an entity API key for this product.
18
+ * Used to route an incoming credential to entity auth instead of user auth.
19
+ */
20
+ function isEntityApiKeyFormat(value) {
21
+ return (value.startsWith(prefixWithSeparator) &&
22
+ value.length > prefixWithSeparator.length);
23
+ }
24
+ /**
25
+ * Extract an entity API key from request headers.
26
+ *
27
+ * Accepts `X-API-Key: <prefix>_...` (preferred, unambiguous) and
28
+ * `Authorization: Bearer <prefix>_...`. Anything without the prefix is left
29
+ * alone so it can be tried as a personal key or a Firebase ID token instead.
30
+ *
31
+ * @param getHeader Reads a request header by name, case-insensitively
32
+ * @returns The key, or null when the request carries none
33
+ */
34
+ function extractEntityApiKeyFromHeaders(getHeader) {
35
+ const headerKey = getHeader("X-API-Key");
36
+ if (headerKey && isEntityApiKeyFormat(headerKey))
37
+ return headerKey;
38
+ const authHeader = getHeader("Authorization");
39
+ if (authHeader) {
40
+ const [type, token] = authHeader.split(" ");
41
+ if (type === "Bearer" && token && isEntityApiKeyFormat(token)) {
42
+ return token;
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+ return {
48
+ prefix,
49
+ prefixWithSeparator,
50
+ isEntityApiKeyFormat,
51
+ extractEntityApiKeyFromHeaders,
52
+ };
53
+ }
54
+ //# sourceMappingURL=entity-api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-api-key.js","sourceRoot":"","sources":["../../src/lib/entity-api-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,UAAU,wBAAwB,CAAC,MAAc;IACrD,MAAM,mBAAmB,GAAG,GAAG,MAAM,GAAG,CAAC;IAEzC;;;OAGG;IACH,SAAS,oBAAoB,CAAC,KAAa;QACzC,OAAO,CACL,KAAK,CAAC,UAAU,CAAC,mBAAmB,CAAC;YACrC,KAAK,CAAC,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAC1C,CAAC;IACJ,CAAC;IAED;;;;;;;;;OASG;IACH,SAAS,8BAA8B,CACrC,SAA+C;QAE/C,MAAM,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QACzC,IAAI,SAAS,IAAI,oBAAoB,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;QAEnE,MAAM,UAAU,GAAG,SAAS,CAAC,eAAe,CAAC,CAAC;QAC9C,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC5C,IAAI,IAAI,KAAK,QAAQ,IAAI,KAAK,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC9D,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,MAAM;QACN,mBAAmB;QACnB,oBAAoB;QACpB,8BAA8B;KAC/B,CAAC;AACJ,CAAC"}