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