Skip to content

Commit f2c0a05

Browse files
authored
feat(core,schemas): added updated_at to user_sso_identities (#7425)
* feat(core,schemas): added updated_at to user_sso_identities added an `updated_at` field to the `user_sso_identities` table to track the last update time for each record * chore(test): update integration test update integration test * refactor(core,schemas): replace with native set_updated_at func replace with native set_updated_at function * chore(test): adjust integration test adjust integration test * fix(test): fix integration test fix integration test
1 parent c551284 commit f2c0a05

File tree

4 files changed

+59
-7
lines changed

4 files changed

+59
-7
lines changed

.changeset/blue-brooms-clean.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@logto/schemas": minor
3+
"@logto/core": minor
4+
---
5+
6+
added an `updated_at` field to the `user_sso_identities` table to track the last update time for each record.
7+
8+
On each successfull SSO sign-in, the `updated_at` field will be set to the current timestamp. This allows for better tracking of when a user's SSO identity was authenticated and updated.

packages/integration-tests/src/tests/api/experience-api/sign-in-interaction/enterprise-sso.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { InteractionEvent, MfaFactor, SignInIdentifier } from '@logto/schemas';
1+
import { InteractionEvent, MfaFactor, SignInIdentifier, SignInMode } from '@logto/schemas';
22
import { generateStandardId } from '@logto/shared';
33

44
import { createUserMfaVerification, deleteUser, getUser } from '#src/api/admin-user.js';
@@ -30,6 +30,7 @@ describe('enterprise sso sign-in and sign-up', () => {
3030
await ssoConnectorApi.createMockOidcConnector([domain]);
3131
await updateSignInExperience({
3232
singleSignOnEnabled: true,
33+
signInMode: SignInMode.SignInAndRegister,
3334
signUp: { identifiers: [], password: false, verify: false },
3435
});
3536
});
@@ -38,7 +39,7 @@ describe('enterprise sso sign-in and sign-up', () => {
3839
await Promise.all([ssoConnectorApi.cleanUp(), userApi.cleanUp()]);
3940
});
4041

41-
it('should successfully sign-up with enterprise sso and sync email', async () => {
42+
it('should successfully sign-up with enterprise sso and sync email and sync SSO profile on the next sign-in', async () => {
4243
const userId = await signInWithEnterpriseSso(
4344
ssoConnectorApi.firstConnectorId!,
4445
{
@@ -50,11 +51,10 @@ describe('enterprise sso sign-in and sign-up', () => {
5051
);
5152

5253
const { primaryEmail } = await getUser(userId);
54+
5355
expect(primaryEmail).toBe(email);
54-
});
5556

56-
it('should successfully sign-in with enterprise sso', async () => {
57-
const userId = await signInWithEnterpriseSso(ssoConnectorApi.firstConnectorId!, {
57+
await signInWithEnterpriseSso(ssoConnectorApi.firstConnectorId!, {
5858
sub: enterpriseSsoIdentityId,
5959
email,
6060
email_verified: true,
@@ -71,6 +71,7 @@ describe('enterprise sso sign-in and sign-up', () => {
7171
const { userProfile, user } = await generateNewUser({
7272
primaryEmail: true,
7373
});
74+
7475
const { primaryEmail } = userProfile;
7576

7677
const userId = await signInWithEnterpriseSso(ssoConnectorApi.firstConnectorId!, {
@@ -85,10 +86,14 @@ describe('enterprise sso sign-in and sign-up', () => {
8586
const { name, ssoIdentities } = await getUser(userId, true);
8687

8788
expect(name).toBe('John Doe');
88-
expect(ssoIdentities?.some((identity) => identity.identityId === enterpriseSsoIdentityId)).toBe(
89-
true
89+
90+
const enterpriseSsoIdentity = ssoIdentities?.find(
91+
(identity) => identity.identityId === enterpriseSsoIdentityId
9092
);
9193

94+
expect(enterpriseSsoIdentity).toBeTruthy();
95+
expect(enterpriseSsoIdentity?.updatedAt).not.toBeNull();
96+
9297
await deleteUser(userId);
9398
});
9499

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 user_sso_identities
9+
add column updated_at timestamptz not null default(now());
10+
`);
11+
12+
await pool.query(sql`
13+
create trigger set_updated_at
14+
before update on user_sso_identities
15+
for each row
16+
execute procedure set_updated_at();
17+
`);
18+
},
19+
down: async (pool) => {
20+
await pool.query(sql`
21+
drop trigger set_updated_at on user_sso_identities;
22+
`);
23+
24+
await pool.query(sql`
25+
alter table user_sso_identities
26+
drop column updated_at;
27+
`);
28+
},
29+
};
30+
31+
export default alteration;

packages/schemas/tables/user_sso_identities.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,19 @@ create table user_sso_identities (
1010
/** Provider user identity id*/
1111
identity_id varchar(128) not null,
1212
detail jsonb /* @use JsonObject */ not null default '{}'::jsonb,
13+
/** Known issue: created_at uses timestamp instead of timestamptz */
1314
created_at timestamp not null default(now()),
15+
updated_at timestamptz not null default(now()),
1416
sso_connector_id
1517
varchar(128) not null
1618
references sso_connectors (id) on update cascade on delete cascade,
1719
primary key (id),
1820
constraint user_sso_identities__issuer__identity_id
1921
unique (tenant_id, issuer, identity_id)
2022
);
23+
24+
25+
create trigger set_updated_at
26+
before update on user_sso_identities
27+
for each row
28+
execute procedure set_updated_at();

0 commit comments

Comments
 (0)