Skip to content

Commit

Permalink
server: create reset table
Browse files Browse the repository at this point in the history
  • Loading branch information
Shubham-Lal committed Jul 21, 2024
1 parent b1f3e7b commit 504bb50
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 49 deletions.
2 changes: 1 addition & 1 deletion client/src/pages/auth/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect } from "react"
import { useLocation, useNavigate } from "react-router-dom"
import { AuthStatus, AuthTab, useAuthStore, useTempStore } from "../../store/useAuthStore"
import { toast } from "sonner";
import { toast } from "sonner"

export default function AuthPage() {
const location = useLocation();
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function HomePage() {

return (
<div className={`debates ${sidebar ? 'column-debates' : ''}`}>
{isAuthenticated === AuthStatus.Failed && (
{isAuthenticated === AuthStatus.Authenticating || isAuthenticated === AuthStatus.Failed && (
<ClaimUsername />
)}
<ClosedDebateCard />
Expand Down
66 changes: 32 additions & 34 deletions server/controllers/auth.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { hash, compare } = require('bcrypt');
const { sign } = require('jsonwebtoken');
const { ErrorHandler, catchError } = require('../utils/error');
const { sign } = require('jsonwebtoken');
const { hash, compare } = require('bcrypt');
const { randomBytes } = require('node:crypto');
const { sendMail } = require('../utils/mail');

Expand All @@ -15,14 +15,11 @@ exports.handleGoogleAuth = async function (fastify, request, reply) {
await fastify.cache.get(cacheKey, async (err, cachedUser) => {
if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache');

if (cachedUser) {
user = cachedUser.item;
console.log("Getting cache google-auth");
} else {
if (cachedUser) user = cachedUser.item;
else {
const [db_user] = await fastify.mysql.query('SELECT * FROM users WHERE email=?', [userinfo.email]);

if (db_user.length > 0) {
console.log("Setting cache google-auth");
await fastify.cache.set(cacheKey, db_user[0], 432000, (err) => {
if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache');
});
Expand Down Expand Up @@ -70,11 +67,16 @@ exports.register = async function (fastify, request, reply) {
const tempPassword = await hash(randomBytes(32).toString('hex'), 10);

const [result] = await fastify.mysql.query(
'INSERT INTO users (email, username, first_name, last_name, avatar, password, reset_token, reset_token_expiry) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[email, username, first_name, last_name, avatar, `temp:${tempPassword}`, token, tokenExpiry]
'INSERT INTO users (email, username, first_name, last_name, avatar, password) VALUES (?, ?, ?, ?, ?, ?)',
[email, username, first_name, last_name, avatar, `temp:${tempPassword}`]
);

if (result.affectedRows > 0) {
await fastify.mysql.query(
'INSERT INTO reset (username, token, expiry) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE token=?, expiry=?',
[username, token, tokenExpiry, token, tokenExpiry]
);

await sendMail(fastify.mailer, {
to: email,
subject: 'Account Activation',
Expand All @@ -99,21 +101,17 @@ exports.register = async function (fastify, request, reply) {
exports.login = async function (fastify, request, reply) {
try {
const { id, password } = request.body;

let user;
const cacheKey = `user:${id}`;
let user = null;

await fastify.cache.get(cacheKey, async (err, cachedUser) => {
if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache');

if (cachedUser) {
user = cachedUser.item;
console.log("Getting cache login");
} else {
if (cachedUser) user = cachedUser.item;
else {
const [db_user] = await fastify.mysql.query(`SELECT * FROM users WHERE ${id.includes('@') ? 'email=?' : 'username=?'}`, [id, id]);
if (!db_user.length) throw new ErrorHandler(400, false, "Account doesn't exist");

console.log("Setting cache login");
await fastify.cache.set(cacheKey, db_user[0], 432000, (err) => {
if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache');
});
Expand Down Expand Up @@ -148,21 +146,17 @@ exports.login = async function (fastify, request, reply) {
exports.autoLogin = async function (fastify, request, reply) {
try {
const id = request.user.userId;

const cacheKey = `user:${id}`;
let user = null;

await fastify.cache.get(cacheKey, async (err, cachedUser) => {
if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache');

if (cachedUser) {
user = cachedUser.item;
console.log("Getting cache auto-login");
} else {
if (cachedUser) user = cachedUser.item;
else {
const [db_user] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [id]);
if (!db_user.length) throw new ErrorHandler(400, false, 'Invalid credentials');

console.log("Setting cache auto-login");
await fastify.cache.set(cacheKey, db_user[0], 432000, (err) => {
if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache');
});
Expand All @@ -187,9 +181,7 @@ exports.checkUsername = async function (fastify, request, reply) {
const { username } = request.body;

const [user] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [username]);
if (user.length) {
throw new ErrorHandler(400, false, 'Username already taken');
}
if (user.length) throw new ErrorHandler(400, false, 'Username already taken');

return reply.code(200).send({
success: true,
Expand All @@ -210,7 +202,10 @@ exports.recoverAccount = async function (fastify, request, reply) {
const token = randomBytes(32).toString('hex');
const tokenExpiry = new Date(Date.now() + 3600000);

await fastify.mysql.query('UPDATE users SET reset_token=?, reset_token_expiry=? WHERE username=?', [token, tokenExpiry, user[0].username]);
await fastify.mysql.query(
'INSERT INTO reset (username, token, expiry) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE token=?, expiry=?',
[user[0].username, token, tokenExpiry, token, tokenExpiry]
);

await sendMail(fastify.mailer, {
to: user[0].email,
Expand All @@ -234,25 +229,28 @@ exports.resetPassword = async (fastify, request, reply) => {
try {
const { token, password } = request.body;

const [user] = await fastify.mysql.query('SELECT * FROM users WHERE reset_token=? AND reset_token_expiry>?', [token, new Date()]);
if (!user.length) throw new ErrorHandler(400, false, "Invalid or expired token");
const [reset] = await fastify.mysql.query('SELECT * FROM reset WHERE token=? AND expiry>?', [token, new Date()]);
if (!reset.length) throw new ErrorHandler(400, false, "Invalid or expired token");

const hashedPassword = await hash(password, 10);

const [result] = await fastify.mysql.query('UPDATE users SET password=?, reset_token=NULL, reset_token_expiry=NULL WHERE username=?', [hashedPassword, user[0].username]);
const [result] = await fastify.mysql.query('UPDATE users SET password=? WHERE username=?', [hashedPassword, reset[0].username]);

if (result.affectedRows > 0) {
const updatedUser = { ...user[0], password: hashedPassword };
const cacheKeys = [`user:${updatedUser.username}`, `user:${updatedUser.email}`];
await fastify.mysql.query('DELETE FROM reset WHERE username=?', [reset[0].username]);

const [updatedUser] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [reset[0].username]);
if (!updatedUser.length) throw new ErrorHandler(400, false, "Account doesn't exists");

const cacheKeys = [`user:${updatedUser[0].username}`, `user:${updatedUser[0].email}`];
await Promise.all(cacheKeys.map(cacheKey =>
fastify.cache.set(cacheKey, updatedUser, 432000, (err) => {
fastify.cache.set(cacheKey, updatedUser[0], 432000, (err) => {
if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache');
})
));

const token = await new Promise((resolve, reject) => {
sign({ userId: updatedUser.username }, process.env.JWT_SECRET, { expiresIn: '12h' }, (err, token) => {
sign({ userId: updatedUser[0].username }, process.env.JWT_SECRET, { expiresIn: '12h' }, (err, token) => {
if (err) reject(err);
else resolve(token);
});
Expand All @@ -262,7 +260,7 @@ exports.resetPassword = async (fastify, request, reply) => {
success: true,
message: 'Password set successfully',
data: {
user: { ...(({ password, reset_token, reset_token_expiry, ...rest }) => rest)(updatedUser) },
user: { ...(({ password, ...rest }) => rest)(updatedUser[0]) },
token
}
});
Expand Down
13 changes: 11 additions & 2 deletions server/db/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@ CREATE TABLE users (
first_name VARCHAR(30) NOT NULL,
last_name VARCHAR(30) NOT NULL,
avatar VARCHAR(255),
reset_token VARCHAR(255),
reset_token_expiry DATETIME,
PRIMARY KEY (username)
);
```

## Table `reset`
```
CREATE TABLE reset (
username VARCHAR(15) NOT NULL,
token VARCHAR(64) NOT NULL,
expiry DATETIME NOT NULL,
PRIMARY KEY (username),
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
);
```
20 changes: 10 additions & 10 deletions server/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ module.exports = async function (fastify, opts) {
body: {
type: 'object',
properties: {
email: { type: 'string', minLength: 1, pattern: emailRegex.source },
username: { type: 'string', minLength: 1 },
first_name: { type: 'string', minLength: 1 },
last_name: { type: 'string', minLength: 1 },
email: { type: 'string', minLength: 1, maxLength: 255, pattern: emailRegex.source },
username: { type: 'string', minLength: 1, maxLength: 15 },
first_name: { type: 'string', minLength: 1, maxLength: 30 },
last_name: { type: 'string', minLength: 1, maxLength: 30 },
},
required: ['email', 'username', 'first_name', 'last_name']
}
Expand All @@ -22,8 +22,8 @@ module.exports = async function (fastify, opts) {
body: {
type: 'object',
properties: {
id: { type: 'string', minLength: 1 },
password: { type: 'string', minLength: 6 }
id: { type: 'string', minLength: 1, maxLength: 255 },
password: { type: 'string', minLength: 6, maxLength: 255 }
},
required: ['id', 'password']
}
Expand All @@ -33,7 +33,7 @@ module.exports = async function (fastify, opts) {
body: {
type: 'object',
properties: {
username: { type: 'string', minLength: 1 },
username: { type: 'string', minLength: 1, maxLength: 15 },
},
required: ['username']
}
Expand All @@ -43,8 +43,8 @@ module.exports = async function (fastify, opts) {
body: {
type: 'object',
properties: {
email: { type: 'string', minLength: 1, pattern: emailRegex.source },
username: { type: 'string', minLength: 1 }
email: { type: 'string', minLength: 1, maxLength: 255, pattern: emailRegex.source },
username: { type: 'string', minLength: 1, maxLength: 15 }
},
anyOf: [
{ required: ['email'] },
Expand All @@ -58,7 +58,7 @@ module.exports = async function (fastify, opts) {
type: 'object',
properties: {
token: { type: 'string', minLength: 1 },
password: { type: 'string', minLength: 1 },
password: { type: 'string', minLength: 1, maxLength: 255 },
},
required: ['token', 'password']
}
Expand Down
1 change: 0 additions & 1 deletion server/utils/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ function catchError(reply, err) {
message: err.message
});
} else {
console.log(err);
return reply.code(500).send({
success: false,
message: 'Internal Server Error'
Expand Down

0 comments on commit 504bb50

Please sign in to comment.