Skip to content

Repository files navigation

Lockify πŸ”

A lightweight, framework-agnostic authentication package for Node.js with bcrypt and JWT helpers. Designed to be simple, secure, and compatible with all Node.js frameworks.

npm version License: MIT TypeScript

Features

  • πŸ”’ Secure password hashing with bcrypt
  • 🎫 JWT token generation and validation
  • 🌐 Framework-agnostic - works with Express, Koa, Fastify, and more
  • πŸ“ Full TypeScript support with type definitions
  • πŸ›‘οΈ Custom error handling for better debugging
  • βš™οΈ Advanced JWT options (expiration, issuer, audience, etc.)
  • πŸ§ͺ Thoroughly tested with Jest
  • πŸ“¦ Zero configuration - works out of the box

Installation

npm install @soldead/lockify

Quick Start

import { 
  hashPassword, 
  comparePassword, 
  generateToken, 
  verifyToken, 
  requireAuth,
  optionalAuth,
  requireRole,
  validatePassword,
  generateSalt
} from 'lockify';

// Hash a password
const hashedPassword = await hashPassword('mySecretPassword');

// Compare password
const isValid = await comparePassword('mySecretPassword', hashedPassword);

// Generate JWT token
const token = generateToken({ userId: 123, role: 'user' }, 'your-secret-key');

// Verify JWT token
const decoded = verifyToken(token, 'your-secret-key');

API Reference

Password Helpers

hashPassword(password: string): Promise<string>

Hashes a password using bcrypt with a default salt rounds of 12.

const hashedPassword = await hashPassword('userPassword123');
console.log(hashedPassword); // $2b$12$...

comparePassword(password: string, hash: string): Promise<boolean>

Compares a plain text password with a bcrypt hash.

const isValid = await comparePassword('userPassword123', hashedPassword);
console.log(isValid); // true or false

validatePassword(password: string, options?: PasswordValidationOptions): boolean

Validates password strength according to security requirements.

const isStrong = validatePassword('MySecurePassword123!', {
  minLength: 8,
  requireUppercase: true,
  requireLowercase: true,
  requireNumbers: true,
  requireSymbols: true
});
console.log(isStrong); // true or false

generateSalt(rounds?: number): Promise<string>

Generates a random salt for bcrypt hashing.

const salt = await generateSalt(12);
console.log(salt); // $2b$12$...

JWT Helpers

generateToken(payload: JwtPayload, secret: string, options?: JwtOptions): string

Generates a JWT token with the given payload and secret.

// Basic usage
const token = generateToken({ userId: 123 }, 'your-secret-key');

// With options
const token = generateToken(
  { userId: 123, role: 'admin' },
  'your-secret-key',
  {
    expiresIn: '24h',
    issuer: 'your-app-name',
    audience: 'your-users'
  }
);

verifyToken(token: string, secret: string): JwtPayload

Verifies and decodes a JWT token. Throws an error if the token is invalid or expired.

try {
  const decoded = verifyToken(token, 'your-secret-key');
  console.log('User ID:', decoded.userId);
} catch (error) {
  console.log('Token verification failed:', error.message);
}

decodeToken(token: string): JwtPayload | null

Decodes a JWT token without verification (unsafe). Returns null if the token is malformed.

const decoded = decodeToken(token);
if (decoded) {
  console.log('Token payload:', decoded);
}

isTokenExpired(token: string): boolean

Checks if a JWT token is expired.

const expired = isTokenExpired(token);
console.log('Token expired:', expired);

getTokenExpiration(token: string): Date | null

Gets the expiration date of a JWT token.

const expiration = getTokenExpiration(token);
if (expiration) {
  console.log('Token expires at:', expiration);
}

refreshToken(token: string, secret: string, options?: JwtOptions): string

Refreshes an existing token with a new expiration time.

const newToken = refreshToken(oldToken, 'your-secret-key', {
  expiresIn: '24h'
});

Authentication Middleware

requireAuth(getUserById: GetUserById, secret: string): MiddlewareFunction

Creates a middleware function that requires authentication for accessing protected routes.

type GetUserById = (id: string) => Promise<any>;

const authMiddleware = requireAuth(
  async (id: string) => {
    // Your user lookup logic
    return await db.users.findById(id);
  },
  'your-jwt-secret'
);

optionalAuth(getUserById: GetUserById, secret: string): MiddlewareFunction

Creates an optional authentication middleware that adds user information to the request if a valid token is provided, but doesn't fail if no token is present.

const optionalAuthMiddleware = optionalAuth(
  async (id: string) => {
    return await db.users.findById(id);
  },
  'your-jwt-secret'
);

requireRole(getUserById: GetUserById, secret: string, allowedRoles: string[]): MiddlewareFunction

Creates a role-based authentication middleware that checks if the authenticated user has the required role(s).

const adminMiddleware = requireRole(
  async (id: string) => {
    return await db.users.findById(id);
  },
  'your-jwt-secret',
  ['admin', 'superuser']
);

Framework Integration Examples

Express.js

