@0xmonaco/react 0.8.4 → 0.8.7-develop.34bd452
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/dist/hooks/useAuth/types.d.ts +16 -11
- package/dist/hooks/useAuth/useAuth.js +12 -17
- package/dist/hooks/useTokenLifecycle/useTokenLifecycle.js +2 -2
- package/dist/hooks/useUserMovements/useUserMovements.js +1 -1
- package/dist/hooks/useUserOrders/useUserOrders.js +1 -1
- package/dist/utils/tokenStorage.d.ts +7 -3
- package/dist/utils/tokenStorage.js +15 -11
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthState, ChallengeResponse,
|
|
1
|
+
import type { AuthState, ChallengeResponse, SessionRefreshResponse } from "@0xmonaco/types";
|
|
2
2
|
import type { AuthenticationStatus } from "../../provider";
|
|
3
3
|
export interface UseAuthState {
|
|
4
4
|
/** Current authentication status */
|
|
@@ -6,25 +6,30 @@ export interface UseAuthState {
|
|
|
6
6
|
}
|
|
7
7
|
export interface UseAuthReturn extends UseAuthState {
|
|
8
8
|
/**
|
|
9
|
-
* Complete authentication flow.
|
|
9
|
+
* Complete authentication flow. Generates a session keypair, has the wallet
|
|
10
|
+
* authorize it, and returns the AuthState (including the session keypair).
|
|
10
11
|
* @param options - Optional configuration
|
|
11
12
|
* @param options.connectWebSocket - Auto-connect all authenticated WebSocket channels after login (currently Orders) (default: false)
|
|
12
13
|
*/
|
|
13
14
|
login: (options?: {
|
|
14
15
|
connectWebSocket?: boolean;
|
|
15
16
|
}) => Promise<AuthState>;
|
|
16
|
-
/** Logout user and clear state. Revokes the
|
|
17
|
+
/** Logout user and clear state. Revokes the session on the server. */
|
|
17
18
|
logout: () => Promise<void>;
|
|
18
|
-
/** Refresh
|
|
19
|
+
/** Refresh the current session, extending its expiry. */
|
|
19
20
|
refreshAuth: () => Promise<AuthState>;
|
|
20
21
|
/** Sign a challenge message with the connected wallet. */
|
|
21
22
|
signChallenge: (message: string) => Promise<string>;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Create an authentication challenge for the given address, binding the
|
|
25
|
+
* provided session public key. Advanced/manual flow — most callers should
|
|
26
|
+
* use `login`/`authenticate`, which manage the keypair for you.
|
|
27
|
+
*/
|
|
28
|
+
createChallenge: (address: string, sessionPublicKey: string) => Promise<ChallengeResponse>;
|
|
29
|
+
/** Complete authentication with signature. Returns the AuthState. */
|
|
25
30
|
authenticate: () => Promise<AuthState>;
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
/** Revoke the current session
|
|
29
|
-
|
|
31
|
+
/** Extend the current session's expiry (signed with the active session key). */
|
|
32
|
+
refreshSession: () => Promise<SessionRefreshResponse>;
|
|
33
|
+
/** Revoke the current session on the server. */
|
|
34
|
+
revokeSession: () => Promise<void>;
|
|
30
35
|
}
|
|
@@ -65,14 +65,16 @@ export const useAuth = () => {
|
|
|
65
65
|
throw new Error("Message is required and cannot be empty");
|
|
66
66
|
return await sdk.auth.signChallenge(message);
|
|
67
67
|
}, [sdk]);
|
|
68
|
-
const createChallenge = useCallback(async (address) => {
|
|
68
|
+
const createChallenge = useCallback(async (address, sessionPublicKey) => {
|
|
69
69
|
if (!sdk)
|
|
70
70
|
throw new Error("SDK not available");
|
|
71
71
|
if (!address?.trim())
|
|
72
72
|
throw new Error("Address is required and cannot be empty");
|
|
73
|
+
if (!sessionPublicKey?.trim())
|
|
74
|
+
throw new Error("Session public key is required and cannot be empty");
|
|
73
75
|
if (!clientId?.trim())
|
|
74
76
|
throw new Error("Client ID is required and cannot be empty");
|
|
75
|
-
return await sdk.auth.createChallenge(address, clientId);
|
|
77
|
+
return await sdk.auth.createChallenge(address, clientId, sessionPublicKey);
|
|
76
78
|
}, [sdk, clientId]);
|
|
77
79
|
const authenticate = useCallback(async () => {
|
|
78
80
|
if (!sdk)
|
|
@@ -84,26 +86,19 @@ export const useAuth = () => {
|
|
|
84
86
|
const result = await sdk.auth.authenticate(clientId);
|
|
85
87
|
const authenticated = sdk.isAuthenticated();
|
|
86
88
|
setAuthenticationStatus(authenticated ? AuthenticationStatus.AUTHENTICATED : AuthenticationStatus.UNAUTHENTICATED);
|
|
87
|
-
return
|
|
88
|
-
accessToken: result.accessToken,
|
|
89
|
-
refreshToken: result.refreshToken,
|
|
90
|
-
expiresAt: result.expiresAt,
|
|
91
|
-
user: result.user,
|
|
92
|
-
};
|
|
89
|
+
return result;
|
|
93
90
|
}
|
|
94
91
|
catch (error) {
|
|
95
92
|
setAuthenticationStatus(AuthenticationStatus.UNAUTHENTICATED);
|
|
96
93
|
throw error;
|
|
97
94
|
}
|
|
98
95
|
}, [sdk, clientId, setAuthenticationStatus]);
|
|
99
|
-
const
|
|
96
|
+
const refreshSession = useCallback(async () => {
|
|
100
97
|
if (!sdk)
|
|
101
98
|
throw new Error("SDK not available");
|
|
102
|
-
if (!token?.trim())
|
|
103
|
-
throw new Error("Refresh token is required and cannot be empty");
|
|
104
99
|
setAuthenticationStatus(AuthenticationStatus.AUTHENTICATING);
|
|
105
100
|
try {
|
|
106
|
-
const result = await sdk.auth.
|
|
101
|
+
const result = await sdk.auth.refreshSession();
|
|
107
102
|
const authenticated = sdk.isAuthenticated();
|
|
108
103
|
setAuthenticationStatus(authenticated ? AuthenticationStatus.AUTHENTICATED : AuthenticationStatus.UNAUTHENTICATED);
|
|
109
104
|
return result;
|
|
@@ -113,12 +108,12 @@ export const useAuth = () => {
|
|
|
113
108
|
throw error;
|
|
114
109
|
}
|
|
115
110
|
}, [sdk, setAuthenticationStatus]);
|
|
116
|
-
const
|
|
111
|
+
const revokeSession = useCallback(async () => {
|
|
117
112
|
if (!sdk)
|
|
118
113
|
throw new Error("SDK not available");
|
|
119
114
|
setAuthenticationStatus(AuthenticationStatus.AUTHENTICATING);
|
|
120
115
|
try {
|
|
121
|
-
await sdk.auth.
|
|
116
|
+
await sdk.auth.revokeSession();
|
|
122
117
|
const authenticated = sdk.isAuthenticated();
|
|
123
118
|
setAuthenticationStatus(authenticated ? AuthenticationStatus.AUTHENTICATED : AuthenticationStatus.UNAUTHENTICATED);
|
|
124
119
|
}
|
|
@@ -133,10 +128,10 @@ export const useAuth = () => {
|
|
|
133
128
|
// Primary auth actions
|
|
134
129
|
login,
|
|
135
130
|
logout,
|
|
136
|
-
//
|
|
131
|
+
// Session management
|
|
137
132
|
refreshAuth,
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
refreshSession,
|
|
134
|
+
revokeSession,
|
|
140
135
|
// Low-level auth functions
|
|
141
136
|
authenticate,
|
|
142
137
|
signChallenge,
|
|
@@ -53,8 +53,8 @@ export const useTokenLifecycle = (sdk, config = {}) => {
|
|
|
53
53
|
return refreshPromiseRef.current;
|
|
54
54
|
}
|
|
55
55
|
const currentAuthState = currentAuthStateRef.current;
|
|
56
|
-
if (!currentAuthState
|
|
57
|
-
console.warn("No
|
|
56
|
+
if (!currentAuthState) {
|
|
57
|
+
console.warn("No active session available for refresh");
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
const refreshPromise = (async () => {
|
|
@@ -75,7 +75,7 @@ export function useUserMovements(options = {}) {
|
|
|
75
75
|
sdk.profile
|
|
76
76
|
.getPaginatedUserMovements({ page_size: requestLimit, entry_type, transaction_type, asset_id })
|
|
77
77
|
.then(async (response) => {
|
|
78
|
-
// Combine latest_movements (from
|
|
78
|
+
// Combine latest_movements (from the live engine cache) with movements (from PostgreSQL)
|
|
79
79
|
// latest_movements contains real-time data that may not yet be in PostgreSQL
|
|
80
80
|
// Deduplicate by id, preferring latest_movements (newer data)
|
|
81
81
|
const latestMovements = (response.latest_movements || []).map((m) => ledgerMovementToEvent(m, userId));
|
|
@@ -25,7 +25,7 @@ export function useUserOrders(maxOrders = 50) {
|
|
|
25
25
|
page: 1,
|
|
26
26
|
page_size: maxOrders,
|
|
27
27
|
});
|
|
28
|
-
// Combine latest_orders (from
|
|
28
|
+
// Combine latest_orders (from the live engine cache) with orders (from PostgreSQL)
|
|
29
29
|
// latest_orders contains real-time data that may not yet be in PostgreSQL
|
|
30
30
|
// Deduplicate by id, preferring latest_orders (newer data)
|
|
31
31
|
const latestOrders = response.latest_orders || [];
|
|
@@ -2,14 +2,18 @@ import type { AuthState } from "@0xmonaco/types";
|
|
|
2
2
|
/**
|
|
3
3
|
* Save auth state to localStorage
|
|
4
4
|
*
|
|
5
|
-
* @security This function stores
|
|
6
|
-
* which is accessible to any JavaScript running on the
|
|
5
|
+
* @security This function stores the session keypair (including the private
|
|
6
|
+
* key) in localStorage, which is accessible to any JavaScript running on the
|
|
7
|
+
* page. This is the same XSS exposure as the previous JWT-based scheme — an
|
|
8
|
+
* attacker who can read localStorage can act as the user until the session
|
|
9
|
+
* expires either way.
|
|
7
10
|
*
|
|
8
11
|
* Security recommendations:
|
|
9
12
|
* - Implement Content Security Policy (CSP) headers to mitigate XSS risks
|
|
10
13
|
* - Sanitize all user input to prevent XSS vulnerabilities
|
|
11
14
|
* - Consider the tradeoffs: localStorage provides persistence across sessions but sacrifices XSS protection
|
|
12
|
-
* - For
|
|
15
|
+
* - For higher-security requirements, back the session key with a
|
|
16
|
+
* non-extractable WebCrypto key (out of scope for this default helper)
|
|
13
17
|
*
|
|
14
18
|
* @param authState - The authentication state to persist
|
|
15
19
|
*/
|
|
@@ -5,22 +5,26 @@ const STORAGE_KEY = "monaco_auth_state";
|
|
|
5
5
|
/**
|
|
6
6
|
* Save auth state to localStorage
|
|
7
7
|
*
|
|
8
|
-
* @security This function stores
|
|
9
|
-
* which is accessible to any JavaScript running on the
|
|
8
|
+
* @security This function stores the session keypair (including the private
|
|
9
|
+
* key) in localStorage, which is accessible to any JavaScript running on the
|
|
10
|
+
* page. This is the same XSS exposure as the previous JWT-based scheme — an
|
|
11
|
+
* attacker who can read localStorage can act as the user until the session
|
|
12
|
+
* expires either way.
|
|
10
13
|
*
|
|
11
14
|
* Security recommendations:
|
|
12
15
|
* - Implement Content Security Policy (CSP) headers to mitigate XSS risks
|
|
13
16
|
* - Sanitize all user input to prevent XSS vulnerabilities
|
|
14
17
|
* - Consider the tradeoffs: localStorage provides persistence across sessions but sacrifices XSS protection
|
|
15
|
-
* - For
|
|
18
|
+
* - For higher-security requirements, back the session key with a
|
|
19
|
+
* non-extractable WebCrypto key (out of scope for this default helper)
|
|
16
20
|
*
|
|
17
21
|
* @param authState - The authentication state to persist
|
|
18
22
|
*/
|
|
19
23
|
export function saveAuthState(authState) {
|
|
20
24
|
try {
|
|
21
25
|
const data = JSON.stringify({
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
sessionPublicKey: authState.sessionPublicKey,
|
|
27
|
+
sessionPrivateKey: authState.sessionPrivateKey,
|
|
24
28
|
expiresAt: authState.expiresAt,
|
|
25
29
|
user: authState.user,
|
|
26
30
|
});
|
|
@@ -41,10 +45,10 @@ export function loadAuthState() {
|
|
|
41
45
|
return null;
|
|
42
46
|
const parsed = JSON.parse(data);
|
|
43
47
|
// Validate required fields and types
|
|
44
|
-
if (typeof parsed.
|
|
45
|
-
parsed.
|
|
46
|
-
typeof parsed.
|
|
47
|
-
parsed.
|
|
48
|
+
if (typeof parsed.sessionPublicKey !== "string" ||
|
|
49
|
+
parsed.sessionPublicKey.length === 0 ||
|
|
50
|
+
typeof parsed.sessionPrivateKey !== "string" ||
|
|
51
|
+
parsed.sessionPrivateKey.length === 0 ||
|
|
48
52
|
typeof parsed.expiresAt !== "number" ||
|
|
49
53
|
!Number.isFinite(parsed.expiresAt) ||
|
|
50
54
|
parsed.expiresAt <= 0 ||
|
|
@@ -58,8 +62,8 @@ export function loadAuthState() {
|
|
|
58
62
|
return null;
|
|
59
63
|
}
|
|
60
64
|
return {
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
sessionPublicKey: parsed.sessionPublicKey,
|
|
66
|
+
sessionPrivateKey: parsed.sessionPrivateKey,
|
|
63
67
|
expiresAt: parsed.expiresAt,
|
|
64
68
|
user: parsed.user,
|
|
65
69
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/react",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7-develop.34bd452",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"lint": "biome lint ."
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@0xmonaco/core": "0.8.
|
|
24
|
-
"@0xmonaco/types": "0.8.
|
|
23
|
+
"@0xmonaco/core": "0.8.7-develop.34bd452",
|
|
24
|
+
"@0xmonaco/types": "0.8.7-develop.34bd452"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/react": "^19.1.12",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"viem": "^2.45.2",
|
|
33
|
+
"wagmi": "^2.0.0 || ^3.0.0"
|
|
34
34
|
},
|
|
35
35
|
"exports": {
|
|
36
36
|
".": {
|