@verb-js/allow
v0.0.1
Published
Authentication library for Verb framework
Readme
Allow
Authentication library for Verb. Sessions, JWT, OAuth, and account linking.
Part of the Verb Ecosystem
| Package | Description | |---------|-------------| | Verb | Fast web framework for Bun | | Hull | Ecto-inspired database toolkit | | Allow | Authentication library (this repo) | | Hoist | Deployment platform |
Features
- 🔐 Multiple Authentication Strategies: Local (email/password), OAuth (GitHub, Google, Discord), JWT, and custom strategies
- 🔗 Account Linking: Users can link multiple authentication methods to a single account
- 🗄️ Database Integration: Optional SQLite/PostgreSQL storage for user data and sessions
- 🧩 TypeScript First: Full type safety and excellent developer experience
- 🚀 Bun Optimized: Built specifically for Bun runtime with native crypto and password hashing
- 🌐 Verb Integration: Seamless integration with Verb's request/response handling
- 🔧 Flexible Configuration: Configure strategies via TypeScript or store in database
- 🧪 Testing Ready: Comprehensive test suite with Bun test
Installation
bun add @verb-js/allowQuick Start
import { createServer } from "verb";
import { createAllow, getSessionMiddleware, getMiddleware, getHandlers } from "@verb-js/allow";
const allow = createAllow({
secret: "your-secret-key",
sessionDuration: 86400000, // 24 hours
database: {
type: "sqlite",
connection: "auth.db",
migrate: true
},
strategies: [
{
name: "local",
type: "local",
config: {
usernameField: "email",
passwordField: "password"
}
},
{
name: "github",
type: "oauth",
config: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
callbackURL: "http://localhost:3000/auth/github/callback",
scope: ["user:email"]
}
}
]
});
const app = createServer();
// Get middleware and handlers
const sessionMw = getSessionMiddleware(allow);
const middleware = getMiddleware(allow);
const handlers = getHandlers(allow);
// Add session middleware
app.use(sessionMw);
// Authentication routes
app.get("/auth/github", handlers.login("github"));
app.get("/auth/github/callback", handlers.callback("github"));
// Protected routes
app.get("/profile", middleware.requireAuth, handlers.profile);
app.get("/admin", middleware.requireRole("admin"), (req, res) => {
res.json({ message: "Admin area", user: req.user });
});
// Account linking
app.get("/link/github", middleware.requireAuth, handlers.link("github"));
app.post("/unlink/github", middleware.requireAuth, handlers.unlink("github"));
// Logout
app.post("/logout", handlers.logout);
app.listen(3000);Configuration
Basic Configuration
interface AuthConfig {
secret: string; // JWT secret key
sessionDuration?: number; // Session duration in milliseconds
database?: DatabaseConfig; // Optional database configuration
strategies: StrategyConfig[]; // Authentication strategies
}Database Configuration
interface DatabaseConfig {
type: "sqlite" | "postgres";
connection: string; // Database connection string
migrate?: boolean; // Run migrations on startup
}Strategy Configuration
interface StrategyConfig {
name: string; // Strategy name
type: "local" | "oauth" | "jwt"; // Strategy type
config: Record<string, any>; // Strategy-specific configuration
enabled?: boolean; // Enable/disable strategy
}Authentication Strategies
Local Strategy (Email/Password)
{
name: "local",
type: "local",
config: {
usernameField: "email", // Field name for username
passwordField: "password", // Field name for password
hashRounds: 12 // Bcrypt hash rounds
}
}OAuth Strategy
{
name: "github",
type: "oauth",
config: {
clientId: "your-client-id",
clientSecret: "your-client-secret",
callbackURL: "http://localhost:3000/auth/github/callback",
scope: ["user:email"]
}
}Built-in OAuth Providers
import { useStrategy, githubStrategy, googleStrategy, discordStrategy } from "@verb-js/allow";
// GitHub
useStrategy(allow, githubStrategy({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
callbackURL: "http://localhost:3000/auth/github/callback"
}));
// Google
useStrategy(allow, googleStrategy({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: "http://localhost:3000/auth/google/callback"
}));
// Discord
useStrategy(allow, discordStrategy({
clientId: process.env.DISCORD_CLIENT_ID!,
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
callbackURL: "http://localhost:3000/auth/discord/callback"
}));JWT Strategy
{
name: "jwt",
type: "jwt",
config: {
secret: "jwt-secret",
algorithm: "HS256",
expiresIn: "1h"
}
}Custom Strategies
Create custom authentication strategies as functions:
import { useStrategy, generateError, generateSuccess } from "@verb-js/allow";
import type { VerbRequest, AuthResult, AuthStrategy } from "@verb-js/allow";
function createAPIKeyStrategy(): AuthStrategy {
return {
name: "apikey",
async authenticate(req: VerbRequest): Promise<AuthResult> {
const apiKey = req.headers.get("X-API-Key");
if (!apiKey) {
return generateError("Missing API key");
}
const user = await validateAPIKey(apiKey);
if (!user) {
return generateError("Invalid API key");
}
return generateSuccess(user);
}
};
}
async function validateAPIKey(apiKey: string) {
// Your validation logic here
return null;
}
// Register custom strategy
useStrategy(allow, createAPIKeyStrategy());Middleware
Authentication Middleware
import { getMiddleware } from "@verb-js/allow";
const middleware = getMiddleware(allow);
// Require authentication
app.get("/protected", middleware.requireAuth, (req, res) => {
res.json({ user: req.user });
});
// Optional authentication
app.get("/mixed", middleware.optionalAuth, (req, res) => {
if (req.isAuthenticated()) {
res.json({ user: req.user });
} else {
res.json({ message: "Public content" });
}
});
// Require specific role
app.get("/admin", middleware.requireRole("admin"), (req, res) => {
res.json({ message: "Admin area" });
});Session Middleware
import { getSessionMiddleware } from "@verb-js/allow";
// Add session support
const sessionMw = getSessionMiddleware(allow);
app.use(sessionMw);Account Linking
Allow users to link multiple authentication methods to a single account:
import { getMiddleware, getHandlers, getUserStrategies } from "@verb-js/allow";
const middleware = getMiddleware(allow);
const handlers = getHandlers(allow);
// Link GitHub account to current user
app.get("/link/github", middleware.requireAuth, handlers.link("github"));
// Unlink GitHub account
app.post("/unlink/github", middleware.requireAuth, handlers.unlink("github"));
// Get user's linked strategies
const strategies = await getUserStrategies(allow, userId);Database Migrations
Run database migrations to set up the required tables:
import { runMigrations } from "@verb-js/allow";
await runMigrations({
database: {
type: "sqlite",
connection: "auth.db"
}
});Or run migrations via CLI:
bun run migratePassword Hashing
Use Bun's built-in password hashing for local authentication:
import { hashPassword, verifyPassword } from "@verb-js/allow";
// Hash password
const hash = await hashPassword("password123");
// Verify password
const isValid = await verifyPassword("password123", hash);Testing
# Run all tests
bun test
# Run tests in watch mode
bun test --watch
# Run specific test file
bun test src/allow.test.tsExamples
Check out the examples directory for complete working examples:
- Basic Usage - Simple authentication setup
- Custom Strategy - Implementing custom authentication
- Multiple Strategies - Account linking and multiple auth methods
API Reference
Core Functions
// Main factory function
function createAllow(config: AuthConfig): AllowInstance
// Strategy management
function useStrategy(allow: AllowInstance, strategy: AuthStrategy): void
function authenticate(allow: AllowInstance, strategyName: string, req: VerbRequest): Promise<AuthResult>
function callback(allow: AllowInstance, strategyName: string, req: VerbRequest): Promise<AuthResult>
// Session management
function createSession(allow: AllowInstance, user: AuthUser, data?: Record<string, any>): Promise<AuthSession>
function getSession(allow: AllowInstance, sessionId: string): Promise<AuthSession | null>
function updateSession(allow: AllowInstance, sessionId: string, data: Record<string, any>): Promise<void>
function destroySession(allow: AllowInstance, sessionId: string): Promise<void>
// User management
function getUser(allow: AllowInstance, req: VerbRequest): Promise<AuthUser | null>
function linkStrategy(allow: AllowInstance, userId: string, strategyName: string, strategyId: string, profile: any, tokens?: any): Promise<UserStrategy>
function unlinkStrategy(allow: AllowInstance, userId: string, strategyName: string): Promise<void>
function getUserStrategies(allow: AllowInstance, userId: string): Promise<UserStrategy[]>
// Middleware and handlers
function getMiddleware(allow: AllowInstance): AuthMiddleware
function getSessionMiddleware(allow: AllowInstance): Function
function getHandlers(allow: AllowInstance): AuthHandlersTypes
interface AuthUser {
id: string;
username?: string;
email?: string;
profile?: Record<string, any>;
strategies: UserStrategy[];
createdAt: Date;
updatedAt: Date;
}
interface AuthSession {
id: string;
userId: string;
data: Record<string, any>;
expiresAt: Date;
createdAt: Date;
}
interface AuthResult {
success: boolean;
user?: AuthUser;
error?: string;
redirect?: string;
tokens?: {
access_token?: string;
refresh_token?: string;
expires_at?: Date;
};
}Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see the LICENSE file for details.
