From 9a36cac9f1dac8123a64afa9d2d2521b5f4602ae Mon Sep 17 00:00:00 2001 From: Adilson Fuxe Date: Sun, 1 Jan 2023 21:44:26 +0100 Subject: [PATCH 1/5] refactor: change account schema to suport multiple sessions --- src/domain/models/account.ts | 16 +++++++++---- .../db/mongoose/models/account-models.ts | 23 +++++++++++++++++-- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/domain/models/account.ts b/src/domain/models/account.ts index ac65079..3c28ddf 100644 --- a/src/domain/models/account.ts +++ b/src/domain/models/account.ts @@ -1,12 +1,18 @@ +type Session = { + accessToken: string; + createdAt: Date; + updatedAt: Date; +}; + export type AccountModel = { id: string; firstName: string; lastName: string; email: string; password: string; - accessToken?: string; - forgotPasswordAccessToken?: number; - forgotPasswordExpiresIn?: Date; - createdAt?: Date; - updatedAt?: Date; + sessions: Session[]; + forgotPasswordAccessToken: number; + forgotPasswordExpiresIn: Date; + createdAt: Date; + updatedAt: Date; }; diff --git a/src/infra/db/mongoose/models/account-models.ts b/src/infra/db/mongoose/models/account-models.ts index 501a55d..a9cb1bc 100644 --- a/src/infra/db/mongoose/models/account-models.ts +++ b/src/infra/db/mongoose/models/account-models.ts @@ -5,6 +5,24 @@ import { Schemas, } from './models-protocols'; +const SessionSchema = new Schema( + { + accessToken: { + type: String, + required: true, + }, + ip: { + type: String, + required: true, + }, + device: { + type: String, + required: true, + }, + }, + { timestamps: true } +); + const AccountSchema = new Schema( { firstName: { @@ -29,8 +47,9 @@ const AccountSchema = new Schema( required: true, minlength: [6, 'password must be at least 6 characters'], }, - accessToken: { - type: String, + sessions: { + type: [SessionSchema], + default: [], }, forgotPasswordAccessToken: { type: Number, From ccfd7b88f62020a4806c76175162dbee97b96d63 Mon Sep 17 00:00:00 2001 From: Adilson Fuxe Date: Mon, 2 Jan 2023 23:09:52 +0100 Subject: [PATCH 2/5] refactor: improove signoutRepository --- src/data/protocols/db/signout-repository.ts | 3 ++- src/data/usecases/db-signout/index.ts | 4 ++-- src/domain/usecases/signout.ts | 5 ++++- .../db/mongoose/models/account-models.ts | 8 -------- src/infra/db/mongoose/repositories/index.ts | 19 ++++++++++++++----- src/interface/controllers/signout/index.ts | 6 +++++- 6 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/data/protocols/db/signout-repository.ts b/src/data/protocols/db/signout-repository.ts index 35fc26e..4085948 100644 --- a/src/data/protocols/db/signout-repository.ts +++ b/src/data/protocols/db/signout-repository.ts @@ -1,7 +1,8 @@ import { Signout } from '@src/domain/usecases'; export type SignoutRepository = ( - accountId: string + accountId: string, + accessToken: string ) => SignoutRepository.Response; export namespace SignoutRepository { diff --git a/src/data/usecases/db-signout/index.ts b/src/data/usecases/db-signout/index.ts index 9fd5e2f..dde1b33 100644 --- a/src/data/usecases/db-signout/index.ts +++ b/src/data/usecases/db-signout/index.ts @@ -2,6 +2,6 @@ import { BuildDbSignoutRepository } from './protocols'; export const dbSignout: BuildDbSignoutRepository = ({ signoutRepository }) => - async (accountId: string) => { - await signoutRepository(accountId); + async (accountId: string, accessToken: string) => { + await signoutRepository(accountId, accessToken); }; diff --git a/src/domain/usecases/signout.ts b/src/domain/usecases/signout.ts index 2751500..5677bdf 100644 --- a/src/domain/usecases/signout.ts +++ b/src/domain/usecases/signout.ts @@ -1,4 +1,7 @@ -export type Signout = (accountId: string) => Signout.Response; +export type Signout = ( + accountId: string, + accessToken: string +) => Signout.Response; export namespace Signout { export type Response = Promise; diff --git a/src/infra/db/mongoose/models/account-models.ts b/src/infra/db/mongoose/models/account-models.ts index a9cb1bc..2b6fd7f 100644 --- a/src/infra/db/mongoose/models/account-models.ts +++ b/src/infra/db/mongoose/models/account-models.ts @@ -11,14 +11,6 @@ const SessionSchema = new Schema( type: String, required: true, }, - ip: { - type: String, - required: true, - }, - device: { - type: String, - required: true, - }, }, { timestamps: true } ); diff --git a/src/infra/db/mongoose/repositories/index.ts b/src/infra/db/mongoose/repositories/index.ts index 071fc72..46e222d 100644 --- a/src/infra/db/mongoose/repositories/index.ts +++ b/src/infra/db/mongoose/repositories/index.ts @@ -33,7 +33,9 @@ export const loadAccountByIdRepository: LoadAccountByIdRepository = async ( export const loadAccountByTokenRepository: LoadAccountByTokenRepository = async (accessToken) => { - const account = await AccountModel.findOne({ accessToken }).lean(); + const account = await AccountModel.findOne({ + 'sessions.accessToken': accessToken, + }).lean(); return MongoHelper.serialize(account); }; @@ -41,7 +43,11 @@ export const updateAccessTokenRepository: UpdateAccessTokenRepository = async ( id, token ) => { - await AccountModel.findByIdAndUpdate(id, { accessToken: token }); + await AccountModel.findByIdAndUpdate(id, { + $push: { + sessions: { accessToken: token }, + }, + }); }; export const updateForgotPasswordAccessTokenRepository: UpdateForgotPasswordAccessTokenRepository = @@ -68,8 +74,11 @@ export const updatePasswordRepository: UpdatePasswordRepository = async ( }); }; -export const signoutRepository: SignoutRepository = async (id) => { - await AccountModel.findByIdAndUpdate(id, { - accessToken: null, +export const signoutRepository: SignoutRepository = async ( + accountId, + accessToken +) => { + await AccountModel.findByIdAndUpdate(accountId, { + $pull: { sessions: { accessToken } }, }); }; diff --git a/src/interface/controllers/signout/index.ts b/src/interface/controllers/signout/index.ts index e354754..18d7cf7 100644 --- a/src/interface/controllers/signout/index.ts +++ b/src/interface/controllers/signout/index.ts @@ -4,7 +4,11 @@ import { BuildSignoutController } from './protocols'; const buildSignoutController: BuildSignoutController = ({ signout }) => async (httpRequest) => { - await signout(httpRequest.accountId); + const header = + httpRequest.headers?.['x-access-token'] || + httpRequest.headers?.authorization; + const accessToken = header.replace(/^Bearer\s+/, ''); + await signout(httpRequest.accountId, accessToken); return noContent(); }; From ff86c7b334e9e4c4f4eebb0d7b8510058f0f6735 Mon Sep 17 00:00:00 2001 From: Adilson Fuxe Date: Tue, 3 Jan 2023 22:05:28 +0100 Subject: [PATCH 3/5] fix: testes bug --- __tests__/data/usecases/db-signout.spec.ts | 9 ++++++--- .../load-account-by-token-repository.spec.ts | 11 ++++++++--- .../repositories/signout-repository.spec.ts | 14 ++++++++++---- .../update-access-token-repository.spec.ts | 4 ++-- .../controllers/signout-controller.spec.ts | 5 ++++- globalConfig.json | 2 +- test-suite/domain/index.ts | 10 +++++++++- 7 files changed, 40 insertions(+), 15 deletions(-) diff --git a/__tests__/data/usecases/db-signout.spec.ts b/__tests__/data/usecases/db-signout.spec.ts index 5f68a9a..db057d9 100644 --- a/__tests__/data/usecases/db-signout.spec.ts +++ b/__tests__/data/usecases/db-signout.spec.ts @@ -13,14 +13,17 @@ const makeSut = () => { describe('Signout Usecases', () => { it('Should call signoutRepository with correct accountId', async () => { const { sut, signoutRepository } = makeSut(); - await sut('any_account_id'); - expect(signoutRepository).toHaveBeenCalledWith('any_account_id'); + await sut('any_account_id', 'valid_accessToken'); + expect(signoutRepository).toHaveBeenCalledWith( + 'any_account_id', + 'valid_accessToken' + ); }); it('Should throw if signoutRepository throws', async () => { const { sut, signoutRepository } = makeSut(); signoutRepository.mockRejectedValue(new Error()); - const promise = sut('any_account_id'); + const promise = sut('any_account_id', 'valid_accessToken'); await expect(promise).rejects.toThrow(); }); }); diff --git a/__tests__/infra/db/mongoose/repositories/load-account-by-token-repository.spec.ts b/__tests__/infra/db/mongoose/repositories/load-account-by-token-repository.spec.ts index 183ce6e..787f66a 100644 --- a/__tests__/infra/db/mongoose/repositories/load-account-by-token-repository.spec.ts +++ b/__tests__/infra/db/mongoose/repositories/load-account-by-token-repository.spec.ts @@ -17,7 +17,7 @@ describe('LoadAccountByTokenMongoRepository', () => { it('Should return null account loaded not found', async () => { const sut = makeSut(); - const account = await sut(faker.random.uuid()); + const account = await sut(faker.random.alphaNumeric(10)); expect(account).toBeNull(); }); @@ -29,10 +29,15 @@ describe('LoadAccountByTokenMongoRepository', () => { lastName: faker.name.lastName(), email: faker.internet.email(), password: faker.internet.password(), - accessToken, + sessions: [ + { + accessToken, + createdAt: new Date(2023, 9, 24), + updatedAt: new Date(2023, 9, 24), + }, + ], }); const account = await sut(accessToken); expect(account).toBeTruthy(); - expect(account.accessToken).toBe(accessToken); }); }); diff --git a/__tests__/infra/db/mongoose/repositories/signout-repository.spec.ts b/__tests__/infra/db/mongoose/repositories/signout-repository.spec.ts index a3f23b4..eb68b6f 100644 --- a/__tests__/infra/db/mongoose/repositories/signout-repository.spec.ts +++ b/__tests__/infra/db/mongoose/repositories/signout-repository.spec.ts @@ -23,11 +23,17 @@ describe('SignoutRepository', () => { lastName: faker.name.lastName(), email: faker.internet.email(), password: faker.internet.password(), - accessToken, + sessions: [ + { + accessToken, + createdAt: new Date(2023, 9, 24), + updatedAt: new Date(2023, 9, 24), + }, + ], }); - expect(account.accessToken).toBe(accessToken); - await sut(account.id); + expect(account.sessions[0].accessToken).toBe(accessToken); + await sut(account.id, accessToken); const result = await AccountModel.findById(account.id); - expect(result.accessToken).toBeNull(); + expect(result.sessions).toHaveLength(0); }); }); diff --git a/__tests__/infra/db/mongoose/repositories/update-access-token-repository.spec.ts b/__tests__/infra/db/mongoose/repositories/update-access-token-repository.spec.ts index 62949cf..58fcf46 100644 --- a/__tests__/infra/db/mongoose/repositories/update-access-token-repository.spec.ts +++ b/__tests__/infra/db/mongoose/repositories/update-access-token-repository.spec.ts @@ -23,10 +23,10 @@ describe('UpdateAccessTokenMongoRepository', () => { email: faker.internet.email(), password: faker.internet.password(), }); - expect(account.accessToken).toBeFalsy(); + expect(account.sessions).toHaveLength(0); const accessToken = faker.datatype.uuid(); await sut(account.id, accessToken); const result = await AccountModel.findById(account.id); - expect(result.accessToken).toBe(accessToken); + expect(result.sessions).toHaveLength(1); }); }); diff --git a/__tests__/interface/controllers/signout-controller.spec.ts b/__tests__/interface/controllers/signout-controller.spec.ts index 9116027..c7f1332 100644 --- a/__tests__/interface/controllers/signout-controller.spec.ts +++ b/__tests__/interface/controllers/signout-controller.spec.ts @@ -6,6 +6,9 @@ import { mockSignout } from '@test-suite/interface'; const makeSut = () => { const mockHttpRequest = (): HttpRequest => ({ accountId: 'any_account_id', + headers: { + 'x-access-token': 'Bearer any_token', + }, }); const signout = jest.fn(mockSignout()); const sut = signoutController({ signout }); @@ -16,7 +19,7 @@ describe('SignoutController', () => { it('Should call signout with correct accountId', async () => { const { sut, signout, mockHttpRequest } = makeSut(); await sut(mockHttpRequest()); - expect(signout).toHaveBeenCalledWith('any_account_id'); + expect(signout).toHaveBeenCalledWith('any_account_id', 'any_token'); }); it('Should return 500 if signout throws', async () => { diff --git a/globalConfig.json b/globalConfig.json index f617898..c5aea23 100644 --- a/globalConfig.json +++ b/globalConfig.json @@ -1 +1 @@ -{"mongoUri":"mongodb://127.0.0.1:32875/"} \ No newline at end of file +{"mongoUri":"mongodb://127.0.0.1:45577/"} \ No newline at end of file diff --git a/test-suite/domain/index.ts b/test-suite/domain/index.ts index 4c99d56..5d44638 100644 --- a/test-suite/domain/index.ts +++ b/test-suite/domain/index.ts @@ -9,8 +9,16 @@ export const mockAccount = (): AccountModel => { lastName: 'valid_last_name', email: 'valid_email', password: 'valid_password', - accessToken: 'valid_accessToken', + sessions: [ + { + accessToken: 'valid_accessToken', + createdAt: new Date(2023, 9, 24), + updatedAt: new Date(2023, 9, 24), + }, + ], forgotPasswordAccessToken: 123456, forgotPasswordExpiresIn: date, + createdAt: new Date(2023, 9, 24), + updatedAt: new Date(2023, 9, 24), }; }; From 0e001cdf706225200067ee9e16d1f96a02e5d5e7 Mon Sep 17 00:00:00 2001 From: Adilson Fuxe Date: Tue, 3 Jan 2023 22:16:27 +0100 Subject: [PATCH 4/5] chore: release 2.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 20ccf0c..1c32565 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "auth-microservice", - "version": "2.1.0", + "version": "2.2.0", "description": "", "main": "index.js", "scripts": { From 4b8d4a0627e164e406b0b69512fa7c933d8a96d3 Mon Sep 17 00:00:00 2001 From: Adilson Fuxe Date: Tue, 3 Jan 2023 22:20:18 +0100 Subject: [PATCH 5/5] fix: fix auth test --- __tests__/main/routes/signout-route.test.ts | 8 ++++++-- globalConfig.json | 2 +- test-suite/helper/index.ts | 13 ++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/__tests__/main/routes/signout-route.test.ts b/__tests__/main/routes/signout-route.test.ts index b0109be..7e47746 100644 --- a/__tests__/main/routes/signout-route.test.ts +++ b/__tests__/main/routes/signout-route.test.ts @@ -20,12 +20,16 @@ describe('Delete /signout', () => { it('Should return 204 on signout success', async () => { const accessToken = await mockAuthenticateUser(); - const accountBeforeSignout = await AccountModel.findOne({ accessToken }); + const accountBeforeSignout = await AccountModel.findOne({ + 'sessions.accessToken': accessToken, + }); await request(app) .delete('/api/v1/signout') .set('x-access-token', accessToken) .expect(204); - const accountAfterSignout = await AccountModel.findOne({ accessToken }); + const accountAfterSignout = await AccountModel.findOne({ + 'sessions.accessToken': accessToken, + }); expect(accountBeforeSignout).toBeTruthy(); expect(accountAfterSignout).toBeFalsy(); }); diff --git a/globalConfig.json b/globalConfig.json index c5aea23..f5bae3e 100644 --- a/globalConfig.json +++ b/globalConfig.json @@ -1 +1 @@ -{"mongoUri":"mongodb://127.0.0.1:45577/"} \ No newline at end of file +{"mongoUri":"mongodb://127.0.0.1:46787/"} \ No newline at end of file diff --git a/test-suite/helper/index.ts b/test-suite/helper/index.ts index f340860..0f9d92e 100644 --- a/test-suite/helper/index.ts +++ b/test-suite/helper/index.ts @@ -37,7 +37,18 @@ export const mockCreateAccountOnDb = export const mockAuthenticateUser = async (): Promise => { const { accountId } = await mockCreateAccountOnDb(); const accessToken = sign({ accountId }, env.jwtSecret); - await AccountModel.updateOne({ _id: accountId }, { accessToken }); + await AccountModel.updateOne( + { _id: accountId }, + { + sessions: [ + { + accessToken, + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + } + ); return accessToken; };