@meshagent/meshagent-react-auth 0.36.3 → 0.37.0
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 +7 -0
- package/README.md +406 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +2 -4
- package/dist/cjs/mauth-response.d.ts +9 -1
- package/dist/cjs/mauth-response.js +75 -35
- package/dist/cjs/reference-key.d.ts +1 -0
- package/dist/cjs/reference-key.js +18 -0
- package/dist/cjs/use-auth.d.ts +35 -0
- package/dist/cjs/use-auth.js +482 -0
- package/dist/cjs/use-ensure-login.d.ts +13 -0
- package/dist/cjs/use-ensure-login.js +29 -0
- package/dist/cjs/use-login-scope.d.ts +4 -14
- package/dist/cjs/use-login-scope.js +5 -222
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/mauth-response.d.ts +9 -1
- package/dist/esm/mauth-response.js +75 -35
- package/dist/esm/reference-key.d.ts +1 -0
- package/dist/esm/reference-key.js +15 -0
- package/dist/esm/use-auth.d.ts +35 -0
- package/dist/esm/use-auth.js +478 -0
- package/dist/esm/use-ensure-login.d.ts +13 -0
- package/dist/esm/use-ensure-login.js +26 -0
- package/dist/esm/use-login-scope.d.ts +4 -14
- package/dist/esm/use-login-scope.js +5 -221
- package/package.json +7 -5
|
@@ -1,227 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildOAuthAuthorizeUrl = buildOAuthAuthorizeUrl;
|
|
4
3
|
exports.useLoginScope = useLoginScope;
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return value instanceof URL ? value : new URL(value);
|
|
11
|
-
}
|
|
12
|
-
class UserProfileFetchError extends Error {
|
|
13
|
-
constructor(status, message) {
|
|
14
|
-
super(message);
|
|
15
|
-
Object.defineProperty(this, "status", {
|
|
16
|
-
enumerable: true,
|
|
17
|
-
configurable: true,
|
|
18
|
-
writable: true,
|
|
19
|
-
value: void 0
|
|
20
|
-
});
|
|
21
|
-
this.name = "UserProfileFetchError";
|
|
22
|
-
this.status = status;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function isAuthProfileError(error) {
|
|
26
|
-
return error instanceof UserProfileFetchError
|
|
27
|
-
&& (error.status === 401 || error.status === 403);
|
|
28
|
-
}
|
|
29
|
-
async function fetchUserProfile(params) {
|
|
30
|
-
const response = await fetch(new URL("/accounts/profiles/me", toUrl(params.serverUrl)), {
|
|
31
|
-
signal: params.signal,
|
|
32
|
-
headers: {
|
|
33
|
-
"Authorization": `Bearer ${params.accessToken}`,
|
|
34
|
-
"Accept": "application/json",
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
if (!response.ok) {
|
|
38
|
-
throw new UserProfileFetchError(response.status, `Failed to fetch user profile: ${response.status} ${await response.text()}`);
|
|
39
|
-
}
|
|
40
|
-
return await response.json();
|
|
41
|
-
}
|
|
42
|
-
function serializeExtraQueryParams(extraQueryParams) {
|
|
43
|
-
return JSON.stringify(Object.entries(extraQueryParams ?? {}).sort(([left], [right]) => left.localeCompare(right)));
|
|
44
|
-
}
|
|
45
|
-
async function buildOAuthAuthorizeUrl(params) {
|
|
46
|
-
const serverUrl = toUrl(params.serverUrl);
|
|
47
|
-
const callbackUrl = toUrl(params.callbackUrl);
|
|
48
|
-
const generator = new meshagent_ts_auth_1.PkceGenerator();
|
|
49
|
-
const pair = await generator.generate();
|
|
50
|
-
const cache = new meshagent_ts_auth_1.LocalStoragePkceCache(params.storage ?? (0, meshagent_ts_auth_1.getDefaultStorage)());
|
|
51
|
-
await cache.saveVerifier(pair.codeVerifier);
|
|
52
|
-
const query = new URLSearchParams({
|
|
53
|
-
...(params.extraQueryParams ?? {}),
|
|
54
|
-
scope: params.scope ?? "profile",
|
|
55
|
-
client_id: params.oauthClientId,
|
|
56
|
-
code_challenge: pair.codeChallenge,
|
|
57
|
-
response_type: "code",
|
|
58
|
-
redirect_uri: callbackUrl.toString(),
|
|
59
|
-
prompt: "select_account",
|
|
60
|
-
});
|
|
61
|
-
if (params.provider) {
|
|
62
|
-
query.set("provider", params.provider);
|
|
63
|
-
}
|
|
64
|
-
return new URL(`/oauth/authorize?${query.toString()}`, serverUrl);
|
|
65
|
-
}
|
|
66
|
-
async function launchSignIn(params) {
|
|
67
|
-
const authorizeUrl = await buildOAuthAuthorizeUrl({
|
|
68
|
-
serverUrl: params.serverUrl,
|
|
69
|
-
callbackUrl: params.callbackUrl,
|
|
70
|
-
oauthClientId: params.oauthClientId,
|
|
71
|
-
scope: params.scope,
|
|
72
|
-
provider: params.provider,
|
|
73
|
-
extraQueryParams: params.extraQueryParams,
|
|
74
|
-
storage: params.storage,
|
|
75
|
-
});
|
|
76
|
-
if (typeof window === "undefined") {
|
|
77
|
-
throw new Error("LoginScope requires a browser environment");
|
|
78
|
-
}
|
|
79
|
-
window.location.assign(authorizeUrl.toString());
|
|
80
|
-
}
|
|
81
|
-
function useLoginScope(options) {
|
|
82
|
-
const storage = options.storage ?? (0, meshagent_ts_auth_1.getDefaultStorage)();
|
|
83
|
-
const auth = options.auth ?? meshagent_ts_auth_1.meshagentAuth;
|
|
84
|
-
const authSnapshot = (0, use_meshagent_auth_1.useMeshagentAuth)(auth);
|
|
85
|
-
const session = (0, react_1.useMemo)(() => new meshagent_ts_auth_1.OAuthSessionManager({
|
|
86
|
-
serverUrl: options.serverUrl,
|
|
87
|
-
clientId: options.oauthClientId,
|
|
88
|
-
auth,
|
|
89
|
-
}), [auth, options.oauthClientId, options.serverUrl]);
|
|
90
|
-
const autoSignIn = options.autoSignIn ?? true;
|
|
91
|
-
const extraQueryParamsKey = (0, react_1.useMemo)(() => serializeExtraQueryParams(options.extraQueryParams), [options.extraQueryParams]);
|
|
92
|
-
const [signInFailed, setSignInFailed] = (0, react_1.useState)(null);
|
|
93
|
-
const [isSigningIn, setIsSigningIn] = (0, react_1.useState)(false);
|
|
94
|
-
const [isCancelled, setIsCancelled] = (0, react_1.useState)(false);
|
|
95
|
-
const [refreshVersion, setRefreshVersion] = (0, react_1.useState)(0);
|
|
96
|
-
const signIn = async (provider) => {
|
|
97
|
-
setIsSigningIn(true);
|
|
98
|
-
setSignInFailed(null);
|
|
99
|
-
setIsCancelled(false);
|
|
100
|
-
try {
|
|
101
|
-
await launchSignIn({
|
|
102
|
-
serverUrl: options.serverUrl,
|
|
103
|
-
callbackUrl: options.callbackUrl,
|
|
104
|
-
oauthClientId: options.oauthClientId,
|
|
105
|
-
scope: options.scope,
|
|
106
|
-
provider,
|
|
107
|
-
extraQueryParams: options.extraQueryParams,
|
|
108
|
-
storage,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
setSignInFailed(error);
|
|
113
|
-
setIsSigningIn(false);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
const loginQuery = (0, react_query_1.useQuery)({
|
|
117
|
-
queryKey: [
|
|
118
|
-
"login-scope",
|
|
119
|
-
toUrl(options.serverUrl).toString(),
|
|
120
|
-
toUrl(options.callbackUrl).toString(),
|
|
121
|
-
options.oauthClientId,
|
|
122
|
-
options.scope ?? "profile",
|
|
123
|
-
extraQueryParamsKey,
|
|
124
|
-
autoSignIn,
|
|
125
|
-
authSnapshot.accessToken,
|
|
126
|
-
refreshVersion,
|
|
127
|
-
],
|
|
128
|
-
retry: false,
|
|
129
|
-
refetchOnReconnect: false,
|
|
130
|
-
refetchOnWindowFocus: false,
|
|
131
|
-
queryFn: async ({ signal }) => {
|
|
132
|
-
if (!authSnapshot.accessToken) {
|
|
133
|
-
setSignInFailed(null);
|
|
134
|
-
if (autoSignIn) {
|
|
135
|
-
setIsSigningIn(true);
|
|
136
|
-
try {
|
|
137
|
-
await launchSignIn({
|
|
138
|
-
serverUrl: options.serverUrl,
|
|
139
|
-
callbackUrl: options.callbackUrl,
|
|
140
|
-
oauthClientId: options.oauthClientId,
|
|
141
|
-
scope: options.scope,
|
|
142
|
-
provider: null,
|
|
143
|
-
extraQueryParams: options.extraQueryParams,
|
|
144
|
-
storage,
|
|
145
|
-
});
|
|
146
|
-
return {
|
|
147
|
-
failed: null,
|
|
148
|
-
isLoginLaunched: false,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
setIsSigningIn(false);
|
|
153
|
-
return {
|
|
154
|
-
failed: error,
|
|
155
|
-
isLoginLaunched: true,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
setIsSigningIn(false);
|
|
160
|
-
return {
|
|
161
|
-
failed: null,
|
|
162
|
-
isLoginLaunched: true,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
let accessToken;
|
|
166
|
-
try {
|
|
167
|
-
accessToken = await session.getValidAccessTokenOrThrow();
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
auth.signOut();
|
|
171
|
-
setIsSigningIn(false);
|
|
172
|
-
return {
|
|
173
|
-
failed: error,
|
|
174
|
-
isLoginLaunched: true,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
try {
|
|
178
|
-
const user = await fetchUserProfile({
|
|
179
|
-
serverUrl: options.serverUrl,
|
|
180
|
-
accessToken,
|
|
181
|
-
signal,
|
|
182
|
-
});
|
|
183
|
-
auth.setUser(user);
|
|
184
|
-
setSignInFailed(null);
|
|
185
|
-
setIsSigningIn(false);
|
|
186
|
-
return {
|
|
187
|
-
failed: null,
|
|
188
|
-
isLoginLaunched: false,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
setIsSigningIn(false);
|
|
193
|
-
if (isAuthProfileError(error)) {
|
|
194
|
-
auth.signOut();
|
|
195
|
-
return {
|
|
196
|
-
failed: error,
|
|
197
|
-
isLoginLaunched: true,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
failed: error,
|
|
202
|
-
isLoginLaunched: false,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
},
|
|
4
|
+
const use_auth_1 = require("./use-auth");
|
|
5
|
+
function useLoginScope(props) {
|
|
6
|
+
return (0, use_auth_1.useAuth)({
|
|
7
|
+
...props,
|
|
8
|
+
scope: props.scope ?? "email",
|
|
206
9
|
});
|
|
207
|
-
const refresh = () => {
|
|
208
|
-
setSignInFailed(null);
|
|
209
|
-
setIsCancelled(false);
|
|
210
|
-
setRefreshVersion((version) => version + 1);
|
|
211
|
-
};
|
|
212
|
-
const failed = signInFailed ?? (loginQuery.isFetching ? null : loginQuery.data?.failed ?? null);
|
|
213
|
-
const isLoginLaunched = signInFailed !== null
|
|
214
|
-
? true
|
|
215
|
-
: loginQuery.data?.isLoginLaunched ?? false;
|
|
216
|
-
const refreshing = loginQuery.isPending || loginQuery.isFetching;
|
|
217
|
-
return {
|
|
218
|
-
failed,
|
|
219
|
-
isCancelled,
|
|
220
|
-
isSigningIn,
|
|
221
|
-
refreshing,
|
|
222
|
-
user: authSnapshot.user,
|
|
223
|
-
signIn,
|
|
224
|
-
refresh,
|
|
225
|
-
isLoginLaunched,
|
|
226
|
-
};
|
|
227
10
|
}
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StorageLike, MeshagentAuth } from "@meshagent/meshagent-ts-auth";
|
|
2
2
|
declare enum UseMAuthResponseStatus {
|
|
3
3
|
Idle = "idle",
|
|
4
4
|
Loading = "loading",
|
|
5
5
|
Success = "success",
|
|
6
6
|
Error = "error"
|
|
7
7
|
}
|
|
8
|
+
export interface MAuthResponseProps {
|
|
9
|
+
serverUrl?: string | URL;
|
|
10
|
+
callbackUrl: string | URL;
|
|
11
|
+
oauthClientId: string;
|
|
12
|
+
authorizationCode?: string;
|
|
13
|
+
storage?: StorageLike;
|
|
14
|
+
auth?: MeshagentAuth;
|
|
15
|
+
}
|
|
8
16
|
export interface MAuthResponse {
|
|
9
17
|
status: UseMAuthResponseStatus;
|
|
10
18
|
error: string | null;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
import { getAuthorizationCode, exchangeToken } from "@meshagent/meshagent-ts-auth";
|
|
3
|
+
import { getReferenceKey } from "./reference-key";
|
|
3
4
|
function toUrl(value) {
|
|
4
5
|
return value instanceof URL ? value : new URL(value);
|
|
5
6
|
}
|
|
7
|
+
const inFlightTokenExchanges = new Map();
|
|
6
8
|
var UseMAuthResponseStatus;
|
|
7
9
|
(function (UseMAuthResponseStatus) {
|
|
8
10
|
UseMAuthResponseStatus["Idle"] = "idle";
|
|
@@ -10,50 +12,88 @@ var UseMAuthResponseStatus;
|
|
|
10
12
|
UseMAuthResponseStatus["Success"] = "success";
|
|
11
13
|
UseMAuthResponseStatus["Error"] = "error";
|
|
12
14
|
})(UseMAuthResponseStatus || (UseMAuthResponseStatus = {}));
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
function buildTokenExchangeKey(params) {
|
|
16
|
+
return JSON.stringify([
|
|
17
|
+
params.serverUrl,
|
|
18
|
+
params.callbackUrl,
|
|
19
|
+
params.oauthClientId,
|
|
20
|
+
params.code,
|
|
21
|
+
params.storageKey,
|
|
22
|
+
params.authKey,
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
function toErrorMessage(error) {
|
|
26
|
+
if (error instanceof Error) {
|
|
27
|
+
return error.message;
|
|
22
28
|
}
|
|
23
|
-
return
|
|
29
|
+
return String(error);
|
|
24
30
|
}
|
|
25
31
|
;
|
|
26
32
|
export function useMAuthResponse({ serverUrl, callbackUrl, oauthClientId, authorizationCode, storage, auth, }) {
|
|
33
|
+
const serverUrlKey = toUrl(serverUrl ?? "http://api.meshcentral.com").toString();
|
|
34
|
+
const callbackUrlKey = toUrl(callbackUrl).toString();
|
|
27
35
|
const code = authorizationCode ?? getAuthorizationCode() ?? undefined;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
const [status, setStatus] = useState(code === undefined ? UseMAuthResponseStatus.Idle : UseMAuthResponseStatus.Loading);
|
|
37
|
+
const [error, setError] = useState(null);
|
|
38
|
+
const tokenExchangeKey = code === undefined
|
|
39
|
+
? null
|
|
40
|
+
: buildTokenExchangeKey({
|
|
41
|
+
serverUrl: serverUrlKey,
|
|
42
|
+
callbackUrl: callbackUrlKey,
|
|
33
43
|
oauthClientId,
|
|
34
44
|
code,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
storageKey: getReferenceKey(storage, "default-storage"),
|
|
46
|
+
authKey: getReferenceKey(auth, "default-auth"),
|
|
47
|
+
});
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (code === undefined || tokenExchangeKey === null) {
|
|
50
|
+
setStatus(UseMAuthResponseStatus.Idle);
|
|
51
|
+
setError(null);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let cancelled = false;
|
|
55
|
+
setStatus(UseMAuthResponseStatus.Loading);
|
|
56
|
+
setError(null);
|
|
57
|
+
let tokenExchange = inFlightTokenExchanges.get(tokenExchangeKey);
|
|
58
|
+
if (!tokenExchange) {
|
|
59
|
+
tokenExchange = exchangeToken({
|
|
60
|
+
serverUrl: serverUrlKey,
|
|
46
61
|
callbackUrl,
|
|
47
62
|
oauthClientId,
|
|
48
63
|
authorizationCode: code,
|
|
49
64
|
storage,
|
|
50
65
|
auth,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
}).finally(() => {
|
|
67
|
+
inFlightTokenExchanges.delete(tokenExchangeKey);
|
|
68
|
+
});
|
|
69
|
+
inFlightTokenExchanges.set(tokenExchangeKey, tokenExchange);
|
|
70
|
+
}
|
|
71
|
+
tokenExchange
|
|
72
|
+
.then(() => {
|
|
73
|
+
if (cancelled) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
setStatus(UseMAuthResponseStatus.Success);
|
|
77
|
+
setError(null);
|
|
78
|
+
})
|
|
79
|
+
.catch((exchangeError) => {
|
|
80
|
+
if (cancelled) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
setStatus(UseMAuthResponseStatus.Error);
|
|
84
|
+
setError(toErrorMessage(exchangeError));
|
|
85
|
+
});
|
|
86
|
+
return () => {
|
|
87
|
+
cancelled = true;
|
|
88
|
+
};
|
|
89
|
+
}, [
|
|
90
|
+
auth,
|
|
91
|
+
callbackUrl,
|
|
92
|
+
code,
|
|
93
|
+
oauthClientId,
|
|
94
|
+
serverUrl,
|
|
95
|
+
storage,
|
|
96
|
+
tokenExchangeKey,
|
|
97
|
+
]);
|
|
98
|
+
return { status, error };
|
|
59
99
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getReferenceKey(value: object | null | undefined, fallback: string): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const objectIds = new WeakMap();
|
|
2
|
+
let nextObjectId = 1;
|
|
3
|
+
export function getReferenceKey(value, fallback) {
|
|
4
|
+
if (!value) {
|
|
5
|
+
return fallback;
|
|
6
|
+
}
|
|
7
|
+
const existing = objectIds.get(value);
|
|
8
|
+
if (existing !== undefined) {
|
|
9
|
+
return `${fallback}:${existing}`;
|
|
10
|
+
}
|
|
11
|
+
const id = nextObjectId;
|
|
12
|
+
nextObjectId += 1;
|
|
13
|
+
objectIds.set(value, id);
|
|
14
|
+
return `${fallback}:${id}`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { MeshagentAuth } from "@meshagent/meshagent-ts-auth";
|
|
2
|
+
import type { StorageLike, MeshagentUser } from "@meshagent/meshagent-ts-auth";
|
|
3
|
+
export declare function buildOAuthAuthorizeUrl(params: {
|
|
4
|
+
serverUrl: string | URL;
|
|
5
|
+
callbackUrl: string | URL;
|
|
6
|
+
oauthClientId: string;
|
|
7
|
+
scope?: string;
|
|
8
|
+
provider?: string | null;
|
|
9
|
+
extraQueryParams?: Record<string, string>;
|
|
10
|
+
storage?: StorageLike;
|
|
11
|
+
}): Promise<URL>;
|
|
12
|
+
export interface AuthRenderProps {
|
|
13
|
+
failed: unknown;
|
|
14
|
+
isCancelled: boolean;
|
|
15
|
+
isSigningIn: boolean;
|
|
16
|
+
refreshing: boolean;
|
|
17
|
+
user: MeshagentUser | null;
|
|
18
|
+
signIn: (provider?: string | null) => Promise<void>;
|
|
19
|
+
refresh: () => void;
|
|
20
|
+
}
|
|
21
|
+
export interface UseAuthProps {
|
|
22
|
+
serverUrl?: string | URL;
|
|
23
|
+
callbackUrl?: string | URL;
|
|
24
|
+
oauthClientId: string;
|
|
25
|
+
scope?: string;
|
|
26
|
+
provider?: string;
|
|
27
|
+
extraQueryParams?: Record<string, string>;
|
|
28
|
+
storage?: StorageLike;
|
|
29
|
+
auth?: MeshagentAuth;
|
|
30
|
+
autoSignIn?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface UseAuthResult extends AuthRenderProps {
|
|
33
|
+
isLoginLaunched: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function useAuth(props: UseAuthProps): UseAuthResult;
|