@c15t/backend 1.2.0-canary.13 → 1.2.0-canary.2

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 (222) hide show
  1. package/.turbo/turbo-build.log +20 -22
  2. package/.turbo/turbo-fmt.log +4 -4
  3. package/.turbo/turbo-test.log +531 -0
  4. package/coverage/coverage-final.json +84 -0
  5. package/coverage/coverage-summary.json +85 -0
  6. package/coverage/html/backend/index.html +116 -0
  7. package/coverage/html/backend/rslib.config.ts.html +415 -0
  8. package/coverage/html/backend/src/contracts/consent/index.html +161 -0
  9. package/coverage/html/backend/src/contracts/consent/index.ts.html +112 -0
  10. package/coverage/html/backend/src/contracts/consent/post.contract.ts.html +559 -0
  11. package/coverage/html/backend/src/contracts/consent/show-banner.contract.ts.html +220 -0
  12. package/coverage/html/backend/src/contracts/consent/verify.contract.ts.html +463 -0
  13. package/coverage/html/backend/src/contracts/index.html +116 -0
  14. package/coverage/html/backend/src/contracts/index.ts.html +139 -0
  15. package/coverage/html/backend/src/contracts/meta/index.html +131 -0
  16. package/coverage/html/backend/src/contracts/meta/index.ts.html +100 -0
  17. package/coverage/html/backend/src/contracts/meta/status.contract.ts.html +196 -0
  18. package/coverage/html/backend/src/contracts/shared/index.html +116 -0
  19. package/coverage/html/backend/src/contracts/shared/jurisdiction.schema.ts.html +175 -0
  20. package/coverage/html/backend/src/core.ts.html +1624 -0
  21. package/coverage/html/backend/src/handlers/consent/index.html +161 -0
  22. package/coverage/html/backend/src/handlers/consent/index.ts.html +112 -0
  23. package/coverage/html/backend/src/handlers/consent/post.handler.ts.html +889 -0
  24. package/coverage/html/backend/src/handlers/consent/show-banner.handler.ts.html +535 -0
  25. package/coverage/html/backend/src/handlers/consent/verify.handler.ts.html +1000 -0
  26. package/coverage/html/backend/src/handlers/meta/index.html +131 -0
  27. package/coverage/html/backend/src/handlers/meta/index.ts.html +100 -0
  28. package/coverage/html/backend/src/handlers/meta/status.handler.ts.html +226 -0
  29. package/coverage/html/backend/src/index.html +161 -0
  30. package/coverage/html/backend/src/init.ts.html +1018 -0
  31. package/coverage/html/backend/src/pkgs/api-router/hooks/index.html +116 -0
  32. package/coverage/html/backend/src/pkgs/api-router/hooks/processor.ts.html +544 -0
  33. package/coverage/html/backend/src/pkgs/api-router/index.html +116 -0
  34. package/coverage/html/backend/src/pkgs/api-router/telemetry.ts.html +334 -0
  35. package/coverage/html/backend/src/pkgs/api-router/utils/cors.ts.html +304 -0
  36. package/coverage/html/backend/src/pkgs/api-router/utils/index.html +131 -0
  37. package/coverage/html/backend/src/pkgs/api-router/utils/ip.ts.html +361 -0
  38. package/coverage/html/backend/src/pkgs/data-model/fields/field-factory.ts.html +709 -0
  39. package/coverage/html/backend/src/pkgs/data-model/fields/id-generator.ts.html +256 -0
  40. package/coverage/html/backend/src/pkgs/data-model/fields/index.html +161 -0
  41. package/coverage/html/backend/src/pkgs/data-model/fields/superjson-utils.ts.html +136 -0
  42. package/coverage/html/backend/src/pkgs/data-model/fields/zod-fields.ts.html +496 -0
  43. package/coverage/html/backend/src/pkgs/data-model/hooks/create-hooks.ts.html +349 -0
  44. package/coverage/html/backend/src/pkgs/data-model/hooks/index.html +176 -0
  45. package/coverage/html/backend/src/pkgs/data-model/hooks/update-hooks.ts.html +358 -0
  46. package/coverage/html/backend/src/pkgs/data-model/hooks/update-many-hooks.ts.html +613 -0
  47. package/coverage/html/backend/src/pkgs/data-model/hooks/utils.ts.html +538 -0
  48. package/coverage/html/backend/src/pkgs/data-model/hooks/with-hooks-factory.ts.html +289 -0
  49. package/coverage/html/backend/src/pkgs/db-adapters/adapter-factory.ts.html +289 -0
  50. package/coverage/html/backend/src/pkgs/db-adapters/adapters/drizzle-adapter/drizzle-adapter.ts.html +2203 -0
  51. package/coverage/html/backend/src/pkgs/db-adapters/adapters/drizzle-adapter/index.html +116 -0
  52. package/coverage/html/backend/src/pkgs/db-adapters/adapters/index.html +116 -0
  53. package/coverage/html/backend/src/pkgs/db-adapters/adapters/kysely-adapter/dialect.ts.html +670 -0
  54. package/coverage/html/backend/src/pkgs/db-adapters/adapters/kysely-adapter/index.html +131 -0
  55. package/coverage/html/backend/src/pkgs/db-adapters/adapters/kysely-adapter/kysely-adapter.ts.html +3634 -0
  56. package/coverage/html/backend/src/pkgs/db-adapters/adapters/kysely-adapter/tests/index.html +116 -0
  57. package/coverage/html/backend/src/pkgs/db-adapters/adapters/kysely-adapter/tests/test-utils.ts.html +1417 -0
  58. package/coverage/html/backend/src/pkgs/db-adapters/adapters/memory-adapter/index.html +116 -0
  59. package/coverage/html/backend/src/pkgs/db-adapters/adapters/memory-adapter/memory-adapter.ts.html +2071 -0
  60. package/coverage/html/backend/src/pkgs/db-adapters/adapters/prisma-adapter/index.html +116 -0
  61. package/coverage/html/backend/src/pkgs/db-adapters/adapters/prisma-adapter/prisma-adapter.ts.html +1834 -0
  62. package/coverage/html/backend/src/pkgs/db-adapters/adapters/test.ts.html +316 -0
  63. package/coverage/html/backend/src/pkgs/db-adapters/index.html +131 -0
  64. package/coverage/html/backend/src/pkgs/db-adapters/utils.ts.html +238 -0
  65. package/coverage/html/backend/src/pkgs/migrations/get-migration.ts.html +343 -0
  66. package/coverage/html/backend/src/pkgs/migrations/get-schema/get-schema.ts.html +217 -0
  67. package/coverage/html/backend/src/pkgs/migrations/get-schema/index.html +146 -0
  68. package/coverage/html/backend/src/pkgs/migrations/get-schema/process-fields.ts.html +280 -0
  69. package/coverage/html/backend/src/pkgs/migrations/get-schema/process-tables.ts.html +289 -0
  70. package/coverage/html/backend/src/pkgs/migrations/index.html +176 -0
  71. package/coverage/html/backend/src/pkgs/migrations/migration-builders.ts.html +595 -0
  72. package/coverage/html/backend/src/pkgs/migrations/migration-execution.ts.html +301 -0
  73. package/coverage/html/backend/src/pkgs/migrations/schema-comparison.ts.html +694 -0
  74. package/coverage/html/backend/src/pkgs/migrations/type-mapping.ts.html +817 -0
  75. package/coverage/html/backend/src/pkgs/results/core/error-class.ts.html +976 -0
  76. package/coverage/html/backend/src/pkgs/results/core/error-codes.ts.html +703 -0
  77. package/coverage/html/backend/src/pkgs/results/core/index.html +146 -0
  78. package/coverage/html/backend/src/pkgs/results/core/tracing.ts.html +280 -0
  79. package/coverage/html/backend/src/pkgs/results/create-telemetry-options.ts.html +271 -0
  80. package/coverage/html/backend/src/pkgs/results/index.html +131 -0
  81. package/coverage/html/backend/src/pkgs/results/orpc-error-handler.ts.html +496 -0
  82. package/coverage/html/backend/src/pkgs/results/results/index.html +131 -0
  83. package/coverage/html/backend/src/pkgs/results/results/recovery-utils.ts.html +628 -0
  84. package/coverage/html/backend/src/pkgs/results/results/result-helpers.ts.html +1234 -0
  85. package/coverage/html/backend/src/pkgs/utils/env.ts.html +337 -0
  86. package/coverage/html/backend/src/pkgs/utils/index.html +146 -0
  87. package/coverage/html/backend/src/pkgs/utils/logger.ts.html +199 -0
  88. package/coverage/html/backend/src/pkgs/utils/url.ts.html +400 -0
  89. package/coverage/html/backend/src/router.ts.html +109 -0
  90. package/coverage/html/backend/src/schema/audit-log/index.html +146 -0
  91. package/coverage/html/backend/src/schema/audit-log/registry.ts.html +436 -0
  92. package/coverage/html/backend/src/schema/audit-log/schema.ts.html +223 -0
  93. package/coverage/html/backend/src/schema/audit-log/table.ts.html +640 -0
  94. package/coverage/html/backend/src/schema/consent/index.html +146 -0
  95. package/coverage/html/backend/src/schema/consent/registry.ts.html +616 -0
  96. package/coverage/html/backend/src/schema/consent/schema.ts.html +238 -0
  97. package/coverage/html/backend/src/schema/consent/table.ts.html +748 -0
  98. package/coverage/html/backend/src/schema/consent-policy/index.html +146 -0
  99. package/coverage/html/backend/src/schema/consent-policy/registry.ts.html +1063 -0
  100. package/coverage/html/backend/src/schema/consent-policy/schema.ts.html +265 -0
  101. package/coverage/html/backend/src/schema/consent-policy/table.ts.html +535 -0
  102. package/coverage/html/backend/src/schema/consent-purpose/index.html +146 -0
  103. package/coverage/html/backend/src/schema/consent-purpose/registry.ts.html +589 -0
  104. package/coverage/html/backend/src/schema/consent-purpose/schema.ts.html +259 -0
  105. package/coverage/html/backend/src/schema/consent-purpose/table.ts.html +547 -0
  106. package/coverage/html/backend/src/schema/consent-record/index.html +131 -0
  107. package/coverage/html/backend/src/schema/consent-record/schema.ts.html +211 -0
  108. package/coverage/html/backend/src/schema/consent-record/table.ts.html +457 -0
  109. package/coverage/html/backend/src/schema/create-registry.ts.html +148 -0
  110. package/coverage/html/backend/src/schema/definition.ts.html +685 -0
  111. package/coverage/html/backend/src/schema/domain/index.html +146 -0
  112. package/coverage/html/backend/src/schema/domain/registry.ts.html +973 -0
  113. package/coverage/html/backend/src/schema/domain/schema.ts.html +214 -0
  114. package/coverage/html/backend/src/schema/domain/table.ts.html +496 -0
  115. package/coverage/html/backend/src/schema/index.html +146 -0
  116. package/coverage/html/backend/src/schema/schemas.ts.html +166 -0
  117. package/coverage/html/backend/src/schema/subject/index.html +146 -0
  118. package/coverage/html/backend/src/schema/subject/registry.ts.html +973 -0
  119. package/coverage/html/backend/src/schema/subject/schema.ts.html +208 -0
  120. package/coverage/html/backend/src/schema/subject/table.ts.html +499 -0
  121. package/coverage/html/backend/src/server.ts.html +475 -0
  122. package/coverage/html/backend/src/testing/contract-testing.ts.html +1348 -0
  123. package/coverage/html/backend/src/testing/index.html +116 -0
  124. package/coverage/html/base.css +224 -0
  125. package/coverage/html/block-navigation.js +87 -0
  126. package/coverage/html/favicon.png +0 -0
  127. package/coverage/html/index.html +626 -0
  128. package/coverage/html/prettify.css +1 -0
  129. package/coverage/html/prettify.js +2 -0
  130. package/coverage/html/sort-arrow-sprite.png +0 -0
  131. package/coverage/html/sorter.js +196 -0
  132. package/dist/contracts/consent/index.d.ts +2 -2
  133. package/dist/contracts/consent/post.contract.d.ts +2 -2
  134. package/dist/contracts/consent/post.contract.d.ts.map +1 -1
  135. package/dist/contracts/index.d.ts +5 -5
  136. package/dist/contracts/index.d.ts.map +1 -1
  137. package/dist/core.cjs +244 -388
  138. package/dist/core.d.ts +5 -3
  139. package/dist/core.d.ts.map +1 -1
  140. package/dist/core.js +244 -388
  141. package/dist/handlers/consent/index.d.ts +2 -2
  142. package/dist/handlers/consent/post.handler.d.ts +2 -2
  143. package/dist/handlers/consent/show-banner.handler.d.ts.map +1 -1
  144. package/dist/pkgs/api-router/utils/core.test.d.ts +2 -0
  145. package/dist/pkgs/api-router/utils/core.test.d.ts.map +1 -0
  146. package/dist/pkgs/api-router/utils/cors.d.ts +14 -0
  147. package/dist/pkgs/api-router/utils/cors.d.ts.map +1 -0
  148. package/dist/pkgs/data-model/fields/zod-fields.d.ts +32 -32
  149. package/dist/pkgs/data-model/index.cjs +39 -59
  150. package/dist/pkgs/data-model/index.js +39 -59
  151. package/dist/pkgs/data-model/schema/index.cjs +39 -59
  152. package/dist/pkgs/data-model/schema/index.js +39 -59
  153. package/dist/pkgs/db-adapters/adapters/drizzle-adapter/index.cjs +1 -0
  154. package/dist/pkgs/db-adapters/adapters/drizzle-adapter/index.js +1 -0
  155. package/dist/pkgs/db-adapters/adapters/kysely-adapter/index.cjs +1 -0
  156. package/dist/pkgs/db-adapters/adapters/kysely-adapter/index.js +1 -0
  157. package/dist/pkgs/db-adapters/adapters/memory-adapter/index.cjs +1 -0
  158. package/dist/pkgs/db-adapters/adapters/memory-adapter/index.js +1 -0
  159. package/dist/pkgs/db-adapters/adapters/prisma-adapter/index.cjs +1 -0
  160. package/dist/pkgs/db-adapters/adapters/prisma-adapter/index.js +1 -0
  161. package/dist/pkgs/db-adapters/index.cjs +1 -0
  162. package/dist/pkgs/db-adapters/index.js +1 -0
  163. package/dist/pkgs/migrations/index.cjs +1 -0
  164. package/dist/pkgs/migrations/index.js +1 -0
  165. package/dist/router.cjs +5 -3
  166. package/dist/router.d.ts +2 -2
  167. package/dist/router.js +5 -3
  168. package/dist/schema/consent-policy/registry.d.ts +4 -4
  169. package/dist/schema/consent-policy/registry.d.ts.map +1 -1
  170. package/dist/schema/consent-policy/schema.d.ts +2 -2
  171. package/dist/schema/consent-policy/table.d.ts +2 -2
  172. package/dist/schema/consent-purpose/registry.d.ts +2 -2
  173. package/dist/schema/consent-purpose/schema.d.ts +2 -2
  174. package/dist/schema/consent-purpose/table.d.ts +2 -2
  175. package/dist/schema/create-registry.d.ts +6 -6
  176. package/dist/schema/create-registry.d.ts.map +1 -1
  177. package/dist/schema/definition.d.ts +4 -4
  178. package/dist/schema/index.cjs +39 -59
  179. package/dist/schema/index.js +39 -59
  180. package/dist/schema/schemas.d.ts +4 -4
  181. package/package.json +2 -8
  182. package/rslib.config.ts +0 -1
  183. package/src/contracts/consent/post.contract.ts +6 -1
  184. package/src/contracts/index.ts +0 -2
  185. package/src/core.ts +195 -96
  186. package/src/handlers/consent/show-banner.handler.test.ts +1 -1
  187. package/src/handlers/consent/show-banner.handler.ts +2 -1
  188. package/src/{middleware/cors/is-origin-trusted.test.ts → pkgs/api-router/utils/core.test.ts} +1 -1
  189. package/src/pkgs/api-router/utils/cors.ts +73 -0
  190. package/src/schema/consent-policy/registry.ts +50 -76
  191. package/src/server.ts +5 -1
  192. package/dist/__tests__/server.test.d.ts +0 -2
  193. package/dist/__tests__/server.test.d.ts.map +0 -1
  194. package/dist/contracts.cjs +0 -708
  195. package/dist/contracts.js +0 -661
  196. package/dist/middleware/cors/cors.d.ts +0 -37
  197. package/dist/middleware/cors/cors.d.ts.map +0 -1
  198. package/dist/middleware/cors/cors.test.d.ts +0 -2
  199. package/dist/middleware/cors/cors.test.d.ts.map +0 -1
  200. package/dist/middleware/cors/index.d.ts +0 -30
  201. package/dist/middleware/cors/index.d.ts.map +0 -1
  202. package/dist/middleware/cors/is-origin-trusted.d.ts +0 -49
  203. package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
  204. package/dist/middleware/cors/is-origin-trusted.test.d.ts +0 -2
  205. package/dist/middleware/cors/is-origin-trusted.test.d.ts.map +0 -1
  206. package/dist/middleware/cors/process-cors.d.ts +0 -31
  207. package/dist/middleware/cors/process-cors.d.ts.map +0 -1
  208. package/dist/middleware/openapi/config.d.ts +0 -28
  209. package/dist/middleware/openapi/config.d.ts.map +0 -1
  210. package/dist/middleware/openapi/handlers.d.ts +0 -29
  211. package/dist/middleware/openapi/handlers.d.ts.map +0 -1
  212. package/dist/middleware/openapi/index.d.ts +0 -11
  213. package/dist/middleware/openapi/index.d.ts.map +0 -1
  214. package/src/__tests__/server.test.ts +0 -96
  215. package/src/middleware/cors/cors.test.ts +0 -419
  216. package/src/middleware/cors/cors.ts +0 -192
  217. package/src/middleware/cors/index.ts +0 -30
  218. package/src/middleware/cors/is-origin-trusted.ts +0 -126
  219. package/src/middleware/cors/process-cors.ts +0 -91
  220. package/src/middleware/openapi/config.ts +0 -28
  221. package/src/middleware/openapi/handlers.ts +0 -132
  222. package/src/middleware/openapi/index.ts +0 -11
