@elevasis/core 0.35.1 → 0.36.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.
@@ -349,6 +349,18 @@ declare const UpdateOrganizationSchema: z.ZodObject<{
349
349
  }, z.core.$strip>>>>;
350
350
  metadata: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
351
351
  }, z.core.$strict>;
352
+ /**
353
+ * Update organization auth config
354
+ * PATCH /organizations/:id/config/auth
355
+ *
356
+ * Security:
357
+ * - Narrow config surface: only appOrigin is accepted
358
+ * - App origin is an origin URL, not a route URL
359
+ * - Strict mode prevents accidental writes to unrelated config keys
360
+ */
361
+ declare const UpdateOrganizationAuthConfigSchema: z.ZodObject<{
362
+ appOrigin: z.ZodString;
363
+ }, z.core.$strict>;
352
364
  /**
353
365
  * List organizations with filters
354
366
  * GET /organizations
@@ -364,6 +376,7 @@ declare const ListOrganizationsQuerySchema: z.ZodObject<{
364
376
  }, z.core.$strip>;
365
377
  type CreateOrganizationInput = z.infer<typeof CreateOrganizationSchema>;
366
378
  type UpdateOrganizationInput = z.infer<typeof UpdateOrganizationSchema>;
379
+ type UpdateOrganizationAuthConfigInput = z.infer<typeof UpdateOrganizationAuthConfigSchema>;
367
380
  type ListOrganizationsQuery = z.infer<typeof ListOrganizationsQuerySchema>;
368
381
  type OrganizationIdParam = z.infer<typeof OrganizationIdParamSchema>;
369
382
 
@@ -3876,6 +3889,17 @@ type Database = {
3876
3889
  Args: never;
3877
3890
  Returns: undefined;
3878
3891
  };
3892
+ repair_membership_role_assignments: {
3893
+ Args: never;
3894
+ Returns: {
3895
+ membership_id: string;
3896
+ organization_id: string;
3897
+ repaired: boolean;
3898
+ role_id: string;
3899
+ role_slug: string;
3900
+ user_id: string;
3901
+ }[];
3902
+ };
3879
3903
  sync_all_memberships_with_role: {
3880
3904
  Args: {
3881
3905
  p_role_id: string;
@@ -3888,6 +3912,13 @@ type Database = {
3888
3912
  };
3889
3913
  Returns: undefined;
3890
3914
  };
3915
+ update_membership_role_assignment: {
3916
+ Args: {
3917
+ p_role_slug: string;
3918
+ p_workos_membership_id: string;
3919
+ };
3920
+ Returns: string;
3921
+ };
3891
3922
  upsert_user_profile: {
3892
3923
  Args: never;
3893
3924
  Returns: {
@@ -4108,6 +4139,17 @@ declare const SendInvitationSchema: z.ZodObject<{
4108
4139
  declare const AcceptInvitationSchema: z.ZodObject<{
4109
4140
  invitation_token: z.ZodString;
4110
4141
  }, z.core.$strict>;
4142
+ /**
4143
+ * Public invitation router query
4144
+ * GET /api/invite
4145
+ *
4146
+ * Security:
4147
+ * - Token validated as present only; service layer performs WorkOS lookup
4148
+ * - Router redirects with only the token needed by tenant AuthKit login
4149
+ */
4150
+ declare const InviteRouterQuerySchema: z.ZodObject<{
4151
+ invitation_token: z.ZodString;
4152
+ }, z.core.$strict>;
4111
4153
  /**
4112
4154
  * List invitations with filters
4113
4155
  * GET /invitations
@@ -4130,6 +4172,7 @@ declare const ListInvitationsQuerySchema: z.ZodObject<{
4130
4172
  }, z.core.$strict>;
4131
4173
  type SendInvitationInput = z.infer<typeof SendInvitationSchema>;
4132
4174
  type AcceptInvitationInput = z.infer<typeof AcceptInvitationSchema>;
4175
+ type InviteRouterQuery = z.infer<typeof InviteRouterQuerySchema>;
4133
4176
  type ListInvitationsQuery = z.infer<typeof ListInvitationsQuerySchema>;
4134
4177
  type InvitationIdParam = z.infer<typeof InvitationIdParamSchema>;
4135
4178
 
@@ -5415,5 +5458,5 @@ interface AccessModel {
5415
5458
  declare function checkAccess(input: AccessKeyInput, ctx: AccessContext): AccessAnswer;
5416
5459
  declare function createAccessModel(organizationModel: OrganizationModel): AccessModel;
5417
5460
 
5418
- export { AcceptInvitationSchema, AccessActionSchema, AccessKeyInputSchema, AccessKeyObjectSchema, AccessKeySchema, AccessKeys, AssignMembershipRoleRequestSchema, CreateMembershipSchema, CreateOrgRoleRequestSchema, CreateOrganizationSchema, DEFAULT_ACCESS_ACTION, DIAGNOSTIC_VIEW_ACCESS_KEYS, ExternalIdParamSchema, InvitationIdParamSchema, ListInvitationsQuerySchema, ListMembershipsQuerySchema, ListOrganizationsQuerySchema, ListUsersQuerySchema, MEMBERSHIP_STATUS_COLORS, MembershipIdParamSchema, MembershipParamsSchema, MembershipRoleParamsSchema, MembershipRoleSchema, MembershipStatusSchema, MyOrgPermissionsResponseSchema, NormalizedAccessKeySchema, OrgIdParamSchema, OrgRoleParamsSchema, OrgRolesParamsSchema, OrganizationDomainSchema, OrganizationIdParamSchema, OrganizationNameSchema, PERMISSIONS, PERMISSION_CATALOG, PLATFORM_ADMIN_ACCESS_KEY, PLATFORM_ADMIN_ACCESS_KEY_SHORTHAND, SendInvitationSchema, THEME_PRESETS, ThemePresetEnum, UpdateMembershipSchema, UpdateMyProfileSchema, UpdateOrgRoleRequestSchema, UpdateOrganizationSchema, UpdateUserSchema, UserIdParamSchema, accessKeyToString, asSupabaseOrgId, asSupabaseOrgIdOrNull, asWorkOsOrgId, asWorkOsOrgIdOrNull, brandSupabaseOrgId, brandWorkOsOrgId, checkAccess, createAccessModel, deriveAccessKeyCatalog, findAccessCatalogEntry, isPermissionKey, isSupabaseOrgId, isWorkOsOrgId, listAccessKeys, normalizeAccessKey, rolePermissionForAccessKey, transformMembershipToTableRow, transformSupabaseToInvitation, transformSupabaseToMembership };
5419
- export type { AcceptInvitationInput, AcceptInvitationRequest, AcceptanceResult, AccessAction, AccessAnswer, AccessCatalogEntry, AccessCatalogEntrySource, AccessContext, AccessContextMembership, AccessContextProfile, AccessKey, AccessKeyCatalog, AccessKeyInput, AccessKeyObject, AccessModel, AccessReason, AccessRestrictedBy, AdminOrgInvitation, AssignMembershipRoleInput, CreateCredentialParams, CreateMembershipInput, CreateMembershipRequest, CreateOrgRoleInput, CreateOrganizationInput, CreateUserRequest, CredentialMetadata, DeriveAccessKeyCatalogOptions, ExternalIdParam, Invitation, InvitationIdParam, InvitationListQuery, InvitationState, InvitationSummaryResponse, InvitationWithDetails, ListInvitationsParams, ListInvitationsQuery, ListMembershipsParams, ListMembershipsQuery, ListMembershipsResponse, ListOrganizationsQuery, ListUsersParams, ListUsersQuery, ListUsersResponse, MembershipIdParam, MembershipParams, MembershipRole, MembershipRoleParams, MembershipStatus, MembershipTableRow, MembershipWithDetails, MyOrgPermissionsResponse, NormalizedAccessKey, OrgIdParam, OrgMetadata, OrgRoleParams, OrgRolesParams, Organization, OrganizationIdParam, OrganizationMembership, OrganizationSummary, PermissionDescriptor, PermissionKey, SendInvitationInput, SendInvitationRequest, SupabaseOrgId, SupabaseOrgInvitation, SupabaseOrgInvitationInsert, SupabaseOrgInvitationUpdate, SupabaseOrgMembership, SupabaseOrgMembershipInsert, SupabaseOrgMembershipUpdate, ThemePresetName, UpdateMembershipInput, UpdateMembershipRequest, UpdateMyProfileInput, UpdateOrgRoleInput, UpdateOrganizationInput, UpdateUserInput, UpdateUserRequest, User, UserConfig, UserIdParam, UserSummary, WorkOsOrgId };
5461
+ export { AcceptInvitationSchema, AccessActionSchema, AccessKeyInputSchema, AccessKeyObjectSchema, AccessKeySchema, AccessKeys, AssignMembershipRoleRequestSchema, CreateMembershipSchema, CreateOrgRoleRequestSchema, CreateOrganizationSchema, DEFAULT_ACCESS_ACTION, DIAGNOSTIC_VIEW_ACCESS_KEYS, ExternalIdParamSchema, InvitationIdParamSchema, InviteRouterQuerySchema, ListInvitationsQuerySchema, ListMembershipsQuerySchema, ListOrganizationsQuerySchema, ListUsersQuerySchema, MEMBERSHIP_STATUS_COLORS, MembershipIdParamSchema, MembershipParamsSchema, MembershipRoleParamsSchema, MembershipRoleSchema, MembershipStatusSchema, MyOrgPermissionsResponseSchema, NormalizedAccessKeySchema, OrgIdParamSchema, OrgRoleParamsSchema, OrgRolesParamsSchema, OrganizationDomainSchema, OrganizationIdParamSchema, OrganizationNameSchema, PERMISSIONS, PERMISSION_CATALOG, PLATFORM_ADMIN_ACCESS_KEY, PLATFORM_ADMIN_ACCESS_KEY_SHORTHAND, SendInvitationSchema, THEME_PRESETS, ThemePresetEnum, UpdateMembershipSchema, UpdateMyProfileSchema, UpdateOrgRoleRequestSchema, UpdateOrganizationAuthConfigSchema, UpdateOrganizationSchema, UpdateUserSchema, UserIdParamSchema, accessKeyToString, asSupabaseOrgId, asSupabaseOrgIdOrNull, asWorkOsOrgId, asWorkOsOrgIdOrNull, brandSupabaseOrgId, brandWorkOsOrgId, checkAccess, createAccessModel, deriveAccessKeyCatalog, findAccessCatalogEntry, isPermissionKey, isSupabaseOrgId, isWorkOsOrgId, listAccessKeys, normalizeAccessKey, rolePermissionForAccessKey, transformMembershipToTableRow, transformSupabaseToInvitation, transformSupabaseToMembership };
5462
+ export type { AcceptInvitationInput, AcceptInvitationRequest, AcceptanceResult, AccessAction, AccessAnswer, AccessCatalogEntry, AccessCatalogEntrySource, AccessContext, AccessContextMembership, AccessContextProfile, AccessKey, AccessKeyCatalog, AccessKeyInput, AccessKeyObject, AccessModel, AccessReason, AccessRestrictedBy, AdminOrgInvitation, AssignMembershipRoleInput, CreateCredentialParams, CreateMembershipInput, CreateMembershipRequest, CreateOrgRoleInput, CreateOrganizationInput, CreateUserRequest, CredentialMetadata, DeriveAccessKeyCatalogOptions, ExternalIdParam, Invitation, InvitationIdParam, InvitationListQuery, InvitationState, InvitationSummaryResponse, InvitationWithDetails, InviteRouterQuery, ListInvitationsParams, ListInvitationsQuery, ListMembershipsParams, ListMembershipsQuery, ListMembershipsResponse, ListOrganizationsQuery, ListUsersParams, ListUsersQuery, ListUsersResponse, MembershipIdParam, MembershipParams, MembershipRole, MembershipRoleParams, MembershipStatus, MembershipTableRow, MembershipWithDetails, MyOrgPermissionsResponse, NormalizedAccessKey, OrgIdParam, OrgMetadata, OrgRoleParams, OrgRolesParams, Organization, OrganizationIdParam, OrganizationMembership, OrganizationSummary, PermissionDescriptor, PermissionKey, SendInvitationInput, SendInvitationRequest, SupabaseOrgId, SupabaseOrgInvitation, SupabaseOrgInvitationInsert, SupabaseOrgInvitationUpdate, SupabaseOrgMembership, SupabaseOrgMembershipInsert, SupabaseOrgMembershipUpdate, ThemePresetName, UpdateMembershipInput, UpdateMembershipRequest, UpdateMyProfileInput, UpdateOrgRoleInput, UpdateOrganizationAuthConfigInput, UpdateOrganizationInput, UpdateUserInput, UpdateUserRequest, User, UserConfig, UserIdParam, UserSummary, WorkOsOrgId };
@@ -226,6 +226,16 @@ var CreateOrganizationSchema = z.object({
226
226
  var UpdateOrganizationSchema = CreateOrganizationSchema.partial().strict().refine((data) => Object.keys(data).length > 0, {
227
227
  message: "At least one field (name, domainData, or metadata) must be provided"
228
228
  });
229
+ var UpdateOrganizationAuthConfigSchema = z.object({
230
+ appOrigin: z.string().trim().url("App origin must be a valid URL").max(2048, "App origin must be at most 2048 characters").refine((value) => {
231
+ try {
232
+ const url = new URL(value);
233
+ return url.pathname === "/" && url.search === "" && url.hash === "";
234
+ } catch {
235
+ return false;
236
+ }
237
+ }, "App origin must not include a path, query string, or hash")
238
+ }).strict();
229
239
  var ListOrganizationsQuerySchema = z.object({
230
240
  limit: z.coerce.number().int().min(1).max(100).default(20),
231
241
  before: z.string().optional(),
@@ -394,6 +404,9 @@ var SendInvitationSchema = z.object({
394
404
  var AcceptInvitationSchema = z.object({
395
405
  invitation_token: z.string().min(1, "Invitation token is required")
396
406
  }).strict();
407
+ var InviteRouterQuerySchema = z.object({
408
+ invitation_token: z.string().min(1, "Invitation token is required")
409
+ }).strict();
397
410
  var ListInvitationsQuerySchema = z.object({
398
411
  organizationId: z.string().optional(),
399
412
  userId: z.string().optional(),
@@ -636,4 +649,4 @@ function createAccessModel(organizationModel) {
636
649
  };
637
650
  }
638
651
 
639
- export { AcceptInvitationSchema, AccessActionSchema, AccessKeyInputSchema, AccessKeyObjectSchema, AccessKeySchema, AccessKeys, AssignMembershipRoleRequestSchema, CreateMembershipSchema, CreateOrgRoleRequestSchema, CreateOrganizationSchema, DEFAULT_ACCESS_ACTION, DIAGNOSTIC_VIEW_ACCESS_KEYS, ExternalIdParamSchema, InvitationIdParamSchema, ListInvitationsQuerySchema, ListMembershipsQuerySchema, ListOrganizationsQuerySchema, ListUsersQuerySchema, MEMBERSHIP_STATUS_COLORS, MembershipIdParamSchema, MembershipParamsSchema, MembershipRoleParamsSchema, MembershipRoleSchema, MembershipStatusSchema, MyOrgPermissionsResponseSchema, NormalizedAccessKeySchema, OrgIdParamSchema, OrgRoleParamsSchema, OrgRolesParamsSchema, OrganizationDomainSchema, OrganizationIdParamSchema, OrganizationNameSchema, PERMISSIONS, PERMISSION_CATALOG, PLATFORM_ADMIN_ACCESS_KEY, PLATFORM_ADMIN_ACCESS_KEY_SHORTHAND, SendInvitationSchema, THEME_PRESETS, ThemePresetEnum, UpdateMembershipSchema, UpdateMyProfileSchema, UpdateOrgRoleRequestSchema, UpdateOrganizationSchema, UpdateUserSchema, UserIdParamSchema, accessKeyToString, asSupabaseOrgId, asSupabaseOrgIdOrNull, asWorkOsOrgId, asWorkOsOrgIdOrNull, brandSupabaseOrgId, brandWorkOsOrgId, checkAccess, createAccessModel, deriveAccessKeyCatalog, findAccessCatalogEntry, isPermissionKey, isSupabaseOrgId, isWorkOsOrgId, listAccessKeys, normalizeAccessKey, rolePermissionForAccessKey, transformMembershipToTableRow, transformSupabaseToInvitation, transformSupabaseToMembership };
652
+ export { AcceptInvitationSchema, AccessActionSchema, AccessKeyInputSchema, AccessKeyObjectSchema, AccessKeySchema, AccessKeys, AssignMembershipRoleRequestSchema, CreateMembershipSchema, CreateOrgRoleRequestSchema, CreateOrganizationSchema, DEFAULT_ACCESS_ACTION, DIAGNOSTIC_VIEW_ACCESS_KEYS, ExternalIdParamSchema, InvitationIdParamSchema, InviteRouterQuerySchema, ListInvitationsQuerySchema, ListMembershipsQuerySchema, ListOrganizationsQuerySchema, ListUsersQuerySchema, MEMBERSHIP_STATUS_COLORS, MembershipIdParamSchema, MembershipParamsSchema, MembershipRoleParamsSchema, MembershipRoleSchema, MembershipStatusSchema, MyOrgPermissionsResponseSchema, NormalizedAccessKeySchema, OrgIdParamSchema, OrgRoleParamsSchema, OrgRolesParamsSchema, OrganizationDomainSchema, OrganizationIdParamSchema, OrganizationNameSchema, PERMISSIONS, PERMISSION_CATALOG, PLATFORM_ADMIN_ACCESS_KEY, PLATFORM_ADMIN_ACCESS_KEY_SHORTHAND, SendInvitationSchema, THEME_PRESETS, ThemePresetEnum, UpdateMembershipSchema, UpdateMyProfileSchema, UpdateOrgRoleRequestSchema, UpdateOrganizationAuthConfigSchema, UpdateOrganizationSchema, UpdateUserSchema, UserIdParamSchema, accessKeyToString, asSupabaseOrgId, asSupabaseOrgIdOrNull, asWorkOsOrgId, asWorkOsOrgIdOrNull, brandSupabaseOrgId, brandWorkOsOrgId, checkAccess, createAccessModel, deriveAccessKeyCatalog, findAccessCatalogEntry, isPermissionKey, isSupabaseOrgId, isWorkOsOrgId, listAccessKeys, normalizeAccessKey, rolePermissionForAccessKey, transformMembershipToTableRow, transformSupabaseToInvitation, transformSupabaseToMembership };
@@ -3126,6 +3126,17 @@ type Database = {
3126
3126
  Args: never;
3127
3127
  Returns: undefined;
3128
3128
  };
3129
+ repair_membership_role_assignments: {
3130
+ Args: never;
3131
+ Returns: {
3132
+ membership_id: string;
3133
+ organization_id: string;
3134
+ repaired: boolean;
3135
+ role_id: string;
3136
+ role_slug: string;
3137
+ user_id: string;
3138
+ }[];
3139
+ };
3129
3140
  sync_all_memberships_with_role: {
3130
3141
  Args: {
3131
3142
  p_role_id: string;
@@ -3138,6 +3149,13 @@ type Database = {
3138
3149
  };
3139
3150
  Returns: undefined;
3140
3151
  };
3152
+ update_membership_role_assignment: {
3153
+ Args: {
3154
+ p_role_slug: string;
3155
+ p_workos_membership_id: string;
3156
+ };
3157
+ Returns: string;
3158
+ };
3141
3159
  upsert_user_profile: {
3142
3160
  Args: never;
3143
3161
  Returns: {
@@ -2011,7 +2011,7 @@ var RLSTestContext = class {
2011
2011
  if (roleErr || !roleDef) {
2012
2012
  throw new Error(`Failed to look up system role '${slug}': ${roleErr?.message ?? "not found"}`);
2013
2013
  }
2014
- const { error: assignErr } = await this.adminClient.from("org_rol_assignments").insert({ membership_id: membershipId, role_id: roleDef.id });
2014
+ const { error: assignErr } = await this.adminClient.from("org_rol_assignments").upsert({ membership_id: membershipId, role_id: roleDef.id }, { onConflict: "membership_id,role_id" });
2015
2015
  if (assignErr) {
2016
2016
  throw new Error(`Failed to assign system role '${slug}' to membership: ${assignErr.message}`);
2017
2017
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/core",
3
- "version": "0.35.1",
3
+ "version": "0.36.0",
4
4
  "license": "MIT",
5
5
  "description": "Minimal shared constants across Elevasis monorepo",
6
6
  "sideEffects": false,
@@ -59,11 +59,25 @@ export const SendInvitationSchema = z
59
59
  * Security:
60
60
  * - Token validated (non-empty string)
61
61
  */
