@lanonasis/oauth-client 1.2.8 → 2.0.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/README.md +19 -0
- package/dist/constants-BOZ6jJU4.d.cts +110 -0
- package/dist/constants-BOZ6jJU4.d.ts +110 -0
- package/dist/index.cjs +18 -2
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.mjs +18 -2
- package/dist/react/index.cjs +261 -0
- package/dist/react/index.d.cts +95 -0
- package/dist/react/index.d.ts +95 -0
- package/dist/react/index.mjs +238 -0
- package/dist/server/index.cjs +169 -0
- package/dist/server/index.d.cts +184 -0
- package/dist/server/index.d.ts +184 -0
- package/dist/server/index.mjs +146 -0
- package/package.json +19 -3
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { S as SSOConfig, U as UseSSOReturn, a as SupabaseSession, b as SSOSyncConfig, c as UseSSOSyncReturn, d as SSOUser } from '../constants-BOZ6jJU4.cjs';
|
|
2
|
+
export { C as COOKIE_NAMES, D as DEFAULT_AUTH_GATEWAY, f as DEFAULT_COOKIE_DOMAIN, g as DEFAULT_POLL_INTERVAL, h as DEFAULT_PROJECT_SCOPE, e as SSOState } from '../constants-BOZ6jJU4.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React hook for SSO authentication state
|
|
6
|
+
* @module @lanonasis/oauth-client/react
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* React hook for SSO authentication state
|
|
11
|
+
*
|
|
12
|
+
* Reads the lanonasis_user cookie to determine auth state.
|
|
13
|
+
* Works across all *.lanonasis.com subdomains.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* function Navbar() {
|
|
18
|
+
* const { isAuthenticated, user, logout } = useSSO();
|
|
19
|
+
*
|
|
20
|
+
* if (isAuthenticated && user) {
|
|
21
|
+
* return <span>Welcome, {user.email}</span>;
|
|
22
|
+
* }
|
|
23
|
+
* return <a href="https://auth.lanonasis.com/web/login">Sign In</a>;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function useSSO(config?: SSOConfig): UseSSOReturn;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* React hook to sync Supabase auth state with lanonasis cookies
|
|
31
|
+
* @module @lanonasis/oauth-client/react
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* React hook to sync Supabase auth state with lanonasis cookies
|
|
36
|
+
*
|
|
37
|
+
* Call this hook in components that use Supabase auth to ensure
|
|
38
|
+
* cross-subdomain SSO cookies are set when user logs in.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* function AuthProvider({ children }) {
|
|
43
|
+
* const { data: { session } } = useSupabaseSession();
|
|
44
|
+
*
|
|
45
|
+
* // Sync Supabase session with SSO cookies
|
|
46
|
+
* useSSOSync(session, {
|
|
47
|
+
* projectScope: 'my-app',
|
|
48
|
+
* onSyncComplete: (success) => {
|
|
49
|
+
* console.log('SSO sync:', success ? 'success' : 'failed');
|
|
50
|
+
* }
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* return <>{children}</>;
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function useSSOSync(supabaseSession: SupabaseSession | null, config?: SSOSyncConfig): UseSSOSyncReturn;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Browser-side cookie utilities for SSO authentication
|
|
61
|
+
* @module @lanonasis/oauth-client/react
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if we're in a browser environment
|
|
66
|
+
*/
|
|
67
|
+
declare function isBrowser(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Parse the lanonasis_user cookie (non-HttpOnly, readable by JS)
|
|
70
|
+
* This cookie contains: { id, email, role, name?, avatar_url? }
|
|
71
|
+
*
|
|
72
|
+
* @returns User data or null if cookie doesn't exist or is invalid
|
|
73
|
+
*/
|
|
74
|
+
declare function parseUserCookie(): SSOUser | null;
|
|
75
|
+
/**
|
|
76
|
+
* Check if the session cookie exists (cannot read value due to HttpOnly)
|
|
77
|
+
*
|
|
78
|
+
* @returns true if lanonasis_session cookie exists
|
|
79
|
+
*/
|
|
80
|
+
declare function hasSessionCookie(): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Check if user appears to be authenticated based on cookies
|
|
83
|
+
*
|
|
84
|
+
* @returns true if both session and user cookies exist
|
|
85
|
+
*/
|
|
86
|
+
declare function hasAuthCookies(): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Clear auth cookies (client-side only clears non-HttpOnly cookies)
|
|
89
|
+
* Full logout should redirect to auth gateway
|
|
90
|
+
*
|
|
91
|
+
* @param domain - Cookie domain (default: .lanonasis.com)
|
|
92
|
+
*/
|
|
93
|
+
declare function clearUserCookie(domain?: string): void;
|
|
94
|
+
|
|
95
|
+
export { SSOConfig, SSOSyncConfig, SSOUser, SupabaseSession, UseSSOReturn, UseSSOSyncReturn, clearUserCookie, hasAuthCookies, hasSessionCookie, isBrowser, parseUserCookie, useSSO, useSSOSync };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { S as SSOConfig, U as UseSSOReturn, a as SupabaseSession, b as SSOSyncConfig, c as UseSSOSyncReturn, d as SSOUser } from '../constants-BOZ6jJU4.js';
|
|
2
|
+
export { C as COOKIE_NAMES, D as DEFAULT_AUTH_GATEWAY, f as DEFAULT_COOKIE_DOMAIN, g as DEFAULT_POLL_INTERVAL, h as DEFAULT_PROJECT_SCOPE, e as SSOState } from '../constants-BOZ6jJU4.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React hook for SSO authentication state
|
|
6
|
+
* @module @lanonasis/oauth-client/react
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* React hook for SSO authentication state
|
|
11
|
+
*
|
|
12
|
+
* Reads the lanonasis_user cookie to determine auth state.
|
|
13
|
+
* Works across all *.lanonasis.com subdomains.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* function Navbar() {
|
|
18
|
+
* const { isAuthenticated, user, logout } = useSSO();
|
|
19
|
+
*
|
|
20
|
+
* if (isAuthenticated && user) {
|
|
21
|
+
* return <span>Welcome, {user.email}</span>;
|
|
22
|
+
* }
|
|
23
|
+
* return <a href="https://auth.lanonasis.com/web/login">Sign In</a>;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function useSSO(config?: SSOConfig): UseSSOReturn;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* React hook to sync Supabase auth state with lanonasis cookies
|
|
31
|
+
* @module @lanonasis/oauth-client/react
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* React hook to sync Supabase auth state with lanonasis cookies
|
|
36
|
+
*
|
|
37
|
+
* Call this hook in components that use Supabase auth to ensure
|
|
38
|
+
* cross-subdomain SSO cookies are set when user logs in.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* function AuthProvider({ children }) {
|
|
43
|
+
* const { data: { session } } = useSupabaseSession();
|
|
44
|
+
*
|
|
45
|
+
* // Sync Supabase session with SSO cookies
|
|
46
|
+
* useSSOSync(session, {
|
|
47
|
+
* projectScope: 'my-app',
|
|
48
|
+
* onSyncComplete: (success) => {
|
|
49
|
+
* console.log('SSO sync:', success ? 'success' : 'failed');
|
|
50
|
+
* }
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* return <>{children}</>;
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function useSSOSync(supabaseSession: SupabaseSession | null, config?: SSOSyncConfig): UseSSOSyncReturn;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Browser-side cookie utilities for SSO authentication
|
|
61
|
+
* @module @lanonasis/oauth-client/react
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if we're in a browser environment
|
|
66
|
+
*/
|
|
67
|
+
declare function isBrowser(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Parse the lanonasis_user cookie (non-HttpOnly, readable by JS)
|
|
70
|
+
* This cookie contains: { id, email, role, name?, avatar_url? }
|
|
71
|
+
*
|
|
72
|
+
* @returns User data or null if cookie doesn't exist or is invalid
|
|
73
|
+
*/
|
|
74
|
+
declare function parseUserCookie(): SSOUser | null;
|
|
75
|
+
/**
|
|
76
|
+
* Check if the session cookie exists (cannot read value due to HttpOnly)
|
|
77
|
+
*
|
|
78
|
+
* @returns true if lanonasis_session cookie exists
|
|
79
|
+
*/
|
|
80
|
+
declare function hasSessionCookie(): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Check if user appears to be authenticated based on cookies
|
|
83
|
+
*
|
|
84
|
+
* @returns true if both session and user cookies exist
|
|
85
|
+
*/
|
|
86
|
+
declare function hasAuthCookies(): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Clear auth cookies (client-side only clears non-HttpOnly cookies)
|
|
89
|
+
* Full logout should redirect to auth gateway
|
|
90
|
+
*
|
|
91
|
+
* @param domain - Cookie domain (default: .lanonasis.com)
|
|
92
|
+
*/
|
|
93
|
+
declare function clearUserCookie(domain?: string): void;
|
|
94
|
+
|
|
95
|
+
export { SSOConfig, SSOSyncConfig, SSOUser, SupabaseSession, UseSSOReturn, UseSSOSyncReturn, clearUserCookie, hasAuthCookies, hasSessionCookie, isBrowser, parseUserCookie, useSSO, useSSOSync };
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// src/react/useSSO.ts
|
|
2
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
// src/cookies/constants.ts
|
|
5
|
+
var COOKIE_NAMES = {
|
|
6
|
+
/** HttpOnly JWT session token */
|
|
7
|
+
SESSION: "lanonasis_session",
|
|
8
|
+
/** Readable user metadata (JSON) */
|
|
9
|
+
USER: "lanonasis_user"
|
|
10
|
+
};
|
|
11
|
+
var DEFAULT_COOKIE_DOMAIN = ".lanonasis.com";
|
|
12
|
+
var DEFAULT_AUTH_GATEWAY = "https://auth.lanonasis.com";
|
|
13
|
+
var DEFAULT_POLL_INTERVAL = 3e4;
|
|
14
|
+
var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
|
|
15
|
+
|
|
16
|
+
// src/react/cookie-utils-browser.ts
|
|
17
|
+
function isBrowser() {
|
|
18
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
19
|
+
}
|
|
20
|
+
function parseUserCookie() {
|
|
21
|
+
if (!isBrowser()) return null;
|
|
22
|
+
try {
|
|
23
|
+
const cookies = document.cookie.split(";");
|
|
24
|
+
const userCookie = cookies.find(
|
|
25
|
+
(c) => c.trim().startsWith(`${COOKIE_NAMES.USER}=`)
|
|
26
|
+
);
|
|
27
|
+
if (!userCookie) return null;
|
|
28
|
+
const value = userCookie.split("=").slice(1).join("=").trim();
|
|
29
|
+
const decoded = decodeURIComponent(value);
|
|
30
|
+
const parsed = JSON.parse(decoded);
|
|
31
|
+
if (!parsed.id || !parsed.email || !parsed.role) {
|
|
32
|
+
console.warn("[oauth-client] Invalid user cookie: missing required fields");
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.warn("[oauth-client] Failed to parse user cookie:", error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function hasSessionCookie() {
|
|
42
|
+
if (!isBrowser()) return false;
|
|
43
|
+
return document.cookie.includes(`${COOKIE_NAMES.SESSION}=`);
|
|
44
|
+
}
|
|
45
|
+
function hasAuthCookies() {
|
|
46
|
+
return hasSessionCookie() && parseUserCookie() !== null;
|
|
47
|
+
}
|
|
48
|
+
function clearUserCookie(domain = ".lanonasis.com") {
|
|
49
|
+
if (!isBrowser()) return;
|
|
50
|
+
document.cookie = `${COOKIE_NAMES.USER}=; domain=${domain}; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/react/useSSO.ts
|
|
54
|
+
function useSSO(config = {}) {
|
|
55
|
+
const {
|
|
56
|
+
authGatewayUrl = DEFAULT_AUTH_GATEWAY,
|
|
57
|
+
onAuthChange,
|
|
58
|
+
pollInterval = DEFAULT_POLL_INTERVAL
|
|
59
|
+
} = config;
|
|
60
|
+
const [state, setState] = useState({
|
|
61
|
+
isAuthenticated: false,
|
|
62
|
+
isLoading: true,
|
|
63
|
+
user: null,
|
|
64
|
+
error: null
|
|
65
|
+
});
|
|
66
|
+
const prevStateRef = useRef(null);
|
|
67
|
+
const checkAuthState = useCallback(() => {
|
|
68
|
+
if (!isBrowser()) {
|
|
69
|
+
setState({
|
|
70
|
+
isAuthenticated: false,
|
|
71
|
+
isLoading: false,
|
|
72
|
+
user: null,
|
|
73
|
+
error: null
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const hasSession = hasSessionCookie();
|
|
79
|
+
const user = parseUserCookie();
|
|
80
|
+
const newState = {
|
|
81
|
+
isAuthenticated: hasSession && user !== null,
|
|
82
|
+
isLoading: false,
|
|
83
|
+
user: hasSession ? user : null,
|
|
84
|
+
error: null
|
|
85
|
+
};
|
|
86
|
+
setState(newState);
|
|
87
|
+
if (onAuthChange && prevStateRef.current && prevStateRef.current.isAuthenticated !== newState.isAuthenticated) {
|
|
88
|
+
onAuthChange(newState);
|
|
89
|
+
}
|
|
90
|
+
prevStateRef.current = newState;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
const errorState = {
|
|
93
|
+
isAuthenticated: false,
|
|
94
|
+
isLoading: false,
|
|
95
|
+
user: null,
|
|
96
|
+
error: error instanceof Error ? error.message : "Failed to check auth state"
|
|
97
|
+
};
|
|
98
|
+
setState(errorState);
|
|
99
|
+
prevStateRef.current = errorState;
|
|
100
|
+
}
|
|
101
|
+
}, [onAuthChange]);
|
|
102
|
+
const refresh = useCallback(() => {
|
|
103
|
+
setState((prev) => ({ ...prev, isLoading: true }));
|
|
104
|
+
checkAuthState();
|
|
105
|
+
}, [checkAuthState]);
|
|
106
|
+
const logout = useCallback((returnTo) => {
|
|
107
|
+
if (!isBrowser()) return;
|
|
108
|
+
const logoutUrl = new URL("/web/logout", authGatewayUrl);
|
|
109
|
+
if (returnTo) {
|
|
110
|
+
logoutUrl.searchParams.set("return_to", returnTo);
|
|
111
|
+
}
|
|
112
|
+
window.location.href = logoutUrl.toString();
|
|
113
|
+
}, [authGatewayUrl]);
|
|
114
|
+
const getLoginUrl = useCallback((returnTo) => {
|
|
115
|
+
const loginUrl = new URL("/web/login", authGatewayUrl);
|
|
116
|
+
if (returnTo) {
|
|
117
|
+
loginUrl.searchParams.set("return_to", returnTo);
|
|
118
|
+
}
|
|
119
|
+
return loginUrl.toString();
|
|
120
|
+
}, [authGatewayUrl]);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
const timer = setTimeout(checkAuthState, 0);
|
|
123
|
+
return () => clearTimeout(timer);
|
|
124
|
+
}, [checkAuthState]);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!isBrowser() || pollInterval <= 0) return;
|
|
127
|
+
const interval = setInterval(checkAuthState, pollInterval);
|
|
128
|
+
return () => clearInterval(interval);
|
|
129
|
+
}, [checkAuthState, pollInterval]);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!isBrowser()) return;
|
|
132
|
+
const handleStorageChange = (event) => {
|
|
133
|
+
if (event.key === null || event.key?.includes("auth")) {
|
|
134
|
+
checkAuthState();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
window.addEventListener("storage", handleStorageChange);
|
|
138
|
+
return () => window.removeEventListener("storage", handleStorageChange);
|
|
139
|
+
}, [checkAuthState]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (!isBrowser()) return;
|
|
142
|
+
const handleFocus = () => {
|
|
143
|
+
checkAuthState();
|
|
144
|
+
};
|
|
145
|
+
window.addEventListener("focus", handleFocus);
|
|
146
|
+
return () => window.removeEventListener("focus", handleFocus);
|
|
147
|
+
}, [checkAuthState]);
|
|
148
|
+
return {
|
|
149
|
+
...state,
|
|
150
|
+
refresh,
|
|
151
|
+
logout,
|
|
152
|
+
getLoginUrl
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/react/useSSOSync.ts
|
|
157
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
158
|
+
function useSSOSync(supabaseSession, config = {}) {
|
|
159
|
+
const {
|
|
160
|
+
authGatewayUrl = DEFAULT_AUTH_GATEWAY,
|
|
161
|
+
projectScope = DEFAULT_PROJECT_SCOPE,
|
|
162
|
+
onSyncComplete
|
|
163
|
+
} = config;
|
|
164
|
+
const lastSyncedTokenRef = useRef2(null);
|
|
165
|
+
const syncWithGateway = useCallback2(
|
|
166
|
+
async (session) => {
|
|
167
|
+
if (!isBrowser()) return false;
|
|
168
|
+
try {
|
|
169
|
+
const response = await fetch(
|
|
170
|
+
`${authGatewayUrl}/v1/auth/token/exchange`,
|
|
171
|
+
{
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: {
|
|
174
|
+
Authorization: `Bearer ${session.access_token}`,
|
|
175
|
+
"Content-Type": "application/json",
|
|
176
|
+
"X-Project-Scope": projectScope
|
|
177
|
+
},
|
|
178
|
+
credentials: "include",
|
|
179
|
+
// Important: include cookies
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
project_scope: projectScope,
|
|
182
|
+
platform: "web"
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
console.warn("[oauth-client] SSO sync failed:", response.status);
|
|
188
|
+
onSyncComplete?.(false);
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const data = await response.json();
|
|
192
|
+
if (data.cookies_set) {
|
|
193
|
+
console.log("[oauth-client] SSO cookies set successfully");
|
|
194
|
+
}
|
|
195
|
+
onSyncComplete?.(true);
|
|
196
|
+
return true;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error("[oauth-client] SSO sync error:", error);
|
|
199
|
+
onSyncComplete?.(false);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
[authGatewayUrl, projectScope, onSyncComplete]
|
|
204
|
+
);
|
|
205
|
+
const sync = useCallback2(async () => {
|
|
206
|
+
if (!supabaseSession?.access_token) {
|
|
207
|
+
console.warn("[oauth-client] Cannot sync: no Supabase session");
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return syncWithGateway(supabaseSession);
|
|
211
|
+
}, [supabaseSession, syncWithGateway]);
|
|
212
|
+
useEffect2(() => {
|
|
213
|
+
const token = supabaseSession?.access_token;
|
|
214
|
+
if (!token || token === lastSyncedTokenRef.current) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
lastSyncedTokenRef.current = token;
|
|
218
|
+
syncWithGateway(supabaseSession);
|
|
219
|
+
}, [supabaseSession?.access_token, syncWithGateway, supabaseSession]);
|
|
220
|
+
return {
|
|
221
|
+
sync,
|
|
222
|
+
syncWithGateway
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
export {
|
|
226
|
+
COOKIE_NAMES,
|
|
227
|
+
DEFAULT_AUTH_GATEWAY,
|
|
228
|
+
DEFAULT_COOKIE_DOMAIN,
|
|
229
|
+
DEFAULT_POLL_INTERVAL,
|
|
230
|
+
DEFAULT_PROJECT_SCOPE,
|
|
231
|
+
clearUserCookie,
|
|
232
|
+
hasAuthCookies,
|
|
233
|
+
hasSessionCookie,
|
|
234
|
+
isBrowser,
|
|
235
|
+
parseUserCookie,
|
|
236
|
+
useSSO,
|
|
237
|
+
useSSOSync
|
|
238
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server/index.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
COOKIE_NAMES: () => COOKIE_NAMES,
|
|
24
|
+
DEFAULT_AUTH_GATEWAY: () => DEFAULT_AUTH_GATEWAY,
|
|
25
|
+
DEFAULT_COOKIE_DOMAIN: () => DEFAULT_COOKIE_DOMAIN,
|
|
26
|
+
DEFAULT_PROJECT_SCOPE: () => DEFAULT_PROJECT_SCOPE,
|
|
27
|
+
getSSOUserFromRequest: () => getSSOUserFromRequest,
|
|
28
|
+
getSessionToken: () => getSessionToken,
|
|
29
|
+
getSessionTokenFromRequest: () => getSessionTokenFromRequest,
|
|
30
|
+
hasAuthCookiesServer: () => hasAuthCookiesServer,
|
|
31
|
+
hasSSOfromRequest: () => hasSSOfromRequest,
|
|
32
|
+
hasSessionCookieServer: () => hasSessionCookieServer,
|
|
33
|
+
optionalAuth: () => optionalAuth,
|
|
34
|
+
parseCookieHeader: () => parseCookieHeader,
|
|
35
|
+
parseUserCookieServer: () => parseUserCookieServer,
|
|
36
|
+
requireAuth: () => requireAuth,
|
|
37
|
+
requireRole: () => requireRole,
|
|
38
|
+
validateSessionMiddleware: () => validateSessionMiddleware
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(server_exports);
|
|
41
|
+
|
|
42
|
+
// src/cookies/constants.ts
|
|
43
|
+
var COOKIE_NAMES = {
|
|
44
|
+
/** HttpOnly JWT session token */
|
|
45
|
+
SESSION: "lanonasis_session",
|
|
46
|
+
/** Readable user metadata (JSON) */
|
|
47
|
+
USER: "lanonasis_user"
|
|
48
|
+
};
|
|
49
|
+
var DEFAULT_COOKIE_DOMAIN = ".lanonasis.com";
|
|
50
|
+
var DEFAULT_AUTH_GATEWAY = "https://auth.lanonasis.com";
|
|
51
|
+
var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
|
|
52
|
+
|
|
53
|
+
// src/server/cookie-utils.ts
|
|
54
|
+
function parseCookieHeader(cookieHeader) {
|
|
55
|
+
if (!cookieHeader) return {};
|
|
56
|
+
const cookies = {};
|
|
57
|
+
const pairs = cookieHeader.split(";");
|
|
58
|
+
for (const pair of pairs) {
|
|
59
|
+
const [name, ...valueParts] = pair.trim().split("=");
|
|
60
|
+
if (name) {
|
|
61
|
+
cookies[name.trim()] = valueParts.join("=").trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return cookies;
|
|
65
|
+
}
|
|
66
|
+
function getSessionToken(cookies) {
|
|
67
|
+
if (!cookies) return null;
|
|
68
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
69
|
+
const token = parsed[COOKIE_NAMES.SESSION];
|
|
70
|
+
return token || null;
|
|
71
|
+
}
|
|
72
|
+
function parseUserCookieServer(cookies) {
|
|
73
|
+
if (!cookies) return null;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
76
|
+
const userCookie = parsed[COOKIE_NAMES.USER];
|
|
77
|
+
if (!userCookie) return null;
|
|
78
|
+
const decoded = decodeURIComponent(userCookie);
|
|
79
|
+
const user = JSON.parse(decoded);
|
|
80
|
+
if (!user.id || !user.email || !user.role) {
|
|
81
|
+
console.warn("[oauth-client/server] Invalid user cookie: missing required fields");
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return user;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.warn("[oauth-client/server] Failed to parse user cookie:", error);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function hasSessionCookieServer(cookies) {
|
|
91
|
+
if (!cookies) return false;
|
|
92
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
93
|
+
return COOKIE_NAMES.SESSION in parsed && !!parsed[COOKIE_NAMES.SESSION];
|
|
94
|
+
}
|
|
95
|
+
function hasAuthCookiesServer(cookies) {
|
|
96
|
+
return hasSessionCookieServer(cookies) && parseUserCookieServer(cookies) !== null;
|
|
97
|
+
}
|
|
98
|
+
function getSSOUserFromRequest(req) {
|
|
99
|
+
if (req.cookies) {
|
|
100
|
+
return parseUserCookieServer(req.cookies);
|
|
101
|
+
}
|
|
102
|
+
return parseUserCookieServer(req.headers?.cookie);
|
|
103
|
+
}
|
|
104
|
+
function getSessionTokenFromRequest(req) {
|
|
105
|
+
if (req.cookies) {
|
|
106
|
+
return getSessionToken(req.cookies);
|
|
107
|
+
}
|
|
108
|
+
return getSessionToken(req.headers?.cookie);
|
|
109
|
+
}
|
|
110
|
+
function hasSSOfromRequest(req) {
|
|
111
|
+
if (req.cookies) {
|
|
112
|
+
return hasAuthCookiesServer(req.cookies);
|
|
113
|
+
}
|
|
114
|
+
return hasAuthCookiesServer(req.headers?.cookie);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/server/middleware.ts
|
|
118
|
+
function validateSessionMiddleware(config = {}) {
|
|
119
|
+
const {
|
|
120
|
+
cookieDomain = DEFAULT_COOKIE_DOMAIN,
|
|
121
|
+
allowAnonymous = false
|
|
122
|
+
} = config;
|
|
123
|
+
return (req, res, next) => {
|
|
124
|
+
if (hasSSOfromRequest(req)) {
|
|
125
|
+
const user = getSSOUserFromRequest(req);
|
|
126
|
+
if (user) {
|
|
127
|
+
req.user = user;
|
|
128
|
+
return next();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (allowAnonymous) {
|
|
132
|
+
return next();
|
|
133
|
+
}
|
|
134
|
+
res.clearCookie(COOKIE_NAMES.SESSION, { domain: cookieDomain, path: "/" });
|
|
135
|
+
res.clearCookie(COOKIE_NAMES.USER, { domain: cookieDomain, path: "/" });
|
|
136
|
+
return res.status(401).json({
|
|
137
|
+
error: "Authentication required",
|
|
138
|
+
code: "AUTH_REQUIRED",
|
|
139
|
+
login_url: `${config.authGatewayUrl || DEFAULT_AUTH_GATEWAY}/web/login`
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function requireAuth(config = {}) {
|
|
144
|
+
return validateSessionMiddleware({ ...config, allowAnonymous: false });
|
|
145
|
+
}
|
|
146
|
+
function optionalAuth(config = {}) {
|
|
147
|
+
return validateSessionMiddleware({ ...config, allowAnonymous: true });
|
|
148
|
+
}
|
|
149
|
+
function requireRole(role, config = {}) {
|
|
150
|
+
const roles = Array.isArray(role) ? role : [role];
|
|
151
|
+
return (req, res, next) => {
|
|
152
|
+
if (!req.user) {
|
|
153
|
+
return res.status(401).json({
|
|
154
|
+
error: "Authentication required",
|
|
155
|
+
code: "AUTH_REQUIRED",
|
|
156
|
+
login_url: `${config.authGatewayUrl || DEFAULT_AUTH_GATEWAY}/web/login`
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (!roles.includes(req.user.role)) {
|
|
160
|
+
return res.status(403).json({
|
|
161
|
+
error: "Insufficient permissions",
|
|
162
|
+
code: "FORBIDDEN",
|
|
163
|
+
required_role: roles,
|
|
164
|
+
current_role: req.user.role
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return next();
|
|
168
|
+
};
|
|
169
|
+
}
|