package/src/core.ts CHANGED
@@ -1,19 +1,16 @@
1
- import { createLogger } from '@doubletie/logger';
1
+ import { type Logger, createLogger } from '@doubletie/logger';
2
+ import { OpenAPIGenerator } from '@orpc/openapi';
2
3
  import { OpenAPIHandler } from '@orpc/openapi/fetch';
3
4
  import { CORSPlugin } from '@orpc/server/plugins';
5
+ import { ZodToJsonSchemaConverter } from '@orpc/zod';
4
6
  import { DoubleTieError, ERROR_CODES } from '~/pkgs/results';
5
7
  import type { C15TContext, C15TOptions, C15TPlugin } from '~/types';
8
+ import packageJson from '../package.json';
6
9
  import { init } from './init';
7
- import { createCORSOptions, processCors } from './middleware/cors';
8
- import {
9
- createDocsUI,
10
- createOpenAPIConfig,
11
- createOpenAPISpec,
12
- } from './middleware/openapi';
13
10
  import { withRequestSpan } from './pkgs/api-router/telemetry';
11
+ import { isOriginTrusted } from './pkgs/api-router/utils/cors';
14
12
  import { getIp } from './pkgs/api-router/utils/ip';
15
13
  import { router } from './router';
16
-
17
14
  /**
18
15
  * Type representing an API route
19
16
  */
