@meshagent/meshagent-ts-auth 0.35.6

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 (50) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/LICENSE +201 -0
  3. package/README.md +271 -0
  4. package/dist/browser/access-token-provider.d.ts +9 -0
  5. package/dist/browser/browser-global.d.ts +7 -0
  6. package/dist/browser/ensure-login.d.ts +6 -0
  7. package/dist/browser/index.d.ts +7 -0
  8. package/dist/browser/ma-auth.global.js +26382 -0
  9. package/dist/browser/mauth-response.d.ts +15 -0
  10. package/dist/browser/meshagent-auth.d.ts +40 -0
  11. package/dist/browser/oauth-session-manager.d.ts +22 -0
  12. package/dist/browser/pkce-generator.d.ts +27 -0
  13. package/dist/browser/storage.d.ts +6 -0
  14. package/dist/cjs/access-token-provider.d.ts +9 -0
  15. package/dist/cjs/access-token-provider.js +19 -0
  16. package/dist/cjs/browser-global.d.ts +7 -0
  17. package/dist/cjs/browser-global.js +2 -0
  18. package/dist/cjs/ensure-login.d.ts +6 -0
  19. package/dist/cjs/ensure-login.js +118 -0
  20. package/dist/cjs/index.d.ts +7 -0
  21. package/dist/cjs/index.js +23 -0
  22. package/dist/cjs/mauth-response.d.ts +15 -0
  23. package/dist/cjs/mauth-response.js +94 -0
  24. package/dist/cjs/meshagent-auth.d.ts +40 -0
  25. package/dist/cjs/meshagent-auth.js +177 -0
  26. package/dist/cjs/oauth-session-manager.d.ts +22 -0
  27. package/dist/cjs/oauth-session-manager.js +116 -0
  28. package/dist/cjs/pkce-generator.d.ts +27 -0
  29. package/dist/cjs/pkce-generator.js +94 -0
  30. package/dist/cjs/storage.d.ts +6 -0
  31. package/dist/cjs/storage.js +29 -0
  32. package/dist/esm/access-token-provider.d.ts +9 -0
  33. package/dist/esm/access-token-provider.js +15 -0
  34. package/dist/esm/browser-global.d.ts +7 -0
  35. package/dist/esm/browser-global.js +1 -0
  36. package/dist/esm/ensure-login.d.ts +6 -0
  37. package/dist/esm/ensure-login.js +115 -0
  38. package/dist/esm/index.d.ts +7 -0
  39. package/dist/esm/index.js +7 -0
  40. package/dist/esm/mauth-response.d.ts +15 -0
  41. package/dist/esm/mauth-response.js +88 -0
  42. package/dist/esm/meshagent-auth.d.ts +40 -0
  43. package/dist/esm/meshagent-auth.js +173 -0
  44. package/dist/esm/oauth-session-manager.d.ts +22 -0
  45. package/dist/esm/oauth-session-manager.js +112 -0
  46. package/dist/esm/pkce-generator.d.ts +27 -0
  47. package/dist/esm/pkce-generator.js +89 -0
  48. package/dist/esm/storage.d.ts +6 -0
  49. package/dist/esm/storage.js +26 -0
  50. package/package.json +50 -0
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuthSessionManager = void 0;
4
+ const meshagent_auth_1 = require("./meshagent-auth");
5
+ function toUrl(value) {
6
+ return value instanceof URL ? value : new URL(value);
7
+ }
8
+ class OAuthSessionManager {
9
+ constructor({ serverUrl, clientId, clientSecret, refreshSkew = 2 * 60 * 1000, minValidFor = 8 * 60 * 60 * 1000, auth = meshagent_auth_1.meshagentAuth, }) {
10
+ Object.defineProperty(this, "serverUrl", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: void 0
15
+ });
16
+ Object.defineProperty(this, "clientId", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: void 0
21
+ });
22
+ Object.defineProperty(this, "clientSecret", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: void 0
27
+ });
28
+ Object.defineProperty(this, "refreshSkew", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: void 0
33
+ });
34
+ Object.defineProperty(this, "minValidFor", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: void 0
39
+ });
40
+ Object.defineProperty(this, "auth", {
41
+ enumerable: true,
42
+ configurable: true,
43
+ writable: true,
44
+ value: void 0
45
+ });
46
+ this.serverUrl = toUrl(serverUrl);
47
+ this.clientId = clientId;
48
+ this.clientSecret = clientSecret;
49
+ this.refreshSkew = refreshSkew;
50
+ this.minValidFor = minValidFor;
51
+ this.auth = auth;
52
+ }
53
+ get isLoggedIn() {
54
+ return this.auth.isLoggedIn();
55
+ }
56
+ async getValidAccessTokenOrThrow() {
57
+ const token = this.auth.getAccessToken();
58
+ if (!token) {
59
+ throw new Error("Not logged in");
60
+ }
61
+ if (this.isAccessTokenValidEnough()) {
62
+ return token;
63
+ }
64
+ await this.refreshOrThrow();
65
+ const refreshed = this.auth.getAccessToken();
66
+ if (!refreshed) {
67
+ throw new Error("Refresh succeeded but access token is missing");
68
+ }
69
+ return refreshed;
70
+ }
71
+ isAccessTokenValidEnough() {
72
+ const expiration = this.auth.expiration;
73
+ if (!expiration) {
74
+ return false;
75
+ }
76
+ const threshold = Date.now() + this.minValidFor + this.refreshSkew;
77
+ return expiration.getTime() > threshold;
78
+ }
79
+ async refreshOrThrow() {
80
+ const refreshToken = this.auth.getRefreshToken();
81
+ if (!refreshToken) {
82
+ throw new Error("No refresh token available");
83
+ }
84
+ const response = await fetch(new URL("/oauth/token", this.serverUrl), {
85
+ method: "POST",
86
+ headers: {
87
+ "Content-Type": "application/json",
88
+ "Accept": "application/json",
89
+ },
90
+ body: JSON.stringify({
91
+ grant_type: "refresh_token",
92
+ refresh_token: refreshToken,
93
+ client_id: this.clientId,
94
+ ...(this.clientSecret ? { client_secret: this.clientSecret } : {}),
95
+ }),
96
+ });
97
+ if (!response.ok) {
98
+ throw new Error(`Failed to refresh token: ${response.status} ${await response.text()}`);
99
+ }
100
+ const data = await response.json();
101
+ const accessToken = data["access_token"];
102
+ const nextRefreshToken = data["refresh_token"];
103
+ const expiresIn = data["expires_in"];
104
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
105
+ throw new Error("Refresh response missing access_token");
106
+ }
107
+ this.auth.setSession({
108
+ accessToken,
109
+ refreshToken: typeof nextRefreshToken === "string" && nextRefreshToken.length > 0
110
+ ? nextRefreshToken
111
+ : refreshToken,
112
+ expiresIn: typeof expiresIn === "number" ? expiresIn : null,
113
+ });
114
+ }
115
+ }
116
+ exports.OAuthSessionManager = OAuthSessionManager;
@@ -0,0 +1,27 @@
1
+ import type { StorageLike } from "./storage";
2
+ export interface PkceCache {
3
+ saveVerifier(verifier: string): Promise<void>;
4
+ readVerifier(): Promise<string | null>;
5
+ clearVerifier(): Promise<void>;
6
+ }
7
+ export declare class LocalStoragePkceCache implements PkceCache {
8
+ private readonly storage;
9
+ constructor(storage?: StorageLike);
10
+ saveVerifier(verifier: string): Promise<void>;
11
+ readVerifier(): Promise<string | null>;
12
+ clearVerifier(): Promise<void>;
13
+ }
14
+ export interface PkcePair {
15
+ codeVerifier: string;
16
+ codeChallenge: string;
17
+ }
18
+ export declare class PkceGenerator {
19
+ static readonly minVerifierLength = 43;
20
+ static readonly maxVerifierLength = 128;
21
+ private readonly cryptoApi;
22
+ constructor(cryptoApi?: Crypto);
23
+ generate(verifierLength?: number): Promise<PkcePair>;
24
+ private validateLength;
25
+ private generateCodeVerifier;
26
+ private createCodeChallenge;
27
+ }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PkceGenerator = exports.LocalStoragePkceCache = void 0;
4
+ const storage_1 = require("./storage");
5
+ const PKCE_VERIFIER_KEY = "cv";
6
+ class LocalStoragePkceCache {
7
+ constructor(storage = (0, storage_1.getDefaultStorage)()) {
8
+ Object.defineProperty(this, "storage", {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value: void 0
13
+ });
14
+ this.storage = storage;
15
+ }
16
+ async saveVerifier(verifier) {
17
+ this.storage.setItem(PKCE_VERIFIER_KEY, verifier);
18
+ }
19
+ async readVerifier() {
20
+ return this.storage.getItem(PKCE_VERIFIER_KEY);
21
+ }
22
+ async clearVerifier() {
23
+ this.storage.removeItem(PKCE_VERIFIER_KEY);
24
+ }
25
+ }
26
+ exports.LocalStoragePkceCache = LocalStoragePkceCache;
27
+ function assertCryptoAvailable() {
28
+ if (!globalThis.crypto || typeof globalThis.crypto.getRandomValues !== "function" || !globalThis.crypto.subtle) {
29
+ throw new Error("Web Crypto is required for PKCE generation");
30
+ }
31
+ return globalThis.crypto;
32
+ }
33
+ function encodeBase64Url(bytes) {
34
+ let binary = "";
35
+ for (const byte of bytes) {
36
+ binary += String.fromCharCode(byte);
37
+ }
38
+ const base64 = globalThis.btoa(binary);
39
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
40
+ }
41
+ class PkceGenerator {
42
+ constructor(cryptoApi = assertCryptoAvailable()) {
43
+ Object.defineProperty(this, "cryptoApi", {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value: void 0
48
+ });
49
+ this.cryptoApi = cryptoApi;
50
+ }
51
+ async generate(verifierLength = 64) {
52
+ this.validateLength(verifierLength);
53
+ const verifier = this.generateCodeVerifier(verifierLength);
54
+ const challenge = await this.createCodeChallenge(verifier);
55
+ return {
56
+ codeVerifier: verifier,
57
+ codeChallenge: challenge,
58
+ };
59
+ }
60
+ validateLength(length) {
61
+ if (length < PkceGenerator.minVerifierLength || length > PkceGenerator.maxVerifierLength) {
62
+ throw new Error(`verifierLength must be between ${PkceGenerator.minVerifierLength} and ${PkceGenerator.maxVerifierLength}`);
63
+ }
64
+ }
65
+ generateCodeVerifier(targetLength) {
66
+ let bytesNeeded = Math.ceil((targetLength * 3) / 4);
67
+ while (true) {
68
+ const bytes = new Uint8Array(bytesNeeded);
69
+ this.cryptoApi.getRandomValues(bytes);
70
+ const candidate = encodeBase64Url(bytes);
71
+ if (candidate.length >= targetLength) {
72
+ return candidate.slice(0, targetLength);
73
+ }
74
+ bytesNeeded = Math.ceil(bytesNeeded * 1.25);
75
+ }
76
+ }
77
+ async createCodeChallenge(codeVerifier) {
78
+ const digest = await this.cryptoApi.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
79
+ return encodeBase64Url(new Uint8Array(digest));
80
+ }
81
+ }
82
+ exports.PkceGenerator = PkceGenerator;
83
+ Object.defineProperty(PkceGenerator, "minVerifierLength", {
84
+ enumerable: true,
85
+ configurable: true,
86
+ writable: true,
87
+ value: 43
88
+ });
89
+ Object.defineProperty(PkceGenerator, "maxVerifierLength", {
90
+ enumerable: true,
91
+ configurable: true,
92
+ writable: true,
93
+ value: 128
94
+ });
@@ -0,0 +1,6 @@
1
+ export interface StorageLike {
2
+ getItem(key: string): string | null;
3
+ setItem(key: string, value: string): void;
4
+ removeItem(key: string): void;
5
+ }
6
+ export declare function getDefaultStorage(): StorageLike;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDefaultStorage = getDefaultStorage;
4
+ class MemoryStorage {
5
+ constructor() {
6
+ Object.defineProperty(this, "values", {
7
+ enumerable: true,
8
+ configurable: true,
9
+ writable: true,
10
+ value: new Map()
11
+ });
12
+ }
13
+ getItem(key) {
14
+ return this.values.get(key) ?? null;
15
+ }
16
+ setItem(key, value) {
17
+ this.values.set(key, value);
18
+ }
19
+ removeItem(key) {
20
+ this.values.delete(key);
21
+ }
22
+ }
23
+ const fallbackStorage = new MemoryStorage();
24
+ function getDefaultStorage() {
25
+ if (typeof window !== "undefined" && window.localStorage) {
26
+ return window.localStorage;
27
+ }
28
+ return fallbackStorage;
29
+ }
@@ -0,0 +1,9 @@
1
+ import type { OAuthSessionManagerOptions } from "./oauth-session-manager";
2
+ export interface AccessTokenProvider {
3
+ getToken(): Promise<string>;
4
+ }
5
+ export declare class RefreshAccessTokenProvider implements AccessTokenProvider {
6
+ private readonly session;
7
+ constructor(options: OAuthSessionManagerOptions);
8
+ getToken(): Promise<string>;
9
+ }
@@ -0,0 +1,15 @@
1
+ import { OAuthSessionManager } from "./oauth-session-manager";
2
+ export class RefreshAccessTokenProvider {
3
+ constructor(options) {
4
+ Object.defineProperty(this, "session", {
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true,
8
+ value: void 0
9
+ });
10
+ this.session = new OAuthSessionManager(options);
11
+ }
12
+ getToken() {
13
+ return this.session.getValidAccessTokenOrThrow();
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ import type * as MeshagentAuthModule from "./index";
2
+ declare global {
3
+ interface Window {
4
+ maAuth: typeof MeshagentAuthModule;
5
+ }
6
+ }
7
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ export declare function ensureLogin({ oauthClientId, serverUrl, callbackUrl, provider, }: {
2
+ oauthClientId: string;
3
+ serverUrl?: string;
4
+ callbackUrl?: string;
5
+ provider?: string;
6
+ }): Promise<void>;
@@ -0,0 +1,115 @@
1
+ import { LocalStoragePkceCache, PkceGenerator } from "./pkce-generator";
2
+ import { exchangeToken, getAuthorizationCode } from "./mauth-response";
3
+ import { MeshagentAuth } from "./meshagent-auth";
4
+ import { getDefaultStorage } from "./storage";
5
+ const DEFAULT_SERVER_URL = "https://api.meshagent.com";
6
+ const CALLBACK_URL_KEY = "ma:callback_url";
7
+ const CALLBACK_SOURCE_QUERY_PARAM = "ma_origin";
8
+ const CALLBACK_SOURCE_QUERY_VALUE = "meshagent_auth";
9
+ function getCurrentUrl() {
10
+ if (typeof window === "undefined") {
11
+ throw new Error("ensureLogin requires a browser environment");
12
+ }
13
+ return new URL(window.location.href);
14
+ }
15
+ function clearMeshagentAuthQueryParams(url) {
16
+ url.searchParams.delete("code");
17
+ url.searchParams.delete("state");
18
+ url.searchParams.delete("error");
19
+ url.searchParams.delete("error_description");
20
+ url.searchParams.delete("error_uri");
21
+ url.searchParams.delete(CALLBACK_SOURCE_QUERY_PARAM);
22
+ }
23
+ function hasMeshagentCallbackMarker(url) {
24
+ return url.searchParams.get(CALLBACK_SOURCE_QUERY_PARAM) === CALLBACK_SOURCE_QUERY_VALUE;
25
+ }
26
+ function isMeshagentCallbackUrl(callbackUrl) {
27
+ const currentUrl = getCurrentUrl();
28
+ if (!currentUrl.searchParams.has("code")) {
29
+ return false;
30
+ }
31
+ if (!callbackUrl) {
32
+ return hasMeshagentCallbackMarker(currentUrl);
33
+ }
34
+ const expectedCallbackUrl = new URL(callbackUrl, currentUrl);
35
+ clearMeshagentAuthQueryParams(currentUrl);
36
+ clearMeshagentAuthQueryParams(expectedCallbackUrl);
37
+ return currentUrl.toString() === expectedCallbackUrl.toString();
38
+ }
39
+ function getRedirectCallbackUrl(callbackUrl) {
40
+ if (callbackUrl) {
41
+ return callbackUrl;
42
+ }
43
+ const currentUrl = getCurrentUrl();
44
+ clearMeshagentAuthQueryParams(currentUrl);
45
+ currentUrl.searchParams.set(CALLBACK_SOURCE_QUERY_PARAM, CALLBACK_SOURCE_QUERY_VALUE);
46
+ return currentUrl.toString();
47
+ }
48
+ function getExchangeCallbackUrl(callbackUrl, storage) {
49
+ if (callbackUrl) {
50
+ return callbackUrl;
51
+ }
52
+ const storedCallbackUrl = storage.getItem(CALLBACK_URL_KEY);
53
+ if (storedCallbackUrl) {
54
+ return storedCallbackUrl;
55
+ }
56
+ return getRedirectCallbackUrl(undefined);
57
+ }
58
+ function saveCallbackUrl(storage, callbackUrl) {
59
+ storage.setItem(CALLBACK_URL_KEY, callbackUrl);
60
+ }
61
+ function clearCallbackUrl(storage) {
62
+ storage.removeItem(CALLBACK_URL_KEY);
63
+ }
64
+ function replaceCurrentUrlWithoutAuthParams() {
65
+ if (typeof window === "undefined" || typeof window.history.replaceState !== "function") {
66
+ return;
67
+ }
68
+ const currentUrl = getCurrentUrl();
69
+ clearMeshagentAuthQueryParams(currentUrl);
70
+ window.history.replaceState(window.history.state, "", currentUrl.toString());
71
+ }
72
+ export async function ensureLogin({ oauthClientId, serverUrl, callbackUrl, provider, }) {
73
+ const serverBaseUrl = serverUrl ?? DEFAULT_SERVER_URL;
74
+ const storage = getDefaultStorage();
75
+ const cache = new LocalStoragePkceCache(storage);
76
+ const authorizationCode = isMeshagentCallbackUrl(callbackUrl)
77
+ ? getAuthorizationCode()
78
+ : null;
79
+ if (authorizationCode) {
80
+ const exchangeCallbackUrl = getExchangeCallbackUrl(callbackUrl, storage);
81
+ await exchangeToken({
82
+ oauthClientId,
83
+ serverUrl: serverBaseUrl,
84
+ callbackUrl: exchangeCallbackUrl,
85
+ authorizationCode,
86
+ storage,
87
+ auth: MeshagentAuth.current,
88
+ });
89
+ await cache.clearVerifier();
90
+ clearCallbackUrl(storage);
91
+ replaceCurrentUrlWithoutAuthParams();
92
+ return;
93
+ }
94
+ if (MeshagentAuth.current.isLoggedIn()) {
95
+ return;
96
+ }
97
+ const redirectCallbackUrl = getRedirectCallbackUrl(callbackUrl);
98
+ const generator = new PkceGenerator();
99
+ const pkce = await generator.generate();
100
+ await cache.saveVerifier(pkce.codeVerifier);
101
+ saveCallbackUrl(storage, redirectCallbackUrl);
102
+ const query = new URLSearchParams({
103
+ scope: "email",
104
+ client_id: oauthClientId,
105
+ code_challenge: pkce.codeChallenge,
106
+ response_type: "code",
107
+ redirect_uri: redirectCallbackUrl,
108
+ prompt: "select_account",
109
+ });
110
+ if (provider) {
111
+ query.set("provider", provider);
112
+ }
113
+ const authorizeUrl = new URL(`/oauth/authorize?${query.toString()}`, serverBaseUrl);
114
+ window.location.assign(authorizeUrl.toString());
115
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./access-token-provider";
2
+ export * from "./ensure-login";
3
+ export * from "./mauth-response";
4
+ export * from "./meshagent-auth";
5
+ export * from "./oauth-session-manager";
6
+ export * from "./pkce-generator";
7
+ export * from "./storage";
@@ -0,0 +1,7 @@
1
+ export * from "./access-token-provider";
2
+ export * from "./ensure-login";
3
+ export * from "./mauth-response";
4
+ export * from "./meshagent-auth";
5
+ export * from "./oauth-session-manager";
6
+ export * from "./pkce-generator";
7
+ export * from "./storage";
@@ -0,0 +1,15 @@
1
+ import { Meshagent } from "@meshagent/meshagent";
2
+ import { MeshagentAuth } from "./meshagent-auth";
3
+ import type { StorageLike } from "./storage";
4
+ export declare function getAuthorizationCode(): string | null;
5
+ export declare function normalizedExpiresIn(expiresIn: unknown): number | null;
6
+ export declare function getMeshagentClient(auth: MeshagentAuth, serverUrl: URL | string): Meshagent;
7
+ export interface MAuthResponseProps {
8
+ serverUrl: string | URL;
9
+ callbackUrl: string | URL;
10
+ oauthClientId: string;
11
+ authorizationCode?: string;
12
+ storage?: StorageLike;
13
+ auth?: MeshagentAuth;
14
+ }
15
+ export declare function exchangeToken(props: MAuthResponseProps, signal?: AbortSignal): Promise<void>;
@@ -0,0 +1,88 @@
1
+ import { Meshagent } from "@meshagent/meshagent";
2
+ import { MeshagentAuth } from "./meshagent-auth";
3
+ import { LocalStoragePkceCache } from "./pkce-generator";
4
+ import { getDefaultStorage } from "./storage";
5
+ function toUrl(value) {
6
+ return value instanceof URL ? value : new URL(value);
7
+ }
8
+ export function getAuthorizationCode() {
9
+ if (typeof window === "undefined") {
10
+ return null;
11
+ }
12
+ const url = new URL(window.location.href);
13
+ return url.searchParams.get("code");
14
+ }
15
+ export function normalizedExpiresIn(expiresIn) {
16
+ if (typeof expiresIn === "number" && Number.isFinite(expiresIn)) {
17
+ return expiresIn;
18
+ }
19
+ if (typeof expiresIn === "string") {
20
+ if (expiresIn.trim() === "") {
21
+ return null;
22
+ }
23
+ const val = Number(expiresIn);
24
+ if (Number.isFinite(val)) {
25
+ return val;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ export function getMeshagentClient(auth, serverUrl) {
31
+ const token = auth.getAccessToken();
32
+ const baseUrl = serverUrl.toString();
33
+ if (!token) {
34
+ throw Error("No access token - you are not logged in");
35
+ }
36
+ return new Meshagent({ baseUrl, token });
37
+ }
38
+ export async function exchangeToken(props, signal) {
39
+ const serverUrl = toUrl(props.serverUrl);
40
+ const callbackUrl = toUrl(props.callbackUrl);
41
+ const authorizationCode = props.authorizationCode ?? getAuthorizationCode();
42
+ const storage = props.storage ?? getDefaultStorage();
43
+ const auth = props.auth ?? MeshagentAuth.current;
44
+ const cache = new LocalStoragePkceCache(storage);
45
+ if (!authorizationCode) {
46
+ throw new Error("Missing authorization code");
47
+ }
48
+ const codeVerifier = await cache.readVerifier();
49
+ if (!codeVerifier) {
50
+ throw new Error("Missing PKCE code verifier");
51
+ }
52
+ const response = await fetch(new URL("/oauth/token", serverUrl), {
53
+ method: "POST",
54
+ signal,
55
+ headers: {
56
+ "content-type": "application/json",
57
+ "accept": "application/json",
58
+ },
59
+ body: JSON.stringify({
60
+ "client_id": props.oauthClientId,
61
+ "code_verifier": codeVerifier,
62
+ "code": authorizationCode,
63
+ "grant_type": "authorization_code",
64
+ "redirect_uri": callbackUrl.toString(),
65
+ }),
66
+ });
67
+ if (!response.ok) {
68
+ throw new Error(`Token exchange failed: ${response.status} ${await response.text()}`);
69
+ }
70
+ const data = await response.json();
71
+ const accessToken = data["access_token"];
72
+ const refreshToken = data["refresh_token"];
73
+ const expiresIn = data["expires_in"];
74
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
75
+ throw new Error("Token exchange response missing access token");
76
+ }
77
+ auth.setAccessToken(accessToken);
78
+ if (typeof refreshToken !== "string" || refreshToken.length === 0) {
79
+ throw new Error("Token exchange response missing refresh token");
80
+ }
81
+ auth.setRefreshToken(refreshToken);
82
+ const client = getMeshagentClient(auth, serverUrl);
83
+ const me = await client.getUserProfile("me");
84
+ if (me) {
85
+ auth.setUser(me);
86
+ }
87
+ auth.setExpiresIn(normalizedExpiresIn(expiresIn));
88
+ }
@@ -0,0 +1,40 @@
1
+ import type { StorageLike } from "./storage";
2
+ export type MeshagentUser = Record<string, unknown>;
3
+ export interface MeshagentAuthSnapshot {
4
+ accessToken: string | null;
5
+ refreshToken: string | null;
6
+ expiration: Date | null;
7
+ user: MeshagentUser | null;
8
+ }
9
+ export interface MeshagentAuthSession {
10
+ accessToken: string | null;
11
+ refreshToken: string | null;
12
+ expiresIn: number | null | undefined;
13
+ }
14
+ type MeshagentAuthListener = () => void;
15
+ export declare class MeshagentAuth {
16
+ static current: MeshagentAuth;
17
+ private readonly storage;
18
+ private readonly listeners;
19
+ private snapshot;
20
+ constructor(storage?: StorageLike);
21
+ subscribe(listener: MeshagentAuthListener): () => void;
22
+ getSnapshot(): MeshagentAuthSnapshot;
23
+ setSession(session: MeshagentAuthSession): void;
24
+ setUser(user: MeshagentUser | null): void;
25
+ getUser(): MeshagentUser | null;
26
+ signOut(): void;
27
+ isLoggedIn(): boolean;
28
+ get expiration(): Date | null;
29
+ isExpired(): boolean;
30
+ getAccessToken(): string | null;
31
+ getRefreshToken(): string | null;
32
+ setAccessToken(token: string | null): void;
33
+ setRefreshToken(refreshToken: string | null): void;
34
+ setExpiresIn(expiresIn: number | null | undefined): void;
35
+ private writeValue;
36
+ private publish;
37
+ private isEqual;
38
+ }
39
+ export declare const meshagentAuth: MeshagentAuth;
40
+ export {};