Skip to content

Commit e4379a5

Browse files
committed
feat: enhance Socket.IO configuration and update AuthService token handling
- Introduced custom Socket.IO ping interval and timeout settings to improve connection stability. - Updated AuthService to exclude 'mfaVerifiedAt' from JWT sign options when renewing tokens, ensuring cleaner token management. - Enhanced LifecycleHooksService to conditionally set identity state based on lifecycle mutations, improving identity processing logic. - Added tests to verify the exclusion of 'mfaVerifiedAt' in token renewal process.
1 parent 30f4085 commit e4379a5

7 files changed

Lines changed: 72 additions & 8 deletions

File tree

apps/api/src/_common/adapters/sesame-io.adapter.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { Server as HttpServer } from 'http';
33
import { Server as HttpsServer } from 'https';
44
import { ServerOptions } from 'socket.io';
55

6+
/** Défaut Socket.IO : 25 s — trop verbeux en long-polling seul (dev). */
7+
const SOCKET_IO_PING_INTERVAL_MS = 60_000;
8+
const SOCKET_IO_PING_TIMEOUT_MS = 45_000;
9+
610
export class SesameIoAdapter extends IoAdapter {
711
private ioServer: ReturnType<IoAdapter['createIOServer']> | undefined;
812

@@ -12,7 +16,11 @@ export class SesameIoAdapter extends IoAdapter {
1216

1317
public createIOServer(port: number, options?: ServerOptions) {
1418
if (!this.ioServer) {
15-
this.ioServer = super.createIOServer(port, options);
19+
this.ioServer = super.createIOServer(port, {
20+
...options,
21+
pingInterval: SOCKET_IO_PING_INTERVAL_MS,
22+
pingTimeout: SOCKET_IO_PING_TIMEOUT_MS,
23+
});
1624
}
1725

1826
return this.ioServer;

apps/api/src/core/auth/auth.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ export class AuthService extends AbstractService implements OnModuleInit {
764764
expiresIn: this.ACCESS_TOKEN_EXPIRES_IN,
765765
jwtid,
766766
subject: `${identity._id}`,
767-
...omit(options, ['scopes', 'mfaVerified']),
767+
...omit(options, ['scopes', 'mfaVerified', 'mfaVerifiedAt']),
768768
},
769769
);
770770
this.logger.debug(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function hasLifecycleMutation(mutation: object | undefined | null): boolean {
2+
return mutation != null && typeof mutation === 'object' && Object.keys(mutation).length > 0;
3+
}

apps/api/src/management/lifecycle/lifecycle-hooks.service.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import { CronJob } from 'cron';
55
import dayjs from 'dayjs';
66
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
77
import { isConsoleEntrypoint } from '~/_common/functions/is-cli';
8+
import { IdentityState } from '../identities/_enums/states.enum';
89
import { Identities } from '../identities/_schemas/identities.schema';
910
import { AbstractLifecycleService } from './_abstracts/abstract.lifecycle.service';
1011
import { ConfigRulesObjectSchemaDTO } from './_dto/config-rules.dto';
12+
import { hasLifecycleMutation } from './_functions/has-lifecycle-mutation.function';
1113
import { loadCustomStates } from './_functions/load-custom-states.function';
1214
import { loadLifecycleRules } from './_functions/load-lifecycle-rules.function';
1315
import { validateLifecycleCronRules } from './_functions/validate-lifecycle-cron-rules.function';
@@ -331,13 +333,15 @@ export class LifecycleHooksService extends AbstractLifecycleService {
331333
this.logger.verbose(`identities process request`, JSON.stringify(req, null, 2));
332334

333335
for (const identity of identities) {
336+
const hasMutation = hasLifecycleMutation(resolvedMutation);
334337
const updated = await this.identitiesService.model.findOneAndUpdate(
335338
{ _id: identity._id },
336339
{
337340
$set: {
338341
...resolvedMutation,
339342
lifecycle: idRule.target,
340343
lastLifecycleUpdate: new Date(),
344+
...(hasMutation ? { state: IdentityState.TO_SYNC } : {}),
341345
},
342346
},
343347
{ new: true },
@@ -526,6 +530,7 @@ export class LifecycleHooksService extends AbstractLifecycleService {
526530
continue; // Skip processing if it's a trigger-based rule
527531
}
528532

533+
const hasMutation = hasLifecycleMutation(resolvedMutation);
529534
const res = await this.identitiesService.model.findOneAndUpdate(
530535
{
531536
...resolvedRules,
@@ -537,6 +542,7 @@ export class LifecycleHooksService extends AbstractLifecycleService {
537542
...resolvedMutation,
538543
lifecycle: lcs.target,
539544
lastLifecycleUpdate: new Date(),
545+
...(hasMutation ? { state: IdentityState.TO_SYNC } : {}),
540546
},
541547
},
542548
{
@@ -556,12 +562,17 @@ export class LifecycleHooksService extends AbstractLifecycleService {
556562
date: new Date(),
557563
});
558564

559-
const identities = res._id ? [{ id: res._id.toString(), before: after, after: res }] : [];
560-
await this.backendsService.lifecycleChangedIdentities(identities);
561-
562-
this.logger.log(
563-
`Identity <${res._id}> updated to lifecycle <${lcs.target}> based on rules from source <${after.lifecycle}>`,
564-
);
565+
if (hasMutation) {
566+
this.logger.log(
567+
`Identity <${res._id}> updated to lifecycle <${lcs.target}> with attribute mutation, set to TO_SYNC`,
568+
);
569+
} else {
570+
const identities = res._id ? [{ id: res._id.toString(), before: after, after: res }] : [];
571+
await this.backendsService.lifecycleChangedIdentities(identities);
572+
this.logger.log(
573+
`Identity <${res._id}> updated to lifecycle <${lcs.target}> based on rules from source <${after.lifecycle}>`,
574+
);
575+
}
565576
return;
566577
}
567578
}

apps/api/tests/unit/core/auth/auth.service.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,36 @@ describe('AuthService', () => {
106106
expect(tokens.access_token).toBe('new-a');
107107
expect(createTokensSpy).toHaveBeenCalledWith({ _id: 'a1', username: 'john' }, 'existing-r', {
108108
mfaVerified: undefined,
109+
mfaVerifiedAt: undefined,
109110
});
110111
});
111112

113+
it('should not pass mfaVerifiedAt to jwt sign options when renewing tokens', async () => {
114+
const verifiedAt = 1_700_000_000_000;
115+
redisGet.mockResolvedValueOnce(
116+
JSON.stringify({ identityId: 'a1', mfaVerified: true, mfaVerifiedAt: verifiedAt }),
117+
);
118+
agentsFindOne.mockResolvedValueOnce({
119+
_id: 'a1',
120+
toObject: () => ({ _id: 'a1', username: 'john', password: 'hash' }),
121+
toJSON: () => ({ _id: 'a1', username: 'john' }),
122+
});
123+
jwtSign.mockReturnValue('access-token');
124+
125+
await service.renewTokens('existing-r');
126+
127+
const signOptions = jwtSign.mock.calls[0]?.[1];
128+
expect(signOptions).toEqual(
129+
expect.objectContaining({
130+
expiresIn: expect.any(Number),
131+
jwtid: expect.any(String),
132+
subject: 'a1',
133+
}),
134+
);
135+
expect(signOptions).not.toHaveProperty('mfaVerifiedAt');
136+
expect(signOptions).not.toHaveProperty('mfaVerified');
137+
});
138+
112139
it('should throw UnauthorizedException when renew token does not exist', async () => {
113140
redisGet.mockResolvedValue(null);
114141

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { hasLifecycleMutation } from '~/management/lifecycle/_functions/has-lifecycle-mutation.function';
2+
3+
describe('hasLifecycleMutation', () => {
4+
it('should return false for empty or missing mutation', () => {
5+
expect(hasLifecycleMutation(undefined)).toBe(false);
6+
expect(hasLifecycleMutation(null)).toBe(false);
7+
expect(hasLifecycleMutation({})).toBe(false);
8+
});
9+
10+
it('should return true when mutation has at least one field', () => {
11+
expect(hasLifecycleMutation({ 'inetOrgPerson.cn': 'mutated' })).toBe(true);
12+
});
13+
});

apps/web/src/composables/useSocketIoClient.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,7 @@ export function buildSocketIoClientOptions(auth: { id: string; key: string }): P
3939
transports: socketIoTransports(),
4040
upgrade: !pollingOnly,
4141
reconnectionAttempts: 10,
42+
reconnectionDelay: 5_000,
43+
reconnectionDelayMax: 30_000,
4244
}
4345
}

0 commit comments

Comments
 (0)