@softlock/sdk
v0.0.11
Published
Official Softlock SDK for access key validation and management
Downloads
38
Maintainers
Readme
Softlock SDK
Official SDK for integrating Softlock access key validation into your applications.
Installation
npm install @softlock/sdk
# or
yarn add @softlock/sdk
# or
pnpm add @softlock/sdkArchitecture
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
// 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
// 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
"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
"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
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
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
// 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
// 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
// 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
"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
// 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}</>;
}// 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
// 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 (
{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
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)
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
// 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)
// 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
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
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
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
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:
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
- Initialize Early: Call
initSoftlock()at the top level of your app - Cache Wisely: Use appropriate cache TTL based on your security needs
- Handle Errors: Always handle validation errors gracefully
- Secure Keys: Never expose API keys in client-side code
- Monitor Usage: Use debug mode during development
TypeScript Support
The SDK is written in TypeScript and provides full type safety:
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 for contribution guidelines.
License
MIT
