Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The format is based on Keep a Changelog and this project follows semantic versio
- Remove the obsolete generic `opalApiUrl` proxy configuration from `ProxyConfiguration` and default proxy
config.
- Clear the logged-in user's Redis-backed user-state cache entry during SSO logout.
- Support a cluster-aware Redis session client for managed Redis deployments while retaining standalone Redis by default.

## Changelog Policy

Expand Down
1 change: 1 addition & 0 deletions src/interfaces/session-storage-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class SessionStorageConfiguration {
domain!: string;
redisEnabled!: boolean;
redisConnectionString!: string;
redisClusterEnabled!: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P0]: Build and API Integrity
Problem: redisClusterEnabled is added as a required property on the exported SessionStorageConfiguration type.
Why: Existing consumers that construct this config without the new field will fail TypeScript compilation, even though enableFor already treats a missing value as standalone Redis via sessionStorage.redisClusterEnabled === true. That makes this a breaking API change without a version bump or breaking-change migration note.
Fix: Make the field optional, or provide a default-compatible shape, for example redisClusterEnabled?: boolean;.

}

export default SessionStorageConfiguration;
69 changes: 54 additions & 15 deletions src/session/session-storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,69 @@ import { RedisStore } from 'connect-redis';
import cookieParser from 'cookie-parser';
import { Application } from 'express';
import session from 'express-session';
import { createClient } from 'redis';
import { createClient, createCluster, type RedisClientType, type RedisClusterType } from 'redis';
import FileStoreFactory from 'session-file-store';
import { REDIS_CLIENT_APP_LOCAL_KEY } from '../../constants/redis-client-app-local-key.js';

const FileStore = FileStoreFactory(session);
const logger = Logger.getLogger('session-storage');
const MAX_REDIS_RECONNECT_ATTEMPTS = 20;
const REDIS_TLS_PROTOCOL = 'rediss:';

type RedisSessionClient = RedisClientType | RedisClusterType;

export default class SessionStorage {
private getStore(app: Application, enabled: boolean, connectionString: string) {
if (enabled) {
const redisUrl = new URL(connectionString);
logger.info('Using Redis session store', `${redisUrl.protocol}//${redisUrl.host}`);
const client = createClient({
private getReconnectStrategy() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add method docs.

return function (retries: number) {
if (retries > MAX_REDIS_RECONNECT_ATTEMPTS) {
logger.log('Too many attempts to reconnect. Redis connection was terminated');
return new Error('Too many retries.');
} else {
return retries * 500;
}
};
}

private createRedisClient(connectionString: string, clusterEnabled: boolean): RedisSessionClient {
if (!clusterEnabled) {
return createClient({
url: connectionString,
socket: {
reconnectStrategy: function (retries) {
if (retries > 20) {
logger.log('Too many attempts to reconnect. Redis connection was terminated');
return new Error('Too many retries.');
} else {
return retries * 500;
}
},
reconnectStrategy: this.getReconnectStrategy(),
},
});
}

const redisUrl = new URL(connectionString);

return createCluster({
rootNodes: [{ url: connectionString }],
defaults: {
...(redisUrl.username ? { username: decodeURIComponent(redisUrl.username) } : {}),
...(redisUrl.password ? { password: decodeURIComponent(redisUrl.password) } : {}),
socket: {
tls: redisUrl.protocol === REDIS_TLS_PROTOCOL,
reconnectStrategy: this.getReconnectStrategy(),
},
},
});
}

private getStore(app: Application, enabled: boolean, connectionString: string, clusterEnabled: boolean) {
if (enabled) {
const redisUrl = new URL(connectionString);
logger.info(
`Using ${clusterEnabled ? 'clustered' : 'standalone'} Redis session store`,
`${redisUrl.protocol}//${redisUrl.host}`,
);
const client = this.createRedisClient(connectionString, clusterEnabled);

client.on('error', (err) => {
logger.error('Redis Client Error', err);
});
client.on('node-error', (err) => {
logger.error('Redis Cluster Node Error', err);
});

client.connect().catch(() => {
process.exit();
Expand Down Expand Up @@ -62,7 +96,12 @@ export default class SessionStorage {
domain: sessionStorage.domain,
},
rolling: true,
store: this.getStore(app, sessionStorage.redisEnabled, sessionStorage.redisConnectionString),
store: this.getStore(
app,
sessionStorage.redisEnabled,
sessionStorage.redisConnectionString,
sessionStorage.redisClusterEnabled === true,
),
}),
);
}
Expand Down
1 change: 1 addition & 0 deletions yarn-known-issues
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
{"value":"@opentelemetry/core","children":{"ID":1120821,"Issue":"OpenTelemetry Core: Unbounded memory allocation in W3C Baggage propagation","URL":"https://github.com/advisories/GHSA-8988-4f7v-96qf","Severity":"moderate","Vulnerable Versions":"<2.8.0","Tree Versions":["1.30.1","2.0.1"],"Dependents":["@azure/opentelemetry-instrumentation-azure-sdk@npm:1.0.0-beta.9","applicationinsights@virtual:5843eacae55b71dda0add913fd01c2f69e8548e48f97802ab3aa541175282495254dd2e854c37d72d02e8efecf55850dba3d1505e8b006284e02644e6f897e9c#npm:2.9.8"]}}
{"value":"uuid","children":{"ID":1119441,"Issue":"uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided","URL":"https://github.com/advisories/GHSA-w5hq-g745-h8pq","Severity":"moderate","Vulnerable Versions":"<11.1.1","Tree Versions":["8.3.2"],"Dependents":["@azure/msal-node@npm:3.8.6"]}}