Browse Source

introduce an authentication service and prepare user management

Sven Czarnian 2 years ago
parent
commit
58b98456ac

+ 5 - 48
src/auth/auth.controller.ts

@@ -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 {

+ 5 - 1
src/auth/auth.module.ts

@@ -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 - 0
src/auth/auth.service.spec.ts

@@ -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 - 0
src/auth/auth.service.ts

@@ -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 - 0
src/auth/models/user.model.ts

@@ -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);