Refactored Auth with Sessions #12
|
@ -36,9 +36,13 @@
|
|||
"argon2": "^0.40.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"connect-typeorm": "^2.0.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"express-session": "^1.18.0",
|
||||
"install": "^0.13.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.11.5",
|
||||
"reflect-metadata": "^0.2.0",
|
||||
"rxjs": "^7.8.1",
|
||||
|
@ -53,8 +57,10 @@
|
|||
"@types/argon2": "^0.15.0",
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.42.0",
|
||||
|
|
|
@ -44,15 +44,27 @@ dependencies:
|
|||
class-validator:
|
||||
specifier: ^0.14.1
|
||||
version: 0.14.1
|
||||
connect-typeorm:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(typeorm@0.3.20)
|
||||
cookie-parser:
|
||||
specifier: ^1.4.6
|
||||
version: 1.4.6
|
||||
express-session:
|
||||
specifier: ^1.18.0
|
||||
version: 1.18.0
|
||||
install:
|
||||
specifier: ^0.13.0
|
||||
version: 0.13.0
|
||||
passport:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
passport-jwt:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
passport-local:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
pg:
|
||||
specifier: ^8.11.5
|
||||
version: 8.11.5
|
||||
|
@ -91,12 +103,18 @@ devDependencies:
|
|||
'@types/express':
|
||||
specifier: ^4.17.17
|
||||
version: 4.17.21
|
||||
'@types/express-session':
|
||||
specifier: ^1.18.0
|
||||
version: 1.18.0
|
||||
'@types/jest':
|
||||
specifier: ^29.5.2
|
||||
version: 29.5.12
|
||||
'@types/node':
|
||||
specifier: ^20.3.1
|
||||
version: 20.12.4
|
||||
'@types/passport-local':
|
||||
specifier: ^1.0.38
|
||||
version: 1.0.38
|
||||
'@types/supertest':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.2
|
||||
|
@ -1304,13 +1322,11 @@ packages:
|
|||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 20.12.4
|
||||
dev: true
|
||||
|
||||
/@types/connect@3.4.38:
|
||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||
dependencies:
|
||||
'@types/node': 20.12.4
|
||||
dev: true
|
||||
|
||||
/@types/cookie-parser@1.4.7:
|
||||
resolution: {integrity: sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==}
|
||||
|
@ -1322,6 +1338,10 @@ packages:
|
|||
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
|
||||
dev: true
|
||||
|
||||
/@types/debug@0.0.31:
|
||||
resolution: {integrity: sha512-LS1MCPaQKqspg7FvexuhmDbWUhE2yIJ+4AgVIyObfc06/UKZ8REgxGNjZc82wPLWmbeOm7S+gSsLgo75TanG4A==}
|
||||
dev: false
|
||||
|
||||
/@types/eslint-scope@3.7.7:
|
||||
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
||||
dependencies:
|
||||
|
@ -1347,7 +1367,11 @@ packages:
|
|||
'@types/qs': 6.9.14
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.4
|
||||
dev: true
|
||||
|
||||
/@types/express-session@1.18.0:
|
||||
resolution: {integrity: sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==}
|
||||
dependencies:
|
||||
'@types/express': 4.17.21
|
||||
|
||||
/@types/express@4.17.21:
|
||||
resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
|
||||
|
@ -1356,7 +1380,6 @@ packages:
|
|||
'@types/express-serve-static-core': 4.17.43
|
||||
'@types/qs': 6.9.14
|
||||
'@types/serve-static': 1.15.7
|
||||
dev: true
|
||||
|
||||
/@types/graceful-fs@4.1.9:
|
||||
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
|
||||
|
@ -1366,7 +1389,6 @@ packages:
|
|||
|
||||
/@types/http-errors@2.0.4:
|
||||
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
|
||||
dev: true
|
||||
|
||||
/@types/istanbul-lib-coverage@2.0.6:
|
||||
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
|
||||
|
@ -1411,27 +1433,44 @@ packages:
|
|||
|
||||
/@types/mime@1.3.5:
|
||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||
dev: true
|
||||
|
||||
/@types/node@20.12.4:
|
||||
resolution: {integrity: sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/passport-local@1.0.38:
|
||||
resolution: {integrity: sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==}
|
||||
dependencies:
|
||||
'@types/express': 4.17.21
|
||||
'@types/passport': 1.0.16
|
||||
'@types/passport-strategy': 0.2.38
|
||||
dev: true
|
||||
|
||||
/@types/passport-strategy@0.2.38:
|
||||
resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==}
|
||||
dependencies:
|
||||
'@types/express': 4.17.21
|
||||
'@types/passport': 1.0.16
|
||||
dev: true
|
||||
|
||||
/@types/passport@1.0.16:
|
||||
resolution: {integrity: sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A==}
|
||||
dependencies:
|
||||
'@types/express': 4.17.21
|
||||
dev: true
|
||||
|
||||
/@types/qs@6.9.14:
|
||||
resolution: {integrity: sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==}
|
||||
dev: true
|
||||
|
||||
/@types/range-parser@1.2.7:
|
||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||
dev: true
|
||||
|
||||
/@types/send@0.17.4:
|
||||
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
'@types/node': 20.12.4
|
||||
dev: true
|
||||
|
||||
/@types/serve-static@1.15.7:
|
||||
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
|
||||
|
@ -1439,7 +1478,6 @@ packages:
|
|||
'@types/http-errors': 2.0.4
|
||||
'@types/node': 20.12.4
|
||||
'@types/send': 0.17.4
|
||||
dev: true
|
||||
|
||||
/@types/stack-utils@2.0.3:
|
||||
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
|
||||
|
@ -2254,6 +2292,20 @@ packages:
|
|||
readable-stream: 2.3.8
|
||||
typedarray: 0.0.6
|
||||
|
||||
/connect-typeorm@2.0.0(typeorm@0.3.20):
|
||||
resolution: {integrity: sha512-0OcbHJkNMTJjSrbcKGljr4PKgRq13Dds7zQq3+8oaf4syQTgGvGv9OgnXo2qg+Bljkh4aJNzIvW74QOVLn8zrw==}
|
||||
peerDependencies:
|
||||
typeorm: ^0.3.0
|
||||
dependencies:
|
||||
'@types/debug': 0.0.31
|
||||
'@types/express-session': 1.18.0
|
||||
debug: 4.3.4
|
||||
express-session: 1.18.0
|
||||
typeorm: 0.3.20(pg@8.11.5)(ts-node@10.9.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/consola@2.15.3:
|
||||
resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
|
||||
|
||||
|
@ -2282,6 +2334,10 @@ packages:
|
|||
/cookie-signature@1.0.6:
|
||||
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||
|
||||
/cookie-signature@1.0.7:
|
||||
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
|
||||
dev: false
|
||||
|
||||
/cookie@0.4.1:
|
||||
resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -2709,6 +2765,22 @@ packages:
|
|||
jest-util: 29.7.0
|
||||
dev: true
|
||||
|
||||
/express-session@1.18.0:
|
||||
resolution: {integrity: sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
cookie: 0.6.0
|
||||
cookie-signature: 1.0.7
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
on-headers: 1.0.2
|
||||
parseurl: 1.3.3
|
||||
safe-buffer: 5.2.1
|
||||
uid-safe: 2.1.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/express@4.19.2:
|
||||
resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
@ -3224,6 +3296,11 @@ packages:
|
|||
wrap-ansi: 6.2.0
|
||||
dev: true
|
||||
|
||||
/install@0.13.0:
|
||||
resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dev: false
|
||||
|
||||
/interpret@1.4.0:
|
||||
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
@ -4259,6 +4336,11 @@ packages:
|
|||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
|
||||
/on-headers@1.0.2:
|
||||
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
|
@ -4379,6 +4461,13 @@ packages:
|
|||
passport-strategy: 1.0.0
|
||||
dev: false
|
||||
|
||||
/passport-local@1.0.0:
|
||||
resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dependencies:
|
||||
passport-strategy: 1.0.0
|
||||
dev: false
|
||||
|
||||
/passport-strategy@1.0.0:
|
||||
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
@ -4616,6 +4705,11 @@ packages:
|
|||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
|
||||
/random-bytes@1.0.0:
|
||||
resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/randombytes@2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
dependencies:
|
||||
|
@ -5457,6 +5551,13 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
/uid-safe@2.1.5:
|
||||
resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
random-bytes: 1.0.0
|
||||
dev: false
|
||||
|
||||
/uid@2.0.2:
|
||||
resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { SecurityHeadersMiddleware } from './middleware/security-middleware/secu
|
|||
import { AuthModule } from './modules/auth-module/auth.module';
|
||||
import { DatabaseModule } from './modules/database-module/database.module';
|
||||
import { SendgridModule } from './modules/sendgrid-module/sendgrid.module';
|
||||
import { SessionModule } from './modules/session/session.module';
|
||||
import { UserModule } from './modules/user-module/user.module';
|
||||
import { VerifyModule } from './modules/verify-module/verify.module';
|
||||
|
||||
|
@ -26,6 +27,7 @@ import { VerifyModule } from './modules/verify-module/verify.module';
|
|||
UserModule,
|
||||
SendgridModule,
|
||||
VerifyModule,
|
||||
SessionModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, ClearExpiredSessionsCron],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import { SessionService } from 'src/modules/auth-module/services/session.service';
|
||||
import { SessionService } from 'src/modules/session/services/session.service';
|
||||
|
||||
@Injectable()
|
||||
export class ClearExpiredSessionsCron {
|
||||
|
@ -14,6 +14,6 @@ export class ClearExpiredSessionsCron {
|
|||
})
|
||||
public handleCron(): void {
|
||||
this.logger.log('Cronjob Executed: Clear-Expired-Sessions');
|
||||
this.sessionService.clearExpiredSessions();
|
||||
this.sessionService.deleteAllExpiredSessions();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,29 @@
|
|||
import { ISession } from 'connect-typeorm';
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
BaseEntity,
|
||||
Index,
|
||||
DeleteDateColumn,
|
||||
PrimaryColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { UserCredentials } from './user-credentials.entity';
|
||||
|
||||
@Entity()
|
||||
export class Session {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
export class Session extends BaseEntity implements ISession {
|
||||
@PrimaryColumn('varchar', { length: 255 })
|
||||
public id: string;
|
||||
|
||||
@Column()
|
||||
public sessionId: string;
|
||||
@Index()
|
||||
@Column('bigint')
|
||||
public expiredAt: number;
|
||||
|
||||
@Column({ type: 'timestamp' })
|
||||
public expiresAt: Date;
|
||||
@Column({ type: 'text' })
|
||||
public json: string;
|
||||
|
||||
@Column({})
|
||||
public userAgent: string;
|
||||
|
||||
@ManyToOne(() => UserCredentials, (userCredentials) => userCredentials.id, {
|
||||
nullable: false,
|
||||
})
|
||||
@JoinColumn({ name: 'userCredentialsId' })
|
||||
public userCredentials: UserCredentials['id'];
|
||||
@DeleteDateColumn()
|
||||
public destroyedAt?: Date;
|
||||
|
||||
@CreateDateColumn()
|
||||
public createdAt: Date;
|
||||
|
|
|
@ -7,6 +7,7 @@ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
|||
import * as cookieParser from 'cookie-parser';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
import { SessionInitService } from './modules/session/services';
|
||||
|
||||
async function setupSwagger(app: INestApplication): Promise<void> {
|
||||
const config = new DocumentBuilder()
|
||||
|
@ -31,6 +32,12 @@ async function setupSwagger(app: INestApplication): Promise<void> {
|
|||
);
|
||||
}
|
||||
|
||||
async function setupSessions(app: INestApplication): Promise<void> {
|
||||
const sessionService = app.get(SessionInitService);
|
||||
|
||||
app.use(sessionService.initSession());
|
||||
}
|
||||
|
||||
async function setupCookieParser(app: INestApplication): Promise<void> {
|
||||
app.use(cookieParser());
|
||||
}
|
||||
|
@ -50,6 +57,7 @@ async function bootstrap(): Promise<void> {
|
|||
await setupSwagger(app);
|
||||
await setupPrefix(app);
|
||||
await setupClassValidator(app);
|
||||
await setupSessions(app);
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Session, UserCredentials } from 'src/entities';
|
||||
import { UserCredentials } from 'src/entities';
|
||||
|
||||
import { SendgridModule } from '../sendgrid-module/sendgrid.module';
|
||||
import { SessionModule } from '../session/session.module';
|
||||
import { UserModule } from '../user-module/user.module';
|
||||
import { VerifyModule } from '../verify-module/verify.module';
|
||||
|
||||
import { AuthController } from './controller/auth.controller';
|
||||
import { SessionRepository } from './repositories/session.repository';
|
||||
import { UserCredentialsRepository } from './repositories/user-credentials.repository';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { SessionService } from './services/session.service';
|
||||
import { TokenManagementService } from './services/token-management.service';
|
||||
import { LocalStrategy } from './strategies/local.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
JwtModule.register({}),
|
||||
TypeOrmModule.forFeature([UserCredentials]),
|
||||
PassportModule,
|
||||
SessionModule,
|
||||
UserModule,
|
||||
SendgridModule,
|
||||
VerifyModule,
|
||||
JwtModule.register({}),
|
||||
TypeOrmModule.forFeature([UserCredentials, Session]),
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
SessionService,
|
||||
TokenManagementService,
|
||||
UserCredentialsRepository,
|
||||
SessionRepository,
|
||||
],
|
||||
providers: [AuthService, UserCredentialsRepository, LocalStrategy],
|
||||
controllers: [AuthController],
|
||||
exports: [SessionService],
|
||||
exports: [],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Body,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { Request } from 'express';
|
||||
import { Public } from 'src/shared/decorator';
|
||||
|
||||
import { LocalAuthGuard } from '../guard';
|
||||
import { LoginResponseDto, UserCredentialsDto } from '../models/dto';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
|
@ -23,20 +33,19 @@ export class AuthController {
|
|||
return this.authService.signup(userCredentials);
|
||||
}
|
||||
|
||||
// @ApiCreatedResponse({
|
||||
// description: 'User signin successfully',
|
||||
// type: LoginResponseDto,
|
||||
// })
|
||||
// @HttpCode(HttpStatus.OK)
|
||||
// @Public()
|
||||
// @Post('signin')
|
||||
// public async signin(
|
||||
// @Res({ passthrough: true }) response: Response,
|
||||
// @Req() request: Request,
|
||||
// @Body() userCredentials: UserCredentialsDto
|
||||
// ): Promise<LoginResponseDto> {
|
||||
// return await this.authService.signin(userCredentials, response, request);
|
||||
// }
|
||||
@ApiCreatedResponse({
|
||||
description: 'User signin successfully',
|
||||
type: LoginResponseDto,
|
||||
})
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Public()
|
||||
@UseGuards(LocalAuthGuard)
|
||||
@Post('signin')
|
||||
public async signin(@Req() request: Request): Promise<void> {
|
||||
// console.log('request', userCredentials);
|
||||
console.log('request', request.user);
|
||||
//return await this.authService.signin(userCredentials);
|
||||
}
|
||||
|
||||
// @ApiCreatedResponse({
|
||||
// description: 'User tokens refreshed successfully',
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './local.auth.guard';
|
|
@ -0,0 +1,13 @@
|
|||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class LocalAuthGuard extends AuthGuard('local') {
|
||||
public async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const result = (await super.canActivate(context)) as boolean;
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
await super.logIn(request);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class AccessTokenDto {
|
||||
@ApiProperty({
|
||||
title: 'Access token',
|
||||
description: 'Access token',
|
||||
example: 'eyJhbGci',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
public access_token: string;
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
export * from './user-credentials.dto';
|
||||
export * from './login-response.dto';
|
||||
export * from './access-token.dto';
|
||||
|
|
|
@ -9,7 +9,7 @@ export class LoginResponseDto {
|
|||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
public access_token: string;
|
||||
public access_token?: string;
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Email',
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
export * from './jwt-payload.type';
|
||||
// export * from './jwt-payload-with-refresh-token.type';
|
||||
export * from './token-payload.type';
|
||||
export * from './tokens.type';
|
|
@ -1,3 +0,0 @@
|
|||
// import { JwtPayload } from './jwt-payload.type';
|
||||
|
||||
// export type JwtPayloadWithRefreshToken = JwtPayload & { refresh_token: string };
|
|
@ -1,4 +0,0 @@
|
|||
export type JwtPayload = {
|
||||
email: string;
|
||||
sub: number;
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
export type TokenPayload = {
|
||||
sub: string;
|
||||
email: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
export type Tokens = {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
};
|
|
@ -1,105 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Response } from 'express';
|
||||
import { Session } from 'src/entities';
|
||||
import { DeleteResult, Repository } from 'typeorm';
|
||||
import { LessThan } from 'typeorm';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@Injectable()
|
||||
export class SessionRepository {
|
||||
public constructor(
|
||||
@InjectRepository(Session) private sessionRepository: Repository<Session>
|
||||
) {}
|
||||
|
||||
public createSession(userId: string, userAgent: string): Promise<Session> {
|
||||
const sessionId = uuidv4();
|
||||
const expirationDate = new Date();
|
||||
|
||||
expirationDate.setHours(expirationDate.getHours() + 1);
|
||||
|
||||
const session = this.sessionRepository.create({
|
||||
userCredentials: userId,
|
||||
sessionId,
|
||||
expiresAt: expirationDate,
|
||||
userAgent,
|
||||
});
|
||||
|
||||
return this.sessionRepository.save(session);
|
||||
}
|
||||
|
||||
public findSessionBySessionId(sessionId: string): Promise<Session> {
|
||||
return this.sessionRepository.findOne({
|
||||
where: { sessionId },
|
||||
relations: ['userCredentials'],
|
||||
});
|
||||
}
|
||||
|
||||
public findSessionByUserId(userId: string): Promise<Session[]> {
|
||||
return this.sessionRepository
|
||||
.createQueryBuilder('session')
|
||||
.where('session.userCredentialsId = :userId', {
|
||||
userId,
|
||||
})
|
||||
.getMany();
|
||||
}
|
||||
|
||||
public attachSessionToResponse(response: Response, sessionId: string): void {
|
||||
response.cookie('session_id', sessionId, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'strict',
|
||||
});
|
||||
}
|
||||
|
||||
public validateSessionUserAgent(
|
||||
sessionId: string,
|
||||
currentUserAgent: string
|
||||
): Promise<boolean> {
|
||||
return this.sessionRepository
|
||||
.findOne({
|
||||
where: { sessionId },
|
||||
select: ['userAgent'],
|
||||
})
|
||||
.then((session) =>
|
||||
session ? session.userAgent === currentUserAgent : false
|
||||
);
|
||||
}
|
||||
|
||||
public checkSessionLimit(userId: string): Promise<DeleteResult> {
|
||||
return this.sessionRepository
|
||||
.createQueryBuilder('session')
|
||||
.leftJoinAndSelect('session.userCredentials', 'userCredentials')
|
||||
.where('userCredentials.id = :userId', { userId })
|
||||
.orderBy('session.expiresAt', 'ASC')
|
||||
.getMany()
|
||||
.then((userSessions) => {
|
||||
if (userSessions.length >= 5) {
|
||||
return this.sessionRepository.delete(userSessions[0].id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public invalidateAllSessionsForUser(userId: string): Promise<DeleteResult> {
|
||||
return this.sessionRepository.delete({ userCredentials: userId });
|
||||
}
|
||||
|
||||
public extendSessionExpiration(sessionId: string): Promise<Session> {
|
||||
return this.sessionRepository
|
||||
.findOne({ where: { sessionId } })
|
||||
.then((session) => {
|
||||
if (session) {
|
||||
session.expiresAt = new Date(
|
||||
session.expiresAt.setMinutes(session.expiresAt.getMinutes() + 30)
|
||||
);
|
||||
return this.sessionRepository.save(session);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public clearExpiredSessions(): Promise<DeleteResult> {
|
||||
const now = new Date();
|
||||
|
||||
return this.sessionRepository.delete({ expiresAt: LessThan(now) });
|
||||
}
|
||||
}
|
|
@ -1,29 +1,26 @@
|
|||
import {
|
||||
ConflictException,
|
||||
ForbiddenException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { UserCredentials } from 'src/entities';
|
||||
import { EncryptionService } from 'src/shared';
|
||||
|
||||
import { PasswordConfirmationMailService } from '../../sendgrid-module/services/password-confirmation.mail.service';
|
||||
import { UserDataRepository } from '../../user-module/repositories/user-data.repository';
|
||||
import { EmailVerificationService } from '../../verify-module/services/email-verification.service';
|
||||
import { LoginResponseDto, UserCredentialsDto } from '../models/dto';
|
||||
import { UserCredentialsDto } from '../models/dto';
|
||||
import { UserCredentialsRepository } from '../repositories/user-credentials.repository';
|
||||
|
||||
import { SessionService } from './session.service';
|
||||
import { TokenManagementService } from './token-management.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
public constructor(
|
||||
private readonly userCredentialsRepository: UserCredentialsRepository,
|
||||
private readonly userDataRepository: UserDataRepository,
|
||||
private readonly tokenManagementService: TokenManagementService,
|
||||
private readonly passwordConfirmationMailService: PasswordConfirmationMailService,
|
||||
private readonly emailVerificationService: EmailVerificationService,
|
||||
private readonly sessionService: SessionService
|
||||
private readonly emailVerificationService: EmailVerificationService
|
||||
) {}
|
||||
|
||||
public async signup(
|
||||
|
@ -76,39 +73,63 @@ export class AuthService {
|
|||
}
|
||||
}
|
||||
|
||||
// public async signin(
|
||||
// userCredentials: UserCredentialsDto,
|
||||
// response: Response,
|
||||
// request: Request
|
||||
// ): Promise<LoginResponseDto> {
|
||||
// const user = await this.userCredentialsRepository.findUserByEmail(
|
||||
// userCredentials.email
|
||||
// );
|
||||
public async validateUser(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<UserCredentials> {
|
||||
try {
|
||||
const user = await this.userCredentialsRepository.findUserByEmail(email);
|
||||
|
||||
// if (!user) {
|
||||
// throw new ForbiddenException('Access Denied');
|
||||
// }
|
||||
if (!user) {
|
||||
throw new ForbiddenException('Access Denied');
|
||||
}
|
||||
|
||||
// const passwordMatch = await EncryptionService.compareHash(
|
||||
// userCredentials.password,
|
||||
// user.hash
|
||||
// );
|
||||
const passwordMatch = await EncryptionService.compareHash(
|
||||
password,
|
||||
user.hashedPassword
|
||||
);
|
||||
|
||||
// if (!passwordMatch) {
|
||||
// throw new ForbiddenException('Access Denied');
|
||||
// }
|
||||
if (!passwordMatch) {
|
||||
throw new ForbiddenException('Access Denied');
|
||||
}
|
||||
|
||||
// await this.sessionService.checkSessionLimit(user.id);
|
||||
return user;
|
||||
} catch (error) {
|
||||
if (error instanceof ForbiddenException) {
|
||||
throw new ForbiddenException(
|
||||
'Die eingebenen Daten sind nicht korrekt. Bitte versuchen Sie es erneut.'
|
||||
);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'Fehler beim Login',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// const sesseionId = await this.sessionService.createSession(
|
||||
// user.id,
|
||||
// request.headers['user-agent']
|
||||
// );
|
||||
|
||||
// this.sessionService.attachSessionToResponse(response, sesseionId.sessionId);
|
||||
|
||||
// return this.generateAndPersistTokens(user.id, user.email, true);
|
||||
// }
|
||||
public async signin(userCredentials: UserCredentialsDto): Promise<void> {
|
||||
// const user = await this.userCredentialsRepository.findUserByEmail(
|
||||
// userCredentials.email
|
||||
// );
|
||||
// if (!user) {
|
||||
// throw new ForbiddenException('Access Denied');
|
||||
// }
|
||||
// const passwordMatch = await EncryptionService.compareHash(
|
||||
// userCredentials.password,
|
||||
// user.hash
|
||||
// );
|
||||
// if (!passwordMatch) {
|
||||
// throw new ForbiddenException('Access Denied');
|
||||
// }
|
||||
// await this.sessionService.checkSessionLimit(user.id);
|
||||
// const sesseionId = await this.sessionService.createSession(
|
||||
// user.id,
|
||||
// request.headers['user-agent']
|
||||
// );
|
||||
// this.sessionService.attachSessionToResponse(response, sesseionId.sessionId);
|
||||
// return this.generateAndPersistTokens(user.id, user.email, true);
|
||||
}
|
||||
|
||||
// public async logout(userId: string): Promise<boolean> {
|
||||
// const affected =
|
||||
|
@ -157,17 +178,17 @@ export class AuthService {
|
|||
// return { access_token: newTokens.access_token };
|
||||
// }
|
||||
|
||||
private async generateAndPersistTokens(
|
||||
userId: string,
|
||||
email: string
|
||||
): Promise<LoginResponseDto> {
|
||||
const tokens = await this.tokenManagementService.generateTokens(
|
||||
userId,
|
||||
email
|
||||
);
|
||||
// private async generateAndPersistTokens(
|
||||
// userId: string,
|
||||
// email: string
|
||||
// ): Promise<LoginResponseDto> {
|
||||
// const tokens = await this.tokenManagementService.generateTokens(
|
||||
// userId,
|
||||
// email
|
||||
// );
|
||||
|
||||
return { access_token: tokens.access_token, email: email, userId: userId };
|
||||
}
|
||||
// return { access_token: tokens.access_token, email: email, userId: userId };
|
||||
// }
|
||||
|
||||
// private async validateRefreshToken(userId: string): Promise<TokenPayload> {
|
||||
// const user = await this.userCredentialsRepository.findUserById(userId);
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { Session } from 'src/entities';
|
||||
import { DeleteResult } from 'typeorm';
|
||||
|
||||
import { SessionRepository } from '../repositories/session.repository';
|
||||
|
||||
@Injectable()
|
||||
export class SessionService {
|
||||
public constructor(private readonly sessionRepository: SessionRepository) {}
|
||||
|
||||
public createSession(userId: string, userAgent: string): Promise<Session> {
|
||||
return this.sessionRepository.createSession(userId, userAgent);
|
||||
}
|
||||
|
||||
public validateSessionUserAgent(
|
||||
sessionId: string,
|
||||
currentUserAgent: string
|
||||
): Promise<boolean> {
|
||||
return this.sessionRepository.validateSessionUserAgent(
|
||||
sessionId,
|
||||
currentUserAgent
|
||||
);
|
||||
}
|
||||
|
||||
public checkSessionLimit(userId: string): Promise<DeleteResult> {
|
||||
return this.sessionRepository.checkSessionLimit(userId);
|
||||
}
|
||||
|
||||
public invalidateAllSessionsForUser(userId: string): Promise<DeleteResult> {
|
||||
return this.sessionRepository.invalidateAllSessionsForUser(userId);
|
||||
}
|
||||
|
||||
public clearExpiredSessions(): Promise<DeleteResult> {
|
||||
return this.sessionRepository.clearExpiredSessions();
|
||||
}
|
||||
|
||||
public extendSessionExpiration(sessionId: string): Promise<Session> {
|
||||
return this.sessionRepository.extendSessionExpiration(sessionId);
|
||||
}
|
||||
|
||||
public findSessionBySessionId(sessionId: string): Promise<Session> {
|
||||
return this.sessionRepository.findSessionBySessionId(sessionId);
|
||||
}
|
||||
|
||||
public findSessionByUserId(userId: string): Promise<Session[]> {
|
||||
return this.sessionRepository.findSessionByUserId(userId);
|
||||
}
|
||||
|
||||
public attachSessionToResponse(response: Response, sessionId: string): void {
|
||||
this.sessionRepository.attachSessionToResponse(response, sessionId);
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
import { AccessTokenDto } from '../models/dto';
|
||||
import { TokenPayload } from '../models/types';
|
||||
|
||||
@Injectable()
|
||||
export class TokenManagementService {
|
||||
private readonly ACCESS_TOKEN_EXPIRY: string;
|
||||
private readonly JWT_SECRET_AT: string;
|
||||
|
||||
public constructor(
|
||||
private readonly jwt: JwtService,
|
||||
private readonly configService: ConfigService
|
||||
) {
|
||||
this.ACCESS_TOKEN_EXPIRY = this.configService.get<string>(
|
||||
'ACCESS_TOKEN_EXPIRY'
|
||||
);
|
||||
this.JWT_SECRET_AT = this.configService.get<string>('JWT_SECRET_AT');
|
||||
}
|
||||
|
||||
public async generateTokens(
|
||||
userId: string,
|
||||
email: string
|
||||
): Promise<AccessTokenDto> {
|
||||
const access_token: string = await this.createAccessToken(userId, email);
|
||||
|
||||
return { access_token };
|
||||
}
|
||||
|
||||
public async verifyRefreshToken(token: string): Promise<TokenPayload> {
|
||||
return this.jwt.verifyAsync(token, {
|
||||
secret: this.JWT_SECRET_AT,
|
||||
});
|
||||
}
|
||||
|
||||
private async createAccessToken(
|
||||
userId: string,
|
||||
email: string
|
||||
): Promise<string> {
|
||||
return this.jwt.signAsync(
|
||||
{ sub: userId, email },
|
||||
{
|
||||
expiresIn: this.ACCESS_TOKEN_EXPIRY,
|
||||
secret: this.JWT_SECRET_AT,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy } from 'passport-local';
|
||||
import { UserCredentials } from 'src/entities';
|
||||
import { SessionService } from 'src/modules/session/services/session.service';
|
||||
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
public constructor(
|
||||
private readonly authService: AuthService,
|
||||
private readonly sessionService: SessionService
|
||||
) {
|
||||
super({ usernameField: 'email', passwordField: 'password' });
|
||||
}
|
||||
|
||||
public async validate(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<UserCredentials> {
|
||||
const user = await this.authService.validateUser(email, password);
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
await this.sessionService.enforceSessionLimit(user.id);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Session } from 'src/entities';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class SessionRepository {
|
||||
public constructor(
|
||||
@InjectRepository(Session)
|
||||
private readonly repository: Repository<Session>
|
||||
) {}
|
||||
|
||||
public async findSessionByUserId(id: string): Promise<Session | undefined> {
|
||||
return this.repository.findOne({ where: { id } });
|
||||
}
|
||||
|
||||
// TODO Fix select()
|
||||
public async findUserIdBySessionId(id: string): Promise<string | undefined> {
|
||||
return this.repository
|
||||
.createQueryBuilder('session')
|
||||
.select('session.json::jsonb -> "passport" -> "user" ->> "id"', 'userId')
|
||||
.where('session.id = :id', { id: id })
|
||||
.getRawOne();
|
||||
}
|
||||
|
||||
public async deleteAllExpiredSessions(): Promise<void> {
|
||||
const currentTime = Date.now();
|
||||
|
||||
await this.repository
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(Session)
|
||||
.where('expiredAt < :currentTime', { currentTime })
|
||||
.execute();
|
||||
}
|
||||
|
||||
// TODO Fix where()
|
||||
public async deleteAllSessionsForUser(userId: string): Promise<void> {
|
||||
await this.repository
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(Session)
|
||||
.where('json::jsonb -> "passport" -> "user" ->> "id" = :userId', {
|
||||
userId,
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
public async enforceSessionLimit(userId: string): Promise<void> {
|
||||
const sessions = await this.repository
|
||||
.createQueryBuilder('session')
|
||||
.withDeleted()
|
||||
.where('session.json ::jsonb @> :jsonFilter', {
|
||||
jsonFilter: { passport: { user: { id: userId } } },
|
||||
})
|
||||
.orderBy('session.expiredAt', 'ASC')
|
||||
.getMany();
|
||||
|
||||
if (sessions.length >= 5) {
|
||||
const sessionsToDelete = sessions.slice(0, sessions.length - 5);
|
||||
|
||||
await this.repository.remove(sessionsToDelete);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './session-init.service';
|
||||
export * from './session-serializer.service';
|
|
@ -0,0 +1,37 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TypeormStore } from 'connect-typeorm';
|
||||
import { RequestHandler } from 'express';
|
||||
import * as session from 'express-session';
|
||||
import { Session } from 'src/entities';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class SessionInitService {
|
||||
public constructor(
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly configService: ConfigService
|
||||
) {}
|
||||
|
||||
public initSession(): RequestHandler<unknown> {
|
||||
return session({
|
||||
secret: [this.configService.get<string>('SESSION_SECRET')],
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: new TypeormStore({
|
||||
cleanupLimit: 2,
|
||||
limitSubquery: false,
|
||||
ttl: 86400,
|
||||
}).connect(this.dataSource.getRepository(Session)),
|
||||
cookie: {
|
||||
maxAge: 86400000,
|
||||
httpOnly: true,
|
||||
secure:
|
||||
this.configService.get<string>('NODE_ENV') === 'production'
|
||||
? true
|
||||
: false,
|
||||
sameSite: 'strict',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportSerializer } from '@nestjs/passport';
|
||||
import { UserCredentials } from 'src/entities';
|
||||
|
||||
@Injectable()
|
||||
export class SessionSerializerService extends PassportSerializer {
|
||||
public serializeUser(
|
||||
user: UserCredentials,
|
||||
done: (err: Error, user: any) => void
|
||||
): void {
|
||||
done(null, { id: user.id });
|
||||
}
|
||||
|
||||
public deserializeUser(
|
||||
payload: any,
|
||||
done: (err: Error, payload: string) => void
|
||||
): void {
|
||||
done(null, payload);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { UriEncoderService } from 'src/shared';
|
||||
|
||||
import { SessionRepository } from '../repository/session.repository';
|
||||
|
||||
@Injectable()
|
||||
export class SessionService {
|
||||
public constructor(private readonly sessionRepository: SessionRepository) {}
|
||||
|
||||
public async enforceSessionLimit(userId: string): Promise<void> {
|
||||
return this.sessionRepository.enforceSessionLimit(userId);
|
||||
}
|
||||
|
||||
public async deleteAllExpiredSessions(): Promise<void> {
|
||||
return this.sessionRepository.deleteAllExpiredSessions();
|
||||
}
|
||||
|
||||
private extractSessionIdFromCookie(cookie: string): string | null {
|
||||
try {
|
||||
const decodedCookie = UriEncoderService.decodeUri(cookie);
|
||||
const sessionIdPart = decodedCookie.split('.')[0];
|
||||
|
||||
if (sessionIdPart.startsWith('s:')) {
|
||||
return sessionIdPart.substring(2);
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Extrahieren der Session-ID:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Session } from 'src/entities';
|
||||
|
||||
import { SessionRepository } from './repository/session.repository';
|
||||
import { SessionInitService, SessionSerializerService } from './services';
|
||||
import { SessionService } from './services/session.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Session])],
|
||||
providers: [
|
||||
SessionInitService,
|
||||
SessionSerializerService,
|
||||
SessionRepository,
|
||||
SessionService,
|
||||
],
|
||||
controllers: [],
|
||||
exports: [SessionService],
|
||||
})
|
||||
export class SessionModule {}
|
Loading…
Reference in New Issue