@voyant-travel/auth 0.129.0 → 0.130.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/README.md +14 -0
- package/dist/cloud-broker/members.d.ts +9 -0
- package/dist/cloud-broker/members.js +10 -0
- package/dist/cloud-broker/members.js.map +1 -1
- package/dist/cloud-broker.d.ts +1 -1
- package/dist/cloud-broker.js +1 -1
- package/dist/cloud-broker.js.map +1 -1
- package/dist/identity-access-graph-runtime.js +2 -1
- package/dist/identity-access-graph-runtime.js.map +1 -1
- package/dist/invitations-routes.js +4 -0
- package/dist/invitations-routes.js.map +1 -1
- package/dist/local-member-access.d.ts +15 -0
- package/dist/local-member-access.js +119 -0
- package/dist/local-member-access.js.map +1 -0
- package/dist/runtime-contributor.js +18 -1
- package/dist/runtime-contributor.js.map +1 -1
- package/dist/server.d.ts +10 -6
- package/dist/server.js +6 -3
- package/dist/server.js.map +1 -1
- package/dist/team-management-cloud-adapter.d.ts +7 -0
- package/dist/team-management-cloud-adapter.js +146 -0
- package/dist/team-management-cloud-adapter.js.map +1 -0
- package/dist/team-management-local-adapter.d.ts +8 -0
- package/dist/team-management-local-adapter.js +285 -0
- package/dist/team-management-local-adapter.js.map +1 -0
- package/dist/team-management-policy.d.ts +27 -0
- package/dist/team-management-policy.js +109 -0
- package/dist/team-management-policy.js.map +1 -0
- package/dist/team-management-runtime-port.d.ts +60 -0
- package/dist/team-management-runtime-port.js +25 -0
- package/dist/team-management-runtime-port.js.map +1 -0
- package/dist/team-routes.d.ts +3 -3
- package/dist/team-routes.js +89 -138
- package/dist/team-routes.js.map +1 -1
- package/dist/voyant.js +35 -2
- package/dist/voyant.js.map +1 -1
- package/openapi/admin/team.json +42 -74
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -45,6 +45,20 @@ return auth.handler(request)
|
|
|
45
45
|
Auth provider wiring is starter-owned — core Voyant packages only depend on
|
|
46
46
|
the normalized `{ userId, actor }` contract, not on Better Auth specifically.
|
|
47
47
|
|
|
48
|
+
## Local Team Access
|
|
49
|
+
|
|
50
|
+
Local team deactivation is durable auth state. It revokes the member's current
|
|
51
|
+
sessions and API keys, blocks cached-session reuse and new Better Auth sessions,
|
|
52
|
+
and suppresses email OTP flows for the deactivated address. Reactivation
|
|
53
|
+
restores the member's sign-in providers so new password, social, or OTP sessions
|
|
54
|
+
can be created; previously revoked sessions and API keys remain revoked.
|
|
55
|
+
|
|
56
|
+
Local role changes, activation, and deactivation require an interactive
|
|
57
|
+
transaction. Owner-removing mutations serialize and recheck the active-owner
|
|
58
|
+
count inside that transaction, so concurrent requests cannot remove the last
|
|
59
|
+
active owner. Deployments mounting the team API must therefore select a
|
|
60
|
+
transaction-capable database adapter.
|
|
61
|
+
|
|
48
62
|
`createBetterAuth` forwards Better Auth's `user` options, including
|
|
49
63
|
`user.additionalFields`, while preserving Voyant's default change-email support.
|
|
50
64
|
When using additional user fields with the Drizzle adapter, the consuming app is
|
|
@@ -22,9 +22,12 @@ export type CloudAdminMember = {
|
|
|
22
22
|
membershipId: string;
|
|
23
23
|
externalUserId: string;
|
|
24
24
|
email: string | null;
|
|
25
|
+
name?: string | null;
|
|
25
26
|
roleSlug: string | null;
|
|
26
27
|
roleName: string | null;
|
|
27
28
|
status: string;
|
|
29
|
+
createdAt?: string | null;
|
|
30
|
+
lastActivityAt?: string | null;
|
|
28
31
|
/** Full platform access (owner/admin) — reaches every deployment. */
|
|
29
32
|
hasFullPlatformAccess: boolean;
|
|
30
33
|
/** Whether the member can currently sign into this deployment. */
|
|
@@ -42,6 +45,8 @@ export type CloudAdminMemberRole = {
|
|
|
42
45
|
export type CloudAdminInvitation = {
|
|
43
46
|
id: string;
|
|
44
47
|
email: string;
|
|
48
|
+
roleSlug?: string | null;
|
|
49
|
+
roleName?: string | null;
|
|
45
50
|
state: "pending" | "accepted" | "expired" | "revoked";
|
|
46
51
|
acceptedAt: string | null;
|
|
47
52
|
revokedAt: string | null;
|
|
@@ -88,6 +93,10 @@ export declare function setCloudAdminMemberAccess(input: CloudAdminMembersReques
|
|
|
88
93
|
membershipId: string;
|
|
89
94
|
hasAccess: boolean;
|
|
90
95
|
}): Promise<CloudAdminMember>;
|
|
96
|
+
export declare function setCloudAdminMemberRole(input: CloudAdminMembersRequest & {
|
|
97
|
+
membershipId: string;
|
|
98
|
+
roleSlug: string;
|
|
99
|
+
}): Promise<CloudAdminMember>;
|
|
91
100
|
/**
|
|
92
101
|
* Set a member's granular RBAC permissions for this deployment (`resource:action`
|
|
93
102
|
* strings). A non-empty set also grants sign-in access; an empty set clears both.
|
|
@@ -100,6 +100,16 @@ export function setCloudAdminMemberAccess(input) {
|
|
|
100
100
|
body: { hasAccess: input.hasAccess },
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
+
export function setCloudAdminMemberRole(input) {
|
|
104
|
+
return request({
|
|
105
|
+
config: input.config,
|
|
106
|
+
actingWorkosUserId: input.actingWorkosUserId,
|
|
107
|
+
fetch: input.fetch,
|
|
108
|
+
path: `/members/${encodeURIComponent(input.membershipId)}/role`,
|
|
109
|
+
method: "PUT",
|
|
110
|
+
body: { roleSlug: input.roleSlug },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
103
113
|
/**
|
|
104
114
|
* Set a member's granular RBAC permissions for this deployment (`resource:action`
|
|
105
115
|
* strings). A non-empty set also grants sign-in access; an empty set clears both.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"members.js","sourceRoot":"","sources":["../../src/cloud-broker/members.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Deployment-side client for Voyant Cloud member management.\n *\n * The team roster of a `voyant-cloud`-auth deployment lives on the platform, not\n * in the deployment DB. This client lets the deployment manage that roster\n * (list members, list/send/revoke invitations, grant/revoke this deployment's\n * access) by calling the platform's `/cloud/v1/admin-auth/*` member endpoints —\n * the management counterpart to {@link revalidateCloudAdminAuthAccess}.\n *\n * Every request carries the deployment client token (Bearer) plus the acting\n * staff user's WorkOS id; the platform re-verifies that user is an org manager\n * before mutating, so the deployment never self-asserts authority.\n */\nimport type { CloudAdminAuthRevalidateConfig } from \"./assertion.js\"\nimport { isRecord, normalizeAbsoluteUrl } from \"./utils.js\"\n\nexport type CloudAdminMembersConfig = {\n /** Base URL of the platform member API, e.g. `.../cloud/v1/admin-auth`. */\n baseUrl: string\n deploymentId: string\n clientToken: string\n}\n\nexport type CloudAdminMember = {\n membershipId: string\n externalUserId: string\n email: string | null\n roleSlug: string | null\n roleName: string | null\n status: string\n /** Full platform access (owner/admin) — reaches every deployment. */\n hasFullPlatformAccess: boolean\n /** Whether the member can currently sign into this deployment. */\n hasDeploymentAccess: boolean\n /** True when this deployment is explicitly listed in their grants. */\n isExplicitGrant: boolean\n /** Explicit per-deployment RBAC scope set for this app, or null (role-derived). */\n permissions: string[] | null\n}\n\nexport type CloudAdminMemberRole = {\n slug: string\n name: string\n description: string | null\n}\n\nexport type CloudAdminInvitation = {\n id: string\n email: string\n state: \"pending\" | \"accepted\" | \"expired\" | \"revoked\"\n acceptedAt: string | null\n revokedAt: string | null\n expiresAt: string\n inviterUserId: string | null\n acceptedUserId: string | null\n acceptInvitationUrl: string\n createdAt: string\n updatedAt: string\n}\n\nexport type CloudAdminInviteInput = {\n email: string\n roleSlug?: string | null\n expiresInDays?: number\n}\n\n/** Raised when the platform rejects a member request; carries the HTTP status. */\nexport class CloudAdminMembersError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly reason?: string,\n ) {\n super(message)\n this.name = \"CloudAdminMembersError\"\n }\n}\n\n/**\n * Derive the member API base URL from the revalidate URL — they share the\n * `/cloud/v1/admin-auth` prefix, so a deployment needs no extra env var.\n */\nexport function deriveCloudAdminMembersBaseUrl(revalidateUrl: string): string {\n return normalizeAbsoluteUrl(revalidateUrl, \"revalidateUrl\").replace(/\\/revalidate$/, \"\")\n}\n\n/** Build a member-API config by reusing the deployment's revalidate config. */\nexport function cloudAdminMembersConfigFromRevalidate(\n config: CloudAdminAuthRevalidateConfig,\n): CloudAdminMembersConfig {\n return {\n baseUrl: deriveCloudAdminMembersBaseUrl(config.revalidateUrl),\n deploymentId: config.deploymentId,\n clientToken: config.clientToken,\n }\n}\n\ntype RequestInput = {\n config: CloudAdminMembersConfig\n actingWorkosUserId: string\n path: string\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\"\n body?: unknown\n fetch?: typeof fetch\n}\n\nfunction validateConfig(config: CloudAdminMembersConfig) {\n normalizeAbsoluteUrl(config.baseUrl, \"baseUrl\")\n if (!config.deploymentId.trim()) {\n throw new Error(\"Voyant Cloud member config is missing deploymentId\")\n }\n if (!config.clientToken.trim()) {\n throw new Error(\"Voyant Cloud member config is missing clientToken\")\n }\n}\n\nasync function request<T>({\n config,\n actingWorkosUserId,\n path,\n method = \"GET\",\n body,\n fetch: fetchFn = fetch,\n}: RequestInput): Promise<T> {\n validateConfig(config)\n if (!actingWorkosUserId.trim()) {\n throw new Error(\"Voyant Cloud member request is missing the acting user\")\n }\n\n const response = await fetchFn(`${config.baseUrl}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${config.clientToken}`,\n \"Content-Type\": \"application/json\",\n \"x-voyant-deployment-id\": config.deploymentId,\n \"x-voyant-acting-user-id\": actingWorkosUserId,\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n })\n\n if (response.status === 204) {\n return undefined as T\n }\n\n const payload = (await response.json().catch(() => null)) as unknown\n\n if (!response.ok) {\n const reason =\n isRecord(payload) && typeof payload.error === \"string\" ? payload.error : undefined\n throw new CloudAdminMembersError(\n reason ?? `Voyant Cloud member request failed with HTTP ${response.status}`,\n response.status,\n reason,\n )\n }\n\n if (isRecord(payload) && \"data\" in payload) {\n return payload.data as T\n }\n return payload as T\n}\n\nexport type CloudAdminMembersRequest = {\n config: CloudAdminMembersConfig\n actingWorkosUserId: string\n fetch?: typeof fetch\n}\n\nexport function listCloudAdminMembers(\n input: CloudAdminMembersRequest,\n): Promise<CloudAdminMember[]> {\n return request<CloudAdminMember[]>({ ...input, path: \"/members\" })\n}\n\nexport function listCloudAdminMemberRoles(\n input: CloudAdminMembersRequest,\n): Promise<CloudAdminMemberRole[]> {\n return request<CloudAdminMemberRole[]>({ ...input, path: \"/roles\" })\n}\n\nexport function listCloudAdminInvitations(\n input: CloudAdminMembersRequest,\n): Promise<CloudAdminInvitation[]> {\n return request<CloudAdminInvitation[]>({ ...input, path: \"/invitations\" })\n}\n\nexport function inviteCloudAdminMember(\n input: CloudAdminMembersRequest & { input: CloudAdminInviteInput },\n): Promise<CloudAdminInvitation> {\n return request<CloudAdminInvitation>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: \"/invitations\",\n method: \"POST\",\n body: input.input,\n })\n}\n\nexport function revokeCloudAdminInvitation(\n input: CloudAdminMembersRequest & { invitationId: string },\n): Promise<void> {\n return request<void>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: `/invitations/${encodeURIComponent(input.invitationId)}`,\n method: \"DELETE\",\n })\n}\n\nexport function setCloudAdminMemberAccess(\n input: CloudAdminMembersRequest & {\n membershipId: string\n hasAccess: boolean\n },\n): Promise<CloudAdminMember> {\n return request<CloudAdminMember>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: `/members/${encodeURIComponent(input.membershipId)}/access`,\n method: \"PUT\",\n body: { hasAccess: input.hasAccess },\n })\n}\n\n/**\n * Set a member's granular RBAC permissions for this deployment (`resource:action`\n * strings). A non-empty set also grants sign-in access; an empty set clears both.\n */\nexport function setCloudAdminMemberPermissions(\n input: CloudAdminMembersRequest & {\n membershipId: string\n permissions: string[]\n },\n): Promise<CloudAdminMember> {\n return request<CloudAdminMember>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: `/members/${encodeURIComponent(input.membershipId)}/permissions`,\n method: \"PUT\",\n body: { permissions: input.permissions },\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"members.js","sourceRoot":"","sources":["../../src/cloud-broker/members.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Deployment-side client for Voyant Cloud member management.\n *\n * The team roster of a `voyant-cloud`-auth deployment lives on the platform, not\n * in the deployment DB. This client lets the deployment manage that roster\n * (list members, list/send/revoke invitations, grant/revoke this deployment's\n * access) by calling the platform's `/cloud/v1/admin-auth/*` member endpoints —\n * the management counterpart to {@link revalidateCloudAdminAuthAccess}.\n *\n * Every request carries the deployment client token (Bearer) plus the acting\n * staff user's WorkOS id; the platform re-verifies that user is an org manager\n * before mutating, so the deployment never self-asserts authority.\n */\nimport type { CloudAdminAuthRevalidateConfig } from \"./assertion.js\"\nimport { isRecord, normalizeAbsoluteUrl } from \"./utils.js\"\n\nexport type CloudAdminMembersConfig = {\n /** Base URL of the platform member API, e.g. `.../cloud/v1/admin-auth`. */\n baseUrl: string\n deploymentId: string\n clientToken: string\n}\n\nexport type CloudAdminMember = {\n membershipId: string\n externalUserId: string\n email: string | null\n name?: string | null\n roleSlug: string | null\n roleName: string | null\n status: string\n createdAt?: string | null\n lastActivityAt?: string | null\n /** Full platform access (owner/admin) — reaches every deployment. */\n hasFullPlatformAccess: boolean\n /** Whether the member can currently sign into this deployment. */\n hasDeploymentAccess: boolean\n /** True when this deployment is explicitly listed in their grants. */\n isExplicitGrant: boolean\n /** Explicit per-deployment RBAC scope set for this app, or null (role-derived). */\n permissions: string[] | null\n}\n\nexport type CloudAdminMemberRole = {\n slug: string\n name: string\n description: string | null\n}\n\nexport type CloudAdminInvitation = {\n id: string\n email: string\n roleSlug?: string | null\n roleName?: string | null\n state: \"pending\" | \"accepted\" | \"expired\" | \"revoked\"\n acceptedAt: string | null\n revokedAt: string | null\n expiresAt: string\n inviterUserId: string | null\n acceptedUserId: string | null\n acceptInvitationUrl: string\n createdAt: string\n updatedAt: string\n}\n\nexport type CloudAdminInviteInput = {\n email: string\n roleSlug?: string | null\n expiresInDays?: number\n}\n\n/** Raised when the platform rejects a member request; carries the HTTP status. */\nexport class CloudAdminMembersError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly reason?: string,\n ) {\n super(message)\n this.name = \"CloudAdminMembersError\"\n }\n}\n\n/**\n * Derive the member API base URL from the revalidate URL — they share the\n * `/cloud/v1/admin-auth` prefix, so a deployment needs no extra env var.\n */\nexport function deriveCloudAdminMembersBaseUrl(revalidateUrl: string): string {\n return normalizeAbsoluteUrl(revalidateUrl, \"revalidateUrl\").replace(/\\/revalidate$/, \"\")\n}\n\n/** Build a member-API config by reusing the deployment's revalidate config. */\nexport function cloudAdminMembersConfigFromRevalidate(\n config: CloudAdminAuthRevalidateConfig,\n): CloudAdminMembersConfig {\n return {\n baseUrl: deriveCloudAdminMembersBaseUrl(config.revalidateUrl),\n deploymentId: config.deploymentId,\n clientToken: config.clientToken,\n }\n}\n\ntype RequestInput = {\n config: CloudAdminMembersConfig\n actingWorkosUserId: string\n path: string\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\"\n body?: unknown\n fetch?: typeof fetch\n}\n\nfunction validateConfig(config: CloudAdminMembersConfig) {\n normalizeAbsoluteUrl(config.baseUrl, \"baseUrl\")\n if (!config.deploymentId.trim()) {\n throw new Error(\"Voyant Cloud member config is missing deploymentId\")\n }\n if (!config.clientToken.trim()) {\n throw new Error(\"Voyant Cloud member config is missing clientToken\")\n }\n}\n\nasync function request<T>({\n config,\n actingWorkosUserId,\n path,\n method = \"GET\",\n body,\n fetch: fetchFn = fetch,\n}: RequestInput): Promise<T> {\n validateConfig(config)\n if (!actingWorkosUserId.trim()) {\n throw new Error(\"Voyant Cloud member request is missing the acting user\")\n }\n\n const response = await fetchFn(`${config.baseUrl}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${config.clientToken}`,\n \"Content-Type\": \"application/json\",\n \"x-voyant-deployment-id\": config.deploymentId,\n \"x-voyant-acting-user-id\": actingWorkosUserId,\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n })\n\n if (response.status === 204) {\n return undefined as T\n }\n\n const payload = (await response.json().catch(() => null)) as unknown\n\n if (!response.ok) {\n const reason =\n isRecord(payload) && typeof payload.error === \"string\" ? payload.error : undefined\n throw new CloudAdminMembersError(\n reason ?? `Voyant Cloud member request failed with HTTP ${response.status}`,\n response.status,\n reason,\n )\n }\n\n if (isRecord(payload) && \"data\" in payload) {\n return payload.data as T\n }\n return payload as T\n}\n\nexport type CloudAdminMembersRequest = {\n config: CloudAdminMembersConfig\n actingWorkosUserId: string\n fetch?: typeof fetch\n}\n\nexport function listCloudAdminMembers(\n input: CloudAdminMembersRequest,\n): Promise<CloudAdminMember[]> {\n return request<CloudAdminMember[]>({ ...input, path: \"/members\" })\n}\n\nexport function listCloudAdminMemberRoles(\n input: CloudAdminMembersRequest,\n): Promise<CloudAdminMemberRole[]> {\n return request<CloudAdminMemberRole[]>({ ...input, path: \"/roles\" })\n}\n\nexport function listCloudAdminInvitations(\n input: CloudAdminMembersRequest,\n): Promise<CloudAdminInvitation[]> {\n return request<CloudAdminInvitation[]>({ ...input, path: \"/invitations\" })\n}\n\nexport function inviteCloudAdminMember(\n input: CloudAdminMembersRequest & { input: CloudAdminInviteInput },\n): Promise<CloudAdminInvitation> {\n return request<CloudAdminInvitation>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: \"/invitations\",\n method: \"POST\",\n body: input.input,\n })\n}\n\nexport function revokeCloudAdminInvitation(\n input: CloudAdminMembersRequest & { invitationId: string },\n): Promise<void> {\n return request<void>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: `/invitations/${encodeURIComponent(input.invitationId)}`,\n method: \"DELETE\",\n })\n}\n\nexport function setCloudAdminMemberAccess(\n input: CloudAdminMembersRequest & {\n membershipId: string\n hasAccess: boolean\n },\n): Promise<CloudAdminMember> {\n return request<CloudAdminMember>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: `/members/${encodeURIComponent(input.membershipId)}/access`,\n method: \"PUT\",\n body: { hasAccess: input.hasAccess },\n })\n}\n\nexport function setCloudAdminMemberRole(\n input: CloudAdminMembersRequest & {\n membershipId: string\n roleSlug: string\n },\n): Promise<CloudAdminMember> {\n return request<CloudAdminMember>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: `/members/${encodeURIComponent(input.membershipId)}/role`,\n method: \"PUT\",\n body: { roleSlug: input.roleSlug },\n })\n}\n\n/**\n * Set a member's granular RBAC permissions for this deployment (`resource:action`\n * strings). A non-empty set also grants sign-in access; an empty set clears both.\n */\nexport function setCloudAdminMemberPermissions(\n input: CloudAdminMembersRequest & {\n membershipId: string\n permissions: string[]\n },\n): Promise<CloudAdminMember> {\n return request<CloudAdminMember>({\n config: input.config,\n actingWorkosUserId: input.actingWorkosUserId,\n fetch: input.fetch,\n path: `/members/${encodeURIComponent(input.membershipId)}/permissions`,\n method: \"PUT\",\n body: { permissions: input.permissions },\n })\n}\n"]}
|
package/dist/cloud-broker.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { CloudAdminAssertion, CloudAdminAuthExchangeConfig, CloudAdminAuthRevalidateConfig, CloudAdminAuthRevalidationResult, ExchangeCloudAdminAuthCodeInput, RevalidateCloudAdminAuthAccessInput, } from "./cloud-broker/assertion.js";
|
|
2
2
|
export { exchangeCloudAdminAuthCode, revalidateCloudAdminAuthAccess, VOYANT_CLOUD_ADMIN_ASSERTION_ISSUER, verifyCloudAdminAssertion, } from "./cloud-broker/assertion.js";
|
|
3
3
|
export type { CloudAdminInvitation, CloudAdminInviteInput, CloudAdminMember, CloudAdminMemberRole, CloudAdminMembersConfig, CloudAdminMembersRequest, } from "./cloud-broker/members.js";
|
|
4
|
-
export { CloudAdminMembersError, cloudAdminMembersConfigFromRevalidate, deriveCloudAdminMembersBaseUrl, inviteCloudAdminMember, listCloudAdminInvitations, listCloudAdminMemberRoles, listCloudAdminMembers, revokeCloudAdminInvitation, setCloudAdminMemberAccess, setCloudAdminMemberPermissions, } from "./cloud-broker/members.js";
|
|
4
|
+
export { CloudAdminMembersError, cloudAdminMembersConfigFromRevalidate, deriveCloudAdminMembersBaseUrl, inviteCloudAdminMember, listCloudAdminInvitations, listCloudAdminMemberRoles, listCloudAdminMembers, revokeCloudAdminInvitation, setCloudAdminMemberAccess, setCloudAdminMemberPermissions, setCloudAdminMemberRole, } from "./cloud-broker/members.js";
|
|
5
5
|
export type { CloudAdminAuthStartConfig, CloudAdminAuthState, CreateCloudAdminAuthStartInput, CreateCloudAdminAuthStartResult, VerifyCloudAdminAuthCallbackInput, VerifyCloudAdminAuthCallbackResult, } from "./cloud-broker/state.js";
|
|
6
6
|
export { buildClearCloudAdminAuthStateCookie, createCloudAdminAuthStart, normalizeCloudAdminAuthNext, VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE, verifyCloudAdminAuthCallback, } from "./cloud-broker/state.js";
|
package/dist/cloud-broker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { exchangeCloudAdminAuthCode, revalidateCloudAdminAuthAccess, VOYANT_CLOUD_ADMIN_ASSERTION_ISSUER, verifyCloudAdminAssertion, } from "./cloud-broker/assertion.js";
|
|
2
|
-
export { CloudAdminMembersError, cloudAdminMembersConfigFromRevalidate, deriveCloudAdminMembersBaseUrl, inviteCloudAdminMember, listCloudAdminInvitations, listCloudAdminMemberRoles, listCloudAdminMembers, revokeCloudAdminInvitation, setCloudAdminMemberAccess, setCloudAdminMemberPermissions, } from "./cloud-broker/members.js";
|
|
2
|
+
export { CloudAdminMembersError, cloudAdminMembersConfigFromRevalidate, deriveCloudAdminMembersBaseUrl, inviteCloudAdminMember, listCloudAdminInvitations, listCloudAdminMemberRoles, listCloudAdminMembers, revokeCloudAdminInvitation, setCloudAdminMemberAccess, setCloudAdminMemberPermissions, setCloudAdminMemberRole, } from "./cloud-broker/members.js";
|
|
3
3
|
export { buildClearCloudAdminAuthStateCookie, createCloudAdminAuthStart, normalizeCloudAdminAuthNext, VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE, verifyCloudAdminAuthCallback, } from "./cloud-broker/state.js";
|
|
4
4
|
//# sourceMappingURL=cloud-broker.js.map
|
package/dist/cloud-broker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-broker.js","sourceRoot":"","sources":["../src/cloud-broker.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,mCAAmC,EACnC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAA;AASpC,OAAO,EACL,sBAAsB,EACtB,qCAAqC,EACrC,8BAA8B,EAC9B,sBAAsB,EACtB,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,8BAA8B,
|
|
1
|
+
{"version":3,"file":"cloud-broker.js","sourceRoot":"","sources":["../src/cloud-broker.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,mCAAmC,EACnC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAA;AASpC,OAAO,EACL,sBAAsB,EACtB,qCAAqC,EACrC,8BAA8B,EAC9B,sBAAsB,EACtB,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,2BAA2B,CAAA;AASlC,OAAO,EACL,mCAAmC,EACnC,yBAAyB,EACzB,2BAA2B,EAC3B,oCAAoC,EACpC,4BAA4B,GAC7B,MAAM,yBAAyB,CAAA","sourcesContent":["export type {\n CloudAdminAssertion,\n CloudAdminAuthExchangeConfig,\n CloudAdminAuthRevalidateConfig,\n CloudAdminAuthRevalidationResult,\n ExchangeCloudAdminAuthCodeInput,\n RevalidateCloudAdminAuthAccessInput,\n} from \"./cloud-broker/assertion.js\"\nexport {\n exchangeCloudAdminAuthCode,\n revalidateCloudAdminAuthAccess,\n VOYANT_CLOUD_ADMIN_ASSERTION_ISSUER,\n verifyCloudAdminAssertion,\n} from \"./cloud-broker/assertion.js\"\nexport type {\n CloudAdminInvitation,\n CloudAdminInviteInput,\n CloudAdminMember,\n CloudAdminMemberRole,\n CloudAdminMembersConfig,\n CloudAdminMembersRequest,\n} from \"./cloud-broker/members.js\"\nexport {\n CloudAdminMembersError,\n cloudAdminMembersConfigFromRevalidate,\n deriveCloudAdminMembersBaseUrl,\n inviteCloudAdminMember,\n listCloudAdminInvitations,\n listCloudAdminMemberRoles,\n listCloudAdminMembers,\n revokeCloudAdminInvitation,\n setCloudAdminMemberAccess,\n setCloudAdminMemberPermissions,\n setCloudAdminMemberRole,\n} from \"./cloud-broker/members.js\"\nexport type {\n CloudAdminAuthStartConfig,\n CloudAdminAuthState,\n CreateCloudAdminAuthStartInput,\n CreateCloudAdminAuthStartResult,\n VerifyCloudAdminAuthCallbackInput,\n VerifyCloudAdminAuthCallbackResult,\n} from \"./cloud-broker/state.js\"\nexport {\n buildClearCloudAdminAuthStateCookie,\n createCloudAdminAuthStart,\n normalizeCloudAdminAuthNext,\n VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE,\n verifyCloudAdminAuthCallback,\n} from \"./cloud-broker/state.js\"\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineGraphRuntimeFactory } from "@voyant-travel/core/project";
|
|
2
2
|
import { identityAccessRuntimePort } from "./identity-access-runtime-port.js";
|
|
3
3
|
import { createInvitationsAdminRoutes, createInvitationsPublicRoutes, } from "./invitations-routes.js";
|
|
4
|
+
import { teamManagementRuntimePort } from "./team-management-runtime-port.js";
|
|
4
5
|
import { createTeamAdminRoutes } from "./team-routes.js";
|
|
5
6
|
export const createInvitationsVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => {
|
|
6
7
|
const runtime = await getPort(identityAccessRuntimePort);
|
|
@@ -12,6 +13,6 @@ export const createInvitationsVoyantRuntime = defineGraphRuntimeFactory(async ({
|
|
|
12
13
|
});
|
|
13
14
|
export const createTeamVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => ({
|
|
14
15
|
module: { name: "team" },
|
|
15
|
-
adminRoutes: createTeamAdminRoutes(await getPort(
|
|
16
|
+
adminRoutes: createTeamAdminRoutes(await getPort(teamManagementRuntimePort)),
|
|
16
17
|
}));
|
|
17
18
|
//# sourceMappingURL=identity-access-graph-runtime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-access-graph-runtime.js","sourceRoot":"","sources":["../src/identity-access-graph-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AAEvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAExD,MAAM,CAAC,MAAM,8BAA8B,GAAG,yBAAyB,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC5F,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAA;IACxD,OAAO;QACL,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;QAC/B,WAAW,EAAE,4BAA4B,CAAC,OAAO,CAAC;QAClD,YAAY,EAAE,6BAA6B,EAAE;KAC9C,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IACvF,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;IACxB,WAAW,EAAE,qBAAqB,CAAC,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC7E,CAAC,CAAC,CAAA","sourcesContent":["import { defineGraphRuntimeFactory } from \"@voyant-travel/core/project\"\n\nimport { identityAccessRuntimePort } from \"./identity-access-runtime-port.js\"\nimport {\n createInvitationsAdminRoutes,\n createInvitationsPublicRoutes,\n} from \"./invitations-routes.js\"\nimport { createTeamAdminRoutes } from \"./team-routes.js\"\n\nexport const createInvitationsVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => {\n const runtime = await getPort(identityAccessRuntimePort)\n return {\n module: { name: \"invitations\" },\n adminRoutes: createInvitationsAdminRoutes(runtime),\n publicRoutes: createInvitationsPublicRoutes(),\n }\n})\n\nexport const createTeamVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => ({\n module: { name: \"team\" },\n adminRoutes: createTeamAdminRoutes(await getPort(
|
|
1
|
+
{"version":3,"file":"identity-access-graph-runtime.js","sourceRoot":"","sources":["../src/identity-access-graph-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AAEvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAExD,MAAM,CAAC,MAAM,8BAA8B,GAAG,yBAAyB,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC5F,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAA;IACxD,OAAO;QACL,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;QAC/B,WAAW,EAAE,4BAA4B,CAAC,OAAO,CAAC;QAClD,YAAY,EAAE,6BAA6B,EAAE;KAC9C,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IACvF,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;IACxB,WAAW,EAAE,qBAAqB,CAAC,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC7E,CAAC,CAAC,CAAA","sourcesContent":["import { defineGraphRuntimeFactory } from \"@voyant-travel/core/project\"\n\nimport { identityAccessRuntimePort } from \"./identity-access-runtime-port.js\"\nimport {\n createInvitationsAdminRoutes,\n createInvitationsPublicRoutes,\n} from \"./invitations-routes.js\"\nimport { teamManagementRuntimePort } from \"./team-management-runtime-port.js\"\nimport { createTeamAdminRoutes } from \"./team-routes.js\"\n\nexport const createInvitationsVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => {\n const runtime = await getPort(identityAccessRuntimePort)\n return {\n module: { name: \"invitations\" },\n adminRoutes: createInvitationsAdminRoutes(runtime),\n publicRoutes: createInvitationsPublicRoutes(),\n }\n})\n\nexport const createTeamVoyantRuntime = defineGraphRuntimeFactory(async ({ getPort }) => ({\n module: { name: \"team\" },\n adminRoutes: createTeamAdminRoutes(await getPort(teamManagementRuntimePort)),\n}))\n"]}
|
|
@@ -4,6 +4,7 @@ import { authAccount, authUser, userInvitationsTable, userProfilesTable, } from
|
|
|
4
4
|
import { openApiValidationHook } from "@voyant-travel/hono";
|
|
5
5
|
import { hashPassword } from "better-auth/crypto";
|
|
6
6
|
import { and, desc, eq, gt, isNull } from "drizzle-orm";
|
|
7
|
+
import { localRoleForRedeemedInvitation } from "./team-management-local-adapter.js";
|
|
7
8
|
const DEFAULT_EXPIRY_HOURS = 72;
|
|
8
9
|
const createInviteSchema = z.object({
|
|
9
10
|
email: z.string().email(),
|
|
@@ -204,6 +205,7 @@ export function createInvitationsPublicRoutes() {
|
|
|
204
205
|
const userId = crypto.randomUUID();
|
|
205
206
|
const name = input.name.trim();
|
|
206
207
|
const [firstName, ...rest] = name.split(/\s+/);
|
|
208
|
+
const teamRole = localRoleForRedeemedInvitation(invite.metadata);
|
|
207
209
|
await db.insert(authUser).values({
|
|
208
210
|
id: userId,
|
|
209
211
|
name,
|
|
@@ -229,6 +231,8 @@ export function createInvitationsPublicRoutes() {
|
|
|
229
231
|
firstName: firstName ?? null,
|
|
230
232
|
lastName: rest.join(" ") || null,
|
|
231
233
|
avatarUrl: null,
|
|
234
|
+
isSuperAdmin: teamRole.isSuperAdmin,
|
|
235
|
+
permissions: teamRole.permissions,
|
|
232
236
|
})
|
|
233
237
|
.onConflictDoNothing();
|
|
234
238
|
await db
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invitations-routes.js","sourceRoot":"","sources":["../src/invitations-routes.ts"],"names":[],"mappings":"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","sourcesContent":["import { createRoute, OpenAPIHono, z } from \"@hono/zod-openapi\"\nimport { newId } from \"@voyant-travel/db/lib/typeid\"\nimport {\n authAccount,\n authUser,\n userInvitationsTable,\n userProfilesTable,\n} from \"@voyant-travel/db/schema/iam\"\nimport { openApiValidationHook, type VoyantDb } from \"@voyant-travel/hono\"\nimport { hashPassword } from \"better-auth/crypto\"\nimport { and, desc, eq, gt, isNull } from \"drizzle-orm\"\n\nimport type { IdentityAccessRuntimeProvider } from \"./identity-access-runtime-port.js\"\n\ntype IdentityAccessEnv = {\n Bindings: Record<string, unknown>\n Variables: { userId?: string; db: VoyantDb }\n}\n\nconst DEFAULT_EXPIRY_HOURS = 72\n\nconst createInviteSchema = z.object({\n email: z.string().email(),\n expiresInHours: z\n .number()\n .int()\n .positive()\n .max(24 * 30)\n .optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\nconst redeemInviteSchema = z.object({\n name: z.string().min(1).max(200),\n password: z.string().min(8).max(128),\n})\n\nconst invitationAdminApiId = \"@voyant-travel/auth#invitations.api.admin\"\nconst invitationPublicApiId = \"@voyant-travel/auth#invitations.api.public\"\nconst jsonBody = <T extends z.ZodTypeAny>(schema: T) => ({\n required: true,\n content: { \"application/json\": { schema } },\n})\nconst responses = (...statuses: number[]) =>\n Object.fromEntries(statuses.map((status) => [status, { description: `HTTP ${status}` }]))\n\nconst listInvitationsRoute = createRoute({\n method: \"get\",\n path: \"/\",\n operationId: \"listAuthInvitations\",\n \"x-voyant-api-id\": invitationAdminApiId,\n responses: responses(200, 403),\n})\nconst createInvitationRoute = createRoute({\n method: \"post\",\n path: \"/\",\n operationId: \"createAuthInvitation\",\n \"x-voyant-api-id\": invitationAdminApiId,\n request: { body: jsonBody(createInviteSchema) },\n responses: responses(200, 403, 409),\n})\nconst deleteInvitationRoute = createRoute({\n method: \"delete\",\n path: \"/{id}\",\n operationId: \"deleteAuthInvitation\",\n \"x-voyant-api-id\": invitationAdminApiId,\n request: { params: z.object({ id: z.string() }) },\n responses: responses(200, 403),\n})\nconst inspectInvitationRoute = createRoute({\n method: \"get\",\n path: \"/{token}\",\n operationId: \"inspectAuthInvitation\",\n \"x-voyant-api-id\": invitationPublicApiId,\n request: { params: z.object({ token: z.string() }) },\n responses: responses(200, 404, 410),\n})\nconst redeemInvitationRoute = createRoute({\n method: \"post\",\n path: \"/{token}/redeem\",\n operationId: \"redeemAuthInvitation\",\n \"x-voyant-api-id\": invitationPublicApiId,\n request: {\n params: z.object({ token: z.string() }),\n body: jsonBody(redeemInviteSchema),\n },\n responses: responses(200, 409, 410),\n})\n\nfunction randomTokenBase64Url(bytes: number): string {\n const buf = new Uint8Array(bytes)\n crypto.getRandomValues(buf)\n let binary = \"\"\n for (const byte of buf) binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\")\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(input))\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\")\n}\n\nasync function isSuperAdmin(db: VoyantDb, userId: string): Promise<boolean> {\n const [row] = await db\n .select({ isSuperAdmin: userProfilesTable.isSuperAdmin })\n .from(userProfilesTable)\n .where(eq(userProfilesTable.id, userId))\n .limit(1)\n return !!row?.isSuperAdmin\n}\n\nexport function createInvitationsAdminRoutes(runtime: IdentityAccessRuntimeProvider) {\n const routes = new OpenAPIHono<IdentityAccessEnv>({ defaultHook: openApiValidationHook })\n\n routes.openapi(listInvitationsRoute, async (c) => {\n const userId = c.get(\"userId\")\n const db = c.get(\"db\")\n if (!userId || !(await isSuperAdmin(db, userId))) return c.json({ error: \"Forbidden\" }, 403)\n\n const rows = await db\n .select({\n id: userInvitationsTable.id,\n email: userInvitationsTable.email,\n expiresAt: userInvitationsTable.expiresAt,\n redeemedAt: userInvitationsTable.redeemedAt,\n createdBy: userInvitationsTable.createdBy,\n createdAt: userInvitationsTable.createdAt,\n })\n .from(userInvitationsTable)\n .orderBy(desc(userInvitationsTable.createdAt))\n .limit(100)\n\n return c.json({ data: rows })\n })\n\n routes.openapi(createInvitationRoute, async (c) => {\n const userId = c.get(\"userId\")\n const db = c.get(\"db\")\n if (!userId || !(await isSuperAdmin(db, userId))) return c.json({ error: \"Forbidden\" }, 403)\n\n const input = c.req.valid(\"json\")\n const email = input.email.trim().toLowerCase()\n const [existingUser] = await db\n .select({ id: authUser.id })\n .from(authUser)\n .where(eq(authUser.email, email))\n .limit(1)\n if (existingUser) return c.json({ error: \"A user with this email already exists.\" }, 409)\n\n const rawToken = randomTokenBase64Url(32)\n const hours = input.expiresInHours ?? DEFAULT_EXPIRY_HOURS\n const expiresAt = new Date(Date.now() + hours * 3_600_000)\n const id = newId(\"user_invitations\")\n await db.insert(userInvitationsTable).values({\n id,\n email,\n tokenHash: await sha256Hex(rawToken),\n expiresAt,\n createdBy: userId,\n metadata: input.metadata ?? null,\n })\n\n const { appUrl } = runtime.resolveDeployment(c.env)\n const acceptUrl = `${appUrl}/accept-invite?token=${encodeURIComponent(rawToken)}`\n const emailSent = await runtime.sendInvitationEmail(c.env, {\n acceptUrl,\n expiresInHours: hours,\n to: email,\n })\n\n return c.json({\n data: { id, email, expiresAt: expiresAt.toISOString(), acceptUrl, emailSent },\n })\n })\n\n routes.openapi(deleteInvitationRoute, async (c) => {\n const userId = c.get(\"userId\")\n const db = c.get(\"db\")\n if (!userId || !(await isSuperAdmin(db, userId))) return c.json({ error: \"Forbidden\" }, 403)\n\n const { id } = c.req.valid(\"param\")\n await db.delete(userInvitationsTable).where(eq(userInvitationsTable.id, id))\n return c.json({ data: { id } })\n })\n\n return routes\n}\n\nexport function createInvitationsPublicRoutes() {\n const routes = new OpenAPIHono<IdentityAccessEnv>({ defaultHook: openApiValidationHook })\n\n routes.openapi(inspectInvitationRoute, async (c) => {\n const tokenHash = await sha256Hex(c.req.valid(\"param\").token)\n const [row] = await c\n .get(\"db\")\n .select({\n email: userInvitationsTable.email,\n expiresAt: userInvitationsTable.expiresAt,\n redeemedAt: userInvitationsTable.redeemedAt,\n })\n .from(userInvitationsTable)\n .where(eq(userInvitationsTable.tokenHash, tokenHash))\n .limit(1)\n\n if (!row) return c.json({ valid: false, reason: \"not_found\" }, 404)\n if (row.redeemedAt) return c.json({ valid: false, reason: \"redeemed\" }, 410)\n if (row.expiresAt.getTime() < Date.now()) {\n return c.json({ valid: false, reason: \"expired\" }, 410)\n }\n return c.json({ valid: true, email: row.email, expiresAt: row.expiresAt.toISOString() })\n })\n\n routes.openapi(redeemInvitationRoute, async (c) => {\n const input = c.req.valid(\"json\")\n const db = c.get(\"db\")\n const now = new Date()\n const [invite] = await db\n .select()\n .from(userInvitationsTable)\n .where(\n and(\n eq(userInvitationsTable.tokenHash, await sha256Hex(c.req.valid(\"param\").token)),\n isNull(userInvitationsTable.redeemedAt),\n gt(userInvitationsTable.expiresAt, now),\n ),\n )\n .limit(1)\n if (!invite) {\n return c.json({ error: \"Invitation is invalid, expired, or already redeemed.\" }, 410)\n }\n\n const [existingUser] = await db\n .select({ id: authUser.id })\n .from(authUser)\n .where(eq(authUser.email, invite.email))\n .limit(1)\n if (existingUser) return c.json({ error: \"A user with this email already exists.\" }, 409)\n\n const userId = crypto.randomUUID()\n const name = input.name.trim()\n const [firstName, ...rest] = name.split(/\\s+/)\n await db.insert(authUser).values({\n id: userId,\n name,\n email: invite.email,\n emailVerified: true,\n image: null,\n createdAt: now,\n updatedAt: now,\n })\n await db.insert(authAccount).values({\n id: `acc_${userId}`,\n userId,\n accountId: invite.email,\n providerId: \"credential\",\n password: await hashPassword(input.password),\n createdAt: now,\n updatedAt: now,\n })\n await db\n .insert(userProfilesTable)\n .values({\n id: userId,\n firstName: firstName ?? null,\n lastName: rest.join(\" \") || null,\n avatarUrl: null,\n })\n .onConflictDoNothing()\n await db\n .update(userInvitationsTable)\n .set({ redeemedAt: now, redeemedByUserId: userId })\n .where(eq(userInvitationsTable.id, invite.id))\n\n return c.json({ data: { id: userId, email: invite.email, name } })\n })\n\n return routes\n}\n"]}
|
|
1
|
+
{"version":3,"file":"invitations-routes.js","sourceRoot":"","sources":["../src/invitations-routes.ts"],"names":[],"mappings":"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","sourcesContent":["import { createRoute, OpenAPIHono, z } from \"@hono/zod-openapi\"\nimport { newId } from \"@voyant-travel/db/lib/typeid\"\nimport {\n authAccount,\n authUser,\n userInvitationsTable,\n userProfilesTable,\n} from \"@voyant-travel/db/schema/iam\"\nimport { openApiValidationHook, type VoyantDb } from \"@voyant-travel/hono\"\nimport { hashPassword } from \"better-auth/crypto\"\nimport { and, desc, eq, gt, isNull } from \"drizzle-orm\"\n\nimport type { IdentityAccessRuntimeProvider } from \"./identity-access-runtime-port.js\"\nimport { localRoleForRedeemedInvitation } from \"./team-management-local-adapter.js\"\n\ntype IdentityAccessEnv = {\n Bindings: Record<string, unknown>\n Variables: { userId?: string; db: VoyantDb }\n}\n\nconst DEFAULT_EXPIRY_HOURS = 72\n\nconst createInviteSchema = z.object({\n email: z.string().email(),\n expiresInHours: z\n .number()\n .int()\n .positive()\n .max(24 * 30)\n .optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\nconst redeemInviteSchema = z.object({\n name: z.string().min(1).max(200),\n password: z.string().min(8).max(128),\n})\n\nconst invitationAdminApiId = \"@voyant-travel/auth#invitations.api.admin\"\nconst invitationPublicApiId = \"@voyant-travel/auth#invitations.api.public\"\nconst jsonBody = <T extends z.ZodTypeAny>(schema: T) => ({\n required: true,\n content: { \"application/json\": { schema } },\n})\nconst responses = (...statuses: number[]) =>\n Object.fromEntries(statuses.map((status) => [status, { description: `HTTP ${status}` }]))\n\nconst listInvitationsRoute = createRoute({\n method: \"get\",\n path: \"/\",\n operationId: \"listAuthInvitations\",\n \"x-voyant-api-id\": invitationAdminApiId,\n responses: responses(200, 403),\n})\nconst createInvitationRoute = createRoute({\n method: \"post\",\n path: \"/\",\n operationId: \"createAuthInvitation\",\n \"x-voyant-api-id\": invitationAdminApiId,\n request: { body: jsonBody(createInviteSchema) },\n responses: responses(200, 403, 409),\n})\nconst deleteInvitationRoute = createRoute({\n method: \"delete\",\n path: \"/{id}\",\n operationId: \"deleteAuthInvitation\",\n \"x-voyant-api-id\": invitationAdminApiId,\n request: { params: z.object({ id: z.string() }) },\n responses: responses(200, 403),\n})\nconst inspectInvitationRoute = createRoute({\n method: \"get\",\n path: \"/{token}\",\n operationId: \"inspectAuthInvitation\",\n \"x-voyant-api-id\": invitationPublicApiId,\n request: { params: z.object({ token: z.string() }) },\n responses: responses(200, 404, 410),\n})\nconst redeemInvitationRoute = createRoute({\n method: \"post\",\n path: \"/{token}/redeem\",\n operationId: \"redeemAuthInvitation\",\n \"x-voyant-api-id\": invitationPublicApiId,\n request: {\n params: z.object({ token: z.string() }),\n body: jsonBody(redeemInviteSchema),\n },\n responses: responses(200, 409, 410),\n})\n\nfunction randomTokenBase64Url(bytes: number): string {\n const buf = new Uint8Array(bytes)\n crypto.getRandomValues(buf)\n let binary = \"\"\n for (const byte of buf) binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\")\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(input))\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\")\n}\n\nasync function isSuperAdmin(db: VoyantDb, userId: string): Promise<boolean> {\n const [row] = await db\n .select({ isSuperAdmin: userProfilesTable.isSuperAdmin })\n .from(userProfilesTable)\n .where(eq(userProfilesTable.id, userId))\n .limit(1)\n return !!row?.isSuperAdmin\n}\n\nexport function createInvitationsAdminRoutes(runtime: IdentityAccessRuntimeProvider) {\n const routes = new OpenAPIHono<IdentityAccessEnv>({ defaultHook: openApiValidationHook })\n\n routes.openapi(listInvitationsRoute, async (c) => {\n const userId = c.get(\"userId\")\n const db = c.get(\"db\")\n if (!userId || !(await isSuperAdmin(db, userId))) return c.json({ error: \"Forbidden\" }, 403)\n\n const rows = await db\n .select({\n id: userInvitationsTable.id,\n email: userInvitationsTable.email,\n expiresAt: userInvitationsTable.expiresAt,\n redeemedAt: userInvitationsTable.redeemedAt,\n createdBy: userInvitationsTable.createdBy,\n createdAt: userInvitationsTable.createdAt,\n })\n .from(userInvitationsTable)\n .orderBy(desc(userInvitationsTable.createdAt))\n .limit(100)\n\n return c.json({ data: rows })\n })\n\n routes.openapi(createInvitationRoute, async (c) => {\n const userId = c.get(\"userId\")\n const db = c.get(\"db\")\n if (!userId || !(await isSuperAdmin(db, userId))) return c.json({ error: \"Forbidden\" }, 403)\n\n const input = c.req.valid(\"json\")\n const email = input.email.trim().toLowerCase()\n const [existingUser] = await db\n .select({ id: authUser.id })\n .from(authUser)\n .where(eq(authUser.email, email))\n .limit(1)\n if (existingUser) return c.json({ error: \"A user with this email already exists.\" }, 409)\n\n const rawToken = randomTokenBase64Url(32)\n const hours = input.expiresInHours ?? DEFAULT_EXPIRY_HOURS\n const expiresAt = new Date(Date.now() + hours * 3_600_000)\n const id = newId(\"user_invitations\")\n await db.insert(userInvitationsTable).values({\n id,\n email,\n tokenHash: await sha256Hex(rawToken),\n expiresAt,\n createdBy: userId,\n metadata: input.metadata ?? null,\n })\n\n const { appUrl } = runtime.resolveDeployment(c.env)\n const acceptUrl = `${appUrl}/accept-invite?token=${encodeURIComponent(rawToken)}`\n const emailSent = await runtime.sendInvitationEmail(c.env, {\n acceptUrl,\n expiresInHours: hours,\n to: email,\n })\n\n return c.json({\n data: { id, email, expiresAt: expiresAt.toISOString(), acceptUrl, emailSent },\n })\n })\n\n routes.openapi(deleteInvitationRoute, async (c) => {\n const userId = c.get(\"userId\")\n const db = c.get(\"db\")\n if (!userId || !(await isSuperAdmin(db, userId))) return c.json({ error: \"Forbidden\" }, 403)\n\n const { id } = c.req.valid(\"param\")\n await db.delete(userInvitationsTable).where(eq(userInvitationsTable.id, id))\n return c.json({ data: { id } })\n })\n\n return routes\n}\n\nexport function createInvitationsPublicRoutes() {\n const routes = new OpenAPIHono<IdentityAccessEnv>({ defaultHook: openApiValidationHook })\n\n routes.openapi(inspectInvitationRoute, async (c) => {\n const tokenHash = await sha256Hex(c.req.valid(\"param\").token)\n const [row] = await c\n .get(\"db\")\n .select({\n email: userInvitationsTable.email,\n expiresAt: userInvitationsTable.expiresAt,\n redeemedAt: userInvitationsTable.redeemedAt,\n })\n .from(userInvitationsTable)\n .where(eq(userInvitationsTable.tokenHash, tokenHash))\n .limit(1)\n\n if (!row) return c.json({ valid: false, reason: \"not_found\" }, 404)\n if (row.redeemedAt) return c.json({ valid: false, reason: \"redeemed\" }, 410)\n if (row.expiresAt.getTime() < Date.now()) {\n return c.json({ valid: false, reason: \"expired\" }, 410)\n }\n return c.json({ valid: true, email: row.email, expiresAt: row.expiresAt.toISOString() })\n })\n\n routes.openapi(redeemInvitationRoute, async (c) => {\n const input = c.req.valid(\"json\")\n const db = c.get(\"db\")\n const now = new Date()\n const [invite] = await db\n .select()\n .from(userInvitationsTable)\n .where(\n and(\n eq(userInvitationsTable.tokenHash, await sha256Hex(c.req.valid(\"param\").token)),\n isNull(userInvitationsTable.redeemedAt),\n gt(userInvitationsTable.expiresAt, now),\n ),\n )\n .limit(1)\n if (!invite) {\n return c.json({ error: \"Invitation is invalid, expired, or already redeemed.\" }, 410)\n }\n\n const [existingUser] = await db\n .select({ id: authUser.id })\n .from(authUser)\n .where(eq(authUser.email, invite.email))\n .limit(1)\n if (existingUser) return c.json({ error: \"A user with this email already exists.\" }, 409)\n\n const userId = crypto.randomUUID()\n const name = input.name.trim()\n const [firstName, ...rest] = name.split(/\\s+/)\n const teamRole = localRoleForRedeemedInvitation(invite.metadata)\n await db.insert(authUser).values({\n id: userId,\n name,\n email: invite.email,\n emailVerified: true,\n image: null,\n createdAt: now,\n updatedAt: now,\n })\n await db.insert(authAccount).values({\n id: `acc_${userId}`,\n userId,\n accountId: invite.email,\n providerId: \"credential\",\n password: await hashPassword(input.password),\n createdAt: now,\n updatedAt: now,\n })\n await db\n .insert(userProfilesTable)\n .values({\n id: userId,\n firstName: firstName ?? null,\n lastName: rest.join(\" \") || null,\n avatarUrl: null,\n isSuperAdmin: teamRole.isSuperAdmin,\n permissions: teamRole.permissions,\n })\n .onConflictDoNothing()\n await db\n .update(userInvitationsTable)\n .set({ redeemedAt: now, redeemedByUserId: userId })\n .where(eq(userInvitationsTable.id, invite.id))\n\n return c.json({ data: { id: userId, email: invite.email, name } })\n })\n\n return routes\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { VoyantDb } from "@voyant-travel/hono";
|
|
2
|
+
import type { BetterAuthPlugin } from "better-auth/types";
|
|
3
|
+
import type { TeamMemberStatus } from "./team-management-runtime-port.js";
|
|
4
|
+
export declare const DEACTIVATED_PROVIDER_PREFIX = "voyant-deactivated:";
|
|
5
|
+
export declare function localProviderIdForStatus(providerId: string, status: TeamMemberStatus): string;
|
|
6
|
+
export declare function isDeactivatedProviderId(providerId: string): boolean;
|
|
7
|
+
export declare function isLocalMemberDeactivated(db: VoyantDb, userId: string): Promise<boolean>;
|
|
8
|
+
export declare function isLocalMemberEmailDeactivated(db: VoyantDb, email: string): Promise<boolean>;
|
|
9
|
+
export declare function isLocalMemberSessionActive(db: VoyantDb, sessionId: string, userId: string): Promise<boolean>;
|
|
10
|
+
export interface LocalMemberAccessResolver {
|
|
11
|
+
isEmailDeactivated(email: string): Promise<boolean>;
|
|
12
|
+
isSessionActive(sessionId: string, userId: string): Promise<boolean>;
|
|
13
|
+
isUserDeactivated(userId: string): Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
export declare function createLocalMemberAccessPlugin(resolver: LocalMemberAccessResolver): BetterAuthPlugin;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { authAccount, authSession, authUser } from "@voyant-travel/db/schema/iam";
|
|
2
|
+
import { APIError, createAuthMiddleware, getSessionFromCtx } from "better-auth/api";
|
|
3
|
+
import { deleteSessionCookie } from "better-auth/cookies";
|
|
4
|
+
import { and, eq } from "drizzle-orm";
|
|
5
|
+
export const DEACTIVATED_PROVIDER_PREFIX = "voyant-deactivated:";
|
|
6
|
+
export function localProviderIdForStatus(providerId, status) {
|
|
7
|
+
if (status === "deactivated") {
|
|
8
|
+
return providerId.startsWith(DEACTIVATED_PROVIDER_PREFIX)
|
|
9
|
+
? providerId
|
|
10
|
+
: `${DEACTIVATED_PROVIDER_PREFIX}${providerId}`;
|
|
11
|
+
}
|
|
12
|
+
return providerId.startsWith(DEACTIVATED_PROVIDER_PREFIX)
|
|
13
|
+
? providerId.slice(DEACTIVATED_PROVIDER_PREFIX.length)
|
|
14
|
+
: providerId;
|
|
15
|
+
}
|
|
16
|
+
export function isDeactivatedProviderId(providerId) {
|
|
17
|
+
return providerId.startsWith(DEACTIVATED_PROVIDER_PREFIX);
|
|
18
|
+
}
|
|
19
|
+
async function accountProvidersForUser(db, userId) {
|
|
20
|
+
const accounts = await db
|
|
21
|
+
.select({ providerId: authAccount.providerId })
|
|
22
|
+
.from(authAccount)
|
|
23
|
+
.where(eq(authAccount.userId, userId));
|
|
24
|
+
return accounts.map((account) => account.providerId);
|
|
25
|
+
}
|
|
26
|
+
export async function isLocalMemberDeactivated(db, userId) {
|
|
27
|
+
const providerIds = await accountProvidersForUser(db, userId);
|
|
28
|
+
return providerIds.length > 0 && providerIds.every(isDeactivatedProviderId);
|
|
29
|
+
}
|
|
30
|
+
export async function isLocalMemberEmailDeactivated(db, email) {
|
|
31
|
+
const accounts = await db
|
|
32
|
+
.select({ providerId: authAccount.providerId })
|
|
33
|
+
.from(authAccount)
|
|
34
|
+
.innerJoin(authUser, eq(authUser.id, authAccount.userId))
|
|
35
|
+
.where(and(eq(authUser.email, email.trim().toLowerCase()), eq(authAccount.userId, authUser.id)));
|
|
36
|
+
return (accounts.length > 0 && accounts.every((account) => isDeactivatedProviderId(account.providerId)));
|
|
37
|
+
}
|
|
38
|
+
export async function isLocalMemberSessionActive(db, sessionId, userId) {
|
|
39
|
+
const sessions = await db
|
|
40
|
+
.select({ id: authSession.id })
|
|
41
|
+
.from(authSession)
|
|
42
|
+
.where(and(eq(authSession.id, sessionId), eq(authSession.userId, userId)))
|
|
43
|
+
.limit(1);
|
|
44
|
+
return sessions.length > 0;
|
|
45
|
+
}
|
|
46
|
+
const ACCESS_DENIED_MESSAGE = "This account is deactivated.";
|
|
47
|
+
function requestEmail(context) {
|
|
48
|
+
const path = context.path ?? "";
|
|
49
|
+
if (!path.includes("email-otp") && !path.includes("email") && !path.includes("password")) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
for (const input of [context.body, context.query]) {
|
|
53
|
+
if (!input || typeof input !== "object")
|
|
54
|
+
continue;
|
|
55
|
+
const email = input.email;
|
|
56
|
+
if (typeof email === "string" && email.trim().length > 0) {
|
|
57
|
+
return email.trim().toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function denyAccess() {
|
|
63
|
+
throw new APIError("FORBIDDEN", { message: ACCESS_DENIED_MESSAGE });
|
|
64
|
+
}
|
|
65
|
+
export function createLocalMemberAccessPlugin(resolver) {
|
|
66
|
+
return {
|
|
67
|
+
id: "voyant-local-member-access",
|
|
68
|
+
init() {
|
|
69
|
+
return {
|
|
70
|
+
options: {
|
|
71
|
+
databaseHooks: {
|
|
72
|
+
account: {
|
|
73
|
+
create: {
|
|
74
|
+
before: async (account) => {
|
|
75
|
+
if (await resolver.isUserDeactivated(account.userId))
|
|
76
|
+
denyAccess();
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
session: {
|
|
81
|
+
create: {
|
|
82
|
+
before: async (session) => {
|
|
83
|
+
if (await resolver.isUserDeactivated(session.userId))
|
|
84
|
+
denyAccess();
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
hooks: {
|
|
93
|
+
before: [
|
|
94
|
+
{
|
|
95
|
+
matcher: () => true,
|
|
96
|
+
handler: createAuthMiddleware(async (context) => {
|
|
97
|
+
const session = await getSessionFromCtx(context);
|
|
98
|
+
if (session) {
|
|
99
|
+
const [isDeactivated, isSessionActive] = await Promise.all([
|
|
100
|
+
resolver.isUserDeactivated(session.user.id),
|
|
101
|
+
resolver.isSessionActive(session.session.id, session.user.id),
|
|
102
|
+
]);
|
|
103
|
+
if (isDeactivated || !isSessionActive) {
|
|
104
|
+
deleteSessionCookie(context);
|
|
105
|
+
if (context.path === "/get-session")
|
|
106
|
+
return context.json(null);
|
|
107
|
+
denyAccess();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const email = requestEmail(context);
|
|
111
|
+
if (email && (await resolver.isEmailDeactivated(email)))
|
|
112
|
+
denyAccess();
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=local-member-access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-member-access.js","sourceRoot":"","sources":["../src/local-member-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAA;AAEjF,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAEzD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAIrC,MAAM,CAAC,MAAM,2BAA2B,GAAG,qBAAqB,CAAA;AAEhE,MAAM,UAAU,wBAAwB,CAAC,UAAkB,EAAE,MAAwB;IACnF,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;QAC7B,OAAO,UAAU,CAAC,UAAU,CAAC,2BAA2B,CAAC;YACvD,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,GAAG,2BAA2B,GAAG,UAAU,EAAE,CAAA;IACnD,CAAC;IACD,OAAO,UAAU,CAAC,UAAU,CAAC,2BAA2B,CAAC;QACvD,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,2BAA2B,CAAC,MAAM,CAAC;QACtD,CAAC,CAAC,UAAU,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,UAAkB;IACxD,OAAO,UAAU,CAAC,UAAU,CAAC,2BAA2B,CAAC,CAAA;AAC3D,CAAC;AAED,KAAK,UAAU,uBAAuB,CAAC,EAAY,EAAE,MAAc;IACjE,MAAM,QAAQ,GAAG,MAAM,EAAE;SACtB,MAAM,CAAC,EAAE,UAAU,EAAE,WAAW,CAAC,UAAU,EAAE,CAAC;SAC9C,IAAI,CAAC,WAAW,CAAC;SACjB,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxC,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,EAAY,EAAE,MAAc;IACzE,MAAM,WAAW,GAAG,MAAM,uBAAuB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;IAC7D,OAAO,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CAAC,EAAY,EAAE,KAAa;IAC7E,MAAM,QAAQ,GAAG,MAAM,EAAE;SACtB,MAAM,CAAC,EAAE,UAAU,EAAE,WAAW,CAAC,UAAU,EAAE,CAAC;SAC9C,IAAI,CAAC,WAAW,CAAC;SACjB,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;SACxD,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAClG,OAAO,CACL,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,uBAAuB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAChG,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,EAAY,EACZ,SAAiB,EACjB,MAAc;IAEd,MAAM,QAAQ,GAAG,MAAM,EAAE;SACtB,MAAM,CAAC,EAAE,EAAE,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC;SAC9B,IAAI,CAAC,WAAW,CAAC;SACjB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;SACzE,KAAK,CAAC,CAAC,CAAC,CAAA;IACX,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;AAC5B,CAAC;AAQD,MAAM,qBAAqB,GAAG,8BAA8B,CAAA;AAE5D,SAAS,YAAY,CAAC,OAA2D;IAC/E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAA;IAC/B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACzF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACjD,MAAM,KAAK,GAAI,KAAiC,CAAC,KAAK,CAAA;QACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACnC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,UAAU;IACjB,MAAM,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,QAAmC;IAEnC,OAAO;QACL,EAAE,EAAE,4BAA4B;QAChC,IAAI;YACF,OAAO;gBACL,OAAO,EAAE;oBACP,aAAa,EAAE;wBACb,OAAO,EAAE;4BACP,MAAM,EAAE;gCACN,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;oCACxB,IAAI,MAAM,QAAQ,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC;wCAAE,UAAU,EAAE,CAAA;gCACpE,CAAC;6BACF;yBACF;wBACD,OAAO,EAAE;4BACP,MAAM,EAAE;gCACN,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;oCACxB,IAAI,MAAM,QAAQ,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC;wCAAE,UAAU,EAAE,CAAA;gCACpE,CAAC;6BACF;yBACF;qBACF;iBACF;aACF,CAAA;QACH,CAAC;QACD,KAAK,EAAE;YACL,MAAM,EAAE;gBACN;oBACE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;oBACnB,OAAO,EAAE,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;wBAC9C,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAA;wBAChD,IAAI,OAAO,EAAE,CAAC;4BACZ,MAAM,CAAC,aAAa,EAAE,eAAe,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gCACzD,QAAQ,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gCAC3C,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;6BAC9D,CAAC,CAAA;4BACF,IAAI,aAAa,IAAI,CAAC,eAAe,EAAE,CAAC;gCACtC,mBAAmB,CAAC,OAAO,CAAC,CAAA;gCAC5B,IAAI,OAAO,CAAC,IAAI,KAAK,cAAc;oCAAE,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gCAC9D,UAAU,EAAE,CAAA;4BACd,CAAC;wBACH,CAAC;wBAED,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;wBACnC,IAAI,KAAK,IAAI,CAAC,MAAM,QAAQ,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;4BAAE,UAAU,EAAE,CAAA;oBACvE,CAAC,CAAC;iBACH;aACF;SACF;KACF,CAAA;AACH,CAAC","sourcesContent":["import { authAccount, authSession, authUser } from \"@voyant-travel/db/schema/iam\"\nimport type { VoyantDb } from \"@voyant-travel/hono\"\nimport { APIError, createAuthMiddleware, getSessionFromCtx } from \"better-auth/api\"\nimport { deleteSessionCookie } from \"better-auth/cookies\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\nimport { and, eq } from \"drizzle-orm\"\n\nimport type { TeamMemberStatus } from \"./team-management-runtime-port.js\"\n\nexport const DEACTIVATED_PROVIDER_PREFIX = \"voyant-deactivated:\"\n\nexport function localProviderIdForStatus(providerId: string, status: TeamMemberStatus): string {\n if (status === \"deactivated\") {\n return providerId.startsWith(DEACTIVATED_PROVIDER_PREFIX)\n ? providerId\n : `${DEACTIVATED_PROVIDER_PREFIX}${providerId}`\n }\n return providerId.startsWith(DEACTIVATED_PROVIDER_PREFIX)\n ? providerId.slice(DEACTIVATED_PROVIDER_PREFIX.length)\n : providerId\n}\n\nexport function isDeactivatedProviderId(providerId: string): boolean {\n return providerId.startsWith(DEACTIVATED_PROVIDER_PREFIX)\n}\n\nasync function accountProvidersForUser(db: VoyantDb, userId: string): Promise<string[]> {\n const accounts = await db\n .select({ providerId: authAccount.providerId })\n .from(authAccount)\n .where(eq(authAccount.userId, userId))\n return accounts.map((account) => account.providerId)\n}\n\nexport async function isLocalMemberDeactivated(db: VoyantDb, userId: string): Promise<boolean> {\n const providerIds = await accountProvidersForUser(db, userId)\n return providerIds.length > 0 && providerIds.every(isDeactivatedProviderId)\n}\n\nexport async function isLocalMemberEmailDeactivated(db: VoyantDb, email: string): Promise<boolean> {\n const accounts = await db\n .select({ providerId: authAccount.providerId })\n .from(authAccount)\n .innerJoin(authUser, eq(authUser.id, authAccount.userId))\n .where(and(eq(authUser.email, email.trim().toLowerCase()), eq(authAccount.userId, authUser.id)))\n return (\n accounts.length > 0 && accounts.every((account) => isDeactivatedProviderId(account.providerId))\n )\n}\n\nexport async function isLocalMemberSessionActive(\n db: VoyantDb,\n sessionId: string,\n userId: string,\n): Promise<boolean> {\n const sessions = await db\n .select({ id: authSession.id })\n .from(authSession)\n .where(and(eq(authSession.id, sessionId), eq(authSession.userId, userId)))\n .limit(1)\n return sessions.length > 0\n}\n\nexport interface LocalMemberAccessResolver {\n isEmailDeactivated(email: string): Promise<boolean>\n isSessionActive(sessionId: string, userId: string): Promise<boolean>\n isUserDeactivated(userId: string): Promise<boolean>\n}\n\nconst ACCESS_DENIED_MESSAGE = \"This account is deactivated.\"\n\nfunction requestEmail(context: { path?: string; body?: unknown; query?: unknown }): string | null {\n const path = context.path ?? \"\"\n if (!path.includes(\"email-otp\") && !path.includes(\"email\") && !path.includes(\"password\")) {\n return null\n }\n\n for (const input of [context.body, context.query]) {\n if (!input || typeof input !== \"object\") continue\n const email = (input as Record<string, unknown>).email\n if (typeof email === \"string\" && email.trim().length > 0) {\n return email.trim().toLowerCase()\n }\n }\n return null\n}\n\nfunction denyAccess(): never {\n throw new APIError(\"FORBIDDEN\", { message: ACCESS_DENIED_MESSAGE })\n}\n\nexport function createLocalMemberAccessPlugin(\n resolver: LocalMemberAccessResolver,\n): BetterAuthPlugin {\n return {\n id: \"voyant-local-member-access\",\n init() {\n return {\n options: {\n databaseHooks: {\n account: {\n create: {\n before: async (account) => {\n if (await resolver.isUserDeactivated(account.userId)) denyAccess()\n },\n },\n },\n session: {\n create: {\n before: async (session) => {\n if (await resolver.isUserDeactivated(session.userId)) denyAccess()\n },\n },\n },\n },\n },\n }\n },\n hooks: {\n before: [\n {\n matcher: () => true,\n handler: createAuthMiddleware(async (context) => {\n const session = await getSessionFromCtx(context)\n if (session) {\n const [isDeactivated, isSessionActive] = await Promise.all([\n resolver.isUserDeactivated(session.user.id),\n resolver.isSessionActive(session.session.id, session.user.id),\n ])\n if (isDeactivated || !isSessionActive) {\n deleteSessionCookie(context)\n if (context.path === \"/get-session\") return context.json(null)\n denyAccess()\n }\n }\n\n const email = requestEmail(context)\n if (email && (await resolver.isEmailDeactivated(email))) denyAccess()\n }),\n },\n ],\n },\n }\n}\n"]}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { cloudAdminMembersConfigFromRevalidate } from "./cloud-broker.js";
|
|
2
2
|
import { identityAccessRuntimePort, } from "./identity-access-runtime-port.js";
|
|
3
|
+
import { createCloudTeamManagementAdapter } from "./team-management-cloud-adapter.js";
|
|
4
|
+
import { createLocalTeamManagementAdapter } from "./team-management-local-adapter.js";
|
|
5
|
+
import { createGuardedTeamManagementProvider } from "./team-management-policy.js";
|
|
6
|
+
import { teamManagementRuntimePort } from "./team-management-runtime-port.js";
|
|
3
7
|
/** Package-owned registration map for Auth deployment adapters. */
|
|
4
8
|
export function createAuthRuntimePortContribution(host) {
|
|
5
9
|
const identityAccess = {
|
|
@@ -47,6 +51,19 @@ export function createAuthRuntimePortContribution(host) {
|
|
|
47
51
|
}
|
|
48
52
|
},
|
|
49
53
|
};
|
|
50
|
-
|
|
54
|
+
const localTeamManagement = createLocalTeamManagementAdapter(identityAccess);
|
|
55
|
+
const cloudTeamManagement = createCloudTeamManagementAdapter(identityAccess);
|
|
56
|
+
const teamManagement = createGuardedTeamManagementProvider((context) => {
|
|
57
|
+
const selectedAuthProvider = host.primitives.config.read(context.bindings, "deployment.providers.auth");
|
|
58
|
+
if (selectedAuthProvider === "better-auth")
|
|
59
|
+
return localTeamManagement;
|
|
60
|
+
if (selectedAuthProvider === "voyant-cloud")
|
|
61
|
+
return cloudTeamManagement;
|
|
62
|
+
throw new Error("Team management requires deployment.providers.auth to select better-auth or voyant-cloud.");
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
[identityAccessRuntimePort.id]: identityAccess,
|
|
66
|
+
[teamManagementRuntimePort.id]: teamManagement,
|
|
67
|
+
};
|
|
51
68
|
}
|
|
52
69
|
//# sourceMappingURL=runtime-contributor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-contributor.js","sourceRoot":"","sources":["../src/runtime-contributor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qCAAqC,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,EAEL,yBAAyB,GAC1B,MAAM,mCAAmC,CAAA;
|
|
1
|
+
{"version":3,"file":"runtime-contributor.js","sourceRoot":"","sources":["../src/runtime-contributor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { VoyantRuntimeHostPrimitives } from \"@voyant-travel/core\"\nimport { cloudAdminMembersConfigFromRevalidate } from \"./cloud-broker.js\"\nimport {\n type IdentityAccessRuntimeProvider,\n identityAccessRuntimePort,\n} from \"./identity-access-runtime-port.js\"\nimport { createCloudTeamManagementAdapter } from \"./team-management-cloud-adapter.js\"\nimport { createLocalTeamManagementAdapter } from \"./team-management-local-adapter.js\"\nimport { createGuardedTeamManagementProvider } from \"./team-management-policy.js\"\nimport { teamManagementRuntimePort } from \"./team-management-runtime-port.js\"\n\ninterface InvitationNotificationProvider {\n readonly channels: ReadonlyArray<string>\n send(payload: {\n channel: \"email\"\n to: string\n template: string\n subject: string\n html: string\n }): Promise<unknown>\n}\n\nexport interface AuthRuntimeContributorHost {\n primitives: VoyantRuntimeHostPrimitives\n}\n\n/** Package-owned registration map for Auth deployment adapters. */\nexport function createAuthRuntimePortContribution(\n host: AuthRuntimeContributorHost,\n): Readonly<Record<string, unknown>> {\n const identityAccess: IdentityAccessRuntimeProvider = {\n resolveDeployment(bindings) {\n const env = bindings as Record<string, string | undefined>\n const selectedAuthProvider = host.primitives.config.read(\n bindings,\n \"deployment.providers.auth\",\n )\n if (selectedAuthProvider !== \"better-auth\" && selectedAuthProvider !== \"voyant-cloud\") {\n throw new Error(\n \"Auth runtime requires deployment.providers.auth to select better-auth or voyant-cloud.\",\n )\n }\n const appUrl = (env.APP_URL || env.DASH_BASE_URL || \"http://localhost:3300\")\n .trim()\n .replace(/\\/$/, \"\")\n const deploymentId = env.VOYANT_CLOUD_DEPLOYMENT_ID?.trim()\n const revalidateUrl = env.VOYANT_CLOUD_ADMIN_AUTH_REVALIDATE_URL?.trim()\n const clientToken = env.VOYANT_CLOUD_ADMIN_AUTH_CLIENT_TOKEN?.trim()\n return {\n appUrl,\n authMode: selectedAuthProvider === \"voyant-cloud\" ? \"voyant-cloud\" : \"local\",\n cloudAdminMembers:\n deploymentId && revalidateUrl && clientToken\n ? cloudAdminMembersConfigFromRevalidate({ revalidateUrl, deploymentId, clientToken })\n : null,\n }\n },\n async sendInvitationEmail(bindings, message) {\n const resolver = host.primitives.config.read(bindings, \"notificationProviders\")\n const providers =\n typeof resolver === \"function\"\n ? (resolver(\n host.primitives.env(bindings),\n ) as ReadonlyArray<InvitationNotificationProvider>)\n : []\n const provider = providers.find((candidate) => candidate.channels.includes(\"email\"))\n if (!provider) return false\n try {\n await provider.send({\n channel: \"email\",\n to: message.to,\n template: \"auth.invitation\",\n subject: \"You've been invited to Voyant\",\n html: `<p>You've been invited to join a Voyant workspace.</p><p><a href=\"${message.acceptUrl}\">Accept invitation</a></p><p>The link expires in ${message.expiresInHours} hours.</p>`,\n })\n return true\n } catch (error) {\n console.error(\"[invitations] email send failed:\", error)\n return false\n }\n },\n }\n\n const localTeamManagement = createLocalTeamManagementAdapter(identityAccess)\n const cloudTeamManagement = createCloudTeamManagementAdapter(identityAccess)\n const teamManagement = createGuardedTeamManagementProvider((context) => {\n const selectedAuthProvider = host.primitives.config.read(\n context.bindings,\n \"deployment.providers.auth\",\n )\n if (selectedAuthProvider === \"better-auth\") return localTeamManagement\n if (selectedAuthProvider === \"voyant-cloud\") return cloudTeamManagement\n throw new Error(\n \"Team management requires deployment.providers.auth to select better-auth or voyant-cloud.\",\n )\n })\n\n return {\n [identityAccessRuntimePort.id]: identityAccess,\n [teamManagementRuntimePort.id]: teamManagement,\n }\n}\n"]}
|
package/dist/server.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type BetterAuthOptions } from "better-auth";
|
|
|
4
4
|
import { emailOTP } from "better-auth/plugins";
|
|
5
5
|
import type { BetterAuthPlugin } from "better-auth/types";
|
|
6
6
|
import type { AnyPgTable } from "drizzle-orm/pg-core";
|
|
7
|
+
import { createLocalMemberAccessPlugin } from "./local-member-access.js";
|
|
7
8
|
export declare function getAuthSecret(): string;
|
|
8
9
|
export type { AccountProfileUpdateHandler, ApiTokenRotationStore, BetterAuthApiTokenManagement, HandleAccountProfileRequestOptions, HandleApiTokenManagementRequestOptions, HandleOrganizationMembersRequestOptions, OrganizationMemberRecord, OrganizationMembersListHandler, OrganizationMembersListInput, } from "./auth-facades.js";
|
|
9
10
|
export { handleAccountProfileRequest, handleApiTokenManagementRequest, handleOrganizationMembersRequest, } from "./auth-facades.js";
|
|
@@ -22,7 +23,11 @@ type ResolvedBetterAuthChangeEmail<UserOptions extends BetterAuthOptions["user"]
|
|
|
22
23
|
type ResolvedBetterAuthUserOptions<UserOptions extends BetterAuthOptions["user"]> = Omit<DefinedBetterAuthUserOptions<UserOptions>, "changeEmail"> & {
|
|
23
24
|
changeEmail: ResolvedBetterAuthChangeEmail<UserOptions>;
|
|
24
25
|
};
|
|
25
|
-
type VoyantBetterAuthPlugins = [
|
|
26
|
+
type VoyantBetterAuthPlugins = [
|
|
27
|
+
ReturnType<typeof apiKey>,
|
|
28
|
+
ReturnType<typeof emailOTP>,
|
|
29
|
+
ReturnType<typeof createLocalMemberAccessPlugin>
|
|
30
|
+
];
|
|
26
31
|
type ResolvedBetterAuthPlugins<Plugins extends BetterAuthPlugin[] | undefined> = Plugins extends BetterAuthPlugin[] ? [...VoyantBetterAuthPlugins, ...Plugins] : VoyantBetterAuthPlugins;
|
|
27
32
|
export interface DisableSignupWhenUsersExistOptions {
|
|
28
33
|
/**
|
|
@@ -85,11 +90,10 @@ export interface CreateBetterAuthOptions<UserOptions extends BetterAuthOptions["
|
|
|
85
90
|
}) => Promise<void>;
|
|
86
91
|
/**
|
|
87
92
|
* Better Auth session cookie cache: session data rides in a short-lived
|
|
88
|
-
* signed cookie
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* `maxAge` (seconds).
|
|
93
|
+
* signed cookie. Enabled by default with a 5-minute TTL. Voyant still
|
|
94
|
+
* validates local membership and session existence against Postgres so a
|
|
95
|
+
* deleted session cannot be revived from the cookie cache. Pass `false` to
|
|
96
|
+
* disable payload caching, or tune `maxAge` (seconds).
|
|
93
97
|
*/
|
|
94
98
|
sessionCookieCache?: false | {
|
|
95
99
|
maxAge?: number;
|
package/dist/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import { betterAuth } from "better-auth";
|
|
|
5
5
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
6
6
|
import { emailOTP } from "better-auth/plugins";
|
|
7
7
|
import { sql } from "drizzle-orm";
|
|
8
|
+
import { createLocalMemberAccessPlugin, isLocalMemberDeactivated, isLocalMemberEmailDeactivated, isLocalMemberSessionActive, } from "./local-member-access.js";
|
|
8
9
|
import { expandTrustedOrigins, getTrustedOrigins } from "./trusted-origins.js";
|
|
9
10
|
import { isFirstAuthUser, provisionCurrentUserProfile } from "./workspace.js";
|
|
10
11
|
export function getAuthSecret() {
|
|
@@ -122,9 +123,6 @@ export function createBetterAuth(options = {}) {
|
|
|
122
123
|
session: {
|
|
123
124
|
cookieCache: {
|
|
124
125
|
enabled: true,
|
|
125
|
-
// Caps how long a revoked session stays usable from the
|
|
126
|
-
// cookie alone; within the window getSession answers from
|
|
127
|
-
// the signed cookie with zero DB roundtrips.
|
|
128
126
|
maxAge: options.sessionCookieCache?.maxAge ?? 300,
|
|
129
127
|
},
|
|
130
128
|
},
|
|
@@ -182,6 +180,11 @@ export function createBetterAuth(options = {}) {
|
|
|
182
180
|
enabled: true,
|
|
183
181
|
},
|
|
184
182
|
}),
|
|
183
|
+
createLocalMemberAccessPlugin({
|
|
184
|
+
isEmailDeactivated: (email) => isLocalMemberEmailDeactivated(db, email),
|
|
185
|
+
isSessionActive: (sessionId, userId) => isLocalMemberSessionActive(db, sessionId, userId),
|
|
186
|
+
isUserDeactivated: (userId) => isLocalMemberDeactivated(db, userId),
|
|
187
|
+
}),
|
|
185
188
|
...extraPlugins,
|
|
186
189
|
],
|
|
187
190
|
databaseHooks: {
|