@logto/schemas 1.11.0 → 1.12.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/alterations/1.12.0-1700031616-update-org-role-foreign-keys.ts +35 -0
- package/alterations/1.12.0-1701054133-add-unique-constraint-to-the-sso-connector-name.ts +21 -0
- package/alterations/1.12.0-1701245520-add-single-sign-on-enabled-flag-to-sie.ts +20 -0
- package/alterations-js/1.12.0-1700031616-update-org-role-foreign-keys.d.ts +3 -0
- package/alterations-js/1.12.0-1700031616-update-org-role-foreign-keys.js +31 -0
- package/alterations-js/1.12.0-1701054133-add-unique-constraint-to-the-sso-connector-name.d.ts +3 -0
- package/alterations-js/1.12.0-1701054133-add-unique-constraint-to-the-sso-connector-name.js +17 -0
- package/alterations-js/1.12.0-1701245520-add-single-sign-on-enabled-flag-to-sie.d.ts +3 -0
- package/alterations-js/1.12.0-1701245520-add-single-sign-on-enabled-flag-to-sie.js +16 -0
- package/lib/consts/index.d.ts +1 -0
- package/lib/consts/index.js +1 -0
- package/lib/consts/subscriptions.d.ts +6 -0
- package/lib/consts/subscriptions.js +7 -0
- package/lib/db-entries/sign-in-experience.d.ts +3 -1
- package/lib/db-entries/sign-in-experience.js +4 -0
- package/lib/db-entries/sso-connector.d.ts +2 -2
- package/lib/foundations/jsonb-types/sso-connector.d.ts +3 -0
- package/lib/foundations/jsonb-types/sso-connector.js +1 -0
- package/lib/types/hook.d.ts +1 -0
- package/lib/types/sso-connector.d.ts +54 -57
- package/lib/types/sso-connector.js +41 -7
- package/lib/types/tenant.js +3 -0
- package/lib/types/user.d.ts +3 -0
- package/lib/types/user.js +2 -1
- package/lib/utils/domain.d.ts +10 -0
- package/lib/utils/domain.js +28 -0
- package/lib/utils/domain.test.d.ts +1 -0
- package/lib/utils/domain.test.js +34 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/package.json +5 -5
- package/tables/organization_role_user_relations.sql +8 -6
- package/tables/sign_in_experiences.sql +1 -0
- package/tables/sso_connectors.sql +4 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
alter table organization_role_user_relations
|
|
9
|
+
drop constraint organization_role_user_relations_organization_id_fkey;
|
|
10
|
+
alter table organization_role_user_relations
|
|
11
|
+
drop constraint organization_role_user_relations_user_id_fkey;
|
|
12
|
+
alter table organization_role_user_relations
|
|
13
|
+
add foreign key (tenant_id, organization_id, user_id)
|
|
14
|
+
references organization_user_relations (tenant_id, organization_id, user_id)
|
|
15
|
+
on update cascade on delete cascade;
|
|
16
|
+
`);
|
|
17
|
+
},
|
|
18
|
+
down: async (pool) => {
|
|
19
|
+
await pool.query(sql`
|
|
20
|
+
alter table organization_role_user_relations
|
|
21
|
+
-- The constraint name is strange because it's generated by Postgres and it has a 63 character limit
|
|
22
|
+
drop constraint organization_role_user_relati_tenant_id_organization_id_us_fkey;
|
|
23
|
+
alter table organization_role_user_relations
|
|
24
|
+
add foreign key (organization_id)
|
|
25
|
+
references organizations (id)
|
|
26
|
+
on update cascade on delete cascade;
|
|
27
|
+
alter table organization_role_user_relations
|
|
28
|
+
add foreign key (user_id)
|
|
29
|
+
references users (id)
|
|
30
|
+
on update cascade on delete cascade;
|
|
31
|
+
`);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default alteration;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
alter table sso_connectors
|
|
9
|
+
add constraint sso_connectors__connector_name__unique
|
|
10
|
+
unique (tenant_id, connector_name);
|
|
11
|
+
`);
|
|
12
|
+
},
|
|
13
|
+
down: async (pool) => {
|
|
14
|
+
await pool.query(sql`
|
|
15
|
+
alter table sso_connectors
|
|
16
|
+
drop constraint sso_connectors__connector_name__unique;
|
|
17
|
+
`);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default alteration;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
alter table sign_in_experiences
|
|
9
|
+
add column single_sign_on_enabled boolean not null default false;
|
|
10
|
+
`);
|
|
11
|
+
},
|
|
12
|
+
down: async (pool) => {
|
|
13
|
+
await pool.query(sql`
|
|
14
|
+
alter table sign_in_experiences
|
|
15
|
+
drop column single_sign_on_enabled;
|
|
16
|
+
`);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default alteration;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
alter table organization_role_user_relations
|
|
6
|
+
drop constraint organization_role_user_relations_organization_id_fkey;
|
|
7
|
+
alter table organization_role_user_relations
|
|
8
|
+
drop constraint organization_role_user_relations_user_id_fkey;
|
|
9
|
+
alter table organization_role_user_relations
|
|
10
|
+
add foreign key (tenant_id, organization_id, user_id)
|
|
11
|
+
references organization_user_relations (tenant_id, organization_id, user_id)
|
|
12
|
+
on update cascade on delete cascade;
|
|
13
|
+
`);
|
|
14
|
+
},
|
|
15
|
+
down: async (pool) => {
|
|
16
|
+
await pool.query(sql `
|
|
17
|
+
alter table organization_role_user_relations
|
|
18
|
+
-- The constraint name is strange because it's generated by Postgres and it has a 63 character limit
|
|
19
|
+
drop constraint organization_role_user_relati_tenant_id_organization_id_us_fkey;
|
|
20
|
+
alter table organization_role_user_relations
|
|
21
|
+
add foreign key (organization_id)
|
|
22
|
+
references organizations (id)
|
|
23
|
+
on update cascade on delete cascade;
|
|
24
|
+
alter table organization_role_user_relations
|
|
25
|
+
add foreign key (user_id)
|
|
26
|
+
references users (id)
|
|
27
|
+
on update cascade on delete cascade;
|
|
28
|
+
`);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export default alteration;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
alter table sso_connectors
|
|
6
|
+
add constraint sso_connectors__connector_name__unique
|
|
7
|
+
unique (tenant_id, connector_name);
|
|
8
|
+
`);
|
|
9
|
+
},
|
|
10
|
+
down: async (pool) => {
|
|
11
|
+
await pool.query(sql `
|
|
12
|
+
alter table sso_connectors
|
|
13
|
+
drop constraint sso_connectors__connector_name__unique;
|
|
14
|
+
`);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
export default alteration;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
alter table sign_in_experiences
|
|
6
|
+
add column single_sign_on_enabled boolean not null default false;
|
|
7
|
+
`);
|
|
8
|
+
},
|
|
9
|
+
down: async (pool) => {
|
|
10
|
+
await pool.query(sql `
|
|
11
|
+
alter table sign_in_experiences
|
|
12
|
+
drop column single_sign_on_enabled;
|
|
13
|
+
`);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export default alteration;
|
package/lib/consts/index.d.ts
CHANGED
package/lib/consts/index.js
CHANGED
|
@@ -21,6 +21,7 @@ export type CreateSignInExperience = {
|
|
|
21
21
|
customContent?: CustomContent;
|
|
22
22
|
passwordPolicy?: PartialPasswordPolicy;
|
|
23
23
|
mfa?: Mfa;
|
|
24
|
+
singleSignOnEnabled?: boolean;
|
|
24
25
|
};
|
|
25
26
|
export type SignInExperience = {
|
|
26
27
|
tenantId: string;
|
|
@@ -38,6 +39,7 @@ export type SignInExperience = {
|
|
|
38
39
|
customContent: CustomContent;
|
|
39
40
|
passwordPolicy: PartialPasswordPolicy;
|
|
40
41
|
mfa: Mfa;
|
|
42
|
+
singleSignOnEnabled: boolean;
|
|
41
43
|
};
|
|
42
|
-
export type SignInExperienceKeys = 'tenantId' | 'id' | 'color' | 'branding' | 'languageInfo' | 'termsOfUseUrl' | 'privacyPolicyUrl' | 'signIn' | 'signUp' | 'socialSignInConnectorTargets' | 'signInMode' | 'customCss' | 'customContent' | 'passwordPolicy' | 'mfa';
|
|
44
|
+
export type SignInExperienceKeys = 'tenantId' | 'id' | 'color' | 'branding' | 'languageInfo' | 'termsOfUseUrl' | 'privacyPolicyUrl' | 'signIn' | 'signUp' | 'socialSignInConnectorTargets' | 'signInMode' | 'customCss' | 'customContent' | 'passwordPolicy' | 'mfa' | 'singleSignOnEnabled';
|
|
43
45
|
export declare const SignInExperiences: GeneratedSchema<SignInExperienceKeys, CreateSignInExperience, SignInExperience, 'sign_in_experiences', 'sign_in_experience'>;
|
|
@@ -18,6 +18,7 @@ const createGuard = z.object({
|
|
|
18
18
|
customContent: customContentGuard.optional(),
|
|
19
19
|
passwordPolicy: partialPasswordPolicyGuard.optional(),
|
|
20
20
|
mfa: mfaGuard.optional(),
|
|
21
|
+
singleSignOnEnabled: z.boolean().optional(),
|
|
21
22
|
});
|
|
22
23
|
const guard = z.object({
|
|
23
24
|
tenantId: z.string().max(21),
|
|
@@ -35,6 +36,7 @@ const guard = z.object({
|
|
|
35
36
|
customContent: customContentGuard,
|
|
36
37
|
passwordPolicy: partialPasswordPolicyGuard,
|
|
37
38
|
mfa: mfaGuard,
|
|
39
|
+
singleSignOnEnabled: z.boolean(),
|
|
38
40
|
});
|
|
39
41
|
export const SignInExperiences = Object.freeze({
|
|
40
42
|
table: 'sign_in_experiences',
|
|
@@ -55,6 +57,7 @@ export const SignInExperiences = Object.freeze({
|
|
|
55
57
|
customContent: 'custom_content',
|
|
56
58
|
passwordPolicy: 'password_policy',
|
|
57
59
|
mfa: 'mfa',
|
|
60
|
+
singleSignOnEnabled: 'single_sign_on_enabled',
|
|
58
61
|
},
|
|
59
62
|
fieldKeys: [
|
|
60
63
|
'tenantId',
|
|
@@ -72,6 +75,7 @@ export const SignInExperiences = Object.freeze({
|
|
|
72
75
|
'customContent',
|
|
73
76
|
'passwordPolicy',
|
|
74
77
|
'mfa',
|
|
78
|
+
'singleSignOnEnabled',
|
|
75
79
|
],
|
|
76
80
|
createGuard,
|
|
77
81
|
guard,
|
|
@@ -8,7 +8,7 @@ export type CreateSsoConnector = {
|
|
|
8
8
|
tenantId?: string;
|
|
9
9
|
/** The globally unique identifier of the SSO connector. */
|
|
10
10
|
id: string;
|
|
11
|
-
/** The
|
|
11
|
+
/** The identifier of connector's SSO provider */
|
|
12
12
|
providerName: string;
|
|
13
13
|
/** The name of the SSO provider for display. */
|
|
14
14
|
connectorName: string;
|
|
@@ -27,7 +27,7 @@ export type SsoConnector = {
|
|
|
27
27
|
tenantId: string;
|
|
28
28
|
/** The globally unique identifier of the SSO connector. */
|
|
29
29
|
id: string;
|
|
30
|
-
/** The
|
|
30
|
+
/** The identifier of connector's SSO provider */
|
|
31
31
|
providerName: string;
|
|
32
32
|
/** The name of the SSO provider for display. */
|
|
33
33
|
connectorName: string;
|
|
@@ -2,12 +2,15 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const ssoDomainsGuard: z.ZodArray<z.ZodString, "many">;
|
|
3
3
|
export type SsoDomains = z.infer<typeof ssoDomainsGuard>;
|
|
4
4
|
export declare const ssoBrandingGuard: z.ZodObject<{
|
|
5
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
5
6
|
logo: z.ZodOptional<z.ZodString>;
|
|
6
7
|
darkLogo: z.ZodOptional<z.ZodString>;
|
|
7
8
|
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
displayName?: string | undefined;
|
|
8
10
|
logo?: string | undefined;
|
|
9
11
|
darkLogo?: string | undefined;
|
|
10
12
|
}, {
|
|
13
|
+
displayName?: string | undefined;
|
|
11
14
|
logo?: string | undefined;
|
|
12
15
|
darkLogo?: string | undefined;
|
|
13
16
|
}>;
|
package/lib/types/hook.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type HookEventPayload = {
|
|
|
9
9
|
sessionId?: string;
|
|
10
10
|
userAgent?: string;
|
|
11
11
|
userId?: string;
|
|
12
|
+
userIp?: string;
|
|
12
13
|
user?: Pick<User, (typeof userInfoSelectFields)[number]>;
|
|
13
14
|
application?: Pick<Application, 'id' | 'type' | 'name' | 'description'>;
|
|
14
15
|
} & Record<string, unknown>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { type SsoConnector } from '../db-entries/sso-connector.js';
|
|
2
3
|
/**
|
|
3
4
|
* SSO Connector data type that are returned to the experience client for sign-in use.
|
|
4
5
|
*/
|
|
@@ -19,71 +20,57 @@ export declare const ssoConnectorMetadataGuard: z.ZodObject<{
|
|
|
19
20
|
darkLogo?: string | undefined;
|
|
20
21
|
}>;
|
|
21
22
|
export type SsoConnectorMetadata = z.infer<typeof ssoConnectorMetadataGuard>;
|
|
22
|
-
declare
|
|
23
|
-
|
|
23
|
+
export declare enum SsoProviderName {
|
|
24
|
+
OIDC = "OIDC",
|
|
25
|
+
SAML = "SAML",
|
|
26
|
+
AZURE_AD = "AzureAD",
|
|
27
|
+
GOOGLE_WORKSPACE = "GoogleWorkspace",
|
|
28
|
+
OKTA = "Okta"
|
|
29
|
+
}
|
|
30
|
+
export declare const singleSignOnDomainBlackList: readonly string[];
|
|
31
|
+
export type SupportedSsoConnector = Omit<SsoConnector, 'providerName'> & {
|
|
32
|
+
providerName: SsoProviderName;
|
|
33
|
+
};
|
|
34
|
+
declare const ssoConnectorProviderDetailGuard: z.ZodObject<{
|
|
35
|
+
providerName: z.ZodNativeEnum<typeof SsoProviderName>;
|
|
24
36
|
logo: z.ZodString;
|
|
37
|
+
logoDark: z.ZodString;
|
|
25
38
|
description: z.ZodString;
|
|
39
|
+
name: z.ZodString;
|
|
26
40
|
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
name: string;
|
|
27
42
|
logo: string;
|
|
28
43
|
description: string;
|
|
29
|
-
|
|
44
|
+
logoDark: string;
|
|
45
|
+
providerName: SsoProviderName;
|
|
30
46
|
}, {
|
|
47
|
+
name: string;
|
|
31
48
|
logo: string;
|
|
32
49
|
description: string;
|
|
33
|
-
|
|
50
|
+
logoDark: string;
|
|
51
|
+
providerName: SsoProviderName;
|
|
34
52
|
}>;
|
|
35
|
-
export type
|
|
36
|
-
export declare const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
logo: string;
|
|
43
|
-
description: string;
|
|
44
|
-
providerName: string;
|
|
45
|
-
}, {
|
|
46
|
-
logo: string;
|
|
47
|
-
description: string;
|
|
48
|
-
providerName: string;
|
|
49
|
-
}>, "many">;
|
|
50
|
-
providerConnectors: z.ZodArray<z.ZodObject<{
|
|
51
|
-
providerName: z.ZodString;
|
|
52
|
-
logo: z.ZodString;
|
|
53
|
-
description: z.ZodString;
|
|
54
|
-
}, "strip", z.ZodTypeAny, {
|
|
55
|
-
logo: string;
|
|
56
|
-
description: string;
|
|
57
|
-
providerName: string;
|
|
58
|
-
}, {
|
|
59
|
-
logo: string;
|
|
60
|
-
description: string;
|
|
61
|
-
providerName: string;
|
|
62
|
-
}>, "many">;
|
|
53
|
+
export type SsoConnectorProviderDetail = z.infer<typeof ssoConnectorProviderDetailGuard>;
|
|
54
|
+
export declare const ssoConnectorProvidersResponseGuard: z.ZodArray<z.ZodObject<{
|
|
55
|
+
providerName: z.ZodNativeEnum<typeof SsoProviderName>;
|
|
56
|
+
logo: z.ZodString;
|
|
57
|
+
logoDark: z.ZodString;
|
|
58
|
+
description: z.ZodString;
|
|
59
|
+
name: z.ZodString;
|
|
63
60
|
}, "strip", z.ZodTypeAny, {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
providerConnectors: {
|
|
70
|
-
logo: string;
|
|
71
|
-
description: string;
|
|
72
|
-
providerName: string;
|
|
73
|
-
}[];
|
|
61
|
+
name: string;
|
|
62
|
+
logo: string;
|
|
63
|
+
description: string;
|
|
64
|
+
logoDark: string;
|
|
65
|
+
providerName: SsoProviderName;
|
|
74
66
|
}, {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
description: string;
|
|
83
|
-
providerName: string;
|
|
84
|
-
}[];
|
|
85
|
-
}>;
|
|
86
|
-
export type SsoConnectorFactoriesResponse = z.infer<typeof ssoConnectorFactoriesResponseGuard>;
|
|
67
|
+
name: string;
|
|
68
|
+
logo: string;
|
|
69
|
+
description: string;
|
|
70
|
+
logoDark: string;
|
|
71
|
+
providerName: SsoProviderName;
|
|
72
|
+
}>, "many">;
|
|
73
|
+
export type SsoConnectorProvidersResponse = z.infer<typeof ssoConnectorProvidersResponseGuard>;
|
|
87
74
|
export declare const ssoConnectorWithProviderConfigGuard: z.ZodObject<{
|
|
88
75
|
id: z.ZodType<string, z.ZodTypeDef, string>;
|
|
89
76
|
tenantId: z.ZodType<string, z.ZodTypeDef, string>;
|
|
@@ -92,17 +79,22 @@ export declare const ssoConnectorWithProviderConfigGuard: z.ZodObject<{
|
|
|
92
79
|
config: z.ZodType<import("@withtyped/server").JsonObject, z.ZodTypeDef, import("@withtyped/server").JsonObject>;
|
|
93
80
|
domains: z.ZodType<string[], z.ZodTypeDef, string[]>;
|
|
94
81
|
branding: z.ZodType<{
|
|
82
|
+
displayName?: string | undefined;
|
|
95
83
|
logo?: string | undefined;
|
|
96
84
|
darkLogo?: string | undefined;
|
|
97
85
|
}, z.ZodTypeDef, {
|
|
86
|
+
displayName?: string | undefined;
|
|
98
87
|
logo?: string | undefined;
|
|
99
88
|
darkLogo?: string | undefined;
|
|
100
89
|
}>;
|
|
101
|
-
providerName: z.ZodType<string, z.ZodTypeDef, string>;
|
|
102
90
|
connectorName: z.ZodType<string, z.ZodTypeDef, string>;
|
|
91
|
+
name: z.ZodString;
|
|
92
|
+
providerName: z.ZodNativeEnum<typeof SsoProviderName>;
|
|
103
93
|
providerLogo: z.ZodString;
|
|
94
|
+
providerLogoDark: z.ZodString;
|
|
104
95
|
providerConfig: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
105
96
|
}, "strip", z.ZodTypeAny, {
|
|
97
|
+
name: string;
|
|
106
98
|
id: string;
|
|
107
99
|
tenantId: string;
|
|
108
100
|
createdAt: number;
|
|
@@ -110,14 +102,17 @@ export declare const ssoConnectorWithProviderConfigGuard: z.ZodObject<{
|
|
|
110
102
|
config: import("@withtyped/server").JsonObject;
|
|
111
103
|
domains: string[];
|
|
112
104
|
branding: {
|
|
105
|
+
displayName?: string | undefined;
|
|
113
106
|
logo?: string | undefined;
|
|
114
107
|
darkLogo?: string | undefined;
|
|
115
108
|
};
|
|
116
|
-
providerName:
|
|
109
|
+
providerName: SsoProviderName;
|
|
117
110
|
connectorName: string;
|
|
118
111
|
providerLogo: string;
|
|
112
|
+
providerLogoDark: string;
|
|
119
113
|
providerConfig?: Record<string, unknown> | undefined;
|
|
120
114
|
}, {
|
|
115
|
+
name: string;
|
|
121
116
|
id: string;
|
|
122
117
|
tenantId: string;
|
|
123
118
|
createdAt: number;
|
|
@@ -125,12 +120,14 @@ export declare const ssoConnectorWithProviderConfigGuard: z.ZodObject<{
|
|
|
125
120
|
config: import("@withtyped/server").JsonObject;
|
|
126
121
|
domains: string[];
|
|
127
122
|
branding: {
|
|
123
|
+
displayName?: string | undefined;
|
|
128
124
|
logo?: string | undefined;
|
|
129
125
|
darkLogo?: string | undefined;
|
|
130
126
|
};
|
|
131
|
-
providerName:
|
|
127
|
+
providerName: SsoProviderName;
|
|
132
128
|
connectorName: string;
|
|
133
129
|
providerLogo: string;
|
|
130
|
+
providerLogoDark: string;
|
|
134
131
|
providerConfig?: Record<string, unknown> | undefined;
|
|
135
132
|
}>;
|
|
136
133
|
export type SsoConnectorWithProviderConfig = z.infer<typeof ssoConnectorWithProviderConfigGuard>;
|
|
@@ -9,16 +9,50 @@ export const ssoConnectorMetadataGuard = z.object({
|
|
|
9
9
|
logo: z.string(),
|
|
10
10
|
darkLogo: z.string().optional(),
|
|
11
11
|
});
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
export var SsoProviderName;
|
|
13
|
+
(function (SsoProviderName) {
|
|
14
|
+
SsoProviderName["OIDC"] = "OIDC";
|
|
15
|
+
SsoProviderName["SAML"] = "SAML";
|
|
16
|
+
SsoProviderName["AZURE_AD"] = "AzureAD";
|
|
17
|
+
SsoProviderName["GOOGLE_WORKSPACE"] = "GoogleWorkspace";
|
|
18
|
+
SsoProviderName["OKTA"] = "Okta";
|
|
19
|
+
})(SsoProviderName || (SsoProviderName = {}));
|
|
20
|
+
export const singleSignOnDomainBlackList = Object.freeze([
|
|
21
|
+
'gmail.com',
|
|
22
|
+
'yahoo.com',
|
|
23
|
+
'hotmail.com',
|
|
24
|
+
'outlook.com',
|
|
25
|
+
'live.com',
|
|
26
|
+
'icloud.com',
|
|
27
|
+
'aol.com',
|
|
28
|
+
'yandex.com',
|
|
29
|
+
'mail.com',
|
|
30
|
+
'protonmail.com',
|
|
31
|
+
'yanex.com',
|
|
32
|
+
'gmx.com',
|
|
33
|
+
'mail.ru',
|
|
34
|
+
'zoho.com',
|
|
35
|
+
'qq.com',
|
|
36
|
+
'163.com',
|
|
37
|
+
'126.com',
|
|
38
|
+
'sina.com',
|
|
39
|
+
'sohu.com',
|
|
40
|
+
]);
|
|
41
|
+
const ssoConnectorProviderDetailGuard = z.object({
|
|
42
|
+
providerName: z.nativeEnum(SsoProviderName),
|
|
14
43
|
logo: z.string(),
|
|
44
|
+
logoDark: z.string(),
|
|
15
45
|
description: z.string(),
|
|
46
|
+
name: z.string(),
|
|
16
47
|
});
|
|
17
|
-
export const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
})
|
|
21
|
-
|
|
48
|
+
export const ssoConnectorProvidersResponseGuard = z.array(ssoConnectorProviderDetailGuard);
|
|
49
|
+
// API response guard for all the SSO connectors CRUD APIs
|
|
50
|
+
export const ssoConnectorWithProviderConfigGuard = SsoConnectors.guard
|
|
51
|
+
.omit({ providerName: true })
|
|
52
|
+
.merge(z.object({
|
|
53
|
+
name: z.string(),
|
|
54
|
+
providerName: z.nativeEnum(SsoProviderName),
|
|
22
55
|
providerLogo: z.string(),
|
|
56
|
+
providerLogoDark: z.string(),
|
|
23
57
|
providerConfig: z.record(z.unknown()).optional(),
|
|
24
58
|
}));
|
package/lib/types/tenant.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export var TenantTag;
|
|
2
2
|
(function (TenantTag) {
|
|
3
|
+
/* Development tenants are free to use but are not meant to be used as production environment */
|
|
3
4
|
TenantTag["Development"] = "development";
|
|
5
|
+
/* @deprecated */
|
|
4
6
|
TenantTag["Staging"] = "staging";
|
|
7
|
+
/* A production tenant must have an associated subscription plan, even if it's a free plan */
|
|
5
8
|
TenantTag["Production"] = "production";
|
|
6
9
|
})(TenantTag || (TenantTag = {}));
|
package/lib/types/user.d.ts
CHANGED
|
@@ -245,6 +245,7 @@ export declare const userProfileResponseGuard: z.ZodObject<{
|
|
|
245
245
|
isSuspended: z.ZodType<boolean, z.ZodTypeDef, boolean>;
|
|
246
246
|
lastSignInAt: z.ZodType<number | null, z.ZodTypeDef, number | null>;
|
|
247
247
|
hasPassword: z.ZodOptional<z.ZodBoolean>;
|
|
248
|
+
ssoIdentities: z.ZodOptional<z.ZodArray<import("../foundations/schemas.js").Guard<import("../db-entries/user-sso-identity.js").UserSsoIdentity>, "many">>;
|
|
248
249
|
}, "strip", z.ZodTypeAny, {
|
|
249
250
|
name: string | null;
|
|
250
251
|
id: string;
|
|
@@ -292,6 +293,7 @@ export declare const userProfileResponseGuard: z.ZodObject<{
|
|
|
292
293
|
isSuspended: boolean;
|
|
293
294
|
lastSignInAt: number | null;
|
|
294
295
|
hasPassword?: boolean | undefined;
|
|
296
|
+
ssoIdentities?: import("../db-entries/user-sso-identity.js").UserSsoIdentity[] | undefined;
|
|
295
297
|
}, {
|
|
296
298
|
name: string | null;
|
|
297
299
|
id: string;
|
|
@@ -339,6 +341,7 @@ export declare const userProfileResponseGuard: z.ZodObject<{
|
|
|
339
341
|
isSuspended: boolean;
|
|
340
342
|
lastSignInAt: number | null;
|
|
341
343
|
hasPassword?: boolean | undefined;
|
|
344
|
+
ssoIdentities?: import("../db-entries/user-sso-identity.js").UserSsoIdentity[] | undefined;
|
|
342
345
|
}>;
|
|
343
346
|
export type UserProfileResponse = z.infer<typeof userProfileResponseGuard>;
|
|
344
347
|
export declare const userMfaVerificationResponseGuard: z.ZodArray<z.ZodObject<{
|
package/lib/types/user.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { Users } from '../db-entries/index.js';
|
|
2
|
+
import { Users, UserSsoIdentities } from '../db-entries/index.js';
|
|
3
3
|
import { MfaFactor } from '../foundations/index.js';
|
|
4
4
|
export const userInfoSelectFields = Object.freeze([
|
|
5
5
|
'id',
|
|
@@ -18,6 +18,7 @@ export const userInfoSelectFields = Object.freeze([
|
|
|
18
18
|
export const userInfoGuard = Users.guard.pick(Object.fromEntries(userInfoSelectFields.map((key) => [key, true])));
|
|
19
19
|
export const userProfileResponseGuard = userInfoGuard.extend({
|
|
20
20
|
hasPassword: z.boolean().optional(),
|
|
21
|
+
ssoIdentities: z.array(UserSsoIdentities.guard).optional(),
|
|
21
22
|
});
|
|
22
23
|
export const userMfaVerificationResponseGuard = z
|
|
23
24
|
.object({
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find duplicated domains and blocked domains using the domain blacklist.
|
|
3
|
+
*
|
|
4
|
+
* @param domains Array of email domains.
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export declare const findDuplicatedOrBlockedEmailDomains: (domains?: string[]) => {
|
|
8
|
+
duplicatedDomains: Set<string>;
|
|
9
|
+
forbiddenDomains: Set<string>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { singleSignOnDomainBlackList } from '../types/sso-connector.js';
|
|
2
|
+
/**
|
|
3
|
+
* Find duplicated domains and blocked domains using the domain blacklist.
|
|
4
|
+
*
|
|
5
|
+
* @param domains Array of email domains.
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export const findDuplicatedOrBlockedEmailDomains = (domains) => {
|
|
9
|
+
const blackListSet = new Set(singleSignOnDomainBlackList);
|
|
10
|
+
const validDomainSet = new Set();
|
|
11
|
+
const duplicatedDomains = new Set();
|
|
12
|
+
const forbiddenDomains = new Set();
|
|
13
|
+
for (const domain of domains ?? []) {
|
|
14
|
+
if (blackListSet.has(domain)) {
|
|
15
|
+
forbiddenDomains.add(domain);
|
|
16
|
+
}
|
|
17
|
+
if (validDomainSet.has(domain)) {
|
|
18
|
+
duplicatedDomains.add(domain);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
validDomainSet.add(domain);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
duplicatedDomains,
|
|
26
|
+
forbiddenDomains,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { findDuplicatedOrBlockedEmailDomains } from './domain.js';
|
|
2
|
+
describe('findDuplicatedOrBlockedEmailDomains', () => {
|
|
3
|
+
it('should return blocked domains and duplicated domains correctly', () => {
|
|
4
|
+
const { duplicatedDomains, forbiddenDomains } = findDuplicatedOrBlockedEmailDomains([
|
|
5
|
+
'gmail.com',
|
|
6
|
+
'silverhand.io',
|
|
7
|
+
'logto.io',
|
|
8
|
+
'yahoo.com',
|
|
9
|
+
'outlook.com',
|
|
10
|
+
'logto.io',
|
|
11
|
+
]);
|
|
12
|
+
expect(duplicatedDomains).toEqual(new Set(['logto.io']));
|
|
13
|
+
expect(forbiddenDomains).toEqual(new Set(['gmail.com', 'yahoo.com', 'outlook.com']));
|
|
14
|
+
});
|
|
15
|
+
it('should return empty `duplicatedDomains` and `forbiddenDomains` sets if all domains are valid', () => {
|
|
16
|
+
const { duplicatedDomains, forbiddenDomains } = findDuplicatedOrBlockedEmailDomains([
|
|
17
|
+
'silverhand.io',
|
|
18
|
+
'logto.io',
|
|
19
|
+
'metalhand.io',
|
|
20
|
+
]);
|
|
21
|
+
expect(duplicatedDomains).toEqual(new Set());
|
|
22
|
+
expect(forbiddenDomains).toEqual(new Set());
|
|
23
|
+
});
|
|
24
|
+
it('should return empty `duplicatedDomains` and `forbiddenDomains` sets if input is undefined', () => {
|
|
25
|
+
const { duplicatedDomains, forbiddenDomains } = findDuplicatedOrBlockedEmailDomains();
|
|
26
|
+
expect(duplicatedDomains).toEqual(new Set());
|
|
27
|
+
expect(forbiddenDomains).toEqual(new Set());
|
|
28
|
+
});
|
|
29
|
+
it('should return empty `duplicatedDomains` and `forbiddenDomains` sets if input is empty array', () => {
|
|
30
|
+
const { duplicatedDomains, forbiddenDomains } = findDuplicatedOrBlockedEmailDomains([]);
|
|
31
|
+
expect(duplicatedDomains).toEqual(new Set());
|
|
32
|
+
expect(forbiddenDomains).toEqual(new Set());
|
|
33
|
+
});
|
|
34
|
+
});
|
package/lib/utils/index.d.ts
CHANGED
package/lib/utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/schemas",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@types/inquirer": "^9.0.0",
|
|
31
31
|
"@types/jest": "^29.4.0",
|
|
32
32
|
"@types/node": "^18.11.18",
|
|
33
|
-
"@types/pluralize": "^0.0.
|
|
33
|
+
"@types/pluralize": "^0.0.33",
|
|
34
34
|
"camelcase": "^8.0.0",
|
|
35
35
|
"chalk": "^5.0.0",
|
|
36
36
|
"eslint": "^8.44.0",
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"prettier": "@silverhand/eslint-config/.prettierrc",
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@logto/connector-kit": "^2.0.0",
|
|
68
|
-
"@logto/core-kit": "^2.2.
|
|
68
|
+
"@logto/core-kit": "^2.2.1",
|
|
69
69
|
"@logto/language-kit": "^1.0.0",
|
|
70
|
-
"@logto/phrases": "^1.
|
|
71
|
-
"@logto/phrases-experience": "^1.
|
|
70
|
+
"@logto/phrases": "^1.8.0",
|
|
71
|
+
"@logto/phrases-experience": "^1.5.0",
|
|
72
72
|
"@logto/shared": "^3.0.0",
|
|
73
73
|
"@withtyped/server": "^0.12.9"
|
|
74
74
|
},
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
/* init_order =
|
|
1
|
+
/* init_order = 3 */
|
|
2
2
|
|
|
3
3
|
/** The relations between organizations, organization roles, and users. A relation means that a user has a role in an organization. */
|
|
4
4
|
create table organization_role_user_relations (
|
|
5
5
|
tenant_id varchar(21) not null
|
|
6
6
|
references tenants (id) on update cascade on delete cascade,
|
|
7
|
-
organization_id varchar(21) not null
|
|
8
|
-
references organizations (id) on update cascade on delete cascade,
|
|
7
|
+
organization_id varchar(21) not null,
|
|
9
8
|
organization_role_id varchar(21) not null
|
|
10
9
|
references organization_roles (id) on update cascade on delete cascade,
|
|
11
|
-
user_id varchar(21) not null
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
user_id varchar(21) not null,
|
|
11
|
+
primary key (tenant_id, organization_id, organization_role_id, user_id),
|
|
12
|
+
/** User's roles in an organization should be synchronized with the user's membership in the organization. */
|
|
13
|
+
foreign key (tenant_id, organization_id, user_id)
|
|
14
|
+
references organization_user_relations (tenant_id, organization_id, user_id)
|
|
15
|
+
on update cascade on delete cascade
|
|
14
16
|
);
|
|
@@ -17,5 +17,6 @@ create table sign_in_experiences (
|
|
|
17
17
|
custom_content jsonb /* @use CustomContent */ not null default '{}'::jsonb,
|
|
18
18
|
password_policy jsonb /* @use PartialPasswordPolicy */ not null default '{}'::jsonb,
|
|
19
19
|
mfa jsonb /* @use Mfa */ not null default '{}'::jsonb,
|
|
20
|
+
single_sign_on_enabled boolean not null default false,
|
|
20
21
|
primary key (tenant_id, id)
|
|
21
22
|
);
|
|
@@ -4,7 +4,7 @@ create table sso_connectors (
|
|
|
4
4
|
references tenants (id) on update cascade on delete cascade,
|
|
5
5
|
/** The globally unique identifier of the SSO connector. */
|
|
6
6
|
id varchar(128) not null,
|
|
7
|
-
/** The
|
|
7
|
+
/** The identifier of connector's SSO provider */
|
|
8
8
|
provider_name varchar(128) not null,
|
|
9
9
|
/** The name of the SSO provider for display. */
|
|
10
10
|
connector_name varchar(128) not null,
|
|
@@ -18,7 +18,9 @@ create table sso_connectors (
|
|
|
18
18
|
sync_profile boolean not null default FALSE,
|
|
19
19
|
/** When the SSO connector was created. */
|
|
20
20
|
created_at timestamptz not null default(now()),
|
|
21
|
-
primary key (id)
|
|
21
|
+
primary key (id),
|
|
22
|
+
constraint sso_connectors__connector_name__unique
|
|
23
|
+
unique (tenant_id, connector_name)
|
|
22
24
|
);
|
|
23
25
|
|
|
24
26
|
create index sso_connectors__id
|