62
- export const AcceptInvitationSchema = z
63
- .object({
64
- invitation_token: z.string().min(1, 'Invitation token is required')
65
- })
66
- .strict()
62
+ export const AcceptInvitationSchema = z
63
+ .object({
64
+ invitation_token: z.string().min(1, 'Invitation token is required')
65
+ })
66
+ .strict()
67
+
68
+ /**
69
+ * Public invitation router query
70
+ * GET /api/invite
71
+ *
72
+ * Security:
73
+ * - Token validated as present only; service layer performs WorkOS lookup
74
+ * - Router redirects with only the token needed by tenant AuthKit login
75
+ */
76
+ export const InviteRouterQuerySchema = z
77
+ .object({
78
+ invitation_token: z.string().min(1, 'Invitation token is required')
79
+ })
80
+ .strict()
67
81
 
68
82
  // ============================================================================
69
83
  // Query Parameters
@@ -98,7 +112,8 @@ export const ListInvitationsQuerySchema = z
98
112
  // ============================================================================
99
113
 
100
114
  // Export inferred types for use in route handlers
101
- export type SendInvitationInput = z.infer<typeof SendInvitationSchema>
102
- export type AcceptInvitationInput = z.infer<typeof AcceptInvitationSchema>
103
- export type ListInvitationsQuery = z.infer<typeof ListInvitationsQuerySchema>
104
- export type InvitationIdParam = z.infer<typeof InvitationIdParamSchema>
115
+ export type SendInvitationInput = z.infer<typeof SendInvitationSchema>
116
+ export type AcceptInvitationInput = z.infer<typeof AcceptInvitationSchema>
117
+ export type InviteRouterQuery = z.infer<typeof InviteRouterQuerySchema>
118
+ export type ListInvitationsQuery = z.infer<typeof ListInvitationsQuerySchema>
119
+ export type InvitationIdParam = z.infer<typeof InvitationIdParamSchema>
@@ -27,12 +27,14 @@ export { transformSupabaseToInvitation } from './supabase'
27
27
 
