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
14 changes: 14 additions & 0 deletions infra/controller.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as cookie from "cookie";
import session from "models/session";
import {
InternalServerError,
MethodNotAllowedError,
Expand Down Expand Up @@ -29,11 +31,23 @@ function onErrorHandler(error, request, response) {
response.status(publicErrorObject.statusCode).json(publicErrorObject);
}

async function setSessionCookie(sessionToken, response) {
const setCookie = cookie.serialize("session_id", sessionToken, {
path: "/",
maxAge: session.EXPIRATION_IN_MILLISECONDS / 1000,
secure: process.env.NODE_ENV === "production",
httpOnly: true,
});

response.setHeader("Set-Cookie", setCookie);
}

const controller = {
errorHandlers: {
onNoMatch: onNoMatchHandler,
onError: onErrorHandler,
},
setSessionCookie,
};

export default controller;
96 changes: 96 additions & 0 deletions models/session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import crypto from "node:crypto";
import database from "infra/database";
import { UnauthorizedError } from "infra/errors";

const EXPIRATION_IN_MILLISECONDS = 60 * 60 * 24 * 30 * 1000;

async function findOneValidByToken(sessionToken) {
const sessionFound = await runSelectQuery(sessionToken);

return sessionFound;

async function runSelectQuery(sessionToken) {
const results = await database.query({
text: `
SELECT
*
FROM
sessions
WHERE
token = $1
AND expires_at > NOW()
LIMIT
1
`,
values: [sessionToken],
});

if (results.rowCount === 0) {
throw new UnauthorizedError({
message: "Usuario não possui sessão ativa.",
action: "Verifique se este usuário está logado e tente novamente.",
});
}

return results.rows[0];
}
}

async function create(userId) {
const token = crypto.randomBytes(48).toString("hex");
const expires_at = new Date(Date.now() + EXPIRATION_IN_MILLISECONDS);

const newSession = await runInsertQuery(token, userId, expires_at);
return newSession;

async function runInsertQuery(token, userId, expires_at) {
const results = await database.query({
text: `
INSERT INTO
sessions (token, user_id, expires_at)
VALUES
($1, $2, $3)
RETURNING
*
`,
values: [token, userId, expires_at],
});

return results.rows[0];
}
}

async function renew(sessionId) {
const expires_at = new Date(Date.now() + EXPIRATION_IN_MILLISECONDS);

const renewedSessionObject = runUpdateQuery(sessionId, expires_at);
return renewedSessionObject;

async function runUpdateQuery(sessionId, expires_at) {
const results = await database.query({
text: `
UPDATE
sessions
SET
expires_at = $1,
updated_at = NOW()
WHERE
id = $2
RETURNING
*
;`,
values: [expires_at, sessionId],
});

return results.rows[0];
}
}

const session = {
create,
renew,
findOneValidByToken,
EXPIRATION_IN_MILLISECONDS,
};

export default session;
35 changes: 0 additions & 35 deletions models/sessions.js

This file was deleted.

32 changes: 32 additions & 0 deletions models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@ import database from "infra/database";
import password from "models/password";
import { ValidationError, NotFoundError } from "infra/errors";

async function findOneById(id) {
const userFound = await runSelectQuery(id);

return userFound;

async function runSelectQuery(id) {
const result = await database.query({
text: `
SELECT
*
FROM
users
WHERE
id = $1
LIMIT
1
;`,
values: [id],
});

if (result.rowCount === 0) {
throw new NotFoundError({
message: "O id informando não foi encotrado no sistema.",
action: "Verificque se o id está digitando corretamente.",
});
}

return result.rows[0];
}
}

async function findOneByUsername(username) {
const userFound = await runSelectQuery(username);

Expand Down Expand Up @@ -188,6 +219,7 @@ async function hashPasswordInObject(userInputValue) {

const user = {
create,
findOneById,
findOneByUsername,
findOneByEmail,
update,
Expand Down
12 changes: 3 additions & 9 deletions pages/api/v1/sessions/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createRouter } from "next-connect";
import * as cookie from "cookie";

import controller from "infra/controller";
import authentication from "models/authentication";
import session from "models/sessions";
import session from "models/session";

const router = createRouter();

Expand All @@ -20,13 +20,7 @@ async function postHandler(request, response) {

const newSession = await session.create(authenticatedUser.id);

const setCookie = cookie.serialize("session_id", newSession.token, {
path: "/",
maxAge: session.EXPIRATION_IN_MILLISECONDS / 1000,
secure: process.env.NODE_ENV === "production",
httpOnly: true,
});
response.setHeader("Set-Cookie", setCookie);
controller.setSessionCookie(newSession.token, response);

return response.status(201).json(newSession);
}
22 changes: 22 additions & 0 deletions pages/api/v1/user/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createRouter } from "next-connect";
import controller from "infra/controller";
import user from "models/user";
import session from "models/session";

const router = createRouter();

router.get(getHandler);

export default router.handler(controller.errorHandlers);

async function getHandler(request, response) {
const sessionToken = request.cookies.session_id;

const sessionObject = await session.findOneValidByToken(sessionToken);
const renewedSessionObject = await session.renew(sessionObject.id);

controller.setSessionCookie(renewedSessionObject.token, response);

const userFound = await user.findOneById(sessionObject.user_id);
return response.status(200).json(userFound);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { faker } from "@faker-js/faker";
import database from "infra/database";
import migrator from "models/migrator";
import user from "models/user";
import session from "models/session";

async function waitForAllServices() {
await waitForWebServer();
Expand All @@ -29,6 +30,10 @@ async function clearDatabase() {
await database.query("drop schema public cascade; create schema public;");
}

async function createSession(userId) {
return await session.create(userId);
}

async function runPendingMigrations() {
await migrator.runPendingMigrations();
}
Expand All @@ -47,6 +52,7 @@ const orchestrator = {
clearDatabase,
runPendingMigrations,
createUser,
createSession,
};

export default orchestrator;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { version as uuidVersion } from "uuid";
import setCookieParser from "set-cookie-parser";
import orchestrator from "../orchestrator";
import session from "models/sessions";
import session from "models/session";

beforeAll(async () => {
await orchestrator.waitForAllServices();
Expand Down
Loading
Loading