@xyd-js/plugin-access-control 0.0.0-canary-188f589-20260429221601

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) LiveSession Sp.z.o.o
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,130 @@
1
+ // src/components/AccessControlContext.tsx
2
+ import React, { createContext, useContext, useEffect, useState, useCallback } from "react";
3
+
4
+ // src/devOnly.ts
5
+ function isDevEnvironment() {
6
+ if (typeof window === "undefined") {
7
+ return process.env.NODE_ENV !== "production";
8
+ }
9
+ try {
10
+ return !!import.meta.env?.DEV;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ // src/components/AccessControlContext.tsx
17
+ var AccessControlCtx = createContext(null);
18
+ function useAccessControl() {
19
+ const ctx = useContext(AccessControlCtx);
20
+ if (!ctx) {
21
+ throw new Error("useAccessControl must be used inside <AccessControlProvider>");
22
+ }
23
+ return ctx;
24
+ }
25
+ function AccessControlProvider({ children }) {
26
+ const [config, setConfig] = useState(null);
27
+ const [error, setError] = useState("");
28
+ const [ready, setReady] = useState(false);
29
+ useEffect(() => {
30
+ const globalConfig = window.__xydAccessControlSettings?.accessControlConfig;
31
+ if (globalConfig) {
32
+ setConfig(globalConfig);
33
+ setReady(true);
34
+ return;
35
+ }
36
+ import("virtual:xyd-access-control-settings").then((mod) => {
37
+ setConfig(mod.accessControlConfig);
38
+ setReady(true);
39
+ }).catch(() => setReady(true));
40
+ }, []);
41
+ const providerType = config?.provider?.type || "jwt";
42
+ const loginUrl = config?.provider?.loginUrl || "";
43
+ const authorizationUrl = config?.provider?.authorizationUrl || "";
44
+ const hasExternalAuth = providerType === "oauth" ? !!authorizationUrl : !!loginUrl && loginUrl !== "/login";
45
+ const loginConfig = typeof config?.login === "string" ? {} : config?.login || {};
46
+ const title = loginConfig.title || "Sign in to access documentation";
47
+ const description = loginConfig.description || "";
48
+ const logo = loginConfig.logo || "";
49
+ const backgroundImage = loginConfig.backgroundImage || "";
50
+ const params = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
51
+ const redirectUrl = params?.get("redirect") || "/";
52
+ const storeTokenAndRedirect = useCallback((token) => {
53
+ const cookieName = config?.session?.cookieName || "xyd-auth-token";
54
+ localStorage.setItem(cookieName, token);
55
+ document.cookie = `${cookieName}=${token}; path=/; max-age=86400; SameSite=Lax`;
56
+ window.location.href = redirectUrl;
57
+ }, [config, redirectUrl]);
58
+ const signInWithOAuth = useCallback(() => {
59
+ if (!authorizationUrl) {
60
+ setError("No OAuth authorization URL configured");
61
+ return;
62
+ }
63
+ const callbackPath = config?.provider?.callbackPath || "/auth/callback";
64
+ const redirectUri = window.location.origin + callbackPath;
65
+ const scopes = config?.provider?.scopes || [];
66
+ const clientId = config?.provider?.clientId || "";
67
+ const oauthParams = new URLSearchParams({
68
+ client_id: clientId,
69
+ redirect_uri: redirectUri,
70
+ response_type: "code",
71
+ scope: scopes.join(" "),
72
+ state: redirectUrl
73
+ });
74
+ window.location.href = `${authorizationUrl}?${oauthParams.toString()}`;
75
+ }, [config, authorizationUrl, redirectUrl]);
76
+ const signInWithRedirect = useCallback(() => {
77
+ if (!loginUrl || loginUrl === "/login") {
78
+ setError("No external login URL configured");
79
+ return;
80
+ }
81
+ const sep = loginUrl.includes("?") ? "&" : "?";
82
+ window.location.href = `${loginUrl}${sep}redirect=${encodeURIComponent(redirectUrl)}`;
83
+ }, [loginUrl, redirectUrl]);
84
+ const signInWithGroups = useCallback((groups) => {
85
+ const hasDeploy = !!config?.deploy;
86
+ if (hasDeploy) {
87
+ const groupsParam = groups.length ? `&groups=${groups.join(",")}` : "";
88
+ window.location.href = `/auth/test-login?redirect=${encodeURIComponent(redirectUrl)}${groupsParam}`;
89
+ return;
90
+ }
91
+ if (!isDevEnvironment()) {
92
+ setError("Test login is only available in development mode.");
93
+ return;
94
+ }
95
+ const header = btoa(JSON.stringify({ alg: "HS256", typ: "JWT" }));
96
+ const payload = btoa(JSON.stringify({
97
+ sub: "test-user",
98
+ groups,
99
+ exp: Math.floor(Date.now() / 1e3) + 86400,
100
+ iat: Math.floor(Date.now() / 1e3)
101
+ }));
102
+ storeTokenAndRedirect(`${header}.${payload}.dGVzdA`);
103
+ }, [config, redirectUrl, storeTokenAndRedirect]);
104
+ const signInAsUser = useCallback(() => signInWithGroups([]), [signInWithGroups]);
105
+ const signInAsAdmin = useCallback(() => signInWithGroups(["admin"]), [signInWithGroups]);
106
+ const value = {
107
+ config,
108
+ ready,
109
+ error,
110
+ clearError: () => setError(""),
111
+ signInWithOAuth,
112
+ signInWithRedirect,
113
+ signInAsUser,
114
+ signInAsAdmin,
115
+ signInWithGroups,
116
+ providerType,
117
+ hasExternalAuth,
118
+ title,
119
+ description,
120
+ logo,
121
+ backgroundImage,
122
+ redirectUrl
123
+ };
124
+ return /* @__PURE__ */ React.createElement(AccessControlCtx.Provider, { value }, children);
125
+ }
126
+ export {
127
+ AccessControlProvider,
128
+ useAccessControl
129
+ };
130
+ //# sourceMappingURL=AccessControlContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/AccessControlContext.tsx","../src/devOnly.ts"],"sourcesContent":["import React, { createContext, useContext, useEffect, useState, useCallback } from \"react\";\nimport { isDevEnvironment as isDevEnv } from \"../devOnly\";\n\nexport interface AccessControlActions {\n /** Current auth config from docs.json */\n config: any | null;\n /** Whether config has loaded */\n ready: boolean;\n /** Last error message */\n error: string;\n /** Clear error */\n clearError: () => void;\n\n // --- Auth actions ---\n\n /** Redirect to external OAuth provider */\n signInWithOAuth: () => void;\n /** Redirect to external JWT login URL */\n signInWithRedirect: () => void;\n /** Sign in with test token (dev/edge server) */\n signInAsUser: () => void;\n /** Sign in as admin with test token (dev/edge server) */\n signInAsAdmin: () => void;\n /** Sign in with specific groups */\n signInWithGroups: (groups: string[]) => void;\n // --- Info ---\n\n /** Provider type: \"jwt\" | \"oauth\" */\n providerType: string;\n /** Whether an external auth URL is configured */\n hasExternalAuth: boolean;\n /** Login page title from config */\n title: string;\n /** Login page description from config */\n description: string;\n /** Login page logo URL from config */\n logo: string;\n /** Login page background image from config */\n backgroundImage: string;\n /** The redirect URL (where to go after login) */\n redirectUrl: string;\n}\n\nconst AccessControlCtx = createContext<AccessControlActions | null>(null);\n\n/**\n * Hook to access auth actions in custom login pages.\n *\n * @example\n * ```tsx\n * import { useAccessControl } from \"@xyd-js/plugin-access-control\"\n *\n * function MyLoginPage() {\n * const { signInWithOAuth, title, logo, error } = useAccessControl()\n *\n * return (\n * <div>\n * {logo && <img src={logo} />}\n * <h1>{title}</h1>\n * {error && <p>{error}</p>}\n * <button onClick={signInWithOAuth}>Sign in</button>\n * </div>\n * )\n * }\n * ```\n */\nexport function useAccessControl(): AccessControlActions {\n const ctx = useContext(AccessControlCtx);\n if (!ctx) {\n throw new Error(\"useAccessControl must be used inside <AccessControlProvider>\");\n }\n return ctx;\n}\n\nexport function AccessControlProvider({ children }: { children: React.ReactNode }) {\n const [config, setConfig] = useState<any>(null);\n const [error, setError] = useState(\"\");\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n const globalConfig = (window as any).__xydAccessControlSettings?.accessControlConfig;\n if (globalConfig) {\n setConfig(globalConfig);\n setReady(true);\n return;\n }\n // @ts-ignore\n import(\"virtual:xyd-access-control-settings\")\n .then((mod: any) => { setConfig(mod.accessControlConfig); setReady(true); })\n .catch(() => setReady(true));\n }, []);\n\n const providerType = config?.provider?.type || \"jwt\";\n const loginUrl = config?.provider?.loginUrl || \"\";\n const authorizationUrl = config?.provider?.authorizationUrl || \"\";\n const hasExternalAuth = providerType === \"oauth\"\n ? !!authorizationUrl\n : !!loginUrl && loginUrl !== \"/login\";\n\n // login can be a string (custom component path) or an object (config)\n const loginConfig = typeof config?.login === \"string\" ? {} : (config?.login || {});\n const title = loginConfig.title || \"Sign in to access documentation\";\n const description = loginConfig.description || \"\";\n const logo = loginConfig.logo || \"\";\n const backgroundImage = loginConfig.backgroundImage || \"\";\n\n const params = typeof window !== \"undefined\" ? new URLSearchParams(window.location.search) : null;\n const redirectUrl = params?.get(\"redirect\") || \"/\";\n\n const storeTokenAndRedirect = useCallback((token: string) => {\n const cookieName = config?.session?.cookieName || \"xyd-auth-token\";\n localStorage.setItem(cookieName, token);\n document.cookie = `${cookieName}=${token}; path=/; max-age=86400; SameSite=Lax`;\n window.location.href = redirectUrl;\n }, [config, redirectUrl]);\n\n const signInWithOAuth = useCallback(() => {\n if (!authorizationUrl) { setError(\"No OAuth authorization URL configured\"); return; }\n const callbackPath = config?.provider?.callbackPath || \"/auth/callback\";\n const redirectUri = window.location.origin + callbackPath;\n const scopes = config?.provider?.scopes || [];\n const clientId = config?.provider?.clientId || \"\";\n\n const oauthParams = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: scopes.join(\" \"),\n state: redirectUrl,\n });\n window.location.href = `${authorizationUrl}?${oauthParams.toString()}`;\n }, [config, authorizationUrl, redirectUrl]);\n\n const signInWithRedirect = useCallback(() => {\n if (!loginUrl || loginUrl === \"/login\") { setError(\"No external login URL configured\"); return; }\n const sep = loginUrl.includes(\"?\") ? \"&\" : \"?\";\n window.location.href = `${loginUrl}${sep}redirect=${encodeURIComponent(redirectUrl)}`;\n }, [loginUrl, redirectUrl]);\n\n const signInWithGroups = useCallback((groups: string[]) => {\n // Production with deploy config: use /auth/test-login (server-signed)\n const hasDeploy = !!config?.deploy;\n if (hasDeploy) {\n const groupsParam = groups.length ? `&groups=${groups.join(\",\")}` : \"\";\n window.location.href = `/auth/test-login?redirect=${encodeURIComponent(redirectUrl)}${groupsParam}`;\n return;\n }\n\n // Dev only: generate unsigned client-side token\n if (!isDevEnv()) {\n setError(\"Test login is only available in development mode.\");\n return;\n }\n const header = btoa(JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }));\n const payload = btoa(JSON.stringify({\n sub: \"test-user\",\n groups,\n exp: Math.floor(Date.now() / 1000) + 86400,\n iat: Math.floor(Date.now() / 1000),\n }));\n storeTokenAndRedirect(`${header}.${payload}.dGVzdA`);\n }, [config, redirectUrl, storeTokenAndRedirect]);\n\n const signInAsUser = useCallback(() => signInWithGroups([]), [signInWithGroups]);\n const signInAsAdmin = useCallback(() => signInWithGroups([\"admin\"]), [signInWithGroups]);\n\n const value: AccessControlActions = {\n config,\n ready,\n error,\n clearError: () => setError(\"\"),\n signInWithOAuth,\n signInWithRedirect,\n signInAsUser,\n signInAsAdmin,\n signInWithGroups,\n providerType,\n hasExternalAuth,\n title,\n description,\n logo,\n backgroundImage,\n redirectUrl,\n };\n\n return <AccessControlCtx.Provider value={value}>{children}</AccessControlCtx.Provider>;\n}","/**\n * Check if running in development mode.\n * Server-side: checks NODE_ENV\n * Client-side: checks import.meta.env (set by Vite)\n */\nexport function isDevEnvironment(): boolean {\n // Server-side (Node.js)\n if (typeof window === \"undefined\") {\n return process.env.NODE_ENV !== \"production\";\n }\n\n // Client-side (Vite)\n try {\n return !!(import.meta as any).env?.DEV;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if auth bypass is enabled. Only works in development mode.\n * Controlled by XYD_AUTH_BYPASS=1 environment variable.\n */\nexport function isAuthBypassed(): boolean {\n if (!isDevEnvironment()) return false;\n\n return (\n process.env.XYD_AUTH_BYPASS === \"1\" ||\n process.env.XYD_AUTH_BYPASS === \"true\"\n );\n}"],"mappings":"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","names":[]}
@@ -0,0 +1,116 @@
1
+ // src/components/AuthCallbackPage.tsx
2
+ import React, { useEffect, useState } from "react";
3
+ function AuthCallbackPage() {
4
+ const [error, setError] = useState("");
5
+ useEffect(() => {
6
+ if (typeof window === "undefined") return;
7
+ import("virtual:xyd-access-control-settings").then((mod) => {
8
+ const config = mod.accessControlConfig;
9
+ handleCallback(config);
10
+ }).catch(() => {
11
+ handleCallback(null);
12
+ });
13
+ }, []);
14
+ function storeTokenAndRedirect(token, groups, redirect, cookieName) {
15
+ localStorage.setItem(cookieName, token);
16
+ document.cookie = `${cookieName}=${token}; path=/; max-age=86400; SameSite=Lax`;
17
+ window.__xydAuthState = {
18
+ authenticated: true,
19
+ groups,
20
+ token
21
+ };
22
+ document.documentElement.setAttribute("data-auth", "authenticated");
23
+ window.location.href = redirect;
24
+ }
25
+ async function handleCallback(config) {
26
+ const params = new URLSearchParams(window.location.search);
27
+ const hash = window.location.hash.slice(1);
28
+ const code = params.get("code");
29
+ const state = params.get("state") || "/";
30
+ const cookieName = config?.session?.cookieName || "xyd-auth-token";
31
+ const groupsClaim = config?.provider?.groupsClaim || "groups";
32
+ if (code && config?.provider?.type === "oauth") {
33
+ try {
34
+ await handleOAuthCallback(code, state, config, cookieName, groupsClaim);
35
+ } catch (e) {
36
+ setError(e instanceof Error ? e.message : "OAuth authentication failed");
37
+ }
38
+ return;
39
+ }
40
+ const token = params.get("token") || hash;
41
+ const redirect = params.get("redirect") || state;
42
+ if (token) {
43
+ try {
44
+ handleJWTCallback(token, redirect, cookieName, groupsClaim);
45
+ } catch (e) {
46
+ setError(e instanceof Error ? e.message : "JWT authentication failed");
47
+ }
48
+ return;
49
+ }
50
+ setError("No authentication data found in callback URL.");
51
+ }
52
+ function handleJWTCallback(hash, redirect, cookieName, groupsClaim) {
53
+ const parts = hash.split(".");
54
+ if (parts.length !== 3) throw new Error("Invalid JWT format");
55
+ const payload = JSON.parse(atob(parts[1]));
56
+ if (payload.exp && payload.exp * 1e3 < Date.now()) {
57
+ throw new Error("Token has expired");
58
+ }
59
+ storeTokenAndRedirect(hash, payload[groupsClaim] || [], redirect, cookieName);
60
+ }
61
+ async function handleOAuthCallback(code, redirect, config, cookieName, groupsClaim) {
62
+ const tokenUrl = config.provider.tokenUrl;
63
+ const userInfoUrl = config.provider.userInfoUrl;
64
+ const clientId = config.provider.clientId || "";
65
+ const callbackPath = config.provider.callbackPath || "/auth/callback";
66
+ const redirectUri = window.location.origin + callbackPath;
67
+ if (!tokenUrl) throw new Error("No token URL configured");
68
+ const tokenRes = await fetch(tokenUrl, {
69
+ method: "POST",
70
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
71
+ body: new URLSearchParams({
72
+ grant_type: "authorization_code",
73
+ code,
74
+ client_id: clientId,
75
+ redirect_uri: redirectUri
76
+ }).toString()
77
+ });
78
+ if (!tokenRes.ok) {
79
+ throw new Error(`Token exchange failed: ${tokenRes.status}`);
80
+ }
81
+ const tokenData = await tokenRes.json();
82
+ const accessToken = tokenData.access_token;
83
+ if (!accessToken) throw new Error("No access token in response");
84
+ let groups = [];
85
+ if (userInfoUrl) {
86
+ try {
87
+ const userRes = await fetch(userInfoUrl, {
88
+ headers: { Authorization: `Bearer ${accessToken}` }
89
+ });
90
+ if (userRes.ok) {
91
+ const userInfo = await userRes.json();
92
+ groups = userInfo[groupsClaim] || userInfo.roles || userInfo.groups || [];
93
+ }
94
+ } catch {
95
+ }
96
+ }
97
+ const header = btoa(JSON.stringify({ alg: "HS256", typ: "JWT" }));
98
+ const payload = btoa(JSON.stringify({
99
+ sub: "oauth-user",
100
+ [groupsClaim]: groups,
101
+ exp: Math.floor(Date.now() / 1e3) + 86400,
102
+ iat: Math.floor(Date.now() / 1e3),
103
+ access_token: accessToken
104
+ }));
105
+ const sessionToken = `${header}.${payload}.oauth`;
106
+ storeTokenAndRedirect(sessionToken, groups, redirect, cookieName);
107
+ }
108
+ if (error) {
109
+ return /* @__PURE__ */ React.createElement("div", { className: "xyd-callback-page" }, /* @__PURE__ */ React.createElement("div", { part: "card" }, /* @__PURE__ */ React.createElement("h2", { part: "title", "data-error": "" }, "Authentication Error"), /* @__PURE__ */ React.createElement("p", { part: "message" }, error), /* @__PURE__ */ React.createElement("button", { part: "button", onClick: () => window.location.href = "/" }, "Go to homepage")));
110
+ }
111
+ return /* @__PURE__ */ React.createElement("div", { className: "xyd-callback-page" }, /* @__PURE__ */ React.createElement("div", { part: "card" }, /* @__PURE__ */ React.createElement("p", { part: "message" }, "Authenticating...")));
112
+ }
113
+ export {
114
+ AuthCallbackPage as default
115
+ };
116
+ //# sourceMappingURL=AuthCallbackPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/AuthCallbackPage.tsx"],"sourcesContent":["import React, { useEffect, useState } from \"react\";\n\n/**\n * Handles both JWT and OAuth callback flows:\n *\n * JWT: /auth/jwt-callback#eyJ... → extract token from hash\n * OAuth: /auth/callback?code=XXX&state=/page → exchange code for token\n */\nexport default function AuthCallbackPage() {\n const [error, setError] = useState(\"\");\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n // @ts-ignore - virtual module resolved by Vite at runtime\n import(\"virtual:xyd-access-control-settings\")\n .then((mod: any) => {\n const config = mod.accessControlConfig;\n handleCallback(config);\n })\n .catch(() => {\n // Fallback: try JWT flow without config\n handleCallback(null);\n });\n }, []);\n\n function storeTokenAndRedirect(\n token: string,\n groups: string[],\n redirect: string,\n cookieName: string\n ) {\n localStorage.setItem(cookieName, token);\n document.cookie = `${cookieName}=${token}; path=/; max-age=86400; SameSite=Lax`;\n\n (window as any).__xydAuthState = {\n authenticated: true,\n groups,\n token,\n };\n document.documentElement.setAttribute(\"data-auth\", \"authenticated\");\n window.location.href = redirect;\n }\n\n async function handleCallback(config: any) {\n const params = new URLSearchParams(window.location.search);\n const hash = window.location.hash.slice(1);\n const code = params.get(\"code\");\n const state = params.get(\"state\") || \"/\";\n const cookieName = config?.session?.cookieName || \"xyd-auth-token\";\n const groupsClaim = config?.provider?.groupsClaim || \"groups\";\n\n // OAuth flow: code in query params\n if (code && config?.provider?.type === \"oauth\") {\n try {\n await handleOAuthCallback(code, state, config, cookieName, groupsClaim);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"OAuth authentication failed\");\n }\n return;\n }\n\n // JWT flow: token in query param (?token=...) or hash fragment (#eyJ...)\n const token = params.get(\"token\") || hash;\n const redirect = params.get(\"redirect\") || state;\n if (token) {\n try {\n handleJWTCallback(token, redirect, cookieName, groupsClaim);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"JWT authentication failed\");\n }\n return;\n }\n\n setError(\"No authentication data found in callback URL.\");\n }\n\n function handleJWTCallback(\n hash: string,\n redirect: string,\n cookieName: string,\n groupsClaim: string\n ) {\n const parts = hash.split(\".\");\n if (parts.length !== 3) throw new Error(\"Invalid JWT format\");\n\n const payload = JSON.parse(atob(parts[1]));\n if (payload.exp && payload.exp * 1000 < Date.now()) {\n throw new Error(\"Token has expired\");\n }\n\n storeTokenAndRedirect(hash, payload[groupsClaim] || [], redirect, cookieName);\n }\n\n async function handleOAuthCallback(\n code: string,\n redirect: string,\n config: any,\n cookieName: string,\n groupsClaim: string\n ) {\n const tokenUrl = config.provider.tokenUrl;\n const userInfoUrl = config.provider.userInfoUrl;\n const clientId = config.provider.clientId || \"\";\n const callbackPath = config.provider.callbackPath || \"/auth/callback\";\n const redirectUri = window.location.origin + callbackPath;\n\n if (!tokenUrl) throw new Error(\"No token URL configured\");\n\n // Exchange code for access token\n const tokenRes = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n redirect_uri: redirectUri,\n }).toString(),\n });\n\n if (!tokenRes.ok) {\n throw new Error(`Token exchange failed: ${tokenRes.status}`);\n }\n\n const tokenData = await tokenRes.json();\n const accessToken = tokenData.access_token;\n if (!accessToken) throw new Error(\"No access token in response\");\n\n // Fetch user info to get groups/roles\n let groups: string[] = [];\n if (userInfoUrl) {\n try {\n const userRes = await fetch(userInfoUrl, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n if (userRes.ok) {\n const userInfo = await userRes.json();\n groups = userInfo[groupsClaim] || userInfo.roles || userInfo.groups || [];\n }\n } catch {\n // User info fetch failed — continue without groups\n }\n }\n\n // Create a session JWT from the access token info\n const header = btoa(JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }));\n const payload = btoa(JSON.stringify({\n sub: \"oauth-user\",\n [groupsClaim]: groups,\n exp: Math.floor(Date.now() / 1000) + 86400,\n iat: Math.floor(Date.now() / 1000),\n access_token: accessToken,\n }));\n const sessionToken = `${header}.${payload}.oauth`;\n\n storeTokenAndRedirect(sessionToken, groups, redirect, cookieName);\n }\n\n if (error) {\n return (\n <div className=\"xyd-callback-page\">\n <div part=\"card\">\n <h2 part=\"title\" data-error=\"\">Authentication Error</h2>\n <p part=\"message\">{error}</p>\n <button part=\"button\" onClick={() => (window.location.href = \"/\")}>\n Go to homepage\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"xyd-callback-page\">\n <div part=\"card\">\n <p part=\"message\">Authenticating...</p>\n </div>\n </div>\n );\n}\n"],"mappings":";AAAA,OAAO,SAAS,WAAW,gBAAgB;AAQ5B,SAAR,mBAAoC;AACzC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AAErC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AAGnC,WAAO,qCAAqC,EACzC,KAAK,CAAC,QAAa;AAClB,YAAM,SAAS,IAAI;AACnB,qBAAe,MAAM;AAAA,IACvB,CAAC,EACA,MAAM,MAAM;AAEX,qBAAe,IAAI;AAAA,IACrB,CAAC;AAAA,EACL,GAAG,CAAC,CAAC;AAEL,WAAS,sBACP,OACA,QACA,UACA,YACA;AACA,iBAAa,QAAQ,YAAY,KAAK;AACtC,aAAS,SAAS,GAAG,UAAU,IAAI,KAAK;AAExC,IAAC,OAAe,iBAAiB;AAAA,MAC/B,eAAe;AAAA,MACf;AAAA,MACA;AAAA,IACF;AACA,aAAS,gBAAgB,aAAa,aAAa,eAAe;AAClE,WAAO,SAAS,OAAO;AAAA,EACzB;AAEA,iBAAe,eAAe,QAAa;AACzC,UAAM,SAAS,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACzD,UAAM,OAAO,OAAO,SAAS,KAAK,MAAM,CAAC;AACzC,UAAM,OAAO,OAAO,IAAI,MAAM;AAC9B,UAAM,QAAQ,OAAO,IAAI,OAAO,KAAK;AACrC,UAAM,aAAa,QAAQ,SAAS,cAAc;AAClD,UAAM,cAAc,QAAQ,UAAU,eAAe;AAGrD,QAAI,QAAQ,QAAQ,UAAU,SAAS,SAAS;AAC9C,UAAI;AACF,cAAM,oBAAoB,MAAM,OAAO,QAAQ,YAAY,WAAW;AAAA,MACxE,SAAS,GAAG;AACV,iBAAS,aAAa,QAAQ,EAAE,UAAU,6BAA6B;AAAA,MACzE;AACA;AAAA,IACF;AAGA,UAAM,QAAQ,OAAO,IAAI,OAAO,KAAK;AACrC,UAAM,WAAW,OAAO,IAAI,UAAU,KAAK;AAC3C,QAAI,OAAO;AACT,UAAI;AACF,0BAAkB,OAAO,UAAU,YAAY,WAAW;AAAA,MAC5D,SAAS,GAAG;AACV,iBAAS,aAAa,QAAQ,EAAE,UAAU,2BAA2B;AAAA,MACvE;AACA;AAAA,IACF;AAEA,aAAS,+CAA+C;AAAA,EAC1D;AAEA,WAAS,kBACP,MACA,UACA,YACA,aACA;AACA,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAI,MAAM,WAAW,EAAG,OAAM,IAAI,MAAM,oBAAoB;AAE5D,UAAM,UAAU,KAAK,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC;AACzC,QAAI,QAAQ,OAAO,QAAQ,MAAM,MAAO,KAAK,IAAI,GAAG;AAClD,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACrC;AAEA,0BAAsB,MAAM,QAAQ,WAAW,KAAK,CAAC,GAAG,UAAU,UAAU;AAAA,EAC9E;AAEA,iBAAe,oBACb,MACA,UACA,QACA,YACA,aACA;AACA,UAAM,WAAW,OAAO,SAAS;AACjC,UAAM,cAAc,OAAO,SAAS;AACpC,UAAM,WAAW,OAAO,SAAS,YAAY;AAC7C,UAAM,eAAe,OAAO,SAAS,gBAAgB;AACrD,UAAM,cAAc,OAAO,SAAS,SAAS;AAE7C,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,yBAAyB;AAGxD,UAAM,WAAW,MAAM,MAAM,UAAU;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,MAC/D,MAAM,IAAI,gBAAgB;AAAA,QACxB,YAAY;AAAA,QACZ;AAAA,QACA,WAAW;AAAA,QACX,cAAc;AAAA,MAChB,CAAC,EAAE,SAAS;AAAA,IACd,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,0BAA0B,SAAS,MAAM,EAAE;AAAA,IAC7D;AAEA,UAAM,YAAY,MAAM,SAAS,KAAK;AACtC,UAAM,cAAc,UAAU;AAC9B,QAAI,CAAC,YAAa,OAAM,IAAI,MAAM,6BAA6B;AAG/D,QAAI,SAAmB,CAAC;AACxB,QAAI,aAAa;AACf,UAAI;AACF,cAAM,UAAU,MAAM,MAAM,aAAa;AAAA,UACvC,SAAS,EAAE,eAAe,UAAU,WAAW,GAAG;AAAA,QACpD,CAAC;AACD,YAAI,QAAQ,IAAI;AACd,gBAAM,WAAW,MAAM,QAAQ,KAAK;AACpC,mBAAS,SAAS,WAAW,KAAK,SAAS,SAAS,SAAS,UAAU,CAAC;AAAA,QAC1E;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,UAAM,SAAS,KAAK,KAAK,UAAU,EAAE,KAAK,SAAS,KAAK,MAAM,CAAC,CAAC;AAChE,UAAM,UAAU,KAAK,KAAK,UAAU;AAAA,MAClC,KAAK;AAAA,MACL,CAAC,WAAW,GAAG;AAAA,MACf,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;AAAA,MACrC,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,MACjC,cAAc;AAAA,IAChB,CAAC,CAAC;AACF,UAAM,eAAe,GAAG,MAAM,IAAI,OAAO;AAEzC,0BAAsB,cAAc,QAAQ,UAAU,UAAU;AAAA,EAClE;AAEA,MAAI,OAAO;AACT,WACE,oCAAC,SAAI,WAAU,uBACb,oCAAC,SAAI,MAAK,UACR,oCAAC,QAAG,MAAK,SAAQ,cAAW,MAAG,sBAAoB,GACnD,oCAAC,OAAE,MAAK,aAAW,KAAM,GACzB,oCAAC,YAAO,MAAK,UAAS,SAAS,MAAO,OAAO,SAAS,OAAO,OAAM,gBAEnE,CACF,CACF;AAAA,EAEJ;AAEA,SACE,oCAAC,SAAI,WAAU,uBACb,oCAAC,SAAI,MAAK,UACR,oCAAC,OAAE,MAAK,aAAU,mBAAiB,CACrC,CACF;AAEJ;","names":[]}
@@ -0,0 +1,115 @@
1
+ // src/components/AuthGuard.tsx
2
+ import React, {
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useState,
7
+ useMemo
8
+ } from "react";
9
+ var AuthContext = createContext({
10
+ authenticated: false,
11
+ groups: [],
12
+ token: null
13
+ });
14
+ function buildLoginUrl(loginUrl, returnPath) {
15
+ const separator = loginUrl.includes("?") ? "&" : "?";
16
+ return `${loginUrl}${separator}redirect=${encodeURIComponent(returnPath)}`;
17
+ }
18
+ function AuthGuard({ children, accessMap, config }) {
19
+ const [authState, setAuthState] = useState(() => {
20
+ if (typeof window !== "undefined" && window.__xydAuthState) {
21
+ return window.__xydAuthState;
22
+ }
23
+ return { authenticated: false, groups: [], token: null };
24
+ });
25
+ useEffect(() => {
26
+ const handleStorage = (e) => {
27
+ const cookieName = config.session?.cookieName || "xyd-auth-token";
28
+ if (e.key === cookieName) {
29
+ if (e.newValue) {
30
+ try {
31
+ const payload = JSON.parse(atob(e.newValue.split(".")[1]));
32
+ setAuthState({
33
+ authenticated: true,
34
+ groups: payload.groups || [],
35
+ token: e.newValue
36
+ });
37
+ } catch {
38
+ setAuthState({ authenticated: false, groups: [], token: null });
39
+ }
40
+ } else {
41
+ setAuthState({ authenticated: false, groups: [], token: null });
42
+ }
43
+ }
44
+ };
45
+ window.addEventListener("storage", handleStorage);
46
+ return () => window.removeEventListener("storage", handleStorage);
47
+ }, [config.session?.cookieName]);
48
+ const value = useMemo(() => authState, [authState]);
49
+ return /* @__PURE__ */ React.createElement(AuthContext.Provider, { value }, /* @__PURE__ */ React.createElement(AuthEnforcer, { accessMap, config }, children));
50
+ }
51
+ function AuthEnforcer({
52
+ children,
53
+ accessMap,
54
+ config
55
+ }) {
56
+ const authState = useContext(AuthContext);
57
+ useEffect(() => {
58
+ if (typeof window === "undefined") return;
59
+ const pathname = window.location.pathname;
60
+ const pageAccess = accessMap[pathname];
61
+ if (!pageAccess || pageAccess === "public") return;
62
+ if (!authState.authenticated) {
63
+ const loginUrl = config.provider.loginUrl;
64
+ if (loginUrl) {
65
+ window.location.href = buildLoginUrl(loginUrl, pathname);
66
+ }
67
+ return;
68
+ }
69
+ if (pageAccess !== "authenticated") {
70
+ const requiredGroups = pageAccess.split(",");
71
+ const hasAccess = requiredGroups.some(
72
+ (g) => authState.groups.includes(g)
73
+ );
74
+ if (!hasAccess) {
75
+ if (config.unauthorizedBehavior === "404") {
76
+ window.location.href = "/404";
77
+ } else {
78
+ const loginUrl = config.provider.loginUrl;
79
+ if (loginUrl) {
80
+ window.location.href = buildLoginUrl(loginUrl, pathname);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }, [authState, accessMap, config]);
86
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
87
+ }
88
+ function useAuth() {
89
+ const state = useContext(AuthContext);
90
+ return {
91
+ ...state,
92
+ login() {
93
+ console.warn("[xyd:access-control] login() called without config");
94
+ },
95
+ logout() {
96
+ if (typeof window === "undefined") return;
97
+ const cookieName = "xyd-auth-token";
98
+ localStorage.removeItem(cookieName);
99
+ document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
100
+ document.cookie = `${cookieName}-state=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
101
+ window.__xydAuthState = {
102
+ authenticated: false,
103
+ groups: [],
104
+ token: null
105
+ };
106
+ document.documentElement.setAttribute("data-auth", "anonymous");
107
+ window.location.reload();
108
+ }
109
+ };
110
+ }
111
+ export {
112
+ AuthGuard as default,
113
+ useAuth
114
+ };
115
+ //# sourceMappingURL=AuthGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/AuthGuard.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useEffect,\n useState,\n useMemo,\n} from \"react\";\n\nexport interface AuthState {\n authenticated: boolean;\n groups: string[];\n token: string | null;\n}\n\nconst AuthContext = createContext<AuthState>({\n authenticated: false,\n groups: [],\n token: null,\n});\n\ndeclare global {\n interface Window {\n __xydAuthState?: AuthState;\n __xydAuthDebug?: any;\n }\n}\n\ninterface AuthGuardProps {\n children: React.ReactNode;\n accessMap: Record<string, string>;\n config: {\n provider: { type: string; loginUrl?: string };\n unauthorizedBehavior?: \"redirect\" | \"404\";\n session?: { cookieName?: string };\n };\n}\n\nfunction buildLoginUrl(loginUrl: string, returnPath: string): string {\n const separator = loginUrl.includes(\"?\") ? \"&\" : \"?\";\n return `${loginUrl}${separator}redirect=${encodeURIComponent(returnPath)}`;\n}\n\n/**\n * AuthGuard wraps the application and enforces access control.\n * It reads auth state from window.__xydAuthState (set by pre-hydration script)\n * and checks the access map for the current route.\n */\nexport default function AuthGuard({ children, accessMap, config }: AuthGuardProps) {\n const [authState, setAuthState] = useState<AuthState>(() => {\n if (typeof window !== \"undefined\" && window.__xydAuthState) {\n return window.__xydAuthState;\n }\n return { authenticated: false, groups: [], token: null };\n });\n\n useEffect(() => {\n // Listen for auth state changes (e.g., after login callback)\n const handleStorage = (e: StorageEvent) => {\n const cookieName = config.session?.cookieName || \"xyd-auth-token\";\n if (e.key === cookieName) {\n if (e.newValue) {\n try {\n const payload = JSON.parse(atob(e.newValue.split(\".\")[1]));\n setAuthState({\n authenticated: true,\n groups: payload.groups || [],\n token: e.newValue,\n });\n } catch {\n setAuthState({ authenticated: false, groups: [], token: null });\n }\n } else {\n setAuthState({ authenticated: false, groups: [], token: null });\n }\n }\n };\n\n window.addEventListener(\"storage\", handleStorage);\n return () => window.removeEventListener(\"storage\", handleStorage);\n }, [config.session?.cookieName]);\n\n const value = useMemo(() => authState, [authState]);\n\n return (\n <AuthContext.Provider value={value}>\n <AuthEnforcer accessMap={accessMap} config={config}>\n {children}\n </AuthEnforcer>\n </AuthContext.Provider>\n );\n}\n\n/**\n * Enforces access control on route changes.\n */\nfunction AuthEnforcer({\n children,\n accessMap,\n config,\n}: {\n children: React.ReactNode;\n accessMap: Record<string, string>;\n config: AuthGuardProps[\"config\"];\n}) {\n const authState = useContext(AuthContext);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const pathname = window.location.pathname;\n const pageAccess = accessMap[pathname];\n\n // No access entry or public = allow\n if (!pageAccess || pageAccess === \"public\") return;\n\n // Requires auth but user is not authenticated\n if (!authState.authenticated) {\n const loginUrl = config.provider.loginUrl;\n if (loginUrl) {\n window.location.href = buildLoginUrl(loginUrl, pathname);\n }\n return;\n }\n\n // Group-based access\n if (pageAccess !== \"authenticated\") {\n const requiredGroups = pageAccess.split(\",\");\n const hasAccess = requiredGroups.some((g) =>\n authState.groups.includes(g)\n );\n if (!hasAccess) {\n if (config.unauthorizedBehavior === \"404\") {\n // Replace with a 404-like state\n window.location.href = \"/404\";\n } else {\n const loginUrl = config.provider.loginUrl;\n if (loginUrl) {\n window.location.href = buildLoginUrl(loginUrl, pathname);\n }\n }\n }\n }\n }, [authState, accessMap, config]);\n\n return <>{children}</>;\n}\n\n/**\n * Hook to access authentication state.\n */\nexport function useAuth(): AuthState & {\n login: () => void;\n logout: () => void;\n} {\n const state = useContext(AuthContext);\n\n return {\n ...state,\n login() {\n // Will be overridden by config at runtime\n console.warn(\"[xyd:access-control] login() called without config\");\n },\n logout() {\n if (typeof window === \"undefined\") return;\n\n const cookieName = \"xyd-auth-token\";\n localStorage.removeItem(cookieName);\n document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;\n document.cookie = `${cookieName}-state=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;\n\n window.__xydAuthState = {\n authenticated: false,\n groups: [],\n token: null,\n };\n document.documentElement.setAttribute(\"data-auth\", \"anonymous\");\n window.location.reload();\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,158 @@
1
+ // src/components/LoginPage.tsx
2
+ import React2 from "react";
3
+
4
+ // src/components/AccessControlContext.tsx
5
+ import React, { createContext, useContext, useEffect, useState, useCallback } from "react";
6
+
7
+ // src/devOnly.ts
8
+ function isDevEnvironment() {
9
+ if (typeof window === "undefined") {
10
+ return process.env.NODE_ENV !== "production";
11
+ }
12
+ try {
13
+ return !!import.meta.env?.DEV;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ // src/components/AccessControlContext.tsx
20
+ var AccessControlCtx = createContext(null);
21
+ function useAccessControl() {
22
+ const ctx = useContext(AccessControlCtx);
23
+ if (!ctx) {
24
+ throw new Error("useAccessControl must be used inside <AccessControlProvider>");
25
+ }
26
+ return ctx;
27
+ }
28
+ function AccessControlProvider({ children }) {
29
+ const [config, setConfig] = useState(null);
30
+ const [error, setError] = useState("");
31
+ const [ready, setReady] = useState(false);
32
+ useEffect(() => {
33
+ const globalConfig = window.__xydAccessControlSettings?.accessControlConfig;
34
+ if (globalConfig) {
35
+ setConfig(globalConfig);
36
+ setReady(true);
37
+ return;
38
+ }
39
+ import("virtual:xyd-access-control-settings").then((mod) => {
40
+ setConfig(mod.accessControlConfig);
41
+ setReady(true);
42
+ }).catch(() => setReady(true));
43
+ }, []);
44
+ const providerType = config?.provider?.type || "jwt";
45
+ const loginUrl = config?.provider?.loginUrl || "";
46
+ const authorizationUrl = config?.provider?.authorizationUrl || "";
47
+ const hasExternalAuth = providerType === "oauth" ? !!authorizationUrl : !!loginUrl && loginUrl !== "/login";
48
+ const loginConfig = typeof config?.login === "string" ? {} : config?.login || {};
49
+ const title = loginConfig.title || "Sign in to access documentation";
50
+ const description = loginConfig.description || "";
51
+ const logo = loginConfig.logo || "";
52
+ const backgroundImage = loginConfig.backgroundImage || "";
53
+ const params = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
54
+ const redirectUrl = params?.get("redirect") || "/";
55
+ const storeTokenAndRedirect = useCallback((token) => {
56
+ const cookieName = config?.session?.cookieName || "xyd-auth-token";
57
+ localStorage.setItem(cookieName, token);
58
+ document.cookie = `${cookieName}=${token}; path=/; max-age=86400; SameSite=Lax`;
59
+ window.location.href = redirectUrl;
60
+ }, [config, redirectUrl]);
61
+ const signInWithOAuth = useCallback(() => {
62
+ if (!authorizationUrl) {
63
+ setError("No OAuth authorization URL configured");
64
+ return;
65
+ }
66
+ const callbackPath = config?.provider?.callbackPath || "/auth/callback";
67
+ const redirectUri = window.location.origin + callbackPath;
68
+ const scopes = config?.provider?.scopes || [];
69
+ const clientId = config?.provider?.clientId || "";
70
+ const oauthParams = new URLSearchParams({
71
+ client_id: clientId,
72
+ redirect_uri: redirectUri,
73
+ response_type: "code",
74
+ scope: scopes.join(" "),
75
+ state: redirectUrl
76
+ });
77
+ window.location.href = `${authorizationUrl}?${oauthParams.toString()}`;
78
+ }, [config, authorizationUrl, redirectUrl]);
79
+ const signInWithRedirect = useCallback(() => {
80
+ if (!loginUrl || loginUrl === "/login") {
81
+ setError("No external login URL configured");
82
+ return;
83
+ }
84
+ const sep = loginUrl.includes("?") ? "&" : "?";
85
+ window.location.href = `${loginUrl}${sep}redirect=${encodeURIComponent(redirectUrl)}`;
86
+ }, [loginUrl, redirectUrl]);
87
+ const signInWithGroups = useCallback((groups) => {
88
+ const hasDeploy = !!config?.deploy;
89
+ if (hasDeploy) {
90
+ const groupsParam = groups.length ? `&groups=${groups.join(",")}` : "";
91
+ window.location.href = `/auth/test-login?redirect=${encodeURIComponent(redirectUrl)}${groupsParam}`;
92
+ return;
93
+ }
94
+ if (!isDevEnvironment()) {
95
+ setError("Test login is only available in development mode.");
96
+ return;
97
+ }
98
+ const header = btoa(JSON.stringify({ alg: "HS256", typ: "JWT" }));
99
+ const payload = btoa(JSON.stringify({
100
+ sub: "test-user",
101
+ groups,
102
+ exp: Math.floor(Date.now() / 1e3) + 86400,
103
+ iat: Math.floor(Date.now() / 1e3)
104
+ }));
105
+ storeTokenAndRedirect(`${header}.${payload}.dGVzdA`);
106
+ }, [config, redirectUrl, storeTokenAndRedirect]);
107
+ const signInAsUser = useCallback(() => signInWithGroups([]), [signInWithGroups]);
108
+ const signInAsAdmin = useCallback(() => signInWithGroups(["admin"]), [signInWithGroups]);
109
+ const value = {
110
+ config,
111
+ ready,
112
+ error,
113
+ clearError: () => setError(""),
114
+ signInWithOAuth,
115
+ signInWithRedirect,
116
+ signInAsUser,
117
+ signInAsAdmin,
118
+ signInWithGroups,
119
+ providerType,
120
+ hasExternalAuth,
121
+ title,
122
+ description,
123
+ logo,
124
+ backgroundImage,
125
+ redirectUrl
126
+ };
127
+ return /* @__PURE__ */ React.createElement(AccessControlCtx.Provider, { value }, children);
128
+ }
129
+
130
+ // src/components/LoginPage.tsx
131
+ function LoginPage() {
132
+ return /* @__PURE__ */ React2.createElement(AccessControlProvider, null, /* @__PURE__ */ React2.createElement(LoginPageUI, null));
133
+ }
134
+ function LoginPageUI() {
135
+ const {
136
+ providerType,
137
+ hasExternalAuth,
138
+ title,
139
+ description,
140
+ logo,
141
+ backgroundImage,
142
+ error,
143
+ signInWithOAuth,
144
+ signInWithRedirect,
145
+ signInAsUser,
146
+ signInAsAdmin
147
+ } = useAccessControl();
148
+ const handleSignIn = () => {
149
+ if (providerType === "oauth") signInWithOAuth();
150
+ else signInWithRedirect();
151
+ };
152
+ const bgOverride = backgroundImage ? { backgroundImage: `url(${backgroundImage})`, backgroundSize: "cover", backgroundPosition: "center", background: "none" } : void 0;
153
+ return /* @__PURE__ */ React2.createElement("div", { className: "xyd-login-page", style: bgOverride }, /* @__PURE__ */ React2.createElement("div", { part: "card" }, logo && /* @__PURE__ */ React2.createElement("img", { part: "logo", src: logo, alt: "" }), /* @__PURE__ */ React2.createElement("h1", { part: "title" }, title), description && /* @__PURE__ */ React2.createElement("p", { part: "description" }, description), error && /* @__PURE__ */ React2.createElement("p", { part: "error" }, error), hasExternalAuth ? /* @__PURE__ */ React2.createElement("button", { part: "button", onClick: handleSignIn }, "Sign in") : /* @__PURE__ */ React2.createElement("div", { part: "actions" }, /* @__PURE__ */ React2.createElement("button", { part: "button", onClick: signInAsUser }, "Sign in as User"), /* @__PURE__ */ React2.createElement("button", { part: "button", "data-kind": "secondary", onClick: signInAsAdmin }, "Sign in as Admin"))));
154
+ }
155
+ export {
156
+ LoginPage as default
157
+ };
158
+ //# sourceMappingURL=LoginPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/LoginPage.tsx","../src/components/AccessControlContext.tsx","../src/devOnly.ts"],"sourcesContent":["import React from \"react\";\nimport { AccessControlProvider, useAccessControl } from \"./AccessControlContext\";\n\n/**\n * Default login page. Wraps itself with AccessControlProvider\n * so it works regardless of the host wrapper.\n */\nexport default function LoginPage() {\n return (\n <AccessControlProvider>\n <LoginPageUI />\n </AccessControlProvider>\n );\n}\n\nfunction LoginPageUI() {\n const {\n providerType,\n hasExternalAuth,\n title,\n description,\n logo,\n backgroundImage,\n error,\n signInWithOAuth,\n signInWithRedirect,\n signInAsUser,\n signInAsAdmin,\n } = useAccessControl();\n\n const handleSignIn = () => {\n if (providerType === \"oauth\") signInWithOAuth();\n else signInWithRedirect();\n };\n\n const bgOverride = backgroundImage\n ? { backgroundImage: `url(${backgroundImage})`, backgroundSize: \"cover\" as const, backgroundPosition: \"center\", background: \"none\" }\n : undefined;\n\n return (\n <div className=\"xyd-login-page\" style={bgOverride}>\n <div part=\"card\">\n {logo && <img part=\"logo\" src={logo} alt=\"\" />}\n\n <h1 part=\"title\">{title}</h1>\n\n {description && <p part=\"description\">{description}</p>}\n\n {error && <p part=\"error\">{error}</p>}\n\n {hasExternalAuth ? (\n <button part=\"button\" onClick={handleSignIn}>Sign in</button>\n ) : (\n <div part=\"actions\">\n <button part=\"button\" onClick={signInAsUser}>Sign in as User</button>\n <button part=\"button\" data-kind=\"secondary\" onClick={signInAsAdmin}>Sign in as Admin</button>\n </div>\n )}\n </div>\n </div>\n );\n}","import React, { createContext, useContext, useEffect, useState, useCallback } from \"react\";\nimport { isDevEnvironment as isDevEnv } from \"../devOnly\";\n\nexport interface AccessControlActions {\n /** Current auth config from docs.json */\n config: any | null;\n /** Whether config has loaded */\n ready: boolean;\n /** Last error message */\n error: string;\n /** Clear error */\n clearError: () => void;\n\n // --- Auth actions ---\n\n /** Redirect to external OAuth provider */\n signInWithOAuth: () => void;\n /** Redirect to external JWT login URL */\n signInWithRedirect: () => void;\n /** Sign in with test token (dev/edge server) */\n signInAsUser: () => void;\n /** Sign in as admin with test token (dev/edge server) */\n signInAsAdmin: () => void;\n /** Sign in with specific groups */\n signInWithGroups: (groups: string[]) => void;\n // --- Info ---\n\n /** Provider type: \"jwt\" | \"oauth\" */\n providerType: string;\n /** Whether an external auth URL is configured */\n hasExternalAuth: boolean;\n /** Login page title from config */\n title: string;\n /** Login page description from config */\n description: string;\n /** Login page logo URL from config */\n logo: string;\n /** Login page background image from config */\n backgroundImage: string;\n /** The redirect URL (where to go after login) */\n redirectUrl: string;\n}\n\nconst AccessControlCtx = createContext<AccessControlActions | null>(null);\n\n/**\n * Hook to access auth actions in custom login pages.\n *\n * @example\n * ```tsx\n * import { useAccessControl } from \"@xyd-js/plugin-access-control\"\n *\n * function MyLoginPage() {\n * const { signInWithOAuth, title, logo, error } = useAccessControl()\n *\n * return (\n * <div>\n * {logo && <img src={logo} />}\n * <h1>{title}</h1>\n * {error && <p>{error}</p>}\n * <button onClick={signInWithOAuth}>Sign in</button>\n * </div>\n * )\n * }\n * ```\n */\nexport function useAccessControl(): AccessControlActions {\n const ctx = useContext(AccessControlCtx);\n if (!ctx) {\n throw new Error(\"useAccessControl must be used inside <AccessControlProvider>\");\n }\n return ctx;\n}\n\nexport function AccessControlProvider({ children }: { children: React.ReactNode }) {\n const [config, setConfig] = useState<any>(null);\n const [error, setError] = useState(\"\");\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n const globalConfig = (window as any).__xydAccessControlSettings?.accessControlConfig;\n if (globalConfig) {\n setConfig(globalConfig);\n setReady(true);\n return;\n }\n // @ts-ignore\n import(\"virtual:xyd-access-control-settings\")\n .then((mod: any) => { setConfig(mod.accessControlConfig); setReady(true); })\n .catch(() => setReady(true));\n }, []);\n\n const providerType = config?.provider?.type || \"jwt\";\n const loginUrl = config?.provider?.loginUrl || \"\";\n const authorizationUrl = config?.provider?.authorizationUrl || \"\";\n const hasExternalAuth = providerType === \"oauth\"\n ? !!authorizationUrl\n : !!loginUrl && loginUrl !== \"/login\";\n\n // login can be a string (custom component path) or an object (config)\n const loginConfig = typeof config?.login === \"string\" ? {} : (config?.login || {});\n const title = loginConfig.title || \"Sign in to access documentation\";\n const description = loginConfig.description || \"\";\n const logo = loginConfig.logo || \"\";\n const backgroundImage = loginConfig.backgroundImage || \"\";\n\n const params = typeof window !== \"undefined\" ? new URLSearchParams(window.location.search) : null;\n const redirectUrl = params?.get(\"redirect\") || \"/\";\n\n const storeTokenAndRedirect = useCallback((token: string) => {\n const cookieName = config?.session?.cookieName || \"xyd-auth-token\";\n localStorage.setItem(cookieName, token);\n document.cookie = `${cookieName}=${token}; path=/; max-age=86400; SameSite=Lax`;\n window.location.href = redirectUrl;\n }, [config, redirectUrl]);\n\n const signInWithOAuth = useCallback(() => {\n if (!authorizationUrl) { setError(\"No OAuth authorization URL configured\"); return; }\n const callbackPath = config?.provider?.callbackPath || \"/auth/callback\";\n const redirectUri = window.location.origin + callbackPath;\n const scopes = config?.provider?.scopes || [];\n const clientId = config?.provider?.clientId || \"\";\n\n const oauthParams = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: scopes.join(\" \"),\n state: redirectUrl,\n });\n window.location.href = `${authorizationUrl}?${oauthParams.toString()}`;\n }, [config, authorizationUrl, redirectUrl]);\n\n const signInWithRedirect = useCallback(() => {\n if (!loginUrl || loginUrl === \"/login\") { setError(\"No external login URL configured\"); return; }\n const sep = loginUrl.includes(\"?\") ? \"&\" : \"?\";\n window.location.href = `${loginUrl}${sep}redirect=${encodeURIComponent(redirectUrl)}`;\n }, [loginUrl, redirectUrl]);\n\n const signInWithGroups = useCallback((groups: string[]) => {\n // Production with deploy config: use /auth/test-login (server-signed)\n const hasDeploy = !!config?.deploy;\n if (hasDeploy) {\n const groupsParam = groups.length ? `&groups=${groups.join(\",\")}` : \"\";\n window.location.href = `/auth/test-login?redirect=${encodeURIComponent(redirectUrl)}${groupsParam}`;\n return;\n }\n\n // Dev only: generate unsigned client-side token\n if (!isDevEnv()) {\n setError(\"Test login is only available in development mode.\");\n return;\n }\n const header = btoa(JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }));\n const payload = btoa(JSON.stringify({\n sub: \"test-user\",\n groups,\n exp: Math.floor(Date.now() / 1000) + 86400,\n iat: Math.floor(Date.now() / 1000),\n }));\n storeTokenAndRedirect(`${header}.${payload}.dGVzdA`);\n }, [config, redirectUrl, storeTokenAndRedirect]);\n\n const signInAsUser = useCallback(() => signInWithGroups([]), [signInWithGroups]);\n const signInAsAdmin = useCallback(() => signInWithGroups([\"admin\"]), [signInWithGroups]);\n\n const value: AccessControlActions = {\n config,\n ready,\n error,\n clearError: () => setError(\"\"),\n signInWithOAuth,\n signInWithRedirect,\n signInAsUser,\n signInAsAdmin,\n signInWithGroups,\n providerType,\n hasExternalAuth,\n title,\n description,\n logo,\n backgroundImage,\n redirectUrl,\n };\n\n return <AccessControlCtx.Provider value={value}>{children}</AccessControlCtx.Provider>;\n}","/**\n * Check if running in development mode.\n * Server-side: checks NODE_ENV\n * Client-side: checks import.meta.env (set by Vite)\n */\nexport function isDevEnvironment(): boolean {\n // Server-side (Node.js)\n if (typeof window === \"undefined\") {\n return process.env.NODE_ENV !== \"production\";\n }\n\n // Client-side (Vite)\n try {\n return !!(import.meta as any).env?.DEV;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if auth bypass is enabled. Only works in development mode.\n * Controlled by XYD_AUTH_BYPASS=1 environment variable.\n */\nexport function isAuthBypassed(): boolean {\n if (!isDevEnvironment()) return false;\n\n return (\n process.env.XYD_AUTH_BYPASS === \"1\" ||\n process.env.XYD_AUTH_BYPASS === \"true\"\n );\n}"],"mappings":"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","names":["React","React"]}