Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions src/controller/user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Request, Response } from "express";
import { loginSchema } from "~/zod/userSchema.js";
import { registerSchema } from "~/zod/userSchema.js";
import { AuthenticatedUser, validateSession } from "~/models/session.js";
import { registerUser, login } from "~/models/user.js";
import { AuthenticatedUser, sessionModel } from "~/models/session.js";
import { userModel } from "~/models/user.js";
import { CookieResponse } from "~/models/cookieResponse.js";

export const register = async (req: Request, res: Response) => {
Expand All @@ -15,7 +15,7 @@ export const register = async (req: Request, res: Response) => {
}

try {
const registeredUser = await registerUser(result.data);
const registeredUser = await userModel.create(result.data);
response.created({
message: `Usuário '${registeredUser.name}' criado com sucesso!`,
});
Expand All @@ -25,7 +25,7 @@ export const register = async (req: Request, res: Response) => {
}
};

export const authentication = async (req: Request, res: Response) => {
export const login = async (req: Request, res: Response) => {
const result = loginSchema.safeParse(req.body);

const response = new CookieResponse(res);
Expand All @@ -34,16 +34,16 @@ export const authentication = async (req: Request, res: Response) => {
return;
}
try {
const sessionObject = await login(result.data);
if (!sessionObject) {
const user = await userModel.authenticateUser(result.data);
if (!user) {
response.unauthorized(
"Verifique os dados enviados e tente novamente",
"Email ou senha inválido",
);
return;
}

new CookieResponse(res).setCookie("session", sessionObject.id);
const session = await sessionModel.create(user.id);
new CookieResponse(res).setCookie("session", session.id);
response.created({ message: "Usuário autenticado!" });
} catch (error) {
console.error("Erro insperado ao efetuar login: ", error);
Expand All @@ -63,7 +63,7 @@ export const getUser = async (
}

try {
const user = await validateSession(sessionId);
const user = await sessionModel.validate(sessionId);

if (!user) {
response.clearCookie("session");
Expand Down
13 changes: 6 additions & 7 deletions src/middleware/authenticate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { CookieResponse } from "~/models/cookieResponse.js";
import { AuthenticatedUser, validateSession } from "~/models/session.js";
import { AuthenticatedUser, sessionModel } from "~/models/session.js";

export interface AuthenticatedRequest extends Request {
context?: {
Expand All @@ -20,20 +20,19 @@ export async function authenticate(
return;
}

const userOrError = await validateSession(sessionId);
const user = await sessionModel.validate(sessionId);

if (!userOrError) {
if (!user) {
response.clearCookie("session");
response.unauthorized(
"Por favor, faça o login novamente.",
"Sessão inválida ou expirada.",
);
return;
}
if (!req.context) {
req.context = { user: userOrError };
}

req.context.user = userOrError;
if (!req.context) req.context = { user };

req.context.user = user;
next();
}
43 changes: 43 additions & 0 deletions src/models/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
type PrismaBaseDelegate = {
findMany(args: any): Promise<any>;
findFirst(args: any): Promise<any>;
findUnique(args: any): Promise<any>;
create(args: { data: any }): Promise<any>;
update(args: { where: any; data: any }): Promise<any>;
delete(args: { where: any }): Promise<any>;
};

export class BaseModel<M extends PrismaBaseDelegate> {
protected readonly model: M;

constructor(model: M) {
this.model = model;
}

protected async findMany(args: Parameters<M["findMany"]>[0]) {
return this.model.findMany(args);
}

protected async findFirst(args: Parameters<M["findFirst"]>[0]) {
return this.model.findFirst(args);
}

protected async createOne(data: Parameters<M["create"]>[0]["data"]) {
return this.model.create({ data });
}

protected async updateOne(
where: Parameters<M["update"]>[0]["where"],
data: Parameters<M["update"]>[0]["data"],
) {
return this.model.update({ where, data });
}

protected async deleteOneById(where: Parameters<M["delete"]>[0]["where"]) {
return this.model.delete({ where });
}

protected async findUnique(args: Parameters<M["findUnique"]>[0]) {
return this.model.findUnique(args);
}
}
110 changes: 63 additions & 47 deletions src/models/session.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,9 @@
import { randomUUID } from "node:crypto";
import { env } from "~/config/env.js";
import { prisma } from "~/infra/database.js";

export const createSession = async (userId: string) => {
const sessionId = randomUUID();
const now = new Date();

const session = await prisma.userSession.create({
data: {
id: sessionId,
user_id: userId,
expires_at: new Date(now.getTime() + env.INACTIVITY_TIMEOUT),
last_active_at: now,
},
});
return session;
};

export const validateSession = async (sessionId: string) => {
const session = await prisma.userSession.findUnique({
where: {
id: sessionId,
},
});
if (!session) return false;

const userDb = await prisma.user.findUnique({
where: {
id: session.user_id,
},
});
if (!userDb) return false;

const now = new Date();
const lastActive = new Date(String(session.last_active_at));

if (now.getTime() - lastActive.getTime() > env.INACTIVITY_TIMEOUT)
return false;

await prisma.userSession.update({
where: { id: sessionId },
data: { last_active_at: now },
});

const { email, id, name, created_at, updated_at } = userDb;
const secureObjectValue = { id, email, name, created_at, updated_at };
return secureObjectValue;
};
import { BaseModel } from "./base.js";
import { UserDelegate, UserSessionDelegate } from "@/generated/models.js";
import { userModel, UserModelClass } from "./user.js";

export type AuthenticatedUser = {
id: string;
Expand All @@ -56,4 +13,63 @@ export type AuthenticatedUser = {
updated_at: Date;
};

export type UserSession = Awaited<ReturnType<typeof createSession>>;
export type UserSession = {
id: string;
user_id: string;
created_at: Date;
expires_at: Date;
last_active_at: Date;
};

class SessionModel extends BaseModel<UserSessionDelegate> {
constructor(
model: UserSessionDelegate,
private userModel: UserModelClass,
) {
super(model);
this.userModel = userModel;
}

async create(user_id: string): Promise<UserSession> {
const id = randomUUID();
const now = new Date();
const expires_at = new Date(now.getTime() + env.INACTIVITY_TIMEOUT);

return await this.createOne({
id,
user_id,
expires_at,
last_active_at: now,
});
}

async validate(sessionId: string): Promise<AuthenticatedUser | false> {
const sessionObject: UserSession = await this.findUnique({
where: { id: sessionId },
});
if (!sessionObject) return false;

const user = await this.userModel.findOne(sessionObject.user_id);
if (!user) return false;

const now = new Date();
const lastActive = new Date(String(sessionObject.last_active_at));

if (now.getTime() - lastActive.getTime() > env.INACTIVITY_TIMEOUT)
return false;

await this.updateOne({ id: sessionId }, { last_active_at: now });

const { email, id, name, created_at, updated_at } = user;
const secureObjectValue: AuthenticatedUser = {
id,
email,
name,
created_at,
updated_at,
};
return secureObjectValue;
}
}

export const sessionModel = new SessionModel(prisma.userSession, userModel);
43 changes: 18 additions & 25 deletions src/models/task.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { TaskDelegate } from "@/generated/models.js";
import { prisma } from "~/infra/database.js";
import { Task as TaskEnt, UpdateTask } from "~/zod/taskSchema.js";
import { BaseModel } from "~/models/base.js";
import { TaskDelegate } from "@/generated/models.js";
import { TaskInput, UpdateTask } from "~/zod/taskSchema.js";
import { Task } from "@/generated/client.js";

class TaskModel {
private model: TaskDelegate;
constructor() {
this.model = prisma.task;
class TaskModel extends BaseModel<TaskDelegate> {
constructor(model: TaskDelegate) {
super(model);
}

async findAll(userId: string) {
const tasks = await this.model.findMany({
async findAll(userId: string): Promise<Task[] | null> {
const tasks: Task[] = await this.findMany({
where: {
user_id: userId,
},
Expand All @@ -24,21 +25,17 @@ class TaskModel {
return tasks;
}

async create(providedData: TaskEnt, user_id: string) {
async create(providedData: TaskInput, user_id: string): Promise<Task> {
const data = {
...providedData,
user_id,
};

const task = await this.model.create({
data,
});

return task;
return await this.createOne(data);
}

async findOne(id: string) {
const task = await this.model.findFirst({
async findOne(id: string): Promise<Task | null> {
const task: Task = await this.findFirst({
where: { id },
});

Expand All @@ -47,24 +44,20 @@ class TaskModel {
return task;
}

async update(id: string, providedData: UpdateTask) {
async update(id: string, providedData: UpdateTask): Promise<Task | null> {
const now = new Date();
const data = {
...providedData,
updated_at: now,
};

const updatedTask = await this.model.update({
where: { id },
data,
});
const updatedTask = await this.updateOne({ id }, data);
return updatedTask;
}

async deleteOne(id: string) {
await this.model.delete({
where: { id },
});
await this.deleteOneById({ id });
}
}

export const taskModel = new TaskModel();
export const taskModel = new TaskModel(prisma.task);
Loading
Loading