import express from 'express';
import { requireAuth, generateToken, hashPassword, comparePassword } from 'lockify';

const app = express();
app.use(express.json());

// User lookup function
const getUserById = async (id: string) => {
  // Replace with your actual user lookup logic
  return await db.users.findById(id);
};

// Create auth middleware
const authMiddleware = requireAuth(getUserById, process.env.JWT_SECRET!);

// Public route - Login
app.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    // Find user by email
    const user = await db.users.findByEmail(email);
    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    
    // Verify password
    const isValid = await comparePassword(password, user.password);
    if (!isValid) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    
    // Generate token
    const token = generateToken(
      { userId: user.id, email: user.email },
      process.env.JWT_SECRET!,
      { expiresIn: '24h' }
    );
    
    res.json({ token, user: { id: user.id, email: user.email } });
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

// Public route - Register
app.post('/register', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    // Hash password
    const hashedPassword = await hashPassword(password);
    
    // Create user
    const user = await db.users.create({
      email,
      password: hashedPassword
    });
    
    res.status(201).json({ message: 'User created successfully' });
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

// Protected route
app.get('/profile', authMiddleware, (req, res) => {
  // req.user is available here
  res.json({ user: req.user });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Koa.js

import Koa from 'koa';
import Router from 'koa-router';
import { requireAuth } from 'lockify';

const app = new Koa();
const router = new Router();

// User lookup function
const getUserById = async (id: string) => {
  return await db.users.findById(id);
};

// Create auth middleware
const authMiddleware = requireAuth(getUserById, process.env.JWT_SECRET!);

// Convert to Koa middleware
const koaAuthMiddleware = async (ctx: Koa.Context, next: Koa.Next) => {
  await new Promise((resolve, reject) => {
    authMiddleware(ctx.request, ctx.response, (err?: any) => {
      if (err) reject(err);
      else resolve(void 0);
    });
  });
  await next();
};

// Protected route
router.get('/profile', koaAuthMiddleware, async (ctx) => {
  ctx.body = { user: ctx.request.user };
});

app.use(router.routes());
app.listen(3000);

Fastify

import fastify from 'fastify';
import { requireAuth } from 'lockify';

const app = fastify();

// User lookup function
const getUserById = async (id: string) => {
  return await db.users.findById(id);
};

// Create auth middleware
const authMiddleware = requireAuth(getUserById, process.env.JWT_SECRET!);

// Register as Fastify plugin
app.register(async (fastify) => {
  fastify.addHook('preHandler', async (request, reply) => {
    await new Promise((resolve, reject) => {
      authMiddleware(request.raw, reply.raw, (err?: any) => {
        if (err) reject(err);
        else resolve(void 0);
      });
    });
  });
  
  fastify.get('/profile', async (request, reply) => {
    return { user: (request.raw as any).user };
  });
});

app.listen({ port: 3000 });

TypeScript Support

Lockify is written in TypeScript and provides full type definitions:

import { 
  JwtPayload, 
  JwtOptions, 
  GetUserById, 
  AuthError,
  HashOptions,
  PasswordValidationOptions
} from 'lockify';

// Custom JWT payload
interface CustomPayload extends JwtPayload {
  userId: number;
  role: 'admin' | 'user';
  permissions: string[];
}

// Strongly typed user lookup
const getUserById: GetUserById = async (id: string) => {
  const user = await db.users.findById(id);
  if (!user) throw new AuthError('User not found');
  return user;
};

// Generate token with custom payload
const token = generateToken<CustomPayload>(
  {
    userId: 123,
    role: 'admin',
    permissions: ['read', 'write']
  },
  'secret'
);

// Hash password with custom options
const hash = await hashPassword('password', { saltRounds: 14 });

// Validate password with custom rules
const isValid = validatePassword('password', {
  minLength: 12,
  requireUppercase: true,
  requireLowercase: true,
  requireNumbers: true,
  requireSymbols: true
});

Error Handling

Lockify provides custom error classes for better error handling:

import { 
  AuthError, 
  TokenError, 
  HashError,
  InvalidTokenError,
  ExpiredTokenError,
  MissingTokenError,
  MalformedTokenError,
  UserNotFoundError,
  InvalidPasswordError,
  WeakPasswordError
} from 'lockify';

try {
  const decoded = verifyToken(token, secret);
} catch (error) {
  if (error instanceof ExpiredTokenError) {
    console.log('Token has expired:', error.message);
  } else if (error instanceof InvalidTokenError) {
    console.log('Invalid token:', error.message);
  } else if (error instanceof TokenError) {
    console.log('Token error:', error.message);
  } else if (error instanceof AuthError) {
    console.log('Auth error:', error.message);
  }
}

Error Types

  • AuthError: General authentication errors
  • TokenError: JWT token related errors
  • HashError: Password hashing related errors
  • InvalidTokenError: Invalid token format or signature
  • ExpiredTokenError: Token has expired
  • MissingTokenError: No token provided
  • MalformedTokenError: Token format is invalid
  • UserNotFoundError: User lookup failed
  • InvalidPasswordError: Password validation failed
  • WeakPasswordError: Password doesn't meet security requirements

Advanced Configuration

JWT Options

interface JwtOptions {
  expiresIn?: string | number;
  issuer?: string;
  audience?: string | string[];
  subject?: string;
  algorithm?: 
    | 'HS256' | 'HS384' | 'HS512'
    | 'RS256' | 'RS384' | 'RS512'
    | 'PS256' | 'PS384' | 'PS512'
    | 'ES256' | 'ES384' | 'ES512'
    | 'none';
  keyid?: string;
  noTimestamp?: boolean;
  header?: { [key: string]: unknown };
  encoding?: string;
}

interface HashOptions {
  saltRounds?: number;
}

interface PasswordValidationOptions {
  minLength?: number;
  maxLength?: number;
  requireUppercase?: boolean;
  requireLowercase?: boolean;
  requireNumbers?: boolean;
  requireSymbols?: boolean;
  forbiddenPasswords?: string[];
}

Custom Salt Rounds for Bcrypt

import { hashPassword } from 'lockify';

// Default salt rounds (12)
const hash1 = await hashPassword('password');

// Custom salt rounds
const hash2 = await hashPassword('password', { saltRounds: 14 });

Best Practices

  1. Environment Variables: Always store JWT secrets in environment variables
  2. Token Expiration: Set appropriate expiration times for tokens
  3. Error Handling: Always handle authentication errors gracefully
  4. HTTPS: Use HTTPS in production to protect tokens in transit
  5. Refresh Tokens: Implement refresh token mechanism for better security
// Good practice example
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';

if (!JWT_SECRET) {
  throw new Error('JWT_SECRET environment variable is required');
}

const token = generateToken(
  { userId: user.id },
  JWT_SECRET,
  { expiresIn: JWT_EXPIRES_IN }
);

Utility Functions

Lockify provides additional utility functions for enhanced security and validation:

Header Utilities

import {
  extractTokenFromHeader,
  extractBearerToken,
  validateAuthHeader,
  extractTokenByScheme,
  extractTokenFromCookie
} from 'lockify';

// Extract token from Authorization header
const token = extractTokenFromHeader('Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');

// Extract specifically Bearer tokens
const bearerToken = extractBearerToken('Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');

// Extract token by custom scheme
const customToken = extractTokenByScheme('Custom eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', 'Custom');

// Extract token from cookie string
const cookieToken = extractTokenFromCookie('auth_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', 'auth_token');

Validation Utilities

import {
  validatePasswordStrength,
  validateEmail,
  validateJwtSecret,
  sanitizeInput,
  isPasswordForbidden
} from 'lockify';

// Validate password strength
const isStrong = validatePasswordStrength('MyPassword123!');

// Validate email format
const isValidEmail = validateEmail('user@example.com');

// Validate JWT secret strength
const isSecureSecret = validateJwtSecret('your-super-secret-key');

// Sanitize user input
const clean = sanitizeInput('<script>alert("xss")</script>user input');

// Check if password is in forbidden list
const isForbidden = isPasswordForbidden('password123', ['password123', '123456']);

Security Utilities

import {
  generateSecureRandom,
  generateJwtSecret,
  hashSha256,
  generateHmac,
  verifyHmac,
  constantTimeCompare,
  generateUuid
} from 'lockify';

// Generate secure random bytes
const randomBytes = generateSecureRandom(32);

// Generate secure JWT secret
const jwtSecret = generateJwtSecret(64);

// Hash data with SHA-256
const hash = hashSha256('data to hash');

// Generate HMAC
const hmac = generateHmac('data', 'secret');

// Verify HMAC
const isValid = verifyHmac('data', 'secret', hmac);

// Constant-time string comparison
const isEqual = constantTimeCompare('string1', 'string2');

// Generate UUID
const uuid = generateUuid();

Testing

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Setup

# Clone the repository
git clone https://github.com/yourusername/lockify.git

# Install dependencies
npm install

# Run in development mode
npm run dev

# Build the project
npm run build

# Run tests
npm test

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

Changelog

v1.0.0

  • ✨ Enhanced Authentication System
    • Password hashing with bcrypt and configurable salt rounds
    • Advanced password validation with customizable strength requirements
    • JWT token generation, verification, and management
    • Token refresh functionality and expiration checking
    • Multiple authentication middleware options (required, optional, role-based)
  • πŸ›‘οΈ Security Features
    • Comprehensive error handling with specific error types
    • Secure random generation and HMAC utilities
    • Constant-time comparison functions
    • Input sanitization and validation
  • 🌐 Framework Compatibility
    • Works with Express, Koa, Fastify, and other Node.js frameworks
    • Framework-agnostic middleware design
  • πŸ“ TypeScript Support
    • Full type definitions for all functions and interfaces
    • Generic support for custom JWT payloads
    • Type-safe middleware functions
  • πŸ§ͺ Testing & Quality
    • Comprehensive test suite with Jest
    • ESLint and Prettier configuration
    • CI/CD pipeline integration

Made with ❀️ by the Lockify team

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages