@osdk/oauth 1.8.0-beta.1 → 1.8.0-beta.2

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/browser/common.js +33 -13
  3. package/build/browser/common.js.map +1 -1
  4. package/build/browser/common.test.js +6 -4
  5. package/build/browser/common.test.js.map +1 -1
  6. package/build/browser/createConfidentialOauthClient.js +1 -1
  7. package/build/browser/createConfidentialOauthClient.js.map +1 -1
  8. package/build/browser/createPublicOauthClient.js +19 -9
  9. package/build/browser/createPublicOauthClient.js.map +1 -1
  10. package/build/browser/index.js.map +1 -1
  11. package/build/browser/publicOauth.test.js +91 -1
  12. package/build/browser/publicOauth.test.js.map +1 -1
  13. package/build/browser/utils.js +2 -1
  14. package/build/browser/utils.js.map +1 -1
  15. package/build/cjs/index.cjs +34 -22
  16. package/build/cjs/index.cjs.map +1 -1
  17. package/build/cjs/index.d.cts +25 -1
  18. package/build/esm/common.js +32 -13
  19. package/build/esm/common.js.map +1 -1
  20. package/build/esm/common.test.js +6 -4
  21. package/build/esm/common.test.js.map +1 -1
  22. package/build/esm/createConfidentialOauthClient.js +1 -1
  23. package/build/esm/createConfidentialOauthClient.js.map +1 -1
  24. package/build/esm/createPublicOauthClient.js +19 -9
  25. package/build/esm/createPublicOauthClient.js.map +1 -1
  26. package/build/esm/index.js.map +1 -1
  27. package/build/esm/publicOauth.test.js +91 -1
  28. package/build/esm/publicOauth.test.js.map +1 -1
  29. package/build/esm/utils.js +2 -1
  30. package/build/esm/utils.js.map +1 -1
  31. package/build/types/common.d.ts +19 -4
  32. package/build/types/common.d.ts.map +1 -1
  33. package/build/types/createPublicOauthClient.d.ts +16 -0
  34. package/build/types/createPublicOauthClient.d.ts.map +1 -1
  35. package/build/types/index.d.ts +2 -1
  36. package/build/types/index.d.ts.map +1 -1
  37. package/build/types/utils.d.ts +3 -1
  38. package/build/types/utils.d.ts.map +1 -1
  39. package/package.json +3 -3
@@ -19,6 +19,15 @@ interface BaseOauthClient<K extends keyof Events & string> {
19
19
  removeEventListener: <T extends K>(type: T, callback: ((evt: Events[T]) => void) | null, options?: EventListenerOptions | boolean) => void;
20
20
  }
21
21
 
22
+ /**
23
+ * Specifies where to store refresh tokens.
24
+ *
25
+ * - `'localStorage'`: Tokens persist across browser sessions. Best UX but vulnerable on shared devices without browser separation.
26
+ * - `'sessionStorage'`: Tokens cleared when browser/tab closes. Recommended for shared devices.
27
+ * - `'none'`: Tokens never stored. Users re-authenticate on every page load.
28
+ */
29
+ type TokenStorageType = "localStorage" | "sessionStorage" | "none";
30
+
22
31
  interface ConfidentialOauthClient extends BaseOauthClient<"signIn" | "signOut"> {
23
32
  }
24
33
 
@@ -66,6 +75,21 @@ interface PublicOauthClientOptions {
66
75
  * Allows for an additional value to be appended to the local storage key for the refresh token.
67
76
  */
68
77
  refreshTokenMarker?: string;
78
+ /**
79
+ * Controls how refresh tokens are stored. Affects security vs. convenience tradeoff.
80
+ *
81
+ * - `'localStorage'` (default): Tokens persist across browser sessions.
82
+ * Best UX but vulnerable on shared devices.
83
+ *
84
+ * - `'sessionStorage'`: Tokens cleared when browser/tab closes.
85
+ * Recommended for shared devices. Users re-authenticate each browser session.
86
+ *
87
+ * - `'none'`: Tokens never stored. Users re-authenticate on every page load.
88
+ * Most secure but worst UX. Does not request `offline_access` scope.
89
+ *
90
+ * @default 'localStorage'
91
+ */
92
+ tokenStorage?: TokenStorageType;
69
93
  }
70
94
  /**
71
95
  * Creates a PublicOauthClient for authentication.
@@ -93,4 +117,4 @@ declare function createPublicOauthClient(clientId: string, url: string, redirect
93
117
  */
94
118
  declare function createPublicOauthClient(client_id: string, url: string, redirectUrl: string, useHistory?: boolean, loginPage?: string, postLoginPage?: string, scopes?: string[], fetchFn?: typeof globalThis.fetch, ctxPath?: string): PublicOauthClient;
95
119
 
96
- export { type ConfidentialOauthClient, type PublicOauthClient, createConfidentialOauthClient, createPublicOauthClient };
120
+ export { type ConfidentialOauthClient, type PublicOauthClient, type PublicOauthClientOptions, type TokenStorageType, createConfidentialOauthClient, createPublicOauthClient };
@@ -30,21 +30,40 @@ const CustomEvent = globalThis.CustomEvent ?? class extends Event {
30
30
  return this.#detail;
31
31
  }
32
32
  };
33
+
34
+ /**
35
+ * Specifies where to store refresh tokens.
36
+ *
37
+ * - `'localStorage'`: Tokens persist across browser sessions. Best UX but vulnerable on shared devices without browser separation.
38
+ * - `'sessionStorage'`: Tokens cleared when browser/tab closes. Recommended for shared devices.
39
+ * - `'none'`: Tokens never stored. Users re-authenticate on every page load.
40
+ */
41
+
33
42
  function localStorageKey(client) {
34
43
  return `@osdk/oauth : refresh : ${client.client_id}`;
35
44
  }
