-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a persistent authentication across tabs and windows
- Loading branch information
Maxime Naulleau
committed
Jan 24, 2025
1 parent
c770141
commit 0dfdc73
Showing
21 changed files
with
585 additions
and
65 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
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,48 @@ | ||
import { defineConfig, devices } from "@playwright/experimental-ct-react"; | ||
|
||
/** | ||
* See https://playwright.dev/docs/test-configuration. | ||
*/ | ||
export default defineConfig({ | ||
testDir: "./", | ||
// Glob patterns or regular expressions that match test files. | ||
testMatch: "*src/**/*.ct-spec.tsx", | ||
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ | ||
snapshotDir: "./__snapshots__", | ||
/* Maximum time one test can run for. */ | ||
timeout: 10 * 1000, | ||
/* Run tests in files in parallel */ | ||
fullyParallel: true, | ||
/* Fail the build on CI if you accidentally left test.only in the source code. */ | ||
forbidOnly: !!process.env.CI, | ||
/* Retry on CI only */ | ||
retries: process.env.CI ? 1 : 0, | ||
/* Opt out of parallel tests on CI. */ | ||
workers: process.env.CI ? 1 : undefined, | ||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ | ||
reporter: process.env.CI ? "html" : "list", | ||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ | ||
use: { | ||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ | ||
trace: "on-first-retry", | ||
|
||
/* Port to use for Playwright component endpoint. */ | ||
ctPort: 3100, | ||
}, | ||
|
||
/* Configure projects for major browsers */ | ||
projects: [ | ||
{ | ||
name: "chromium", | ||
use: { ...devices["Desktop Chrome"] }, | ||
}, | ||
{ | ||
name: "firefox", | ||
use: { ...devices["Desktop Firefox"] }, | ||
}, | ||
{ | ||
name: "webkit", | ||
use: { ...devices["Desktop Safari"] }, | ||
}, | ||
], | ||
}); |
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,12 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Testing Page</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="./index.tsx"></script> | ||
</body> | ||
</html> |
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,2 @@ | ||
// Import styles, initialize component theme here. | ||
// import '../src/common.css'; |
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
103 changes: 103 additions & 0 deletions
103
...rc/authentication/adapters/secondary/providers/indexedDbAuthenticationStorage.provider.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,103 @@ | ||
import { AuthenticatedUserSM } from "../../../core-logic/gateways/Authentication.gateway"; | ||
import { AuthenticationStorageProvider } from "../../../core-logic/providers/authenticationStorage.provider"; | ||
|
||
export class IndexedDbAuthenticationStorageProvider | ||
implements AuthenticationStorageProvider | ||
{ | ||
private readonly dbName = "fondation"; | ||
private readonly storeName = "authentication"; | ||
private db: IDBDatabase | null = null; | ||
|
||
constructor() { | ||
if (!IndexedDbAuthenticationStorageProvider.browserSupportsIndexedDB()) { | ||
console.warn("Your browser doesn't support IndexedDB."); | ||
return; | ||
} | ||
|
||
this.initDB(); | ||
} | ||
|
||
private async initDB() { | ||
const request = indexedDB.open(this.dbName, 1); | ||
this.db = await this.requestDbToPromise(request); | ||
} | ||
|
||
async storeAuthentication(payload: AuthenticatedUserSM): Promise<void> { | ||
if (!this.db) return; | ||
|
||
const transaction = this.db.transaction(this.storeName, "readwrite"); | ||
const store = transaction.objectStore(this.storeName); | ||
const requestAuthenticated = store.put({ | ||
key: "authenticated", | ||
value: "true", | ||
}); | ||
const requestUser = store.put({ | ||
key: "user", | ||
value: JSON.stringify(payload), | ||
}); | ||
|
||
await this.requestToPromise(requestAuthenticated); | ||
await this.requestToPromise(requestUser); | ||
} | ||
|
||
async storeDisconnection() { | ||
if (!this.db) return; | ||
|
||
const transaction = this.db.transaction(this.storeName, "readwrite"); | ||
const store = transaction.objectStore(this.storeName); | ||
store.put({ key: "authenticated", value: "false" }); | ||
const request = store.delete("user"); | ||
|
||
await this.requestToPromise(request); | ||
} | ||
|
||
async isAuthenticated() { | ||
if (!this.db) return false; | ||
|
||
const transaction = this.db.transaction(this.storeName, "readonly"); | ||
const store = transaction.objectStore(this.storeName); | ||
const request = store.get("authenticated"); | ||
const value = await this.requesToPromiseValue(request); | ||
|
||
return value === "true"; | ||
} | ||
|
||
async getUser() { | ||
if (!this.db) { | ||
return null; | ||
} | ||
|
||
const transaction = this.db.transaction(this.storeName, "readonly"); | ||
const store = transaction.objectStore(this.storeName); | ||
const request = store.get("user"); | ||
const value = await this.requesToPromiseValue(request); | ||
|
||
return value ? JSON.parse(value) : null; | ||
} | ||
|
||
private requestDbToPromise( | ||
request: IDBRequest, | ||
): Promise<IDBOpenDBRequest["result"]> { | ||
return new Promise((resolve, reject) => { | ||
request.onsuccess = (event) => | ||
resolve((event.target as IDBOpenDBRequest).result); | ||
request.onerror = () => reject(request.error); | ||
}); | ||
} | ||
|
||
private async requesToPromiseValue(request: IDBRequest) { | ||
const result = await this.requestToPromise(request); | ||
return result?.value; | ||
} | ||
|
||
private requestToPromise(request: IDBRequest): Promise<IDBRequest["result"]> { | ||
return new Promise((resolve, reject) => { | ||
request.onsuccess = () => resolve(request.result); | ||
request.onerror = () => reject(request.error); | ||
}); | ||
} | ||
|
||
static browserSupportsIndexedDB() { | ||
return !!indexedDB; | ||
} | ||
} |
33 changes: 31 additions & 2 deletions
33
apps/webapp/src/authentication/core-logic/listeners/authentication.listeners.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 |
---|---|---|
@@ -1,19 +1,48 @@ | ||
import { Listener } from "../../../store/listeners"; | ||
import { authenticationStateInitFromStore } from "../reducers/authentication.slice"; | ||
import { authenticate } from "../use-cases/authentication/authenticate"; | ||
|
||
export const storeAuthenticationOnLoginSuccess: Listener = ( | ||
startAppListening, | ||
) => | ||
startAppListening({ | ||
actionCreator: authenticate.fulfilled, | ||
effect: ( | ||
effect: async ( | ||
action, | ||
{ | ||
extra: { | ||
providers: { authenticationStorageProvider }, | ||
}, | ||
}, | ||
) => { | ||
authenticationStorageProvider?.storeAuthentication(action.payload); | ||
await authenticationStorageProvider?.storeAuthentication(action.payload); | ||
}, | ||
}); | ||
|
||
export const initializeAuthenticationState: Listener = (startAppListening) => | ||
startAppListening({ | ||
predicate: (_, state) => | ||
state.authentication.initializedFromStore === false, | ||
effect: async ( | ||
_, | ||
{ | ||
dispatch, | ||
extra: { | ||
providers: { authenticationStorageProvider }, | ||
}, | ||
}, | ||
) => { | ||
// There is no retry mechanism here, but it could be useful | ||
// because the IndexedDB instance access could take some time. | ||
const authenticated = | ||
await authenticationStorageProvider?.isAuthenticated(); | ||
const user = await authenticationStorageProvider?.getUser(); | ||
|
||
dispatch( | ||
authenticationStateInitFromStore({ | ||
authenticated: !!authenticated, | ||
user: user || null, | ||
}), | ||
); | ||
}, | ||
}); |
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
8 changes: 4 additions & 4 deletions
8
apps/webapp/src/authentication/core-logic/providers/authenticationStorage.provider.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 |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import { AuthenticatedUserSM } from "../gateways/Authentication.gateway"; | ||
|
||
export interface AuthenticationStorageProvider { | ||
storeAuthentication(payload: AuthenticatedUserSM): void; | ||
storeDisconnection(): void; | ||
isAuthenticated: () => boolean; | ||
getUser: () => AuthenticatedUserSM | null; | ||
storeAuthentication(payload: AuthenticatedUserSM): Promise<void>; | ||
storeDisconnection(): Promise<void>; | ||
isAuthenticated: () => Promise<boolean>; | ||
getUser: () => Promise<AuthenticatedUserSM | null>; | ||
} |
Oops, something went wrong.