Skip to content

Latest commit

 

History

History
467 lines (361 loc) · 13.7 KB

File metadata and controls

467 lines (361 loc) · 13.7 KB

@profullstack/auth-system

A flexible authentication system with user registration, login/logout, password reset, and session management.

Features

  • User Management: Registration, login, profile management
  • Authentication: JWT-based authentication with access and refresh tokens
  • Password Management: Secure password hashing, validation, reset
  • Email Verification: Email verification for new accounts
  • Adapters: Pluggable storage adapters (memory, Supabase, MySQL, PostgreSQL, MongoDB, PocketBase, etc.)
  • Middleware: Express/Connect/Hono middleware for protected routes
  • Validation: Input validation for emails, passwords, etc.
  • Customization: Configurable password requirements, token expiry, etc.

Installation

npm install @profullstack/auth-system

Basic Usage

import { createAuthSystem } from '@profullstack/auth-system';

// Create an auth system with default options
const authSystem = createAuthSystem({
  tokenOptions: {
    secret: 'your-secret-key-here',
    accessTokenExpiry: 3600, // 1 hour
    refreshTokenExpiry: 604800 // 7 days
  }
});

// Register a new user
const registrationResult = await authSystem.register({
  email: 'user@example.com',
  password: 'Password123',
  profile: {
    firstName: 'John',
    lastName: 'Doe'
  }
});

// Login
const loginResult = await authSystem.login({
  email: 'user@example.com',
  password: 'Password123'
});

// Use the tokens for authentication
const { accessToken, refreshToken } = loginResult.tokens;

API Reference

Creating an Auth System

import { createAuthSystem, MemoryAdapter } from '@profullstack/auth-system';

const authSystem = createAuthSystem({
  // Storage adapter (optional, defaults to in-memory)
  adapter: new MemoryAdapter(),
  
  // Token configuration (optional)
  tokenOptions: {
    accessTokenExpiry: 3600, // 1 hour
    refreshTokenExpiry: 604800, // 7 days
    secret: 'your-secret-key-here'
  },
  
  // Password configuration (optional)
  passwordOptions: {
    minLength: 8,
    requireUppercase: true,
    requireLowercase: true,
    requireNumbers: true,
    requireSpecialChars: false
  },
  
  // Email configuration (optional)
  emailOptions: {
    sendEmail: async (emailData) => {
      // Your email sending implementation
    },
    fromEmail: 'noreply@example.com',
    resetPasswordTemplate: {
      subject: 'Reset Your Password',
      text: 'Click the link to reset your password: {resetLink}',
      html: '<p>Click the link to reset your password: <a href="{resetLink}">{resetLink}</a></p>'
    },
    verificationTemplate: {
      subject: 'Verify Your Email',
      text: 'Click the link to verify your email: {verificationLink}',
      html: '<p>Click the link to verify your email: <a href="{verificationLink}">{verificationLink}</a></p>'
    }
  }
});

User Registration

const registrationResult = await authSystem.register({
  email: 'user@example.com',
  password: 'Password123',
  profile: {
    firstName: 'John',
    lastName: 'Doe'
  },
  autoVerify: false // Set to true to skip email verification
});

User Login

const loginResult = await authSystem.login({
  email: 'user@example.com',
  password: 'Password123'
});

// The login result contains user data and tokens
const { user, tokens } = loginResult;

Token Refresh

const refreshResult = await authSystem.refreshToken(refreshToken);

// The refresh result contains new tokens
const { accessToken, refreshToken } = refreshResult.tokens;

Password Reset

// Request password reset
const resetResult = await authSystem.resetPassword('user@example.com');

// Confirm password reset (in a real app, the token would come from the email link)
const confirmResult = await authSystem.resetPasswordConfirm({
  token: 'reset-token-from-email',
  password: 'NewPassword123'
});

Email Verification

// Verify email (in a real app, the token would come from the email link)
const verificationResult = await authSystem.verifyEmail('verification-token-from-email');

User Profile Management

// Get user profile
const profileResult = await authSystem.getProfile(userId);

// Update user profile
const updateResult = await authSystem.updateProfile({
  userId,
  profile: {
    firstName: 'John',
    lastName: 'Doe',
    phoneNumber: '555-123-4567'
  }
});

