@timbenniks/contentstack-platform-sdk 0.1.5 → 0.1.7

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 (63) hide show
  1. package/dist/auth/index.cjs +179 -0
  2. package/dist/auth/index.cjs.map +1 -1
  3. package/dist/auth/index.d.cts +2 -1
  4. package/dist/auth/index.d.ts +2 -1
  5. package/dist/auth/index.mjs +5 -1
  6. package/dist/{chunk-2BOUGPJ5.mjs → chunk-DTMY6LNR.mjs} +4 -4
  7. package/dist/{chunk-OGTRGKYK.mjs → chunk-G4JKXIL2.mjs} +4 -4
  8. package/dist/chunk-K3QIVKGD.mjs +430 -0
  9. package/dist/chunk-K3QIVKGD.mjs.map +1 -0
  10. package/dist/{chunk-NKLOZ5VI.mjs → chunk-V33LD4WS.mjs} +1 -1
  11. package/dist/chunk-V33LD4WS.mjs.map +1 -0
  12. package/dist/{chunk-SU5QEKYW.mjs → chunk-VLZEA5NW.mjs} +3 -3
  13. package/dist/chunk-VLZEA5NW.mjs.map +1 -0
  14. package/dist/{chunk-3C6J2BDB.mjs → chunk-WFPYU6CJ.mjs} +1 -1
  15. package/dist/chunk-WFPYU6CJ.mjs.map +1 -0
  16. package/dist/index.cjs +179 -0
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.mjs +8 -4
  21. package/dist/react/auth/index.cjs.map +1 -1
  22. package/dist/react/auth/index.d.cts +2 -2
  23. package/dist/react/auth/index.d.ts +2 -2
  24. package/dist/react/auth/index.mjs +1 -1
  25. package/dist/react/hooks/index.mjs +3 -3
  26. package/dist/react/index.cjs.map +1 -1
  27. package/dist/react/index.d.cts +1 -1
  28. package/dist/react/index.d.ts +1 -1
  29. package/dist/react/index.mjs +7 -7
  30. package/dist/server/index.cjs +1 -1
  31. package/dist/server/index.cjs.map +1 -1
  32. package/dist/server/index.d.cts +1 -1
  33. package/dist/server/index.d.ts +1 -1
  34. package/dist/server/index.mjs +3 -3
  35. package/dist/server/middleware/index.cjs +1 -1
  36. package/dist/server/middleware/index.cjs.map +1 -1
  37. package/dist/server/middleware/index.d.cts +3 -2
  38. package/dist/server/middleware/index.d.ts +3 -2
  39. package/dist/server/middleware/index.mjs +3 -3
  40. package/dist/server/proxy/index.mjs +2 -2
  41. package/dist/server/webhooks/index.mjs +2 -2
  42. package/dist/types-BPoq1WOf.d.cts +83 -0
  43. package/dist/types-CUCWv0Vp.d.ts +83 -0
  44. package/dist/ui/index.mjs +8 -8
  45. package/dist/vue/auth/index.cjs.map +1 -1
  46. package/dist/vue/auth/index.d.cts +4 -4
  47. package/dist/vue/auth/index.d.ts +4 -4
  48. package/dist/vue/auth/index.mjs +1 -1
  49. package/dist/vue/composables/index.mjs +3 -3
  50. package/dist/vue/index.cjs.map +1 -1
  51. package/dist/vue/index.d.cts +1 -1
  52. package/dist/vue/index.d.ts +1 -1
  53. package/dist/vue/index.mjs +10 -10
  54. package/package.json +1 -1
  55. package/dist/chunk-3C6J2BDB.mjs.map +0 -1
  56. package/dist/chunk-NKLOZ5VI.mjs.map +0 -1
  57. package/dist/chunk-SU5QEKYW.mjs.map +0 -1
  58. package/dist/chunk-VW7DD6HV.mjs +0 -253
  59. package/dist/chunk-VW7DD6HV.mjs.map +0 -1
  60. package/dist/types-6D9VR7pT.d.cts +0 -26
  61. package/dist/types-Bu5yCgmw.d.ts +0 -26
  62. /package/dist/{chunk-2BOUGPJ5.mjs.map → chunk-DTMY6LNR.mjs.map} +0 -0
  63. /package/dist/{chunk-OGTRGKYK.mjs.map → chunk-G4JKXIL2.mjs.map} +0 -0
