UNPKG

@mgcmnd/auth-client

Version:

Client-side authentication provider for mgcmnd.net services.

226 lines (223 loc) 7.01 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { AuthCallbackHandler: () => AuthCallbackHandler, AuthProvider: () => AuthProvider, useAuth: () => useAuth }); module.exports = __toCommonJS(index_exports); // src/AuthContext.tsx var import_react = require("react"); var import_jwt_decode = require("jwt-decode"); var import_jsx_runtime = require("react/jsx-runtime"); var DEFAULT_CONFIG = { authServerUrl: "https://auth.mgcmnd.net", tokenStorageKey: "mgcmnd_auth_token", redirectPath: "/auth/callback", onLoginSuccess: () => { }, onLogoutSuccess: () => { } }; var AuthContext = (0, import_react.createContext)(void 0); var AuthProvider = ({ children, config = {} }) => { const cfg = { ...DEFAULT_CONFIG, ...config }; const [state, setState] = (0, import_react.useState)({ isAuthenticated: false, user: null, isLoading: false, error: null, token: null }); const generateState = () => { const array = new Uint8Array(32); window.crypto.getRandomValues(array); return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(""); }; const getRedirectUri = (path) => { const basePath = path || cfg.redirectPath; return `${window.location.origin}${basePath}`; }; const decodeAndSetToken = (token, skipCallback = false) => { try { if (state.token === token && state.isAuthenticated) { return; } const decoded = (0, import_jwt_decode.jwtDecode)(token); const user = { id: decoded.sub || decoded.id, email: decoded.email, name: decoded.name, picture: decoded.picture, ...decoded }; localStorage.setItem(cfg.tokenStorageKey, token); setState({ isAuthenticated: true, user, isLoading: false, error: null, token }); if (!skipCallback) { cfg.onLoginSuccess(user, token); } } catch (error) { setState((prev) => ({ ...prev, isLoading: false, error: "Invalid token format" })); } }; (0, import_react.useEffect)(() => { const token = localStorage.getItem(cfg.tokenStorageKey); if (token) { decodeAndSetToken(token, true); } }, []); const loginWithProvider = (provider, redirectPath) => { const state2 = generateState(); sessionStorage.setItem("oauth_state", state2); const params = new URLSearchParams({ redirect_uri: getRedirectUri(redirectPath), state: state2 }); window.location.href = `${cfg.authServerUrl}/${provider}/login?${params}`; }; const loginWithGoogle = (redirectPath) => loginWithProvider("google", redirectPath); const loginWithGitHub = (redirectPath) => loginWithProvider("github", redirectPath); const loginWithEmail = async (email, redirectPath) => { setState((prev) => ({ ...prev, isLoading: true, error: null })); try { const state2 = generateState(); sessionStorage.setItem("oauth_state", state2); const response = await fetch(`${cfg.authServerUrl}/email/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, redirect_uri: getRedirectUri(redirectPath), state: state2 }) }); if (!response.ok) { throw new Error("Failed to request email login"); } setState((prev) => ({ ...prev, isLoading: false })); } catch (error) { setState((prev) => ({ ...prev, isLoading: false, error: error instanceof Error ? error.message : "Email login failed" })); throw error; } }; const logout = (redirectUrl) => { localStorage.removeItem(cfg.tokenStorageKey); setState({ isAuthenticated: false, user: null, isLoading: false, error: null, token: null }); cfg.onLogoutSuccess(); if (redirectUrl) { window.location.href = redirectUrl; } }; const handleCallback = () => { const params = new URLSearchParams(window.location.search); const token = params.get("token"); const stateParam = params.get("state"); const error = params.get("error"); const storedState = sessionStorage.getItem("oauth_state"); sessionStorage.removeItem("oauth_state"); window.history.replaceState({}, document.title, window.location.pathname); if (error) { setState((prev) => ({ ...prev, error })); return; } if (stateParam !== storedState) { setState((prev) => ({ ...prev, error: "Invalid state parameter" })); return; } if (token) { decodeAndSetToken(token, false); } else { setState((prev) => ({ ...prev, error: "No token received" })); } }; const value = { ...state, loginWithGoogle, loginWithGitHub, loginWithEmail, logout, handleCallback }; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value, children }); }; var useAuth = () => { const context = (0, import_react.useContext)(AuthContext); if (!context) { throw new Error("useAuth must be used within an AuthProvider"); } return context; }; // src/AuthCallbackHandler.tsx var import_react2 = require("react"); var import_jsx_runtime2 = require("react/jsx-runtime"); var AuthCallbackHandler = ({ onSuccess, onError, loading = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "Processing authentication..." }) }) => { const { handleCallback, isAuthenticated, error, isLoading } = useAuth(); (0, import_react2.useEffect)(() => { handleCallback(); }, []); (0, import_react2.useEffect)(() => { if (!isLoading) { if (isAuthenticated && onSuccess) { onSuccess(); } else if (error && onError) { onError(error); } } }, [isLoading, isAuthenticated, error, onSuccess, onError]); if (isLoading) { return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: loading }); } return null; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AuthCallbackHandler, AuthProvider, useAuth }); //# sourceMappingURL=index.cjs.map