@open-mercato/onboarding 0.6.5-develop.5187.1.82e5532561 → 0.6.5-develop.5212.1.b47932beef

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.
@@ -1,2 +1,2 @@
1
- [build:onboarding] found 25 entry points
1
+ [build:onboarding] found 26 entry points
2
2
  [build:onboarding] built successfully
@@ -1,4 +1,4 @@
1
- import { after, NextResponse } from "next/server";
1
+ import { after } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import {
@@ -9,6 +9,11 @@ import {
9
9
  import { onboardingVerifySchema } from "@open-mercato/onboarding/modules/onboarding/data/validators";
10
10
  import { OnboardingService } from "@open-mercato/onboarding/modules/onboarding/lib/service";
11
11
  import { sendWorkspaceReadyEmail } from "@open-mercato/onboarding/modules/onboarding/lib/ready-email";
12
+ import {
13
+ redirectToLogin,
14
+ redirectToPreparing,
15
+ redirectWithStatus
16
+ } from "@open-mercato/onboarding/modules/onboarding/lib/verify-redirects";
12
17
  import { setupInitialTenant } from "@open-mercato/core/modules/auth/lib/setup-app";
13
18
  import { UserConsent } from "@open-mercato/core/modules/auth/data/entities";
14
19
  import { computeConsentIntegrityHash } from "@open-mercato/core/modules/auth/lib/consentIntegrity";
@@ -39,46 +44,6 @@ function resolveTrustedBaseUrl(req) {
39
44
  throw error;
40
45
  }
41
46
  }
42
- function clearAuthCookies(response) {
43
- response.cookies.set("auth_token", "", { path: "/", maxAge: 0 });
44
- response.cookies.set("session_token", "", { path: "/", maxAge: 0 });
45
- response.cookies.set("om_login_tenant", "", { path: "/", maxAge: 0 });
46
- }
47
- function redirectWithStatus(baseUrl, status) {
48
- const response = NextResponse.redirect(`${baseUrl}/onboarding?status=${encodeURIComponent(status)}`);
49
- clearAuthCookies(response);
50
- return response;
51
- }
52
- function redirectToPreparing(baseUrl, tenantId) {
53
- const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : "";
54
- const response = NextResponse.redirect(`${baseUrl}/onboarding/preparing${tenantParam}`);
55
- clearAuthCookies(response);
56
- if (tenantId) {
57
- response.cookies.set("om_login_tenant", tenantId, {
58
- httpOnly: false,
59
- sameSite: "lax",
60
- secure: process.env.NODE_ENV === "production",
61
- path: "/",
62
- maxAge: 60 * 60 * 24 * 14
63
- });
64
- }
65
- return response;
66
- }
67
- function redirectToLogin(baseUrl, tenantId) {
68
- const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : "";
69
- const response = NextResponse.redirect(`${baseUrl}/login${tenantParam}`);
70
- clearAuthCookies(response);
71
- if (tenantId) {
72
- response.cookies.set("om_login_tenant", tenantId, {
73
- httpOnly: false,
74
- sameSite: "lax",
75
- secure: process.env.NODE_ENV === "production",
76
- path: "/",
77
- maxAge: 60 * 60 * 24 * 14
78
- });
79
- }
80
- return response;
81
- }
82
47
  const VECTOR_REINDEX_ENQUEUE_TIMEOUT_MS = 5e3;
83
48
  const SEED_DEFAULTS_TIMEOUT_MS = 15e3;
84
49
  const SEED_EXAMPLES_TIMEOUT_MS = 15e3;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/onboarding/api/get/onboarding/verify.ts"],
4
- "sourcesContent": ["import { after, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { SearchIndexer } from '@open-mercato/search'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n AppOriginConfigurationError,\n AppOriginRejectedError,\n getSecurityEmailBaseUrl,\n} from '@open-mercato/shared/lib/url'\nimport { onboardingVerifySchema } from '@open-mercato/onboarding/modules/onboarding/data/validators'\nimport { OnboardingService } from '@open-mercato/onboarding/modules/onboarding/lib/service'\nimport { sendWorkspaceReadyEmail } from '@open-mercato/onboarding/modules/onboarding/lib/ready-email'\nimport { setupInitialTenant } from '@open-mercato/core/modules/auth/lib/setup-app'\nimport { UserConsent } from '@open-mercato/core/modules/auth/data/entities'\nimport { computeConsentIntegrityHash } from '@open-mercato/core/modules/auth/lib/consentIntegrity'\nimport { resolveConsentClientIp } from '@open-mercato/onboarding/modules/onboarding/lib/consentClientIp'\nimport { reindexEntity } from '@open-mercato/core/modules/query_index/lib/reindexer'\nimport { purgeIndexScope } from '@open-mercato/core/modules/query_index/lib/purge'\nimport { refreshCoverageSnapshot } from '@open-mercato/core/modules/query_index/lib/coverage'\nimport { flattenSystemEntityIds } from '@open-mercato/shared/lib/entities/system-entities'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { getModules } from '@open-mercato/shared/lib/modules/registry'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n path: '/onboarding/onboarding/verify',\n GET: {\n requireAuth: false,\n },\n}\n\nfunction resolveTrustedBaseUrl(req: Request): string {\n try {\n return getSecurityEmailBaseUrl(req)\n } catch (error) {\n if (error instanceof AppOriginRejectedError || error instanceof AppOriginConfigurationError) {\n console.error('[onboarding.verify] rejected request origin for redirect base', {\n requestUrl: req.url,\n reason: error.message,\n })\n return new URL(req.url).origin\n }\n throw error\n }\n}\n\nfunction clearAuthCookies(response: NextResponse) {\n response.cookies.set('auth_token', '', { path: '/', maxAge: 0 })\n response.cookies.set('session_token', '', { path: '/', maxAge: 0 })\n response.cookies.set('om_login_tenant', '', { path: '/', maxAge: 0 })\n}\n\nfunction redirectWithStatus(baseUrl: string, status: string) {\n const response = NextResponse.redirect(`${baseUrl}/onboarding?status=${encodeURIComponent(status)}`)\n clearAuthCookies(response)\n return response\n}\n\nfunction redirectToPreparing(baseUrl: string, tenantId: string | null) {\n const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : ''\n const response = NextResponse.redirect(`${baseUrl}/onboarding/preparing${tenantParam}`)\n clearAuthCookies(response)\n if (tenantId) {\n response.cookies.set('om_login_tenant', tenantId, {\n httpOnly: false,\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n maxAge: 60 * 60 * 24 * 14,\n })\n }\n return response\n}\n\nfunction redirectToLogin(baseUrl: string, tenantId: string | null) {\n const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : ''\n const response = NextResponse.redirect(`${baseUrl}/login${tenantParam}`)\n clearAuthCookies(response)\n if (tenantId) {\n response.cookies.set('om_login_tenant', tenantId, {\n httpOnly: false,\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n maxAge: 60 * 60 * 24 * 14,\n })\n }\n return response\n}\n\nconst VECTOR_REINDEX_ENQUEUE_TIMEOUT_MS = 5_000\nconst SEED_DEFAULTS_TIMEOUT_MS = 15_000\nconst SEED_EXAMPLES_TIMEOUT_MS = 15_000\n\nfunction createTimeoutPromise(label: string, timeoutMs: number): Promise<never> {\n return new Promise((_, reject) => {\n setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)\n })\n}\n\nasync function runModuleSetupHook(args: {\n moduleId: string\n phase: 'seedDefaults' | 'seedExamples'\n timeoutMs: number\n run: () => Promise<void>\n}) {\n const startedAt = Date.now()\n console.info('[onboarding.verify] module hook started', {\n moduleId: args.moduleId,\n phase: args.phase,\n timeoutMs: args.timeoutMs,\n })\n try {\n await Promise.race([\n args.run(),\n createTimeoutPromise(`module ${args.moduleId} ${args.phase}`, args.timeoutMs),\n ])\n console.info('[onboarding.verify] module hook completed', {\n moduleId: args.moduleId,\n phase: args.phase,\n durationMs: Math.max(0, Date.now() - startedAt),\n })\n } catch (error) {\n console.error('[onboarding.verify] module hook failed', {\n moduleId: args.moduleId,\n phase: args.phase,\n durationMs: Math.max(0, Date.now() - startedAt),\n timeoutMs: args.timeoutMs,\n error,\n })\n throw error\n }\n}\n\nasync function markWorkspaceReady(args: {\n requestId: string\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.findById(args.requestId)\n if (!request || request.preparationCompletedAt) return\n await service.markPreparationCompleted(request, new Date())\n}\n\nasync function enqueueVectorReindex(args: {\n container: { resolve: <T = unknown>(name: string) => T }\n tenantId: string\n organizationId: string\n}) {\n let searchIndexer: SearchIndexer | null = null\n try {\n searchIndexer = args.container.resolve<SearchIndexer>('searchIndexer')\n } catch {\n searchIndexer = null\n }\n if (!searchIndexer) return\n\n await Promise.race([\n searchIndexer.reindexAllToVector({\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n purgeFirst: true,\n useQueue: true,\n }),\n createTimeoutPromise('vector reindex enqueue', VECTOR_REINDEX_ENQUEUE_TIMEOUT_MS),\n ])\n}\n\nasync function rebuildTenantQueryIndexes(args: {\n em: EntityManager\n tenantId: string\n organizationId: string\n}) {\n const coverageRefreshKeys = new Set<string>()\n try {\n const allEntities = getEntityIds()\n const entityIds = flattenSystemEntityIds(allEntities)\n for (const entityType of entityIds) {\n try {\n await purgeIndexScope(args.em, { entityType, tenantId: args.tenantId })\n } catch (error) {\n console.error('[onboarding.verify] failed to purge query index scope', {\n entityType,\n tenantId: args.tenantId,\n error,\n })\n }\n try {\n await reindexEntity(args.em, {\n entityType,\n tenantId: args.tenantId,\n force: true,\n emitVectorizeEvents: false,\n vectorService: null,\n })\n } catch (error) {\n console.error('[onboarding.verify] failed to reindex entity', {\n entityType,\n tenantId: args.tenantId,\n error,\n })\n }\n coverageRefreshKeys.add(`${entityType}|${args.tenantId}|__null__`)\n coverageRefreshKeys.add(`${entityType}|${args.tenantId}|${args.organizationId}`)\n }\n } catch (error) {\n console.error('[onboarding.verify] failed to rebuild query indexes', { tenantId: args.tenantId, error })\n }\n\n if (!coverageRefreshKeys.size) return\n\n for (const entry of coverageRefreshKeys) {\n const [entityType, tenantKey, orgKey] = entry.split('|')\n const orgScope = orgKey === '__null__' ? null : orgKey\n try {\n await refreshCoverageSnapshot(\n args.em,\n {\n entityType,\n tenantId: tenantKey,\n organizationId: orgScope,\n withDeleted: false,\n },\n )\n } catch (error) {\n console.error('[onboarding.verify] failed to refresh coverage snapshot', {\n entityType,\n tenantId: tenantKey,\n organizationId: orgScope,\n error,\n })\n }\n }\n}\n\nasync function runDeferredProvisioning(args: {\n requestId: string\n tenantId: string\n organizationId: string\n}) {\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const modules = getModules()\n\n for (const mod of modules) {\n if (!mod.setup?.seedExamples) continue\n try {\n await runModuleSetupHook({\n moduleId: mod.id,\n phase: 'seedExamples',\n timeoutMs: SEED_EXAMPLES_TIMEOUT_MS,\n run: () => mod.setup!.seedExamples!({\n em,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n container,\n }),\n })\n } catch (error) {\n console.error('[onboarding.verify] deferred seedExamples failed', {\n moduleId: mod.id,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n error,\n })\n }\n }\n\n await markWorkspaceReady({\n requestId: args.requestId,\n })\n\n await sendWorkspaceReadyEmail({\n requestId: args.requestId,\n tenantId: args.tenantId,\n }).catch((error) => {\n console.error('[onboarding.verify] ready email failed', {\n requestId: args.requestId,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n error,\n })\n throw error\n })\n\n await rebuildTenantQueryIndexes({\n em,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n })\n\n await enqueueVectorReindex({\n container,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n }).catch((error) => {\n console.warn('[onboarding.verify] vector reindex enqueue did not complete promptly', {\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n reason: error instanceof Error ? error.message : String(error),\n })\n })\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const baseUrl = resolveTrustedBaseUrl(req)\n const token = url.searchParams.get('token') ?? ''\n const parsed = onboardingVerifySchema.safeParse({ token })\n if (!parsed.success) {\n return redirectWithStatus(baseUrl, 'invalid')\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.findByToken(parsed.data.token)\n if (!request) {\n return redirectWithStatus(baseUrl, 'invalid')\n }\n if (request.expiresAt <= new Date() && request.status !== 'completed') {\n return redirectWithStatus(baseUrl, 'invalid')\n }\n if (request.status === 'completed' && request.tenantId) {\n if (!request.preparationCompletedAt) {\n return redirectToPreparing(baseUrl, request.tenantId)\n }\n if (!request.readyEmailSentAt) {\n after(async () => {\n await sendWorkspaceReadyEmail({\n requestId: request.id,\n tenantId: request.tenantId!,\n }).catch((error) => {\n console.error('[onboarding.verify] retry ready email failed', {\n requestId: request.id,\n tenantId: request.tenantId,\n organizationId: request.organizationId,\n error,\n })\n })\n })\n }\n return redirectToLogin(baseUrl, request.tenantId)\n }\n const lockWindowMs = 15 * 60 * 1000\n const processingStartedAt = request.processingStartedAt?.getTime() ?? 0\n const processingFresh = request.status === 'processing' && processingStartedAt > Date.now() - lockWindowMs\n if (processingFresh) {\n return redirectToPreparing(baseUrl, request.tenantId ?? null)\n }\n if (request.status === 'processing' && !processingFresh) {\n await service.resetProcessing(request)\n }\n if (request.status !== 'pending') {\n return redirectWithStatus(baseUrl, 'invalid')\n }\n const claimed = await service.startProcessing(request, new Date())\n if (!claimed) {\n // A concurrent verify request already claimed this token (pending \u2192 processing).\n // Re-read on a fresh fork \u2014 the request EM's identity map still holds the stale\n // pre-claim copy \u2014 and route off the winner's committed state instead of re-running\n // provisioning, which would throw USER_EXISTS and could strand the request (#2742).\n const current = await new OnboardingService(em.fork()).findById(request.id)\n if (current?.status === 'completed' && current.tenantId) {\n return current.preparationCompletedAt\n ? redirectToLogin(baseUrl, current.tenantId)\n : redirectToPreparing(baseUrl, current.tenantId)\n }\n return redirectToPreparing(baseUrl, current?.tenantId ?? request.tenantId ?? null)\n }\n if (!request.passwordHash) {\n console.error('[onboarding.verify] missing password hash for request', request.id)\n await service.resetProcessing(request)\n return redirectWithStatus(baseUrl, 'error')\n }\n\n let tenantId: string | null = null\n let organizationId: string | null = null\n let userId: string | null = null\n\n try {\n const setupResult = await setupInitialTenant(em, {\n orgName: request.organizationName,\n includeDerivedUsers: false,\n failIfUserExists: true,\n primaryUserRoles: ['admin'],\n includeSuperadminRole: false,\n primaryUser: {\n email: request.email,\n firstName: request.firstName,\n lastName: request.lastName,\n displayName: `${request.firstName} ${request.lastName}`.trim(),\n hashedPassword: request.passwordHash,\n confirm: true,\n },\n modules: getModules(),\n })\n\n const resolvedTenantId = String(setupResult.tenantId)\n const resolvedOrganizationId = String(setupResult.organizationId)\n tenantId = resolvedTenantId\n organizationId = resolvedOrganizationId\n\n const mainUserSnapshot = setupResult.users.find((entry) => entry.user.email === request.email)\n if (!mainUserSnapshot) throw new Error('USER_NOT_CREATED')\n const user = mainUserSnapshot.user\n const resolvedUserId = String(user.id)\n userId = resolvedUserId\n await service.updateProvisioningIds(request, {\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n userId: resolvedUserId,\n })\n\n if (request.marketingConsent) {\n const now = new Date()\n const clientIp = resolveConsentClientIp(req)\n const integrityHash = computeConsentIntegrityHash({\n userId: resolvedUserId,\n consentType: 'marketing_email',\n isGranted: true,\n grantedAt: now,\n ipAddress: clientIp,\n source: 'onboarding',\n })\n // Wrap the request-em consent write in a transaction so it commits\n // all-or-nothing. setupInitialTenant already manages its own internal\n // transactions, and the seedDefaults hooks below resolve container\n // services that fork their own EM (and may enqueue work), so neither can\n // join this transaction \u2014 only the direct request-em writes are wrapped.\n await em.transactional(async (txEm) => {\n txEm.create(UserConsent, {\n userId: resolvedUserId,\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n consentType: 'marketing_email',\n isGranted: true,\n grantedAt: now,\n source: 'onboarding',\n ipAddress: clientIp,\n integrityHash,\n createdAt: now,\n })\n })\n }\n\n // Call module seedDefaults + seedExamples hooks\n const modules = getModules()\n for (const mod of modules) {\n if (mod.setup?.seedDefaults) {\n await runModuleSetupHook({\n moduleId: mod.id,\n phase: 'seedDefaults',\n timeoutMs: SEED_DEFAULTS_TIMEOUT_MS,\n run: () => mod.setup!.seedDefaults!({\n em,\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n container,\n }),\n })\n }\n }\n await service.markCompleted(request, {\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n userId: resolvedUserId,\n })\n // TODO: Move deferred provisioning into a durable job keyed by request id so process restarts can resume\n // seedExamples/index rebuild/email dispatch instead of leaving completed requests stuck on preparing.\n after(async () => {\n await runDeferredProvisioning({\n requestId: request.id,\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n })\n })\n return redirectToPreparing(baseUrl, resolvedTenantId)\n } catch (error) {\n if (error instanceof Error && error.message === 'USER_EXISTS') {\n await service.resetProcessing(request)\n return redirectWithStatus(baseUrl, 'already_exists')\n }\n console.error('[onboarding.verify] failed', error)\n await service.resetProcessing(request)\n return redirectWithStatus(baseUrl, 'error')\n }\n}\n\nexport default GET\n\nconst onboardingTag = 'Onboarding'\n\nconst onboardingVerifyQuerySchema = z.object({\n token: onboardingVerifySchema.shape.token,\n})\n\nconst onboardingVerifyDoc: OpenApiMethodDoc = {\n summary: 'Verify onboarding token',\n description: 'Validates the onboarding token, provisions the tenant, seeds demo data, and redirects the user to the login screen.',\n tags: [onboardingTag],\n query: onboardingVerifyQuerySchema,\n responses: [\n { status: 302, description: 'Redirect to onboarding UI or login' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: onboardingTag,\n summary: 'Onboarding verification redirect',\n methods: {\n GET: onboardingVerifyDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { after } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { SearchIndexer } from '@open-mercato/search'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n AppOriginConfigurationError,\n AppOriginRejectedError,\n getSecurityEmailBaseUrl,\n} from '@open-mercato/shared/lib/url'\nimport { onboardingVerifySchema } from '@open-mercato/onboarding/modules/onboarding/data/validators'\nimport { OnboardingService } from '@open-mercato/onboarding/modules/onboarding/lib/service'\nimport { sendWorkspaceReadyEmail } from '@open-mercato/onboarding/modules/onboarding/lib/ready-email'\nimport {\n redirectToLogin,\n redirectToPreparing,\n redirectWithStatus,\n} from '@open-mercato/onboarding/modules/onboarding/lib/verify-redirects'\nimport { setupInitialTenant } from '@open-mercato/core/modules/auth/lib/setup-app'\nimport { UserConsent } from '@open-mercato/core/modules/auth/data/entities'\nimport { computeConsentIntegrityHash } from '@open-mercato/core/modules/auth/lib/consentIntegrity'\nimport { resolveConsentClientIp } from '@open-mercato/onboarding/modules/onboarding/lib/consentClientIp'\nimport { reindexEntity } from '@open-mercato/core/modules/query_index/lib/reindexer'\nimport { purgeIndexScope } from '@open-mercato/core/modules/query_index/lib/purge'\nimport { refreshCoverageSnapshot } from '@open-mercato/core/modules/query_index/lib/coverage'\nimport { flattenSystemEntityIds } from '@open-mercato/shared/lib/entities/system-entities'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { getModules } from '@open-mercato/shared/lib/modules/registry'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n path: '/onboarding/onboarding/verify',\n GET: {\n requireAuth: false,\n },\n}\n\nfunction resolveTrustedBaseUrl(req: Request): string {\n try {\n return getSecurityEmailBaseUrl(req)\n } catch (error) {\n if (error instanceof AppOriginRejectedError || error instanceof AppOriginConfigurationError) {\n console.error('[onboarding.verify] rejected request origin for redirect base', {\n requestUrl: req.url,\n reason: error.message,\n })\n return new URL(req.url).origin\n }\n throw error\n }\n}\n\nconst VECTOR_REINDEX_ENQUEUE_TIMEOUT_MS = 5_000\nconst SEED_DEFAULTS_TIMEOUT_MS = 15_000\nconst SEED_EXAMPLES_TIMEOUT_MS = 15_000\n\nfunction createTimeoutPromise(label: string, timeoutMs: number): Promise<never> {\n return new Promise((_, reject) => {\n setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)\n })\n}\n\nasync function runModuleSetupHook(args: {\n moduleId: string\n phase: 'seedDefaults' | 'seedExamples'\n timeoutMs: number\n run: () => Promise<void>\n}) {\n const startedAt = Date.now()\n console.info('[onboarding.verify] module hook started', {\n moduleId: args.moduleId,\n phase: args.phase,\n timeoutMs: args.timeoutMs,\n })\n try {\n await Promise.race([\n args.run(),\n createTimeoutPromise(`module ${args.moduleId} ${args.phase}`, args.timeoutMs),\n ])\n console.info('[onboarding.verify] module hook completed', {\n moduleId: args.moduleId,\n phase: args.phase,\n durationMs: Math.max(0, Date.now() - startedAt),\n })\n } catch (error) {\n console.error('[onboarding.verify] module hook failed', {\n moduleId: args.moduleId,\n phase: args.phase,\n durationMs: Math.max(0, Date.now() - startedAt),\n timeoutMs: args.timeoutMs,\n error,\n })\n throw error\n }\n}\n\nasync function markWorkspaceReady(args: {\n requestId: string\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.findById(args.requestId)\n if (!request || request.preparationCompletedAt) return\n await service.markPreparationCompleted(request, new Date())\n}\n\nasync function enqueueVectorReindex(args: {\n container: { resolve: <T = unknown>(name: string) => T }\n tenantId: string\n organizationId: string\n}) {\n let searchIndexer: SearchIndexer | null = null\n try {\n searchIndexer = args.container.resolve<SearchIndexer>('searchIndexer')\n } catch {\n searchIndexer = null\n }\n if (!searchIndexer) return\n\n await Promise.race([\n searchIndexer.reindexAllToVector({\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n purgeFirst: true,\n useQueue: true,\n }),\n createTimeoutPromise('vector reindex enqueue', VECTOR_REINDEX_ENQUEUE_TIMEOUT_MS),\n ])\n}\n\nasync function rebuildTenantQueryIndexes(args: {\n em: EntityManager\n tenantId: string\n organizationId: string\n}) {\n const coverageRefreshKeys = new Set<string>()\n try {\n const allEntities = getEntityIds()\n const entityIds = flattenSystemEntityIds(allEntities)\n for (const entityType of entityIds) {\n try {\n await purgeIndexScope(args.em, { entityType, tenantId: args.tenantId })\n } catch (error) {\n console.error('[onboarding.verify] failed to purge query index scope', {\n entityType,\n tenantId: args.tenantId,\n error,\n })\n }\n try {\n await reindexEntity(args.em, {\n entityType,\n tenantId: args.tenantId,\n force: true,\n emitVectorizeEvents: false,\n vectorService: null,\n })\n } catch (error) {\n console.error('[onboarding.verify] failed to reindex entity', {\n entityType,\n tenantId: args.tenantId,\n error,\n })\n }\n coverageRefreshKeys.add(`${entityType}|${args.tenantId}|__null__`)\n coverageRefreshKeys.add(`${entityType}|${args.tenantId}|${args.organizationId}`)\n }\n } catch (error) {\n console.error('[onboarding.verify] failed to rebuild query indexes', { tenantId: args.tenantId, error })\n }\n\n if (!coverageRefreshKeys.size) return\n\n for (const entry of coverageRefreshKeys) {\n const [entityType, tenantKey, orgKey] = entry.split('|')\n const orgScope = orgKey === '__null__' ? null : orgKey\n try {\n await refreshCoverageSnapshot(\n args.em,\n {\n entityType,\n tenantId: tenantKey,\n organizationId: orgScope,\n withDeleted: false,\n },\n )\n } catch (error) {\n console.error('[onboarding.verify] failed to refresh coverage snapshot', {\n entityType,\n tenantId: tenantKey,\n organizationId: orgScope,\n error,\n })\n }\n }\n}\n\nasync function runDeferredProvisioning(args: {\n requestId: string\n tenantId: string\n organizationId: string\n}) {\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const modules = getModules()\n\n for (const mod of modules) {\n if (!mod.setup?.seedExamples) continue\n try {\n await runModuleSetupHook({\n moduleId: mod.id,\n phase: 'seedExamples',\n timeoutMs: SEED_EXAMPLES_TIMEOUT_MS,\n run: () => mod.setup!.seedExamples!({\n em,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n container,\n }),\n })\n } catch (error) {\n console.error('[onboarding.verify] deferred seedExamples failed', {\n moduleId: mod.id,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n error,\n })\n }\n }\n\n await markWorkspaceReady({\n requestId: args.requestId,\n })\n\n await sendWorkspaceReadyEmail({\n requestId: args.requestId,\n tenantId: args.tenantId,\n }).catch((error) => {\n console.error('[onboarding.verify] ready email failed', {\n requestId: args.requestId,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n error,\n })\n throw error\n })\n\n await rebuildTenantQueryIndexes({\n em,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n })\n\n await enqueueVectorReindex({\n container,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n }).catch((error) => {\n console.warn('[onboarding.verify] vector reindex enqueue did not complete promptly', {\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n reason: error instanceof Error ? error.message : String(error),\n })\n })\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const baseUrl = resolveTrustedBaseUrl(req)\n const token = url.searchParams.get('token') ?? ''\n const parsed = onboardingVerifySchema.safeParse({ token })\n if (!parsed.success) {\n return redirectWithStatus(baseUrl, 'invalid')\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.findByToken(parsed.data.token)\n if (!request) {\n return redirectWithStatus(baseUrl, 'invalid')\n }\n if (request.expiresAt <= new Date() && request.status !== 'completed') {\n return redirectWithStatus(baseUrl, 'invalid')\n }\n if (request.status === 'completed' && request.tenantId) {\n if (!request.preparationCompletedAt) {\n return redirectToPreparing(baseUrl, request.tenantId)\n }\n if (!request.readyEmailSentAt) {\n after(async () => {\n await sendWorkspaceReadyEmail({\n requestId: request.id,\n tenantId: request.tenantId!,\n }).catch((error) => {\n console.error('[onboarding.verify] retry ready email failed', {\n requestId: request.id,\n tenantId: request.tenantId,\n organizationId: request.organizationId,\n error,\n })\n })\n })\n }\n return redirectToLogin(baseUrl, request.tenantId)\n }\n const lockWindowMs = 15 * 60 * 1000\n const processingStartedAt = request.processingStartedAt?.getTime() ?? 0\n const processingFresh = request.status === 'processing' && processingStartedAt > Date.now() - lockWindowMs\n if (processingFresh) {\n return redirectToPreparing(baseUrl, request.tenantId ?? null)\n }\n if (request.status === 'processing' && !processingFresh) {\n await service.resetProcessing(request)\n }\n if (request.status !== 'pending') {\n return redirectWithStatus(baseUrl, 'invalid')\n }\n const claimed = await service.startProcessing(request, new Date())\n if (!claimed) {\n // A concurrent verify request already claimed this token (pending \u2192 processing).\n // Re-read on a fresh fork \u2014 the request EM's identity map still holds the stale\n // pre-claim copy \u2014 and route off the winner's committed state instead of re-running\n // provisioning, which would throw USER_EXISTS and could strand the request (#2742).\n const current = await new OnboardingService(em.fork()).findById(request.id)\n if (current?.status === 'completed' && current.tenantId) {\n return current.preparationCompletedAt\n ? redirectToLogin(baseUrl, current.tenantId)\n : redirectToPreparing(baseUrl, current.tenantId)\n }\n return redirectToPreparing(baseUrl, current?.tenantId ?? request.tenantId ?? null)\n }\n if (!request.passwordHash) {\n console.error('[onboarding.verify] missing password hash for request', request.id)\n await service.resetProcessing(request)\n return redirectWithStatus(baseUrl, 'error')\n }\n\n let tenantId: string | null = null\n let organizationId: string | null = null\n let userId: string | null = null\n\n try {\n const setupResult = await setupInitialTenant(em, {\n orgName: request.organizationName,\n includeDerivedUsers: false,\n failIfUserExists: true,\n primaryUserRoles: ['admin'],\n includeSuperadminRole: false,\n primaryUser: {\n email: request.email,\n firstName: request.firstName,\n lastName: request.lastName,\n displayName: `${request.firstName} ${request.lastName}`.trim(),\n hashedPassword: request.passwordHash,\n confirm: true,\n },\n modules: getModules(),\n })\n\n const resolvedTenantId = String(setupResult.tenantId)\n const resolvedOrganizationId = String(setupResult.organizationId)\n tenantId = resolvedTenantId\n organizationId = resolvedOrganizationId\n\n const mainUserSnapshot = setupResult.users.find((entry) => entry.user.email === request.email)\n if (!mainUserSnapshot) throw new Error('USER_NOT_CREATED')\n const user = mainUserSnapshot.user\n const resolvedUserId = String(user.id)\n userId = resolvedUserId\n await service.updateProvisioningIds(request, {\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n userId: resolvedUserId,\n })\n\n if (request.marketingConsent) {\n const now = new Date()\n const clientIp = resolveConsentClientIp(req)\n const integrityHash = computeConsentIntegrityHash({\n userId: resolvedUserId,\n consentType: 'marketing_email',\n isGranted: true,\n grantedAt: now,\n ipAddress: clientIp,\n source: 'onboarding',\n })\n // Wrap the request-em consent write in a transaction so it commits\n // all-or-nothing. setupInitialTenant already manages its own internal\n // transactions, and the seedDefaults hooks below resolve container\n // services that fork their own EM (and may enqueue work), so neither can\n // join this transaction \u2014 only the direct request-em writes are wrapped.\n await em.transactional(async (txEm) => {\n txEm.create(UserConsent, {\n userId: resolvedUserId,\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n consentType: 'marketing_email',\n isGranted: true,\n grantedAt: now,\n source: 'onboarding',\n ipAddress: clientIp,\n integrityHash,\n createdAt: now,\n })\n })\n }\n\n // Call module seedDefaults + seedExamples hooks\n const modules = getModules()\n for (const mod of modules) {\n if (mod.setup?.seedDefaults) {\n await runModuleSetupHook({\n moduleId: mod.id,\n phase: 'seedDefaults',\n timeoutMs: SEED_DEFAULTS_TIMEOUT_MS,\n run: () => mod.setup!.seedDefaults!({\n em,\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n container,\n }),\n })\n }\n }\n await service.markCompleted(request, {\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n userId: resolvedUserId,\n })\n // TODO: Move deferred provisioning into a durable job keyed by request id so process restarts can resume\n // seedExamples/index rebuild/email dispatch instead of leaving completed requests stuck on preparing.\n after(async () => {\n await runDeferredProvisioning({\n requestId: request.id,\n tenantId: resolvedTenantId,\n organizationId: resolvedOrganizationId,\n })\n })\n return redirectToPreparing(baseUrl, resolvedTenantId)\n } catch (error) {\n if (error instanceof Error && error.message === 'USER_EXISTS') {\n await service.resetProcessing(request)\n return redirectWithStatus(baseUrl, 'already_exists')\n }\n console.error('[onboarding.verify] failed', error)\n await service.resetProcessing(request)\n return redirectWithStatus(baseUrl, 'error')\n }\n}\n\nexport default GET\n\nconst onboardingTag = 'Onboarding'\n\nconst onboardingVerifyQuerySchema = z.object({\n token: onboardingVerifySchema.shape.token,\n})\n\nconst onboardingVerifyDoc: OpenApiMethodDoc = {\n summary: 'Verify onboarding token',\n description: 'Validates the onboarding token, provisions the tenant, seeds demo data, and redirects the user to the login screen.',\n tags: [onboardingTag],\n query: onboardingVerifyQuerySchema,\n responses: [\n { status: 302, description: 'Redirect to onboarding UI or login' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: onboardingTag,\n summary: 'Onboarding verification redirect',\n methods: {\n GET: onboardingVerifyDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,43 @@
1
+ import { NextResponse } from "next/server";
2
+ const TENANT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 14;
3
+ function clearAuthCookies(response) {
4
+ response.cookies.set("auth_token", "", { path: "/", maxAge: 0 });
5
+ response.cookies.set("session_token", "", { path: "/", maxAge: 0 });
6
+ response.cookies.set("om_login_tenant", "", { path: "/", maxAge: 0 });
7
+ }
8
+ function setTenantCookie(response, tenantId) {
9
+ response.cookies.set("om_login_tenant", tenantId, {
10
+ httpOnly: false,
11
+ sameSite: "lax",
12
+ secure: process.env.NODE_ENV === "production",
13
+ path: "/",
14
+ maxAge: TENANT_COOKIE_MAX_AGE_SECONDS
15
+ });
16
+ }
17
+ function redirectWithStatus(baseUrl, status) {
18
+ return NextResponse.redirect(`${baseUrl}/onboarding?status=${encodeURIComponent(status)}`);
19
+ }
20
+ function redirectToPreparing(baseUrl, tenantId) {
21
+ const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : "";
22
+ const response = NextResponse.redirect(`${baseUrl}/onboarding/preparing${tenantParam}`);
23
+ clearAuthCookies(response);
24
+ if (tenantId) {
25
+ setTenantCookie(response, tenantId);
26
+ }
27
+ return response;
28
+ }
29
+ function redirectToLogin(baseUrl, tenantId) {
30
+ const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : "";
31
+ const response = NextResponse.redirect(`${baseUrl}/login${tenantParam}`);
32
+ clearAuthCookies(response);
33
+ if (tenantId) {
34
+ setTenantCookie(response, tenantId);
35
+ }
36
+ return response;
37
+ }
38
+ export {
39
+ redirectToLogin,
40
+ redirectToPreparing,
41
+ redirectWithStatus
42
+ };
43
+ //# sourceMappingURL=verify-redirects.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/onboarding/lib/verify-redirects.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\n\nconst TENANT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 14\n\nfunction clearAuthCookies(response: NextResponse) {\n response.cookies.set('auth_token', '', { path: '/', maxAge: 0 })\n response.cookies.set('session_token', '', { path: '/', maxAge: 0 })\n response.cookies.set('om_login_tenant', '', { path: '/', maxAge: 0 })\n}\n\nfunction setTenantCookie(response: NextResponse, tenantId: string) {\n response.cookies.set('om_login_tenant', tenantId, {\n httpOnly: false,\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n maxAge: TENANT_COOKIE_MAX_AGE_SECONDS,\n })\n}\n\nexport function redirectWithStatus(baseUrl: string, status: string) {\n return NextResponse.redirect(`${baseUrl}/onboarding?status=${encodeURIComponent(status)}`)\n}\n\nexport function redirectToPreparing(baseUrl: string, tenantId: string | null) {\n const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : ''\n const response = NextResponse.redirect(`${baseUrl}/onboarding/preparing${tenantParam}`)\n clearAuthCookies(response)\n if (tenantId) {\n setTenantCookie(response, tenantId)\n }\n return response\n}\n\nexport function redirectToLogin(baseUrl: string, tenantId: string | null) {\n const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : ''\n const response = NextResponse.redirect(`${baseUrl}/login${tenantParam}`)\n clearAuthCookies(response)\n if (tenantId) {\n setTenantCookie(response, tenantId)\n }\n return response\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,MAAM,gCAAgC,KAAK,KAAK,KAAK;AAErD,SAAS,iBAAiB,UAAwB;AAChD,WAAS,QAAQ,IAAI,cAAc,IAAI,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;AAC/D,WAAS,QAAQ,IAAI,iBAAiB,IAAI,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;AAClE,WAAS,QAAQ,IAAI,mBAAmB,IAAI,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;AACtE;AAEA,SAAS,gBAAgB,UAAwB,UAAkB;AACjE,WAAS,QAAQ,IAAI,mBAAmB,UAAU;AAAA,IAChD,UAAU;AAAA,IACV,UAAU;AAAA,IACV,QAAQ,QAAQ,IAAI,aAAa;AAAA,IACjC,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACH;AAEO,SAAS,mBAAmB,SAAiB,QAAgB;AAClE,SAAO,aAAa,SAAS,GAAG,OAAO,sBAAsB,mBAAmB,MAAM,CAAC,EAAE;AAC3F;AAEO,SAAS,oBAAoB,SAAiB,UAAyB;AAC5E,QAAM,cAAc,WAAW,WAAW,mBAAmB,QAAQ,CAAC,KAAK;AAC3E,QAAM,WAAW,aAAa,SAAS,GAAG,OAAO,wBAAwB,WAAW,EAAE;AACtF,mBAAiB,QAAQ;AACzB,MAAI,UAAU;AACZ,oBAAgB,UAAU,QAAQ;AAAA,EACpC;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,SAAiB,UAAyB;AACxE,QAAM,cAAc,WAAW,WAAW,mBAAmB,QAAQ,CAAC,KAAK;AAC3E,QAAM,WAAW,aAAa,SAAS,GAAG,OAAO,SAAS,WAAW,EAAE;AACvE,mBAAiB,QAAQ;AACzB,MAAI,UAAU;AACZ,oBAAgB,UAAU,QAAQ;AAAA,EACpC;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/onboarding",
3
- "version": "0.6.5-develop.5187.1.82e5532561",
3
+ "version": "0.6.5-develop.5212.1.b47932beef",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -69,10 +69,10 @@
69
69
  }
70
70
  },
