introduce an authentication service and prepare user management

This commit is contained in:
Sven Czarnian
2022-11-03 01:30:27 +01:00
parent ce17a29f7c
commit 58b98456ac
5 changed files with 144 additions and 49 deletions

View File

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

View File

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

View 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
View 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;
}
}

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