introduce an authentication service and prepare user management
This commit is contained in:
		| @@ -11,13 +11,13 @@ import { ApiQuery } from '@nestjs/swagger'; | |||||||
| import { ConfigService } from '@nestjs/config'; | import { ConfigService } from '@nestjs/config'; | ||||||
| import { JwtService } from '@nestjs/jwt'; | import { JwtService } from '@nestjs/jwt'; | ||||||
| import { catchError, lastValueFrom, map } from 'rxjs'; | import { catchError, lastValueFrom, map } from 'rxjs'; | ||||||
|  | import { AuthService } from './auth.service'; | ||||||
|  |  | ||||||
| @Controller('auth') | @Controller('auth') | ||||||
| export class AuthController { | export class AuthController { | ||||||
|   constructor( |   constructor( | ||||||
|     private config: ConfigService, |     private config: ConfigService, | ||||||
|     private httpService: HttpService, |     private authService: AuthService, | ||||||
|     private jwtService: JwtService, |  | ||||||
|   ) {} |   ) {} | ||||||
|  |  | ||||||
|   @Get('/vatsim') |   @Get('/vatsim') | ||||||
| @@ -35,58 +35,15 @@ export class AuthController { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const token = await lastValueFrom( |     const token = await this.authService.login(code); | ||||||
|       this.httpService |  | ||||||
|         .post( |  | ||||||
|           `${this.config.get<string>( |  | ||||||
|             'vatsim-auth.base-url', |  | ||||||
|           )}/${this.config.get<string>('vatsim-auth.token-endpoint')}`, |  | ||||||
|           { |  | ||||||
|             grant_type: 'authorization_code', |  | ||||||
|             client_id: this.config.get<string>('vatsim-auth.client-id'), |  | ||||||
|             client_secret: this.config.get<string>('vatsim-auth.client-secret'), |  | ||||||
|             redirect_uri: 'http://localhost:3000/auth/vatsim', |  | ||||||
|             code, |  | ||||||
|           }, |  | ||||||
|         ) |  | ||||||
|         .pipe( |  | ||||||
|           map((response) => response.data.access_token), |  | ||||||
|           catchError((err) => { |  | ||||||
|             throw new HttpException(err.response.data, err.response.status); |  | ||||||
|           }), |  | ||||||
|         ), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     const userdata = await lastValueFrom( |     if (token !== undefined) { | ||||||
|       this.httpService |  | ||||||
|         .get( |  | ||||||
|           `${this.config.get<string>( |  | ||||||
|             'vatsim-auth.base-url', |  | ||||||
|           )}/${this.config.get<string>('vatsim-auth.user-endpoint')}`, |  | ||||||
|           { |  | ||||||
|             headers: { |  | ||||||
|               Authorization: `Bearer ${token}`, |  | ||||||
|               Accept: 'application/json', |  | ||||||
|             }, |  | ||||||
|           }, |  | ||||||
|         ) |  | ||||||
|         .pipe( |  | ||||||
|           map((response) => response.data.data), |  | ||||||
|           catchError((err) => { |  | ||||||
|             throw new HttpException(err.response.data, err.response.status); |  | ||||||
|           }), |  | ||||||
|         ), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     if (userdata.oauth.token_valid) { |  | ||||||
|       const payload = { username: userdata.cid, sub: token }; |  | ||||||
|       const accessToken = this.jwtService.sign(payload); |  | ||||||
|       return { |       return { | ||||||
|         url: `${this.config.get<string>( |         url: `${this.config.get<string>( | ||||||
|           'frontend.base-url', |           'frontend.base-url', | ||||||
|         )}/${this.config.get<string>( |         )}/${this.config.get<string>( | ||||||
|           'frontend.login-endpoint', |           'frontend.login-endpoint', | ||||||
|         )}?token=${accessToken}`, |         )}?token=${token}`, | ||||||
|       }; |       }; | ||||||
|     } else { |     } else { | ||||||
|       return { |       return { | ||||||
|   | |||||||
| @@ -2,8 +2,11 @@ import { HttpModule } from '@nestjs/axios'; | |||||||
| import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||||
| import { ConfigService } from '@nestjs/config'; | import { ConfigService } from '@nestjs/config'; | ||||||
| import { JwtModule } from '@nestjs/jwt'; | import { JwtModule } from '@nestjs/jwt'; | ||||||
|  | import { MongooseModule } from '@nestjs/mongoose'; | ||||||
| import { AuthController } from './auth.controller'; | import { AuthController } from './auth.controller'; | ||||||
|  | import { UserSchema } from './models/user.model'; | ||||||
| import { JwtStrategy } from './strategies/jwt.strategy'; | import { JwtStrategy } from './strategies/jwt.strategy'; | ||||||
|  | import { AuthService } from './auth.service'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ |   imports: [ | ||||||
| @@ -15,8 +18,9 @@ import { JwtStrategy } from './strategies/jwt.strategy'; | |||||||
|         signOptions: { expiresIn: '1h' }, |         signOptions: { expiresIn: '1h' }, | ||||||
|       }), |       }), | ||||||
|     }), |     }), | ||||||
|  |     MongooseModule.forFeature([{ name: 'user', schema: UserSchema }]), | ||||||
|   ], |   ], | ||||||
|   providers: [JwtStrategy], |   providers: [JwtStrategy, AuthService], | ||||||
|   controllers: [AuthController], |   controllers: [AuthController], | ||||||
| }) | }) | ||||||
| export class AuthModule {} | export class AuthModule {} | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								src/auth/auth.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/auth/auth.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | import { Test, TestingModule } from '@nestjs/testing'; | ||||||
|  | import { AuthService } from './auth.service'; | ||||||
|  |  | ||||||
|  | describe('AuthService', () => { | ||||||
|  |   let service: AuthService; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     const module: TestingModule = await Test.createTestingModule({ | ||||||
|  |       providers: [AuthService], | ||||||
|  |     }).compile(); | ||||||
|  |  | ||||||
|  |     service = module.get<AuthService>(AuthService); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should be defined', () => { | ||||||
|  |     expect(service).toBeDefined(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										72
									
								
								src/auth/auth.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/auth/auth.service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | import { HttpService } from '@nestjs/axios'; | ||||||
|  | import { HttpException, Injectable } from '@nestjs/common'; | ||||||
|  | import { ConfigService } from '@nestjs/config'; | ||||||
|  | import { JwtService } from '@nestjs/jwt'; | ||||||
|  | import { InjectModel } from '@nestjs/mongoose'; | ||||||
|  | import { Model } from 'mongoose'; | ||||||
|  | import { catchError, lastValueFrom, map } from 'rxjs'; | ||||||
|  |  | ||||||
|  | import { UserDocument } from './models/user.model'; | ||||||
|  |  | ||||||
|  | @Injectable() | ||||||
|  | export class AuthService { | ||||||
|  |   constructor( | ||||||
|  |     @InjectModel('user') | ||||||
|  |     private readonly userModel: Model<UserDocument>, | ||||||
|  |     private config: ConfigService, | ||||||
|  |     private httpService: HttpService, | ||||||
|  |     private jwtService: JwtService, | ||||||
|  |   ) {} | ||||||
|  |  | ||||||
|  |   async login(code: string): Promise<string> { | ||||||
|  |     const token = await lastValueFrom( | ||||||
|  |       this.httpService | ||||||
|  |         .post( | ||||||
|  |           `${this.config.get<string>( | ||||||
|  |             'vatsim-auth.base-url', | ||||||
|  |           )}/${this.config.get<string>('vatsim-auth.token-endpoint')}`, | ||||||
|  |           { | ||||||
|  |             grant_type: 'authorization_code', | ||||||
|  |             client_id: this.config.get<string>('vatsim-auth.client-id'), | ||||||
|  |             client_secret: this.config.get<string>('vatsim-auth.client-secret'), | ||||||
|  |             redirect_uri: 'http://localhost:3000/auth/vatsim', | ||||||
|  |             code, | ||||||
|  |           }, | ||||||
|  |         ) | ||||||
|  |         .pipe( | ||||||
|  |           map((response) => response.data.access_token), | ||||||
|  |           catchError((err) => { | ||||||
|  |             throw new HttpException(err.response.data, err.response.status); | ||||||
|  |           }), | ||||||
|  |         ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     const userdata = await lastValueFrom( | ||||||
|  |       this.httpService | ||||||
|  |         .get( | ||||||
|  |           `${this.config.get<string>( | ||||||
|  |             'vatsim-auth.base-url', | ||||||
|  |           )}/${this.config.get<string>('vatsim-auth.user-endpoint')}`, | ||||||
|  |           { | ||||||
|  |             headers: { | ||||||
|  |               Authorization: `Bearer ${token}`, | ||||||
|  |               Accept: 'application/json', | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ) | ||||||
|  |         .pipe( | ||||||
|  |           map((response) => response.data.data), | ||||||
|  |           catchError((err) => { | ||||||
|  |             throw new HttpException(err.response.data, err.response.status); | ||||||
|  |           }), | ||||||
|  |         ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     if (userdata.oauth.token_valid) { | ||||||
|  |       const payload = { username: userdata.cid, sub: token }; | ||||||
|  |       return this.jwtService.sign(payload); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return undefined; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								src/auth/models/user.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/auth/models/user.model.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; | ||||||
|  | import { Document } from 'mongoose'; | ||||||
|  |  | ||||||
|  | export type UserDocument = User & Document; | ||||||
|  |  | ||||||
|  | @Schema() | ||||||
|  | export class User { | ||||||
|  |   @Prop({ | ||||||
|  |     required: true, | ||||||
|  |     index: true, | ||||||
|  |     type: String, | ||||||
|  |   }) | ||||||
|  |   vatsimId: string; | ||||||
|  |  | ||||||
|  |   @Prop({ | ||||||
|  |     type: String, | ||||||
|  |   }) | ||||||
|  |   fullName: string; | ||||||
|  |  | ||||||
|  |   @Prop({ | ||||||
|  |     required: true, | ||||||
|  |     type: String, | ||||||
|  |   }) | ||||||
|  |   vatsimToken: string; | ||||||
|  |  | ||||||
|  |   @Prop({ | ||||||
|  |     type: String, | ||||||
|  |   }) | ||||||
|  |   vatsimRefreshToken: string; | ||||||
|  |  | ||||||
|  |   @Prop({ | ||||||
|  |     type: Boolean, | ||||||
|  |     default: false, | ||||||
|  |   }) | ||||||
|  |   administrator: boolean; | ||||||
|  |  | ||||||
|  |   @Prop({ | ||||||
|  |     type: [String], | ||||||
|  |     default: [], | ||||||
|  |   }) | ||||||
|  |   airportConfigurationAccess: string[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const UserSchema = SchemaFactory.createForClass(User); | ||||||
		Reference in New Issue
	
	Block a user