Files
swiss-datashare/backend/src/auth/auth.service.ts

172 lines
4.7 KiB
TypeScript
Raw Normal View History

import {
BadRequestException,
2022-12-05 15:53:24 +01:00
ForbiddenException,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { User } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import * as argon from "argon2";
import * as moment from "moment";
import { ConfigService } from "src/config/config.service";
import { PrismaService } from "src/prisma/prisma.service";
import { AuthRegisterDTO } from "./dto/authRegister.dto";
2022-10-10 17:58:42 +02:00
import { AuthSignInDTO } from "./dto/authSignIn.dto";
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
private config: ConfigService
) {}
async signUp(dto: AuthRegisterDTO) {
const isFirstUser = this.config.get("SETUP_STATUS") == "STARTED";
const hash = await argon.hash(dto.password);
try {
const user = await this.prisma.user.create({
data: {
email: dto.email,
2022-12-01 23:07:49 +01:00
username: dto.username,
password: hash,
isAdmin: isFirstUser,
},
});
if (isFirstUser) {
await this.config.changeSetupStatus("REGISTERED");
}
2023-01-04 11:54:28 +01:00
const { refreshToken, refreshTokenId } = await this.createRefreshToken(
user.id
);
const accessToken = await this.createAccessToken(user, refreshTokenId);
return { accessToken, refreshToken };
} catch (e) {
if (e instanceof PrismaClientKnownRequestError) {
if (e.code == "P2002") {
2022-12-01 23:07:49 +01:00
const duplicatedField: string = e.meta.target[0];
throw new BadRequestException(
`A user with this ${duplicatedField} already exists`
);
}
}
}
}
2022-10-10 17:58:42 +02:00
async signIn(dto: AuthSignInDTO) {
2022-12-01 23:07:49 +01:00
if (!dto.email && !dto.username)
throw new BadRequestException("Email or username is required");
const user = await this.prisma.user.findFirst({
where: {
2022-12-01 23:07:49 +01:00
OR: [{ email: dto.email }, { username: dto.username }],
},
});
if (!user || !(await argon.verify(user.password, dto.password)))
throw new UnauthorizedException("Wrong email or password");
// TODO: Make all old loginTokens invalid when a new one is created
// Check if the user has TOTP enabled
if (user.totpVerified) {
const loginToken = await this.createLoginToken(user.id);
return { loginToken };
}
2023-01-04 11:54:28 +01:00
const { refreshToken, refreshTokenId } = await this.createRefreshToken(
user.id
);
const accessToken = await this.createAccessToken(user, refreshTokenId);
return { accessToken, refreshToken };
}
2022-12-05 15:53:24 +01:00
async updatePassword(user: User, oldPassword: string, newPassword: string) {
if (!(await argon.verify(user.password, oldPassword)))
2022-12-05 15:53:24 +01:00
throw new ForbiddenException("Invalid password");
const hash = await argon.hash(newPassword);
await this.prisma.refreshToken.deleteMany({
where: { userId: user.id },
});
await this.prisma.user.update({
2022-12-05 15:53:24 +01:00
where: { id: user.id },
data: { password: hash },
});
return this.createRefreshToken(user.id);
2022-12-05 15:53:24 +01:00
}
2023-01-04 11:54:28 +01:00
async createAccessToken(user: User, refreshTokenId: string) {
return this.jwtService.sign(
{
sub: user.id,
email: user.email,
isAdmin: user.isAdmin,
2023-01-04 11:54:28 +01:00
refreshTokenId,
},
{
expiresIn: "15min",
secret: this.config.get("JWT_SECRET"),
}
);
}
2023-01-04 11:54:28 +01:00
async signOut(accessToken: string) {
const { refreshTokenId } =
(this.jwtService.decode(accessToken) as {
refreshTokenId: string;
}) || {};
if (refreshTokenId) {
await this.prisma.refreshToken
.delete({ where: { id: refreshTokenId } })
.catch((e) => {
// Ignore error if refresh token doesn't exist
if (e.code != "P2025") throw e;
});
}
2023-01-04 11:54:28 +01:00
}
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();
2023-01-04 11:54:28 +01:00
return this.createAccessToken(
refreshTokenMetaData.user,
refreshTokenMetaData.id
);
}
async createRefreshToken(userId: string) {
2023-01-04 11:54:28 +01:00
const { id, token } = await this.prisma.refreshToken.create({
data: { userId, expiresAt: moment().add(3, "months").toDate() },
});
2023-01-04 11:54:28 +01:00
return { refreshTokenId: id, refreshToken: token };
}
async createLoginToken(userId: string) {
const loginToken = (
await this.prisma.loginToken.create({
data: { userId, expiresAt: moment().add(5, "minutes").toDate() },
})
).token;
return loginToken;
}
}