@@ -0,0 +1,430 @@
1
+ import {
2
+ ContentstackHttpClient
3
+ } from "./chunk-4CJ4IVPJ.mjs";
4
+ import {
5
+ resolveEndpoints
6
+ } from "./chunk-BK2IBTQS.mjs";
7
+ import {
8
+ ContentstackAuthError,
9
+ ContentstackConfigError,
10
+ ContentstackError
11
+ } from "./chunk-DMERADWM.mjs";
12
+
13
+ // src/auth/scopes.ts
14
+ var OAUTH_SCOPE_DESCRIPTIONS = {
15
+ // User
16
+ "user:read": "View user details",
17
+ "user:write": "Update user details",
18
+ "user.assignments:read": "View user assignments",
19
+ // Organization
20
+ "organizations:read": "View details of all organizations associated with the user",
21
+ "organization:read": "View details of an organization",
22
+ "organization.logs:read": "View organization logs",
23
+ "organization.ownership:write": "Transfer organization ownership",
24
+ "organization.roles:read": "View organization-level roles",
25
+ "organization.share:write": "Update or remove organization invitation shares",
26
+ "organization.share:read": "View details of organization invitations shared with users",
27
+ // Stack management
28
+ "cm.stacks.management:read": "View all stacks",
29
+ "cm.stacks.management:write": "Create, update, or remove stacks",
30
+ "cm.stack.users:read": "View users associated with a stack",
31
+ "cm.stack.users:write": "Update user roles and user associations",
32
+ "cm.stack.management:write": "Update stack management properties",
33
+ "cm.stack.settings:read": "View stack settings",
34
+ "cm.stack:share": "Share stack invitation with users",
35
+ "cm.stack:unshare": "Unshare stack invitations",
36
+ // Tokens
37
+ "cm.stack.delivery-tokens:read": "View delivery tokens associated with a stack",
38
+ "cm.stack.delivery-tokens:write": "Create, update, or remove delivery tokens",
39
+ "cm.stack.management-tokens:read": "View management tokens associated with a stack",
40
+ "cm.stack.management-tokens:write": "Create, update, or remove management tokens",
41
+ // Content types
42
+ "cm.content-types.management:read": "View all content types",
43
+ "cm.content-types.management:write": "Create, update, or remove content types",
44
+ "cm.content-type:read": "View content type details",
45
+ "cm.content-types:export": "Export content types",
46
+ "cm.content-types:import": "Import content types",
47
+ // Global fields
48
+ "cm.global-fields.management:read": "View all global fields",
49
+ "cm.global-fields.management:write": "Create, update, or remove global fields",
50
+ "cm.global-fields:import": "Import global fields",
51
+ // Entries
52
+ "cm.entries.management:read": "View all entries",
53
+ "cm.entries.management:write": "Create, update, or remove entries",
54
+ "cm.entry:write": "Update details associated with an entry",
55
+ "cm.entry:read": "View details associated with an entry",
56
+ "cm.entries:export": "Export entries",
57
+ "cm.entries:import": "Import entries",
58
+ "cm.entry:publish": "Publish an entry",
59
+ "cm.entry:unpublish": "Unpublish an entry",
60
+ "cm.entry.workflow:write": "Set workflow stage for an entry",
61
+ // Bulk operations
62
+ "cm.bulk-operations:publish": "Publish items in bulk",
63
+ "cm.bulk-operations:unpublish": "Unpublish items in bulk",
64
+ "cm.bulk-operations:delete": "Delete items in bulk",
65
+ "cm.bulk-operations:workflow": "Set workflow stage in bulk",
66
+ // Assets
67
+ "cm.assets.management:read": "View all assets",
68
+ "cm.assets.management:write": "Create, update, or remove assets",
69
+ "cm.assets:download": "Download assets",
70
+ "cm.assets.rt:read": "View all RTE assets",
71
+ "cm.asset:write": "Create, update, or remove an asset",
72
+ "cm.asset:read": "View asset details",
73
+ "cm.asset:publish": "Publish an asset",
74
+ "cm.asset:unpublish": "Unpublish an asset",
75
+ // Extensions
76
+ "cm.extensions.management:read": "View all extensions",
77
+ "cm.extensions.management:write": "Create, update, or remove extensions",
78
+ // Releases
79
+ "cm.releases.management:read": "View all releases",
80
+ "cm.releases.management:write": "Create, update, or remove releases",
81
+ "cm.release:read": "View details associated with a release",
82
+ "cm.release:write": "Update details associated with a release",
83
+ "cm.release:deploy": "Deploy a release",
84
+ "cm.release:clone": "Clone a release",
85
+ // Workflows
86
+ "cm.workflows.management:read": "View all workflows",
87
+ "cm.workflows.management:write": "Create, update, or remove workflows",
88
+ "cm.workflows.publishing-rules:write": "Create, update, or remove workflow publishing rules",
89
+ "cm.workflows.publishing-rules:read": "View all workflow publishing rules",
90
+ // Labels
91
+ "cm.labels.management:read": "View all labels",
92
+ "cm.labels.management:write": "Create, update, or remove labels",
93
+ // Languages
94
+ "cm.languages.management:read": "View all languages",
95
+ "cm.languages.management:write": "Create, update, or remove languages",
96
+ // Environments
97
+ "cm.environments.management:read": "View all environments",
98
+ "cm.environments.management:write": "Create, update, or remove environments",
99
+ // Roles
100
+ "cm.roles.management:read": "View all roles",
101
+ "cm.roles.management:write": "Create, update, or remove roles",
102
+ // Webhooks
103
+ "cm.webhooks.management:read": "View all webhooks",
104
+ "cm.webhooks.management:write": "Create, update, or remove webhooks",
105
+ "cm.webhooks:export": "Export webhooks",
106
+ "cm.webhooks:import": "Import webhooks",
107
+ "cm.webhook:read": "View webhook details",
108
+ // Audit & publish queue
109
+ "cm.audit-logs:read": "View all audit logs",
110
+ "cm.publish-queue.management:read": "View all publish queue items",
111
+ "cm.publish-queue.management:write": "Create, update, or remove publish queue items",
112
+ // Branches
113
+ "cm.branches.management:read": "View all branches",
114
+ "cm.branches.management:write": "Create or delete a branch",
115
+ "cm.branch-aliases.management:read": "View all branch aliases",
116
+ "cm.branch-aliases.management:write": "Create, assign, or delete a branch alias",
117
+ // Taxonomies
118
+ "cm.taxonomies.management:read": "View all taxonomies",
119
+ "cm.taxonomies.management:write": "Create, update, or delete taxonomies",
120
+ "cm.taxonomy.terms:read": "View all terms of a taxonomy",
121
+ "cm.taxonomy.terms:write": "Create, update, move, or delete taxonomy terms",
122
+ // Brand Kit
123
+ "brand-kits:read": "View Brand Kits and Voice Profiles; use generative AI content generation",
124
+ "brand-kits:manage": "Create, update, or delete Brand Kits, Voice Profiles, and Knowledge Vault items",
125
+ // Personalization
126
+ "personalize:read": "View attributes, audiences, events, experiences, analytics, and regional data",
127
+ "personalize:manage": "Create, update, or delete personalization components",
128
+ // Launch
129
+ "launch:manage": "Read, update, and manage all Launch resources",
130
+ "launch.projects:read": "Read Launch projects and deployments",
131
+ "launch.projects:write": "Create and update Launch projects and deployments",
132
+ "launch.projects:delete": "Delete Launch projects"
133
+ };
134
+ var OAUTH_SCOPE_PRESETS = {
135
+ /** Minimal: only read the authenticated user's profile. */
136
+ readOnly: ["user:read"],
137
+ /** Read content types, entries, and assets. */
138
+ contentReader: [
139
+ "user:read",
140
+ "cm.content-types.management:read",
141
+ "cm.entries.management:read",
142
+ "cm.assets.management:read"
143
+ ],
144
+ /** Read and write entries and assets; read content types. */
145
+ cmsAuthoring: [
146
+ "user:read",
147
+ "cm.content-types.management:read",
148
+ "cm.entries.management:read",
149
+ "cm.entries.management:write",
150
+ "cm.entry:publish",
151
+ "cm.entry:unpublish",
152
+ "cm.assets.management:read",
153
+ "cm.assets.management:write",
154
+ "cm.asset:publish",
155
+ "cm.asset:unpublish"
156
+ ],
157
+ /** CMS authoring plus taxonomy management. */
158
+ cmsAuthoringWithTaxonomies: [
159
+ "user:read",
160
+ "cm.content-types.management:read",
161
+ "cm.entries.management:read",
162
+ "cm.entries.management:write",
163
+ "cm.entry:publish",
164
+ "cm.entry:unpublish",
165
+ "cm.assets.management:read",
166
+ "cm.assets.management:write",
167
+ "cm.asset:publish",
168
+ "cm.asset:unpublish",
169
+ "cm.taxonomies.management:read",
170
+ "cm.taxonomies.management:write",
171
+ "cm.taxonomy.terms:read",
172
+ "cm.taxonomy.terms:write"
173
+ ],
174
+ /** Brand Kit: read and manage voice profiles and knowledge vault. */
175
+ brandKit: ["user:read", "brand-kits:read", "brand-kits:manage"],
176
+ /** Launch: manage projects and deployments. */
177
+ launch: [
178
+ "user:read",
179
+ "launch:manage",
180
+ "launch.projects:read",
181
+ "launch.projects:write",
182
+ "launch.projects:delete"
183
+ ],
184
+ /** Personalization: read and manage experiences. */
185
+ personalization: ["user:read", "personalize:read", "personalize:manage"]
186
+ };
187
+
188
+ // src/auth/pkce.ts
189
+ var CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
190
+ function generateCodeVerifier() {
191
+ const bytes = new Uint8Array(43);
192
+ globalThis.crypto.getRandomValues(bytes);
193
+ let result = "";
194
+ for (const byte of bytes) {
195
+ result += CHARSET[byte % CHARSET.length];
196
+ }
197
+ return result;
198
+ }
199
+ async function generateCodeChallenge(verifier) {
200
+ const encoder = new TextEncoder();
201
+ const data = encoder.encode(verifier);
202
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", data);
203
+ const bytes = new Uint8Array(digest);
204
+ let binary = "";
205
+ for (const byte of bytes) {
206
+ binary += String.fromCharCode(byte);
207
+ }
208
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
209
+ }
210
+
211
+ // src/auth/oauth-client.ts
212
+ function validateConfig(config) {
213
+ if (!config.appId) {
214
+ throw new ContentstackConfigError(
215
+ "appId is required for OAuth. This is your Contentstack app's UID, not the client ID."
216
+ );
217
+ }
218
+ if (config.appId === config.clientId) {
219
+ throw new ContentstackConfigError(
220
+ "appId and clientId must be different. appId is your Contentstack app UID; clientId is the OAuth client identifier."
221
+ );
222
+ }
223
+ }
224
+ async function buildAuthorizationUrl(config, options) {
225
+ validateConfig(config);
226
+ const endpoints = resolveEndpoints(config.region);
227
+ const authorizationUrl = `${endpoints.app}/apps/${config.appId}/authorize`;
228
+ const state = options?.state ?? generateCodeVerifier();
229
+ const params = new URLSearchParams({
230
+ response_type: "code",
231
+ client_id: config.clientId,
232
+ redirect_uri: config.redirectUri,
233
+ scope: config.scopes.join(" "),
234
+ state
235
+ });
236
+ let codeVerifier;
237
+ if (options?.usePKCE) {
238
+ codeVerifier = generateCodeVerifier();
239
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
240
+ params.set("code_challenge", codeChallenge);
241
+ params.set("code_challenge_method", "S256");
242
+ }
243
+ return {
244
+ url: `${authorizationUrl}?${params.toString()}`,
245
+ state,
246
+ codeVerifier
247
+ };
248
+ }
249
+ function mapTokenResponse(data) {
250
+ return {
251
+ accessToken: data.access_token,
252
+ refreshToken: data.refresh_token,
253
+ expiresIn: data.expires_in,
254
+ tokenType: data.token_type
255
+ };
256
+ }
257
+ async function makeTokenRequest(appBaseUrl, body, errorMessage) {
258
+ const client = new ContentstackHttpClient({ baseUrl: appBaseUrl });
259
+ try {
260
+ const { data } = await client.postForm("/apps-api/token", body);
261
+ return mapTokenResponse(data);
262
+ } catch (err) {
263
+ if (err instanceof ContentstackError) {
264
+ throw new ContentstackAuthError(err.message || errorMessage, {
265
+ status: err.status,
266
+ requestPath: "/apps-api/token",
267
+ cause: err
268
+ });
269
+ }
270
+ throw new ContentstackAuthError(errorMessage, {
271
+ requestPath: "/apps-api/token",
272
+ cause: err instanceof Error ? err : void 0
273
+ });
274
+ }
275
+ }
276
+ async function exchangeCode(config, code, options) {
277
+ const endpoints = resolveEndpoints(config.region);
278
+ const body = new URLSearchParams({
279
+ grant_type: "authorization_code",
280
+ code,
281
+ redirect_uri: config.redirectUri,
282
+ client_id: config.clientId,
283
+ client_secret: config.clientSecret
284
+ });
285
+ if (options?.codeVerifier) {
286
+ body.set("code_verifier", options.codeVerifier);
287
+ }
288
+ return makeTokenRequest(endpoints.app, body, "Failed to exchange authorization code");
289
+ }
290
+ async function refreshToken(config, token) {
291
+ const endpoints = resolveEndpoints(config.region);
292
+ const body = new URLSearchParams({
293
+ grant_type: "refresh_token",
294
+ refresh_token: token,
295
+ client_id: config.clientId,
296
+ client_secret: config.clientSecret
297
+ });
298
+ return makeTokenRequest(endpoints.app, body, "Failed to refresh token");
299
+ }
300
+ function mapUser(user) {
301
+ return {
302
+ uid: user.uid,
303
+ email: user.email,
304
+ firstName: user.first_name ?? void 0,
305
+ lastName: user.last_name ?? void 0,
306
+ username: user.username ?? void 0,
307
+ profileImage: user.profile_image ?? void 0
308
+ };
309
+ }
310
+ async function getUser(region, accessToken) {
311
+ const endpoints = resolveEndpoints(region);
312
+ const client = new ContentstackHttpClient({
313
+ baseUrl: `${endpoints.cma}/v3`,
314
+ headers: { Authorization: `Bearer ${accessToken}` }
315
+ });
316
+ try {
317
+ const { data } = await client.get("/user");
318
+ return mapUser(data.user);
319
+ } catch (err) {
320
+ if (err instanceof ContentstackError) {
321
+ throw new ContentstackAuthError(err.message || "Failed to fetch user profile", {
322
+ status: err.status,
323
+ requestPath: "/v3/user",
324
+ cause: err
325
+ });
326
+ }
327
+ throw new ContentstackAuthError("Failed to fetch user profile", {
328
+ requestPath: "/v3/user",
329
+ cause: err instanceof Error ? err : void 0
330
+ });
331
+ }
332
+ }
333
+ function createAuthProvider(config) {
334
+ validateConfig(config);
335
+ const endpoints = resolveEndpoints(config.region);
336
+ const userClient = new ContentstackHttpClient({ baseUrl: `${endpoints.cma}/v3` });
337
+ return {
338
+ id: "contentstack",
339
+ name: "Contentstack",
340
+ type: "oauth",
341
+ checks: ["state"],
342
+ authorization: {
343
+ url: `${endpoints.app}/apps/${config.appId}/authorize`,
344
+ params: {
345
+ response_type: "code",
346
+ scope: config.scopes.join(" ")
347
+ }
348
+ },
349
+ token: `${endpoints.app}/apps-api/token`,
350
+ userinfo: {
351
+ url: `${endpoints.cma}/v3/user`,
352
+ async request({ tokens }) {
353
+ const authedClient = userClient.withHeaders({
354
+ Authorization: `Bearer ${tokens.access_token}`
355
+ });
356
+ const { data } = await authedClient.get("/user");
357
+ return data;
358
+ }
359
+ },
360
+ profile(profile) {
361
+ const user = profile.user;
362
+ return {
363
+ id: user.uid,
364
+ name: [user.first_name, user.last_name].filter(Boolean).join(" ") || user.username,
365
+ email: user.email,
366
+ image: user.profile_image ?? null
367
+ };
368
+ },
369
+ clientId: config.clientId,
370
+ clientSecret: config.clientSecret
371
+ };
372
+ }
373
+ function contentstackAuthCallbacks(config) {
374
+ return {
375
+ async jwt({
376
+ token,
377
+ account
378
+ }) {
379
+ if (account) {
380
+ token.accessToken = account.access_token;
381
+ token.refreshToken = account.refresh_token;
382
+ token.accessTokenExpiresAt = Date.now() + (account.expires_in ?? 3600) * 1e3;
383
+ return token;
384
+ }
385
+ const expiresAt = token.accessTokenExpiresAt;
386
+ if (expiresAt && Date.now() < expiresAt - 6e4) {
387
+ return token;
388
+ }
389
+ const currentRefreshToken = token.refreshToken;
390
+ if (!currentRefreshToken) {
391
+ token.error = "RefreshAccessTokenError";
392
+ return token;
393
+ }
394
+ try {
395
+ const tokens = await refreshToken(config, currentRefreshToken);
396
+ token.accessToken = tokens.accessToken;
397
+ token.refreshToken = tokens.refreshToken;
398
+ token.accessTokenExpiresAt = Date.now() + tokens.expiresIn * 1e3;
399
+ token.error = void 0;
400
+ } catch {
401
+ token.error = "RefreshAccessTokenError";
402
+ }
403
+ return token;
404
+ },
405
+ async session({
406
+ session,
407
+ token
408
+ }) {
409
+ session.accessToken = token.accessToken;
410
+ if (token.error) {
411
+ session.error = token.error;
412
+ }
413
+ return session;
414
+ }
415
+ };
416
+ }
417
+
418
+ export {
419
+ OAUTH_SCOPE_DESCRIPTIONS,
420
+ OAUTH_SCOPE_PRESETS,
421
+ generateCodeVerifier,
422
+ generateCodeChallenge,
423
+ buildAuthorizationUrl,
424
+ exchangeCode,
425
+ refreshToken,
426
+ getUser,
427
+ createAuthProvider,
428
+ contentstackAuthCallbacks
429
+ };
430
+ //# sourceMappingURL=chunk-K3QIVKGD.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth/scopes.ts","../src/auth/pkce.ts","../src/auth/oauth-client.ts"],"sourcesContent":["/**\n * All available Contentstack OAuth scopes.\n *\n * Use `OAUTH_SCOPE_DESCRIPTIONS` for human-readable explanations of each scope.\n * Use `OAUTH_SCOPE_PRESETS` for common scope combinations.\n *\n * @see https://www.contentstack.com/docs/developers/developer-hub/oauth-scopes\n */\nexport type ContentstackOAuthScope =\n // ── User ──────────────────────────────────────────────────────────────\n | \"user:read\"\n | \"user:write\"\n | \"user.assignments:read\"\n\n // ── Organization ──────────────────────────────────────────────────────\n | \"organizations:read\"\n | \"organization:read\"\n | \"organization.logs:read\"\n | \"organization.ownership:write\"\n | \"organization.roles:read\"\n | \"organization.share:write\"\n | \"organization.share:read\"\n\n // ── Stack management ──────────────────────────────────────────────────\n | \"cm.stacks.management:read\"\n | \"cm.stacks.management:write\"\n | \"cm.stack.users:read\"\n | \"cm.stack.users:write\"\n | \"cm.stack.management:write\"\n | \"cm.stack.settings:read\"\n | \"cm.stack:share\"\n | \"cm.stack:unshare\"\n\n // ── Tokens ────────────────────────────────────────────────────────────\n | \"cm.stack.delivery-tokens:read\"\n | \"cm.stack.delivery-tokens:write\"\n | \"cm.stack.management-tokens:read\"\n | \"cm.stack.management-tokens:write\"\n\n // ── Content types ─────────────────────────────────────────────────────\n | \"cm.content-types.management:read\"\n | \"cm.content-types.management:write\"\n | \"cm.content-type:read\"\n | \"cm.content-types:export\"\n | \"cm.content-types:import\"\n\n // ── Global fields ─────────────────────────────────────────────────────\n | \"cm.global-fields.management:read\"\n | \"cm.global-fields.management:write\"\n | \"cm.global-fields:import\"\n\n // ── Entries ────────────────────────────────────────────────────────────\n | \"cm.entries.management:read\"\n | \"cm.entries.management:write\"\n | \"cm.entry:write\"\n | \"cm.entry:read\"\n | \"cm.entries:export\"\n | \"cm.entries:import\"\n | \"cm.entry:publish\"\n | \"cm.entry:unpublish\"\n | \"cm.entry.workflow:write\"\n\n // ── Bulk operations ───────────────────────────────────────────────────\n | \"cm.bulk-operations:publish\"\n | \"cm.bulk-operations:unpublish\"\n | \"cm.bulk-operations:delete\"\n | \"cm.bulk-operations:workflow\"\n\n // ── Assets ────────────────────────────────────────────────────────────\n | \"cm.assets.management:read\"\n | \"cm.assets.management:write\"\n | \"cm.assets:download\"\n | \"cm.assets.rt:read\"\n | \"cm.asset:write\"\n | \"cm.asset:read\"\n | \"cm.asset:publish\"\n | \"cm.asset:unpublish\"\n\n // ── Extensions ────────────────────────────────────────────────────────\n | \"cm.extensions.management:read\"\n | \"cm.extensions.management:write\"\n\n // ── Releases ──────────────────────────────────────────────────────────\n | \"cm.releases.management:read\"\n | \"cm.releases.management:write\"\n | \"cm.release:read\"\n | \"cm.release:write\"\n | \"cm.release:deploy\"\n | \"cm.release:clone\"\n\n // ── Workflows ─────────────────────────────────────────────────────────\n | \"cm.workflows.management:read\"\n | \"cm.workflows.management:write\"\n | \"cm.workflows.publishing-rules:write\"\n | \"cm.workflows.publishing-rules:read\"\n\n // ── Labels ────────────────────────────────────────────────────────────\n | \"cm.labels.management:read\"\n | \"cm.labels.management:write\"\n\n // ── Languages ─────────────────────────────────────────────────────────\n | \"cm.languages.management:read\"\n | \"cm.languages.management:write\"\n\n // ── Environments ──────────────────────────────────────────────────────\n | \"cm.environments.management:read\"\n | \"cm.environments.management:write\"\n\n // ── Roles ─────────────────────────────────────────────────────────────\n | \"cm.roles.management:read\"\n | \"cm.roles.management:write\"\n\n // ── Webhooks ──────────────────────────────────────────────────────────\n | \"cm.webhooks.management:read\"\n | \"cm.webhooks.management:write\"\n | \"cm.webhooks:export\"\n | \"cm.webhooks:import\"\n | \"cm.webhook:read\"\n\n // ── Audit & publish queue ─────────────────────────────────────────────\n | \"cm.audit-logs:read\"\n | \"cm.publish-queue.management:read\"\n | \"cm.publish-queue.management:write\"\n\n // ── Branches ──────────────────────────────────────────────────────────\n | \"cm.branches.management:read\"\n | \"cm.branches.management:write\"\n | \"cm.branch-aliases.management:read\"\n | \"cm.branch-aliases.management:write\"\n\n // ── Taxonomies ────────────────────────────────────────────────────────\n | \"cm.taxonomies.management:read\"\n | \"cm.taxonomies.management:write\"\n | \"cm.taxonomy.terms:read\"\n | \"cm.taxonomy.terms:write\"\n\n // ── Brand Kit ─────────────────────────────────────────────────────────\n | \"brand-kits:read\"\n | \"brand-kits:manage\"\n\n // ── Personalization ───────────────────────────────────────────────────\n | \"personalize:read\"\n | \"personalize:manage\"\n\n // ── Launch ────────────────────────────────────────────────────────────\n | \"launch:manage\"\n | \"launch.projects:read\"\n | \"launch.projects:write\"\n | \"launch.projects:delete\"\n\n/**\n * Human-readable descriptions for every Contentstack OAuth scope.\n *\n * Useful for AI agents, documentation generators, and UI scope pickers.\n *\n * @example\n * ```ts\n * import { OAUTH_SCOPE_DESCRIPTIONS } from \"@timbenniks/contentstack-platform-sdk/auth\"\n *\n * // Get description for a scope\n * console.log(OAUTH_SCOPE_DESCRIPTIONS[\"cm.entries.management:read\"])\n * // → \"View all entries\"\n * ```\n */\nexport const OAUTH_SCOPE_DESCRIPTIONS: Record<ContentstackOAuthScope, string> = {\n // User\n \"user:read\": \"View user details\",\n \"user:write\": \"Update user details\",\n \"user.assignments:read\": \"View user assignments\",\n\n // Organization\n \"organizations:read\": \"View details of all organizations associated with the user\",\n \"organization:read\": \"View details of an organization\",\n \"organization.logs:read\": \"View organization logs\",\n \"organization.ownership:write\": \"Transfer organization ownership\",\n \"organization.roles:read\": \"View organization-level roles\",\n \"organization.share:write\": \"Update or remove organization invitation shares\",\n \"organization.share:read\": \"View details of organization invitations shared with users\",\n\n // Stack management\n \"cm.stacks.management:read\": \"View all stacks\",\n \"cm.stacks.management:write\": \"Create, update, or remove stacks\",\n \"cm.stack.users:read\": \"View users associated with a stack\",\n \"cm.stack.users:write\": \"Update user roles and user associations\",\n \"cm.stack.management:write\": \"Update stack management properties\",\n \"cm.stack.settings:read\": \"View stack settings\",\n \"cm.stack:share\": \"Share stack invitation with users\",\n \"cm.stack:unshare\": \"Unshare stack invitations\",\n\n // Tokens\n \"cm.stack.delivery-tokens:read\": \"View delivery tokens associated with a stack\",\n \"cm.stack.delivery-tokens:write\": \"Create, update, or remove delivery tokens\",\n \"cm.stack.management-tokens:read\": \"View management tokens associated with a stack\",\n \"cm.stack.management-tokens:write\": \"Create, update, or remove management tokens\",\n\n // Content types\n \"cm.content-types.management:read\": \"View all content types\",\n \"cm.content-types.management:write\": \"Create, update, or remove content types\",\n \"cm.content-type:read\": \"View content type details\",\n \"cm.content-types:export\": \"Export content types\",\n \"cm.content-types:import\": \"Import content types\",\n\n // Global fields\n \"cm.global-fields.management:read\": \"View all global fields\",\n \"cm.global-fields.management:write\": \"Create, update, or remove global fields\",\n \"cm.global-fields:import\": \"Import global fields\",\n\n // Entries\n \"cm.entries.management:read\": \"View all entries\",\n \"cm.entries.management:write\": \"Create, update, or remove entries\",\n \"cm.entry:write\": \"Update details associated with an entry\",\n \"cm.entry:read\": \"View details associated with an entry\",\n \"cm.entries:export\": \"Export entries\",\n \"cm.entries:import\": \"Import entries\",\n \"cm.entry:publish\": \"Publish an entry\",\n \"cm.entry:unpublish\": \"Unpublish an entry\",\n \"cm.entry.workflow:write\": \"Set workflow stage for an entry\",\n\n // Bulk operations\n \"cm.bulk-operations:publish\": \"Publish items in bulk\",\n \"cm.bulk-operations:unpublish\": \"Unpublish items in bulk\",\n \"cm.bulk-operations:delete\": \"Delete items in bulk\",\n \"cm.bulk-operations:workflow\": \"Set workflow stage in bulk\",\n\n // Assets\n \"cm.assets.management:read\": \"View all assets\",\n \"cm.assets.management:write\": \"Create, update, or remove assets\",\n \"cm.assets:download\": \"Download assets\",\n \"cm.assets.rt:read\": \"View all RTE assets\",\n \"cm.asset:write\": \"Create, update, or remove an asset\",\n \"cm.asset:read\": \"View asset details\",\n \"cm.asset:publish\": \"Publish an asset\",\n \"cm.asset:unpublish\": \"Unpublish an asset\",\n\n // Extensions\n \"cm.extensions.management:read\": \"View all extensions\",\n \"cm.extensions.management:write\": \"Create, update, or remove extensions\",\n\n // Releases\n \"cm.releases.management:read\": \"View all releases\",\n \"cm.releases.management:write\": \"Create, update, or remove releases\",\n \"cm.release:read\": \"View details associated with a release\",\n \"cm.release:write\": \"Update details associated with a release\",\n \"cm.release:deploy\": \"Deploy a release\",\n \"cm.release:clone\": \"Clone a release\",\n\n // Workflows\n \"cm.workflows.management:read\": \"View all workflows\",\n \"cm.workflows.management:write\": \"Create, update, or remove workflows\",\n \"cm.workflows.publishing-rules:write\": \"Create, update, or remove workflow publishing rules\",\n \"cm.workflows.publishing-rules:read\": \"View all workflow publishing rules\",\n\n // Labels\n \"cm.labels.management:read\": \"View all labels\",\n \"cm.labels.management:write\": \"Create, update, or remove labels\",\n\n // Languages\n \"cm.languages.management:read\": \"View all languages\",\n \"cm.languages.management:write\": \"Create, update, or remove languages\",\n\n // Environments\n \"cm.environments.management:read\": \"View all environments\",\n \"cm.environments.management:write\": \"Create, update, or remove environments\",\n\n // Roles\n \"cm.roles.management:read\": \"View all roles\",\n \"cm.roles.management:write\": \"Create, update, or remove roles\",\n\n // Webhooks\n \"cm.webhooks.management:read\": \"View all webhooks\",\n \"cm.webhooks.management:write\": \"Create, update, or remove webhooks\",\n \"cm.webhooks:export\": \"Export webhooks\",\n \"cm.webhooks:import\": \"Import webhooks\",\n \"cm.webhook:read\": \"View webhook details\",\n\n // Audit & publish queue\n \"cm.audit-logs:read\": \"View all audit logs\",\n \"cm.publish-queue.management:read\": \"View all publish queue items\",\n \"cm.publish-queue.management:write\": \"Create, update, or remove publish queue items\",\n\n // Branches\n \"cm.branches.management:read\": \"View all branches\",\n \"cm.branches.management:write\": \"Create or delete a branch\",\n \"cm.branch-aliases.management:read\": \"View all branch aliases\",\n \"cm.branch-aliases.management:write\": \"Create, assign, or delete a branch alias\",\n\n // Taxonomies\n \"cm.taxonomies.management:read\": \"View all taxonomies\",\n \"cm.taxonomies.management:write\": \"Create, update, or delete taxonomies\",\n \"cm.taxonomy.terms:read\": \"View all terms of a taxonomy\",\n \"cm.taxonomy.terms:write\": \"Create, update, move, or delete taxonomy terms\",\n\n // Brand Kit\n \"brand-kits:read\": \"View Brand Kits and Voice Profiles; use generative AI content generation\",\n \"brand-kits:manage\":\n \"Create, update, or delete Brand Kits, Voice Profiles, and Knowledge Vault items\",\n\n // Personalization\n \"personalize:read\":\n \"View attributes, audiences, events, experiences, analytics, and regional data\",\n \"personalize:manage\": \"Create, update, or delete personalization components\",\n\n // Launch\n \"launch:manage\": \"Read, update, and manage all Launch resources\",\n \"launch.projects:read\": \"Read Launch projects and deployments\",\n \"launch.projects:write\": \"Create and update Launch projects and deployments\",\n \"launch.projects:delete\": \"Delete Launch projects\",\n} as const\n\n/**\n * Pre-built scope combinations for common use cases.\n *\n * Each preset includes `user:read` as a baseline since it is required\n * for all OAuth flows that need user profile information.\n *\n * @example\n * ```ts\n * import { OAUTH_SCOPE_PRESETS } from \"@timbenniks/contentstack-platform-sdk/auth\"\n *\n * const config: OAuthConfig = {\n * scopes: OAUTH_SCOPE_PRESETS.cmsAuthoring,\n * // ...\n * }\n * ```\n */\nexport const OAUTH_SCOPE_PRESETS = {\n /** Minimal: only read the authenticated user's profile. */\n readOnly: [\"user:read\"] as const,\n\n /** Read content types, entries, and assets. */\n contentReader: [\n \"user:read\",\n \"cm.content-types.management:read\",\n \"cm.entries.management:read\",\n \"cm.assets.management:read\",\n ] as const,\n\n /** Read and write entries and assets; read content types. */\n cmsAuthoring: [\n \"user:read\",\n \"cm.content-types.management:read\",\n \"cm.entries.management:read\",\n \"cm.entries.management:write\",\n \"cm.entry:publish\",\n \"cm.entry:unpublish\",\n \"cm.assets.management:read\",\n \"cm.assets.management:write\",\n \"cm.asset:publish\",\n \"cm.asset:unpublish\",\n ] as const,\n\n /** CMS authoring plus taxonomy management. */\n cmsAuthoringWithTaxonomies: [\n \"user:read\",\n \"cm.content-types.management:read\",\n \"cm.entries.management:read\",\n \"cm.entries.management:write\",\n \"cm.entry:publish\",\n \"cm.entry:unpublish\",\n \"cm.assets.management:read\",\n \"cm.assets.management:write\",\n \"cm.asset:publish\",\n \"cm.asset:unpublish\",\n \"cm.taxonomies.management:read\",\n \"cm.taxonomies.management:write\",\n \"cm.taxonomy.terms:read\",\n \"cm.taxonomy.terms:write\",\n ] as const,\n\n /** Brand Kit: read and manage voice profiles and knowledge vault. */\n brandKit: [\"user:read\", \"brand-kits:read\", \"brand-kits:manage\"] as const,\n\n /** Launch: manage projects and deployments. */\n launch: [\n \"user:read\",\n \"launch:manage\",\n \"launch.projects:read\",\n \"launch.projects:write\",\n \"launch.projects:delete\",\n ] as const,\n\n /** Personalization: read and manage experiences. */\n personalization: [\"user:read\", \"personalize:read\", \"personalize:manage\"] as const,\n} satisfies Record<string, readonly ContentstackOAuthScope[]>\n","const CHARSET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~\"\n\n/**\n * Generate a cryptographically random code verifier for PKCE.\n * Returns a 43-character URL-safe string (the minimum length per RFC 7636).\n * Uses globalThis.crypto for cross-runtime compatibility (Node 18+, Deno, Bun, edge).\n */\nexport function generateCodeVerifier(): string {\n const bytes = new Uint8Array(43)\n globalThis.crypto.getRandomValues(bytes)\n let result = \"\"\n for (const byte of bytes) {\n result += CHARSET[byte % CHARSET.length]\n }\n return result\n}\n\n/**\n * Compute an S256 code challenge from a code verifier.\n * SHA-256 hash → base64url encoded (no padding).\n * Uses globalThis.crypto.subtle for cross-runtime compatibility.\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder()\n const data = encoder.encode(verifier)\n const digest = await globalThis.crypto.subtle.digest(\"SHA-256\", data)\n const bytes = new Uint8Array(digest)\n let binary = \"\"\n for (const byte of bytes) {\n binary += String.fromCharCode(byte)\n }\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\")\n}\n","import { ContentstackHttpClient } from \"../http/client.js\"\nimport {\n ContentstackAuthError,\n ContentstackConfigError,\n ContentstackError,\n} from \"../http/errors.js\"\nimport type { ContentstackRegion } from \"../regions/index.js\"\nimport { resolveEndpoints } from \"../regions/index.js\"\nimport { generateCodeChallenge, generateCodeVerifier } from \"./pkce.js\"\nimport type { ContentstackUser, OAuthConfig, OAuthTokens } from \"./types.js\"\n\ninterface AuthorizationUrlOptions {\n state?: string\n usePKCE?: boolean\n}\n\ninterface AuthorizationUrlResult {\n url: string\n state: string\n codeVerifier?: string\n}\n\ninterface ExchangeCodeOptions {\n codeVerifier?: string\n}\n\nfunction validateConfig(config: OAuthConfig): void {\n if (!config.appId) {\n throw new ContentstackConfigError(\n \"appId is required for OAuth. This is your Contentstack app's UID, not the client ID.\",\n )\n }\n if (config.appId === config.clientId) {\n throw new ContentstackConfigError(\n \"appId and clientId must be different. appId is your Contentstack app UID; clientId is the OAuth client identifier.\",\n )\n }\n}\n\n/**\n * Build a Contentstack OAuth authorization URL.\n *\n * @example\n * ```ts\n * const { url, state, codeVerifier } = await buildAuthorizationUrl({\n * region: \"us\",\n * appId: \"app-uid\",\n * clientId: \"client-id\",\n * clientSecret: \"secret\",\n * scopes: [\"user:read\"],\n * redirectUri: \"http://localhost:3000/api/auth/callback/contentstack\",\n * }, { usePKCE: true })\n * ```\n */\nexport async function buildAuthorizationUrl(\n config: OAuthConfig,\n options?: AuthorizationUrlOptions,\n): Promise<AuthorizationUrlResult> {\n validateConfig(config)\n\n const endpoints = resolveEndpoints(config.region)\n const authorizationUrl = `${endpoints.app}/apps/${config.appId}/authorize`\n\n const state = options?.state ?? generateCodeVerifier()\n\n const params = new URLSearchParams({\n response_type: \"code\",\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n scope: config.scopes.join(\" \"),\n state,\n })\n\n let codeVerifier: string | undefined\n if (options?.usePKCE) {\n codeVerifier = generateCodeVerifier()\n const codeChallenge = await generateCodeChallenge(codeVerifier)\n params.set(\"code_challenge\", codeChallenge)\n params.set(\"code_challenge_method\", \"S256\")\n }\n\n return {\n url: `${authorizationUrl}?${params.toString()}`,\n state,\n codeVerifier,\n }\n}\n\nfunction mapTokenResponse(data: Record<string, unknown>): OAuthTokens {\n return {\n accessToken: data.access_token as string,\n refreshToken: data.refresh_token as string,\n expiresIn: data.expires_in as number,\n tokenType: data.token_type as string,\n }\n}\n\nasync function makeTokenRequest(\n appBaseUrl: string,\n body: URLSearchParams,\n errorMessage: string,\n): Promise<OAuthTokens> {\n const client = new ContentstackHttpClient({ baseUrl: appBaseUrl })\n try {\n const { data } = await client.postForm<Record<string, unknown>>(\"/apps-api/token\", body)\n return mapTokenResponse(data)\n } catch (err) {\n if (err instanceof ContentstackError) {\n throw new ContentstackAuthError(err.message || errorMessage, {\n status: err.status,\n requestPath: \"/apps-api/token\",\n cause: err,\n })\n }\n throw new ContentstackAuthError(errorMessage, {\n requestPath: \"/apps-api/token\",\n cause: err instanceof Error ? err : undefined,\n })\n }\n}\n\n/**\n * Exchange an authorization code for OAuth tokens.\n */\nexport async function exchangeCode(\n config: OAuthConfig,\n code: string,\n options?: ExchangeCodeOptions,\n): Promise<OAuthTokens> {\n const endpoints = resolveEndpoints(config.region)\n\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n client_secret: config.clientSecret,\n })\n\n if (options?.codeVerifier) {\n body.set(\"code_verifier\", options.codeVerifier)\n }\n\n return makeTokenRequest(endpoints.app, body, \"Failed to exchange authorization code\")\n}\n\n/**\n * Refresh an expired access token using a refresh token.\n */\nexport async function refreshToken(config: OAuthConfig, token: string): Promise<OAuthTokens> {\n const endpoints = resolveEndpoints(config.region)\n\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: token,\n client_id: config.clientId,\n client_secret: config.clientSecret,\n })\n\n return makeTokenRequest(endpoints.app, body, \"Failed to refresh token\")\n}\n\nfunction mapUser(user: Record<string, unknown>): ContentstackUser {\n return {\n uid: user.uid as string,\n email: user.email as string,\n firstName: (user.first_name as string) ?? undefined,\n lastName: (user.last_name as string) ?? undefined,\n username: (user.username as string) ?? undefined,\n profileImage: (user.profile_image as string) ?? undefined,\n }\n}\n\n/**\n * Fetch the authenticated user's profile from Contentstack.\n * Unwraps the nested `user` key and maps snake_case → camelCase.\n */\nexport async function getUser(\n region: ContentstackRegion,\n accessToken: string,\n): Promise<ContentstackUser> {\n const endpoints = resolveEndpoints(region)\n const client = new ContentstackHttpClient({\n baseUrl: `${endpoints.cma}/v3`,\n headers: { Authorization: `Bearer ${accessToken}` },\n })\n\n try {\n const { data } = await client.get<{ user: Record<string, unknown> }>(\"/user\")\n return mapUser(data.user)\n } catch (err) {\n if (err instanceof ContentstackError) {\n throw new ContentstackAuthError(err.message || \"Failed to fetch user profile\", {\n status: err.status,\n requestPath: \"/v3/user\",\n cause: err,\n })\n }\n throw new ContentstackAuthError(\"Failed to fetch user profile\", {\n requestPath: \"/v3/user\",\n cause: err instanceof Error ? err : undefined,\n })\n }\n}\n\n/**\n * Create an Auth.js v5 provider config object for Contentstack.\n *\n * No Auth.js dependency is required in this package — the returned object\n * conforms to the Auth.js OAuthConfig shape and can be passed directly to\n * `next-auth` or `@auth/core`.\n *\n * @example\n * ```ts\n * // app/api/auth/[...nextauth]/route.ts\n * import NextAuth from \"next-auth\"\n * import { createAuthProvider } from \"@timbenniks/contentstack-platform-sdk/auth\"\n *\n * export const { handlers, signIn, signOut, auth } = NextAuth({\n * providers: [createAuthProvider({ region: \"us\", appId: \"...\", ... })],\n * })\n * ```\n */\nexport function createAuthProvider(config: OAuthConfig): Record<string, unknown> {\n validateConfig(config)\n\n const endpoints = resolveEndpoints(config.region)\n const userClient = new ContentstackHttpClient({ baseUrl: `${endpoints.cma}/v3` })\n\n return {\n id: \"contentstack\",\n name: \"Contentstack\",\n type: \"oauth\",\n checks: [\"state\"],\n authorization: {\n url: `${endpoints.app}/apps/${config.appId}/authorize`,\n params: {\n response_type: \"code\",\n scope: config.scopes.join(\" \"),\n },\n },\n token: `${endpoints.app}/apps-api/token`,\n userinfo: {\n url: `${endpoints.cma}/v3/user`,\n async request({ tokens }: { tokens: { access_token: string } }) {\n const authedClient = userClient.withHeaders({\n Authorization: `Bearer ${tokens.access_token}`,\n })\n const { data } = await authedClient.get<Record<string, unknown>>(\"/user\")\n return data\n },\n },\n profile(profile: { user: Record<string, unknown> }) {\n const user = profile.user\n return {\n id: user.uid as string,\n name:\n [user.first_name, user.last_name].filter(Boolean).join(\" \") || (user.username as string),\n email: user.email as string,\n image: (user.profile_image as string) ?? null,\n }\n },\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n }\n}\n\n/**\n * Create Auth.js v5 callbacks for token persistence and automatic refresh.\n *\n * The `jwt` callback persists OAuth tokens on initial sign-in and attempts\n * to refresh expired tokens (with a 60-second safety window).\n *\n * The `session` callback exposes the access token and any refresh errors\n * on the session object.\n *\n * @example\n * ```ts\n * import NextAuth from \"next-auth\"\n * import { createAuthProvider, contentstackAuthCallbacks } from \"@timbenniks/contentstack-platform-sdk/auth\"\n *\n * export const { handlers, auth } = NextAuth({\n * providers: [createAuthProvider({ ... })],\n * callbacks: contentstackAuthCallbacks({ region: \"us\", ... }),\n * })\n * ```\n */\nexport function contentstackAuthCallbacks(config: OAuthConfig) {\n return {\n async jwt({\n token,\n account,\n }: { token: Record<string, unknown>; account?: Record<string, unknown> | null }) {\n // Initial sign-in: persist tokens from the OAuth account\n if (account) {\n token.accessToken = account.access_token\n token.refreshToken = account.refresh_token\n token.accessTokenExpiresAt = Date.now() + ((account.expires_in as number) ?? 3600) * 1000\n return token\n }\n\n // Subsequent calls: check if token needs refresh (60s safety window)\n const expiresAt = token.accessTokenExpiresAt as number | undefined\n if (expiresAt && Date.now() < expiresAt - 60_000) {\n return token\n }\n\n // Token is expired or about to expire — attempt refresh\n const currentRefreshToken = token.refreshToken as string | undefined\n if (!currentRefreshToken) {\n token.error = \"RefreshAccessTokenError\"\n return token\n }\n\n try {\n const tokens = await refreshToken(config, currentRefreshToken)\n token.accessToken = tokens.accessToken\n token.refreshToken = tokens.refreshToken\n token.accessTokenExpiresAt = Date.now() + tokens.expiresIn * 1000\n token.error = undefined\n } catch {\n token.error = \"RefreshAccessTokenError\"\n }\n\n return token\n },\n async session({\n session,\n token,\n }: { session: Record<string, unknown>; token: Record<string, unknown> }) {\n session.accessToken = token.accessToken\n if (token.error) {\n session.error = token.error\n }\n return session\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -109,4 +109,4 @@ export {
109
109
  ContentstackLogoutButton,
