@deskree/atomic-auth-sdk
Version:
Unified Ory Kratos authentication SDK for frontend projects
627 lines (612 loc) • 18.3 kB
JavaScript
;
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 = "/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)) {
setSession(newSession);
const userData = extractUserFromSession2(newSession);
setUser(userData);
saveUserToStorage(userData, storageKey);
} else {
setSession(null);
setUser(null);
removeUserFromStorage(storageKey);
}
setError(null);
} catch (err) {
setError(
err instanceof Error ? err : new Error("Failed to refresh session")
);
setSession(null);
setUser(null);
removeUserFromStorage(storageKey);
} finally {
setIsLoading(false);
}
};
const refreshSession = async () => {
try {
setIsLoading(true);
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);
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);
}
refreshSession();
}, []);
(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
});