feat(api-gateway): add in memory cache for home server (#582)
This commit is contained in:
parent
aa8bd1f8dc
commit
0900dc75ac
2 changed files with 80 additions and 1 deletions
|
@ -15,6 +15,7 @@ import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionToken
|
|||
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
|
||||
import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache'
|
||||
import { WebSocketAuthMiddleware } from '../Controller/WebSocketAuthMiddleware'
|
||||
import { InMemoryCrossServiceTokenCache } from '../Infra/InMemory/InMemoryCrossServiceTokenCache'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
|
@ -26,6 +27,8 @@ export class ContainerConfigLoader {
|
|||
|
||||
const container = new Container()
|
||||
|
||||
const isConfiguredForHomeServer = env.get('DB_TYPE') === 'sqlite'
|
||||
|
||||
const newrelicWinstonFormatter = newrelicFormatter(winston)
|
||||
const winstonFormatters = [winston.format.splat(), winston.format.json()]
|
||||
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
|
||||
|
@ -75,9 +78,16 @@ export class ContainerConfigLoader {
|
|||
|
||||
// Services
|
||||
container.bind<HttpServiceInterface>(TYPES.HTTPService).to(HttpService)
|
||||
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
|
||||
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
|
||||
|
||||
if (isConfiguredForHomeServer) {
|
||||
container
|
||||
.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache)
|
||||
.toConstantValue(new InMemoryCrossServiceTokenCache(container.get(TYPES.Timer)))
|
||||
} else {
|
||||
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { CrossServiceTokenCacheInterface } from '../../Service/Cache/CrossServiceTokenCacheInterface'
|
||||
|
||||
export class InMemoryCrossServiceTokenCache implements CrossServiceTokenCacheInterface {
|
||||
private readonly PREFIX = 'cst'
|
||||
private readonly USER_CST_PREFIX = 'user-cst'
|
||||
|
||||
private crossServiceTokenCache: Map<string, string> = new Map()
|
||||
private crossServiceTokenTTLCache: Map<string, number> = new Map()
|
||||
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
async set(dto: {
|
||||
authorizationHeaderValue: string
|
||||
encodedCrossServiceToken: string
|
||||
expiresAtInSeconds: number
|
||||
userUuid: string
|
||||
}): Promise<void> {
|
||||
let userAuthHeaders = []
|
||||
const userAuthHeadersJSON = this.crossServiceTokenCache.get(`${this.USER_CST_PREFIX}:${dto.userUuid}`)
|
||||
if (userAuthHeadersJSON) {
|
||||
userAuthHeaders = JSON.parse(userAuthHeadersJSON)
|
||||
}
|
||||
userAuthHeaders.push(dto.authorizationHeaderValue)
|
||||
|
||||
this.crossServiceTokenCache.set(`${this.USER_CST_PREFIX}:${dto.userUuid}`, JSON.stringify(userAuthHeaders))
|
||||
this.crossServiceTokenTTLCache.set(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.expiresAtInSeconds)
|
||||
|
||||
this.crossServiceTokenCache.set(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.encodedCrossServiceToken)
|
||||
this.crossServiceTokenTTLCache.set(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.expiresAtInSeconds)
|
||||
}
|
||||
|
||||
async get(authorizationHeaderValue: string): Promise<string | null> {
|
||||
this.invalidateExpiredTokens()
|
||||
|
||||
const cachedToken = this.crossServiceTokenCache.get(`${this.PREFIX}:${authorizationHeaderValue}`)
|
||||
if (!cachedToken) {
|
||||
return null
|
||||
}
|
||||
|
||||
return cachedToken
|
||||
}
|
||||
|
||||
async invalidate(userUuid: string): Promise<void> {
|
||||
let userAuthorizationHeaderValues = []
|
||||
const userAuthHeadersJSON = this.crossServiceTokenCache.get(`${this.USER_CST_PREFIX}:${userUuid}`)
|
||||
if (userAuthHeadersJSON) {
|
||||
userAuthorizationHeaderValues = JSON.parse(userAuthHeadersJSON)
|
||||
}
|
||||
|
||||
for (const authorizationHeaderValue of userAuthorizationHeaderValues) {
|
||||
this.crossServiceTokenCache.delete(`${this.PREFIX}:${authorizationHeaderValue}`)
|
||||
this.crossServiceTokenTTLCache.delete(`${this.PREFIX}:${authorizationHeaderValue}`)
|
||||
}
|
||||
this.crossServiceTokenCache.delete(`${this.USER_CST_PREFIX}:${userUuid}`)
|
||||
this.crossServiceTokenTTLCache.delete(`${this.USER_CST_PREFIX}:${userUuid}`)
|
||||
}
|
||||
|
||||
private invalidateExpiredTokens(): void {
|
||||
const nowInSeconds = this.timer.getTimestampInSeconds()
|
||||
for (const [key, expiresAtInSeconds] of this.crossServiceTokenTTLCache.entries()) {
|
||||
if (expiresAtInSeconds <= nowInSeconds) {
|
||||
this.crossServiceTokenCache.delete(key)
|
||||
this.crossServiceTokenTTLCache.delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue