-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #112 from shapehq/develop
Deploy to production
- Loading branch information
Showing
15 changed files
with
217 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import SessionLockingAccessTokenService from "../../src/features/auth/domain/SessionLockingAccessTokenService" | ||
|
||
test("It acquires a lock", async () => { | ||
let didAcquireLock = false | ||
const sut = new SessionLockingAccessTokenService({ | ||
async getUserId() { | ||
return "foo" | ||
} | ||
}, { | ||
makeMutex() { | ||
return { | ||
async acquire() { | ||
didAcquireLock = true | ||
}, | ||
async release() {} | ||
} | ||
} | ||
}, { | ||
async getAccessToken() { | ||
return "" | ||
} | ||
}) | ||
await sut.getAccessToken() | ||
expect(didAcquireLock).toBeTruthy() | ||
}) | ||
|
||
test("It releases the acquired lock", async () => { | ||
let didReleaseLock = false | ||
const sut = new SessionLockingAccessTokenService({ | ||
async getUserId() { | ||
return "foo" | ||
} | ||
}, { | ||
makeMutex() { | ||
return { | ||
async acquire() {}, | ||
async release() { | ||
didReleaseLock = true | ||
} | ||
} | ||
} | ||
}, { | ||
async getAccessToken() { | ||
return "" | ||
} | ||
}) | ||
await sut.getAccessToken() | ||
expect(didReleaseLock).toBeTruthy() | ||
}) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export default interface IMutex { | ||
acquire(): Promise<void> | ||
release(): Promise<void> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import IMutex from "./IMutex" | ||
|
||
export default interface IMutexFactory { | ||
makeMutex(key: string): IMutex | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import Redis from "ioredis" | ||
import IMutex from "./IMutex" | ||
import IMutexFactory from "./IMutexFactory" | ||
import { Mutex } from "redis-semaphore" | ||
|
||
class RedisMutex implements IMutex { | ||
private readonly mutex: Mutex | ||
|
||
constructor(redis: Redis, key: string) { | ||
this.mutex = new Mutex(redis, key) | ||
} | ||
|
||
async acquire(): Promise<void> { | ||
return await this.mutex.acquire() | ||
} | ||
|
||
async release(): Promise<void> { | ||
return await this.mutex.release() | ||
} | ||
} | ||
|
||
export default class RedisMutexFactory implements IMutexFactory { | ||
private readonly redis: Redis | ||
|
||
constructor(url: string) { | ||
this.redis = new Redis(url) | ||
} | ||
|
||
makeMutex(key: string): IMutex { | ||
return new RedisMutex(this.redis, key) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import IMutex from "./IMutex" | ||
|
||
export default async function withMutex<T>( | ||
mutex: IMutex, | ||
f: () => Promise<T> | ||
): Promise<T> { | ||
await mutex.acquire() | ||
try { | ||
const value = await f() | ||
await mutex.release() | ||
return value | ||
} catch(error) { | ||
await mutex.release() | ||
throw error | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { getSession } from "@auth0/nextjs-auth0" | ||
import ISession from "./ISession" | ||
import { UnauthorizedError } from "@/features/auth/domain/AuthError" | ||
|
||
export default class Auth0Session implements ISession { | ||
async getUserId(): Promise<string> { | ||
const session = await getSession() | ||
if (!session) { | ||
throw new UnauthorizedError("User ID is unavailable because the user is not authenticated.") | ||
} | ||
return session.user.sub | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export default interface ISession { | ||
getUserId(): Promise<string> | ||
} | ||
|
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import ISession from "../session/ISession" | ||
import ISessionDataRepository from "@/common/userData/ISessionDataRepository" | ||
import IUserDataRepository from "@/common/userData/IUserDataRepository" | ||
|
||
export default class SessionDataRepository<T> implements ISessionDataRepository<T> { | ||
private readonly session: ISession | ||
private readonly repository: IUserDataRepository<T> | ||
|
||
constructor(session: ISession, repository: IUserDataRepository<T>) { | ||
this.session = session | ||
this.repository = repository | ||
} | ||
|
||
async get(): Promise<T | null> { | ||
const userId = await this.session.getUserId() | ||
return await this.repository.get(userId) | ||
} | ||
|
||
async set(value: T): Promise<void> { | ||
const userId = await this.session.getUserId() | ||
return await this.repository.set(userId, value) | ||
} | ||
|
||
async delete(): Promise<void> { | ||
const userId = await this.session.getUserId() | ||
return await this.repository.delete(userId) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default interface IAccessTokenService { | ||
getAccessToken(): Promise<string> | ||
} |
28 changes: 28 additions & 0 deletions
28
src/features/auth/domain/SessionLockingAccessTokenService.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import ISession from "@/common/session/ISession" | ||
import IMutexFactory from "@/common/mutex/IMutexFactory" | ||
import withMutex from "../../../common/mutex/withMutex" | ||
import IAccessTokenService from "./IAccessTokenService" | ||
|
||
export default class SessionLockingAccessTokenService implements IAccessTokenService { | ||
private readonly session: ISession | ||
private readonly mutexFactory: IMutexFactory | ||
private readonly accessTokenService: IAccessTokenService | ||
|
||
constructor( | ||
session: ISession, | ||
mutexFactory: IMutexFactory, | ||
accessTokenService: IAccessTokenService | ||
) { | ||
this.session = session | ||
this.mutexFactory = mutexFactory | ||
this.accessTokenService = accessTokenService | ||
} | ||
|
||
async getAccessToken(): Promise<string> { | ||
const userId = await this.session.getUserId() | ||
const mutex = this.mutexFactory.makeMutex(`mutexAccessToken[${userId}]`) | ||
return await withMutex(mutex, async () => { | ||
return await this.accessTokenService.getAccessToken() | ||
}) | ||
} | ||
} |