@vertesia/ui 1.6.0-dev.20260918.061604Z → 1.6.0-dev.20260918.063824Z
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/env/index.d.ts +6 -4
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js +4 -0
- package/lib/env/index.js.map +1 -1
- package/lib/session/auth/domainRouting.d.ts +3 -4
- package/lib/session/auth/domainRouting.d.ts.map +1 -1
- package/lib/session/auth/domainRouting.js +3 -4
- package/lib/session/auth/domainRouting.js.map +1 -1
- package/lib/vertesia-ui-env.js +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/package.json +7 -7
- package/src/env/index.test.ts +21 -0
- package/src/env/index.ts +8 -2
- package/src/session/auth/auth-flow.md +21 -9
- package/src/session/auth/domainRouting.test.ts +1 -1
- package/src/session/auth/domainRouting.ts +3 -4
package/lib/env/index.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export interface EnvProps {
|
|
|
14
14
|
/**
|
|
15
15
|
* Central Auth broker that issues the sign-in redirect and hosts `/logout`.
|
|
16
16
|
*
|
|
17
|
-
* Optional: when unset the session falls back to the
|
|
18
|
-
* does not set it
|
|
17
|
+
* Optional: when unset the session falls back to the first-party broker, so an app that
|
|
18
|
+
* does not set it uses auth.vertesia.io. Set it to move one environment at a
|
|
19
19
|
* time onto a different broker.
|
|
20
20
|
*/
|
|
21
21
|
auth?: string;
|
|
@@ -75,6 +75,8 @@ export type VertesiaRuntimeConfig = {
|
|
|
75
75
|
};
|
|
76
76
|
} | {
|
|
77
77
|
authMode: 'central';
|
|
78
|
+
/** Broker selected by the serving gateway. Overrides build-time configuration. */
|
|
79
|
+
authUrl?: string;
|
|
78
80
|
};
|
|
79
81
|
declare global {
|
|
80
82
|
interface Window {
|
|
@@ -105,8 +107,8 @@ export declare class VertesiaEnvironment implements Readonly<EnvProps> {
|
|
|
105
107
|
/**
|
|
106
108
|
* Central Auth broker that issues the sign-in redirect and hosts `/logout`.
|
|
107
109
|
*
|
|
108
|
-
* Optional: when unset the session falls back to the
|
|
109
|
-
* does not set it
|
|
110
|
+
* Optional: when unset the session falls back to the first-party broker, so an app that
|
|
111
|
+
* does not set it uses auth.vertesia.io. Set it to move one environment at a
|
|
110
112
|
* time onto a different broker.
|
|
111
113
|
*/
|
|
112
114
|
auth?: string;
|
package/lib/env/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;IACpE,SAAS,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ;;;;;;WAMG;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,sFAAsF;QACtF,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,QAAQ,CAAC,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,oEAAoE;QACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,iGAAiG;IACjG,oBAAoB,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtD,MAAM,CAAC,EAAE;QACL,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QAChD,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QAChD,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QACjD,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;KACpD,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,MAAM,MAAM,qBAAqB,GAC3B;IACI,QAAQ,EAAE,UAAU,CAAC;IACrB,QAAQ,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC;CACL,GACD;IACI,QAAQ,EAAE,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;IACpE,SAAS,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ;;;;;;WAMG;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,sFAAsF;QACtF,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,QAAQ,CAAC,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,oEAAoE;QACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,iGAAiG;IACjG,oBAAoB,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtD,MAAM,CAAC,EAAE;QACL,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QAChD,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QAChD,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QACjD,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;KACpD,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,MAAM,MAAM,qBAAqB,GAC3B;IACI,QAAQ,EAAE,UAAU,CAAC;IACrB,QAAQ,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC;CACL,GACD;IACI,QAAQ,EAAE,SAAS,CAAC;IACpB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAER,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,SAAS,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;QACnC,2BAA2B,CAAC,EAAE,qBAAqB,CAAC;KACvD;CACJ;AAaD,sEAAsE;AACtE,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;AAiC/E,qBAAa,mBAAoB,YAAW,QAAQ,CAAC,QAAQ,CAAC;IAC9C,OAAO,CAAC,MAAM,CAAC;IAA3B,YAAoB,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,EAAI;IAErD,IAAI,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,mBAAmB,QA8BpD;IAED,OAAO,CAAC,IAAI;IAOZ,IAAI,OAAO,WAEV;IAED,IAAI,eAAe,uBAElB;IAED,IAAI,UAAU,uBAEb;IAED,IAAI,IAAI,WAEP;IAED,IAAI,IAAI,WAEP;IAED,IAAI,MAAM,YAET;IAED,IAAI,KAAK,YAER;IAED,IAAI,UAAU,YAEb;IAED,IAAI,QAAQ,YAEX;IAED,IAAI,SAAS;cAzMH,MAAM;gBACJ,MAAM;aACT,MAAM;QACX;;;;;;WAMG;eACI,MAAM;cACP,MAAM;cACN,MAAM;QACZ,sFAAsF;kBAC5E,MAAM;MA6LnB;IAED,IAAI,QAAQ;gBA5LA,MAAM;oBACF,MAAM;mBACP,MAAM;gBACT,MAAM;uBACC,MAAM;QACrB,oEAAoE;mBACzD,MAAM;kBAwLpB;IAED,IAAI,MAAM,uBAET;IAED;;;OAGG;IACH,IAAI,OAAO,YAEV;IAED,IAAI,UAAU,YAEb;IAED,IAAI,WAAW,YAEd;IAED,IAAI,YAAY,uBAEf;IAED,IAAI,oBAAoB;oBA/Ma,MAAM;oBAAc,MAAM;kBAiN9D;IAED,IAAI,iBAAiB,WAjMK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,cAmMpD;IAED,IAAI,MAAM;cAnMA,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI;cACzC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI;eACxC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI;eACzC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI;MAkMnD;IAED;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,gBAAgB,QAE9B;IAED;;OAEG;IACH,QAAQ,SAEP;CACJ;AAED,QAAA,MAAM,GAAG,qBAA4B,CAAC;AAEtC,OAAO,EAAE,GAAG,EAAE,CAAC"}
|
package/lib/env/index.js
CHANGED
|
@@ -56,6 +56,10 @@ export class VertesiaEnvironment {
|
|
|
56
56
|
const runtimeConfig = injectedRuntimeConfig() ?? buildRuntimeConfig(buildEnv);
|
|
57
57
|
const runtimeFirebase = runtimeConfig?.authMode === 'firebase' ? runtimeConfig.firebase : undefined;
|
|
58
58
|
this._props = props && runtimeFirebase && !props.firebase ? { ...props, firebase: runtimeFirebase } : props;
|
|
59
|
+
const authUrl = runtimeConfig?.authMode === 'central' ? runtimeConfig.authUrl?.trim() : undefined;
|
|
60
|
+
if (this._props && authUrl) {
|
|
61
|
+
this._props = { ...this._props, endpoints: { ...this._props.endpoints, auth: authUrl } };
|
|
62
|
+
}
|
|
59
63
|
const tenantId = typeof buildEnv?.VITE_FIREBASE_TENANT_ID === 'string' ? buildEnv.VITE_FIREBASE_TENANT_ID.trim() : '';
|
|
60
64
|
if (this._props?.firebase && tenantId && !this._props.firebase.tenantId) {
|
|
61
65
|
this._props = { ...this._props, firebase: { ...this._props.firebase, tenantId } };
|
package/lib/env/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,8DAA8D;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,11 +6,10 @@ declare global {
|
|
|
6
6
|
/**
|
|
7
7
|
* The broker this app sends users to for sign-in and logout.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* different broker, and it keeps this default so an app that configures nothing is unaffected.
|
|
9
|
+
* A gateway or app can select a regional broker through Env.endpoints.auth.
|
|
10
|
+
* Unconfigured apps use the first-party central broker.
|
|
12
11
|
*/
|
|
13
|
-
export declare const DEFAULT_CENTRAL_AUTH_URL = "https://
|
|
12
|
+
export declare const DEFAULT_CENTRAL_AUTH_URL = "https://auth.vertesia.io/";
|
|
14
13
|
export declare function centralAuthUrl(): string;
|
|
15
14
|
export declare function shouldUseFirebaseAuth(_hostname?: string): boolean;
|
|
16
15
|
export declare function shouldRedirectToCentralAuth(_hostname?: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domainRouting.d.ts","sourceRoot":"","sources":["../../../src/session/auth/domainRouting.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,SAAS,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;KACtC;CACJ;AAED
|
|
1
|
+
{"version":3,"file":"domainRouting.d.ts","sourceRoot":"","sources":["../../../src/session/auth/domainRouting.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,SAAS,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;KACtC;CACJ;AAED;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,8BAA8B,CAAC;AAEpE,wBAAgB,cAAc,IAAI,MAAM,CAGvC;AAED,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,WAEvD;AAED,wBAAgB,2BAA2B,CAAC,SAAS,CAAC,EAAE,MAAM,WAE7D;AAED,UAAU,aAAa;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,IAAI,GAAG,CAMnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,GAAG,CAKlC;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACvC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,aAAkB,GAC9B,GAAG,CAUL;AA0BD,6GAA6G;AAC7G,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,GAAE,aAAkB,GAAG,IAAI,CAUzE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,IAAI,OAAO,CAMtD"}
|
|
@@ -3,11 +3,10 @@ import { generateAuthState } from './authState';
|
|
|
3
3
|
/**
|
|
4
4
|
* The broker this app sends users to for sign-in and logout.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* different broker, and it keeps this default so an app that configures nothing is unaffected.
|
|
6
|
+
* A gateway or app can select a regional broker through Env.endpoints.auth.
|
|
7
|
+
* Unconfigured apps use the first-party central broker.
|
|
9
8
|
*/
|
|
10
|
-
export const DEFAULT_CENTRAL_AUTH_URL = 'https://
|
|
9
|
+
export const DEFAULT_CENTRAL_AUTH_URL = 'https://auth.vertesia.io/';
|
|
11
10
|
export function centralAuthUrl() {
|
|
12
11
|
const configured = Env.endpoints.auth;
|
|
13
12
|
return configured ? configured : DEFAULT_CENTRAL_AUTH_URL;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domainRouting.js","sourceRoot":"","sources":["../../../src/session/auth/domainRouting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAQhD
|
|
1
|
+
{"version":3,"file":"domainRouting.js","sourceRoot":"","sources":["../../../src/session/auth/domainRouting.ts"],"names":[],"mappings":"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"}
|
package/lib/vertesia-ui-env.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(){if(typeof window>`u`)return;let e=window.__VERTESIA_RUNTIME_CONFIG__;if(e?.authMode===`central`)return e;if(e?.authMode!==`firebase`)return;let t=e.firebase;if(t?.apiKey&&t.authDomain&&t.projectId&&t.appId)return e}function t(e){if(!e)return;let t=t=>typeof e[t]==`string`?e[t].trim():``,n=t(`VITE_AUTH_MODE`);if(n&&n!==`firebase`&&n!==`central`)throw Error(`VITE_AUTH_MODE must be firebase or central`);if(!n)return;if(n===`central`)return{authMode:`central`};let r={apiKey:`VITE_FIREBASE_API_KEY`,authDomain:`VITE_FIREBASE_AUTH_DOMAIN`,projectId:`VITE_FIREBASE_PROJECT_ID`,appId:`VITE_FIREBASE_APP_ID`},i=Object.values(r).filter(e=>!t(e));if(i.length)throw Error(`Firebase authentication requires: ${i.join(`, `)}`);return{authMode:`firebase`,firebase:{apiKey:t(r.apiKey),authDomain:t(r.authDomain),projectId:t(r.projectId),appId:t(r.appId)}}}var n=class{_props;constructor(e){this._props=e}init(n,r){let i=e()??t(r),a=i?.authMode===`firebase`?i.firebase:void 0;this._props=n&&a&&!n.firebase?{...n,firebase:a}:n;let o=typeof r?.VITE_FIREBASE_TENANT_ID==`string`?r.VITE_FIREBASE_TENANT_ID.trim():``;if(this._props?.firebase&&
|
|
1
|
+
function e(){if(typeof window>`u`)return;let e=window.__VERTESIA_RUNTIME_CONFIG__;if(e?.authMode===`central`)return e;if(e?.authMode!==`firebase`)return;let t=e.firebase;if(t?.apiKey&&t.authDomain&&t.projectId&&t.appId)return e}function t(e){if(!e)return;let t=t=>typeof e[t]==`string`?e[t].trim():``,n=t(`VITE_AUTH_MODE`);if(n&&n!==`firebase`&&n!==`central`)throw Error(`VITE_AUTH_MODE must be firebase or central`);if(!n)return;if(n===`central`)return{authMode:`central`};let r={apiKey:`VITE_FIREBASE_API_KEY`,authDomain:`VITE_FIREBASE_AUTH_DOMAIN`,projectId:`VITE_FIREBASE_PROJECT_ID`,appId:`VITE_FIREBASE_APP_ID`},i=Object.values(r).filter(e=>!t(e));if(i.length)throw Error(`Firebase authentication requires: ${i.join(`, `)}`);return{authMode:`firebase`,firebase:{apiKey:t(r.apiKey),authDomain:t(r.authDomain),projectId:t(r.projectId),appId:t(r.appId)}}}var n=class{_props;constructor(e){this._props=e}init(n,r){let i=e()??t(r),a=i?.authMode===`firebase`?i.firebase:void 0;this._props=n&&a&&!n.firebase?{...n,firebase:a}:n;let o=i?.authMode===`central`?i.authUrl?.trim():void 0;this._props&&o&&(this._props={...this._props,endpoints:{...this._props.endpoints,auth:o}});let s=typeof r?.VITE_FIREBASE_TENANT_ID==`string`?r.VITE_FIREBASE_TENANT_ID.trim():``;if(this._props?.firebase&&s&&!this._props.firebase.tenantId&&(this._props={...this._props,firebase:{...this._props.firebase,tenantId:s}}),this._props&&this._props.defaultAuthSelection===void 0&&r){let e=typeof r.VITE_VERTESIA_ACCOUNT_ID==`string`&&r.VITE_VERTESIA_ACCOUNT_ID.trim()||void 0,t=typeof r.VITE_VERTESIA_PROJECT_ID==`string`&&r.VITE_VERTESIA_PROJECT_ID.trim()||void 0;(e||t)&&(this._props={...this._props,defaultAuthSelection:{accountId:e,projectId:t}})}return i&&typeof window<`u`&&window.AUTH_MODE===void 0&&(window.AUTH_MODE=i.authMode),this}prop(e){if(!this._props)throw Error(`VertesiaEnvironment was not initialized`);return this._props[e]}get version(){return this.prop(`version`)}get commitTimestamp(){return this._props?.commitTimestamp}get sdkVersion(){return this._props?.sdkVersion}get name(){return this.prop(`name`)}get type(){return this.prop(`type`)}get isProd(){return this.type===`production`||this.type===`preview`}get isDev(){return!this.isProd}get isLocalDev(){return this.prop(`isLocalDev`)}get isDocker(){return this.prop(`isDocker`)}get endpoints(){return this.prop(`endpoints`)}get firebase(){return this.prop(`firebase`)}get region(){return this._props?.region}get datadog(){return this.datadogRum}get datadogRum(){return this._props?.datadogRum??!1}get datadogLogs(){return this._props?.datadogLogs??!1}get devAuthToken(){return this._props?.devAuthToken}get defaultAuthSelection(){return this._props?.defaultAuthSelection}get authTokenProvider(){return this._props?.authTokenProvider}get logger(){return this._props?.logger??console}onLogin(e){this._props?.onLogin?.(e)}onLogout(){this._props?.onLogout?.()}};const r=new n;export{r as Env,n as VertesiaEnvironment};
|
|
2
2
|
//# sourceMappingURL=vertesia-ui-env.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertesia-ui-env.js","names":[],"sources":["env/index.js"],"sourcesContent":["// hook to initialize the environment and auth session\n// the main app must call this hook before rendering the page.\nfunction injectedRuntimeConfig() {\n if (typeof window === 'undefined')\n return undefined;\n const runtimeConfig = window.__VERTESIA_RUNTIME_CONFIG__;\n if (runtimeConfig?.authMode === 'central')\n return runtimeConfig;\n if (runtimeConfig?.authMode !== 'firebase')\n return undefined;\n const firebase = runtimeConfig.firebase;\n if (!firebase?.apiKey || !firebase.authDomain || !firebase.projectId || !firebase.appId)\n return undefined;\n return runtimeConfig;\n}\nfunction buildRuntimeConfig(env) {\n if (!env)\n return undefined;\n const value = (name) => (typeof env[name] === 'string' ? env[name].trim() : '');\n const mode = value('VITE_AUTH_MODE');\n if (mode && mode !== 'firebase' && mode !== 'central') {\n throw new Error('VITE_AUTH_MODE must be firebase or central');\n }\n // Firebase build settings are only consumed after an explicit opt-in.\n if (!mode)\n return undefined;\n if (mode === 'central')\n return { authMode: 'central' };\n const fields = {\n apiKey: 'VITE_FIREBASE_API_KEY',\n authDomain: 'VITE_FIREBASE_AUTH_DOMAIN',\n projectId: 'VITE_FIREBASE_PROJECT_ID',\n appId: 'VITE_FIREBASE_APP_ID',\n };\n const missing = Object.values(fields).filter((name) => !value(name));\n if (missing.length) {\n throw new Error(`Firebase authentication requires: ${missing.join(', ')}`);\n }\n return {\n authMode: 'firebase',\n firebase: {\n apiKey: value(fields.apiKey),\n authDomain: value(fields.authDomain),\n projectId: value(fields.projectId),\n appId: value(fields.appId),\n },\n };\n}\nexport class VertesiaEnvironment {\n _props;\n constructor(_props) {\n this._props = _props;\n }\n init(props, buildEnv) {\n // Host runtime configuration takes precedence over static deployment defaults.\n const runtimeConfig = injectedRuntimeConfig() ?? buildRuntimeConfig(buildEnv);\n const runtimeFirebase = runtimeConfig?.authMode === 'firebase' ? runtimeConfig.firebase : undefined;\n this._props = props && runtimeFirebase && !props.firebase ? { ...props, firebase: runtimeFirebase } : props;\n const tenantId = typeof buildEnv?.VITE_FIREBASE_TENANT_ID === 'string' ? buildEnv.VITE_FIREBASE_TENANT_ID.trim() : '';\n if (this._props?.firebase && tenantId && !this._props.firebase.tenantId) {\n this._props = { ...this._props, firebase: { ...this._props.firebase, tenantId } };\n }\n if (this._props && this._props.defaultAuthSelection === undefined && buildEnv) {\n const accountId = typeof buildEnv.VITE_VERTESIA_ACCOUNT_ID === 'string'\n ? buildEnv.VITE_VERTESIA_ACCOUNT_ID.trim() || undefined\n : undefined;\n const projectId = typeof buildEnv.VITE_VERTESIA_PROJECT_ID === 'string'\n ? buildEnv.VITE_VERTESIA_PROJECT_ID.trim() || undefined\n : undefined;\n if (accountId || projectId)\n this._props = { ...this._props, defaultAuthSelection: { accountId, projectId } };\n }\n if (runtimeConfig && typeof window !== 'undefined' && window.AUTH_MODE === undefined) {\n window.AUTH_MODE = runtimeConfig.authMode;\n }\n return this;\n }\n prop(name) {\n if (!this._props) {\n throw new Error('VertesiaEnvironment was not initialized');\n }\n return this._props[name];\n }\n get version() {\n return this.prop('version');\n }\n get commitTimestamp() {\n return this._props?.commitTimestamp;\n }\n get sdkVersion() {\n return this._props?.sdkVersion;\n }\n get name() {\n return this.prop('name');\n }\n get type() {\n return this.prop('type');\n }\n get isProd() {\n return this.type === 'production' || this.type === 'preview';\n }\n get isDev() {\n return !this.isProd;\n }\n get isLocalDev() {\n return this.prop('isLocalDev');\n }\n get isDocker() {\n return this.prop('isDocker');\n }\n get endpoints() {\n return this.prop('endpoints');\n }\n get firebase() {\n return this.prop('firebase');\n }\n get region() {\n return this._props?.region;\n }\n /**\n * @deprecated Use datadogRum and datadogLogs instead for more granular control. This will be removed in a future release. Is an alias for DatadogRUM\n *\n */\n get datadog() {\n return this.datadogRum;\n }\n get datadogRum() {\n return this._props?.datadogRum ?? false;\n }\n get datadogLogs() {\n return this._props?.datadogLogs ?? false;\n }\n get devAuthToken() {\n return this._props?.devAuthToken;\n }\n get defaultAuthSelection() {\n return this._props?.defaultAuthSelection;\n }\n get authTokenProvider() {\n return this._props?.authTokenProvider;\n }\n get logger() {\n return this._props?.logger ?? console;\n }\n /**\n * Callback to notify the host app when the user logs in.\n */\n onLogin(token) {\n this._props?.onLogin?.(token);\n }\n /**\n * Callback to notify the host app when the user logs out\n */\n onLogout() {\n this._props?.onLogout?.();\n }\n}\nconst Env = new VertesiaEnvironment();\nexport { Env };\n//# sourceMappingURL=index.js.map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vertesia-ui-env.js","names":[],"sources":["env/index.js"],"sourcesContent":["// hook to initialize the environment and auth session\n// the main app must call this hook before rendering the page.\nfunction injectedRuntimeConfig() {\n if (typeof window === 'undefined')\n return undefined;\n const runtimeConfig = window.__VERTESIA_RUNTIME_CONFIG__;\n if (runtimeConfig?.authMode === 'central')\n return runtimeConfig;\n if (runtimeConfig?.authMode !== 'firebase')\n return undefined;\n const firebase = runtimeConfig.firebase;\n if (!firebase?.apiKey || !firebase.authDomain || !firebase.projectId || !firebase.appId)\n return undefined;\n return runtimeConfig;\n}\nfunction buildRuntimeConfig(env) {\n if (!env)\n return undefined;\n const value = (name) => (typeof env[name] === 'string' ? env[name].trim() : '');\n const mode = value('VITE_AUTH_MODE');\n if (mode && mode !== 'firebase' && mode !== 'central') {\n throw new Error('VITE_AUTH_MODE must be firebase or central');\n }\n // Firebase build settings are only consumed after an explicit opt-in.\n if (!mode)\n return undefined;\n if (mode === 'central')\n return { authMode: 'central' };\n const fields = {\n apiKey: 'VITE_FIREBASE_API_KEY',\n authDomain: 'VITE_FIREBASE_AUTH_DOMAIN',\n projectId: 'VITE_FIREBASE_PROJECT_ID',\n appId: 'VITE_FIREBASE_APP_ID',\n };\n const missing = Object.values(fields).filter((name) => !value(name));\n if (missing.length) {\n throw new Error(`Firebase authentication requires: ${missing.join(', ')}`);\n }\n return {\n authMode: 'firebase',\n firebase: {\n apiKey: value(fields.apiKey),\n authDomain: value(fields.authDomain),\n projectId: value(fields.projectId),\n appId: value(fields.appId),\n },\n };\n}\nexport class VertesiaEnvironment {\n _props;\n constructor(_props) {\n this._props = _props;\n }\n init(props, buildEnv) {\n // Host runtime configuration takes precedence over static deployment defaults.\n const runtimeConfig = injectedRuntimeConfig() ?? buildRuntimeConfig(buildEnv);\n const runtimeFirebase = runtimeConfig?.authMode === 'firebase' ? runtimeConfig.firebase : undefined;\n this._props = props && runtimeFirebase && !props.firebase ? { ...props, firebase: runtimeFirebase } : props;\n const authUrl = runtimeConfig?.authMode === 'central' ? runtimeConfig.authUrl?.trim() : undefined;\n if (this._props && authUrl) {\n this._props = { ...this._props, endpoints: { ...this._props.endpoints, auth: authUrl } };\n }\n const tenantId = typeof buildEnv?.VITE_FIREBASE_TENANT_ID === 'string' ? buildEnv.VITE_FIREBASE_TENANT_ID.trim() : '';\n if (this._props?.firebase && tenantId && !this._props.firebase.tenantId) {\n this._props = { ...this._props, firebase: { ...this._props.firebase, tenantId } };\n }\n if (this._props && this._props.defaultAuthSelection === undefined && buildEnv) {\n const accountId = typeof buildEnv.VITE_VERTESIA_ACCOUNT_ID === 'string'\n ? buildEnv.VITE_VERTESIA_ACCOUNT_ID.trim() || undefined\n : undefined;\n const projectId = typeof buildEnv.VITE_VERTESIA_PROJECT_ID === 'string'\n ? buildEnv.VITE_VERTESIA_PROJECT_ID.trim() || undefined\n : undefined;\n if (accountId || projectId)\n this._props = { ...this._props, defaultAuthSelection: { accountId, projectId } };\n }\n if (runtimeConfig && typeof window !== 'undefined' && window.AUTH_MODE === undefined) {\n window.AUTH_MODE = runtimeConfig.authMode;\n }\n return this;\n }\n prop(name) {\n if (!this._props) {\n throw new Error('VertesiaEnvironment was not initialized');\n }\n return this._props[name];\n }\n get version() {\n return this.prop('version');\n }\n get commitTimestamp() {\n return this._props?.commitTimestamp;\n }\n get sdkVersion() {\n return this._props?.sdkVersion;\n }\n get name() {\n return this.prop('name');\n }\n get type() {\n return this.prop('type');\n }\n get isProd() {\n return this.type === 'production' || this.type === 'preview';\n }\n get isDev() {\n return !this.isProd;\n }\n get isLocalDev() {\n return this.prop('isLocalDev');\n }\n get isDocker() {\n return this.prop('isDocker');\n }\n get endpoints() {\n return this.prop('endpoints');\n }\n get firebase() {\n return this.prop('firebase');\n }\n get region() {\n return this._props?.region;\n }\n /**\n * @deprecated Use datadogRum and datadogLogs instead for more granular control. This will be removed in a future release. Is an alias for DatadogRUM\n *\n */\n get datadog() {\n return this.datadogRum;\n }\n get datadogRum() {\n return this._props?.datadogRum ?? false;\n }\n get datadogLogs() {\n return this._props?.datadogLogs ?? false;\n }\n get devAuthToken() {\n return this._props?.devAuthToken;\n }\n get defaultAuthSelection() {\n return this._props?.defaultAuthSelection;\n }\n get authTokenProvider() {\n return this._props?.authTokenProvider;\n }\n get logger() {\n return this._props?.logger ?? console;\n }\n /**\n * Callback to notify the host app when the user logs in.\n */\n onLogin(token) {\n this._props?.onLogin?.(token);\n }\n /**\n * Callback to notify the host app when the user logs out\n */\n onLogout() {\n this._props?.onLogout?.();\n }\n}\nconst Env = new VertesiaEnvironment();\nexport { Env };\n//# sourceMappingURL=index.js.map"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{NO_ACCESSIBLE_ACCOUNT_ERROR_CODE as e,REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE as t,RESTRICTED_ENVIRONMENT_ERROR_CODE as n}from"@vertesia/common";import{Env as r}from"@vertesia/ui/env";import{jwtDecode as i}from"jwt-decode";import{getAnalytics as a,logEvent as o}from"firebase/analytics";import{initializeApp as s}from"firebase/app";import{getAuth as c,onAuthStateChanged as l}from"firebase/auth";import{createContext as u,useCallback as d,useContext as f,useEffect as p,useMemo as m,useRef as h,useState as g}from"react";import{NAMESPACE as ee,i18nInstance as _}from"@vertesia/ui/i18n";import{VertesiaClient as te}from"@vertesia/client";import{jsx as ne}from"react/jsx-runtime";const v=`composableai.lastSelectedAccountId`,y=`composableai.lastSelectedProjectId`,b=`auth_state`,x=`auth_state_expiry`;function S(){let e=crypto.randomUUID();return sessionStorage.setItem(b,e),sessionStorage.setItem(x,String(Date.now()+3e5)),e}function re(e){if(!e)return`Missing state`;let t=sessionStorage.getItem(b),n=parseInt(sessionStorage.getItem(x)||`0`,10);if(t!==e)return`State mismatched (${t} !== ${e})`;if(Date.now()>n)return`State expired`}function ie(){sessionStorage.removeItem(b),sessionStorage.removeItem(x)}function C(){return r.endpoints.auth||`https://internal-auth.vertesia.app/`}function ae(e){return window.AUTH_MODE===`firebase`}function w(e){return!ae()}function T(){let e=new URL(document.baseURI),t=new URL(window.location.href),n=t.pathname.startsWith(e.pathname)?t:e;return n.hash=``,n}function E(){let e=new URL(document.baseURI);return e.hash=``,e.search=``,e}function D(e,t,n,r,i={}){let a=new URL(n);i.projectId&&a.searchParams.set(`p`,i.projectId),i.accountId&&a.searchParams.set(`a`,i.accountId);let o=new URL(e);return o.searchParams.set(`sts`,t),o.searchParams.set(`redirect_uri`,a.toString()),o.searchParams.set(`state`,r),o}function oe(e,t){return`vtsauth=${e}; Path=/; Max-Age=${t}; SameSite=Lax${window.location.protocol===`https:`?`; Secure`:``}`}function se(){document.cookie=oe(`1`,300)}function ce(){document.cookie=oe(``,0)}function O(e={}){let t=D(C(),r.endpoints.sts??`https://sts.vertesia.io`,T(),S(),e);se(),location.replace(t.toString())}function le(){if(!w()||r.authTokenProvider||r.isLocalDev&&r.devAuthToken)return!1;let e=new URLSearchParams(window.location.hash.substring(1));return!(e.get(`token`)&&e.get(`state`))}let k=null,ue=null,A=null;function j(){if(!k)try{if(!r.firebase)throw Error(`Firebase configuration is not available in the environment`);k=s(r.firebase)}catch(e){throw console.error(`Failed to initialize Firebase app:`,e),Error(`Firebase initialization failed - environment may not be properly initialized`,{cause:e})}return k}function de(){return ue||=a(j()),ue}function M(){return A||(A=c(j()),A.tenantId=r.firebase?.tenantId??null),A}async function fe(e){if(r.firebase?.tenantId)return M().tenantId=r.firebase.tenantId,{firebaseTenantId:r.firebase.tenantId,provider:r.firebase.providerType??`oidc`,name:``,label:``};if(!e){console.log(`No tenant name or email specified, skipping tenant setup`);return}if(!r.firebase){console.log(`Firebase configuration is not available in the environment`);return}try{e&&console.log(`Resolving tenant ID from email: ${e}`);let t=3,n=250;for(;t>0;)try{let t=await fetch(`/api/resolve-tenant`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tenantEmail:e}),signal:AbortSignal.timeout(5e3)});if(!t)throw Error(`No response received from tenant API`);if(!t.ok){try{let e=await t.json();console.error(`Failed to resolve tenant ID:`,e.error)}catch{console.error(`Failed to resolve tenant ID: HTTP ${t.status}`)}if(t.status===404){console.warn(`Tenant not found for ${e}`);return}throw Error(`HTTP error ${t.status}`)}let n=await t.json();if(n?.firebaseTenantId){let e=M();return e.tenantId=n.firebaseTenantId,r.firebase.providerType=n.provider??`oidc`,console.log(`Tenant ID set to ${e.tenantId}`),n}console.error(`Invalid response format, missing tenantId for ${e}`);return}catch(e){if(t>1)console.warn(`Tenant resolution failed, retrying in ${n}ms...`,e),await new Promise(e=>setTimeout(e,n)),n*=2,t--;else throw e}}catch(e){console.error(`Error setting Firebase tenant:`,e instanceof Error?e.message:`Unknown error`)}}async function pe(e){let t=M().currentUser;return t?t.getIdToken(e).then(n=>(r.logger.debug(`Got Firebase token`,{vertesia:{user_email:t.email,user_name:t.displayName,user_id:t.uid,refresh:e}}),n)).catch(n=>(r.logger.error(`Failed to get Firebase token`,{vertesia:{user_email:t.email,user_name:t.displayName,user_id:t.uid,refresh:e,error:n}}),console.error(`Failed to get access token`,n),null)):(r.logger.warn(`No user found`),Promise.resolve(null))}let N,P,F=!1;const I=`vt.centralAuthRenewalAt`;function L(){if(!F||!w())return!1;let e=0;try{e=Number(sessionStorage.getItem(I)??`0`)}catch{}if(Number.isFinite(e)&&e>0&&Date.now()-e<6e4)return r.logger.warn(`Central Auth session renewal already attempted; surfacing the failure instead`,{vertesia:{last_attempt_ms_ago:Date.now()-e}}),!1;try{sessionStorage.setItem(I,String(Date.now()))}catch{}r.logger.info(`Renewing an expired Central Auth session through the broker`);let t=D(C(),r.endpoints.sts??`https://sts.vertesia.io`,T(),S());return location.replace(t.toString()),!0}function me(e,t){if(!e)return;let n=`${y}-${e}`;if(t){let r=localStorage.getItem(n)===t;r&&localStorage.removeItem(n),r&&localStorage.getItem(`composableai.lastSelectedAccountId`)===e&&localStorage.removeItem(v);return}localStorage.getItem(`composableai.lastSelectedAccountId`)===e&&(localStorage.removeItem(v),localStorage.removeItem(n))}function he(e){try{let t=i(e);return typeof t.email!=`string`||!t.email?void 0:{email:t.email,name:typeof t.name==`string`&&t.name?t.name:void 0}}catch{return}}function R(e){let t=e.searchParams.has(`a`)||e.searchParams.has(`p`)?void 0:r.defaultAuthSelection,n=e.searchParams.get(`a`)??t?.accountId,i=e.searchParams.get(`p`)??t?.projectId,a=n??(i===void 0?localStorage.getItem(`composableai.lastSelectedAccountId`)??void 0:void 0);return{accountId:a,projectId:i??localStorage.getItem(`composableai.lastSelectedProjectId-${a}`)??void 0}}function z(e){return e?.replace(/\/+$/,``)}function B(e){return i(e)}function V(e){if(!e)return!1;try{return z(B(e).iss)===z(r.endpoints.sts)}catch{return!1}}function ge(e,t,n){let r=B(e);return!(!r.exp||r.exp<=Date.now()/1e3+300||!(r.permissions?.length||r.account_roles?.length||r.project_roles?.length||r.apps?.length)||t&&r.account?.id!==t||n&&r.project?.id!==n)}async function H(a,o,s,c,l=0){r.logger.debug(`Getting/refreshing composable token`,{vertesia:{account_id:o,project_id:s,retry_count:l}});let u=await a();if(!u)throw Error(`No id token found`);let d=r.endpoints.sts;r.logger.debug(`Using STS for token generation`,{vertesia:{account_id:o,project_id:s,sts_url:d}});try{let f=new URL(`${d}/token/issue`),p={type:`user`,account_id:o,project_id:s,expires_at:c?Math.floor(Date.now()/1e3)+c:void 0},m=await fetch(f,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${u}`},body:JSON.stringify(p)}).catch(e=>{throw console.error(`Failed to call STS endpoint`,e),r.logger.error(`Failed to call STS endpoint`,{vertesia:{account_id:o,project_id:s,error:e}}),new Y(`The authentication service could not be reached`,d,{cause:e})});if(u&&m?.status===404){r.logger.debug(`404: User not found - calling ensure-user endpoint`,{vertesia:{account_id:o,project_id:s,status:m?.status}});let e=await fetch(`${r.endpoints.studio}/auth/ensure-user`,{method:`POST`,headers:{Authorization:`Bearer ${u}`,"Content-Type":`application/json`}});if(e.status===412){r.logger.debug(`412: No invite found - signup required`,{vertesia:{account_id:o,project_id:s}});let e=i(u);throw e?.email?new W(`User not found - signup required`,e.email):(r.logger.error(`No email found in id token`),Error(`No email found in id token`))}if(e.status===403)throw r.logger.warn(`403: Customer-domain user requires an invite to join`,{vertesia:{account_id:o,project_id:s}}),Error(`Customer-domain user requires an invite to join`);if(!e.ok)throw console.error(`Failed to ensure user exists`,e.status),r.logger.error(`Failed to ensure user exists`,{vertesia:{account_id:o,project_id:s,status:e.status}}),Error(`Failed to ensure user exists`);return r.logger.debug(`User ensured - retrying token generation`,{vertesia:{account_id:o,project_id:s}}),H(a,o,s,c,l)}if(u&&m?.status===412){r.logger.info(`412: auth succeeded but user doesn't exist - signup required`,{vertesia:{account_id:o,project_id:s,status:m?.status}});let e=i(u);throw e?.email?(r.logger.info(`User not found`,{vertesia:{account_id:o,project_id:s,email:e.email}}),new W(`User not found`,e.email)):(r.logger.error(`No email found in id token`),Error(`No email found in id token`))}if(m.status===401)throw r.logger.warn(`STS rejected the identity credential`,{vertesia:{account_id:o,project_id:s,status:m.status}}),new J(`Your sign-in credentials could not be verified`,d,{status:m.status});if(m.status===403){let i=await m.json().catch(()=>void 0),a=he(u);throw i?.errorCode===n?(r.logger.warn(`403: User lacks early-access for this restricted environment`,{vertesia:{account_id:o,project_id:s}}),new X(i.message??`You don't have sufficient permission to visit unstable environments`)):i?.errorCode===e?(r.logger.warn(`STS identity has no accessible account`,{vertesia:{error_code:i.errorCode,status:m.status}}),new q(d,i.message,a)):!i?.errorCode||i.errorCode===t?(r.logger.warn(`STS rejected the requested account or project`,{vertesia:{account_id:o,project_id:s,status:m.status,error_code:i?.errorCode??t}}),new K(d,o,s,i?.errorCode?i.message:void 0,a)):(r.logger.error(`STS returned an unrecognized forbidden response`,{vertesia:{account_id:o,project_id:s,status:m.status,error_code:i.errorCode}}),new Y(`The authentication service rejected the request`,d,{status:m.status,errorCode:i.errorCode,cause:i}))}if(!m.ok){let e=await m.text();throw console.error(`STS token generation failed:`,m.status,e),r.logger.error(`STS token generation failed`,{vertesia:{status:m.status,error:e,account_id:o,project_id:s}}),new Y(`The authentication service could not complete sign-in`,d,{status:m.status,cause:e})}let{token:h}=await m.json();return r.logger.debug(`Successfully got token from STS`),h}catch(e){throw e instanceof W||e instanceof G||e instanceof X||e instanceof Error&&e.message===`Customer-domain user requires an invite to join`?e:(console.error(`Failed to get composable token from STS`,e),r.logger.error(`Failed to get composable token from STS`,{vertesia:{account_id:o,project_id:s,error:e}}),new Y(`The authentication service could not complete sign-in`,d,{cause:e}))}}async function _e(e,t,n){return H(pe,e,t,n)}async function ve(e,t,n,r){return H(()=>Promise.resolve(e),t,n,r)}function ye(){return N}async function U(e,t,n,i=!1,a=!1){let o=e??(t===void 0?localStorage.getItem(`composableai.lastSelectedAccountId`)??void 0:void 0),s=t??localStorage.getItem(`composableai.lastSelectedProjectId-${o}`)??void 0,c=r.isLocalDev?r.devAuthToken:void 0,l=c??n??N,u=(!o||P?.account?.id===o)&&(!s||P?.project?.id===s);if(!i&&N&&P&&u&&P.exp>Date.now()/1e3+300)return F=!0,{rawToken:N,token:P,error:!1};if(!i&&V(l)&&ge(l,o,s)){if(N=l,P=B(N),!P.exp)throw Error(`Invalid composable token`);return F=!0,{rawToken:N,token:P,error:!1}}try{if(!c&&!a&&M().currentUser)N=await _e(o,s);else if(c)c&&(N=c);else{let e=await r.authTokenProvider?.()??n??N;N=!i&&e&&V(e)&&ge(e,o,s)?e:e?await H(()=>Promise.resolve(e),o,s):void 0}}catch(e){throw(e instanceof K||e instanceof q||e instanceof J)&&(N=void 0,P=void 0,e instanceof K&&me(o,s)),e instanceof J&&L(),e}if(!N)throw r.logger.error(`Cannot acquire a composable token`,{vertesia:{account_id:o,project_id:s}}),L(),Error(`Cannot acquire a composable token`);if(P=B(N),!P?.exp||!N)throw console.error(`Invalid composable token`,P),r.logger.error(`Invalid composable token`,{vertesia:{account_id:o,project_id:s}}),Error(`Invalid composable token`);return F=!0,{rawToken:N,token:P,error:!1}}var W=class extends Error{email;constructor(e,t){super(e),this.name=`UserNotFoundError`,this.email=t}},G=class extends Error{stsURL;status;errorCode;constructor(e,t,n){super(e,n),this.name=`STSError`,this.stsURL=t,this.status=n?.status,this.errorCode=n?.errorCode}},K=class extends G{accountId;projectId;identity;requestedScope;selectorSource;recoveryUrl;constructor(e,n,r,i,a){super(i??`The requested account or project is not available.`,e,{status:403,errorCode:t}),this.accountId=n,this.projectId=r,this.identity=a,this.name=`RequestedScopeUnavailableError`,this.requestedScope=r?`project`:`account`}};const be=K;var q=class extends G{identity;constructor(t,n,r){super(n??`No accessible account is available for this user.`,t,{status:403,errorCode:e}),this.identity=r,this.name=`NoAccessibleAccountError`}},J=class extends G{constructor(e,t,n){super(e,t,n),this.name=`CredentialError`}},Y=class extends G{constructor(e,t,n){super(e,t,n),this.name=`AuthenticationServiceError`}};function xe(e){return e instanceof K}function Se(e){return e instanceof q}function Ce(e){return e instanceof J}function we(e){return e instanceof Y}var X=class extends Error{constructor(e){super(e),this.name=`RestrictedEnvironmentError`}};function Z(){return{generateState:d(()=>S(),[]),verifyState:d(e=>re(e),[]),clearState:d(()=>ie(),[])}}var Q=class e{isLoading=!0;client;authError;authToken;setSession;lastSelectedAccount;lastSelectedProject;onboardingComplete;constructor(e,t){this.client=e||new te({serverUrl:r.endpoints.studio,storeUrl:r.endpoints.zeno,tokenServerUrl:r.endpoints.sts}),t&&(this.setSession=t),this.logout=this.logout.bind(this),this.signOut=this.signOut.bind(this)}get store(){return this.client.store}get user(){return this.authToken}get account(){return this.authToken?.account}get project(){return this.authToken?.project}get accounts(){return this.authToken?.accounts}get authCallback(){return this.rawAuthToken.then(e=>`Bearer ${e}`)}get rawAuthToken(){return U().then(e=>{let t=e?.rawToken;if(!t)throw Error(`No token available`);return this.authToken=i(t),t})}async refreshAuthToken(){let e=(await U(void 0,void 0,void 0,!0))?.rawToken;if(!e)throw Error(`No token available`);return this.authToken=i(e),this.setSession?.(this.clone()),this.authToken}signOut(){this.logout()}getAccount(){return this.authToken?.account}async login(e,t={}){return this.authError=void 0,this.isLoading=!1,this.client.withAuthCallback(()=>this.authCallback),this.authToken=i(e),console.log(`Logging in as ${this.authToken?.name} with account ${this.authToken?.account.name} (${this.authToken?.account.id}, and project ${this.authToken?.project?.name} (${this.authToken?.project?.id})`),localStorage.setItem(v,this.authToken.account.id),localStorage.setItem(`${y}-${this.authToken.account.id}`,this.authToken.project?.id??``),r.onLogin?.(this.authToken),(t.loadOnboardingStatus??!0)&&await this.fetchOnboardingStatus(),Promise.resolve()}isLoggedIn(){return!!this.authToken}logout(){if(console.log(`Logging out`),w()){console.log(`Using central auth logout`),this.authError=void 0,this.isLoading=!1,this.authToken=void 0,this.setSession=void 0,this.client.withAuthCallback(void 0);let e=new URL(C()),t=T();e.pathname=`/logout`,e.searchParams.set(`redirect_uri`,t.toString()),location.replace(e.toString())}else{console.log(`Using Firebase logout`);let e=!!this.authToken;this.authToken&&M().signOut(),this.authError=void 0,this.isLoading=!1,this.authToken=void 0,this.setSession=void 0,this.client.withAuthCallback(void 0),e&&location.replace(E().toString())}}async switchAccount(e){localStorage.setItem(v,e),this&&(this.account&&this.project?localStorage.setItem(`${y}-${this.account.id}`,this.project.id):this.account&&localStorage.removeItem(`${y}-${this.account.id}`));let t=E();t.searchParams.set(`a`,e),window.location.replace(t.toString())}async switchProject(e){this.account&&localStorage.setItem(`${y}-${this.account.id}`,e);let t=E();this.account?.id&&t.searchParams.set(`a`,this.account.id),t.searchParams.set(`p`,e),window.location.replace(t.toString())}async fetchAccounts(){return this.client.accounts.list().then(e=>{if(!this.authToken)throw Error(`No token available`);this.authToken.accounts=e,this.setSession?.(this.clone())}).catch(e=>{throw console.error(`Failed to fetch accounts`,e),e})}async fetchProjects(e){return this.client.projects.list([e]).then(t=>{if(!this.authToken)throw Error(`No token available`);this.authToken={...this.authToken,accounts:this.authToken.accounts?.map(n=>n.id===e?{...n,projects:t.filter(t=>t.account===e)}:n)},this.setSession?.(this.clone())}).catch(e=>{throw console.error(`Failed to fetch projects`,e),e})}async fetchOnboardingStatus(){if(!this.project?.id)return!1;if(this.onboardingComplete)return console.log(`Onboarding already completed`),!1;let e=this.onboardingComplete;try{let t=await this.client.account.onboardingProgress();return this.onboardingComplete=Object.values(t).every(e=>e===!0),e!==this.onboardingComplete&&this.setSession?.(this.clone()),e===!1&&this.onboardingComplete}catch(t){console.error(`Error fetching onboarding status:`,t),this.onboardingComplete=!1,e!==this.onboardingComplete&&this.setSession?.(this.clone())}return!1}clone(){let t=new e(this.client);return t.isLoading=this.isLoading,t.authError=this.authError,t.authToken=this.authToken,t.setSession=this.setSession,t.lastSelectedAccount=this.lastSelectedAccount,t.switchAccount=this.switchAccount,t.onboardingComplete=this.onboardingComplete,t}};const $=u(void 0);function Te(){let e=f($);if(!e)throw Error(`useUserSession must be used within a UserSessionProvider`);return e}function Ee(){let{user:e}=Te(),[t,n]=g(null),[r,i]=g(!0),[a,o]=g(null);return p(()=>{(async()=>{if(!e?.email){n(null),i(!1);return}try{let t=await fetch(`/api/resolve-tenant`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tenantEmail:e.email})});if(t.ok){let e=await t.json();n(e?{tenantKey:e.name||`unknown`,name:e.label||e.name||`Unknown`,domain:e.domain||[],firebaseTenantId:e.firebaseTenantId,provider:e.provider,logo:e.logo}:null)}else n(null)}catch(e){console.error(`Error loading current tenant:`,e),o(_.getFixedT(null,ee)(`errors.failedToLoadTenantConfig`)),n(null)}finally{i(!1)}})()},[e?.email]),{currentTenant:t,isLoading:r,error:a}}function De(e){let[,t]=e.split(`.`);if(!t)throw Error(`Invalid Vertesia auth token`);let n=t.replace(/-/g,`+`).replace(/_/g,`/`).padEnd(Math.ceil(t.length/4)*4,`=`);return JSON.parse(atob(n))}function Oe({children:e,token:t}){let n=m(()=>{let e=new Q;e.isLoading=!1;try{e.authToken=De(t),e.client.withAuthCallback(()=>Promise.resolve(`Bearer ${t}`)),localStorage.setItem(v,e.authToken.account.id),localStorage.setItem(`${y}-${e.authToken.account.id}`,e.authToken.project?.id??``)}catch(t){e.authError=t instanceof Error?t:Error(String(t))}return e.clone()},[t]);return ne($.Provider,{value:n,children:e})}function ke(){let e=new URL(window.location.href);e.hash=``,window.history.replaceState(window.history.state,``,`${e.pathname}${e.search}`)}function Ae(e,t,n=!1){let r=new URL(e),i=r.searchParams.get(`a`)??void 0,a=r.searchParams.get(`p`)??void 0;return i&&a&&i===t.accountId&&a===t.projectId?(r.searchParams.delete(`a`),r.searchParams.delete(`p`),t.requestedScope=`project`,t.selectorSource=`url`):a&&a===t.projectId?(r.searchParams.delete(`p`),t.requestedScope=`project`,t.selectorSource=`url`):i&&i===t.accountId?(r.searchParams.delete(`a`),t.requestedScope=`account`,t.selectorSource=`url`):(t.requestedScope=t.projectId?`project`:`account`,t.selectorSource=`persisted`),n&&(r.hash=``),t.recoveryUrl=r.toString(),r}function je({children:e,loadOnboardingStatus:n=!0}){let i=new URLSearchParams(location.hash.substring(1)),a=i.get(`token`),o=i.get(`state`),[s,c]=g(new Q),{verifyState:u,clearState:d}=Z(),f=h(!1),m=h(void 0),ee=(e,n=!1)=>{let i=Ae(new URL(window.location.href),e,n);window.history.replaceState(window.history.state,``,i),r.logger.warn(`Sanitized an unavailable authentication scope`,{vertesia:{account_id:e.accountId,project_id:e.projectId,error_code:t,selector_source:e.selectorSource,requested_scope:e.requestedScope,recovery_action:`url_sanitized`}})},_=(e,t=!1)=>e instanceof K||e instanceof q||e instanceof J||e instanceof Y||e instanceof W||e instanceof X?(e instanceof K?ee(e,t):t&&ke(),s.isLoading=!1,s.authError=e,c(s.clone()),!0):!1;return m.current=()=>{if(f.current){r.logger.debug(`Skipping duplicate auth flow initiation`);return}f.current=!0,r.logger.debug(`Starting auth flow`);let{accountId:e,projectId:t}=R(new URL(window.location.href));if(r.logger.debug(`Selected account and project`,{vertesia:{account_id:e,project_id:t}}),r.isLocalDev&&r.devAuthToken){s.setSession=c,U(e,t,r.devAuthToken).then(e=>s.login(e.rawToken).then(()=>c(s.clone()))).catch(n=>{_(n)||(console.error(`Failed to initialize dev auth token`,n),r.logger.error(`Failed to initialize dev auth token`,{vertesia:{account_id:e,project_id:t,error:n}}),s.isLoading=!1,s.authError=n instanceof Error?n:Error(String(n)),c(s.clone()))});return}if(a&&o){s.setSession=c;let i=u(o);i?(console.error(`Auth: invalid state: ${i}`),r.logger.error(`Invalid state: ${i}`,{vertesia:{state:o}}),O({accountId:e,projectId:t})):d(),U(e,t,a,!1,w()).then(e=>s.login(e.rawToken,{loadOnboardingStatus:n}).then(()=>{c(s.clone()),ke()})).catch(n=>{_(n,!0)||(console.error(`Failed to fetch user token from studio, redirecting to central auth`,n),r.logger.error(`Failed to fetch user token from studio, redirecting to central auth`,{vertesia:{error:n}}),O({accountId:e,projectId:t}))});return}let i=!1,p,m=()=>{if(!i){if(!s.isLoggedIn()){if(r.logger.debug(`Not logged in & no token/state`,{vertesia:{account_id:e,project_id:t}}),w()){r.logger.debug(`Redirecting to central auth with selection`,{vertesia:{account_id:e,project_id:t}}),O({accountId:e,projectId:t});return}r.logger.debug(`Host is in Firebase auth allowlist`,{vertesia:{account_id:e,project_id:t}})}p=l(M(),async i=>{i?(r.logger.debug(`Successful login with firebase`,{vertesia:{account_id:e,project_id:t}}),s.setSession=c,await U(e,t,void 0,!1,w()).then(e=>s.login(e.rawToken,{loadOnboardingStatus:n}).then(()=>c(s.clone()))).catch(n=>{_(n)||(console.error(`Unexpected failure while fetching a user token`,n),r.logger.error(`Unexpected failure while fetching a user token`,{vertesia:{account_id:e,project_id:t,error:n}}),s.logout(),s.isLoading=!1,s.authError=n,c(s.clone()))})):(r.logger.debug(`Using anonymous user`,{vertesia:{account_id:e,project_id:t}}),s.client.withAuthCallback(void 0),s.logout(),c(s.clone()))})}};return r.authTokenProvider?(s.setSession=c,r.authTokenProvider().then(async r=>{if(i)return;if(!r){m();return}let a=await U(e,t,r,!1,w());await s.login(a.rawToken,{loadOnboardingStatus:n}),i||c(s.clone())}).catch(n=>{i||_(n)||(console.warn(`Auth: failed to initialize injected auth token`,n),r.logger.warn(`Failed to initialize injected auth token`,{vertesia:{account_id:e,project_id:t,error:n}}),s.isLoading=!1,s.authError=n instanceof Error?n:Error(String(n)),c(s.clone()))}),()=>{i=!0,f.current=!1,p?.()}):(m(),()=>{i=!0,f.current=!1,p?.()})},p(()=>m.current?.(),[]),ne($.Provider,{value:s,children:e})}function Me(){return{tagUserSession:async e=>{let t=window.localStorage.getItem(`composableSignupData`);if(!e){console.error(`No user found -- skipping tagging`);return}t&&window.localStorage.removeItem(`composableSignupData`)},trackEvent:(e,t)=>{r.isProd||console.debug(`track event`,e,t),r.firebase?.apiKey&&o(de(),e,{...t,debug_mode:!r.isProd})}}}export{Y as AuthenticationServiceError,J as CredentialError,Oe as DevSessionProvider,v as LastSelectedAccountId_KEY,y as LastSelectedProjectId_KEY,q as NoAccessibleAccountError,K as RequestedScopeUnavailableError,X as RestrictedEnvironmentError,G as STSError,be as TokenAuthorizationError,W as UserNotFoundError,Q as UserSession,$ as UserSessionContext,je as UserSessionProvider,D as buildCentralAuthRedirectUrl,ce as clearCentralAuthRoundTripMarker,H as fetchComposableToken,_e as fetchComposableTokenFromFirebaseToken,ve as fetchComposableTokenFromVertesiaToken,U as getComposableToken,ye as getCurrentVertesiaToken,de as getFirebaseAnalytics,j as getFirebaseApp,M as getFirebaseAuth,pe as getFirebaseAuthToken,we as isAuthenticationServiceError,le as isCentralAuthRedirectPending,Ce as isCredentialError,Se as isNoAccessibleAccountError,xe as isRequestedScopeUnavailableError,O as redirectToCentralAuth,R as resolveAuthSelection,Ae as sanitizeRejectedScopeUrl,fe as setFirebaseTenant,Z as useAuthState,Ee as useCurrentTenant,Me as useUXTracking,Te as useUserSession};
|
|
1
|
+
import{NO_ACCESSIBLE_ACCOUNT_ERROR_CODE as e,REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE as t,RESTRICTED_ENVIRONMENT_ERROR_CODE as n}from"@vertesia/common";import{Env as r}from"@vertesia/ui/env";import{jwtDecode as i}from"jwt-decode";import{getAnalytics as a,logEvent as o}from"firebase/analytics";import{initializeApp as s}from"firebase/app";import{getAuth as c,onAuthStateChanged as l}from"firebase/auth";import{createContext as u,useCallback as d,useContext as f,useEffect as p,useMemo as m,useRef as h,useState as g}from"react";import{NAMESPACE as ee,i18nInstance as _}from"@vertesia/ui/i18n";import{VertesiaClient as te}from"@vertesia/client";import{jsx as ne}from"react/jsx-runtime";const v=`composableai.lastSelectedAccountId`,y=`composableai.lastSelectedProjectId`,b=`auth_state`,x=`auth_state_expiry`;function S(){let e=crypto.randomUUID();return sessionStorage.setItem(b,e),sessionStorage.setItem(x,String(Date.now()+3e5)),e}function re(e){if(!e)return`Missing state`;let t=sessionStorage.getItem(b),n=parseInt(sessionStorage.getItem(x)||`0`,10);if(t!==e)return`State mismatched (${t} !== ${e})`;if(Date.now()>n)return`State expired`}function ie(){sessionStorage.removeItem(b),sessionStorage.removeItem(x)}function C(){return r.endpoints.auth||`https://auth.vertesia.io/`}function ae(e){return window.AUTH_MODE===`firebase`}function w(e){return!ae()}function T(){let e=new URL(document.baseURI),t=new URL(window.location.href),n=t.pathname.startsWith(e.pathname)?t:e;return n.hash=``,n}function E(){let e=new URL(document.baseURI);return e.hash=``,e.search=``,e}function D(e,t,n,r,i={}){let a=new URL(n);i.projectId&&a.searchParams.set(`p`,i.projectId),i.accountId&&a.searchParams.set(`a`,i.accountId);let o=new URL(e);return o.searchParams.set(`sts`,t),o.searchParams.set(`redirect_uri`,a.toString()),o.searchParams.set(`state`,r),o}function oe(e,t){return`vtsauth=${e}; Path=/; Max-Age=${t}; SameSite=Lax${window.location.protocol===`https:`?`; Secure`:``}`}function se(){document.cookie=oe(`1`,300)}function ce(){document.cookie=oe(``,0)}function O(e={}){let t=D(C(),r.endpoints.sts??`https://sts.vertesia.io`,T(),S(),e);se(),location.replace(t.toString())}function le(){if(!w()||r.authTokenProvider||r.isLocalDev&&r.devAuthToken)return!1;let e=new URLSearchParams(window.location.hash.substring(1));return!(e.get(`token`)&&e.get(`state`))}let k=null,ue=null,A=null;function j(){if(!k)try{if(!r.firebase)throw Error(`Firebase configuration is not available in the environment`);k=s(r.firebase)}catch(e){throw console.error(`Failed to initialize Firebase app:`,e),Error(`Firebase initialization failed - environment may not be properly initialized`,{cause:e})}return k}function de(){return ue||=a(j()),ue}function M(){return A||(A=c(j()),A.tenantId=r.firebase?.tenantId??null),A}async function fe(e){if(r.firebase?.tenantId)return M().tenantId=r.firebase.tenantId,{firebaseTenantId:r.firebase.tenantId,provider:r.firebase.providerType??`oidc`,name:``,label:``};if(!e){console.log(`No tenant name or email specified, skipping tenant setup`);return}if(!r.firebase){console.log(`Firebase configuration is not available in the environment`);return}try{e&&console.log(`Resolving tenant ID from email: ${e}`);let t=3,n=250;for(;t>0;)try{let t=await fetch(`/api/resolve-tenant`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tenantEmail:e}),signal:AbortSignal.timeout(5e3)});if(!t)throw Error(`No response received from tenant API`);if(!t.ok){try{let e=await t.json();console.error(`Failed to resolve tenant ID:`,e.error)}catch{console.error(`Failed to resolve tenant ID: HTTP ${t.status}`)}if(t.status===404){console.warn(`Tenant not found for ${e}`);return}throw Error(`HTTP error ${t.status}`)}let n=await t.json();if(n?.firebaseTenantId){let e=M();return e.tenantId=n.firebaseTenantId,r.firebase.providerType=n.provider??`oidc`,console.log(`Tenant ID set to ${e.tenantId}`),n}console.error(`Invalid response format, missing tenantId for ${e}`);return}catch(e){if(t>1)console.warn(`Tenant resolution failed, retrying in ${n}ms...`,e),await new Promise(e=>setTimeout(e,n)),n*=2,t--;else throw e}}catch(e){console.error(`Error setting Firebase tenant:`,e instanceof Error?e.message:`Unknown error`)}}async function pe(e){let t=M().currentUser;return t?t.getIdToken(e).then(n=>(r.logger.debug(`Got Firebase token`,{vertesia:{user_email:t.email,user_name:t.displayName,user_id:t.uid,refresh:e}}),n)).catch(n=>(r.logger.error(`Failed to get Firebase token`,{vertesia:{user_email:t.email,user_name:t.displayName,user_id:t.uid,refresh:e,error:n}}),console.error(`Failed to get access token`,n),null)):(r.logger.warn(`No user found`),Promise.resolve(null))}let N,P,F=!1;const I=`vt.centralAuthRenewalAt`;function L(){if(!F||!w())return!1;let e=0;try{e=Number(sessionStorage.getItem(I)??`0`)}catch{}if(Number.isFinite(e)&&e>0&&Date.now()-e<6e4)return r.logger.warn(`Central Auth session renewal already attempted; surfacing the failure instead`,{vertesia:{last_attempt_ms_ago:Date.now()-e}}),!1;try{sessionStorage.setItem(I,String(Date.now()))}catch{}r.logger.info(`Renewing an expired Central Auth session through the broker`);let t=D(C(),r.endpoints.sts??`https://sts.vertesia.io`,T(),S());return location.replace(t.toString()),!0}function me(e,t){if(!e)return;let n=`${y}-${e}`;if(t){let r=localStorage.getItem(n)===t;r&&localStorage.removeItem(n),r&&localStorage.getItem(`composableai.lastSelectedAccountId`)===e&&localStorage.removeItem(v);return}localStorage.getItem(`composableai.lastSelectedAccountId`)===e&&(localStorage.removeItem(v),localStorage.removeItem(n))}function he(e){try{let t=i(e);return typeof t.email!=`string`||!t.email?void 0:{email:t.email,name:typeof t.name==`string`&&t.name?t.name:void 0}}catch{return}}function R(e){let t=e.searchParams.has(`a`)||e.searchParams.has(`p`)?void 0:r.defaultAuthSelection,n=e.searchParams.get(`a`)??t?.accountId,i=e.searchParams.get(`p`)??t?.projectId,a=n??(i===void 0?localStorage.getItem(`composableai.lastSelectedAccountId`)??void 0:void 0);return{accountId:a,projectId:i??localStorage.getItem(`composableai.lastSelectedProjectId-${a}`)??void 0}}function z(e){return e?.replace(/\/+$/,``)}function B(e){return i(e)}function V(e){if(!e)return!1;try{return z(B(e).iss)===z(r.endpoints.sts)}catch{return!1}}function ge(e,t,n){let r=B(e);return!(!r.exp||r.exp<=Date.now()/1e3+300||!(r.permissions?.length||r.account_roles?.length||r.project_roles?.length||r.apps?.length)||t&&r.account?.id!==t||n&&r.project?.id!==n)}async function H(a,o,s,c,l=0){r.logger.debug(`Getting/refreshing composable token`,{vertesia:{account_id:o,project_id:s,retry_count:l}});let u=await a();if(!u)throw Error(`No id token found`);let d=r.endpoints.sts;r.logger.debug(`Using STS for token generation`,{vertesia:{account_id:o,project_id:s,sts_url:d}});try{let f=new URL(`${d}/token/issue`),p={type:`user`,account_id:o,project_id:s,expires_at:c?Math.floor(Date.now()/1e3)+c:void 0},m=await fetch(f,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${u}`},body:JSON.stringify(p)}).catch(e=>{throw console.error(`Failed to call STS endpoint`,e),r.logger.error(`Failed to call STS endpoint`,{vertesia:{account_id:o,project_id:s,error:e}}),new Y(`The authentication service could not be reached`,d,{cause:e})});if(u&&m?.status===404){r.logger.debug(`404: User not found - calling ensure-user endpoint`,{vertesia:{account_id:o,project_id:s,status:m?.status}});let e=await fetch(`${r.endpoints.studio}/auth/ensure-user`,{method:`POST`,headers:{Authorization:`Bearer ${u}`,"Content-Type":`application/json`}});if(e.status===412){r.logger.debug(`412: No invite found - signup required`,{vertesia:{account_id:o,project_id:s}});let e=i(u);throw e?.email?new W(`User not found - signup required`,e.email):(r.logger.error(`No email found in id token`),Error(`No email found in id token`))}if(e.status===403)throw r.logger.warn(`403: Customer-domain user requires an invite to join`,{vertesia:{account_id:o,project_id:s}}),Error(`Customer-domain user requires an invite to join`);if(!e.ok)throw console.error(`Failed to ensure user exists`,e.status),r.logger.error(`Failed to ensure user exists`,{vertesia:{account_id:o,project_id:s,status:e.status}}),Error(`Failed to ensure user exists`);return r.logger.debug(`User ensured - retrying token generation`,{vertesia:{account_id:o,project_id:s}}),H(a,o,s,c,l)}if(u&&m?.status===412){r.logger.info(`412: auth succeeded but user doesn't exist - signup required`,{vertesia:{account_id:o,project_id:s,status:m?.status}});let e=i(u);throw e?.email?(r.logger.info(`User not found`,{vertesia:{account_id:o,project_id:s,email:e.email}}),new W(`User not found`,e.email)):(r.logger.error(`No email found in id token`),Error(`No email found in id token`))}if(m.status===401)throw r.logger.warn(`STS rejected the identity credential`,{vertesia:{account_id:o,project_id:s,status:m.status}}),new J(`Your sign-in credentials could not be verified`,d,{status:m.status});if(m.status===403){let i=await m.json().catch(()=>void 0),a=he(u);throw i?.errorCode===n?(r.logger.warn(`403: User lacks early-access for this restricted environment`,{vertesia:{account_id:o,project_id:s}}),new X(i.message??`You don't have sufficient permission to visit unstable environments`)):i?.errorCode===e?(r.logger.warn(`STS identity has no accessible account`,{vertesia:{error_code:i.errorCode,status:m.status}}),new q(d,i.message,a)):!i?.errorCode||i.errorCode===t?(r.logger.warn(`STS rejected the requested account or project`,{vertesia:{account_id:o,project_id:s,status:m.status,error_code:i?.errorCode??t}}),new K(d,o,s,i?.errorCode?i.message:void 0,a)):(r.logger.error(`STS returned an unrecognized forbidden response`,{vertesia:{account_id:o,project_id:s,status:m.status,error_code:i.errorCode}}),new Y(`The authentication service rejected the request`,d,{status:m.status,errorCode:i.errorCode,cause:i}))}if(!m.ok){let e=await m.text();throw console.error(`STS token generation failed:`,m.status,e),r.logger.error(`STS token generation failed`,{vertesia:{status:m.status,error:e,account_id:o,project_id:s}}),new Y(`The authentication service could not complete sign-in`,d,{status:m.status,cause:e})}let{token:h}=await m.json();return r.logger.debug(`Successfully got token from STS`),h}catch(e){throw e instanceof W||e instanceof G||e instanceof X||e instanceof Error&&e.message===`Customer-domain user requires an invite to join`?e:(console.error(`Failed to get composable token from STS`,e),r.logger.error(`Failed to get composable token from STS`,{vertesia:{account_id:o,project_id:s,error:e}}),new Y(`The authentication service could not complete sign-in`,d,{cause:e}))}}async function _e(e,t,n){return H(pe,e,t,n)}async function ve(e,t,n,r){return H(()=>Promise.resolve(e),t,n,r)}function ye(){return N}async function U(e,t,n,i=!1,a=!1){let o=e??(t===void 0?localStorage.getItem(`composableai.lastSelectedAccountId`)??void 0:void 0),s=t??localStorage.getItem(`composableai.lastSelectedProjectId-${o}`)??void 0,c=r.isLocalDev?r.devAuthToken:void 0,l=c??n??N,u=(!o||P?.account?.id===o)&&(!s||P?.project?.id===s);if(!i&&N&&P&&u&&P.exp>Date.now()/1e3+300)return F=!0,{rawToken:N,token:P,error:!1};if(!i&&V(l)&&ge(l,o,s)){if(N=l,P=B(N),!P.exp)throw Error(`Invalid composable token`);return F=!0,{rawToken:N,token:P,error:!1}}try{if(!c&&!a&&M().currentUser)N=await _e(o,s);else if(c)c&&(N=c);else{let e=await r.authTokenProvider?.()??n??N;N=!i&&e&&V(e)&&ge(e,o,s)?e:e?await H(()=>Promise.resolve(e),o,s):void 0}}catch(e){throw(e instanceof K||e instanceof q||e instanceof J)&&(N=void 0,P=void 0,e instanceof K&&me(o,s)),e instanceof J&&L(),e}if(!N)throw r.logger.error(`Cannot acquire a composable token`,{vertesia:{account_id:o,project_id:s}}),L(),Error(`Cannot acquire a composable token`);if(P=B(N),!P?.exp||!N)throw console.error(`Invalid composable token`,P),r.logger.error(`Invalid composable token`,{vertesia:{account_id:o,project_id:s}}),Error(`Invalid composable token`);return F=!0,{rawToken:N,token:P,error:!1}}var W=class extends Error{email;constructor(e,t){super(e),this.name=`UserNotFoundError`,this.email=t}},G=class extends Error{stsURL;status;errorCode;constructor(e,t,n){super(e,n),this.name=`STSError`,this.stsURL=t,this.status=n?.status,this.errorCode=n?.errorCode}},K=class extends G{accountId;projectId;identity;requestedScope;selectorSource;recoveryUrl;constructor(e,n,r,i,a){super(i??`The requested account or project is not available.`,e,{status:403,errorCode:t}),this.accountId=n,this.projectId=r,this.identity=a,this.name=`RequestedScopeUnavailableError`,this.requestedScope=r?`project`:`account`}};const be=K;var q=class extends G{identity;constructor(t,n,r){super(n??`No accessible account is available for this user.`,t,{status:403,errorCode:e}),this.identity=r,this.name=`NoAccessibleAccountError`}},J=class extends G{constructor(e,t,n){super(e,t,n),this.name=`CredentialError`}},Y=class extends G{constructor(e,t,n){super(e,t,n),this.name=`AuthenticationServiceError`}};function xe(e){return e instanceof K}function Se(e){return e instanceof q}function Ce(e){return e instanceof J}function we(e){return e instanceof Y}var X=class extends Error{constructor(e){super(e),this.name=`RestrictedEnvironmentError`}};function Z(){return{generateState:d(()=>S(),[]),verifyState:d(e=>re(e),[]),clearState:d(()=>ie(),[])}}var Q=class e{isLoading=!0;client;authError;authToken;setSession;lastSelectedAccount;lastSelectedProject;onboardingComplete;constructor(e,t){this.client=e||new te({serverUrl:r.endpoints.studio,storeUrl:r.endpoints.zeno,tokenServerUrl:r.endpoints.sts}),t&&(this.setSession=t),this.logout=this.logout.bind(this),this.signOut=this.signOut.bind(this)}get store(){return this.client.store}get user(){return this.authToken}get account(){return this.authToken?.account}get project(){return this.authToken?.project}get accounts(){return this.authToken?.accounts}get authCallback(){return this.rawAuthToken.then(e=>`Bearer ${e}`)}get rawAuthToken(){return U().then(e=>{let t=e?.rawToken;if(!t)throw Error(`No token available`);return this.authToken=i(t),t})}async refreshAuthToken(){let e=(await U(void 0,void 0,void 0,!0))?.rawToken;if(!e)throw Error(`No token available`);return this.authToken=i(e),this.setSession?.(this.clone()),this.authToken}signOut(){this.logout()}getAccount(){return this.authToken?.account}async login(e,t={}){return this.authError=void 0,this.isLoading=!1,this.client.withAuthCallback(()=>this.authCallback),this.authToken=i(e),console.log(`Logging in as ${this.authToken?.name} with account ${this.authToken?.account.name} (${this.authToken?.account.id}, and project ${this.authToken?.project?.name} (${this.authToken?.project?.id})`),localStorage.setItem(v,this.authToken.account.id),localStorage.setItem(`${y}-${this.authToken.account.id}`,this.authToken.project?.id??``),r.onLogin?.(this.authToken),(t.loadOnboardingStatus??!0)&&await this.fetchOnboardingStatus(),Promise.resolve()}isLoggedIn(){return!!this.authToken}logout(){if(console.log(`Logging out`),w()){console.log(`Using central auth logout`),this.authError=void 0,this.isLoading=!1,this.authToken=void 0,this.setSession=void 0,this.client.withAuthCallback(void 0);let e=new URL(C()),t=T();e.pathname=`/logout`,e.searchParams.set(`redirect_uri`,t.toString()),location.replace(e.toString())}else{console.log(`Using Firebase logout`);let e=!!this.authToken;this.authToken&&M().signOut(),this.authError=void 0,this.isLoading=!1,this.authToken=void 0,this.setSession=void 0,this.client.withAuthCallback(void 0),e&&location.replace(E().toString())}}async switchAccount(e){localStorage.setItem(v,e),this&&(this.account&&this.project?localStorage.setItem(`${y}-${this.account.id}`,this.project.id):this.account&&localStorage.removeItem(`${y}-${this.account.id}`));let t=E();t.searchParams.set(`a`,e),window.location.replace(t.toString())}async switchProject(e){this.account&&localStorage.setItem(`${y}-${this.account.id}`,e);let t=E();this.account?.id&&t.searchParams.set(`a`,this.account.id),t.searchParams.set(`p`,e),window.location.replace(t.toString())}async fetchAccounts(){return this.client.accounts.list().then(e=>{if(!this.authToken)throw Error(`No token available`);this.authToken.accounts=e,this.setSession?.(this.clone())}).catch(e=>{throw console.error(`Failed to fetch accounts`,e),e})}async fetchProjects(e){return this.client.projects.list([e]).then(t=>{if(!this.authToken)throw Error(`No token available`);this.authToken={...this.authToken,accounts:this.authToken.accounts?.map(n=>n.id===e?{...n,projects:t.filter(t=>t.account===e)}:n)},this.setSession?.(this.clone())}).catch(e=>{throw console.error(`Failed to fetch projects`,e),e})}async fetchOnboardingStatus(){if(!this.project?.id)return!1;if(this.onboardingComplete)return console.log(`Onboarding already completed`),!1;let e=this.onboardingComplete;try{let t=await this.client.account.onboardingProgress();return this.onboardingComplete=Object.values(t).every(e=>e===!0),e!==this.onboardingComplete&&this.setSession?.(this.clone()),e===!1&&this.onboardingComplete}catch(t){console.error(`Error fetching onboarding status:`,t),this.onboardingComplete=!1,e!==this.onboardingComplete&&this.setSession?.(this.clone())}return!1}clone(){let t=new e(this.client);return t.isLoading=this.isLoading,t.authError=this.authError,t.authToken=this.authToken,t.setSession=this.setSession,t.lastSelectedAccount=this.lastSelectedAccount,t.switchAccount=this.switchAccount,t.onboardingComplete=this.onboardingComplete,t}};const $=u(void 0);function Te(){let e=f($);if(!e)throw Error(`useUserSession must be used within a UserSessionProvider`);return e}function Ee(){let{user:e}=Te(),[t,n]=g(null),[r,i]=g(!0),[a,o]=g(null);return p(()=>{(async()=>{if(!e?.email){n(null),i(!1);return}try{let t=await fetch(`/api/resolve-tenant`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tenantEmail:e.email})});if(t.ok){let e=await t.json();n(e?{tenantKey:e.name||`unknown`,name:e.label||e.name||`Unknown`,domain:e.domain||[],firebaseTenantId:e.firebaseTenantId,provider:e.provider,logo:e.logo}:null)}else n(null)}catch(e){console.error(`Error loading current tenant:`,e),o(_.getFixedT(null,ee)(`errors.failedToLoadTenantConfig`)),n(null)}finally{i(!1)}})()},[e?.email]),{currentTenant:t,isLoading:r,error:a}}function De(e){let[,t]=e.split(`.`);if(!t)throw Error(`Invalid Vertesia auth token`);let n=t.replace(/-/g,`+`).replace(/_/g,`/`).padEnd(Math.ceil(t.length/4)*4,`=`);return JSON.parse(atob(n))}function Oe({children:e,token:t}){let n=m(()=>{let e=new Q;e.isLoading=!1;try{e.authToken=De(t),e.client.withAuthCallback(()=>Promise.resolve(`Bearer ${t}`)),localStorage.setItem(v,e.authToken.account.id),localStorage.setItem(`${y}-${e.authToken.account.id}`,e.authToken.project?.id??``)}catch(t){e.authError=t instanceof Error?t:Error(String(t))}return e.clone()},[t]);return ne($.Provider,{value:n,children:e})}function ke(){let e=new URL(window.location.href);e.hash=``,window.history.replaceState(window.history.state,``,`${e.pathname}${e.search}`)}function Ae(e,t,n=!1){let r=new URL(e),i=r.searchParams.get(`a`)??void 0,a=r.searchParams.get(`p`)??void 0;return i&&a&&i===t.accountId&&a===t.projectId?(r.searchParams.delete(`a`),r.searchParams.delete(`p`),t.requestedScope=`project`,t.selectorSource=`url`):a&&a===t.projectId?(r.searchParams.delete(`p`),t.requestedScope=`project`,t.selectorSource=`url`):i&&i===t.accountId?(r.searchParams.delete(`a`),t.requestedScope=`account`,t.selectorSource=`url`):(t.requestedScope=t.projectId?`project`:`account`,t.selectorSource=`persisted`),n&&(r.hash=``),t.recoveryUrl=r.toString(),r}function je({children:e,loadOnboardingStatus:n=!0}){let i=new URLSearchParams(location.hash.substring(1)),a=i.get(`token`),o=i.get(`state`),[s,c]=g(new Q),{verifyState:u,clearState:d}=Z(),f=h(!1),m=h(void 0),ee=(e,n=!1)=>{let i=Ae(new URL(window.location.href),e,n);window.history.replaceState(window.history.state,``,i),r.logger.warn(`Sanitized an unavailable authentication scope`,{vertesia:{account_id:e.accountId,project_id:e.projectId,error_code:t,selector_source:e.selectorSource,requested_scope:e.requestedScope,recovery_action:`url_sanitized`}})},_=(e,t=!1)=>e instanceof K||e instanceof q||e instanceof J||e instanceof Y||e instanceof W||e instanceof X?(e instanceof K?ee(e,t):t&&ke(),s.isLoading=!1,s.authError=e,c(s.clone()),!0):!1;return m.current=()=>{if(f.current){r.logger.debug(`Skipping duplicate auth flow initiation`);return}f.current=!0,r.logger.debug(`Starting auth flow`);let{accountId:e,projectId:t}=R(new URL(window.location.href));if(r.logger.debug(`Selected account and project`,{vertesia:{account_id:e,project_id:t}}),r.isLocalDev&&r.devAuthToken){s.setSession=c,U(e,t,r.devAuthToken).then(e=>s.login(e.rawToken).then(()=>c(s.clone()))).catch(n=>{_(n)||(console.error(`Failed to initialize dev auth token`,n),r.logger.error(`Failed to initialize dev auth token`,{vertesia:{account_id:e,project_id:t,error:n}}),s.isLoading=!1,s.authError=n instanceof Error?n:Error(String(n)),c(s.clone()))});return}if(a&&o){s.setSession=c;let i=u(o);i?(console.error(`Auth: invalid state: ${i}`),r.logger.error(`Invalid state: ${i}`,{vertesia:{state:o}}),O({accountId:e,projectId:t})):d(),U(e,t,a,!1,w()).then(e=>s.login(e.rawToken,{loadOnboardingStatus:n}).then(()=>{c(s.clone()),ke()})).catch(n=>{_(n,!0)||(console.error(`Failed to fetch user token from studio, redirecting to central auth`,n),r.logger.error(`Failed to fetch user token from studio, redirecting to central auth`,{vertesia:{error:n}}),O({accountId:e,projectId:t}))});return}let i=!1,p,m=()=>{if(!i){if(!s.isLoggedIn()){if(r.logger.debug(`Not logged in & no token/state`,{vertesia:{account_id:e,project_id:t}}),w()){r.logger.debug(`Redirecting to central auth with selection`,{vertesia:{account_id:e,project_id:t}}),O({accountId:e,projectId:t});return}r.logger.debug(`Host is in Firebase auth allowlist`,{vertesia:{account_id:e,project_id:t}})}p=l(M(),async i=>{i?(r.logger.debug(`Successful login with firebase`,{vertesia:{account_id:e,project_id:t}}),s.setSession=c,await U(e,t,void 0,!1,w()).then(e=>s.login(e.rawToken,{loadOnboardingStatus:n}).then(()=>c(s.clone()))).catch(n=>{_(n)||(console.error(`Unexpected failure while fetching a user token`,n),r.logger.error(`Unexpected failure while fetching a user token`,{vertesia:{account_id:e,project_id:t,error:n}}),s.logout(),s.isLoading=!1,s.authError=n,c(s.clone()))})):(r.logger.debug(`Using anonymous user`,{vertesia:{account_id:e,project_id:t}}),s.client.withAuthCallback(void 0),s.logout(),c(s.clone()))})}};return r.authTokenProvider?(s.setSession=c,r.authTokenProvider().then(async r=>{if(i)return;if(!r){m();return}let a=await U(e,t,r,!1,w());await s.login(a.rawToken,{loadOnboardingStatus:n}),i||c(s.clone())}).catch(n=>{i||_(n)||(console.warn(`Auth: failed to initialize injected auth token`,n),r.logger.warn(`Failed to initialize injected auth token`,{vertesia:{account_id:e,project_id:t,error:n}}),s.isLoading=!1,s.authError=n instanceof Error?n:Error(String(n)),c(s.clone()))}),()=>{i=!0,f.current=!1,p?.()}):(m(),()=>{i=!0,f.current=!1,p?.()})},p(()=>m.current?.(),[]),ne($.Provider,{value:s,children:e})}function Me(){return{tagUserSession:async e=>{let t=window.localStorage.getItem(`composableSignupData`);if(!e){console.error(`No user found -- skipping tagging`);return}t&&window.localStorage.removeItem(`composableSignupData`)},trackEvent:(e,t)=>{r.isProd||console.debug(`track event`,e,t),r.firebase?.apiKey&&o(de(),e,{...t,debug_mode:!r.isProd})}}}export{Y as AuthenticationServiceError,J as CredentialError,Oe as DevSessionProvider,v as LastSelectedAccountId_KEY,y as LastSelectedProjectId_KEY,q as NoAccessibleAccountError,K as RequestedScopeUnavailableError,X as RestrictedEnvironmentError,G as STSError,be as TokenAuthorizationError,W as UserNotFoundError,Q as UserSession,$ as UserSessionContext,je as UserSessionProvider,D as buildCentralAuthRedirectUrl,ce as clearCentralAuthRoundTripMarker,H as fetchComposableToken,_e as fetchComposableTokenFromFirebaseToken,ve as fetchComposableTokenFromVertesiaToken,U as getComposableToken,ye as getCurrentVertesiaToken,de as getFirebaseAnalytics,j as getFirebaseApp,M as getFirebaseAuth,pe as getFirebaseAuthToken,we as isAuthenticationServiceError,le as isCentralAuthRedirectPending,Ce as isCredentialError,Se as isNoAccessibleAccountError,xe as isRequestedScopeUnavailableError,O as redirectToCentralAuth,R as resolveAuthSelection,Ae as sanitizeRejectedScopeUrl,fe as setFirebaseTenant,Z as useAuthState,Ee as useCurrentTenant,Me as useUXTracking,Te as useUserSession};
|
|
2
2
|
//# sourceMappingURL=vertesia-ui-session.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertesia-ui-session.js","names":["_jsx","_jsx"],"sources":["session/constants.js","session/auth/authState.js","session/auth/domainRouting.js","session/auth/firebase.js","session/auth/composable.js","session/auth/useAuthState.js","session/UserSession.js","session/auth/useCurrentTenant.js","session/DevSessionProvider.js","session/UserSessionProvider.js","session/useUXTracking.js"],"sourcesContent":["export const LastSelectedAccountId_KEY = 'composableai.lastSelectedAccountId';\nexport const LastSelectedProjectId_KEY = 'composableai.lastSelectedProjectId';\n//# sourceMappingURL=constants.js.map","/**\n * The `state` parameter binding a Central Auth round-trip to the tab that started it.\n *\n * Plain functions rather than a hook, because the round-trip is not always started from a\n * component: an expired session is renewed from the token layer, which has no React context to\n * call {@link useAuthState} from. The hook wraps these so both paths write the same record.\n */\nconst AUTH_STATE_KEY = 'auth_state';\nconst STATE_EXPIRY_KEY = 'auth_state_expiry';\nconst STATE_TTL = 5 * 60 * 1000; // 5 min\n/** Mint a state value and record it for {@link verifyAuthState}. */\nexport function generateAuthState() {\n const state = crypto.randomUUID();\n sessionStorage.setItem(AUTH_STATE_KEY, state);\n sessionStorage.setItem(STATE_EXPIRY_KEY, String(Date.now() + STATE_TTL));\n return state;\n}\n/** The reason `returnedState` is unacceptable, or undefined when it is good. */\nexport function verifyAuthState(returnedState) {\n if (!returnedState) {\n return 'Missing state';\n }\n const savedState = sessionStorage.getItem(AUTH_STATE_KEY);\n const expiryTime = parseInt(sessionStorage.getItem(STATE_EXPIRY_KEY) || '0', 10);\n if (savedState !== returnedState) {\n return `State mismatched (${savedState} !== ${returnedState})`;\n }\n if (Date.now() > expiryTime) {\n return 'State expired';\n }\n return undefined;\n}\nexport function clearAuthState() {\n sessionStorage.removeItem(AUTH_STATE_KEY);\n sessionStorage.removeItem(STATE_EXPIRY_KEY);\n}\n//# sourceMappingURL=authState.js.map","import { Env } from '@vertesia/ui/env';\nimport { generateAuthState } from './authState';\n/**\n * The broker this app sends users to for sign-in and logout.\n *\n * Hard-coded until now, which meant every consumer of this package reached the same deployment no\n * matter what was running. It is read from the environment so a single app can be pointed at a\n * different broker, and it keeps this default so an app that configures nothing is unaffected.\n */\nexport const DEFAULT_CENTRAL_AUTH_URL = 'https://internal-auth.vertesia.app/';\nexport function centralAuthUrl() {\n const configured = Env.endpoints.auth;\n return configured ? configured : DEFAULT_CENTRAL_AUTH_URL;\n}\nexport function shouldUseFirebaseAuth(_hostname) {\n return window.AUTH_MODE === 'firebase';\n}\nexport function shouldRedirectToCentralAuth(_hostname) {\n return !shouldUseFirebaseAuth();\n}\n/**\n * The page URL to return to after a central-auth round-trip (`redirect_uri`).\n *\n * Apps served under a deep gateway mount carry a `<base href=\"/tenants/<t>/apps/<app>/.../app/\">`\n * that the app-gateway injects at serve time. The in-app router rewrites the address bar relative\n * to the origin rather than that mount, so by the time the auth flow runs `window.location` can\n * read as the bare origin `/`. Building `redirect_uri` from that drops the app path, and the\n * post-login token bounces back to a URL that serves no app.\n *\n * Prefer the live location when it still sits under the mount (preserves the in-app deep route);\n * otherwise fall back to `document.baseURI` — the mount, which the router cannot clobber. With no\n * `<base>` element (the Studio UI), `document.baseURI` is the document URL and its `pathname` is a\n * prefix of the live location, so the live URL is used unchanged — no behavior change there.\n */\nexport function authReturnUrl() {\n const base = new URL(document.baseURI);\n const current = new URL(window.location.href);\n const target = current.pathname.startsWith(base.pathname) ? current : base;\n target.hash = '';\n return target;\n}\n/**\n * The app's mount root URL — the served `<base href>` (or the origin root for the Studio UI).\n *\n * Used for full-reload navigations that intentionally reset to the app root (logout, account /\n * project switch). Building these from a bare `/` drops a gateway-mounted app off its mount and\n * lands on a URL that serves no app; resolving against `document.baseURI` keeps the reload inside\n * the mount. For the Studio UI (no `<base>` element) `document.baseURI` is the origin root, so the\n * behavior is unchanged.\n */\nexport function mountRootUrl() {\n const url = new URL(document.baseURI);\n url.hash = '';\n url.search = '';\n return url;\n}\n/**\n * The broker URL a sign-in or renewal round-trip navigates to.\n *\n * Every parameter goes on through `searchParams`, never by concatenating a query string onto\n * `centralAuth`. The endpoint is configurable, so it may already carry its own query or a\n * fragment -- and appending `?sts=...` to one of those folds the parameter into the existing value\n * or hides it in the fragment, leaving Central Auth with no `sts` at all. `searchParams` also\n * percent-encodes the values, which the interpolated URLs did not.\n */\nexport function buildCentralAuthRedirectUrl(centralAuth, stsUrl, returnUrl, state, selection = {}) {\n const selectedReturnUrl = new URL(returnUrl);\n if (selection.projectId)\n selectedReturnUrl.searchParams.set('p', selection.projectId);\n if (selection.accountId)\n selectedReturnUrl.searchParams.set('a', selection.accountId);\n const url = new URL(centralAuth);\n url.searchParams.set('sts', stsUrl);\n url.searchParams.set('redirect_uri', selectedReturnUrl.toString());\n url.searchParams.set('state', state);\n return url;\n}\n/**\n * A short-lived marker saying \"a Central Auth round-trip is in flight for this browser\".\n *\n * It exists for the server that generates index.html. Preload hints are markup, so they start\n * fetching before any script runs: a page load that is going to bounce off to the broker cannot\n * avoid downloading the application by any decision made in the page itself. The server can, but\n * only if it can tell the outgoing leg from the return leg -- and the token comes back in the URL\n * *fragment*, which a server never sees. Hence a cookie: set here, immediately before navigating to\n * the broker, and cleared by the app as soon as it mounts.\n *\n * It carries no identity and grants nothing; it is a hint about which of two page loads this is.\n * The TTL bounds the cost of the case where the app never mounts (the visitor abandons the login\n * screen): after it lapses, cold loads are back to withholding the application preloads.\n */\nconst AUTH_ROUND_TRIP_COOKIE = 'vtsauth';\nconst AUTH_ROUND_TRIP_TTL_SECONDS = 300;\nfunction authRoundTripCookie(value, maxAgeSeconds) {\n // Secure only over https: setting it on http://localhost would make the browser drop the\n // cookie, and local development would silently lose the return-leg preloads.\n const secure = window.location.protocol === 'https:' ? '; Secure' : '';\n return `${AUTH_ROUND_TRIP_COOKIE}=${value}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax${secure}`;\n}\n/** Record that this browser is on its way to the broker. Called for you by {@link redirectToCentralAuth}. */\nexport function markCentralAuthRoundTripStarted() {\n // biome-ignore lint/suspicious/noDocumentCookie: CookieStore is async; this must land before location.replace()\n document.cookie = authRoundTripCookie('1', AUTH_ROUND_TRIP_TTL_SECONDS);\n}\n/**\n * Drop the marker. An app calls this once it is actually rendering, so that its *next* cold load is\n * recognized as an outgoing leg again and does not preload an application it is about to discard.\n */\nexport function clearCentralAuthRoundTripMarker() {\n // biome-ignore lint/suspicious/noDocumentCookie: pairs with the write above; see that comment.\n document.cookie = authRoundTripCookie('', 0);\n}\n/**\n * Start a Central Auth round-trip for the current page.\n *\n * The single place the broker URL is assembled, so the boot-time shortcut in an app's entry module\n * and {@link isCentralAuthRedirectPending}'s counterpart inside `UserSessionProvider` cannot drift\n * into producing different `redirect_uri` / `state` / `sts` values for the same page load.\n */\nexport function redirectToCentralAuth(selection = {}) {\n const url = buildCentralAuthRedirectUrl(centralAuthUrl(), Env.endpoints.sts ?? 'https://sts.vertesia.io', authReturnUrl(), generateAuthState(), selection);\n markCentralAuthRoundTripStarted();\n location.replace(url.toString());\n}\n/**\n * Whether this page load is already certain to end in a Central Auth round-trip, decidable before\n * any application module has run.\n *\n * A fresh `UserSession` holds no token and nothing persists one across loads, so on a cold load\n * `session.isLoggedIn()` in `UserSessionProvider` is always false. Every condition that can keep\n * that provider from redirecting is therefore knowable up front, and this predicate enumerates\n * them in the same order:\n *\n * - Firebase-allowlisted hosts sign in in place rather than at the broker;\n * - a host app that injects a token through `Env.authTokenProvider` may not need the broker\n * (and when its token turns out to be empty the provider still redirects — answering `false`\n * here just lets the normal flow decide, which is the safe direction);\n * - local development with a configured dev token skips auth entirely;\n * - a load carrying `#token=…&state=…` *is* the return leg and must be allowed to exchange it.\n *\n * When this returns true the app is going to navigate away, so an entry module can redirect\n * immediately instead of mounting a full React tree, initializing telemetry and warming route\n * chunks that the navigation is about to discard.\n */\nexport function isCentralAuthRedirectPending() {\n if (!shouldRedirectToCentralAuth())\n return false;\n if (Env.authTokenProvider)\n return false;\n if (Env.isLocalDev && Env.devAuthToken)\n return false;\n const hashParams = new URLSearchParams(window.location.hash.substring(1));\n return !(hashParams.get('token') && hashParams.get('state'));\n}\n//# sourceMappingURL=domainRouting.js.map","import { Env } from '@vertesia/ui/env';\nimport { getAnalytics } from 'firebase/analytics';\nimport { initializeApp } from 'firebase/app';\nimport { getAuth } from 'firebase/auth';\n// Use lazy initialization to avoid accessing Env before it's initialized\nlet _firebaseApp = null;\nlet _analytics = null;\nlet _firebaseAuth = null;\n// Getters that lazily initialize Firebase components when first accessed\nexport function getFirebaseApp() {\n if (!_firebaseApp) {\n try {\n if (!Env.firebase) {\n throw new Error('Firebase configuration is not available in the environment');\n }\n _firebaseApp = initializeApp(Env.firebase);\n }\n catch (error) {\n console.error('Failed to initialize Firebase app:', error);\n throw new Error('Firebase initialization failed - environment may not be properly initialized', {\n cause: error,\n });\n }\n }\n return _firebaseApp;\n}\nexport function getFirebaseAnalytics() {\n if (!_analytics) {\n _analytics = getAnalytics(getFirebaseApp());\n }\n return _analytics;\n}\nexport function getFirebaseAuth() {\n if (!_firebaseAuth) {\n _firebaseAuth = getAuth(getFirebaseApp());\n _firebaseAuth.tenantId = Env.firebase?.tenantId ?? null;\n }\n return _firebaseAuth;\n}\nexport async function setFirebaseTenant(tenantEmail) {\n if (Env.firebase?.tenantId) {\n getFirebaseAuth().tenantId = Env.firebase.tenantId;\n return {\n firebaseTenantId: Env.firebase.tenantId,\n provider: Env.firebase.providerType ?? 'oidc',\n name: '',\n label: '',\n };\n }\n if (!tenantEmail) {\n console.log('No tenant name or email specified, skipping tenant setup');\n return;\n }\n if (!Env.firebase) {\n console.log('Firebase configuration is not available in the environment');\n return;\n }\n try {\n if (tenantEmail)\n console.log(`Resolving tenant ID from email: ${tenantEmail}`);\n // Add retry logic with exponential backoff\n let retries = 3;\n let retryDelay = 250; // Start with 250ms delay\n while (retries > 0) {\n try {\n // Call the API endpoint to resolve the tenant ID\n const response = await fetch('/api/resolve-tenant', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n tenantEmail: tenantEmail,\n }),\n // Add timeout to prevent hanging requests\n signal: AbortSignal.timeout(5000),\n });\n // Check for network errors\n if (!response) {\n throw new Error('No response received from tenant API');\n }\n // Handle HTTP error responses\n if (!response.ok) {\n // Try to parse the error response\n try {\n const errorData = await response.json();\n console.error('Failed to resolve tenant ID:', errorData.error);\n }\n catch {\n console.error(`Failed to resolve tenant ID: HTTP ${response.status}`);\n }\n // If the error is 404 Not Found, no need to retry\n if (response.status === 404) {\n console.warn(`Tenant not found for ${tenantEmail}`);\n return;\n }\n throw new Error(`HTTP error ${response.status}`);\n }\n // Successfully got a response, parse it\n const data = (await response.json());\n if (data?.firebaseTenantId) {\n const auth = getFirebaseAuth();\n auth.tenantId = data.firebaseTenantId;\n Env.firebase.providerType = data.provider ?? 'oidc';\n console.log(`Tenant ID set to ${auth.tenantId}`);\n return data;\n }\n else {\n console.error(`Invalid response format, missing tenantId for ${tenantEmail}`);\n return; // No need to retry for invalid response format\n }\n }\n catch (fetchError) {\n // Only retry for network-related errors\n if (retries > 1) {\n console.warn(`Tenant resolution failed, retrying in ${retryDelay}ms...`, fetchError);\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n retryDelay *= 2; // Exponential backoff\n retries--;\n }\n else {\n throw fetchError; // Last retry failed, propagate error\n }\n }\n }\n }\n catch (error) {\n // Final error handler\n console.error('Error setting Firebase tenant:', error instanceof Error ? error.message : 'Unknown error');\n // Continue without tenant ID - authentication will work without multi-tenancy\n // but the user will access the default tenant\n }\n}\nexport async function getFirebaseAuthToken(refresh) {\n const auth = getFirebaseAuth();\n const user = auth.currentUser;\n if (user) {\n return user\n .getIdToken(refresh)\n .then((token) => {\n Env.logger.debug('Got Firebase token', {\n vertesia: {\n user_email: user.email,\n user_name: user.displayName,\n user_id: user.uid,\n refresh: refresh,\n },\n });\n return token;\n })\n .catch((err) => {\n Env.logger.error('Failed to get Firebase token', {\n vertesia: {\n user_email: user.email,\n user_name: user.displayName,\n user_id: user.uid,\n refresh: refresh,\n error: err,\n },\n });\n console.error('Failed to get access token', err);\n return null;\n });\n }\n else {\n Env.logger.warn('No user found');\n return Promise.resolve(null);\n }\n}\n//# sourceMappingURL=firebase.js.map","/**\n * Handle client caching and refresh of auth token\n */\nimport { NO_ACCESSIBLE_ACCOUNT_ERROR_CODE, REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE, RESTRICTED_ENVIRONMENT_ERROR_CODE, } from '@vertesia/common';\nimport { Env } from '@vertesia/ui/env';\nimport { jwtDecode } from 'jwt-decode';\nimport { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from '../constants';\nimport { generateAuthState } from './authState';\nimport { authReturnUrl, buildCentralAuthRedirectUrl, centralAuthUrl, shouldRedirectToCentralAuth, } from './domainRouting';\nimport { getFirebaseAuth, getFirebaseAuthToken } from './firebase';\nlet AUTH_TOKEN_RAW;\nlet AUTH_TOKEN;\n/**\n * Whether this tab ever held a token.\n *\n * Separates \"the session expired\" from \"there is no session yet\". Only the first is renewed from\n * here; the first load is UserSessionProvider's, and it has the account/project selection that\n * this layer does not.\n */\nlet HAD_SESSION = false;\n/** Session-storage stamp of the last renewal redirect, so the cooldown survives the navigation. */\nconst RENEWAL_ATTEMPT_KEY = 'vt.centralAuthRenewalAt';\nconst RENEWAL_COOLDOWN_MS = 60_000;\n/**\n * Renew an expired Central Auth session by bouncing through the broker.\n *\n * A Central Auth session holds exactly one credential -- the STS JWT, in memory -- and STS refuses\n * it once it has expired. There is no Firebase user in this browser to mint a replacement from and\n * no refresh token, so the only way back is the broker, which still has the user's own session and\n * returns a fresh identity token without prompting. That is precisely what a manual reload does\n * today; doing it here turns a page that dead-ends on \"Cannot acquire a composable token\" into a\n * redirect the user usually does not notice.\n *\n * The cooldown is the important part. If the broker returns a credential STS also rejects, an\n * unguarded redirect bounces the browser between the two forever -- and a redirect loop is a worse\n * failure than the error page it replaced, because it never comes to rest anywhere the user can\n * read. One attempt per minute, recorded in sessionStorage because the module state does not\n * survive the navigation this function starts.\n *\n * Returns whether a redirect was started.\n */\nfunction renewExpiredCentralAuthSession() {\n if (!HAD_SESSION || !shouldRedirectToCentralAuth()) {\n return false;\n }\n let lastAttempt = 0;\n try {\n lastAttempt = Number(sessionStorage.getItem(RENEWAL_ATTEMPT_KEY) ?? '0');\n }\n catch {\n // sessionStorage unavailable -- treated as \"no previous attempt\", so the redirect is\n // allowed once and the failure surfaces normally if it does not stick.\n }\n if (Number.isFinite(lastAttempt) && lastAttempt > 0 && Date.now() - lastAttempt < RENEWAL_COOLDOWN_MS) {\n Env.logger.warn('Central Auth session renewal already attempted; surfacing the failure instead', {\n vertesia: { last_attempt_ms_ago: Date.now() - lastAttempt },\n });\n return false;\n }\n try {\n sessionStorage.setItem(RENEWAL_ATTEMPT_KEY, String(Date.now()));\n }\n catch {\n // Without the stamp the cooldown cannot be enforced across the navigation. Still worth\n // redirecting once: the alternative is a page the user can only fix by reloading manually.\n }\n Env.logger.info('Renewing an expired Central Auth session through the broker');\n const url = buildCentralAuthRedirectUrl(centralAuthUrl(), Env.endpoints.sts ?? 'https://sts.vertesia.io', authReturnUrl(), generateAuthState());\n location.replace(url.toString());\n return true;\n}\nfunction clearRejectedPersistedScope(accountId, projectId) {\n if (!accountId)\n return;\n const projectKey = `${LastSelectedProjectId_KEY}-${accountId}`;\n if (projectId) {\n const persistedProjectMatches = localStorage.getItem(projectKey) === projectId;\n if (persistedProjectMatches) {\n localStorage.removeItem(projectKey);\n }\n if (persistedProjectMatches && localStorage.getItem(LastSelectedAccountId_KEY) === accountId) {\n localStorage.removeItem(LastSelectedAccountId_KEY);\n }\n return;\n }\n if (localStorage.getItem(LastSelectedAccountId_KEY) === accountId) {\n localStorage.removeItem(LastSelectedAccountId_KEY);\n localStorage.removeItem(projectKey);\n }\n}\nfunction identityFromAcceptedToken(token) {\n try {\n const payload = jwtDecode(token);\n if (typeof payload.email !== 'string' || !payload.email)\n return undefined;\n return {\n email: payload.email,\n name: typeof payload.name === 'string' && payload.name ? payload.name : undefined,\n };\n }\n catch {\n return undefined;\n }\n}\nexport function resolveAuthSelection(currentUrl) {\n // A URL selection owns the whole pair: never attach an unrelated configured account/project.\n const hasUrlScope = currentUrl.searchParams.has('a') || currentUrl.searchParams.has('p');\n const defaults = hasUrlScope ? undefined : Env.defaultAuthSelection;\n const urlAccount = currentUrl.searchParams.get('a') ?? defaults?.accountId;\n const urlProject = currentUrl.searchParams.get('p') ?? defaults?.projectId;\n const accountId = urlAccount ??\n (urlProject === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);\n const projectId = urlProject ?? localStorage.getItem(`${LastSelectedProjectId_KEY}-${accountId}`) ?? undefined;\n return { accountId, projectId };\n}\nfunction normalizeIssuer(value) {\n return value?.replace(/\\/+$/, '');\n}\nfunction decodeToken(token) {\n return jwtDecode(token);\n}\nfunction isVertesiaIssuedToken(token) {\n if (!token)\n return false;\n try {\n const decoded = decodeToken(token);\n return normalizeIssuer(decoded.iss) === normalizeIssuer(Env.endpoints.sts);\n }\n catch {\n return false;\n }\n}\nfunction canUseVertesiaTokenDirectly(token, accountId, projectId) {\n const decoded = decodeToken(token);\n if (!decoded.exp || decoded.exp <= Date.now() / 1000 + 300) {\n return false;\n }\n const hasAuthorizationClaims = Boolean(decoded.permissions?.length ||\n decoded.account_roles?.length ||\n decoded.project_roles?.length ||\n decoded.apps?.length);\n if (!hasAuthorizationClaims) {\n return false;\n }\n if (accountId && decoded.account?.id !== accountId) {\n return false;\n }\n if (projectId && decoded.project?.id !== projectId) {\n return false;\n }\n return true;\n}\nexport async function fetchComposableToken(getIdToken, accountId, projectId, ttl, retryCount = 0) {\n Env.logger.debug('Getting/refreshing composable token', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n retry_count: retryCount,\n },\n });\n const idToken = await getIdToken(); //get from firebase\n if (!idToken) {\n throw new Error('No id token found');\n }\n // Use STS endpoint - either configured or default to sts.vertesia.io\n const stsEndpoint = Env.endpoints.sts;\n Env.logger.debug('Using STS for token generation', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n sts_url: stsEndpoint,\n },\n });\n try {\n // Call STS to generate a user token\n const stsUrl = new URL(`${stsEndpoint}/token/issue`);\n const requestBody = {\n type: 'user',\n account_id: accountId,\n project_id: projectId,\n expires_at: ttl ? Math.floor(Date.now() / 1000) + ttl : undefined,\n };\n const stsRes = await fetch(stsUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${idToken}`, // Firebase token for authentication\n },\n body: JSON.stringify(requestBody),\n }).catch((error) => {\n console.error('Failed to call STS endpoint', error);\n Env.logger.error('Failed to call STS endpoint', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n error: error,\n },\n });\n throw new AuthenticationServiceError('The authentication service could not be reached', stsEndpoint, {\n cause: error,\n });\n });\n if (idToken && stsRes?.status === 404) {\n // User not found in token-server - call ensure-user endpoint\n Env.logger.debug('404: User not found - calling ensure-user endpoint', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: stsRes?.status,\n },\n });\n const ensureResponse = await fetch(`${Env.endpoints.studio}/auth/ensure-user`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${idToken}`,\n 'Content-Type': 'application/json',\n },\n });\n if (ensureResponse.status === 412) {\n // No invite - trigger signup\n Env.logger.debug('412: No invite found - signup required', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n },\n });\n const idTokenDecoded = jwtDecode(idToken);\n if (!idTokenDecoded?.email) {\n Env.logger.error('No email found in id token');\n throw new Error('No email found in id token');\n }\n throw new UserNotFoundError('User not found - signup required', idTokenDecoded.email);\n }\n if (ensureResponse.status === 403) {\n // SigninScreen keys the invite-required view off this message.\n Env.logger.warn('403: Customer-domain user requires an invite to join', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n },\n });\n throw new Error('Customer-domain user requires an invite to join');\n }\n if (!ensureResponse.ok) {\n console.error('Failed to ensure user exists', ensureResponse.status);\n Env.logger.error('Failed to ensure user exists', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: ensureResponse.status,\n },\n });\n throw new Error('Failed to ensure user exists');\n }\n // User created/exists - retry token generation\n Env.logger.debug('User ensured - retrying token generation', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n },\n });\n return fetchComposableToken(getIdToken, accountId, projectId, ttl, retryCount);\n }\n if (idToken && stsRes?.status === 412) {\n Env.logger.info(\"412: auth succeeded but user doesn't exist - signup required\", {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: stsRes?.status,\n },\n });\n const idTokenDecoded = jwtDecode(idToken);\n if (!idTokenDecoded?.email) {\n Env.logger.error('No email found in id token');\n throw new Error('No email found in id token');\n }\n Env.logger.info('User not found', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n email: idTokenDecoded.email,\n },\n });\n throw new UserNotFoundError('User not found', idTokenDecoded.email);\n }\n if (stsRes.status === 401) {\n Env.logger.warn('STS rejected the identity credential', {\n vertesia: { account_id: accountId, project_id: projectId, status: stsRes.status },\n });\n throw new CredentialError('Your sign-in credentials could not be verified', stsEndpoint, {\n status: stsRes.status,\n });\n }\n if (stsRes.status === 403) {\n const body = (await stsRes.json().catch(() => undefined));\n const identity = identityFromAcceptedToken(idToken);\n if (body?.errorCode === RESTRICTED_ENVIRONMENT_ERROR_CODE) {\n Env.logger.warn('403: User lacks early-access for this restricted environment', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n },\n });\n throw new RestrictedEnvironmentError(body.message ?? \"You don't have sufficient permission to visit unstable environments\");\n }\n if (body?.errorCode === NO_ACCESSIBLE_ACCOUNT_ERROR_CODE) {\n Env.logger.warn('STS identity has no accessible account', {\n vertesia: { error_code: body.errorCode, status: stsRes.status },\n });\n throw new NoAccessibleAccountError(stsEndpoint, body.message, identity);\n }\n // During rolling deployment an older STS may return an uncoded user-token 403.\n // Treat it as a requested-scope rejection; all calls in this function issue user tokens.\n if (!body?.errorCode || body.errorCode === REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE) {\n Env.logger.warn('STS rejected the requested account or project', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: stsRes.status,\n error_code: body?.errorCode ?? REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,\n },\n });\n throw new RequestedScopeUnavailableError(stsEndpoint, accountId, projectId, body?.errorCode ? body.message : undefined, identity);\n }\n Env.logger.error('STS returned an unrecognized forbidden response', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: stsRes.status,\n error_code: body.errorCode,\n },\n });\n throw new AuthenticationServiceError('The authentication service rejected the request', stsEndpoint, {\n status: stsRes.status,\n errorCode: body.errorCode,\n cause: body,\n });\n }\n if (!stsRes.ok) {\n const errorText = await stsRes.text();\n console.error('STS token generation failed:', stsRes.status, errorText);\n Env.logger.error('STS token generation failed', {\n vertesia: {\n status: stsRes.status,\n error: errorText,\n account_id: accountId,\n project_id: projectId,\n },\n });\n throw new AuthenticationServiceError('The authentication service could not complete sign-in', stsEndpoint, {\n status: stsRes.status,\n cause: errorText,\n });\n }\n const { token } = await stsRes.json();\n Env.logger.debug('Successfully got token from STS');\n return token;\n }\n catch (error) {\n if (error instanceof UserNotFoundError ||\n error instanceof STSError ||\n error instanceof RestrictedEnvironmentError) {\n // Re-throw typed auth errors to be handled separately in the caller\n throw error;\n }\n if (error instanceof Error && error.message === 'Customer-domain user requires an invite to join')\n throw error;\n console.error('Failed to get composable token from STS', error);\n Env.logger.error('Failed to get composable token from STS', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n error: error,\n },\n });\n throw new AuthenticationServiceError('The authentication service could not complete sign-in', stsEndpoint, {\n cause: error,\n });\n }\n}\n/**\n *\n * @param accountId\n * @param projectId\n * @param ttl time to live for the token in seconds\n * @returns\n */\nexport async function fetchComposableTokenFromFirebaseToken(accountId, projectId, ttl) {\n return fetchComposableToken(getFirebaseAuthToken, accountId, projectId, ttl);\n}\n/**\n * Mint a scoped Vertesia token from an existing Vertesia JWT. STS accepts STS-issued\n * tokens on /token/issue, so this works for sessions established via Central Auth where\n * the browser has no Firebase user.\n */\nexport async function fetchComposableTokenFromVertesiaToken(vertesiaToken, accountId, projectId, ttl) {\n return fetchComposableToken(() => Promise.resolve(vertesiaToken), accountId, projectId, ttl);\n}\n/** Returns the cached Vertesia raw JWT, if any. Does not refresh. */\nexport function getCurrentVertesiaToken() {\n return AUTH_TOKEN_RAW;\n}\nexport async function getComposableToken(accountId, projectId, initToken, forceRefresh = false, useInternalAuth = false) {\n const selectedAccount = accountId ??\n (projectId === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);\n const selectedProject = projectId ?? localStorage.getItem(`${LastSelectedProjectId_KEY}-${selectedAccount}`) ?? undefined;\n const devAuthToken = Env.isLocalDev ? Env.devAuthToken : undefined;\n const suppliedToken = devAuthToken ?? initToken ?? AUTH_TOKEN_RAW;\n const cachedTokenMatchesScope = (!selectedAccount || AUTH_TOKEN?.account?.id === selectedAccount) &&\n (!selectedProject || AUTH_TOKEN?.project?.id === selectedProject);\n // Token is still valid for more than 5 minutes and belongs to the requested scope.\n if (!forceRefresh &&\n AUTH_TOKEN_RAW &&\n AUTH_TOKEN &&\n cachedTokenMatchesScope &&\n AUTH_TOKEN.exp > Date.now() / 1000 + 300) {\n HAD_SESSION = true;\n return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };\n }\n if (!forceRefresh &&\n isVertesiaIssuedToken(suppliedToken) &&\n canUseVertesiaTokenDirectly(suppliedToken, selectedAccount, selectedProject)) {\n AUTH_TOKEN_RAW = suppliedToken;\n AUTH_TOKEN = decodeToken(AUTH_TOKEN_RAW);\n if (!AUTH_TOKEN.exp) {\n throw new Error('Invalid composable token');\n }\n HAD_SESSION = true;\n return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };\n }\n try {\n //token is close to expire, refresh it\n if (!devAuthToken && !useInternalAuth && getFirebaseAuth().currentUser) {\n //we have a firebase user, get the token from there\n AUTH_TOKEN_RAW = await fetchComposableTokenFromFirebaseToken(selectedAccount, selectedProject);\n }\n else if (!devAuthToken) {\n // Embedded apps can reacquire a fresh credential from their host after their cached token expires.\n const refreshCredential = (await Env.authTokenProvider?.()) ?? initToken ?? AUTH_TOKEN_RAW;\n // `forceRefresh` has to defeat this shortcut, not just the cache above it. A caller\n // asking for a forced refresh wants claims recomputed -- `refreshAuthToken()` exists so\n // a stale `apps` claim can be re-read after an ACE change, and STS recomputes it on\n // every issuance. Adopting the credential we already hold satisfies the check and\n // returns the very token whose claims were suspect, silently making the call a no-op\n // for exactly the sessions that have no other credential to fall back on.\n if (!forceRefresh &&\n refreshCredential &&\n isVertesiaIssuedToken(refreshCredential) &&\n canUseVertesiaTokenDirectly(refreshCredential, selectedAccount, selectedProject)) {\n AUTH_TOKEN_RAW = refreshCredential;\n }\n else if (refreshCredential) {\n AUTH_TOKEN_RAW = await fetchComposableToken(() => Promise.resolve(refreshCredential), selectedAccount, selectedProject);\n }\n else {\n AUTH_TOKEN_RAW = undefined;\n }\n }\n else if (devAuthToken) {\n AUTH_TOKEN_RAW = devAuthToken;\n }\n }\n catch (error) {\n if (error instanceof RequestedScopeUnavailableError ||\n error instanceof NoAccessibleAccountError ||\n error instanceof CredentialError) {\n AUTH_TOKEN_RAW = undefined;\n AUTH_TOKEN = undefined;\n if (error instanceof RequestedScopeUnavailableError) {\n clearRejectedPersistedScope(selectedAccount, selectedProject);\n }\n }\n // An expired Central Auth session presents as a rejected credential: the JWT we sent STS to\n // renew from is the one that timed out. The broker can still vouch for the user. Only this\n // error -- a scope or account failure is about who the user is, and the broker would hand\n // back the same identity and the same refusal.\n if (error instanceof CredentialError) {\n renewExpiredCentralAuthSession();\n }\n throw error;\n }\n if (!AUTH_TOKEN_RAW) {\n Env.logger.error('Cannot acquire a composable token', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n // Reached once the credential has already been discarded -- typically the call after the\n // one that saw STS reject it. Same remedy, and the cooldown keeps this from redirecting a\n // second time for the same expiry.\n renewExpiredCentralAuthSession();\n throw new Error('Cannot acquire a composable token');\n }\n AUTH_TOKEN = decodeToken(AUTH_TOKEN_RAW);\n if (!AUTH_TOKEN?.exp || !AUTH_TOKEN_RAW) {\n console.error('Invalid composable token', AUTH_TOKEN);\n Env.logger.error('Invalid composable token', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n throw new Error('Invalid composable token');\n }\n HAD_SESSION = true;\n return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };\n}\nexport class UserNotFoundError extends Error {\n email;\n constructor(message, email) {\n super(message);\n this.name = 'UserNotFoundError';\n this.email = email;\n }\n}\nexport class STSError extends Error {\n stsURL;\n status;\n errorCode;\n constructor(message, stsURL, options) {\n super(message, options);\n this.name = 'STSError';\n this.stsURL = stsURL;\n this.status = options?.status;\n this.errorCode = options?.errorCode;\n }\n}\nexport class RequestedScopeUnavailableError extends STSError {\n accountId;\n projectId;\n identity;\n requestedScope;\n selectorSource;\n recoveryUrl;\n constructor(stsURL, accountId, projectId, responseMessage, identity) {\n super(responseMessage ?? 'The requested account or project is not available.', stsURL, {\n status: 403,\n errorCode: REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,\n });\n this.accountId = accountId;\n this.projectId = projectId;\n this.identity = identity;\n this.name = 'RequestedScopeUnavailableError';\n this.requestedScope = projectId ? 'project' : 'account';\n }\n}\n/** @deprecated Use RequestedScopeUnavailableError. */\nexport const TokenAuthorizationError = RequestedScopeUnavailableError;\nexport class NoAccessibleAccountError extends STSError {\n identity;\n constructor(stsURL, responseMessage, identity) {\n super(responseMessage ?? 'No accessible account is available for this user.', stsURL, {\n status: 403,\n errorCode: NO_ACCESSIBLE_ACCOUNT_ERROR_CODE,\n });\n this.identity = identity;\n this.name = 'NoAccessibleAccountError';\n }\n}\nexport class CredentialError extends STSError {\n constructor(message, stsURL, options) {\n super(message, stsURL, options);\n this.name = 'CredentialError';\n }\n}\nexport class AuthenticationServiceError extends STSError {\n constructor(message, stsURL, options) {\n super(message, stsURL, options);\n this.name = 'AuthenticationServiceError';\n }\n}\nexport function isRequestedScopeUnavailableError(error) {\n return error instanceof RequestedScopeUnavailableError;\n}\nexport function isNoAccessibleAccountError(error) {\n return error instanceof NoAccessibleAccountError;\n}\nexport function isCredentialError(error) {\n return error instanceof CredentialError;\n}\nexport function isAuthenticationServiceError(error) {\n return error instanceof AuthenticationServiceError;\n}\n/**\n * Thrown when the STS rejects sign-in because the current environment (preview/preprod) is\n * restricted to early-access users and this user lacks the `early-access` annotation. The\n * sign-in screen renders a dedicated \"restricted environment\" step for this error.\n * See docs/restrict-access-to-non-production-envs.md.\n */\nexport class RestrictedEnvironmentError extends Error {\n constructor(message) {\n super(message);\n this.name = 'RestrictedEnvironmentError';\n }\n}\n//# sourceMappingURL=composable.js.map","/**\n * This hook is used to generate and verify state for OAuth2 authorization requests.\n *\n * The implementation lives in ./authState so the token layer can start the same round-trip when it\n * renews an expired Central Auth session, where no component is available to call a hook.\n */\nimport { useCallback } from 'react';\nimport { clearAuthState, generateAuthState, verifyAuthState } from './authState';\nexport function useAuthState() {\n const generateState = useCallback(() => generateAuthState(), []);\n const verifyState = useCallback((returnedState) => verifyAuthState(returnedState), []);\n const clearState = useCallback(() => clearAuthState(), []);\n return { generateState, verifyState, clearState };\n}\n//# sourceMappingURL=useAuthState.js.map","import { VertesiaClient } from '@vertesia/client';\nimport { Env } from '@vertesia/ui/env';\nimport { jwtDecode } from 'jwt-decode';\nimport { createContext, useContext } from 'react';\nimport { getComposableToken } from './auth/composable';\nimport { authReturnUrl, centralAuthUrl, mountRootUrl, shouldRedirectToCentralAuth } from './auth/domainRouting';\nimport { getFirebaseAuth } from './auth/firebase';\nimport { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from './constants';\nexport { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY };\nclass UserSession {\n isLoading = true;\n client;\n authError;\n authToken;\n setSession;\n lastSelectedAccount;\n lastSelectedProject;\n onboardingComplete;\n constructor(client, setSession) {\n if (client) {\n this.client = client;\n }\n else {\n this.client = new VertesiaClient({\n serverUrl: Env.endpoints.studio,\n storeUrl: Env.endpoints.zeno,\n tokenServerUrl: Env.endpoints.sts,\n });\n }\n if (setSession) {\n this.setSession = setSession;\n }\n this.logout = this.logout.bind(this);\n this.signOut = this.signOut.bind(this);\n }\n get store() {\n return this.client.store;\n }\n get user() {\n //compatibility\n return this.authToken;\n }\n get account() {\n //compatibility\n return this.authToken?.account;\n }\n get project() {\n return this.authToken?.project;\n }\n get accounts() {\n //compatibility\n return this.authToken?.accounts;\n }\n get authCallback() {\n return this.rawAuthToken.then((token) => `Bearer ${token}`);\n }\n get rawAuthToken() {\n return getComposableToken().then((res) => {\n const token = res?.rawToken;\n if (!token) {\n throw new Error('No token available');\n }\n this.authToken = jwtDecode(token);\n return token;\n });\n }\n /**\n * Force a fresh Vertesia JWT from STS, bypassing the in-memory cache.\n * Use this when the current token's claims (e.g. `apps`) are suspected\n * stale — STS recomputes the `apps` claim from ACEs on every issuance.\n */\n async refreshAuthToken() {\n const res = await getComposableToken(undefined, undefined, undefined, true);\n const token = res?.rawToken;\n if (!token) {\n throw new Error('No token available');\n }\n this.authToken = jwtDecode(token);\n this.setSession?.(this.clone());\n return this.authToken;\n }\n signOut() {\n //compatibility\n this.logout();\n }\n getAccount() {\n return this.authToken?.account;\n }\n async login(token, options = {}) {\n this.authError = undefined;\n this.isLoading = false;\n this.client.withAuthCallback(() => this.authCallback);\n this.authToken = jwtDecode(token);\n console.log(`Logging in as ${this.authToken?.name} with account ${this.authToken?.account.name} (${this.authToken?.account.id}, and project ${this.authToken?.project?.name} (${this.authToken?.project?.id})`);\n //store selected account in local storage\n localStorage.setItem(LastSelectedAccountId_KEY, this.authToken.account.id);\n localStorage.setItem(`${LastSelectedProjectId_KEY}-${this.authToken.account.id}`, this.authToken.project?.id ?? '');\n // notify the host app of the login\n Env.onLogin?.(this.authToken);\n if (options.loadOnboardingStatus ?? true) {\n await this.fetchOnboardingStatus();\n }\n return Promise.resolve();\n }\n isLoggedIn() {\n return !!this.authToken;\n }\n logout() {\n console.log('Logging out');\n if (shouldRedirectToCentralAuth()) {\n // Redirect to central auth for logout\n // Central auth will handle Firebase logout\n console.log('Using central auth logout');\n this.authError = undefined;\n this.isLoading = false;\n this.authToken = undefined;\n this.setSession = undefined;\n this.client.withAuthCallback(undefined);\n const logoutUrl = new URL(centralAuthUrl());\n const currentUrl = authReturnUrl();\n logoutUrl.pathname = '/logout';\n logoutUrl.searchParams.set('redirect_uri', currentUrl.toString());\n location.replace(logoutUrl.toString());\n }\n else {\n // Use Firebase logout directly\n console.log('Using Firebase logout');\n const wasLoggedIn = !!this.authToken;\n if (this.authToken) {\n void getFirebaseAuth().signOut();\n }\n this.authError = undefined;\n this.isLoading = false;\n this.authToken = undefined;\n this.setSession = undefined;\n this.client.withAuthCallback(undefined);\n // Navigate to the app root to avoid React rendering errors when\n // unmounting deeply nested route components during logout.\n // Use the mount root (not bare '/') so a gateway-mounted app stays\n // on its mount. Only navigate if user was actually logged in to\n // avoid an infinite reload loop on fresh/incognito sessions.\n if (wasLoggedIn) {\n location.replace(mountRootUrl().toString());\n }\n }\n }\n async switchAccount(targetAccountId) {\n localStorage.setItem(LastSelectedAccountId_KEY, targetAccountId);\n if (this) {\n if (this.account && this.project) {\n localStorage.setItem(`${LastSelectedProjectId_KEY}-${this.account.id}`, this.project.id);\n }\n else if (this.account) {\n localStorage.removeItem(`${LastSelectedProjectId_KEY}-${this.account.id}`);\n }\n }\n const url = mountRootUrl();\n url.searchParams.set('a', targetAccountId);\n window.location.replace(url.toString());\n }\n async switchProject(targetProjectId) {\n if (this.account) {\n localStorage.setItem(`${LastSelectedProjectId_KEY}-${this.account.id}`, targetProjectId);\n }\n const url = mountRootUrl();\n if (this.account?.id) {\n url.searchParams.set('a', this.account.id);\n }\n url.searchParams.set('p', targetProjectId);\n window.location.replace(url.toString());\n }\n async fetchAccounts() {\n return this.client.accounts\n .list()\n .then((accounts) => {\n if (!this.authToken) {\n throw new Error('No token available');\n }\n this.authToken.accounts = accounts;\n this.setSession?.(this.clone());\n })\n .catch((err) => {\n console.error('Failed to fetch accounts', err);\n throw err;\n });\n }\n async fetchProjects(accountId) {\n return this.client.projects\n .list([accountId])\n .then((projects) => {\n if (!this.authToken) {\n throw new Error('No token available');\n }\n this.authToken = {\n ...this.authToken,\n accounts: this.authToken.accounts?.map((account) => {\n if (account.id === accountId) {\n return {\n ...account,\n projects: projects.filter((project) => project.account === accountId),\n };\n }\n return account;\n }),\n };\n this.setSession?.(this.clone());\n })\n .catch((err) => {\n console.error('Failed to fetch projects', err);\n throw err;\n });\n }\n async fetchOnboardingStatus() {\n if (!this.project?.id) {\n return false;\n }\n if (this.onboardingComplete) {\n console.log('Onboarding already completed');\n return false;\n }\n const previousStatus = this.onboardingComplete;\n try {\n const onboarding = await this.client.account.onboardingProgress();\n this.onboardingComplete = Object.values(onboarding).every((value) => value === true);\n if (previousStatus !== this.onboardingComplete) {\n this.setSession?.(this.clone());\n }\n return previousStatus === false && this.onboardingComplete;\n }\n catch (error) {\n console.error('Error fetching onboarding status:', error);\n this.onboardingComplete = false;\n if (previousStatus !== this.onboardingComplete) {\n this.setSession?.(this.clone());\n }\n }\n return false;\n }\n clone() {\n const session = new UserSession(this.client);\n session.isLoading = this.isLoading;\n session.authError = this.authError;\n session.authToken = this.authToken;\n session.setSession = this.setSession;\n session.lastSelectedAccount = this.lastSelectedAccount;\n session.switchAccount = this.switchAccount;\n session.onboardingComplete = this.onboardingComplete;\n return session;\n }\n}\nconst UserSessionContext = createContext(undefined);\nexport function useUserSession() {\n const session = useContext(UserSessionContext);\n if (!session) {\n throw new Error('useUserSession must be used within a UserSessionProvider');\n }\n return session;\n}\nexport { UserSession, UserSessionContext };\n//# sourceMappingURL=UserSession.js.map","import { i18nInstance, NAMESPACE } from '@vertesia/ui/i18n';\nimport { useEffect, useState } from 'react';\nimport { useUserSession } from '../UserSession';\nexport function useCurrentTenant() {\n const { user } = useUserSession();\n const [currentTenant, setCurrentTenant] = useState(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState(null);\n useEffect(() => {\n const loadCurrentTenant = async () => {\n if (!user?.email) {\n setCurrentTenant(null);\n setIsLoading(false);\n return;\n }\n try {\n const response = await fetch('/api/resolve-tenant', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n tenantEmail: user.email,\n }),\n });\n if (response.ok) {\n const tenantData = await response.json();\n if (tenantData) {\n // Convert the resolved tenant data to our TenantConfig format\n setCurrentTenant({\n tenantKey: tenantData.name || 'unknown',\n name: tenantData.label || tenantData.name || 'Unknown',\n domain: tenantData.domain || [],\n firebaseTenantId: tenantData.firebaseTenantId,\n provider: tenantData.provider,\n logo: tenantData.logo,\n });\n }\n else {\n setCurrentTenant(null);\n }\n }\n else {\n setCurrentTenant(null);\n }\n }\n catch (error) {\n console.error('Error loading current tenant:', error);\n setError(i18nInstance.getFixedT(null, NAMESPACE)('errors.failedToLoadTenantConfig'));\n setCurrentTenant(null);\n }\n finally {\n setIsLoading(false);\n }\n };\n void loadCurrentTenant();\n }, [user?.email]);\n return {\n currentTenant,\n isLoading,\n error,\n };\n}\n//# sourceMappingURL=useCurrentTenant.js.map","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { useMemo } from 'react';\nimport { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from './constants';\nimport { UserSession, UserSessionContext } from './UserSession';\nfunction decodeJwtPayload(token) {\n const [, payload] = token.split('.');\n if (!payload) {\n throw new Error('Invalid Vertesia auth token');\n }\n const padded = payload\n .replace(/-/g, '+')\n .replace(/_/g, '/')\n .padEnd(Math.ceil(payload.length / 4) * 4, '=');\n return JSON.parse(atob(padded));\n}\nexport function DevSessionProvider({ children, token }) {\n const session = useMemo(() => {\n const next = new UserSession();\n next.isLoading = false;\n try {\n next.authToken = decodeJwtPayload(token);\n next.client.withAuthCallback(() => Promise.resolve(`Bearer ${token}`));\n localStorage.setItem(LastSelectedAccountId_KEY, next.authToken.account.id);\n localStorage.setItem(`${LastSelectedProjectId_KEY}-${next.authToken.account.id}`, next.authToken.project?.id ?? '');\n }\n catch (error) {\n next.authError = error instanceof Error ? error : new Error(String(error));\n }\n return next.clone();\n }, [token]);\n return _jsx(UserSessionContext.Provider, { value: session, children: children });\n}\n//# sourceMappingURL=DevSessionProvider.js.map","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE } from '@vertesia/common';\nimport { Env } from '@vertesia/ui/env';\nimport { onAuthStateChanged } from 'firebase/auth';\nimport { useEffect, useRef, useState } from 'react';\nimport { AuthenticationServiceError, CredentialError, getComposableToken, NoAccessibleAccountError, RequestedScopeUnavailableError, RestrictedEnvironmentError, resolveAuthSelection, UserNotFoundError, } from './auth/composable';\nimport { buildCentralAuthRedirectUrl, redirectToCentralAuth, shouldRedirectToCentralAuth } from './auth/domainRouting';\nimport { getFirebaseAuth } from './auth/firebase';\nimport { useAuthState } from './auth/useAuthState';\nimport { UserSession, UserSessionContext } from './UserSession';\nfunction clearAuthHash() {\n const url = new URL(window.location.href);\n url.hash = '';\n window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}`);\n}\n// Re-exported from its new home in ./auth/domainRouting, which the token layer can import without\n// a cycle -- it renews an expired Central Auth session through the same URL.\nexport { buildCentralAuthRedirectUrl };\nexport function sanitizeRejectedScopeUrl(currentUrl, error, clearHash = false) {\n const sanitizedUrl = new URL(currentUrl);\n const urlAccountId = sanitizedUrl.searchParams.get('a') ?? undefined;\n const urlProjectId = sanitizedUrl.searchParams.get('p') ?? undefined;\n if (urlAccountId && urlProjectId && urlAccountId === error.accountId && urlProjectId === error.projectId) {\n sanitizedUrl.searchParams.delete('a');\n sanitizedUrl.searchParams.delete('p');\n error.requestedScope = 'project';\n error.selectorSource = 'url';\n }\n else if (urlProjectId && urlProjectId === error.projectId) {\n sanitizedUrl.searchParams.delete('p');\n error.requestedScope = 'project';\n error.selectorSource = 'url';\n }\n else if (urlAccountId && urlAccountId === error.accountId) {\n sanitizedUrl.searchParams.delete('a');\n error.requestedScope = 'account';\n error.selectorSource = 'url';\n }\n else {\n error.requestedScope = error.projectId ? 'project' : 'account';\n error.selectorSource = 'persisted';\n }\n if (clearHash)\n sanitizedUrl.hash = '';\n error.recoveryUrl = sanitizedUrl.toString();\n return sanitizedUrl;\n}\nexport function UserSessionProvider({ children, loadOnboardingStatus = true }) {\n const hashParams = new URLSearchParams(location.hash.substring(1));\n const token = hashParams.get('token');\n const state = hashParams.get('state');\n const [session, setSession] = useState(new UserSession());\n const { verifyState, clearState } = useAuthState();\n const hasInitiatedAuthRef = useRef(false);\n const authFlowRef = useRef(undefined);\n const clearRejectedUrlScope = (error, clearHash = false) => {\n const sanitizedUrl = sanitizeRejectedScopeUrl(new URL(window.location.href), error, clearHash);\n window.history.replaceState(window.history.state, '', sanitizedUrl);\n Env.logger.warn('Sanitized an unavailable authentication scope', {\n vertesia: {\n account_id: error.accountId,\n project_id: error.projectId,\n error_code: REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,\n selector_source: error.selectorSource,\n requested_scope: error.requestedScope,\n recovery_action: 'url_sanitized',\n },\n });\n };\n const surfaceAuthError = (error, clearHash = false) => {\n if (!(error instanceof RequestedScopeUnavailableError ||\n error instanceof NoAccessibleAccountError ||\n error instanceof CredentialError ||\n error instanceof AuthenticationServiceError ||\n error instanceof UserNotFoundError ||\n error instanceof RestrictedEnvironmentError)) {\n return false;\n }\n if (error instanceof RequestedScopeUnavailableError) {\n clearRejectedUrlScope(error, clearHash);\n }\n else if (clearHash) {\n clearAuthHash();\n }\n session.isLoading = false;\n session.authError = error;\n setSession(session.clone());\n return true;\n };\n authFlowRef.current = () => {\n // Make this effect idempotent - only run auth flow once\n if (hasInitiatedAuthRef.current) {\n Env.logger.debug('Skipping duplicate auth flow initiation');\n return;\n }\n hasInitiatedAuthRef.current = true;\n Env.logger.debug('Starting auth flow');\n const currentUrl = new URL(window.location.href);\n const { accountId: selectedAccount, projectId: selectedProject } = resolveAuthSelection(currentUrl);\n Env.logger.debug('Selected account and project', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n if (Env.isLocalDev && Env.devAuthToken) {\n session.setSession = setSession;\n getComposableToken(selectedAccount, selectedProject, Env.devAuthToken)\n .then((res) => {\n return session.login(res.rawToken).then(() => setSession(session.clone()));\n })\n .catch((err) => {\n if (surfaceAuthError(err))\n return;\n console.error('Failed to initialize dev auth token', err);\n Env.logger.error('Failed to initialize dev auth token', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n error: err,\n },\n });\n session.isLoading = false;\n session.authError = err instanceof Error ? err : new Error(String(err));\n setSession(session.clone());\n });\n return;\n }\n if (token && state) {\n session.setSession = setSession;\n const validationError = verifyState(state);\n if (validationError) {\n console.error(`Auth: invalid state: ${validationError}`);\n Env.logger.error(`Invalid state: ${validationError}`, {\n vertesia: {\n state: state,\n },\n });\n redirectToCentralAuth({ accountId: selectedAccount, projectId: selectedProject });\n }\n else {\n clearState();\n }\n getComposableToken(selectedAccount, selectedProject, token, false, shouldRedirectToCentralAuth())\n .then((res) => {\n return session.login(res.rawToken, { loadOnboardingStatus }).then(() => {\n setSession(session.clone());\n clearAuthHash();\n });\n })\n .catch((err) => {\n if (surfaceAuthError(err, true))\n return;\n console.error('Failed to fetch user token from studio, redirecting to central auth', err);\n Env.logger.error('Failed to fetch user token from studio, redirecting to central auth', {\n vertesia: {\n error: err,\n },\n });\n redirectToCentralAuth({ accountId: selectedAccount, projectId: selectedProject });\n });\n return;\n }\n let cancelled = false;\n let unsubscribe;\n const startFirebaseOrCentralAuth = () => {\n if (cancelled)\n return;\n // If the current host is not in the Firebase allowlist, central auth owns sign-in.\n if (!session.isLoggedIn()) {\n Env.logger.debug('Not logged in & no token/state', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n if (shouldRedirectToCentralAuth()) {\n Env.logger.debug('Redirecting to central auth with selection', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n redirectToCentralAuth({ accountId: selectedAccount, projectId: selectedProject });\n return; // Don't register onAuthStateChanged listener when redirecting\n }\n Env.logger.debug('Host is in Firebase auth allowlist', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n }\n unsubscribe = onAuthStateChanged(getFirebaseAuth(), async (firebaseUser) => {\n if (firebaseUser) {\n Env.logger.debug('Successful login with firebase', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n session.setSession = setSession;\n await getComposableToken(selectedAccount, selectedProject, undefined, false, shouldRedirectToCentralAuth())\n .then((res) => {\n return session\n .login(res.rawToken, { loadOnboardingStatus })\n .then(() => setSession(session.clone()));\n })\n .catch((err) => {\n if (surfaceAuthError(err))\n return;\n console.error('Unexpected failure while fetching a user token', err);\n Env.logger.error('Unexpected failure while fetching a user token', {\n vertesia: { account_id: selectedAccount, project_id: selectedProject, error: err },\n });\n session.logout();\n session.isLoading = false;\n session.authError = err;\n setSession(session.clone());\n });\n }\n else {\n // anonymous user\n Env.logger.debug('Using anonymous user', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n session.client.withAuthCallback(undefined);\n session.logout();\n setSession(session.clone());\n }\n });\n };\n if (Env.authTokenProvider) {\n session.setSession = setSession;\n void Env.authTokenProvider()\n .then(async (injectedToken) => {\n if (cancelled)\n return;\n if (!injectedToken) {\n startFirebaseOrCentralAuth();\n return;\n }\n const res = await getComposableToken(selectedAccount, selectedProject, injectedToken, false, shouldRedirectToCentralAuth());\n await session.login(res.rawToken, { loadOnboardingStatus });\n if (!cancelled)\n setSession(session.clone());\n })\n .catch((err) => {\n if (cancelled)\n return;\n if (surfaceAuthError(err))\n return;\n console.warn('Auth: failed to initialize injected auth token', err);\n Env.logger.warn('Failed to initialize injected auth token', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n error: err,\n },\n });\n session.isLoading = false;\n session.authError = err instanceof Error ? err : new Error(String(err));\n setSession(session.clone());\n });\n return () => {\n cancelled = true;\n hasInitiatedAuthRef.current = false;\n unsubscribe?.();\n };\n }\n startFirebaseOrCentralAuth();\n return () => {\n cancelled = true;\n hasInitiatedAuthRef.current = false;\n unsubscribe?.();\n };\n };\n useEffect(() => {\n return authFlowRef.current?.();\n }, []);\n return _jsx(UserSessionContext.Provider, { value: session, children: children });\n}\n//# sourceMappingURL=UserSessionProvider.js.map","import { Env } from '@vertesia/ui/env';\nimport { logEvent } from 'firebase/analytics';\nimport { getFirebaseAnalytics } from './auth/firebase';\nexport function useUXTracking() {\n //identify user in monitoring and UX systems\n const tagUserSession = async (user) => {\n const signupData = window.localStorage.getItem('composableSignupData');\n if (!user) {\n console.error('No user found -- skipping tagging');\n return;\n }\n if (signupData) {\n window.localStorage.removeItem('composableSignupData');\n }\n };\n //send event to analytics and UX systems\n const trackEvent = (eventName, eventProperties) => {\n if (!Env.isProd) {\n console.debug('track event', eventName, eventProperties);\n }\n // GA via firebase — needs a complete Firebase config. A partial one (no apiKey, as in\n // central-auth deployments and local setups) would make the Installations SDK log an\n // async FirebaseError on every event, so analytics is a no-op there.\n if (!Env.firebase?.apiKey) {\n return;\n }\n logEvent(getFirebaseAnalytics(), eventName, { ...eventProperties, debug_mode: !Env.isProd });\n };\n return {\n tagUserSession,\n trackEvent,\n };\n}\n//# sourceMappingURL=useUXTracking.js.map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vertesia-ui-session.js","names":["_jsx","_jsx"],"sources":["session/constants.js","session/auth/authState.js","session/auth/domainRouting.js","session/auth/firebase.js","session/auth/composable.js","session/auth/useAuthState.js","session/UserSession.js","session/auth/useCurrentTenant.js","session/DevSessionProvider.js","session/UserSessionProvider.js","session/useUXTracking.js"],"sourcesContent":["export const LastSelectedAccountId_KEY = 'composableai.lastSelectedAccountId';\nexport const LastSelectedProjectId_KEY = 'composableai.lastSelectedProjectId';\n//# sourceMappingURL=constants.js.map","/**\n * The `state` parameter binding a Central Auth round-trip to the tab that started it.\n *\n * Plain functions rather than a hook, because the round-trip is not always started from a\n * component: an expired session is renewed from the token layer, which has no React context to\n * call {@link useAuthState} from. The hook wraps these so both paths write the same record.\n */\nconst AUTH_STATE_KEY = 'auth_state';\nconst STATE_EXPIRY_KEY = 'auth_state_expiry';\nconst STATE_TTL = 5 * 60 * 1000; // 5 min\n/** Mint a state value and record it for {@link verifyAuthState}. */\nexport function generateAuthState() {\n const state = crypto.randomUUID();\n sessionStorage.setItem(AUTH_STATE_KEY, state);\n sessionStorage.setItem(STATE_EXPIRY_KEY, String(Date.now() + STATE_TTL));\n return state;\n}\n/** The reason `returnedState` is unacceptable, or undefined when it is good. */\nexport function verifyAuthState(returnedState) {\n if (!returnedState) {\n return 'Missing state';\n }\n const savedState = sessionStorage.getItem(AUTH_STATE_KEY);\n const expiryTime = parseInt(sessionStorage.getItem(STATE_EXPIRY_KEY) || '0', 10);\n if (savedState !== returnedState) {\n return `State mismatched (${savedState} !== ${returnedState})`;\n }\n if (Date.now() > expiryTime) {\n return 'State expired';\n }\n return undefined;\n}\nexport function clearAuthState() {\n sessionStorage.removeItem(AUTH_STATE_KEY);\n sessionStorage.removeItem(STATE_EXPIRY_KEY);\n}\n//# sourceMappingURL=authState.js.map","import { Env } from '@vertesia/ui/env';\nimport { generateAuthState } from './authState';\n/**\n * The broker this app sends users to for sign-in and logout.\n *\n * A gateway or app can select a regional broker through Env.endpoints.auth.\n * Unconfigured apps use the first-party central broker.\n */\nexport const DEFAULT_CENTRAL_AUTH_URL = 'https://auth.vertesia.io/';\nexport function centralAuthUrl() {\n const configured = Env.endpoints.auth;\n return configured ? configured : DEFAULT_CENTRAL_AUTH_URL;\n}\nexport function shouldUseFirebaseAuth(_hostname) {\n return window.AUTH_MODE === 'firebase';\n}\nexport function shouldRedirectToCentralAuth(_hostname) {\n return !shouldUseFirebaseAuth();\n}\n/**\n * The page URL to return to after a central-auth round-trip (`redirect_uri`).\n *\n * Apps served under a deep gateway mount carry a `<base href=\"/tenants/<t>/apps/<app>/.../app/\">`\n * that the app-gateway injects at serve time. The in-app router rewrites the address bar relative\n * to the origin rather than that mount, so by the time the auth flow runs `window.location` can\n * read as the bare origin `/`. Building `redirect_uri` from that drops the app path, and the\n * post-login token bounces back to a URL that serves no app.\n *\n * Prefer the live location when it still sits under the mount (preserves the in-app deep route);\n * otherwise fall back to `document.baseURI` — the mount, which the router cannot clobber. With no\n * `<base>` element (the Studio UI), `document.baseURI` is the document URL and its `pathname` is a\n * prefix of the live location, so the live URL is used unchanged — no behavior change there.\n */\nexport function authReturnUrl() {\n const base = new URL(document.baseURI);\n const current = new URL(window.location.href);\n const target = current.pathname.startsWith(base.pathname) ? current : base;\n target.hash = '';\n return target;\n}\n/**\n * The app's mount root URL — the served `<base href>` (or the origin root for the Studio UI).\n *\n * Used for full-reload navigations that intentionally reset to the app root (logout, account /\n * project switch). Building these from a bare `/` drops a gateway-mounted app off its mount and\n * lands on a URL that serves no app; resolving against `document.baseURI` keeps the reload inside\n * the mount. For the Studio UI (no `<base>` element) `document.baseURI` is the origin root, so the\n * behavior is unchanged.\n */\nexport function mountRootUrl() {\n const url = new URL(document.baseURI);\n url.hash = '';\n url.search = '';\n return url;\n}\n/**\n * The broker URL a sign-in or renewal round-trip navigates to.\n *\n * Every parameter goes on through `searchParams`, never by concatenating a query string onto\n * `centralAuth`. The endpoint is configurable, so it may already carry its own query or a\n * fragment -- and appending `?sts=...` to one of those folds the parameter into the existing value\n * or hides it in the fragment, leaving Central Auth with no `sts` at all. `searchParams` also\n * percent-encodes the values, which the interpolated URLs did not.\n */\nexport function buildCentralAuthRedirectUrl(centralAuth, stsUrl, returnUrl, state, selection = {}) {\n const selectedReturnUrl = new URL(returnUrl);\n if (selection.projectId)\n selectedReturnUrl.searchParams.set('p', selection.projectId);\n if (selection.accountId)\n selectedReturnUrl.searchParams.set('a', selection.accountId);\n const url = new URL(centralAuth);\n url.searchParams.set('sts', stsUrl);\n url.searchParams.set('redirect_uri', selectedReturnUrl.toString());\n url.searchParams.set('state', state);\n return url;\n}\n/**\n * A short-lived marker saying \"a Central Auth round-trip is in flight for this browser\".\n *\n * It exists for the server that generates index.html. Preload hints are markup, so they start\n * fetching before any script runs: a page load that is going to bounce off to the broker cannot\n * avoid downloading the application by any decision made in the page itself. The server can, but\n * only if it can tell the outgoing leg from the return leg -- and the token comes back in the URL\n * *fragment*, which a server never sees. Hence a cookie: set here, immediately before navigating to\n * the broker, and cleared by the app as soon as it mounts.\n *\n * It carries no identity and grants nothing; it is a hint about which of two page loads this is.\n * The TTL bounds the cost of the case where the app never mounts (the visitor abandons the login\n * screen): after it lapses, cold loads are back to withholding the application preloads.\n */\nconst AUTH_ROUND_TRIP_COOKIE = 'vtsauth';\nconst AUTH_ROUND_TRIP_TTL_SECONDS = 300;\nfunction authRoundTripCookie(value, maxAgeSeconds) {\n // Secure only over https: setting it on http://localhost would make the browser drop the\n // cookie, and local development would silently lose the return-leg preloads.\n const secure = window.location.protocol === 'https:' ? '; Secure' : '';\n return `${AUTH_ROUND_TRIP_COOKIE}=${value}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax${secure}`;\n}\n/** Record that this browser is on its way to the broker. Called for you by {@link redirectToCentralAuth}. */\nexport function markCentralAuthRoundTripStarted() {\n // biome-ignore lint/suspicious/noDocumentCookie: CookieStore is async; this must land before location.replace()\n document.cookie = authRoundTripCookie('1', AUTH_ROUND_TRIP_TTL_SECONDS);\n}\n/**\n * Drop the marker. An app calls this once it is actually rendering, so that its *next* cold load is\n * recognized as an outgoing leg again and does not preload an application it is about to discard.\n */\nexport function clearCentralAuthRoundTripMarker() {\n // biome-ignore lint/suspicious/noDocumentCookie: pairs with the write above; see that comment.\n document.cookie = authRoundTripCookie('', 0);\n}\n/**\n * Start a Central Auth round-trip for the current page.\n *\n * The single place the broker URL is assembled, so the boot-time shortcut in an app's entry module\n * and {@link isCentralAuthRedirectPending}'s counterpart inside `UserSessionProvider` cannot drift\n * into producing different `redirect_uri` / `state` / `sts` values for the same page load.\n */\nexport function redirectToCentralAuth(selection = {}) {\n const url = buildCentralAuthRedirectUrl(centralAuthUrl(), Env.endpoints.sts ?? 'https://sts.vertesia.io', authReturnUrl(), generateAuthState(), selection);\n markCentralAuthRoundTripStarted();\n location.replace(url.toString());\n}\n/**\n * Whether this page load is already certain to end in a Central Auth round-trip, decidable before\n * any application module has run.\n *\n * A fresh `UserSession` holds no token and nothing persists one across loads, so on a cold load\n * `session.isLoggedIn()` in `UserSessionProvider` is always false. Every condition that can keep\n * that provider from redirecting is therefore knowable up front, and this predicate enumerates\n * them in the same order:\n *\n * - Firebase-allowlisted hosts sign in in place rather than at the broker;\n * - a host app that injects a token through `Env.authTokenProvider` may not need the broker\n * (and when its token turns out to be empty the provider still redirects — answering `false`\n * here just lets the normal flow decide, which is the safe direction);\n * - local development with a configured dev token skips auth entirely;\n * - a load carrying `#token=…&state=…` *is* the return leg and must be allowed to exchange it.\n *\n * When this returns true the app is going to navigate away, so an entry module can redirect\n * immediately instead of mounting a full React tree, initializing telemetry and warming route\n * chunks that the navigation is about to discard.\n */\nexport function isCentralAuthRedirectPending() {\n if (!shouldRedirectToCentralAuth())\n return false;\n if (Env.authTokenProvider)\n return false;\n if (Env.isLocalDev && Env.devAuthToken)\n return false;\n const hashParams = new URLSearchParams(window.location.hash.substring(1));\n return !(hashParams.get('token') && hashParams.get('state'));\n}\n//# sourceMappingURL=domainRouting.js.map","import { Env } from '@vertesia/ui/env';\nimport { getAnalytics } from 'firebase/analytics';\nimport { initializeApp } from 'firebase/app';\nimport { getAuth } from 'firebase/auth';\n// Use lazy initialization to avoid accessing Env before it's initialized\nlet _firebaseApp = null;\nlet _analytics = null;\nlet _firebaseAuth = null;\n// Getters that lazily initialize Firebase components when first accessed\nexport function getFirebaseApp() {\n if (!_firebaseApp) {\n try {\n if (!Env.firebase) {\n throw new Error('Firebase configuration is not available in the environment');\n }\n _firebaseApp = initializeApp(Env.firebase);\n }\n catch (error) {\n console.error('Failed to initialize Firebase app:', error);\n throw new Error('Firebase initialization failed - environment may not be properly initialized', {\n cause: error,\n });\n }\n }\n return _firebaseApp;\n}\nexport function getFirebaseAnalytics() {\n if (!_analytics) {\n _analytics = getAnalytics(getFirebaseApp());\n }\n return _analytics;\n}\nexport function getFirebaseAuth() {\n if (!_firebaseAuth) {\n _firebaseAuth = getAuth(getFirebaseApp());\n _firebaseAuth.tenantId = Env.firebase?.tenantId ?? null;\n }\n return _firebaseAuth;\n}\nexport async function setFirebaseTenant(tenantEmail) {\n if (Env.firebase?.tenantId) {\n getFirebaseAuth().tenantId = Env.firebase.tenantId;\n return {\n firebaseTenantId: Env.firebase.tenantId,\n provider: Env.firebase.providerType ?? 'oidc',\n name: '',\n label: '',\n };\n }\n if (!tenantEmail) {\n console.log('No tenant name or email specified, skipping tenant setup');\n return;\n }\n if (!Env.firebase) {\n console.log('Firebase configuration is not available in the environment');\n return;\n }\n try {\n if (tenantEmail)\n console.log(`Resolving tenant ID from email: ${tenantEmail}`);\n // Add retry logic with exponential backoff\n let retries = 3;\n let retryDelay = 250; // Start with 250ms delay\n while (retries > 0) {\n try {\n // Call the API endpoint to resolve the tenant ID\n const response = await fetch('/api/resolve-tenant', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n tenantEmail: tenantEmail,\n }),\n // Add timeout to prevent hanging requests\n signal: AbortSignal.timeout(5000),\n });\n // Check for network errors\n if (!response) {\n throw new Error('No response received from tenant API');\n }\n // Handle HTTP error responses\n if (!response.ok) {\n // Try to parse the error response\n try {\n const errorData = await response.json();\n console.error('Failed to resolve tenant ID:', errorData.error);\n }\n catch {\n console.error(`Failed to resolve tenant ID: HTTP ${response.status}`);\n }\n // If the error is 404 Not Found, no need to retry\n if (response.status === 404) {\n console.warn(`Tenant not found for ${tenantEmail}`);\n return;\n }\n throw new Error(`HTTP error ${response.status}`);\n }\n // Successfully got a response, parse it\n const data = (await response.json());\n if (data?.firebaseTenantId) {\n const auth = getFirebaseAuth();\n auth.tenantId = data.firebaseTenantId;\n Env.firebase.providerType = data.provider ?? 'oidc';\n console.log(`Tenant ID set to ${auth.tenantId}`);\n return data;\n }\n else {\n console.error(`Invalid response format, missing tenantId for ${tenantEmail}`);\n return; // No need to retry for invalid response format\n }\n }\n catch (fetchError) {\n // Only retry for network-related errors\n if (retries > 1) {\n console.warn(`Tenant resolution failed, retrying in ${retryDelay}ms...`, fetchError);\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n retryDelay *= 2; // Exponential backoff\n retries--;\n }\n else {\n throw fetchError; // Last retry failed, propagate error\n }\n }\n }\n }\n catch (error) {\n // Final error handler\n console.error('Error setting Firebase tenant:', error instanceof Error ? error.message : 'Unknown error');\n // Continue without tenant ID - authentication will work without multi-tenancy\n // but the user will access the default tenant\n }\n}\nexport async function getFirebaseAuthToken(refresh) {\n const auth = getFirebaseAuth();\n const user = auth.currentUser;\n if (user) {\n return user\n .getIdToken(refresh)\n .then((token) => {\n Env.logger.debug('Got Firebase token', {\n vertesia: {\n user_email: user.email,\n user_name: user.displayName,\n user_id: user.uid,\n refresh: refresh,\n },\n });\n return token;\n })\n .catch((err) => {\n Env.logger.error('Failed to get Firebase token', {\n vertesia: {\n user_email: user.email,\n user_name: user.displayName,\n user_id: user.uid,\n refresh: refresh,\n error: err,\n },\n });\n console.error('Failed to get access token', err);\n return null;\n });\n }\n else {\n Env.logger.warn('No user found');\n return Promise.resolve(null);\n }\n}\n//# sourceMappingURL=firebase.js.map","/**\n * Handle client caching and refresh of auth token\n */\nimport { NO_ACCESSIBLE_ACCOUNT_ERROR_CODE, REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE, RESTRICTED_ENVIRONMENT_ERROR_CODE, } from '@vertesia/common';\nimport { Env } from '@vertesia/ui/env';\nimport { jwtDecode } from 'jwt-decode';\nimport { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from '../constants';\nimport { generateAuthState } from './authState';\nimport { authReturnUrl, buildCentralAuthRedirectUrl, centralAuthUrl, shouldRedirectToCentralAuth, } from './domainRouting';\nimport { getFirebaseAuth, getFirebaseAuthToken } from './firebase';\nlet AUTH_TOKEN_RAW;\nlet AUTH_TOKEN;\n/**\n * Whether this tab ever held a token.\n *\n * Separates \"the session expired\" from \"there is no session yet\". Only the first is renewed from\n * here; the first load is UserSessionProvider's, and it has the account/project selection that\n * this layer does not.\n */\nlet HAD_SESSION = false;\n/** Session-storage stamp of the last renewal redirect, so the cooldown survives the navigation. */\nconst RENEWAL_ATTEMPT_KEY = 'vt.centralAuthRenewalAt';\nconst RENEWAL_COOLDOWN_MS = 60_000;\n/**\n * Renew an expired Central Auth session by bouncing through the broker.\n *\n * A Central Auth session holds exactly one credential -- the STS JWT, in memory -- and STS refuses\n * it once it has expired. There is no Firebase user in this browser to mint a replacement from and\n * no refresh token, so the only way back is the broker, which still has the user's own session and\n * returns a fresh identity token without prompting. That is precisely what a manual reload does\n * today; doing it here turns a page that dead-ends on \"Cannot acquire a composable token\" into a\n * redirect the user usually does not notice.\n *\n * The cooldown is the important part. If the broker returns a credential STS also rejects, an\n * unguarded redirect bounces the browser between the two forever -- and a redirect loop is a worse\n * failure than the error page it replaced, because it never comes to rest anywhere the user can\n * read. One attempt per minute, recorded in sessionStorage because the module state does not\n * survive the navigation this function starts.\n *\n * Returns whether a redirect was started.\n */\nfunction renewExpiredCentralAuthSession() {\n if (!HAD_SESSION || !shouldRedirectToCentralAuth()) {\n return false;\n }\n let lastAttempt = 0;\n try {\n lastAttempt = Number(sessionStorage.getItem(RENEWAL_ATTEMPT_KEY) ?? '0');\n }\n catch {\n // sessionStorage unavailable -- treated as \"no previous attempt\", so the redirect is\n // allowed once and the failure surfaces normally if it does not stick.\n }\n if (Number.isFinite(lastAttempt) && lastAttempt > 0 && Date.now() - lastAttempt < RENEWAL_COOLDOWN_MS) {\n Env.logger.warn('Central Auth session renewal already attempted; surfacing the failure instead', {\n vertesia: { last_attempt_ms_ago: Date.now() - lastAttempt },\n });\n return false;\n }\n try {\n sessionStorage.setItem(RENEWAL_ATTEMPT_KEY, String(Date.now()));\n }\n catch {\n // Without the stamp the cooldown cannot be enforced across the navigation. Still worth\n // redirecting once: the alternative is a page the user can only fix by reloading manually.\n }\n Env.logger.info('Renewing an expired Central Auth session through the broker');\n const url = buildCentralAuthRedirectUrl(centralAuthUrl(), Env.endpoints.sts ?? 'https://sts.vertesia.io', authReturnUrl(), generateAuthState());\n location.replace(url.toString());\n return true;\n}\nfunction clearRejectedPersistedScope(accountId, projectId) {\n if (!accountId)\n return;\n const projectKey = `${LastSelectedProjectId_KEY}-${accountId}`;\n if (projectId) {\n const persistedProjectMatches = localStorage.getItem(projectKey) === projectId;\n if (persistedProjectMatches) {\n localStorage.removeItem(projectKey);\n }\n if (persistedProjectMatches && localStorage.getItem(LastSelectedAccountId_KEY) === accountId) {\n localStorage.removeItem(LastSelectedAccountId_KEY);\n }\n return;\n }\n if (localStorage.getItem(LastSelectedAccountId_KEY) === accountId) {\n localStorage.removeItem(LastSelectedAccountId_KEY);\n localStorage.removeItem(projectKey);\n }\n}\nfunction identityFromAcceptedToken(token) {\n try {\n const payload = jwtDecode(token);\n if (typeof payload.email !== 'string' || !payload.email)\n return undefined;\n return {\n email: payload.email,\n name: typeof payload.name === 'string' && payload.name ? payload.name : undefined,\n };\n }\n catch {\n return undefined;\n }\n}\nexport function resolveAuthSelection(currentUrl) {\n // A URL selection owns the whole pair: never attach an unrelated configured account/project.\n const hasUrlScope = currentUrl.searchParams.has('a') || currentUrl.searchParams.has('p');\n const defaults = hasUrlScope ? undefined : Env.defaultAuthSelection;\n const urlAccount = currentUrl.searchParams.get('a') ?? defaults?.accountId;\n const urlProject = currentUrl.searchParams.get('p') ?? defaults?.projectId;\n const accountId = urlAccount ??\n (urlProject === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);\n const projectId = urlProject ?? localStorage.getItem(`${LastSelectedProjectId_KEY}-${accountId}`) ?? undefined;\n return { accountId, projectId };\n}\nfunction normalizeIssuer(value) {\n return value?.replace(/\\/+$/, '');\n}\nfunction decodeToken(token) {\n return jwtDecode(token);\n}\nfunction isVertesiaIssuedToken(token) {\n if (!token)\n return false;\n try {\n const decoded = decodeToken(token);\n return normalizeIssuer(decoded.iss) === normalizeIssuer(Env.endpoints.sts);\n }\n catch {\n return false;\n }\n}\nfunction canUseVertesiaTokenDirectly(token, accountId, projectId) {\n const decoded = decodeToken(token);\n if (!decoded.exp || decoded.exp <= Date.now() / 1000 + 300) {\n return false;\n }\n const hasAuthorizationClaims = Boolean(decoded.permissions?.length ||\n decoded.account_roles?.length ||\n decoded.project_roles?.length ||\n decoded.apps?.length);\n if (!hasAuthorizationClaims) {\n return false;\n }\n if (accountId && decoded.account?.id !== accountId) {\n return false;\n }\n if (projectId && decoded.project?.id !== projectId) {\n return false;\n }\n return true;\n}\nexport async function fetchComposableToken(getIdToken, accountId, projectId, ttl, retryCount = 0) {\n Env.logger.debug('Getting/refreshing composable token', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n retry_count: retryCount,\n },\n });\n const idToken = await getIdToken(); //get from firebase\n if (!idToken) {\n throw new Error('No id token found');\n }\n // Use STS endpoint - either configured or default to sts.vertesia.io\n const stsEndpoint = Env.endpoints.sts;\n Env.logger.debug('Using STS for token generation', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n sts_url: stsEndpoint,\n },\n });\n try {\n // Call STS to generate a user token\n const stsUrl = new URL(`${stsEndpoint}/token/issue`);\n const requestBody = {\n type: 'user',\n account_id: accountId,\n project_id: projectId,\n expires_at: ttl ? Math.floor(Date.now() / 1000) + ttl : undefined,\n };\n const stsRes = await fetch(stsUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${idToken}`, // Firebase token for authentication\n },\n body: JSON.stringify(requestBody),\n }).catch((error) => {\n console.error('Failed to call STS endpoint', error);\n Env.logger.error('Failed to call STS endpoint', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n error: error,\n },\n });\n throw new AuthenticationServiceError('The authentication service could not be reached', stsEndpoint, {\n cause: error,\n });\n });\n if (idToken && stsRes?.status === 404) {\n // User not found in token-server - call ensure-user endpoint\n Env.logger.debug('404: User not found - calling ensure-user endpoint', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: stsRes?.status,\n },\n });\n const ensureResponse = await fetch(`${Env.endpoints.studio}/auth/ensure-user`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${idToken}`,\n 'Content-Type': 'application/json',\n },\n });\n if (ensureResponse.status === 412) {\n // No invite - trigger signup\n Env.logger.debug('412: No invite found - signup required', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n },\n });\n const idTokenDecoded = jwtDecode(idToken);\n if (!idTokenDecoded?.email) {\n Env.logger.error('No email found in id token');\n throw new Error('No email found in id token');\n }\n throw new UserNotFoundError('User not found - signup required', idTokenDecoded.email);\n }\n if (ensureResponse.status === 403) {\n // SigninScreen keys the invite-required view off this message.\n Env.logger.warn('403: Customer-domain user requires an invite to join', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n },\n });\n throw new Error('Customer-domain user requires an invite to join');\n }\n if (!ensureResponse.ok) {\n console.error('Failed to ensure user exists', ensureResponse.status);\n Env.logger.error('Failed to ensure user exists', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: ensureResponse.status,\n },\n });\n throw new Error('Failed to ensure user exists');\n }\n // User created/exists - retry token generation\n Env.logger.debug('User ensured - retrying token generation', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n },\n });\n return fetchComposableToken(getIdToken, accountId, projectId, ttl, retryCount);\n }\n if (idToken && stsRes?.status === 412) {\n Env.logger.info(\"412: auth succeeded but user doesn't exist - signup required\", {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: stsRes?.status,\n },\n });\n const idTokenDecoded = jwtDecode(idToken);\n if (!idTokenDecoded?.email) {\n Env.logger.error('No email found in id token');\n throw new Error('No email found in id token');\n }\n Env.logger.info('User not found', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n email: idTokenDecoded.email,\n },\n });\n throw new UserNotFoundError('User not found', idTokenDecoded.email);\n }\n if (stsRes.status === 401) {\n Env.logger.warn('STS rejected the identity credential', {\n vertesia: { account_id: accountId, project_id: projectId, status: stsRes.status },\n });\n throw new CredentialError('Your sign-in credentials could not be verified', stsEndpoint, {\n status: stsRes.status,\n });\n }\n if (stsRes.status === 403) {\n const body = (await stsRes.json().catch(() => undefined));\n const identity = identityFromAcceptedToken(idToken);\n if (body?.errorCode === RESTRICTED_ENVIRONMENT_ERROR_CODE) {\n Env.logger.warn('403: User lacks early-access for this restricted environment', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n },\n });\n throw new RestrictedEnvironmentError(body.message ?? \"You don't have sufficient permission to visit unstable environments\");\n }\n if (body?.errorCode === NO_ACCESSIBLE_ACCOUNT_ERROR_CODE) {\n Env.logger.warn('STS identity has no accessible account', {\n vertesia: { error_code: body.errorCode, status: stsRes.status },\n });\n throw new NoAccessibleAccountError(stsEndpoint, body.message, identity);\n }\n // During rolling deployment an older STS may return an uncoded user-token 403.\n // Treat it as a requested-scope rejection; all calls in this function issue user tokens.\n if (!body?.errorCode || body.errorCode === REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE) {\n Env.logger.warn('STS rejected the requested account or project', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: stsRes.status,\n error_code: body?.errorCode ?? REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,\n },\n });\n throw new RequestedScopeUnavailableError(stsEndpoint, accountId, projectId, body?.errorCode ? body.message : undefined, identity);\n }\n Env.logger.error('STS returned an unrecognized forbidden response', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n status: stsRes.status,\n error_code: body.errorCode,\n },\n });\n throw new AuthenticationServiceError('The authentication service rejected the request', stsEndpoint, {\n status: stsRes.status,\n errorCode: body.errorCode,\n cause: body,\n });\n }\n if (!stsRes.ok) {\n const errorText = await stsRes.text();\n console.error('STS token generation failed:', stsRes.status, errorText);\n Env.logger.error('STS token generation failed', {\n vertesia: {\n status: stsRes.status,\n error: errorText,\n account_id: accountId,\n project_id: projectId,\n },\n });\n throw new AuthenticationServiceError('The authentication service could not complete sign-in', stsEndpoint, {\n status: stsRes.status,\n cause: errorText,\n });\n }\n const { token } = await stsRes.json();\n Env.logger.debug('Successfully got token from STS');\n return token;\n }\n catch (error) {\n if (error instanceof UserNotFoundError ||\n error instanceof STSError ||\n error instanceof RestrictedEnvironmentError) {\n // Re-throw typed auth errors to be handled separately in the caller\n throw error;\n }\n if (error instanceof Error && error.message === 'Customer-domain user requires an invite to join')\n throw error;\n console.error('Failed to get composable token from STS', error);\n Env.logger.error('Failed to get composable token from STS', {\n vertesia: {\n account_id: accountId,\n project_id: projectId,\n error: error,\n },\n });\n throw new AuthenticationServiceError('The authentication service could not complete sign-in', stsEndpoint, {\n cause: error,\n });\n }\n}\n/**\n *\n * @param accountId\n * @param projectId\n * @param ttl time to live for the token in seconds\n * @returns\n */\nexport async function fetchComposableTokenFromFirebaseToken(accountId, projectId, ttl) {\n return fetchComposableToken(getFirebaseAuthToken, accountId, projectId, ttl);\n}\n/**\n * Mint a scoped Vertesia token from an existing Vertesia JWT. STS accepts STS-issued\n * tokens on /token/issue, so this works for sessions established via Central Auth where\n * the browser has no Firebase user.\n */\nexport async function fetchComposableTokenFromVertesiaToken(vertesiaToken, accountId, projectId, ttl) {\n return fetchComposableToken(() => Promise.resolve(vertesiaToken), accountId, projectId, ttl);\n}\n/** Returns the cached Vertesia raw JWT, if any. Does not refresh. */\nexport function getCurrentVertesiaToken() {\n return AUTH_TOKEN_RAW;\n}\nexport async function getComposableToken(accountId, projectId, initToken, forceRefresh = false, useInternalAuth = false) {\n const selectedAccount = accountId ??\n (projectId === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);\n const selectedProject = projectId ?? localStorage.getItem(`${LastSelectedProjectId_KEY}-${selectedAccount}`) ?? undefined;\n const devAuthToken = Env.isLocalDev ? Env.devAuthToken : undefined;\n const suppliedToken = devAuthToken ?? initToken ?? AUTH_TOKEN_RAW;\n const cachedTokenMatchesScope = (!selectedAccount || AUTH_TOKEN?.account?.id === selectedAccount) &&\n (!selectedProject || AUTH_TOKEN?.project?.id === selectedProject);\n // Token is still valid for more than 5 minutes and belongs to the requested scope.\n if (!forceRefresh &&\n AUTH_TOKEN_RAW &&\n AUTH_TOKEN &&\n cachedTokenMatchesScope &&\n AUTH_TOKEN.exp > Date.now() / 1000 + 300) {\n HAD_SESSION = true;\n return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };\n }\n if (!forceRefresh &&\n isVertesiaIssuedToken(suppliedToken) &&\n canUseVertesiaTokenDirectly(suppliedToken, selectedAccount, selectedProject)) {\n AUTH_TOKEN_RAW = suppliedToken;\n AUTH_TOKEN = decodeToken(AUTH_TOKEN_RAW);\n if (!AUTH_TOKEN.exp) {\n throw new Error('Invalid composable token');\n }\n HAD_SESSION = true;\n return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };\n }\n try {\n //token is close to expire, refresh it\n if (!devAuthToken && !useInternalAuth && getFirebaseAuth().currentUser) {\n //we have a firebase user, get the token from there\n AUTH_TOKEN_RAW = await fetchComposableTokenFromFirebaseToken(selectedAccount, selectedProject);\n }\n else if (!devAuthToken) {\n // Embedded apps can reacquire a fresh credential from their host after their cached token expires.\n const refreshCredential = (await Env.authTokenProvider?.()) ?? initToken ?? AUTH_TOKEN_RAW;\n // `forceRefresh` has to defeat this shortcut, not just the cache above it. A caller\n // asking for a forced refresh wants claims recomputed -- `refreshAuthToken()` exists so\n // a stale `apps` claim can be re-read after an ACE change, and STS recomputes it on\n // every issuance. Adopting the credential we already hold satisfies the check and\n // returns the very token whose claims were suspect, silently making the call a no-op\n // for exactly the sessions that have no other credential to fall back on.\n if (!forceRefresh &&\n refreshCredential &&\n isVertesiaIssuedToken(refreshCredential) &&\n canUseVertesiaTokenDirectly(refreshCredential, selectedAccount, selectedProject)) {\n AUTH_TOKEN_RAW = refreshCredential;\n }\n else if (refreshCredential) {\n AUTH_TOKEN_RAW = await fetchComposableToken(() => Promise.resolve(refreshCredential), selectedAccount, selectedProject);\n }\n else {\n AUTH_TOKEN_RAW = undefined;\n }\n }\n else if (devAuthToken) {\n AUTH_TOKEN_RAW = devAuthToken;\n }\n }\n catch (error) {\n if (error instanceof RequestedScopeUnavailableError ||\n error instanceof NoAccessibleAccountError ||\n error instanceof CredentialError) {\n AUTH_TOKEN_RAW = undefined;\n AUTH_TOKEN = undefined;\n if (error instanceof RequestedScopeUnavailableError) {\n clearRejectedPersistedScope(selectedAccount, selectedProject);\n }\n }\n // An expired Central Auth session presents as a rejected credential: the JWT we sent STS to\n // renew from is the one that timed out. The broker can still vouch for the user. Only this\n // error -- a scope or account failure is about who the user is, and the broker would hand\n // back the same identity and the same refusal.\n if (error instanceof CredentialError) {\n renewExpiredCentralAuthSession();\n }\n throw error;\n }\n if (!AUTH_TOKEN_RAW) {\n Env.logger.error('Cannot acquire a composable token', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n // Reached once the credential has already been discarded -- typically the call after the\n // one that saw STS reject it. Same remedy, and the cooldown keeps this from redirecting a\n // second time for the same expiry.\n renewExpiredCentralAuthSession();\n throw new Error('Cannot acquire a composable token');\n }\n AUTH_TOKEN = decodeToken(AUTH_TOKEN_RAW);\n if (!AUTH_TOKEN?.exp || !AUTH_TOKEN_RAW) {\n console.error('Invalid composable token', AUTH_TOKEN);\n Env.logger.error('Invalid composable token', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n throw new Error('Invalid composable token');\n }\n HAD_SESSION = true;\n return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };\n}\nexport class UserNotFoundError extends Error {\n email;\n constructor(message, email) {\n super(message);\n this.name = 'UserNotFoundError';\n this.email = email;\n }\n}\nexport class STSError extends Error {\n stsURL;\n status;\n errorCode;\n constructor(message, stsURL, options) {\n super(message, options);\n this.name = 'STSError';\n this.stsURL = stsURL;\n this.status = options?.status;\n this.errorCode = options?.errorCode;\n }\n}\nexport class RequestedScopeUnavailableError extends STSError {\n accountId;\n projectId;\n identity;\n requestedScope;\n selectorSource;\n recoveryUrl;\n constructor(stsURL, accountId, projectId, responseMessage, identity) {\n super(responseMessage ?? 'The requested account or project is not available.', stsURL, {\n status: 403,\n errorCode: REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,\n });\n this.accountId = accountId;\n this.projectId = projectId;\n this.identity = identity;\n this.name = 'RequestedScopeUnavailableError';\n this.requestedScope = projectId ? 'project' : 'account';\n }\n}\n/** @deprecated Use RequestedScopeUnavailableError. */\nexport const TokenAuthorizationError = RequestedScopeUnavailableError;\nexport class NoAccessibleAccountError extends STSError {\n identity;\n constructor(stsURL, responseMessage, identity) {\n super(responseMessage ?? 'No accessible account is available for this user.', stsURL, {\n status: 403,\n errorCode: NO_ACCESSIBLE_ACCOUNT_ERROR_CODE,\n });\n this.identity = identity;\n this.name = 'NoAccessibleAccountError';\n }\n}\nexport class CredentialError extends STSError {\n constructor(message, stsURL, options) {\n super(message, stsURL, options);\n this.name = 'CredentialError';\n }\n}\nexport class AuthenticationServiceError extends STSError {\n constructor(message, stsURL, options) {\n super(message, stsURL, options);\n this.name = 'AuthenticationServiceError';\n }\n}\nexport function isRequestedScopeUnavailableError(error) {\n return error instanceof RequestedScopeUnavailableError;\n}\nexport function isNoAccessibleAccountError(error) {\n return error instanceof NoAccessibleAccountError;\n}\nexport function isCredentialError(error) {\n return error instanceof CredentialError;\n}\nexport function isAuthenticationServiceError(error) {\n return error instanceof AuthenticationServiceError;\n}\n/**\n * Thrown when the STS rejects sign-in because the current environment (preview/preprod) is\n * restricted to early-access users and this user lacks the `early-access` annotation. The\n * sign-in screen renders a dedicated \"restricted environment\" step for this error.\n * See docs/restrict-access-to-non-production-envs.md.\n */\nexport class RestrictedEnvironmentError extends Error {\n constructor(message) {\n super(message);\n this.name = 'RestrictedEnvironmentError';\n }\n}\n//# sourceMappingURL=composable.js.map","/**\n * This hook is used to generate and verify state for OAuth2 authorization requests.\n *\n * The implementation lives in ./authState so the token layer can start the same round-trip when it\n * renews an expired Central Auth session, where no component is available to call a hook.\n */\nimport { useCallback } from 'react';\nimport { clearAuthState, generateAuthState, verifyAuthState } from './authState';\nexport function useAuthState() {\n const generateState = useCallback(() => generateAuthState(), []);\n const verifyState = useCallback((returnedState) => verifyAuthState(returnedState), []);\n const clearState = useCallback(() => clearAuthState(), []);\n return { generateState, verifyState, clearState };\n}\n//# sourceMappingURL=useAuthState.js.map","import { VertesiaClient } from '@vertesia/client';\nimport { Env } from '@vertesia/ui/env';\nimport { jwtDecode } from 'jwt-decode';\nimport { createContext, useContext } from 'react';\nimport { getComposableToken } from './auth/composable';\nimport { authReturnUrl, centralAuthUrl, mountRootUrl, shouldRedirectToCentralAuth } from './auth/domainRouting';\nimport { getFirebaseAuth } from './auth/firebase';\nimport { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from './constants';\nexport { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY };\nclass UserSession {\n isLoading = true;\n client;\n authError;\n authToken;\n setSession;\n lastSelectedAccount;\n lastSelectedProject;\n onboardingComplete;\n constructor(client, setSession) {\n if (client) {\n this.client = client;\n }\n else {\n this.client = new VertesiaClient({\n serverUrl: Env.endpoints.studio,\n storeUrl: Env.endpoints.zeno,\n tokenServerUrl: Env.endpoints.sts,\n });\n }\n if (setSession) {\n this.setSession = setSession;\n }\n this.logout = this.logout.bind(this);\n this.signOut = this.signOut.bind(this);\n }\n get store() {\n return this.client.store;\n }\n get user() {\n //compatibility\n return this.authToken;\n }\n get account() {\n //compatibility\n return this.authToken?.account;\n }\n get project() {\n return this.authToken?.project;\n }\n get accounts() {\n //compatibility\n return this.authToken?.accounts;\n }\n get authCallback() {\n return this.rawAuthToken.then((token) => `Bearer ${token}`);\n }\n get rawAuthToken() {\n return getComposableToken().then((res) => {\n const token = res?.rawToken;\n if (!token) {\n throw new Error('No token available');\n }\n this.authToken = jwtDecode(token);\n return token;\n });\n }\n /**\n * Force a fresh Vertesia JWT from STS, bypassing the in-memory cache.\n * Use this when the current token's claims (e.g. `apps`) are suspected\n * stale — STS recomputes the `apps` claim from ACEs on every issuance.\n */\n async refreshAuthToken() {\n const res = await getComposableToken(undefined, undefined, undefined, true);\n const token = res?.rawToken;\n if (!token) {\n throw new Error('No token available');\n }\n this.authToken = jwtDecode(token);\n this.setSession?.(this.clone());\n return this.authToken;\n }\n signOut() {\n //compatibility\n this.logout();\n }\n getAccount() {\n return this.authToken?.account;\n }\n async login(token, options = {}) {\n this.authError = undefined;\n this.isLoading = false;\n this.client.withAuthCallback(() => this.authCallback);\n this.authToken = jwtDecode(token);\n console.log(`Logging in as ${this.authToken?.name} with account ${this.authToken?.account.name} (${this.authToken?.account.id}, and project ${this.authToken?.project?.name} (${this.authToken?.project?.id})`);\n //store selected account in local storage\n localStorage.setItem(LastSelectedAccountId_KEY, this.authToken.account.id);\n localStorage.setItem(`${LastSelectedProjectId_KEY}-${this.authToken.account.id}`, this.authToken.project?.id ?? '');\n // notify the host app of the login\n Env.onLogin?.(this.authToken);\n if (options.loadOnboardingStatus ?? true) {\n await this.fetchOnboardingStatus();\n }\n return Promise.resolve();\n }\n isLoggedIn() {\n return !!this.authToken;\n }\n logout() {\n console.log('Logging out');\n if (shouldRedirectToCentralAuth()) {\n // Redirect to central auth for logout\n // Central auth will handle Firebase logout\n console.log('Using central auth logout');\n this.authError = undefined;\n this.isLoading = false;\n this.authToken = undefined;\n this.setSession = undefined;\n this.client.withAuthCallback(undefined);\n const logoutUrl = new URL(centralAuthUrl());\n const currentUrl = authReturnUrl();\n logoutUrl.pathname = '/logout';\n logoutUrl.searchParams.set('redirect_uri', currentUrl.toString());\n location.replace(logoutUrl.toString());\n }\n else {\n // Use Firebase logout directly\n console.log('Using Firebase logout');\n const wasLoggedIn = !!this.authToken;\n if (this.authToken) {\n void getFirebaseAuth().signOut();\n }\n this.authError = undefined;\n this.isLoading = false;\n this.authToken = undefined;\n this.setSession = undefined;\n this.client.withAuthCallback(undefined);\n // Navigate to the app root to avoid React rendering errors when\n // unmounting deeply nested route components during logout.\n // Use the mount root (not bare '/') so a gateway-mounted app stays\n // on its mount. Only navigate if user was actually logged in to\n // avoid an infinite reload loop on fresh/incognito sessions.\n if (wasLoggedIn) {\n location.replace(mountRootUrl().toString());\n }\n }\n }\n async switchAccount(targetAccountId) {\n localStorage.setItem(LastSelectedAccountId_KEY, targetAccountId);\n if (this) {\n if (this.account && this.project) {\n localStorage.setItem(`${LastSelectedProjectId_KEY}-${this.account.id}`, this.project.id);\n }\n else if (this.account) {\n localStorage.removeItem(`${LastSelectedProjectId_KEY}-${this.account.id}`);\n }\n }\n const url = mountRootUrl();\n url.searchParams.set('a', targetAccountId);\n window.location.replace(url.toString());\n }\n async switchProject(targetProjectId) {\n if (this.account) {\n localStorage.setItem(`${LastSelectedProjectId_KEY}-${this.account.id}`, targetProjectId);\n }\n const url = mountRootUrl();\n if (this.account?.id) {\n url.searchParams.set('a', this.account.id);\n }\n url.searchParams.set('p', targetProjectId);\n window.location.replace(url.toString());\n }\n async fetchAccounts() {\n return this.client.accounts\n .list()\n .then((accounts) => {\n if (!this.authToken) {\n throw new Error('No token available');\n }\n this.authToken.accounts = accounts;\n this.setSession?.(this.clone());\n })\n .catch((err) => {\n console.error('Failed to fetch accounts', err);\n throw err;\n });\n }\n async fetchProjects(accountId) {\n return this.client.projects\n .list([accountId])\n .then((projects) => {\n if (!this.authToken) {\n throw new Error('No token available');\n }\n this.authToken = {\n ...this.authToken,\n accounts: this.authToken.accounts?.map((account) => {\n if (account.id === accountId) {\n return {\n ...account,\n projects: projects.filter((project) => project.account === accountId),\n };\n }\n return account;\n }),\n };\n this.setSession?.(this.clone());\n })\n .catch((err) => {\n console.error('Failed to fetch projects', err);\n throw err;\n });\n }\n async fetchOnboardingStatus() {\n if (!this.project?.id) {\n return false;\n }\n if (this.onboardingComplete) {\n console.log('Onboarding already completed');\n return false;\n }\n const previousStatus = this.onboardingComplete;\n try {\n const onboarding = await this.client.account.onboardingProgress();\n this.onboardingComplete = Object.values(onboarding).every((value) => value === true);\n if (previousStatus !== this.onboardingComplete) {\n this.setSession?.(this.clone());\n }\n return previousStatus === false && this.onboardingComplete;\n }\n catch (error) {\n console.error('Error fetching onboarding status:', error);\n this.onboardingComplete = false;\n if (previousStatus !== this.onboardingComplete) {\n this.setSession?.(this.clone());\n }\n }\n return false;\n }\n clone() {\n const session = new UserSession(this.client);\n session.isLoading = this.isLoading;\n session.authError = this.authError;\n session.authToken = this.authToken;\n session.setSession = this.setSession;\n session.lastSelectedAccount = this.lastSelectedAccount;\n session.switchAccount = this.switchAccount;\n session.onboardingComplete = this.onboardingComplete;\n return session;\n }\n}\nconst UserSessionContext = createContext(undefined);\nexport function useUserSession() {\n const session = useContext(UserSessionContext);\n if (!session) {\n throw new Error('useUserSession must be used within a UserSessionProvider');\n }\n return session;\n}\nexport { UserSession, UserSessionContext };\n//# sourceMappingURL=UserSession.js.map","import { i18nInstance, NAMESPACE } from '@vertesia/ui/i18n';\nimport { useEffect, useState } from 'react';\nimport { useUserSession } from '../UserSession';\nexport function useCurrentTenant() {\n const { user } = useUserSession();\n const [currentTenant, setCurrentTenant] = useState(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState(null);\n useEffect(() => {\n const loadCurrentTenant = async () => {\n if (!user?.email) {\n setCurrentTenant(null);\n setIsLoading(false);\n return;\n }\n try {\n const response = await fetch('/api/resolve-tenant', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n tenantEmail: user.email,\n }),\n });\n if (response.ok) {\n const tenantData = await response.json();\n if (tenantData) {\n // Convert the resolved tenant data to our TenantConfig format\n setCurrentTenant({\n tenantKey: tenantData.name || 'unknown',\n name: tenantData.label || tenantData.name || 'Unknown',\n domain: tenantData.domain || [],\n firebaseTenantId: tenantData.firebaseTenantId,\n provider: tenantData.provider,\n logo: tenantData.logo,\n });\n }\n else {\n setCurrentTenant(null);\n }\n }\n else {\n setCurrentTenant(null);\n }\n }\n catch (error) {\n console.error('Error loading current tenant:', error);\n setError(i18nInstance.getFixedT(null, NAMESPACE)('errors.failedToLoadTenantConfig'));\n setCurrentTenant(null);\n }\n finally {\n setIsLoading(false);\n }\n };\n void loadCurrentTenant();\n }, [user?.email]);\n return {\n currentTenant,\n isLoading,\n error,\n };\n}\n//# sourceMappingURL=useCurrentTenant.js.map","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { useMemo } from 'react';\nimport { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from './constants';\nimport { UserSession, UserSessionContext } from './UserSession';\nfunction decodeJwtPayload(token) {\n const [, payload] = token.split('.');\n if (!payload) {\n throw new Error('Invalid Vertesia auth token');\n }\n const padded = payload\n .replace(/-/g, '+')\n .replace(/_/g, '/')\n .padEnd(Math.ceil(payload.length / 4) * 4, '=');\n return JSON.parse(atob(padded));\n}\nexport function DevSessionProvider({ children, token }) {\n const session = useMemo(() => {\n const next = new UserSession();\n next.isLoading = false;\n try {\n next.authToken = decodeJwtPayload(token);\n next.client.withAuthCallback(() => Promise.resolve(`Bearer ${token}`));\n localStorage.setItem(LastSelectedAccountId_KEY, next.authToken.account.id);\n localStorage.setItem(`${LastSelectedProjectId_KEY}-${next.authToken.account.id}`, next.authToken.project?.id ?? '');\n }\n catch (error) {\n next.authError = error instanceof Error ? error : new Error(String(error));\n }\n return next.clone();\n }, [token]);\n return _jsx(UserSessionContext.Provider, { value: session, children: children });\n}\n//# sourceMappingURL=DevSessionProvider.js.map","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE } from '@vertesia/common';\nimport { Env } from '@vertesia/ui/env';\nimport { onAuthStateChanged } from 'firebase/auth';\nimport { useEffect, useRef, useState } from 'react';\nimport { AuthenticationServiceError, CredentialError, getComposableToken, NoAccessibleAccountError, RequestedScopeUnavailableError, RestrictedEnvironmentError, resolveAuthSelection, UserNotFoundError, } from './auth/composable';\nimport { buildCentralAuthRedirectUrl, redirectToCentralAuth, shouldRedirectToCentralAuth } from './auth/domainRouting';\nimport { getFirebaseAuth } from './auth/firebase';\nimport { useAuthState } from './auth/useAuthState';\nimport { UserSession, UserSessionContext } from './UserSession';\nfunction clearAuthHash() {\n const url = new URL(window.location.href);\n url.hash = '';\n window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}`);\n}\n// Re-exported from its new home in ./auth/domainRouting, which the token layer can import without\n// a cycle -- it renews an expired Central Auth session through the same URL.\nexport { buildCentralAuthRedirectUrl };\nexport function sanitizeRejectedScopeUrl(currentUrl, error, clearHash = false) {\n const sanitizedUrl = new URL(currentUrl);\n const urlAccountId = sanitizedUrl.searchParams.get('a') ?? undefined;\n const urlProjectId = sanitizedUrl.searchParams.get('p') ?? undefined;\n if (urlAccountId && urlProjectId && urlAccountId === error.accountId && urlProjectId === error.projectId) {\n sanitizedUrl.searchParams.delete('a');\n sanitizedUrl.searchParams.delete('p');\n error.requestedScope = 'project';\n error.selectorSource = 'url';\n }\n else if (urlProjectId && urlProjectId === error.projectId) {\n sanitizedUrl.searchParams.delete('p');\n error.requestedScope = 'project';\n error.selectorSource = 'url';\n }\n else if (urlAccountId && urlAccountId === error.accountId) {\n sanitizedUrl.searchParams.delete('a');\n error.requestedScope = 'account';\n error.selectorSource = 'url';\n }\n else {\n error.requestedScope = error.projectId ? 'project' : 'account';\n error.selectorSource = 'persisted';\n }\n if (clearHash)\n sanitizedUrl.hash = '';\n error.recoveryUrl = sanitizedUrl.toString();\n return sanitizedUrl;\n}\nexport function UserSessionProvider({ children, loadOnboardingStatus = true }) {\n const hashParams = new URLSearchParams(location.hash.substring(1));\n const token = hashParams.get('token');\n const state = hashParams.get('state');\n const [session, setSession] = useState(new UserSession());\n const { verifyState, clearState } = useAuthState();\n const hasInitiatedAuthRef = useRef(false);\n const authFlowRef = useRef(undefined);\n const clearRejectedUrlScope = (error, clearHash = false) => {\n const sanitizedUrl = sanitizeRejectedScopeUrl(new URL(window.location.href), error, clearHash);\n window.history.replaceState(window.history.state, '', sanitizedUrl);\n Env.logger.warn('Sanitized an unavailable authentication scope', {\n vertesia: {\n account_id: error.accountId,\n project_id: error.projectId,\n error_code: REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,\n selector_source: error.selectorSource,\n requested_scope: error.requestedScope,\n recovery_action: 'url_sanitized',\n },\n });\n };\n const surfaceAuthError = (error, clearHash = false) => {\n if (!(error instanceof RequestedScopeUnavailableError ||\n error instanceof NoAccessibleAccountError ||\n error instanceof CredentialError ||\n error instanceof AuthenticationServiceError ||\n error instanceof UserNotFoundError ||\n error instanceof RestrictedEnvironmentError)) {\n return false;\n }\n if (error instanceof RequestedScopeUnavailableError) {\n clearRejectedUrlScope(error, clearHash);\n }\n else if (clearHash) {\n clearAuthHash();\n }\n session.isLoading = false;\n session.authError = error;\n setSession(session.clone());\n return true;\n };\n authFlowRef.current = () => {\n // Make this effect idempotent - only run auth flow once\n if (hasInitiatedAuthRef.current) {\n Env.logger.debug('Skipping duplicate auth flow initiation');\n return;\n }\n hasInitiatedAuthRef.current = true;\n Env.logger.debug('Starting auth flow');\n const currentUrl = new URL(window.location.href);\n const { accountId: selectedAccount, projectId: selectedProject } = resolveAuthSelection(currentUrl);\n Env.logger.debug('Selected account and project', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n if (Env.isLocalDev && Env.devAuthToken) {\n session.setSession = setSession;\n getComposableToken(selectedAccount, selectedProject, Env.devAuthToken)\n .then((res) => {\n return session.login(res.rawToken).then(() => setSession(session.clone()));\n })\n .catch((err) => {\n if (surfaceAuthError(err))\n return;\n console.error('Failed to initialize dev auth token', err);\n Env.logger.error('Failed to initialize dev auth token', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n error: err,\n },\n });\n session.isLoading = false;\n session.authError = err instanceof Error ? err : new Error(String(err));\n setSession(session.clone());\n });\n return;\n }\n if (token && state) {\n session.setSession = setSession;\n const validationError = verifyState(state);\n if (validationError) {\n console.error(`Auth: invalid state: ${validationError}`);\n Env.logger.error(`Invalid state: ${validationError}`, {\n vertesia: {\n state: state,\n },\n });\n redirectToCentralAuth({ accountId: selectedAccount, projectId: selectedProject });\n }\n else {\n clearState();\n }\n getComposableToken(selectedAccount, selectedProject, token, false, shouldRedirectToCentralAuth())\n .then((res) => {\n return session.login(res.rawToken, { loadOnboardingStatus }).then(() => {\n setSession(session.clone());\n clearAuthHash();\n });\n })\n .catch((err) => {\n if (surfaceAuthError(err, true))\n return;\n console.error('Failed to fetch user token from studio, redirecting to central auth', err);\n Env.logger.error('Failed to fetch user token from studio, redirecting to central auth', {\n vertesia: {\n error: err,\n },\n });\n redirectToCentralAuth({ accountId: selectedAccount, projectId: selectedProject });\n });\n return;\n }\n let cancelled = false;\n let unsubscribe;\n const startFirebaseOrCentralAuth = () => {\n if (cancelled)\n return;\n // If the current host is not in the Firebase allowlist, central auth owns sign-in.\n if (!session.isLoggedIn()) {\n Env.logger.debug('Not logged in & no token/state', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n if (shouldRedirectToCentralAuth()) {\n Env.logger.debug('Redirecting to central auth with selection', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n redirectToCentralAuth({ accountId: selectedAccount, projectId: selectedProject });\n return; // Don't register onAuthStateChanged listener when redirecting\n }\n Env.logger.debug('Host is in Firebase auth allowlist', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n }\n unsubscribe = onAuthStateChanged(getFirebaseAuth(), async (firebaseUser) => {\n if (firebaseUser) {\n Env.logger.debug('Successful login with firebase', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n session.setSession = setSession;\n await getComposableToken(selectedAccount, selectedProject, undefined, false, shouldRedirectToCentralAuth())\n .then((res) => {\n return session\n .login(res.rawToken, { loadOnboardingStatus })\n .then(() => setSession(session.clone()));\n })\n .catch((err) => {\n if (surfaceAuthError(err))\n return;\n console.error('Unexpected failure while fetching a user token', err);\n Env.logger.error('Unexpected failure while fetching a user token', {\n vertesia: { account_id: selectedAccount, project_id: selectedProject, error: err },\n });\n session.logout();\n session.isLoading = false;\n session.authError = err;\n setSession(session.clone());\n });\n }\n else {\n // anonymous user\n Env.logger.debug('Using anonymous user', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n },\n });\n session.client.withAuthCallback(undefined);\n session.logout();\n setSession(session.clone());\n }\n });\n };\n if (Env.authTokenProvider) {\n session.setSession = setSession;\n void Env.authTokenProvider()\n .then(async (injectedToken) => {\n if (cancelled)\n return;\n if (!injectedToken) {\n startFirebaseOrCentralAuth();\n return;\n }\n const res = await getComposableToken(selectedAccount, selectedProject, injectedToken, false, shouldRedirectToCentralAuth());\n await session.login(res.rawToken, { loadOnboardingStatus });\n if (!cancelled)\n setSession(session.clone());\n })\n .catch((err) => {\n if (cancelled)\n return;\n if (surfaceAuthError(err))\n return;\n console.warn('Auth: failed to initialize injected auth token', err);\n Env.logger.warn('Failed to initialize injected auth token', {\n vertesia: {\n account_id: selectedAccount,\n project_id: selectedProject,\n error: err,\n },\n });\n session.isLoading = false;\n session.authError = err instanceof Error ? err : new Error(String(err));\n setSession(session.clone());\n });\n return () => {\n cancelled = true;\n hasInitiatedAuthRef.current = false;\n unsubscribe?.();\n };\n }\n startFirebaseOrCentralAuth();\n return () => {\n cancelled = true;\n hasInitiatedAuthRef.current = false;\n unsubscribe?.();\n };\n };\n useEffect(() => {\n return authFlowRef.current?.();\n }, []);\n return _jsx(UserSessionContext.Provider, { value: session, children: children });\n}\n//# sourceMappingURL=UserSessionProvider.js.map","import { Env } from '@vertesia/ui/env';\nimport { logEvent } from 'firebase/analytics';\nimport { getFirebaseAnalytics } from './auth/firebase';\nexport function useUXTracking() {\n //identify user in monitoring and UX systems\n const tagUserSession = async (user) => {\n const signupData = window.localStorage.getItem('composableSignupData');\n if (!user) {\n console.error('No user found -- skipping tagging');\n return;\n }\n if (signupData) {\n window.localStorage.removeItem('composableSignupData');\n }\n };\n //send event to analytics and UX systems\n const trackEvent = (eventName, eventProperties) => {\n if (!Env.isProd) {\n console.debug('track event', eventName, eventProperties);\n }\n // GA via firebase — needs a complete Firebase config. A partial one (no apiKey, as in\n // central-auth deployments and local setups) would make the Installations SDK log an\n // async FirebaseError on every event, so analytics is a no-op there.\n if (!Env.firebase?.apiKey) {\n return;\n }\n logEvent(getFirebaseAnalytics(), eventName, { ...eventProperties, debug_mode: !Env.isProd });\n };\n return {\n tagUserSession,\n trackEvent,\n };\n}\n//# sourceMappingURL=useUXTracking.js.map"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertesia/ui",
|
|
3
|
-
"version": "1.6.0-dev.20260918.
|
|
3
|
+
"version": "1.6.0-dev.20260918.063824Z",
|
|
4
4
|
"description": "Vertesia UI components and and hooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"@radix-ui/react-switch": "^1.3.7",
|
|
46
46
|
"@radix-ui/react-tabs": "^1.1.21",
|
|
47
47
|
"@radix-ui/react-tooltip": "^1.2.16",
|
|
48
|
-
"@vertesia/client": "1.6.0-dev.20260918.
|
|
49
|
-
"@vertesia/common": "1.6.0-dev.20260918.
|
|
50
|
-
"@vertesia/fusion-ux": "1.6.0-dev.20260918.
|
|
51
|
-
"@vertesia/json": "1.6.0-dev.20260918.
|
|
52
|
-
"@vertesia/rich-text": "1.6.0-dev.20260918.
|
|
48
|
+
"@vertesia/client": "1.6.0-dev.20260918.063824Z",
|
|
49
|
+
"@vertesia/common": "1.6.0-dev.20260918.063824Z",
|
|
50
|
+
"@vertesia/fusion-ux": "1.6.0-dev.20260918.063824Z",
|
|
51
|
+
"@vertesia/json": "1.6.0-dev.20260918.063824Z",
|
|
52
|
+
"@vertesia/rich-text": "1.6.0-dev.20260918.063824Z",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
55
55
|
"aria-hidden": "^1.2.6",
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
"url": "https://github.com/vertesia/composableai.git",
|
|
189
189
|
"directory": "packages/ui"
|
|
190
190
|
},
|
|
191
|
-
"gitHead": "
|
|
191
|
+
"gitHead": "5e9878a6ab82875248b0696621bb957732d42377",
|
|
192
192
|
"scripts": {
|
|
193
193
|
"clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
|
|
194
194
|
"clean": "rimraf ./node_modules ./lib ./tsconfig.tsbuildinfo",
|
package/src/env/index.test.ts
CHANGED
|
@@ -81,6 +81,27 @@ describe('VertesiaEnvironment runtime configuration', () => {
|
|
|
81
81
|
expect(window.AUTH_MODE).toBeUndefined();
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
it('uses the gateway broker ahead of an app build endpoint', () => {
|
|
85
|
+
vi.stubGlobal('window', {
|
|
86
|
+
__VERTESIA_RUNTIME_CONFIG__: { authMode: 'central', authUrl: 'https://auth.dev1.vertesia.io/' },
|
|
87
|
+
});
|
|
88
|
+
const env = new VertesiaEnvironment().init({
|
|
89
|
+
...baseProps,
|
|
90
|
+
endpoints: { ...baseProps.endpoints, auth: 'https://internal-auth.vertesia.app/' },
|
|
91
|
+
});
|
|
92
|
+
expect(env.endpoints.auth).toBe('https://auth.dev1.vertesia.io/');
|
|
93
|
+
expect(window.AUTH_MODE).toBe('central');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('preserves the app broker with an older central runtime contract', () => {
|
|
97
|
+
vi.stubGlobal('window', { __VERTESIA_RUNTIME_CONFIG__: { authMode: 'central' } });
|
|
98
|
+
const env = new VertesiaEnvironment().init({
|
|
99
|
+
...baseProps,
|
|
100
|
+
endpoints: { ...baseProps.endpoints, auth: 'https://auth.dev1.vertesia.io/' },
|
|
101
|
+
});
|
|
102
|
+
expect(env.endpoints.auth).toBe('https://auth.dev1.vertesia.io/');
|
|
103
|
+
});
|
|
104
|
+
|
|
84
105
|
it('accepts injected Central Auth mode without Firebase configuration', () => {
|
|
85
106
|
vi.stubGlobal('window', {
|
|
86
107
|
__VERTESIA_RUNTIME_CONFIG__: { authMode: 'central' },
|
package/src/env/index.ts
CHANGED
|
@@ -18,8 +18,8 @@ export interface EnvProps {
|
|
|
18
18
|
/**
|
|
19
19
|
* Central Auth broker that issues the sign-in redirect and hosts `/logout`.
|
|
20
20
|
*
|
|
21
|
-
* Optional: when unset the session falls back to the
|
|
22
|
-
* does not set it
|
|
21
|
+
* Optional: when unset the session falls back to the first-party broker, so an app that
|
|
22
|
+
* does not set it uses auth.vertesia.io. Set it to move one environment at a
|
|
23
23
|
* time onto a different broker.
|
|
24
24
|
*/
|
|
25
25
|
auth?: string;
|
|
@@ -79,6 +79,8 @@ export type VertesiaRuntimeConfig =
|
|
|
79
79
|
}
|
|
80
80
|
| {
|
|
81
81
|
authMode: 'central';
|
|
82
|
+
/** Broker selected by the serving gateway. Overrides build-time configuration. */
|
|
83
|
+
authUrl?: string;
|
|
82
84
|
};
|
|
83
85
|
|
|
84
86
|
declare global {
|
|
@@ -141,6 +143,10 @@ export class VertesiaEnvironment implements Readonly<EnvProps> {
|
|
|
141
143
|
const runtimeConfig = injectedRuntimeConfig() ?? buildRuntimeConfig(buildEnv);
|
|
142
144
|
const runtimeFirebase = runtimeConfig?.authMode === 'firebase' ? runtimeConfig.firebase : undefined;
|
|
143
145
|
this._props = props && runtimeFirebase && !props.firebase ? { ...props, firebase: runtimeFirebase } : props;
|
|
146
|
+
const authUrl = runtimeConfig?.authMode === 'central' ? runtimeConfig.authUrl?.trim() : undefined;
|
|
147
|
+
if (this._props && authUrl) {
|
|
148
|
+
this._props = { ...this._props, endpoints: { ...this._props.endpoints, auth: authUrl } };
|
|
149
|
+
}
|
|
144
150
|
const tenantId =
|
|
145
151
|
typeof buildEnv?.VITE_FIREBASE_TENANT_ID === 'string' ? buildEnv.VITE_FIREBASE_TENANT_ID.trim() : '';
|
|
146
152
|
if (this._props?.firebase && tenantId && !this._props.firebase.tenantId) {
|
|
@@ -6,10 +6,22 @@ This document describes the authentication flow for the Vertesia user-facing app
|
|
|
6
6
|
|
|
7
7
|
The authentication system uses:
|
|
8
8
|
- **Firebase** for identity management and OAuth provider integration
|
|
9
|
-
- **Central Auth** (
|
|
9
|
+
- **Central Auth** (auth.vertesia.io) for centralized authentication flow
|
|
10
10
|
- **STS (Security Token Service)** (sts.vertesia.io) to validate Firebase tokens and generate Vertesia JWT tokens
|
|
11
11
|
- **Session state management** to validate redirects and prevent CSRF attacks
|
|
12
12
|
|
|
13
|
+
### Central Auth endpoint selection
|
|
14
|
+
|
|
15
|
+
`https://auth.vertesia.io/` is the default broker. Regional scaffolding can set
|
|
16
|
+
`VITE_AUTH_ENDPOINT`; applications can also configure the `Env` initialization property `endpoints.auth` explicitly.
|
|
17
|
+
For compatible generated app shells, a gateway-provided central runtime config
|
|
18
|
+
(`window.__VERTESIA_RUNTIME_CONFIG__` with `authMode: 'central'` and `authUrl`)
|
|
19
|
+
takes precedence over the build-time endpoint. A runtime config without `authUrl`
|
|
20
|
+
retains the configured endpoint or default. Explicit Firebase mode uses direct
|
|
21
|
+
Firebase authentication instead of the central broker.
|
|
22
|
+
|
|
23
|
+
The diagrams below use the default hostname; redirects use the selected broker.
|
|
24
|
+
|
|
13
25
|
### STS Endpoints
|
|
14
26
|
- Production/Preview: `https://sts.vertesia.io`
|
|
15
27
|
- Staging: `https://sts.dev1.vertesia.io`
|
|
@@ -179,7 +191,7 @@ The authentication system uses:
|
|
|
179
191
|
┌──────────────────────┐ │ - Microsoft │ ┌──────────────────────┐
|
|
180
192
|
│ Build redirect URL: │ └──────────┬───────────┘ │ Redirect to SSO │
|
|
181
193
|
│ │ │ │ provider (Okta, │
|
|
182
|
-
│
|
|
194
|
+
│ auth.vertesia.io │ ▼ │ Azure AD, etc.) │
|
|
183
195
|
│ .vertesia.app │ ┌──────────────────────┐ └──────────┬───────────┘
|
|
184
196
|
│ ?sts=... │ │ Firebase OAuth │ │
|
|
185
197
|
│ &redirect_uri=... │ │ redirect & return │ ▼
|
|
@@ -329,7 +341,7 @@ The authentication system uses:
|
|
|
329
341
|
|
|
330
342
|
### Path A: Central Auth (Option 1) - Returns with JWT
|
|
331
343
|
1. User clicks "Continue with Central Auth"
|
|
332
|
-
2. Redirects to
|
|
344
|
+
2. Redirects to auth.vertesia.io
|
|
333
345
|
3. Central auth authenticates user and gets Firebase token
|
|
334
346
|
4. **Central auth calls STS** (`POST sts.vertesia.io/token/issue` with Firebase token)
|
|
335
347
|
5. **STS validates Firebase token and generates Vertesia JWT**
|
|
@@ -472,7 +484,7 @@ The authentication system uses:
|
|
|
472
484
|
│ Redirect to Central │ │ getFirebaseAuth() │
|
|
473
485
|
│ Auth for logout: │ │ .signOut() │
|
|
474
486
|
│ │ │ │
|
|
475
|
-
│
|
|
487
|
+
│ auth.vertesia.io │ │ (Triggers │
|
|
476
488
|
│ .vertesia.app │ │ onAuthStateChanged │
|
|
477
489
|
│ /logout │ │ with anonymous user)│
|
|
478
490
|
│ │ └──────────┬───────────┘
|
|
@@ -712,7 +724,7 @@ flowchart TB
|
|
|
712
724
|
subgraph Option1[OPTION 1: CENTRAL AUTH]
|
|
713
725
|
C1[Generate state<br/>Save to sessionStorage]
|
|
714
726
|
C2[Get account/project<br/>from URL or localStorage]
|
|
715
|
-
C3[Build redirect URL:<br/>
|
|
727
|
+
C3[Build redirect URL:<br/>auth.vertesia.io<br/>?sts=...&redirect_uri=...&state=...]
|
|
716
728
|
C4[window.location.replace<br/>to central auth]
|
|
717
729
|
C5[Central Auth Page<br/>User authenticates<br/>Gets Firebase token]
|
|
718
730
|
C6[Central auth calls STS:<br/>POST sts.vertesia.io/token/issue<br/>Authorization: Bearer Firebase<br/>Body: type, account_id, project_id]
|
|
@@ -774,7 +786,7 @@ flowchart TB
|
|
|
774
786
|
|
|
775
787
|
Check -->|YES| ShouldRedirect{shouldRedirectTo<br/>CentralAuth?}
|
|
776
788
|
|
|
777
|
-
ShouldRedirect -->|YES| RedirectLogout[Redirect to Central Auth<br/>for logout<br/><br/>
|
|
789
|
+
ShouldRedirect -->|YES| RedirectLogout[Redirect to Central Auth<br/>for logout<br/><br/>auth.vertesia.io<br/>/logout<br/><br/>Central auth handles<br/>Firebase logout]
|
|
778
790
|
|
|
779
791
|
ShouldRedirect -->|NO| FirebaseSignout[getFirebaseAuth.signOut<br/><br/>Triggers onAuthStateChanged<br/>with anonymous user]
|
|
780
792
|
|
|
@@ -837,7 +849,7 @@ flowchart TB
|
|
|
837
849
|
sequenceDiagram
|
|
838
850
|
actor User
|
|
839
851
|
participant App as Composable UI
|
|
840
|
-
participant CentralAuth as Central Auth<br/>(
|
|
852
|
+
participant CentralAuth as Central Auth<br/>(auth.vertesia.io)
|
|
841
853
|
participant Firebase as Firebase Auth
|
|
842
854
|
participant STS as STS<br/>(sts.vertesia.io)
|
|
843
855
|
|
|
@@ -1030,7 +1042,7 @@ flowchart TB
|
|
|
1030
1042
|
|
|
1031
1043
|
Check -->|YES| ShouldRedirect{shouldRedirectTo<br/>CentralAuth?}
|
|
1032
1044
|
|
|
1033
|
-
ShouldRedirect -->|YES| RedirectLogout[Redirect to Central Auth<br/>for logout<br/><br/>
|
|
1045
|
+
ShouldRedirect -->|YES| RedirectLogout[Redirect to Central Auth<br/>for logout<br/><br/>auth.vertesia.io<br/>/logout<br/><br/>Central auth handles<br/>Firebase logout]
|
|
1034
1046
|
|
|
1035
1047
|
ShouldRedirect -->|NO| FirebaseSignout[getFirebaseAuth.signOut<br/><br/>Triggers onAuthStateChanged<br/>with anonymous user]
|
|
1036
1048
|
|
|
@@ -1078,7 +1090,7 @@ flowchart TB
|
|
|
1078
1090
|
LO2[session.logout or<br/>session.signOut]
|
|
1079
1091
|
LO3{authToken<br/>exists?}
|
|
1080
1092
|
LO4{shouldRedirectTo<br/>CentralAuth?}
|
|
1081
|
-
LO5[Redirect to Central Auth<br/>for logout<br/><br/>
|
|
1093
|
+
LO5[Redirect to Central Auth<br/>for logout<br/><br/>auth.vertesia.io<br/>/logout<br/><br/>Central auth handles<br/>Firebase logout]
|
|
1082
1094
|
LO6[getFirebaseAuth.signOut<br/><br/>Triggers onAuthStateChanged<br/>with anonymous user]
|
|
1083
1095
|
LO7[Clear session data:<br/>- authError = undefined<br/>- isLoading = false<br/>- authToken = undefined<br/>- typeRegistry = undefined]
|
|
1084
1096
|
LO8[client.withAuthCallback<br/>undefined<br/><br/>Clear client auth]
|
|
@@ -77,7 +77,7 @@ describe('centralAuthUrl', () => {
|
|
|
77
77
|
it('falls back to the default broker when no auth endpoint is configured', () => {
|
|
78
78
|
initEnv(undefined);
|
|
79
79
|
expect(centralAuthUrl()).toBe(DEFAULT_CENTRAL_AUTH_URL);
|
|
80
|
-
expect(DEFAULT_CENTRAL_AUTH_URL).toBe('https://
|
|
80
|
+
expect(DEFAULT_CENTRAL_AUTH_URL).toBe('https://auth.vertesia.io/');
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
it('uses the configured auth endpoint when one is set', () => {
|
|
@@ -10,11 +10,10 @@ declare global {
|
|
|
10
10
|
/**
|
|
11
11
|
* The broker this app sends users to for sign-in and logout.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* different broker, and it keeps this default so an app that configures nothing is unaffected.
|
|
13
|
+
* A gateway or app can select a regional broker through Env.endpoints.auth.
|
|
14
|
+
* Unconfigured apps use the first-party central broker.
|
|
16
15
|
*/
|
|
17
|
-
export const DEFAULT_CENTRAL_AUTH_URL = 'https://
|
|
16
|
+
export const DEFAULT_CENTRAL_AUTH_URL = 'https://auth.vertesia.io/';
|
|
18
17
|
|
|
19
18
|
export function centralAuthUrl(): string {
|
|
20
19
|
const configured = Env.endpoints.auth;
|