mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat: add saml app sessions table
This commit is contained in:
parent
69986bc179
commit
ebeca1607d
4 changed files with 138 additions and 0 deletions
66
packages/core/src/saml-applications/queries/sessions.ts
Normal file
66
packages/core/src/saml-applications/queries/sessions.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { type SamlApplicationSession, SamlApplicationSessions } from '@logto/schemas';
|
||||||
|
import type { CommonQueryMethods } from '@silverhand/slonik';
|
||||||
|
import { sql } from '@silverhand/slonik';
|
||||||
|
|
||||||
|
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||||
|
import { buildUpdateWhereWithPool } from '#src/database/update-where.js';
|
||||||
|
import { convertToIdentifiers } from '#src/utils/sql.js';
|
||||||
|
|
||||||
|
const { table, fields } = convertToIdentifiers(SamlApplicationSessions);
|
||||||
|
|
||||||
|
export const createSamlApplicationSessionQueries = (pool: CommonQueryMethods) => {
|
||||||
|
const insertSession = buildInsertIntoWithPool(pool)(SamlApplicationSessions, {
|
||||||
|
returning: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateSession = buildUpdateWhereWithPool(pool)(SamlApplicationSessions, true);
|
||||||
|
|
||||||
|
const deleteExpiredOrFullyUsedSessions = async () => {
|
||||||
|
const { rowCount } = await pool.query(sql`
|
||||||
|
delete from ${table}
|
||||||
|
where ${fields.expiresAt} < now()
|
||||||
|
or (${fields.isSamlResponseSent} = true and ${fields.isOidcStateChecked} = true)
|
||||||
|
`);
|
||||||
|
|
||||||
|
return rowCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findSessionsByApplicationId = async (applicationId: string) =>
|
||||||
|
pool.any<SamlApplicationSession>(sql`
|
||||||
|
select ${sql.join(Object.values(fields), sql`, `)}
|
||||||
|
from ${table}
|
||||||
|
where ${fields.applicationId}=${applicationId}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const findAvailableSessionByAppIdAndState = async (applicationId: string, state: string) =>
|
||||||
|
pool.one<SamlApplicationSession>(sql`
|
||||||
|
select ${sql.join(Object.values(fields), sql`, `)}
|
||||||
|
from ${table}
|
||||||
|
where ${fields.applicationId}=${applicationId}
|
||||||
|
and ${fields.oidcState}=${state} and ${fields.isOidcStateChecked} = false and ${
|
||||||
|
fields.expiresAt
|
||||||
|
} > now()
|
||||||
|
`);
|
||||||
|
|
||||||
|
const findAvailableSessionByAppIdAndSamlRequestId = async (
|
||||||
|
applicationId: string,
|
||||||
|
samlRequestId: string
|
||||||
|
) =>
|
||||||
|
pool.one<SamlApplicationSession>(sql`
|
||||||
|
select ${sql.join(Object.values(fields), sql`, `)}
|
||||||
|
from ${table}
|
||||||
|
where ${fields.applicationId}=${applicationId}
|
||||||
|
and ${fields.samlRequestId}=${samlRequestId} and ${fields.isSamlResponseSent} = false and ${
|
||||||
|
fields.expiresAt
|
||||||
|
} > now()
|
||||||
|
`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
insertSession,
|
||||||
|
updateSession,
|
||||||
|
deleteExpiredOrFullyUsedSessions,
|
||||||
|
findSessionsByApplicationId,
|
||||||
|
findAvailableSessionByAppIdAndState,
|
||||||
|
findAvailableSessionByAppIdAndSamlRequestId,
|
||||||
|
};
|
||||||
|
};
|
|
@ -30,6 +30,7 @@ import { createUsersRolesQueries } from '#src/queries/users-roles.js';
|
||||||
import { createVerificationStatusQueries } from '#src/queries/verification-status.js';
|
import { createVerificationStatusQueries } from '#src/queries/verification-status.js';
|
||||||
import { createSamlApplicationConfigQueries } from '#src/saml-applications/queries/configs.js';
|
import { createSamlApplicationConfigQueries } from '#src/saml-applications/queries/configs.js';
|
||||||
import { createSamlApplicationSecretsQueries } from '#src/saml-applications/queries/secrets.js';
|
import { createSamlApplicationSecretsQueries } from '#src/saml-applications/queries/secrets.js';
|
||||||
|
import { createSamlApplicationSessionQueries } from '#src/saml-applications/queries/sessions.js';
|
||||||
|
|
||||||
import { AccountCenterQueries } from '../queries/account-center.js';
|
import { AccountCenterQueries } from '../queries/account-center.js';
|
||||||
import { PersonalAccessTokensQueries } from '../queries/personal-access-tokens.js';
|
import { PersonalAccessTokensQueries } from '../queries/personal-access-tokens.js';
|
||||||
|
@ -64,6 +65,7 @@ export default class Queries {
|
||||||
subjectTokens = createSubjectTokenQueries(this.pool);
|
subjectTokens = createSubjectTokenQueries(this.pool);
|
||||||
samlApplicationSecrets = createSamlApplicationSecretsQueries(this.pool);
|
samlApplicationSecrets = createSamlApplicationSecretsQueries(this.pool);
|
||||||
samlApplicationConfigs = createSamlApplicationConfigQueries(this.pool);
|
samlApplicationConfigs = createSamlApplicationConfigQueries(this.pool);
|
||||||
|
samlApplicationSessions = createSamlApplicationSessionQueries(this.pool);
|
||||||
personalAccessTokens = new PersonalAccessTokensQueries(this.pool);
|
personalAccessTokens = new PersonalAccessTokensQueries(this.pool);
|
||||||
verificationRecords = new VerificationRecordQueries(this.pool);
|
verificationRecords = new VerificationRecordQueries(this.pool);
|
||||||
accountCenters = new AccountCenterQueries(this.pool);
|
accountCenters = new AccountCenterQueries(this.pool);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
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 saml_application_sessions (
|
||||||
|
tenant_id varchar(21) not null
|
||||||
|
references tenants (id) on update cascade on delete cascade,
|
||||||
|
id varchar(32) not null,
|
||||||
|
application_id varchar(21) not null
|
||||||
|
references applications (id) on update cascade on delete cascade,
|
||||||
|
saml_request_id varchar(128),
|
||||||
|
oidc_state varchar(32),
|
||||||
|
is_oidc_state_checked boolean not null default false,
|
||||||
|
is_saml_response_sent boolean not null default false,
|
||||||
|
created_at timestamptz not null default(now()),
|
||||||
|
expires_at timestamptz not null,
|
||||||
|
primary key (tenant_id, id),
|
||||||
|
constraint saml_application_sessions__application_type
|
||||||
|
check (check_application_type(application_id, 'SAML'))
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index saml_application_sessions__oidc_state
|
||||||
|
on saml_application_sessions (tenant_id, oidc_state);
|
||||||
|
create unique index saml_application_sessions__saml_request_id
|
||||||
|
on saml_application_sessions (tenant_id, saml_request_id);
|
||||||
|
`);
|
||||||
|
await applyTableRls(pool, 'saml_application_sessions');
|
||||||
|
},
|
||||||
|
down: async (pool) => {
|
||||||
|
await dropTableRls(pool, 'saml_application_sessions');
|
||||||
|
await pool.query(sql`
|
||||||
|
drop table if exists saml_application_sessions;
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default alteration;
|
28
packages/schemas/tables/saml_application_sessions.sql
Normal file
28
packages/schemas/tables/saml_application_sessions.sql
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/* init_order = 2 */
|
||||||
|
|
||||||
|
create table saml_application_sessions (
|
||||||
|
tenant_id varchar(21) not null
|
||||||
|
references tenants (id) on update cascade on delete cascade,
|
||||||
|
/** The globally unique identifier of the session. */
|
||||||
|
id varchar(32) not null,
|
||||||
|
application_id varchar(21) not null
|
||||||
|
references applications (id) on update cascade on delete cascade,
|
||||||
|
/** The identifier of the SAML SSO auth request ID, SAML request ID is pretty long. */
|
||||||
|
saml_request_id varchar(128),
|
||||||
|
/** The identifier of the OIDC auth request state. */
|
||||||
|
oidc_state varchar(32),
|
||||||
|
/** When checking the OIDC auth state, we should have this flag to prevent replay attack. */
|
||||||
|
is_oidc_state_checked boolean not null default false,
|
||||||
|
/** When sending the SAML authn response, we should have this flag to prevent replay attack. */
|
||||||
|
is_saml_response_sent boolean not null default false,
|
||||||
|
created_at timestamptz not null default(now()),
|
||||||
|
expires_at timestamptz not null,
|
||||||
|
primary key (tenant_id, id),
|
||||||
|
constraint saml_application_sessions__application_type
|
||||||
|
check (check_application_type(application_id, 'SAML'))
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index saml_application_sessions__oidc_state
|
||||||
|
on saml_application_sessions (tenant_id, oidc_state);
|
||||||
|
create unique index saml_application_sessions__saml_request_id
|
||||||
|
on saml_application_sessions (tenant_id, saml_request_id);
|
Loading…
Reference in a new issue