UNPKG

@deskree/atomic-auth-sdk

Version:

Unified Ory Kratos authentication SDK for frontend projects

630 lines (615 loc) 18.6 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { AuthProvider: () => AuthProvider2, OryAuthClient: () => OryAuthClient, createOryAuth: () => createOryAuth, useAuth: () => useAuth }); module.exports = __toCommonJS(index_exports); // src/client/index.ts var import_client = require("@ory/client"); var OryAuthClient = class { constructor(config) { var _a; this.basePath = config.basePath; this.client = new import_client.FrontendApi( new import_client.Configuration({ basePath: config.basePath, baseOptions: { withCredentials: (_a = config.withCredentials) != null ? _a : true } }) ); } async getSession() { try { const { data } = await this.client.toSession(); return { session: data, error: null }; } catch (error) { return { session: null, error }; } } async refreshSession() { try { const { data } = await this.client.toSession(); return { session: data, error: null }; } catch (error) { return { session: null, error }; } } async logout() { try { const { data } = await this.client.createBrowserLogoutFlow(); return { logoutUrl: data.logout_url, error: null }; } catch (error) { return { logoutUrl: null, error }; } } async login(returnTo) { try { const { data } = await this.client.createBrowserLoginFlow({ returnTo }); return { loginUrl: data.request_url, error: null }; } catch (error) { return { loginUrl: null, error }; } } async register(returnTo) { try { const { data } = await this.client.createBrowserRegistrationFlow({ returnTo }); return { registrationUrl: data.request_url, error: null }; } catch (error) { return { registrationUrl: null, error }; } } // Create a verification flow for email/phone verification async createVerificationFlow(returnTo) { try { const { data } = await this.client.createBrowserVerificationFlow({ returnTo }); return { verificationUrl: data.request_url, error: null, flowId: data.id }; } catch (error) { return { verificationUrl: null, error, flowId: null }; } } // Send verification email with code method async sendVerificationCode(email, flowId) { var _a, _b; try { const { data: flow } = await this.client.getVerificationFlow({ id: flowId }); const csrfToken = ((_a = flow.ui.nodes.find( (node) => { var _a2; return ((_a2 = node.attributes) == null ? void 0 : _a2.name) === "csrf_token"; } )) == null ? void 0 : _a.attributes) ? ((_b = flow.ui.nodes.find( (node) => { var _a2; return ((_a2 = node.attributes) == null ? void 0 : _a2.name) === "csrf_token"; } )) == null ? void 0 : _b.attributes).value : ""; const { data } = await this.client.updateVerificationFlow({ flow: flowId, updateVerificationFlowBody: { method: "code", email, csrf_token: csrfToken } }); return { success: true, error: null, flow: data }; } catch (error) { return { success: false, error, flow: null }; } } // Verify code for email verification async verifyCode(flowId, code) { var _a, _b; try { const { data: flow } = await this.client.getVerificationFlow({ id: flowId }); const csrfToken = ((_a = flow.ui.nodes.find( (node) => { var _a2; return ((_a2 = node.attributes) == null ? void 0 : _a2.name) === "csrf_token"; } )) == null ? void 0 : _a.attributes) ? ((_b = flow.ui.nodes.find( (node) => { var _a2; return ((_a2 = node.attributes) == null ? void 0 : _a2.name) === "csrf_token"; } )) == null ? void 0 : _b.attributes).value : ""; const { data } = await this.client.updateVerificationFlow({ flow: flowId, updateVerificationFlowBody: { method: "code", code, csrf_token: csrfToken } }); const isVerified = data.state === "passed_challenge"; return { success: isVerified, error: null, flow: data }; } catch (error) { return { success: false, error, flow: null }; } } // Get verification flow status async getVerificationFlow(flowId) { try { const { data } = await this.client.getVerificationFlow({ id: flowId }); return { flow: data, error: null }; } catch (error) { return { flow: null, error }; } } // Check if identity is verified based on the session isIdentityVerified(session) { if (!session || !session.identity) return false; const identity = session.identity; const verifiedAddresses = identity.verifiable_addresses || []; return verifiedAddresses.length > 0; } }; // src/context/ClientAuthProvider.tsx var import_react3 = __toESM(require("react")); // src/context/AuthProvider.tsx var import_react2 = __toESM(require("react")); // src/session/validation.ts function extractUserFromSession(session) { var _a, _b; const identity = session.identity; return { id: identity.id, email: identity.traits.email, name: { first: (_a = identity.traits.name) == null ? void 0 : _a.first, last: (_b = identity.traits.name) == null ? void 0 : _b.last } }; } function isSessionValid(session) { if (!session) return false; if (session.expires_at) { const expiresAt = new Date(session.expires_at); if (expiresAt < /* @__PURE__ */ new Date()) return false; } return true; } // src/session/storage.ts var isBrowser = typeof window !== "undefined"; function saveUserToStorage(userData, key) { if (!isBrowser) return; try { localStorage.setItem(key, JSON.stringify(userData)); } catch (error) { console.error("Failed to save user data to localStorage:", error); } } function getUserFromStorage(key) { if (!isBrowser) return null; try { const data = localStorage.getItem(key); return data ? JSON.parse(data) : null; } catch (error) { console.error("Failed to parse user data from localStorage:", error); return null; } } function removeUserFromStorage(key) { if (!isBrowser) return; localStorage.removeItem(key); } // src/context/auth-context.ts var import_react = require("react"); var AuthContext = (0, import_react.createContext)({ isLoading: true, isAuthenticated: false, user: null, session: null, error: null, isVerified: false, login: async () => { }, logout: async () => { }, refresh: async () => { }, refreshSession: async () => false, startVerification: async () => ({ verificationUrl: null, error: null, flowId: null }), sendVerificationCode: async () => ({ success: false, error: null, flow: null }), verifyCode: async () => ({ success: false, error: null, flow: null }), getVerificationFlow: async () => ({ flow: null, error: null }), checkVerification: async () => false }); // src/context/AuthProvider.tsx function AuthProvider({ children, client, loginPath = "/auth/login", storageKey = "user_data", basePath = process.env.NEXT_PUBLIC_ORY_SDK_URL || "http://localhost:4455" }) { const [authClient] = (0, import_react2.useState)( () => client || new OryAuthClient({ basePath }) ); const [isLoading, setIsLoading] = (0, import_react2.useState)(true); const [session, setSession] = (0, import_react2.useState)(null); const [user, setUser] = (0, import_react2.useState)(null); const [error, setError] = (0, import_react2.useState)(null); const [isVerified, setIsVerified] = (0, import_react2.useState)(false); const extractUserFromSession2 = (session2) => { var _a, _b; const identity = session2.identity; const verifiedAddresses = identity.verifiable_addresses || []; const isVerified2 = verifiedAddresses.length > 0; return { id: identity.id, email: identity.traits.email, verified: isVerified2, name: { first: (_a = identity.traits.name) == null ? void 0 : _a.first, last: (_b = identity.traits.name) == null ? void 0 : _b.last } }; }; const refresh = async () => { try { setIsLoading(true); const { session: newSession, error: error2 } = await authClient.getSession(); if (error2) throw error2; if (!(newSession && isSessionValid(newSession))) { throw new Error("Invalid session"); } setSession(newSession); const userData = extractUserFromSession2(newSession); setUser(userData); saveUserToStorage(userData, storageKey); setError(null); } catch (err) { setError(err instanceof Error ? err : new Error("Failed to refresh session")); setSession(null); setUser(null); removeUserFromStorage(storageKey); if (typeof window !== "undefined" && window.location.pathname !== loginPath) { window.location.href = loginPath; } } finally { setIsLoading(false); } }; const refreshSession = async () => { try { setIsLoading(true); if (session && session.expires_at && new Date(session.expires_at).getTime() < Date.now()) { window.location.href = loginPath; return false; } const { session: newSession, error: error2 } = await authClient.refreshSession(); if (error2) { throw error2; } if (newSession) { setSession(newSession); const userData = extractUserFromSession2(newSession); setUser(userData); setIsVerified(userData.verified); saveUserToStorage(userData, storageKey); return true; } else { setSession(null); setUser(null); setIsVerified(false); removeUserFromStorage(storageKey); window.location.href = loginPath; return false; } setError(null); } catch (err) { setError( err instanceof Error ? err : new Error("Failed to refresh session") ); setSession(null); setUser(null); setIsVerified(false); removeUserFromStorage(storageKey); return false; } finally { setIsLoading(false); } }; const login = async (returnTo) => { const { loginUrl, error: error2 } = await authClient.login(returnTo); if (error2) { setError( error2 instanceof Error ? error2 : new Error("Failed to initiate login") ); return; } if (loginUrl) { window.location.href = loginUrl; } else { window.location.href = loginPath; } }; const logout = async () => { try { const { logoutUrl, error: error2 } = await authClient.logout(); if (error2) throw error2; removeUserFromStorage(storageKey); setUser(null); setSession(null); if (logoutUrl) { window.location.href = logoutUrl; } } catch (err) { setError(err instanceof Error ? err : new Error("Failed to logout")); } }; const startVerification = async (options = {}) => { const result = await authClient.createVerificationFlow(options.returnTo); return { verificationUrl: result.verificationUrl || null, error: result.error ? result.error : null, flowId: result.flowId || null }; }; const sendVerificationCode = async (email, flowId) => { const result = await authClient.sendVerificationCode(email, flowId); return { success: result.success, error: result.error ? result.error : null, flow: result.flow }; }; const verifyCode = async (flowId, code) => { const result = await authClient.verifyCode(flowId, code); if (result.success) { await refreshSession(); } return { success: result.success, error: result.error ? result.error : null, flow: result.flow }; }; const getVerificationFlow = async (flowId) => { const result = await authClient.getVerificationFlow(flowId); return { flow: result.flow, error: result.error ? result.error : null }; }; const checkVerification = async () => { try { await refreshSession(); return isVerified; } catch (err) { return false; } }; (0, import_react2.useEffect)(() => { const savedUser = getUserFromStorage(storageKey); if (savedUser) { setUser(savedUser); setIsVerified(savedUser.verified || false); } refresh(); }, []); (0, import_react2.useEffect)(() => { if (!session) { return; } const refreshTimer = setInterval(() => { refreshSession(); }, 5 * 60 * 1e3); return () => clearInterval(refreshTimer); }, [session]); const value = { isLoading, isAuthenticated: !!session, user, session, isVerified, error, login, logout, refreshSession, startVerification, sendVerificationCode, verifyCode, getVerificationFlow, checkVerification, refresh }; return /* @__PURE__ */ import_react2.default.createElement(AuthContext.Provider, { value }, children); } // src/context/ClientAuthProvider.tsx function AuthProvider2({ children, basePath = process.env.NEXT_PUBLIC_ORY_SDK_URL || "http://localhost:4433" }) { const client = new OryAuthClient({ basePath, withCredentials: true }); return /* @__PURE__ */ import_react3.default.createElement(AuthProvider, { client }, children); } // src/hooks/useAuth.ts var import_react4 = require("react"); function useAuth() { const context = (0, import_react4.useContext)(AuthContext); if (context === void 0) { throw new Error("useAuth must be used within an AuthProvider"); } return context; } // src/createOryAuth.tsx var import_react6 = __toESM(require("react")); // src/middleware/withAuth.tsx var import_react5 = __toESM(require("react")); var import_router = require("next/router"); function withAuth(Component, options = {}) { const WithAuthComponent = (props) => { const router = (0, import_router.useRouter)(); const { isAuthenticated, isLoading } = useAuth(); const loginPath = options.loginPath || "/login"; (0, import_react5.useEffect)(() => { if (!isLoading && !isAuthenticated) { const returnPath = encodeURIComponent(router.asPath); router.replace(`${loginPath}?returnTo=${returnPath}`); } }, [isLoading, isAuthenticated, router, loginPath]); if (isLoading) { return options.loadingComponent || /* @__PURE__ */ import_react5.default.createElement("div", null, "Loading..."); } if (!isAuthenticated) { return null; } return /* @__PURE__ */ import_react5.default.createElement(Component, { ...props }); }; WithAuthComponent.displayName = `withAuth(${Component.displayName || Component.name || "Component"})`; return WithAuthComponent; } // src/middleware/createAuthApi.ts function createAuthApi(handler, options) { return async (req, res) => { try { const { session, error } = await options.client.getSession(); if (error || !session || !isSessionValid(session)) { return res.status(401).json({ error: "Unauthorized", message: "You must be logged in to access this resource" }); } const user = extractUserFromSession(session); return handler(req, res, user); } catch (error) { console.error("Auth middleware error:", error); return res.status(500).json({ error: "Internal server error", message: "An error occurred while processing your request" }); } }; } // src/createOryAuth.tsx function createOryAuth(config) { const client = new OryAuthClient({ basePath: config.oryUrl, withCredentials: true }); function AuthProvider3({ children }) { return /* @__PURE__ */ import_react6.default.createElement(AuthProvider, { client }, children); } const refreshSession = async () => { const { session, error } = await client.refreshSession(); return { session, error }; }; const sendVerificationCode = async (email, flowId) => { return await client.sendVerificationCode(email, flowId); }; const verifyCode = async (flowId, code) => { return await client.verifyCode(flowId, code); }; const getVerificationFlow = async (flowId) => { return await client.getVerificationFlow(flowId); }; return { AuthProvider: AuthProvider3, useAuth, refreshSession, sendVerificationCode, verifyCode, getVerificationFlow, withAuth: (Component, options = {}) => withAuth(Component, { client, loginPath: config.loginPath, ...options }), createAuthApi: (handler) => createAuthApi(handler, { client }) }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AuthProvider, OryAuthClient, createOryAuth, useAuth });