@logto/schemas 1.23.0 → 1.24.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.
@@ -0,0 +1,35 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ enum NameIdFormat {
6
+ /** Uses unique and persistent identifiers for the user. */
7
+ Persistent = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
8
+ }
9
+
10
+ const alteration: AlterationScript = {
11
+ up: async (pool) => {
12
+ await pool.query(sql`
13
+ alter table saml_application_configs
14
+ add column encryption jsonb,
15
+ add column name_id_format varchar(128);
16
+ `);
17
+ await pool.query(sql`
18
+ update saml_application_configs
19
+ set name_id_format = ${NameIdFormat.Persistent};
20
+ `);
21
+ await pool.query(sql`
22
+ alter table saml_application_configs
23
+ alter column name_id_format set not null;
24
+ `);
25
+ },
26
+ down: async (pool) => {
27
+ await pool.query(sql`
28
+ alter table saml_application_configs
29
+ drop column encryption,
30
+ drop column name_id_format;
31
+ `);
32
+ },
33
+ };
34
+
35
+ export default alteration;
@@ -0,0 +1,28 @@
1
+ import { sql } from '@silverhand/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 applications drop constraint check_saml_app_third_party_consistency;
9
+ `);
10
+ await pool.query(sql`
11
+ update applications set is_third_party = false
12
+ where type = 'SAML';
13
+ `);
14
+ },
15
+ down: async (pool) => {
16
+ await pool.query(sql`
17
+ update applications set is_third_party = true
18
+ where type = 'SAML';
19
+ `);
20
+ await pool.query(sql`
21
+ alter table applications
22
+ add constraint check_saml_app_third_party_consistency
23
+ check (type != 'SAML' OR (type = 'SAML' AND is_third_party = true));
24
+ `);
25
+ },
26
+ };
27
+
28
+ export default alteration;
@@ -0,0 +1,31 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ var NameIdFormat;
3
+ (function (NameIdFormat) {
4
+ /** Uses unique and persistent identifiers for the user. */
5
+ NameIdFormat["Persistent"] = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent";
6
+ })(NameIdFormat || (NameIdFormat = {}));
7
+ const alteration = {
8
+ up: async (pool) => {
9
+ await pool.query(sql `
10
+ alter table saml_application_configs
11
+ add column encryption jsonb,
12
+ add column name_id_format varchar(128);
13
+ `);
14
+ await pool.query(sql `
15
+ update saml_application_configs
16
+ set name_id_format = ${NameIdFormat.Persistent};
17
+ `);
18
+ await pool.query(sql `
19
+ alter table saml_application_configs
20
+ alter column name_id_format set not null;
21
+ `);
22
+ },
23
+ down: async (pool) => {
24
+ await pool.query(sql `
25
+ alter table saml_application_configs
26
+ drop column encryption,
27
+ drop column name_id_format;
28
+ `);
29
+ },
30
+ };
31
+ export default alteration;
@@ -0,0 +1,24 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table applications drop constraint check_saml_app_third_party_consistency;
6
+ `);
7
+ await pool.query(sql `
8
+ update applications set is_third_party = false
9
+ where type = 'SAML';
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql `
14
+ update applications set is_third_party = true
15
+ where type = 'SAML';
16
+ `);
17
+ await pool.query(sql `
18
+ alter table applications
19
+ add constraint check_saml_app_third_party_consistency
20
+ check (type != 'SAML' OR (type = 'SAML' AND is_third_party = true));
21
+ `);
22
+ },
23
+ };
24
+ export default alteration;
@@ -1,4 +1,4 @@
1
- import { SamlAttributeMapping, SamlAcsUrl, GeneratedSchema } from './../foundations/index.js';
1
+ import { SamlAttributeMapping, SamlAcsUrl, SamlEncryption, NameIdFormat, GeneratedSchema } from './../foundations/index.js';
2
2
  /**
3
3
  * The SAML application config and SAML-type application have a one-to-one correspondence: 1. a SAML-type application can only have one SAML application config. (CANNOT use "semicolon" in comments, since it indicates the end of query.) 2. a SAML application config can only configure one SAML-type application.
4
4
  *
@@ -11,6 +11,8 @@ export type CreateSamlApplicationConfig = {
11
11
  attributeMapping?: SamlAttributeMapping;
12
12
  entityId?: string | null;
13
13
  acsUrl?: SamlAcsUrl | null;
14
+ encryption?: SamlEncryption | null;
15
+ nameIdFormat: NameIdFormat;
14
16
  };
15
17
  /** The SAML application config and SAML-type application have a one-to-one correspondence: 1. a SAML-type application can only have one SAML application config. (CANNOT use "semicolon" in comments, since it indicates the end of query.) 2. a SAML application config can only configure one SAML-type application. */
16
18
  export type SamlApplicationConfig = {
@@ -19,6 +21,8 @@ export type SamlApplicationConfig = {
19
21
  attributeMapping: SamlAttributeMapping;
20
22
  entityId: string | null;
21
23
  acsUrl: SamlAcsUrl | null;
24
+ encryption: SamlEncryption | null;
25
+ nameIdFormat: NameIdFormat;
22
26
  };
23
- export type SamlApplicationConfigKeys = 'applicationId' | 'tenantId' | 'attributeMapping' | 'entityId' | 'acsUrl';
27
+ export type SamlApplicationConfigKeys = 'applicationId' | 'tenantId' | 'attributeMapping' | 'entityId' | 'acsUrl' | 'encryption' | 'nameIdFormat';
24
28
  export declare const SamlApplicationConfigs: GeneratedSchema<SamlApplicationConfigKeys, CreateSamlApplicationConfig, SamlApplicationConfig, 'saml_application_configs', 'saml_application_config'>;
@@ -1,12 +1,14 @@
1
1
  // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
2
  import { z } from 'zod';
3
- import { samlAttributeMappingGuard, samlAcsUrlGuard } from './../foundations/index.js';
3
+ import { samlAttributeMappingGuard, samlAcsUrlGuard, samlEncryptionGuard, nameIdFormatGuard } from './../foundations/index.js';
4
4
  const createGuard = z.object({
5
5
  applicationId: z.string().min(1).max(21),
6
6
  tenantId: z.string().max(21).optional(),
7
7
  attributeMapping: samlAttributeMappingGuard.optional(),
8
8
  entityId: z.string().max(128).nullable().optional(),
9
9
  acsUrl: samlAcsUrlGuard.nullable().optional(),
10
+ encryption: samlEncryptionGuard.nullable().optional(),
11
+ nameIdFormat: nameIdFormatGuard,
10
12
  });
11
13
  const guard = z.object({
12
14
  applicationId: z.string().min(1).max(21),
@@ -14,6 +16,8 @@ const guard = z.object({
14
16
  attributeMapping: samlAttributeMappingGuard,
15
17
  entityId: z.string().max(128).nullable(),
16
18
  acsUrl: samlAcsUrlGuard.nullable(),
19
+ encryption: samlEncryptionGuard.nullable(),
20
+ nameIdFormat: nameIdFormatGuard,
17
21
  });
18
22
  export const SamlApplicationConfigs = Object.freeze({
19
23
  table: 'saml_application_configs',
@@ -24,6 +28,8 @@ export const SamlApplicationConfigs = Object.freeze({
24
28
  attributeMapping: 'attribute_mapping',
25
29
  entityId: 'entity_id',
26
30
  acsUrl: 'acs_url',
31
+ encryption: 'encryption',
32
+ nameIdFormat: 'name_id_format',
27
33
  },
28
34
  fieldKeys: [
29
35
  'applicationId',
@@ -31,6 +37,8 @@ export const SamlApplicationConfigs = Object.freeze({
31
37
  'attributeMapping',
32
38
  'entityId',
33
39
  'acsUrl',
40
+ 'encryption',
41
+ 'nameIdFormat',
34
42
  ],
35
43
  createGuard,
36
44
  guard,
@@ -1,6 +1,14 @@
1
+ import { type UserClaim } from '@logto/core-kit';
1
2
  import { z } from 'zod';
2
- export type SamlAttributeMapping = Record<string, string>;
3
- export declare const samlAttributeMappingGuard: z.ZodRecord<z.ZodString, z.ZodString>;
3
+ export type SamlAttributeMapping = Partial<Record<UserClaim | 'sub', string>>;
4
+ export declare const samlAttributeMappingKeys: readonly ("name" | "username" | "email" | "nickname" | "profile" | "website" | "gender" | "birthdate" | "zoneinfo" | "locale" | "address" | "given_name" | "family_name" | "middle_name" | "preferred_username" | "picture" | "email_verified" | "phone_number" | "phone_number_verified" | "updated_at" | "roles" | "organizations" | "organization_data" | "organization_roles" | "custom_data" | "identities" | "sso_identities" | "created_at" | "sub")[];
5
+ export declare const samlAttributeMappingGuard: z.ZodObject<{
6
+ [x: string]: z.ZodOptional<z.ZodString>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ [x: string]: string | undefined;
9
+ }, {
10
+ [x: string]: string | undefined;
11
+ }>;
4
12
  export declare enum BindingType {
5
13
  Post = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
6
14
  Redirect = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
@@ -19,3 +27,36 @@ export declare const samlAcsUrlGuard: z.ZodObject<{
19
27
  url: string;
20
28
  binding: BindingType;
21
29
  }>;
30
+ export declare const samlEncryptionGuard: z.ZodEffects<z.ZodObject<{
31
+ encryptAssertion: z.ZodOptional<z.ZodBoolean>;
32
+ encryptThenSign: z.ZodOptional<z.ZodBoolean>;
33
+ certificate: z.ZodOptional<z.ZodString>;
34
+ }, "strip", z.ZodTypeAny, {
35
+ encryptAssertion?: boolean | undefined;
36
+ encryptThenSign?: boolean | undefined;
37
+ certificate?: string | undefined;
38
+ }, {
39
+ encryptAssertion?: boolean | undefined;
40
+ encryptThenSign?: boolean | undefined;
41
+ certificate?: string | undefined;
42
+ }>, {
43
+ encryptAssertion?: boolean | undefined;
44
+ encryptThenSign?: boolean | undefined;
45
+ certificate?: string | undefined;
46
+ }, {
47
+ encryptAssertion?: boolean | undefined;
48
+ encryptThenSign?: boolean | undefined;
49
+ certificate?: string | undefined;
50
+ }>;
51
+ export type SamlEncryption = z.input<typeof samlEncryptionGuard>;
52
+ export declare enum NameIdFormat {
53
+ /** Uses unique and persistent identifiers for the user. */
54
+ Persistent = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
55
+ /** Returns the email address of the user. */
56
+ EmailAddress = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
57
+ /** Uses unique and transient identifiers for the user, which can be different for each session. */
58
+ Transient = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
59
+ /** The Identity Provider can determine the format. */
60
+ Unspecified = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
61
+ }
62
+ export declare const nameIdFormatGuard: z.ZodNativeEnum<typeof NameIdFormat>;
@@ -1,5 +1,9 @@
1
+ import { userClaimsList } from '@logto/core-kit';
1
2
  import { z } from 'zod';
2
- export const samlAttributeMappingGuard = z.record(z.string());
3
+ export const samlAttributeMappingKeys = Object.freeze(['sub', ...userClaimsList]);
4
+ export const samlAttributeMappingGuard = z
5
+ .object(Object.fromEntries(samlAttributeMappingKeys.map((claim) => [claim, z.string()])))
6
+ .partial();
3
7
  export var BindingType;
4
8
  (function (BindingType) {
5
9
  BindingType["Post"] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
@@ -9,3 +13,30 @@ export const samlAcsUrlGuard = z.object({
9
13
  binding: z.nativeEnum(BindingType),
10
14
  url: z.string().url(),
11
15
  });
16
+ export const samlEncryptionGuard = z
17
+ .object({
18
+ encryptAssertion: z.boolean().optional(),
19
+ encryptThenSign: z.boolean().optional(),
20
+ certificate: z.string().optional(),
21
+ })
22
+ .superRefine(({ encryptAssertion, encryptThenSign, certificate }, ctx) => {
23
+ if (encryptAssertion && (encryptThenSign === undefined || certificate === undefined)) {
24
+ ctx.addIssue({
25
+ code: z.ZodIssueCode.custom,
26
+ message: '`encryptThenSign` and `certificate` are required when `encryptAssertion` is `true`',
27
+ });
28
+ return z.NEVER;
29
+ }
30
+ });
31
+ export var NameIdFormat;
32
+ (function (NameIdFormat) {
33
+ /** Uses unique and persistent identifiers for the user. */
34
+ NameIdFormat["Persistent"] = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent";
35
+ /** Returns the email address of the user. */
36
+ NameIdFormat["EmailAddress"] = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
37
+ /** Uses unique and transient identifiers for the user, which can be different for each session. */
38
+ NameIdFormat["Transient"] = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
39
+ /** The Identity Provider can determine the format. */
40
+ NameIdFormat["Unspecified"] = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified";
41
+ })(NameIdFormat || (NameIdFormat = {}));
42
+ export const nameIdFormatGuard = z.nativeEnum(NameIdFormat);
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { samlEncryptionGuard } from './saml-application-configs.js';
3
+ describe('samlEncryptionGuard', () => {
4
+ // Test valid configurations
5
+ it('should pass when encryption is disabled', () => {
6
+ const result = samlEncryptionGuard.safeParse({
7
+ encryptAssertion: false,
8
+ });
9
+ expect(result.success).toBe(true);
10
+ });
11
+ it('should pass when encryption is enabled with all required fields', () => {
12
+ const result = samlEncryptionGuard.safeParse({
13
+ encryptAssertion: true,
14
+ encryptThenSign: true,
15
+ certificate: '-----BEGIN CERTIFICATE-----\nMIICYDCCAcmgAwIBA...',
16
+ });
17
+ expect(result.success).toBe(true);
18
+ });
19
+ // Test invalid configurations
20
+ it('should fail when encryptAssertion is true but missing encryptThenSign', () => {
21
+ const result = samlEncryptionGuard.safeParse({
22
+ encryptAssertion: true,
23
+ certificate: '-----BEGIN CERTIFICATE-----\nMIICYDCCAcmgAwIBA...',
24
+ });
25
+ expect(result.success).toBe(false);
26
+ if (!result.success) {
27
+ expect(result.error.issues[0]?.message).toBe('`encryptThenSign` and `certificate` are required when `encryptAssertion` is `true`');
28
+ }
29
+ });
30
+ it('should fail when encryptAssertion is true but missing certificate', () => {
31
+ const result = samlEncryptionGuard.safeParse({
32
+ encryptAssertion: true,
33
+ encryptThenSign: true,
34
+ });
35
+ expect(result.success).toBe(false);
36
+ if (!result.success) {
37
+ expect(result.error.issues[0]?.message).toBe('`encryptThenSign` and `certificate` are required when `encryptAssertion` is `true`');
38
+ }
39
+ });
40
+ it('should fail when encryptAssertion is true but missing both encryptThenSign and certificate', () => {
41
+ const result = samlEncryptionGuard.safeParse({
42
+ encryptAssertion: true,
43
+ });
44
+ expect(result.success).toBe(false);
45
+ if (!result.success) {
46
+ expect(result.error.issues[0]?.message).toBe('`encryptThenSign` and `certificate` are required when `encryptAssertion` is `true`');
47
+ }
48
+ });
49
+ });
@@ -1,21 +1,25 @@
1
1
  import type * as hook from './hook.js';
2
2
  import type * as interaction from './interaction.js';
3
3
  import type * as jwtCustomizer from './jwt-customizer.js';
4
+ import type * as saml from './saml.js';
4
5
  import type * as token from './token.js';
5
6
  export * as interaction from './interaction.js';
6
7
  export * as token from './token.js';
7
8
  export * as hook from './hook.js';
8
9
  export * as jwtCustomizer from './jwt-customizer.js';
10
+ export * as saml from './saml.js';
9
11
  /** Fallback for empty or unrecognized log keys. */
10
12
  export declare const LogKeyUnknown = "Unknown";
11
- export type AuditLogKey = typeof LogKeyUnknown | interaction.LogKey | token.LogKey;
13
+ export type AuditLogKey = typeof LogKeyUnknown | interaction.LogKey | token.LogKey | saml.LogKey;
12
14
  export type WebhookLogKey = hook.LogKey;
13
15
  export type JwtCustomizerLogKey = jwtCustomizer.LogKey;
16
+ export type SamlLogKey = saml.LogKey;
14
17
  /**
15
18
  * The union type of all available log keys.
16
19
  * Note duplicate keys are allowed but should be avoided.
17
20
  *
18
21
  * @see {@link interaction.LogKey} for interaction log keys.
19
22
  * @see {@link token.LogKey} for token log keys.
23
+ * @see {@link saml.LogKey} for SAML application log keys.
20
24
  **/
21
25
  export type LogKey = AuditLogKey | WebhookLogKey | JwtCustomizerLogKey;
@@ -2,5 +2,6 @@ export * as interaction from './interaction.js';
2
2
  export * as token from './token.js';
3
3
  export * as hook from './hook.js';
4
4
  export * as jwtCustomizer from './jwt-customizer.js';
5
+ export * as saml from './saml.js';
5
6
  /** Fallback for empty or unrecognized log keys. */
6
7
  export const LogKeyUnknown = 'Unknown';
@@ -0,0 +1,7 @@
1
+ export type Prefix = 'SamlApplication';
2
+ export declare const prefix: Prefix;
3
+ export declare enum Scenario {
4
+ Callback = "Callback",
5
+ AuthnRequest = "AuthnRequest"
6
+ }
7
+ export type LogKey = `${Prefix}.${Scenario}`;
@@ -0,0 +1,6 @@
1
+ export const prefix = 'SamlApplication';
2
+ export var Scenario;
3
+ (function (Scenario) {
4
+ Scenario["Callback"] = "Callback";
5
+ Scenario["AuthnRequest"] = "AuthnRequest";
6
+ })(Scenario || (Scenario = {}));