diff --git a/package.json b/package.json index 4303833..f47a408 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,13 @@ "@types/express": "^4.17.17", "cors": "^2.8.5", "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "zod": "^3.21.4" }, "devDependencies": { + "@types/jsonwebtoken": "^9.0.1", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^3.1.0", diff --git a/src/services/control-plane/adapters/drivens/index.ts b/src/services/control-plane/adapters/drivens/index.ts new file mode 100644 index 0000000..7592a3a --- /dev/null +++ b/src/services/control-plane/adapters/drivens/index.ts @@ -0,0 +1,2 @@ +export * from './logger-stub-adapter' +export * from './repo-querier-stub-adapter' diff --git a/src/services/control-plane/adapters/drivens/logger-stub-adapter.ts b/src/services/control-plane/adapters/drivens/logger-stub-adapter.ts new file mode 100644 index 0000000..a231d8f --- /dev/null +++ b/src/services/control-plane/adapters/drivens/logger-stub-adapter.ts @@ -0,0 +1,7 @@ +import { ForMonitoringAuthDetails } from '../../ports/drivens/for-monitoring' + +export class LoggerStubAdapter implements ForMonitoringAuthDetails { + log(event: string, message: string) { + console.log(event, message) + } +} diff --git a/src/services/control-plane/adapters/drivens/repo-querier-stub-adapter.ts b/src/services/control-plane/adapters/drivens/repo-querier-stub-adapter.ts new file mode 100644 index 0000000..e043c40 --- /dev/null +++ b/src/services/control-plane/adapters/drivens/repo-querier-stub-adapter.ts @@ -0,0 +1,16 @@ +import { ExternalUser } from '../../../repository/app/schemas' +import { ForRepoQuerying } from '../../ports/drivens' + +const userMock: ExternalUser = { + id: '1', + name: 'John Doe', + email: 'john@gmail.com', + admin: false, + user: true, +} + +export class RepoQuerierAdapter implements ForRepoQuerying { + getUser(_email: string): Promise { + return Promise.resolve(userMock) + } +} diff --git a/src/services/control-plane/adapters/drivers/auth-details-proxy.ts b/src/services/control-plane/adapters/drivers/auth-details-proxy.ts new file mode 100644 index 0000000..9f9f77d --- /dev/null +++ b/src/services/control-plane/adapters/drivers/auth-details-proxy.ts @@ -0,0 +1,15 @@ +import { ControlPlane } from '../../app/control-plane' +import { AuthDetails, Permissions } from '../../app/schemas' +import { ForManagingAuthDetails } from '../../ports/drivers/for-managing-auth-details' + +export class AuthDetailsProxy implements ForManagingAuthDetails { + constructor(private readonly controlPlane: ControlPlane) {} + + async getAuthDetails(email: string, password: string): Promise { + return this.controlPlane.getAuthDetails(email, password) + } + + async getPermissions(email: string): Promise { + return this.controlPlane.getPermissions(email) + } +} diff --git a/src/services/control-plane/adapters/drivers/index.ts b/src/services/control-plane/adapters/drivers/index.ts new file mode 100644 index 0000000..04ac8ac --- /dev/null +++ b/src/services/control-plane/adapters/drivers/index.ts @@ -0,0 +1 @@ +export * from './auth-details-proxy' diff --git a/src/services/control-plane/app/composition-root.ts b/src/services/control-plane/app/composition-root.ts new file mode 100644 index 0000000..02f7cee --- /dev/null +++ b/src/services/control-plane/app/composition-root.ts @@ -0,0 +1,30 @@ +import { LoggerStubAdapter } from '../adapters/drivens' +import { RepoQuerierAdapter } from '../adapters/drivens/repo-querier-stub-adapter' +import { AuthDetailsProxy } from '../adapters/drivers' +import { ControlPlane } from './control-plane' + +const compositionMock = () => { + const monitorStub = new LoggerStubAdapter() + const repoQueryStub = new RepoQuerierAdapter() + const dashboardApiMock = new ControlPlane(monitorStub, repoQueryStub) + + const authenticatorProxyAdapter = new AuthDetailsProxy(dashboardApiMock) + + return { + authenticatorProxyAdapter, + } +} + +export const { authenticatorProxyAdapter } = compositionMock() + +const registerMock = { + name: 'John', + email: 'jhon@gmail.com', + password: 'password', +} + +authenticatorProxyAdapter.getAuthDetails( + registerMock.email, + registerMock.password +) +authenticatorProxyAdapter.getPermissions(registerMock.email) diff --git a/src/services/control-plane/app/control-plane.test.ts b/src/services/control-plane/app/control-plane.test.ts new file mode 100644 index 0000000..d8d71fd --- /dev/null +++ b/src/services/control-plane/app/control-plane.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest' +import { RepoQuerierAdapter } from '../adapters/drivens/repo-querier-stub-adapter' +import { LoggerStubAdapter } from '../adapters/drivens' +import { ControlPlane } from './control-plane' +import { AuthDetails, Permissions } from './schemas' + +describe('ControlPlane', () => { + const monitorStub = new LoggerStubAdapter() + const repoQueryStub = new RepoQuerierAdapter() + const controPlaneMock = new ControlPlane(monitorStub, repoQueryStub) + + it.concurrent('should not expect a 123 token', async () => { + //GIVEN + const mockedParams = { + email: `12`, + password: '12345678', + } + + const expectedResult: AuthDetails = { + token: '123', + refreshToken: '123', + } + + //WHEN + let result + try { + result = await controPlaneMock.getAuthDetails( + mockedParams.email, + mockedParams.password + ) + } catch (error) {} + + //THEN + expect(result).not.toEqual(expectedResult) + }) + + it.concurrent('should get user permissions', async () => { + //GIVEN + + const expectedResult: Permissions = { + admin: false, + user: true, + } + + //WHEN + const result = await controPlaneMock.getPermissions('john@gmail.com') + + //THEN + expect(result).toEqual(expectedResult) + }) +}) diff --git a/src/services/control-plane/app/control-plane.ts b/src/services/control-plane/app/control-plane.ts new file mode 100644 index 0000000..6c80b8c --- /dev/null +++ b/src/services/control-plane/app/control-plane.ts @@ -0,0 +1,72 @@ +import { ForRepoQuerying } from '../ports/drivens' +import { ForMonitoringAuthDetails } from '../ports/drivens/for-monitoring' +import { ForManagingAuthDetails } from '../ports/drivers/for-managing-auth-details' +import { AuthDetails, Permissions } from './schemas' +import jwt from 'jsonwebtoken' + +export class ControlPlane implements ForManagingAuthDetails { + private secretKey: string = 'mySecretKey' + constructor( + private readonly logger: ForMonitoringAuthDetails, + private readonly repoQuerier: ForRepoQuerying + ) {} + + async getAuthDetails(email: string, password: string): Promise { + const generateToken = ( + payload: object, + secretKey: string, + expiresIn: string + ): string => { + const token = jwt.sign(payload, secretKey, { expiresIn }) + + return token + } + const generateRefreshToken = ( + payload: object, + secretKey: string, + expiresIn: string + ): string => { + const refreshToken = jwt.sign(payload, secretKey, { expiresIn }) + + return refreshToken + } + const token = generateToken({ email, password }, this.secretKey, '30m') + if (!token) { + this.logger.log('Token creation', 'FAILED') + throw new Error('Failed creating token, please check credentials') + } + + const refreshToken = generateRefreshToken( + { email, password }, + this.secretKey, + '1d' + ) + + if (!refreshToken) { + this.logger.log('RefreshToken creation', 'FAILED') + throw new Error('Failed creating Refreshtoken, please check credentials') + } + + const result = { + token, + refreshToken, + } + + console.log('AUTHDETAILS', result) + + return { token, refreshToken } + } + + async getPermissions(email: string): Promise { + const user = await this.repoQuerier.getUser(email) + + const result = { + admin: user?.admin, + user: user?.user, + } + + console.log('PERMISSIONS', result) + + return { admin: user?.admin, user: user?.user } + } +} diff --git a/src/services/control-plane/app/schemas/details.ts b/src/services/control-plane/app/schemas/details.ts new file mode 100644 index 0000000..88c4ab1 --- /dev/null +++ b/src/services/control-plane/app/schemas/details.ts @@ -0,0 +1,4 @@ +export interface AuthDetails { + token: string + refreshToken: string +} diff --git a/src/services/control-plane/app/schemas/index.ts b/src/services/control-plane/app/schemas/index.ts new file mode 100644 index 0000000..e9024b3 --- /dev/null +++ b/src/services/control-plane/app/schemas/index.ts @@ -0,0 +1,2 @@ +export * from './details' +export * from './permissions' diff --git a/src/services/control-plane/app/schemas/permissions.ts b/src/services/control-plane/app/schemas/permissions.ts new file mode 100644 index 0000000..e42bf80 --- /dev/null +++ b/src/services/control-plane/app/schemas/permissions.ts @@ -0,0 +1,4 @@ +export interface Permissions { + admin: boolean + user: boolean +} diff --git a/src/services/control-plane/ports/drivens/for-monitoring.ts b/src/services/control-plane/ports/drivens/for-monitoring.ts new file mode 100644 index 0000000..c0f66f6 --- /dev/null +++ b/src/services/control-plane/ports/drivens/for-monitoring.ts @@ -0,0 +1,3 @@ +export interface ForMonitoringAuthDetails { + log(event: string, message: string): void +} diff --git a/src/services/control-plane/ports/drivens/for-repo-querying.ts b/src/services/control-plane/ports/drivens/for-repo-querying.ts new file mode 100644 index 0000000..71effe7 --- /dev/null +++ b/src/services/control-plane/ports/drivens/for-repo-querying.ts @@ -0,0 +1,5 @@ +import { ExternalUser } from '../../../repository/app/schemas' + +export interface ForRepoQuerying { + getUser(email: string): Promise +} diff --git a/src/services/control-plane/ports/drivens/index.ts b/src/services/control-plane/ports/drivens/index.ts new file mode 100644 index 0000000..311b054 --- /dev/null +++ b/src/services/control-plane/ports/drivens/index.ts @@ -0,0 +1,2 @@ +export * from './for-monitoring' +export * from './for-repo-querying' diff --git a/src/services/control-plane/ports/drivers/for-managing-auth-details.ts b/src/services/control-plane/ports/drivers/for-managing-auth-details.ts new file mode 100644 index 0000000..22a2de5 --- /dev/null +++ b/src/services/control-plane/ports/drivers/for-managing-auth-details.ts @@ -0,0 +1,6 @@ +import { AuthDetails, Permissions } from '../../app/schemas' + +export interface ForManagingAuthDetails { + getAuthDetails(email: string, password: string): Promise + getPermissions(email: string): Promise +} diff --git a/src/services/control-plane/ports/drivers/index.ts b/src/services/control-plane/ports/drivers/index.ts new file mode 100644 index 0000000..2a743fa --- /dev/null +++ b/src/services/control-plane/ports/drivers/index.ts @@ -0,0 +1 @@ +export * from './for-managing-auth-details' diff --git a/src/services/dashboard-api/adapters/drivens/index.ts b/src/services/dashboard-api/adapters/drivens/index.ts index 8bbd5a5..fdfef91 100644 --- a/src/services/dashboard-api/adapters/drivens/index.ts +++ b/src/services/dashboard-api/adapters/drivens/index.ts @@ -1,2 +1,2 @@ -export * from './control-authenticator-stub-adapter'; -export * from './repo-querier-stub-adapter'; +export * from './control-authenticator-stub-adapter' +export * from './repo-querier-stub-adapter' diff --git a/src/services/dashboard-api/adapters/drivens/repo-querier-stub-adapter.ts b/src/services/dashboard-api/adapters/drivens/repo-querier-stub-adapter.ts index 556bfc7..6729e92 100644 --- a/src/services/dashboard-api/adapters/drivens/repo-querier-stub-adapter.ts +++ b/src/services/dashboard-api/adapters/drivens/repo-querier-stub-adapter.ts @@ -1,19 +1,21 @@ -import { ExternalUser } from "../../../repository/app/schemas"; -import { User } from "../../app/schemas"; -import { ForRepoQuerying } from "../../ports/drivens"; +import { ExternalUser } from '../../../repository/app/schemas' +import { User } from '../../app/schemas' +import { ForRepoQuerying } from '../../ports/drivens' const userMock: ExternalUser = { - id: "1", - name: "John Doe", - email: "john@gmail.com", -}; + id: '1', + name: 'John Doe', + email: 'john@gmail.com', + admin: false, + user: true, +} export class RepoQuerierStub implements ForRepoQuerying { getUser(_email: string): Promise { - return Promise.resolve(userMock); + return Promise.resolve(userMock) } createUser(_user: User): Promise { - return Promise.resolve(userMock); + return Promise.resolve(userMock) } } diff --git a/src/services/dashboard-api/app/dashboard-api.test.ts b/src/services/dashboard-api/app/dashboard-api.test.ts index 3d1d736..0fab294 100644 --- a/src/services/dashboard-api/app/dashboard-api.test.ts +++ b/src/services/dashboard-api/app/dashboard-api.test.ts @@ -1,71 +1,75 @@ -import { describe, expect, it } from "vitest"; -import { ControlAuthenticatorStub } from "../adapters/drivens/control-authenticator-stub-adapter"; -import { RepoQuerierStub } from "../adapters/drivens/repo-querier-stub-adapter"; -import { DashboardApi } from "./dashboard-api"; -import { AuthenticatedUser, User } from "./schemas"; +import { describe, expect, it } from 'vitest' +import { ControlAuthenticatorStub } from '../adapters/drivens/control-authenticator-stub-adapter' +import { RepoQuerierStub } from '../adapters/drivens/repo-querier-stub-adapter' +import { DashboardApi } from './dashboard-api' +import { AuthenticatedUser, User } from './schemas' -describe("DashboardApi", () => { - const controlAuthenticatorStub = new ControlAuthenticatorStub(); - const repoQuerierStub = new RepoQuerierStub(); +describe('DashboardApi', () => { + const controlAuthenticatorStub = new ControlAuthenticatorStub() + const repoQuerierStub = new RepoQuerierStub() const dashboardApiMock = new DashboardApi( controlAuthenticatorStub, repoQuerierStub - ); + ) - it.concurrent("should login", async () => { + it.concurrent('should login', async () => { //GIVEN const mockedParams = { - email: "john@gmail.com", - password: "12345678", - }; + email: 'john@gmail.com', + password: '12345678', + } const expectedResult: AuthenticatedUser = { - id: "1", - name: "John Doe", - email: "john@gmail.com", - token: "token", - refreshToken: "refreshToken", + id: '1', + name: 'John Doe', + email: 'john@gmail.com', + token: 'token', + refreshToken: 'refreshToken', permissions: { admin: true, user: true, }, - }; + admin: false, + user: true, + } //WHEN const result = await dashboardApiMock.login( mockedParams.email, mockedParams.password - ); + ) //THEN - expect(result).toEqual(expectedResult); - }); + expect(result).toEqual(expectedResult) + }) - it.concurrent("should register", async () => { + it.concurrent('should register', async () => { //GIVEN const mockedUser: User = { - name: "John", - email: "john@gmail.com", - password: "password", - }; + name: 'John', + email: 'john@gmail.com', + password: 'password', + } const expectedResult: AuthenticatedUser = { - id: "1", - name: "John Doe", - email: "john@gmail.com", - token: "token", - refreshToken: "refreshToken", + id: '1', + name: 'John Doe', + email: 'john@gmail.com', + token: 'token', + refreshToken: 'refreshToken', permissions: { admin: true, user: true, }, - }; + admin: false, + user: true, + } //WHEN - const result = await dashboardApiMock.register(mockedUser); + const result = await dashboardApiMock.register(mockedUser) //THEN - expect(result).toEqual(expectedResult); - }); -}); + expect(result).toEqual(expectedResult) + }) +}) diff --git a/src/services/dashboard-api/app/schemas/user.ts b/src/services/dashboard-api/app/schemas/user.ts index 3147683..cd92faf 100644 --- a/src/services/dashboard-api/app/schemas/user.ts +++ b/src/services/dashboard-api/app/schemas/user.ts @@ -20,4 +20,4 @@ export type AuthenticatedUser = z.infer; export interface User extends Pick { password: string; -} +} \ No newline at end of file diff --git a/src/services/repository/app/repository.test.ts b/src/services/repository/app/repository.test.ts index 9574a5e..4d679fc 100644 --- a/src/services/repository/app/repository.test.ts +++ b/src/services/repository/app/repository.test.ts @@ -1,96 +1,106 @@ -import { describe, expect, it } from "vitest"; -import { LoggerStubAdapter } from "../adapters/drivens"; -import { Repository } from "./repository"; +import { describe, expect, it } from 'vitest' +import { LoggerStubAdapter } from '../adapters/drivens' +import { Repository } from './repository' -describe("Repository", () => { - const monitorStub = new LoggerStubAdapter(); - const repositoryMock = new Repository(monitorStub); +describe('Repository', () => { + const monitorStub = new LoggerStubAdapter() + const repositoryMock = new Repository(monitorStub) - it.concurrent("should control that the user does not exist", async () => { + it.concurrent('should control that the user does not exist', async () => { //GIVEN - const mockedEmail = "samuelcito@gmail.com"; + const mockedEmail = 'samuelcito@gmail.com' const expectedResult = { - id: "1", - name: "Samuel", - email: "samuelcito@gmail.com", - }; + id: '1', + name: 'Samuel', + email: 'samuelcito@gmail.com', + } //WHEN - let result; + let result try { - result = await repositoryMock.getUser(mockedEmail); + result = await repositoryMock.getUser(mockedEmail) } catch (error) {} //THEN - expect(result).not.toEqual(expectedResult); - }); + expect(result).not.toEqual(expectedResult) + }) - it.concurrent("should create a new user", async () => { + it.concurrent('should create a new user', async () => { //GIVEN const mockedUser = { - name: "Samuel", - email: "samuelcito@gmail.com", - password: "password", - }; + name: 'Samuel', + email: 'samuelcito@gmail.com', + password: 'password', + admin: false, + user: true, + } const expectedResult = { - id: "1", + id: '1', name: mockedUser.name, email: mockedUser.email, - }; + admin: false, + user: true, + } // WHEN - let result; + let result try { - result = await repositoryMock.createUser(mockedUser); + result = await repositoryMock.createUser(mockedUser) } catch (error) {} //THEN - expect(result).toEqual(expectedResult); - }); + expect(result).toEqual(expectedResult) + }) - it.concurrent("should control that the user already exists", async () => { + it.concurrent('should control that the user already exists', async () => { //GIVEN const mockedUser = { - name: "Samuel", - email: "samuelcito@gmail.com", - password: "password", - }; + name: 'Samuel', + email: 'samuelcito@gmail.com', + password: 'password', + admin: false, + user: true, + } const expectedResult = { - id: "1", + id: '1', name: mockedUser.name, email: mockedUser.email, - }; + admin: false, + user: true, + } // WHEN - let result; + let result try { - result = await repositoryMock.createUser(mockedUser); + result = await repositoryMock.createUser(mockedUser) } catch (error) {} //THEN - expect(result).not.toEqual(expectedResult); - }); + expect(result).not.toEqual(expectedResult) + }) - it.concurrent("should get a user", async () => { + it.concurrent('should get a user', async () => { //GIVEN - const mockedEmail = "samuelcito@gmail.com"; + const mockedEmail = 'samuelcito@gmail.com' const expectedResult = { - id: "1", - name: "Samuel", - email: "samuelcito@gmail.com", - }; + id: '1', + name: 'Samuel', + email: 'samuelcito@gmail.com', + admin: false, + user: true, + } //WHEN - let result; + let result try { - result = await repositoryMock.getUser(mockedEmail); + result = await repositoryMock.getUser(mockedEmail) } catch (error) {} //THEN - expect(result).toEqual(expectedResult); - }); -}); + expect(result).toEqual(expectedResult) + }) +}) diff --git a/src/services/repository/app/repository.ts b/src/services/repository/app/repository.ts index 6be4ce5..fc66f10 100644 --- a/src/services/repository/app/repository.ts +++ b/src/services/repository/app/repository.ts @@ -1,40 +1,46 @@ -import { ForMonitoring } from "../ports/drivens"; -import { ForManagingUser } from "../ports/drivers"; -import { ExternalUser, RepoUser, User } from "./schemas"; +import { ForMonitoring } from '../ports/drivens' +import { ForManagingUser } from '../ports/drivers' +import { ExternalUser, RepoUser, User } from './schemas' export class Repository implements ForManagingUser { - private userList: RepoUser[] = []; + private userList: RepoUser[] = [] constructor(private readonly logger: ForMonitoring) {} async getUser(email: string): Promise { - const user = this.userList.find((user) => user.email === email); + const user = this.userList.find((user) => user.email === email) if (!user) { - this.logger.log("GetUser", "User not found"); - throw new Error("User not found"); + this.logger.log('GetUser', 'User not found') + throw new Error('User not found') } return { id: user.id, name: user.name, email: user.email, - }; + admin: user.admin, + user: user.user, + } } async createUser(user: User): Promise { - const userExists = this.userList.find((user) => user.email === user.email); + const userExists = this.userList.find((user) => user.email === user.email) if (userExists) { - this.logger.log("CreateUser", "User already exists"); - throw new Error("User already exists"); + this.logger.log('CreateUser', 'User already exists') + throw new Error('User already exists') } const newUser = { ...user, + admin: user?.admin ?? false, + user: user?.user ?? true, id: String(this.userList.length + 1), - }; + } - this.userList.push(newUser); + this.userList.push(newUser) return { id: newUser.id, name: newUser.name, email: newUser.email, - }; + admin: newUser.admin, + user: newUser.user, + } } } diff --git a/src/services/repository/app/schemas/user.ts b/src/services/repository/app/schemas/user.ts index b70195b..3ba6366 100644 --- a/src/services/repository/app/schemas/user.ts +++ b/src/services/repository/app/schemas/user.ts @@ -1,11 +1,13 @@ export interface User { - name: string; - email: string; - password: string; + name: string + email: string + password: string + admin: boolean + user: boolean } export interface RepoUser extends User { - id: string; + id: string } -export type ExternalUser = Omit; +export type ExternalUser = Omit diff --git a/tsconfig.json b/tsconfig.json index 3d0a51a..c3e0a33 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,10 @@ "noEmit": true, "jsx": "react-jsx" }, + "ts-node": { + "esm": true, + "experimentalSpecifierResolution": "node" + }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/yarn.lock b/yarn.lock index 1264244..31511e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1560,4 +1560,4 @@ yocto-queue@^1.0.0: zod@^3.21.4: version "3.21.4" resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" - integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== + integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== \ No newline at end of file