Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complete integration of in-memory cache, and connect with AuthCodeEntity #21

Merged
merged 8 commits into from
Aug 11, 2021
16 changes: 11 additions & 5 deletions server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { db, app, http } from './setup'
import { db, app, http, cache } from './setup'

interface AuthOptions {
port: string
}

export interface Persistence {
xujustinj marked this conversation as resolved.
Show resolved Hide resolved
db: db.DB
cache: cache.Cache
}

const auth = async (options: AuthOptions) => {
const { orm, repos } = await db.setupMikroDB()
const migrator = orm.getMigrator()
const dbRepo = await db.setupMikroDB()
const cacheRepo = await cache.setupRedisCache()
const migrator = dbRepo.orm.getMigrator()
await migrator.up()

const { useCases, controllers } = app.setupApplication(repos)
const { useCases, controllers } = app.setupApplication({db: dbRepo, cache: cacheRepo})

const { webServer } = http.setupAuthExpress(controllers, useCases, { mikroORM: orm })
const { webServer } = http.setupAuthExpress(controllers, useCases, { mikroORM: dbRepo.orm })
webServer.listen(options.port, () => {
console.log(`LOOLABS AUTH server running on http://localhost:${port}/api/v1 🦆`)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { EncryptedClientSecret } from '../../value-objects/encrypted-client-secr

interface AuthSecretProps {
clientId: string,
encryptedClientSecret: EncryptedClientSecret
encryptedClientSecret: EncryptedClientSecret,
isVerified: boolean
}

export class AuthSecret extends AggregateRoot<AuthSecretProps> {
Expand All @@ -31,4 +32,8 @@ export class AuthSecret extends AggregateRoot<AuthSecretProps> {
get encryptedClientSecret(): EncryptedClientSecret {
return this.props.encryptedClientSecret
}

get isVerified() :boolean {
return this.props.isVerified
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Result } from '../../../../../../shared/core/result'
import { AuthCodeEntity } from '../../../../../../shared/infra/db/entities/auth-code.entity'
import { AuthCodeEntity } from '../../../../../../shared/infra/cache/entities/auth-code-entity'
import { DBError, DBErrors } from '../../../../../../shared/infra/db/errors/errors'
import { AuthCode } from '../../../../domain/entities/auth-code'
import { AuthCodeString } from '../../../../domain/value-objects/auth-code'
Expand All @@ -26,7 +26,7 @@ export class MockAuthCodeRepo implements AuthCodeRepo {

async save(authCode: AuthCode): Promise<void> {
const authCodeEntity = await AuthCodeMap.toPersistence(authCode)
this.authCodeEntities.set(authCodeEntity.id, authCodeEntity)
this.authCodeEntities.set(authCodeEntity.authCodeString, authCodeEntity)
}

async delete(authCode: AuthCode): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Result } from '../../../../../../shared/core/result'
import { AuthCodeEntity } from '../../../../../../shared/infra/cache/entities/auth-code-entity'
import { RedisRepository } from '../../../../../../shared/infra/cache/redis-repository'
import { DBError, DBErrors } from '../../../../../../shared/infra/db/errors/errors'
import { AuthCode } from '../../../../domain/entities/auth-code'
import { AuthCodeString } from '../../../../domain/value-objects/auth-code'
import { AuthCodeMap } from '../../../../mappers/auth-code-map'
import { AuthCodeRepo } from '../auth-code-repo'

export const AUTH_CODE_CACHE_TTL_SECONDS = 300 //5 minutes

export class RedisAuthCodeRepo implements AuthCodeRepo {
constructor(protected authCodeEntityRepo: RedisRepository<AuthCodeEntity>) {}

async getAuthCodeFromAuthCodeString(authCodeString: AuthCodeString): Promise<Result<AuthCode, DBErrors>> {
const authCode = await this.authCodeEntityRepo.getEntity(authCodeString.getValue())
if (authCode.isErr()) return Result.err(new DBError.AuthSecretNotFoundError(authCodeString.getValue()))
return Result.ok(AuthCodeMap.toDomain(authCode.value))
}

async save(authCode: AuthCode): Promise<void> {
const authCodeEntity = await AuthCodeMap.toPersistence(authCode)
this.authCodeEntityRepo.saveEntity(authCodeEntity, {mode: 'EX', value: AUTH_CODE_CACHE_TTL_SECONDS})
}

async delete(authCode: AuthCode): Promise<void> {
const authCodeEntity = await AuthCodeMap.toPersistence(authCode)
this.authCodeEntityRepo.removeEntity(authCodeEntity)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ import { DBErrors } from '../../../../../shared/infra/db/errors/errors'
import { AuthSecret } from '../../../domain/entities/auth-secret'

export abstract class AuthSecretRepo {
abstract exists(clientId: string): Promise<Result<boolean, DBErrors>>
abstract getAuthSecretByClientId(clientId: string): Promise<Result<AuthSecret, DBErrors>>
abstract save(authSecret: AuthSecret): Promise<void>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,30 @@ import { AuthSecretRepo } from '../../auth-secret-repo/auth-secret-repo'
export class MikroAuthSecretRepo implements AuthSecretRepo {
constructor(protected authSecretEntityRepo: EntityRepository<AuthSecretEntity>) {}

async exists(clientId: string): Promise<Result<boolean, DBErrors>> {
const authSecretEntity = await this.authSecretEntityRepo.findOne({
clientId: clientId,
})
if(authSecretEntity !== null){
return Result.ok(authSecretEntity !== null)
} else {
return Result.err(new DBError.AuthSecretNotFoundError(clientId))
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this function isn't doing what's intended. If the repo can't find an authSecretEntity, shouldn't it return false instead of an error? Same comment also applies to Mock repo.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, good call.


async getAuthSecretByClientId(clientId: string): Promise<Result<AuthSecret, DBErrors>> {
const authSecret = await this.authSecretEntityRepo.findOne({ clientId: clientId })
if (authSecret === null) return Result.err(new DBError.AuthSecretNotFoundError(clientId))
return Result.ok(AuthSecretMap.toDomain(authSecret))
}

async save(authSecret: AuthSecret): Promise<void> {
const exists = await this.exists(authSecret.clientId)

if (exists.isOk() && exists.value) return

const authSecretEntity = await AuthSecretMap.toPersistence(authSecret)
this.authSecretEntityRepo.persist(authSecretEntity)
this.authSecretEntityRepo.flush()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export class MockAuthSecretRepo implements AuthSecretRepo {
))
}

async exists(clientId: string): Promise<Result<boolean, DBErrors>> {
for (const authSecretEntity of this.authSecretEntities.values()) {
if (authSecretEntity.clientId === clientId) return Result.ok(true)
}
return Result.err(new DBError.AuthSecretNotFoundError(clientId))
}

async getAuthSecretByClientId(clientId: string): Promise<Result<AuthSecret, DBErrors>> {
const authSecretEntity = this.authSecretEntities.get(clientId)

Expand All @@ -22,4 +29,13 @@ export class MockAuthSecretRepo implements AuthSecretRepo {
}
return Result.ok(AuthSecretMap.toDomain(authSecretEntity))
}

async save(authSecret: AuthSecret): Promise<void> {
const exists = await this.exists(authSecret.clientId)
if (exists) return

const authSecretEntity = await AuthSecretMap.toPersistence(authSecret)
this.authSecretEntities.set(authSecretEntity.clientId, authSecretEntity)
}

}
7 changes: 2 additions & 5 deletions server/src/modules/users/mappers/auth-code-map.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { UniqueEntityID } from '../../../shared/domain/unique-entity-id'
import { AuthCodeEntity } from '../../../shared/infra/db/entities/auth-code.entity'

import { AuthCodeEntity } from '../../../shared/infra/cache/entities/auth-code-entity'
import { AuthCode } from '../domain/entities/auth-code'
import { AuthCodeString } from '../domain/value-objects/auth-code'

Expand All @@ -13,8 +11,7 @@ export class AuthCodeMap {
clientId: authCodeEntity.clientId,
userId: authCodeEntity.clientId,
authCodeString: new AuthCodeString(authCodeEntity.authCodeString)
},
new UniqueEntityID(authCodeEntity.id)
}
)
if (authCode.isErr()) throw new Error() // TODO: check if we should handle error differently

Expand Down
12 changes: 11 additions & 1 deletion server/src/modules/users/mappers/auth-secret-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class AuthSecretMap {
const authCodeResult = AuthSecret.create(
{
clientId: authSecretEntity.clientId,
encryptedClientSecret: encryptedClientSecret.value
encryptedClientSecret: encryptedClientSecret.value,
isVerified: authSecretEntity.isVerified
},
new UniqueEntityID(authSecretEntity.id)
)
Expand All @@ -26,4 +27,13 @@ export class AuthSecretMap {

return authCodeResult.value
}

public static async toPersistence(authSecret: AuthSecret): Promise<AuthSecretEntity> {
const authSecretEntity = new AuthSecretEntity()
authSecretEntity.clientId = authSecret.clientId
authSecretEntity.encryptedClientSecret = authSecret.encryptedClientSecret.value
authSecretEntity.isVerified = authSecret.isVerified

return authSecretEntity
}
}
6 changes: 3 additions & 3 deletions server/src/setup/application/application.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Application } from './types'
import { Repos } from '../database'
import { setupControllers } from './controllers'
import { setupUseCases } from './use-cases'
import { Persistence } from '../..'

export const setupApplication = (repos: Repos): Application => {
const useCases = setupUseCases(repos)
export const setupApplication = (persistence: Persistence): Application => {
const useCases = setupUseCases(persistence)
const controllers = setupControllers(useCases)

return {
Expand Down
10 changes: 5 additions & 5 deletions server/src/setup/application/use-cases.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { UseCases } from './types'
import { Repos } from '../database'
import { CreateUserUseCase } from '../../modules/users/application/use-cases/create-user/create-user-use-case'
import { LoginUserUseCase } from '../../modules/users/application/use-cases/login-user/login-user-use-case'
import { PassportUserAuthHandler } from '../../shared/auth/implementations/passport-user-auth-handler'
import { GetUserUseCase } from '../../modules/users/application/use-cases/get-user/get-user-use-case'
import { AuthenticateUserUseCase } from '../../modules/users/application/use-cases/authenticate-user/authenticate-user-use-case'
import { ProtectedUserUseCase } from '../../modules/users/application/use-cases/protected-user/protected-user-use-case'
import { Persistence } from '../..'

export const setupUseCases = (repos: Repos): UseCases => {
export const setupUseCases = ({db}: Persistence): UseCases => {
return {
createUser: new CreateUserUseCase(new PassportUserAuthHandler(), repos.user),
createUser: new CreateUserUseCase(new PassportUserAuthHandler(), db.repos.user),
loginUser: new LoginUserUseCase(new PassportUserAuthHandler()),
getUser: new GetUserUseCase(repos.user),
authUser: new AuthenticateUserUseCase(repos.user),
getUser: new GetUserUseCase(db.repos.user),
authUser: new AuthenticateUserUseCase(db.repos.user),
protectedUser: new ProtectedUserUseCase()
}
}
5 changes: 4 additions & 1 deletion server/src/setup/cache/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export { Client } from './redis'
export { RedisClient } from './redis'
export { Repos, Cache } from './types'
export { RedisEntityRepos, RedisRepos, RedisCache, setupRedisCache } from './redis-cache'
export { MockEntities, MockRepos, MockDB, setupMockDB } from './mock-cache'
31 changes: 31 additions & 0 deletions server/src/setup/cache/mock-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AuthCodeRepo } from '../../modules/users/infra/repos/auth-code-repo/auth-code-repo'
import { MockAuthCodeRepo } from '../../modules/users/infra/repos/auth-code-repo/implementations/mock-auth-code-repo'
import { AuthCodeEntity } from '../../shared/infra/cache/entities/auth-code-entity'
import { Repos, Cache } from './types'


interface MockEntities {
authCodes?: Array<AuthCodeEntity>
}

interface MockRepos extends Repos {
authCode: AuthCodeRepo
}

const setupMockRepos = (entities: MockEntities): MockRepos => {
return {
authCode: new MockAuthCodeRepo(entities.authCodes)
}
}

interface MockDB extends Cache {
xujustinj marked this conversation as resolved.
Show resolved Hide resolved
repos: MockRepos
}

const setupMockDB = (entities: MockEntities): MockDB => {
return {
repos: setupMockRepos(entities),
}
}

export { MockEntities, MockRepos, MockDB, setupMockDB }
41 changes: 41 additions & 0 deletions server/src/setup/cache/redis-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { RedisAuthCodeRepo } from "../../modules/users/infra/repos/auth-code-repo/implementations/redis-auth-code-repo"
import { AuthCodeEntity } from "../../shared/infra/cache/entities/auth-code-entity"
import { RedisRepository } from "../../shared/infra/cache/redis-repository"
import { Cache, Repos } from "./types"

interface RedisEntityRepos {
authCode: RedisRepository<AuthCodeEntity>
}

const setupRedisEntityRepos = (): RedisEntityRepos => {
return {
authCode: new RedisRepository<AuthCodeEntity>()
}
}

interface RedisRepos extends Repos {
authCode: RedisAuthCodeRepo
}

const setupRedisRepos = (redisEntityRepos: RedisEntityRepos): RedisRepos => {
return {
authCode: new RedisAuthCodeRepo(redisEntityRepos.authCode)
}
}

interface RedisCache extends Cache {
entityRepos: RedisEntityRepos
repos: RedisRepos
}

const setupRedisCache = async (): Promise<RedisCache> => {
const entityRepos = setupRedisEntityRepos()
const repos = setupRedisRepos(entityRepos)

return {
entityRepos,
repos
}
}

export { RedisEntityRepos, RedisRepos, RedisCache, setupRedisCache }
16 changes: 12 additions & 4 deletions server/src/setup/cache/redis.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import redis from 'redis'

export const Client = redis.createClient({
url: `${process.env.CACHE_URL}`,
password: `${process.env.CACHE_PASSWORD}`
})
let redisClient: null | redis.RedisClient = null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice use of caching


export const RedisClient = () => {
if(redisClient === null){
redisClient = redis.createClient({
url: `${process.env.CACHE_URL}`,
password: `${process.env.CACHE_PASSWORD}`
})
}
return redisClient
}

9 changes: 9 additions & 0 deletions server/src/setup/cache/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { AuthCodeRepo } from '../../modules/users/infra/repos/auth-code-repo/auth-code-repo';

export interface Repos {
authCode: AuthCodeRepo
}

export interface Cache {
repos: Repos
}
Loading