Initial commit

This commit is contained in:
2024-06-05 21:13:55 -04:00
parent 7c21aea7e3
commit 30e9a3efcb
27 changed files with 1131 additions and 88 deletions
+5
View File
@@ -0,0 +1,5 @@
export class CreateUserDto {
method: 'discord' | 'password';
username?: string;
password: string;
}
+20
View File
@@ -0,0 +1,20 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
import { ApiProperty } from '@nestjs/swagger';
export class LoginDto extends PartialType(CreateUserDto) {
constructor(id: string, global_name: string, avatar: string) {
super();
this.id = id;
this.global_name = global_name;
this.avatar = avatar;
}
@ApiProperty()
id: string;
@ApiProperty()
global_name: string;
@ApiProperty()
avatar: string;
}
+9
View File
@@ -0,0 +1,9 @@
import { Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
export type UserDocument = HydratedDocument<User>;
@Schema()
export class User {}
export const UserSchema = SchemaFactory.createForClass(User);
+20
View File
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
+46
View File
@@ -0,0 +1,46 @@
import {
Controller,
Get,
Query,
Session,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { LoginDto } from './dto/login.dto';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('auth')
@Controller()
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get('/login')
async login(
@Query('code') code: string,
@Session() session: Record<string, any>,
) {
const tokenData = await this.usersService.saveDiscordToken(code);
session.tokenData = tokenData;
const user = await this.usersService.useDiscordToken(
tokenData.token_type,
tokenData.token,
);
return user;
}
@Get('/whoami')
async remove(@Session() session: Record<string, any>): Promise<LoginDto> {
if (!session.tokenData) {
throw new HttpException(
"Session doesn't contains auth information",
HttpStatus.UNAUTHORIZED,
);
}
const user = await this.usersService.useDiscordToken(
session.tokenData.token_type,
session.tokenData.token,
);
return user;
}
}
+17
View File
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './schemas/user.entity';
@Module({
imports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
controllers: [UsersController],
providers: [UsersService],
exports: [MongooseModule],
})
export class UsersModule {
schema = UserSchema;
}
+18
View File
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
+63
View File
@@ -0,0 +1,63 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { LoginDto } from './dto/login.dto';
import { URLSearchParams } from 'url';
@Injectable()
export class UsersService {
async saveDiscordToken(code: string) {
const req = await fetch('https://discord.com/api/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: process.env.DISCORD_CLIENTID,
client_secret: process.env.DISCORD_CLIENTSECRET,
code: code,
grant_type: 'authorization_code',
redirect_uri: process.env.API_ENDPOINT + '/login',
scope: 'identify',
}),
});
if (!req.ok) {
console.error(await req.json());
throw new HttpException(
"Couldn't fetch Discord api",
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const data = await req.json();
if (!data.token_type || !data.access_token) {
console.error(data);
throw new HttpException(
'Invalid Discord API response',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const token_type = data.token_type;
const token = data.access_token;
return { token, token_type };
}
async useDiscordToken(token_type: string, token: string) {
const req2 = await fetch('https://discord.com/api/users/@me', {
headers: {
authorization: token_type + ' ' + token,
},
});
if (!req2.ok) {
throw new HttpException(
"Couldn't fetch Discord api",
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const data2 = await req2.json();
return new LoginDto(data2.id, data2.global_name, data2.avatar);
}
remove(id: number) {
return `This action removes a #${id} user`;
}
}