This repository has been archived by the owner on Oct 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: 🔄 update session logic, introduce cookie-session module
Adjusted the session logic by replacing the Session entity with UserSession. The change has been applied uniformly across all relevant files. A new `express-session` module was included to handle session data. This takes care of storing each user's ID within their respective session, improving the user identification process. A new cookie session functionality was also integrated with the use of the `nestjs-cookie-session` module.
- Loading branch information
Showing
24 changed files
with
495 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
121 changes: 121 additions & 0 deletions
121
apps/server/src/common/middleware/cookie-session/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Cookie Session | ||
This module implements a session with storing data directly in `Cookie`. | ||
|
||
If you want to store data in one of [external stores](https://github.com/expressjs/session#compatible-session-stores) and passing ID of session to client via `Cookie`/`Set-Cookie` headers, you can look at [nestjs-session](https://github.com/iamolegga/nestjs-session). | ||
|
||
## Example | ||
|
||
Register module: | ||
|
||
```ts | ||
// app.module.ts | ||
import { Module } from '@nestjs/common'; | ||
import { | ||
NestCookieSessionOptions, | ||
CookieSessionModule, | ||
} from 'nestjs-cookie-session'; | ||
import { ViewsController } from './views.controller'; | ||
|
||
@Module({ | ||
imports: [ | ||
// sync params: | ||
|
||
CookieSessionModule.forRoot({ | ||
session: { secret: 'keyboard cat' }, | ||
}), | ||
|
||
// or async: | ||
|
||
CookieSessionModule.forRootAsync({ | ||
imports: [ConfigModule], | ||
inject: [Config], | ||
// TIP: to get autocomplete in return object | ||
// add `NestCookieSessionOptions` here ↓↓↓ | ||
useFactory: async (config: Config): Promise<NestCookieSessionOptions> => { | ||
return { | ||
session: { secret: config.secret }, | ||
}; | ||
}, | ||
}), | ||
], | ||
controllers: [ViewsController], | ||
}) | ||
export class AppModule {} | ||
``` | ||
|
||
Use in controllers with NestJS built-in `Session` decorator: | ||
|
||
```ts | ||
// views.controller.ts | ||
import { Controller, Get, Session } from '@nestjs/common'; | ||
|
||
@Controller('views') | ||
export class ViewsController { | ||
@Get() | ||
getViews(@Session() session: { views?: number }) { | ||
session.views = (session.views || 0) + 1; | ||
return session.views; | ||
} | ||
} | ||
``` | ||
|
||
To run examples: | ||
|
||
```sh | ||
git clone https://github.com/iamolegga/nestjs-cookie-session.git | ||
cd nestjs-cookie-session | ||
npm i | ||
npm run build | ||
cd example | ||
npm i | ||
npm start | ||
``` | ||
|
||
## Install | ||
|
||
```sh | ||
npm i nestjs-cookie-session cookie-session @types/cookie-session | ||
``` | ||
|
||
## API | ||
|
||
### CookieSessionModule | ||
|
||
`CookieSessionModule` class has two static methods, that returns `DynamicModule`, that you need to import: | ||
|
||
- `CookieSessionModule.forRoot` for sync configuration without dependencies | ||
- `CookieSessionModule.forRootAsync` for sync/async configuration with dependencies | ||
|
||
### CookieSessionModule.forRoot | ||
|
||
Accept `NestCookieSessionOptions`. Returns NestJS `DynamicModule` for import. | ||
|
||
### CookieSessionModule.forRootAsync | ||
|
||
Accept `NestCookieSessionAsyncOptions`. Returns NestJS `DynamicModule` for import. | ||
|
||
### NestCookieSessionOptions | ||
|
||
`NestCookieSessionOptions` is the interface of all options, has next properties: | ||
|
||
- `session` - **required** - [cookie-session options](https://github.com/expressjs/cookie-session#options). | ||
- `forRoutes` - **optional** - same as NestJS built-in `MiddlewareConfigProxy['forRoutes']` [See examples in official docs](https://docs.nestjs.com/middleware#applying-middleware). Specify routes, that should have access to session. If `forRoutes` and `exclude` will not be set, then sessions will be set to all routes. | ||
- `exclude` - **optional** - same as NestJS built-in `MiddlewareConfigProxy['exclude']` [See examples in official docs](https://docs.nestjs.com/middleware#applying-middleware). Specify routes, that should not have access to session. If `forRoutes` and `exclude` will not be set, then sessions will be set to all routes. | ||
|
||
### NestCookieSessionAsyncOptions | ||
|
||
`NestCookieSessionOptions` is the interface of options to create cookie session module, that depends on other modules, has next properties: | ||
|
||
- `imports` - **optional** - modules, that cookie session module depends on. See [official docs](https://docs.nestjs.com/modules). | ||
- `inject` - **optional** - providers from `imports`-property modules, that will be passed as arguments to `useFactory` method. | ||
- `useFactory` - **required** - method, that returns `NestCookieSessionOptions`. | ||
|
||
## Migration | ||
|
||
### v2 | ||
|
||
`cookie-session` and `@types/cookie-session` are moved to peer dependencies, so you can update them independently. | ||
|
||
<h2 align="center">Do you use this library?<br/>Don't be shy to give it a star! ★</h2> | ||
|
||
<h3 align="center">Also if you are into NestJS you might be interested in one of my <a href="https://github.com/iamolegga#nestjs">other NestJS libs</a>.</h3> |
19 changes: 19 additions & 0 deletions
19
apps/server/src/common/middleware/cookie-session/cookie-session-module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import cookieSession = require('cookie-session') | ||
import {AsyncOptions, createModule, SyncOptions} from 'create-nestjs-middleware-module'; | ||
|
||
|
||
|
||
interface Options { | ||
/** | ||
* cookie-session options. @see https://www.npmjs.com/package/cookie-session#options | ||
*/ | ||
session: Parameters<typeof cookieSession>[0]; | ||
} | ||
|
||
export type NestCookieSessionOptions = SyncOptions<Options>; | ||
|
||
export type NestCookieSessionAsyncOptions = AsyncOptions<Options>; | ||
|
||
export const CookieSessionModule = createModule<Options>(({ session }) => | ||
cookieSession(session), | ||
); |
40 changes: 40 additions & 0 deletions
40
apps/server/src/common/middleware/session/retries-middleware.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import {InternalServerErrorException} from '@nestjs/common'; | ||
import * as express from 'express'; | ||
|
||
|
||
|
||
type WaitingStrategy = (attempt: number) => number; | ||
|
||
export function createRetriesMiddleware( | ||
sessionMiddleware: express.RequestHandler, | ||
retries: number, | ||
retiesStrategy: WaitingStrategy = () => 0, | ||
): express.RequestHandler { | ||
return async (req, res, next) => { | ||
let attempt = 0; | ||
|
||
async function lookupSession(error?: unknown) { | ||
if (error) { | ||
return next(error); | ||
} | ||
|
||
if (req.session !== undefined) { | ||
return next(); | ||
} | ||
|
||
if (attempt > retries) { | ||
return next(new InternalServerErrorException('Cannot create session')); | ||
} | ||
|
||
if (attempt !== 0) { | ||
await new Promise((r) => setTimeout(r, retiesStrategy(attempt))); | ||
} | ||
|
||
attempt++; | ||
|
||
sessionMiddleware(req, res, lookupSession); | ||
} | ||
|
||
await lookupSession(); | ||
}; | ||
} |
43 changes: 43 additions & 0 deletions
43
apps/server/src/common/middleware/session/session-middleware-module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import {AsyncOptions, createModule, SyncOptions} from 'create-nestjs-middleware-module'; | ||
import expressSession from 'express-session'; | ||
|
||
import {createRetriesMiddleware} from './retries-middleware.js'; | ||
|
||
|
||
|
||
interface Options { | ||
/** | ||
* express-session options. @see https://github.com/expressjs/session#options | ||
*/ | ||
session: expressSession.SessionOptions; | ||
/** | ||
* by default if your session store lost connection to database it will return | ||
* session as `undefined`, and no errors will be thrown, and then you need to | ||
* check session in controller. But you can set this property how many times | ||
* it should retry to get session, and on fail `InternalServerErrorException` | ||
* will be thrown. If you don't want retries, but just want to | ||
* `InternalServerErrorException` to be throw, then set to `0`. Set this | ||
* option, if you dont't want manualy check session inside controllers. | ||
*/ | ||
retries?: number; | ||
/** | ||
* function that returns number of ms to wait between next attempt. Not calls | ||
* on first attempt. | ||
*/ | ||
retriesStrategy?: Parameters<typeof createRetriesMiddleware>[2]; | ||
} | ||
|
||
export type NestSessionOptions = SyncOptions<Options>; | ||
|
||
export type NestSessionAsyncOptions = AsyncOptions<Options>; | ||
|
||
export const SessionMiddlewareModule = createModule<Options>((options) => { | ||
const { retries, session, retriesStrategy } = options; | ||
let middleware = expressSession(session); | ||
|
||
if (retries !== undefined) { | ||
middleware = createRetriesMiddleware(middleware, retries, retriesStrategy); | ||
} | ||
|
||
return middleware; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,61 @@ | ||
import {Module} from "@nestjs/common" | ||
import {DocumentaitonModule} from "./modules/documentation/documentaiton-module.js" | ||
import {DeveloperToolsModule} from "./modules/environment/dev-tools/developer-tools.module.js" | ||
import {AsyncLocalStorageModule} from "./modules/environment/local-storage/async-local-storage-module.js" | ||
import {ContinuationLocalStorageModule} from "./modules/environment/local-storage/continuation-local-storage-module.js" | ||
import {HealthModule} from "./modules/observability/healthcheck/health-module.js" | ||
import {TelemetryModule} from "./modules/observability/telemetry/telemetry-module.js" | ||
import {DatabaseModule} from "./modules/storage/database/database.module.js" | ||
|
||
import {Module} from "@nestjs/common" | ||
import { | ||
SessionMiddlewareModule | ||
} from "./middleware/session/session-middleware-module.js" | ||
import { | ||
DocumentaitonModule | ||
} from "./modules/documentation/documentaiton-module.js" | ||
import { | ||
DeveloperToolsModule | ||
} from "./modules/environment/dev-tools/developer-tools.module.js" | ||
import { | ||
AsyncLocalStorageModule | ||
} from "./modules/environment/local-storage/async-local-storage-module.js" | ||
import { | ||
ContinuationLocalStorageModule | ||
} from "./modules/environment/local-storage/continuation-local-storage-module.js" | ||
import { | ||
HealthModule | ||
} from "./modules/observability/healthcheck/health-module.js" | ||
import { | ||
TelemetryModule | ||
} from "./modules/observability/telemetry/telemetry-module.js" | ||
import {DatabaseModule} from "./modules/storage/database/database.module.js" | ||
|
||
|
||
@Module({ | ||
imports: [ | ||
TelemetryModule, DatabaseModule, ContinuationLocalStorageModule, | ||
AsyncLocalStorageModule, DocumentaitonModule, HealthModule, | ||
TelemetryModule, | ||
DatabaseModule, | ||
ContinuationLocalStorageModule, | ||
AsyncLocalStorageModule, | ||
DocumentaitonModule, | ||
HealthModule, | ||
DeveloperToolsModule, | ||
SessionMiddlewareModule.forRoot({ | ||
session: { | ||
secret: "secretomitted", | ||
rolling: false, | ||
resave: false, | ||
saveUninitialized: false, | ||
}, | ||
}), | ||
// https://stackoverflow.com/questions/56046527/express-session-req-session-touch-not-a-function | ||
//CookieSessionModule.forRoot({ | ||
// session: { | ||
// name: 'session', secret: 'secretomitted', maxAge: 0 | ||
// } | ||
//}) | ||
], | ||
exports: [ | ||
TelemetryModule, DatabaseModule, ContinuationLocalStorageModule, | ||
AsyncLocalStorageModule | ||
, DocumentaitonModule, HealthModule, DeveloperToolsModule, | ||
TelemetryModule, | ||
DatabaseModule, | ||
ContinuationLocalStorageModule, | ||
AsyncLocalStorageModule, | ||
DocumentaitonModule, | ||
HealthModule, | ||
DeveloperToolsModule, | ||
], | ||
}) | ||
export class SharedModule {} | ||
export class SharedModule { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import session from 'express-session'; | ||
|
||
declare module 'express-session' { | ||
export interface SessionData extends session.SessionData { | ||
userId?: number; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.