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 { JwtService } from '@nestjs/jwt'; | ||||
| import { catchError, lastValueFrom, map } from 'rxjs'; | ||||
| import { AuthService } from './auth.service'; | ||||
|  | ||||
| @Controller('auth') | ||||
| export class AuthController { | ||||
|   constructor( | ||||
|     private config: ConfigService, | ||||
|     private httpService: HttpService, | ||||
|     private jwtService: JwtService, | ||||
|     private authService: AuthService, | ||||
|   ) {} | ||||
|  | ||||
|   @Get('/vatsim') | ||||
| @@ -35,58 +35,15 @@ export class AuthController { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     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 token = await this.authService.login(code); | ||||
|  | ||||
|     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 }; | ||||
|       const accessToken = this.jwtService.sign(payload); | ||||
|     if (token !== undefined) { | ||||
|       return { | ||||
|         url: `${this.config.get<string>( | ||||
|           'frontend.base-url', | ||||
|         )}/${this.config.get<string>( | ||||
|           'frontend.login-endpoint', | ||||
|         )}?token=${accessToken}`, | ||||
|         )}?token=${token}`, | ||||
|       }; | ||||
|     } else { | ||||
|       return { | ||||
|   | ||||
| @@ -2,8 +2,11 @@ import { HttpModule } from '@nestjs/axios'; | ||||
| import { Module } from '@nestjs/common'; | ||||
| import { ConfigService } from '@nestjs/config'; | ||||
| import { JwtModule } from '@nestjs/jwt'; | ||||
| import { MongooseModule } from '@nestjs/mongoose'; | ||||
| import { AuthController } from './auth.controller'; | ||||
| import { UserSchema } from './models/user.model'; | ||||
| import { JwtStrategy } from './strategies/jwt.strategy'; | ||||
| import { AuthService } from './auth.service'; | ||||
|  | ||||
| @Module({ | ||||
|   imports: [ | ||||
| @@ -15,8 +18,9 @@ import { JwtStrategy } from './strategies/jwt.strategy'; | ||||
|         signOptions: { expiresIn: '1h' }, | ||||
|       }), | ||||
|     }), | ||||
|     MongooseModule.forFeature([{ name: 'user', schema: UserSchema }]), | ||||
|   ], | ||||
|   providers: [JwtStrategy], | ||||
|   providers: [JwtStrategy, AuthService], | ||||
|   controllers: [AuthController], | ||||
| }) | ||||
| 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