@seamless-auth/types 0.24.0 → 0.26.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/CHANGELOG.md +34 -0
- package/LICENSE +202 -661
- package/README.md +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/auth/auth.schema.d.ts +27 -0
- package/dist/schemas/auth/auth.schema.d.ts.map +1 -1
- package/dist/schemas/auth/auth.schema.js +10 -0
- package/dist/schemas/auth/auth.schema.js.map +1 -1
- package/dist/schemas/authEvent/schema.d.ts +10 -1
- package/dist/schemas/authEvent/schema.d.ts.map +1 -1
- package/dist/schemas/authEvent/schema.js +3 -0
- package/dist/schemas/authEvent/schema.js.map +1 -1
- package/dist/schemas/common/schema.d.ts +2 -0
- package/dist/schemas/common/schema.d.ts.map +1 -1
- package/dist/schemas/common/schema.js +3 -0
- package/dist/schemas/common/schema.js.map +1 -1
- package/dist/schemas/enrollment/schema.d.ts +171 -0
- package/dist/schemas/enrollment/schema.d.ts.map +1 -0
- package/dist/schemas/enrollment/schema.js +89 -0
- package/dist/schemas/enrollment/schema.js.map +1 -0
- package/dist/schemas/me/schema.d.ts +2 -0
- package/dist/schemas/me/schema.d.ts.map +1 -1
- package/dist/schemas/messaging/schema.d.ts +4 -0
- package/dist/schemas/messaging/schema.d.ts.map +1 -1
- package/dist/schemas/messaging/schema.js +6 -0
- package/dist/schemas/messaging/schema.js.map +1 -1
- package/dist/schemas/oauth/schema.d.ts +8 -1
- package/dist/schemas/oauth/schema.d.ts.map +1 -1
- package/dist/schemas/oauth/schema.js +13 -2
- package/dist/schemas/oauth/schema.js.map +1 -1
- package/dist/schemas/organization/schema.d.ts +10 -0
- package/dist/schemas/organization/schema.d.ts.map +1 -1
- package/dist/schemas/organization/schema.js +9 -1
- package/dist/schemas/organization/schema.js.map +1 -1
- package/dist/schemas/systemConfig/schema.d.ts +23 -12
- package/dist/schemas/systemConfig/schema.d.ts.map +1 -1
- package/dist/schemas/systemConfig/schema.js +27 -2
- package/dist/schemas/systemConfig/schema.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/schemas/auth/auth.schema.ts +13 -0
- package/src/schemas/authEvent/schema.ts +3 -0
- package/src/schemas/common/schema.ts +6 -0
- package/src/schemas/enrollment/schema.ts +118 -0
- package/src/schemas/messaging/schema.ts +6 -0
- package/src/schemas/oauth/schema.ts +13 -2
- package/src/schemas/organization/schema.ts +12 -1
- package/src/schemas/systemConfig/schema.ts +34 -5
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { IsoDate } from '../../shared.js';
|
|
3
|
+
import { PaginationQuerySchema } from '../common/schema.js';
|
|
4
|
+
import { AuthDeliverySchema } from '../messaging/schema.js';
|
|
5
|
+
import { RedirectTargetSchema } from '../systemConfig/schema.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* How far a user is through passkey enrollment, counted in WebAuthn credentials
|
|
9
|
+
* (passkeys and security keys; TOTP does not count). Two or more is the goal, so
|
|
10
|
+
* losing one authenticator does not lock the user out.
|
|
11
|
+
*/
|
|
12
|
+
export const EnrollmentStatusSchema = z.enum(['none', 'one', 'two_or_more']);
|
|
13
|
+
|
|
14
|
+
export type EnrollmentStatus = z.infer<typeof EnrollmentStatusSchema>;
|
|
15
|
+
|
|
16
|
+
// Accepts the parsed boolean as well as the query string, so parsing an already
|
|
17
|
+
// parsed query (as the server does to recover coerced types) gives the same answer.
|
|
18
|
+
const BooleanQuerySchema = z.union([
|
|
19
|
+
z.boolean(),
|
|
20
|
+
z.enum(['true', 'false']).transform((value) => value === 'true'),
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const AdminEnrollmentQuerySchema = PaginationQuerySchema.extend({
|
|
24
|
+
organizationId: z.uuid().optional(),
|
|
25
|
+
status: EnrollmentStatusSchema.optional(),
|
|
26
|
+
/** Only users brought in through `POST /admin/users/import`. */
|
|
27
|
+
imported: BooleanQuerySchema.optional(),
|
|
28
|
+
search: z.string().trim().min(1).max(120).optional(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export type AdminEnrollmentQuery = z.infer<typeof AdminEnrollmentQuerySchema>;
|
|
32
|
+
|
|
33
|
+
export const EnrollmentSummarySchema = z.object({
|
|
34
|
+
total: z.number().int().nonnegative(),
|
|
35
|
+
none: z.number().int().nonnegative(),
|
|
36
|
+
one: z.number().int().nonnegative(),
|
|
37
|
+
twoOrMore: z.number().int().nonnegative(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export type EnrollmentSummary = z.infer<typeof EnrollmentSummarySchema>;
|
|
41
|
+
|
|
42
|
+
export const EnrollmentUserSchema = z.object({
|
|
43
|
+
id: z.string(),
|
|
44
|
+
email: z.string(),
|
|
45
|
+
imported: z.boolean(),
|
|
46
|
+
credentialCount: z.number().int().nonnegative(),
|
|
47
|
+
status: EnrollmentStatusSchema,
|
|
48
|
+
lastLogin: IsoDate.nullable(),
|
|
49
|
+
enrollmentInvitedAt: IsoDate.nullable(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export type EnrollmentUser = z.infer<typeof EnrollmentUserSchema>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `summary` counts every user matching `organizationId` and `imported`, whatever the
|
|
56
|
+
* `status` filter; `users` and `total` are the filtered, paginated list.
|
|
57
|
+
*/
|
|
58
|
+
export const AdminEnrollmentResponseSchema = z.object({
|
|
59
|
+
summary: EnrollmentSummarySchema,
|
|
60
|
+
users: z.array(EnrollmentUserSchema),
|
|
61
|
+
total: z.number().int().nonnegative(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export type AdminEnrollmentResponse = z.infer<typeof AdminEnrollmentResponseSchema>;
|
|
65
|
+
|
|
66
|
+
export const ENROLLMENT_INVITE_MAX_USERS = 200;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Invites either the listed users or, with `organizationId`, the organization's members
|
|
70
|
+
* whose enrollment is at or below `status`. An organization-wide call sends at most
|
|
71
|
+
* `ENROLLMENT_INVITE_MAX_USERS` and reports how many remain, skipping anyone invited
|
|
72
|
+
* within the last day, so repeating it works through a large organization.
|
|
73
|
+
*/
|
|
74
|
+
export const EnrollmentInviteRequestSchema = z
|
|
75
|
+
.object({
|
|
76
|
+
userIds: z.array(z.uuid()).min(1).max(ENROLLMENT_INVITE_MAX_USERS).optional(),
|
|
77
|
+
organizationId: z.uuid().optional(),
|
|
78
|
+
status: z.enum(['none', 'one']).default('none'),
|
|
79
|
+
/** Where the email sends the user. Defaults to the tenant's sign-in page. */
|
|
80
|
+
signInUrl: RedirectTargetSchema.optional(),
|
|
81
|
+
})
|
|
82
|
+
.strict()
|
|
83
|
+
.refine((value) => Boolean(value.userIds) !== Boolean(value.organizationId), {
|
|
84
|
+
message: 'exactly one of userIds or organizationId is required',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export type EnrollmentInviteRequest = z.infer<typeof EnrollmentInviteRequestSchema>;
|
|
88
|
+
|
|
89
|
+
export const ENROLLMENT_INVITE_SKIP_REASONS = [
|
|
90
|
+
'not_found',
|
|
91
|
+
'already_enrolled',
|
|
92
|
+
'recently_invited',
|
|
93
|
+
'delivery_failed',
|
|
94
|
+
] as const;
|
|
95
|
+
|
|
96
|
+
export const EnrollmentInviteSkipReasonSchema = z.enum(ENROLLMENT_INVITE_SKIP_REASONS);
|
|
97
|
+
|
|
98
|
+
export type EnrollmentInviteSkipReason = z.infer<typeof EnrollmentInviteSkipReasonSchema>;
|
|
99
|
+
|
|
100
|
+
export const EnrollmentInviteResultSchema = z.object({
|
|
101
|
+
userId: z.string(),
|
|
102
|
+
status: z.enum(['sent', 'skipped']),
|
|
103
|
+
reason: EnrollmentInviteSkipReasonSchema.optional(),
|
|
104
|
+
/** Present in external delivery mode, for the caller to send itself. */
|
|
105
|
+
delivery: AuthDeliverySchema.optional(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export type EnrollmentInviteResult = z.infer<typeof EnrollmentInviteResultSchema>;
|
|
109
|
+
|
|
110
|
+
export const EnrollmentInviteResponseSchema = z.object({
|
|
111
|
+
sent: z.number().int().nonnegative(),
|
|
112
|
+
skipped: z.number().int().nonnegative(),
|
|
113
|
+
/** Organization-wide calls only: eligible members left for a later call. */
|
|
114
|
+
remaining: z.number().int().nonnegative().optional(),
|
|
115
|
+
results: z.array(EnrollmentInviteResultSchema),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export type EnrollmentInviteResponse = z.infer<typeof EnrollmentInviteResponseSchema>;
|
|
@@ -81,6 +81,12 @@ export const AuthDeliverySchema = z.discriminatedUnion('kind', [
|
|
|
81
81
|
token: z.string().optional(),
|
|
82
82
|
magicLinkUrl: z.string(),
|
|
83
83
|
}),
|
|
84
|
+
z.object({
|
|
85
|
+
kind: z.literal('enrollment_invite_email'),
|
|
86
|
+
to: z.string(),
|
|
87
|
+
// The sign-in page. It carries no credential, so it signs nobody in on its own.
|
|
88
|
+
signInUrl: z.string(),
|
|
89
|
+
}),
|
|
84
90
|
]);
|
|
85
91
|
|
|
86
92
|
export type AuthDeliveryInstruction = z.infer<typeof AuthDeliverySchema>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { RefreshSuccessResponseSchema } from '../auth/auth.schema.js';
|
|
3
|
-
import { OAuthProviderIdSchema
|
|
2
|
+
import { NextStepSchema, RefreshSuccessResponseSchema } from '../auth/auth.schema.js';
|
|
3
|
+
import { OAuthProviderIdSchema } from '../common/schema.js';
|
|
4
|
+
import { RedirectTargetSchema } from '../systemConfig/schema.js';
|
|
4
5
|
|
|
5
6
|
export const OAuthProviderParamSchema = z.object({
|
|
6
7
|
providerId: OAuthProviderIdSchema,
|
|
@@ -58,6 +59,13 @@ export const OAuthLoginSuccessResponseSchema = RefreshSuccessResponseSchema.omit
|
|
|
58
59
|
* as an error.
|
|
59
60
|
*/
|
|
60
61
|
returnTo: RedirectTargetSchema.optional(),
|
|
62
|
+
/**
|
|
63
|
+
* Present when the provider has `promptPasskeyEnrollment` set and the user has no
|
|
64
|
+
* passkey. The session in this response is a full access session, so the client sends
|
|
65
|
+
* the user into passkey enrollment before the app. Absent means there is nothing
|
|
66
|
+
* further to do.
|
|
67
|
+
*/
|
|
68
|
+
nextStep: NextStepSchema.optional(),
|
|
61
69
|
});
|
|
62
70
|
|
|
63
71
|
export type OAuthLoginSuccessResponse = z.infer<typeof OAuthLoginSuccessResponseSchema>;
|
|
@@ -66,6 +74,9 @@ export const OAUTH_ERROR_CODES = [
|
|
|
66
74
|
'oauth_missing_email',
|
|
67
75
|
'oauth_email_not_verified',
|
|
68
76
|
'oauth_missing_subject',
|
|
77
|
+
'oauth_invalid_id_token',
|
|
78
|
+
// A member of an organization that has retired this provider. Answered with 403.
|
|
79
|
+
'oauth_provider_retired',
|
|
69
80
|
] as const;
|
|
70
81
|
|
|
71
82
|
export const OAuthErrorCodeSchema = z.enum(OAUTH_ERROR_CODES);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { IsoDate } from '../../shared.js';
|
|
3
|
-
import { MetadataSchema } from '../common/schema.js';
|
|
3
|
+
import { MetadataSchema, OAuthProviderIdSchema } from '../common/schema.js';
|
|
4
4
|
|
|
5
5
|
export const OrganizationIdParamSchema = z.object({
|
|
6
6
|
organizationId: z.uuid(),
|
|
@@ -8,6 +8,12 @@ export const OrganizationIdParamSchema = z.object({
|
|
|
8
8
|
|
|
9
9
|
export type OrganizationIdParam = z.infer<typeof OrganizationIdParamSchema>;
|
|
10
10
|
|
|
11
|
+
export const OrganizationOAuthProviderParamSchema = OrganizationIdParamSchema.extend({
|
|
12
|
+
providerId: OAuthProviderIdSchema,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type OrganizationOAuthProviderParam = z.infer<typeof OrganizationOAuthProviderParamSchema>;
|
|
16
|
+
|
|
11
17
|
export const OrganizationMemberParamSchema = OrganizationIdParamSchema.extend({
|
|
12
18
|
userId: z.uuid(),
|
|
13
19
|
});
|
|
@@ -86,6 +92,11 @@ export const OrganizationSchema = z.object({
|
|
|
86
92
|
updatedAt: IsoDate,
|
|
87
93
|
membership: OrganizationMembershipSchema.optional(),
|
|
88
94
|
memberCount: z.number().int().nonnegative().optional(),
|
|
95
|
+
/**
|
|
96
|
+
* OAuth providers this organization's members can no longer sign in with, for cutting
|
|
97
|
+
* over from a legacy identity provider.
|
|
98
|
+
*/
|
|
99
|
+
retiredOAuthProviders: z.array(OAuthProviderIdSchema).optional(),
|
|
89
100
|
});
|
|
90
101
|
|
|
91
102
|
export type Organization = z.infer<typeof OrganizationSchema>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { OAuthProviderIdSchema } from '../common/schema.js';
|
|
2
3
|
import { UserImportSourceSchema } from '../userImport/schema.js';
|
|
3
4
|
import { RoleNameSchema } from '../role/schema.js';
|
|
4
5
|
|
|
@@ -44,10 +45,6 @@ export const RedirectTargetSchema = z.url().refine(
|
|
|
44
45
|
|
|
45
46
|
export type RedirectTarget = z.infer<typeof RedirectTargetSchema>;
|
|
46
47
|
|
|
47
|
-
export const OAuthProviderIdSchema = z.string().regex(/^[a-z0-9-]{2,40}$/);
|
|
48
|
-
|
|
49
|
-
export type OAuthProviderId = z.infer<typeof OAuthProviderIdSchema>;
|
|
50
|
-
|
|
51
48
|
export const OAuthProviderConfigSchema = z.object({
|
|
52
49
|
id: OAuthProviderIdSchema,
|
|
53
50
|
name: z.string().trim().min(1).max(80),
|
|
@@ -85,6 +82,12 @@ export const OAuthProviderConfigSchema = z.object({
|
|
|
85
82
|
*/
|
|
86
83
|
externalIdSource: UserImportSourceSchema.optional(),
|
|
87
84
|
externalIdJsonPath: z.string().trim().min(1).optional(),
|
|
85
|
+
/**
|
|
86
|
+
* After a sign-in through this provider by a user with no passkey, the callback
|
|
87
|
+
* response carries `nextStep: 'enroll_passkey'`. Meant for a legacy identity provider
|
|
88
|
+
* that users are being moved off.
|
|
89
|
+
*/
|
|
90
|
+
promptPasskeyEnrollment: z.boolean().default(false),
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
export type OAuthProviderConfig = z.infer<typeof OAuthProviderConfigSchema>;
|
|
@@ -280,6 +283,13 @@ export const SystemConfigSchema = z.object({
|
|
|
280
283
|
available_roles: z.array(RoleNameSchema).min(1),
|
|
281
284
|
login_methods: z.array(LoginMethodSchema).min(1),
|
|
282
285
|
passkey_login_fallback_enabled: z.boolean(),
|
|
286
|
+
/**
|
|
287
|
+
* After an email or phone code sign-in, or a magic link sign-in, by a user with no
|
|
288
|
+
* passkey, the session response carries `nextStep: 'enroll_passkey'`. For moving an
|
|
289
|
+
* organization onto passkeys, for example after importing its users. OAuth
|
|
290
|
+
* providers have their own `promptPasskeyEnrollment`.
|
|
291
|
+
*/
|
|
292
|
+
prompt_passkey_enrollment: z.boolean().default(false),
|
|
283
293
|
oauth_providers: z.array(OAuthProviderConfigSchema).default([]),
|
|
284
294
|
lockout_policy: LockoutPolicySchema.default(DefaultLockoutPolicy),
|
|
285
295
|
// Derived from the field defaults rather than restated, so the two cannot drift.
|
|
@@ -354,6 +364,7 @@ export const SystemConfigPatchSchema = z
|
|
|
354
364
|
login_methods: SystemConfigSchema.shape.login_methods.optional(),
|
|
355
365
|
passkey_login_fallback_enabled:
|
|
356
366
|
SystemConfigSchema.shape.passkey_login_fallback_enabled.optional(),
|
|
367
|
+
prompt_passkey_enrollment: z.boolean().optional(),
|
|
357
368
|
oauth_providers: z.array(OAuthProviderConfigSchema).optional(),
|
|
358
369
|
lockout_policy: LockoutPolicySchema.optional(),
|
|
359
370
|
authenticator_policy: AuthenticatorPolicySchema.optional(),
|
|
@@ -413,10 +424,28 @@ export const OAuthProviderIdParamSchema = z.object({
|
|
|
413
424
|
|
|
414
425
|
export type OAuthProviderIdParam = z.infer<typeof OAuthProviderIdParamSchema>;
|
|
415
426
|
|
|
427
|
+
type WithoutDefaults<Shape extends z.ZodRawShape> = {
|
|
428
|
+
[Key in keyof Shape]: Shape[Key] extends z.ZodDefault<infer Inner> ? Inner : Shape[Key];
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
function withoutDefaults<Shape extends z.ZodRawShape>(shape: Shape): WithoutDefaults<Shape> {
|
|
432
|
+
return Object.fromEntries(
|
|
433
|
+
Object.entries(shape).map(([key, field]) => [
|
|
434
|
+
key,
|
|
435
|
+
field instanceof z.ZodDefault ? field.unwrap() : field,
|
|
436
|
+
]),
|
|
437
|
+
) as WithoutDefaults<Shape>;
|
|
438
|
+
}
|
|
439
|
+
|
|
416
440
|
// The id is immutable and taken from the path, so it is omitted here. Every other
|
|
417
441
|
// field is optional so callers can patch a single attribute without resending the
|
|
418
442
|
// whole provider; the merged result is re-validated against the full schema.
|
|
419
|
-
|
|
443
|
+
//
|
|
444
|
+
// Defaults are stripped first: `.partial()` keeps them, so a patch of one field would
|
|
445
|
+
// parse to that field plus every default, and merging that over the stored provider
|
|
446
|
+
// silently reset settings such as `accountLinking` and `allowSignup`.
|
|
447
|
+
export const OAuthProviderUpdateSchema = z
|
|
448
|
+
.object(withoutDefaults(OAuthProviderConfigSchema.omit({ id: true }).shape))
|
|
420
449
|
.partial()
|
|
421
450
|
.strict();
|
|
422
451
|
|