-
Notifications
You must be signed in to change notification settings - Fork 253
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(clerk-js): Reorganize cookies code and fix TokenUpdate event (#…
…3362) * fix(shared): Change createCookieHandler#.set to return void * chore(clerk-js): Centralize cookie code into single module * chore(clerk-js): Refactor SessionCookieService and auth/cookies handlers for consistency There is also a fix for duplicate TokenUpdate event and for cleaning the token in sign-out flow. * chore(clerk-js): Refactor Clerk.#environment modifier to protected to use type assert * chore(repo): Add changeset * chore(clerk-js): Rename SessionCookieService to AuthCookieService for clarity * chore(clerk-js): Add JSDoc for auth services and cookies * chore(clerk-js): Rename urlWithAuth to decorateUrlWithDevBrowserToken method of auth service * fix(clerk-js): Fix isSignedOut() to use both clientUat and clerk.user This change was applied to keep the existing behaviour: - clerk.user is used when clerk is loaded - clientUat cookie is used when clerk is NOT loaded
- Loading branch information
Showing
21 changed files
with
396 additions
and
309 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,6 @@ | ||
--- | ||
'@clerk/clerk-js': patch | ||
'@clerk/shared': patch | ||
--- | ||
|
||
Re-organize cookie codebase into a central place, fix TokenUpdate event to be triggered on sign-out and drop duplicate event on refreshing token. |
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,155 @@ | ||
import { setDevBrowserJWTInURL } from '@clerk/shared/devBrowser'; | ||
import { is4xxError, isClerkAPIResponseError, isNetworkError } from '@clerk/shared/error'; | ||
import type { Clerk, EnvironmentResource } from '@clerk/types'; | ||
|
||
import { clerkCoreErrorTokenRefreshFailed, clerkMissingDevBrowserJwt } from '../errors'; | ||
import { eventBus, events } from '../events'; | ||
import type { FapiClient } from '../fapiClient'; | ||
import type { ClientUatCookieHandler } from './cookies/clientUat'; | ||
import { createClientUatCookie } from './cookies/clientUat'; | ||
import type { SessionCookieHandler } from './cookies/session'; | ||
import { createSessionCookie } from './cookies/session'; | ||
import type { DevBrowser } from './devBrowser'; | ||
import { createDevBrowser } from './devBrowser'; | ||
import { SessionCookiePoller } from './SessionCookiePoller'; | ||
|
||
// TODO(@dimkl): make AuthCookieService singleton since it handles updating cookies using a poller | ||
// and we need to avoid updating them concurrently. | ||
/** | ||
* The AuthCookieService class is a service responsible to handle | ||
* all operations and helpers required in a standard browser context | ||
* based on the cookies to remove the dependency between cookies | ||
* and auth from the Clerk instance. | ||
* This service is responsible to: | ||
* - refresh the session cookie using a poller | ||
* - refresh the session cookie on tab visibility change | ||
* - update the related cookies listening to the `token:update` event | ||
* - initialize auth related cookies for development instances (eg __client_uat, __clerk_db_jwt) | ||
* - cookie setup for production / development instances | ||
* It also provides the following helpers: | ||
* - isSignedOut(): check if the current user is signed-out using cookies | ||
* - decorateUrlWithDevBrowserToken(): decorates url with auth related info (eg dev browser jwt) | ||
* - handleUnauthenticatedDevBrowser(): resets dev browser in case of invalid dev browser | ||
* - setEnvironment(): update cookies (eg client_uat) related to environment | ||
*/ | ||
export class AuthCookieService { | ||
private environment: EnvironmentResource | undefined; | ||
private poller: SessionCookiePoller | null = null; | ||
private clientUat: ClientUatCookieHandler; | ||
private sessionCookie: SessionCookieHandler; | ||
private devBrowser: DevBrowser; | ||
|
||
constructor(private clerk: Clerk, fapiClient: FapiClient) { | ||
// set cookie on token update | ||
eventBus.on(events.TokenUpdate, ({ token }) => { | ||
this.updateSessionCookie(token && token.getRawString()); | ||
this.setClientUatCookieForDevelopmentInstances(); | ||
}); | ||
|
||
this.refreshTokenOnVisibilityChange(); | ||
this.startPollingForToken(); | ||
|
||
this.clientUat = createClientUatCookie(); | ||
this.sessionCookie = createSessionCookie(); | ||
this.devBrowser = createDevBrowser({ | ||
frontendApi: clerk.frontendApi, | ||
fapiClient, | ||
}); | ||
} | ||
|
||
// TODO(@dimkl): Replace this method call with an event listener to decouple Clerk with setEnvironment | ||
public setEnvironment(environment: EnvironmentResource) { | ||
this.environment = environment; | ||
this.setClientUatCookieForDevelopmentInstances(); | ||
} | ||
|
||
public isSignedOut() { | ||
if (!this.clerk.loaded) { | ||
return this.clientUat.get() <= 0; | ||
} | ||
return !!this.clerk.user; | ||
} | ||
|
||
public async setupDevelopment() { | ||
await this.devBrowser.setup(); | ||
} | ||
|
||
public setupProduction() { | ||
this.devBrowser.clear(); | ||
} | ||
|
||
public async handleUnauthenticatedDevBrowser() { | ||
this.devBrowser.clear(); | ||
await this.devBrowser.setup(); | ||
} | ||
|
||
public decorateUrlWithDevBrowserToken(url: URL): URL { | ||
const devBrowserJwt = this.devBrowser.getDevBrowserJWT(); | ||
if (!devBrowserJwt) { | ||
return clerkMissingDevBrowserJwt(); | ||
} | ||
|
||
return setDevBrowserJWTInURL(url, devBrowserJwt); | ||
} | ||
|
||
private startPollingForToken() { | ||
if (!this.poller) { | ||
this.poller = new SessionCookiePoller(); | ||
} | ||
this.poller.startPollingForSessionToken(() => this.refreshSessionToken()); | ||
} | ||
|
||
private refreshTokenOnVisibilityChange() { | ||
document.addEventListener('visibilitychange', () => { | ||
if (document.visibilityState === 'visible') { | ||
void this.refreshSessionToken(); | ||
} | ||
}); | ||
} | ||
|
||
private async refreshSessionToken(): Promise<void> { | ||
if (!this.clerk.session) { | ||
return; | ||
} | ||
|
||
try { | ||
await this.clerk.session.getToken(); | ||
} catch (e) { | ||
return this.handleGetTokenError(e); | ||
} | ||
} | ||
|
||
private updateSessionCookie(token: string | null) { | ||
return token ? this.sessionCookie.set(token) : this.sessionCookie.remove(); | ||
} | ||
|
||
private setClientUatCookieForDevelopmentInstances() { | ||
if (this.environment?.isDevelopmentOrStaging() && this.inCustomDevelopmentDomain()) { | ||
this.clientUat.set(this.clerk.client); | ||
} | ||
} | ||
|
||
private inCustomDevelopmentDomain() { | ||
const domain = this.clerk.frontendApi.replace('clerk.', ''); | ||
return !window.location.host.endsWith(domain); | ||
} | ||
|
||
private handleGetTokenError(e: any) { | ||
//throw if not a clerk error | ||
if (!isClerkAPIResponseError(e)) { | ||
clerkCoreErrorTokenRefreshFailed(e.message || e); | ||
} | ||
|
||
//sign user out if a 4XX error | ||
if (is4xxError(e)) { | ||
void this.clerk.handleUnauthenticated(); | ||
return; | ||
} | ||
|
||
if (isNetworkError(e)) { | ||
return; | ||
} | ||
|
||
clerkCoreErrorTokenRefreshFailed(e.toString()); | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
...ces/authentication/SessionCookiePoller.ts → ...k-js/src/core/auth/SessionCookiePoller.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
2 changes: 1 addition & 1 deletion
2
...-js/src/core/__tests__/devBrowser.test.ts → ...rc/core/auth/__tests__/devBrowser.test.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
File renamed without changes.
Oops, something went wrong.