28
28
  // Validation schemas
29
29
  export {
30
- SendInvitationSchema,
31
- AcceptInvitationSchema,
32
- ListInvitationsQuerySchema,
33
- InvitationIdParamSchema,
34
- type SendInvitationInput,
35
- type AcceptInvitationInput,
36
- type ListInvitationsQuery,
37
- type InvitationIdParam
38
- } from './api-schemas'
30
+ SendInvitationSchema,
31
+ AcceptInvitationSchema,
32
+ InviteRouterQuerySchema,
33
+ ListInvitationsQuerySchema,
34
+ InvitationIdParamSchema,
35
+ type SendInvitationInput,
36
+ type AcceptInvitationInput,
37
+ type InviteRouterQuery,
38
+ type ListInvitationsQuery,
39
+ type InvitationIdParam
40
+ } from './api-schemas'
@@ -1,5 +1,10 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { CreateOrganizationSchema, ListOrganizationsQuerySchema, UpdateOrganizationSchema } from '../api-schemas'
2
+ import {
3
+ CreateOrganizationSchema,
4
+ ListOrganizationsQuerySchema,
5
+ UpdateOrganizationAuthConfigSchema,
6
+ UpdateOrganizationSchema
7
+ } from '../api-schemas'
3
8
 
4
9
  // ---------------------------------------------------------------------------