// Change password
const changePasswordResult = await authSystem.changePassword({
  userId,
  currentPassword: 'Password123',
  newPassword: 'NewPassword123'
});

Token Validation

// Validate an access token
const user = await authSystem.validateToken(accessToken);

if (user) {
  // Token is valid, user contains user data
  console.log(`Valid token for user: ${user.email}`);
} else {
  // Token is invalid or expired
  console.log('Invalid token');
}

Logout

// Logout (invalidates the refresh token)
const logoutResult = await authSystem.logout(refreshToken);

Middleware

import express from 'express';
import { createAuthSystem } from '@profullstack/auth-system';

const app = express();
const authSystem = createAuthSystem();

// Protect routes with authentication middleware
app.use('/api/protected', authSystem.middleware());

app.get('/api/protected/profile', (req, res) => {
  // req.user contains the authenticated user data
  res.json({ user: req.user });
});

app.listen(3000);

Storage Adapters

Memory Adapter (Default)

Stores user data in memory. Suitable for development or testing.

import { createAuthSystem, MemoryAdapter } from '@profullstack/auth-system';

const authSystem = createAuthSystem({
  adapter: new MemoryAdapter()
});

JWT Adapter

Uses JSON Web Tokens (JWT) for authentication. Requires a database adapter for user storage.

import { createAuthSystem, MemoryAdapter, JwtAdapter } from '@profullstack/auth-system';

const dbAdapter = new MemoryAdapter();
const jwtAdapter = new JwtAdapter({
  dbAdapter,
  secret: 'your-secret-key-here'
});

const authSystem = createAuthSystem({
  adapter: jwtAdapter
});

Supabase Adapter

Uses Supabase for user storage and authentication. Requires the @supabase/supabase-js package.

import { createAuthSystem, SupabaseAdapter } from '@profullstack/auth-system';

const supabaseAdapter = new SupabaseAdapter({
  supabaseUrl: 'https://your-project-id.supabase.co',
  supabaseKey: 'your-supabase-api-key',
  tableName: 'users', // Optional: defaults to 'users'
  tokensTableName: 'invalidated_tokens' // Optional: defaults to 'invalidated_tokens'
});

const authSystem = createAuthSystem({
  adapter: supabaseAdapter,
  tokenOptions: {
    secret: 'your-jwt-secret-key'
  }
});

Note: Before using the Supabase adapter, you need to set up the required tables in your Supabase database. You can use the supabase-schema.sql file to create the necessary tables and indexes.

MySQL Adapter

Uses MySQL for user storage and authentication. Requires the mysql2 package.

import { createAuthSystem, MySQLAdapter } from '@profullstack/auth-system';

const mysqlAdapter = new MySQLAdapter({
  host: 'localhost',
  port: 3306,
  database: 'auth_system',
  user: 'root',
  password: 'password',
  usersTable: 'users', // Optional: defaults to 'users'
  tokensTable: 'invalidated_tokens' // Optional: defaults to 'invalidated_tokens'
});

const authSystem = createAuthSystem({
  adapter: mysqlAdapter,
  tokenOptions: {
    secret: 'your-jwt-secret-key'
  }
});

Note: This is a stub implementation. You'll need to complete the implementation before using it in production.

PostgreSQL Adapter

Uses PostgreSQL for user storage and authentication. Requires the pg package.

import { createAuthSystem, PostgresAdapter } from '@profullstack/auth-system';

const postgresAdapter = new PostgresAdapter({
  host: 'localhost',
  port: 5432,
  database: 'auth_system',
  user: 'postgres',
  password: 'password',
  usersTable: 'users', // Optional: defaults to 'users'
  tokensTable: 'invalidated_tokens' // Optional: defaults to 'invalidated_tokens'
});

const authSystem = createAuthSystem({
  adapter: postgresAdapter,
  tokenOptions: {
    secret: 'your-jwt-secret-key'
  }
});

Note: This is a stub implementation. You'll need to complete the implementation before using it in production.

MongoDB Adapter

Uses MongoDB for user storage and authentication. Requires the mongodb package.

import { createAuthSystem, MongoDBAdapter } from '@profullstack/auth-system';

const mongodbAdapter = new MongoDBAdapter({
  uri: 'mongodb://localhost:27017',
  dbName: 'auth_system',
  usersCollection: 'users', // Optional: defaults to 'users'
  tokensCollection: 'invalidated_tokens' // Optional: defaults to 'invalidated_tokens'
});

