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