5
10
  // CreateOrganizationSchema
@@ -87,7 +92,7 @@ describe('CreateOrganizationSchema', () => {
87
92
  // UpdateOrganizationSchema
88
93
  // ---------------------------------------------------------------------------
89
94
 
90
- describe('UpdateOrganizationSchema', () => {
95
+ describe('UpdateOrganizationSchema', () => {
91
96
  it('rejects an empty object (at least one field required)', () => {
92
97
  // UpdateOrganizationSchema is CreateOrganizationSchema.partial().strict().refine(...)
93
98
  // The .refine() enforces the documented "at least one field" convention
@@ -135,7 +140,38 @@ describe('UpdateOrganizationSchema', () => {
135
140
  it('rejects unknown top-level fields (.strict() mode)', () => {
136
141
  expect(UpdateOrganizationSchema.safeParse({ name: 'Valid Name', unknownField: 'bad' }).success).toBe(false)
137
142
  })
138
- })
143
+ })
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // UpdateOrganizationAuthConfigSchema
147
+ // ---------------------------------------------------------------------------
148
+
149
+ describe('UpdateOrganizationAuthConfigSchema', () => {
150
+ it('accepts an HTTPS app origin', () => {
151
+ expect(UpdateOrganizationAuthConfigSchema.safeParse({ appOrigin: 'https://app.example.com' }).success).toBe(true)
152
+ })
153
+
154
+ it('rejects app origins with paths, query strings, or hashes', () => {
155
+ expect(UpdateOrganizationAuthConfigSchema.safeParse({ appOrigin: 'https://app.example.com/login' }).success).toBe(
156
+ false
157
+ )
158
+ expect(UpdateOrganizationAuthConfigSchema.safeParse({ appOrigin: 'https://app.example.com?x=1' }).success).toBe(
159
+ false
160
+ )
161
+ expect(UpdateOrganizationAuthConfigSchema.safeParse({ appOrigin: 'https://app.example.com#hash' }).success).toBe(
162
+ false
163
+ )
164
+ })
165
+
166
+ it('rejects unknown config fields', () => {
167
+ expect(
168
+ UpdateOrganizationAuthConfigSchema.safeParse({
169
+ appOrigin: 'https://app.example.com',
170
+ other: true
171
+ }).success
172
+ ).toBe(false)
173
+ })
174
+ })
139
175
 
140
176
  // ---------------------------------------------------------------------------
141
177
  // ListOrganizationsQuerySchema
@@ -101,11 +101,38 @@ export const CreateOrganizationSchema = z
101
101
  * - At least one field required (matches documented Update schema convention,
102
102
  * see .claude/rules/core-package.md → "api-schemas.ts Pattern")
103
103
  */
104
- export const UpdateOrganizationSchema = CreateOrganizationSchema.partial()
105
- .strict()
106
- .refine((data) => Object.keys(data).length > 0, {
107
- message: 'At least one field (name, domainData, or metadata) must be provided'
108
- })
104
+ export const UpdateOrganizationSchema = CreateOrganizationSchema.partial()
105
+ .strict()
106
+ .refine((data) => Object.keys(data).length > 0, {
107
+ message: 'At least one field (name, domainData, or metadata) must be provided'
108
+ })
109
+
110
+ /**
111
+ * Update organization auth config
112
+ * PATCH /organizations/:id/config/auth
113
+ *
114
+ * Security:
115
+ * - Narrow config surface: only appOrigin is accepted
116
+ * - App origin is an origin URL, not a route URL
117
+ * - Strict mode prevents accidental writes to unrelated config keys
118
+ */
119
+ export const UpdateOrganizationAuthConfigSchema = z
120
+ .object({
121
+ appOrigin: z
122
+ .string()
123
+ .trim()
124
+ .url('App origin must be a valid URL')
125
+ .max(2048, 'App origin must be at most 2048 characters')
126
+ .refine((value) => {
127
+ try {
128
+ const url = new URL(value)
129
+ return url.pathname === '/' && url.search === '' && url.hash === ''
130
+ } catch {
131
+ return false
132
+ }
133
+ }, 'App origin must not include a path, query string, or hash')
134
+ })
135
+ .strict()
109
136
 
110
137
  // ============================================================================
111
138
  // Query Parameters
@@ -131,6 +158,7 @@ export const ListOrganizationsQuerySchema = z.object({
131
158
 
132
159
  // Export inferred types for use in route handlers
133
160
  export type CreateOrganizationInput = z.infer<typeof CreateOrganizationSchema>
134
- export type UpdateOrganizationInput = z.infer<typeof UpdateOrganizationSchema>
135
- export type ListOrganizationsQuery = z.infer<typeof ListOrganizationsQuerySchema>
136
- export type OrganizationIdParam = z.infer<typeof OrganizationIdParamSchema>
161
+ export type UpdateOrganizationInput = z.infer<typeof UpdateOrganizationSchema>
162
+ export type UpdateOrganizationAuthConfigInput = z.infer<typeof UpdateOrganizationAuthConfigSchema>
163
+ export type ListOrganizationsQuery = z.infer<typeof ListOrganizationsQuerySchema>
164
+ export type OrganizationIdParam = z.infer<typeof OrganizationIdParamSchema>
@@ -8,16 +8,18 @@ export type { Organization, OrganizationSummary } from './organization'
8
8
 
9
9
  // Validation schemas
10
10
  export {
11
- CreateOrganizationSchema,
12
- UpdateOrganizationSchema,
13
- ListOrganizationsQuerySchema,
14
- OrganizationIdParamSchema,
15
- OrganizationNameSchema,
16
- OrganizationDomainSchema,
17
- type CreateOrganizationInput,
18
- type UpdateOrganizationInput,
19
- type ListOrganizationsQuery,
20
- type OrganizationIdParam
21
- } from './api-schemas'
11
+ CreateOrganizationSchema,
12
+ UpdateOrganizationSchema,
13
+ UpdateOrganizationAuthConfigSchema,
14
+ ListOrganizationsQuerySchema,
15
+ OrganizationIdParamSchema,
16
+ OrganizationNameSchema,
17
+ OrganizationDomainSchema,
18
+ type CreateOrganizationInput,
19
+ type UpdateOrganizationInput,
20
+ type UpdateOrganizationAuthConfigInput,
21
+ type ListOrganizationsQuery,
22
+ type OrganizationIdParam
23
+ } from './api-schemas'
22
24
 
23
25
  // Note: WorkOS types and transforms available via @repo/core/server
@@ -170,6 +170,40 @@ describe('createBuildPlanSnapshotFromTemplate', () => {
170
170
  ]
171
171
  })
172
172
  })
173
+
174
+ it('normalizes stage-key dependencies to step ids for snapshots', () => {
175
+ const snapshot = createBuildPlanSnapshotFromTemplate({
176
+ id: 'stage-key-dependency',
177
+ label: 'Stage Key Dependency',
178
+ steps: [
179
+ {
180
+ id: 'source-companies',
181
+ label: 'Companies found',
182
+ primaryEntity: 'company',
183
+ outputs: ['company'],
184
+ stageKey: 'populated',
185
+ dependencyMode: 'per-record-eligibility',
186
+ actionKey: 'lead-gen.company.source',
187
+ defaultBatchSize: 100,
188
+ maxBatchSize: 250
189
+ },
190
+ {
191
+ id: 'qualify-companies',
192
+ label: 'Companies qualified',
193
+ primaryEntity: 'company',
194
+ outputs: ['company'],
195
+ stageKey: 'qualified',
196
+ dependsOn: ['populated'],
197
+ dependencyMode: 'per-record-eligibility',
198
+ actionKey: 'lead-gen.company.qualify',
199
+ defaultBatchSize: 50,
200
+ maxBatchSize: 100
201
+ }
202
+ ]
203
+ })
204
+
205
+ expect(snapshot.steps[1]?.dependsOn).toEqual(['source-companies'])
206
+ })
173
207
  })
174
208
 
175
209
  describe('createBuildPlanSnapshotFromTemplateId', () => {
@@ -30,6 +30,9 @@ export function isProspectingBuildTemplateId(
30
30
  }
31
31
 
32
32
  export function createBuildPlanSnapshotFromTemplate(template: ProspectingBuildTemplate): BuildPlanSnapshot {
33
+ const stepIdByStageKey = new Map(template.steps.map((step) => [step.stageKey, step.id]))
34
+ const stepIds = new Set(template.steps.map((step) => step.id))
35
+
33
36
  return {
34
37
  templateId: template.id,
35
38
  templateLabel: template.label,
@@ -50,7 +53,11 @@ export function createBuildPlanSnapshotFromTemplate(template: ProspectingBuildTe
50
53
 
51
54
  if (step.description) snapshotStep.description = step.description
52
55
  if (step.recordEntity) snapshotStep.recordEntity = step.recordEntity
53
- if (step.dependsOn?.length) snapshotStep.dependsOn = [...step.dependsOn]
56
+ if (step.dependsOn?.length) {
57
+ snapshotStep.dependsOn = step.dependsOn.map((dependency) =>
58
+ stepIds.has(dependency) ? dependency : (stepIdByStageKey.get(dependency) ?? dependency)
59
+ )
60
+ }
54
61
  if (step.credentialRequirements?.length) {
55
62
  snapshotStep.credentialRequirements = step.credentialRequirements.map((requirement: CredentialRequirement) => ({
56
63
  ...requirement
@@ -114,17 +114,6 @@ function modelWithBuildTemplate(): OrganizationModel {
114
114
  kind: 'template-step',
115
115
  appliesTo: 'sales.lead-gen:object/list',
116
116
  entries: {
117
- 'source-companies': {
118
- label: 'Source Companies',
119
- order: 10,
120
- primaryEntity: 'company',
121
- outputs: ['company'],
122
- stageKey: 'populated',
123
- dependencyMode: 'per-record-eligibility',
124
- actionKey: 'lead-gen.company.source',
125
- defaultBatchSize: 100,
126
- maxBatchSize: 500
127
- },
128
117
  'qualify-companies': {
129
118
  label: 'Qualify Companies',
130
119
  order: 20,
@@ -136,6 +125,17 @@ function modelWithBuildTemplate(): OrganizationModel {
136
125
  actionKey: 'lead-gen.company.qualify',
137
126
  defaultBatchSize: 100,
138
127
  maxBatchSize: 500
128
+ },
129
+ 'source-companies': {
130
+ label: 'Source Companies',
131
+ order: 10,
132
+ primaryEntity: 'company',
133
+ outputs: ['company'],
134
+ stageKey: 'populated',
135
+ dependencyMode: 'per-record-eligibility',
136
+ actionKey: 'lead-gen.company.source',
137
+ defaultBatchSize: 100,
138
+ maxBatchSize: 500
139
139
  }
140
140
  }
141
141
  }
@@ -124,7 +124,14 @@ export function getAllBuildTemplates(model: OrganizationModel): BuildTemplate[]
124
124
  entriesOf(catalog).map(([templateId, templateEntry]) => {
125
125
  const stepCatalogId = stringValue(templateEntry.stepCatalog) as OntologyId | undefined
126
126
  const stepCatalog = stepCatalogId !== undefined ? stepCatalogs.get(stepCatalogId) : undefined
127
- const steps = stepCatalog === undefined ? [] : entriesOf(stepCatalog)
127
+ const steps =
128
+ stepCatalog === undefined
129
+ ? []
130
+ : entriesOf(stepCatalog).sort(
131
+ ([leftId, left], [rightId, right]) =>
132
+ numberValue(left.order, Number.MAX_SAFE_INTEGER) -
133
+ numberValue(right.order, Number.MAX_SAFE_INTEGER) || leftId.localeCompare(rightId)
134
+ )
128
135
 
129
136
  return {
130
137
  order: numberValue(templateEntry.order, Number.MAX_SAFE_INTEGER),
@@ -1,3 +1,3 @@
1
1
  export const VERSION = {
2
- CURRENT: '1.12.9'
2
+ CURRENT: '1.12.12'
3
3
  }