updated login flow
This commit is contained in:
@@ -1,61 +1,106 @@
|
||||
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { getUserByEmail, updateUserInternal } from "../user/user.service";
|
||||
import { getUserByEmail, getUserByToken } from "../user/user.service";
|
||||
import { createSession, deleteSession } from "./auth.service";
|
||||
import { hash, verify } from "argon2";
|
||||
import { validatePassword } from "../utils/password";
|
||||
|
||||
export async function authRoutes(fastify: FastifyInstance) {
|
||||
fastify.get(
|
||||
"/microsoft/token",
|
||||
{},
|
||||
fastify.post(
|
||||
"/register",
|
||||
{
|
||||
schema: {
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["password", "token"],
|
||||
properties: {
|
||||
password: { type: "string" },
|
||||
token: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (req: FastifyRequest, res: FastifyReply) => {
|
||||
const { password, token } = req.body as {
|
||||
password: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
try {
|
||||
const { token } =
|
||||
await fastify.microsoftOauth.getAccessTokenFromAuthorizationCodeFlow(
|
||||
req
|
||||
);
|
||||
const userInDB = await getUserByToken(token);
|
||||
if (!userInDB) return res.code(404).send({ error: "not found" });
|
||||
if (new Date() > userInDB.token.expiry)
|
||||
return res.code(400).send({ error: "link expired" });
|
||||
|
||||
const user = (await fastify.microsoftOauth.userinfo(token)) as {
|
||||
givenname: string;
|
||||
familyname: string;
|
||||
email: string;
|
||||
picture: string;
|
||||
};
|
||||
if (!validatePassword(password))
|
||||
return res.code(400).send({
|
||||
error:
|
||||
"weak password. Make sure the password has atleast 2 uppercase, 2 lowercase, 2 numeric and 2 special characters",
|
||||
});
|
||||
|
||||
const userInDb = await getUserByEmail(user.email);
|
||||
if (userInDb == null)
|
||||
return res.code(401).send({ error: "not_allowed" });
|
||||
const hashedPassword = await hash(password);
|
||||
userInDB.passwordHash = hashedPassword;
|
||||
|
||||
await updateUserInternal(userInDb.pid, {
|
||||
firstName: user.givenname,
|
||||
lastName: user.familyname,
|
||||
email: user.email,
|
||||
avatar: user.picture,
|
||||
});
|
||||
|
||||
const session = await createSession(
|
||||
userInDb.id,
|
||||
req.ip,
|
||||
req.headers["user-agent"]
|
||||
);
|
||||
|
||||
return res.code(201).send({ session_token: session.sid });
|
||||
await userInDB.save();
|
||||
} catch (err) {
|
||||
//@ts-ignore
|
||||
if (err.data) {
|
||||
//@ts-ignore
|
||||
fastify.log.warn(err.data.payload);
|
||||
return res.code(400).send();
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
fastify.delete("/logout", {}, async (req, res) => {
|
||||
if (!req.headers.authorization) return res.code(200).send();
|
||||
fastify.post(
|
||||
"/login",
|
||||
{
|
||||
schema: {
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["email", "password"],
|
||||
properties: {
|
||||
email: { type: "string" },
|
||||
password: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (req: FastifyRequest, res: FastifyReply) => {
|
||||
const { email, password } = req.body as {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const auth = req.headers.authorization.split(" ")[1];
|
||||
await deleteSession(auth);
|
||||
return res.code(200).send();
|
||||
});
|
||||
try {
|
||||
const userInDB = await getUserByEmail(email);
|
||||
if (!userInDB)
|
||||
return res.code(401).send({ error: "invalid email or password" });
|
||||
|
||||
const match = await verify(userInDB.passwordHash, password);
|
||||
if (!match)
|
||||
return res.code(401).send({ error: "invalid email or password" });
|
||||
|
||||
const newSession = await createSession(
|
||||
userInDB.id,
|
||||
req.ip,
|
||||
req.headers["user-agent"]
|
||||
);
|
||||
|
||||
userInDB.lastLogin = new Date();
|
||||
await userInDB.save();
|
||||
|
||||
res.send({ session_token: newSession.sid });
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
fastify.delete(
|
||||
"/logout",
|
||||
{},
|
||||
async (req: FastifyRequest, res: FastifyReply) => {
|
||||
if (!req.headers.authorization) return res.code(200).send();
|
||||
|
||||
const auth = req.headers.authorization.split(" ")[1];
|
||||
await deleteSession(auth);
|
||||
return res.code(200).send();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const userSchema = new mongoose.Schema({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
passwordHash: String,
|
||||
passKeys: [new mongoose.Schema({}, { _id: false, strict: false })],
|
||||
challenge: new mongoose.Schema(
|
||||
{
|
||||
@@ -68,6 +69,7 @@ const userCore = {
|
||||
avatar: z.string().optional(),
|
||||
role: z.enum(roles),
|
||||
orgId: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
};
|
||||
|
||||
const createUserInput = z
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CreateUserInput, UpdateUserInput, userModel } from "./user.schema";
|
||||
import { sendMail } from "../utils/mail";
|
||||
import { AuthenticatedUser } from "../auth";
|
||||
import { createUserConfig } from "../userConfig/userConfig.service";
|
||||
import { hash } from "argon2";
|
||||
|
||||
export const ErrUserNotFound = new Error("user not found");
|
||||
export const ErrOpNotValid = new Error("operation is not valid");
|
||||
@@ -26,7 +27,8 @@ export async function createUser(
|
||||
throw ErrMissingOrdId;
|
||||
}
|
||||
|
||||
const token = await generateToken();
|
||||
let hashedPassword = "";
|
||||
if (input.password) hashedPassword = await hash(input.password);
|
||||
|
||||
const newUser = await userModel.create({
|
||||
tenantId: user.tenantId,
|
||||
@@ -34,16 +36,25 @@ export async function createUser(
|
||||
name: input.firstName + " " + input.lastName,
|
||||
createdAt: new Date(),
|
||||
createdBy: user.userId,
|
||||
token: {
|
||||
value: token,
|
||||
expiry: new Date(Date.now() + 3600 * 48 * 1000),
|
||||
},
|
||||
status: "invited",
|
||||
status: input.password ? "active" : "invited",
|
||||
passwordHash: hashedPassword,
|
||||
...input,
|
||||
});
|
||||
|
||||
await createUserConfig(newUser.id, newUser.tenantId);
|
||||
|
||||
if (input.password)
|
||||
return newUser.populate({ path: "orgId", select: "pid name avatar" });
|
||||
|
||||
const token = await generateToken();
|
||||
|
||||
newUser.token = {
|
||||
value: token,
|
||||
expiry: new Date(Date.now() + 3600 * 48 * 1000),
|
||||
};
|
||||
|
||||
await newUser.save();
|
||||
|
||||
const sent = await sendMail(
|
||||
input.email,
|
||||
"You have been invited to Quicker Permtis.",
|
||||
|
||||
@@ -17,13 +17,3 @@ export async function generateToken(): Promise<string> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function generateOTP() {
|
||||
let otp = "";
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
otp += crypto.randomInt(10);
|
||||
}
|
||||
|
||||
return parseInt(otp);
|
||||
}
|
||||
|
||||
25
src/utils/password.ts
Normal file
25
src/utils/password.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export function validatePassword(password: string): boolean {
|
||||
const charCounts = {
|
||||
upper: 0,
|
||||
lower: 0,
|
||||
number: 0,
|
||||
special: 0,
|
||||
};
|
||||
|
||||
for (const char of password.split("")) {
|
||||
if ("ABCDEFGHIJKLMNOPQRSTUVWXYZ".includes(char)) charCounts.upper++;
|
||||
if ("abcdefghijklmnopqrstuvwxyz".includes(char)) charCounts.lower++;
|
||||
if ("0123456789".includes(char)) charCounts.number++;
|
||||
if ("!@#$%^&*()<>?;:{}[]~".includes(char)) charCounts.special++;
|
||||
}
|
||||
|
||||
if (
|
||||
charCounts.upper >= 2 &&
|
||||
charCounts.lower >= 2 &&
|
||||
charCounts.number >= 2 &&
|
||||
charCounts.special >= 2
|
||||
)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user