36
- export function saveLocal(client, x) {
37
- // MUST `localStorage?` as nodejs does not have localStorage
38
- globalThis.localStorage?.setItem(localStorageKey(client), JSON.stringify(x));
45
+
46
+ /**
47
+ * Returns the appropriate Storage object based on the storage type.
48
+ * Returns undefined if storage type is 'none' or if running in Node.js (where storage APIs don't exist).
49
+ *
50
+ * Call this once at client initialization and pass the result to storage functions.
51
+ */
52
+ export function getStorage(storageType) {
53
+ if (storageType === "none") return undefined;
54
+
55
+ // Node.js doesn't have Web Storage APIs
56
+
57
+ return undefined;
39
58
  }
40
- export function removeLocal(client) {
41
- // MUST `localStorage?` as nodejs does not have localStorage
42
- globalThis.localStorage?.removeItem(localStorageKey(client));
59
+ export function saveLocal(client, x, storage) {
60
+ storage?.setItem(localStorageKey(client), JSON.stringify(x));
43
61
  }
44
- export function readLocal(client) {
45
- return JSON.parse(
46
- // MUST `localStorage?` as nodejs does not have localStorage
47
- globalThis.localStorage?.getItem(localStorageKey(client)) ?? "{}");
62
+ export function removeLocal(client, storage) {
63
+ storage?.removeItem(localStorageKey(client));
64
+ }
65
+ export function readLocal(client, storage) {
66
+ return JSON.parse(storage?.getItem(localStorageKey(client)) ?? "{}");
48
67
  }
49
68
  export function saveSession(client, x) {
50
69
  // MUST `sessionStorage?` as nodejs does not have sessionStorage
@@ -59,7 +78,7 @@ export function readSession(client) {
59
78
  // MUST `sessionStorage?` as nodejs does not have sessionStorage
60
79
  globalThis.sessionStorage?.getItem(localStorageKey(client)) ?? "{}");
61
80
  }
62
- export function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTokenMarker, scopes) {
81
+ export function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTokenMarker, scopes, storage) {
63
82
  let token;
64
83
  const eventTarget = new TypedEventTarget();
65
84
  function makeTokenAndSaveRefresh(resp, type) {
@@ -73,7 +92,7 @@ export function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTo
73
92
  refresh_token,
74
93
  refreshTokenMarker,
75
94
  requestedScopes: scopes
76
- });
95
+ }, storage);
77
96
  token = {
78
97
  refresh_token,
79
98
  expires_in,
@@ -129,7 +148,7 @@ export function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTo
129
148
  rmTimeout();
130
149
 
131
150
  // Clean up
132
- removeLocal(client);
151
+ removeLocal(client, storage);
133
152
  token = undefined;
134
153
  throwIfError(result);
135
154
  eventTarget.dispatchTypedEvent("signOut", new Event("signOut"));
@@ -1 +1 @@
1
- {"version":3,"file":"common.js","names":["processRevocationResponse","revocationRequest","invariant","TypedEventTarget","throwIfError","CustomEvent","globalThis","Event","detail","constructor","type","options","localStorageKey","client","client_id","saveLocal","x","localStorage","setItem","JSON","stringify","removeLocal","removeItem","readLocal","parse","getItem","saveSession","sessionStorage","removeSession","readSession","common","as","_signIn","oauthHttpOptions","refresh","refreshTokenMarker","scopes","token","eventTarget","makeTokenAndSaveRefresh","resp","refresh_token","expires_in","access_token","process","env","NODE_ENV","requestedScopes","expires_at","Date","now","dispatchTypedEvent","refreshTimeout","rmTimeout","clearTimeout","restartRefreshTimer","evt","setTimeout","pendingSignIn","signIn","undefined","addEventListener","getTokenOrUndefined","getToken","Object","assign","signOut","result","bind","removeEventListener","createAuthorizationServer","ctxPath","url","issuer","URL","endsWith","token_endpoint","authorization_endpoint","revocation_endpoint"],"sources":["common.ts"],"sourcesContent":["/*\n * Copyright 2024 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n AuthorizationServer,\n Client,\n HttpRequestOptions,\n OAuth2TokenEndpointResponse,\n} from \"oauth4webapi\";\nimport { processRevocationResponse, revocationRequest } from \"oauth4webapi\";\nimport invariant from \"tiny-invariant\";\nimport { TypedEventTarget } from \"typescript-event-target\";\nimport type { BaseOauthClient, Events } from \"./BaseOauthClient.js\";\nimport { throwIfError } from \"./throwIfError.js\";\nimport type { Token } from \"./Token.js\";\n\n// Node 18 is supposed to have a `CustomEvent` but it is not exposed on `globalThis`\n// which creates a problem for making a single codebase for node and browser. This polyfill works around it\nconst CustomEvent = process.env.TARGET === \"browser\"\n ? globalThis.CustomEvent\n : globalThis.CustomEvent\n ?? class CustomEvent<T> extends Event {\n #detail: T | null;\n\n constructor(type: string, options: EventInit & { detail: T }) {\n super(type, options);\n this.#detail = options?.detail ?? null;\n }\n\n get detail() {\n return this.#detail;\n }\n };\n\ndeclare const process: {\n env: Record<string, string | undefined>;\n};\n\nexport interface LocalStorageState {\n refresh_token?: string;\n refreshTokenMarker?: string;\n // The stringified space-separated list of scopes requested during the initial auth grant, which our refresh token is still valid for\n // Note any or none of these scopes may have actually been granted when we received our last access token\n requestedScopes?: string;\n}\n\nexport type SessionStorageState =\n // when we are going to the login page\n | {\n codeVerifier?: never;\n state?: never;\n oldUrl: string;\n }\n // when we are redirecting to oauth login\n | {\n codeVerifier: string;\n state: string;\n oldUrl: string;\n }\n // when we have the refresh token\n | {\n codeVerifier?: never;\n state?: never;\n oldUrl?: never;\n };\n\nfunction localStorageKey(client: Client) {\n return `@osdk/oauth : refresh : ${client.client_id}`;\n}\n\nexport function saveLocal(client: Client, x: LocalStorageState): void {\n // MUST `localStorage?` as nodejs does not have localStorage\n globalThis.localStorage?.setItem(\n localStorageKey(client),\n JSON.stringify(x),\n );\n}\n\nexport function removeLocal(client: Client): void {\n // MUST `localStorage?` as nodejs does not have localStorage\n globalThis.localStorage?.removeItem(\n localStorageKey(client),\n );\n}\n\nexport function readLocal(client: Client): LocalStorageState {\n return JSON.parse(\n // MUST `localStorage?` as nodejs does not have localStorage\n globalThis.localStorage?.getItem(\n localStorageKey(client),\n )\n ?? \"{}\",\n );\n}\n\nexport function saveSession(client: Client, x: SessionStorageState): void {\n // MUST `sessionStorage?` as nodejs does not have sessionStorage\n globalThis.sessionStorage?.setItem(\n localStorageKey(client),\n JSON.stringify(x),\n );\n}\n\nexport function removeSession(client: Client): void {\n // MUST `sessionStorage?` as nodejs does not have sessionStorage\n globalThis.sessionStorage?.removeItem(\n localStorageKey(client),\n );\n}\n\nexport function readSession(client: Client): SessionStorageState {\n return JSON.parse(\n // MUST `sessionStorage?` as nodejs does not have sessionStorage\n globalThis.sessionStorage?.getItem(\n localStorageKey(client),\n )\n ?? \"{}\",\n );\n}\n\nexport function common<\n R extends undefined | (() => Promise<Token | undefined>),\n>(\n client: Client,\n as: AuthorizationServer,\n _signIn: () => Promise<Token>,\n oauthHttpOptions: HttpRequestOptions,\n refresh: R,\n refreshTokenMarker: string | undefined,\n scopes: string,\n): {\n getToken: BaseOauthClient<keyof Events & string> & { refresh: R };\n makeTokenAndSaveRefresh: (\n resp: OAuth2TokenEndpointResponse,\n type: \"signIn\" | \"refresh\",\n ) => Token;\n} {\n let token: Token | undefined;\n const eventTarget = new TypedEventTarget<Events>();\n\n function makeTokenAndSaveRefresh(\n resp: OAuth2TokenEndpointResponse,\n type: \"signIn\" | \"refresh\",\n ): Token {\n const { refresh_token, expires_in, access_token } = resp;\n invariant(expires_in != null);\n saveLocal(client, {\n refresh_token,\n refreshTokenMarker,\n requestedScopes: scopes,\n });\n token = {\n refresh_token,\n expires_in,\n access_token,\n expires_at: Date.now() + expires_in * 1000,\n };\n\n eventTarget.dispatchTypedEvent(\n type,\n new CustomEvent(\n type,\n { detail: token },\n ),\n );\n return token;\n }\n\n let refreshTimeout: ReturnType<typeof setTimeout>;\n function rmTimeout() {\n if (refreshTimeout) clearTimeout(refreshTimeout);\n }\n function restartRefreshTimer(evt: CustomEvent<Token>) {\n if (refresh) {\n rmTimeout();\n refreshTimeout = setTimeout(\n refresh,\n evt.detail.expires_in * 1000 - 60 * 1000,\n );\n }\n }\n\n async function signOut() {\n invariant(token, \"not signed in\");\n\n const result = await processRevocationResponse(\n await revocationRequest(\n as,\n client,\n token.access_token,\n oauthHttpOptions,\n ),\n );\n\n rmTimeout();\n\n // Clean up\n removeLocal(client);\n token = undefined;\n throwIfError(result);\n eventTarget.dispatchTypedEvent(\"signOut\", new Event(\"signOut\"));\n }\n\n let pendingSignIn: Promise<Token> | undefined;\n async function signIn() {\n if (pendingSignIn) {\n return pendingSignIn;\n }\n try {\n pendingSignIn = _signIn();\n return await pendingSignIn;\n } finally {\n pendingSignIn = undefined;\n }\n }\n\n eventTarget.addEventListener(\"signIn\", restartRefreshTimer);\n eventTarget.addEventListener(\"refresh\", restartRefreshTimer);\n\n function getTokenOrUndefined() {\n if (!token || Date.now() >= token.expires_at) {\n return undefined;\n }\n return token?.access_token;\n }\n\n const getToken = Object.assign(async function getToken() {\n if (!token || Date.now() >= token.expires_at) {\n token = await signIn();\n }\n return token?.access_token;\n }, {\n signIn,\n refresh,\n signOut,\n rmTimeout,\n getTokenOrUndefined,\n addEventListener: eventTarget.addEventListener.bind(\n eventTarget,\n ) as typeof eventTarget.addEventListener,\n removeEventListener: eventTarget.removeEventListener.bind(\n eventTarget,\n ) as typeof eventTarget.removeEventListener,\n });\n\n return { getToken, makeTokenAndSaveRefresh };\n}\n\nexport function createAuthorizationServer(\n ctxPath: string,\n url: string,\n): Required<\n Pick<\n AuthorizationServer,\n | \"issuer\"\n | \"token_endpoint\"\n | \"authorization_endpoint\"\n | \"revocation_endpoint\"\n >\n> {\n const issuer = `${new URL(ctxPath, url.endsWith(\"/\") ? url : url + \"/\")}`;\n return {\n token_endpoint: `${issuer}/api/oauth2/token`,\n authorization_endpoint: `${issuer}/api/oauth2/authorize`,\n revocation_endpoint: `${issuer}/api/oauth2/revoke_token`,\n issuer,\n };\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"common.js","names":["processRevocationResponse","revocationRequest","invariant","TypedEventTarget","throwIfError","CustomEvent","globalThis","Event","detail","constructor","type","options","localStorageKey","client","client_id","getStorage","storageType","undefined","saveLocal","x","storage","setItem","JSON","stringify","removeLocal","removeItem","readLocal","parse","getItem","saveSession","sessionStorage","removeSession","readSession","common","as","_signIn","oauthHttpOptions","refresh","refreshTokenMarker","scopes","token","eventTarget","makeTokenAndSaveRefresh","resp","refresh_token","expires_in","access_token","process","env","NODE_ENV","requestedScopes","expires_at","Date","now","dispatchTypedEvent","refreshTimeout","rmTimeout","clearTimeout","restartRefreshTimer","evt","setTimeout","pendingSignIn","signIn","addEventListener","getTokenOrUndefined","getToken","Object","assign","signOut","result","bind","removeEventListener","createAuthorizationServer","ctxPath","url","issuer","URL","endsWith","token_endpoint","authorization_endpoint","revocation_endpoint"],"sources":["common.ts"],"sourcesContent":["/*\n * Copyright 2024 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n AuthorizationServer,\n Client,\n HttpRequestOptions,\n OAuth2TokenEndpointResponse,\n} from \"oauth4webapi\";\nimport { processRevocationResponse, revocationRequest } from \"oauth4webapi\";\nimport invariant from \"tiny-invariant\";\nimport { TypedEventTarget } from \"typescript-event-target\";\nimport type { BaseOauthClient, Events } from \"./BaseOauthClient.js\";\nimport { throwIfError } from \"./throwIfError.js\";\nimport type { Token } from \"./Token.js\";\n\n// Node 18 is supposed to have a `CustomEvent` but it is not exposed on `globalThis`\n// which creates a problem for making a single codebase for node and browser. This polyfill works around it\nconst CustomEvent = process.env.TARGET === \"browser\"\n ? globalThis.CustomEvent\n : globalThis.CustomEvent\n ?? class CustomEvent<T> extends Event {\n #detail: T | null;\n\n constructor(type: string, options: EventInit & { detail: T }) {\n super(type, options);\n this.#detail = options?.detail ?? null;\n }\n\n get detail() {\n return this.#detail;\n }\n };\n\ndeclare const process: {\n env: Record<string, string | undefined>;\n};\n\nexport interface LocalStorageState {\n refresh_token?: string;\n refreshTokenMarker?: string;\n // The stringified space-separated list of scopes requested during the initial auth grant, which our refresh token is still valid for\n // Note any or none of these scopes may have actually been granted when we received our last access token\n requestedScopes?: string;\n}\n\n/**\n * Specifies where to store refresh tokens.\n *\n * - `'localStorage'`: Tokens persist across browser sessions. Best UX but vulnerable on shared devices without browser separation.\n * - `'sessionStorage'`: Tokens cleared when browser/tab closes. Recommended for shared devices.\n * - `'none'`: Tokens never stored. Users re-authenticate on every page load.\n */\nexport type TokenStorageType = \"localStorage\" | \"sessionStorage\" | \"none\";\n\nexport type SessionStorageState =\n // when we are going to the login page\n | {\n codeVerifier?: never;\n state?: never;\n oldUrl: string;\n }\n // when we are redirecting to oauth login\n | {\n codeVerifier: string;\n state: string;\n oldUrl: string;\n }\n // when we have the refresh token\n | {\n codeVerifier?: never;\n state?: never;\n oldUrl?: never;\n };\n\nfunction localStorageKey(client: Client) {\n return `@osdk/oauth : refresh : ${client.client_id}`;\n}\n\n/**\n * Returns the appropriate Storage object based on the storage type.\n * Returns undefined if storage type is 'none' or if running in Node.js (where storage APIs don't exist).\n *\n * Call this once at client initialization and pass the result to storage functions.\n */\nexport function getStorage(\n storageType: TokenStorageType,\n): Storage | undefined {\n if (storageType === \"none\") return undefined;\n\n // Node.js doesn't have Web Storage APIs\n if (process.env.TARGET !== \"browser\") {\n return undefined;\n }\n\n if (storageType === \"sessionStorage\") return globalThis.sessionStorage;\n return globalThis.localStorage;\n}\n\nexport function saveLocal(\n client: Client,\n x: LocalStorageState,\n storage: Storage | undefined,\n): void {\n storage?.setItem(\n localStorageKey(client),\n JSON.stringify(x),\n );\n}\n\nexport function removeLocal(\n client: Client,\n storage: Storage | undefined,\n): void {\n storage?.removeItem(\n localStorageKey(client),\n );\n}\n\nexport function readLocal(\n client: Client,\n storage: Storage | undefined,\n): LocalStorageState {\n return JSON.parse(\n storage?.getItem(\n localStorageKey(client),\n )\n ?? \"{}\",\n );\n}\n\nexport function saveSession(client: Client, x: SessionStorageState): void {\n // MUST `sessionStorage?` as nodejs does not have sessionStorage\n globalThis.sessionStorage?.setItem(\n localStorageKey(client),\n JSON.stringify(x),\n );\n}\n\nexport function removeSession(client: Client): void {\n // MUST `sessionStorage?` as nodejs does not have sessionStorage\n globalThis.sessionStorage?.removeItem(\n localStorageKey(client),\n );\n}\n\nexport function readSession(client: Client): SessionStorageState {\n return JSON.parse(\n // MUST `sessionStorage?` as nodejs does not have sessionStorage\n globalThis.sessionStorage?.getItem(\n localStorageKey(client),\n )\n ?? \"{}\",\n );\n}\n\nexport function common<\n R extends undefined | (() => Promise<Token | undefined>),\n>(\n client: Client,\n as: AuthorizationServer,\n _signIn: () => Promise<Token>,\n oauthHttpOptions: HttpRequestOptions,\n refresh: R,\n refreshTokenMarker: string | undefined,\n scopes: string,\n storage: Storage | undefined,\n): {\n getToken: BaseOauthClient<keyof Events & string> & { refresh: R };\n makeTokenAndSaveRefresh: (\n resp: OAuth2TokenEndpointResponse,\n type: \"signIn\" | \"refresh\",\n ) => Token;\n} {\n let token: Token | undefined;\n const eventTarget = new TypedEventTarget<Events>();\n\n function makeTokenAndSaveRefresh(\n resp: OAuth2TokenEndpointResponse,\n type: \"signIn\" | \"refresh\",\n ): Token {\n const { refresh_token, expires_in, access_token } = resp;\n invariant(expires_in != null);\n saveLocal(client, {\n refresh_token,\n refreshTokenMarker,\n requestedScopes: scopes,\n }, storage);\n token = {\n refresh_token,\n expires_in,\n access_token,\n expires_at: Date.now() + expires_in * 1000,\n };\n\n eventTarget.dispatchTypedEvent(\n type,\n new CustomEvent(\n type,\n { detail: token },\n ),\n );\n return token;\n }\n\n let refreshTimeout: ReturnType<typeof setTimeout>;\n function rmTimeout() {\n if (refreshTimeout) clearTimeout(refreshTimeout);\n }\n function restartRefreshTimer(evt: CustomEvent<Token>) {\n if (refresh) {\n rmTimeout();\n refreshTimeout = setTimeout(\n refresh,\n evt.detail.expires_in * 1000 - 60 * 1000,\n );\n }\n }\n\n async function signOut() {\n invariant(token, \"not signed in\");\n\n const result = await processRevocationResponse(\n await revocationRequest(\n as,\n client,\n token.access_token,\n oauthHttpOptions,\n ),\n );\n\n rmTimeout();\n\n // Clean up\n removeLocal(client, storage);\n token = undefined;\n throwIfError(result);\n eventTarget.dispatchTypedEvent(\"signOut\", new Event(\"signOut\"));\n }\n\n let pendingSignIn: Promise<Token> | undefined;\n async function signIn() {\n if (pendingSignIn) {\n return pendingSignIn;\n }\n try {\n pendingSignIn = _signIn();\n return await pendingSignIn;\n } finally {\n pendingSignIn = undefined;\n }\n }\n\n eventTarget.addEventListener(\"signIn\", restartRefreshTimer);\n eventTarget.addEventListener(\"refresh\", restartRefreshTimer);\n\n function getTokenOrUndefined() {\n if (!token || Date.now() >= token.expires_at) {\n return undefined;\n }\n return token?.access_token;\n }\n\n const getToken = Object.assign(async function getToken() {\n if (!token || Date.now() >= token.expires_at) {\n token = await signIn();\n }\n return token?.access_token;\n }, {\n signIn,\n refresh,\n signOut,\n rmTimeout,\n getTokenOrUndefined,\n addEventListener: eventTarget.addEventListener.bind(\n eventTarget,\n ) as typeof eventTarget.addEventListener,\n removeEventListener: eventTarget.removeEventListener.bind(\n eventTarget,\n ) as typeof eventTarget.removeEventListener,\n });\n\n return { getToken, makeTokenAndSaveRefresh };\n}\n\nexport function createAuthorizationServer(\n ctxPath: string,\n url: string,\n): Required<\n Pick<\n AuthorizationServer,\n | \"issuer\"\n | \"token_endpoint\"\n | \"authorization_endpoint\"\n | \"revocation_endpoint\"\n >\n> {\n const issuer = `${new URL(ctxPath, url.endsWith(\"/\") ? url : url + \"/\")}`;\n return {\n token_endpoint: `${issuer}/api/oauth2/token`,\n authorization_endpoint: `${issuer}/api/oauth2/authorize`,\n revocation_endpoint: `${issuer}/api/oauth2/revoke_token`,\n issuer,\n };\n}\n"],"mappings":"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","ignoreList":[]}
@@ -33,9 +33,10 @@ describe("local functions", () => {
33
33
  $refreshTokenMarker: undefined
34
34
  };
35
35
  const oldLocalStorageKey = `@osdk/oauth : refresh : ${client.client_id}`;
36
- readLocal(client);
37
- removeLocal(client);
38
- saveLocal(client, {});
36
+ const storage = globalThis.localStorage;
37
+ readLocal(client, storage);
38
+ removeLocal(client, storage);
39
+ saveLocal(client, {}, storage);
39
40
  expect(vi.mocked(globalThis.localStorage.getItem)).toBeCalledWith(oldLocalStorageKey);
40
41
  expect(vi.mocked(globalThis.localStorage.setItem)).toBeCalledWith(oldLocalStorageKey, expect.anything());
41
42
  expect(vi.mocked(globalThis.localStorage.removeItem)).toBeCalledWith(oldLocalStorageKey);
@@ -46,10 +47,11 @@ describe("local functions", () => {
46
47
  };
47
48
  const signIn = vi.fn();
48
49
  const refresh = vi.fn();
50
+ const storage = globalThis.localStorage;
49
51
  const authServer = createAuthorizationServer("multipass", "https://stack.palantir.com");
50
52
  const {
51
53
  makeTokenAndSaveRefresh
52
- } = common(client, authServer, signIn, {}, refresh, "marker marker", "yay:my-fun-scope sad:my-boring-scope");
54
+ } = common(client, authServer, signIn, {}, refresh, "marker marker", "yay:my-fun-scope sad:my-boring-scope", storage);
53
55
  makeTokenAndSaveRefresh({
54
56
  refresh_token: "refresh",
55
57
  access_token: "access",
@@ -1 +1 @@
1
- {"version":3,"file":"common.test.js","names":["afterEach","beforeEach","describe","expect","it","vi","common","createAuthorizationServer","readLocal","removeLocal","saveLocal","stubGlobal","setItem","fn","removeItem","getItem","unstubAllGlobals","client","client_id","$refreshTokenMarker","undefined","oldLocalStorageKey","mocked","globalThis","localStorage","toBeCalledWith","anything","signIn","refresh","authServer","makeTokenAndSaveRefresh","refresh_token","access_token","token_type","expires_in","JSON","stringify","refreshTokenMarker","requestedScopes"],"sources":["common.test.ts"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport {\n common,\n createAuthorizationServer,\n readLocal,\n removeLocal,\n saveLocal,\n} from \"./common.js\";\n\ndescribe(\"local functions\", () => {\n beforeEach(() => {\n vi.stubGlobal(\"localStorage\", {\n setItem: vi.fn(),\n removeItem: vi.fn(),\n getItem: vi.fn(),\n });\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it(\"should use the old style keys when there is no refresh token marker\", () => {\n const client = {\n client_id: \"hi_mom\",\n $refreshTokenMarker: undefined,\n };\n\n const oldLocalStorageKey = `@osdk/oauth : refresh : ${client.client_id}`;\n\n readLocal(client);\n removeLocal(client);\n saveLocal(client, {});\n\n expect(vi.mocked(globalThis.localStorage.getItem)).toBeCalledWith(\n oldLocalStorageKey,\n );\n\n expect(vi.mocked(globalThis.localStorage.setItem)).toBeCalledWith(\n oldLocalStorageKey,\n expect.anything(),\n );\n\n expect(vi.mocked(globalThis.localStorage.removeItem)).toBeCalledWith(\n oldLocalStorageKey,\n );\n });\n\n it(\"should save the refreshTokenMaker if it exists, as well as the requested scopes\", () => {\n const client = {\n client_id: \"hi_mom\",\n };\n\n const signIn = vi.fn();\n const refresh = vi.fn();\n\n const authServer = createAuthorizationServer(\n \"multipass\",\n \"https://stack.palantir.com\",\n );\n\n const { makeTokenAndSaveRefresh } = common(\n client,\n authServer,\n signIn,\n {},\n refresh,\n \"marker marker\",\n \"yay:my-fun-scope sad:my-boring-scope\",\n );\n\n makeTokenAndSaveRefresh({\n refresh_token: \"refresh\",\n access_token: \"access\",\n token_type: \"idk\",\n expires_in: 10_000,\n }, \"signIn\");\n\n expect(globalThis.localStorage.setItem).toBeCalledWith(\n `@osdk/oauth : refresh : ${client.client_id}`,\n JSON.stringify({\n refresh_token: \"refresh\",\n refreshTokenMarker: \"marker marker\",\n requestedScopes: \"yay:my-fun-scope sad:my-boring-scope\",\n }),\n );\n });\n});\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,SAAS,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,QAAQ;AACxE,SACEC,MAAM,EACNC,yBAAyB,EACzBC,SAAS,EACTC,WAAW,EACXC,SAAS,QACJ,aAAa;AAEpBR,QAAQ,CAAC,iBAAiB,EAAE,MAAM;EAChCD,UAAU,CAAC,MAAM;IACfI,EAAE,CAACM,UAAU,CAAC,cAAc,EAAE;MAC5BC,OAAO,EAAEP,EAAE,CAACQ,EAAE,CAAC,CAAC;MAChBC,UAAU,EAAET,EAAE,CAACQ,EAAE,CAAC,CAAC;MACnBE,OAAO,EAAEV,EAAE,CAACQ,EAAE,CAAC;IACjB,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFb,SAAS,CAAC,MAAM;IACdK,EAAE,CAACW,gBAAgB,CAAC,CAAC;EACvB,CAAC,CAAC;EAEFZ,EAAE,CAAC,qEAAqE,EAAE,MAAM;IAC9E,MAAMa,MAAM,GAAG;MACbC,SAAS,EAAE,QAAQ;MACnBC,mBAAmB,EAAEC;IACvB,CAAC;IAED,MAAMC,kBAAkB,GAAG,2BAA2BJ,MAAM,CAACC,SAAS,EAAE;IAExEV,SAAS,CAACS,MAAM,CAAC;IACjBR,WAAW,CAACQ,MAAM,CAAC;IACnBP,SAAS,CAACO,MAAM,EAAE,CAAC,CAAC,CAAC;IAErBd,MAAM,CAACE,EAAE,CAACiB,MAAM,CAACC,UAAU,CAACC,YAAY,CAACT,OAAO,CAAC,CAAC,CAACU,cAAc,CAC/DJ,kBACF,CAAC;IAEDlB,MAAM,CAACE,EAAE,CAACiB,MAAM,CAACC,UAAU,CAACC,YAAY,CAACZ,OAAO,CAAC,CAAC,CAACa,cAAc,CAC/DJ,kBAAkB,EAClBlB,MAAM,CAACuB,QAAQ,CAAC,CAClB,CAAC;IAEDvB,MAAM,CAACE,EAAE,CAACiB,MAAM,CAACC,UAAU,CAACC,YAAY,CAACV,UAAU,CAAC,CAAC,CAACW,cAAc,CAClEJ,kBACF,CAAC;EACH,CAAC,CAAC;EAEFjB,EAAE,CAAC,iFAAiF,EAAE,MAAM;IAC1F,MAAMa,MAAM,GAAG;MACbC,SAAS,EAAE;IACb,CAAC;IAED,MAAMS,MAAM,GAAGtB,EAAE,CAACQ,EAAE,CAAC,CAAC;IACtB,MAAMe,OAAO,GAAGvB,EAAE,CAACQ,EAAE,CAAC,CAAC;IAEvB,MAAMgB,UAAU,GAAGtB,yBAAyB,CAC1C,WAAW,EACX,4BACF,CAAC;IAED,MAAM;MAAEuB;IAAwB,CAAC,GAAGxB,MAAM,CACxCW,MAAM,EACNY,UAAU,EACVF,MAAM,EACN,CAAC,CAAC,EACFC,OAAO,EACP,eAAe,EACf,sCACF,CAAC;IAEDE,uBAAuB,CAAC;MACtBC,aAAa,EAAE,SAAS;MACxBC,YAAY,EAAE,QAAQ;MACtBC,UAAU,EAAE,KAAK;MACjBC,UAAU,EAAE;IACd,CAAC,EAAE,QAAQ,CAAC;IAEZ/B,MAAM,CAACoB,UAAU,CAACC,YAAY,CAACZ,OAAO,CAAC,CAACa,cAAc,CACpD,2BAA2BR,MAAM,CAACC,SAAS,EAAE,EAC7CiB,IAAI,CAACC,SAAS,CAAC;MACbL,aAAa,EAAE,SAAS;MACxBM,kBAAkB,EAAE,eAAe;MACnCC,eAAe,EAAE;IACnB,CAAC,CACH,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"common.test.js","names":["afterEach","beforeEach","describe","expect","it","vi","common","createAuthorizationServer","readLocal","removeLocal","saveLocal","stubGlobal","setItem","fn","removeItem","getItem","unstubAllGlobals","client","client_id","$refreshTokenMarker","undefined","oldLocalStorageKey","storage","globalThis","localStorage","mocked","toBeCalledWith","anything","signIn","refresh","authServer","makeTokenAndSaveRefresh","refresh_token","access_token","token_type","expires_in","JSON","stringify","refreshTokenMarker","requestedScopes"],"sources":["common.test.ts"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport {\n common,\n createAuthorizationServer,\n readLocal,\n removeLocal,\n saveLocal,\n} from \"./common.js\";\n\ndescribe(\"local functions\", () => {\n beforeEach(() => {\n vi.stubGlobal(\"localStorage\", {\n setItem: vi.fn(),\n removeItem: vi.fn(),\n getItem: vi.fn(),\n });\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it(\"should use the old style keys when there is no refresh token marker\", () => {\n const client = {\n client_id: \"hi_mom\",\n $refreshTokenMarker: undefined,\n };\n\n const oldLocalStorageKey = `@osdk/oauth : refresh : ${client.client_id}`;\n const storage = globalThis.localStorage;\n\n readLocal(client, storage);\n removeLocal(client, storage);\n saveLocal(client, {}, storage);\n\n expect(vi.mocked(globalThis.localStorage.getItem)).toBeCalledWith(\n oldLocalStorageKey,\n );\n\n expect(vi.mocked(globalThis.localStorage.setItem)).toBeCalledWith(\n oldLocalStorageKey,\n expect.anything(),\n );\n\n expect(vi.mocked(globalThis.localStorage.removeItem)).toBeCalledWith(\n oldLocalStorageKey,\n );\n });\n\n it(\"should save the refreshTokenMaker if it exists, as well as the requested scopes\", () => {\n const client = {\n client_id: \"hi_mom\",\n };\n\n const signIn = vi.fn();\n const refresh = vi.fn();\n const storage = globalThis.localStorage;\n\n const authServer = createAuthorizationServer(\n \"multipass\",\n \"https://stack.palantir.com\",\n );\n\n const { makeTokenAndSaveRefresh } = common(\n client,\n authServer,\n signIn,\n {},\n refresh,\n \"marker marker\",\n \"yay:my-fun-scope sad:my-boring-scope\",\n storage,\n );\n\n makeTokenAndSaveRefresh({\n refresh_token: \"refresh\",\n access_token: \"access\",\n token_type: \"idk\",\n expires_in: 10_000,\n }, \"signIn\");\n\n expect(globalThis.localStorage.setItem).toBeCalledWith(\n `@osdk/oauth : refresh : ${client.client_id}`,\n JSON.stringify({\n refresh_token: \"refresh\",\n refreshTokenMarker: \"marker marker\",\n requestedScopes: \"yay:my-fun-scope sad:my-boring-scope\",\n }),\n );\n });\n});\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,SAAS,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,QAAQ;AACxE,SACEC,MAAM,EACNC,yBAAyB,EACzBC,SAAS,EACTC,WAAW,EACXC,SAAS,QACJ,aAAa;AAEpBR,QAAQ,CAAC,iBAAiB,EAAE,MAAM;EAChCD,UAAU,CAAC,MAAM;IACfI,EAAE,CAACM,UAAU,CAAC,cAAc,EAAE;MAC5BC,OAAO,EAAEP,EAAE,CAACQ,EAAE,CAAC,CAAC;MAChBC,UAAU,EAAET,EAAE,CAACQ,EAAE,CAAC,CAAC;MACnBE,OAAO,EAAEV,EAAE,CAACQ,EAAE,CAAC;IACjB,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFb,SAAS,CAAC,MAAM;IACdK,EAAE,CAACW,gBAAgB,CAAC,CAAC;EACvB,CAAC,CAAC;EAEFZ,EAAE,CAAC,qEAAqE,EAAE,MAAM;IAC9E,MAAMa,MAAM,GAAG;MACbC,SAAS,EAAE,QAAQ;MACnBC,mBAAmB,EAAEC;IACvB,CAAC;IAED,MAAMC,kBAAkB,GAAG,2BAA2BJ,MAAM,CAACC,SAAS,EAAE;IACxE,MAAMI,OAAO,GAAGC,UAAU,CAACC,YAAY;IAEvChB,SAAS,CAACS,MAAM,EAAEK,OAAO,CAAC;IAC1Bb,WAAW,CAACQ,MAAM,EAAEK,OAAO,CAAC;IAC5BZ,SAAS,CAACO,MAAM,EAAE,CAAC,CAAC,EAAEK,OAAO,CAAC;IAE9BnB,MAAM,CAACE,EAAE,CAACoB,MAAM,CAACF,UAAU,CAACC,YAAY,CAACT,OAAO,CAAC,CAAC,CAACW,cAAc,CAC/DL,kBACF,CAAC;IAEDlB,MAAM,CAACE,EAAE,CAACoB,MAAM,CAACF,UAAU,CAACC,YAAY,CAACZ,OAAO,CAAC,CAAC,CAACc,cAAc,CAC/DL,kBAAkB,EAClBlB,MAAM,CAACwB,QAAQ,CAAC,CAClB,CAAC;IAEDxB,MAAM,CAACE,EAAE,CAACoB,MAAM,CAACF,UAAU,CAACC,YAAY,CAACV,UAAU,CAAC,CAAC,CAACY,cAAc,CAClEL,kBACF,CAAC;EACH,CAAC,CAAC;EAEFjB,EAAE,CAAC,iFAAiF,EAAE,MAAM;IAC1F,MAAMa,MAAM,GAAG;MACbC,SAAS,EAAE;IACb,CAAC;IAED,MAAMU,MAAM,GAAGvB,EAAE,CAACQ,EAAE,CAAC,CAAC;IACtB,MAAMgB,OAAO,GAAGxB,EAAE,CAACQ,EAAE,CAAC,CAAC;IACvB,MAAMS,OAAO,GAAGC,UAAU,CAACC,YAAY;IAEvC,MAAMM,UAAU,GAAGvB,yBAAyB,CAC1C,WAAW,EACX,4BACF,CAAC;IAED,MAAM;MAAEwB;IAAwB,CAAC,GAAGzB,MAAM,CACxCW,MAAM,EACNa,UAAU,EACVF,MAAM,EACN,CAAC,CAAC,EACFC,OAAO,EACP,eAAe,EACf,sCAAsC,EACtCP,OACF,CAAC;IAEDS,uBAAuB,CAAC;MACtBC,aAAa,EAAE,SAAS;MACxBC,YAAY,EAAE,QAAQ;MACtBC,UAAU,EAAE,KAAK;MACjBC,UAAU,EAAE;IACd,CAAC,EAAE,QAAQ,CAAC;IAEZhC,MAAM,CAACoB,UAAU,CAACC,YAAY,CAACZ,OAAO,CAAC,CAACc,cAAc,CACpD,2BAA2BT,MAAM,CAACC,SAAS,EAAE,EAC7CkB,IAAI,CAACC,SAAS,CAAC;MACbL,aAAa,EAAE,SAAS;MACxBM,kBAAkB,EAAE,eAAe;MACnCC,eAAe,EAAE;IACnB,CAAC,CACH,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
@@ -44,7 +44,7 @@ export function createConfidentialOauthClient(client_id, client_secret, url, sco
44
44
  return makeTokenAndSaveRefresh(throwIfError(await processClientCredentialsResponse(authServer, client, await clientCredentialsGrantRequest(authServer, client, new URLSearchParams({
45
45
  scope: joinedScopes
46
46
  }), oauthHttpOptions))), "signIn");
47
- }, oauthHttpOptions, undefined, undefined, joinedScopes);
47
+ }, oauthHttpOptions, undefined, undefined, joinedScopes, undefined);
48
48
  return getToken;
49
49
  }
50
50
  //# sourceMappingURL=createConfidentialOauthClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createConfidentialOauthClient.js","names":["clientCredentialsGrantRequest","customFetch","processClientCredentialsResponse","common","createAuthorizationServer","throwIfError","createConfidentialOauthClient","client_id","client_secret","url","scopes","fetchFn","globalThis","fetch","ctxPath","client","authServer","oauthHttpOptions","joinedScopes","join","getToken","makeTokenAndSaveRefresh","URLSearchParams","scope","undefined"],"sources":["createConfidentialOauthClient.ts"],"sourcesContent":["/*\n * Copyright 2024 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Client, HttpRequestOptions } from \"oauth4webapi\";\nimport {\n clientCredentialsGrantRequest,\n customFetch,\n processClientCredentialsResponse,\n} from \"oauth4webapi\";\nimport { common, createAuthorizationServer } from \"./common.js\";\nimport type { ConfidentialOauthClient } from \"./ConfidentialOauthClient.js\";\nimport { throwIfError } from \"./throwIfError.js\";\n\n/**\n * @param client_id\n * @param client_secret\n * @param url the base url of your foundry server\n * @param scopes\n * @param fetchFn\n * @param ctxPath\n * @returns which can be used as a token provider\n */\nexport function createConfidentialOauthClient(\n client_id: string,\n client_secret: string,\n url: string,\n scopes: string[] = [\n \"api:read-data\",\n \"api:write-data\",\n \"api:use-ontologies-read\",\n \"api:use-ontologies-write\",\n ],\n fetchFn: typeof globalThis.fetch = globalThis.fetch,\n ctxPath: string = \"multipass\",\n): ConfidentialOauthClient {\n const client: Client = { client_id, client_secret };\n const authServer = createAuthorizationServer(ctxPath, url);\n const oauthHttpOptions: HttpRequestOptions = { [customFetch]: fetchFn };\n const joinedScopes = scopes.join(\" \");\n\n const { getToken, makeTokenAndSaveRefresh } = common(\n client,\n authServer,\n _signIn,\n oauthHttpOptions,\n undefined,\n undefined,\n joinedScopes,\n );\n\n async function _signIn() {\n return makeTokenAndSaveRefresh(\n throwIfError(\n await processClientCredentialsResponse(\n authServer,\n client,\n await clientCredentialsGrantRequest(\n authServer,\n client,\n new URLSearchParams({ scope: joinedScopes }),\n oauthHttpOptions,\n ),\n ),\n ),\n \"signIn\",\n );\n }\n\n return getToken;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA,SACEA,6BAA6B,EAC7BC,WAAW,EACXC,gCAAgC,QAC3B,cAAc;AACrB,SAASC,MAAM,EAAEC,yBAAyB,QAAQ,aAAa;AAE/D,SAASC,YAAY,QAAQ,mBAAmB;;AAEhD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,6BAA6BA,CAC3CC,SAAiB,EACjBC,aAAqB,EACrBC,GAAW,EACXC,MAAgB,GAAG,CACjB,eAAe,EACf,gBAAgB,EAChB,yBAAyB,EACzB,0BAA0B,CAC3B,EACDC,OAAgC,GAAGC,UAAU,CAACC,KAAK,EACnDC,OAAe,GAAG,WAAW,EACJ;EACzB,MAAMC,MAAc,GAAG;IAAER,SAAS;IAAEC;EAAc,CAAC;EACnD,MAAMQ,UAAU,GAAGZ,yBAAyB,CAACU,OAAO,EAAEL,GAAG,CAAC;EAC1D,MAAMQ,gBAAoC,GAAG;IAAE,CAAChB,WAAW,GAAGU;EAAQ,CAAC;EACvE,MAAMO,YAAY,GAAGR,MAAM,CAACS,IAAI,CAAC,GAAG,CAAC;EAErC,MAAM;IAAEC,QAAQ;IAAEC;EAAwB,CAAC,GAAGlB,MAAM,CAClDY,MAAM,EACNC,UAAU,EAQZ,kBAAyB;IACvB,OAAOK,uBAAuB,CAC5BhB,YAAY,CACV,MAAMH,gCAAgC,CACpCc,UAAU,EACVD,MAAM,EACN,MAAMf,6BAA6B,CACjCgB,UAAU,EACVD,MAAM,EACN,IAAIO,eAAe,CAAC;MAAEC,KAAK,EAAEL;IAAa,CAAC,CAAC,EAC5CD,gBACF,CACF,CACF,CAAC,EACD,QACF,CAAC;EACH,CAAC,EAtBCA,gBAAgB,EAChBO,SAAS,EACTA,SAAS,EACTN,YACF,CAAC;EAoBD,OAAOE,QAAQ;AACjB","ignoreList":[]}
1
+ {"version":3,"file":"createConfidentialOauthClient.js","names":["clientCredentialsGrantRequest","customFetch","processClientCredentialsResponse","common","createAuthorizationServer","throwIfError","createConfidentialOauthClient","client_id","client_secret","url","scopes","fetchFn","globalThis","fetch","ctxPath","client","authServer","oauthHttpOptions","joinedScopes","join","getToken","makeTokenAndSaveRefresh","URLSearchParams","scope","undefined"],"sources":["createConfidentialOauthClient.ts"],"sourcesContent":["/*\n * Copyright 2024 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Client, HttpRequestOptions } from \"oauth4webapi\";\nimport {\n clientCredentialsGrantRequest,\n customFetch,\n processClientCredentialsResponse,\n} from \"oauth4webapi\";\nimport { common, createAuthorizationServer } from \"./common.js\";\nimport type { ConfidentialOauthClient } from \"./ConfidentialOauthClient.js\";\nimport { throwIfError } from \"./throwIfError.js\";\n\n/**\n * @param client_id\n * @param client_secret\n * @param url the base url of your foundry server\n * @param scopes\n * @param fetchFn\n * @param ctxPath\n * @returns which can be used as a token provider\n */\nexport function createConfidentialOauthClient(\n client_id: string,\n client_secret: string,\n url: string,\n scopes: string[] = [\n \"api:read-data\",\n \"api:write-data\",\n \"api:use-ontologies-read\",\n \"api:use-ontologies-write\",\n ],\n fetchFn: typeof globalThis.fetch = globalThis.fetch,\n ctxPath: string = \"multipass\",\n): ConfidentialOauthClient {\n const client: Client = { client_id, client_secret };\n const authServer = createAuthorizationServer(ctxPath, url);\n const oauthHttpOptions: HttpRequestOptions = { [customFetch]: fetchFn };\n const joinedScopes = scopes.join(\" \");\n\n const { getToken, makeTokenAndSaveRefresh } = common(\n client,\n authServer,\n _signIn,\n oauthHttpOptions,\n undefined,\n undefined,\n joinedScopes,\n undefined,\n );\n\n async function _signIn() {\n return makeTokenAndSaveRefresh(\n throwIfError(\n await processClientCredentialsResponse(\n authServer,\n client,\n await clientCredentialsGrantRequest(\n authServer,\n client,\n new URLSearchParams({ scope: joinedScopes }),\n oauthHttpOptions,\n ),\n ),\n ),\n \"signIn\",\n );\n }\n\n return getToken;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA,SACEA,6BAA6B,EAC7BC,WAAW,EACXC,gCAAgC,QAC3B,cAAc;AACrB,SAASC,MAAM,EAAEC,yBAAyB,QAAQ,aAAa;AAE/D,SAASC,YAAY,QAAQ,mBAAmB;;AAEhD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,6BAA6BA,CAC3CC,SAAiB,EACjBC,aAAqB,EACrBC,GAAW,EACXC,MAAgB,GAAG,CACjB,eAAe,EACf,gBAAgB,EAChB,yBAAyB,EACzB,0BAA0B,CAC3B,EACDC,OAAgC,GAAGC,UAAU,CAACC,KAAK,EACnDC,OAAe,GAAG,WAAW,EACJ;EACzB,MAAMC,MAAc,GAAG;IAAER,SAAS;IAAEC;EAAc,CAAC;EACnD,MAAMQ,UAAU,GAAGZ,yBAAyB,CAACU,OAAO,EAAEL,GAAG,CAAC;EAC1D,MAAMQ,gBAAoC,GAAG;IAAE,CAAChB,WAAW,GAAGU;EAAQ,CAAC;EACvE,MAAMO,YAAY,GAAGR,MAAM,CAACS,IAAI,CAAC,GAAG,CAAC;EAErC,MAAM;IAAEC,QAAQ;IAAEC;EAAwB,CAAC,GAAGlB,MAAM,CAClDY,MAAM,EACNC,UAAU,EASZ,kBAAyB;IACvB,OAAOK,uBAAuB,CAC5BhB,YAAY,CACV,MAAMH,gCAAgC,CACpCc,UAAU,EACVD,MAAM,EACN,MAAMf,6BAA6B,CACjCgB,UAAU,EACVD,MAAM,EACN,IAAIO,eAAe,CAAC;MAAEC,KAAK,EAAEL;IAAa,CAAC,CAAC,EAC5CD,gBACF,CACF,CACF,CAAC,EACD,QACF,CAAC;EACH,CAAC,EAvBCA,gBAAgB,EAChBO,SAAS,EACTA,SAAS,EACTN,YAAY,EACZM,SACF,CAAC;EAoBD,OAAOJ,QAAQ;AACjB","ignoreList":[]}
@@ -16,7 +16,7 @@
16
16
 
17
17
  import delay from "delay";
18
18
  import { authorizationCodeGrantRequest, calculatePKCECodeChallenge, customFetch, generateRandomCodeVerifier, generateRandomState, processAuthorizationCodeOAuth2Response, refreshTokenGrantRequest, validateAuthResponse } from "oauth4webapi";
19
- import { common, createAuthorizationServer, readLocal, readSession, removeLocal, removeSession, saveLocal, saveSession } from "./common.js";
19
+ import { common, createAuthorizationServer, getStorage, readLocal, readSession, removeLocal, removeSession, saveLocal, saveSession } from "./common.js";
20
20
  import { throwIfError } from "./throwIfError.js";
21
21
  import { processOptionsAndAssignDefaults } from "./utils.js";
22
22
 
@@ -48,6 +48,7 @@ import { processOptionsAndAssignDefaults } from "./utils.js";
48
48
  export function createPublicOauthClient(client_id, url, redirect_uri, useHistory, loginPage, postLoginPage, scopes, fetchFn, ctxPath) {
49
49
  let refreshTokenMarker;
50
50
  let joinedScopes;
51
+ let tokenStorage;
51
52
  ({
52
53
  useHistory,
53
54
  loginPage,
@@ -55,7 +56,8 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
55
56
  joinedScopes,
56
57
  fetchFn,
57
58
  ctxPath,
58
- refreshTokenMarker
59
+ refreshTokenMarker,
60
+ tokenStorage
59
61
  } = processOptionsAndAssignDefaults(url, redirect_uri, useHistory, loginPage, postLoginPage, scopes, fetchFn, ctxPath));
60
62
  const client = {
61
63
  client_id,
@@ -65,6 +67,7 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
65
67
  const oauthHttpOptions = {
66
68
  [customFetch]: fetchFn
67
69
  };
70
+ const storage = getStorage(tokenStorage);
68
71
  const {
69
72
  makeTokenAndSaveRefresh,
70
73
  getToken
@@ -76,7 +79,7 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
76
79
  ) ?? (await maybeHandleAuthReturn())
77
80
  // 3. If we haven't been able to load the token from one of the two above ways, we need to make the initial auth request
78
81
  ?? (await initiateLoginRedirect());
79
- }, oauthHttpOptions, maybeRefresh.bind(globalThis, true), refreshTokenMarker, joinedScopes);
82
+ }, oauthHttpOptions, maybeRefresh.bind(globalThis, true), refreshTokenMarker, joinedScopes, storage);
80
83
 
81
84
  // as an arrow function, `useHistory` is known to be a boolean
82
85
  const go = async x => {
@@ -88,11 +91,15 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
88
91
  throw new Error("Unable to redirect");
89
92
  };
90
93
  async function maybeRefresh(expectRefreshToken) {
94
+ if (tokenStorage === "none") {
95
+ if (expectRefreshToken) throw new Error("No refresh token found");
96
+ return;
97
+ }
91
98
  const {
92
99
  refresh_token,
93
100
  refreshTokenMarker: lastRefreshTokenMarker,
94
101
  requestedScopes: initialRequestedScopes
95
- } = readLocal(client);
102
+ } = readLocal(client, storage);
96
103
  const areScopesEqual = initialRequestedScopes != null && joinedScopes === initialRequestedScopes;
97
104
  if (!refresh_token || lastRefreshTokenMarker !== refreshTokenMarker || !areScopesEqual) {
98
105
  if (expectRefreshToken) throw new Error("No refresh token found");
@@ -115,7 +122,7 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
115
122
  // eslint-disable-next-line no-console
116
123
  console.warn("Failed to get OAuth2 refresh token. Removing refresh token", e);
117
124
  }
118
- removeLocal(client);
125
+ removeLocal(client, storage);
119
126
  if (expectRefreshToken) {
120
127
  throw new Error("Could not refresh token");
121
128
  }
@@ -137,7 +144,7 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
137
144
  // eslint-disable-next-line no-console
138
145
  console.warn("Failed to get OAuth2 token using PKCE, removing PKCE and starting a new auth flow", e);
139
146
  }
140
- removeLocal(client);
147
+ removeLocal(client, storage);
141
148
  removeSession(client);
142
149
  throw e;
143
150
  }
@@ -146,7 +153,7 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
146
153
  // As an arrow function, `scopes` and `postLoginPage` are known at compile time
147
154
  const initiateLoginRedirect = async () => {
148
155
  if (loginPage && window.location.href !== loginPage && window.location.pathname !== loginPage) {
149
- saveLocal(client, {});
156
+ saveLocal(client, {}, storage);
150
157
  saveSession(client, {
151
158
  oldUrl: postLoginPage
152
159
  });
@@ -156,12 +163,15 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
156
163
  const state = generateRandomState();
157
164
  const codeVerifier = generateRandomCodeVerifier();
158
165
  const oldUrl = readSession(client).oldUrl ?? window.location.toString();
159
- saveLocal(client, {});
166
+ saveLocal(client, {}, storage);
160
167
  saveSession(client, {
161
168
  codeVerifier,
162
169
  state,
163
170
  oldUrl
164
171
  });
172
+
173
+ // Only request offline_access (refresh tokens) if we're going to store them
174
+ const scopeString = tokenStorage === "none" ? joinedScopes : `offline_access ${joinedScopes}`;
165
175
  window.location.assign(`${authServer.authorization_endpoint}?${new URLSearchParams({
166
176
  client_id,
167
177
  response_type: "code",
@@ -169,7 +179,7 @@ export function createPublicOauthClient(client_id, url, redirect_uri, useHistory
169
179
  redirect_uri,
170
180
  code_challenge: await calculatePKCECodeChallenge(codeVerifier),
171
181
  code_challenge_method: "S256",
172
- scope: `offline_access ${joinedScopes}`
182
+ scope: scopeString
173
183
  })}`);
174
184
 
175
185
  // Give time for redirect to happen
@@ -1 +1 @@
1
- {"version":3,"file":"createPublicOauthClient.js","names":["delay","authorizationCodeGrantRequest","calculatePKCECodeChallenge","customFetch","generateRandomCodeVerifier","generateRandomState","processAuthorizationCodeOAuth2Response","refreshTokenGrantRequest","validateAuthResponse","common","createAuthorizationServer","readLocal","readSession","removeLocal","removeSession","saveLocal","saveSession","throwIfError","processOptionsAndAssignDefaults","createPublicOauthClient","client_id","url","redirect_uri","useHistory","loginPage","postLoginPage","scopes","fetchFn","ctxPath","refreshTokenMarker","joinedScopes","client","token_endpoint_auth_method","authServer","oauthHttpOptions","makeTokenAndSaveRefresh","getToken","_signIn","maybeRefresh","maybeHandleAuthReturn","initiateLoginRedirect","bind","globalThis","go","x","window","history","replaceState","location","assign","Error","expectRefreshToken","refresh_token","lastRefreshTokenMarker","requestedScopes","initialRequestedScopes","areScopesEqual","result","pathname","URL","oldUrl","e","process","env","NODE_ENV","console","warn","state","codeVerifier","ret","href","toString","authorization_endpoint","URLSearchParams","response_type","code_challenge","code_challenge_method","scope"],"sources":["createPublicOauthClient.ts"],"sourcesContent":["/*\n * Copyright 2024 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport delay from \"delay\";\nimport type { Client, HttpRequestOptions } from \"oauth4webapi\";\nimport {\n authorizationCodeGrantRequest,\n calculatePKCECodeChallenge,\n customFetch,\n generateRandomCodeVerifier,\n generateRandomState,\n processAuthorizationCodeOAuth2Response,\n refreshTokenGrantRequest,\n validateAuthResponse,\n} from \"oauth4webapi\";\nimport {\n common,\n createAuthorizationServer,\n readLocal,\n readSession,\n removeLocal,\n removeSession,\n saveLocal,\n saveSession,\n} from \"./common.js\";\nimport type { PublicOauthClient } from \"./PublicOauthClient.js\";\nimport { throwIfError } from \"./throwIfError.js\";\nimport type { Token } from \"./Token.js\";\nimport { processOptionsAndAssignDefaults } from \"./utils.js\";\n\ndeclare const process: {\n env: {\n NODE_ENV: \"production\" | \"development\";\n TARGET: \"browser\" | \"node\";\n };\n};\n\nexport interface PublicOauthClientOptions {\n /**\n * If true, uses `history.replaceState()`, otherwise uses `window.location.assign()` (defaults to true)\n */\n useHistory?: boolean;\n\n /**\n * Custom landing page URL prior to logging in\n */\n loginPage?: string;\n\n /**\n * URL to return to after completed authentication cycle (defaults to `window.location.toString()`)\n */\n postLoginPage?: string;\n\n /**\n * * @param {string[]} [scopes=[]] - OAuth scopes to request. If not provided, defaults to `[\"api:read-data\", \"api:write-data\", \"api:use-ontologies-read\", \"api:use-ontologies-write\"]`\n */\n scopes?: string[];\n\n /**\n * Custom fetch function to use for requests (defaults to `globalThis.fetch`)\n */\n fetchFn?: typeof globalThis.fetch;\n\n /**\n * Context path for the authorization server (defaults to \"multipass\")\n */\n ctxPath?: string;\n\n /**\n * Allows for an additional value to be appended to the local storage key for the refresh token.\n */\n refreshTokenMarker?: string;\n}\n\n/**\n * Creates a PublicOauthClient for authentication.\n *\n * @param {string} clientId - The client_id from the OAuth configuration on the server\n * @param {string} url - The base URL of your Foundry server\n * @param {string} redirectUrl - The URL configured for redirect in the OAuth configuration on the server\n * @param {PublicOauthClientOptions} options - Additional options for the client\n * @returns {PublicOauthClient} A client that can be used as a token provider\n */\nexport function createPublicOauthClient(\n clientId: string,\n url: string,\n redirectUrl: string,\n options?: PublicOauthClientOptions,\n): PublicOauthClient;\n\n/**\n * Creates a PublicOauthClient for authentication.\n *\n * @param {string} clientId - The client_id from the OAuth configuration on the server\n * @param {string} url - The base URL of your Foundry server\n * @param {string} redirectUrl - The URL configured for redirect in the OAuth configuration on the server\n * @param {boolean} useHistory - If true, uses `history.replaceState()`, otherwise uses `window.location.assign()` (defaults to true)\n * @param {string} loginPage - Custom landing page URL prior to logging in\n * @param {string} postLoginPage - URL to return to after completed authentication cycle (defaults to `window.location.toString()`)\n * @param {string[]} scopes - OAuth scopes to request. If not provided, defaults to `[\"api:read-data\", \"api:write-data\", \"api:use-ontologies-read\", \"api:use-ontologies-write\"]`\n * @param {typeof globalThis.fetch} fetchFn - Custom fetch function to use for requests (defaults to `globalThis.fetch`)\n * @param {string} ctxPath - Context path for the authorization server (defaults to \"multipass\")\n * @returns {PublicOauthClient} A client that can be used as a token provider\n */\nexport function createPublicOauthClient(\n client_id: string,\n url: string,\n redirectUrl: string,\n useHistory?: boolean,\n loginPage?: string,\n postLoginPage?: string,\n scopes?: string[],\n fetchFn?: typeof globalThis.fetch,\n ctxPath?: string,\n): PublicOauthClient;\nexport function createPublicOauthClient(\n client_id: string,\n url: string,\n redirect_uri: string,\n useHistory?: boolean | PublicOauthClientOptions,\n loginPage?: string,\n postLoginPage?: string,\n scopes?: string[],\n fetchFn?: typeof globalThis.fetch,\n ctxPath?: string,\n): PublicOauthClient {\n let refreshTokenMarker: string | undefined;\n let joinedScopes: string;\n ({\n useHistory,\n loginPage,\n postLoginPage,\n joinedScopes,\n fetchFn,\n ctxPath,\n refreshTokenMarker,\n } = processOptionsAndAssignDefaults(\n url,\n redirect_uri,\n useHistory,\n loginPage,\n postLoginPage,\n scopes,\n fetchFn,\n ctxPath,\n ));\n\n const client: Client = {\n client_id,\n token_endpoint_auth_method: \"none\",\n };\n const authServer = createAuthorizationServer(ctxPath, url);\n const oauthHttpOptions: HttpRequestOptions = { [customFetch]: fetchFn };\n\n const { makeTokenAndSaveRefresh, getToken } = common(\n client,\n authServer,\n _signIn,\n oauthHttpOptions,\n maybeRefresh.bind(globalThis, true),\n refreshTokenMarker,\n joinedScopes,\n );\n\n // as an arrow function, `useHistory` is known to be a boolean\n const go = async (x: string) => {\n if (useHistory) {\n window.history.replaceState({}, \"\", x);\n return;\n } else window.location.assign(x);\n\n await delay(1000);\n throw new Error(\"Unable to redirect\");\n };\n\n async function maybeRefresh(\n expectRefreshToken?: boolean,\n ): Promise<Token | undefined> {\n const {\n refresh_token,\n refreshTokenMarker: lastRefreshTokenMarker,\n requestedScopes: initialRequestedScopes,\n } = readLocal(client);\n\n const areScopesEqual = initialRequestedScopes != null\n && joinedScopes === initialRequestedScopes;\n\n if (\n !refresh_token || lastRefreshTokenMarker !== refreshTokenMarker\n || !areScopesEqual\n ) {\n if (expectRefreshToken) throw new Error(\"No refresh token found\");\n return;\n }\n\n try {\n // note, we don't use processRefreshTokenResponse because the mp oauth implementation\n // doesn't do refresh tokens quite right and this lets us skip the id check\n const result = makeTokenAndSaveRefresh(\n throwIfError(\n await processAuthorizationCodeOAuth2Response(\n authServer,\n client,\n await refreshTokenGrantRequest(\n authServer,\n client,\n refresh_token,\n oauthHttpOptions,\n ),\n ),\n ),\n \"refresh\",\n );\n\n if (\n result && window.location.pathname === new URL(redirect_uri).pathname\n ) {\n const { oldUrl } = readSession(client);\n // don't block on the redirect\n void go(oldUrl ?? \"/\");\n }\n return result;\n } catch (e) {\n if (process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"Failed to get OAuth2 refresh token. Removing refresh token\",\n e,\n );\n }\n removeLocal(client);\n if (expectRefreshToken) {\n throw new Error(\"Could not refresh token\");\n }\n }\n }\n\n async function maybeHandleAuthReturn() {\n const { state, oldUrl, codeVerifier } = readSession(client);\n if (!codeVerifier) return;\n\n try {\n const ret = makeTokenAndSaveRefresh(\n throwIfError(\n await processAuthorizationCodeOAuth2Response(\n authServer,\n client,\n await authorizationCodeGrantRequest(\n authServer,\n client,\n throwIfError(\n validateAuthResponse(\n authServer,\n client,\n new URL(window.location.href),\n state,\n ),\n ),\n redirect_uri,\n codeVerifier,\n oauthHttpOptions,\n ),\n ),\n ),\n \"signIn\",\n );\n\n void go(oldUrl);\n return ret;\n } catch (e) {\n if (process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"Failed to get OAuth2 token using PKCE, removing PKCE and starting a new auth flow\",\n e,\n );\n }\n removeLocal(client);\n removeSession(client);\n throw e;\n }\n }\n\n // As an arrow function, `scopes` and `postLoginPage` are known at compile time\n const initiateLoginRedirect = async (): Promise<void> => {\n if (\n loginPage\n && window.location.href !== loginPage\n && window.location.pathname !== loginPage\n ) {\n saveLocal(client, {});\n saveSession(client, { oldUrl: postLoginPage });\n await go(loginPage);\n return;\n }\n\n const state = generateRandomState()!;\n const codeVerifier = generateRandomCodeVerifier();\n const oldUrl = readSession(client).oldUrl ?? window.location.toString();\n saveLocal(client, {});\n saveSession(client, { codeVerifier, state, oldUrl });\n\n window.location.assign(`${authServer\n .authorization_endpoint!}?${new URLSearchParams({\n client_id,\n response_type: \"code\",\n state,\n redirect_uri,\n code_challenge: await calculatePKCECodeChallenge(codeVerifier),\n code_challenge_method: \"S256\",\n scope: `offline_access ${joinedScopes}`,\n })}`);\n\n // Give time for redirect to happen\n await delay(1000);\n throw new Error(\"Unable to redirect\");\n };\n\n /** Will throw if there is no token! */\n async function _signIn() {\n // 1. Check if we have a refresh token in local storage\n return await maybeRefresh()\n // 2. If there is no refresh token we are likely trying to perform the callback\n ?? await maybeHandleAuthReturn()\n // 3. If we haven't been able to load the token from one of the two above ways, we need to make the initial auth request\n ?? await initiateLoginRedirect() as unknown as Token;\n }\n\n return getToken;\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"createPublicOauthClient.js","names":["delay","authorizationCodeGrantRequest","calculatePKCECodeChallenge","customFetch","generateRandomCodeVerifier","generateRandomState","processAuthorizationCodeOAuth2Response","refreshTokenGrantRequest","validateAuthResponse","common","createAuthorizationServer","getStorage","readLocal","readSession","removeLocal","removeSession","saveLocal","saveSession","throwIfError","processOptionsAndAssignDefaults","createPublicOauthClient","client_id","url","redirect_uri","useHistory","loginPage","postLoginPage","scopes","fetchFn","ctxPath","refreshTokenMarker","joinedScopes","tokenStorage","client","token_endpoint_auth_method","authServer","oauthHttpOptions","storage","makeTokenAndSaveRefresh","getToken","_signIn","maybeRefresh","maybeHandleAuthReturn","initiateLoginRedirect","bind","globalThis","go","x","window","history","replaceState","location","assign","Error","expectRefreshToken","refresh_token","lastRefreshTokenMarker","requestedScopes","initialRequestedScopes","areScopesEqual","result","pathname","URL","oldUrl","e","process","env","NODE_ENV","console","warn","state","codeVerifier","ret","href","toString","scopeString","authorization_endpoint","URLSearchParams","response_type","code_challenge","code_challenge_method","scope"],"sources":["createPublicOauthClient.ts"],"sourcesContent":["/*\n * Copyright 2024 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport delay from \"delay\";\nimport type { Client, HttpRequestOptions } from \"oauth4webapi\";\nimport {\n authorizationCodeGrantRequest,\n calculatePKCECodeChallenge,\n customFetch,\n generateRandomCodeVerifier,\n generateRandomState,\n processAuthorizationCodeOAuth2Response,\n refreshTokenGrantRequest,\n validateAuthResponse,\n} from \"oauth4webapi\";\nimport {\n common,\n createAuthorizationServer,\n getStorage,\n readLocal,\n readSession,\n removeLocal,\n removeSession,\n saveLocal,\n saveSession,\n} from \"./common.js\";\nimport type { TokenStorageType } from \"./common.js\";\nimport type { PublicOauthClient } from \"./PublicOauthClient.js\";\nimport { throwIfError } from \"./throwIfError.js\";\nimport type { Token } from \"./Token.js\";\nimport { processOptionsAndAssignDefaults } from \"./utils.js\";\n\ndeclare const process: {\n env: {\n NODE_ENV: \"production\" | \"development\";\n TARGET: \"browser\" | \"node\";\n };\n};\n\nexport interface PublicOauthClientOptions {\n /**\n * If true, uses `history.replaceState()`, otherwise uses `window.location.assign()` (defaults to true)\n */\n useHistory?: boolean;\n\n /**\n * Custom landing page URL prior to logging in\n */\n loginPage?: string;\n\n /**\n * URL to return to after completed authentication cycle (defaults to `window.location.toString()`)\n */\n postLoginPage?: string;\n\n /**\n * * @param {string[]} [scopes=[]] - OAuth scopes to request. If not provided, defaults to `[\"api:read-data\", \"api:write-data\", \"api:use-ontologies-read\", \"api:use-ontologies-write\"]`\n */\n scopes?: string[];\n\n /**\n * Custom fetch function to use for requests (defaults to `globalThis.fetch`)\n */\n fetchFn?: typeof globalThis.fetch;\n\n /**\n * Context path for the authorization server (defaults to \"multipass\")\n */\n ctxPath?: string;\n\n /**\n * Allows for an additional value to be appended to the local storage key for the refresh token.\n */\n refreshTokenMarker?: string;\n\n /**\n * Controls how refresh tokens are stored. Affects security vs. convenience tradeoff.\n *\n * - `'localStorage'` (default): Tokens persist across browser sessions.\n * Best UX but vulnerable on shared devices.\n *\n * - `'sessionStorage'`: Tokens cleared when browser/tab closes.\n * Recommended for shared devices. Users re-authenticate each browser session.\n *\n * - `'none'`: Tokens never stored. Users re-authenticate on every page load.\n * Most secure but worst UX. Does not request `offline_access` scope.\n *\n * @default 'localStorage'\n */\n tokenStorage?: TokenStorageType;\n}\n\n/**\n * Creates a PublicOauthClient for authentication.\n *\n * @param {string} clientId - The client_id from the OAuth configuration on the server\n * @param {string} url - The base URL of your Foundry server\n * @param {string} redirectUrl - The URL configured for redirect in the OAuth configuration on the server\n * @param {PublicOauthClientOptions} options - Additional options for the client\n * @returns {PublicOauthClient} A client that can be used as a token provider\n */\nexport function createPublicOauthClient(\n clientId: string,\n url: string,\n redirectUrl: string,\n options?: PublicOauthClientOptions,\n): PublicOauthClient;\n\n/**\n * Creates a PublicOauthClient for authentication.\n *\n * @param {string} clientId - The client_id from the OAuth configuration on the server\n * @param {string} url - The base URL of your Foundry server\n * @param {string} redirectUrl - The URL configured for redirect in the OAuth configuration on the server\n * @param {boolean} useHistory - If true, uses `history.replaceState()`, otherwise uses `window.location.assign()` (defaults to true)\n * @param {string} loginPage - Custom landing page URL prior to logging in\n * @param {string} postLoginPage - URL to return to after completed authentication cycle (defaults to `window.location.toString()`)\n * @param {string[]} scopes - OAuth scopes to request. If not provided, defaults to `[\"api:read-data\", \"api:write-data\", \"api:use-ontologies-read\", \"api:use-ontologies-write\"]`\n * @param {typeof globalThis.fetch} fetchFn - Custom fetch function to use for requests (defaults to `globalThis.fetch`)\n * @param {string} ctxPath - Context path for the authorization server (defaults to \"multipass\")\n * @returns {PublicOauthClient} A client that can be used as a token provider\n */\nexport function createPublicOauthClient(\n client_id: string,\n url: string,\n redirectUrl: string,\n useHistory?: boolean,\n loginPage?: string,\n postLoginPage?: string,\n scopes?: string[],\n fetchFn?: typeof globalThis.fetch,\n ctxPath?: string,\n): PublicOauthClient;\nexport function createPublicOauthClient(\n client_id: string,\n url: string,\n redirect_uri: string,\n useHistory?: boolean | PublicOauthClientOptions,\n loginPage?: string,\n postLoginPage?: string,\n scopes?: string[],\n fetchFn?: typeof globalThis.fetch,\n ctxPath?: string,\n): PublicOauthClient {\n let refreshTokenMarker: string | undefined;\n let joinedScopes: string;\n let tokenStorage: TokenStorageType;\n ({\n useHistory,\n loginPage,\n postLoginPage,\n joinedScopes,\n fetchFn,\n ctxPath,\n refreshTokenMarker,\n tokenStorage,\n } = processOptionsAndAssignDefaults(\n url,\n redirect_uri,\n useHistory,\n loginPage,\n postLoginPage,\n scopes,\n fetchFn,\n ctxPath,\n ));\n\n const client: Client = {\n client_id,\n token_endpoint_auth_method: \"none\",\n };\n const authServer = createAuthorizationServer(ctxPath, url);\n const oauthHttpOptions: HttpRequestOptions = { [customFetch]: fetchFn };\n\n const storage = getStorage(tokenStorage);\n\n const { makeTokenAndSaveRefresh, getToken } = common(\n client,\n authServer,\n _signIn,\n oauthHttpOptions,\n maybeRefresh.bind(globalThis, true),\n refreshTokenMarker,\n joinedScopes,\n storage,\n );\n\n // as an arrow function, `useHistory` is known to be a boolean\n const go = async (x: string) => {\n if (useHistory) {\n window.history.replaceState({}, \"\", x);\n return;\n } else window.location.assign(x);\n\n await delay(1000);\n throw new Error(\"Unable to redirect\");\n };\n\n async function maybeRefresh(\n expectRefreshToken?: boolean,\n ): Promise<Token | undefined> {\n if (tokenStorage === \"none\") {\n if (expectRefreshToken) throw new Error(\"No refresh token found\");\n return;\n }\n\n const {\n refresh_token,\n refreshTokenMarker: lastRefreshTokenMarker,\n requestedScopes: initialRequestedScopes,\n } = readLocal(client, storage);\n\n const areScopesEqual = initialRequestedScopes != null\n && joinedScopes === initialRequestedScopes;\n\n if (\n !refresh_token || lastRefreshTokenMarker !== refreshTokenMarker\n || !areScopesEqual\n ) {\n if (expectRefreshToken) throw new Error(\"No refresh token found\");\n return;\n }\n\n try {\n // note, we don't use processRefreshTokenResponse because the mp oauth implementation\n // doesn't do refresh tokens quite right and this lets us skip the id check\n const result = makeTokenAndSaveRefresh(\n throwIfError(\n await processAuthorizationCodeOAuth2Response(\n authServer,\n client,\n await refreshTokenGrantRequest(\n authServer,\n client,\n refresh_token,\n oauthHttpOptions,\n ),\n ),\n ),\n \"refresh\",\n );\n\n if (\n result && window.location.pathname === new URL(redirect_uri).pathname\n ) {\n const { oldUrl } = readSession(client);\n // don't block on the redirect\n void go(oldUrl ?? \"/\");\n }\n return result;\n } catch (e) {\n if (process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"Failed to get OAuth2 refresh token. Removing refresh token\",\n e,\n );\n }\n removeLocal(client, storage);\n if (expectRefreshToken) {\n throw new Error(\"Could not refresh token\");\n }\n }\n }\n\n async function maybeHandleAuthReturn() {\n const { state, oldUrl, codeVerifier } = readSession(client);\n if (!codeVerifier) return;\n\n try {\n const ret = makeTokenAndSaveRefresh(\n throwIfError(\n await processAuthorizationCodeOAuth2Response(\n authServer,\n client,\n await authorizationCodeGrantRequest(\n authServer,\n client,\n throwIfError(\n validateAuthResponse(\n authServer,\n client,\n new URL(window.location.href),\n state,\n ),\n ),\n redirect_uri,\n codeVerifier,\n oauthHttpOptions,\n ),\n ),\n ),\n \"signIn\",\n );\n\n void go(oldUrl);\n return ret;\n } catch (e) {\n if (process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"Failed to get OAuth2 token using PKCE, removing PKCE and starting a new auth flow\",\n e,\n );\n }\n removeLocal(client, storage);\n removeSession(client);\n throw e;\n }\n }\n\n // As an arrow function, `scopes` and `postLoginPage` are known at compile time\n const initiateLoginRedirect = async (): Promise<void> => {\n if (\n loginPage\n && window.location.href !== loginPage\n && window.location.pathname !== loginPage\n ) {\n saveLocal(client, {}, storage);\n saveSession(client, { oldUrl: postLoginPage });\n await go(loginPage);\n return;\n }\n\n const state = generateRandomState()!;\n const codeVerifier = generateRandomCodeVerifier();\n const oldUrl = readSession(client).oldUrl ?? window.location.toString();\n saveLocal(client, {}, storage);\n saveSession(client, { codeVerifier, state, oldUrl });\n\n // Only request offline_access (refresh tokens) if we're going to store them\n const scopeString = tokenStorage === \"none\"\n ? joinedScopes\n : `offline_access ${joinedScopes}`;\n\n window.location.assign(`${authServer\n .authorization_endpoint!}?${new URLSearchParams({\n client_id,\n response_type: \"code\",\n state,\n redirect_uri,\n code_challenge: await calculatePKCECodeChallenge(codeVerifier),\n code_challenge_method: \"S256\",\n scope: scopeString,\n })}`);\n\n // Give time for redirect to happen\n await delay(1000);\n throw new Error(\"Unable to redirect\");\n };\n\n /** Will throw if there is no token! */\n async function _signIn() {\n // 1. Check if we have a refresh token in local storage\n return await maybeRefresh()\n // 2. If there is no refresh token we are likely trying to perform the callback\n ?? await maybeHandleAuthReturn()\n // 3. If we haven't been able to load the token from one of the two above ways, we need to make the initial auth request\n ?? await initiateLoginRedirect() as unknown as Token;\n }\n\n return getToken;\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["createConfidentialOauthClient","createPublicOauthClient"],"sources":["index.ts"],"sourcesContent":["/*\n * Copyright 2024 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport type { ConfidentialOauthClient } from \"./ConfidentialOauthClient.js\";\nexport { createConfidentialOauthClient } from \"./createConfidentialOauthClient.js\";\nexport { createPublicOauthClient } from \"./createPublicOauthClient.js\";\nexport type { PublicOauthClient } from \"./PublicOauthClient.js\";\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA,SAASA,6BAA6B,QAAQ,oCAAoC;AAClF,SAASC,uBAAuB,QAAQ,8BAA8B","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["createConfidentialOauthClient","createPublicOauthClient"],"sources":["index.ts"],"sourcesContent":["/*\n * Copyright 2024 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport type { TokenStorageType } from \"./common.js\";\nexport type { ConfidentialOauthClient } from \"./ConfidentialOauthClient.js\";\nexport { createConfidentialOauthClient } from \"./createConfidentialOauthClient.js\";\nexport {\n createPublicOauthClient,\n type PublicOauthClientOptions,\n} from \"./createPublicOauthClient.js\";\nexport type { PublicOauthClient } from \"./PublicOauthClient.js\";\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIA,SAASA,6BAA6B,QAAQ,oCAAoC;AAClF,SACEC,uBAAuB,QAElB,8BAA8B","ignoreList":[]}
@@ -260,7 +260,7 @@ describe(createPublicOauthClient, () => {
260
260
  expect(vi.mocked(commonJs.common)).toHaveBeenCalledWith(expect.objectContaining({
261
261
  client_id: clientArgs.clientId,
262
262
  token_endpoint_auth_method: "none"
263
- }), commonJs.createAuthorizationServer(clientArgs.ctxPath ?? "/multipass", clientArgs.foundryUrl), expect.any(Function), expect.anything(), expect.any(Function), undefined, (clientArgs.scopes ?? ["api:read-data", "api:write-data", "api:use-ontologies-read", "api:use-ontologies-write"]).sort().join(" "));
263
+ }), commonJs.createAuthorizationServer(clientArgs.ctxPath ?? "/multipass", clientArgs.foundryUrl), expect.any(Function), expect.anything(), expect.any(Function), undefined, (clientArgs.scopes ?? ["api:read-data", "api:write-data", "api:use-ontologies-read", "api:use-ontologies-write"]).sort().join(" "), undefined);
264
264
  });
265
265
  describe.each([{
266
266
  localStorage: {
@@ -342,5 +342,95 @@ describe(createPublicOauthClient, () => {
342
342
  }
343
343
  });
344
344
  });
345
+ describe("tokenStorage option", () => {
346
+ beforeEach(() => {
347
+ setupWindowLocation("https://foundry.local");
348
+ setupLocalState({});
349
+ vi.mocked(commonJs.readSession).mockImplementation(() => ({}));
350
+ // getStorage() checks process.env.TARGET to determine if we're in a browser
351
+ vi.stubGlobal("process", {
352
+ env: {
353
+ TARGET: "browser"
354
+ }
355
+ });
356
+ });
357
+ afterEach(() => {
358
+ vi.unstubAllGlobals();
359
+ // Re-stub the globals that the outer describe needs
360
+ vi.stubGlobal("window", mockWindow);
361
+ vi.stubGlobal("localStorage", mockLocalStorage);
362
+ vi.stubGlobal("sessionStorage", mockSessionStorage);
363
+ });
364
+ it("should pass localStorage to common when tokenStorage is 'localStorage'", () => {
365
+ createPublicOauthClient(BASE_CLIENT_ARGS.clientId, BASE_CLIENT_ARGS.foundryUrl, BASE_CLIENT_ARGS.redirectUrl, {
366
+ tokenStorage: "localStorage"
367
+ });
368
+
369
+ // common() is called with 8 args, the last one being the storage
370
+ const lastArg = vi.mocked(commonJs.common).mock.calls[0][7];
371
+ expect(lastArg).toBe(mockLocalStorage);
372
+ });
373
+ it("should pass sessionStorage to common when tokenStorage is 'sessionStorage'", () => {
374
+ createPublicOauthClient(BASE_CLIENT_ARGS.clientId, BASE_CLIENT_ARGS.foundryUrl, BASE_CLIENT_ARGS.redirectUrl, {
375
+ tokenStorage: "sessionStorage"
376
+ });
377
+ const lastArg = vi.mocked(commonJs.common).mock.calls[0][7];
378
+ expect(lastArg).toBe(mockSessionStorage);
379
+ });
380
+ it("should pass undefined to common when tokenStorage is 'none'", () => {
381
+ createPublicOauthClient(BASE_CLIENT_ARGS.clientId, BASE_CLIENT_ARGS.foundryUrl, BASE_CLIENT_ARGS.redirectUrl, {
382
+ tokenStorage: "none"
383
+ });
384
+ const lastArg = vi.mocked(commonJs.common).mock.calls[0][7];
385
+ expect(lastArg).toBeUndefined();
386
+ });
387
+ it("should not include offline_access scope when tokenStorage is 'none'", async () => {
388
+ const client = createPublicOauthClient(BASE_CLIENT_ARGS.clientId, BASE_CLIENT_ARGS.foundryUrl, BASE_CLIENT_ARGS.redirectUrl, {
389
+ tokenStorage: "none"
390
+ });
391
+
392
+ // Trigger the auth flow - this should redirect to multipass
393
+ await expect(client()).rejects.toThrowError("Unable to redirect");
394
+ expect(mockWindow.location.assign).toHaveBeenCalledOnce();
395
+ const url = new URL(mockWindow.location.assign.mock.calls[0][0]);
396
+ const scope = url.searchParams.get("scope");
397
+
398
+ // Should NOT include offline_access
399
+ expect(scope).not.toContain("offline_access");
400
+ // Should still include the regular scopes
401
+ expect(scope).toContain("api:read-data");
402
+ });
403
+ it("should pass localStorage to common when tokenStorage is not provided", () => {
404
+ createPublicOauthClient(BASE_CLIENT_ARGS.clientId, BASE_CLIENT_ARGS.foundryUrl, BASE_CLIENT_ARGS.redirectUrl);
405
+ const lastArg = vi.mocked(commonJs.common).mock.calls[0][7];
406
+ expect(lastArg).toBe(mockLocalStorage);
407
+ });
408
+ it("should include offline_access scope when tokenStorage is 'localStorage'", async () => {
409
+ const client = createPublicOauthClient(BASE_CLIENT_ARGS.clientId, BASE_CLIENT_ARGS.foundryUrl, BASE_CLIENT_ARGS.redirectUrl, {
410
+ tokenStorage: "localStorage"
411
+ });
412
+ await expect(client()).rejects.toThrowError("Unable to redirect");
413
+ const url = new URL(mockWindow.location.assign.mock.calls[0][0]);
414
+ const scope = url.searchParams.get("scope");
415
+ expect(scope).toContain("offline_access");
416
+ });
417
+ it("should include offline_access scope when tokenStorage is 'sessionStorage'", async () => {
418
+ const client = createPublicOauthClient(BASE_CLIENT_ARGS.clientId, BASE_CLIENT_ARGS.foundryUrl, BASE_CLIENT_ARGS.redirectUrl, {
419
+ tokenStorage: "sessionStorage"
420
+ });
421
+ await expect(client()).rejects.toThrowError("Unable to redirect");
422
+ const url = new URL(mockWindow.location.assign.mock.calls[0][0]);
423
+ const scope = url.searchParams.get("scope");
424
+ expect(scope).toContain("offline_access");
425
+ });
426
+ it("should not attempt to read from storage when tokenStorage is 'none'", async () => {
427
+ vi.mocked(commonJs.readLocal).mockClear();
428
+ const client = createPublicOauthClient(BASE_CLIENT_ARGS.clientId, BASE_CLIENT_ARGS.foundryUrl, BASE_CLIENT_ARGS.redirectUrl, {
429
+ tokenStorage: "none"
430
+ });
431
+ await expect(client()).rejects.toThrowError("Unable to redirect");
432
+ expect(vi.mocked(commonJs.readLocal)).not.toHaveBeenCalled();
433
+ });
434
+ });
345
435
  });
346
436
  //# sourceMappingURL=publicOauth.test.js.map