Skip to content

Commit

Permalink
fix(nestjs-oidc): session setting & redirect statusCode chanage
Browse files Browse the repository at this point in the history
* add connect-redis to store session
* set express-session in dependencies and use memorystore as store
* use 302 as redirect statusCode
  • Loading branch information
hubert committed Nov 6, 2024
1 parent 6f2fdca commit d23e5c8
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 75 deletions.
12 changes: 7 additions & 5 deletions packages/nestjs-oidc/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "nestjs-oidc",
"description": "OpenID-Connect client module for NestJS.",
"version": "0.0.8",
"version": "0.1.0",
"author": "Hubert<[email protected]>",
"main": "lib/index.js",
"module": "esm/index.js",
Expand Down Expand Up @@ -39,7 +39,7 @@
],
"scripts": {
"serve": "run -T concurrently --raw \"tsc --project tsconfig.json -watch\"",
"build": "run -T rimraf dist && yarn build:version && yarn build:cjs && yarn build:esm",
"build": "run -T rimraf -rf lib esm dist && yarn build:version && yarn build:cjs && yarn build:esm",
"build:version": "node -p \"'export const version = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts",
"build:cjs": "run -T tsc --project tsconfig.build.json",
"build:esm": "run -T tsc --project tsconfig.build.json --module es2015 --outDir esm",
Expand All @@ -52,8 +52,10 @@
"dependencies": {
"@nestjs/passport": "^10.0.0",
"cookie": "^0.5.0",
"express-session": "^1.17.3",
"flatted": "^3.2.6",
"jose": "^4.15.9",
"memorystore": "^1.6.7",
"openid-client": "^5.6.5",
"passport": "^0.6.0",
"querystring": "^0.2.1",
Expand All @@ -67,14 +69,14 @@
"@types/passport": "^1.0.9",
"@types/uuid": "^9.0.7",
"connect-mongo": "^5.1.0",
"express-session": "^1.17.3"
"connect-redis": "^7.1.1"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/graphql": "^11.0.0",
"connect-mongo": "4.6.0",
"express-session": "^1.17.3",
"connect-redis": "^7.1.1",
"graphql": "^16.0.0",
"graphql-parse-resolve-info": "^4.12.0"
},
Expand All @@ -85,7 +87,7 @@
"connect-mongo": {
"optional": true
},
"express-session": {
"connect-redis": {
"optional": true
},
"graphql": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export class MisdirectedExceptionFilter implements ExceptionFilter {
const requestedChannel = request.params.channelType;
const originalTenant = request.user?.profile.tenantId;
const originalChannel = request.user?.profile.channelType;
httpAdapter.redirect(
return httpAdapter.redirect(
response,
HttpStatus.MOVED_PERMANENTLY,
HttpStatus.FOUND,
`/tenant-switch-warn?requestedTenant=${requestedTenant}&requestedChannel=${requestedChannel}&originalTenant=${originalTenant}&originalChannel=${originalChannel}`,
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/nestjs-oidc/src/filters/unauthorized.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export class UnauthorizedFilter implements ExceptionFilter {
const prefix = this.getPrefixFromRequest(request);

// If you're using the multitenancy authentication, you'll need to get the prefix
httpAdapter.redirect(
return httpAdapter.redirect(
response,
HttpStatus.MOVED_PERMANENTLY,
HttpStatus.FOUND,
`${prefix}/login?redirect=${httpAdapter.getRequestUrl(request)}${searchParams ? `&${searchParams}` : ''}`,
);
}
Expand Down
29 changes: 26 additions & 3 deletions packages/nestjs-oidc/src/oidc.setup-session.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
import { INestApplication } from '@nestjs/common';
import { SessionOptions } from 'express-session';
import { sessionInMemory } from './session/session-in-memory';
import { sessionInMongo } from './session/session-in-mongo';
import { sessionInRedis } from './session/session-in-redis';

export const setupSession = (app: INestApplication, name: string) => {
return sessionInMemory(app, name);
};
/**
* setup session
*/
export function setupSession(
app: INestApplication,
type: 'memory' | 'mongo' | 'redis' = 'memory',
options?: Partial<SessionOptions> & {
[key: string]: any;
},
) {
switch (type) {
case 'memory':
return sessionInMemory(app, options);
case 'mongo':
if (!options?.connectMongoOptions) throw new Error('connectMongoOptions is required for session type mongo');
return sessionInMongo(app, options as any);
case 'redis':
if (!options?.connectRedisOptions) throw new Error('connectRedisOptions is required for session type redis');
return sessionInRedis(app, options as any);
default:
throw new Error(`session type ${type} is not supported`);
}
}
37 changes: 37 additions & 0 deletions packages/nestjs-oidc/src/session/base-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import session, { SessionOptions } from 'express-session';
import { v4 as uuid } from 'uuid';
import { ChannelType } from '../interfaces/index';

// https://github.com/expressjs/session/issues/725#issuecomment-605922223
Object.defineProperty(session.Cookie.prototype, 'sameSite', {
// sameSite cannot be set to `None` if cookie is not marked secure
get() {
return this._sameSite === 'none' && !this.secure ? 'lax' : this._sameSite;
},
set(value) {
this._sameSite = value;
},
});

export const defaultOptions = function (): SessionOptions {
return {
secret: process.env.SESSION_SECRET || uuid(), // to sign session id
resave: false, // will default to false in near future: https://github.com/expressjs/session#resave
saveUninitialized: false, // will default to false in near future: https://github.com/expressjs/session#saveuninitialized
rolling: true, // keep session alive
proxy: true, // trust first proxy
cookie: {
maxAge: 60 * 60 * 1000, // session expires in 1hr, refreshed by `rolling: true` option.
httpOnly: true, // so that cookie can't be accessed via client-side script
secure: 'auto', // set to true if your communication is over HTTPS
sameSite: 'none', // set to 'none' if your communication is over HTTPS
},
};
};

declare module 'express-session' {
interface SessionData {
tenantId?: string;
channelType?: ChannelType;
}
}
39 changes: 0 additions & 39 deletions packages/nestjs-oidc/src/session/express-session.ts

This file was deleted.

5 changes: 3 additions & 2 deletions packages/nestjs-oidc/src/session/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './session-in-memory';
export * from './session-mongo';
export * from './session-in-memory';
export * from './session-in-mongo';
export * from './session-in-redis';
29 changes: 21 additions & 8 deletions packages/nestjs-oidc/src/session/session-in-memory.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import { INestApplication } from '@nestjs/common';
import session, { SessionOptions } from 'express-session';
import createMemoryStore from 'memorystore';
import passport from 'passport';
import { createExpressSession } from './express-session';
import { defaultOptions } from './base-session';

const MomeryStore = createMemoryStore(session);

/**
* setup session with in-memory store
*/
export function sessionInMemory(
app: INestApplication,
name: string,
options?: {
sessionStrategy?: (options: { name: string; [key: string]: any }) => any;
// rest of sessionStrategy options
[key: string]: any;
options?: Partial<SessionOptions> & {
memoryOptions?: ConstructorParameters<ReturnType<typeof createMemoryStore>>[0];
},
) {
const { sessionStrategy, ...rest } = options ?? {};
app.use((sessionStrategy ?? createExpressSession)({ ...rest, name }));
const { memoryOptions, ...rest } = options ?? {};
app.use(
session({
...defaultOptions(),
...rest,
store: new MomeryStore({
checkPeriod: 86400000, // prune expired entries every 24h
...memoryOptions,
}),
}),
);
app.use(passport.initialize());
app.use(passport.session());
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { INestApplication } from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import session, { SessionOptions } from 'express-session';
import passport from 'passport';
import ConnectMongo from 'connect-mongo';
import { createExpressSession } from './express-session';
import { defaultOptions } from './base-session';

export const sessionMongo = (
/**
* setup session with mongo store
*/
export const sessionInMongo = (
app: INestApplication,
name: string,
options: {
options: Partial<SessionOptions> & {
connectMongoOptions: ConnectMongoOptions;
sessionStrategy?: (options: { name: string; store: ConnectMongo; [key: string]: any }) => any;
// rest of sessionStrategy options
[key: string]: any;
},
) => {
const MongoStore = loadPackage('connect-mongo', 'SessionModule', () =>
require('connect-mongo'),
) as typeof ConnectMongo;

const { sessionStrategy, connectMongoOptions, ...rest } = options;
const { connectMongoOptions, ...rest } = options;
app.use(
(sessionStrategy ?? createExpressSession)({
session({
...defaultOptions(),
...rest,
name,
store: MongoStore.create(connectMongoOptions),
}),
);
Expand Down
30 changes: 30 additions & 0 deletions packages/nestjs-oidc/src/session/session-in-redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { INestApplication } from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import session, { SessionOptions } from 'express-session';
import passport from 'passport';
import ConnectRedis from 'connect-redis';
import { defaultOptions } from './base-session';

/**
* setup session with redis store
*/
export const sessionInRedis = (
app: INestApplication,
options: Partial<SessionOptions> & {
connectRedisOptions: ConstructorParameters<typeof ConnectRedis>[0];
},
) => {
const RedisStore = loadPackage('connect-redis', 'SessionModule', () => require('connect-redis'))
.default as typeof ConnectRedis;

const { connectRedisOptions, ...rest } = options;
app.use(
session({
...defaultOptions(),
...rest,
store: new RedisStore(connectRedisOptions),
}),
);
app.use(passport.initialize());
app.use(passport.session());
};
2 changes: 1 addition & 1 deletion packages/nestjs-oidc/src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const version = "0.0.8";
export const version = "0.1.0";
37 changes: 34 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10225,6 +10225,15 @@ __metadata:
languageName: node
linkType: hard

"connect-redis@npm:^7.1.1":
version: 7.1.1
resolution: "connect-redis@npm:7.1.1::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fconnect-redis%2F-%2Fconnect-redis-7.1.1.tgz"
peerDependencies:
express-session: ">=1"
checksum: ac91ee818d0f467866b6982f66b3423fee58de9da3562618f6d1df2ddeea426354c1efe70b3f799be1b52e3cc67f2043b9ae203678fd3ff9db5dff44b078f0ca
languageName: node
linkType: hard

"consola@npm:^2.15.0":
version: 2.15.3
resolution: "consola@npm:2.15.3"
Expand Down Expand Up @@ -10997,7 +11006,7 @@ __metadata:
languageName: node
linkType: hard

"debug@npm:^4.3.1":
"debug@npm:^4.3.0, debug@npm:^4.3.1":
version: 4.3.7
resolution: "debug@npm:4.3.7::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fdebug%2F-%2Fdebug-4.3.7.tgz"
dependencies:
Expand Down Expand Up @@ -17553,6 +17562,16 @@ __metadata:
languageName: node
linkType: hard

"lru-cache@npm:^4.0.3":
version: 4.1.5
resolution: "lru-cache@npm:4.1.5::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Flru-cache%2F-%2Flru-cache-4.1.5.tgz"
dependencies:
pseudomap: ^1.0.2
yallist: ^2.1.2
checksum: 4bb4b58a36cd7dc4dcec74cbe6a8f766a38b7426f1ff59d4cf7d82a2aa9b9565cd1cb98f6ff60ce5cd174524868d7bc9b7b1c294371851356066ca9ac4cf135a
languageName: node
linkType: hard

"lru-cache@npm:^5.1.1":
version: 5.1.1
resolution: "lru-cache@npm:5.1.1"
Expand Down Expand Up @@ -17853,6 +17872,16 @@ __metadata:
languageName: node
linkType: hard

"memorystore@npm:^1.6.7":
version: 1.6.7
resolution: "memorystore@npm:1.6.7::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fmemorystore%2F-%2Fmemorystore-1.6.7.tgz"
dependencies:
debug: ^4.3.0
lru-cache: ^4.0.3
checksum: caa5cc523ec39ad8b317ddb5560771df28a10266281066f554bd77b2179a34f21f06891fffbcfe875ed1afc79cf7e106714eff98189ded1e6bfc273159d07948
languageName: node
linkType: hard

"meow@npm:^10.1.5":
version: 10.1.5
resolution: "meow@npm:10.1.5"
Expand Down Expand Up @@ -18544,10 +18573,12 @@ __metadata:
"@types/passport": ^1.0.9
"@types/uuid": ^9.0.7
connect-mongo: ^5.1.0
connect-redis: ^7.1.1
cookie: ^0.5.0
express-session: ^1.17.3
flatted: ^3.2.6
jose: ^4.15.9
memorystore: ^1.6.7
openid-client: ^5.6.5
passport: ^0.6.0
querystring: ^0.2.1
Expand All @@ -18557,15 +18588,15 @@ __metadata:
"@nestjs/core": ^10.0.0
"@nestjs/graphql": ^11.0.0
connect-mongo: 4.6.0
express-session: ^1.17.3
connect-redis: ^7.1.1
graphql: ^16.0.0
graphql-parse-resolve-info: ^4.12.0
peerDependenciesMeta:
"@nestjs/graphql":
optional: true
connect-mongo:
optional: true
express-session:
connect-redis:
optional: true
graphql:
optional: true
Expand Down

0 comments on commit d23e5c8

Please sign in to comment.