UNPKG

@softlock/sdk

Version:

Official Softlock SDK for access key validation and management

694 lines (548 loc) 16 kB
# Softlock SDK Official SDK for integrating Softlock access key validation into your applications. ## Installation ```bash npm install @softlock/sdk # or yarn add @softlock/sdk # or pnpm add @softlock/sdk ``` ## Architecture The SDK is split into client and server modules to ensure compatibility with Next.js App Router and React Server Components: - **`@softlock/sdk/client`** - React hooks and components (client-side only) - **`@softlock/sdk/server`** - Validation utilities and middleware (server-safe) - **`@softlock/sdk`** - Core client and server-safe utilities ## Quick Start ### Server-Side Validation ```typescript // Server components, API routes, middleware import { initSoftlock, validateKey } from "@softlock/sdk/server"; // Initialize with your tenant ID initSoftlock({ tenantId: "your-tenant-id", // Your Discord server ID or tenant UUID baseUrl: "https://your-softlock-instance.com", // Optional: custom instance cacheTtl: 300000, // Optional: cache TTL in ms (default: 5 minutes) debug: true, // Optional: enable debug logging }); async function checkAccess(userKey: string) { const result = await validateKey(userKey); if (result.valid) { console.log("Access granted!", result.key); } else { console.log("Access denied:", result.error); } } ``` ### Client-Side Usage ```typescript // Client components only import { initSoftlock } from "@softlock/sdk/client"; // Initialize on client-side (typically in a provider or root component) initSoftlock({ tenantId: "your-tenant-id", baseUrl: "https://your-softlock-instance.com", }); ``` ## React Integration ### Using Hooks ```tsx "use client"; // Required for client components import { useAccessKey } from "@softlock/sdk/client"; import { useState } from "react"; function AccessChecker() { const { result, loading, error, validate } = useAccessKey(); const [keyInput, setKeyInput] = useState(""); const handleValidate = () => { validate(keyInput); }; return ( <div> <input value={keyInput} onChange={(e) => setKeyInput(e.target.value)} placeholder="Enter your access key" /> <button onClick={handleValidate} disabled={loading}> {loading ? "Validating..." : "Validate"} </button> {result && <div>{result.valid ? "✅ Valid" : "❌ Invalid"}</div>} </div> ); } ``` ### Using Components ```tsx "use client"; // Required for client components import { AccessKeyValidator, AccessGuard, AccessStatus, } from "@softlock/sdk/client"; import { useState } from "react"; function App() { const [userKey, setUserKey] = useState(""); return ( <div> {/* Built-in validator component */} <AccessKeyValidator placeholder="Enter your access key..." onValidation={(result) => { if (result.valid) { setUserKey(result.key?.key_value || ""); } }} autoValidate={true} /> {/* Protected content */} <AccessGuard keyValue={userKey} fallback={<div>You need a valid access key to see this content.</div>} > <h1>Secret Content!</h1> <p>This content is only visible to users with valid access keys.</p> </AccessGuard> {/* Show access status */} <AccessStatus keyValue={userKey} showDetails={true} refreshInterval={60000} // Refresh every minute /> </div> ); } ``` ## Middleware Protection ### Express.js ```javascript import express from "express"; import { createExpressMiddleware, initSoftlock } from "@softlock/sdk/server"; const app = express(); // Initialize Softlock first initSoftlock({ tenantId: "your-tenant-id" }); // Protect routes const softlockAuth = createExpressMiddleware({ extractKey: (req) => req.headers["x-api-key"], // Custom extraction onUnauthorized: (req, res) => { res.status(403).json({ error: "Access denied" }); }, }); app.get("/protected", softlockAuth, (req, res) => { // Access granted! User data available in req.softlock res.json({ message: "Hello!", user: req.softlock.user, }); }); ``` ### Next.js API Routes ```typescript import { withSoftlockAuth } from "@softlock/sdk/server"; async function handler(req: NextApiRequest, res: NextApiResponse) { // This handler only runs if access key is valid // User data available in req.softlock res.json({ message: "Protected data", user: req.softlock?.user, }); } export default withSoftlockAuth(handler); ``` ### Next.js Middleware #### Basic Protection ```typescript // middleware.ts import { NextRequest } from "next/server"; import { createNextMiddleware } from "@softlock/sdk/server"; export async function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith("/protected")) { return createNextMiddleware()(request); } } export const config = { matcher: "/protected/:path*", }; ``` #### Advanced Protection with Redirects ```typescript // middleware.ts import { NextRequest, NextResponse } from "next/server"; import { createNextMiddleware, initSoftlock } from "@softlock/sdk/server"; initSoftlock({ tenantId: "your-tenant-id", baseUrl: "https://your-softlock-instance.com", debug: true, }); export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; // Skip beta-access page to avoid redirect loops if (pathname === "/beta-access") { return NextResponse.next(); } // Check if user has valid access key const validKey = request.cookies.get("softlock_valid_key")?.value; // If no valid key, redirect to beta access page if (!validKey) { return NextResponse.redirect(new URL("/beta-access", request.url)); } // Use Softlock middleware for additional validation return createNextMiddleware({ extractKey: (req) => { return req.cookies.get("softlock_valid_key")?.value || ""; }, })(request); } export const config = { matcher: ["/dashboard/:path*", "/premium/:path*"], }; ``` ## Advanced Usage ### Custom Client Configuration ```typescript // Server-side import { SoftlockClient } from "@softlock/sdk/server"; const client = new SoftlockClient({ tenantId: "your-tenant-id", baseUrl: "https://api.yourapp.com", apiKey: "your-api-key", // For server-side validation cacheTtl: 600000, // 10 minutes debug: false, }); // Use client directly const result = await client.validateKey("ak_..."); ``` ### Access Management Hook ```tsx "use client"; import { useAccessKey, useUserAccess } from "@softlock/sdk/client"; import { useState, useEffect } from "react"; function AccessManager() { const [userKey, setUserKey] = useState(""); const { result: keyResult } = useAccessKey(userKey, { autoValidate: true }); const { user, loading, error, refreshAccess } = useUserAccess(); useEffect(() => { if (keyResult?.valid) { // Store valid key localStorage.setItem("access_key", userKey); } }, [keyResult, userKey]); return ( <div> <input value={userKey} onChange={(e) => setUserKey(e.target.value)} placeholder="Access key" /> {keyResult?.valid && ( <div> <h3>Access Granted</h3> <p>User: {user?.discord_tag}</p> <button onClick={refreshAccess}>Refresh Access</button> </div> )} </div> ); } ``` ## Next.js App Router Integration ### Provider Setup ```tsx // app/providers.tsx "use client"; import { initSoftlock } from "@softlock/sdk/client"; import { useEffect } from "react"; export function SoftlockProvider({ children }: { children: React.ReactNode }) { useEffect(() => { initSoftlock({ tenantId: process.env.NEXT_PUBLIC_TENANT_ID!, baseUrl: process.env.NEXT_PUBLIC_SOFTLOCK_URL, debug: process.env.NODE_ENV === "development", }); }, []); return <>{children}</>; } ``` ```tsx // app/layout.tsx import { SoftlockProvider } from "./providers"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <SoftlockProvider>{children}</SoftlockProvider> </body> </html> ); } ``` ### Server Components with Validation ```tsx // app/protected/page.tsx import { validateKey } from "@softlock/sdk/server"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; export default async function ProtectedPage() { const cookieStore = cookies(); const accessKey = cookieStore.get("access_key")?.value; if (!accessKey) { redirect("/beta-access"); } const result = await validateKey(accessKey); if (!result.valid) { redirect("/beta-access"); } return ( <div> <h1>Protected Content</h1> <p>Welcome, {result.key?.discord_tag}!</p> </div> ); } ``` import { useUserAccess } from "@softlock/sdk"; function UserDashboard({ userId }: { userId: string }) { const { hasAccess, loading, checkAccess, revokeAccess } = useUserAccess(userId); return ( <div> {loading && <div>Checking access...</div>} {hasAccess === true && ( <div> <h2>Welcome! You have access.</h2> <button onClick={revokeAccess}>Revoke Access</button> </div> )} {hasAccess === false && ( <div> <h2>Access Required</h2> <button onClick={() => checkAccess("ak_...")}> Check Access Key </button> </div> )} </div> ); } ```` ### Access Guard with Redirect ```tsx import { AccessGuard } from "@softlock/sdk"; function ProtectedPage() { return ( <AccessGuard keyValue={getUserKey()} // Your function to get user's key redirectUrl="/login" onUnauthorized={() => { console.log("User tried to access protected content"); // Track analytics, show notification, etc. }} loadingComponent={<div>Verifying access...</div>} > <YourProtectedContent /> </AccessGuard> ); } ```` ## Accessing Discord User Information The SDK provides multiple ways to retrieve the Discord ID and other user information associated with an access key: ### 1. From Validation Result ```typescript import { validateKey } from "@softlock/sdk"; async function getUserInfo(accessKey: string) { const result = await validateKey(accessKey); if (result.valid && result.key) { const discordId = result.key.discord_user_id; const discordTag = result.key.discord_tag; // username#discriminator console.log("Discord User ID:", discordId); console.log("Discord Tag:", discordTag); return { discordId, discordTag, keyId: result.key.id, status: result.key.status, }; } return null; } ``` ### 2. In React Components (Hooks) ```tsx import { useAccessKey } from "@softlock/sdk"; function UserProfile() { const { result } = useAccessKey(); if (result?.valid && result.key) { return ( <div> <h3>User Information</h3> <p>Discord ID: {result.key.discord_user_id}</p> <p>Discord Tag: {result.key.discord_tag}</p> <p>Key Status: {result.key.status}</p> <p> Key Created: {new Date(result.key.created_at).toLocaleDateString()} </p> </div> ); } return <div>No valid access key found</div>; } ``` ### 3. In API Routes/Middleware ```typescript // Express.js app.get("/user-info", softlockAuth, (req, res) => { const discordId = req.softlock?.user?.discordId; const discordTag = req.softlock?.user?.discordTag; res.json({ discordId, discordTag, keyData: req.softlock?.key, }); }); // Next.js API Route export default withSoftlockAuth((req, res) => { const userInfo = { discordId: req.softlock?.user?.discordId, discordTag: req.softlock?.user?.discordTag, keyId: req.softlock?.key?.id, }; res.json({ user: userInfo }); }); ``` ### 4. In Next.js Middleware (Headers) ```typescript // middleware.ts export async function middleware(request: NextRequest) { // ... validation logic ... if (validationResult.valid && validationResult.key) { const requestHeaders = new Headers(request.headers); // Add Discord ID to headers for downstream use requestHeaders.set( "x-discord-user-id", validationResult.key.discord_user_id || "" ); requestHeaders.set("x-discord-tag", validationResult.key.discord_tag || ""); return NextResponse.next({ request: { headers: requestHeaders, }, }); } } // In your page/component, access via headers export async function getServerSideProps({ req }) { const discordId = req.headers["x-discord-user-id"]; const discordTag = req.headers["x-discord-tag"]; return { props: { discordId, discordTag }, }; } ``` ### 5. Using the Client Directly ```typescript import { SoftlockClient } from "@softlock/sdk"; const client = new SoftlockClient({ tenantId: "your-tenant-id", }); async function lookupUser(accessKey: string) { try { const validation = await client.validateKey(accessKey); if (validation.valid && validation.key) { return { discordId: validation.key.discord_user_id, discordTag: validation.key.discord_tag, keyDetails: { id: validation.key.id, status: validation.key.status, createdAt: validation.key.created_at, expiresAt: validation.key.expires_at, lastUsed: validation.key.used_at, }, }; } } catch (error) { console.error("Failed to validate key:", error); } return null; } ``` ## API Reference ### Configuration ```typescript interface SoftlockConfig { tenantId: string; // Required: Your tenant/server ID baseUrl?: string; // Optional: Custom API base URL apiKey?: string; // Optional: API key for server-side cacheTtl?: number; // Optional: Cache TTL in ms debug?: boolean; // Optional: Enable debug logging } ``` ### Validation Result ```typescript interface ValidationResult { valid: boolean; // Whether the key is valid key?: AccessKey; // Key details if valid error?: string; // Error message if invalid cached?: boolean; // Whether result came from cache } ``` ### Access Key ```typescript interface AccessKey { id: string; // Unique key ID key_value: string; // The actual key value status: "active" | "revoked" | "expired"; discord_user_id?: string; // Associated Discord user discord_tag?: string; // Discord username#discriminator created_at: string; // ISO timestamp expires_at?: string; // ISO timestamp (if expires) used_at?: string; // ISO timestamp (when first used) } ``` ## Error Handling The SDK provides specific error types for better error handling: ```typescript import { SoftlockError, ValidationError, NetworkError, ConfigurationError, } from "@softlock/sdk"; try { const result = await validateKey("invalid-key"); } catch (error) { if (error instanceof ValidationError) { console.log("Key validation failed:", error.message); } else if (error instanceof NetworkError) { console.log("Network issue:", error.message); } else if (error instanceof ConfigurationError) { console.log("Configuration issue:", error.message); } } ``` ## Best Practices 1. **Initialize Early**: Call `initSoftlock()` at the top level of your app 2. **Cache Wisely**: Use appropriate cache TTL based on your security needs 3. **Handle Errors**: Always handle validation errors gracefully 4. **Secure Keys**: Never expose API keys in client-side code 5. **Monitor Usage**: Use debug mode during development ## TypeScript Support The SDK is written in TypeScript and provides full type safety: ```typescript import type { ValidationResult, AccessKey, SoftlockConfig, } from "@softlock/sdk"; // All types are exported and ready to use const config: SoftlockConfig = { tenantId: "your-tenant-id", }; ``` ## Contributing See the [main repository](https://github.com/eXt-Ra/softlock) for contribution guidelines. ## License MIT