const authSystem = createAuthSystem({
  adapter: mongodbAdapter,
  tokenOptions: {
    secret: 'your-jwt-secret-key'
  }
});

Note: This is a stub implementation. You'll need to complete the implementation before using it in production.

PocketBase Adapter

Uses PocketBase for user storage and authentication. Requires the pocketbase package.

import { createAuthSystem, PocketBaseAdapter } from '@profullstack/auth-system';

const pocketbaseAdapter = new PocketBaseAdapter({
  url: 'http://127.0.0.1:8090',
  usersCollection: 'auth_users', // Optional: defaults to 'auth_users'
  tokensCollection: 'auth_invalidated_tokens', // Optional: defaults to 'auth_invalidated_tokens'
  adminEmail: 'admin@example.com', // Optional: for admin authentication
  adminPassword: 'password' // Optional: for admin authentication
});

const authSystem = createAuthSystem({
  adapter: pocketbaseAdapter,
  tokenOptions: {
    secret: 'your-jwt-secret-key'
  }
});

Creating Custom Adapters

You can create custom adapters by implementing the adapter interface:

class CustomAdapter {
  async createUser(userData) { /* ... */ }
  async getUserById(userId) { /* ... */ }
  async getUserByEmail(email) { /* ... */ }
  async updateUser(userId, updates) { /* ... */ }
  async deleteUser(userId) { /* ... */ }
  async invalidateToken(token) { /* ... */ }
  async isTokenInvalidated(token) { /* ... */ }
}

Note: Before using the PocketBase adapter, you need to set up the required collections in your PocketBase database. You can use the pocketbase-schema.json file to create the necessary collections and indexes.

OAuth 2.1 and CLI sign-in

Every Profullstack app signs its CLI, TUI, stdio MCP server and desktop shell in the same way, so the pieces live here instead of being rebuilt per app.

Server (@profullstack/auth-system/oauth2): authorization code + PKCE S256 only, RFC 8252 loopback redirects, single-use 5-minute codes, 1-hour access tokens, refresh tokens that rotate on every use, and reuse detection that revokes the whole sign-in. Only hashes are stored.

import { createOAuthServer } from '@profullstack/auth-system/oauth2';
import { OAUTH2_SCHEMA, postgresStore } from '@profullstack/auth-system/oauth2/postgres';

await sql.unsafe(OAUTH2_SCHEMA); // once, idempotent
const oauth = createOAuthServer({
  store: postgresStore(sql),
  issuer: 'https://example.com',
  tokenPrefix: 'ex',
  clients: { 'example-cli': { name: 'example CLI', redirectUris: ['http://127.0.0.1/callback', 'https://example.com/oauth/cli'] } },
});
// GET  /oauth/authorize -> oauth.validateAuthorize(query), show consent, then redirect to oauth.approve({...params, userId})
// POST /oauth/token     -> oauth.token(formBody)
// POST /oauth/revoke    -> oauth.revoke(formBody.token)
// GET  /.well-known/oauth-authorization-server -> oauth.metadata()
// API auth: await oauth.verifyAccessToken(req.headers.authorization)

Client (@profullstack/auth-system/cli): opens the browser, waits on a loopback port, checks state, exchanges the code with its PKCE verifier and keeps the tokens at ~/.config/<app>/auth.json (0600). Over SSH it prints the URL and asks for the code the server's /oauth/cli page shows.

import { createTokenStore, getAccessToken, login, logout } from '@profullstack/auth-system/cli';

const store = createTokenStore('example');
await login({ issuer: 'https://example.com', clientId: 'example-cli', store });
const bearer = await getAccessToken({ store }); // refreshes + stores the rotated pair
await logout({ store });                        // revokes server-side, forgets locally

Examples

See the examples directory for complete usage examples:

Browser Integration

The browser integration example provides a complete authentication system for web applications, including:

  • User registration with payment integration (Stripe, crypto)
  • Login/logout functionality
  • Password reset and change
  • Profile management
  • API key management
  • Authentication status utilities

It includes a browser-friendly wrapper around the auth-system module with localStorage support and event handling. See the Browser Integration README for more details.

License

MIT