From 86434532f5591008414bda0a39d5506f3d521629 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:29:52 +0100 Subject: [PATCH 01/62] First round of adaption --- .../web-core/src/services/SessionService.ts | 68 ++++--------------- packages/web-core/src/services/index.ts | 2 - 2 files changed, 15 insertions(+), 55 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index fc94ad8d5..22e7244b2 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -28,14 +28,13 @@ import { } from '../utils'; import { WebAuthnService } from './WebAuthnService'; -const shortSessionKey = 'cbo_short_session'; const sessionTokenKey = 'cbo_session_token'; -const longSessionKey = 'cbo_long_session'; +const refreshTokenKey = 'cbo_refresh_token'; -// controls how long before the shortSession expires we should refresh it -const shortSessionRefreshBeforeExpirationSeconds = 60; -// controls how often we check if we need to refresh the session -const shortSessionRefreshIntervalMs = 10_000; +// controls how long before the session-token expires we should refresh it +const sessionTokenRefreshBeforeExpirationSeconds = 60; +// controls how often we check if we need to refresh the session-token +const sessionTokenRefreshIntervalMs = 10_000; const packageVersion = '0.0.0'; @@ -50,7 +49,6 @@ export class SessionService { #usersApi: UsersApi = new UsersApi(); #webAuthnService: WebAuthnService; - readonly #setShortSessionCookie: boolean; readonly #isPreviewMode: boolean; readonly #projectId: string; readonly #frontendApiUrlSuffix: string; @@ -64,12 +62,11 @@ export class SessionService { #shortSessionChanges: BehaviorSubject = new BehaviorSubject(undefined); #authStateChanges: BehaviorSubject = new BehaviorSubject(AuthState.LoggedOut); - constructor(projectId: string, setShortSessionCookie: boolean, isPreviewMode: boolean, frontendApiUrlSuffix: string) { + constructor(projectId: string, isPreviewMode: boolean, frontendApiUrlSuffix: string) { this.#projectId = projectId; this.#frontendApiUrlSuffix = frontendApiUrlSuffix; this.#webAuthnService = new WebAuthnService(); this.#longSession = undefined; - this.#setShortSessionCookie = setShortSessionCookie; this.#isPreviewMode = isPreviewMode; } @@ -103,7 +100,7 @@ export class SessionService { // init scheduled session refresh this.#refreshIntervalId = setInterval(() => { void this.#handleRefreshRequest(); - }, shortSessionRefreshIntervalMs); + }, sessionTokenRefreshIntervalMs); document.addEventListener('visibilitychange', () => { this.#handleVisibilityChange(); @@ -324,23 +321,11 @@ export class SessionService { * Gets the short term session token. */ static #getShortTermSessionToken(): ShortSession | undefined { - const localStorageValue = localStorage.getItem(shortSessionKey); - if (localStorageValue) { - return new ShortSession(localStorageValue); - } - - // Get new session-token const sessionToken = this.#getCookieValue(sessionTokenKey); if (sessionToken) { return new ShortSession(sessionToken); } - // Fallback for deprecated short-term session - const shortSession = this.#getCookieValue(shortSessionKey); - if (shortSession) { - return new ShortSession(shortSession); - } - return undefined; } @@ -358,7 +343,7 @@ export class SessionService { * Deletes the long term session token cookie for dev environment in localStorage. */ #deleteLongSessionToken(): void { - localStorage.removeItem(longSessionKey); + localStorage.removeItem(refreshTokenKey); this.#longSession = ''; } @@ -366,7 +351,7 @@ export class SessionService { * Gets the long term session token. */ static #getLongSessionToken() { - return (localStorage.getItem(longSessionKey) as string) ?? ''; + return (localStorage.getItem(refreshTokenKey) as string) ?? ''; } /** @@ -374,37 +359,20 @@ export class SessionService { * @param value */ #setShortTermSessionToken(value: ShortSession): void { - localStorage.setItem(shortSessionKey, value.toString()); this.#shortSession = value; - if (this.#setShortSessionCookie) { - const cookieConfig = this.#getShortSessionCookieConfig(); - - document.cookie = this.#getShortSessionCookieString(cookieConfig, value); - document.cookie = this.#getSessionTokenCookieString(cookieConfig, value); - } + const cookieConfig = this.#getShortSessionCookieConfig(); + document.cookie = this.#getSessionTokenCookieString(cookieConfig, value); } /** * Deletes the short term session token. */ #deleteShortTermSessionToken(): void { - localStorage.removeItem(shortSessionKey); this.#shortSession = undefined; - if (this.#setShortSessionCookie) { - const cookieConfig = this.#getShortSessionCookieConfig(); - - document.cookie = this.#getDeleteShortSessionCookieString(cookieConfig); - document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); - } - } - - #getShortSessionCookieString(config: ShortSessionCookieConfig, value: ShortSession): string { - const expires = new Date(Date.now() + config.lifetimeSeconds * 1000).toUTCString(); - return `${shortSessionKey}=${value}; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ - config.sameSite - }; path=${config.path}; expires=${expires}`; + const cookieConfig = this.#getShortSessionCookieConfig(); + document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); } #getSessionTokenCookieString(config: ShortSessionCookieConfig, value: ShortSession): string { @@ -414,12 +382,6 @@ export class SessionService { }; path=${config.path}; expires=${expires}`; } - #getDeleteShortSessionCookieString(config: ShortSessionCookieConfig) { - return `${shortSessionKey}=; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ - config.sameSite - }; path=${config.path}; expires=${new Date().toUTCString()}`; - } - #getDeleteSessionTokenCookieString(config: ShortSessionCookieConfig) { return `${sessionTokenKey}=; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ config.sameSite @@ -435,7 +397,7 @@ export class SessionService { return; } - localStorage.setItem(longSessionKey, longSessionToken); + localStorage.setItem(refreshTokenKey, longSessionToken); this.#longSession = longSessionToken; } @@ -448,7 +410,7 @@ export class SessionService { } // refresh, token too old - if (!this.#shortSession.isValidForXMoreSeconds(shortSessionRefreshBeforeExpirationSeconds)) { + if (!this.#shortSession.isValidForXMoreSeconds(sessionTokenRefreshBeforeExpirationSeconds)) { await this.#refresh(); } diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index f8eed6fcb..2cb52c102 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -28,14 +28,12 @@ export class CorbadoApp { projectId, apiTimeout = defaultTimeout, frontendApiUrlSuffix = 'frontendapi.corbado.io', - setShortSessionCookie = true, isPreviewMode = false, } = corbadoParams; this.#projectId = projectId; this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApiUrlSuffix); this.#sessionService = new SessionService( this.#projectId, - setShortSessionCookie, isPreviewMode, frontendApiUrlSuffix, ); From ddca8158a55875411ada83e1835f03e9316b6e9f Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:40:40 +0100 Subject: [PATCH 02/62] First round of adaption --- .../web-core/src/services/SessionService.ts | 108 +++++++++--------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 22e7244b2..0e42d3a44 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -54,8 +54,8 @@ export class SessionService { readonly #frontendApiUrlSuffix: string; #sessionConfig: SessionConfigRsp | undefined; - #shortSession: ShortSession | undefined; - #longSession: string | undefined; + #sessionToken: ShortSession | undefined; + #refreshToken: string | undefined; #refreshIntervalId: NodeJS.Timeout | undefined; #userChanges: BehaviorSubject = new BehaviorSubject(undefined); @@ -66,7 +66,7 @@ export class SessionService { this.#projectId = projectId; this.#frontendApiUrlSuffix = frontendApiUrlSuffix; this.#webAuthnService = new WebAuthnService(); - this.#longSession = undefined; + this.#refreshToken = undefined; this.#isPreviewMode = isPreviewMode; } @@ -84,18 +84,18 @@ export class SessionService { } this.#sessionConfig = sessionConfig.val; - this.#longSession = SessionService.#getLongSessionToken(); - this.#shortSession = SessionService.#getShortTermSessionToken(); + this.#refreshToken = SessionService.#getRefreshToken(); + this.#sessionToken = SessionService.#getSessionToken(); // if the session is valid, we emit it - if (this.#shortSession && this.#shortSession.isValidForXMoreSeconds(0)) { - log.debug('emit shortsession', this.#shortSession); - this.#onShortSessionChange(this.#shortSession); + if (this.#sessionToken && this.#sessionToken.isValidForXMoreSeconds(0)) { + log.debug('emit shortsession', this.#sessionToken); + this.#onSessionTokenChange(this.#sessionToken); } else { await this.#handleRefreshRequest(); } - this.#setApisV2(this.#longSession); + this.#setApisV2(this.#refreshToken); // init scheduled session refresh this.#refreshIntervalId = setInterval(() => { @@ -114,7 +114,7 @@ export class SessionService { * @returns The short term session token or null if it's not set. */ public get shortSession() { - return this.#shortSession; + return this.#sessionToken; } /** @@ -122,11 +122,11 @@ export class SessionService { * @returns The username or null if it's not set. */ public getUser(): SessionUser | undefined { - if (!this.#shortSession) { + if (!this.#sessionToken) { return; } - const sessionParts = this.#shortSession.value.split('.'); + const sessionParts = this.#sessionToken.value.split('.'); return JSON.parse(base64decode(sessionParts[1])); } @@ -212,18 +212,19 @@ export class SessionService { }); this.clear(); - this.#onShortSessionChange(undefined); + this.#onSessionTokenChange(undefined); } - #onShortSessionChange(shortSession: ShortSession | undefined) { + #onSessionTokenChange(sessionToken: ShortSession | undefined) { const user = this.getUser(); - if (user && shortSession) { - this.#shortSessionChanges.next(shortSession.value); + if (user && sessionToken) { + this.#shortSessionChanges.next(sessionToken.value); this.#updateAuthState(AuthState.LoggedIn); this.#updateUser(user); } else { - console.log('user is logged out', user, shortSession); + log.debug('user is logged out', user, sessionToken); + this.#shortSessionChanges.next(undefined); this.#updateAuthState(AuthState.LoggedOut); this.#updateUser(undefined); @@ -232,17 +233,17 @@ export class SessionService { /** Method to set Session * It sets the short term session token, long term session token, and username for the Corbado Application. - * @param shortSessionValue The short term session token to be set. - * @param longSession The long term session token to be set. + * @param sessionToken The short term session token to be set. + * @param refreshToken The long term session token to be set. */ - setSession(shortSessionValue: string, longSession: string | undefined) { - const shortSession = new ShortSession(shortSessionValue); + setSession(sessionToken: string, refreshToken: string | undefined) { + const sessionTokenModel = new ShortSession(sessionToken); - this.#setShortTermSessionToken(shortSession); - this.#setApisV2(longSession ?? ''); + this.#setSessionToken(sessionTokenModel); + this.#setApisV2(refreshToken ?? ''); - this.#onShortSessionChange(shortSession); - this.#setLongSessionToken(longSession); + this.#onSessionTokenChange(sessionTokenModel); + this.#setRefreshToken(refreshToken); } #setApisV2(longSession: string): void { @@ -309,8 +310,8 @@ export class SessionService { * It deletes the short term session token, long term session token, and username for the Corbado Application. */ clear() { - this.#deleteShortTermSessionToken(); - this.#deleteLongSessionToken(); + this.#deleteSessionToken(); + this.#deleteRefreshToken(); if (this.#refreshIntervalId) { clearInterval(this.#refreshIntervalId); @@ -318,9 +319,9 @@ export class SessionService { } /** - * Gets the short term session token. + * Gets the session-token. */ - static #getShortTermSessionToken(): ShortSession | undefined { + static #getSessionToken(): ShortSession | undefined { const sessionToken = this.#getCookieValue(sessionTokenKey); if (sessionToken) { return new ShortSession(sessionToken); @@ -340,38 +341,40 @@ export class SessionService { } /** - * Deletes the long term session token cookie for dev environment in localStorage. + * Deletes the refresh-token. + * @todo Explain above (at property?) why this is needed. (Safari, Third-party cookies etc.) */ - #deleteLongSessionToken(): void { + #deleteRefreshToken(): void { localStorage.removeItem(refreshTokenKey); - this.#longSession = ''; + this.#refreshToken = ''; } /** - * Gets the long term session token. + * Gets the refresh-token. + * @todo Explain above (at property?) why this is needed. (Safari, Third-party cookies etc.) */ - static #getLongSessionToken() { + static #getRefreshToken() { return (localStorage.getItem(refreshTokenKey) as string) ?? ''; } /** - * Sets a short term session token. + * Sets the session-token. * @param value */ - #setShortTermSessionToken(value: ShortSession): void { - this.#shortSession = value; + #setSessionToken(value: ShortSession): void { + this.#sessionToken = value; - const cookieConfig = this.#getShortSessionCookieConfig(); + const cookieConfig = this.#getSessionTokenCookieConfig(); document.cookie = this.#getSessionTokenCookieString(cookieConfig, value); } /** - * Deletes the short term session token. + * Deletes the session-token. */ - #deleteShortTermSessionToken(): void { - this.#shortSession = undefined; + #deleteSessionToken(): void { + this.#sessionToken = undefined; - const cookieConfig = this.#getShortSessionCookieConfig(); + const cookieConfig = this.#getSessionTokenCookieConfig(); document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); } @@ -389,28 +392,27 @@ export class SessionService { } /** - * Sets a long term session token for dev environment in localStorage. - * For production, it sets a cookie. + * Sets the refresh-token. */ - #setLongSessionToken(longSessionToken: string | undefined): void { - if (!longSessionToken) { + #setRefreshToken(refreshToken: string | undefined): void { + if (!refreshToken) { return; } - localStorage.setItem(refreshTokenKey, longSessionToken); - this.#longSession = longSessionToken; + localStorage.setItem(refreshTokenKey, refreshToken); + this.#refreshToken = refreshToken; } async #handleRefreshRequest() { - // no shortSession => user is not logged in => nothing to refresh - if (!this.#shortSession) { + // no session-token => user is not logged in => nothing to refresh + if (!this.#sessionToken) { log.debug('session refresh: no refresh, user not logged in'); return; } // refresh, token too old - if (!this.#shortSession.isValidForXMoreSeconds(sessionTokenRefreshBeforeExpirationSeconds)) { + if (!this.#sessionToken.isValidForXMoreSeconds(sessionTokenRefreshBeforeExpirationSeconds)) { await this.#refresh(); } @@ -425,7 +427,7 @@ export class SessionService { try { const options: AxiosRequestConfig = { headers: { - Authorization: `Bearer ${this.#longSession}`, + Authorization: `Bearer ${this.#refreshToken}`, }, }; const response = await this.#usersApi.currentUserSessionRefresh(options); @@ -497,7 +499,7 @@ export class SessionService { return this.#sessionConfig; }; - #getShortSessionCookieConfig = (): ShortSessionCookieConfig => { + #getSessionTokenCookieConfig = (): ShortSessionCookieConfig => { const cfg = this.#getSessionConfig().shortSessionCookieConfig; if (!cfg) { throw CorbadoError.invalidConfig(); From 35554079ca9dbb6bf294eee586b18a7949402115 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:48:05 +0100 Subject: [PATCH 03/62] Third round of adaption --- .../models/{session.ts => sessionToken.ts} | 2 +- .../web-core/src/services/SessionService.ts | 44 +++++++++---------- packages/web-js/src/models/CorbadoAppState.ts | 4 +- 3 files changed, 25 insertions(+), 25 deletions(-) rename packages/web-core/src/models/{session.ts => sessionToken.ts} (96%) diff --git a/packages/web-core/src/models/session.ts b/packages/web-core/src/models/sessionToken.ts similarity index 96% rename from packages/web-core/src/models/session.ts rename to packages/web-core/src/models/sessionToken.ts index ccb52c92a..de706e0e8 100644 --- a/packages/web-core/src/models/session.ts +++ b/packages/web-core/src/models/sessionToken.ts @@ -2,7 +2,7 @@ import type { SessionUser } from '@corbado/types'; import { base64decode } from '../utils'; -export class ShortSession { +export class SessionToken { readonly #value: string; readonly #user: SessionUser; diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 0e42d3a44..9da648e74 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -16,7 +16,7 @@ import { Err, Ok, Result } from 'ts-results'; import { Configuration } from '../api/v1'; import type { SessionConfigRsp, ShortSessionCookieConfig } from '../api/v2'; import { ConfigsApi, UsersApi } from '../api/v2'; -import { ShortSession } from '../models/session'; +import { SessionToken } from '../models/sessionToken'; import { AuthState, base64decode, @@ -39,11 +39,11 @@ const sessionTokenRefreshIntervalMs = 10_000; const packageVersion = '0.0.0'; /** - * The SessionService manages user sessions for the Corbado Application, handling shortSession and longSession. + * The SessionService manages user sessions for the Corbado Application, handling session-token and refresh-token. * It offers methods to set, delete, and retrieve these tokens and the username, * as well as a method to fetch the full user object from the Corbado API. * - * The longSession should not be exposed from this service as it is only used for session refresh. + * The refresh-token should not be exposed from this service as it is only used for session refresh. */ export class SessionService { #usersApi: UsersApi = new UsersApi(); @@ -54,12 +54,12 @@ export class SessionService { readonly #frontendApiUrlSuffix: string; #sessionConfig: SessionConfigRsp | undefined; - #sessionToken: ShortSession | undefined; + #sessionToken: SessionToken | undefined; #refreshToken: string | undefined; #refreshIntervalId: NodeJS.Timeout | undefined; #userChanges: BehaviorSubject = new BehaviorSubject(undefined); - #shortSessionChanges: BehaviorSubject = new BehaviorSubject(undefined); + #sessionTokenChanges: BehaviorSubject = new BehaviorSubject(undefined); #authStateChanges: BehaviorSubject = new BehaviorSubject(AuthState.LoggedOut); constructor(projectId: string, isPreviewMode: boolean, frontendApiUrlSuffix: string) { @@ -71,7 +71,7 @@ export class SessionService { } /** - * Initializes the SessionService by registering a callback that is called when the shortSession changes. + * Initializes the SessionService by registering a callback that is called when the session-token changes. */ async init(): Promise { const sessionConfig = await this.#loadSessionConfig(); @@ -89,7 +89,7 @@ export class SessionService { // if the session is valid, we emit it if (this.#sessionToken && this.#sessionToken.isValidForXMoreSeconds(0)) { - log.debug('emit shortsession', this.#sessionToken); + log.debug('emit session-token', this.#sessionToken); this.#onSessionTokenChange(this.#sessionToken); } else { await this.#handleRefreshRequest(); @@ -110,10 +110,10 @@ export class SessionService { } /** - * Getter method for retrieving the short term session token. - * @returns The short term session token or null if it's not set. + * Getter method for retrieving the session-token. + * @returns The session-token or null if it's not set. */ - public get shortSession() { + public get sessionToken() { return this.#sessionToken; } @@ -139,10 +139,10 @@ export class SessionService { } /** - * Exposes changes to the shortSession + * Exposes changes to the session-token. */ - get shortSessionChanges(): BehaviorSubject { - return this.#shortSessionChanges; + get sessionTokenChanges(): BehaviorSubject { + return this.#sessionTokenChanges; } /** @@ -215,17 +215,17 @@ export class SessionService { this.#onSessionTokenChange(undefined); } - #onSessionTokenChange(sessionToken: ShortSession | undefined) { + #onSessionTokenChange(sessionToken: SessionToken | undefined) { const user = this.getUser(); if (user && sessionToken) { - this.#shortSessionChanges.next(sessionToken.value); + this.#sessionTokenChanges.next(sessionToken.value); this.#updateAuthState(AuthState.LoggedIn); this.#updateUser(user); } else { log.debug('user is logged out', user, sessionToken); - this.#shortSessionChanges.next(undefined); + this.#sessionTokenChanges.next(undefined); this.#updateAuthState(AuthState.LoggedOut); this.#updateUser(undefined); } @@ -237,7 +237,7 @@ export class SessionService { * @param refreshToken The long term session token to be set. */ setSession(sessionToken: string, refreshToken: string | undefined) { - const sessionTokenModel = new ShortSession(sessionToken); + const sessionTokenModel = new SessionToken(sessionToken); this.#setSessionToken(sessionTokenModel); this.#setApisV2(refreshToken ?? ''); @@ -321,10 +321,10 @@ export class SessionService { /** * Gets the session-token. */ - static #getSessionToken(): ShortSession | undefined { + static #getSessionToken(): SessionToken | undefined { const sessionToken = this.#getCookieValue(sessionTokenKey); if (sessionToken) { - return new ShortSession(sessionToken); + return new SessionToken(sessionToken); } return undefined; @@ -361,7 +361,7 @@ export class SessionService { * Sets the session-token. * @param value */ - #setSessionToken(value: ShortSession): void { + #setSessionToken(value: SessionToken): void { this.#sessionToken = value; const cookieConfig = this.#getSessionTokenCookieConfig(); @@ -378,7 +378,7 @@ export class SessionService { document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); } - #getSessionTokenCookieString(config: ShortSessionCookieConfig, value: ShortSession): string { + #getSessionTokenCookieString(config: ShortSessionCookieConfig, value: SessionToken): string { const expires = new Date(Date.now() + config.lifetimeSeconds * 1000).toUTCString(); return `${sessionTokenKey}=${value}; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ config.sameSite @@ -437,7 +437,7 @@ export class SessionService { } if (!response.data.shortSession) { - log.warn('refresh error, missing short session'); + log.warn('refresh error, missing session-token'); return; } diff --git a/packages/web-js/src/models/CorbadoAppState.ts b/packages/web-js/src/models/CorbadoAppState.ts index 1f05d5528..2822bc80c 100644 --- a/packages/web-js/src/models/CorbadoAppState.ts +++ b/packages/web-js/src/models/CorbadoAppState.ts @@ -15,7 +15,7 @@ export class CorbadoAppState { constructor(corbadoAppProps: CorbadoConfig) { const corbadoApp = new CorbadoApp(corbadoAppProps); - corbadoApp.sessionService.shortSessionChanges.subscribe(value => { + corbadoApp.sessionService.sessionTokenChanges.subscribe(value => { this.#shortSession = value; }); @@ -54,7 +54,7 @@ export class CorbadoAppState { } get shortSessionChanges() { - return this.#corbadoApp.sessionService.shortSessionChanges; + return this.#corbadoApp.sessionService.sessionTokenChanges; } get isAuthenticated() { From 233f4fb547f6617f373885981d8e5c465caeb9cb Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:59:25 +0100 Subject: [PATCH 04/62] Bugfixing --- packages/react/src/contexts/CorbadoSessionProvider.tsx | 2 +- packages/web-core/src/models/auth.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react/src/contexts/CorbadoSessionProvider.tsx b/packages/react/src/contexts/CorbadoSessionProvider.tsx index c3b8b6c43..4fe0550b2 100644 --- a/packages/react/src/contexts/CorbadoSessionProvider.tsx +++ b/packages/react/src/contexts/CorbadoSessionProvider.tsx @@ -46,7 +46,7 @@ export const CorbadoSessionProvider: FC = ({ setIsAuthenticated(!!value); }); - const shortSessionSub = corbadoApp.sessionService.shortSessionChanges.subscribe((value: string | undefined) => { + const shortSessionSub = corbadoApp.sessionService.sessionTokenChanges.subscribe((value: string | undefined) => { setShortSession(value); }); diff --git a/packages/web-core/src/models/auth.ts b/packages/web-core/src/models/auth.ts index 4bb6dfe42..8b0afad6d 100644 --- a/packages/web-core/src/models/auth.ts +++ b/packages/web-core/src/models/auth.ts @@ -1,12 +1,12 @@ import type { AuthenticationRsp } from '../api/v1'; -import { ShortSession } from './session'; +import { SessionToken } from './sessionToken'; export class AuthenticationResponse { - shortSession: ShortSession; + shortSession: SessionToken; redirectURL: string; longSession?: string; - constructor(shortSession: ShortSession, redirectURL: string, longSession?: string) { + constructor(shortSession: SessionToken, redirectURL: string, longSession?: string) { this.shortSession = shortSession; this.redirectURL = redirectURL; this.longSession = longSession; @@ -17,6 +17,6 @@ export class AuthenticationResponse { throw new Error('ShortSession is undefined. This must never happen.'); } - return new AuthenticationResponse(new ShortSession(value.shortSession.value), value.redirectURL, value.longSession); + return new AuthenticationResponse(new SessionToken(value.shortSession.value), value.redirectURL, value.longSession); } } From b531a9df9466453820b8d30f6513d821e8623280 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:29:08 +0100 Subject: [PATCH 05/62] Bugfixing --- packages/web-core/src/models/auth.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web-core/src/models/auth.ts b/packages/web-core/src/models/auth.ts index 8b0afad6d..917cd3b51 100644 --- a/packages/web-core/src/models/auth.ts +++ b/packages/web-core/src/models/auth.ts @@ -2,14 +2,14 @@ import type { AuthenticationRsp } from '../api/v1'; import { SessionToken } from './sessionToken'; export class AuthenticationResponse { - shortSession: SessionToken; + sessionToken: SessionToken; redirectURL: string; - longSession?: string; + refreshToken?: string; - constructor(shortSession: SessionToken, redirectURL: string, longSession?: string) { - this.shortSession = shortSession; + constructor(sessionToken: SessionToken, redirectURL: string, refreshToken?: string) { + this.sessionToken = sessionToken; this.redirectURL = redirectURL; - this.longSession = longSession; + this.refreshToken = refreshToken; } static fromApiAuthenticationRsp(value: AuthenticationRsp): AuthenticationResponse { From caee9ae05a2a2c88f0f3e1e5b4c7390f1e23fdc1 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:37:20 +0100 Subject: [PATCH 06/62] Updated OpenAPI public FAPI v2 client --- packages/web-core/openapi/spec_v2.yaml | 14 +++++++++++++- packages/web-core/src/api/v2/api.ts | 23 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/web-core/openapi/spec_v2.yaml b/packages/web-core/openapi/spec_v2.yaml index 3f783716c..321b3788f 100644 --- a/packages/web-core/openapi/spec_v2.yaml +++ b/packages/web-core/openapi/spec_v2.yaml @@ -1170,9 +1170,13 @@ components: type: object required: - shortSession + - sessionToken properties: shortSession: type: string + deprecated: true + sessionToken: + type: string mePasskeyDeleteRsp: type: object @@ -1820,14 +1824,22 @@ components: required: - blockType - shortSession + - sessionToken properties: blockType: type: string longSession: type: string - description: Only given when project environment is dev + deprecated: true + description: This is only set if the project environment is set to 'dev'. If set the UI components will set the longSession in local storage because the cookie dropping will not work in Safari for example ("third-party cookie"). + refreshToken: + type: string + description: This is only set if the project environment is set to 'dev'. If set the UI components will set the longSession in local storage because the cookie dropping will not work in Safari for example ("third-party cookie"). shortSession: type: string + deprecated: true + sessionToken: + type: string passkeyOperation: $ref: '#/components/schemas/passkeyOperation' diff --git a/packages/web-core/src/api/v2/api.ts b/packages/web-core/src/api/v2/api.ts index adab9da74..f292731fd 100644 --- a/packages/web-core/src/api/v2/api.ts +++ b/packages/web-core/src/api/v2/api.ts @@ -855,17 +855,31 @@ export interface GeneralBlockCompleted { */ 'blockType': string; /** - * Only given when project environment is dev + * This is only set if the project environment is set to \'dev\'. If set the UI components will set the longSession in local storage because the cookie dropping will not work in Safari for example (\"third-party cookie\"). * @type {string} * @memberof GeneralBlockCompleted + * @deprecated */ 'longSession'?: string; + /** + * This is only set if the project environment is set to \'dev\'. If set the UI components will set the longSession in local storage because the cookie dropping will not work in Safari for example (\"third-party cookie\"). + * @type {string} + * @memberof GeneralBlockCompleted + */ + 'refreshToken'?: string; /** * * @type {string} * @memberof GeneralBlockCompleted + * @deprecated */ 'shortSession': string; + /** + * + * @type {string} + * @memberof GeneralBlockCompleted + */ + 'sessionToken': string; /** * * @type {PasskeyOperation} @@ -1644,8 +1658,15 @@ export interface MeRefreshRsp { * * @type {string} * @memberof MeRefreshRsp + * @deprecated */ 'shortSession': string; + /** + * + * @type {string} + * @memberof MeRefreshRsp + */ + 'sessionToken': string; } /** * From 581027acd89b0e77503f5cfdf9b9dcc734d1d426 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:41:58 +0100 Subject: [PATCH 07/62] Forth round of adaption --- packages/web-core/src/models/auth.ts | 2 ++ packages/web-core/src/services/SessionService.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/web-core/src/models/auth.ts b/packages/web-core/src/models/auth.ts index 917cd3b51..0a1124334 100644 --- a/packages/web-core/src/models/auth.ts +++ b/packages/web-core/src/models/auth.ts @@ -1,6 +1,8 @@ import type { AuthenticationRsp } from '../api/v1'; import { SessionToken } from './sessionToken'; +// @todo MK can we remove this file? + export class AuthenticationResponse { sessionToken: SessionToken; redirectURL: string; diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 9da648e74..764c375c3 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -232,9 +232,9 @@ export class SessionService { } /** Method to set Session - * It sets the short term session token, long term session token, and username for the Corbado Application. - * @param sessionToken The short term session token to be set. - * @param refreshToken The long term session token to be set. + * It sets the session-token, refresh-token, and username for the Corbado Application. + * @param sessionToken The session-token to be set. + * @param refreshToken The refresh-token to be set. */ setSession(sessionToken: string, refreshToken: string | undefined) { const sessionTokenModel = new SessionToken(sessionToken); @@ -307,7 +307,7 @@ export class SessionService { /** * Method to delete Session. - * It deletes the short term session token, long term session token, and username for the Corbado Application. + * It deletes the session-token, refresh-token and username for the Corbado Application. */ clear() { this.#deleteSessionToken(); @@ -436,12 +436,12 @@ export class SessionService { return; } - if (!response.data.shortSession) { + if (!response.data.sessionToken) { log.warn('refresh error, missing session-token'); return; } - this.setSession(response.data.shortSession, undefined); + this.setSession(response.data.sessionToken, undefined); } catch (e) { // if it's a network error, we should do a retry // for all other errors, we should log out the user From b955c3587285361dcef7e87f24a10dc312524d8c Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:16:45 +0100 Subject: [PATCH 08/62] Adapted react --- examples/nextjs/middleware.ts | 6 +++--- packages/react/src/contexts/CorbadoSessionContext.tsx | 6 ------ .../react/src/contexts/CorbadoSessionProvider.tsx | 11 ++++------- packages/shared-ui/src/flowHandler/processHandler.ts | 2 +- packages/web-js/src/core/Corbado.ts | 8 ++++---- packages/web-js/src/models/CorbadoAppState.ts | 10 +++++----- 6 files changed, 17 insertions(+), 26 deletions(-) diff --git a/examples/nextjs/middleware.ts b/examples/nextjs/middleware.ts index 9476a3c44..be1086ad7 100644 --- a/examples/nextjs/middleware.ts +++ b/examples/nextjs/middleware.ts @@ -15,9 +15,9 @@ export async function middleware(request: NextRequest) { return NextResponse.next(); } - const cookie = request.cookies.get('cbo_short_session'); - const shortSession = cookie?.value; - const isSessionValid = await validateSession(shortSession); + const cookie = request.cookies.get('cbo_session_token'); + const sessionToken = cookie?.value; + const isSessionValid = await validateSession(sessionToken); if (isSessionValid && routes.authPaths.includes(url.pathname)) { url.pathname = '/dashboard'; diff --git a/packages/react/src/contexts/CorbadoSessionContext.tsx b/packages/react/src/contexts/CorbadoSessionContext.tsx index 78bfe5766..2fb203055 100644 --- a/packages/react/src/contexts/CorbadoSessionContext.tsx +++ b/packages/react/src/contexts/CorbadoSessionContext.tsx @@ -10,11 +10,6 @@ const missingImplementation = (): never => { export interface CorbadoSessionContextProps { corbadoApp: CorbadoApp | undefined; - /** - * @deprecated Use sessionToken instead - */ - shortSession: string | undefined; - sessionToken: string | undefined; loading: boolean; isAuthenticated: boolean; @@ -29,7 +24,6 @@ export interface CorbadoSessionContextProps { export const initialContext: CorbadoSessionContextProps = { corbadoApp: undefined, - shortSession: undefined, sessionToken: undefined, loading: true, isAuthenticated: false, diff --git a/packages/react/src/contexts/CorbadoSessionProvider.tsx b/packages/react/src/contexts/CorbadoSessionProvider.tsx index 4fe0550b2..ff4833fc0 100644 --- a/packages/react/src/contexts/CorbadoSessionProvider.tsx +++ b/packages/react/src/contexts/CorbadoSessionProvider.tsx @@ -22,7 +22,7 @@ export const CorbadoSessionProvider: FC = ({ const [loading, setLoading] = useState(true); const [user, setUser] = useState(); const [isAuthenticated, setIsAuthenticated] = useState(false); - const [shortSession, setShortSession] = useState(); + const [sessionToken, setSessionToken] = useState(); const init = async () => { setLoading(true); @@ -46,14 +46,14 @@ export const CorbadoSessionProvider: FC = ({ setIsAuthenticated(!!value); }); - const shortSessionSub = corbadoApp.sessionService.sessionTokenChanges.subscribe((value: string | undefined) => { - setShortSession(value); + const sessionTokenSub = corbadoApp.sessionService.sessionTokenChanges.subscribe((value: string | undefined) => { + setSessionToken(value); }); return () => { userSub.unsubscribe(); authStateSub.unsubscribe(); - shortSessionSub.unsubscribe(); + sessionTokenSub.unsubscribe(); }; }, []); @@ -86,13 +86,10 @@ export const CorbadoSessionProvider: FC = ({ [corbadoApp], ); - const sessionToken = shortSession; - return ( { - this.#shortSession = value; + this.#sessionToken = value; }); corbadoApp.sessionService.userChanges.subscribe(value => { @@ -49,11 +49,11 @@ export class CorbadoAppState { return this.#corbadoAppProps; } - get shortSession() { - return this.#shortSession; + get sessionToken() { + return this.#sessionToken; } - get shortSessionChanges() { + get sessionTokenChanges() { return this.#corbadoApp.sessionService.sessionTokenChanges; } From 700fb8b9cb5efabd0592deaaef73b09899355799 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:43:55 +0100 Subject: [PATCH 09/62] Adapted NextJS example --- examples/nextjs/README.md | 3 +-- examples/nextjs/app/providers.tsx | 3 --- examples/nextjs/app/ui/dashboard/current-user.tsx | 4 ++-- examples/nextjs/app/ui/dashboard/session-details.tsx | 10 +++++----- examples/nextjs/app/ui/right-intro-section.tsx | 6 +++--- examples/nextjs/app/utils/validateSession.ts | 10 +++++----- 6 files changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/nextjs/README.md b/examples/nextjs/README.md index de163b09e..50722512d 100644 --- a/examples/nextjs/README.md +++ b/examples/nextjs/README.md @@ -13,10 +13,9 @@ These are the different pages that you will find in this application: - `/signup` : Signup component page - `/dashboard` : The dashboard has three nested routes: - `/` : User Details page which uses `@corbado/node-sdk` to fetch complete details of the currently logged-in user. - - `/session-details` : this page deserializes the short session and show cases the details stored in the short session. + - `/session-details` : this page deserializes the session-token and show cases the details stored in the session-token. - `/passkey-list` : this page uses the `@corbado/react`'s `` component to showcase its use ## Points to Note - When you use a component from `@corbado/react` either a UI component or a Provider component you need to `use client` for client side rendering as the components make use of react contexts. -- For using `` with NextJS application we need to set `setShortSessionCookie` to true. It's important to set this since Corbado uses refresh tokens to keep the user logged in and by storing short session cookies, the user will be able to stay logged in even if the token is refreshed diff --git a/examples/nextjs/app/providers.tsx b/examples/nextjs/app/providers.tsx index e5a5d6ef5..cf89cb526 100644 --- a/examples/nextjs/app/providers.tsx +++ b/examples/nextjs/app/providers.tsx @@ -7,9 +7,6 @@ export function Providers({ children }: { children: React.ReactNode }) { {children} diff --git a/examples/nextjs/app/ui/dashboard/current-user.tsx b/examples/nextjs/app/ui/dashboard/current-user.tsx index 27d5b421a..fa5b47edf 100644 --- a/examples/nextjs/app/ui/dashboard/current-user.tsx +++ b/examples/nextjs/app/ui/dashboard/current-user.tsx @@ -4,9 +4,9 @@ import createNodeSDK from '@/app/utils/createNodeSDK'; export default async function CurrentUser() { const cookieStore = cookies(); - const session = cookieStore.get('cbo_short_session'); + const sessionTokenCookie = cookieStore.get('cbo_session_token'); const sdk = createNodeSDK(); - const currentSessionUser = await sdk.sessions().getCurrentUser(session?.value ?? ''); + const currentSessionUser = await sdk.sessions().getCurrentUser(sessionTokenCookie?.value ?? ''); const userResp = await sdk.users().get(currentSessionUser?.getID() ?? ''); const user = userResp.data; const activeEmail = user.emails.find(email => email.status === 'active'); diff --git a/examples/nextjs/app/ui/dashboard/session-details.tsx b/examples/nextjs/app/ui/dashboard/session-details.tsx index 224da75f7..b4c9d58c4 100644 --- a/examples/nextjs/app/ui/dashboard/session-details.tsx +++ b/examples/nextjs/app/ui/dashboard/session-details.tsx @@ -3,16 +3,16 @@ import { cookies } from 'next/headers'; export default function SessionDetails() { const cookieStore = cookies(); - const session = cookieStore.get('cbo_short_session'); + const sessionTokenCookie = cookieStore.get('cbo_session_token'); - const decodedShortSession = jwtDecode(session?.value ?? ''); - const serializedDecodedShortSession = JSON.stringify(decodedShortSession, null, 2); + const decodedSessionToken = jwtDecode(sessionTokenCookie?.value ?? ''); + const serializedDecodedSessionToken = JSON.stringify(decodedSessionToken, null, 2); return ( <>
-

