@banata-auth/shared 0.1.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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/index.cjs +788 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1204 -0
- package/dist/index.d.ts +1204 -0
- package/dist/index.js +742 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
|
|
5
|
+
// src/constants.ts
|
|
6
|
+
var ID_PREFIXES = {
|
|
7
|
+
user: "usr",
|
|
8
|
+
session: "ses",
|
|
9
|
+
account: "acc",
|
|
10
|
+
organization: "org",
|
|
11
|
+
organizationMember: "mem",
|
|
12
|
+
organizationInvitation: "inv",
|
|
13
|
+
team: "team",
|
|
14
|
+
ssoConnection: "conn",
|
|
15
|
+
ssoProfile: "prof",
|
|
16
|
+
directory: "dir",
|
|
17
|
+
directoryUser: "diru",
|
|
18
|
+
directoryGroup: "dirg",
|
|
19
|
+
auditEvent: "evt",
|
|
20
|
+
event: "event",
|
|
21
|
+
webhookEndpoint: "wh",
|
|
22
|
+
webhookDelivery: "whd",
|
|
23
|
+
apiKey: "ak",
|
|
24
|
+
role: "role",
|
|
25
|
+
vaultSecret: "vsec",
|
|
26
|
+
domainVerification: "dv",
|
|
27
|
+
fgaTuple: "fga",
|
|
28
|
+
radarEvent: "radar",
|
|
29
|
+
project: "proj",
|
|
30
|
+
environment: "env",
|
|
31
|
+
emailTemplate: "etpl"
|
|
32
|
+
};
|
|
33
|
+
var RATE_LIMITS = {
|
|
34
|
+
general: 600,
|
|
35
|
+
signIn: 30,
|
|
36
|
+
signUp: 10,
|
|
37
|
+
passwordReset: 10,
|
|
38
|
+
emailOperations: 10,
|
|
39
|
+
scim: 100,
|
|
40
|
+
admin: 120,
|
|
41
|
+
webhookDelivery: 0
|
|
42
|
+
// No limit (outbound)
|
|
43
|
+
};
|
|
44
|
+
var TOKEN_LIFETIMES = {
|
|
45
|
+
/** JWT access token: 15 minutes */
|
|
46
|
+
accessToken: 15 * 60,
|
|
47
|
+
/** Session record: 7 days */
|
|
48
|
+
session: 7 * 24 * 60 * 60,
|
|
49
|
+
/** Session absolute max: 30 days */
|
|
50
|
+
sessionAbsoluteMax: 30 * 24 * 60 * 60,
|
|
51
|
+
/** Password reset token: 1 hour */
|
|
52
|
+
passwordReset: 60 * 60,
|
|
53
|
+
/** Email verification token: 24 hours */
|
|
54
|
+
emailVerification: 24 * 60 * 60,
|
|
55
|
+
/** Magic link: 10 minutes */
|
|
56
|
+
magicLink: 10 * 60,
|
|
57
|
+
/** Email OTP: 10 minutes */
|
|
58
|
+
emailOtp: 10 * 60,
|
|
59
|
+
/** Admin portal link (short-lived): 5 minutes */
|
|
60
|
+
adminPortalShort: 5 * 60,
|
|
61
|
+
/** Admin portal link (long-lived): 30 days */
|
|
62
|
+
adminPortalLong: 30 * 24 * 60 * 60,
|
|
63
|
+
/** Organization invitation: 7 days */
|
|
64
|
+
invitation: 7 * 24 * 60 * 60,
|
|
65
|
+
/** JWKS key rotation: 90 days */
|
|
66
|
+
jwksRotation: 90 * 24 * 60 * 60
|
|
67
|
+
};
|
|
68
|
+
var SIZE_LIMITS = {
|
|
69
|
+
/** Max custom metadata size in bytes */
|
|
70
|
+
metadataMaxBytes: 16 * 1024,
|
|
71
|
+
// 16KB
|
|
72
|
+
/** Max SAML response size in bytes */
|
|
73
|
+
samlMaxBytes: 256 * 1024,
|
|
74
|
+
// 256KB
|
|
75
|
+
/** Max SAML XML depth */
|
|
76
|
+
samlMaxDepth: 50,
|
|
77
|
+
/** Max webhook payload size in bytes */
|
|
78
|
+
webhookMaxBytes: 256 * 1024,
|
|
79
|
+
// 256KB
|
|
80
|
+
/** Max webhook response body stored (bytes) */
|
|
81
|
+
webhookResponseMaxBytes: 10 * 1024,
|
|
82
|
+
// 10KB
|
|
83
|
+
/** Max SCIM request body size in bytes */
|
|
84
|
+
scimMaxBytes: 1024 * 1024,
|
|
85
|
+
// 1MB
|
|
86
|
+
/** Max password length */
|
|
87
|
+
passwordMaxLength: 128,
|
|
88
|
+
/** Min password length */
|
|
89
|
+
passwordMinLength: 8,
|
|
90
|
+
/** Max FGA hierarchy depth */
|
|
91
|
+
fgaMaxDepth: 10
|
|
92
|
+
};
|
|
93
|
+
var WEBHOOK_RETRY_DELAYS = [
|
|
94
|
+
0,
|
|
95
|
+
// Attempt 1: immediate
|
|
96
|
+
5 * 60 * 1e3,
|
|
97
|
+
// Attempt 2: 5 minutes
|
|
98
|
+
30 * 60 * 1e3,
|
|
99
|
+
// Attempt 3: 30 minutes
|
|
100
|
+
2 * 60 * 60 * 1e3,
|
|
101
|
+
// Attempt 4: 2 hours
|
|
102
|
+
24 * 60 * 60 * 1e3
|
|
103
|
+
// Attempt 5: 24 hours
|
|
104
|
+
];
|
|
105
|
+
var WEBHOOK_MAX_CONSECUTIVE_FAILURES = 3;
|
|
106
|
+
var WEBHOOK_MAX_ATTEMPTS = 5;
|
|
107
|
+
|
|
108
|
+
// src/id.ts
|
|
109
|
+
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
110
|
+
var ENCODING_LEN = ENCODING.length;
|
|
111
|
+
var TIME_LEN = 10;
|
|
112
|
+
var RANDOM_LEN = 16;
|
|
113
|
+
function encodeTime(now, len) {
|
|
114
|
+
let str = "";
|
|
115
|
+
let remaining = now;
|
|
116
|
+
for (let i = len; i > 0; i--) {
|
|
117
|
+
const mod = remaining % ENCODING_LEN;
|
|
118
|
+
str = ENCODING[mod] + str;
|
|
119
|
+
remaining = (remaining - mod) / ENCODING_LEN;
|
|
120
|
+
}
|
|
121
|
+
return str;
|
|
122
|
+
}
|
|
123
|
+
function encodeRandom(len) {
|
|
124
|
+
let str = "";
|
|
125
|
+
const bytes = new Uint8Array(len);
|
|
126
|
+
crypto.getRandomValues(bytes);
|
|
127
|
+
for (let i = 0; i < len; i++) {
|
|
128
|
+
str += ENCODING[bytes[i] % ENCODING_LEN];
|
|
129
|
+
}
|
|
130
|
+
return str;
|
|
131
|
+
}
|
|
132
|
+
function ulid() {
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
return encodeTime(now, TIME_LEN) + encodeRandom(RANDOM_LEN);
|
|
135
|
+
}
|
|
136
|
+
function generateId(resourceType) {
|
|
137
|
+
const prefix = ID_PREFIXES[resourceType];
|
|
138
|
+
return `${prefix}_${ulid()}`;
|
|
139
|
+
}
|
|
140
|
+
function getResourceType(id) {
|
|
141
|
+
const underscoreIndex = id.indexOf("_");
|
|
142
|
+
if (underscoreIndex === -1) return null;
|
|
143
|
+
const prefix = id.slice(0, underscoreIndex);
|
|
144
|
+
const entry = Object.entries(ID_PREFIXES).find(([, v]) => v === prefix);
|
|
145
|
+
return entry ? entry[0] : null;
|
|
146
|
+
}
|
|
147
|
+
function validateId(id, expectedType) {
|
|
148
|
+
const prefix = ID_PREFIXES[expectedType];
|
|
149
|
+
return id.startsWith(`${prefix}_`);
|
|
150
|
+
}
|
|
151
|
+
function generateRandomToken(byteLength = 32) {
|
|
152
|
+
const bytes = new Uint8Array(byteLength);
|
|
153
|
+
crypto.getRandomValues(bytes);
|
|
154
|
+
const base64 = btoa(String.fromCharCode(...bytes));
|
|
155
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
156
|
+
}
|
|
157
|
+
function generateOtp(length = 6) {
|
|
158
|
+
const bytes = new Uint8Array(length);
|
|
159
|
+
crypto.getRandomValues(bytes);
|
|
160
|
+
let otp = "";
|
|
161
|
+
for (let i = 0; i < length; i++) {
|
|
162
|
+
otp += (bytes[i] % 10).toString();
|
|
163
|
+
}
|
|
164
|
+
return otp;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/errors.ts
|
|
168
|
+
var BanataAuthError = class extends Error {
|
|
169
|
+
status;
|
|
170
|
+
code;
|
|
171
|
+
requestId;
|
|
172
|
+
retryable;
|
|
173
|
+
constructor(options) {
|
|
174
|
+
super(options.message);
|
|
175
|
+
this.name = "BanataAuthError";
|
|
176
|
+
this.status = options.status;
|
|
177
|
+
this.code = options.code;
|
|
178
|
+
this.requestId = options.requestId ?? "";
|
|
179
|
+
this.retryable = options.retryable ?? false;
|
|
180
|
+
}
|
|
181
|
+
toJSON() {
|
|
182
|
+
return {
|
|
183
|
+
name: this.name,
|
|
184
|
+
message: this.message,
|
|
185
|
+
status: this.status,
|
|
186
|
+
code: this.code,
|
|
187
|
+
requestId: this.requestId
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
var AuthenticationError = class extends BanataAuthError {
|
|
192
|
+
constructor(options = {}) {
|
|
193
|
+
super({
|
|
194
|
+
message: options.message ?? "Authentication required",
|
|
195
|
+
status: 401,
|
|
196
|
+
code: "authentication_required",
|
|
197
|
+
requestId: options.requestId,
|
|
198
|
+
retryable: false
|
|
199
|
+
});
|
|
200
|
+
this.name = "AuthenticationError";
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
var ForbiddenError = class extends BanataAuthError {
|
|
204
|
+
constructor(options = {}) {
|
|
205
|
+
super({
|
|
206
|
+
message: options.message ?? "Insufficient permissions",
|
|
207
|
+
status: 403,
|
|
208
|
+
code: "forbidden",
|
|
209
|
+
requestId: options.requestId,
|
|
210
|
+
retryable: false
|
|
211
|
+
});
|
|
212
|
+
this.name = "ForbiddenError";
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var NotFoundError = class extends BanataAuthError {
|
|
216
|
+
constructor(options = {}) {
|
|
217
|
+
super({
|
|
218
|
+
message: options.message ?? (options.resource ? `${options.resource} not found` : "Not found"),
|
|
219
|
+
status: 404,
|
|
220
|
+
code: "not_found",
|
|
221
|
+
requestId: options.requestId,
|
|
222
|
+
retryable: false
|
|
223
|
+
});
|
|
224
|
+
this.name = "NotFoundError";
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
var ConflictError = class extends BanataAuthError {
|
|
228
|
+
constructor(options = {}) {
|
|
229
|
+
super({
|
|
230
|
+
message: options.message ?? "Resource already exists",
|
|
231
|
+
status: 409,
|
|
232
|
+
code: "conflict",
|
|
233
|
+
requestId: options.requestId,
|
|
234
|
+
retryable: false
|
|
235
|
+
});
|
|
236
|
+
this.name = "ConflictError";
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var ValidationError = class extends BanataAuthError {
|
|
240
|
+
errors;
|
|
241
|
+
constructor(options) {
|
|
242
|
+
super({
|
|
243
|
+
message: options.message ?? "Validation failed",
|
|
244
|
+
status: 422,
|
|
245
|
+
code: "validation_error",
|
|
246
|
+
requestId: options.requestId,
|
|
247
|
+
retryable: false
|
|
248
|
+
});
|
|
249
|
+
this.name = "ValidationError";
|
|
250
|
+
this.errors = options.errors;
|
|
251
|
+
}
|
|
252
|
+
toJSON() {
|
|
253
|
+
return {
|
|
254
|
+
...super.toJSON(),
|
|
255
|
+
errors: this.errors
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
var RateLimitError = class extends BanataAuthError {
|
|
260
|
+
retryAfter;
|
|
261
|
+
constructor(options) {
|
|
262
|
+
super({
|
|
263
|
+
message: `Rate limit exceeded. Retry after ${options.retryAfter} seconds`,
|
|
264
|
+
status: 429,
|
|
265
|
+
code: "rate_limit_exceeded",
|
|
266
|
+
requestId: options.requestId,
|
|
267
|
+
retryable: true
|
|
268
|
+
});
|
|
269
|
+
this.name = "RateLimitError";
|
|
270
|
+
this.retryAfter = options.retryAfter;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
var InternalError = class extends BanataAuthError {
|
|
274
|
+
constructor(options = {}) {
|
|
275
|
+
super({
|
|
276
|
+
message: options.message ?? "Internal server error",
|
|
277
|
+
status: 500,
|
|
278
|
+
code: "internal_error",
|
|
279
|
+
requestId: options.requestId,
|
|
280
|
+
retryable: true
|
|
281
|
+
});
|
|
282
|
+
this.name = "InternalError";
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
function createErrorFromStatus(status, body, requestId) {
|
|
286
|
+
switch (status) {
|
|
287
|
+
case 401:
|
|
288
|
+
return new AuthenticationError({ message: body.message, requestId });
|
|
289
|
+
case 403:
|
|
290
|
+
return new ForbiddenError({ message: body.message, requestId });
|
|
291
|
+
case 404:
|
|
292
|
+
return new NotFoundError({ message: body.message, requestId });
|
|
293
|
+
case 409:
|
|
294
|
+
return new ConflictError({ message: body.message, requestId });
|
|
295
|
+
case 422:
|
|
296
|
+
return new ValidationError({
|
|
297
|
+
message: body.message,
|
|
298
|
+
errors: body.errors ?? [],
|
|
299
|
+
requestId
|
|
300
|
+
});
|
|
301
|
+
case 429: {
|
|
302
|
+
return new RateLimitError({ retryAfter: 60, requestId });
|
|
303
|
+
}
|
|
304
|
+
default:
|
|
305
|
+
if (status >= 500) {
|
|
306
|
+
return new InternalError({ message: body.message, requestId });
|
|
307
|
+
}
|
|
308
|
+
return new BanataAuthError({
|
|
309
|
+
message: body.message ?? "Unknown error",
|
|
310
|
+
status,
|
|
311
|
+
code: body.code ?? "unknown_error",
|
|
312
|
+
requestId
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
var emailSchema = zod.z.string().email("Invalid email address").max(255).toLowerCase().trim();
|
|
317
|
+
var passwordSchema = zod.z.string().min(SIZE_LIMITS.passwordMinLength, `Password must be at least ${SIZE_LIMITS.passwordMinLength} characters`).max(SIZE_LIMITS.passwordMaxLength, `Password must be at most ${SIZE_LIMITS.passwordMaxLength} characters`);
|
|
318
|
+
var nameSchema = zod.z.string().trim().min(1, "Name is required").max(255);
|
|
319
|
+
var slugSchema = zod.z.string().min(2, "Slug must be at least 2 characters").max(128).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "Slug must be lowercase alphanumeric with hyphens").trim();
|
|
320
|
+
var urlSchema = zod.z.string().url("Invalid URL").max(2048);
|
|
321
|
+
var httpsUrlSchema = urlSchema.refine((url) => url.startsWith("https://"), {
|
|
322
|
+
message: "URL must use HTTPS"
|
|
323
|
+
});
|
|
324
|
+
var metadataSchema = zod.z.record(zod.z.unknown()).refine((data) => JSON.stringify(data).length <= SIZE_LIMITS.metadataMaxBytes, {
|
|
325
|
+
message: `Metadata must be less than ${SIZE_LIMITS.metadataMaxBytes / 1024}KB`
|
|
326
|
+
}).optional();
|
|
327
|
+
var domainSchema = zod.z.string().trim().toLowerCase().min(3).max(255).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/, {
|
|
328
|
+
message: "Invalid domain format"
|
|
329
|
+
});
|
|
330
|
+
var paginationSchema = zod.z.object({
|
|
331
|
+
limit: zod.z.coerce.number().int().min(1).max(100).default(10),
|
|
332
|
+
before: zod.z.string().optional(),
|
|
333
|
+
after: zod.z.string().optional(),
|
|
334
|
+
order: zod.z.enum(["asc", "desc"]).default("desc")
|
|
335
|
+
});
|
|
336
|
+
var createUserSchema = zod.z.object({
|
|
337
|
+
email: emailSchema,
|
|
338
|
+
password: passwordSchema.optional(),
|
|
339
|
+
name: nameSchema,
|
|
340
|
+
image: urlSchema.optional(),
|
|
341
|
+
username: zod.z.string().min(2).max(64).optional(),
|
|
342
|
+
phoneNumber: zod.z.string().optional(),
|
|
343
|
+
emailVerified: zod.z.boolean().default(false),
|
|
344
|
+
role: zod.z.enum(["user", "admin"]).default("user"),
|
|
345
|
+
metadata: metadataSchema
|
|
346
|
+
});
|
|
347
|
+
var updateUserSchema = zod.z.object({
|
|
348
|
+
name: nameSchema.optional(),
|
|
349
|
+
image: urlSchema.optional().nullable(),
|
|
350
|
+
username: zod.z.string().min(2).max(64).optional(),
|
|
351
|
+
phoneNumber: zod.z.string().optional().nullable(),
|
|
352
|
+
metadata: metadataSchema
|
|
353
|
+
});
|
|
354
|
+
var createOrganizationSchema = zod.z.object({
|
|
355
|
+
name: nameSchema,
|
|
356
|
+
slug: slugSchema.optional(),
|
|
357
|
+
logo: urlSchema.optional(),
|
|
358
|
+
metadata: metadataSchema,
|
|
359
|
+
requireMfa: zod.z.boolean().default(false),
|
|
360
|
+
allowedEmailDomains: zod.z.array(domainSchema).optional(),
|
|
361
|
+
maxMembers: zod.z.number().int().positive().optional()
|
|
362
|
+
});
|
|
363
|
+
var updateOrganizationSchema = zod.z.object({
|
|
364
|
+
name: nameSchema.optional(),
|
|
365
|
+
slug: slugSchema.optional(),
|
|
366
|
+
logo: urlSchema.optional().nullable(),
|
|
367
|
+
metadata: metadataSchema,
|
|
368
|
+
requireMfa: zod.z.boolean().optional(),
|
|
369
|
+
ssoEnforced: zod.z.boolean().optional(),
|
|
370
|
+
allowedEmailDomains: zod.z.array(domainSchema).optional().nullable(),
|
|
371
|
+
maxMembers: zod.z.number().int().positive().optional().nullable()
|
|
372
|
+
});
|
|
373
|
+
var inviteMemberSchema = zod.z.object({
|
|
374
|
+
email: emailSchema,
|
|
375
|
+
role: zod.z.string().min(1)
|
|
376
|
+
});
|
|
377
|
+
var createSsoConnectionSchema = zod.z.object({
|
|
378
|
+
organizationId: zod.z.string(),
|
|
379
|
+
type: zod.z.enum(["saml", "oidc"]),
|
|
380
|
+
name: nameSchema,
|
|
381
|
+
domains: zod.z.array(domainSchema).min(1),
|
|
382
|
+
samlConfig: zod.z.object({
|
|
383
|
+
idpEntityId: zod.z.string(),
|
|
384
|
+
idpSsoUrl: urlSchema,
|
|
385
|
+
idpCertificate: zod.z.string().min(1),
|
|
386
|
+
nameIdFormat: zod.z.string().optional(),
|
|
387
|
+
signRequest: zod.z.boolean().default(false),
|
|
388
|
+
allowIdpInitiated: zod.z.boolean().default(false),
|
|
389
|
+
attributeMapping: zod.z.record(zod.z.string()).optional()
|
|
390
|
+
}).optional(),
|
|
391
|
+
oidcConfig: zod.z.object({
|
|
392
|
+
issuer: urlSchema,
|
|
393
|
+
clientId: zod.z.string().min(1),
|
|
394
|
+
clientSecret: zod.z.string().min(1),
|
|
395
|
+
scopes: zod.z.array(zod.z.string()).default(["openid", "email", "profile"])
|
|
396
|
+
}).optional()
|
|
397
|
+
});
|
|
398
|
+
var createWebhookEndpointSchema = zod.z.object({
|
|
399
|
+
url: httpsUrlSchema,
|
|
400
|
+
eventTypes: zod.z.array(zod.z.string()).default([]),
|
|
401
|
+
enabled: zod.z.boolean().default(true)
|
|
402
|
+
});
|
|
403
|
+
var createAuditEventSchema = zod.z.object({
|
|
404
|
+
action: zod.z.string().min(1),
|
|
405
|
+
actor: zod.z.object({
|
|
406
|
+
type: zod.z.enum(["user", "admin", "system", "api_key", "scim"]),
|
|
407
|
+
id: zod.z.string(),
|
|
408
|
+
name: zod.z.string().optional(),
|
|
409
|
+
email: zod.z.string().optional(),
|
|
410
|
+
metadata: zod.z.record(zod.z.string()).optional()
|
|
411
|
+
}),
|
|
412
|
+
targets: zod.z.array(
|
|
413
|
+
zod.z.object({
|
|
414
|
+
type: zod.z.string(),
|
|
415
|
+
id: zod.z.string(),
|
|
416
|
+
name: zod.z.string().optional(),
|
|
417
|
+
metadata: zod.z.record(zod.z.string()).optional()
|
|
418
|
+
})
|
|
419
|
+
).default([]),
|
|
420
|
+
context: zod.z.object({
|
|
421
|
+
organizationId: zod.z.string().optional(),
|
|
422
|
+
ipAddress: zod.z.string().optional(),
|
|
423
|
+
userAgent: zod.z.string().optional(),
|
|
424
|
+
requestId: zod.z.string().optional()
|
|
425
|
+
}).default({}),
|
|
426
|
+
changes: zod.z.object({
|
|
427
|
+
before: zod.z.record(zod.z.unknown()).optional(),
|
|
428
|
+
after: zod.z.record(zod.z.unknown()).optional()
|
|
429
|
+
}).optional(),
|
|
430
|
+
idempotencyKey: zod.z.string().optional(),
|
|
431
|
+
metadata: zod.z.record(zod.z.string()).optional(),
|
|
432
|
+
occurredAt: zod.z.number().optional()
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// src/runtime-auth-config.ts
|
|
436
|
+
function listEnabledSocialProviderIds(config) {
|
|
437
|
+
if (!config) return [];
|
|
438
|
+
return Object.entries(config.socialProviders).filter(([, provider]) => provider.enabled).map(([providerId]) => providerId);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/email-blocks.ts
|
|
442
|
+
var BLOCK_PALETTE = [
|
|
443
|
+
{ type: "heading", label: "Heading", description: "Title or section heading", icon: "Heading" },
|
|
444
|
+
{
|
|
445
|
+
type: "text",
|
|
446
|
+
label: "Text",
|
|
447
|
+
description: "Paragraph of body text",
|
|
448
|
+
icon: "AlignLeft"
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
type: "button",
|
|
452
|
+
label: "Button",
|
|
453
|
+
description: "Call-to-action button link",
|
|
454
|
+
icon: "MousePointerClick"
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
type: "image",
|
|
458
|
+
label: "Image",
|
|
459
|
+
description: "Inline image",
|
|
460
|
+
icon: "Image"
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
type: "divider",
|
|
464
|
+
label: "Divider",
|
|
465
|
+
description: "Horizontal separator line",
|
|
466
|
+
icon: "Minus"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
type: "spacer",
|
|
470
|
+
label: "Spacer",
|
|
471
|
+
description: "Empty vertical space",
|
|
472
|
+
icon: "Space"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
type: "code",
|
|
476
|
+
label: "Code",
|
|
477
|
+
description: "Monospace code or OTP display",
|
|
478
|
+
icon: "Code"
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
type: "link",
|
|
482
|
+
label: "Link",
|
|
483
|
+
description: "Inline hyperlink",
|
|
484
|
+
icon: "Link"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
type: "columns",
|
|
488
|
+
label: "Columns",
|
|
489
|
+
description: "Multi-column layout (2\u20134)",
|
|
490
|
+
icon: "Columns2"
|
|
491
|
+
}
|
|
492
|
+
];
|
|
493
|
+
function makeId() {
|
|
494
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
495
|
+
return crypto.randomUUID();
|
|
496
|
+
}
|
|
497
|
+
return `blk_${Array.from({ length: 12 }, () => Math.floor(Math.random() * 16).toString(16)).join("")}`;
|
|
498
|
+
}
|
|
499
|
+
function createDefaultBlock(type) {
|
|
500
|
+
const id = makeId();
|
|
501
|
+
switch (type) {
|
|
502
|
+
case "heading":
|
|
503
|
+
return { id, type: "heading", as: "h1", text: "Heading" };
|
|
504
|
+
case "text":
|
|
505
|
+
return {
|
|
506
|
+
id,
|
|
507
|
+
type: "text",
|
|
508
|
+
text: "Start writing your email content here..."
|
|
509
|
+
};
|
|
510
|
+
case "button":
|
|
511
|
+
return {
|
|
512
|
+
id,
|
|
513
|
+
type: "button",
|
|
514
|
+
text: "Click Here",
|
|
515
|
+
href: "https://example.com",
|
|
516
|
+
variant: "primary"
|
|
517
|
+
};
|
|
518
|
+
case "image":
|
|
519
|
+
return {
|
|
520
|
+
id,
|
|
521
|
+
type: "image",
|
|
522
|
+
src: "https://placehold.co/600x200/e2e8f0/64748b?text=Image",
|
|
523
|
+
alt: "Image",
|
|
524
|
+
width: 600
|
|
525
|
+
};
|
|
526
|
+
case "divider":
|
|
527
|
+
return { id, type: "divider" };
|
|
528
|
+
case "spacer":
|
|
529
|
+
return { id, type: "spacer", height: 24 };
|
|
530
|
+
case "code":
|
|
531
|
+
return { id, type: "code", text: "123456" };
|
|
532
|
+
case "link":
|
|
533
|
+
return {
|
|
534
|
+
id,
|
|
535
|
+
type: "link",
|
|
536
|
+
text: "Click here",
|
|
537
|
+
href: "https://example.com"
|
|
538
|
+
};
|
|
539
|
+
case "columns":
|
|
540
|
+
return {
|
|
541
|
+
id,
|
|
542
|
+
type: "columns",
|
|
543
|
+
columns: [
|
|
544
|
+
{ width: "50%", blocks: [] },
|
|
545
|
+
{ width: "50%", blocks: [] }
|
|
546
|
+
]
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function interpolateVariables(text, variables) {
|
|
551
|
+
return text.replace(/\{\{(\w+)\}\}/g, (match, name) => {
|
|
552
|
+
return variables[name] ?? match;
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
function extractVariables(blocks) {
|
|
556
|
+
const vars = /* @__PURE__ */ new Set();
|
|
557
|
+
const regex = /\{\{(\w+)\}\}/g;
|
|
558
|
+
function scan(text) {
|
|
559
|
+
for (let m = regex.exec(text); m !== null; m = regex.exec(text)) {
|
|
560
|
+
if (m[1]) vars.add(m[1]);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function walk(blockList) {
|
|
564
|
+
for (const block of blockList) {
|
|
565
|
+
switch (block.type) {
|
|
566
|
+
case "heading":
|
|
567
|
+
case "text":
|
|
568
|
+
case "code":
|
|
569
|
+
scan(block.text);
|
|
570
|
+
break;
|
|
571
|
+
case "button":
|
|
572
|
+
scan(block.text);
|
|
573
|
+
scan(block.href);
|
|
574
|
+
break;
|
|
575
|
+
case "link":
|
|
576
|
+
scan(block.text);
|
|
577
|
+
scan(block.href);
|
|
578
|
+
break;
|
|
579
|
+
case "image":
|
|
580
|
+
scan(block.src);
|
|
581
|
+
break;
|
|
582
|
+
case "columns":
|
|
583
|
+
for (const col of block.columns) {
|
|
584
|
+
walk(col.blocks);
|
|
585
|
+
}
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
walk(blocks);
|
|
591
|
+
return [...vars];
|
|
592
|
+
}
|
|
593
|
+
function getBuiltInTemplateBlocks(type) {
|
|
594
|
+
switch (type) {
|
|
595
|
+
case "verification":
|
|
596
|
+
return [
|
|
597
|
+
{ id: makeId(), type: "heading", as: "h1", text: "Verify your email address" },
|
|
598
|
+
{ id: makeId(), type: "text", text: "Hi {{userName}}," },
|
|
599
|
+
{
|
|
600
|
+
id: makeId(),
|
|
601
|
+
type: "text",
|
|
602
|
+
text: "Thanks for signing up! Please verify your email address by clicking the button below."
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
id: makeId(),
|
|
606
|
+
type: "button",
|
|
607
|
+
text: "Verify Email",
|
|
608
|
+
href: "{{verificationUrl}}",
|
|
609
|
+
variant: "primary"
|
|
610
|
+
},
|
|
611
|
+
{ id: makeId(), type: "divider" },
|
|
612
|
+
{
|
|
613
|
+
id: makeId(),
|
|
614
|
+
type: "text",
|
|
615
|
+
text: "If the button doesn't work, copy and paste this URL into your browser:",
|
|
616
|
+
style: { fontSize: 12, color: "#94a3b8" }
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
id: makeId(),
|
|
620
|
+
type: "link",
|
|
621
|
+
text: "{{verificationUrl}}",
|
|
622
|
+
href: "{{verificationUrl}}",
|
|
623
|
+
style: { fontSize: 12 }
|
|
624
|
+
}
|
|
625
|
+
];
|
|
626
|
+
case "password-reset":
|
|
627
|
+
return [
|
|
628
|
+
{ id: makeId(), type: "heading", as: "h1", text: "Reset your password" },
|
|
629
|
+
{ id: makeId(), type: "text", text: "Hi {{userName}}," },
|
|
630
|
+
{
|
|
631
|
+
id: makeId(),
|
|
632
|
+
type: "text",
|
|
633
|
+
text: "We received a request to reset your password. Click the button below to choose a new password."
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
id: makeId(),
|
|
637
|
+
type: "button",
|
|
638
|
+
text: "Reset Password",
|
|
639
|
+
href: "{{resetUrl}}",
|
|
640
|
+
variant: "primary"
|
|
641
|
+
},
|
|
642
|
+
{ id: makeId(), type: "divider" },
|
|
643
|
+
{
|
|
644
|
+
id: makeId(),
|
|
645
|
+
type: "text",
|
|
646
|
+
text: "This link expires in 1 hour. If you didn't request a password reset, you can safely ignore this email.",
|
|
647
|
+
style: { fontSize: 12, color: "#94a3b8" }
|
|
648
|
+
}
|
|
649
|
+
];
|
|
650
|
+
case "magic-link":
|
|
651
|
+
return [
|
|
652
|
+
{ id: makeId(), type: "heading", as: "h1", text: "Sign in to your account" },
|
|
653
|
+
{ id: makeId(), type: "text", text: "Hi there," },
|
|
654
|
+
{
|
|
655
|
+
id: makeId(),
|
|
656
|
+
type: "text",
|
|
657
|
+
text: "Click the button below to sign in to your account. This link expires in 10 minutes."
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
id: makeId(),
|
|
661
|
+
type: "button",
|
|
662
|
+
text: "Sign In",
|
|
663
|
+
href: "{{magicLinkUrl}}",
|
|
664
|
+
variant: "primary"
|
|
665
|
+
},
|
|
666
|
+
{ id: makeId(), type: "divider" },
|
|
667
|
+
{
|
|
668
|
+
id: makeId(),
|
|
669
|
+
type: "text",
|
|
670
|
+
text: "If you didn't request this link, you can safely ignore this email.",
|
|
671
|
+
style: { fontSize: 12, color: "#94a3b8" }
|
|
672
|
+
}
|
|
673
|
+
];
|
|
674
|
+
case "email-otp":
|
|
675
|
+
return [
|
|
676
|
+
{ id: makeId(), type: "heading", as: "h1", text: "Your verification code" },
|
|
677
|
+
{
|
|
678
|
+
id: makeId(),
|
|
679
|
+
type: "text",
|
|
680
|
+
text: "Use the code below to verify your identity. It expires in 10 minutes."
|
|
681
|
+
},
|
|
682
|
+
{ id: makeId(), type: "code", text: "{{otp}}" },
|
|
683
|
+
{ id: makeId(), type: "divider" },
|
|
684
|
+
{
|
|
685
|
+
id: makeId(),
|
|
686
|
+
type: "text",
|
|
687
|
+
text: "If you didn't request this code, you can safely ignore this email. Never share this code with anyone.",
|
|
688
|
+
style: { fontSize: 12, color: "#94a3b8" }
|
|
689
|
+
}
|
|
690
|
+
];
|
|
691
|
+
case "invitation":
|
|
692
|
+
return [
|
|
693
|
+
{ id: makeId(), type: "heading", as: "h1", text: "You've been invited" },
|
|
694
|
+
{
|
|
695
|
+
id: makeId(),
|
|
696
|
+
type: "text",
|
|
697
|
+
text: "<strong>{{inviterName}}</strong> has invited you to join <strong>{{organizationName}}</strong>."
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
id: makeId(),
|
|
701
|
+
type: "button",
|
|
702
|
+
text: "Accept Invitation",
|
|
703
|
+
href: "{{acceptUrl}}",
|
|
704
|
+
variant: "primary"
|
|
705
|
+
},
|
|
706
|
+
{ id: makeId(), type: "divider" },
|
|
707
|
+
{
|
|
708
|
+
id: makeId(),
|
|
709
|
+
type: "link",
|
|
710
|
+
text: "{{acceptUrl}}",
|
|
711
|
+
href: "{{acceptUrl}}",
|
|
712
|
+
style: { fontSize: 12 }
|
|
713
|
+
}
|
|
714
|
+
];
|
|
715
|
+
case "welcome":
|
|
716
|
+
return [
|
|
717
|
+
{ id: makeId(), type: "heading", as: "h1", text: "Welcome to {{appName}}" },
|
|
718
|
+
{ id: makeId(), type: "text", text: "Hi {{userName}}," },
|
|
719
|
+
{
|
|
720
|
+
id: makeId(),
|
|
721
|
+
type: "text",
|
|
722
|
+
text: "Your account has been created successfully. You're all set to get started."
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
id: makeId(),
|
|
726
|
+
type: "button",
|
|
727
|
+
text: "Go to Dashboard",
|
|
728
|
+
href: "{{dashboardUrl}}",
|
|
729
|
+
variant: "primary"
|
|
730
|
+
},
|
|
731
|
+
{ id: makeId(), type: "divider" },
|
|
732
|
+
{
|
|
733
|
+
id: makeId(),
|
|
734
|
+
type: "text",
|
|
735
|
+
text: "If you didn't create this account, please contact support.",
|
|
736
|
+
style: { fontSize: 12, color: "#94a3b8" }
|
|
737
|
+
}
|
|
738
|
+
];
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
exports.AuthenticationError = AuthenticationError;
|
|
743
|
+
exports.BLOCK_PALETTE = BLOCK_PALETTE;
|
|
744
|
+
exports.BanataAuthError = BanataAuthError;
|
|
745
|
+
exports.ConflictError = ConflictError;
|
|
746
|
+
exports.ForbiddenError = ForbiddenError;
|
|
747
|
+
exports.ID_PREFIXES = ID_PREFIXES;
|
|
748
|
+
exports.InternalError = InternalError;
|
|
749
|
+
exports.NotFoundError = NotFoundError;
|
|
750
|
+
exports.RATE_LIMITS = RATE_LIMITS;
|
|
751
|
+
exports.RateLimitError = RateLimitError;
|
|
752
|
+
exports.SIZE_LIMITS = SIZE_LIMITS;
|
|
753
|
+
exports.TOKEN_LIFETIMES = TOKEN_LIFETIMES;
|
|
754
|
+
exports.ValidationError = ValidationError;
|
|
755
|
+
exports.WEBHOOK_MAX_ATTEMPTS = WEBHOOK_MAX_ATTEMPTS;
|
|
756
|
+
exports.WEBHOOK_MAX_CONSECUTIVE_FAILURES = WEBHOOK_MAX_CONSECUTIVE_FAILURES;
|
|
757
|
+
exports.WEBHOOK_RETRY_DELAYS = WEBHOOK_RETRY_DELAYS;
|
|
758
|
+
exports.createAuditEventSchema = createAuditEventSchema;
|
|
759
|
+
exports.createDefaultBlock = createDefaultBlock;
|
|
760
|
+
exports.createErrorFromStatus = createErrorFromStatus;
|
|
761
|
+
exports.createOrganizationSchema = createOrganizationSchema;
|
|
762
|
+
exports.createSsoConnectionSchema = createSsoConnectionSchema;
|
|
763
|
+
exports.createUserSchema = createUserSchema;
|
|
764
|
+
exports.createWebhookEndpointSchema = createWebhookEndpointSchema;
|
|
765
|
+
exports.domainSchema = domainSchema;
|
|
766
|
+
exports.emailSchema = emailSchema;
|
|
767
|
+
exports.extractVariables = extractVariables;
|
|
768
|
+
exports.generateId = generateId;
|
|
769
|
+
exports.generateOtp = generateOtp;
|
|
770
|
+
exports.generateRandomToken = generateRandomToken;
|
|
771
|
+
exports.getBuiltInTemplateBlocks = getBuiltInTemplateBlocks;
|
|
772
|
+
exports.getResourceType = getResourceType;
|
|
773
|
+
exports.httpsUrlSchema = httpsUrlSchema;
|
|
774
|
+
exports.interpolateVariables = interpolateVariables;
|
|
775
|
+
exports.inviteMemberSchema = inviteMemberSchema;
|
|
776
|
+
exports.listEnabledSocialProviderIds = listEnabledSocialProviderIds;
|
|
777
|
+
exports.metadataSchema = metadataSchema;
|
|
778
|
+
exports.nameSchema = nameSchema;
|
|
779
|
+
exports.paginationSchema = paginationSchema;
|
|
780
|
+
exports.passwordSchema = passwordSchema;
|
|
781
|
+
exports.slugSchema = slugSchema;
|
|
782
|
+
exports.ulid = ulid;
|
|
783
|
+
exports.updateOrganizationSchema = updateOrganizationSchema;
|
|
784
|
+
exports.updateUserSchema = updateUserSchema;
|
|
785
|
+
exports.urlSchema = urlSchema;
|
|
786
|
+
exports.validateId = validateId;
|
|
787
|
+
//# sourceMappingURL=index.cjs.map
|
|
788
|
+
//# sourceMappingURL=index.cjs.map
|