@open-mercato/onboarding 0.6.6-develop.5412.1.e2a52b14f0 → 0.6.6-develop.5431.1.384a97c7a2

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 (37) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/onboarding/__integration__/TC-ONB-001-self-service-consent.spec.js +141 -0
  3. package/dist/modules/onboarding/__integration__/TC-ONB-001-self-service-consent.spec.js.map +7 -0
  4. package/dist/modules/onboarding/api/get/onboarding/status.js +34 -17
  5. package/dist/modules/onboarding/api/get/onboarding/status.js.map +2 -2
  6. package/dist/modules/onboarding/api/get/onboarding/verify.js +77 -195
  7. package/dist/modules/onboarding/api/get/onboarding/verify.js.map +2 -2
  8. package/dist/modules/onboarding/api/post/onboarding.js +10 -1
  9. package/dist/modules/onboarding/api/post/onboarding.js.map +2 -2
  10. package/dist/modules/onboarding/frontend/onboarding/OnboardingPageClient.js +31 -1
  11. package/dist/modules/onboarding/frontend/onboarding/OnboardingPageClient.js.map +2 -2
  12. package/dist/modules/onboarding/frontend/onboarding/preparing/PreparingPageClient.js +36 -7
  13. package/dist/modules/onboarding/frontend/onboarding/preparing/PreparingPageClient.js.map +2 -2
  14. package/dist/modules/onboarding/lib/deferred-provisioning.js +219 -0
  15. package/dist/modules/onboarding/lib/deferred-provisioning.js.map +7 -0
  16. package/dist/modules/onboarding/lib/provisioning.js +18 -0
  17. package/dist/modules/onboarding/lib/provisioning.js.map +7 -0
  18. package/dist/modules/onboarding/lib/verify-base-url.js +86 -0
  19. package/dist/modules/onboarding/lib/verify-base-url.js.map +7 -0
  20. package/package.json +3 -3
  21. package/src/__tests__/onboarding-submit-rate-limit.test.ts +22 -0
  22. package/src/__tests__/provisioning.test.ts +89 -0
  23. package/src/__tests__/status-endpoint-auth.test.ts +92 -3
  24. package/src/__tests__/verify-base-url.test.ts +65 -0
  25. package/src/modules/onboarding/__integration__/TC-ONB-001-self-service-consent.spec.ts +176 -0
  26. package/src/modules/onboarding/api/get/onboarding/status.ts +36 -17
  27. package/src/modules/onboarding/api/get/onboarding/verify.ts +96 -227
  28. package/src/modules/onboarding/api/post/onboarding.ts +9 -0
  29. package/src/modules/onboarding/frontend/onboarding/OnboardingPageClient.tsx +32 -2
  30. package/src/modules/onboarding/frontend/onboarding/preparing/PreparingPageClient.tsx +38 -6
  31. package/src/modules/onboarding/i18n/de.json +10 -1
  32. package/src/modules/onboarding/i18n/en.json +10 -1
  33. package/src/modules/onboarding/i18n/es.json +10 -1
  34. package/src/modules/onboarding/i18n/pl.json +10 -1
  35. package/src/modules/onboarding/lib/deferred-provisioning.ts +258 -0
  36. package/src/modules/onboarding/lib/provisioning.ts +35 -0
  37. package/src/modules/onboarding/lib/verify-base-url.ts +107 -0
@@ -1,2 +1,2 @@
1
- [build:onboarding] found 26 entry points
1
+ [build:onboarding] found 30 entry points
2
2
  [build:onboarding] built successfully
