1
14import { z } from "zod";
15
16export const SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
17export const SCIM_GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group";
18export const SCIM_PATCH_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:PatchOp";
19export const SCIM_LIST_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:ListResponse";
20
21
22
23
24
25const ScimNameSchema = z
26 .object({
27 formatted: z.string().min(1).max(256).optional(),
28 givenName: z.string().min(1).max(128).optional(),
29 familyName: z.string().min(1).max(128).optional()
30 });
31
32
33const ScimMemberSchema = z
34 .object({
35 value: z.string().min(1).max(256),
36 $ref: z.string().max(64).optional()
37 });
38
39const ScimEmailSchema = z
40 .object({
41 value: z.string().min(3).max(320),
42 type: z.string().max(64).optional(),
43 primary: z.boolean().optional()
44 });
45
46
48const schemasArr = z.array(z.string()).optional();
49
50export const ScimUserCreateSchema = z
51 .object({
52 schemas: schemasArr,
53 id: z.string().max(64).optional(),
54 userName: z.string().min(1).max(320),
55 name: ScimNameSchema.optional(),
56 displayName: z.string().min(1).max(256).optional(),
57 emails: z.array(ScimEmailSchema).max(20).optional(),
58 active: z.boolean().optional(),
59
61 password: z.string().min(8).max(512).optional(),
62 externalId: z.string().max(256).optional(),
63 meta: z.record(z.unknown()).optional()
64 })
65 .passthrough();
66export type ScimUserCreate = z.infer<typeof ScimUserCreateSchema>;
67
68
70export const ScimOpSchema = z
71 .object({
72 op: z.enum(["add", "replace", "remove"]),
73
74
75 path: z.enum(["active", "name", "displayName", "emails", "userName", "members"]).optional(),
76
77
78
79
80
81
82
83 value: z.unknown().optional(),
84 value2: z.record(z.unknown()).optional()
85 })
86 .strict();
87export type ScimOp = z.infer<typeof ScimOpSchema>;
88
89export const ScimPatchSchema = z
90 .object({
91 schemas: schemasArr,
92
93
94 Operations: z.array(ScimOpSchema).min(1).max(50).optional(),
95 operations: z.array(ScimOpSchema).min(1).max(50).optional()
96 })
97 .strict()
98 .refine((b) => (b.Operations ?? b.operations) !== undefined, { message: "missing Operations" });
99export type ScimPatch = z.infer<typeof ScimPatchSchema>;
100
101
102
103
104
105export const ScimGroupCreateSchema = z
106 .object({
107 schemas: schemasArr,
108 id: z.string().max(64).optional(),
109 displayName: z.string().min(1).max(128),
110 description: z.string().max(1024).optional(),
111 members: z.array(ScimMemberSchema).max(500).optional(),
112 meta: z.record(z.unknown()).optional()
113 })
114 .passthrough();
115export type ScimGroupCreate = z.infer<typeof ScimGroupCreateSchema>;
116
117
118
119
120
121
124export const ScimConfigSchema = z.object({
125 bearerTokenHash: z.string().regex(/^[a-f0-9]{64}$/),
126 tokenHint: z.string().max(8).default(""),
127 userEmailDomain: z.string().min(3).max(254).optional(),
128 defaultRole: z.enum(["GUEST", "MEMBER"]).default("MEMBER"),
129 autoJoinOrgWorkspaces: z.boolean().default(true),
130 enabledAt: z.string()
131});
132export type ScimConfig = z.infer<typeof ScimConfigSchema>;
133
134
135
136
137
138export const ScimEnableSchema = z.object({
139
141 userEmailDomain: z.string().min(3).max(254).optional(),
142 defaultRole: z.enum(["GUEST", "MEMBER"]).optional(),
143 autoJoinOrgWorkspaces: z.boolean().optional()
144});
145export type ScimEnable = z.infer<typeof ScimEnableSchema>;
146
147export const ScimAdminUpdateSchema = z
148 .object({
149
150 rotate: z.boolean().optional(),
151
152 disable: z.boolean().optional(),
153 userEmailDomain: z.string().min(3).max(254).nullable().optional(),
154 defaultRole: z.enum(["GUEST", "MEMBER"]).optional(),
155 autoJoinOrgWorkspaces: z.boolean().optional()
156 })
157 .refine((b) => Object.keys(b).length > 0, { message: "no fields to update" });
158export type ScimAdminUpdate = z.infer<typeof ScimAdminUpdateSchema>;
159
160
162export const ScimTestSchema = z.object({ token: z.string().min(10).max(256) });
163