feat: remove appwrite and add nextjs backend

This commit is contained in:
Elias Schneider
2022-10-09 22:30:32 +02:00
parent 7728351158
commit 4bab33ad8a
153 changed files with 13400 additions and 2811 deletions

View File

@@ -0,0 +1,42 @@
import {
Body,
Controller,
ForbiddenException,
HttpCode,
Post,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { AuthService } from "./auth.service";
import { AuthDTO } from "./dto/auth.dto";
import { AuthRegisterDTO } from "./dto/authRegister.dto";
import { RefreshAccessTokenDTO } from "./dto/refreshAccessToken.dto";
@Controller("auth")
export class AuthController {
constructor(
private authService: AuthService,
private config: ConfigService
) {}
@Post("signUp")
signUp(@Body() dto: AuthRegisterDTO) {
if (!this.config.get("ALLOW_REGISTRATION"))
throw new ForbiddenException("Registration is not allowed");
return this.authService.signUp(dto);
}
@Post("signIn")
signIn(@Body() dto: AuthDTO) {
return this.authService.signIn(dto);
}
@Post("token")
@HttpCode(200)
async refreshAccessToken(@Body() body: RefreshAccessTokenDTO) {
const accessToken = await this.authService.refreshAccessToken(
body.refreshToken
);
return { accessToken };
}
}

View File

@@ -0,0 +1,13 @@
import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { JwtStrategy } from "./strategy/jwt.strategy";
@Module({
imports: [JwtModule.register({})],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}

View File

@@ -0,0 +1,95 @@
import {
BadRequestException,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { JwtService } from "@nestjs/jwt";
import { User } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import * as argon from "argon2";
import { PrismaService } from "src/prisma/prisma.service";
import { AuthDTO } from "./dto/auth.dto";
import { AuthRegisterDTO } from "./dto/authRegister.dto";
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
private config: ConfigService
) {}
async signUp(dto: AuthRegisterDTO) {
const hash = await argon.hash(dto.password);
try {
const user = await this.prisma.user.create({
data: {
email: dto.email,
password: hash,
},
});
const accessToken = await this.createAccessToken(user);
const refreshToken = await this.createRefreshToken(user.id);
return { accessToken, refreshToken };
} catch (e) {
if (e instanceof PrismaClientKnownRequestError) {
if (e.code == "P2002") {
throw new BadRequestException("Credentials taken");
}
}
}
}
async signIn(dto: AuthDTO) {
const user = await this.prisma.user.findUnique({
where: {
email: dto.email,
},
});
if (!user || !(await argon.verify(user.password, dto.password)))
throw new UnauthorizedException("Wrong email or password");
const accessToken = await this.createAccessToken(user);
const refreshToken = await this.createRefreshToken(user.id);
return { accessToken, refreshToken };
}
async createAccessToken(user: User) {
return this.jwtService.sign(
{
sub: user.id,
email: user.email,
},
{
expiresIn: "15min",
secret: this.config.get("JWT_SECRET"),
}
);
}
async refreshAccessToken(refreshToken: string) {
const refreshTokenMetaData = await this.prisma.refreshToken.findUnique({
where: { token: refreshToken },
include: { user: true },
});
if (!refreshTokenMetaData || refreshTokenMetaData.expiresAt < new Date())
throw new UnauthorizedException();
return this.createAccessToken(refreshTokenMetaData.user);
}
async createRefreshToken(userId: string) {
const refreshToken = (
await this.prisma.refreshToken.create({ data: { userId } })
).token;
return refreshToken;
}
}

View File

@@ -0,0 +1,9 @@
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const GetUser = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
}
);

View File

@@ -0,0 +1,26 @@
import { Expose, plainToClass } from "class-transformer";
import { IsEmail, IsNotEmpty, IsString } from "class-validator";
export class AuthDTO {
@Expose()
id: string;
@Expose()
firstName: string;
@Expose()
lastName: string;
@Expose()
@IsNotEmpty()
@IsEmail()
email: string;
@IsNotEmpty()
@IsString()
password: string;
constructor(partial: Partial<AuthDTO>) {
return plainToClass(AuthDTO, partial, { excludeExtraneousValues: true });
}
}

View File

@@ -0,0 +1,4 @@
import { PickType } from "@nestjs/swagger";
import { AuthDTO } from "./auth.dto";
export class AuthRegisterDTO extends AuthDTO {}

View File

@@ -0,0 +1,7 @@
import { PickType } from "@nestjs/swagger";
import { AuthDTO } from "./auth.dto";
export class AuthSignInDTO extends PickType(AuthDTO, [
"email",
"password",
] as const) {}

View File

@@ -0,0 +1,6 @@
import { IsNotEmpty, IsString } from "class-validator";
export class RefreshAccessTokenDTO {
@IsNotEmpty()
refreshToken: string;
}

View File

@@ -0,0 +1,7 @@
import { AuthGuard } from "@nestjs/passport";
export class JwtGuard extends AuthGuard("jwt") {
constructor() {
super();
}
}

View File

@@ -0,0 +1,37 @@
import { Injectable } from "@nestjs/common";
import { Cron } from "@nestjs/schedule";
import { FileService } from "src/file/file.service";
import { PrismaService } from "src/prisma/prisma.service";
@Injectable()
export class JobsService {
constructor(
private prisma: PrismaService,
private fileService: FileService
) {}
@Cron("0 * * * *")
async deleteExpiredShares() {
const expiredShares = await this.prisma.share.findMany({
where: { expiration: { lt: new Date() } },
});
for (const expiredShare of expiredShares) {
await this.prisma.share.delete({
where: { id: expiredShare.id },
});
await this.fileService.deleteAllFiles(expiredShare.id);
}
console.log(`job: deleted ${expiredShares.length} expired shares`);
}
@Cron("0 * * * *")
async deleteExpiredRefreshTokens() {
const expiredShares = await this.prisma.refreshToken.deleteMany({
where: { expiresAt: { lt: new Date() } },
});
console.log(`job: deleted ${expiredShares.count} expired refresh tokens`);
}
}

View File

@@ -0,0 +1,24 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PassportStrategy } from "@nestjs/passport";
import { User } from "@prisma/client";
import { ExtractJwt, Strategy } from "passport-jwt";
import { PrismaService } from "src/prisma/prisma.service";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(config: ConfigService, private prisma: PrismaService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get("JWT_SECRET"),
});
}
async validate(payload: any) {
const user: User = await this.prisma.user.findUnique({
where: { id: payload.sub },
});
return user;
}
}