110
110
  ContentstackAuthGuard
111
111
  };
112
- //# sourceMappingURL=chunk-NKLOZ5VI.mjs.map
112
+ //# sourceMappingURL=chunk-V33LD4WS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vue/auth/useContentstackUser.ts","../src/vue/auth/ContentstackLoginButton.ts","../src/vue/auth/ContentstackLogoutButton.ts","../src/vue/auth/ContentstackAuthGuard.ts"],"sourcesContent":["import { type Ref, computed, onMounted, ref } from \"vue\"\nimport type { ContentstackUser } from \"../../index.js\"\n\nexport interface UseContentstackUserReturn {\n user: Ref<ContentstackUser | null>\n loading: Ref<boolean>\n isAuthenticated: Ref<boolean>\n}\n\nexport function useContentstackUser(): UseContentstackUserReturn {\n const user = ref<ContentstackUser | null>(null)\n const loading = ref(true)\n const isAuthenticated = computed(() => user.value !== null)\n\n async function fetchSession() {\n loading.value = true\n try {\n const res = await fetch(\"/api/auth/session\")\n if (!res.ok) {\n user.value = null\n return\n }\n const data = await res.json()\n if (data?.user) {\n user.value = {\n uid: data.user.uid ?? data.user.id ?? \"\",\n email: data.user.email ?? \"\",\n firstName: data.user.firstName ?? data.user.name?.split(\" \")[0],\n lastName: data.user.lastName ?? data.user.name?.split(\" \").slice(1).join(\" \"),\n username: data.user.username,\n profileImage: data.user.profileImage ?? data.user.image,\n }\n } else {\n user.value = null\n }\n } catch {\n user.value = null\n } finally {\n loading.value = false\n }\n }\n\n onMounted(() => {\n fetchSession()\n })\n\n return { user, loading, isAuthenticated }\n}\n","import { type PropType, defineComponent, h } from \"vue\"\nimport type { ContentstackOAuthScope } from \"../../auth/scopes.js\"\n\nexport const ContentstackLoginButton = defineComponent({\n name: \"ContentstackLoginButton\",\n props: {\n scopes: {\n type: Array as PropType<ContentstackOAuthScope[]>,\n default: undefined,\n },\n redirectTo: {\n type: String,\n default: undefined,\n },\n class: {\n type: String,\n default: undefined,\n },\n },\n setup(props, { slots }) {\n return () =>\n h(\"form\", { action: \"/api/auth/signin/contentstack\", method: \"POST\" }, [\n props.redirectTo\n ? h(\"input\", { type: \"hidden\", name: \"callbackUrl\", value: props.redirectTo })\n : null,\n h(\n \"button\",\n { type: \"submit\", class: props.class },\n slots.default?.() ?? \"Sign in with Contentstack\",\n ),\n ])\n },\n})\n","import { defineComponent, h } from \"vue\"\n\nexport const ContentstackLogoutButton = defineComponent({\n name: \"ContentstackLogoutButton\",\n props: {\n redirectTo: {\n type: String,\n default: undefined,\n },\n class: {\n type: String,\n default: undefined,\n },\n },\n setup(props, { slots }) {\n return () =>\n h(\"form\", { action: \"/api/auth/signout\", method: \"POST\" }, [\n props.redirectTo\n ? h(\"input\", { type: \"hidden\", name: \"callbackUrl\", value: props.redirectTo })\n : null,\n h(\"button\", { type: \"submit\", class: props.class }, slots.default?.() ?? \"Sign out\"),\n ])\n },\n})\n","import { defineComponent, h } from \"vue\"\nimport { ContentstackLoginButton } from \"./ContentstackLoginButton\"\nimport { useContentstackUser } from \"./useContentstackUser\"\n\nexport const ContentstackAuthGuard = defineComponent({\n name: \"ContentstackAuthGuard\",\n setup(_props, { slots }) {\n const { isAuthenticated, loading } = useContentstackUser()\n\n return () => {\n if (loading.value) return slots.loading?.() ?? null\n if (isAuthenticated.value) return slots.default?.()\n return slots.fallback?.() ?? h(ContentstackLoginButton)\n }\n },\n})\n"],"mappings":"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","names":["defineComponent","h","defineComponent","h","defineComponent","h"]}
@@ -2,7 +2,7 @@ import {
2
2
  contentstackAuthCallbacks,
3
3
  createAuthProvider,
4
4
  getUser
5
- } from "./chunk-VW7DD6HV.mjs";
5
+ } from "./chunk-K3QIVKGD.mjs";
6
6
  import {
7
7
  ContentstackAuthError
8
8
  } from "./chunk-DMERADWM.mjs";
