diff --git a/api/package.json b/api/package.json index 21ce699..bc5a860 100644 --- a/api/package.json +++ b/api/package.json @@ -19,13 +19,18 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", + "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.2", + "class-transformer": "catalog:", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "pg": "^8.12.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", - "typeorm": "catalog:", - "class-transformer": "catalog:" + "typeorm": "catalog:" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -34,6 +39,8 @@ "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", + "@types/passport-jwt": "^4.0.1", + "@types/passport-local": "^1.0.38", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 3ffdb42..ed979d3 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -2,10 +2,13 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ApiConfigModule } from '@api/modules/config/app-config.module'; +import { APP_GUARD } from '@nestjs/core'; +import { JwtAuthGuard } from '@auth/guards/jwt-auth.guard'; +import { AuthModule } from '@auth/auth.module'; @Module({ - imports: [ApiConfigModule], + imports: [ApiConfigModule, AuthModule], controllers: [AppController], - providers: [AppService], + providers: [AppService, { provide: APP_GUARD, useClass: JwtAuthGuard }], }) export class AppModule {} diff --git a/api/src/main.ts b/api/src/main.ts index b65213c..cc16479 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -3,6 +3,7 @@ import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.enableCors(); await app.listen(4000); } void bootstrap(); diff --git a/api/src/modules/auth/auth.module.ts b/api/src/modules/auth/auth.module.ts new file mode 100644 index 0000000..fcd2d33 --- /dev/null +++ b/api/src/modules/auth/auth.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AuthenticationModule } from '@auth/authentication/authentication.module'; +import { AuthorisationModule } from '@auth/authorisation/authorisation.module'; + +@Module({ + imports: [AuthenticationModule, AuthorisationModule], + controllers: [], + providers: [], +}) +export class AuthModule {} diff --git a/api/src/modules/auth/authentication/authentication.module.ts b/api/src/modules/auth/authentication/authentication.module.ts index 0c26a9b..ed06daa 100644 --- a/api/src/modules/auth/authentication/authentication.module.ts +++ b/api/src/modules/auth/authentication/authentication.module.ts @@ -1,9 +1,37 @@ import { Module } from '@nestjs/common'; import { AuthenticationService } from './authentication.service'; import { AuthenticationController } from './authentication.controller'; +import { PassportModule } from '@nestjs/passport'; +import { JwtModule } from '@nestjs/jwt'; +import { ApiConfigModule } from '@api/modules/config/app-config.module'; +import { ApiConfigService } from '@api/modules/config/app-config.service'; +import { JwtStrategy } from '@auth/strategies/jwt.strategy'; +import { UsersService } from '@api/modules/users/users.service'; +import { UsersModule } from '@api/modules/users/users.module'; @Module({ - providers: [AuthenticationService], + imports: [ + PassportModule.register({ defaultStrategy: 'jwt' }), + JwtModule.registerAsync({ + imports: [ApiConfigModule], + inject: [ApiConfigService], + useFactory: (config: ApiConfigService) => ({ + secret: config.getJWTConfig().secret, + signOptions: { expiresIn: config.getJWTConfig().expiresIn }, + }), + }), + UsersModule, + ], + providers: [ + AuthenticationService, + { + provide: JwtStrategy, + useFactory: (users: UsersService, config: ApiConfigService) => { + return new JwtStrategy(users, config); + }, + inject: [UsersService, ApiConfigService], + }, + ], controllers: [AuthenticationController], }) export class AuthenticationModule {} diff --git a/api/src/modules/auth/decorators/get-user.decorator.ts b/api/src/modules/auth/decorators/get-user.decorator.ts new file mode 100644 index 0000000..20111a0 --- /dev/null +++ b/api/src/modules/auth/decorators/get-user.decorator.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { User } from '@shared/entities/users/user.entity'; + +export const GetUser = createParamDecorator( + (data: unknown, ctx: ExecutionContext): User => { + const request = ctx.switchToHttp().getRequest(); + return request.user; + }, +); diff --git a/api/src/modules/auth/decorators/is-public.decorator.ts b/api/src/modules/auth/decorators/is-public.decorator.ts new file mode 100644 index 0000000..c0ac137 --- /dev/null +++ b/api/src/modules/auth/decorators/is-public.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common'; + +/** + * @description Decorator to inject a IS_PUBLIC_KEY metadata to the handler, which will be read by the JwtAuthGuard to allow public access to the handler. + */ + +export const IS_PUBLIC_KEY = 'isPublic'; +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/api/src/modules/auth/guards/jwt-auth.guard.ts b/api/src/modules/auth/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..5e0ea14 --- /dev/null +++ b/api/src/modules/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,27 @@ +import { Injectable, ExecutionContext } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { Reflector } from '@nestjs/core'; +import { Observable } from 'rxjs'; +import { IS_PUBLIC_KEY } from '@auth/decorators/is-public.decorator'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + constructor(private readonly reflector: Reflector) { + super(); + } + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + const isPublic: boolean = this.reflector.get( + IS_PUBLIC_KEY, + context.getHandler(), + ); + + if (isPublic) { + return true; + } + + return super.canActivate(context); + } +} diff --git a/api/src/modules/auth/strategies/jwt.strategy.ts b/api/src/modules/auth/strategies/jwt.strategy.ts new file mode 100644 index 0000000..e35a8a6 --- /dev/null +++ b/api/src/modules/auth/strategies/jwt.strategy.ts @@ -0,0 +1,30 @@ +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { UsersService } from '@api/modules/users/users.service'; +import { ApiConfigService } from '@api/modules/config/app-config.service'; + +export type JwtPayload = { id: string }; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor( + private readonly userService: UsersService, + private readonly config: ApiConfigService, + ) { + const { secret } = config.getJWTConfig(); + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: secret, + }); + } + + async validate(payload: JwtPayload) { + const { id } = payload; + const user = await this.userService.findOneBy(id); + if (!user) { + throw new UnauthorizedException(); + } + return user; + } +} diff --git a/api/src/modules/auth/strategies/local.strategy.ts b/api/src/modules/auth/strategies/local.strategy.ts new file mode 100644 index 0000000..88cded0 --- /dev/null +++ b/api/src/modules/auth/strategies/local.strategy.ts @@ -0,0 +1,29 @@ +// import { Injectable, UnauthorizedException } from '@nestjs/common'; +// import { PassportStrategy } from '@nestjs/passport'; +// +// import { Strategy } from 'passport-local'; +// import { User } from '@shared/dto/users/user.entity'; +// import { AuthService } from '@api/modules/auth/auth.service'; +// +// /** +// * @description: LocalStrategy is used by passport to authenticate by email and password rather than a token. +// */ +// +// @Injectable() +// export class LocalStrategy extends PassportStrategy(Strategy) { +// constructor(private readonly authService: AuthService) { +// super({ usernameField: 'email' }); +// } +// +// async validate(email: string, password: string): Promise { +// const user: User | null = await this.authService.validateUser( +// email, +// password, +// ); +// +// if (!user) { +// throw new UnauthorizedException(); +// } +// return user; +// } +// } diff --git a/api/src/modules/config/app-config.service.ts b/api/src/modules/config/app-config.service.ts index b69e6fd..1fec47e 100644 --- a/api/src/modules/config/app-config.service.ts +++ b/api/src/modules/config/app-config.service.ts @@ -2,6 +2,11 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { DATABASE_ENTITIES } from '@shared/entities/database.entities'; +export type JWTConfig = { + secret: string; + expiresIn: string; +}; + @Injectable() export class ApiConfigService { constructor(private configService: ConfigService) {} @@ -30,4 +35,11 @@ export class ApiConfigService { private isProduction(): boolean { return this.configService.get('NODE_ENV') === 'production'; } + + getJWTConfig(): JWTConfig { + return { + secret: this.configService.get('JWT_SECRET'), + expiresIn: this.configService.get('JWT_EXPIRES_IN'), + }; + } } diff --git a/api/src/modules/users/users.module.ts b/api/src/modules/users/users.module.ts index 44f028e..2399f95 100644 --- a/api/src/modules/users/users.module.ts +++ b/api/src/modules/users/users.module.ts @@ -6,5 +6,6 @@ import { User } from '@shared/entities/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], + exports: [UsersService], }) export class UsersModule {} diff --git a/api/src/modules/users/users.service.ts b/api/src/modules/users/users.service.ts index ef0d82d..815746f 100644 --- a/api/src/modules/users/users.service.ts +++ b/api/src/modules/users/users.service.ts @@ -1,4 +1,15 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { User } from '@shared/entities/users/user.entity'; +import { Repository } from 'typeorm'; @Injectable() -export class UsersService {} +export class UsersService { + constructor(@InjectRepository(User) private repo: Repository) {} + + async findOneBy(id: string) { + return this.repo.findOne({ + where: { id }, + }); + } +} diff --git a/api/tsconfig.json b/api/tsconfig.json index 23987cd..acf1e7a 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -17,6 +17,6 @@ "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false, - "resolveJsonModule": true + "resolveJsonModule": true, } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index efd3f6d..b30a7de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,12 @@ importers: '@nestjs/core': specifier: ^10.0.0 version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + '@nestjs/passport': + specifier: ^10.0.3 + version: 10.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) '@nestjs/platform-express': specifier: ^10.0.0 version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) @@ -43,6 +49,15 @@ importers: class-transformer: specifier: 'catalog:' version: 0.5.1 + 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.12.0 version: 8.12.0 @@ -74,6 +89,12 @@ importers: '@types/node': specifier: ^20.3.1 version: 20.16.5 + '@types/passport-jwt': + specifier: ^4.0.1 + version: 4.0.1 + '@types/passport-local': + specifier: ^1.0.38 + version: 1.0.38 '@types/supertest': specifier: ^6.0.0 version: 6.0.2 @@ -568,6 +589,17 @@ packages: '@nestjs/websockets': optional: true + '@nestjs/jwt@10.2.0': + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + + '@nestjs/passport@10.0.3': + resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + '@nestjs/platform-express@10.4.1': resolution: {integrity: sha512-ccfqIDAq/bg1ShLI5KGtaLaYGykuAdvCi57ohewH7eKJSIpWY1DQjbgKlFfXokALYUq1YOMGqjeZ244OWHfDQg==} peerDependencies: @@ -780,6 +812,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.5': + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} @@ -789,6 +824,18 @@ packages: '@types/node@20.16.5': resolution: {integrity: sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==} + '@types/passport-jwt@4.0.1': + resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} + + '@types/passport-local@1.0.38': + resolution: {integrity: sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==} + + '@types/passport-strategy@0.2.38': + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} + + '@types/passport@1.0.16': + resolution: {integrity: sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A==} + '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -1231,6 +1278,9 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1581,6 +1631,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2503,10 +2556,20 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2552,12 +2615,33 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -2860,6 +2944,21 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + + passport-local@1.0.0: + resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} + engines: {node: '>= 0.4.0'} + + passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + + passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2889,6 +2988,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} @@ -4398,6 +4500,17 @@ snapshots: transitivePeerDependencies: - encoding + '@nestjs/jwt@10.2.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))': + dependencies: + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@types/jsonwebtoken': 9.0.5 + jsonwebtoken: 9.0.2 + + '@nestjs/passport@10.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0)': + dependencies: + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + passport: 0.7.0 + '@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -4613,6 +4726,10 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.5': + dependencies: + '@types/node': 20.16.5 + '@types/methods@1.1.4': {} '@types/mime@1.3.5': {} @@ -4621,6 +4738,26 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/passport-jwt@4.0.1': + dependencies: + '@types/jsonwebtoken': 9.0.5 + '@types/passport-strategy': 0.2.38 + + '@types/passport-local@1.0.38': + dependencies: + '@types/express': 4.17.21 + '@types/passport': 1.0.16 + '@types/passport-strategy': 0.2.38 + + '@types/passport-strategy@0.2.38': + dependencies: + '@types/express': 4.17.21 + '@types/passport': 1.0.16 + + '@types/passport@1.0.16': + dependencies: + '@types/express': 4.17.21 + '@types/prop-types@15.7.12': {} '@types/qs@6.9.15': {} @@ -5205,6 +5342,8 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -5537,6 +5676,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} ejs@3.1.10: @@ -6865,6 +7008,19 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.3 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -6872,6 +7028,17 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -6907,10 +7074,24 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash@4.17.21: {} log-symbols@4.1.0: @@ -7199,6 +7380,23 @@ snapshots: parseurl@1.3.3: {} + passport-jwt@4.0.1: + dependencies: + jsonwebtoken: 9.0.2 + passport-strategy: 1.0.0 + + passport-local@1.0.0: + dependencies: + passport-strategy: 1.0.0 + + passport-strategy@1.0.0: {} + + passport@0.7.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -7218,6 +7416,8 @@ snapshots: path-type@4.0.0: {} + pause@0.0.1: {} + pg-cloudflare@1.1.1: optional: true diff --git a/shared/config/.env.test b/shared/config/.env.test index 5a764f9..5e2ac7f 100644 --- a/shared/config/.env.test +++ b/shared/config/.env.test @@ -3,4 +3,5 @@ DB_PORT=5432 DB_NAME=blc DB_USERNAME=blue-carbon-cost DB_PASSWORD=blue-carbon-cost -JWT_SECRET=mysecret \ No newline at end of file +JWT_SECRET=mysecret +JWT_EXPIRES_IN=1d \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 0ab4213..a6b76c5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "@api/*": ["./api/src/*"], "@client/*": ["./client/*"], "@shared/*": ["./shared/*"], + "@auth/*": ["./api/src/modules/auth/*"], } } } \ No newline at end of file