@@ -0,0 +1,141 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { expect, test } from "@playwright/test";
3
+ import { withClient } from "@open-mercato/core/helpers/integration/dbFixtures";
4
+ const integrationMeta = {
5
+ dependsOnModules: ["onboarding"],
6
+ requiredEnvVars: ["SELF_SERVICE_ONBOARDING_ENABLED"],
7
+ requiredAnyEnvVars: ["CONSENT_INTEGRITY_SECRET", "AUTH_SECRET", "NEXTAUTH_SECRET", "JWT_SECRET"]
8
+ };
9
+ const ONBOARDING_PASSWORD = "IntegrationPass123!";
10
+ const BASE_URL = process.env.BASE_URL?.trim() || "http://localhost:3000";
11
+ function hashToken(token) {
12
+ return createHash("sha256").update(token).digest("hex");
13
+ }
14
+ async function replaceVerificationToken(email, token) {
15
+ return withClient(async (client) => {
16
+ const result = await client.query(
17
+ `update onboarding_requests
18
+ set token_hash = $2,
19
+ expires_at = now() + interval '24 hours',
20
+ updated_at = now()
21
+ where email = $1
22
+ returning id`,
23
+ [email, hashToken(token)]
24
+ );
25
+ expect(result.rowCount, "onboarding request should exist after submitting the form").toBe(1);
26
+ return result.rows[0].id;
27
+ });
28
+ }
29
+ async function readCompletedRequest(requestId) {
30
+ return withClient(async (client) => {
31
+ const result = await client.query(
32
+ `select id, status, tenant_id, organization_id, user_id, marketing_consent, completed_at
33
+ from onboarding_requests
34
+ where id = $1`,
35
+ [requestId]
36
+ );
37
+ expect(result.rowCount, "completed onboarding request should remain queryable").toBe(1);
38
+ return result.rows[0];
39
+ });
40
+ }
41
+ async function readMarketingConsent(userId) {
42
+ return withClient(async (client) => {
43
+ const result = await client.query(
44
+ `select consent_type, is_granted, source, integrity_hash, granted_at
45
+ from user_consents
46
+ where user_id = $1 and consent_type = 'marketing_email'
47
+ order by created_at desc
48
+ limit 1`,
49
+ [userId]
50
+ );
51
+ expect(result.rowCount, "marketing consent should be persisted for the onboarded user").toBe(1);
52
+ return result.rows[0];
53
+ });
54
+ }
55
+ test.describe("TC-ONB-001: self-service onboarding with marketing consent", () => {
56
+ test("does not offer tenant login while workspace preparation is still running", async ({ page }) => {
57
+ const tenantId = "33333333-3333-4333-8333-333333333333";
58
+ await page.route("**/api/onboarding/onboarding/status?**", async (route) => {
59
+ await route.fulfill({
60
+ status: 200,
61
+ contentType: "application/json",
62
+ body: JSON.stringify({
63
+ ok: true,
64
+ status: "processing",
65
+ ready: false,
66
+ emailSent: false,
67
+ tenantId,
68
+ loginUrl: null
69
+ })
70
+ });
71
+ });
72
+ await page.goto(`/onboarding/preparing?tenant=${tenantId}`);
73
+ await expect(page.getByText("We are preparing your workspace")).toBeVisible();
74
+ await expect(page.getByRole("link", { name: "Open tenant login" })).toHaveCount(0);
75
+ await expect(page.getByRole("link", { name: "Go to home page" })).toBeVisible();
76
+ });
77
+ test("shows a retryable error when workspace status polling fails", async ({ page }) => {
78
+ const tenantId = "33333333-3333-4333-8333-333333333333";
79
+ await page.route("**/api/onboarding/onboarding/status?**", async (route) => {
80
+ await route.fulfill({
81
+ status: 400,
82
+ contentType: "application/json",
83
+ body: JSON.stringify({ ok: false, error: "Invalid request origin" })
84
+ });
85
+ });
86
+ await page.goto(`/onboarding/preparing?tenant=${tenantId}`);
87
+ await expect(page.getByText("We are preparing your workspace")).toBeVisible();
88
+ const statusAlert = page.getByText("Workspace status check failed").locator("..");
89
+ await expect(statusAlert).toContainText("Invalid request origin");
90
+ await expect(page.getByRole("link", { name: "Open tenant login" })).toHaveCount(0);
91
+ });
92
+ test("creates the workspace, verifies from the email link, and logs in to the new tenant", async ({ page }) => {
93
+ const unique = randomUUID().slice(0, 8);
94
+ const email = `qa-onboarding-${unique}@example.test`;
95
+ const organizationName = `QA Onboarding ${unique}`;
96
+ const token = `integration-onboarding-${randomUUID().replace(/-/g, "")}`;
97
+ await page.goto("/onboarding");
98
+ await expect(page.getByText("Create your Open Mercato workspace")).toBeVisible();
99
+ await page.getByLabel("Work email").fill(email);
100
+ await page.getByLabel("First name").fill("QA");
101
+ await page.getByLabel("Last name").fill("Onboarding");
102
+ await page.getByLabel("Organization name").fill(organizationName);
103
+ await page.locator('input[name="password"]').fill(ONBOARDING_PASSWORD);
104
+ await page.locator('input[name="confirmPassword"]').fill(ONBOARDING_PASSWORD);
105
+ await page.locator("#terms").click();
106
+ await page.locator("#marketingConsent").click();
107
+ await page.getByRole("button", { name: "Send verification email" }).click();
108
+ await expect(page.getByRole("status")).toContainText("Check your inbox");
109
+ await expect(page.getByRole("status")).toContainText(email);
110
+ const requestId = await replaceVerificationToken(email, token);
111
+ const verifyUrl = `${BASE_URL}/api/onboarding/onboarding/verify?token=${encodeURIComponent(token)}`;
112
+ await page.setContent(`<a href="${verifyUrl}">Verify workspace</a>`);
113
+ await page.getByRole("link", { name: "Verify workspace" }).click();
114
+ await expect(page).toHaveURL(/\/onboarding\/preparing\?tenant=[0-9a-f-]+/);
115
+ await expect(page.getByText("We are preparing your workspace")).toBeVisible();
116
+ const completed = await readCompletedRequest(requestId);
117
+ expect(completed.status).toBe("completed");
118
+ expect(completed.marketing_consent).toBe(true);
119
+ expect(completed.completed_at).toBeTruthy();
120
+ expect(completed.tenant_id, "tenant id should be recorded").toBeTruthy();
121
+ expect(completed.organization_id, "organization id should be recorded").toBeTruthy();
122
+ expect(completed.user_id, "user id should be recorded").toBeTruthy();
123
+ const consent = await readMarketingConsent(completed.user_id);
124
+ expect(consent.consent_type).toBe("marketing_email");
125
+ expect(consent.is_granted).toBe(true);
126
+ expect(consent.source).toBeTruthy();
127
+ expect(consent.granted_at).toBeTruthy();
128
+ expect(consent.integrity_hash, "consent integrity hash should be computed instead of redirecting to status=error").toBeTruthy();
129
+ await page.goto(`/login?tenant=${encodeURIComponent(completed.tenant_id)}`);
130
+ await expect(page.locator('form[data-auth-ready="1"]')).toBeVisible();
131
+ await expect(page.getByText(/You're logging in to/i)).toBeVisible();
132
+ await page.getByLabel("Email").fill(email);
133
+ await page.getByLabel("Password", { exact: true }).fill(ONBOARDING_PASSWORD);
134
+ await page.getByRole("button", { name: "Sign in" }).click();
135
+ await expect(page).toHaveURL(/\/backend(?:\/.*)?$/);
136
+ });
137
+ });
138
+ export {
139
+ integrationMeta
140
+ };
141
+ //# sourceMappingURL=TC-ONB-001-self-service-consent.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/onboarding/__integration__/TC-ONB-001-self-service-consent.spec.ts"],
4
+ "sourcesContent": ["import { createHash, randomUUID } from 'node:crypto';\nimport { expect, test } from '@playwright/test';\nimport { withClient } from '@open-mercato/core/helpers/integration/dbFixtures';\n\nexport const integrationMeta = {\n dependsOnModules: ['onboarding'],\n requiredEnvVars: ['SELF_SERVICE_ONBOARDING_ENABLED'],\n requiredAnyEnvVars: ['CONSENT_INTEGRITY_SECRET', 'AUTH_SECRET', 'NEXTAUTH_SECRET', 'JWT_SECRET'],\n};\n\ntype OnboardingRequestRow = {\n id: string;\n status: string;\n tenant_id: string | null;\n organization_id: string | null;\n user_id: string | null;\n marketing_consent: boolean | null;\n completed_at: Date | null;\n};\n\ntype UserConsentRow = {\n consent_type: string;\n is_granted: boolean;\n source: string | null;\n integrity_hash: string | null;\n granted_at: Date | null;\n};\n\nconst ONBOARDING_PASSWORD = 'IntegrationPass123!';\nconst BASE_URL = process.env.BASE_URL?.trim() || 'http://localhost:3000';\n\nfunction hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\n\nasync function replaceVerificationToken(email: string, token: string): Promise<string> {\n return withClient(async (client) => {\n const result = await client.query<{ id: string }>(\n `update onboarding_requests\n set token_hash = $2,\n expires_at = now() + interval '24 hours',\n updated_at = now()\n where email = $1\n returning id`,\n [email, hashToken(token)],\n );\n expect(result.rowCount, 'onboarding request should exist after submitting the form').toBe(1);\n return result.rows[0].id;\n });\n}\n\nasync function readCompletedRequest(requestId: string): Promise<OnboardingRequestRow> {\n return withClient(async (client) => {\n const result = await client.query<OnboardingRequestRow>(\n `select id, status, tenant_id, organization_id, user_id, marketing_consent, completed_at\n from onboarding_requests\n where id = $1`,\n [requestId],\n );\n expect(result.rowCount, 'completed onboarding request should remain queryable').toBe(1);\n return result.rows[0];\n });\n}\n\nasync function readMarketingConsent(userId: string): Promise<UserConsentRow> {\n return withClient(async (client) => {\n const result = await client.query<UserConsentRow>(\n `select consent_type, is_granted, source, integrity_hash, granted_at\n from user_consents\n where user_id = $1 and consent_type = 'marketing_email'\n order by created_at desc\n limit 1`,\n [userId],\n );\n expect(result.rowCount, 'marketing consent should be persisted for the onboarded user').toBe(1);\n return result.rows[0];\n });\n}\n\ntest.describe('TC-ONB-001: self-service onboarding with marketing consent', () => {\n test('does not offer tenant login while workspace preparation is still running', async ({ page }) => {\n const tenantId = '33333333-3333-4333-8333-333333333333';\n await page.route('**/api/onboarding/onboarding/status?**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify({\n ok: true,\n status: 'processing',\n ready: false,\n emailSent: false,\n tenantId,\n loginUrl: null,\n }),\n });\n });\n\n await page.goto(`/onboarding/preparing?tenant=${tenantId}`);\n\n await expect(page.getByText('We are preparing your workspace')).toBeVisible();\n await expect(page.getByRole('link', { name: 'Open tenant login' })).toHaveCount(0);\n await expect(page.getByRole('link', { name: 'Go to home page' })).toBeVisible();\n });\n\n test('shows a retryable error when workspace status polling fails', async ({ page }) => {\n const tenantId = '33333333-3333-4333-8333-333333333333';\n await page.route('**/api/onboarding/onboarding/status?**', async (route) => {\n await route.fulfill({\n status: 400,\n contentType: 'application/json',\n body: JSON.stringify({ ok: false, error: 'Invalid request origin' }),\n });\n });\n\n await page.goto(`/onboarding/preparing?tenant=${tenantId}`);\n\n await expect(page.getByText('We are preparing your workspace')).toBeVisible();\n const statusAlert = page.getByText('Workspace status check failed').locator('..');\n await expect(statusAlert).toContainText('Invalid request origin');\n await expect(page.getByRole('link', { name: 'Open tenant login' })).toHaveCount(0);\n });\n\n test('creates the workspace, verifies from the email link, and logs in to the new tenant', async ({ page }) => {\n const unique = randomUUID().slice(0, 8);\n const email = `qa-onboarding-${unique}@example.test`;\n const organizationName = `QA Onboarding ${unique}`;\n const token = `integration-onboarding-${randomUUID().replace(/-/g, '')}`;\n\n await page.goto('/onboarding');\n await expect(page.getByText('Create your Open Mercato workspace')).toBeVisible();\n\n await page.getByLabel('Work email').fill(email);\n await page.getByLabel('First name').fill('QA');\n await page.getByLabel('Last name').fill('Onboarding');\n await page.getByLabel('Organization name').fill(organizationName);\n await page.locator('input[name=\"password\"]').fill(ONBOARDING_PASSWORD);\n await page.locator('input[name=\"confirmPassword\"]').fill(ONBOARDING_PASSWORD);\n await page.locator('#terms').click();\n await page.locator('#marketingConsent').click();\n await page.getByRole('button', { name: 'Send verification email' }).click();\n\n await expect(page.getByRole('status')).toContainText('Check your inbox');\n await expect(page.getByRole('status')).toContainText(email);\n\n const requestId = await replaceVerificationToken(email, token);\n const verifyUrl = `${BASE_URL}/api/onboarding/onboarding/verify?token=${encodeURIComponent(token)}`;\n\n await page.setContent(`<a href=\"${verifyUrl}\">Verify workspace</a>`);\n await page.getByRole('link', { name: 'Verify workspace' }).click();\n await expect(page).toHaveURL(/\\/onboarding\\/preparing\\?tenant=[0-9a-f-]+/);\n await expect(page.getByText('We are preparing your workspace')).toBeVisible();\n\n const completed = await readCompletedRequest(requestId);\n expect(completed.status).toBe('completed');\n expect(completed.marketing_consent).toBe(true);\n expect(completed.completed_at).toBeTruthy();\n expect(completed.tenant_id, 'tenant id should be recorded').toBeTruthy();\n expect(completed.organization_id, 'organization id should be recorded').toBeTruthy();\n expect(completed.user_id, 'user id should be recorded').toBeTruthy();\n\n const consent = await readMarketingConsent(completed.user_id!);\n expect(consent.consent_type).toBe('marketing_email');\n expect(consent.is_granted).toBe(true);\n expect(consent.source).toBeTruthy();\n expect(consent.granted_at).toBeTruthy();\n expect(consent.integrity_hash, 'consent integrity hash should be computed instead of redirecting to status=error').toBeTruthy();\n\n await page.goto(`/login?tenant=${encodeURIComponent(completed.tenant_id!)}`);\n await expect(page.locator('form[data-auth-ready=\"1\"]')).toBeVisible();\n await expect(page.getByText(/You're logging in to/i)).toBeVisible();\n await page.getByLabel('Email').fill(email);\n await page.getByLabel('Password', { exact: true }).fill(ONBOARDING_PASSWORD);\n await page.getByRole('button', { name: 'Sign in' }).click();\n await expect(page).toHaveURL(/\\/backend(?:\\/.*)?$/);\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,9 +1,13 @@
1
- import { NextResponse } from "next/server";
1
+ import { after, NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
- import { getSecurityEmailBaseUrl, mapSecurityEmailUrlError } from "@open-mercato/shared/lib/url";
4
+ import { assertAllowedAppOrigin, mapSecurityEmailUrlError } from "@open-mercato/shared/lib/url";
5
5
  import { OnboardingService } from "@open-mercato/onboarding/modules/onboarding/lib/service";
6
6
  import { sendWorkspaceReadyEmail } from "@open-mercato/onboarding/modules/onboarding/lib/ready-email";
7
+ import {
8
+ resolveProvisioningIds,
9
+ runDeferredProvisioning
10
+ } from "@open-mercato/onboarding/modules/onboarding/lib/deferred-provisioning";
7
11
  const metadata = {
8
12
  path: "/onboarding/onboarding/status",
9
13
  GET: {
@@ -42,9 +46,8 @@ async function GET(req) {
42
46
  if (!loginTenantCookie || loginTenantCookie !== parsed.data.tenantId) {
43
47
  return NextResponse.json({ ok: false, error: "Not authorized for this tenant." }, { status: 403 });
44
48
  }
45
- let baseUrl;
46
49
  try {
47
- baseUrl = getSecurityEmailBaseUrl(req);
50
+ assertAllowedAppOrigin(req);
48
51
  } catch (error) {
49
52
  const mapped = mapSecurityEmailUrlError(error, {
50
53
  scope: "onboarding.status",
@@ -60,23 +63,37 @@ async function GET(req) {
60
63
  if (!request) {
61
64
  return NextResponse.json({ ok: false, error: "Onboarding request not found." }, { status: 404 });
62
65
  }
63
- let emailSent = Boolean(request.readyEmailSentAt);
64
- const ready = request.status === "completed" && Boolean(request.preparationCompletedAt);
65
- const loginUrl = ready && request.tenantId ? `${baseUrl}/login?tenant=${encodeURIComponent(request.tenantId)}` : null;
66
- if (ready && request.tenantId && !request.readyEmailSentAt) {
67
- try {
68
- emailSent = await sendWorkspaceReadyEmail({
66
+ const provisioningIds = resolveProvisioningIds(request);
67
+ if (provisioningIds && request.status === "processing") {
68
+ await service.markCompleted(request, provisioningIds);
69
+ }
70
+ if (provisioningIds && request.status === "completed" && !request.preparationCompletedAt) {
71
+ after(async () => {
72
+ await runDeferredProvisioning({
69
73
  requestId: request.id,
70
- tenantId: request.tenantId
74
+ tenantId: provisioningIds.tenantId,
75
+ organizationId: provisioningIds.organizationId
71
76
  });
72
- } catch (error) {
73
- console.error("[onboarding.status] ready email retry failed", {
77
+ });
78
+ }
79
+ const emailSent = Boolean(request.readyEmailSentAt);
80
+ const ready = request.status === "completed" && Boolean(request.preparationCompletedAt);
81
+ const loginUrl = ready && request.tenantId ? `/login?tenant=${encodeURIComponent(request.tenantId)}` : null;
82
+ if (ready && request.tenantId && !request.readyEmailSentAt) {
83
+ const readyTenantId = request.tenantId;
84
+ after(async () => {
85
+ await sendWorkspaceReadyEmail({
74
86
  requestId: request.id,
75
- tenantId: request.tenantId,
76
- organizationId: request.organizationId,
77
- error
87
+ tenantId: readyTenantId
88
+ }).catch((error) => {
89
+ console.error("[onboarding.status] ready email retry failed", {
90
+ requestId: request.id,
91
+ tenantId: readyTenantId,
92
+ organizationId: request.organizationId,
93
+ error
94
+ });
78
95
  });
79
- }
96
+ });
80
97
  }
81
98
  return NextResponse.json({
82
99
  ok: true,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/onboarding/api/get/onboarding/status.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getSecurityEmailBaseUrl, mapSecurityEmailUrlError } from '@open-mercato/shared/lib/url'\nimport { OnboardingService } from '@open-mercato/onboarding/modules/onboarding/lib/service'\nimport { sendWorkspaceReadyEmail } from '@open-mercato/onboarding/modules/onboarding/lib/ready-email'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n path: '/onboarding/onboarding/status',\n GET: {\n requireAuth: false,\n },\n}\n\nconst ONBOARDING_LOGIN_TENANT_COOKIE = 'om_login_tenant'\n\nconst onboardingStatusQuerySchema = z.object({\n tenantId: z.string().uuid(),\n})\n\nfunction readCookie(req: Request, name: string): string | null {\n const header = req.headers.get('cookie')\n if (!header) return null\n for (const part of header.split(';')) {\n const separatorIndex = part.indexOf('=')\n if (separatorIndex === -1) continue\n const key = part.slice(0, separatorIndex).trim()\n if (key !== name) continue\n const rawValue = part.slice(separatorIndex + 1).trim()\n try {\n return decodeURIComponent(rawValue)\n } catch {\n return rawValue\n }\n }\n return null\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const tenantId = url.searchParams.get('tenantId') || url.searchParams.get('tenant') || ''\n const parsed = onboardingStatusQuerySchema.safeParse({ tenantId })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid tenant id.' }, { status: 400 })\n }\n\n const loginTenantCookie = readCookie(req, ONBOARDING_LOGIN_TENANT_COOKIE)\n if (!loginTenantCookie || loginTenantCookie !== parsed.data.tenantId) {\n return NextResponse.json({ ok: false, error: 'Not authorized for this tenant.' }, { status: 403 })\n }\n\n let baseUrl: string\n try {\n baseUrl = getSecurityEmailBaseUrl(req)\n } catch (error) {\n const mapped = mapSecurityEmailUrlError(error, {\n scope: 'onboarding.status',\n configMessage: 'Onboarding status is not configured.',\n })\n if (mapped) return NextResponse.json({ ok: false, error: mapped.body.error }, { status: mapped.status })\n throw error\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const service = new OnboardingService(em)\n const request = await service.findLatestByTenantId(parsed.data.tenantId)\n if (!request) {\n return NextResponse.json({ ok: false, error: 'Onboarding request not found.' }, { status: 404 })\n }\n\n let emailSent = Boolean(request.readyEmailSentAt)\n const ready = request.status === 'completed' && Boolean(request.preparationCompletedAt)\n const loginUrl = ready && request.tenantId ? `${baseUrl}/login?tenant=${encodeURIComponent(request.tenantId)}` : null\n\n if (ready && request.tenantId && !request.readyEmailSentAt) {\n try {\n emailSent = await sendWorkspaceReadyEmail({\n requestId: request.id,\n tenantId: request.tenantId,\n })\n } catch (error) {\n console.error('[onboarding.status] ready email retry failed', {\n requestId: request.id,\n tenantId: request.tenantId,\n organizationId: request.organizationId,\n error,\n })\n }\n }\n\n return NextResponse.json({\n ok: true,\n status: request.status,\n ready,\n emailSent,\n tenantId: request.tenantId ?? parsed.data.tenantId,\n loginUrl,\n })\n}\n\nconst onboardingTag = 'Onboarding'\n\nconst onboardingStatusSuccessSchema = z.object({\n ok: z.literal(true),\n status: z.enum(['pending', 'processing', 'completed', 'expired']),\n ready: z.boolean(),\n emailSent: z.boolean(),\n tenantId: z.string().uuid(),\n loginUrl: z.string().nullable(),\n})\n\nconst onboardingStatusErrorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n})\n\nconst onboardingStatusDoc: OpenApiMethodDoc = {\n summary: 'Get onboarding preparation status',\n description: 'Resolves whether a tenant workspace finished deferred onboarding preparation and can be opened.',\n tags: [onboardingTag],\n query: onboardingStatusQuerySchema,\n responses: [\n { status: 200, description: 'Onboarding status resolved.', schema: onboardingStatusSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid tenant id or request origin.', schema: onboardingStatusErrorSchema },\n { status: 403, description: 'Caller is not authorized for this tenant.', schema: onboardingStatusErrorSchema },\n { status: 404, description: 'Onboarding request not found.', schema: onboardingStatusErrorSchema },\n { status: 500, description: 'Onboarding status is not configured.', schema: onboardingStatusErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: onboardingTag,\n summary: 'Onboarding preparation status',\n methods: {\n GET: onboardingStatusDoc,\n },\n}\n\nexport default GET\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { after, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { assertAllowedAppOrigin, mapSecurityEmailUrlError } from '@open-mercato/shared/lib/url'\nimport { OnboardingService } from '@open-mercato/onboarding/modules/onboarding/lib/service'\nimport { sendWorkspaceReadyEmail } from '@open-mercato/onboarding/modules/onboarding/lib/ready-email'\nimport {\n resolveProvisioningIds,\n runDeferredProvisioning,\n} from '@open-mercato/onboarding/modules/onboarding/lib/deferred-provisioning'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n path: '/onboarding/onboarding/status',\n GET: {\n requireAuth: false,\n },\n}\n\nconst ONBOARDING_LOGIN_TENANT_COOKIE = 'om_login_tenant'\n\nconst onboardingStatusQuerySchema = z.object({\n tenantId: z.string().uuid(),\n})\n\nfunction readCookie(req: Request, name: string): string | null {\n const header = req.headers.get('cookie')\n if (!header) return null\n for (const part of header.split(';')) {\n const separatorIndex = part.indexOf('=')\n if (separatorIndex === -1) continue\n const key = part.slice(0, separatorIndex).trim()\n if (key !== name) continue\n const rawValue = part.slice(separatorIndex + 1).trim()\n try {\n return decodeURIComponent(rawValue)\n } catch {\n return rawValue\n }\n }\n return null\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const tenantId = url.searchParams.get('tenantId') || url.searchParams.get('tenant') || ''\n const parsed = onboardingStatusQuerySchema.safeParse({ tenantId })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid tenant id.' }, { status: 400 })\n }\n\n const loginTenantCookie = readCookie(req, ONBOARDING_LOGIN_TENANT_COOKIE)\n if (!loginTenantCookie || loginTenantCookie !== parsed.data.tenantId) {\n return NextResponse.json({ ok: false, error: 'Not authorized for this tenant.' }, { status: 403 })\n }\n\n try {\n assertAllowedAppOrigin(req)\n } catch (error) {\n const mapped = mapSecurityEmailUrlError(error, {\n scope: 'onboarding.status',\n configMessage: 'Onboarding status is not configured.',\n })\n if (mapped) return NextResponse.json({ ok: false, error: mapped.body.error }, { status: mapped.status })\n throw error\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const service = new OnboardingService(em)\n const request = await service.findLatestByTenantId(parsed.data.tenantId)\n if (!request) {\n return NextResponse.json({ ok: false, error: 'Onboarding request not found.' }, { status: 404 })\n }\n\n const provisioningIds = resolveProvisioningIds(request)\n if (provisioningIds && request.status === 'processing') {\n await service.markCompleted(request, provisioningIds)\n }\n if (provisioningIds && request.status === 'completed' && !request.preparationCompletedAt) {\n after(async () => {\n await runDeferredProvisioning({\n requestId: request.id,\n tenantId: provisioningIds.tenantId,\n organizationId: provisioningIds.organizationId,\n })\n })\n }\n\n const emailSent = Boolean(request.readyEmailSentAt)\n const ready = request.status === 'completed' && Boolean(request.preparationCompletedAt)\n const loginUrl = ready && request.tenantId ? `/login?tenant=${encodeURIComponent(request.tenantId)}` : null\n\n if (ready && request.tenantId && !request.readyEmailSentAt) {\n const readyTenantId = request.tenantId\n after(async () => {\n await sendWorkspaceReadyEmail({\n requestId: request.id,\n tenantId: readyTenantId,\n }).catch((error) => {\n console.error('[onboarding.status] ready email retry failed', {\n requestId: request.id,\n tenantId: readyTenantId,\n organizationId: request.organizationId,\n error,\n })\n })\n })\n }\n\n return NextResponse.json({\n ok: true,\n status: request.status,\n ready,\n emailSent,\n tenantId: request.tenantId ?? parsed.data.tenantId,\n loginUrl,\n })\n}\n\nconst onboardingTag = 'Onboarding'\n\nconst onboardingStatusSuccessSchema = z.object({\n ok: z.literal(true),\n status: z.enum(['pending', 'processing', 'completed', 'expired']),\n ready: z.boolean(),\n emailSent: z.boolean(),\n tenantId: z.string().uuid(),\n loginUrl: z.string().nullable(),\n})\n\nconst onboardingStatusErrorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n})\n\nconst onboardingStatusDoc: OpenApiMethodDoc = {\n summary: 'Get onboarding preparation status',\n description: 'Resolves whether a tenant workspace finished deferred onboarding preparation and can be opened.',\n tags: [onboardingTag],\n query: onboardingStatusQuerySchema,\n responses: [\n { status: 200, description: 'Onboarding status resolved.', schema: onboardingStatusSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid tenant id or request origin.', schema: onboardingStatusErrorSchema },\n { status: 403, description: 'Caller is not authorized for this tenant.', schema: onboardingStatusErrorSchema },\n { status: 404, description: 'Onboarding request not found.', schema: onboardingStatusErrorSchema },\n { status: 500, description: 'Onboarding status is not configured.', schema: onboardingStatusErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: onboardingTag,\n summary: 'Onboarding preparation status',\n methods: {\n GET: onboardingStatusDoc,\n },\n}\n\nexport default GET\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }