@mgcmnd/auth-client
Version:
Client-side authentication provider for mgcmnd.net services.
226 lines (223 loc) • 7.01 kB
JavaScript
;
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