@open-mercato/enterprise 0.4.6-develop-15c18897fc → 0.4.6-develop-34aa847ce6

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 (195) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/index.js.map +2 -2
  3. package/dist/modules/sso/acl.js +11 -0
  4. package/dist/modules/sso/acl.js.map +7 -0
  5. package/dist/modules/sso/api/admin-context.js +27 -0
  6. package/dist/modules/sso/api/admin-context.js.map +7 -0
  7. package/dist/modules/sso/api/callback/oidc/route.js +103 -0
  8. package/dist/modules/sso/api/callback/oidc/route.js.map +7 -0
  9. package/dist/modules/sso/api/config/[id]/activate/route.js +49 -0
  10. package/dist/modules/sso/api/config/[id]/activate/route.js.map +7 -0
  11. package/dist/modules/sso/api/config/[id]/domains/route.js +96 -0
  12. package/dist/modules/sso/api/config/[id]/domains/route.js.map +7 -0
  13. package/dist/modules/sso/api/config/[id]/route.js +103 -0
  14. package/dist/modules/sso/api/config/[id]/route.js.map +7 -0
  15. package/dist/modules/sso/api/config/[id]/test/route.js +41 -0
  16. package/dist/modules/sso/api/config/[id]/test/route.js.map +7 -0
  17. package/dist/modules/sso/api/config/route.js +83 -0
  18. package/dist/modules/sso/api/config/route.js.map +7 -0
  19. package/dist/modules/sso/api/error-handler.js +28 -0
  20. package/dist/modules/sso/api/error-handler.js.map +7 -0
  21. package/dist/modules/sso/api/hrd/route.js +52 -0
  22. package/dist/modules/sso/api/hrd/route.js.map +7 -0
  23. package/dist/modules/sso/api/initiate/route.js +66 -0
  24. package/dist/modules/sso/api/initiate/route.js.map +7 -0
  25. package/dist/modules/sso/api/scim/context.js +68 -0
  26. package/dist/modules/sso/api/scim/context.js.map +7 -0
  27. package/dist/modules/sso/api/scim/logs/route.js +65 -0
  28. package/dist/modules/sso/api/scim/logs/route.js.map +7 -0
  29. package/dist/modules/sso/api/scim/tokens/[id]/route.js +42 -0
  30. package/dist/modules/sso/api/scim/tokens/[id]/route.js.map +7 -0
  31. package/dist/modules/sso/api/scim/tokens/route.js +83 -0
  32. package/dist/modules/sso/api/scim/tokens/route.js.map +7 -0
  33. package/dist/modules/sso/api/scim/v2/ServiceProviderConfig/route.js +42 -0
  34. package/dist/modules/sso/api/scim/v2/ServiceProviderConfig/route.js.map +7 -0
  35. package/dist/modules/sso/api/scim/v2/Users/[id]/route.js +94 -0
  36. package/dist/modules/sso/api/scim/v2/Users/[id]/route.js.map +7 -0
  37. package/dist/modules/sso/api/scim/v2/Users/route.js +86 -0
  38. package/dist/modules/sso/api/scim/v2/Users/route.js.map +7 -0
  39. package/dist/modules/sso/backend/page.js +173 -0
  40. package/dist/modules/sso/backend/page.js.map +7 -0
  41. package/dist/modules/sso/backend/page.meta.js +31 -0
  42. package/dist/modules/sso/backend/page.meta.js.map +7 -0
  43. package/dist/modules/sso/backend/sso/config/[id]/page.js +749 -0
  44. package/dist/modules/sso/backend/sso/config/[id]/page.js.map +7 -0
  45. package/dist/modules/sso/backend/sso/config/[id]/page.meta.js +19 -0
  46. package/dist/modules/sso/backend/sso/config/[id]/page.meta.js.map +7 -0
  47. package/dist/modules/sso/backend/sso/config/new/page.js +381 -0
  48. package/dist/modules/sso/backend/sso/config/new/page.js.map +7 -0
  49. package/dist/modules/sso/backend/sso/config/new/page.meta.js +19 -0
  50. package/dist/modules/sso/backend/sso/config/new/page.meta.js.map +7 -0
  51. package/dist/modules/sso/data/entities.js +299 -0
  52. package/dist/modules/sso/data/entities.js.map +7 -0
  53. package/dist/modules/sso/data/validators.js +114 -0
  54. package/dist/modules/sso/data/validators.js.map +7 -0
  55. package/dist/modules/sso/di.js +26 -0
  56. package/dist/modules/sso/di.js.map +7 -0
  57. package/dist/modules/sso/events.js +24 -0
  58. package/dist/modules/sso/events.js.map +7 -0
  59. package/dist/modules/sso/i18n/de.json +146 -0
  60. package/dist/modules/sso/i18n/en.json +146 -0
  61. package/dist/modules/sso/i18n/es.json +146 -0
  62. package/dist/modules/sso/i18n/pl.json +146 -0
  63. package/dist/modules/sso/index.js +11 -0
  64. package/dist/modules/sso/index.js.map +7 -0
  65. package/dist/modules/sso/lib/domains.js +30 -0
  66. package/dist/modules/sso/lib/domains.js.map +7 -0
  67. package/dist/modules/sso/lib/oidc-provider.js +140 -0
  68. package/dist/modules/sso/lib/oidc-provider.js.map +7 -0
  69. package/dist/modules/sso/lib/registry.js +15 -0
  70. package/dist/modules/sso/lib/registry.js.map +7 -0
  71. package/dist/modules/sso/lib/scim-filter.js +43 -0
  72. package/dist/modules/sso/lib/scim-filter.js.map +7 -0
  73. package/dist/modules/sso/lib/scim-mapper.js +49 -0
  74. package/dist/modules/sso/lib/scim-mapper.js.map +7 -0
  75. package/dist/modules/sso/lib/scim-patch.js +63 -0
  76. package/dist/modules/sso/lib/scim-patch.js.map +7 -0
  77. package/dist/modules/sso/lib/scim-response.js +34 -0
  78. package/dist/modules/sso/lib/scim-response.js.map +7 -0
  79. package/dist/modules/sso/lib/scim-utils.js +9 -0
  80. package/dist/modules/sso/lib/scim-utils.js.map +7 -0
  81. package/dist/modules/sso/lib/state-cookie.js +67 -0
  82. package/dist/modules/sso/lib/state-cookie.js.map +7 -0
  83. package/dist/modules/sso/lib/types.js +1 -0
  84. package/dist/modules/sso/lib/types.js.map +7 -0
  85. package/dist/modules/sso/migrations/Migration20260219000000_sso.js +20 -0
  86. package/dist/modules/sso/migrations/Migration20260219000000_sso.js.map +7 -0
  87. package/dist/modules/sso/migrations/Migration20260222000000_sso_add_name.js +13 -0
  88. package/dist/modules/sso/migrations/Migration20260222000000_sso_add_name.js.map +7 -0
  89. package/dist/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.js +15 -0
  90. package/dist/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.js.map +7 -0
  91. package/dist/modules/sso/migrations/Migration20260223000000_scim_tables.js +22 -0
  92. package/dist/modules/sso/migrations/Migration20260223000000_scim_tables.js.map +7 -0
  93. package/dist/modules/sso/migrations/Migration20260224000000_sso_external_id.js +15 -0
  94. package/dist/modules/sso/migrations/Migration20260224000000_sso_external_id.js.map +7 -0
  95. package/dist/modules/sso/migrations/Migration20260224100000_sso_role_grants.js +17 -0
  96. package/dist/modules/sso/migrations/Migration20260224100000_sso_role_grants.js.map +7 -0
  97. package/dist/modules/sso/migrations/Migration20260224200000_drop_default_role_id.js +13 -0
  98. package/dist/modules/sso/migrations/Migration20260224200000_drop_default_role_id.js.map +7 -0
  99. package/dist/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.js +23 -0
  100. package/dist/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.js.map +7 -0
  101. package/dist/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.js +14 -0
  102. package/dist/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.js.map +7 -0
  103. package/dist/modules/sso/services/accountLinkingService.js +298 -0
  104. package/dist/modules/sso/services/accountLinkingService.js.map +7 -0
  105. package/dist/modules/sso/services/hrdService.js +18 -0
  106. package/dist/modules/sso/services/hrdService.js.map +7 -0
  107. package/dist/modules/sso/services/scimService.js +372 -0
  108. package/dist/modules/sso/services/scimService.js.map +7 -0
  109. package/dist/modules/sso/services/scimTokenService.js +94 -0
  110. package/dist/modules/sso/services/scimTokenService.js.map +7 -0
  111. package/dist/modules/sso/services/ssoConfigService.js +254 -0
  112. package/dist/modules/sso/services/ssoConfigService.js.map +7 -0
  113. package/dist/modules/sso/services/ssoService.js +125 -0
  114. package/dist/modules/sso/services/ssoService.js.map +7 -0
  115. package/dist/modules/sso/setup.js +47 -0
  116. package/dist/modules/sso/setup.js.map +7 -0
  117. package/dist/modules/sso/subscribers/user-deleted-cleanup.js +21 -0
  118. package/dist/modules/sso/subscribers/user-deleted-cleanup.js.map +7 -0
  119. package/dist/modules/sso/widgets/injection/login-sso/widget.client.js +106 -0
  120. package/dist/modules/sso/widgets/injection/login-sso/widget.client.js.map +7 -0
  121. package/dist/modules/sso/widgets/injection/login-sso/widget.js +16 -0
  122. package/dist/modules/sso/widgets/injection/login-sso/widget.js.map +7 -0
  123. package/dist/modules/sso/widgets/injection-table.js +14 -0
  124. package/dist/modules/sso/widgets/injection-table.js.map +7 -0
  125. package/package.json +5 -4
  126. package/src/index.ts +1 -1
  127. package/src/modules/sso/acl.ts +7 -0
  128. package/src/modules/sso/api/admin-context.ts +36 -0
  129. package/src/modules/sso/api/callback/oidc/route.ts +115 -0
  130. package/src/modules/sso/api/config/[id]/activate/route.ts +53 -0
  131. package/src/modules/sso/api/config/[id]/domains/route.ts +107 -0
  132. package/src/modules/sso/api/config/[id]/route.ts +114 -0
  133. package/src/modules/sso/api/config/[id]/test/route.ts +44 -0
  134. package/src/modules/sso/api/config/route.ts +88 -0
  135. package/src/modules/sso/api/error-handler.ts +36 -0
  136. package/src/modules/sso/api/hrd/route.ts +55 -0
  137. package/src/modules/sso/api/initiate/route.ts +70 -0
  138. package/src/modules/sso/api/scim/context.ts +85 -0
  139. package/src/modules/sso/api/scim/logs/route.ts +69 -0
  140. package/src/modules/sso/api/scim/tokens/[id]/route.ts +45 -0
  141. package/src/modules/sso/api/scim/tokens/route.ts +89 -0
  142. package/src/modules/sso/api/scim/v2/ServiceProviderConfig/route.ts +40 -0
  143. package/src/modules/sso/api/scim/v2/Users/[id]/route.ts +103 -0
  144. package/src/modules/sso/api/scim/v2/Users/route.ts +94 -0
  145. package/src/modules/sso/backend/page.meta.ts +29 -0
  146. package/src/modules/sso/backend/page.tsx +232 -0
  147. package/src/modules/sso/backend/sso/config/[id]/page.meta.ts +15 -0
  148. package/src/modules/sso/backend/sso/config/[id]/page.tsx +1024 -0
  149. package/src/modules/sso/backend/sso/config/new/page.meta.ts +15 -0
  150. package/src/modules/sso/backend/sso/config/new/page.tsx +463 -0
  151. package/src/modules/sso/data/entities.ts +240 -0
  152. package/src/modules/sso/data/validators.ts +140 -0
  153. package/src/modules/sso/di.ts +25 -0
  154. package/src/modules/sso/docs/entra-id-setup.md +281 -0
  155. package/src/modules/sso/docs/google-workspace-setup.md +174 -0
  156. package/src/modules/sso/docs/sso-overview.md +218 -0
  157. package/src/modules/sso/docs/sso-security-audit-2026-02-27.md +118 -0
  158. package/src/modules/sso/docs/zitadel-setup.md +195 -0
  159. package/src/modules/sso/events.ts +21 -0
  160. package/src/modules/sso/i18n/de.json +146 -0
  161. package/src/modules/sso/i18n/en.json +146 -0
  162. package/src/modules/sso/i18n/es.json +146 -0
  163. package/src/modules/sso/i18n/pl.json +146 -0
  164. package/src/modules/sso/index.ts +7 -0
  165. package/src/modules/sso/lib/domains.ts +31 -0
  166. package/src/modules/sso/lib/oidc-provider.ts +196 -0
  167. package/src/modules/sso/lib/registry.ts +13 -0
  168. package/src/modules/sso/lib/scim-filter.ts +62 -0
  169. package/src/modules/sso/lib/scim-mapper.ts +88 -0
  170. package/src/modules/sso/lib/scim-patch.ts +88 -0
  171. package/src/modules/sso/lib/scim-response.ts +40 -0
  172. package/src/modules/sso/lib/scim-utils.ts +5 -0
  173. package/src/modules/sso/lib/state-cookie.ts +79 -0
  174. package/src/modules/sso/lib/types.ts +50 -0
  175. package/src/modules/sso/migrations/.snapshot-open-mercato.json +912 -0
  176. package/src/modules/sso/migrations/Migration20260219000000_sso.ts +21 -0
  177. package/src/modules/sso/migrations/Migration20260222000000_sso_add_name.ts +13 -0
  178. package/src/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.ts +15 -0
  179. package/src/modules/sso/migrations/Migration20260223000000_scim_tables.ts +24 -0
  180. package/src/modules/sso/migrations/Migration20260224000000_sso_external_id.ts +15 -0
  181. package/src/modules/sso/migrations/Migration20260224100000_sso_role_grants.ts +18 -0
  182. package/src/modules/sso/migrations/Migration20260224200000_drop_default_role_id.ts +13 -0
  183. package/src/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.ts +25 -0
  184. package/src/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.ts +14 -0
  185. package/src/modules/sso/services/accountLinkingService.ts +386 -0
  186. package/src/modules/sso/services/hrdService.ts +22 -0
  187. package/src/modules/sso/services/scimService.ts +461 -0
  188. package/src/modules/sso/services/scimTokenService.ts +136 -0
  189. package/src/modules/sso/services/ssoConfigService.ts +337 -0
  190. package/src/modules/sso/services/ssoService.ts +167 -0
  191. package/src/modules/sso/setup.ts +56 -0
  192. package/src/modules/sso/subscribers/user-deleted-cleanup.ts +33 -0
  193. package/src/modules/sso/widgets/injection/login-sso/widget.client.tsx +130 -0
  194. package/src/modules/sso/widgets/injection/login-sso/widget.ts +16 -0
  195. package/src/modules/sso/widgets/injection-table.ts +12 -0
@@ -0,0 +1,83 @@
1
+ import { NextResponse } from "next/server";
2
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
3
+ import { ssoConfigAdminCreateSchema, ssoConfigListQuerySchema } from "../../data/validators.js";
4
+ import { resolveSsoAdminContext } from "../admin-context.js";
5
+ import { handleSsoAdminApiError } from "../error-handler.js";
6
+ const metadata = {
7
+ GET: { requireAuth: true, requireFeatures: ["sso.config.view"] },
8
+ POST: { requireAuth: true, requireFeatures: ["sso.config.manage"] }
9
+ };
10
+ async function GET(req) {
11
+ try {
12
+ const { scope } = await resolveSsoAdminContext(req);
13
+ const url = new URL(req.url);
14
+ const query = ssoConfigListQuerySchema.parse({
15
+ page: url.searchParams.get("page") ?? void 0,
16
+ pageSize: url.searchParams.get("pageSize") ?? void 0,
17
+ search: url.searchParams.get("search") ?? void 0,
18
+ organizationId: url.searchParams.get("organizationId") ?? void 0,
19
+ tenantId: url.searchParams.get("tenantId") ?? void 0
20
+ });
21
+ const container = await createRequestContainer();
22
+ const service = container.resolve("ssoConfigService");
23
+ const result = await service.list(scope, query);
24
+ return NextResponse.json({ ...result, isSuperAdmin: scope.isSuperAdmin });
25
+ } catch (err) {
26
+ return handleSsoAdminApiError(err, "SSO Config API");
27
+ }
28
+ }
29
+ async function POST(req) {
30
+ try {
31
+ const { scope } = await resolveSsoAdminContext(req);
32
+ const body = await req.json();
33
+ const parsed = ssoConfigAdminCreateSchema.safeParse(body);
34
+ if (!parsed.success) {
35
+ return NextResponse.json({ error: "Invalid request", details: parsed.error.flatten() }, { status: 400 });
36
+ }
37
+ const container = await createRequestContainer();
38
+ const service = container.resolve("ssoConfigService");
39
+ const config = await service.create(scope, parsed.data);
40
+ return NextResponse.json(config, { status: 201 });
41
+ } catch (err) {
42
+ return handleSsoAdminApiError(err, "SSO Config API");
43
+ }
44
+ }
45
+ const openApi = {
46
+ tag: "SSO",
47
+ summary: "SSO Configuration",
48
+ methods: {
49
+ GET: {
50
+ summary: "List SSO configurations",
51
+ description: "Returns paginated SSO configurations. Admins see their org only; superadmins see all.",
52
+ tags: ["SSO"],
53
+ responses: [{ status: 200, description: "Paginated list of SSO configs" }],
54
+ errors: [
55
+ { status: 401, description: "Unauthorized" },
56
+ { status: 403, description: "Forbidden \u2014 requires sso.config.view" }
57
+ ]
58
+ },
59
+ POST: {
60
+ summary: "Create SSO configuration",
61
+ description: "Creates a new SSO configuration for an organization. One config per org.",
62
+ tags: ["SSO"],
63
+ requestBody: {
64
+ contentType: "application/json",
65
+ schema: ssoConfigAdminCreateSchema
66
+ },
67
+ responses: [{ status: 201, description: "SSO config created" }],
68
+ errors: [
69
+ { status: 400, description: "Invalid input" },
70
+ { status: 401, description: "Unauthorized" },
71
+ { status: 403, description: "Forbidden \u2014 requires sso.config.manage" },
72
+ { status: 409, description: "Config already exists for this organization" }
73
+ ]
74
+ }
75
+ }
76
+ };
77
+ export {
78
+ GET,
79
+ POST,
80
+ metadata,
81
+ openApi
82
+ };
83
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/sso/api/config/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { SsoConfigService } from '../../services/ssoConfigService'\nimport { ssoConfigAdminCreateSchema, ssoConfigListQuerySchema } from '../../data/validators'\nimport { resolveSsoAdminContext } from '../admin-context'\nimport { handleSsoAdminApiError } from '../error-handler'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['sso.config.view'] },\n POST: { requireAuth: true, requireFeatures: ['sso.config.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const { scope } = await resolveSsoAdminContext(req)\n\n const url = new URL(req.url)\n const query = ssoConfigListQuerySchema.parse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n organizationId: url.searchParams.get('organizationId') ?? undefined,\n tenantId: url.searchParams.get('tenantId') ?? undefined,\n })\n\n const container = await createRequestContainer()\n const service = container.resolve<SsoConfigService>('ssoConfigService')\n const result = await service.list(scope, query)\n\n return NextResponse.json({ ...result, isSuperAdmin: scope.isSuperAdmin })\n } catch (err) {\n return handleSsoAdminApiError(err, 'SSO Config API')\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { scope } = await resolveSsoAdminContext(req)\n\n const body = await req.json()\n const parsed = ssoConfigAdminCreateSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid request', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const service = container.resolve<SsoConfigService>('ssoConfigService')\n const config = await service.create(scope, parsed.data)\n\n return NextResponse.json(config, { status: 201 })\n } catch (err) {\n return handleSsoAdminApiError(err, 'SSO Config API')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'SSO',\n summary: 'SSO Configuration',\n methods: {\n GET: {\n summary: 'List SSO configurations',\n description: 'Returns paginated SSO configurations. Admins see their org only; superadmins see all.',\n tags: ['SSO'],\n responses: [{ status: 200, description: 'Paginated list of SSO configs' }],\n errors: [\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Forbidden \u2014 requires sso.config.view' },\n ],\n },\n POST: {\n summary: 'Create SSO configuration',\n description: 'Creates a new SSO configuration for an organization. One config per org.',\n tags: ['SSO'],\n requestBody: {\n contentType: 'application/json',\n schema: ssoConfigAdminCreateSchema,\n },\n responses: [{ status: 201, description: 'SSO config created' }],\n errors: [\n { status: 400, description: 'Invalid input' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Forbidden \u2014 requires sso.config.manage' },\n { status: 409, description: 'Config already exists for this organization' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AAEvC,SAAS,4BAA4B,gCAAgC;AACrE,SAAS,8BAA8B;AACvC,SAAS,8BAA8B;AAEhC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAAA,EAC/D,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,uBAAuB,GAAG;AAElD,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,QAAQ,yBAAyB,MAAM;AAAA,MAC3C,MAAM,IAAI,aAAa,IAAI,MAAM,KAAK;AAAA,MACtC,UAAU,IAAI,aAAa,IAAI,UAAU,KAAK;AAAA,MAC9C,QAAQ,IAAI,aAAa,IAAI,QAAQ,KAAK;AAAA,MAC1C,gBAAgB,IAAI,aAAa,IAAI,gBAAgB,KAAK;AAAA,MAC1D,UAAU,IAAI,aAAa,IAAI,UAAU,KAAK;AAAA,IAChD,CAAC;AAED,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,UAAU,UAAU,QAA0B,kBAAkB;AACtE,UAAM,SAAS,MAAM,QAAQ,KAAK,OAAO,KAAK;AAE9C,WAAO,aAAa,KAAK,EAAE,GAAG,QAAQ,cAAc,MAAM,aAAa,CAAC;AAAA,EAC1E,SAAS,KAAK;AACZ,WAAO,uBAAuB,KAAK,gBAAgB;AAAA,EACrD;AACF;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,uBAAuB,GAAG;AAElD,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAM,SAAS,2BAA2B,UAAU,IAAI;AACxD,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzG;AAEA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,UAAU,UAAU,QAA0B,kBAAkB;AACtE,UAAM,SAAS,MAAM,QAAQ,OAAO,OAAO,OAAO,IAAI;AAEtD,WAAO,aAAa,KAAK,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClD,SAAS,KAAK;AACZ,WAAO,uBAAuB,KAAK,gBAAgB;AAAA,EACrD;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,KAAK;AAAA,MACZ,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,gCAAgC,CAAC;AAAA,MACzE,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,eAAe;AAAA,QAC3C,EAAE,QAAQ,KAAK,aAAa,4CAAuC;AAAA,MACrE;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,KAAK;AAAA,MACZ,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,qBAAqB,CAAC;AAAA,MAC9D,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB;AAAA,QAC5C,EAAE,QAAQ,KAAK,aAAa,eAAe;AAAA,QAC3C,EAAE,QAAQ,KAAK,aAAa,8CAAyC;AAAA,QACrE,EAAE,QAAQ,KAAK,aAAa,8CAA8C;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from "next/server";
2
+ import { SsoAdminAuthError } from "./admin-context.js";
3
+ import { SsoConfigError } from "../services/ssoConfigService.js";
4
+ import { ScimTokenError } from "../services/scimTokenService.js";
5
+ import { ScimServiceError } from "../services/scimService.js";
6
+ import { scimJson, buildScimError } from "../lib/scim-response.js";
7
+ function isSsoHttpError(err) {
8
+ return err instanceof SsoAdminAuthError || err instanceof SsoConfigError || err instanceof ScimTokenError || err instanceof ScimServiceError;
9
+ }
10
+ function handleSsoAdminApiError(err, label) {
11
+ if (isSsoHttpError(err)) {
12
+ return NextResponse.json({ error: err.message }, { status: err.statusCode });
13
+ }
14
+ console.error(`[${label}] Error:`, err);
15
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 });
16
+ }
17
+ function handleScimApiError(err, label) {
18
+ if (err instanceof ScimServiceError) {
19
+ return scimJson(buildScimError(err.statusCode, err.message), err.statusCode);
20
+ }
21
+ console.error(`[${label}] Error:`, err);
22
+ return scimJson(buildScimError(500, "Internal server error"), 500);
23
+ }
24
+ export {
25
+ handleScimApiError,
26
+ handleSsoAdminApiError
27
+ };
28
+ //# sourceMappingURL=error-handler.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sso/api/error-handler.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { SsoAdminAuthError } from './admin-context'\nimport { SsoConfigError } from '../services/ssoConfigService'\nimport { ScimTokenError } from '../services/scimTokenService'\nimport { ScimServiceError } from '../services/scimService'\nimport { scimJson, buildScimError } from '../lib/scim-response'\n\ninterface SsoHttpError {\n message: string\n statusCode: number\n}\n\nfunction isSsoHttpError(err: unknown): err is SsoHttpError {\n return (\n err instanceof SsoAdminAuthError ||\n err instanceof SsoConfigError ||\n err instanceof ScimTokenError ||\n err instanceof ScimServiceError\n )\n}\n\nexport function handleSsoAdminApiError(err: unknown, label: string): NextResponse {\n if (isSsoHttpError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.statusCode })\n }\n console.error(`[${label}] Error:`, err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n}\n\nexport function handleScimApiError(err: unknown, label: string): Response {\n if (err instanceof ScimServiceError) {\n return scimJson(buildScimError(err.statusCode, err.message), err.statusCode)\n }\n console.error(`[${label}] Error:`, err)\n return scimJson(buildScimError(500, 'Internal server error'), 500)\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC,SAAS,UAAU,sBAAsB;AAOzC,SAAS,eAAe,KAAmC;AACzD,SACE,eAAe,qBACf,eAAe,kBACf,eAAe,kBACf,eAAe;AAEnB;AAEO,SAAS,uBAAuB,KAAc,OAA6B;AAChF,MAAI,eAAe,GAAG,GAAG;AACvB,WAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,WAAW,CAAC;AAAA,EAC7E;AACA,UAAQ,MAAM,IAAI,KAAK,YAAY,GAAG;AACtC,SAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9E;AAEO,SAAS,mBAAmB,KAAc,OAAyB;AACxE,MAAI,eAAe,kBAAkB;AACnC,WAAO,SAAS,eAAe,IAAI,YAAY,IAAI,OAAO,GAAG,IAAI,UAAU;AAAA,EAC7E;AACA,UAAQ,MAAM,IAAI,KAAK,YAAY,GAAG;AACtC,SAAO,SAAS,eAAe,KAAK,uBAAuB,GAAG,GAAG;AACnE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,52 @@
1
+ import { NextResponse } from "next/server";
2
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
3
+ import { hrdRequestSchema } from "../../data/validators.js";
4
+ async function POST(req) {
5
+ try {
6
+ const body = await req.json();
7
+ const parsed = hrdRequestSchema.safeParse(body);
8
+ if (!parsed.success) {
9
+ return NextResponse.json({ error: "Invalid request" }, { status: 400 });
10
+ }
11
+ const container = await createRequestContainer();
12
+ const hrdService = container.resolve("hrdService");
13
+ const config = await hrdService.findActiveConfigByEmailDomain(parsed.data.email);
14
+ if (!config) {
15
+ return NextResponse.json({ hasSso: false });
16
+ }
17
+ return NextResponse.json({
18
+ hasSso: true,
19
+ configId: config.id,
20
+ protocol: config.protocol
21
+ });
22
+ } catch (err) {
23
+ console.error("[SSO HRD] Error:", err);
24
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 });
25
+ }
26
+ }
27
+ const openApi = {
28
+ tag: "SSO",
29
+ summary: "Home Realm Discovery",
30
+ methods: {
31
+ POST: {
32
+ summary: "Check if email domain has SSO configured",
33
+ description: "Given an email address, determines if the associated organization has an active SSO configuration. Called from the login page before authentication.",
34
+ tags: ["SSO"],
35
+ requestBody: {
36
+ contentType: "application/json",
37
+ schema: hrdRequestSchema
38
+ },
39
+ responses: [
40
+ { status: 200, description: "HRD result" }
41
+ ],
42
+ errors: [
43
+ { status: 400, description: "Invalid request" }
44
+ ]
45
+ }
46
+ }
47
+ };
48
+ export {
49
+ POST,
50
+ openApi
51
+ };
52
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/sso/api/hrd/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { HrdService } from '../../services/hrdService'\nimport { hrdRequestSchema } from '../../data/validators'\n\nexport async function POST(req: Request) {\n try {\n const body = await req.json()\n const parsed = hrdRequestSchema.safeParse(body)\n\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid request' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const hrdService = container.resolve<HrdService>('hrdService')\n const config = await hrdService.findActiveConfigByEmailDomain(parsed.data.email)\n\n if (!config) {\n return NextResponse.json({ hasSso: false })\n }\n\n return NextResponse.json({\n hasSso: true,\n configId: config.id,\n protocol: config.protocol,\n })\n } catch (err) {\n console.error('[SSO HRD] Error:', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'SSO',\n summary: 'Home Realm Discovery',\n methods: {\n POST: {\n summary: 'Check if email domain has SSO configured',\n description: 'Given an email address, determines if the associated organization has an active SSO configuration. Called from the login page before authentication.',\n tags: ['SSO'],\n requestBody: {\n contentType: 'application/json',\n schema: hrdRequestSchema,\n },\n responses: [\n { status: 200, description: 'HRD result' },\n ],\n errors: [\n { status: 400, description: 'Invalid request' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AAEvC,SAAS,wBAAwB;AAEjC,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAM,SAAS,iBAAiB,UAAU,IAAI;AAE9C,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,aAAa,KAAK,EAAE,OAAO,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACxE;AAEA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,aAAa,UAAU,QAAoB,YAAY;AAC7D,UAAM,SAAS,MAAM,WAAW,8BAA8B,OAAO,KAAK,KAAK;AAE/E,QAAI,CAAC,QAAQ;AACX,aAAO,aAAa,KAAK,EAAE,QAAQ,MAAM,CAAC;AAAA,IAC5C;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB,QAAQ;AAAA,MACR,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,MAAM,oBAAoB,GAAG;AACrC,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,KAAK;AAAA,MACZ,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,aAAa;AAAA,MAC3C;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,kBAAkB;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,66 @@
1
+ import { NextResponse } from "next/server";
2
+ import { toAbsoluteUrl } from "@open-mercato/shared/lib/url";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { ssoInitiateSchema } from "../../data/validators.js";
5
+ import { emitSsoEvent } from "../../events.js";
6
+ function sanitizeReturnUrl(raw) {
7
+ const value = raw || "/backend";
8
+ if (!value.startsWith("/") || value.startsWith("//")) return "/backend";
9
+ try {
10
+ const parsed = new URL(value, "http://localhost");
11
+ if (parsed.origin !== "http://localhost") return "/backend";
12
+ return parsed.pathname + parsed.search + parsed.hash;
13
+ } catch {
14
+ return "/backend";
15
+ }
16
+ }
17
+ async function GET(req) {
18
+ try {
19
+ const url = new URL(req.url);
20
+ const configId = url.searchParams.get("configId");
21
+ const rawReturnUrl = url.searchParams.get("returnUrl");
22
+ const parsed = ssoInitiateSchema.safeParse({ configId, returnUrl: rawReturnUrl ?? void 0 });
23
+ if (!parsed.success || !parsed.data.configId) {
24
+ return NextResponse.redirect(toAbsoluteUrl(req, "/login?error=sso_missing_config"));
25
+ }
26
+ const returnUrl = sanitizeReturnUrl(rawReturnUrl);
27
+ const redirectUri = toAbsoluteUrl(req, "/api/sso/callback/oidc");
28
+ const container = await createRequestContainer();
29
+ const ssoService = container.resolve("ssoService");
30
+ const { redirectUrl, stateCookie } = await ssoService.initiateLogin(parsed.data.configId, returnUrl, redirectUri);
31
+ const res = NextResponse.redirect(redirectUrl);
32
+ res.cookies.set("sso_state", stateCookie, {
33
+ httpOnly: true,
34
+ path: "/",
35
+ sameSite: "lax",
36
+ secure: process.env.NODE_ENV !== "development",
37
+ maxAge: 300
38
+ });
39
+ return res;
40
+ } catch (err) {
41
+ console.error("[SSO Initiate] Error:", err);
42
+ void emitSsoEvent("sso.login.failed", {
43
+ reason: err instanceof Error ? err.message : "initiate_failed"
44
+ }).catch((e) => console.error("[SSO Event]", e));
45
+ return NextResponse.redirect(toAbsoluteUrl(req, "/login?error=sso_failed"));
46
+ }
47
+ }
48
+ const openApi = {
49
+ tag: "SSO",
50
+ summary: "Initiate SSO login",
51
+ methods: {
52
+ GET: {
53
+ summary: "Start SSO login flow",
54
+ description: "Redirects the browser to the configured IdP authorization endpoint. Sets an encrypted sso_state cookie for CSRF protection.",
55
+ tags: ["SSO"],
56
+ responses: [
57
+ { status: 302, description: "Redirect to IdP authorization endpoint", mediaType: "text/html" }
58
+ ]
59
+ }
60
+ }
61
+ };
62
+ export {
63
+ GET,
64
+ openApi
65
+ };
66
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/sso/api/initiate/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { toAbsoluteUrl } from '@open-mercato/shared/lib/url'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { SsoService } from '../../services/ssoService'\nimport { ssoInitiateSchema } from '../../data/validators'\nimport { emitSsoEvent } from '../../events'\n\nfunction sanitizeReturnUrl(raw: string | null): string {\n const value = raw || '/backend'\n if (!value.startsWith('/') || value.startsWith('//')) return '/backend'\n try {\n const parsed = new URL(value, 'http://localhost')\n if (parsed.origin !== 'http://localhost') return '/backend'\n return parsed.pathname + parsed.search + parsed.hash\n } catch {\n return '/backend'\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const configId = url.searchParams.get('configId')\n const rawReturnUrl = url.searchParams.get('returnUrl')\n\n const parsed = ssoInitiateSchema.safeParse({ configId, returnUrl: rawReturnUrl ?? undefined })\n if (!parsed.success || !parsed.data.configId) {\n return NextResponse.redirect(toAbsoluteUrl(req, '/login?error=sso_missing_config'))\n }\n\n const returnUrl = sanitizeReturnUrl(rawReturnUrl)\n const redirectUri = toAbsoluteUrl(req, '/api/sso/callback/oidc')\n const container = await createRequestContainer()\n const ssoService = container.resolve<SsoService>('ssoService')\n\n const { redirectUrl, stateCookie } = await ssoService.initiateLogin(parsed.data.configId, returnUrl, redirectUri)\n\n const res = NextResponse.redirect(redirectUrl)\n res.cookies.set('sso_state', stateCookie, {\n httpOnly: true,\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV !== 'development',\n maxAge: 300,\n })\n return res\n } catch (err) {\n console.error('[SSO Initiate] Error:', err)\n void emitSsoEvent('sso.login.failed', {\n reason: err instanceof Error ? err.message : 'initiate_failed',\n }).catch((e) => console.error('[SSO Event]', e))\n return NextResponse.redirect(toAbsoluteUrl(req, '/login?error=sso_failed'))\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'SSO',\n summary: 'Initiate SSO login',\n methods: {\n GET: {\n summary: 'Start SSO login flow',\n description: 'Redirects the browser to the configured IdP authorization endpoint. Sets an encrypted sso_state cookie for CSRF protection.',\n tags: ['SSO'],\n responses: [\n { status: 302, description: 'Redirect to IdP authorization endpoint', mediaType: 'text/html' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,qBAAqB;AAC9B,SAAS,8BAA8B;AAEvC,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAE7B,SAAS,kBAAkB,KAA4B;AACrD,QAAM,QAAQ,OAAO;AACrB,MAAI,CAAC,MAAM,WAAW,GAAG,KAAK,MAAM,WAAW,IAAI,EAAG,QAAO;AAC7D,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,OAAO,kBAAkB;AAChD,QAAI,OAAO,WAAW,mBAAoB,QAAO;AACjD,WAAO,OAAO,WAAW,OAAO,SAAS,OAAO;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,WAAW,IAAI,aAAa,IAAI,UAAU;AAChD,UAAM,eAAe,IAAI,aAAa,IAAI,WAAW;AAErD,UAAM,SAAS,kBAAkB,UAAU,EAAE,UAAU,WAAW,gBAAgB,OAAU,CAAC;AAC7F,QAAI,CAAC,OAAO,WAAW,CAAC,OAAO,KAAK,UAAU;AAC5C,aAAO,aAAa,SAAS,cAAc,KAAK,iCAAiC,CAAC;AAAA,IACpF;AAEA,UAAM,YAAY,kBAAkB,YAAY;AAChD,UAAM,cAAc,cAAc,KAAK,wBAAwB;AAC/D,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,aAAa,UAAU,QAAoB,YAAY;AAE7D,UAAM,EAAE,aAAa,YAAY,IAAI,MAAM,WAAW,cAAc,OAAO,KAAK,UAAU,WAAW,WAAW;AAEhH,UAAM,MAAM,aAAa,SAAS,WAAW;AAC7C,QAAI,QAAQ,IAAI,aAAa,aAAa;AAAA,MACxC,UAAU;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,MACV,QAAQ,QAAQ,IAAI,aAAa;AAAA,MACjC,QAAQ;AAAA,IACV,CAAC;AACD,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,GAAG;AAC1C,SAAK,aAAa,oBAAoB;AAAA,MACpC,QAAQ,eAAe,QAAQ,IAAI,UAAU;AAAA,IAC/C,CAAC,EAAE,MAAM,CAAC,MAAM,QAAQ,MAAM,eAAe,CAAC,CAAC;AAC/C,WAAO,aAAa,SAAS,cAAc,KAAK,yBAAyB,CAAC;AAAA,EAC5E;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,KAAK;AAAA,MACZ,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,0CAA0C,WAAW,YAAY;AAAA,MAC/F;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,68 @@
1
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
2
+ import { SsoConfig } from "../../data/entities.js";
3
+ import { buildScimError, scimJson } from "../../lib/scim-response.js";
4
+ async function resolveScimContext(req) {
5
+ const authHeader = req.headers.get("authorization");
6
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
7
+ return {
8
+ ok: false,
9
+ response: scimJson(
10
+ buildScimError(401, "Bearer token required"),
11
+ 401
12
+ )
13
+ };
14
+ }
15
+ const rawToken = authHeader.slice(7);
16
+ if (!rawToken) {
17
+ return {
18
+ ok: false,
19
+ response: scimJson(buildScimError(401, "Bearer token required"), 401)
20
+ };
21
+ }
22
+ const container = await createRequestContainer();
23
+ const scimTokenService = container.resolve("scimTokenService");
24
+ const verified = await scimTokenService.verifyToken(rawToken);
25
+ if (!verified) {
26
+ return {
27
+ ok: false,
28
+ response: scimJson(buildScimError(401, "Invalid or revoked token"), 401)
29
+ };
30
+ }
31
+ const em = container.resolve("em");
32
+ const config = await em.findOne(SsoConfig, {
33
+ id: verified.ssoConfigId,
34
+ organizationId: verified.organizationId,
35
+ deletedAt: null
36
+ });
37
+ if (!config) {
38
+ return {
39
+ ok: false,
40
+ response: scimJson(buildScimError(403, "SSO configuration not found"), 403)
41
+ };
42
+ }
43
+ if (!config.isActive) {
44
+ return {
45
+ ok: false,
46
+ response: scimJson(buildScimError(403, "SSO configuration is not active"), 403)
47
+ };
48
+ }
49
+ if (config.jitEnabled) {
50
+ return {
51
+ ok: false,
52
+ response: scimJson(buildScimError(403, "SCIM provisioning is unavailable \u2014 JIT provisioning is enabled on this configuration"), 403)
53
+ };
54
+ }
55
+ return {
56
+ ok: true,
57
+ scope: {
58
+ ssoConfigId: config.id,
59
+ organizationId: config.organizationId,
60
+ tenantId: verified.tenantId,
61
+ config
62
+ }
63
+ };
64
+ }
65
+ export {
66
+ resolveScimContext
67
+ };
68
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/sso/api/scim/context.ts"],
4
+ "sourcesContent": ["import { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { ScimTokenService } from '../../services/scimTokenService'\nimport { SsoConfig } from '../../data/entities'\nimport { buildScimError, scimJson } from '../../lib/scim-response'\nimport type { EntityManager } from '@mikro-orm/postgresql'\n\nexport interface ScimScope {\n ssoConfigId: string\n organizationId: string\n tenantId: string | null\n config: SsoConfig\n}\n\nexport async function resolveScimContext(req: Request): Promise<\n | { ok: true; scope: ScimScope }\n | { ok: false; response: Response }\n> {\n const authHeader = req.headers.get('authorization')\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n return {\n ok: false,\n response: scimJson(\n buildScimError(401, 'Bearer token required'),\n 401,\n ),\n }\n }\n\n const rawToken = authHeader.slice(7)\n if (!rawToken) {\n return {\n ok: false,\n response: scimJson(buildScimError(401, 'Bearer token required'), 401),\n }\n }\n\n const container = await createRequestContainer()\n const scimTokenService = container.resolve<ScimTokenService>('scimTokenService')\n const verified = await scimTokenService.verifyToken(rawToken)\n\n if (!verified) {\n return {\n ok: false,\n response: scimJson(buildScimError(401, 'Invalid or revoked token'), 401),\n }\n }\n\n const em = container.resolve<EntityManager>('em')\n const config = await em.findOne(SsoConfig, {\n id: verified.ssoConfigId,\n organizationId: verified.organizationId,\n deletedAt: null,\n })\n\n if (!config) {\n return {\n ok: false,\n response: scimJson(buildScimError(403, 'SSO configuration not found'), 403),\n }\n }\n\n if (!config.isActive) {\n return {\n ok: false,\n response: scimJson(buildScimError(403, 'SSO configuration is not active'), 403),\n }\n }\n\n if (config.jitEnabled) {\n return {\n ok: false,\n response: scimJson(buildScimError(403, 'SCIM provisioning is unavailable \u2014 JIT provisioning is enabled on this configuration'), 403),\n }\n }\n\n return {\n ok: true,\n scope: {\n ssoConfigId: config.id,\n organizationId: config.organizationId,\n tenantId: verified.tenantId,\n config,\n },\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,8BAA8B;AAEvC,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB,gBAAgB;AAUzC,eAAsB,mBAAmB,KAGvC;AACA,QAAM,aAAa,IAAI,QAAQ,IAAI,eAAe;AAClD,MAAI,CAAC,cAAc,CAAC,WAAW,WAAW,SAAS,GAAG;AACpD,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,UAAU;AAAA,QACR,eAAe,KAAK,uBAAuB;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,WAAW,MAAM,CAAC;AACnC,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,UAAU,SAAS,eAAe,KAAK,uBAAuB,GAAG,GAAG;AAAA,IACtE;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,mBAAmB,UAAU,QAA0B,kBAAkB;AAC/E,QAAM,WAAW,MAAM,iBAAiB,YAAY,QAAQ;AAE5D,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,UAAU,SAAS,eAAe,KAAK,0BAA0B,GAAG,GAAG;AAAA,IACzE;AAAA,EACF;AAEA,QAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,QAAM,SAAS,MAAM,GAAG,QAAQ,WAAW;AAAA,IACzC,IAAI,SAAS;AAAA,IACb,gBAAgB,SAAS;AAAA,IACzB,WAAW;AAAA,EACb,CAAC;AAED,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,UAAU,SAAS,eAAe,KAAK,6BAA6B,GAAG,GAAG;AAAA,IAC5E;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,UAAU;AACpB,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,UAAU,SAAS,eAAe,KAAK,iCAAiC,GAAG,GAAG;AAAA,IAChF;AAAA,EACF;AAEA,MAAI,OAAO,YAAY;AACrB,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,UAAU,SAAS,eAAe,KAAK,2FAAsF,GAAG,GAAG;AAAA,IACrI;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO;AAAA,MACL,aAAa,OAAO;AAAA,MACpB,gBAAgB,OAAO;AAAA,MACvB,UAAU,SAAS;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,65 @@
1
+ import { NextResponse } from "next/server";
2
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
3
+ import { ScimProvisioningLog } from "../../../data/entities.js";
4
+ import { resolveSsoAdminContext } from "../../admin-context.js";
5
+ import { handleSsoAdminApiError } from "../../error-handler.js";
6
+ const metadata = {
7
+ GET: { requireAuth: true, requireFeatures: ["sso.config.view"] }
8
+ };
9
+ async function GET(req) {
10
+ try {
11
+ const { scope } = await resolveSsoAdminContext(req);
12
+ const url = new URL(req.url);
13
+ const ssoConfigId = url.searchParams.get("ssoConfigId");
14
+ if (!ssoConfigId) {
15
+ return NextResponse.json({ error: "ssoConfigId is required" }, { status: 400 });
16
+ }
17
+ const where = { ssoConfigId };
18
+ if (!scope.isSuperAdmin && scope.organizationId) {
19
+ where.organizationId = scope.organizationId;
20
+ }
21
+ const container = await createRequestContainer();
22
+ const em = container.resolve("em");
23
+ const logs = await em.find(ScimProvisioningLog, where, {
24
+ orderBy: { createdAt: "desc" },
25
+ limit: 50
26
+ });
27
+ return NextResponse.json({
28
+ items: logs.map((log) => ({
29
+ id: log.id,
30
+ operation: log.operation,
31
+ resourceType: log.resourceType,
32
+ resourceId: log.resourceId,
33
+ scimExternalId: log.scimExternalId,
34
+ responseStatus: log.responseStatus,
35
+ errorMessage: log.errorMessage,
36
+ createdAt: log.createdAt.toISOString()
37
+ }))
38
+ });
39
+ } catch (err) {
40
+ return handleSsoAdminApiError(err, "SCIM Logs API");
41
+ }
42
+ }
43
+ const openApi = {
44
+ tag: "SCIM",
45
+ summary: "SCIM Provisioning Logs",
46
+ methods: {
47
+ GET: {
48
+ summary: "List recent provisioning log entries",
49
+ description: "Returns the last 50 SCIM provisioning log entries for a given SSO config.",
50
+ tags: ["SSO", "SCIM"],
51
+ responses: [{ status: 200, description: "List of provisioning log entries" }],
52
+ errors: [
53
+ { status: 400, description: "Missing ssoConfigId" },
54
+ { status: 401, description: "Unauthorized" },
55
+ { status: 403, description: "Forbidden \u2014 requires sso.scim.manage" }
56
+ ]
57
+ }
58
+ }
59
+ };
60
+ export {
61
+ GET,
62
+ metadata,
63
+ openApi
64
+ };
65
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/sso/api/scim/logs/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { ScimProvisioningLog } from '../../../data/entities'\nimport { resolveSsoAdminContext } from '../../admin-context'\nimport { handleSsoAdminApiError } from '../../error-handler'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['sso.config.view'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const { scope } = await resolveSsoAdminContext(req)\n\n const url = new URL(req.url)\n const ssoConfigId = url.searchParams.get('ssoConfigId')\n if (!ssoConfigId) {\n return NextResponse.json({ error: 'ssoConfigId is required' }, { status: 400 })\n }\n\n const where: Record<string, unknown> = { ssoConfigId }\n if (!scope.isSuperAdmin && scope.organizationId) {\n where.organizationId = scope.organizationId\n }\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n const logs = await em.find(ScimProvisioningLog, where, {\n orderBy: { createdAt: 'desc' },\n limit: 50,\n })\n\n return NextResponse.json({\n items: logs.map((log) => ({\n id: log.id,\n operation: log.operation,\n resourceType: log.resourceType,\n resourceId: log.resourceId,\n scimExternalId: log.scimExternalId,\n responseStatus: log.responseStatus,\n errorMessage: log.errorMessage,\n createdAt: log.createdAt.toISOString(),\n })),\n })\n } catch (err) {\n return handleSsoAdminApiError(err, 'SCIM Logs API')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'SCIM',\n summary: 'SCIM Provisioning Logs',\n methods: {\n GET: {\n summary: 'List recent provisioning log entries',\n description: 'Returns the last 50 SCIM provisioning log entries for a given SSO config.',\n tags: ['SSO', 'SCIM'],\n responses: [{ status: 200, description: 'List of provisioning log entries' }],\n errors: [\n { status: 400, description: 'Missing ssoConfigId' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Forbidden \u2014 requires sso.scim.manage' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AAEvC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,8BAA8B;AAEhC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,uBAAuB,GAAG;AAElD,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,cAAc,IAAI,aAAa,IAAI,aAAa;AACtD,QAAI,CAAC,aAAa;AAChB,aAAO,aAAa,KAAK,EAAE,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAChF;AAEA,UAAM,QAAiC,EAAE,YAAY;AACrD,QAAI,CAAC,MAAM,gBAAgB,MAAM,gBAAgB;AAC/C,YAAM,iBAAiB,MAAM;AAAA,IAC/B;AAEA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAK,UAAU,QAAuB,IAAI;AAEhD,UAAM,OAAO,MAAM,GAAG,KAAK,qBAAqB,OAAO;AAAA,MACrD,SAAS,EAAE,WAAW,OAAO;AAAA,MAC7B,OAAO;AAAA,IACT,CAAC;AAED,WAAO,aAAa,KAAK;AAAA,MACvB,OAAO,KAAK,IAAI,CAAC,SAAS;AAAA,QACxB,IAAI,IAAI;AAAA,QACR,WAAW,IAAI;AAAA,QACf,cAAc,IAAI;AAAA,QAClB,YAAY,IAAI;AAAA,QAChB,gBAAgB,IAAI;AAAA,QACpB,gBAAgB,IAAI;AAAA,QACpB,cAAc,IAAI;AAAA,QAClB,WAAW,IAAI,UAAU,YAAY;AAAA,MACvC,EAAE;AAAA,IACJ,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO,uBAAuB,KAAK,eAAe;AAAA,EACpD;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,OAAO,MAAM;AAAA,MACpB,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,mCAAmC,CAAC;AAAA,MAC5E,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,sBAAsB;AAAA,QAClD,EAAE,QAAQ,KAAK,aAAa,eAAe;AAAA,QAC3C,EAAE,QAAQ,KAAK,aAAa,4CAAuC;AAAA,MACrE;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,42 @@
1
+ import { NextResponse } from "next/server";
2
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
3
+ import { resolveSsoAdminContext } from "../../../admin-context.js";
4
+ import { handleSsoAdminApiError } from "../../../error-handler.js";
5
+ const metadata = {
6
+ DELETE: { requireAuth: true, requireFeatures: ["sso.scim.manage"] }
7
+ };
8
+ async function DELETE(req, ctx) {
9
+ try {
10
+ const { id } = await ctx.params;
11
+ const { scope } = await resolveSsoAdminContext(req);
12
+ const container = await createRequestContainer();
13
+ const service = container.resolve("scimTokenService");
14
+ await service.revokeToken(id, scope);
15
+ return NextResponse.json({ ok: true });
16
+ } catch (err) {
17
+ return handleSsoAdminApiError(err, "SCIM Tokens API");
18
+ }
19
+ }
20
+ const openApi = {
21
+ tag: "SSO",
22
+ summary: "SCIM Token by ID",
23
+ methods: {
24
+ DELETE: {
25
+ summary: "Revoke SCIM token",
26
+ description: "Revokes (deactivates) a SCIM token. The token can no longer be used for authentication.",
27
+ tags: ["SSO", "SCIM"],
28
+ responses: [{ status: 200, description: "Token revoked" }],
29
+ errors: [
30
+ { status: 401, description: "Unauthorized" },
31
+ { status: 403, description: "Forbidden \u2014 requires sso.scim.manage" },
32
+ { status: 404, description: "Token not found" }
33
+ ]
34
+ }
35
+ }
36
+ };
37
+ export {
38
+ DELETE,
39
+ metadata,
40
+ openApi
41
+ };
42
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/sso/api/scim/tokens/%5Bid%5D/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { ScimTokenService } from '../../../../services/scimTokenService'\nimport { resolveSsoAdminContext } from '../../../admin-context'\nimport { handleSsoAdminApiError } from '../../../error-handler'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['sso.scim.manage'] },\n}\n\ntype RouteContext = { params: Promise<{ id: string }> }\n\nexport async function DELETE(req: Request, ctx: RouteContext) {\n try {\n const { id } = await ctx.params\n const { scope } = await resolveSsoAdminContext(req)\n\n const container = await createRequestContainer()\n const service = container.resolve<ScimTokenService>('scimTokenService')\n await service.revokeToken(id, scope)\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n return handleSsoAdminApiError(err, 'SCIM Tokens API')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'SSO',\n summary: 'SCIM Token by ID',\n methods: {\n DELETE: {\n summary: 'Revoke SCIM token',\n description: 'Revokes (deactivates) a SCIM token. The token can no longer be used for authentication.',\n tags: ['SSO', 'SCIM'],\n responses: [{ status: 200, description: 'Token revoked' }],\n errors: [\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Forbidden \u2014 requires sso.scim.manage' },\n { status: 404, description: 'Token not found' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AAEvC,SAAS,8BAA8B;AACvC,SAAS,8BAA8B;AAEhC,MAAM,WAAW;AAAA,EACtB,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACpE;AAIA,eAAsB,OAAO,KAAc,KAAmB;AAC5D,MAAI;AACF,UAAM,EAAE,GAAG,IAAI,MAAM,IAAI;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,uBAAuB,GAAG;AAElD,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,UAAU,UAAU,QAA0B,kBAAkB;AACtE,UAAM,QAAQ,YAAY,IAAI,KAAK;AAEnC,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC,SAAS,KAAK;AACZ,WAAO,uBAAuB,KAAK,iBAAiB;AAAA,EACtD;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,OAAO,MAAM;AAAA,MACpB,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,gBAAgB,CAAC;AAAA,MACzD,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,eAAe;AAAA,QAC3C,EAAE,QAAQ,KAAK,aAAa,4CAAuC;AAAA,QACnE,EAAE,QAAQ,KAAK,aAAa,kBAAkB;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,83 @@
1
+ import { NextResponse } from "next/server";
2
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
3
+ import { createScimTokenSchema, scimTokenListSchema } from "../../../data/validators.js";
4
+ import { resolveSsoAdminContext } from "../../admin-context.js";
5
+ import { handleSsoAdminApiError } from "../../error-handler.js";
6
+ const metadata = {
7
+ GET: { requireAuth: true, requireFeatures: ["sso.config.view"] },
8
+ POST: { requireAuth: true, requireFeatures: ["sso.scim.manage"] }
9
+ };
10
+ async function GET(req) {
11
+ try {
12
+ const { scope } = await resolveSsoAdminContext(req);
13
+ const url = new URL(req.url);
14
+ const parsed = scimTokenListSchema.safeParse({
15
+ ssoConfigId: url.searchParams.get("ssoConfigId") ?? void 0
16
+ });
17
+ if (!parsed.success) {
18
+ return NextResponse.json({ error: "Invalid request", details: parsed.error.flatten() }, { status: 400 });
19
+ }
20
+ const container = await createRequestContainer();
21
+ const service = container.resolve("scimTokenService");
22
+ const tokens = await service.listTokens(parsed.data.ssoConfigId, scope);
23
+ return NextResponse.json({ items: tokens });
24
+ } catch (err) {
25
+ return handleSsoAdminApiError(err, "SCIM Tokens API");
26
+ }
27
+ }
28
+ async function POST(req) {
29
+ try {
30
+ const { scope } = await resolveSsoAdminContext(req);
31
+ const body = await req.json();
32
+ const parsed = createScimTokenSchema.safeParse(body);
33
+ if (!parsed.success) {
34
+ return NextResponse.json({ error: "Invalid request", details: parsed.error.flatten() }, { status: 400 });
35
+ }
36
+ const container = await createRequestContainer();
37
+ const service = container.resolve("scimTokenService");
38
+ const result = await service.generateToken(parsed.data.ssoConfigId, parsed.data.name, scope);
39
+ return NextResponse.json(result, { status: 201 });
40
+ } catch (err) {
41
+ return handleSsoAdminApiError(err, "SCIM Tokens API");
42
+ }
43
+ }
44
+ const openApi = {
45
+ tag: "SSO",
46
+ summary: "SCIM Token Management",
47
+ methods: {
48
+ GET: {
49
+ summary: "List SCIM tokens",
50
+ description: "Returns SCIM tokens for a given SSO config. Token hashes are never exposed.",
51
+ tags: ["SSO", "SCIM"],
52
+ responses: [{ status: 200, description: "List of SCIM tokens" }],
53
+ errors: [
54
+ { status: 400, description: "Missing or invalid ssoConfigId" },
55
+ { status: 401, description: "Unauthorized" },
56
+ { status: 403, description: "Forbidden \u2014 requires sso.scim.manage" }
57
+ ]
58
+ },
59
+ POST: {
60
+ summary: "Create SCIM token",
61
+ description: "Generates a new SCIM bearer token. The raw token is returned once and cannot be retrieved again.",
62
+ tags: ["SSO", "SCIM"],
63
+ requestBody: {
64
+ contentType: "application/json",
65
+ schema: createScimTokenSchema
66
+ },
67
+ responses: [{ status: 201, description: "SCIM token created \u2014 raw token included in response" }],
68
+ errors: [
69
+ { status: 400, description: "Invalid input" },
70
+ { status: 401, description: "Unauthorized" },
71
+ { status: 403, description: "Forbidden \u2014 requires sso.scim.manage" },
72
+ { status: 409, description: "Conflict \u2014 cannot create SCIM token while JIT is enabled" }
73
+ ]
74
+ }
75
+ }
76
+ };
77
+ export {
78
+ GET,
79
+ POST,
80
+ metadata,
81
+ openApi
82
+ };
83
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/sso/api/scim/tokens/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { ScimTokenService } from '../../../services/scimTokenService'\nimport { createScimTokenSchema, scimTokenListSchema } from '../../../data/validators'\nimport { resolveSsoAdminContext } from '../../admin-context'\nimport { handleSsoAdminApiError } from '../../error-handler'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['sso.config.view'] },\n POST: { requireAuth: true, requireFeatures: ['sso.scim.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const { scope } = await resolveSsoAdminContext(req)\n\n const url = new URL(req.url)\n const parsed = scimTokenListSchema.safeParse({\n ssoConfigId: url.searchParams.get('ssoConfigId') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid request', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const service = container.resolve<ScimTokenService>('scimTokenService')\n const tokens = await service.listTokens(parsed.data.ssoConfigId, scope)\n\n return NextResponse.json({ items: tokens })\n } catch (err) {\n return handleSsoAdminApiError(err, 'SCIM Tokens API')\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { scope } = await resolveSsoAdminContext(req)\n\n const body = await req.json()\n const parsed = createScimTokenSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid request', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const service = container.resolve<ScimTokenService>('scimTokenService')\n const result = await service.generateToken(parsed.data.ssoConfigId, parsed.data.name, scope)\n\n return NextResponse.json(result, { status: 201 })\n } catch (err) {\n return handleSsoAdminApiError(err, 'SCIM Tokens API')\n }\n}\n\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'SSO',\n summary: 'SCIM Token Management',\n methods: {\n GET: {\n summary: 'List SCIM tokens',\n description: 'Returns SCIM tokens for a given SSO config. Token hashes are never exposed.',\n tags: ['SSO', 'SCIM'],\n responses: [{ status: 200, description: 'List of SCIM tokens' }],\n errors: [\n { status: 400, description: 'Missing or invalid ssoConfigId' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Forbidden \u2014 requires sso.scim.manage' },\n ],\n },\n POST: {\n summary: 'Create SCIM token',\n description: 'Generates a new SCIM bearer token. The raw token is returned once and cannot be retrieved again.',\n tags: ['SSO', 'SCIM'],\n requestBody: {\n contentType: 'application/json',\n schema: createScimTokenSchema,\n },\n responses: [{ status: 201, description: 'SCIM token created \u2014 raw token included in response' }],\n errors: [\n { status: 400, description: 'Invalid input' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Forbidden \u2014 requires sso.scim.manage' },\n { status: 409, description: 'Conflict \u2014 cannot create SCIM token while JIT is enabled' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AAEvC,SAAS,uBAAuB,2BAA2B;AAC3D,SAAS,8BAA8B;AACvC,SAAS,8BAA8B;AAEhC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAAA,EAC/D,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAClE;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,uBAAuB,GAAG;AAElD,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,SAAS,oBAAoB,UAAU;AAAA,MAC3C,aAAa,IAAI,aAAa,IAAI,aAAa,KAAK;AAAA,IACtD,CAAC;AACD,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzG;AAEA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,UAAU,UAAU,QAA0B,kBAAkB;AACtE,UAAM,SAAS,MAAM,QAAQ,WAAW,OAAO,KAAK,aAAa,KAAK;AAEtE,WAAO,aAAa,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,EAC5C,SAAS,KAAK;AACZ,WAAO,uBAAuB,KAAK,iBAAiB;AAAA,EACtD;AACF;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,uBAAuB,GAAG;AAElD,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAM,SAAS,sBAAsB,UAAU,IAAI;AACnD,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzG;AAEA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,UAAU,UAAU,QAA0B,kBAAkB;AACtE,UAAM,SAAS,MAAM,QAAQ,cAAc,OAAO,KAAK,aAAa,OAAO,KAAK,MAAM,KAAK;AAE3F,WAAO,aAAa,KAAK,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClD,SAAS,KAAK;AACZ,WAAO,uBAAuB,KAAK,iBAAiB;AAAA,EACtD;AACF;AAGO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,OAAO,MAAM;AAAA,MACpB,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,sBAAsB,CAAC;AAAA,MAC/D,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,iCAAiC;AAAA,QAC7D,EAAE,QAAQ,KAAK,aAAa,eAAe;AAAA,QAC3C,EAAE,QAAQ,KAAK,aAAa,4CAAuC;AAAA,MACrE;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,OAAO,MAAM;AAAA,MACpB,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,2DAAsD,CAAC;AAAA,MAC/F,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB;AAAA,QAC5C,EAAE,QAAQ,KAAK,aAAa,eAAe;AAAA,QAC3C,EAAE,QAAQ,KAAK,aAAa,4CAAuC;AAAA,QACnE,EAAE,QAAQ,KAAK,aAAa,gEAA2D;AAAA,MACzF;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }