fix(auth): reduce session select queries in favor of insert/update model

This commit is contained in:
Karol Sójko
2023-10-11 10:12:25 +02:00
parent 78b13261bf
commit 25a875cbbc
9 changed files with 65 additions and 33 deletions

View File

@@ -5,5 +5,6 @@ export interface EphemeralSessionRepositoryInterface {
findOneByUuidAndUserUuid(uuid: string, userUuid: string): Promise<EphemeralSession | null>
findAllByUserUuid(userUuid: string): Promise<Array<EphemeralSession>>
deleteOne(uuid: string, userUuid: string): Promise<void>
save(ephemeralSession: EphemeralSession): Promise<void>
insert(ephemeralSession: EphemeralSession): Promise<void>
update(ephemeralSession: EphemeralSession): Promise<void>
}

View File

@@ -3,7 +3,8 @@ import { RevokedSession } from './RevokedSession'
export interface RevokedSessionRepositoryInterface {
findOneByUuid(uuid: string): Promise<RevokedSession | null>
findAllByUserUuid(userUuid: string): Promise<Array<RevokedSession>>
save(revokedSession: RevokedSession): Promise<RevokedSession>
insert(revokedSession: RevokedSession): Promise<void>
update(revokedSession: RevokedSession): Promise<void>
remove(revokedSession: RevokedSession): Promise<RevokedSession>
clearUserAgentByUserUuid(userUuid: string): Promise<void>
}

View File