@@ -16,7 +16,7 @@ async function createContentstackAuth(config) {
16
16
  clientId: config.clientId,
17
17
  clientSecret: config.clientSecret,
18
18
  scopes: config.scopes,
19
- redirectUri: "auto"
19
+ redirectUri: config.redirectUri ?? "auto"
20
20
  };
21
21
  const provider = createAuthProvider(oauthConfig);
22
22
  const baseCallbacks = contentstackAuthCallbacks(oauthConfig);
@@ -80,4 +80,4 @@ export {
80
80
  requireSession,
81
81
  getAccessToken
82
82
  };
83
- //# sourceMappingURL=chunk-SU5QEKYW.mjs.map
83
+ //# sourceMappingURL=chunk-VLZEA5NW.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/middleware/auth.ts","../src/server/middleware/session.ts"],"sourcesContent":["import { contentstackAuthCallbacks, createAuthProvider, getUser } from \"../../index.js\"\nimport type { OAuthMiddlewareConfig } from \"./types.js\"\n\n/**\n * Create a complete Auth.js v5 (NextAuth) configuration for Contentstack OAuth.\n *\n * This wraps NextAuth with all Contentstack-specific config baked in, reducing\n * ~150 lines of Auth.js configuration down to ~10 lines.\n *\n * Requires `next-auth@>=5.0.0-beta.0` as an installed peer dependency.\n *\n * @example\n * ```ts\n * import { createContentstackAuth } from \"@timbenniks/contentstack-platform-sdk/server/middleware\"\n *\n * const { handlers, auth, signIn, signOut } = await createContentstackAuth({\n * region: \"us\",\n * appId: \"your-app-uid\",\n * clientId: \"your-client-id\",\n * clientSecret: \"your-client-secret\",\n * scopes: [\"user:read\"],\n * secret: process.env.AUTH_SECRET!,\n * })\n *\n * // app/api/auth/[...nextauth]/route.ts\n * export const { GET, POST } = handlers\n * ```\n */\nexport async function createContentstackAuth(config: OAuthMiddlewareConfig) {\n // Dynamic import — next-auth is an optional peer dependency.\n // This prevents module resolution errors when importing the middleware\n // module in environments where next-auth is not installed.\n const { default: NextAuth } = await import(\"next-auth\")\n\n const oauthConfig = {\n region: config.region,\n appId: config.appId,\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n scopes: config.scopes,\n redirectUri: config.redirectUri ?? \"auto\",\n }\n\n const provider = createAuthProvider(oauthConfig)\n const baseCallbacks = contentstackAuthCallbacks(oauthConfig)\n\n const callbacks = {\n async jwt(params: {\n token: Record<string, unknown>\n account?: Record<string, unknown> | null\n }) {\n const token = await baseCallbacks.jwt(params)\n\n // Call onSignIn on initial sign-in (account is only present once)\n if (params.account && config.onSignIn) {\n try {\n const user = await getUser(config.region, token.accessToken as string)\n const tokens = {\n accessToken: token.accessToken as string,\n refreshToken: token.refreshToken as string,\n expiresIn: Math.floor(((token.accessTokenExpiresAt as number) - Date.now()) / 1000),\n tokenType: \"Bearer\",\n }\n await config.onSignIn(user, tokens)\n } catch {\n // onSignIn errors should not block authentication\n }\n }\n\n return token\n },\n session: baseCallbacks.session,\n }\n\n // The core package returns generic `Record<string, unknown>` for framework\n // independence. Cast to `never` at the NextAuth boundary — the shape is\n // guaranteed correct by createAuthProvider/contentstackAuthCallbacks.\n return NextAuth({\n providers: [provider as never],\n callbacks: callbacks as never,\n secret: config.secret,\n trustHost: config.trustHost ?? true,\n session: { strategy: \"jwt\" as const },\n pages: {\n signIn: config.signInPage ?? \"/login\",\n error: config.errorPage ?? \"/login\",\n },\n })\n}\n","import { ContentstackAuthError } from \"../../index.js\"\nimport type { AuthFn, ContentstackSession } from \"./types.js\"\n\n/**\n * Get the current Contentstack session.\n *\n * @param authFn - Framework-agnostic auth function (e.g., the `auth()` function from NextAuth)\n * @returns The session with `accessToken`, or `null` if unauthenticated.\n */\nexport async function getSession(authFn: AuthFn): Promise<ContentstackSession | null> {\n const session = await authFn()\n if (!session?.accessToken) return null\n return { accessToken: session.accessToken, user: session.user }\n}\n\n/**\n * Require an authenticated Contentstack session.\n * Throws `ContentstackAuthError` if no session or no access token is present.\n *\n * @param authFn - Framework-agnostic auth function\n * @param redirectTo - Optional redirect path included in the error for the caller to use\n * @returns The session with a guaranteed `accessToken`.\n */\nexport async function requireSession(\n authFn: AuthFn,\n redirectTo?: string,\n): Promise<ContentstackSession> {\n const session = await getSession(authFn)\n if (!session) {\n throw new ContentstackAuthError(\n redirectTo\n ? `Authentication required. Redirect to: ${redirectTo}`\n : \"Authentication required\",\n { status: 401 },\n )\n }\n return session\n}\n\n/**\n * Get the OAuth access token from the current session.\n *\n * @param authFn - Framework-agnostic auth function\n * @returns The access token string, or `null` if unauthenticated.\n */\nexport async function getAccessToken(authFn: AuthFn): Promise<string | null> {\n const session = await authFn()\n return session?.accessToken ?? null\n}\n"],"mappings":";;;;;;;;;;AA4BA,eAAsB,uBAAuB,QAA+B;AAI1E,QAAM,EAAE,SAAS,SAAS,IAAI,MAAM,OAAO,WAAW;AAEtD,QAAM,cAAc;AAAA,IAClB,QAAQ,OAAO;AAAA,IACf,OAAO,OAAO;AAAA,IACd,UAAU,OAAO;AAAA,IACjB,cAAc,OAAO;AAAA,IACrB,QAAQ,OAAO;AAAA,IACf,aAAa,OAAO,eAAe;AAAA,EACrC;AAEA,QAAM,WAAW,mBAAmB,WAAW;AAC/C,QAAM,gBAAgB,0BAA0B,WAAW;AAE3D,QAAM,YAAY;AAAA,IAChB,MAAM,IAAI,QAGP;AACD,YAAM,QAAQ,MAAM,cAAc,IAAI,MAAM;AAG5C,UAAI,OAAO,WAAW,OAAO,UAAU;AACrC,YAAI;AACF,gBAAM,OAAO,MAAM,QAAQ,OAAO,QAAQ,MAAM,WAAqB;AACrE,gBAAM,SAAS;AAAA,YACb,aAAa,MAAM;AAAA,YACnB,cAAc,MAAM;AAAA,YACpB,WAAW,KAAK,OAAQ,MAAM,uBAAkC,KAAK,IAAI,KAAK,GAAI;AAAA,YAClF,WAAW;AAAA,UACb;AACA,gBAAM,OAAO,SAAS,MAAM,MAAM;AAAA,QACpC,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAAA,IACA,SAAS,cAAc;AAAA,EACzB;AAKA,SAAO,SAAS;AAAA,IACd,WAAW,CAAC,QAAiB;AAAA,IAC7B;AAAA,IACA,QAAQ,OAAO;AAAA,IACf,WAAW,OAAO,aAAa;AAAA,IAC/B,SAAS,EAAE,UAAU,MAAe;AAAA,IACpC,OAAO;AAAA,MACL,QAAQ,OAAO,cAAc;AAAA,MAC7B,OAAO,OAAO,aAAa;AAAA,IAC7B;AAAA,EACF,CAAC;AACH;;;AC/EA,eAAsB,WAAW,QAAqD;AACpF,QAAM,UAAU,MAAM,OAAO;AAC7B,MAAI,CAAC,SAAS,YAAa,QAAO;AAClC,SAAO,EAAE,aAAa,QAAQ,aAAa,MAAM,QAAQ,KAAK;AAChE;AAUA,eAAsB,eACpB,QACA,YAC8B;AAC9B,QAAM,UAAU,MAAM,WAAW,MAAM;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,aACI,yCAAyC,UAAU,KACnD;AAAA,MACJ,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAQA,eAAsB,eAAe,QAAwC;AAC3E,QAAM,UAAU,MAAM,OAAO;AAC7B,SAAO,SAAS,eAAe;AACjC;","names":[]}
@@ -81,4 +81,4 @@ export {
81
81
  ContentstackLogoutButton,
82
82
  ContentstackAuthGuard
83
83
  };
84
- //# sourceMappingURL=chunk-3C6J2BDB.mjs.map
84
+ //# sourceMappingURL=chunk-WFPYU6CJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/auth/useContentstackUser.ts","../src/react/auth/ContentstackLoginButton.tsx","../src/react/auth/ContentstackLogoutButton.tsx","../src/react/auth/ContentstackAuthGuard.tsx"],"sourcesContent":["\"use client\"\n\nimport { useCallback, useEffect, useState } from \"react\"\nimport type { ContentstackUser } from \"../../index.js\"\n\nexport interface UseContentstackUserReturn {\n user: ContentstackUser | null\n loading: boolean\n isAuthenticated: boolean\n}\n\nexport function useContentstackUser(): UseContentstackUserReturn {\n const [user, setUser] = useState<ContentstackUser | null>(null)\n const [loading, setLoading] = useState(true)\n\n const fetchSession = useCallback(async () => {\n setLoading(true)\n try {\n const res = await fetch(\"/api/auth/session\")\n if (!res.ok) {\n setUser(null)\n return\n }\n const data = await res.json()\n if (data?.user) {\n setUser({\n uid: data.user.uid ?? data.user.id ?? \"\",\n email: data.user.email ?? \"\",\n firstName: data.user.firstName ?? data.user.name?.split(\" \")[0],\n lastName: data.user.lastName ?? data.user.name?.split(\" \").slice(1).join(\" \"),\n username: data.user.username,\n profileImage: data.user.profileImage ?? data.user.image,\n })\n } else {\n setUser(null)\n }\n } catch {\n setUser(null)\n } finally {\n setLoading(false)\n }\n }, [])\n\n useEffect(() => {\n fetchSession()\n }, [fetchSession])\n\n return { user, loading, isAuthenticated: user !== null }\n}\n","import type { ContentstackOAuthScope } from \"../../auth/scopes.js\"\n\nexport interface ContentstackLoginButtonProps {\n scopes?: ContentstackOAuthScope[]\n redirectTo?: string\n className?: string\n children?: React.ReactNode\n}\n\nexport function ContentstackLoginButton({\n redirectTo,\n className,\n children = \"Sign in with Contentstack\",\n}: ContentstackLoginButtonProps): React.JSX.Element {\n return (\n <form action=\"/api/auth/signin/contentstack\" method=\"POST\">\n {redirectTo && <input type=\"hidden\" name=\"callbackUrl\" value={redirectTo} />}\n <button type=\"submit\" className={className}>\n {children}\n </button>\n </form>\n )\n}\n","export interface ContentstackLogoutButtonProps {\n redirectTo?: string\n className?: string\n children?: React.ReactNode\n}\n\nexport function ContentstackLogoutButton({\n redirectTo,\n className,\n children = \"Sign out\",\n}: ContentstackLogoutButtonProps): React.JSX.Element {\n return (\n <form action=\"/api/auth/signout\" method=\"POST\">\n {redirectTo && <input type=\"hidden\" name=\"callbackUrl\" value={redirectTo} />}\n <button type=\"submit\" className={className}>\n {children}\n </button>\n </form>\n )\n}\n","\"use client\"\n\nimport { ContentstackLoginButton } from \"./ContentstackLoginButton\"\nimport { useContentstackUser } from \"./useContentstackUser\"\n\nexport interface ContentstackAuthGuardProps {\n children: React.ReactNode\n fallback?: React.ReactNode\n loadingFallback?: React.ReactNode\n}\n\nexport function ContentstackAuthGuard({\n children,\n fallback,\n loadingFallback = null,\n}: ContentstackAuthGuardProps): React.JSX.Element | null {\n const { isAuthenticated, loading } = useContentstackUser()\n\n if (loading) return <>{loadingFallback}</>\n if (isAuthenticated) return <>{children}</>\n return <>{fallback ?? <ContentstackLoginButton />}</>\n}\n"],"mappings":";AAEA,SAAS,aAAa,WAAW,gBAAgB;AAS1C,SAAS,sBAAiD;AAC/D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAkC,IAAI;AAC9D,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAE3C,QAAM,eAAe,YAAY,YAAY;AAC3C,eAAW,IAAI;AACf,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,mBAAmB;AAC3C,UAAI,CAAC,IAAI,IAAI;AACX,gBAAQ,IAAI;AACZ;AAAA,MACF;AACA,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAI,MAAM,MAAM;AACd,gBAAQ;AAAA,UACN,KAAK,KAAK,KAAK,OAAO,KAAK,KAAK,MAAM;AAAA,UACtC,OAAO,KAAK,KAAK,SAAS;AAAA,UAC1B,WAAW,KAAK,KAAK,aAAa,KAAK,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC;AAAA,UAC9D,UAAU,KAAK,KAAK,YAAY,KAAK,KAAK,MAAM,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG;AAAA,UAC5E,UAAU,KAAK,KAAK;AAAA,UACpB,cAAc,KAAK,KAAK,gBAAgB,KAAK,KAAK;AAAA,QACpD,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ,IAAI;AAAA,MACd;AAAA,IACF,QAAQ;AACN,cAAQ,IAAI;AAAA,IACd,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,iBAAa;AAAA,EACf,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO,EAAE,MAAM,SAAS,iBAAiB,SAAS,KAAK;AACzD;;;ACjCI,SACiB,KADjB;AANG,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAAoD;AAClD,SACE,qBAAC,UAAK,QAAO,iCAAgC,QAAO,QACjD;AAAA,kBAAc,oBAAC,WAAM,MAAK,UAAS,MAAK,eAAc,OAAO,YAAY;AAAA,IAC1E,oBAAC,YAAO,MAAK,UAAS,WACnB,UACH;AAAA,KACF;AAEJ;;;ACVI,SACiB,OAAAA,MADjB,QAAAC,aAAA;AANG,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAAqD;AACnD,SACE,gBAAAA,MAAC,UAAK,QAAO,qBAAoB,QAAO,QACrC;AAAA,kBAAc,gBAAAD,KAAC,WAAM,MAAK,UAAS,MAAK,eAAc,OAAO,YAAY;AAAA,IAC1E,gBAAAA,KAAC,YAAO,MAAK,UAAS,WACnB,UACH;AAAA,KACF;AAEJ;;;ACDsB,0BAAAE,YAAA;AAPf,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA,kBAAkB;AACpB,GAAyD;AACvD,QAAM,EAAE,iBAAiB,QAAQ,IAAI,oBAAoB;AAEzD,MAAI,QAAS,QAAO,gBAAAA,KAAA,YAAG,2BAAgB;AACvC,MAAI,gBAAiB,QAAO,gBAAAA,KAAA,YAAG,UAAS;AACxC,SAAO,gBAAAA,KAAA,YAAG,sBAAY,gBAAAA,KAAC,2BAAwB,GAAG;AACpD;","names":["jsx","jsxs","jsx"]}