71
71
  "peerDependencies": {
72
- "@open-mercato/shared": "0.6.5-develop.5187.1.82e5532561"
72
+ "@open-mercato/shared": "0.6.5-develop.5212.1.b47932beef"
73
73
  },
74
74
  "devDependencies": {
75
- "@open-mercato/shared": "0.6.5-develop.5187.1.82e5532561",
75
+ "@open-mercato/shared": "0.6.5-develop.5212.1.b47932beef",
76
76
  "@types/jest": "^30.0.0",
77
77
  "jest": "^30.4.2",
78
78
  "ts-jest": "^29.4.11"
@@ -0,0 +1,75 @@
1
+ /** @jest-environment node */
2
+ import {
3
+ redirectToLogin,
4
+ redirectToPreparing,
5
+ redirectWithStatus,
6
+ } from '../modules/onboarding/lib/verify-redirects'
7
+
8
+ const AUTH_COOKIES = ['auth_token', 'session_token', 'om_login_tenant'] as const
9
+
10
+ function getCookie(response: ReturnType<typeof redirectWithStatus>, name: string) {
11
+ return response.cookies.get(name)
12
+ }
13
+
14
+ describe('onboarding verify redirect helpers', () => {
15
+ describe('redirectWithStatus (validation / error paths)', () => {
16
+ it('does NOT clear auth cookies on any status path (logout CSRF guard)', () => {
17
+ for (const status of ['invalid', 'error', 'already_exists']) {
18
+ const response = redirectWithStatus('https://app.example.com', status)
19
+ for (const cookieName of AUTH_COOKIES) {
20
+ expect(getCookie(response, cookieName)).toBeUndefined()
21
+ }
22
+ }
23
+ })
24
+
25
+ it('redirects to the onboarding status URL', () => {
26
+ const response = redirectWithStatus('https://app.example.com', 'invalid')
27
+ expect(response.headers.get('location')).toBe(
28
+ 'https://app.example.com/onboarding?status=invalid',
29
+ )
30
+ })
31
+
32
+ it('does not mutate cookies even for an attacker-supplied error path', () => {
33
+ const response = redirectWithStatus('https://app.example.com', 'error')
34
+ expect(response.cookies.getAll()).toHaveLength(0)
35
+ })
36
+ })
37
+
38
+ describe('redirectToPreparing (success transition)', () => {
39
+ it('clears auth_token and session_token', () => {
40
+ const response = redirectToPreparing('https://app.example.com', 'tenant-123')
41
+ expect(getCookie(response, 'auth_token')?.value).toBe('')
42
+ expect(getCookie(response, 'auth_token')?.maxAge).toBe(0)
43
+ expect(getCookie(response, 'session_token')?.value).toBe('')
44
+ expect(getCookie(response, 'session_token')?.maxAge).toBe(0)
45
+ })
46
+
47
+ it('sets om_login_tenant to the provisioned tenant id', () => {
48
+ const response = redirectToPreparing('https://app.example.com', 'tenant-123')
49
+ const tenantCookie = getCookie(response, 'om_login_tenant')
50
+ expect(tenantCookie?.value).toBe('tenant-123')
51
+ expect(tenantCookie?.maxAge).toBeGreaterThan(0)
52
+ })
53
+
54
+ it('clears om_login_tenant when no tenant id is provided', () => {
55
+ const response = redirectToPreparing('https://app.example.com', null)
56
+ expect(getCookie(response, 'om_login_tenant')?.value).toBe('')
57
+ expect(getCookie(response, 'om_login_tenant')?.maxAge).toBe(0)
58
+ })
59
+ })
60
+
61
+ describe('redirectToLogin (success transition)', () => {
62
+ it('clears auth_token and session_token', () => {
63
+ const response = redirectToLogin('https://app.example.com', 'tenant-123')
64
+ expect(getCookie(response, 'auth_token')?.value).toBe('')
65
+ expect(getCookie(response, 'auth_token')?.maxAge).toBe(0)
66
+ expect(getCookie(response, 'session_token')?.value).toBe('')
67
+ expect(getCookie(response, 'session_token')?.maxAge).toBe(0)
68
+ })
69
+
70
+ it('sets om_login_tenant to the provisioned tenant id', () => {
71
+ const response = redirectToLogin('https://app.example.com', 'tenant-123')
72
+ expect(getCookie(response, 'om_login_tenant')?.value).toBe('tenant-123')
73
+ })
74
+ })
75
+ })
@@ -1,4 +1,4 @@
1
- import { after, NextResponse } from 'next/server'
1
+ import { after } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import type { EntityManager } from '@mikro-orm/postgresql'
4
4
  import type { SearchIndexer } from '@open-mercato/search'
@@ -11,6 +11,11 @@ import {
11
11
  import { onboardingVerifySchema } from '@open-mercato/onboarding/modules/onboarding/data/validators'
12
12
  import { OnboardingService } from '@open-mercato/onboarding/modules/onboarding/lib/service'
13
13
  import { sendWorkspaceReadyEmail } from '@open-mercato/onboarding/modules/onboarding/lib/ready-email'
14
+ import {
15
+ redirectToLogin,
16
+ redirectToPreparing,
17
+ redirectWithStatus,
18
+ } from '@open-mercato/onboarding/modules/onboarding/lib/verify-redirects'
14
19
  import { setupInitialTenant } from '@open-mercato/core/modules/auth/lib/setup-app'
15
20
  import { UserConsent } from '@open-mercato/core/modules/auth/data/entities'
16
21
  import { computeConsentIntegrityHash } from '@open-mercato/core/modules/auth/lib/consentIntegrity'
@@ -45,50 +50,6 @@ function resolveTrustedBaseUrl(req: Request): string {
45
50
  }
46
51
  }
47
52
 
48
- function clearAuthCookies(response: NextResponse) {
49
- response.cookies.set('auth_token', '', { path: '/', maxAge: 0 })
50
- response.cookies.set('session_token', '', { path: '/', maxAge: 0 })
51
- response.cookies.set('om_login_tenant', '', { path: '/', maxAge: 0 })
52
- }
53
-
54
- function redirectWithStatus(baseUrl: string, status: string) {
55
- const response = NextResponse.redirect(`${baseUrl}/onboarding?status=${encodeURIComponent(status)}`)
56
- clearAuthCookies(response)
57
- return response
58
- }
59
-
60
- function redirectToPreparing(baseUrl: string, tenantId: string | null) {
61
- const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : ''
62
- const response = NextResponse.redirect(`${baseUrl}/onboarding/preparing${tenantParam}`)
63
- clearAuthCookies(response)
64
- if (tenantId) {
65
- response.cookies.set('om_login_tenant', tenantId, {
66
- httpOnly: false,
67
- sameSite: 'lax',
68
- secure: process.env.NODE_ENV === 'production',
69
- path: '/',
70
- maxAge: 60 * 60 * 24 * 14,
71
- })
72
- }
73
- return response
74
- }
75
-
76
- function redirectToLogin(baseUrl: string, tenantId: string | null) {
77
- const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : ''
78
- const response = NextResponse.redirect(`${baseUrl}/login${tenantParam}`)
79
- clearAuthCookies(response)
80
- if (tenantId) {
81
- response.cookies.set('om_login_tenant', tenantId, {
82
- httpOnly: false,
83
- sameSite: 'lax',
84
- secure: process.env.NODE_ENV === 'production',
85
- path: '/',
86
- maxAge: 60 * 60 * 24 * 14,
87
- })
88
- }
89
- return response
90
- }
91
-
92
53
  const VECTOR_REINDEX_ENQUEUE_TIMEOUT_MS = 5_000
93
54
  const SEED_DEFAULTS_TIMEOUT_MS = 15_000
94
55
  const SEED_EXAMPLES_TIMEOUT_MS = 15_000
@@ -0,0 +1,43 @@
1
+ import { NextResponse } from 'next/server'
2
+
3
+ const TENANT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 14
4
+
5
+ function clearAuthCookies(response: NextResponse) {
6
+ response.cookies.set('auth_token', '', { path: '/', maxAge: 0 })
7
+ response.cookies.set('session_token', '', { path: '/', maxAge: 0 })
8
+ response.cookies.set('om_login_tenant', '', { path: '/', maxAge: 0 })
9
+ }
10
+
11
+ function setTenantCookie(response: NextResponse, tenantId: string) {
12
+ response.cookies.set('om_login_tenant', tenantId, {
13
+ httpOnly: false,
14
+ sameSite: 'lax',
15
+ secure: process.env.NODE_ENV === 'production',
16
+ path: '/',
17
+ maxAge: TENANT_COOKIE_MAX_AGE_SECONDS,
18
+ })
19
+ }
20
+
21
+ export function redirectWithStatus(baseUrl: string, status: string) {
22
+ return NextResponse.redirect(`${baseUrl}/onboarding?status=${encodeURIComponent(status)}`)
23
+ }
24
+
25
+ export function redirectToPreparing(baseUrl: string, tenantId: string | null) {
26
+ const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : ''
27
+ const response = NextResponse.redirect(`${baseUrl}/onboarding/preparing${tenantParam}`)
28
+ clearAuthCookies(response)
29
+ if (tenantId) {
30
+ setTenantCookie(response, tenantId)
31
+ }
32
+ return response
33
+ }
34
+
35
+ export function redirectToLogin(baseUrl: string, tenantId: string | null) {
36
+ const tenantParam = tenantId ? `?tenant=${encodeURIComponent(tenantId)}` : ''
37
+ const response = NextResponse.redirect(`${baseUrl}/login${tenantParam}`)
38
+ clearAuthCookies(response)
39
+ if (tenantId) {
40
+ setTenantCookie(response, tenantId)
41
+ }
42
+ return response
43
+ }