@@ -9,7 +9,8 @@ export interface SessionRepositoryInterface {
findAllByUserUuid(userUuid: string): Promise<Array<Session>>
deleteAllByUserUuidExceptOne(dto: { userUuid: Uuid; currentSessionUuid: Uuid }): Promise<void>
deleteOneByUuid(uuid: string): Promise<void>
save(session: Session): Promise<Session>
insert(session: Session): Promise<void>
update(session: Session): Promise<void>
remove(session: Session): Promise<Session>
clearUserAgentByUserUuid(userUuid: string): Promise<void>
removeExpiredBefore(date: Date): Promise<void>

View File

@@ -69,18 +69,21 @@ describe('SessionService', () => {
sessionRepository = {} as jest.Mocked<SessionRepositoryInterface>
sessionRepository.findOneByUuid = jest.fn().mockReturnValue(null)
sessionRepository.deleteOneByUuid = jest.fn()
sessionRepository.save = jest.fn().mockReturnValue(existingSession)
sessionRepository.insert = jest.fn()
sessionRepository.update = jest.fn()
settingService = {} as jest.Mocked<SettingServiceInterface>
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
ephemeralSessionRepository = {} as jest.Mocked<EphemeralSessionRepositoryInterface>
ephemeralSessionRepository.save = jest.fn()
ephemeralSessionRepository.insert = jest.fn()
ephemeralSessionRepository.update = jest.fn()
ephemeralSessionRepository.findOneByUuid = jest.fn()
ephemeralSessionRepository.deleteOne = jest.fn()
revokedSessionRepository = {} as jest.Mocked<RevokedSessionRepositoryInterface>
revokedSessionRepository.save = jest.fn()
revokedSessionRepository.insert = jest.fn()
revokedSessionRepository.update = jest.fn()
existingEphemeralSession = {} as jest.Mocked<EphemeralSession>
existingEphemeralSession.uuid = '2-3-4'
@@ -129,7 +132,7 @@ describe('SessionService', () => {
it('should mark a revoked session as received', async () => {
await createService().markRevokedSessionAsReceived(revokedSession)
expect(revokedSessionRepository.save).toHaveBeenCalledWith({
expect(revokedSessionRepository.update).toHaveBeenCalledWith({
uuid: '2e1e43',
received: true,
receivedAt: new Date(1),
@@ -145,8 +148,8 @@ describe('SessionService', () => {
readonly_access: false,
})
expect(sessionRepository.save).toHaveBeenCalled()
expect(ephemeralSessionRepository.save).not.toHaveBeenCalled()
expect(sessionRepository.update).toHaveBeenCalled()
expect(ephemeralSessionRepository.update).not.toHaveBeenCalled()
})
it('should refresh access and refresh tokens for an ephemeral session', async () => {
@@ -158,8 +161,8 @@ describe('SessionService', () => {
readonly_access: false,
})
expect(sessionRepository.save).not.toHaveBeenCalled()
expect(ephemeralSessionRepository.save).toHaveBeenCalled()
expect(sessionRepository.update).not.toHaveBeenCalled()
expect(ephemeralSessionRepository.update).toHaveBeenCalled()
})
it('should create new session for a user', async () => {
@@ -173,8 +176,8 @@ describe('SessionService', () => {
readonlyAccess: false,
})
expect(sessionRepository.save).toHaveBeenCalledWith(expect.any(Session))
expect(sessionRepository.save).toHaveBeenCalledWith({
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
expect(sessionRepository.insert).toHaveBeenCalledWith({
accessExpiration: expect.any(Date),
apiVersion: '003',
createdAt: expect.any(Date),
@@ -209,8 +212,8 @@ describe('SessionService', () => {
readonlyAccess: false,
})
expect(sessionRepository.save).toHaveBeenCalledWith(expect.any(Session))
expect(sessionRepository.save).toHaveBeenCalledWith({
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
expect(sessionRepository.insert).toHaveBeenCalledWith({
accessExpiration: expect.any(Date),
apiVersion: '003',
createdAt: expect.any(Date),
@@ -248,8 +251,8 @@ describe('SessionService', () => {
readonlyAccess: false,
})
expect(sessionRepository.save).toHaveBeenCalledWith(expect.any(Session))
expect(sessionRepository.save).toHaveBeenCalledWith({
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
expect(sessionRepository.insert).toHaveBeenCalledWith({
accessExpiration: expect.any(Date),
apiVersion: '003',
createdAt: expect.any(Date),
@@ -405,8 +408,8 @@ describe('SessionService', () => {
readonlyAccess: false,
})
expect(ephemeralSessionRepository.save).toHaveBeenCalledWith(expect.any(EphemeralSession))
expect(ephemeralSessionRepository.save).toHaveBeenCalledWith({
expect(ephemeralSessionRepository.insert).toHaveBeenCalledWith(expect.any(EphemeralSession))
expect(ephemeralSessionRepository.insert).toHaveBeenCalledWith({
accessExpiration: expect.any(Date),
apiVersion: '003',
createdAt: expect.any(Date),
@@ -684,7 +687,7 @@ describe('SessionService', () => {
it('should revoked a session', async () => {
await createService().createRevokedSession(existingSession)
expect(revokedSessionRepository.save).toHaveBeenCalledWith({
expect(revokedSessionRepository.insert).toHaveBeenCalledWith({
uuid: '2e1e43',
userUuid: '1-2-3',
userAgent: 'Chrome',

View File

@@ -57,7 +57,7 @@ export class SessionService implements SessionServiceInterface {
const sessionPayload = await this.createTokens(session)
await this.sessionRepository.save(session)
await this.sessionRepository.insert(session)
try {
const userSubscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.user.uuid)
@@ -92,7 +92,7 @@ export class SessionService implements SessionServiceInterface {
const sessionPayload = await this.createTokens(ephemeralSession)
await this.ephemeralSessionRepository.save(ephemeralSession)
await this.ephemeralSessionRepository.insert(ephemeralSession)
return {
sessionHttpRepresentation: sessionPayload,
@@ -104,9 +104,9 @@ export class SessionService implements SessionServiceInterface {
const sessionPayload = await this.createTokens(dto.session)
if (dto.isEphemeral) {
await this.ephemeralSessionRepository.save(dto.session)
await this.ephemeralSessionRepository.update(dto.session)
} else {
await this.sessionRepository.save(dto.session)
await this.sessionRepository.update(dto.session)
}
return sessionPayload
@@ -221,7 +221,9 @@ export class SessionService implements SessionServiceInterface {
revokedSession.received = true
revokedSession.receivedAt = this.timer.getUTCDate()
return this.revokedSessionRepository.save(revokedSession)
await this.revokedSessionRepository.update(revokedSession)
return revokedSession
}
async deleteSessionByToken(token: string): Promise<string | null> {
@@ -248,7 +250,9 @@ export class SessionService implements SessionServiceInterface {
revokedSession.apiVersion = session.apiVersion
revokedSession.userAgent = session.userAgent
return this.revokedSessionRepository.save(revokedSession)
await this.revokedSessionRepository.insert(revokedSession)
return revokedSession
}
private async createSession(dto: {

View File

@@ -42,7 +42,7 @@ export class RedisEphemeralSessionRepository implements EphemeralSessionReposito
session.accessExpiration = accessExpiration
session.refreshExpiration = refreshExpiration
await this.save(session)
await this.update(session)
}
async findAllByUserUuid(userUuid: string): Promise<Array<EphemeralSession>> {
@@ -77,7 +77,7 @@ export class RedisEphemeralSessionRepository implements EphemeralSessionReposito
return JSON.parse(stringifiedSession)
}
async save(ephemeralSession: EphemeralSession): Promise<void> {
async insert(ephemeralSession: EphemeralSession): Promise<void> {
const ttl = this.ephemeralSessionAge
const stringifiedSession = JSON.stringify(ephemeralSession)
@@ -92,4 +92,8 @@ export class RedisEphemeralSessionRepository implements EphemeralSessionReposito
await pipeline.exec()
}
async update(ephemeralSession: EphemeralSession): Promise<void> {
return this.insert(ephemeralSession)
}
}

View File

@@ -71,7 +71,11 @@ export class TypeORMEphemeralSessionRepository implements EphemeralSessionReposi
return JSON.parse(stringifiedSession.props.value)
}
async save(ephemeralSession: EphemeralSession): Promise<void> {
async update(ephemeralSession: EphemeralSession): Promise<void> {
return this.insert(ephemeralSession)
}
async insert(ephemeralSession: EphemeralSession): Promise<void> {
const ttl = this.ephemeralSessionAge
ephemeralSession.updatedAt = this.timer.getUTCDate()

View File

@@ -12,8 +12,14 @@ export class TypeORMRevokedSessionRepository implements RevokedSessionRepository
private ormRepository: Repository<RevokedSession>,
) {}
async save(revokedSession: RevokedSession): Promise<RevokedSession> {
return this.ormRepository.save(revokedSession)
async insert(revokedSession: RevokedSession): Promise<void> {
await this.ormRepository.insert(revokedSession)
}
async update(revokedSession: RevokedSession): Promise<void> {
const { uuid, ...revokedSessionProps } = revokedSession
await this.ormRepository.update({ uuid }, revokedSessionProps)
}
async remove(revokedSession: RevokedSession): Promise<RevokedSession> {

View File

@@ -17,10 +17,18 @@ export class TypeORMSessionRepository implements SessionRepositoryInterface {
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
) {}
async save(session: Session): Promise<Session> {
async insert(session: Session): Promise<void> {
session.updatedAt = this.timer.getUTCDate()
return this.ormRepository.save(session)
await this.ormRepository.insert(session)
}
async update(session: Session): Promise<void> {
session.updatedAt = this.timer.getUTCDate()
const { uuid, ...sessionProps } = session
await this.ormRepository.update({ uuid }, sessionProps)
}
async remove(session: Session): Promise<Session> {