@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.
- package/CHANGELOG.md +2 -0
- package/LICENSE +201 -0
- package/README.md +271 -0
- package/dist/browser/access-token-provider.d.ts +9 -0
- package/dist/browser/browser-global.d.ts +7 -0
- package/dist/browser/ensure-login.d.ts +6 -0
- package/dist/browser/index.d.ts +7 -0
- package/dist/browser/ma-auth.global.js +26382 -0
- package/dist/browser/mauth-response.d.ts +15 -0
- package/dist/browser/meshagent-auth.d.ts +40 -0
- package/dist/browser/oauth-session-manager.d.ts +22 -0
- package/dist/browser/pkce-generator.d.ts +27 -0
- package/dist/browser/storage.d.ts +6 -0
- package/dist/cjs/access-token-provider.d.ts +9 -0
- package/dist/cjs/access-token-provider.js +19 -0
- package/dist/cjs/browser-global.d.ts +7 -0
- package/dist/cjs/browser-global.js +2 -0
- package/dist/cjs/ensure-login.d.ts +6 -0
- package/dist/cjs/ensure-login.js +118 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/mauth-response.d.ts +15 -0
- package/dist/cjs/mauth-response.js +94 -0
- package/dist/cjs/meshagent-auth.d.ts +40 -0
- package/dist/cjs/meshagent-auth.js +177 -0
- package/dist/cjs/oauth-session-manager.d.ts +22 -0
- package/dist/cjs/oauth-session-manager.js +116 -0
- package/dist/cjs/pkce-generator.d.ts +27 -0
- package/dist/cjs/pkce-generator.js +94 -0
- package/dist/cjs/storage.d.ts +6 -0
- package/dist/cjs/storage.js +29 -0
- package/dist/esm/access-token-provider.d.ts +9 -0
- package/dist/esm/access-token-provider.js +15 -0
- package/dist/esm/browser-global.d.ts +7 -0
- package/dist/esm/browser-global.js +1 -0
- package/dist/esm/ensure-login.d.ts +6 -0
- package/dist/esm/ensure-login.js +115 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/mauth-response.d.ts +15 -0
- package/dist/esm/mauth-response.js +88 -0
- package/dist/esm/meshagent-auth.d.ts +40 -0
- package/dist/esm/meshagent-auth.js +173 -0
- package/dist/esm/oauth-session-manager.d.ts +22 -0
- package/dist/esm/oauth-session-manager.js +112 -0
- package/dist/esm/pkce-generator.d.ts +27 -0
- package/dist/esm/pkce-generator.js +89 -0
- package/dist/esm/storage.d.ts +6 -0
- package/dist/esm/storage.js +26 -0
- package/package.json +50 -0
|
@@ -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,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 {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MeshagentAuth } from "./meshagent-auth";
|
|
2
|
+
export interface OAuthSessionManagerOptions {
|
|
3
|
+
serverUrl: string | URL;
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret?: string;
|
|
6
|
+
refreshSkew?: number;
|
|
7
|
+
minValidFor?: number;
|
|
8
|
+
auth?: MeshagentAuth;
|
|
9
|
+
}
|
|
10
|
+
export declare class OAuthSessionManager {
|
|
11
|
+
readonly serverUrl: URL;
|
|
12
|
+
readonly clientId: string;
|
|
13
|
+
readonly clientSecret?: string;
|
|
14
|
+
readonly refreshSkew: number;
|
|
15
|
+
readonly minValidFor: number;
|
|
16
|
+
private readonly auth;
|
|
17
|
+
constructor({ serverUrl, clientId, clientSecret, refreshSkew, minValidFor, auth, }: OAuthSessionManagerOptions);
|
|
18
|
+
get isLoggedIn(): boolean;
|
|
19
|
+
getValidAccessTokenOrThrow(): Promise<string>;
|
|
20
|
+
isAccessTokenValidEnough(): boolean;
|
|
21
|
+
refreshOrThrow(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -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,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,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RefreshAccessTokenProvider = void 0;
|
|
4
|
+
const oauth_session_manager_1 = require("./oauth-session-manager");
|
|
5
|
+
class RefreshAccessTokenProvider {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
Object.defineProperty(this, "session", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
writable: true,
|
|
11
|
+
value: void 0
|
|
12
|
+
});
|
|
13
|
+
this.session = new oauth_session_manager_1.OAuthSessionManager(options);
|
|
14
|
+
}
|
|
15
|
+
getToken() {
|
|
16
|
+
return this.session.getValidAccessTokenOrThrow();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.RefreshAccessTokenProvider = RefreshAccessTokenProvider;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureLogin = ensureLogin;
|
|
4
|
+
const pkce_generator_1 = require("./pkce-generator");
|
|
5
|
+
const mauth_response_1 = require("./mauth-response");
|
|
6
|
+
const meshagent_auth_1 = require("./meshagent-auth");
|
|
7
|
+
const storage_1 = require("./storage");
|
|
8
|
+
const DEFAULT_SERVER_URL = "https://api.meshagent.com";
|
|
9
|
+
const CALLBACK_URL_KEY = "ma:callback_url";
|
|
10
|
+
const CALLBACK_SOURCE_QUERY_PARAM = "ma_origin";
|
|
11
|
+
const CALLBACK_SOURCE_QUERY_VALUE = "meshagent_auth";
|
|
12
|
+
function getCurrentUrl() {
|
|
13
|
+
if (typeof window === "undefined") {
|
|
14
|
+
throw new Error("ensureLogin requires a browser environment");
|
|
15
|
+
}
|
|
16
|
+
return new URL(window.location.href);
|
|
17
|
+
}
|
|
18
|
+
function clearMeshagentAuthQueryParams(url) {
|
|
19
|
+
url.searchParams.delete("code");
|
|
20
|
+
url.searchParams.delete("state");
|
|
21
|
+
url.searchParams.delete("error");
|
|
22
|
+
url.searchParams.delete("error_description");
|
|
23
|
+
url.searchParams.delete("error_uri");
|
|
24
|
+
url.searchParams.delete(CALLBACK_SOURCE_QUERY_PARAM);
|
|
25
|
+
}
|
|
26
|
+
function hasMeshagentCallbackMarker(url) {
|
|
27
|
+
return url.searchParams.get(CALLBACK_SOURCE_QUERY_PARAM) === CALLBACK_SOURCE_QUERY_VALUE;
|
|
28
|
+
}
|
|
29
|
+
function isMeshagentCallbackUrl(callbackUrl) {
|
|
30
|
+
const currentUrl = getCurrentUrl();
|
|
31
|
+
if (!currentUrl.searchParams.has("code")) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (!callbackUrl) {
|
|
35
|
+
return hasMeshagentCallbackMarker(currentUrl);
|
|
36
|
+
}
|
|
37
|
+
const expectedCallbackUrl = new URL(callbackUrl, currentUrl);
|
|
38
|
+
clearMeshagentAuthQueryParams(currentUrl);
|
|
39
|
+
clearMeshagentAuthQueryParams(expectedCallbackUrl);
|
|
40
|
+
return currentUrl.toString() === expectedCallbackUrl.toString();
|
|
41
|
+
}
|
|
42
|
+
function getRedirectCallbackUrl(callbackUrl) {
|
|
43
|
+
if (callbackUrl) {
|
|
44
|
+
return callbackUrl;
|
|
45
|
+
}
|
|
46
|
+
const currentUrl = getCurrentUrl();
|
|
47
|
+
clearMeshagentAuthQueryParams(currentUrl);
|
|
48
|
+
currentUrl.searchParams.set(CALLBACK_SOURCE_QUERY_PARAM, CALLBACK_SOURCE_QUERY_VALUE);
|
|
49
|
+
return currentUrl.toString();
|
|
50
|
+
}
|
|
51
|
+
function getExchangeCallbackUrl(callbackUrl, storage) {
|
|
52
|
+
if (callbackUrl) {
|
|
53
|
+
return callbackUrl;
|
|
54
|
+
}
|
|
55
|
+
const storedCallbackUrl = storage.getItem(CALLBACK_URL_KEY);
|
|
56
|
+
if (storedCallbackUrl) {
|
|
57
|
+
return storedCallbackUrl;
|
|
58
|
+
}
|
|
59
|
+
return getRedirectCallbackUrl(undefined);
|
|
60
|
+
}
|
|
61
|
+
function saveCallbackUrl(storage, callbackUrl) {
|
|
62
|
+
storage.setItem(CALLBACK_URL_KEY, callbackUrl);
|
|
63
|
+
}
|
|
64
|
+
function clearCallbackUrl(storage) {
|
|
65
|
+
storage.removeItem(CALLBACK_URL_KEY);
|
|
66
|
+
}
|
|
67
|
+
function replaceCurrentUrlWithoutAuthParams() {
|
|
68
|
+
if (typeof window === "undefined" || typeof window.history.replaceState !== "function") {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const currentUrl = getCurrentUrl();
|
|
72
|
+
clearMeshagentAuthQueryParams(currentUrl);
|
|
73
|
+
window.history.replaceState(window.history.state, "", currentUrl.toString());
|
|
74
|
+
}
|
|
75
|
+
async function ensureLogin({ oauthClientId, serverUrl, callbackUrl, provider, }) {
|
|
76
|
+
const serverBaseUrl = serverUrl ?? DEFAULT_SERVER_URL;
|
|
77
|
+
const storage = (0, storage_1.getDefaultStorage)();
|
|
78
|
+
const cache = new pkce_generator_1.LocalStoragePkceCache(storage);
|
|
79
|
+
const authorizationCode = isMeshagentCallbackUrl(callbackUrl)
|
|
80
|
+
? (0, mauth_response_1.getAuthorizationCode)()
|
|
81
|
+
: null;
|
|
82
|
+
if (authorizationCode) {
|
|
83
|
+
const exchangeCallbackUrl = getExchangeCallbackUrl(callbackUrl, storage);
|
|
84
|
+
await (0, mauth_response_1.exchangeToken)({
|
|
85
|
+
oauthClientId,
|
|
86
|
+
serverUrl: serverBaseUrl,
|
|
87
|
+
callbackUrl: exchangeCallbackUrl,
|
|
88
|
+
authorizationCode,
|
|
89
|
+
storage,
|
|
90
|
+
auth: meshagent_auth_1.MeshagentAuth.current,
|
|
91
|
+
});
|
|
92
|
+
await cache.clearVerifier();
|
|
93
|
+
clearCallbackUrl(storage);
|
|
94
|
+
replaceCurrentUrlWithoutAuthParams();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (meshagent_auth_1.MeshagentAuth.current.isLoggedIn()) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const redirectCallbackUrl = getRedirectCallbackUrl(callbackUrl);
|
|
101
|
+
const generator = new pkce_generator_1.PkceGenerator();
|
|
102
|
+
const pkce = await generator.generate();
|
|
103
|
+
await cache.saveVerifier(pkce.codeVerifier);
|
|
104
|
+
saveCallbackUrl(storage, redirectCallbackUrl);
|
|
105
|
+
const query = new URLSearchParams({
|
|
106
|
+
scope: "email",
|
|
107
|
+
client_id: oauthClientId,
|
|
108
|
+
code_challenge: pkce.codeChallenge,
|
|
109
|
+
response_type: "code",
|
|
110
|
+
redirect_uri: redirectCallbackUrl,
|
|
111
|
+
prompt: "select_account",
|
|
112
|
+
});
|
|
113
|
+
if (provider) {
|
|
114
|
+
query.set("provider", provider);
|
|
115
|
+
}
|
|
116
|
+
const authorizeUrl = new URL(`/oauth/authorize?${query.toString()}`, serverBaseUrl);
|
|
117
|
+
window.location.assign(authorizeUrl.toString());
|
|
118
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./access-token-provider"), exports);
|
|
18
|
+
__exportStar(require("./ensure-login"), exports);
|
|
19
|
+
__exportStar(require("./mauth-response"), exports);
|
|
20
|
+
__exportStar(require("./meshagent-auth"), exports);
|
|
21
|
+
__exportStar(require("./oauth-session-manager"), exports);
|
|
22
|
+
__exportStar(require("./pkce-generator"), exports);
|
|
23
|
+
__exportStar(require("./storage"), exports);
|
|
@@ -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,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAuthorizationCode = getAuthorizationCode;
|
|
4
|
+
exports.normalizedExpiresIn = normalizedExpiresIn;
|
|
5
|
+
exports.getMeshagentClient = getMeshagentClient;
|
|
6
|
+
exports.exchangeToken = exchangeToken;
|
|
7
|
+
const meshagent_1 = require("@meshagent/meshagent");
|
|
8
|
+
const meshagent_auth_1 = require("./meshagent-auth");
|
|
9
|
+
const pkce_generator_1 = require("./pkce-generator");
|
|
10
|
+
const storage_1 = require("./storage");
|
|
11
|
+
function toUrl(value) {
|
|
12
|
+
return value instanceof URL ? value : new URL(value);
|
|
13
|
+
}
|
|
14
|
+
function getAuthorizationCode() {
|
|
15
|
+
if (typeof window === "undefined") {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const url = new URL(window.location.href);
|
|
19
|
+
return url.searchParams.get("code");
|
|
20
|
+
}
|
|
21
|
+
function normalizedExpiresIn(expiresIn) {
|
|
22
|
+
if (typeof expiresIn === "number" && Number.isFinite(expiresIn)) {
|
|
23
|
+
return expiresIn;
|
|
24
|
+
}
|
|
25
|
+
if (typeof expiresIn === "string") {
|
|
26
|
+
if (expiresIn.trim() === "") {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const val = Number(expiresIn);
|
|
30
|
+
if (Number.isFinite(val)) {
|
|
31
|
+
return val;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function getMeshagentClient(auth, serverUrl) {
|
|
37
|
+
const token = auth.getAccessToken();
|
|
38
|
+
const baseUrl = serverUrl.toString();
|
|
39
|
+
if (!token) {
|
|
40
|
+
throw Error("No access token - you are not logged in");
|
|
41
|
+
}
|
|
42
|
+
return new meshagent_1.Meshagent({ baseUrl, token });
|
|
43
|
+
}
|
|
44
|
+
async function exchangeToken(props, signal) {
|
|
45
|
+
const serverUrl = toUrl(props.serverUrl);
|
|
46
|
+
const callbackUrl = toUrl(props.callbackUrl);
|
|
47
|
+
const authorizationCode = props.authorizationCode ?? getAuthorizationCode();
|
|
48
|
+
const storage = props.storage ?? (0, storage_1.getDefaultStorage)();
|
|
49
|
+
const auth = props.auth ?? meshagent_auth_1.MeshagentAuth.current;
|
|
50
|
+
const cache = new pkce_generator_1.LocalStoragePkceCache(storage);
|
|
51
|
+
if (!authorizationCode) {
|
|
52
|
+
throw new Error("Missing authorization code");
|
|
53
|
+
}
|
|
54
|
+
const codeVerifier = await cache.readVerifier();
|
|
55
|
+
if (!codeVerifier) {
|
|
56
|
+
throw new Error("Missing PKCE code verifier");
|
|
57
|
+
}
|
|
58
|
+
const response = await fetch(new URL("/oauth/token", serverUrl), {
|
|
59
|
+
method: "POST",
|
|
60
|
+
signal,
|
|
61
|
+
headers: {
|
|
62
|
+
"content-type": "application/json",
|
|
63
|
+
"accept": "application/json",
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
"client_id": props.oauthClientId,
|
|
67
|
+
"code_verifier": codeVerifier,
|
|
68
|
+
"code": authorizationCode,
|
|
69
|
+
"grant_type": "authorization_code",
|
|
70
|
+
"redirect_uri": callbackUrl.toString(),
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Token exchange failed: ${response.status} ${await response.text()}`);
|
|
75
|
+
}
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
const accessToken = data["access_token"];
|
|
78
|
+
const refreshToken = data["refresh_token"];
|
|
79
|
+
const expiresIn = data["expires_in"];
|
|
80
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
81
|
+
throw new Error("Token exchange response missing access token");
|
|
82
|
+
}
|
|
83
|
+
auth.setAccessToken(accessToken);
|
|
84
|
+
if (typeof refreshToken !== "string" || refreshToken.length === 0) {
|
|
85
|
+
throw new Error("Token exchange response missing refresh token");
|
|
86
|
+
}
|
|
87
|
+
auth.setRefreshToken(refreshToken);
|
|
88
|
+
const client = getMeshagentClient(auth, serverUrl);
|
|
89
|
+
const me = await client.getUserProfile("me");
|
|
90
|
+
if (me) {
|
|
91
|
+
auth.setUser(me);
|
|
92
|
+
}
|
|
93
|
+
auth.setExpiresIn(normalizedExpiresIn(expiresIn));
|
|
94
|
+
}
|
|
@@ -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 {};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.meshagentAuth = exports.MeshagentAuth = void 0;
|
|
4
|
+
const storage_1 = require("./storage");
|
|
5
|
+
const ACCESS_TOKEN_KEY = "ma:access_token";
|
|
6
|
+
const REFRESH_TOKEN_KEY = "ma:refresh_token";
|
|
7
|
+
const EXPIRATION_KEY = "ma:expiration";
|
|
8
|
+
function parseExpiration(value) {
|
|
9
|
+
if (!value) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const parsed = new Date(value);
|
|
13
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
14
|
+
}
|
|
15
|
+
function buildExpiration(expiresIn) {
|
|
16
|
+
if (expiresIn == null || !Number.isFinite(expiresIn)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return new Date(Date.now() + expiresIn * 1000);
|
|
20
|
+
}
|
|
21
|
+
function sameDate(left, right) {
|
|
22
|
+
if (left === right) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (!left || !right) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return left.getTime() === right.getTime();
|
|
29
|
+
}
|
|
30
|
+
class MeshagentAuth {
|
|
31
|
+
constructor(storage = (0, storage_1.getDefaultStorage)()) {
|
|
32
|
+
Object.defineProperty(this, "storage", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "listeners", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: new Set()
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, "snapshot", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: void 0
|
|
49
|
+
});
|
|
50
|
+
this.storage = storage;
|
|
51
|
+
this.snapshot = {
|
|
52
|
+
accessToken: this.storage.getItem(ACCESS_TOKEN_KEY),
|
|
53
|
+
refreshToken: this.storage.getItem(REFRESH_TOKEN_KEY),
|
|
54
|
+
expiration: parseExpiration(this.storage.getItem(EXPIRATION_KEY)),
|
|
55
|
+
user: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
subscribe(listener) {
|
|
59
|
+
this.listeners.add(listener);
|
|
60
|
+
return () => {
|
|
61
|
+
this.listeners.delete(listener);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
getSnapshot() {
|
|
65
|
+
return this.snapshot;
|
|
66
|
+
}
|
|
67
|
+
setSession(session) {
|
|
68
|
+
this.writeValue(ACCESS_TOKEN_KEY, session.accessToken);
|
|
69
|
+
this.writeValue(REFRESH_TOKEN_KEY, session.refreshToken);
|
|
70
|
+
const expiration = buildExpiration(session.expiresIn);
|
|
71
|
+
if (expiration) {
|
|
72
|
+
this.storage.setItem(EXPIRATION_KEY, expiration.toISOString());
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
this.storage.removeItem(EXPIRATION_KEY);
|
|
76
|
+
}
|
|
77
|
+
this.publish({
|
|
78
|
+
...this.snapshot,
|
|
79
|
+
accessToken: session.accessToken,
|
|
80
|
+
refreshToken: session.refreshToken,
|
|
81
|
+
expiration,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
setUser(user) {
|
|
85
|
+
this.publish({
|
|
86
|
+
...this.snapshot,
|
|
87
|
+
user,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
getUser() {
|
|
91
|
+
return this.snapshot.user;
|
|
92
|
+
}
|
|
93
|
+
signOut() {
|
|
94
|
+
this.writeValue(ACCESS_TOKEN_KEY, null);
|
|
95
|
+
this.writeValue(REFRESH_TOKEN_KEY, null);
|
|
96
|
+
this.storage.removeItem(EXPIRATION_KEY);
|
|
97
|
+
this.publish({
|
|
98
|
+
accessToken: null,
|
|
99
|
+
refreshToken: null,
|
|
100
|
+
expiration: null,
|
|
101
|
+
user: null,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
isLoggedIn() {
|
|
105
|
+
return this.snapshot.accessToken !== null;
|
|
106
|
+
}
|
|
107
|
+
get expiration() {
|
|
108
|
+
return this.snapshot.expiration;
|
|
109
|
+
}
|
|
110
|
+
isExpired() {
|
|
111
|
+
const expiration = this.expiration;
|
|
112
|
+
return expiration ? expiration.getTime() <= Date.now() : false;
|
|
113
|
+
}
|
|
114
|
+
getAccessToken() {
|
|
115
|
+
return this.snapshot.accessToken;
|
|
116
|
+
}
|
|
117
|
+
getRefreshToken() {
|
|
118
|
+
return this.snapshot.refreshToken;
|
|
119
|
+
}
|
|
120
|
+
setAccessToken(token) {
|
|
121
|
+
this.writeValue(ACCESS_TOKEN_KEY, token);
|
|
122
|
+
this.publish({
|
|
123
|
+
...this.snapshot,
|
|
124
|
+
accessToken: token,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
setRefreshToken(refreshToken) {
|
|
128
|
+
this.writeValue(REFRESH_TOKEN_KEY, refreshToken);
|
|
129
|
+
this.publish({
|
|
130
|
+
...this.snapshot,
|
|
131
|
+
refreshToken,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
setExpiresIn(expiresIn) {
|
|
135
|
+
const expiration = buildExpiration(expiresIn);
|
|
136
|
+
if (expiration) {
|
|
137
|
+
this.storage.setItem(EXPIRATION_KEY, expiration.toISOString());
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.storage.removeItem(EXPIRATION_KEY);
|
|
141
|
+
}
|
|
142
|
+
this.publish({
|
|
143
|
+
...this.snapshot,
|
|
144
|
+
expiration,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
writeValue(key, value) {
|
|
148
|
+
if (!value) {
|
|
149
|
+
this.storage.removeItem(key);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.storage.setItem(key, value);
|
|
153
|
+
}
|
|
154
|
+
publish(nextSnapshot) {
|
|
155
|
+
if (this.isEqual(nextSnapshot)) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.snapshot = nextSnapshot;
|
|
159
|
+
for (const listener of this.listeners) {
|
|
160
|
+
listener();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
isEqual(nextSnapshot) {
|
|
164
|
+
return this.snapshot.accessToken === nextSnapshot.accessToken
|
|
165
|
+
&& this.snapshot.refreshToken === nextSnapshot.refreshToken
|
|
166
|
+
&& sameDate(this.snapshot.expiration, nextSnapshot.expiration)
|
|
167
|
+
&& this.snapshot.user === nextSnapshot.user;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.MeshagentAuth = MeshagentAuth;
|
|
171
|
+
Object.defineProperty(MeshagentAuth, "current", {
|
|
172
|
+
enumerable: true,
|
|
173
|
+
configurable: true,
|
|
174
|
+
writable: true,
|
|
175
|
+
value: new MeshagentAuth()
|
|
176
|
+
});
|
|
177
|
+
exports.meshagentAuth = MeshagentAuth.current;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MeshagentAuth } from "./meshagent-auth";
|
|
2
|
+
export interface OAuthSessionManagerOptions {
|
|
3
|
+
serverUrl: string | URL;
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret?: string;
|
|
6
|
+
refreshSkew?: number;
|
|
7
|
+
minValidFor?: number;
|
|
8
|
+
auth?: MeshagentAuth;
|
|
9
|
+
}
|
|
10
|
+
export declare class OAuthSessionManager {
|
|
11
|
+
readonly serverUrl: URL;
|
|
12
|
+
readonly clientId: string;
|
|
13
|
+
readonly clientSecret?: string;
|
|
14
|
+
readonly refreshSkew: number;
|
|
15
|
+
readonly minValidFor: number;
|
|
16
|
+
private readonly auth;
|
|
17
|
+
constructor({ serverUrl, clientId, clientSecret, refreshSkew, minValidFor, auth, }: OAuthSessionManagerOptions);
|
|
18
|
+
get isLoggedIn(): boolean;
|
|
19
|
+
getValidAccessTokenOrThrow(): Promise<string>;
|
|
20
|
+
isAccessTokenValidEnough(): boolean;
|
|
21
|
+
refreshOrThrow(): Promise<void>;
|
|
22
|
+
}
|