1
0
forked from noxious/server

(WIP) Added pw reset token row, added checks to reset function

This commit is contained in:
Colin Kallemein 2024-10-29 22:49:21 +01:00
parent 5a36d10f0e
commit a4e96f9ede
7 changed files with 73 additions and 16 deletions

11
package-lock.json generated
View File

@ -28,6 +28,7 @@
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.14.11", "@types/node": "^20.14.11",
"@types/nodemailer": "^6.4.16",
"nodemon": "^3.1.4", "nodemon": "^3.1.4",
"prettier": "^3.3.3" "prettier": "^3.3.3"
} }
@ -728,6 +729,16 @@
"undici-types": "~6.19.2" "undici-types": "~6.19.2"
} }
}, },
"node_modules/@types/nodemailer": {
"version": "6.4.16",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.16.tgz",
"integrity": "sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.16", "version": "6.9.16",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",

View File

@ -29,6 +29,7 @@
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.14.11", "@types/node": "^20.14.11",
"@types/nodemailer": "^6.4.16",
"nodemon": "^3.1.4", "nodemon": "^3.1.4",
"prettier": "^3.3.3" "prettier": "^3.3.3"
} }

View File

@ -49,6 +49,17 @@ CREATE TABLE `User` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `PasswordResetToken` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`userId` INTEGER NOT NULL,
`token` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
UNIQUE INDEX `PasswordResetToken_token_key`(`token`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable -- CreateTable
CREATE TABLE `CharacterType` ( CREATE TABLE `CharacterType` (
`id` INTEGER NOT NULL AUTO_INCREMENT, `id` INTEGER NOT NULL AUTO_INCREMENT,
@ -204,6 +215,9 @@ ALTER TABLE `Chat` ADD CONSTRAINT `Chat_zoneId_fkey` FOREIGN KEY (`zoneId`) REFE
-- AddForeignKey -- AddForeignKey
ALTER TABLE `SpriteAction` ADD CONSTRAINT `SpriteAction_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE `SpriteAction` ADD CONSTRAINT `SpriteAction_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `PasswordResetToken` ADD CONSTRAINT `PasswordResetToken_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey -- AddForeignKey
ALTER TABLE `CharacterType` ADD CONSTRAINT `CharacterType_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE `CharacterType` ADD CONSTRAINT `CharacterType_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -1,10 +1,19 @@
model User { model User {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
username String @unique username String @unique
email String @unique email String @unique
password String password String
online Boolean @default(false) online Boolean @default(false)
characters Character[] characters Character[]
passwordResetTokens PasswordResetToken[]
}
model PasswordResetToken {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
createdAt DateTime @default(now())
} }
enum CharacterGender { enum CharacterGender {
@ -62,4 +71,4 @@ model CharacterItem {
itemId String itemId String
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade) item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
quantity Int quantity Int
} }

View File

@ -1,8 +1,9 @@
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import UserRepository from '../repositories/userRepository' import UserRepository from '../repositories/userRepository'
import prisma from '../utilities/prisma' import prisma from '../utilities/prisma'
import { User } from '@prisma/client' import { User, PasswordResetToken } from '@prisma/client'
import config from '../utilities/config' import config from '../utilities/config'
import NodeMailer from 'nodemailer'
/** /**
* User service * User service
@ -59,10 +60,27 @@ class UserService {
* Reset password * Reset password
* @param email * @param email
*/ */
async resetPassword(email: string): Promise<boolean | User> { async resetPassword(email: string): Promise<boolean | undefined> {
const nodemailer = require("nodemailer");
const transporter = nodemailer.createTransport({ const user = await UserRepository.getByEmail(email)
if ( !user ) return
const token = await bcrypt.genSalt(10)
//Check if password reset has been requested recently
if (await prisma.passwordResetToken.findFirst({
where: {
userId: user.id
},
})) return
prisma.passwordResetToken.create({
data: {
userId: user.id,
token: token,
}
});
const transporter = NodeMailer.createTransport({
host: config.SMTP_HOST, host: config.SMTP_HOST,
port: config.SMTP_PORT, port: config.SMTP_PORT,
secure: false, secure: false,
@ -76,12 +94,15 @@ class UserService {
from: config.SMTP_USER, from: config.SMTP_USER,
to: email, to: email,
subject: "Reset your password", subject: "Reset your password",
text: "A password reset has been requested, reset your password here: ", // Plain text body text: "A password reset has been requested, reset your password here: " + config.CLIENT_URL + "/" + token, // Plain text body
html: "<p>A password reset has been requested, reset your password here: </p>", // Html body html: "<p>A password reset has been requested, reset your password here: " + config.CLIENT_URL + "/" + token + "</p>", // Html body
}); });
console.log("Message sent: %s", info.messageId); console.log("Message sent: %s", info.messageId);
return info.messageId
if (info) {
return true
}
} }
} }

View File

@ -7,6 +7,7 @@ class config {
static REDIS_URL: string = process.env.REDIS_URL || 'redis://@127.0.0.1:6379/4' static REDIS_URL: string = process.env.REDIS_URL || 'redis://@127.0.0.1:6379/4'
static HOST: string = process.env.HOST || '0.0.0.0' static HOST: string = process.env.HOST || '0.0.0.0'
static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 6969 static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 6969
static CLIENT_URL: string = process.env.CLIENT_URL ? process.env.CLIENT_URL : 'https://sylvan.quest/'
static JWT_SECRET: string = process.env.JWT_SECRET || 'secret' static JWT_SECRET: string = process.env.JWT_SECRET || 'secret'
static ALLOW_DIAGONAL_MOVEMENT: boolean = process.env.ALLOW_DIAGONAL_MOVEMENT === 'true' static ALLOW_DIAGONAL_MOVEMENT: boolean = process.env.ALLOW_DIAGONAL_MOVEMENT === 'true'

View File

@ -73,9 +73,9 @@ async function addHttpRoutes(app: Application) {
} }
const userService = new UserService() const userService = new UserService()
const user = await userService.resetPassword( email ) const sentEmail = await userService.resetPassword( email )
if (user) { if (sentEmail) {
return res.status(200).json({ message: 'Email has been sent' }) return res.status(200).json({ message: 'Email has been sent' })
} }