diff --git a/packages/schemas/alterations/next-1728526649-add-idp-initiated-saml-sso-sessions-table.ts b/packages/schemas/alterations/next-1728526649-add-idp-initiated-saml-sso-sessions-table.ts new file mode 100644 index 000000000..23ed3ccb5 --- /dev/null +++ b/packages/schemas/alterations/next-1728526649-add-idp-initiated-saml-sso-sessions-table.ts @@ -0,0 +1,36 @@ +import { sql } from '@silverhand/slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js'; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + create table idp_initiated_saml_sso_sessions ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + /** The globally unique identifier of the assertion record. */ + id varchar(21) not null, + /** The identifier of the SAML SSO connector. */ + connector_id varchar(128) not null + references sso_connectors (id) on update cascade on delete cascade, + /** The SAML assertion. */ + assertion_content jsonb /* @use SsoSamlAssertionContent */ not null default '{}'::jsonb, + created_at timestamptz not null default(now()), + /** The expiration time of the assertion. */ + expires_at timestamptz not null, + primary key (tenant_id, id) + ); + `); + await applyTableRls(pool, 'idp_initiated_saml_sso_sessions'); + }, + down: async (pool) => { + await dropTableRls(pool, 'idp_initiated_saml_sso_sessions'); + await pool.query(sql` + drop table idp_initiated_saml_sso_sessions; + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/src/foundations/jsonb-types/sso-connector.ts b/packages/schemas/src/foundations/jsonb-types/sso-connector.ts index 91d96f647..30f41456e 100644 --- a/packages/schemas/src/foundations/jsonb-types/sso-connector.ts +++ b/packages/schemas/src/foundations/jsonb-types/sso-connector.ts @@ -10,6 +10,8 @@ export const ssoBrandingGuard = z.object({ darkLogo: z.string().optional(), }); +export type SsoBranding = z.infer; + export const idpInitiatedAuthParamsGuard = z .object({ resources: z.array(z.string()).optional(), @@ -19,4 +21,17 @@ export const idpInitiatedAuthParamsGuard = z export type IdpInitiatedAuthParams = z.infer; -export type SsoBranding = z.infer; +export const ssoSamlAssertionContentGuard = z + .object({ + nameID: z.string().optional(), + attributes: z.record(z.string().or(z.array(z.string()))).optional(), + conditions: z + .object({ + notBefore: z.string(), + notOnOrAfter: z.string(), + }) + .optional(), + }) + .catchall(z.unknown()); + +export type SsoSamlAssertionContent = z.infer; diff --git a/packages/schemas/tables/idp_initiated_saml_sso_sessions.sql b/packages/schemas/tables/idp_initiated_saml_sso_sessions.sql new file mode 100644 index 000000000..1f5a886d5 --- /dev/null +++ b/packages/schemas/tables/idp_initiated_saml_sso_sessions.sql @@ -0,0 +1,16 @@ +/* init_order = 2 */ +create table idp_initiated_saml_sso_sessions ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + /** The globally unique identifier of the assertion record. */ + id varchar(21) not null, + /** The identifier of the SAML SSO connector. */ + connector_id varchar(128) not null + references sso_connectors (id) on update cascade on delete cascade, + /** The SAML assertion. */ + assertion_content jsonb /* @use SsoSamlAssertionContent */ not null default '{}'::jsonb, + created_at timestamptz not null default(now()), + /** The expiration time of the assertion. */ + expires_at timestamptz not null, + primary key (tenant_id, id) +);