This is your shortSession:

-
{serializedDecodedShortSession}
+

This is your sessionToken:

+
{serializedDecodedSessionToken}
); diff --git a/examples/nextjs/app/ui/right-intro-section.tsx b/examples/nextjs/app/ui/right-intro-section.tsx index c9e78cb31..4ec6ca386 100644 --- a/examples/nextjs/app/ui/right-intro-section.tsx +++ b/examples/nextjs/app/ui/right-intro-section.tsx @@ -6,9 +6,9 @@ import Image from 'next/image'; export default async function RightIntroSection() { const cookieStore = cookies(); - const sessionCookie = cookieStore.get('cbo_short_session'); - const shortSession = sessionCookie?.value; - const isSessionValid = await validateSession(shortSession); + const sessionTokenCookie = cookieStore.get('cbo_session_token'); + const sessionToken = sessionTokenCookie?.value; + const isSessionValid = await validateSession(sessionToken); if (isSessionValid) { return ( diff --git a/examples/nextjs/app/utils/validateSession.ts b/examples/nextjs/app/utils/validateSession.ts index b571b3b0b..ed97c96f6 100644 --- a/examples/nextjs/app/utils/validateSession.ts +++ b/examples/nextjs/app/utils/validateSession.ts @@ -1,18 +1,18 @@ import { jwtDecode } from 'jwt-decode'; import createNodeSDK from './createNodeSDK'; -export default async function validateSession(shortSession: string | undefined) { - if (!shortSession) { +export default async function validateSession(sessionToken: string | undefined) { + if (!sessionToken) { return false; } const sdk = createNodeSDK(); - const verifiedSession = await sdk.sessions().validateShortSessionValue(shortSession); + const verifiedSession = await sdk.sessions().validateShortSessionValue(sessionToken); if (!verifiedSession.isAuthenticated()) { return false; } - const decodedShortSession = jwtDecode(shortSession); - return !!decodedShortSession.exp && decodedShortSession.exp > Date.now() / 1000; + const decodedSessionToken = jwtDecode(sessionToken); + return !!decodedSessionToken.exp && decodedSessionToken.exp > Date.now() / 1000; } From 5a7a94036addbffca2dd0696a61a3251b0d87276 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:56:20 +0100 Subject: [PATCH 10/62] Removed more old code, updated public FAPI v2 OpenAPI spec once again (new session-token cookie config) --- packages/types/src/common.ts | 1 - packages/web-core/openapi/spec_v2.yaml | 24 ++++++++++++++++++++++++ packages/web-js/src/core/Corbado.ts | 14 -------------- playground/web-js/src/scripts/index.js | 1 - 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index 68fd0f4bb..52c52795b 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -11,6 +11,5 @@ export interface CorbadoAppParams { frontendApiUrl?: string; frontendApiUrlSuffix?: string; isDevMode?: boolean; - setShortSessionCookie?: boolean; isPreviewMode?: boolean; } diff --git a/packages/web-core/openapi/spec_v2.yaml b/packages/web-core/openapi/spec_v2.yaml index 321b3788f..258161e02 100644 --- a/packages/web-core/openapi/spec_v2.yaml +++ b/packages/web-core/openapi/spec_v2.yaml @@ -1137,6 +1137,8 @@ components: type: boolean shortSessionCookieConfig: $ref: '#/components/schemas/shortSessionCookieConfig' + sessionTokenCookieConfig: + $ref: '#/components/schemas/sessionTokenCookieConfig' frontendApiUrl: type: string @@ -1596,6 +1598,28 @@ components: type: boolean shortSessionCookieConfig: + type: object + deprecated: true + required: + - domain + - secure + - sameSite + - path + - lifetimeSeconds + properties: + domain: + type: string + secure: + type: boolean + sameSite: + type: string + enum: [ 'lax', 'strict', 'none' ] + path: + type: string + lifetimeSeconds: + type: integer + + sessionTokenCookieConfig: type: object required: - domain diff --git a/packages/web-js/src/core/Corbado.ts b/packages/web-js/src/core/Corbado.ts index e9b2ee754..d1dcfb2b6 100644 --- a/packages/web-js/src/core/Corbado.ts +++ b/packages/web-js/src/core/Corbado.ts @@ -15,24 +15,10 @@ export class Corbado { return this.#getCorbadoAppState().user; } - /** - * @deprecated Use sessionToken() instead - */ - get shortSession() { - return this.#getCorbadoAppState().sessionToken; - } - get sessionToken() { return this.#getCorbadoAppState().sessionToken; } - /** - * @deprecated Use sessionTokenChanges() instead - */ - get shortSessionChanges() { - return this.#getCorbadoAppState().sessionTokenChanges; - } - get sessionTokenChanges() { return this.#getCorbadoAppState().sessionTokenChanges; } diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index cb0a46360..186d64627 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -15,7 +15,6 @@ async function loadPage() { projectId: projectId, frontendApiUrlSuffix: CORBADO_FRONTEND_API_URL_SUFFIX, darkMode: 'auto', - setShortSessionCookie: true, }); } From da3c6805e0581c0ca85ce2edec6116aca81fe626 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:57:24 +0100 Subject: [PATCH 11/62] Regenerated OpenAPI client --- packages/web-core/src/api/v2/api.ts | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/web-core/src/api/v2/api.ts b/packages/web-core/src/api/v2/api.ts index f292731fd..4ecdd2c09 100644 --- a/packages/web-core/src/api/v2/api.ts +++ b/packages/web-core/src/api/v2/api.ts @@ -2184,8 +2184,15 @@ export interface SessionConfigRsp { * * @type {ShortSessionCookieConfig} * @memberof SessionConfigRsp + * @deprecated */ 'shortSessionCookieConfig'?: ShortSessionCookieConfig; + /** + * + * @type {SessionTokenCookieConfig} + * @memberof SessionConfigRsp + */ + 'sessionTokenCookieConfig'?: SessionTokenCookieConfig; /** * * @type {string} @@ -2193,6 +2200,52 @@ export interface SessionConfigRsp { */ 'frontendApiUrl'?: string; } +/** + * + * @export + * @interface SessionTokenCookieConfig + */ +export interface SessionTokenCookieConfig { + /** + * + * @type {string} + * @memberof SessionTokenCookieConfig + */ + 'domain': string; + /** + * + * @type {boolean} + * @memberof SessionTokenCookieConfig + */ + 'secure': boolean; + /** + * + * @type {string} + * @memberof SessionTokenCookieConfig + */ + 'sameSite': SessionTokenCookieConfigSameSiteEnum; + /** + * + * @type {string} + * @memberof SessionTokenCookieConfig + */ + 'path': string; + /** + * + * @type {number} + * @memberof SessionTokenCookieConfig + */ + 'lifetimeSeconds': number; +} + +export const SessionTokenCookieConfigSameSiteEnum = { + Lax: 'lax', + Strict: 'strict', + None: 'none' +} as const; + +export type SessionTokenCookieConfigSameSiteEnum = typeof SessionTokenCookieConfigSameSiteEnum[keyof typeof SessionTokenCookieConfigSameSiteEnum]; + /** * * @export From 4de5f4eee96007de2c84911edc749e7f603d94bd Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:58:00 +0100 Subject: [PATCH 12/62] Removed old auth model (was using public FAPI v1) --- packages/web-core/src/models/auth.ts | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 packages/web-core/src/models/auth.ts diff --git a/packages/web-core/src/models/auth.ts b/packages/web-core/src/models/auth.ts deleted file mode 100644 index 0a1124334..000000000 --- a/packages/web-core/src/models/auth.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { AuthenticationRsp } from '../api/v1'; -import { SessionToken } from './sessionToken'; - -// @todo MK can we remove this file? - -export class AuthenticationResponse { - sessionToken: SessionToken; - redirectURL: string; - refreshToken?: string; - - constructor(sessionToken: SessionToken, redirectURL: string, refreshToken?: string) { - this.sessionToken = sessionToken; - this.redirectURL = redirectURL; - this.refreshToken = refreshToken; - } - - static fromApiAuthenticationRsp(value: AuthenticationRsp): AuthenticationResponse { - if (!value.shortSession?.value) { - throw new Error('ShortSession is undefined. This must never happen.'); - } - - return new AuthenticationResponse(new SessionToken(value.shortSession.value), value.redirectURL, value.longSession); - } -} From 1e9c56f323c325aec6c257079475ef25bdb3da70 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 08:00:14 +0100 Subject: [PATCH 13/62] Adapted code to new session-token cookie config --- packages/web-core/src/services/SessionService.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 764c375c3..741e34c45 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -14,7 +14,7 @@ import { BehaviorSubject } from 'rxjs'; import { Err, Ok, Result } from 'ts-results'; import { Configuration } from '../api/v1'; -import type { SessionConfigRsp, ShortSessionCookieConfig } from '../api/v2'; +import type { SessionConfigRsp, SessionTokenCookieConfig } from '../api/v2'; import { ConfigsApi, UsersApi } from '../api/v2'; import { SessionToken } from '../models/sessionToken'; import { @@ -378,14 +378,14 @@ export class SessionService { document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); } - #getSessionTokenCookieString(config: ShortSessionCookieConfig, value: SessionToken): string { + #getSessionTokenCookieString(config: SessionTokenCookieConfig, value: SessionToken): string { const expires = new Date(Date.now() + config.lifetimeSeconds * 1000).toUTCString(); return `${sessionTokenKey}=${value}; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ config.sameSite }; path=${config.path}; expires=${expires}`; } - #getDeleteSessionTokenCookieString(config: ShortSessionCookieConfig) { + #getDeleteSessionTokenCookieString(config: SessionTokenCookieConfig) { return `${sessionTokenKey}=; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ config.sameSite }; path=${config.path}; expires=${new Date().toUTCString()}`; @@ -499,8 +499,8 @@ export class SessionService { return this.#sessionConfig; }; - #getSessionTokenCookieConfig = (): ShortSessionCookieConfig => { - const cfg = this.#getSessionConfig().shortSessionCookieConfig; + #getSessionTokenCookieConfig = (): SessionTokenCookieConfig => { + const cfg = this.#getSessionConfig().sessionTokenCookieConfig; if (!cfg) { throw CorbadoError.invalidConfig(); } From 235d783edcf96540f8859134c29b4c16632290fb Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:14:11 +0100 Subject: [PATCH 14/62] Added refresh-token documentation --- packages/web-core/src/services/SessionService.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 741e34c45..b72a17fc5 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -55,6 +55,18 @@ export class SessionService { #sessionConfig: SessionConfigRsp | undefined; #sessionToken: SessionToken | undefined; + + /** + * The refresh-token is returned in the response payload from the Frontend API if the project + * environment is set to 'dev' and set in this property. If the project environment is not set + * to 'dev' the refresh-token will be set as a cookie from the Frontend API (not part of the + * response payload and you can not access it from JavaScript because it is a HTTP only cookie). + * This is needed because Safari does not allow third-party cookies and for local development + * you will most likely run your project on a different origin than the Frontend API + * (e.g. http://localhost:3000 vs. https://pro-xxx.cloud.frontendapi.corbado.io). On production, + * this is "fixed" by setting a CNAME on the Frontend API to align the origins (e.g. + * https://www.example.com and https://auth.example.com). + */ #refreshToken: string | undefined; #refreshIntervalId: NodeJS.Timeout | undefined; From 8da0df70f66dae02c059f06359cef52b523e1228 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:15:28 +0100 Subject: [PATCH 15/62] Morerefresh-token documentation --- packages/web-core/src/services/SessionService.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index b72a17fc5..88b213744 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -353,8 +353,7 @@ export class SessionService { } /** - * Deletes the refresh-token. - * @todo Explain above (at property?) why this is needed. (Safari, Third-party cookies etc.) + * Deletes the refresh-token (see property for more details why this exists). */ #deleteRefreshToken(): void { localStorage.removeItem(refreshTokenKey); @@ -362,8 +361,7 @@ export class SessionService { } /** - * Gets the refresh-token. - * @todo Explain above (at property?) why this is needed. (Safari, Third-party cookies etc.) + * Gets the refresh-token (see property for more details why this exists). */ static #getRefreshToken() { return (localStorage.getItem(refreshTokenKey) as string) ?? ''; @@ -404,7 +402,7 @@ export class SessionService { } /** - * Sets the refresh-token. + * Sets the refresh-token (see property for more details why this exists). */ #setRefreshToken(refreshToken: string | undefined): void { if (!refreshToken) { From bea7979e68966fdb4afb8fde020cb4c7687ff9fd Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 12:04:57 +0100 Subject: [PATCH 16/62] Cleaning up FAPI URL configuration --- packages/types/src/common.ts | 4 +--- packages/web-core/src/services/SessionService.ts | 8 ++++---- packages/web-core/src/services/index.ts | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index 52c52795b..c09ba0779 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -6,10 +6,8 @@ export interface Paging { export interface CorbadoAppParams { projectId: string; + frontendApi: string; apiTimeout?: number; - // deprecated (no longer needed, Corbado backend sets this value automatically) - frontendApiUrl?: string; - frontendApiUrlSuffix?: string; isDevMode?: boolean; isPreviewMode?: boolean; } diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 88b213744..0151f3970 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -51,7 +51,7 @@ export class SessionService { readonly #isPreviewMode: boolean; readonly #projectId: string; - readonly #frontendApiUrlSuffix: string; + readonly #frontendApi: string; #sessionConfig: SessionConfigRsp | undefined; #sessionToken: SessionToken | undefined; @@ -74,9 +74,9 @@ export class SessionService { #sessionTokenChanges: BehaviorSubject = new BehaviorSubject(undefined); #authStateChanges: BehaviorSubject = new BehaviorSubject(AuthState.LoggedOut); - constructor(projectId: string, isPreviewMode: boolean, frontendApiUrlSuffix: string) { + constructor(projectId: string, frontendApi: string, isPreviewMode: boolean) { this.#projectId = projectId; - this.#frontendApiUrlSuffix = frontendApiUrlSuffix; + this.#frontendApi = frontendApi; this.#webAuthnService = new WebAuthnService(); this.#refreshToken = undefined; this.#isPreviewMode = isPreviewMode; @@ -533,7 +533,7 @@ export class SessionService { }; #getDefaultFrontendApiUrl() { - return `https://${this.#projectId}.${this.#frontendApiUrlSuffix}`; + return `https://${this.#projectId}.${this.#frontendApi}`; } async wrapWithErr(callback: () => Promise>): Promise> { diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 2cb52c102..267fcecad 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -26,16 +26,16 @@ export class CorbadoApp { constructor(corbadoParams: CorbadoAppParams) { const { projectId, + frontendApi, apiTimeout = defaultTimeout, - frontendApiUrlSuffix = 'frontendapi.corbado.io', isPreviewMode = false, } = corbadoParams; this.#projectId = projectId; - this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApiUrlSuffix); + this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApi); this.#sessionService = new SessionService( this.#projectId, - isPreviewMode, - frontendApiUrlSuffix, + frontendApi, + isPreviewMode ); } From 785abe4e7e22056af6609f1cf01197d73677635f Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 14:03:37 +0100 Subject: [PATCH 17/62] Cleaning up FAPI URL configuration --- .../web-core/src/services/ProcessService.ts | 24 +++----- .../web-core/src/services/SessionService.ts | 17 ++---- packages/web-core/src/services/index.ts | 55 ++++++++++++++++++- playground/web-js/.env.local | 2 +- playground/web-js/.env.vercel | 2 +- playground/web-js/src/scripts/environment.js | 2 +- playground/web-js/src/scripts/index.js | 4 +- 7 files changed, 70 insertions(+), 36 deletions(-) diff --git a/packages/web-core/src/services/ProcessService.ts b/packages/web-core/src/services/ProcessService.ts index 018df806f..eabe89f90 100644 --- a/packages/web-core/src/services/ProcessService.ts +++ b/packages/web-core/src/services/ProcessService.ts @@ -48,16 +48,15 @@ export class ProcessService { #authApi: AuthApi = new AuthApi(); #webAuthnService: WebAuthnService; - // Private fields for project ID and default timeout for API calls. - #projectId: string; - #timeout: number; + readonly #projectId: string; + readonly #frontendApi: string; + readonly #timeout: number; readonly #isPreviewMode: boolean; - readonly #frontendApiUrlSuffix: string; - constructor(projectId: string, timeout: number = 30 * 1000, isPreviewMode: boolean, frontendApiUrlSuffix: string) { + constructor(projectId: string, frontendApi: string, timeout: number = 30 * 1000, isPreviewMode: boolean) { this.#projectId = projectId; + this.#frontendApi = frontendApi; this.#timeout = timeout; - this.#frontendApiUrlSuffix = frontendApiUrlSuffix; this.#webAuthnService = new WebAuthnService(); this.#isPreviewMode = isPreviewMode; @@ -209,18 +208,13 @@ export class ProcessService { } #setApisV2(process?: AuthProcess): void { - let frontendApiUrl = this.#getDefaultFrontendApiUrl(); - if (process?.frontendApiUrl && process?.frontendApiUrl.length > 0) { - frontendApiUrl = process.frontendApiUrl; - } - const config = new Configuration({ apiKey: this.#projectId, - basePath: frontendApiUrl, + basePath: this.#frontendApi, }); const axiosInstance = this.#createAxiosInstanceV2(process?.id ?? ''); - this.#authApi = new AuthApi(config, frontendApiUrl, axiosInstance); + this.#authApi = new AuthApi(config, this.#frontendApi, axiosInstance); } async #initAuthProcess( @@ -618,10 +612,6 @@ export class ProcessService { this.#webAuthnService.abortOngoingOperation(); } - #getDefaultFrontendApiUrl() { - return `https://${this.#projectId}.${this.#frontendApiUrlSuffix}`; - } - #buildCorbadoFlags = (): string => { const flags: string[] = []; if (this.#isPreviewMode) { diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 0151f3970..4a8d5a3b5 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -52,8 +52,8 @@ export class SessionService { readonly #isPreviewMode: boolean; readonly #projectId: string; readonly #frontendApi: string; - #sessionConfig: SessionConfigRsp | undefined; + #sessionConfig: SessionConfigRsp | undefined; #sessionToken: SessionToken | undefined; /** @@ -259,18 +259,13 @@ export class SessionService { } #setApisV2(longSession: string): void { - let frontendApiUrl = this.#getSessionConfig().frontendApiUrl; - if (!frontendApiUrl || frontendApiUrl.length === 0) { - frontendApiUrl = this.#getDefaultFrontendApiUrl(); - } - const config = new Configuration({ apiKey: this.#projectId, - basePath: frontendApiUrl, + basePath: this.#frontendApi, }); const axiosInstance = this.#createAxiosInstanceV2(longSession); - this.#usersApi = new UsersApi(config, frontendApiUrl, axiosInstance); + this.#usersApi = new UsersApi(config, this.#frontendApi, axiosInstance); } // usually sessionService needs a longSession for all it's requests @@ -524,7 +519,7 @@ export class SessionService { }); const axiosInstance = this.#createAxiosInstanceV2(); - const configsApi = new ConfigsApi(config, this.#getDefaultFrontendApiUrl(), axiosInstance); + const configsApi = new ConfigsApi(config, this.#frontendApi, axiosInstance); return Result.wrapAsync(async () => { const r = await configsApi.getSessionConfig(); @@ -532,10 +527,6 @@ export class SessionService { }); }; - #getDefaultFrontendApiUrl() { - return `https://${this.#projectId}.${this.#frontendApi}`; - } - async wrapWithErr(callback: () => Promise>): Promise> { try { const r = await callback(); diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 267fcecad..701336ee6 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -19,6 +19,7 @@ export class CorbadoApp { #authProcessService: ProcessService; #sessionService: SessionService; #projectId: string; + #frontendApi: string; /** * The constructor initializes the services and sets up the application. @@ -30,8 +31,10 @@ export class CorbadoApp { apiTimeout = defaultTimeout, isPreviewMode = false, } = corbadoParams; + this.#projectId = projectId; - this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApi); + this.#frontendApi = frontendApi; + this.#authProcessService = new ProcessService(this.#projectId, frontendApi, apiTimeout, isPreviewMode); this.#sessionService = new SessionService( this.#projectId, frontendApi, @@ -56,6 +59,11 @@ export class CorbadoApp { return Err(new NonRecoverableError('Invalid project ID')); } + const validationError = this.#validateFrontendApi(this.#frontendApi); + if (validationError !== '') { + return Err(new NonRecoverableError(validationError)); + } + await this.#sessionService.init(); return Ok(void 0); @@ -69,4 +77,49 @@ export class CorbadoApp { #validateProjectId(projectId: string): boolean { return /^pro-\d+$/.test(projectId); } + + #validateFrontendApi(frontendApi: string): string { + if (!frontendApi || frontendApi.trim() === "") { + return 'String must not be empty'; + } + + let url: URL; + try { + url = new URL(frontendApi); + } catch (err: any) { + return `Failed to parse URL: ${err.message}`; + } + + if (url.protocol !== "https:") { + const protocol = url.protocol.replace(":", ""); + + return `Scheme needs to be 'https' in given value '${frontendApi}' (scheme: '${protocol}')`; + } + + if (!url.hostname) { + return `Host must not be empty in given value '${frontendApi}'`; + } + + if (url.username) { + return `Username must be empty in given value '${frontendApi}' (username: '${url.username}')`; + } + + if (url.password) { + return `Password must be empty in given value '${frontendApi}' (password: '${url.password}')`; + } + + if (url.pathname !== "") { + return `Path must be empty in given value '${frontendApi}' (path: '${url.pathname}')`; + } + + if (url.hash !== "") { + return `Fragment must be empty in given value '${frontendApi}' (fragment: '${url.hash}')`; + } + + if (url.search !== "") { + return `Querystring must be empty in given value '${frontendApi}' (querystring: '${url.search}')`; + } + + return ''; + } } diff --git a/playground/web-js/.env.local b/playground/web-js/.env.local index 13935306d..ba90cfb15 100644 --- a/playground/web-js/.env.local +++ b/playground/web-js/.env.local @@ -1,2 +1,2 @@ CORBADO_PROJECT_ID=pro-7566555492758112393 -CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +CORBADO_FRONTEND_API=https://pro-7566555492758112393.frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js/.env.vercel b/playground/web-js/.env.vercel index 140bd45f1..0c011692f 100644 --- a/playground/web-js/.env.vercel +++ b/playground/web-js/.env.vercel @@ -1,2 +1,2 @@ CORBADO_PROJECT_ID=pro-2979442087152677678 -CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +CORBADO_FRONTEND_API=https://pro-2979442087152677678.frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js/src/scripts/environment.js b/playground/web-js/src/scripts/environment.js index 8a96e3c60..c8d3a4f15 100644 --- a/playground/web-js/src/scripts/environment.js +++ b/playground/web-js/src/scripts/environment.js @@ -1,2 +1,2 @@ export const CORBADO_PROJECT_ID = process.env.CORBADO_PROJECT_ID; -export const CORBADO_FRONTEND_API_URL_SUFFIX = process.env.CORBADO_FRONTEND_API_URL_SUFFIX; +export const CORBADO_FRONTEND_API = process.env.CORBADO_FRONTEND_API; diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index 186d64627..6f6229d6c 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -1,5 +1,5 @@ import Corbado from '@corbado/web-js'; -import { CORBADO_PROJECT_ID, CORBADO_FRONTEND_API_URL_SUFFIX } from './environment'; +import { CORBADO_PROJECT_ID, CORBADO_FRONTEND_API } from './environment'; let loaded = false; @@ -13,7 +13,7 @@ async function loadPage() { loaded = true; await Corbado.load({ projectId: projectId, - frontendApiUrlSuffix: CORBADO_FRONTEND_API_URL_SUFFIX, + frontendApi: CORBADO_FRONTEND_API, darkMode: 'auto', }); } From b414b4bec91036359acfd7acf945fb8542940420 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:31:36 +0100 Subject: [PATCH 18/62] Fixed trailing slash validation of FAPI url --- packages/web-core/src/services/index.ts | 20 ++++++++++++++------ playground/web-js-script/index.html | 3 +-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 701336ee6..e14af8192 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -61,6 +61,8 @@ export class CorbadoApp { const validationError = this.#validateFrontendApi(this.#frontendApi); if (validationError !== '') { + // @todo This exception is not shown in browser console + console.log(validationError); return Err(new NonRecoverableError(validationError)); } @@ -79,7 +81,7 @@ export class CorbadoApp { } #validateFrontendApi(frontendApi: string): string { - if (!frontendApi || frontendApi.trim() === "") { + if (!frontendApi || frontendApi.trim() === '') { return 'String must not be empty'; } @@ -90,8 +92,8 @@ export class CorbadoApp { return `Failed to parse URL: ${err.message}`; } - if (url.protocol !== "https:") { - const protocol = url.protocol.replace(":", ""); + if (url.protocol !== 'https:') { + const protocol = url.protocol.replace(':', ''); return `Scheme needs to be 'https' in given value '${frontendApi}' (scheme: '${protocol}')`; } @@ -108,15 +110,21 @@ export class CorbadoApp { return `Password must be empty in given value '${frontendApi}' (password: '${url.password}')`; } - if (url.pathname !== "") { + // We need to check for the trailing slash manually because URL class adds one by default if is + // not there (see next pathname validation). + if (frontendApi[frontendApi.length - 1] === '/') { + return `Trailing slash is not allowed in given value '${frontendApi}'`; + } + + if (url.pathname !== '' && url.pathname !== '/') { return `Path must be empty in given value '${frontendApi}' (path: '${url.pathname}')`; } - if (url.hash !== "") { + if (url.hash !== '') { return `Fragment must be empty in given value '${frontendApi}' (fragment: '${url.hash}')`; } - if (url.search !== "") { + if (url.search !== '') { return `Querystring must be empty in given value '${frontendApi}' (querystring: '${url.search}')`; } diff --git a/playground/web-js-script/index.html b/playground/web-js-script/index.html index 1ef0d961c..eeed76168 100644 --- a/playground/web-js-script/index.html +++ b/playground/web-js-script/index.html @@ -26,9 +26,8 @@ loaded = true; await Corbado.load({ projectId: projectId, - frontendApiUrlSuffix: 'frontendapi.cloud.corbado-staging.io', + frontendApi: `https://${projectId}.frontendapi.cloud.corbado-staging.io`, darkMode: 'auto', - setShortSessionCookie: true, }); } From 63cedc9a4844612b4d3394dec692f4a90bfce712 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Mon, 3 Feb 2025 08:06:37 +0100 Subject: [PATCH 19/62] Error handling debugging --- packages/web-core/src/services/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index e14af8192..3cee39cab 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -56,14 +56,16 @@ export class CorbadoApp { */ async init(): Promise> { if (!this.#validateProjectId(this.#projectId)) { - return Err(new NonRecoverableError('Invalid project ID')); + throw new Error(`Invalid project ID '${this.#projectId}'`); + // @todo Fix this + //return Err(new NonRecoverableError(`Invalid project ID '${this.#projectId}'`)); } const validationError = this.#validateFrontendApi(this.#frontendApi); if (validationError !== '') { - // @todo This exception is not shown in browser console - console.log(validationError); - return Err(new NonRecoverableError(validationError)); + throw new Error(validationError); + // @todo Fix this + //return Err(new NonRecoverableError(validationError)); } await this.#sessionService.init(); From 7b67d0223579514a0325645dbe7494c97f683765 Mon Sep 17 00:00:00 2001 From: Lukas Kratzel Date: Thu, 6 Feb 2025 11:47:48 +0100 Subject: [PATCH 20/62] fix react playground --- playground/react/.env | 2 +- playground/react/src/hoc/withCorbadoProvider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/react/.env b/playground/react/.env index 246ae453b..389dda6c1 100644 --- a/playground/react/.env +++ b/playground/react/.env @@ -1,6 +1,6 @@ REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-3652881945085154854 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-2 -REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +REACT_APP_CORBADO_FRONTEND_API_URL=https://frontendapi.cloud.corbado-staging.io # REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado-dev.io # REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado.io diff --git a/playground/react/src/hoc/withCorbadoProvider.tsx b/playground/react/src/hoc/withCorbadoProvider.tsx index b91d092e3..fa0bbb7c4 100644 --- a/playground/react/src/hoc/withCorbadoProvider.tsx +++ b/playground/react/src/hoc/withCorbadoProvider.tsx @@ -18,7 +18,7 @@ function withCorbadoProvider(WrappedComponent }} darkMode={darkMode ? 'on' : 'off'} isDevMode={true} - frontendApiUrlSuffix={process.env.REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX} + frontendApi={process.env.REACT_APP_CORBADO_FRONTEND_API_URL!} >
From a25eb03d1f16b09f39717943deb45d51912f0fb8 Mon Sep 17 00:00:00 2001 From: Lukas Kratzel Date: Thu, 6 Feb 2025 11:51:47 +0100 Subject: [PATCH 21/62] fmt --- packages/web-core/src/services/index.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 3cee39cab..1b7f8163b 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -1,9 +1,9 @@ import type { CorbadoAppParams } from '@corbado/types'; import type { Result } from 'ts-results'; -import { Err, Ok } from 'ts-results'; +import { Ok } from 'ts-results'; import type { CorbadoError } from '../utils'; -import { defaultTimeout, NonRecoverableError } from '../utils'; +import { defaultTimeout } from '../utils'; import { ProcessService } from './ProcessService'; import { SessionService } from './SessionService'; @@ -25,21 +25,12 @@ export class CorbadoApp { * The constructor initializes the services and sets up the application. */ constructor(corbadoParams: CorbadoAppParams) { - const { - projectId, - frontendApi, - apiTimeout = defaultTimeout, - isPreviewMode = false, - } = corbadoParams; + const { projectId, frontendApi, apiTimeout = defaultTimeout, isPreviewMode = false } = corbadoParams; this.#projectId = projectId; this.#frontendApi = frontendApi; this.#authProcessService = new ProcessService(this.#projectId, frontendApi, apiTimeout, isPreviewMode); - this.#sessionService = new SessionService( - this.#projectId, - frontendApi, - isPreviewMode - ); + this.#sessionService = new SessionService(this.#projectId, frontendApi, isPreviewMode); } get authProcessService() { @@ -114,7 +105,7 @@ export class CorbadoApp { // We need to check for the trailing slash manually because URL class adds one by default if is // not there (see next pathname validation). - if (frontendApi[frontendApi.length - 1] === '/') { + if (frontendApi[frontendApi.length - 1] === '/') { return `Trailing slash is not allowed in given value '${frontendApi}'`; } From 16271ad959338724801da40f58016a965e435570 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:33:48 +0100 Subject: [PATCH 22/62] PR feedback --- packages/web-core/src/services/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 1b7f8163b..c52a4f392 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -46,17 +46,15 @@ export class CorbadoApp { * It fetches the project configuration and initializes the services. */ async init(): Promise> { + // This can be improved by using the Err() type. Then we need to decide how to present the + // error (print it to the browser console, render it in the component, do both etc.) if (!this.#validateProjectId(this.#projectId)) { throw new Error(`Invalid project ID '${this.#projectId}'`); - // @todo Fix this - //return Err(new NonRecoverableError(`Invalid project ID '${this.#projectId}'`)); } const validationError = this.#validateFrontendApi(this.#frontendApi); if (validationError !== '') { throw new Error(validationError); - // @todo Fix this - //return Err(new NonRecoverableError(validationError)); } await this.#sessionService.init(); From 7ed656b6c9f1e8f1a4a90442d7dc54a6c7a7a8fc Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:47:07 +0100 Subject: [PATCH 23/62] Some more longSession => refreshToken renames --- packages/web-core/src/services/SessionService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 4a8d5a3b5..154c2961c 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -258,19 +258,19 @@ export class SessionService { this.#setRefreshToken(refreshToken); } - #setApisV2(longSession: string): void { + #setApisV2(refreshToken: string): void { const config = new Configuration({ apiKey: this.#projectId, basePath: this.#frontendApi, }); - const axiosInstance = this.#createAxiosInstanceV2(longSession); + const axiosInstance = this.#createAxiosInstanceV2(refreshToken); this.#usersApi = new UsersApi(config, this.#frontendApi, axiosInstance); } - // usually sessionService needs a longSession for all it's requests + // usually sessionService needs a refresh-token for all it's requests // just the initial request to fetch the sessionConfig doesn't need it - #createAxiosInstanceV2(longSession?: string): AxiosInstance { + #createAxiosInstanceV2(refreshToken?: string): AxiosInstance { const corbadoVersion = { name: 'web-core', sdkVersion: packageVersion, @@ -288,10 +288,10 @@ export class SessionService { headers['X-Corbado-Flags'] = this.#buildCorbadoFlags(); let axiosInstance: AxiosInstance; - if (longSession) { + if (refreshToken) { axiosInstance = axios.create({ withCredentials: true, - headers: { ...headers, Authorization: `Bearer ${longSession}` }, + headers: { ...headers, Authorization: `Bearer ${refreshToken}` }, }); } else { axiosInstance = axios.create({ From 3edd735025e9ce0f1aa9ba3b7e6c115b92e8511a Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:05:57 +0100 Subject: [PATCH 24/62] Made frontendApi prop optional --- packages/types/src/common.ts | 2 +- packages/web-core/src/services/ProcessService.ts | 14 +++++++++++++- packages/web-core/src/services/SessionService.ts | 15 ++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index c09ba0779..8f6f278f5 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -6,7 +6,7 @@ export interface Paging { export interface CorbadoAppParams { projectId: string; - frontendApi: string; + frontendApi?: string; apiTimeout?: number; isDevMode?: boolean; isPreviewMode?: boolean; diff --git a/packages/web-core/src/services/ProcessService.ts b/packages/web-core/src/services/ProcessService.ts index eabe89f90..c4b68a6eb 100644 --- a/packages/web-core/src/services/ProcessService.ts +++ b/packages/web-core/src/services/ProcessService.ts @@ -210,13 +210,25 @@ export class ProcessService { #setApisV2(process?: AuthProcess): void { const config = new Configuration({ apiKey: this.#projectId, - basePath: this.#frontendApi, + basePath: this.#getBasePath(process), }); const axiosInstance = this.#createAxiosInstanceV2(process?.id ?? ''); this.#authApi = new AuthApi(config, this.#frontendApi, axiosInstance); } + #getBasePath(process?: AuthProcess): string { + if (this.#frontendApi && this.#frontendApi.length > 0) { + return this.#frontendApi; + } + + if (process?.frontendApiUrl && process?.frontendApiUrl.length > 0) { + return process.frontendApiUrl + } + + return `https://${this.#projectId}.frontendapi.cloud.corbado.io`; + } + async #initAuthProcess( abortController: AbortController, frontendPreferredBlockType?: BlockType, diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 154c2961c..de8935043 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -261,13 +261,26 @@ export class SessionService { #setApisV2(refreshToken: string): void { const config = new Configuration({ apiKey: this.#projectId, - basePath: this.#frontendApi, + basePath: this.#getBasePath(), }); const axiosInstance = this.#createAxiosInstanceV2(refreshToken); this.#usersApi = new UsersApi(config, this.#frontendApi, axiosInstance); } + #getBasePath(): string { + if (this.#frontendApi && this.#frontendApi.length > 0) { + return this.#frontendApi; + } + + const frontendApiUrl = this.#getSessionConfig().frontendApiUrl; + if (frontendApiUrl && frontendApiUrl.length > 0) { + return frontendApiUrl + } + + return `https://${this.#projectId}.frontendapi.cloud.corbado.io`; + } + // usually sessionService needs a refresh-token for all it's requests // just the initial request to fetch the sessionConfig doesn't need it #createAxiosInstanceV2(refreshToken?: string): AxiosInstance { From 25bf2cd847b1d359151656d23dff088f74bce448 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:14:23 +0100 Subject: [PATCH 25/62] Bugfixing --- packages/web-core/src/services/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index c52a4f392..523b08dc6 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -25,7 +25,7 @@ export class CorbadoApp { * The constructor initializes the services and sets up the application. */ constructor(corbadoParams: CorbadoAppParams) { - const { projectId, frontendApi, apiTimeout = defaultTimeout, isPreviewMode = false } = corbadoParams; + const { projectId, frontendApi = '', apiTimeout = defaultTimeout, isPreviewMode = false } = corbadoParams; this.#projectId = projectId; this.#frontendApi = frontendApi; From 02a79a15b43a70679919ee656de55a414b94f340 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:16:19 +0100 Subject: [PATCH 26/62] Small improvement --- packages/web-core/src/services/ProcessService.ts | 2 +- packages/web-core/src/services/SessionService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web-core/src/services/ProcessService.ts b/packages/web-core/src/services/ProcessService.ts index c4b68a6eb..559054d4c 100644 --- a/packages/web-core/src/services/ProcessService.ts +++ b/packages/web-core/src/services/ProcessService.ts @@ -218,7 +218,7 @@ export class ProcessService { } #getBasePath(process?: AuthProcess): string { - if (this.#frontendApi && this.#frontendApi.length > 0) { + if (this.#frontendApi.length > 0) { return this.#frontendApi; } diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index de8935043..a4b881ba3 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -269,7 +269,7 @@ export class SessionService { } #getBasePath(): string { - if (this.#frontendApi && this.#frontendApi.length > 0) { + if (this.#frontendApi.length > 0) { return this.#frontendApi; } From d4229042ac6e97e58aa27de90c27003f398d9ed4 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:34:35 +0100 Subject: [PATCH 27/62] Fixed environment variables --- playground/react/.env | 6 +++--- playground/react/.env.production | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/playground/react/.env b/playground/react/.env index 389dda6c1..27948fb3c 100644 --- a/playground/react/.env +++ b/playground/react/.env @@ -1,6 +1,6 @@ REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-3652881945085154854 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-2 -REACT_APP_CORBADO_FRONTEND_API_URL=https://frontendapi.cloud.corbado-staging.io -# REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado-dev.io -# REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado.io +REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado-staging.io +# REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado-dev.io +# REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado.io diff --git a/playground/react/.env.production b/playground/react/.env.production index 55e59365a..af63ffa29 100644 --- a/playground/react/.env.production +++ b/playground/react/.env.production @@ -1,3 +1,4 @@ +// @todo Need to adapt this REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-2979442087152677678 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-7566555492758112393 -REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +REACT_APP_CORBADO_FRONTEND_API_URL=frontendapi.cloud.corbado-staging.io From 6d7ef59f839400a4dde9d812a1f4cb6d11ab86fc Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:40:42 +0100 Subject: [PATCH 28/62] Changed it back to frontendApiUrlSuffix --- packages/types/src/common.ts | 2 +- .../web-core/src/services/ProcessService.ts | 33 +++++----- .../web-core/src/services/SessionService.ts | 39 ++++++----- packages/web-core/src/services/index.ts | 64 +------------------ playground/react/.env | 6 +- playground/react/.env.production | 1 - playground/web-js-script/index.html | 2 +- playground/web-js/.env.local | 2 +- playground/web-js/.env.vercel | 2 +- playground/web-js/src/scripts/environment.js | 2 +- playground/web-js/src/scripts/index.js | 4 +- 11 files changed, 46 insertions(+), 111 deletions(-) diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index 8f6f278f5..9b031e4f9 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -6,8 +6,8 @@ export interface Paging { export interface CorbadoAppParams { projectId: string; - frontendApi?: string; apiTimeout?: number; + frontendApiUrlSuffix?: string; isDevMode?: boolean; isPreviewMode?: boolean; } diff --git a/packages/web-core/src/services/ProcessService.ts b/packages/web-core/src/services/ProcessService.ts index 559054d4c..69eaea74f 100644 --- a/packages/web-core/src/services/ProcessService.ts +++ b/packages/web-core/src/services/ProcessService.ts @@ -49,16 +49,16 @@ export class ProcessService { #webAuthnService: WebAuthnService; readonly #projectId: string; - readonly #frontendApi: string; readonly #timeout: number; readonly #isPreviewMode: boolean; + readonly #frontendApiUrlSuffix: string; - constructor(projectId: string, frontendApi: string, timeout: number = 30 * 1000, isPreviewMode: boolean) { + constructor(projectId: string, timeout: number = 30 * 1000, isPreviewMode: boolean, frontendApiUrlSuffix: string) { this.#projectId = projectId; - this.#frontendApi = frontendApi; this.#timeout = timeout; - this.#webAuthnService = new WebAuthnService(); this.#isPreviewMode = isPreviewMode; + this.#frontendApiUrlSuffix = frontendApiUrlSuffix; + this.#webAuthnService = new WebAuthnService(); // Initializes the API instances with no authentication token. // Authentication tokens are set in the SessionService. @@ -208,25 +208,18 @@ export class ProcessService { } #setApisV2(process?: AuthProcess): void { + let frontendApiUrl = this.#getDefaultFrontendApiUrl(); + if (process?.frontendApiUrl && process?.frontendApiUrl.length > 0) { + frontendApiUrl = process.frontendApiUrl; + } + const config = new Configuration({ apiKey: this.#projectId, - basePath: this.#getBasePath(process), + basePath: frontendApiUrl, }); const axiosInstance = this.#createAxiosInstanceV2(process?.id ?? ''); - this.#authApi = new AuthApi(config, this.#frontendApi, axiosInstance); - } - - #getBasePath(process?: AuthProcess): string { - if (this.#frontendApi.length > 0) { - return this.#frontendApi; - } - - if (process?.frontendApiUrl && process?.frontendApiUrl.length > 0) { - return process.frontendApiUrl - } - - return `https://${this.#projectId}.frontendapi.cloud.corbado.io`; + this.#authApi = new AuthApi(config, frontendApiUrl, axiosInstance); } async #initAuthProcess( @@ -624,6 +617,10 @@ export class ProcessService { this.#webAuthnService.abortOngoingOperation(); } + #getDefaultFrontendApiUrl() { + return `https://${this.#projectId}.${this.#frontendApiUrlSuffix}`; + } + #buildCorbadoFlags = (): string => { const flags: string[] = []; if (this.#isPreviewMode) { diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index a4b881ba3..6a6bc90fe 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -49,9 +49,9 @@ export class SessionService { #usersApi: UsersApi = new UsersApi(); #webAuthnService: WebAuthnService; - readonly #isPreviewMode: boolean; readonly #projectId: string; - readonly #frontendApi: string; + readonly #isPreviewMode: boolean; + readonly #frontendApiUrlSuffix: string; #sessionConfig: SessionConfigRsp | undefined; #sessionToken: SessionToken | undefined; @@ -74,12 +74,13 @@ export class SessionService { #sessionTokenChanges: BehaviorSubject = new BehaviorSubject(undefined); #authStateChanges: BehaviorSubject = new BehaviorSubject(AuthState.LoggedOut); - constructor(projectId: string, frontendApi: string, isPreviewMode: boolean) { + constructor(projectId: string, isPreviewMode: boolean, frontendApiUrlSuffix: string) { this.#projectId = projectId; - this.#frontendApi = frontendApi; + this.#isPreviewMode = isPreviewMode; + this.#frontendApiUrlSuffix = frontendApiUrlSuffix; + this.#webAuthnService = new WebAuthnService(); this.#refreshToken = undefined; - this.#isPreviewMode = isPreviewMode; } /** @@ -259,26 +260,18 @@ export class SessionService { } #setApisV2(refreshToken: string): void { + let frontendApiUrl = this.#getSessionConfig().frontendApiUrl; + if (!frontendApiUrl || frontendApiUrl.length === 0) { + frontendApiUrl = this.#getDefaultFrontendApiUrl(); + } + const config = new Configuration({ apiKey: this.#projectId, - basePath: this.#getBasePath(), + basePath: frontendApiUrl, }); const axiosInstance = this.#createAxiosInstanceV2(refreshToken); - this.#usersApi = new UsersApi(config, this.#frontendApi, axiosInstance); - } - - #getBasePath(): string { - if (this.#frontendApi.length > 0) { - return this.#frontendApi; - } - - const frontendApiUrl = this.#getSessionConfig().frontendApiUrl; - if (frontendApiUrl && frontendApiUrl.length > 0) { - return frontendApiUrl - } - - return `https://${this.#projectId}.frontendapi.cloud.corbado.io`; + this.#usersApi = new UsersApi(config, frontendApiUrl, axiosInstance); } // usually sessionService needs a refresh-token for all it's requests @@ -532,7 +525,7 @@ export class SessionService { }); const axiosInstance = this.#createAxiosInstanceV2(); - const configsApi = new ConfigsApi(config, this.#frontendApi, axiosInstance); + const configsApi = new ConfigsApi(config, this.#getDefaultFrontendApiUrl(), axiosInstance); return Result.wrapAsync(async () => { const r = await configsApi.getSessionConfig(); @@ -540,6 +533,10 @@ export class SessionService { }); }; + #getDefaultFrontendApiUrl() { + return `https://${this.#projectId}.${this.#frontendApiUrlSuffix}`; + } + async wrapWithErr(callback: () => Promise>): Promise> { try { const r = await callback(); diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 523b08dc6..be7b88224 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -19,18 +19,16 @@ export class CorbadoApp { #authProcessService: ProcessService; #sessionService: SessionService; #projectId: string; - #frontendApi: string; /** * The constructor initializes the services and sets up the application. */ constructor(corbadoParams: CorbadoAppParams) { - const { projectId, frontendApi = '', apiTimeout = defaultTimeout, isPreviewMode = false } = corbadoParams; + const { projectId, apiTimeout = defaultTimeout, frontendApiUrlSuffix = 'frontendapi.cloud.corbado.io', isPreviewMode = false } = corbadoParams; this.#projectId = projectId; - this.#frontendApi = frontendApi; - this.#authProcessService = new ProcessService(this.#projectId, frontendApi, apiTimeout, isPreviewMode); - this.#sessionService = new SessionService(this.#projectId, frontendApi, isPreviewMode); + this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApiUrlSuffix); + this.#sessionService = new SessionService(this.#projectId, isPreviewMode, frontendApiUrlSuffix); } get authProcessService() { @@ -52,11 +50,6 @@ export class CorbadoApp { throw new Error(`Invalid project ID '${this.#projectId}'`); } - const validationError = this.#validateFrontendApi(this.#frontendApi); - if (validationError !== '') { - throw new Error(validationError); - } - await this.#sessionService.init(); return Ok(void 0); @@ -70,55 +63,4 @@ export class CorbadoApp { #validateProjectId(projectId: string): boolean { return /^pro-\d+$/.test(projectId); } - - #validateFrontendApi(frontendApi: string): string { - if (!frontendApi || frontendApi.trim() === '') { - return 'String must not be empty'; - } - - let url: URL; - try { - url = new URL(frontendApi); - } catch (err: any) { - return `Failed to parse URL: ${err.message}`; - } - - if (url.protocol !== 'https:') { - const protocol = url.protocol.replace(':', ''); - - return `Scheme needs to be 'https' in given value '${frontendApi}' (scheme: '${protocol}')`; - } - - if (!url.hostname) { - return `Host must not be empty in given value '${frontendApi}'`; - } - - if (url.username) { - return `Username must be empty in given value '${frontendApi}' (username: '${url.username}')`; - } - - if (url.password) { - return `Password must be empty in given value '${frontendApi}' (password: '${url.password}')`; - } - - // We need to check for the trailing slash manually because URL class adds one by default if is - // not there (see next pathname validation). - if (frontendApi[frontendApi.length - 1] === '/') { - return `Trailing slash is not allowed in given value '${frontendApi}'`; - } - - if (url.pathname !== '' && url.pathname !== '/') { - return `Path must be empty in given value '${frontendApi}' (path: '${url.pathname}')`; - } - - if (url.hash !== '') { - return `Fragment must be empty in given value '${frontendApi}' (fragment: '${url.hash}')`; - } - - if (url.search !== '') { - return `Querystring must be empty in given value '${frontendApi}' (querystring: '${url.search}')`; - } - - return ''; - } } diff --git a/playground/react/.env b/playground/react/.env index 27948fb3c..246ae453b 100644 --- a/playground/react/.env +++ b/playground/react/.env @@ -1,6 +1,6 @@ REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-3652881945085154854 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-2 -REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado-staging.io -# REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado-dev.io -# REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado.io +REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +# REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado-dev.io +# REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado.io diff --git a/playground/react/.env.production b/playground/react/.env.production index af63ffa29..22719b0e2 100644 --- a/playground/react/.env.production +++ b/playground/react/.env.production @@ -1,4 +1,3 @@ -// @todo Need to adapt this REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-2979442087152677678 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-7566555492758112393 REACT_APP_CORBADO_FRONTEND_API_URL=frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js-script/index.html b/playground/web-js-script/index.html index eeed76168..51b9df75f 100644 --- a/playground/web-js-script/index.html +++ b/playground/web-js-script/index.html @@ -26,7 +26,7 @@ loaded = true; await Corbado.load({ projectId: projectId, - frontendApi: `https://${projectId}.frontendapi.cloud.corbado-staging.io`, + frontendApiUrlSuffix: 'frontendapi.cloud.corbado-staging.io', darkMode: 'auto', }); } diff --git a/playground/web-js/.env.local b/playground/web-js/.env.local index ba90cfb15..13935306d 100644 --- a/playground/web-js/.env.local +++ b/playground/web-js/.env.local @@ -1,2 +1,2 @@ CORBADO_PROJECT_ID=pro-7566555492758112393 -CORBADO_FRONTEND_API=https://pro-7566555492758112393.frontendapi.cloud.corbado-staging.io +CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js/.env.vercel b/playground/web-js/.env.vercel index 0c011692f..140bd45f1 100644 --- a/playground/web-js/.env.vercel +++ b/playground/web-js/.env.vercel @@ -1,2 +1,2 @@ CORBADO_PROJECT_ID=pro-2979442087152677678 -CORBADO_FRONTEND_API=https://pro-2979442087152677678.frontendapi.cloud.corbado-staging.io +CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js/src/scripts/environment.js b/playground/web-js/src/scripts/environment.js index c8d3a4f15..8a96e3c60 100644 --- a/playground/web-js/src/scripts/environment.js +++ b/playground/web-js/src/scripts/environment.js @@ -1,2 +1,2 @@ export const CORBADO_PROJECT_ID = process.env.CORBADO_PROJECT_ID; -export const CORBADO_FRONTEND_API = process.env.CORBADO_FRONTEND_API; +export const CORBADO_FRONTEND_API_URL_SUFFIX = process.env.CORBADO_FRONTEND_API_URL_SUFFIX; diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index 6f6229d6c..d7a71a8df 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -1,5 +1,5 @@ import Corbado from '@corbado/web-js'; -import { CORBADO_PROJECT_ID, CORBADO_FRONTEND_API } from './environment'; +import {CORBADO_PROJECT_ID, CORBADO_FRONTEND_API, CORBADO_FRONTEND_API_URL_SUFFIX} from './environment'; let loaded = false; @@ -13,7 +13,7 @@ async function loadPage() { loaded = true; await Corbado.load({ projectId: projectId, - frontendApi: CORBADO_FRONTEND_API, + frontendApiUrlSuffix: CORBADO_FRONTEND_API_URL_SUFFIX, darkMode: 'auto', }); } From f58c2465946075687d50ab0b65df390ebd6fcdc8 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 7 Feb 2025 07:12:03 +0100 Subject: [PATCH 29/62] More backporting --- playground/react/.env.production | 2 +- playground/react/src/hoc/withCorbadoProvider.tsx | 2 +- playground/web-js/src/scripts/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/playground/react/.env.production b/playground/react/.env.production index 22719b0e2..55e59365a 100644 --- a/playground/react/.env.production +++ b/playground/react/.env.production @@ -1,3 +1,3 @@ REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-2979442087152677678 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-7566555492758112393 -REACT_APP_CORBADO_FRONTEND_API_URL=frontendapi.cloud.corbado-staging.io +REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/playground/react/src/hoc/withCorbadoProvider.tsx b/playground/react/src/hoc/withCorbadoProvider.tsx index fa0bbb7c4..20bd903e5 100644 --- a/playground/react/src/hoc/withCorbadoProvider.tsx +++ b/playground/react/src/hoc/withCorbadoProvider.tsx @@ -18,7 +18,7 @@ function withCorbadoProvider(WrappedComponent }} darkMode={darkMode ? 'on' : 'off'} isDevMode={true} - frontendApi={process.env.REACT_APP_CORBADO_FRONTEND_API_URL!} + frontendApiUrlSuffix={process.env.REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX!} > diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index d7a71a8df..a5fa450e7 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -1,5 +1,5 @@ import Corbado from '@corbado/web-js'; -import {CORBADO_PROJECT_ID, CORBADO_FRONTEND_API, CORBADO_FRONTEND_API_URL_SUFFIX} from './environment'; +import {CORBADO_PROJECT_ID, CORBADO_FRONTEND_API_URL_SUFFIX} from './environment'; let loaded = false; From 8349edd81ff0d04f3e4c9638ed2d588e5120a46d Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 7 Feb 2025 07:14:12 +0100 Subject: [PATCH 30/62] More backporting --- playground/react/src/hoc/withCorbadoProvider.tsx | 2 +- playground/web-js/src/scripts/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/react/src/hoc/withCorbadoProvider.tsx b/playground/react/src/hoc/withCorbadoProvider.tsx index 20bd903e5..b91d092e3 100644 --- a/playground/react/src/hoc/withCorbadoProvider.tsx +++ b/playground/react/src/hoc/withCorbadoProvider.tsx @@ -18,7 +18,7 @@ function withCorbadoProvider(WrappedComponent }} darkMode={darkMode ? 'on' : 'off'} isDevMode={true} - frontendApiUrlSuffix={process.env.REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX!} + frontendApiUrlSuffix={process.env.REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX} > diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index a5fa450e7..186d64627 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -1,5 +1,5 @@ import Corbado from '@corbado/web-js'; -import {CORBADO_PROJECT_ID, CORBADO_FRONTEND_API_URL_SUFFIX} from './environment'; +import { CORBADO_PROJECT_ID, CORBADO_FRONTEND_API_URL_SUFFIX } from './environment'; let loaded = false; From 2d0e5f74a418a8e74f6d7b77304bc037b3b11ce9 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:29:52 +0100 Subject: [PATCH 31/62] First round of adaption --- .../web-core/src/services/SessionService.ts | 68 ++++--------------- packages/web-core/src/services/index.ts | 2 - 2 files changed, 15 insertions(+), 55 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index fc94ad8d5..22e7244b2 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -28,14 +28,13 @@ import { } from '../utils'; import { WebAuthnService } from './WebAuthnService'; -const shortSessionKey = 'cbo_short_session'; const sessionTokenKey = 'cbo_session_token'; -const longSessionKey = 'cbo_long_session'; +const refreshTokenKey = 'cbo_refresh_token'; -// controls how long before the shortSession expires we should refresh it -const shortSessionRefreshBeforeExpirationSeconds = 60; -// controls how often we check if we need to refresh the session -const shortSessionRefreshIntervalMs = 10_000; +// controls how long before the session-token expires we should refresh it +const sessionTokenRefreshBeforeExpirationSeconds = 60; +// controls how often we check if we need to refresh the session-token +const sessionTokenRefreshIntervalMs = 10_000; const packageVersion = '0.0.0'; @@ -50,7 +49,6 @@ export class SessionService { #usersApi: UsersApi = new UsersApi(); #webAuthnService: WebAuthnService; - readonly #setShortSessionCookie: boolean; readonly #isPreviewMode: boolean; readonly #projectId: string; readonly #frontendApiUrlSuffix: string; @@ -64,12 +62,11 @@ export class SessionService { #shortSessionChanges: BehaviorSubject = new BehaviorSubject(undefined); #authStateChanges: BehaviorSubject = new BehaviorSubject(AuthState.LoggedOut); - constructor(projectId: string, setShortSessionCookie: boolean, isPreviewMode: boolean, frontendApiUrlSuffix: string) { + constructor(projectId: string, isPreviewMode: boolean, frontendApiUrlSuffix: string) { this.#projectId = projectId; this.#frontendApiUrlSuffix = frontendApiUrlSuffix; this.#webAuthnService = new WebAuthnService(); this.#longSession = undefined; - this.#setShortSessionCookie = setShortSessionCookie; this.#isPreviewMode = isPreviewMode; } @@ -103,7 +100,7 @@ export class SessionService { // init scheduled session refresh this.#refreshIntervalId = setInterval(() => { void this.#handleRefreshRequest(); - }, shortSessionRefreshIntervalMs); + }, sessionTokenRefreshIntervalMs); document.addEventListener('visibilitychange', () => { this.#handleVisibilityChange(); @@ -324,23 +321,11 @@ export class SessionService { * Gets the short term session token. */ static #getShortTermSessionToken(): ShortSession | undefined { - const localStorageValue = localStorage.getItem(shortSessionKey); - if (localStorageValue) { - return new ShortSession(localStorageValue); - } - - // Get new session-token const sessionToken = this.#getCookieValue(sessionTokenKey); if (sessionToken) { return new ShortSession(sessionToken); } - // Fallback for deprecated short-term session - const shortSession = this.#getCookieValue(shortSessionKey); - if (shortSession) { - return new ShortSession(shortSession); - } - return undefined; } @@ -358,7 +343,7 @@ export class SessionService { * Deletes the long term session token cookie for dev environment in localStorage. */ #deleteLongSessionToken(): void { - localStorage.removeItem(longSessionKey); + localStorage.removeItem(refreshTokenKey); this.#longSession = ''; } @@ -366,7 +351,7 @@ export class SessionService { * Gets the long term session token. */ static #getLongSessionToken() { - return (localStorage.getItem(longSessionKey) as string) ?? ''; + return (localStorage.getItem(refreshTokenKey) as string) ?? ''; } /** @@ -374,37 +359,20 @@ export class SessionService { * @param value */ #setShortTermSessionToken(value: ShortSession): void { - localStorage.setItem(shortSessionKey, value.toString()); this.#shortSession = value; - if (this.#setShortSessionCookie) { - const cookieConfig = this.#getShortSessionCookieConfig(); - - document.cookie = this.#getShortSessionCookieString(cookieConfig, value); - document.cookie = this.#getSessionTokenCookieString(cookieConfig, value); - } + const cookieConfig = this.#getShortSessionCookieConfig(); + document.cookie = this.#getSessionTokenCookieString(cookieConfig, value); } /** * Deletes the short term session token. */ #deleteShortTermSessionToken(): void { - localStorage.removeItem(shortSessionKey); this.#shortSession = undefined; - if (this.#setShortSessionCookie) { - const cookieConfig = this.#getShortSessionCookieConfig(); - - document.cookie = this.#getDeleteShortSessionCookieString(cookieConfig); - document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); - } - } - - #getShortSessionCookieString(config: ShortSessionCookieConfig, value: ShortSession): string { - const expires = new Date(Date.now() + config.lifetimeSeconds * 1000).toUTCString(); - return `${shortSessionKey}=${value}; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ - config.sameSite - }; path=${config.path}; expires=${expires}`; + const cookieConfig = this.#getShortSessionCookieConfig(); + document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); } #getSessionTokenCookieString(config: ShortSessionCookieConfig, value: ShortSession): string { @@ -414,12 +382,6 @@ export class SessionService { }; path=${config.path}; expires=${expires}`; } - #getDeleteShortSessionCookieString(config: ShortSessionCookieConfig) { - return `${shortSessionKey}=; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ - config.sameSite - }; path=${config.path}; expires=${new Date().toUTCString()}`; - } - #getDeleteSessionTokenCookieString(config: ShortSessionCookieConfig) { return `${sessionTokenKey}=; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ config.sameSite @@ -435,7 +397,7 @@ export class SessionService { return; } - localStorage.setItem(longSessionKey, longSessionToken); + localStorage.setItem(refreshTokenKey, longSessionToken); this.#longSession = longSessionToken; } @@ -448,7 +410,7 @@ export class SessionService { } // refresh, token too old - if (!this.#shortSession.isValidForXMoreSeconds(shortSessionRefreshBeforeExpirationSeconds)) { + if (!this.#shortSession.isValidForXMoreSeconds(sessionTokenRefreshBeforeExpirationSeconds)) { await this.#refresh(); } diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index f8eed6fcb..2cb52c102 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -28,14 +28,12 @@ export class CorbadoApp { projectId, apiTimeout = defaultTimeout, frontendApiUrlSuffix = 'frontendapi.corbado.io', - setShortSessionCookie = true, isPreviewMode = false, } = corbadoParams; this.#projectId = projectId; this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApiUrlSuffix); this.#sessionService = new SessionService( this.#projectId, - setShortSessionCookie, isPreviewMode, frontendApiUrlSuffix, ); From 8de8450f426ec7187defad255b7ad64e39ad54b8 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:40:40 +0100 Subject: [PATCH 32/62] First round of adaption --- .../web-core/src/services/SessionService.ts | 108 +++++++++--------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 22e7244b2..0e42d3a44 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -54,8 +54,8 @@ export class SessionService { readonly #frontendApiUrlSuffix: string; #sessionConfig: SessionConfigRsp | undefined; - #shortSession: ShortSession | undefined; - #longSession: string | undefined; + #sessionToken: ShortSession | undefined; + #refreshToken: string | undefined; #refreshIntervalId: NodeJS.Timeout | undefined; #userChanges: BehaviorSubject = new BehaviorSubject(undefined); @@ -66,7 +66,7 @@ export class SessionService { this.#projectId = projectId; this.#frontendApiUrlSuffix = frontendApiUrlSuffix; this.#webAuthnService = new WebAuthnService(); - this.#longSession = undefined; + this.#refreshToken = undefined; this.#isPreviewMode = isPreviewMode; } @@ -84,18 +84,18 @@ export class SessionService { } this.#sessionConfig = sessionConfig.val; - this.#longSession = SessionService.#getLongSessionToken(); - this.#shortSession = SessionService.#getShortTermSessionToken(); + this.#refreshToken = SessionService.#getRefreshToken(); + this.#sessionToken = SessionService.#getSessionToken(); // if the session is valid, we emit it - if (this.#shortSession && this.#shortSession.isValidForXMoreSeconds(0)) { - log.debug('emit shortsession', this.#shortSession); - this.#onShortSessionChange(this.#shortSession); + if (this.#sessionToken && this.#sessionToken.isValidForXMoreSeconds(0)) { + log.debug('emit shortsession', this.#sessionToken); + this.#onSessionTokenChange(this.#sessionToken); } else { await this.#handleRefreshRequest(); } - this.#setApisV2(this.#longSession); + this.#setApisV2(this.#refreshToken); // init scheduled session refresh this.#refreshIntervalId = setInterval(() => { @@ -114,7 +114,7 @@ export class SessionService { * @returns The short term session token or null if it's not set. */ public get shortSession() { - return this.#shortSession; + return this.#sessionToken; } /** @@ -122,11 +122,11 @@ export class SessionService { * @returns The username or null if it's not set. */ public getUser(): SessionUser | undefined { - if (!this.#shortSession) { + if (!this.#sessionToken) { return; } - const sessionParts = this.#shortSession.value.split('.'); + const sessionParts = this.#sessionToken.value.split('.'); return JSON.parse(base64decode(sessionParts[1])); } @@ -212,18 +212,19 @@ export class SessionService { }); this.clear(); - this.#onShortSessionChange(undefined); + this.#onSessionTokenChange(undefined); } - #onShortSessionChange(shortSession: ShortSession | undefined) { + #onSessionTokenChange(sessionToken: ShortSession | undefined) { const user = this.getUser(); - if (user && shortSession) { - this.#shortSessionChanges.next(shortSession.value); + if (user && sessionToken) { + this.#shortSessionChanges.next(sessionToken.value); this.#updateAuthState(AuthState.LoggedIn); this.#updateUser(user); } else { - console.log('user is logged out', user, shortSession); + log.debug('user is logged out', user, sessionToken); + this.#shortSessionChanges.next(undefined); this.#updateAuthState(AuthState.LoggedOut); this.#updateUser(undefined); @@ -232,17 +233,17 @@ export class SessionService { /** Method to set Session * It sets the short term session token, long term session token, and username for the Corbado Application. - * @param shortSessionValue The short term session token to be set. - * @param longSession The long term session token to be set. + * @param sessionToken The short term session token to be set. + * @param refreshToken The long term session token to be set. */ - setSession(shortSessionValue: string, longSession: string | undefined) { - const shortSession = new ShortSession(shortSessionValue); + setSession(sessionToken: string, refreshToken: string | undefined) { + const sessionTokenModel = new ShortSession(sessionToken); - this.#setShortTermSessionToken(shortSession); - this.#setApisV2(longSession ?? ''); + this.#setSessionToken(sessionTokenModel); + this.#setApisV2(refreshToken ?? ''); - this.#onShortSessionChange(shortSession); - this.#setLongSessionToken(longSession); + this.#onSessionTokenChange(sessionTokenModel); + this.#setRefreshToken(refreshToken); } #setApisV2(longSession: string): void { @@ -309,8 +310,8 @@ export class SessionService { * It deletes the short term session token, long term session token, and username for the Corbado Application. */ clear() { - this.#deleteShortTermSessionToken(); - this.#deleteLongSessionToken(); + this.#deleteSessionToken(); + this.#deleteRefreshToken(); if (this.#refreshIntervalId) { clearInterval(this.#refreshIntervalId); @@ -318,9 +319,9 @@ export class SessionService { } /** - * Gets the short term session token. + * Gets the session-token. */ - static #getShortTermSessionToken(): ShortSession | undefined { + static #getSessionToken(): ShortSession | undefined { const sessionToken = this.#getCookieValue(sessionTokenKey); if (sessionToken) { return new ShortSession(sessionToken); @@ -340,38 +341,40 @@ export class SessionService { } /** - * Deletes the long term session token cookie for dev environment in localStorage. + * Deletes the refresh-token. + * @todo Explain above (at property?) why this is needed. (Safari, Third-party cookies etc.) */ - #deleteLongSessionToken(): void { + #deleteRefreshToken(): void { localStorage.removeItem(refreshTokenKey); - this.#longSession = ''; + this.#refreshToken = ''; } /** - * Gets the long term session token. + * Gets the refresh-token. + * @todo Explain above (at property?) why this is needed. (Safari, Third-party cookies etc.) */ - static #getLongSessionToken() { + static #getRefreshToken() { return (localStorage.getItem(refreshTokenKey) as string) ?? ''; } /** - * Sets a short term session token. + * Sets the session-token. * @param value */ - #setShortTermSessionToken(value: ShortSession): void { - this.#shortSession = value; + #setSessionToken(value: ShortSession): void { + this.#sessionToken = value; - const cookieConfig = this.#getShortSessionCookieConfig(); + const cookieConfig = this.#getSessionTokenCookieConfig(); document.cookie = this.#getSessionTokenCookieString(cookieConfig, value); } /** - * Deletes the short term session token. + * Deletes the session-token. */ - #deleteShortTermSessionToken(): void { - this.#shortSession = undefined; + #deleteSessionToken(): void { + this.#sessionToken = undefined; - const cookieConfig = this.#getShortSessionCookieConfig(); + const cookieConfig = this.#getSessionTokenCookieConfig(); document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); } @@ -389,28 +392,27 @@ export class SessionService { } /** - * Sets a long term session token for dev environment in localStorage. - * For production, it sets a cookie. + * Sets the refresh-token. */ - #setLongSessionToken(longSessionToken: string | undefined): void { - if (!longSessionToken) { + #setRefreshToken(refreshToken: string | undefined): void { + if (!refreshToken) { return; } - localStorage.setItem(refreshTokenKey, longSessionToken); - this.#longSession = longSessionToken; + localStorage.setItem(refreshTokenKey, refreshToken); + this.#refreshToken = refreshToken; } async #handleRefreshRequest() { - // no shortSession => user is not logged in => nothing to refresh - if (!this.#shortSession) { + // no session-token => user is not logged in => nothing to refresh + if (!this.#sessionToken) { log.debug('session refresh: no refresh, user not logged in'); return; } // refresh, token too old - if (!this.#shortSession.isValidForXMoreSeconds(sessionTokenRefreshBeforeExpirationSeconds)) { + if (!this.#sessionToken.isValidForXMoreSeconds(sessionTokenRefreshBeforeExpirationSeconds)) { await this.#refresh(); } @@ -425,7 +427,7 @@ export class SessionService { try { const options: AxiosRequestConfig = { headers: { - Authorization: `Bearer ${this.#longSession}`, + Authorization: `Bearer ${this.#refreshToken}`, }, }; const response = await this.#usersApi.currentUserSessionRefresh(options); @@ -497,7 +499,7 @@ export class SessionService { return this.#sessionConfig; }; - #getShortSessionCookieConfig = (): ShortSessionCookieConfig => { + #getSessionTokenCookieConfig = (): ShortSessionCookieConfig => { const cfg = this.#getSessionConfig().shortSessionCookieConfig; if (!cfg) { throw CorbadoError.invalidConfig(); From 3cab062cd3c25e1964228ec45c5041a29ba2dbfa Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:48:05 +0100 Subject: [PATCH 33/62] Third round of adaption --- .../models/{session.ts => sessionToken.ts} | 2 +- .../web-core/src/services/SessionService.ts | 44 +++++++++---------- packages/web-js/src/models/CorbadoAppState.ts | 4 +- 3 files changed, 25 insertions(+), 25 deletions(-) rename packages/web-core/src/models/{session.ts => sessionToken.ts} (96%) diff --git a/packages/web-core/src/models/session.ts b/packages/web-core/src/models/sessionToken.ts similarity index 96% rename from packages/web-core/src/models/session.ts rename to packages/web-core/src/models/sessionToken.ts index ccb52c92a..de706e0e8 100644 --- a/packages/web-core/src/models/session.ts +++ b/packages/web-core/src/models/sessionToken.ts @@ -2,7 +2,7 @@ import type { SessionUser } from '@corbado/types'; import { base64decode } from '../utils'; -export class ShortSession { +export class SessionToken { readonly #value: string; readonly #user: SessionUser; diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 0e42d3a44..9da648e74 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -16,7 +16,7 @@ import { Err, Ok, Result } from 'ts-results'; import { Configuration } from '../api/v1'; import type { SessionConfigRsp, ShortSessionCookieConfig } from '../api/v2'; import { ConfigsApi, UsersApi } from '../api/v2'; -import { ShortSession } from '../models/session'; +import { SessionToken } from '../models/sessionToken'; import { AuthState, base64decode, @@ -39,11 +39,11 @@ const sessionTokenRefreshIntervalMs = 10_000; const packageVersion = '0.0.0'; /** - * The SessionService manages user sessions for the Corbado Application, handling shortSession and longSession. + * The SessionService manages user sessions for the Corbado Application, handling session-token and refresh-token. * It offers methods to set, delete, and retrieve these tokens and the username, * as well as a method to fetch the full user object from the Corbado API. * - * The longSession should not be exposed from this service as it is only used for session refresh. + * The refresh-token should not be exposed from this service as it is only used for session refresh. */ export class SessionService { #usersApi: UsersApi = new UsersApi(); @@ -54,12 +54,12 @@ export class SessionService { readonly #frontendApiUrlSuffix: string; #sessionConfig: SessionConfigRsp | undefined; - #sessionToken: ShortSession | undefined; + #sessionToken: SessionToken | undefined; #refreshToken: string | undefined; #refreshIntervalId: NodeJS.Timeout | undefined; #userChanges: BehaviorSubject = new BehaviorSubject(undefined); - #shortSessionChanges: BehaviorSubject = new BehaviorSubject(undefined); + #sessionTokenChanges: BehaviorSubject = new BehaviorSubject(undefined); #authStateChanges: BehaviorSubject = new BehaviorSubject(AuthState.LoggedOut); constructor(projectId: string, isPreviewMode: boolean, frontendApiUrlSuffix: string) { @@ -71,7 +71,7 @@ export class SessionService { } /** - * Initializes the SessionService by registering a callback that is called when the shortSession changes. + * Initializes the SessionService by registering a callback that is called when the session-token changes. */ async init(): Promise { const sessionConfig = await this.#loadSessionConfig(); @@ -89,7 +89,7 @@ export class SessionService { // if the session is valid, we emit it if (this.#sessionToken && this.#sessionToken.isValidForXMoreSeconds(0)) { - log.debug('emit shortsession', this.#sessionToken); + log.debug('emit session-token', this.#sessionToken); this.#onSessionTokenChange(this.#sessionToken); } else { await this.#handleRefreshRequest(); @@ -110,10 +110,10 @@ export class SessionService { } /** - * Getter method for retrieving the short term session token. - * @returns The short term session token or null if it's not set. + * Getter method for retrieving the session-token. + * @returns The session-token or null if it's not set. */ - public get shortSession() { + public get sessionToken() { return this.#sessionToken; } @@ -139,10 +139,10 @@ export class SessionService { } /** - * Exposes changes to the shortSession + * Exposes changes to the session-token. */ - get shortSessionChanges(): BehaviorSubject { - return this.#shortSessionChanges; + get sessionTokenChanges(): BehaviorSubject { + return this.#sessionTokenChanges; } /** @@ -215,17 +215,17 @@ export class SessionService { this.#onSessionTokenChange(undefined); } - #onSessionTokenChange(sessionToken: ShortSession | undefined) { + #onSessionTokenChange(sessionToken: SessionToken | undefined) { const user = this.getUser(); if (user && sessionToken) { - this.#shortSessionChanges.next(sessionToken.value); + this.#sessionTokenChanges.next(sessionToken.value); this.#updateAuthState(AuthState.LoggedIn); this.#updateUser(user); } else { log.debug('user is logged out', user, sessionToken); - this.#shortSessionChanges.next(undefined); + this.#sessionTokenChanges.next(undefined); this.#updateAuthState(AuthState.LoggedOut); this.#updateUser(undefined); } @@ -237,7 +237,7 @@ export class SessionService { * @param refreshToken The long term session token to be set. */ setSession(sessionToken: string, refreshToken: string | undefined) { - const sessionTokenModel = new ShortSession(sessionToken); + const sessionTokenModel = new SessionToken(sessionToken); this.#setSessionToken(sessionTokenModel); this.#setApisV2(refreshToken ?? ''); @@ -321,10 +321,10 @@ export class SessionService { /** * Gets the session-token. */ - static #getSessionToken(): ShortSession | undefined { + static #getSessionToken(): SessionToken | undefined { const sessionToken = this.#getCookieValue(sessionTokenKey); if (sessionToken) { - return new ShortSession(sessionToken); + return new SessionToken(sessionToken); } return undefined; @@ -361,7 +361,7 @@ export class SessionService { * Sets the session-token. * @param value */ - #setSessionToken(value: ShortSession): void { + #setSessionToken(value: SessionToken): void { this.#sessionToken = value; const cookieConfig = this.#getSessionTokenCookieConfig(); @@ -378,7 +378,7 @@ export class SessionService { document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); } - #getSessionTokenCookieString(config: ShortSessionCookieConfig, value: ShortSession): string { + #getSessionTokenCookieString(config: ShortSessionCookieConfig, value: SessionToken): string { const expires = new Date(Date.now() + config.lifetimeSeconds * 1000).toUTCString(); return `${sessionTokenKey}=${value}; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ config.sameSite @@ -437,7 +437,7 @@ export class SessionService { } if (!response.data.shortSession) { - log.warn('refresh error, missing short session'); + log.warn('refresh error, missing session-token'); return; } diff --git a/packages/web-js/src/models/CorbadoAppState.ts b/packages/web-js/src/models/CorbadoAppState.ts index 1f05d5528..2822bc80c 100644 --- a/packages/web-js/src/models/CorbadoAppState.ts +++ b/packages/web-js/src/models/CorbadoAppState.ts @@ -15,7 +15,7 @@ export class CorbadoAppState { constructor(corbadoAppProps: CorbadoConfig) { const corbadoApp = new CorbadoApp(corbadoAppProps); - corbadoApp.sessionService.shortSessionChanges.subscribe(value => { + corbadoApp.sessionService.sessionTokenChanges.subscribe(value => { this.#shortSession = value; }); @@ -54,7 +54,7 @@ export class CorbadoAppState { } get shortSessionChanges() { - return this.#corbadoApp.sessionService.shortSessionChanges; + return this.#corbadoApp.sessionService.sessionTokenChanges; } get isAuthenticated() { From 8a90a3069613272f220b1c71f88f43c69b2523fe Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:59:25 +0100 Subject: [PATCH 34/62] Bugfixing --- packages/react/src/contexts/CorbadoSessionProvider.tsx | 2 +- packages/web-core/src/models/auth.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react/src/contexts/CorbadoSessionProvider.tsx b/packages/react/src/contexts/CorbadoSessionProvider.tsx index c3b8b6c43..4fe0550b2 100644 --- a/packages/react/src/contexts/CorbadoSessionProvider.tsx +++ b/packages/react/src/contexts/CorbadoSessionProvider.tsx @@ -46,7 +46,7 @@ export const CorbadoSessionProvider: FC = ({ setIsAuthenticated(!!value); }); - const shortSessionSub = corbadoApp.sessionService.shortSessionChanges.subscribe((value: string | undefined) => { + const shortSessionSub = corbadoApp.sessionService.sessionTokenChanges.subscribe((value: string | undefined) => { setShortSession(value); }); diff --git a/packages/web-core/src/models/auth.ts b/packages/web-core/src/models/auth.ts index 4bb6dfe42..8b0afad6d 100644 --- a/packages/web-core/src/models/auth.ts +++ b/packages/web-core/src/models/auth.ts @@ -1,12 +1,12 @@ import type { AuthenticationRsp } from '../api/v1'; -import { ShortSession } from './session'; +import { SessionToken } from './sessionToken'; export class AuthenticationResponse { - shortSession: ShortSession; + shortSession: SessionToken; redirectURL: string; longSession?: string; - constructor(shortSession: ShortSession, redirectURL: string, longSession?: string) { + constructor(shortSession: SessionToken, redirectURL: string, longSession?: string) { this.shortSession = shortSession; this.redirectURL = redirectURL; this.longSession = longSession; @@ -17,6 +17,6 @@ export class AuthenticationResponse { throw new Error('ShortSession is undefined. This must never happen.'); } - return new AuthenticationResponse(new ShortSession(value.shortSession.value), value.redirectURL, value.longSession); + return new AuthenticationResponse(new SessionToken(value.shortSession.value), value.redirectURL, value.longSession); } } From f3b3a2dad6e119840aa74d3bc16fd2ae82ee7e54 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:29:08 +0100 Subject: [PATCH 35/62] Bugfixing --- packages/web-core/src/models/auth.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web-core/src/models/auth.ts b/packages/web-core/src/models/auth.ts index 8b0afad6d..917cd3b51 100644 --- a/packages/web-core/src/models/auth.ts +++ b/packages/web-core/src/models/auth.ts @@ -2,14 +2,14 @@ import type { AuthenticationRsp } from '../api/v1'; import { SessionToken } from './sessionToken'; export class AuthenticationResponse { - shortSession: SessionToken; + sessionToken: SessionToken; redirectURL: string; - longSession?: string; + refreshToken?: string; - constructor(shortSession: SessionToken, redirectURL: string, longSession?: string) { - this.shortSession = shortSession; + constructor(sessionToken: SessionToken, redirectURL: string, refreshToken?: string) { + this.sessionToken = sessionToken; this.redirectURL = redirectURL; - this.longSession = longSession; + this.refreshToken = refreshToken; } static fromApiAuthenticationRsp(value: AuthenticationRsp): AuthenticationResponse { From 7c4a79b551e116ea0629cdf07f920caef1c4bed9 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:37:20 +0100 Subject: [PATCH 36/62] Updated OpenAPI public FAPI v2 client --- packages/web-core/openapi/spec_v2.yaml | 14 +++++++++++++- packages/web-core/src/api/v2/api.ts | 23 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/web-core/openapi/spec_v2.yaml b/packages/web-core/openapi/spec_v2.yaml index 3f783716c..321b3788f 100644 --- a/packages/web-core/openapi/spec_v2.yaml +++ b/packages/web-core/openapi/spec_v2.yaml @@ -1170,9 +1170,13 @@ components: type: object required: - shortSession + - sessionToken properties: shortSession: type: string + deprecated: true + sessionToken: + type: string mePasskeyDeleteRsp: type: object @@ -1820,14 +1824,22 @@ components: required: - blockType - shortSession + - sessionToken properties: blockType: type: string longSession: type: string - description: Only given when project environment is dev + deprecated: true + description: This is only set if the project environment is set to 'dev'. If set the UI components will set the longSession in local storage because the cookie dropping will not work in Safari for example ("third-party cookie"). + refreshToken: + type: string + description: This is only set if the project environment is set to 'dev'. If set the UI components will set the longSession in local storage because the cookie dropping will not work in Safari for example ("third-party cookie"). shortSession: type: string + deprecated: true + sessionToken: + type: string passkeyOperation: $ref: '#/components/schemas/passkeyOperation' diff --git a/packages/web-core/src/api/v2/api.ts b/packages/web-core/src/api/v2/api.ts index adab9da74..f292731fd 100644 --- a/packages/web-core/src/api/v2/api.ts +++ b/packages/web-core/src/api/v2/api.ts @@ -855,17 +855,31 @@ export interface GeneralBlockCompleted { */ 'blockType': string; /** - * Only given when project environment is dev + * This is only set if the project environment is set to \'dev\'. If set the UI components will set the longSession in local storage because the cookie dropping will not work in Safari for example (\"third-party cookie\"). * @type {string} * @memberof GeneralBlockCompleted + * @deprecated */ 'longSession'?: string; + /** + * This is only set if the project environment is set to \'dev\'. If set the UI components will set the longSession in local storage because the cookie dropping will not work in Safari for example (\"third-party cookie\"). + * @type {string} + * @memberof GeneralBlockCompleted + */ + 'refreshToken'?: string; /** * * @type {string} * @memberof GeneralBlockCompleted + * @deprecated */ 'shortSession': string; + /** + * + * @type {string} + * @memberof GeneralBlockCompleted + */ + 'sessionToken': string; /** * * @type {PasskeyOperation} @@ -1644,8 +1658,15 @@ export interface MeRefreshRsp { * * @type {string} * @memberof MeRefreshRsp + * @deprecated */ 'shortSession': string; + /** + * + * @type {string} + * @memberof MeRefreshRsp + */ + 'sessionToken': string; } /** * From d9f74b32cf6a53c1650f5cbe6d8c81f15d1660f1 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:41:58 +0100 Subject: [PATCH 37/62] Forth round of adaption --- packages/web-core/src/models/auth.ts | 2 ++ packages/web-core/src/services/SessionService.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/web-core/src/models/auth.ts b/packages/web-core/src/models/auth.ts index 917cd3b51..0a1124334 100644 --- a/packages/web-core/src/models/auth.ts +++ b/packages/web-core/src/models/auth.ts @@ -1,6 +1,8 @@ import type { AuthenticationRsp } from '../api/v1'; import { SessionToken } from './sessionToken'; +// @todo MK can we remove this file? + export class AuthenticationResponse { sessionToken: SessionToken; redirectURL: string; diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 9da648e74..764c375c3 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -232,9 +232,9 @@ export class SessionService { } /** Method to set Session - * It sets the short term session token, long term session token, and username for the Corbado Application. - * @param sessionToken The short term session token to be set. - * @param refreshToken The long term session token to be set. + * It sets the session-token, refresh-token, and username for the Corbado Application. + * @param sessionToken The session-token to be set. + * @param refreshToken The refresh-token to be set. */ setSession(sessionToken: string, refreshToken: string | undefined) { const sessionTokenModel = new SessionToken(sessionToken); @@ -307,7 +307,7 @@ export class SessionService { /** * Method to delete Session. - * It deletes the short term session token, long term session token, and username for the Corbado Application. + * It deletes the session-token, refresh-token and username for the Corbado Application. */ clear() { this.#deleteSessionToken(); @@ -436,12 +436,12 @@ export class SessionService { return; } - if (!response.data.shortSession) { + if (!response.data.sessionToken) { log.warn('refresh error, missing session-token'); return; } - this.setSession(response.data.shortSession, undefined); + this.setSession(response.data.sessionToken, undefined); } catch (e) { // if it's a network error, we should do a retry // for all other errors, we should log out the user From 7e89adeab7d9a4017d3452f03d0a2351b1948515 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:16:45 +0100 Subject: [PATCH 38/62] Adapted react --- examples/nextjs/middleware.ts | 6 +++--- packages/react/src/contexts/CorbadoSessionContext.tsx | 6 ------ .../react/src/contexts/CorbadoSessionProvider.tsx | 11 ++++------- packages/shared-ui/src/flowHandler/processHandler.ts | 2 +- packages/web-js/src/core/Corbado.ts | 8 ++++---- packages/web-js/src/models/CorbadoAppState.ts | 10 +++++----- 6 files changed, 17 insertions(+), 26 deletions(-) diff --git a/examples/nextjs/middleware.ts b/examples/nextjs/middleware.ts index 9476a3c44..be1086ad7 100644 --- a/examples/nextjs/middleware.ts +++ b/examples/nextjs/middleware.ts @@ -15,9 +15,9 @@ export async function middleware(request: NextRequest) { return NextResponse.next(); } - const cookie = request.cookies.get('cbo_short_session'); - const shortSession = cookie?.value; - const isSessionValid = await validateSession(shortSession); + const cookie = request.cookies.get('cbo_session_token'); + const sessionToken = cookie?.value; + const isSessionValid = await validateSession(sessionToken); if (isSessionValid && routes.authPaths.includes(url.pathname)) { url.pathname = '/dashboard'; diff --git a/packages/react/src/contexts/CorbadoSessionContext.tsx b/packages/react/src/contexts/CorbadoSessionContext.tsx index 78bfe5766..2fb203055 100644 --- a/packages/react/src/contexts/CorbadoSessionContext.tsx +++ b/packages/react/src/contexts/CorbadoSessionContext.tsx @@ -10,11 +10,6 @@ const missingImplementation = (): never => { export interface CorbadoSessionContextProps { corbadoApp: CorbadoApp | undefined; - /** - * @deprecated Use sessionToken instead - */ - shortSession: string | undefined; - sessionToken: string | undefined; loading: boolean; isAuthenticated: boolean; @@ -29,7 +24,6 @@ export interface CorbadoSessionContextProps { export const initialContext: CorbadoSessionContextProps = { corbadoApp: undefined, - shortSession: undefined, sessionToken: undefined, loading: true, isAuthenticated: false, diff --git a/packages/react/src/contexts/CorbadoSessionProvider.tsx b/packages/react/src/contexts/CorbadoSessionProvider.tsx index 4fe0550b2..ff4833fc0 100644 --- a/packages/react/src/contexts/CorbadoSessionProvider.tsx +++ b/packages/react/src/contexts/CorbadoSessionProvider.tsx @@ -22,7 +22,7 @@ export const CorbadoSessionProvider: FC = ({ const [loading, setLoading] = useState(true); const [user, setUser] = useState(); const [isAuthenticated, setIsAuthenticated] = useState(false); - const [shortSession, setShortSession] = useState(); + const [sessionToken, setSessionToken] = useState(); const init = async () => { setLoading(true); @@ -46,14 +46,14 @@ export const CorbadoSessionProvider: FC = ({ setIsAuthenticated(!!value); }); - const shortSessionSub = corbadoApp.sessionService.sessionTokenChanges.subscribe((value: string | undefined) => { - setShortSession(value); + const sessionTokenSub = corbadoApp.sessionService.sessionTokenChanges.subscribe((value: string | undefined) => { + setSessionToken(value); }); return () => { userSub.unsubscribe(); authStateSub.unsubscribe(); - shortSessionSub.unsubscribe(); + sessionTokenSub.unsubscribe(); }; }, []); @@ -86,13 +86,10 @@ export const CorbadoSessionProvider: FC = ({ [corbadoApp], ); - const sessionToken = shortSession; - return ( { - this.#shortSession = value; + this.#sessionToken = value; }); corbadoApp.sessionService.userChanges.subscribe(value => { @@ -49,11 +49,11 @@ export class CorbadoAppState { return this.#corbadoAppProps; } - get shortSession() { - return this.#shortSession; + get sessionToken() { + return this.#sessionToken; } - get shortSessionChanges() { + get sessionTokenChanges() { return this.#corbadoApp.sessionService.sessionTokenChanges; } From 1dd7419d25de5ffbd6d401bd5adb8b77fec00787 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:43:55 +0100 Subject: [PATCH 39/62] Adapted NextJS example --- examples/nextjs/README.md | 3 +-- examples/nextjs/app/providers.tsx | 3 --- examples/nextjs/app/ui/dashboard/current-user.tsx | 4 ++-- examples/nextjs/app/ui/dashboard/session-details.tsx | 10 +++++----- examples/nextjs/app/ui/right-intro-section.tsx | 6 +++--- examples/nextjs/app/utils/validateSession.ts | 10 +++++----- 6 files changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/nextjs/README.md b/examples/nextjs/README.md index de163b09e..50722512d 100644 --- a/examples/nextjs/README.md +++ b/examples/nextjs/README.md @@ -13,10 +13,9 @@ These are the different pages that you will find in this application: - `/signup` : Signup component page - `/dashboard` : The dashboard has three nested routes: - `/` : User Details page which uses `@corbado/node-sdk` to fetch complete details of the currently logged-in user. - - `/session-details` : this page deserializes the short session and show cases the details stored in the short session. + - `/session-details` : this page deserializes the session-token and show cases the details stored in the session-token. - `/passkey-list` : this page uses the `@corbado/react`'s `` component to showcase its use ## Points to Note - When you use a component from `@corbado/react` either a UI component or a Provider component you need to `use client` for client side rendering as the components make use of react contexts. -- For using `` with NextJS application we need to set `setShortSessionCookie` to true. It's important to set this since Corbado uses refresh tokens to keep the user logged in and by storing short session cookies, the user will be able to stay logged in even if the token is refreshed diff --git a/examples/nextjs/app/providers.tsx b/examples/nextjs/app/providers.tsx index e5a5d6ef5..cf89cb526 100644 --- a/examples/nextjs/app/providers.tsx +++ b/examples/nextjs/app/providers.tsx @@ -7,9 +7,6 @@ export function Providers({ children }: { children: React.ReactNode }) { {children} diff --git a/examples/nextjs/app/ui/dashboard/current-user.tsx b/examples/nextjs/app/ui/dashboard/current-user.tsx index 27d5b421a..fa5b47edf 100644 --- a/examples/nextjs/app/ui/dashboard/current-user.tsx +++ b/examples/nextjs/app/ui/dashboard/current-user.tsx @@ -4,9 +4,9 @@ import createNodeSDK from '@/app/utils/createNodeSDK'; export default async function CurrentUser() { const cookieStore = cookies(); - const session = cookieStore.get('cbo_short_session'); + const sessionTokenCookie = cookieStore.get('cbo_session_token'); const sdk = createNodeSDK(); - const currentSessionUser = await sdk.sessions().getCurrentUser(session?.value ?? ''); + const currentSessionUser = await sdk.sessions().getCurrentUser(sessionTokenCookie?.value ?? ''); const userResp = await sdk.users().get(currentSessionUser?.getID() ?? ''); const user = userResp.data; const activeEmail = user.emails.find(email => email.status === 'active'); diff --git a/examples/nextjs/app/ui/dashboard/session-details.tsx b/examples/nextjs/app/ui/dashboard/session-details.tsx index 224da75f7..b4c9d58c4 100644 --- a/examples/nextjs/app/ui/dashboard/session-details.tsx +++ b/examples/nextjs/app/ui/dashboard/session-details.tsx @@ -3,16 +3,16 @@ import { cookies } from 'next/headers'; export default function SessionDetails() { const cookieStore = cookies(); - const session = cookieStore.get('cbo_short_session'); + const sessionTokenCookie = cookieStore.get('cbo_session_token'); - const decodedShortSession = jwtDecode(session?.value ?? ''); - const serializedDecodedShortSession = JSON.stringify(decodedShortSession, null, 2); + const decodedSessionToken = jwtDecode(sessionTokenCookie?.value ?? ''); + const serializedDecodedSessionToken = JSON.stringify(decodedSessionToken, null, 2); return ( <>
-

This is your shortSession:

-
{serializedDecodedShortSession}
+

This is your sessionToken:

+
{serializedDecodedSessionToken}
); diff --git a/examples/nextjs/app/ui/right-intro-section.tsx b/examples/nextjs/app/ui/right-intro-section.tsx index c9e78cb31..4ec6ca386 100644 --- a/examples/nextjs/app/ui/right-intro-section.tsx +++ b/examples/nextjs/app/ui/right-intro-section.tsx @@ -6,9 +6,9 @@ import Image from 'next/image'; export default async function RightIntroSection() { const cookieStore = cookies(); - const sessionCookie = cookieStore.get('cbo_short_session'); - const shortSession = sessionCookie?.value; - const isSessionValid = await validateSession(shortSession); + const sessionTokenCookie = cookieStore.get('cbo_session_token'); + const sessionToken = sessionTokenCookie?.value; + const isSessionValid = await validateSession(sessionToken); if (isSessionValid) { return ( diff --git a/examples/nextjs/app/utils/validateSession.ts b/examples/nextjs/app/utils/validateSession.ts index b571b3b0b..ed97c96f6 100644 --- a/examples/nextjs/app/utils/validateSession.ts +++ b/examples/nextjs/app/utils/validateSession.ts @@ -1,18 +1,18 @@ import { jwtDecode } from 'jwt-decode'; import createNodeSDK from './createNodeSDK'; -export default async function validateSession(shortSession: string | undefined) { - if (!shortSession) { +export default async function validateSession(sessionToken: string | undefined) { + if (!sessionToken) { return false; } const sdk = createNodeSDK(); - const verifiedSession = await sdk.sessions().validateShortSessionValue(shortSession); + const verifiedSession = await sdk.sessions().validateShortSessionValue(sessionToken); if (!verifiedSession.isAuthenticated()) { return false; } - const decodedShortSession = jwtDecode(shortSession); - return !!decodedShortSession.exp && decodedShortSession.exp > Date.now() / 1000; + const decodedSessionToken = jwtDecode(sessionToken); + return !!decodedSessionToken.exp && decodedSessionToken.exp > Date.now() / 1000; } From f654d0f8a8428f31a1aeb5c8058321dd9dff962e Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:56:20 +0100 Subject: [PATCH 40/62] Removed more old code, updated public FAPI v2 OpenAPI spec once again (new session-token cookie config) --- packages/types/src/common.ts | 1 - packages/web-core/openapi/spec_v2.yaml | 24 ++++++++++++++++++++++++ packages/web-js/src/core/Corbado.ts | 14 -------------- playground/web-js/src/scripts/index.js | 1 - 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index 68fd0f4bb..52c52795b 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -11,6 +11,5 @@ export interface CorbadoAppParams { frontendApiUrl?: string; frontendApiUrlSuffix?: string; isDevMode?: boolean; - setShortSessionCookie?: boolean; isPreviewMode?: boolean; } diff --git a/packages/web-core/openapi/spec_v2.yaml b/packages/web-core/openapi/spec_v2.yaml index 321b3788f..258161e02 100644 --- a/packages/web-core/openapi/spec_v2.yaml +++ b/packages/web-core/openapi/spec_v2.yaml @@ -1137,6 +1137,8 @@ components: type: boolean shortSessionCookieConfig: $ref: '#/components/schemas/shortSessionCookieConfig' + sessionTokenCookieConfig: + $ref: '#/components/schemas/sessionTokenCookieConfig' frontendApiUrl: type: string @@ -1596,6 +1598,28 @@ components: type: boolean shortSessionCookieConfig: + type: object + deprecated: true + required: + - domain + - secure + - sameSite + - path + - lifetimeSeconds + properties: + domain: + type: string + secure: + type: boolean + sameSite: + type: string + enum: [ 'lax', 'strict', 'none' ] + path: + type: string + lifetimeSeconds: + type: integer + + sessionTokenCookieConfig: type: object required: - domain diff --git a/packages/web-js/src/core/Corbado.ts b/packages/web-js/src/core/Corbado.ts index e9b2ee754..d1dcfb2b6 100644 --- a/packages/web-js/src/core/Corbado.ts +++ b/packages/web-js/src/core/Corbado.ts @@ -15,24 +15,10 @@ export class Corbado { return this.#getCorbadoAppState().user; } - /** - * @deprecated Use sessionToken() instead - */ - get shortSession() { - return this.#getCorbadoAppState().sessionToken; - } - get sessionToken() { return this.#getCorbadoAppState().sessionToken; } - /** - * @deprecated Use sessionTokenChanges() instead - */ - get shortSessionChanges() { - return this.#getCorbadoAppState().sessionTokenChanges; - } - get sessionTokenChanges() { return this.#getCorbadoAppState().sessionTokenChanges; } diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index cb0a46360..186d64627 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -15,7 +15,6 @@ async function loadPage() { projectId: projectId, frontendApiUrlSuffix: CORBADO_FRONTEND_API_URL_SUFFIX, darkMode: 'auto', - setShortSessionCookie: true, }); } From 4952770ade6db5579c88952a299ce71b57e6489e Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:57:24 +0100 Subject: [PATCH 41/62] Regenerated OpenAPI client --- packages/web-core/src/api/v2/api.ts | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/web-core/src/api/v2/api.ts b/packages/web-core/src/api/v2/api.ts index f292731fd..4ecdd2c09 100644 --- a/packages/web-core/src/api/v2/api.ts +++ b/packages/web-core/src/api/v2/api.ts @@ -2184,8 +2184,15 @@ export interface SessionConfigRsp { * * @type {ShortSessionCookieConfig} * @memberof SessionConfigRsp + * @deprecated */ 'shortSessionCookieConfig'?: ShortSessionCookieConfig; + /** + * + * @type {SessionTokenCookieConfig} + * @memberof SessionConfigRsp + */ + 'sessionTokenCookieConfig'?: SessionTokenCookieConfig; /** * * @type {string} @@ -2193,6 +2200,52 @@ export interface SessionConfigRsp { */ 'frontendApiUrl'?: string; } +/** + * + * @export + * @interface SessionTokenCookieConfig + */ +export interface SessionTokenCookieConfig { + /** + * + * @type {string} + * @memberof SessionTokenCookieConfig + */ + 'domain': string; + /** + * + * @type {boolean} + * @memberof SessionTokenCookieConfig + */ + 'secure': boolean; + /** + * + * @type {string} + * @memberof SessionTokenCookieConfig + */ + 'sameSite': SessionTokenCookieConfigSameSiteEnum; + /** + * + * @type {string} + * @memberof SessionTokenCookieConfig + */ + 'path': string; + /** + * + * @type {number} + * @memberof SessionTokenCookieConfig + */ + 'lifetimeSeconds': number; +} + +export const SessionTokenCookieConfigSameSiteEnum = { + Lax: 'lax', + Strict: 'strict', + None: 'none' +} as const; + +export type SessionTokenCookieConfigSameSiteEnum = typeof SessionTokenCookieConfigSameSiteEnum[keyof typeof SessionTokenCookieConfigSameSiteEnum]; + /** * * @export From 9609aaa5a2c6c1b4bc4a1d6b9c831a302de88ac0 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:58:00 +0100 Subject: [PATCH 42/62] Removed old auth model (was using public FAPI v1) --- packages/web-core/src/models/auth.ts | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 packages/web-core/src/models/auth.ts diff --git a/packages/web-core/src/models/auth.ts b/packages/web-core/src/models/auth.ts deleted file mode 100644 index 0a1124334..000000000 --- a/packages/web-core/src/models/auth.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { AuthenticationRsp } from '../api/v1'; -import { SessionToken } from './sessionToken'; - -// @todo MK can we remove this file? - -export class AuthenticationResponse { - sessionToken: SessionToken; - redirectURL: string; - refreshToken?: string; - - constructor(sessionToken: SessionToken, redirectURL: string, refreshToken?: string) { - this.sessionToken = sessionToken; - this.redirectURL = redirectURL; - this.refreshToken = refreshToken; - } - - static fromApiAuthenticationRsp(value: AuthenticationRsp): AuthenticationResponse { - if (!value.shortSession?.value) { - throw new Error('ShortSession is undefined. This must never happen.'); - } - - return new AuthenticationResponse(new SessionToken(value.shortSession.value), value.redirectURL, value.longSession); - } -} From 5e8bbcc8fd085df4b255f9c782fe95f7cd2ea5ed Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 08:00:14 +0100 Subject: [PATCH 43/62] Adapted code to new session-token cookie config --- packages/web-core/src/services/SessionService.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 764c375c3..741e34c45 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -14,7 +14,7 @@ import { BehaviorSubject } from 'rxjs'; import { Err, Ok, Result } from 'ts-results'; import { Configuration } from '../api/v1'; -import type { SessionConfigRsp, ShortSessionCookieConfig } from '../api/v2'; +import type { SessionConfigRsp, SessionTokenCookieConfig } from '../api/v2'; import { ConfigsApi, UsersApi } from '../api/v2'; import { SessionToken } from '../models/sessionToken'; import { @@ -378,14 +378,14 @@ export class SessionService { document.cookie = this.#getDeleteSessionTokenCookieString(cookieConfig); } - #getSessionTokenCookieString(config: ShortSessionCookieConfig, value: SessionToken): string { + #getSessionTokenCookieString(config: SessionTokenCookieConfig, value: SessionToken): string { const expires = new Date(Date.now() + config.lifetimeSeconds * 1000).toUTCString(); return `${sessionTokenKey}=${value}; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ config.sameSite }; path=${config.path}; expires=${expires}`; } - #getDeleteSessionTokenCookieString(config: ShortSessionCookieConfig) { + #getDeleteSessionTokenCookieString(config: SessionTokenCookieConfig) { return `${sessionTokenKey}=; domain=${config.domain}; ${config.secure ? 'secure; ' : ''}sameSite=${ config.sameSite }; path=${config.path}; expires=${new Date().toUTCString()}`; @@ -499,8 +499,8 @@ export class SessionService { return this.#sessionConfig; }; - #getSessionTokenCookieConfig = (): ShortSessionCookieConfig => { - const cfg = this.#getSessionConfig().shortSessionCookieConfig; + #getSessionTokenCookieConfig = (): SessionTokenCookieConfig => { + const cfg = this.#getSessionConfig().sessionTokenCookieConfig; if (!cfg) { throw CorbadoError.invalidConfig(); } From 59ad73a1926263380e6a1ea2ab44d837ce8d851b Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:14:11 +0100 Subject: [PATCH 44/62] Added refresh-token documentation --- packages/web-core/src/services/SessionService.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 741e34c45..b72a17fc5 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -55,6 +55,18 @@ export class SessionService { #sessionConfig: SessionConfigRsp | undefined; #sessionToken: SessionToken | undefined; + + /** + * The refresh-token is returned in the response payload from the Frontend API if the project + * environment is set to 'dev' and set in this property. If the project environment is not set + * to 'dev' the refresh-token will be set as a cookie from the Frontend API (not part of the + * response payload and you can not access it from JavaScript because it is a HTTP only cookie). + * This is needed because Safari does not allow third-party cookies and for local development + * you will most likely run your project on a different origin than the Frontend API + * (e.g. http://localhost:3000 vs. https://pro-xxx.cloud.frontendapi.corbado.io). On production, + * this is "fixed" by setting a CNAME on the Frontend API to align the origins (e.g. + * https://www.example.com and https://auth.example.com). + */ #refreshToken: string | undefined; #refreshIntervalId: NodeJS.Timeout | undefined; From 65072d93a430979402a06cbab43e5cbacbf0f006 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:15:28 +0100 Subject: [PATCH 45/62] Morerefresh-token documentation --- packages/web-core/src/services/SessionService.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index b72a17fc5..88b213744 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -353,8 +353,7 @@ export class SessionService { } /** - * Deletes the refresh-token. - * @todo Explain above (at property?) why this is needed. (Safari, Third-party cookies etc.) + * Deletes the refresh-token (see property for more details why this exists). */ #deleteRefreshToken(): void { localStorage.removeItem(refreshTokenKey); @@ -362,8 +361,7 @@ export class SessionService { } /** - * Gets the refresh-token. - * @todo Explain above (at property?) why this is needed. (Safari, Third-party cookies etc.) + * Gets the refresh-token (see property for more details why this exists). */ static #getRefreshToken() { return (localStorage.getItem(refreshTokenKey) as string) ?? ''; @@ -404,7 +402,7 @@ export class SessionService { } /** - * Sets the refresh-token. + * Sets the refresh-token (see property for more details why this exists). */ #setRefreshToken(refreshToken: string | undefined): void { if (!refreshToken) { From e777bb4c0154d431f54714b101c643e6f45eeeaf Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 12:04:57 +0100 Subject: [PATCH 46/62] Cleaning up FAPI URL configuration --- packages/types/src/common.ts | 4 +--- packages/web-core/src/services/SessionService.ts | 8 ++++---- packages/web-core/src/services/index.ts | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index 52c52795b..c09ba0779 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -6,10 +6,8 @@ export interface Paging { export interface CorbadoAppParams { projectId: string; + frontendApi: string; apiTimeout?: number; - // deprecated (no longer needed, Corbado backend sets this value automatically) - frontendApiUrl?: string; - frontendApiUrlSuffix?: string; isDevMode?: boolean; isPreviewMode?: boolean; } diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 88b213744..0151f3970 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -51,7 +51,7 @@ export class SessionService { readonly #isPreviewMode: boolean; readonly #projectId: string; - readonly #frontendApiUrlSuffix: string; + readonly #frontendApi: string; #sessionConfig: SessionConfigRsp | undefined; #sessionToken: SessionToken | undefined; @@ -74,9 +74,9 @@ export class SessionService { #sessionTokenChanges: BehaviorSubject = new BehaviorSubject(undefined); #authStateChanges: BehaviorSubject = new BehaviorSubject(AuthState.LoggedOut); - constructor(projectId: string, isPreviewMode: boolean, frontendApiUrlSuffix: string) { + constructor(projectId: string, frontendApi: string, isPreviewMode: boolean) { this.#projectId = projectId; - this.#frontendApiUrlSuffix = frontendApiUrlSuffix; + this.#frontendApi = frontendApi; this.#webAuthnService = new WebAuthnService(); this.#refreshToken = undefined; this.#isPreviewMode = isPreviewMode; @@ -533,7 +533,7 @@ export class SessionService { }; #getDefaultFrontendApiUrl() { - return `https://${this.#projectId}.${this.#frontendApiUrlSuffix}`; + return `https://${this.#projectId}.${this.#frontendApi}`; } async wrapWithErr(callback: () => Promise>): Promise> { diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 2cb52c102..267fcecad 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -26,16 +26,16 @@ export class CorbadoApp { constructor(corbadoParams: CorbadoAppParams) { const { projectId, + frontendApi, apiTimeout = defaultTimeout, - frontendApiUrlSuffix = 'frontendapi.corbado.io', isPreviewMode = false, } = corbadoParams; this.#projectId = projectId; - this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApiUrlSuffix); + this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApi); this.#sessionService = new SessionService( this.#projectId, - isPreviewMode, - frontendApiUrlSuffix, + frontendApi, + isPreviewMode ); } From d00c6eafe5672b04d10bb1b8a6c572c785f7a6d5 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Sat, 1 Feb 2025 14:03:37 +0100 Subject: [PATCH 47/62] Cleaning up FAPI URL configuration --- .../web-core/src/services/ProcessService.ts | 24 +++----- .../web-core/src/services/SessionService.ts | 17 ++---- packages/web-core/src/services/index.ts | 55 ++++++++++++++++++- playground/web-js/.env.local | 2 +- playground/web-js/.env.vercel | 2 +- playground/web-js/src/scripts/environment.js | 2 +- playground/web-js/src/scripts/index.js | 4 +- 7 files changed, 70 insertions(+), 36 deletions(-) diff --git a/packages/web-core/src/services/ProcessService.ts b/packages/web-core/src/services/ProcessService.ts index 018df806f..eabe89f90 100644 --- a/packages/web-core/src/services/ProcessService.ts +++ b/packages/web-core/src/services/ProcessService.ts @@ -48,16 +48,15 @@ export class ProcessService { #authApi: AuthApi = new AuthApi(); #webAuthnService: WebAuthnService; - // Private fields for project ID and default timeout for API calls. - #projectId: string; - #timeout: number; + readonly #projectId: string; + readonly #frontendApi: string; + readonly #timeout: number; readonly #isPreviewMode: boolean; - readonly #frontendApiUrlSuffix: string; - constructor(projectId: string, timeout: number = 30 * 1000, isPreviewMode: boolean, frontendApiUrlSuffix: string) { + constructor(projectId: string, frontendApi: string, timeout: number = 30 * 1000, isPreviewMode: boolean) { this.#projectId = projectId; + this.#frontendApi = frontendApi; this.#timeout = timeout; - this.#frontendApiUrlSuffix = frontendApiUrlSuffix; this.#webAuthnService = new WebAuthnService(); this.#isPreviewMode = isPreviewMode; @@ -209,18 +208,13 @@ export class ProcessService { } #setApisV2(process?: AuthProcess): void { - let frontendApiUrl = this.#getDefaultFrontendApiUrl(); - if (process?.frontendApiUrl && process?.frontendApiUrl.length > 0) { - frontendApiUrl = process.frontendApiUrl; - } - const config = new Configuration({ apiKey: this.#projectId, - basePath: frontendApiUrl, + basePath: this.#frontendApi, }); const axiosInstance = this.#createAxiosInstanceV2(process?.id ?? ''); - this.#authApi = new AuthApi(config, frontendApiUrl, axiosInstance); + this.#authApi = new AuthApi(config, this.#frontendApi, axiosInstance); } async #initAuthProcess( @@ -618,10 +612,6 @@ export class ProcessService { this.#webAuthnService.abortOngoingOperation(); } - #getDefaultFrontendApiUrl() { - return `https://${this.#projectId}.${this.#frontendApiUrlSuffix}`; - } - #buildCorbadoFlags = (): string => { const flags: string[] = []; if (this.#isPreviewMode) { diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 0151f3970..4a8d5a3b5 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -52,8 +52,8 @@ export class SessionService { readonly #isPreviewMode: boolean; readonly #projectId: string; readonly #frontendApi: string; - #sessionConfig: SessionConfigRsp | undefined; + #sessionConfig: SessionConfigRsp | undefined; #sessionToken: SessionToken | undefined; /** @@ -259,18 +259,13 @@ export class SessionService { } #setApisV2(longSession: string): void { - let frontendApiUrl = this.#getSessionConfig().frontendApiUrl; - if (!frontendApiUrl || frontendApiUrl.length === 0) { - frontendApiUrl = this.#getDefaultFrontendApiUrl(); - } - const config = new Configuration({ apiKey: this.#projectId, - basePath: frontendApiUrl, + basePath: this.#frontendApi, }); const axiosInstance = this.#createAxiosInstanceV2(longSession); - this.#usersApi = new UsersApi(config, frontendApiUrl, axiosInstance); + this.#usersApi = new UsersApi(config, this.#frontendApi, axiosInstance); } // usually sessionService needs a longSession for all it's requests @@ -524,7 +519,7 @@ export class SessionService { }); const axiosInstance = this.#createAxiosInstanceV2(); - const configsApi = new ConfigsApi(config, this.#getDefaultFrontendApiUrl(), axiosInstance); + const configsApi = new ConfigsApi(config, this.#frontendApi, axiosInstance); return Result.wrapAsync(async () => { const r = await configsApi.getSessionConfig(); @@ -532,10 +527,6 @@ export class SessionService { }); }; - #getDefaultFrontendApiUrl() { - return `https://${this.#projectId}.${this.#frontendApi}`; - } - async wrapWithErr(callback: () => Promise>): Promise> { try { const r = await callback(); diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 267fcecad..701336ee6 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -19,6 +19,7 @@ export class CorbadoApp { #authProcessService: ProcessService; #sessionService: SessionService; #projectId: string; + #frontendApi: string; /** * The constructor initializes the services and sets up the application. @@ -30,8 +31,10 @@ export class CorbadoApp { apiTimeout = defaultTimeout, isPreviewMode = false, } = corbadoParams; + this.#projectId = projectId; - this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApi); + this.#frontendApi = frontendApi; + this.#authProcessService = new ProcessService(this.#projectId, frontendApi, apiTimeout, isPreviewMode); this.#sessionService = new SessionService( this.#projectId, frontendApi, @@ -56,6 +59,11 @@ export class CorbadoApp { return Err(new NonRecoverableError('Invalid project ID')); } + const validationError = this.#validateFrontendApi(this.#frontendApi); + if (validationError !== '') { + return Err(new NonRecoverableError(validationError)); + } + await this.#sessionService.init(); return Ok(void 0); @@ -69,4 +77,49 @@ export class CorbadoApp { #validateProjectId(projectId: string): boolean { return /^pro-\d+$/.test(projectId); } + + #validateFrontendApi(frontendApi: string): string { + if (!frontendApi || frontendApi.trim() === "") { + return 'String must not be empty'; + } + + let url: URL; + try { + url = new URL(frontendApi); + } catch (err: any) { + return `Failed to parse URL: ${err.message}`; + } + + if (url.protocol !== "https:") { + const protocol = url.protocol.replace(":", ""); + + return `Scheme needs to be 'https' in given value '${frontendApi}' (scheme: '${protocol}')`; + } + + if (!url.hostname) { + return `Host must not be empty in given value '${frontendApi}'`; + } + + if (url.username) { + return `Username must be empty in given value '${frontendApi}' (username: '${url.username}')`; + } + + if (url.password) { + return `Password must be empty in given value '${frontendApi}' (password: '${url.password}')`; + } + + if (url.pathname !== "") { + return `Path must be empty in given value '${frontendApi}' (path: '${url.pathname}')`; + } + + if (url.hash !== "") { + return `Fragment must be empty in given value '${frontendApi}' (fragment: '${url.hash}')`; + } + + if (url.search !== "") { + return `Querystring must be empty in given value '${frontendApi}' (querystring: '${url.search}')`; + } + + return ''; + } } diff --git a/playground/web-js/.env.local b/playground/web-js/.env.local index 13935306d..ba90cfb15 100644 --- a/playground/web-js/.env.local +++ b/playground/web-js/.env.local @@ -1,2 +1,2 @@ CORBADO_PROJECT_ID=pro-7566555492758112393 -CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +CORBADO_FRONTEND_API=https://pro-7566555492758112393.frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js/.env.vercel b/playground/web-js/.env.vercel index 140bd45f1..0c011692f 100644 --- a/playground/web-js/.env.vercel +++ b/playground/web-js/.env.vercel @@ -1,2 +1,2 @@ CORBADO_PROJECT_ID=pro-2979442087152677678 -CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +CORBADO_FRONTEND_API=https://pro-2979442087152677678.frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js/src/scripts/environment.js b/playground/web-js/src/scripts/environment.js index 8a96e3c60..c8d3a4f15 100644 --- a/playground/web-js/src/scripts/environment.js +++ b/playground/web-js/src/scripts/environment.js @@ -1,2 +1,2 @@ export const CORBADO_PROJECT_ID = process.env.CORBADO_PROJECT_ID; -export const CORBADO_FRONTEND_API_URL_SUFFIX = process.env.CORBADO_FRONTEND_API_URL_SUFFIX; +export const CORBADO_FRONTEND_API = process.env.CORBADO_FRONTEND_API; diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index 186d64627..6f6229d6c 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -1,5 +1,5 @@ import Corbado from '@corbado/web-js'; -import { CORBADO_PROJECT_ID, CORBADO_FRONTEND_API_URL_SUFFIX } from './environment'; +import { CORBADO_PROJECT_ID, CORBADO_FRONTEND_API } from './environment'; let loaded = false; @@ -13,7 +13,7 @@ async function loadPage() { loaded = true; await Corbado.load({ projectId: projectId, - frontendApiUrlSuffix: CORBADO_FRONTEND_API_URL_SUFFIX, + frontendApi: CORBADO_FRONTEND_API, darkMode: 'auto', }); } From 12f4cc8a38ee88a48c98c8b99bfe6a9bd37ac205 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:31:36 +0100 Subject: [PATCH 48/62] Fixed trailing slash validation of FAPI url --- packages/web-core/src/services/index.ts | 20 ++++++++++++++------ playground/web-js-script/index.html | 3 +-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 701336ee6..e14af8192 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -61,6 +61,8 @@ export class CorbadoApp { const validationError = this.#validateFrontendApi(this.#frontendApi); if (validationError !== '') { + // @todo This exception is not shown in browser console + console.log(validationError); return Err(new NonRecoverableError(validationError)); } @@ -79,7 +81,7 @@ export class CorbadoApp { } #validateFrontendApi(frontendApi: string): string { - if (!frontendApi || frontendApi.trim() === "") { + if (!frontendApi || frontendApi.trim() === '') { return 'String must not be empty'; } @@ -90,8 +92,8 @@ export class CorbadoApp { return `Failed to parse URL: ${err.message}`; } - if (url.protocol !== "https:") { - const protocol = url.protocol.replace(":", ""); + if (url.protocol !== 'https:') { + const protocol = url.protocol.replace(':', ''); return `Scheme needs to be 'https' in given value '${frontendApi}' (scheme: '${protocol}')`; } @@ -108,15 +110,21 @@ export class CorbadoApp { return `Password must be empty in given value '${frontendApi}' (password: '${url.password}')`; } - if (url.pathname !== "") { + // We need to check for the trailing slash manually because URL class adds one by default if is + // not there (see next pathname validation). + if (frontendApi[frontendApi.length - 1] === '/') { + return `Trailing slash is not allowed in given value '${frontendApi}'`; + } + + if (url.pathname !== '' && url.pathname !== '/') { return `Path must be empty in given value '${frontendApi}' (path: '${url.pathname}')`; } - if (url.hash !== "") { + if (url.hash !== '') { return `Fragment must be empty in given value '${frontendApi}' (fragment: '${url.hash}')`; } - if (url.search !== "") { + if (url.search !== '') { return `Querystring must be empty in given value '${frontendApi}' (querystring: '${url.search}')`; } diff --git a/playground/web-js-script/index.html b/playground/web-js-script/index.html index 1ef0d961c..eeed76168 100644 --- a/playground/web-js-script/index.html +++ b/playground/web-js-script/index.html @@ -26,9 +26,8 @@ loaded = true; await Corbado.load({ projectId: projectId, - frontendApiUrlSuffix: 'frontendapi.cloud.corbado-staging.io', + frontendApi: `https://${projectId}.frontendapi.cloud.corbado-staging.io`, darkMode: 'auto', - setShortSessionCookie: true, }); } From a140184aadb7e47b45c70eb5130996ae91737732 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Mon, 3 Feb 2025 08:06:37 +0100 Subject: [PATCH 49/62] Error handling debugging --- packages/web-core/src/services/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index e14af8192..3cee39cab 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -56,14 +56,16 @@ export class CorbadoApp { */ async init(): Promise> { if (!this.#validateProjectId(this.#projectId)) { - return Err(new NonRecoverableError('Invalid project ID')); + throw new Error(`Invalid project ID '${this.#projectId}'`); + // @todo Fix this + //return Err(new NonRecoverableError(`Invalid project ID '${this.#projectId}'`)); } const validationError = this.#validateFrontendApi(this.#frontendApi); if (validationError !== '') { - // @todo This exception is not shown in browser console - console.log(validationError); - return Err(new NonRecoverableError(validationError)); + throw new Error(validationError); + // @todo Fix this + //return Err(new NonRecoverableError(validationError)); } await this.#sessionService.init(); From b7a3c9ecf5ac5e7560e4393f208750035f8d3827 Mon Sep 17 00:00:00 2001 From: Lukas Kratzel Date: Thu, 6 Feb 2025 11:47:48 +0100 Subject: [PATCH 50/62] fix react playground --- playground/react/.env | 2 +- playground/react/src/hoc/withCorbadoProvider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/react/.env b/playground/react/.env index 246ae453b..389dda6c1 100644 --- a/playground/react/.env +++ b/playground/react/.env @@ -1,6 +1,6 @@ REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-3652881945085154854 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-2 -REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +REACT_APP_CORBADO_FRONTEND_API_URL=https://frontendapi.cloud.corbado-staging.io # REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado-dev.io # REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado.io diff --git a/playground/react/src/hoc/withCorbadoProvider.tsx b/playground/react/src/hoc/withCorbadoProvider.tsx index b91d092e3..fa0bbb7c4 100644 --- a/playground/react/src/hoc/withCorbadoProvider.tsx +++ b/playground/react/src/hoc/withCorbadoProvider.tsx @@ -18,7 +18,7 @@ function withCorbadoProvider(WrappedComponent }} darkMode={darkMode ? 'on' : 'off'} isDevMode={true} - frontendApiUrlSuffix={process.env.REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX} + frontendApi={process.env.REACT_APP_CORBADO_FRONTEND_API_URL!} >
From d9c0223db2850e30b51dfdecfb40cbb63ff659bd Mon Sep 17 00:00:00 2001 From: Lukas Kratzel Date: Thu, 6 Feb 2025 11:51:47 +0100 Subject: [PATCH 51/62] fmt --- packages/web-core/src/services/index.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 3cee39cab..1b7f8163b 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -1,9 +1,9 @@ import type { CorbadoAppParams } from '@corbado/types'; import type { Result } from 'ts-results'; -import { Err, Ok } from 'ts-results'; +import { Ok } from 'ts-results'; import type { CorbadoError } from '../utils'; -import { defaultTimeout, NonRecoverableError } from '../utils'; +import { defaultTimeout } from '../utils'; import { ProcessService } from './ProcessService'; import { SessionService } from './SessionService'; @@ -25,21 +25,12 @@ export class CorbadoApp { * The constructor initializes the services and sets up the application. */ constructor(corbadoParams: CorbadoAppParams) { - const { - projectId, - frontendApi, - apiTimeout = defaultTimeout, - isPreviewMode = false, - } = corbadoParams; + const { projectId, frontendApi, apiTimeout = defaultTimeout, isPreviewMode = false } = corbadoParams; this.#projectId = projectId; this.#frontendApi = frontendApi; this.#authProcessService = new ProcessService(this.#projectId, frontendApi, apiTimeout, isPreviewMode); - this.#sessionService = new SessionService( - this.#projectId, - frontendApi, - isPreviewMode - ); + this.#sessionService = new SessionService(this.#projectId, frontendApi, isPreviewMode); } get authProcessService() { @@ -114,7 +105,7 @@ export class CorbadoApp { // We need to check for the trailing slash manually because URL class adds one by default if is // not there (see next pathname validation). - if (frontendApi[frontendApi.length - 1] === '/') { + if (frontendApi[frontendApi.length - 1] === '/') { return `Trailing slash is not allowed in given value '${frontendApi}'`; } From 2b7c6a20b8b0225af8f3e1cdb14f8f2a72cb7357 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:33:48 +0100 Subject: [PATCH 52/62] PR feedback --- packages/web-core/src/services/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 1b7f8163b..c52a4f392 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -46,17 +46,15 @@ export class CorbadoApp { * It fetches the project configuration and initializes the services. */ async init(): Promise> { + // This can be improved by using the Err() type. Then we need to decide how to present the + // error (print it to the browser console, render it in the component, do both etc.) if (!this.#validateProjectId(this.#projectId)) { throw new Error(`Invalid project ID '${this.#projectId}'`); - // @todo Fix this - //return Err(new NonRecoverableError(`Invalid project ID '${this.#projectId}'`)); } const validationError = this.#validateFrontendApi(this.#frontendApi); if (validationError !== '') { throw new Error(validationError); - // @todo Fix this - //return Err(new NonRecoverableError(validationError)); } await this.#sessionService.init(); From a19940f647676f5329403b3b16246789d7c5801b Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:47:07 +0100 Subject: [PATCH 53/62] Some more longSession => refreshToken renames --- packages/web-core/src/services/SessionService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 4a8d5a3b5..154c2961c 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -258,19 +258,19 @@ export class SessionService { this.#setRefreshToken(refreshToken); } - #setApisV2(longSession: string): void { + #setApisV2(refreshToken: string): void { const config = new Configuration({ apiKey: this.#projectId, basePath: this.#frontendApi, }); - const axiosInstance = this.#createAxiosInstanceV2(longSession); + const axiosInstance = this.#createAxiosInstanceV2(refreshToken); this.#usersApi = new UsersApi(config, this.#frontendApi, axiosInstance); } - // usually sessionService needs a longSession for all it's requests + // usually sessionService needs a refresh-token for all it's requests // just the initial request to fetch the sessionConfig doesn't need it - #createAxiosInstanceV2(longSession?: string): AxiosInstance { + #createAxiosInstanceV2(refreshToken?: string): AxiosInstance { const corbadoVersion = { name: 'web-core', sdkVersion: packageVersion, @@ -288,10 +288,10 @@ export class SessionService { headers['X-Corbado-Flags'] = this.#buildCorbadoFlags(); let axiosInstance: AxiosInstance; - if (longSession) { + if (refreshToken) { axiosInstance = axios.create({ withCredentials: true, - headers: { ...headers, Authorization: `Bearer ${longSession}` }, + headers: { ...headers, Authorization: `Bearer ${refreshToken}` }, }); } else { axiosInstance = axios.create({ From 5e6d25cf174bfc1fb3792450997c8d8cb77f2c66 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:05:57 +0100 Subject: [PATCH 54/62] Made frontendApi prop optional --- packages/types/src/common.ts | 2 +- packages/web-core/src/services/ProcessService.ts | 14 +++++++++++++- packages/web-core/src/services/SessionService.ts | 15 ++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index c09ba0779..8f6f278f5 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -6,7 +6,7 @@ export interface Paging { export interface CorbadoAppParams { projectId: string; - frontendApi: string; + frontendApi?: string; apiTimeout?: number; isDevMode?: boolean; isPreviewMode?: boolean; diff --git a/packages/web-core/src/services/ProcessService.ts b/packages/web-core/src/services/ProcessService.ts index eabe89f90..c4b68a6eb 100644 --- a/packages/web-core/src/services/ProcessService.ts +++ b/packages/web-core/src/services/ProcessService.ts @@ -210,13 +210,25 @@ export class ProcessService { #setApisV2(process?: AuthProcess): void { const config = new Configuration({ apiKey: this.#projectId, - basePath: this.#frontendApi, + basePath: this.#getBasePath(process), }); const axiosInstance = this.#createAxiosInstanceV2(process?.id ?? ''); this.#authApi = new AuthApi(config, this.#frontendApi, axiosInstance); } + #getBasePath(process?: AuthProcess): string { + if (this.#frontendApi && this.#frontendApi.length > 0) { + return this.#frontendApi; + } + + if (process?.frontendApiUrl && process?.frontendApiUrl.length > 0) { + return process.frontendApiUrl + } + + return `https://${this.#projectId}.frontendapi.cloud.corbado.io`; + } + async #initAuthProcess( abortController: AbortController, frontendPreferredBlockType?: BlockType, diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index 154c2961c..de8935043 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -261,13 +261,26 @@ export class SessionService { #setApisV2(refreshToken: string): void { const config = new Configuration({ apiKey: this.#projectId, - basePath: this.#frontendApi, + basePath: this.#getBasePath(), }); const axiosInstance = this.#createAxiosInstanceV2(refreshToken); this.#usersApi = new UsersApi(config, this.#frontendApi, axiosInstance); } + #getBasePath(): string { + if (this.#frontendApi && this.#frontendApi.length > 0) { + return this.#frontendApi; + } + + const frontendApiUrl = this.#getSessionConfig().frontendApiUrl; + if (frontendApiUrl && frontendApiUrl.length > 0) { + return frontendApiUrl + } + + return `https://${this.#projectId}.frontendapi.cloud.corbado.io`; + } + // usually sessionService needs a refresh-token for all it's requests // just the initial request to fetch the sessionConfig doesn't need it #createAxiosInstanceV2(refreshToken?: string): AxiosInstance { From d97b9ffcd3233ad610b61fa6f82b3659b69b48c8 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:14:23 +0100 Subject: [PATCH 55/62] Bugfixing --- packages/web-core/src/services/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index c52a4f392..523b08dc6 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -25,7 +25,7 @@ export class CorbadoApp { * The constructor initializes the services and sets up the application. */ constructor(corbadoParams: CorbadoAppParams) { - const { projectId, frontendApi, apiTimeout = defaultTimeout, isPreviewMode = false } = corbadoParams; + const { projectId, frontendApi = '', apiTimeout = defaultTimeout, isPreviewMode = false } = corbadoParams; this.#projectId = projectId; this.#frontendApi = frontendApi; From 1ba05851523ecf7d07efd8baa1ec999f3ef0a10b Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:16:19 +0100 Subject: [PATCH 56/62] Small improvement --- packages/web-core/src/services/ProcessService.ts | 2 +- packages/web-core/src/services/SessionService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web-core/src/services/ProcessService.ts b/packages/web-core/src/services/ProcessService.ts index c4b68a6eb..559054d4c 100644 --- a/packages/web-core/src/services/ProcessService.ts +++ b/packages/web-core/src/services/ProcessService.ts @@ -218,7 +218,7 @@ export class ProcessService { } #getBasePath(process?: AuthProcess): string { - if (this.#frontendApi && this.#frontendApi.length > 0) { + if (this.#frontendApi.length > 0) { return this.#frontendApi; } diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index de8935043..a4b881ba3 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -269,7 +269,7 @@ export class SessionService { } #getBasePath(): string { - if (this.#frontendApi && this.#frontendApi.length > 0) { + if (this.#frontendApi.length > 0) { return this.#frontendApi; } From dfd9bb01f21682792b157f5bda39be28b6a60617 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:34:35 +0100 Subject: [PATCH 57/62] Fixed environment variables --- playground/react/.env | 6 +++--- playground/react/.env.production | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/playground/react/.env b/playground/react/.env index 389dda6c1..27948fb3c 100644 --- a/playground/react/.env +++ b/playground/react/.env @@ -1,6 +1,6 @@ REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-3652881945085154854 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-2 -REACT_APP_CORBADO_FRONTEND_API_URL=https://frontendapi.cloud.corbado-staging.io -# REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado-dev.io -# REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado.io +REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado-staging.io +# REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado-dev.io +# REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado.io diff --git a/playground/react/.env.production b/playground/react/.env.production index 55e59365a..af63ffa29 100644 --- a/playground/react/.env.production +++ b/playground/react/.env.production @@ -1,3 +1,4 @@ +// @todo Need to adapt this REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-2979442087152677678 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-7566555492758112393 -REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +REACT_APP_CORBADO_FRONTEND_API_URL=frontendapi.cloud.corbado-staging.io From 1002065e13e456b9cc418e4a946e4f98e3043c33 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:40:42 +0100 Subject: [PATCH 58/62] Changed it back to frontendApiUrlSuffix --- packages/types/src/common.ts | 2 +- .../web-core/src/services/ProcessService.ts | 33 +++++----- .../web-core/src/services/SessionService.ts | 39 ++++++----- packages/web-core/src/services/index.ts | 64 +------------------ playground/react/.env | 6 +- playground/react/.env.production | 1 - playground/web-js-script/index.html | 2 +- playground/web-js/.env.local | 2 +- playground/web-js/.env.vercel | 2 +- playground/web-js/src/scripts/environment.js | 2 +- playground/web-js/src/scripts/index.js | 4 +- 11 files changed, 46 insertions(+), 111 deletions(-) diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index 8f6f278f5..9b031e4f9 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -6,8 +6,8 @@ export interface Paging { export interface CorbadoAppParams { projectId: string; - frontendApi?: string; apiTimeout?: number; + frontendApiUrlSuffix?: string; isDevMode?: boolean; isPreviewMode?: boolean; } diff --git a/packages/web-core/src/services/ProcessService.ts b/packages/web-core/src/services/ProcessService.ts index 559054d4c..69eaea74f 100644 --- a/packages/web-core/src/services/ProcessService.ts +++ b/packages/web-core/src/services/ProcessService.ts @@ -49,16 +49,16 @@ export class ProcessService { #webAuthnService: WebAuthnService; readonly #projectId: string; - readonly #frontendApi: string; readonly #timeout: number; readonly #isPreviewMode: boolean; + readonly #frontendApiUrlSuffix: string; - constructor(projectId: string, frontendApi: string, timeout: number = 30 * 1000, isPreviewMode: boolean) { + constructor(projectId: string, timeout: number = 30 * 1000, isPreviewMode: boolean, frontendApiUrlSuffix: string) { this.#projectId = projectId; - this.#frontendApi = frontendApi; this.#timeout = timeout; - this.#webAuthnService = new WebAuthnService(); this.#isPreviewMode = isPreviewMode; + this.#frontendApiUrlSuffix = frontendApiUrlSuffix; + this.#webAuthnService = new WebAuthnService(); // Initializes the API instances with no authentication token. // Authentication tokens are set in the SessionService. @@ -208,25 +208,18 @@ export class ProcessService { } #setApisV2(process?: AuthProcess): void { + let frontendApiUrl = this.#getDefaultFrontendApiUrl(); + if (process?.frontendApiUrl && process?.frontendApiUrl.length > 0) { + frontendApiUrl = process.frontendApiUrl; + } + const config = new Configuration({ apiKey: this.#projectId, - basePath: this.#getBasePath(process), + basePath: frontendApiUrl, }); const axiosInstance = this.#createAxiosInstanceV2(process?.id ?? ''); - this.#authApi = new AuthApi(config, this.#frontendApi, axiosInstance); - } - - #getBasePath(process?: AuthProcess): string { - if (this.#frontendApi.length > 0) { - return this.#frontendApi; - } - - if (process?.frontendApiUrl && process?.frontendApiUrl.length > 0) { - return process.frontendApiUrl - } - - return `https://${this.#projectId}.frontendapi.cloud.corbado.io`; + this.#authApi = new AuthApi(config, frontendApiUrl, axiosInstance); } async #initAuthProcess( @@ -624,6 +617,10 @@ export class ProcessService { this.#webAuthnService.abortOngoingOperation(); } + #getDefaultFrontendApiUrl() { + return `https://${this.#projectId}.${this.#frontendApiUrlSuffix}`; + } + #buildCorbadoFlags = (): string => { const flags: string[] = []; if (this.#isPreviewMode) { diff --git a/packages/web-core/src/services/SessionService.ts b/packages/web-core/src/services/SessionService.ts index a4b881ba3..6a6bc90fe 100644 --- a/packages/web-core/src/services/SessionService.ts +++ b/packages/web-core/src/services/SessionService.ts @@ -49,9 +49,9 @@ export class SessionService { #usersApi: UsersApi = new UsersApi(); #webAuthnService: WebAuthnService; - readonly #isPreviewMode: boolean; readonly #projectId: string; - readonly #frontendApi: string; + readonly #isPreviewMode: boolean; + readonly #frontendApiUrlSuffix: string; #sessionConfig: SessionConfigRsp | undefined; #sessionToken: SessionToken | undefined; @@ -74,12 +74,13 @@ export class SessionService { #sessionTokenChanges: BehaviorSubject = new BehaviorSubject(undefined); #authStateChanges: BehaviorSubject = new BehaviorSubject(AuthState.LoggedOut); - constructor(projectId: string, frontendApi: string, isPreviewMode: boolean) { + constructor(projectId: string, isPreviewMode: boolean, frontendApiUrlSuffix: string) { this.#projectId = projectId; - this.#frontendApi = frontendApi; + this.#isPreviewMode = isPreviewMode; + this.#frontendApiUrlSuffix = frontendApiUrlSuffix; + this.#webAuthnService = new WebAuthnService(); this.#refreshToken = undefined; - this.#isPreviewMode = isPreviewMode; } /** @@ -259,26 +260,18 @@ export class SessionService { } #setApisV2(refreshToken: string): void { + let frontendApiUrl = this.#getSessionConfig().frontendApiUrl; + if (!frontendApiUrl || frontendApiUrl.length === 0) { + frontendApiUrl = this.#getDefaultFrontendApiUrl(); + } + const config = new Configuration({ apiKey: this.#projectId, - basePath: this.#getBasePath(), + basePath: frontendApiUrl, }); const axiosInstance = this.#createAxiosInstanceV2(refreshToken); - this.#usersApi = new UsersApi(config, this.#frontendApi, axiosInstance); - } - - #getBasePath(): string { - if (this.#frontendApi.length > 0) { - return this.#frontendApi; - } - - const frontendApiUrl = this.#getSessionConfig().frontendApiUrl; - if (frontendApiUrl && frontendApiUrl.length > 0) { - return frontendApiUrl - } - - return `https://${this.#projectId}.frontendapi.cloud.corbado.io`; + this.#usersApi = new UsersApi(config, frontendApiUrl, axiosInstance); } // usually sessionService needs a refresh-token for all it's requests @@ -532,7 +525,7 @@ export class SessionService { }); const axiosInstance = this.#createAxiosInstanceV2(); - const configsApi = new ConfigsApi(config, this.#frontendApi, axiosInstance); + const configsApi = new ConfigsApi(config, this.#getDefaultFrontendApiUrl(), axiosInstance); return Result.wrapAsync(async () => { const r = await configsApi.getSessionConfig(); @@ -540,6 +533,10 @@ export class SessionService { }); }; + #getDefaultFrontendApiUrl() { + return `https://${this.#projectId}.${this.#frontendApiUrlSuffix}`; + } + async wrapWithErr(callback: () => Promise>): Promise> { try { const r = await callback(); diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 523b08dc6..be7b88224 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -19,18 +19,16 @@ export class CorbadoApp { #authProcessService: ProcessService; #sessionService: SessionService; #projectId: string; - #frontendApi: string; /** * The constructor initializes the services and sets up the application. */ constructor(corbadoParams: CorbadoAppParams) { - const { projectId, frontendApi = '', apiTimeout = defaultTimeout, isPreviewMode = false } = corbadoParams; + const { projectId, apiTimeout = defaultTimeout, frontendApiUrlSuffix = 'frontendapi.cloud.corbado.io', isPreviewMode = false } = corbadoParams; this.#projectId = projectId; - this.#frontendApi = frontendApi; - this.#authProcessService = new ProcessService(this.#projectId, frontendApi, apiTimeout, isPreviewMode); - this.#sessionService = new SessionService(this.#projectId, frontendApi, isPreviewMode); + this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApiUrlSuffix); + this.#sessionService = new SessionService(this.#projectId, isPreviewMode, frontendApiUrlSuffix); } get authProcessService() { @@ -52,11 +50,6 @@ export class CorbadoApp { throw new Error(`Invalid project ID '${this.#projectId}'`); } - const validationError = this.#validateFrontendApi(this.#frontendApi); - if (validationError !== '') { - throw new Error(validationError); - } - await this.#sessionService.init(); return Ok(void 0); @@ -70,55 +63,4 @@ export class CorbadoApp { #validateProjectId(projectId: string): boolean { return /^pro-\d+$/.test(projectId); } - - #validateFrontendApi(frontendApi: string): string { - if (!frontendApi || frontendApi.trim() === '') { - return 'String must not be empty'; - } - - let url: URL; - try { - url = new URL(frontendApi); - } catch (err: any) { - return `Failed to parse URL: ${err.message}`; - } - - if (url.protocol !== 'https:') { - const protocol = url.protocol.replace(':', ''); - - return `Scheme needs to be 'https' in given value '${frontendApi}' (scheme: '${protocol}')`; - } - - if (!url.hostname) { - return `Host must not be empty in given value '${frontendApi}'`; - } - - if (url.username) { - return `Username must be empty in given value '${frontendApi}' (username: '${url.username}')`; - } - - if (url.password) { - return `Password must be empty in given value '${frontendApi}' (password: '${url.password}')`; - } - - // We need to check for the trailing slash manually because URL class adds one by default if is - // not there (see next pathname validation). - if (frontendApi[frontendApi.length - 1] === '/') { - return `Trailing slash is not allowed in given value '${frontendApi}'`; - } - - if (url.pathname !== '' && url.pathname !== '/') { - return `Path must be empty in given value '${frontendApi}' (path: '${url.pathname}')`; - } - - if (url.hash !== '') { - return `Fragment must be empty in given value '${frontendApi}' (fragment: '${url.hash}')`; - } - - if (url.search !== '') { - return `Querystring must be empty in given value '${frontendApi}' (querystring: '${url.search}')`; - } - - return ''; - } } diff --git a/playground/react/.env b/playground/react/.env index 27948fb3c..246ae453b 100644 --- a/playground/react/.env +++ b/playground/react/.env @@ -1,6 +1,6 @@ REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-3652881945085154854 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-2 -REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado-staging.io -# REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado-dev.io -# REACT_APP_CORBADO_FRONTEND_API_URL=https://pro-3652881945085154854.frontendapi.cloud.corbado.io +REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +# REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado-dev.io +# REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.corbado.io diff --git a/playground/react/.env.production b/playground/react/.env.production index af63ffa29..22719b0e2 100644 --- a/playground/react/.env.production +++ b/playground/react/.env.production @@ -1,4 +1,3 @@ -// @todo Need to adapt this REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-2979442087152677678 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-7566555492758112393 REACT_APP_CORBADO_FRONTEND_API_URL=frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js-script/index.html b/playground/web-js-script/index.html index eeed76168..51b9df75f 100644 --- a/playground/web-js-script/index.html +++ b/playground/web-js-script/index.html @@ -26,7 +26,7 @@ loaded = true; await Corbado.load({ projectId: projectId, - frontendApi: `https://${projectId}.frontendapi.cloud.corbado-staging.io`, + frontendApiUrlSuffix: 'frontendapi.cloud.corbado-staging.io', darkMode: 'auto', }); } diff --git a/playground/web-js/.env.local b/playground/web-js/.env.local index ba90cfb15..13935306d 100644 --- a/playground/web-js/.env.local +++ b/playground/web-js/.env.local @@ -1,2 +1,2 @@ CORBADO_PROJECT_ID=pro-7566555492758112393 -CORBADO_FRONTEND_API=https://pro-7566555492758112393.frontendapi.cloud.corbado-staging.io +CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js/.env.vercel b/playground/web-js/.env.vercel index 0c011692f..140bd45f1 100644 --- a/playground/web-js/.env.vercel +++ b/playground/web-js/.env.vercel @@ -1,2 +1,2 @@ CORBADO_PROJECT_ID=pro-2979442087152677678 -CORBADO_FRONTEND_API=https://pro-2979442087152677678.frontendapi.cloud.corbado-staging.io +CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/playground/web-js/src/scripts/environment.js b/playground/web-js/src/scripts/environment.js index c8d3a4f15..8a96e3c60 100644 --- a/playground/web-js/src/scripts/environment.js +++ b/playground/web-js/src/scripts/environment.js @@ -1,2 +1,2 @@ export const CORBADO_PROJECT_ID = process.env.CORBADO_PROJECT_ID; -export const CORBADO_FRONTEND_API = process.env.CORBADO_FRONTEND_API; +export const CORBADO_FRONTEND_API_URL_SUFFIX = process.env.CORBADO_FRONTEND_API_URL_SUFFIX; diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index 6f6229d6c..d7a71a8df 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -1,5 +1,5 @@ import Corbado from '@corbado/web-js'; -import { CORBADO_PROJECT_ID, CORBADO_FRONTEND_API } from './environment'; +import {CORBADO_PROJECT_ID, CORBADO_FRONTEND_API, CORBADO_FRONTEND_API_URL_SUFFIX} from './environment'; let loaded = false; @@ -13,7 +13,7 @@ async function loadPage() { loaded = true; await Corbado.load({ projectId: projectId, - frontendApi: CORBADO_FRONTEND_API, + frontendApiUrlSuffix: CORBADO_FRONTEND_API_URL_SUFFIX, darkMode: 'auto', }); } From fa2e7bfb557c10c07115509adecc463b979f559a Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 7 Feb 2025 07:12:03 +0100 Subject: [PATCH 59/62] More backporting --- playground/react/.env.production | 2 +- playground/react/src/hoc/withCorbadoProvider.tsx | 2 +- playground/web-js/src/scripts/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/playground/react/.env.production b/playground/react/.env.production index 22719b0e2..55e59365a 100644 --- a/playground/react/.env.production +++ b/playground/react/.env.production @@ -1,3 +1,3 @@ REACT_APP_CORBADO_PROJECT_ID_ManualTesting=pro-2979442087152677678 REACT_APP_CORBADO_PROJECT_ID_LocalDevelopment=pro-7566555492758112393 -REACT_APP_CORBADO_FRONTEND_API_URL=frontendapi.cloud.corbado-staging.io +REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/playground/react/src/hoc/withCorbadoProvider.tsx b/playground/react/src/hoc/withCorbadoProvider.tsx index fa0bbb7c4..20bd903e5 100644 --- a/playground/react/src/hoc/withCorbadoProvider.tsx +++ b/playground/react/src/hoc/withCorbadoProvider.tsx @@ -18,7 +18,7 @@ function withCorbadoProvider(WrappedComponent }} darkMode={darkMode ? 'on' : 'off'} isDevMode={true} - frontendApi={process.env.REACT_APP_CORBADO_FRONTEND_API_URL!} + frontendApiUrlSuffix={process.env.REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX!} > diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index d7a71a8df..a5fa450e7 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -1,5 +1,5 @@ import Corbado from '@corbado/web-js'; -import {CORBADO_PROJECT_ID, CORBADO_FRONTEND_API, CORBADO_FRONTEND_API_URL_SUFFIX} from './environment'; +import {CORBADO_PROJECT_ID, CORBADO_FRONTEND_API_URL_SUFFIX} from './environment'; let loaded = false; From 1d66515f2f0a72fcfccf1b6985f6308512e2cc5f Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 7 Feb 2025 07:14:12 +0100 Subject: [PATCH 60/62] More backporting --- playground/react/src/hoc/withCorbadoProvider.tsx | 2 +- playground/web-js/src/scripts/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/react/src/hoc/withCorbadoProvider.tsx b/playground/react/src/hoc/withCorbadoProvider.tsx index 20bd903e5..b91d092e3 100644 --- a/playground/react/src/hoc/withCorbadoProvider.tsx +++ b/playground/react/src/hoc/withCorbadoProvider.tsx @@ -18,7 +18,7 @@ function withCorbadoProvider(WrappedComponent }} darkMode={darkMode ? 'on' : 'off'} isDevMode={true} - frontendApiUrlSuffix={process.env.REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX!} + frontendApiUrlSuffix={process.env.REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX} > diff --git a/playground/web-js/src/scripts/index.js b/playground/web-js/src/scripts/index.js index a5fa450e7..186d64627 100644 --- a/playground/web-js/src/scripts/index.js +++ b/playground/web-js/src/scripts/index.js @@ -1,5 +1,5 @@ import Corbado from '@corbado/web-js'; -import {CORBADO_PROJECT_ID, CORBADO_FRONTEND_API_URL_SUFFIX} from './environment'; +import { CORBADO_PROJECT_ID, CORBADO_FRONTEND_API_URL_SUFFIX } from './environment'; let loaded = false; From 0884d822dbdd0e4b70ec57b0e28beb842bda146f Mon Sep 17 00:00:00 2001 From: Lukas Kratzel Date: Fri, 7 Feb 2025 10:02:48 +0100 Subject: [PATCH 61/62] fmt --- packages/web-core/src/services/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index be7b88224..af26b07f1 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -24,7 +24,12 @@ export class CorbadoApp { * The constructor initializes the services and sets up the application. */ constructor(corbadoParams: CorbadoAppParams) { - const { projectId, apiTimeout = defaultTimeout, frontendApiUrlSuffix = 'frontendapi.cloud.corbado.io', isPreviewMode = false } = corbadoParams; + const { + projectId, + apiTimeout = defaultTimeout, + frontendApiUrlSuffix = 'frontendapi.cloud.corbado.io', + isPreviewMode = false, + } = corbadoParams; this.#projectId = projectId; this.#authProcessService = new ProcessService(this.#projectId, apiTimeout, isPreviewMode, frontendApiUrlSuffix); From b0446e7b46f6953bd86794257a5a0b788c1633d9 Mon Sep 17 00:00:00 2001 From: Corbadoman <100508310+corbadoman@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:50:22 +0100 Subject: [PATCH 62/62] Removed .cloud from frontendApiUrlSuffix --- packages/web-core/src/services/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index af26b07f1..c11e410e7 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -27,7 +27,7 @@ export class CorbadoApp { const { projectId, apiTimeout = defaultTimeout, - frontendApiUrlSuffix = 'frontendapi.cloud.corbado.io', + frontendApiUrlSuffix = 'frontendapi.corbado.io', isPreviewMode = false, } = corbadoParams;