diff --git a/__test__/auth/CachingRepositoryAccessReaderConfig.ts b/__test__/auth/CachingRepositoryAccessReaderConfig.ts index 3ee2aa6a..8833cc64 100644 --- a/__test__/auth/CachingRepositoryAccessReaderConfig.ts +++ b/__test__/auth/CachingRepositoryAccessReaderConfig.ts @@ -9,6 +9,7 @@ test("It fetches repository names for user if they are not cached", async () => return null }, async set() {}, + async setExpiring() {}, async delete() {} }, repositoryAccessReader: { @@ -32,6 +33,7 @@ test("It does not fetch repository names if they are cached", async () => { return "[\"foo\"]" }, async set() {}, + async setExpiring() {}, async delete() {} }, repositoryAccessReader: { @@ -57,6 +59,7 @@ test("It caches fetched repository names for user", async () => { cachedUserId = userId cachedRepositoryNames = value }, + async setExpiring() {}, async delete() {} }, repositoryAccessReader: { @@ -77,6 +80,7 @@ test("It decodes cached repository names", async () => { return "[\"foo\",\"bar\"]" }, async set() {}, + async setExpiring() {}, async delete() {} }, repositoryAccessReader: { diff --git a/__test__/auth/CachingUserIdentityProviderReader.test.ts b/__test__/auth/CachingUserIdentityProviderReader.test.ts index 60515704..0075f9a3 100644 --- a/__test__/auth/CachingUserIdentityProviderReader.test.ts +++ b/__test__/auth/CachingUserIdentityProviderReader.test.ts @@ -8,6 +8,7 @@ test("It fetches user identity provider if it is not cached", async () => { return null }, async set() {}, + async setExpiring() {}, async delete() {} }, { async getUserIdentityProvider() { @@ -26,6 +27,7 @@ test("It does not fetch user identity provider if it is cached", async () => { return UserIdentityProvider.GITHUB }, async set() {}, + async setExpiring() {}, async delete() {} }, { async getUserIdentityProvider() { @@ -44,7 +46,8 @@ test("It caches fetched user identity provider for user", async () => { async get() { return null }, - async set(userId, userIdentityProvider) { + async set() {}, + async setExpiring(userId, userIdentityProvider) { cachedUserId = userId cachedUserIdentityProvider = userIdentityProvider }, diff --git a/__test__/auth/GuestAccessTokenService.test.ts b/__test__/auth/GuestAccessTokenService.test.ts index 708a630d..ceeceb4a 100644 --- a/__test__/auth/GuestAccessTokenService.test.ts +++ b/__test__/auth/GuestAccessTokenService.test.ts @@ -13,7 +13,7 @@ test("It gets the access token for the user", async () => { readUserId = userId return "foo" }, - async set() {} + async setExpiring() {} }, dataSource: { async getAccessToken() { @@ -38,7 +38,7 @@ test("It refreshes access token on demand when there is no cached access token", async get() { return null }, - async set() {} + async setExpiring() {} }, dataSource: { async getAccessToken() { diff --git a/__test__/auth/OAuthTokenRepository.test.ts b/__test__/auth/OAuthTokenRepository.test.ts index b5786016..2bcac8a0 100644 --- a/__test__/auth/OAuthTokenRepository.test.ts +++ b/__test__/auth/OAuthTokenRepository.test.ts @@ -11,6 +11,7 @@ test("It reads the auth token for the specified user", async () => { }) }, async set() {}, + async setExpiring() {}, async delete() {} }) await sut.get("1234") @@ -24,7 +25,8 @@ test("It stores the auth token for the specified user", async () => { async get() { return "" }, - async set(userId, data) { + async set() {}, + async setExpiring(userId, data) { storedUserId = userId storedJSON = data }, @@ -48,6 +50,7 @@ test("It deletes the auth token for the specified user", async () => { return "" }, async set() {}, + async setExpiring() {}, async delete(userId) { deletedUserId = userId } diff --git a/__test__/common/userData/KeyValueUserDataRepository.test.ts b/__test__/common/userData/KeyValueUserDataRepository.test.ts index 869403b4..3dc7cb04 100644 --- a/__test__/common/userData/KeyValueUserDataRepository.test.ts +++ b/__test__/common/userData/KeyValueUserDataRepository.test.ts @@ -8,6 +8,7 @@ test("It reads the expected key", async () => { return "" }, async set() {}, + async setExpiring() {}, async delete() {} }, "foo") await sut.get("123") @@ -23,12 +24,31 @@ test("It stores values under the expected key", async () => { async set(key) { storedKey = key }, + async setExpiring() {}, async delete() {} }, "foo") await sut.set("123", "bar") expect(storedKey).toBe("foo[123]") }) +test("It stores values under the expected key with expected time to live", async () => { + let storedKey: string | undefined + let storedTimeToLive: number | undefined + const sut = new KeyValueUserDataRepository({ + async get() { + return "" + }, + async set() {}, + async setExpiring(key, _value, timeToLive) { + storedKey = key + storedTimeToLive = timeToLive + }, + async delete() {} + }, "foo") + await sut.setExpiring("123", "bar", 24 * 3600) + expect(storedKey).toBe("foo[123]") + expect(storedTimeToLive).toBe(24 * 3600) +}) test("It deletes the expected key", async () => { let deletedKey: string | undefined @@ -37,6 +57,7 @@ test("It deletes the expected key", async () => { return "" }, async set() {}, + async setExpiring() {}, async delete(key) { deletedKey = key } diff --git a/src/common/keyValueStore/IKeyValueStore.ts b/src/common/keyValueStore/IKeyValueStore.ts index 8b604f56..470b8558 100644 --- a/src/common/keyValueStore/IKeyValueStore.ts +++ b/src/common/keyValueStore/IKeyValueStore.ts @@ -1,5 +1,10 @@ export default interface IKeyValueStore { get(key: string): Promise set(key: string, data: string | number | Buffer): Promise + setExpiring( + key: string, + data: string | number | Buffer, + timeToLive: number + ): Promise delete(key: string): Promise } diff --git a/src/common/keyValueStore/RedisKeyValueStore.ts b/src/common/keyValueStore/RedisKeyValueStore.ts index 4a552154..8c4c5401 100644 --- a/src/common/keyValueStore/RedisKeyValueStore.ts +++ b/src/common/keyValueStore/RedisKeyValueStore.ts @@ -16,6 +16,14 @@ export default class RedisKeyValueStore implements IKeyValueStore { await this.redis.set(key, data) } + async setExpiring( + key: string, + data: string | number | Buffer, + timeToLive: number + ): Promise { + await this.redis.setex(key, timeToLive, data) + } + async delete(key: string): Promise { await this.redis.del(key) } diff --git a/src/common/userData/IUserDataRepository.ts b/src/common/userData/IUserDataRepository.ts index cefadae4..8fca5d8e 100644 --- a/src/common/userData/IUserDataRepository.ts +++ b/src/common/userData/IUserDataRepository.ts @@ -1,5 +1,6 @@ export default interface IUserDataRepository { get(userId: string): Promise set(userId: string, value: T): Promise + setExpiring(userId: string, value: T, timeToLive: number): Promise delete(userId: string): Promise } diff --git a/src/common/userData/KeyValueUserDataRepository.ts b/src/common/userData/KeyValueUserDataRepository.ts index 58bd4fa6..b6318099 100644 --- a/src/common/userData/KeyValueUserDataRepository.ts +++ b/src/common/userData/KeyValueUserDataRepository.ts @@ -18,6 +18,10 @@ export default class KeyValueUserDataRepository implements IUserDataRepository { + await this.store.setExpiring(this.getKey(userId), value, timeToLive) + } + async delete(userId: string): Promise { await this.store.delete(this.getKey(userId)) } diff --git a/src/features/auth/domain/accessToken/GuestAccessTokenService.ts b/src/features/auth/domain/accessToken/GuestAccessTokenService.ts index a30c26f3..d2e1a059 100644 --- a/src/features/auth/domain/accessToken/GuestAccessTokenService.ts +++ b/src/features/auth/domain/accessToken/GuestAccessTokenService.ts @@ -6,7 +6,7 @@ export interface IUserIDReader { export interface Repository { get(userId: string): Promise - set(userId: string, token: string): Promise + setExpiring(userId: string, token: string, timeToLive: number): Promise } export interface DataSource { @@ -47,7 +47,7 @@ export default class GuestAccessTokenService implements IAccessTokenService { private async getNewAccessToken(): Promise { const userId = await this.userIdReader.getUserId() const newAccessToken = await this.dataSource.getAccessToken(userId) - await this.repository.set(userId, newAccessToken) + await this.repository.setExpiring(userId, newAccessToken, 7 * 24 * 3600) return newAccessToken } } \ No newline at end of file diff --git a/src/features/auth/domain/oAuthToken/OAuthTokenRepository.ts b/src/features/auth/domain/oAuthToken/OAuthTokenRepository.ts index 2a11b700..678f83bd 100644 --- a/src/features/auth/domain/oAuthToken/OAuthTokenRepository.ts +++ b/src/features/auth/domain/oAuthToken/OAuthTokenRepository.ts @@ -19,7 +19,7 @@ export default class OAuthTokenRepository implements IOAuthTokenRepository { async set(userId: string, token: OAuthToken): Promise { const string = ZodJSONCoder.encode(OAuthTokenSchema, token) - await this.repository.set(userId, string) + await this.repository.setExpiring(userId, string, 6 * 30 * 24 * 3600) } async delete(userId: string): Promise { diff --git a/src/features/auth/domain/repositoryAccess/CachingRepositoryAccessReaderConfig.ts b/src/features/auth/domain/repositoryAccess/CachingRepositoryAccessReaderConfig.ts index 15e4a717..02405b43 100644 --- a/src/features/auth/domain/repositoryAccess/CachingRepositoryAccessReaderConfig.ts +++ b/src/features/auth/domain/repositoryAccess/CachingRepositoryAccessReaderConfig.ts @@ -48,7 +48,7 @@ export default class CachingRepositoryAccessReader { const repositoryNames = await this.repositoryAccessReader.getRepositoryNames(userId) try { const str = ZodJSONCoder.encode(RepositoryNamesContainerSchema, repositoryNames) - await this.repository.set(userId, str) + await this.repository.setExpiring(userId, str, 7 * 24 * 3600) } catch (error: unknown) { console.error(error) } diff --git a/src/features/auth/domain/userIdentityProvider/CachingUserIdentityProviderReader.ts b/src/features/auth/domain/userIdentityProvider/CachingUserIdentityProviderReader.ts index 9d1fff8c..86456036 100644 --- a/src/features/auth/domain/userIdentityProvider/CachingUserIdentityProviderReader.ts +++ b/src/features/auth/domain/userIdentityProvider/CachingUserIdentityProviderReader.ts @@ -19,7 +19,7 @@ export default class CachingUserIdentityProviderReader implements IUserIdentityP return cachedValue as UserIdentityProvider } else { const userIdentity = await this.reader.getUserIdentityProvider(userId) - await this.repository.set(userId, userIdentity.toString()) + await this.repository.setExpiring(userId, userIdentity.toString(), 7 * 24 * 3600) return userIdentity } } diff --git a/src/features/projects/domain/ProjectRepository.ts b/src/features/projects/domain/ProjectRepository.ts index 45a494d2..c55e9341 100644 --- a/src/features/projects/domain/ProjectRepository.ts +++ b/src/features/projects/domain/ProjectRepository.ts @@ -29,7 +29,7 @@ export default class ProjectRepository implements IProjectRepository { async set(projects: Project[]): Promise { const userId = await this.userIDReader.getUserId() const string = ZodJSONCoder.encode(ProjectSchema.array(), projects) - await this.repository.set(userId, string) + await this.repository.setExpiring(userId, string, 30 * 24 * 3600) } async delete(): Promise {