@@ -95,6 +92,22 @@ export interface C15TInstance<PluginTypes extends C15TPlugin[] = C15TPlugin[]> {
95
92
  getDocsUI: () => string;
96
93
  }
97
94
 
95
+ // Define middleware context interface
96
+ interface MiddlewareContext {
97
+ logger?: Logger;
98
+ adapter: unknown;
99
+ registry: unknown;
100
+ generateId: unknown;
101
+ ipAddress?: string;
102
+ origin?: string;
103
+ trustedOrigin?: boolean;
104
+ path?: string;
105
+ method?: string;
106
+ headers?: Headers;
107
+ userAgent?: string;
108
+ [key: string]: unknown;
109
+ }
110
+
98
111
  /**
99
112
  * Creates a new c15t consent management instance.
100
113
  *
@@ -106,22 +119,91 @@ export const c15tInstance = <PluginTypes extends C15TPlugin[] = C15TPlugin[]>(
106
119
  // Initialize context
107
120
  const contextPromise = init(options);
108
121
 
109
- // Replace the inline corsOptions with the imported one
110
- const corsOptions = createCORSOptions(options.trustedOrigins);
122
+ const corsOptions = options.trustedOrigins
123
+ ? {
124
+ // When specific origins are configured
125
+ origin: options.trustedOrigins.includes('*')
126
+ ? (origin: string) => origin // Return the actual origin
127
+ : options.trustedOrigins, // Otherwise use the specific list
128
+ credentials: true, // Allow cookies/auth headers
129
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
130
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
131
+ maxAge: 86400,
132
+ }
133
+ : {
134
+ // Default configuration when no origins specified
135
+ origin: '*', // Allow all origins
136
+ credentials: false, // Can't use credentials with wildcard origin
137
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
138
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
139
+ maxAge: 86400,
140
+ };
111
141
 
112
142
  // Create the oRPC handler with plugins
113
143
  const rpcHandler = new OpenAPIHandler(router, {
114
- plugins: [new CORSPlugin(corsOptions)],
144
+ plugins: [
145
+ new CORSPlugin({
146
+ ...corsOptions,
147
+ // Ensure these headers are always set
148
+ origin: (origin: string) => {
149
+ // If no origin, return null to deny the request
150
+ if (!origin) { return null; }
151
+
152
+ // If wildcard is allowed, return the actual origin
153
+ if (options.trustedOrigins?.includes('*')) {
154
+ return origin;
155
+ }
156
+
157
+ // If origin is in trusted list, return it
158
+ if (options.trustedOrigins?.includes(origin)) {
159
+ return origin;
160
+ }
161
+
162
+ // Deny all other origins
163
+ return null;
164
+ },
165
+ credentials: true,
166
+ exposeHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
167
+ }),
168
+ ],
169
+ });
170
+
171
+ // Initialize OpenAPI generator with schema converters
172
+ const openAPIGenerator = new OpenAPIGenerator({
173
+ schemaConverters: [new ZodToJsonSchemaConverter()],
115
174
  });
116
175
 
117
- // Set up OpenAPI configuration
118
- const openApiConfig = createOpenAPIConfig(options);
119
- const getDocsUI = () => createDocsUI(options);
176
+ // Set up OpenAPI configuration with defaults
177
+ const openApiConfig = {
178
+ enabled: true,
179
+ specPath: '/spec.json',
180
+ docsPath: '/docs',
181
+ ...(options.openapi || {}),
182
+ };
183
+
184
+ // Default OpenAPI options
185
+ const defaultOpenApiOptions = {
186
+ info: {
187
+ title: options.appName || 'c15t API',
188
+ version: packageJson.version,
189
+ description: 'API for consent management',
190
+ },
191
+ servers: [{ url: '/' }],
192
+ security: [{ bearerAuth: [] }],
193
+ // components: {
194
+ // securitySchemes: {
195
+ // bearerAuth: {
196
+ // type: 'http',
197
+ // scheme: 'bearer',
198
+ // },
199
+ // },
200
+ // },
201
+ };
120
202
 
121
203
  /**
122
204
  * Process IP tracking and add it to the context
123
205
  */
