@voyant-travel/auth 0.136.0 → 0.138.0
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.
- package/dist/customer-business-accounts-contracts.d.ts +159 -0
- package/dist/customer-business-accounts-contracts.js +112 -0
- package/dist/customer-business-accounts-contracts.js.map +1 -0
- package/dist/customer-business-onboarding-graph-runtime.d.ts +13 -0
- package/dist/customer-business-onboarding-graph-runtime.js +8 -0
- package/dist/customer-business-onboarding-graph-runtime.js.map +1 -0
- package/dist/customer-business-onboarding-routes.d.ts +13 -0
- package/dist/customer-business-onboarding-routes.js +151 -0
- package/dist/customer-business-onboarding-routes.js.map +1 -0
- package/dist/customer-business-onboarding-runtime-port.d.ts +40 -0
- package/dist/customer-business-onboarding-runtime-port.js +25 -0
- package/dist/customer-business-onboarding-runtime-port.js.map +1 -0
- package/dist/customer-business-onboarding-service.d.ts +32 -0
- package/dist/customer-business-onboarding-service.js +237 -0
- package/dist/customer-business-onboarding-service.js.map +1 -0
- package/dist/customer-buyer-accounts.d.ts +0 -19
- package/dist/customer-buyer-accounts.js.map +1 -1
- package/dist/node-runtime.d.ts +13 -0
- package/dist/node-runtime.js +285 -1
- package/dist/node-runtime.js.map +1 -1
- package/dist/ports.d.ts +1 -0
- package/dist/ports.js +2 -0
- package/dist/ports.js.map +1 -0
- package/dist/runtime-contributor.js +11 -0
- package/dist/runtime-contributor.js.map +1 -1
- package/dist/server.d.ts +39 -3
- package/dist/server.js.map +1 -1
- package/dist/storefront-credentials.d.ts +31 -0
- package/dist/storefront-credentials.js +54 -0
- package/dist/storefront-credentials.js.map +1 -0
- package/dist/storefront-customer-auth-resolver.d.ts +30 -0
- package/dist/storefront-customer-auth-resolver.js +72 -0
- package/dist/storefront-customer-auth-resolver.js.map +1 -0
- package/dist/storefront-keys.d.ts +40 -0
- package/dist/storefront-keys.js +50 -0
- package/dist/storefront-keys.js.map +1 -0
- package/dist/storefront-local-adapter.d.ts +11 -0
- package/dist/storefront-local-adapter.js +313 -0
- package/dist/storefront-local-adapter.js.map +1 -0
- package/dist/storefront-origins.d.ts +34 -0
- package/dist/storefront-origins.js +132 -0
- package/dist/storefront-origins.js.map +1 -0
- package/dist/storefront-runtime-port.d.ts +93 -0
- package/dist/storefront-runtime-port.js +35 -0
- package/dist/storefront-runtime-port.js.map +1 -0
- package/dist/voyant.d.ts +2 -0
- package/dist/voyant.js +99 -0
- package/dist/voyant.js.map +1 -1
- package/openapi/admin/customer-business-accounts.json +44 -0
- package/package.json +50 -4
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { customerAuthBusinessAccountRequest, customerAuthMember, customerAuthOrganization, customerAuthUser, } from "@voyant-travel/db/schema/iam";
|
|
2
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
3
|
+
import { customerBusinessAccountRequestSchema, customerBusinessAccountSchema, customerBusinessProfileSchema, } from "./customer-business-accounts-contracts.js";
|
|
4
|
+
import { businessBuyerAccountId } from "./customer-buyer-accounts.js";
|
|
5
|
+
export class CustomerBusinessOnboardingConflictError extends Error {
|
|
6
|
+
}
|
|
7
|
+
export class CustomerBusinessOnboardingNotFoundError extends Error {
|
|
8
|
+
}
|
|
9
|
+
function opaqueId() {
|
|
10
|
+
return crypto.randomUUID();
|
|
11
|
+
}
|
|
12
|
+
function slugBase(name) {
|
|
13
|
+
return (name
|
|
14
|
+
.normalize("NFKD")
|
|
15
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
18
|
+
.replace(/^-|-$/g, "")
|
|
19
|
+
.slice(0, 64) || "business");
|
|
20
|
+
}
|
|
21
|
+
export async function materializeCustomerBusinessAccount(db, input) {
|
|
22
|
+
const [owner] = await db
|
|
23
|
+
.select({ id: customerAuthUser.id })
|
|
24
|
+
.from(customerAuthUser)
|
|
25
|
+
.where(eq(customerAuthUser.id, input.ownerUserId))
|
|
26
|
+
.limit(1);
|
|
27
|
+
if (!owner) {
|
|
28
|
+
throw new CustomerBusinessOnboardingNotFoundError("Customer owner does not exist");
|
|
29
|
+
}
|
|
30
|
+
let [organization] = await db
|
|
31
|
+
.select()
|
|
32
|
+
.from(customerAuthOrganization)
|
|
33
|
+
.where(eq(customerAuthOrganization.relationshipOrganizationId, input.relationshipOrganizationId))
|
|
34
|
+
.limit(1);
|
|
35
|
+
for (let attempt = 0; !organization && attempt < 8; attempt += 1) {
|
|
36
|
+
const suffix = opaqueId().replaceAll("-", "").slice(0, 10);
|
|
37
|
+
const [created] = await db
|
|
38
|
+
.insert(customerAuthOrganization)
|
|
39
|
+
.values({
|
|
40
|
+
id: opaqueId(),
|
|
41
|
+
name: input.name,
|
|
42
|
+
slug: `${slugBase(input.name).slice(0, 52)}-${suffix}`,
|
|
43
|
+
relationshipOrganizationId: input.relationshipOrganizationId,
|
|
44
|
+
createdAt: new Date(),
|
|
45
|
+
})
|
|
46
|
+
.onConflictDoNothing()
|
|
47
|
+
.returning();
|
|
48
|
+
organization = created;
|
|
49
|
+
if (!organization) {
|
|
50
|
+
;
|
|
51
|
+
[organization] = await db
|
|
52
|
+
.select()
|
|
53
|
+
.from(customerAuthOrganization)
|
|
54
|
+
.where(eq(customerAuthOrganization.relationshipOrganizationId, input.relationshipOrganizationId))
|
|
55
|
+
.limit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!organization) {
|
|
59
|
+
throw new CustomerBusinessOnboardingConflictError("Could not allocate a unique customer business organization slug");
|
|
60
|
+
}
|
|
61
|
+
const [membership] = await db
|
|
62
|
+
.insert(customerAuthMember)
|
|
63
|
+
.values({
|
|
64
|
+
id: opaqueId(),
|
|
65
|
+
userId: input.ownerUserId,
|
|
66
|
+
organizationId: organization.id,
|
|
67
|
+
role: "owner",
|
|
68
|
+
createdAt: new Date(),
|
|
69
|
+
})
|
|
70
|
+
.onConflictDoUpdate({
|
|
71
|
+
target: [customerAuthMember.userId, customerAuthMember.organizationId],
|
|
72
|
+
set: { role: "owner" },
|
|
73
|
+
})
|
|
74
|
+
.returning();
|
|
75
|
+
if (!membership) {
|
|
76
|
+
throw new CustomerBusinessOnboardingConflictError("Could not create the customer business owner membership");
|
|
77
|
+
}
|
|
78
|
+
return customerBusinessAccountSchema.parse({
|
|
79
|
+
id: businessBuyerAccountId(organization.id),
|
|
80
|
+
kind: "business",
|
|
81
|
+
name: organization.name,
|
|
82
|
+
authOrganizationId: organization.id,
|
|
83
|
+
relationshipOrganizationId: input.relationshipOrganizationId,
|
|
84
|
+
relationshipPersonId: null,
|
|
85
|
+
membershipId: membership.id,
|
|
86
|
+
membershipRole: membership.role,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function requestProfile(row) {
|
|
90
|
+
return customerBusinessProfileSchema.parse({
|
|
91
|
+
name: row.name,
|
|
92
|
+
legalName: row.legalName,
|
|
93
|
+
taxId: row.taxId,
|
|
94
|
+
website: row.website,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function requestDto(row, requester) {
|
|
98
|
+
return customerBusinessAccountRequestSchema.parse({
|
|
99
|
+
id: row.id,
|
|
100
|
+
requesterUserId: row.requesterUserId,
|
|
101
|
+
requesterEmail: requester.email,
|
|
102
|
+
requesterName: requester.name,
|
|
103
|
+
storefrontOrigin: row.storefrontOrigin,
|
|
104
|
+
mode: row.mode,
|
|
105
|
+
profile: requestProfile(row),
|
|
106
|
+
status: row.status,
|
|
107
|
+
idempotencyKey: row.idempotencyKey,
|
|
108
|
+
authOrganizationId: row.authOrganizationId,
|
|
109
|
+
relationshipOrganizationId: row.relationshipOrganizationId,
|
|
110
|
+
createdAt: row.createdAt.toISOString(),
|
|
111
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
112
|
+
decidedAt: row.decidedAt?.toISOString() ?? null,
|
|
113
|
+
decidedBy: row.decidedBy,
|
|
114
|
+
decisionReason: row.decisionReason,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function hydrateRequest(db, row) {
|
|
118
|
+
const [requester] = await db
|
|
119
|
+
.select({ email: customerAuthUser.email, name: customerAuthUser.name })
|
|
120
|
+
.from(customerAuthUser)
|
|
121
|
+
.where(eq(customerAuthUser.id, row.requesterUserId))
|
|
122
|
+
.limit(1);
|
|
123
|
+
return requestDto(row, requester ?? { email: null, name: null });
|
|
124
|
+
}
|
|
125
|
+
export async function createCustomerBusinessAccountRequest(db, input) {
|
|
126
|
+
const profile = customerBusinessProfileSchema.parse(input.profile);
|
|
127
|
+
const [existing] = await db
|
|
128
|
+
.select()
|
|
129
|
+
.from(customerAuthBusinessAccountRequest)
|
|
130
|
+
.where(and(eq(customerAuthBusinessAccountRequest.requesterUserId, input.requesterUserId), eq(customerAuthBusinessAccountRequest.idempotencyKey, input.idempotencyKey)))
|
|
131
|
+
.limit(1);
|
|
132
|
+
if (existing) {
|
|
133
|
+
const samePayload = existing.storefrontOrigin === input.storefrontOrigin &&
|
|
134
|
+
existing.mode === input.mode &&
|
|
135
|
+
existing.name === profile.name &&
|
|
136
|
+
existing.legalName === profile.legalName &&
|
|
137
|
+
existing.taxId === profile.taxId &&
|
|
138
|
+
existing.website === profile.website;
|
|
139
|
+
if (!samePayload) {
|
|
140
|
+
throw new CustomerBusinessOnboardingConflictError("Idempotency key was already used with a different business-account request");
|
|
141
|
+
}
|
|
142
|
+
return hydrateRequest(db, existing);
|
|
143
|
+
}
|
|
144
|
+
const [created] = await db
|
|
145
|
+
.insert(customerAuthBusinessAccountRequest)
|
|
146
|
+
.values({
|
|
147
|
+
id: opaqueId(),
|
|
148
|
+
requesterUserId: input.requesterUserId,
|
|
149
|
+
storefrontOrigin: input.storefrontOrigin,
|
|
150
|
+
mode: input.mode,
|
|
151
|
+
name: profile.name,
|
|
152
|
+
legalName: profile.legalName,
|
|
153
|
+
taxId: profile.taxId,
|
|
154
|
+
website: profile.website,
|
|
155
|
+
idempotencyKey: input.idempotencyKey,
|
|
156
|
+
})
|
|
157
|
+
.onConflictDoNothing()
|
|
158
|
+
.returning();
|
|
159
|
+
if (created)
|
|
160
|
+
return hydrateRequest(db, created);
|
|
161
|
+
const [racedIdempotent] = await db
|
|
162
|
+
.select()
|
|
163
|
+
.from(customerAuthBusinessAccountRequest)
|
|
164
|
+
.where(and(eq(customerAuthBusinessAccountRequest.requesterUserId, input.requesterUserId), eq(customerAuthBusinessAccountRequest.idempotencyKey, input.idempotencyKey)))
|
|
165
|
+
.limit(1);
|
|
166
|
+
if (racedIdempotent) {
|
|
167
|
+
const samePayload = racedIdempotent.storefrontOrigin === input.storefrontOrigin &&
|
|
168
|
+
racedIdempotent.mode === input.mode &&
|
|
169
|
+
racedIdempotent.name === profile.name &&
|
|
170
|
+
racedIdempotent.legalName === profile.legalName &&
|
|
171
|
+
racedIdempotent.taxId === profile.taxId &&
|
|
172
|
+
racedIdempotent.website === profile.website;
|
|
173
|
+
if (samePayload)
|
|
174
|
+
return hydrateRequest(db, racedIdempotent);
|
|
175
|
+
throw new CustomerBusinessOnboardingConflictError("Idempotency key was already used with a different business-account request");
|
|
176
|
+
}
|
|
177
|
+
const [pending] = await db
|
|
178
|
+
.select({ id: customerAuthBusinessAccountRequest.id })
|
|
179
|
+
.from(customerAuthBusinessAccountRequest)
|
|
180
|
+
.where(and(eq(customerAuthBusinessAccountRequest.requesterUserId, input.requesterUserId), eq(customerAuthBusinessAccountRequest.status, "pending")))
|
|
181
|
+
.limit(1);
|
|
182
|
+
if (pending) {
|
|
183
|
+
throw new CustomerBusinessOnboardingConflictError("Customer already has a pending business-account request");
|
|
184
|
+
}
|
|
185
|
+
throw new CustomerBusinessOnboardingConflictError("Business-account request could not be created");
|
|
186
|
+
}
|
|
187
|
+
export async function listCustomerBusinessAccountRequests(db, input) {
|
|
188
|
+
const conditions = [
|
|
189
|
+
...(input.requesterUserId
|
|
190
|
+
? [eq(customerAuthBusinessAccountRequest.requesterUserId, input.requesterUserId)]
|
|
191
|
+
: []),
|
|
192
|
+
...(input.status ? [eq(customerAuthBusinessAccountRequest.status, input.status)] : []),
|
|
193
|
+
];
|
|
194
|
+
const rows = await db
|
|
195
|
+
.select({
|
|
196
|
+
request: customerAuthBusinessAccountRequest,
|
|
197
|
+
requesterEmail: customerAuthUser.email,
|
|
198
|
+
requesterName: customerAuthUser.name,
|
|
199
|
+
})
|
|
200
|
+
.from(customerAuthBusinessAccountRequest)
|
|
201
|
+
.innerJoin(customerAuthUser, eq(customerAuthUser.id, customerAuthBusinessAccountRequest.requesterUserId))
|
|
202
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
203
|
+
.orderBy(desc(customerAuthBusinessAccountRequest.createdAt));
|
|
204
|
+
return rows.map((row) => requestDto(row.request, { email: row.requesterEmail, name: row.requesterName }));
|
|
205
|
+
}
|
|
206
|
+
export async function getCustomerBusinessAccountRequestForUpdate(db, requestId) {
|
|
207
|
+
const [row] = await db
|
|
208
|
+
.select()
|
|
209
|
+
.from(customerAuthBusinessAccountRequest)
|
|
210
|
+
.where(eq(customerAuthBusinessAccountRequest.id, requestId))
|
|
211
|
+
.for("update")
|
|
212
|
+
.limit(1);
|
|
213
|
+
if (!row)
|
|
214
|
+
throw new CustomerBusinessOnboardingNotFoundError("Business-account request not found");
|
|
215
|
+
return hydrateRequest(db, row);
|
|
216
|
+
}
|
|
217
|
+
export async function decideCustomerBusinessAccountRequest(db, input) {
|
|
218
|
+
const now = new Date();
|
|
219
|
+
const [updated] = await db
|
|
220
|
+
.update(customerAuthBusinessAccountRequest)
|
|
221
|
+
.set({
|
|
222
|
+
status: input.status,
|
|
223
|
+
updatedAt: now,
|
|
224
|
+
decidedAt: now,
|
|
225
|
+
decidedBy: input.decidedBy,
|
|
226
|
+
decisionReason: input.decisionReason ?? null,
|
|
227
|
+
authOrganizationId: input.authOrganizationId,
|
|
228
|
+
relationshipOrganizationId: input.relationshipOrganizationId,
|
|
229
|
+
})
|
|
230
|
+
.where(and(eq(customerAuthBusinessAccountRequest.id, input.requestId), eq(customerAuthBusinessAccountRequest.status, input.fromStatus)))
|
|
231
|
+
.returning();
|
|
232
|
+
if (!updated) {
|
|
233
|
+
throw new CustomerBusinessOnboardingConflictError("Business-account request is no longer pending");
|
|
234
|
+
}
|
|
235
|
+
return hydrateRequest(db, updated);
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=customer-business-onboarding-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customer-business-onboarding-service.js","sourceRoot":"","sources":["../src/customer-business-onboarding-service.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n customerAuthBusinessAccountRequest,\n customerAuthMember,\n customerAuthOrganization,\n customerAuthUser,\n} from \"@voyant-travel/db/schema/iam\"\nimport type { VoyantDb } from \"@voyant-travel/hono\"\nimport { and, desc, eq } from \"drizzle-orm\"\nimport {\n type CustomerBusinessAccountDto,\n type CustomerBusinessAccountRequestDto,\n type CustomerBusinessAccountRequestStatus,\n type CustomerBusinessOnboardingMode,\n type CustomerBusinessProfile,\n customerBusinessAccountRequestSchema,\n customerBusinessAccountSchema,\n customerBusinessProfileSchema,\n} from \"./customer-business-accounts-contracts.js\"\nimport { businessBuyerAccountId } from \"./customer-buyer-accounts.js\"\n\nexport class CustomerBusinessOnboardingConflictError extends Error {}\nexport class CustomerBusinessOnboardingNotFoundError extends Error {}\n\nfunction opaqueId(): string {\n return crypto.randomUUID()\n}\n\nfunction slugBase(name: string): string {\n return (\n name\n .normalize(\"NFKD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 64) || \"business\"\n )\n}\n\nexport async function materializeCustomerBusinessAccount(\n db: VoyantDb,\n input: {\n ownerUserId: string\n name: string\n relationshipOrganizationId: string\n },\n): Promise<CustomerBusinessAccountDto> {\n const [owner] = await db\n .select({ id: customerAuthUser.id })\n .from(customerAuthUser)\n .where(eq(customerAuthUser.id, input.ownerUserId))\n .limit(1)\n if (!owner) {\n throw new CustomerBusinessOnboardingNotFoundError(\"Customer owner does not exist\")\n }\n\n let [organization] = await db\n .select()\n .from(customerAuthOrganization)\n .where(\n eq(customerAuthOrganization.relationshipOrganizationId, input.relationshipOrganizationId),\n )\n .limit(1)\n\n for (let attempt = 0; !organization && attempt < 8; attempt += 1) {\n const suffix = opaqueId().replaceAll(\"-\", \"\").slice(0, 10)\n const [created] = await db\n .insert(customerAuthOrganization)\n .values({\n id: opaqueId(),\n name: input.name,\n slug: `${slugBase(input.name).slice(0, 52)}-${suffix}`,\n relationshipOrganizationId: input.relationshipOrganizationId,\n createdAt: new Date(),\n })\n .onConflictDoNothing()\n .returning()\n organization = created\n if (!organization) {\n ;[organization] = await db\n .select()\n .from(customerAuthOrganization)\n .where(\n eq(customerAuthOrganization.relationshipOrganizationId, input.relationshipOrganizationId),\n )\n .limit(1)\n }\n }\n if (!organization) {\n throw new CustomerBusinessOnboardingConflictError(\n \"Could not allocate a unique customer business organization slug\",\n )\n }\n\n const [membership] = await db\n .insert(customerAuthMember)\n .values({\n id: opaqueId(),\n userId: input.ownerUserId,\n organizationId: organization.id,\n role: \"owner\",\n createdAt: new Date(),\n })\n .onConflictDoUpdate({\n target: [customerAuthMember.userId, customerAuthMember.organizationId],\n set: { role: \"owner\" },\n })\n .returning()\n if (!membership) {\n throw new CustomerBusinessOnboardingConflictError(\n \"Could not create the customer business owner membership\",\n )\n }\n\n return customerBusinessAccountSchema.parse({\n id: businessBuyerAccountId(organization.id),\n kind: \"business\",\n name: organization.name,\n authOrganizationId: organization.id,\n relationshipOrganizationId: input.relationshipOrganizationId,\n relationshipPersonId: null,\n membershipId: membership.id,\n membershipRole: membership.role,\n })\n}\n\ntype RequestRow = typeof customerAuthBusinessAccountRequest.$inferSelect\n\nfunction requestProfile(row: RequestRow): CustomerBusinessProfile {\n return customerBusinessProfileSchema.parse({\n name: row.name,\n legalName: row.legalName,\n taxId: row.taxId,\n website: row.website,\n })\n}\n\nfunction requestDto(\n row: RequestRow,\n requester: { email: string | null; name: string | null },\n): CustomerBusinessAccountRequestDto {\n return customerBusinessAccountRequestSchema.parse({\n id: row.id,\n requesterUserId: row.requesterUserId,\n requesterEmail: requester.email,\n requesterName: requester.name,\n storefrontOrigin: row.storefrontOrigin,\n mode: row.mode,\n profile: requestProfile(row),\n status: row.status,\n idempotencyKey: row.idempotencyKey,\n authOrganizationId: row.authOrganizationId,\n relationshipOrganizationId: row.relationshipOrganizationId,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n decidedAt: row.decidedAt?.toISOString() ?? null,\n decidedBy: row.decidedBy,\n decisionReason: row.decisionReason,\n })\n}\n\nasync function hydrateRequest(\n db: VoyantDb,\n row: RequestRow,\n): Promise<CustomerBusinessAccountRequestDto> {\n const [requester] = await db\n .select({ email: customerAuthUser.email, name: customerAuthUser.name })\n .from(customerAuthUser)\n .where(eq(customerAuthUser.id, row.requesterUserId))\n .limit(1)\n return requestDto(row, requester ?? { email: null, name: null })\n}\n\nexport async function createCustomerBusinessAccountRequest(\n db: VoyantDb,\n input: {\n requesterUserId: string\n storefrontOrigin: string\n mode: CustomerBusinessOnboardingMode\n idempotencyKey: string\n profile: CustomerBusinessProfile\n },\n): Promise<CustomerBusinessAccountRequestDto> {\n const profile = customerBusinessProfileSchema.parse(input.profile)\n const [existing] = await db\n .select()\n .from(customerAuthBusinessAccountRequest)\n .where(\n and(\n eq(customerAuthBusinessAccountRequest.requesterUserId, input.requesterUserId),\n eq(customerAuthBusinessAccountRequest.idempotencyKey, input.idempotencyKey),\n ),\n )\n .limit(1)\n if (existing) {\n const samePayload =\n existing.storefrontOrigin === input.storefrontOrigin &&\n existing.mode === input.mode &&\n existing.name === profile.name &&\n existing.legalName === profile.legalName &&\n existing.taxId === profile.taxId &&\n existing.website === profile.website\n if (!samePayload) {\n throw new CustomerBusinessOnboardingConflictError(\n \"Idempotency key was already used with a different business-account request\",\n )\n }\n return hydrateRequest(db, existing)\n }\n\n const [created] = await db\n .insert(customerAuthBusinessAccountRequest)\n .values({\n id: opaqueId(),\n requesterUserId: input.requesterUserId,\n storefrontOrigin: input.storefrontOrigin,\n mode: input.mode,\n name: profile.name,\n legalName: profile.legalName,\n taxId: profile.taxId,\n website: profile.website,\n idempotencyKey: input.idempotencyKey,\n })\n .onConflictDoNothing()\n .returning()\n if (created) return hydrateRequest(db, created)\n\n const [racedIdempotent] = await db\n .select()\n .from(customerAuthBusinessAccountRequest)\n .where(\n and(\n eq(customerAuthBusinessAccountRequest.requesterUserId, input.requesterUserId),\n eq(customerAuthBusinessAccountRequest.idempotencyKey, input.idempotencyKey),\n ),\n )\n .limit(1)\n if (racedIdempotent) {\n const samePayload =\n racedIdempotent.storefrontOrigin === input.storefrontOrigin &&\n racedIdempotent.mode === input.mode &&\n racedIdempotent.name === profile.name &&\n racedIdempotent.legalName === profile.legalName &&\n racedIdempotent.taxId === profile.taxId &&\n racedIdempotent.website === profile.website\n if (samePayload) return hydrateRequest(db, racedIdempotent)\n throw new CustomerBusinessOnboardingConflictError(\n \"Idempotency key was already used with a different business-account request\",\n )\n }\n\n const [pending] = await db\n .select({ id: customerAuthBusinessAccountRequest.id })\n .from(customerAuthBusinessAccountRequest)\n .where(\n and(\n eq(customerAuthBusinessAccountRequest.requesterUserId, input.requesterUserId),\n eq(customerAuthBusinessAccountRequest.status, \"pending\"),\n ),\n )\n .limit(1)\n if (pending) {\n throw new CustomerBusinessOnboardingConflictError(\n \"Customer already has a pending business-account request\",\n )\n }\n throw new CustomerBusinessOnboardingConflictError(\"Business-account request could not be created\")\n}\n\nexport async function listCustomerBusinessAccountRequests(\n db: VoyantDb,\n input: {\n requesterUserId?: string\n status?: CustomerBusinessAccountRequestStatus\n },\n): Promise<CustomerBusinessAccountRequestDto[]> {\n const conditions = [\n ...(input.requesterUserId\n ? [eq(customerAuthBusinessAccountRequest.requesterUserId, input.requesterUserId)]\n : []),\n ...(input.status ? [eq(customerAuthBusinessAccountRequest.status, input.status)] : []),\n ]\n const rows = await db\n .select({\n request: customerAuthBusinessAccountRequest,\n requesterEmail: customerAuthUser.email,\n requesterName: customerAuthUser.name,\n })\n .from(customerAuthBusinessAccountRequest)\n .innerJoin(\n customerAuthUser,\n eq(customerAuthUser.id, customerAuthBusinessAccountRequest.requesterUserId),\n )\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(desc(customerAuthBusinessAccountRequest.createdAt))\n return rows.map((row) =>\n requestDto(row.request, { email: row.requesterEmail, name: row.requesterName }),\n )\n}\n\nexport async function getCustomerBusinessAccountRequestForUpdate(\n db: VoyantDb,\n requestId: string,\n): Promise<CustomerBusinessAccountRequestDto> {\n const [row] = await db\n .select()\n .from(customerAuthBusinessAccountRequest)\n .where(eq(customerAuthBusinessAccountRequest.id, requestId))\n .for(\"update\")\n .limit(1)\n if (!row) throw new CustomerBusinessOnboardingNotFoundError(\"Business-account request not found\")\n return hydrateRequest(db, row)\n}\n\nexport async function decideCustomerBusinessAccountRequest(\n db: VoyantDb,\n input: {\n requestId: string\n fromStatus: \"pending\"\n status: \"approved\" | \"rejected\" | \"canceled\"\n decidedBy: string | null\n decisionReason?: string | null\n authOrganizationId?: string\n relationshipOrganizationId?: string\n },\n): Promise<CustomerBusinessAccountRequestDto> {\n const now = new Date()\n const [updated] = await db\n .update(customerAuthBusinessAccountRequest)\n .set({\n status: input.status,\n updatedAt: now,\n decidedAt: now,\n decidedBy: input.decidedBy,\n decisionReason: input.decisionReason ?? null,\n authOrganizationId: input.authOrganizationId,\n relationshipOrganizationId: input.relationshipOrganizationId,\n })\n .where(\n and(\n eq(customerAuthBusinessAccountRequest.id, input.requestId),\n eq(customerAuthBusinessAccountRequest.status, input.fromStatus),\n ),\n )\n .returning()\n if (!updated) {\n throw new CustomerBusinessOnboardingConflictError(\n \"Business-account request is no longer pending\",\n )\n }\n return hydrateRequest(db, updated)\n}\n"]}
|
|
@@ -112,23 +112,4 @@ export declare function createDrizzleCustomerBuyerAccountStore(db: VoyantDb): Cu
|
|
|
112
112
|
* retroactively, and revoked rows are never revived.
|
|
113
113
|
*/
|
|
114
114
|
export declare function repairCustomerPersonalBuyerAccountEntitlement(db: VoyantDb, userId: string): Promise<boolean>;
|
|
115
|
-
/**
|
|
116
|
-
* Deployment-owned seam for open/request business onboarding. Implementations
|
|
117
|
-
* must create or select the canonical Relationships Organization before they
|
|
118
|
-
* create the Better Auth membership container and return both ids.
|
|
119
|
-
*/
|
|
120
|
-
export interface CustomerBusinessAccountOnboardingPort<Context = unknown> {
|
|
121
|
-
create(input: {
|
|
122
|
-
userId: string;
|
|
123
|
-
name: string;
|
|
124
|
-
context: Context;
|
|
125
|
-
}): Promise<BusinessCustomerBuyerAccount>;
|
|
126
|
-
request?(input: {
|
|
127
|
-
userId: string;
|
|
128
|
-
name: string;
|
|
129
|
-
context: Context;
|
|
130
|
-
}): Promise<{
|
|
131
|
-
id: string;
|
|
132
|
-
}>;
|
|
133
|
-
}
|
|
134
115
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"customer-buyer-accounts.js","sourceRoot":"","sources":["../src/customer-buyer-accounts.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n customerAuthMember,\n customerAuthOrganization,\n customerAuthPersonalBuyerAccount,\n customerAuthUser,\n} from \"@voyant-travel/db/schema/iam\"\nimport type { VoyantDb } from \"@voyant-travel/hono\"\nimport { asc, eq } from \"drizzle-orm\"\nimport { z } from \"zod\"\n\nexport const customerBuyerAccountKinds = [\"personal\", \"business\"] as const\nexport type CustomerBuyerAccountKind = (typeof customerBuyerAccountKinds)[number]\nexport type CustomerPersonalSignup = \"open\" | \"disabled\"\nexport type CustomerBusinessOnboarding = \"disabled\" | \"open\" | \"request\" | \"invite-only\"\n\nexport interface CustomerBuyerAccountPolicy {\n allowedKinds: readonly CustomerBuyerAccountKind[]\n personalSignup: CustomerPersonalSignup\n businessOnboarding: CustomerBusinessOnboarding\n}\n\nexport const defaultCustomerBuyerAccountPolicy = {\n allowedKinds: [\"personal\"],\n personalSignup: \"open\",\n businessOnboarding: \"disabled\",\n} as const satisfies CustomerBuyerAccountPolicy\n\nexport const customerBuyerAccountPolicySchema = z\n .object({\n allowedKinds: z.array(z.enum(customerBuyerAccountKinds)).min(1).max(2),\n personalSignup: z.enum([\"open\", \"disabled\"]),\n businessOnboarding: z.enum([\"disabled\", \"open\", \"request\", \"invite-only\"]),\n })\n .strict()\n .superRefine((policy, ctx) => {\n if (new Set(policy.allowedKinds).size !== policy.allowedKinds.length) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"allowedKinds\"],\n message: \"allowedKinds must be unique\",\n })\n }\n if (!policy.allowedKinds.includes(\"personal\") && policy.personalSignup !== \"disabled\") {\n ctx.addIssue({\n code: \"custom\",\n path: [\"personalSignup\"],\n message: \"personalSignup must be disabled when personal accounts are not allowed\",\n })\n }\n if (policy.allowedKinds.includes(\"business\") === (policy.businessOnboarding === \"disabled\")) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"businessOnboarding\"],\n message:\n \"businessOnboarding must be disabled exactly when business accounts are not allowed\",\n })\n }\n })\n\nexport function normalizeCustomerBuyerAccountPolicy(input?: unknown): CustomerBuyerAccountPolicy {\n if (input === undefined || input === null) {\n return { ...defaultCustomerBuyerAccountPolicy }\n }\n const parsed = customerBuyerAccountPolicySchema.parse(input)\n return {\n ...parsed,\n allowedKinds: customerBuyerAccountKinds.filter((kind) => parsed.allowedKinds.includes(kind)),\n }\n}\n\nexport function personalBuyerAccountId(userId: string): string {\n return `personal:${userId}`\n}\n\nexport function businessBuyerAccountId(authOrganizationId: string): string {\n return `business:${authOrganizationId}`\n}\n\nexport interface CustomerIdentitySummary {\n userId: string\n name: string | null\n email: string | null\n}\n\nexport interface CustomerBusinessMembership {\n id: string\n authOrganizationId: string\n relationshipOrganizationId: string | null\n name: string\n role: string\n}\n\nexport interface CustomerBuyerAccountStore {\n hasActivePersonalAccount(userId: string): Promise<boolean>\n getRelationshipPersonId(userId: string): Promise<string | null>\n listBusinessMemberships(userId: string): Promise<CustomerBusinessMembership[]>\n}\n\ninterface CustomerBuyerAccountBase {\n id: string\n name: string\n}\n\nexport interface PersonalCustomerBuyerAccount extends CustomerBuyerAccountBase {\n kind: \"personal\"\n authOrganizationId: null\n relationshipOrganizationId: null\n relationshipPersonId: string | null\n membershipId: null\n membershipRole: null\n}\n\nexport interface BusinessCustomerBuyerAccount extends CustomerBuyerAccountBase {\n kind: \"business\"\n authOrganizationId: string\n relationshipOrganizationId: string\n relationshipPersonId: null\n membershipId: string\n membershipRole: string\n}\n\nexport type CustomerBuyerAccount = PersonalCustomerBuyerAccount | BusinessCustomerBuyerAccount\n\nexport type ActiveCustomerBuyerContext = CustomerBuyerAccount & { userId: string }\n\nexport interface CustomerBuyerAccountList {\n accounts: CustomerBuyerAccount[]\n activeAccount: CustomerBuyerAccount | null\n policy: CustomerBuyerAccountPolicy\n requiresSelection: boolean\n}\n\nfunction personalAccount(\n identity: CustomerIdentitySummary,\n relationshipPersonId: string | null,\n): PersonalCustomerBuyerAccount {\n return {\n id: personalBuyerAccountId(identity.userId),\n kind: \"personal\",\n name: identity.name?.trim() || identity.email?.trim() || \"Personal account\",\n authOrganizationId: null,\n relationshipOrganizationId: null,\n relationshipPersonId,\n membershipId: null,\n membershipRole: null,\n }\n}\n\nfunction businessAccount(\n membership: CustomerBusinessMembership & { relationshipOrganizationId: string },\n): BusinessCustomerBuyerAccount {\n return {\n id: businessBuyerAccountId(membership.authOrganizationId),\n kind: \"business\",\n name: membership.name,\n authOrganizationId: membership.authOrganizationId,\n relationshipOrganizationId: membership.relationshipOrganizationId,\n relationshipPersonId: null,\n membershipId: membership.id,\n membershipRole: membership.role,\n }\n}\n\nfunction businessAccountsEnabled(policy: CustomerBuyerAccountPolicy): boolean {\n return policy.allowedKinds.includes(\"business\")\n}\n\nfunction hasRelationshipOrganization(\n membership: CustomerBusinessMembership,\n): membership is CustomerBusinessMembership & { relationshipOrganizationId: string } {\n return Boolean(membership.relationshipOrganizationId)\n}\n\n/**\n * Lists only usable buyer contexts. An unmapped Better Auth organization is\n * intentionally omitted because it is not a canonical business buyer.\n */\nexport async function listCustomerBuyerAccounts(input: {\n identity: CustomerIdentitySummary\n activeAuthOrganizationId: string | null\n policy?: Partial<CustomerBuyerAccountPolicy> | null\n store: CustomerBuyerAccountStore\n}): Promise<CustomerBuyerAccountList> {\n const policy = normalizeCustomerBuyerAccountPolicy(input.policy)\n const accounts: CustomerBuyerAccount[] = []\n\n if (policy.allowedKinds.includes(\"personal\")) {\n if (await input.store.hasActivePersonalAccount(input.identity.userId)) {\n accounts.push(\n personalAccount(\n input.identity,\n await input.store.getRelationshipPersonId(input.identity.userId),\n ),\n )\n }\n }\n\n if (businessAccountsEnabled(policy)) {\n const memberships = await input.store.listBusinessMemberships(input.identity.userId)\n accounts.push(...memberships.filter(hasRelationshipOrganization).map(businessAccount))\n }\n\n const activeAccount = input.activeAuthOrganizationId\n ? (accounts.find((account) => account.authOrganizationId === input.activeAuthOrganizationId) ??\n null)\n : (accounts.find((account) => account.kind === \"personal\") ?? null)\n\n return {\n accounts,\n activeAccount,\n policy,\n requiresSelection: activeAccount === null,\n }\n}\n\n/** Revalidates the active Better Auth organization against live membership. */\nexport async function resolveActiveCustomerBuyerContext(input: {\n identity: CustomerIdentitySummary\n activeAuthOrganizationId: string | null\n policy?: Partial<CustomerBuyerAccountPolicy> | null\n store: CustomerBuyerAccountStore\n}): Promise<ActiveCustomerBuyerContext | null> {\n const listed = await listCustomerBuyerAccounts(input)\n return listed.activeAccount ? { ...listed.activeAccount, userId: input.identity.userId } : null\n}\n\n/** Validates a requested account without trusting a client-supplied organization id. */\nexport async function selectCustomerBuyerAccount(input: {\n accountId: string\n identity: CustomerIdentitySummary\n activeAuthOrganizationId: string | null\n policy?: Partial<CustomerBuyerAccountPolicy> | null\n store: CustomerBuyerAccountStore\n}): Promise<CustomerBuyerAccount | null> {\n const listed = await listCustomerBuyerAccounts(input)\n return listed.accounts.find((account) => account.id === input.accountId) ?? null\n}\n\nexport function createDrizzleCustomerBuyerAccountStore(db: VoyantDb): CustomerBuyerAccountStore {\n return {\n async hasActivePersonalAccount(userId) {\n const [row] = await db\n .select({ revokedAt: customerAuthPersonalBuyerAccount.revokedAt })\n .from(customerAuthPersonalBuyerAccount)\n .where(eq(customerAuthPersonalBuyerAccount.userId, userId))\n .limit(1)\n return row?.revokedAt === null\n },\n async getRelationshipPersonId(userId) {\n const [row] = await db\n .select({ relationshipPersonId: customerAuthUser.relationshipPersonId })\n .from(customerAuthUser)\n .where(eq(customerAuthUser.id, userId))\n .limit(1)\n return row?.relationshipPersonId ?? null\n },\n async listBusinessMemberships(userId) {\n return db\n .select({\n id: customerAuthMember.id,\n authOrganizationId: customerAuthMember.organizationId,\n relationshipOrganizationId: customerAuthOrganization.relationshipOrganizationId,\n name: customerAuthOrganization.name,\n role: customerAuthMember.role,\n })\n .from(customerAuthMember)\n .innerJoin(\n customerAuthOrganization,\n eq(customerAuthOrganization.id, customerAuthMember.organizationId),\n )\n .where(eq(customerAuthMember.userId, userId))\n .orderBy(asc(customerAuthOrganization.name), asc(customerAuthMember.createdAt))\n },\n }\n}\n\n/**\n * Lazily and idempotently materializes the entitlement from the immutable\n * eligibility stamp. Current policy changes cannot grant an entitlement\n * retroactively, and revoked rows are never revived.\n */\nexport async function repairCustomerPersonalBuyerAccountEntitlement(\n db: VoyantDb,\n userId: string,\n): Promise<boolean> {\n const [candidate] = await db\n .select({\n eligible: customerAuthUser.personalBuyerEntitlementEligible,\n entitlementUserId: customerAuthPersonalBuyerAccount.userId,\n })\n .from(customerAuthUser)\n .leftJoin(\n customerAuthPersonalBuyerAccount,\n eq(customerAuthPersonalBuyerAccount.userId, customerAuthUser.id),\n )\n .where(eq(customerAuthUser.id, userId))\n .limit(1)\n\n if (!candidate?.eligible || candidate.entitlementUserId) return false\n\n await db.insert(customerAuthPersonalBuyerAccount).values({ userId }).onConflictDoNothing()\n return true\n}\n\n/**\n * Deployment-owned seam for open/request business onboarding. Implementations\n * must create or select the canonical Relationships Organization before they\n * create the Better Auth membership container and return both ids.\n */\nexport interface CustomerBusinessAccountOnboardingPort<Context = unknown> {\n create(input: {\n userId: string\n name: string\n context: Context\n }): Promise<BusinessCustomerBuyerAccount>\n request?(input: { userId: string; name: string; context: Context }): Promise<{ id: string }>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"customer-buyer-accounts.js","sourceRoot":"","sources":["../src/customer-buyer-accounts.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n customerAuthMember,\n customerAuthOrganization,\n customerAuthPersonalBuyerAccount,\n customerAuthUser,\n} from \"@voyant-travel/db/schema/iam\"\nimport type { VoyantDb } from \"@voyant-travel/hono\"\nimport { asc, eq } from \"drizzle-orm\"\nimport { z } from \"zod\"\n\nexport const customerBuyerAccountKinds = [\"personal\", \"business\"] as const\nexport type CustomerBuyerAccountKind = (typeof customerBuyerAccountKinds)[number]\nexport type CustomerPersonalSignup = \"open\" | \"disabled\"\nexport type CustomerBusinessOnboarding = \"disabled\" | \"open\" | \"request\" | \"invite-only\"\n\nexport interface CustomerBuyerAccountPolicy {\n allowedKinds: readonly CustomerBuyerAccountKind[]\n personalSignup: CustomerPersonalSignup\n businessOnboarding: CustomerBusinessOnboarding\n}\n\nexport const defaultCustomerBuyerAccountPolicy = {\n allowedKinds: [\"personal\"],\n personalSignup: \"open\",\n businessOnboarding: \"disabled\",\n} as const satisfies CustomerBuyerAccountPolicy\n\nexport const customerBuyerAccountPolicySchema = z\n .object({\n allowedKinds: z.array(z.enum(customerBuyerAccountKinds)).min(1).max(2),\n personalSignup: z.enum([\"open\", \"disabled\"]),\n businessOnboarding: z.enum([\"disabled\", \"open\", \"request\", \"invite-only\"]),\n })\n .strict()\n .superRefine((policy, ctx) => {\n if (new Set(policy.allowedKinds).size !== policy.allowedKinds.length) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"allowedKinds\"],\n message: \"allowedKinds must be unique\",\n })\n }\n if (!policy.allowedKinds.includes(\"personal\") && policy.personalSignup !== \"disabled\") {\n ctx.addIssue({\n code: \"custom\",\n path: [\"personalSignup\"],\n message: \"personalSignup must be disabled when personal accounts are not allowed\",\n })\n }\n if (policy.allowedKinds.includes(\"business\") === (policy.businessOnboarding === \"disabled\")) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"businessOnboarding\"],\n message:\n \"businessOnboarding must be disabled exactly when business accounts are not allowed\",\n })\n }\n })\n\nexport function normalizeCustomerBuyerAccountPolicy(input?: unknown): CustomerBuyerAccountPolicy {\n if (input === undefined || input === null) {\n return { ...defaultCustomerBuyerAccountPolicy }\n }\n const parsed = customerBuyerAccountPolicySchema.parse(input)\n return {\n ...parsed,\n allowedKinds: customerBuyerAccountKinds.filter((kind) => parsed.allowedKinds.includes(kind)),\n }\n}\n\nexport function personalBuyerAccountId(userId: string): string {\n return `personal:${userId}`\n}\n\nexport function businessBuyerAccountId(authOrganizationId: string): string {\n return `business:${authOrganizationId}`\n}\n\nexport interface CustomerIdentitySummary {\n userId: string\n name: string | null\n email: string | null\n}\n\nexport interface CustomerBusinessMembership {\n id: string\n authOrganizationId: string\n relationshipOrganizationId: string | null\n name: string\n role: string\n}\n\nexport interface CustomerBuyerAccountStore {\n hasActivePersonalAccount(userId: string): Promise<boolean>\n getRelationshipPersonId(userId: string): Promise<string | null>\n listBusinessMemberships(userId: string): Promise<CustomerBusinessMembership[]>\n}\n\ninterface CustomerBuyerAccountBase {\n id: string\n name: string\n}\n\nexport interface PersonalCustomerBuyerAccount extends CustomerBuyerAccountBase {\n kind: \"personal\"\n authOrganizationId: null\n relationshipOrganizationId: null\n relationshipPersonId: string | null\n membershipId: null\n membershipRole: null\n}\n\nexport interface BusinessCustomerBuyerAccount extends CustomerBuyerAccountBase {\n kind: \"business\"\n authOrganizationId: string\n relationshipOrganizationId: string\n relationshipPersonId: null\n membershipId: string\n membershipRole: string\n}\n\nexport type CustomerBuyerAccount = PersonalCustomerBuyerAccount | BusinessCustomerBuyerAccount\n\nexport type ActiveCustomerBuyerContext = CustomerBuyerAccount & { userId: string }\n\nexport interface CustomerBuyerAccountList {\n accounts: CustomerBuyerAccount[]\n activeAccount: CustomerBuyerAccount | null\n policy: CustomerBuyerAccountPolicy\n requiresSelection: boolean\n}\n\nfunction personalAccount(\n identity: CustomerIdentitySummary,\n relationshipPersonId: string | null,\n): PersonalCustomerBuyerAccount {\n return {\n id: personalBuyerAccountId(identity.userId),\n kind: \"personal\",\n name: identity.name?.trim() || identity.email?.trim() || \"Personal account\",\n authOrganizationId: null,\n relationshipOrganizationId: null,\n relationshipPersonId,\n membershipId: null,\n membershipRole: null,\n }\n}\n\nfunction businessAccount(\n membership: CustomerBusinessMembership & { relationshipOrganizationId: string },\n): BusinessCustomerBuyerAccount {\n return {\n id: businessBuyerAccountId(membership.authOrganizationId),\n kind: \"business\",\n name: membership.name,\n authOrganizationId: membership.authOrganizationId,\n relationshipOrganizationId: membership.relationshipOrganizationId,\n relationshipPersonId: null,\n membershipId: membership.id,\n membershipRole: membership.role,\n }\n}\n\nfunction businessAccountsEnabled(policy: CustomerBuyerAccountPolicy): boolean {\n return policy.allowedKinds.includes(\"business\")\n}\n\nfunction hasRelationshipOrganization(\n membership: CustomerBusinessMembership,\n): membership is CustomerBusinessMembership & { relationshipOrganizationId: string } {\n return Boolean(membership.relationshipOrganizationId)\n}\n\n/**\n * Lists only usable buyer contexts. An unmapped Better Auth organization is\n * intentionally omitted because it is not a canonical business buyer.\n */\nexport async function listCustomerBuyerAccounts(input: {\n identity: CustomerIdentitySummary\n activeAuthOrganizationId: string | null\n policy?: Partial<CustomerBuyerAccountPolicy> | null\n store: CustomerBuyerAccountStore\n}): Promise<CustomerBuyerAccountList> {\n const policy = normalizeCustomerBuyerAccountPolicy(input.policy)\n const accounts: CustomerBuyerAccount[] = []\n\n if (policy.allowedKinds.includes(\"personal\")) {\n if (await input.store.hasActivePersonalAccount(input.identity.userId)) {\n accounts.push(\n personalAccount(\n input.identity,\n await input.store.getRelationshipPersonId(input.identity.userId),\n ),\n )\n }\n }\n\n if (businessAccountsEnabled(policy)) {\n const memberships = await input.store.listBusinessMemberships(input.identity.userId)\n accounts.push(...memberships.filter(hasRelationshipOrganization).map(businessAccount))\n }\n\n const activeAccount = input.activeAuthOrganizationId\n ? (accounts.find((account) => account.authOrganizationId === input.activeAuthOrganizationId) ??\n null)\n : (accounts.find((account) => account.kind === \"personal\") ?? null)\n\n return {\n accounts,\n activeAccount,\n policy,\n requiresSelection: activeAccount === null,\n }\n}\n\n/** Revalidates the active Better Auth organization against live membership. */\nexport async function resolveActiveCustomerBuyerContext(input: {\n identity: CustomerIdentitySummary\n activeAuthOrganizationId: string | null\n policy?: Partial<CustomerBuyerAccountPolicy> | null\n store: CustomerBuyerAccountStore\n}): Promise<ActiveCustomerBuyerContext | null> {\n const listed = await listCustomerBuyerAccounts(input)\n return listed.activeAccount ? { ...listed.activeAccount, userId: input.identity.userId } : null\n}\n\n/** Validates a requested account without trusting a client-supplied organization id. */\nexport async function selectCustomerBuyerAccount(input: {\n accountId: string\n identity: CustomerIdentitySummary\n activeAuthOrganizationId: string | null\n policy?: Partial<CustomerBuyerAccountPolicy> | null\n store: CustomerBuyerAccountStore\n}): Promise<CustomerBuyerAccount | null> {\n const listed = await listCustomerBuyerAccounts(input)\n return listed.accounts.find((account) => account.id === input.accountId) ?? null\n}\n\nexport function createDrizzleCustomerBuyerAccountStore(db: VoyantDb): CustomerBuyerAccountStore {\n return {\n async hasActivePersonalAccount(userId) {\n const [row] = await db\n .select({ revokedAt: customerAuthPersonalBuyerAccount.revokedAt })\n .from(customerAuthPersonalBuyerAccount)\n .where(eq(customerAuthPersonalBuyerAccount.userId, userId))\n .limit(1)\n return row?.revokedAt === null\n },\n async getRelationshipPersonId(userId) {\n const [row] = await db\n .select({ relationshipPersonId: customerAuthUser.relationshipPersonId })\n .from(customerAuthUser)\n .where(eq(customerAuthUser.id, userId))\n .limit(1)\n return row?.relationshipPersonId ?? null\n },\n async listBusinessMemberships(userId) {\n return db\n .select({\n id: customerAuthMember.id,\n authOrganizationId: customerAuthMember.organizationId,\n relationshipOrganizationId: customerAuthOrganization.relationshipOrganizationId,\n name: customerAuthOrganization.name,\n role: customerAuthMember.role,\n })\n .from(customerAuthMember)\n .innerJoin(\n customerAuthOrganization,\n eq(customerAuthOrganization.id, customerAuthMember.organizationId),\n )\n .where(eq(customerAuthMember.userId, userId))\n .orderBy(asc(customerAuthOrganization.name), asc(customerAuthMember.createdAt))\n },\n }\n}\n\n/**\n * Lazily and idempotently materializes the entitlement from the immutable\n * eligibility stamp. Current policy changes cannot grant an entitlement\n * retroactively, and revoked rows are never revived.\n */\nexport async function repairCustomerPersonalBuyerAccountEntitlement(\n db: VoyantDb,\n userId: string,\n): Promise<boolean> {\n const [candidate] = await db\n .select({\n eligible: customerAuthUser.personalBuyerEntitlementEligible,\n entitlementUserId: customerAuthPersonalBuyerAccount.userId,\n })\n .from(customerAuthUser)\n .leftJoin(\n customerAuthPersonalBuyerAccount,\n eq(customerAuthPersonalBuyerAccount.userId, customerAuthUser.id),\n )\n .where(eq(customerAuthUser.id, userId))\n .limit(1)\n\n if (!candidate?.eligible || candidate.entitlementUserId) return false\n\n await db.insert(customerAuthPersonalBuyerAccount).values({ userId }).onConflictDoNothing()\n return true\n}\n"]}
|
package/dist/node-runtime.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { type SelectApikey } from "@voyant-travel/db/schema/iam";
|
|
|
13
13
|
import { type Reporter, type VoyantDb, type VoyantResolvedSessionAuthContext } from "@voyant-travel/hono";
|
|
14
14
|
import type { AccessCatalog } from "@voyant-travel/types/api-keys";
|
|
15
15
|
import { Hono } from "hono";
|
|
16
|
+
import type { CustomerBusinessAccountOnboardingRuntimeProvider } from "./customer-business-onboarding-runtime-port.js";
|
|
16
17
|
import { type CustomerBuyerAccountPolicy } from "./customer-buyer-accounts.js";
|
|
17
18
|
import { type CreateBetterAuthOptions, type CustomerAuthMethods } from "./server.js";
|
|
18
19
|
type OperatorAuthMode = "local" | "voyant-cloud";
|
|
@@ -38,6 +39,7 @@ export interface OperatorAuthNodeEnv extends NodeDatabaseEnv {
|
|
|
38
39
|
CUSTOMER_AUTH_FACEBOOK_CLIENT_SECRET?: string;
|
|
39
40
|
CUSTOMER_AUTH_APPLE_CLIENT_ID?: string;
|
|
40
41
|
CUSTOMER_AUTH_APPLE_CLIENT_SECRET?: string;
|
|
42
|
+
CUSTOMER_STOREFRONT_ORIGIN?: string;
|
|
41
43
|
VOYANT_AUTH_LOG_SECRET_FALLBACKS?: string;
|
|
42
44
|
VOYANT_CLOUD_ADMIN_AUTH_AUDIENCE?: string;
|
|
43
45
|
VOYANT_CLOUD_ADMIN_AUTH_CLIENT_TOKEN?: string;
|
|
@@ -85,6 +87,13 @@ export interface OperatorAuthEmailSender {
|
|
|
85
87
|
otp: string;
|
|
86
88
|
type: string;
|
|
87
89
|
}) => Promise<void>;
|
|
90
|
+
sendCustomerOrganizationInvitation: (input: {
|
|
91
|
+
email: string;
|
|
92
|
+
organizationName: string;
|
|
93
|
+
inviterName: string;
|
|
94
|
+
role: string;
|
|
95
|
+
url: string;
|
|
96
|
+
}) => Promise<void>;
|
|
88
97
|
}
|
|
89
98
|
export interface CreateOperatorAuthNodeRuntimeOptions<Env extends OperatorAuthNodeEnv> {
|
|
90
99
|
accessCatalog: AccessCatalog;
|
|
@@ -99,6 +108,7 @@ export interface CreateOperatorAuthNodeRuntimeOptions<Env extends OperatorAuthNo
|
|
|
99
108
|
};
|
|
100
109
|
cookieAdvanced?: (env: Env) => Pick<BetterAuthAdvancedOptions, "crossSubDomainCookies" | "defaultCookieAttributes"> | undefined;
|
|
101
110
|
resolveEmailSender?: (env: Env) => OperatorAuthEmailSender | null;
|
|
111
|
+
customerBusinessAccountOnboarding?: CustomerBusinessAccountOnboardingRuntimeProvider;
|
|
102
112
|
/**
|
|
103
113
|
* Storefront/BFF seam for a canonical public auth origin and resolved
|
|
104
114
|
* merchant credentials. Never derive these values from Host/X-Forwarded-Host.
|
|
@@ -110,6 +120,8 @@ export interface CustomerAuthRuntimeContext {
|
|
|
110
120
|
publicApiBaseURL?: string;
|
|
111
121
|
/** Canonical origin used internally by Better Auth. */
|
|
112
122
|
baseURL: string;
|
|
123
|
+
/** Explicit trusted storefront origin used for customer invitation links and request audit. */
|
|
124
|
+
invitationAcceptBaseURL?: string;
|
|
113
125
|
trustedOrigins: string[];
|
|
114
126
|
methods: CustomerAuthMethods;
|
|
115
127
|
/** Buyer capabilities are independent from identity sign-up methods. */
|
|
@@ -140,6 +152,7 @@ export declare function withCustomerSocialRedirectUris(methods: CustomerAuthMeth
|
|
|
140
152
|
* the browser-visible storefront/BFF API base.
|
|
141
153
|
*/
|
|
142
154
|
export declare function withCustomerPublicResetPasswordUrl(generatedUrl: string, publicApiBaseURL: string): string;
|
|
155
|
+
export declare function customerOrganizationInvitationUrl(invitationAcceptBaseURL: string, invitationId: string): string;
|
|
143
156
|
export declare function createOperatorAuthNodeRuntime<Env extends OperatorAuthNodeEnv>(runtimeOptions: CreateOperatorAuthNodeRuntimeOptions<Env>): {
|
|
144
157
|
handler: Hono<{
|
|
145
158
|
Bindings: Env;
|