Skip to content

Commit

Permalink
feat: add saml app sessions table
Browse files Browse the repository at this point in the history
  • Loading branch information
darcyYe committed Dec 13, 2024
1 parent 69986bc commit f06a607
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 0 deletions.
66 changes: 66 additions & 0 deletions packages/core/src/saml-applications/queries/sessions.ts
Original file line number Diff line number Diff line change
@@ -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;

Check warning on line 25 in packages/core/src/saml-applications/queries/sessions.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/saml-applications/queries/sessions.ts#L19-L25

Added lines #L19 - L25 were not covered by tests
};

const findSessionsByApplicationId = async (applicationId: string) =>
pool.any<SamlApplicationSession>(sql`
select ${sql.join(Object.values(fields), sql`, `)}
from ${table}
where ${fields.applicationId}=${applicationId}

Check warning on line 32 in packages/core/src/saml-applications/queries/sessions.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/saml-applications/queries/sessions.ts#L29-L32

Added lines #L29 - L32 were not covered by tests
`);

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()

Check warning on line 42 in packages/core/src/saml-applications/queries/sessions.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/saml-applications/queries/sessions.ts#L36-L42

Added lines #L36 - L42 were not covered by tests
`);

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()

Check warning on line 55 in packages/core/src/saml-applications/queries/sessions.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/saml-applications/queries/sessions.ts#L46-L55

Added lines #L46 - L55 were not covered by tests
`);

return {
insertSession,
updateSession,
deleteExpiredOrFullyUsedSessions,
findSessionsByApplicationId,
findAvailableSessionByAppIdAndState,
findAvailableSessionByAppIdAndSamlRequestId,
};
};
2 changes: 2 additions & 0 deletions packages/core/src/tenants/Queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { createUsersRolesQueries } from '#src/queries/users-roles.js';
import { createVerificationStatusQueries } from '#src/queries/verification-status.js';
import { createSamlApplicationConfigQueries } from '#src/saml-applications/queries/configs.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 { PersonalAccessTokensQueries } from '../queries/personal-access-tokens.js';
Expand Down Expand Up @@ -64,6 +65,7 @@ export default class Queries {
subjectTokens = createSubjectTokenQueries(this.pool);
samlApplicationSecrets = createSamlApplicationSecretsQueries(this.pool);
samlApplicationConfigs = createSamlApplicationConfigQueries(this.pool);
samlApplicationSessions = createSamlApplicationSessionQueries(this.pool);
personalAccessTokens = new PersonalAccessTokensQueries(this.pool);
verificationRecords = new VerificationRecordQueries(this.pool);
accountCenters = new AccountCenterQueries(this.pool);
Expand Down
Original file line number Diff line number Diff line change
@@ -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(21) not null,
application_id varchar(21) not null
references applications (id) on update cascade on delete cascade,
saml_request_id varchar(21),
oidc_state varchar(21),
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 changes: 28 additions & 0 deletions packages/schemas/tables/saml_application_sessions.sql
Original file line number Diff line number Diff line change
@@ -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(21) 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 varchar(21),
/** The identifier of the OIDC auth request state. */
oidc_state varchar(21),
/** 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);

0 comments on commit f06a607

Please sign in to comment.