124
- const processIp = (request: Request, context: C15TContext) => {
206
+ const processIp = (request: Request, context: MiddlewareContext) => {
125
207
  const ip = getIp(request, options);
126
208
  if (ip) {
127
209
  context.ipAddress = ip;
@@ -129,10 +211,28 @@ export const c15tInstance = <PluginTypes extends C15TPlugin[] = C15TPlugin[]>(
129
211
  return context;
130
212
  };
131
213
 
214
+ /**
215
+ * Process CORS validation and add it to the context
216
+ */
217
+ const processCors = (request: Request, context: MiddlewareContext) => {
218
+ const origin = request.headers.get('origin');
219
+ if (origin && options.trustedOrigins) {
220
+ const trusted = isOriginTrusted(
221
+ origin,
222
+ options.trustedOrigins,
223
+ context.logger
224
+ );
225
+
226
+ context.origin = origin;
227
+ context.trustedOrigin = trusted;
228
+ }
229
+ return context;
230
+ };
231
+
132
232
  /**
133
233
  * Add telemetry tracking to the context
134
234
  */
135
- const processTelemetry = (request: Request, context: C15TContext) => {
235
+ const processTelemetry = (request: Request, context: MiddlewareContext) => {
136
236
  const url = new URL(request.url);
137
237
  const path = url.pathname;
138
238
  const method = request.method;
@@ -158,6 +258,81 @@ export const c15tInstance = <PluginTypes extends C15TPlugin[] = C15TPlugin[]>(
158
258
  return context;
159
259
  };
160
260
 
261
+ /**
262
+ * Generate the OpenAPI specification document
263
+ */
264
+ const getOpenAPISpec = (async (): Promise<Record<string, unknown>> => {
265
+ // Memoise once per process
266
+ if (getOpenAPISpec.cached) {
267
+ return getOpenAPISpec.cached;
268
+ }
269
+
270
+ // Start with our defaults
271
+ const mergedOptions = { ...defaultOpenApiOptions };
272
+
273
+ // If user provided options, merge them with defaults
274
+ if (openApiConfig.options) {
275
+ // biome-ignore lint: OpenAPI options are dynamically merged
276
+ const userOptions = openApiConfig.options as Record<string, any>;
277
+
278
+ // Handle nested info object (title, description, version) specially
279
+ if (userOptions.info) {
280
+ mergedOptions.info = {
281
+ ...defaultOpenApiOptions.info,
282
+ ...userOptions.info,
283
+ };
284
+ }
285
+
286
+ // For all other top-level properties, override defaults with user settings
287
+ for (const [key, value] of Object.entries(userOptions)) {
288
+ if (key !== 'info') {
289
+ (mergedOptions as Record<string, unknown>)[key] = value;
290
+ }
291
+ }
292
+ }
293
+
294
+ // We need to cast to the expected type due to incompatibilities between the types
295
+ // This is safe as we control the options format and it's compatible with what the generator expects
296
+ const spec = await openAPIGenerator.generate(
297
+ router,
298
+ mergedOptions as Record<string, unknown>
299
+ );
300
+ getOpenAPISpec.cached = spec;
301
+ return spec;
302
+ }) as (() => Promise<Record<string, unknown>>) & {
303
+ cached?: Record<string, unknown>;
304
+ };
305
+
306
+ /**
307
+ * Generate the default UI for API documentation
308
+ */
309
+ const getDocsUI = () => {
310
+ // If a custom template is provided, use it
311
+ if (openApiConfig.customUiTemplate) {
312
+ return openApiConfig.customUiTemplate;
313
+ }
314
+
315
+ // Otherwise, return the default Scalar UI
316
+ return `
317
+ <!doctype html>
318
+ <html>
319
+ <head>
320
+ <title>${options.appName || 'c15t API'} Documentation</title>
321
+ <meta charset="utf-8" />
322
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
323
+ <link rel="icon" type="image/svg+xml" href="https://orpc.unnoq.com/icon.svg" />
324
+ </head>
325
+ <body>
326
+ <script
327
+ id="api-reference"
328
+ data-url="${encodeURI(openApiConfig.specPath)}">
329
+ </script>
330
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
331
+ </body>
332
+ </html>
333
+ `;
334
+ };
335
+
161
336
  /**
162
337
  * Handle OpenAPI spec requests
163
338
  */
@@ -165,24 +340,6 @@ export const c15tInstance = <PluginTypes extends C15TPlugin[] = C15TPlugin[]>(
165
340
  url: URL
166
341
  ): Promise<Response | null> => {
167
342
  if (openApiConfig.enabled && url.pathname === openApiConfig.specPath) {
168
- const ctxResult = await contextPromise;
169
- if (!ctxResult.isOk()) {
170
- throw ctxResult.error;
171
- }
172
- const ctx = ctxResult.value;
173
- const orpcContext = {
174
- adapter: ctx.adapter,
175
- registry: ctx.registry,
176
- logger: ctx.logger,
177
- generateId: ctx.generateId,
178
- headers: new Headers(),
179
- appName: options.appName || 'c15t',
180
- options,
181
- trustedOrigins: options.trustedOrigins || [],
182
- baseURL: options.baseURL || '/',
183
- tables: ctx.tables,
184
- };
185
- const getOpenAPISpec = createOpenAPISpec(orpcContext, options);
186
343
  const spec = await getOpenAPISpec();
187
344
  return new Response(JSON.stringify(spec), {
188
345
  status: 200,
@@ -275,52 +432,32 @@ export const c15tInstance = <PluginTypes extends C15TPlugin[] = C15TPlugin[]>(
275
432
  ctx: C15TContext
276
433
  ): Promise<Response> => {
277
434
  // Create context for the handler with c15t specifics
278
- const orpcContext = {
435
+ const orpcContext: MiddlewareContext = {
279
436
  adapter: ctx.adapter,
280
437
  registry: ctx.registry,
281
438
  logger: ctx.logger,
282
439
  generateId: ctx.generateId,
283
440
  headers: request.headers,
284
441
  userAgent: request.headers.get('user-agent') || undefined,
285
- appName: options.appName || 'c15t',
286
- options,
287
- trustedOrigins: options.trustedOrigins || [],
288
- baseURL: options.baseURL || '/',
289
- tables: ctx.tables,
290
442
  };
291
443
 
292
444
  // Apply middleware processing to enrich the context
293
445
  processIp(request, orpcContext);
294
- processCors(request, orpcContext, options.trustedOrigins);
446
+ processCors(request, orpcContext);
295
447
  processTelemetry(request, orpcContext);
296
448
 
297
449
  // Use oRPC handler to handle the request with our enhanced context
298
450
  const handlerContext = orpcContext as Record<string, unknown>;
299
-
300
- orpcContext.logger.debug?.('Handling prefix', {
301
- prefix: (options.basePath as `/${string}`) || '/',
302
- });
303
-
304
451
  const { matched, response } = await rpcHandler.handle(request, {
305
- prefix: (options.basePath as `/${string}`) || '/',
452
+ prefix: '/',
306
453
  context: handlerContext,
307
454
  });
308
455
 
309
456
  // Return the response if handler matched
310
457
  if (matched && response) {
311
- orpcContext.logger.debug('Handler matched', {
312
- request,
313
- matched,
314
- response,
315
- });
316
458
  return response;
317
459
  }
318
460
 
319
- orpcContext.logger.debug('No handler matched', {
320
- request,
321
- matched,
322
- response,
323
- });
324
461
  // If no handler matched, return 404
325
462
  return new Response('Not Found', { status: 404 });
326
463
  };
@@ -331,11 +468,6 @@ export const c15tInstance = <PluginTypes extends C15TPlugin[] = C15TPlugin[]>(
331
468
  const handler = async (request: Request): Promise<Response> => {
332
469
  try {
333
470
  const url = new URL(request.url);
334
- // Add this debug log:
335
- createLogger(options.logger)?.debug?.('Incoming request', {
336
- method: request.method,
337
- pathname: url.pathname,
338
- });
339
471
 
340
472
  // Check for OpenAPI spec or docs UI requests
341
473
  const openApiResponse = await handleOpenApiSpecRequest(url);
@@ -355,19 +487,6 @@ export const c15tInstance = <PluginTypes extends C15TPlugin[] = C15TPlugin[]>(
355
487
  }
356
488
  const ctx = ctxResult.value;
357
489
 
358
- // After options/baseURL/basePath is set/used
359
- const basePath = options.basePath || options.baseURL || '/';
360
- createLogger(options.logger)?.debug?.('[c15t] Using basePath/baseURL', {
361
- basePath,
362
- });
363
-
364
- // Add this debug log:
365
- createLogger(options.logger)?.debug?.('[c15t] Routing request', {
366
- method: request.method,
367
- url: request.url,
368
- prefix: basePath,
369
- });
370
-
371
490
  // Handle API request
372
491
  return await handleApiRequest(request, ctx);
373
492
  } catch (error) {
@@ -423,27 +542,7 @@ export const c15tInstance = <PluginTypes extends C15TPlugin[] = C15TPlugin[]>(
423
542
  ...createNextHandlers(),
424
543
 
425
544
  // OpenAPI functionality
426
- getOpenAPISpec: async () => {
427
- const ctxResult = await contextPromise;
428
- if (!ctxResult.isOk()) {
429
- throw ctxResult.error;
430
- }
431
- const ctx = ctxResult.value;
432
- const orpcContext = {
433
- adapter: ctx.adapter,
434
- registry: ctx.registry,
435
- logger: ctx.logger,
436
- generateId: ctx.generateId,
437
- headers: new Headers(),
438
- appName: options.appName || 'c15t',
439
- options,
440
- trustedOrigins: options.trustedOrigins || [],
441
- baseURL: options.baseURL || '/',
442
- tables: ctx.tables,
443
- };
444
- const getOpenAPISpec = createOpenAPISpec(orpcContext, options);
445
- return getOpenAPISpec();
446
- },
545
+ getOpenAPISpec,
447
546
  getDocsUI,
448
547
  };
449
548
  };
@@ -119,7 +119,7 @@ describe('Show Consent Banner Handler', () => {
119
119
  createMockContext({ 'cf-ipcountry': 'US' })
120
120
  );
121
121
 
122
- expect(result.showConsentBanner).toBe(true);
122
+ expect(result.showConsentBanner).toBe(false);
123
123
  expect(result.jurisdiction.code).toBe('NONE');
124
124
  expect(result.jurisdiction.message).toBe(JurisdictionMessages.NONE);
125
125
  });
@@ -110,7 +110,7 @@ export function checkJurisdiction(countryCode: string | null) {
110
110
  };
111
111
 
112
112
  // Default to no jurisdiction
113
- const showConsentBanner = true;
113
+ let showConsentBanner = false;
114
114
  let jurisdictionCode: JurisdictionCode = 'NONE';
115
115
 
116
116
  // Check country code against jurisdiction sets
@@ -132,6 +132,7 @@ export function checkJurisdiction(countryCode: string | null) {
132
132
  // Find matching jurisdiction
133
133
  for (const { sets, code } of jurisdictionMap) {
134
134
  if (sets.some((set) => set.has(countryCode))) {
135
+ showConsentBanner = true;
135
136
  jurisdictionCode = code;
136
137
  break;
137
138
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { isOriginTrusted } from './is-origin-trusted';
2
+ import { isOriginTrusted } from './cors';
3
3
 
4
4
  /**
5
5
  * Test suite for CORS utility functions
@@ -0,0 +1,73 @@
1
+ import type { Logger } from '@doubletie/logger';
2
+
3
+ /**
4
+ * Regex to strip protocol, trailing slashes, and port numbers from URLs
5
+ */
6
+ export const STRIP_REGEX = /^(https?:\/\/)|(wss?:\/\/)|(\/+$)|:\d+/g;
7
+
8
+ /**
9
+ * Validates if a given origin matches a trusted domain pattern
10
+ *
11
+ * @param origin - The origin to validate
12
+ * @param trustedDomains - Array of trusted domain patterns (can include wildcard)
13
+ * @returns boolean indicating if the origin is trusted
14
+ */
15
+ export function isOriginTrusted(
16
+ origin: string,
17
+ trustedDomains: string[],
18
+ logger?: Logger
19
+ ): boolean {
20
+ try {
21
+ if (trustedDomains.length === 0) {
22
+ throw new Error('No trusted domains');
23
+ }
24
+
25
+ logger?.debug(
26
+ `Checking if origin ${origin} is trusted in ${trustedDomains}`
27
+ );
28
+
29
+ // Special case: if "*" is in trusted domains, allow all origins
30
+ if (trustedDomains.includes('*')) {
31
+ logger?.debug('Allowing all origins');
32
+ return true;
33
+ }
34
+
35
+ // Parse the origin URL to get just the hostname
36
+ const url = new URL(origin);
37
+ const originHostname = url.hostname.toLowerCase();
38
+ logger?.debug(`Parsed origin hostname: ${originHostname}`);
39
+
40
+ return trustedDomains.some((domain) => {
41
+ // Handle empty domains (which might come from splitting empty strings)
42
+ if (!domain || domain.trim() === '') {
43
+ logger?.debug('Skipping empty domain');
44
+ return false;
45
+ }
46
+
47
+ const strippedDomain = domain.replace(STRIP_REGEX, '').toLowerCase();
48
+ logger?.debug(`Checking against stripped domain: ${strippedDomain}`);
49
+
50
+ if (strippedDomain.startsWith('*.')) {
51
+ // For wildcard domains, ensure there is at least one subdomain
52
+ const wildcardDomain = strippedDomain.slice(2); // Remove *. prefix
53
+ const parts = originHostname.split('.');
54
+
55
+ const isValid =
56
+ parts.length > 2 && originHostname.endsWith(wildcardDomain);
57
+ logger?.debug(
58
+ `Wildcard match result: ${isValid} ${originHostname} ends with ${wildcardDomain} ${parts.length > 2} ${originHostname.endsWith(wildcardDomain)}`
59
+ );
60
+ return isValid;
61
+ }
62
+
63
+ const isMatch = originHostname === strippedDomain;
64
+ logger?.debug(
65
+ `Exact match result: ${isMatch} ${originHostname} === ${strippedDomain}`
66
+ );
67
+ return isMatch;
68
+ });
69
+ } catch (error) {
70
+ logger?.error('Error validating origin:', error);
71
+ return false;
72
+ }
73
+ }
@@ -1,7 +1,10 @@
1
+ import { createHash } from 'node:crypto';
2
+
1
3
  import { getWithHooks } from '~/pkgs/data-model';
2
4
  import type { Where } from '~/pkgs/db-adapters';
3
- import { DoubleTieError, ERROR_CODES } from '~/pkgs/results';
4
5
  import type { GenericEndpointContext, RegistryContext } from '~/pkgs/types';
6
+
7
+ import { DoubleTieError, ERROR_CODES } from '~/pkgs/results';
5
8
  import { validateEntityOutput } from '../definition';
6
9
  import type { ConsentPolicy, PolicyType } from './schema';
7
10
 
@@ -28,26 +31,9 @@ import type { ConsentPolicy, PolicyType } from './schema';
28
31
  *
29
32
  * @internal Used by findOrCreatePolicy to initialize placeholder content
30
33
  */
31
- async function generatePolicyPlaceholder(name: string, date: Date) {
34
+ function generatePolicyPlaceholder(name: string, date: Date) {
32
35
  const content = `[PLACEHOLDER] This is an automatically generated version of the ${name} policy.\n\nThis placeholder content should be replaced with actual policy terms before being presented to users.\n\nGenerated on: ${date.toISOString()}`;
33
-
34
- let contentHash: string;
35
- try {
36
- // Use Web Crypto API which is available in both browsers and edge environments
37
- const encoder = new TextEncoder();
38
- const data = encoder.encode(content);
39
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
40
- contentHash = Array.from(new Uint8Array(hashBuffer))
41
- .map((b) => b.toString(16).padStart(2, '0'))
42
- .join('');
43
- } catch (error) {
44
- throw new DoubleTieError('Failed to generate policy content hash', {
45
- code: ERROR_CODES.INTERNAL_SERVER_ERROR,
46
- status: 500,
47
- cause: error instanceof Error ? error : new Error(String(error)),
48
- });
49
- }
50
-
36
+ const contentHash = createHash('sha256').update(content).digest('hex');
51
37
  return { content, contentHash };
52
38
  }
53
39
 
@@ -280,69 +266,57 @@ export function policyRegistry({ adapter, ...ctx }: RegistryContext) {
280
266
  */
281
267
  findOrCreatePolicy: async (type: PolicyType) => {
282
268
  // Use a transaction to prevent race conditions
283
- return await adapter.transaction({
269
+ return adapter.transaction({
284
270
  callback: async (txAdapter) => {
285
- try {
286
- const now = new Date();
287
- const txRegistry = policyRegistry({
288
- adapter: txAdapter,
289
- ...ctx,
290
- });
271
+ const now = new Date();
272
+ const txRegistry = policyRegistry({
273
+ adapter: txAdapter,
274
+ ...ctx,
275
+ });
291
276
 
292
- // Find latest policy with exact name match directly from database
293
- const rawLatestPolicy = await txAdapter.findOne({
294
- model: 'consentPolicy',
295
- where: [
296
- { field: 'isActive', value: true },
297
- {
298
- field: 'type',
299
- value: type,
300
- },
301
- ],
302
- sortBy: {
303
- field: 'effectiveDate',
304
- direction: 'desc',
277
+ // Find latest policy with exact name match directly from database
278
+ const rawLatestPolicy = await txAdapter.findOne({
279
+ model: 'consentPolicy',
280
+ where: [
281
+ { field: 'isActive', value: true },
282
+ {
283
+ field: 'type',
284
+ value: type,
305
285
  },
306
- });
307
-
308
- const latestPolicy = rawLatestPolicy
309
- ? validateEntityOutput(
310
- 'consentPolicy',
311
- rawLatestPolicy,
312
- ctx.options
313
- )
314
- : null;
286
+ ],
287
+ sortBy: {
288
+ field: 'effectiveDate',
289
+ direction: 'desc',
290
+ },
291
+ });
315
292
 
316
- if (latestPolicy) {
317
- return latestPolicy;
318
- }
293
+ const latestPolicy = rawLatestPolicy
294
+ ? validateEntityOutput(
295
+ 'consentPolicy',
296
+ rawLatestPolicy,
297
+ ctx.options
298
+ )
299
+ : null;
319
300
 
320
- // Generate policy content and hash
321
- const { content: defaultContent, contentHash } =
322
- await generatePolicyPlaceholder(type, now);
301
+ if (latestPolicy) {
302
+ return latestPolicy;
303
+ }
323
304
 
324
- return txRegistry.createConsentPolicy({
325
- version: '1.0.0',
326
- type,
327
- name: type,
328
- effectiveDate: now,
329
- content: defaultContent,
330
- contentHash,
331
- isActive: true,
332
- updatedAt: now,
333
- expirationDate: null,
334
- });
335
- } catch (error) {
336
- // Log the error for debugging purposes
337
- ctx.logger.error('Error in findOrCreatePolicy transaction:', error);
305
+ // Generate policy content and hash
306
+ const { content: defaultContent, contentHash } =
307
+ generatePolicyPlaceholder(type, now);
338
308
 
339
- // Rethrow as a DoubleTieError with appropriate context
340
- throw new DoubleTieError('Failed to find or create policy', {
341
- code: ERROR_CODES.INTERNAL_SERVER_ERROR,
342
- status: 500,
343
- cause: error instanceof Error ? error : new Error(String(error)),
344
- });
345
- }
309
+ return txRegistry.createConsentPolicy({
310
+ version: '1.0.0',
311
+ type,
312
+ name: type,
313
+ effectiveDate: now,
314
+ content: defaultContent,
315
+ contentHash,
316
+ isActive: true,
317
+ updatedAt: now,
318
+ expirationDate: null,
319
+ });
346
320
  },
347
321
  });
348
322
  },
package/src/server.ts CHANGED
@@ -11,7 +11,11 @@ const logger = getLogger({
11
11
 
12
12
  // Create the c15t instance with our configuration
13
13
  const instance = c15tInstance({
14
- trustedOrigins: ['*'], // Allow all origins for development
14
+ advanced: {
15
+ cors: {
16
+ allowedOrigins: ['*'], // Allow all origins for development
17
+ },
18
+ },
15
19
  // Add OpenAPI configuration
16
20
  openapi: {
17
21
  enabled: true, // Set to true to enable docs
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=server.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/server.test.ts"],"names":[],"mappings":""}