npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@mvp-factory/holy-oauth

v1.0.0

Published

OAuth 2.0 authentication system extracted from Holy Habit project with Google OAuth integration

Readme

@mvp-factory/holy-oauth

OAuth 2.0 authentication system extracted from Holy Habit project with Google OAuth integration.

Features

  • 🔐 Google OAuth 2.0 Integration - Complete OAuth flow implementation
  • 🛡️ CSRF Protection - State parameter validation
  • 🔄 Token Refresh - Automatic token refresh handling
  • 📱 Session Management - Secure session handling
  • 🔧 Environment Configuration - Easy setup with environment variables
  • 📦 TypeScript Support - Full TypeScript definitions included

Installation

npm install @mvp-factory/holy-oauth

Quick Start

1. Environment Configuration

Create a .env file with your Google OAuth credentials:

# Google OAuth Configuration
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

# Application URLs
BASE_URL=http://localhost:3000
OAUTH_CALLBACK_PATH=/api/auth/callback

# Redirect URLs
OAUTH_SUCCESS_URL=/dashboard
OAUTH_ERROR_URL=/login?error=oauth_failed
OAUTH_LINK_SUCCESS_URL=/profile?linked=true

2. Express.js Integration

import express from 'express';
import session from 'express-session';
import { OAuthFactory, OAuthConfig, OAuthHandler, StateManager, UserManager } from '@mvp-factory/holy-oauth';

const app = express();

// Session middleware
app.use(session({
  secret: 'your-session-secret',
  resave: false,
  saveUninitialized: false,
  cookie: { secure: false } // Set to true in production with HTTPS
}));

// OAuth login route
app.get('/api/auth/login', async (req, res) => {
  try {
    const provider = 'google';
    
    // Load configuration
    const config = OAuthConfig.load(provider);
    
    // Create OAuth provider
    const oauthProvider = OAuthFactory.create(provider, config);
    
    // Create managers (implement these based on your database)
    const stateManager = new StateManager(/* your database connection */);
    const userManager = new UserManager(/* your database connection */);
    
    // Create handler
    const handler = new OAuthHandler(provider, oauthProvider, stateManager, userManager);
    
    // Initiate login
    const result = await handler.initiateLogin({
      returnUrl: req.query.return as string || '/'
    });
    
    // Store state in session
    req.session.oauth2state = result.state;
    
    // Redirect to OAuth provider
    res.redirect(result.url);
  } catch (error) {
    console.error('OAuth login error:', error);
    res.redirect('/login?error=oauth_setup');
  }
});

// OAuth callback route
app.get('/api/auth/callback', async (req, res) => {
  try {
    const { code, state, error } = req.query;
    
    if (error) {
      return res.redirect(`/login?error=${error}`);
    }
    
    // Validate state
    if (!state || req.session.oauth2state !== state) {
      return res.redirect('/login?error=invalid_state');
    }
    
    // Clear state from session
    delete req.session.oauth2state;
    
    // Process callback
    const provider = 'google';
    const config = OAuthConfig.load(provider);
    const oauthProvider = OAuthFactory.create(provider, config);
    
    const stateManager = new StateManager(/* your database connection */);
    const userManager = new UserManager(/* your database connection */);
    const handler = new OAuthHandler(provider, oauthProvider, stateManager, userManager);
    
    const result = await handler.handleCallback(code as string, state as string);
    
    if (result.success && result.user) {
      // Store user in session
      req.session.userId = result.user.id;
      req.session.userEmail = result.user.email;
      req.session.userName = result.user.name;
      req.session.loggedIn = true;
      
      // Redirect to success page
      res.redirect('/dashboard');
    } else {
      res.redirect('/login?error=oauth_failed');
    }
  } catch (error) {
    console.error('OAuth callback error:', error);
    res.redirect('/login?error=oauth_callback');
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

3. Database Integration

Implement the required managers for your database:

import { StateManager, UserManager } from '@mvp-factory/holy-oauth';

// Example with SQLite
class MySQLiteStateManager extends StateManager {
  constructor(private db: Database) {
    super();
  }
  
  async store(state: string, data: any): Promise<void> {
    const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
    await this.db.run(
      'INSERT INTO oauth_states (state, data, expires_at) VALUES (?, ?, ?)',
      [state, JSON.stringify(data), expiresAt.toISOString()]
    );
  }
  
  async retrieve(state: string): Promise<any> {
    const row = await this.db.get(
      'SELECT data FROM oauth_states WHERE state = ? AND expires_at > datetime("now")',
      [state]
    );
    
    if (row) {
      // Clean up used state
      await this.db.run('DELETE FROM oauth_states WHERE state = ?', [state]);
      return JSON.parse(row.data);
    }
    
    return null;
  }
}

Configuration Options

Environment Variables

| Variable | Required | Default | Description | |----------|----------|---------|-------------| | GOOGLE_CLIENT_ID | Yes | - | Google OAuth Client ID | | GOOGLE_CLIENT_SECRET | Yes | - | Google OAuth Client Secret | | BASE_URL | No | http://localhost:3000 | Your application base URL | | OAUTH_CALLBACK_PATH | No | /api/auth/callback | OAuth callback path | | OAUTH_SUCCESS_URL | No | / | Redirect URL after successful login | | OAUTH_ERROR_URL | No | /login?error=oauth_failed | Redirect URL on error | | OAUTH_LINK_SUCCESS_URL | No | /profile?linked=true | Redirect URL after account linking |

Google OAuth Setup

  1. Go to Google Cloud Console
  2. Create a new project or select existing one
  3. Enable Google+ API
  4. Create OAuth 2.0 credentials
  5. Add your callback URL: http://localhost:3000/api/auth/callback

API Reference

OAuthConfig

// Load configuration for a provider
const config = OAuthConfig.load('google');

// Set custom configuration
OAuthConfig.set('google', {
  provider: 'google',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  redirectUri: 'http://localhost:3000/api/auth/callback',
  scopes: ['openid', 'email', 'profile'],
  successUrl: '/dashboard',
  errorUrl: '/login?error=oauth_failed',
  linkSuccessUrl: '/profile?linked=true',
  options: {
    state: true,
    pkce: false,
    prompt: 'consent',
    accessType: 'offline'
  }
});

OAuthFactory

// Create OAuth provider
const provider = OAuthFactory.create('google', config);

// Check if provider is supported
const isSupported = OAuthFactory.isSupported('google'); // true

// Get supported providers
const providers = OAuthFactory.getSupportedProviders(); // ['google']

OAuthHandler

const handler = new OAuthHandler(provider, oauthProvider, stateManager, userManager);

// Initiate login
const result = await handler.initiateLogin({
  returnUrl: '/dashboard',
  userId: 'existing-user-id', // For account linking
  action: 'link' // 'login' or 'link'
});

// Handle callback
const result = await handler.handleCallback(code, state);

Examples

Account Linking

// Link additional OAuth provider to existing user
app.get('/api/auth/link/:provider', requireAuth, async (req, res) => {
  const { provider } = req.params;
  const userId = req.session.userId;
  
  const config = OAuthConfig.load(provider);
  const oauthProvider = OAuthFactory.create(provider, config);
  const handler = new OAuthHandler(provider, oauthProvider, stateManager, userManager);
  
  const result = await handler.initiateLogin({
    userId: userId,
    action: 'link',
    returnUrl: '/profile'
  });
  
  req.session.oauth2state = result.state;
  res.redirect(result.url);
});

Token Refresh

// Refresh expired tokens
const googleProvider = OAuthFactory.create('google', config);
const newTokens = await googleProvider.refreshToken(user.refreshToken);

// Update user tokens in database
await userManager.updateTokens(user.id, newTokens);

Error Handling

The library throws descriptive errors that you can catch and handle:

try {
  const result = await handler.handleCallback(code, state);
} catch (error) {
  if (error.message.includes('Invalid state')) {
    // Handle CSRF attack attempt
    console.warn('Possible CSRF attack detected');
    res.redirect('/login?error=security');
  } else if (error.message.includes('Failed to exchange code')) {
    // Handle OAuth provider errors
    console.error('OAuth provider error:', error);
    res.redirect('/login?error=provider');
  } else {
    // Handle other errors
    console.error('OAuth error:', error);
    res.redirect('/login?error=unknown');
  }
}

Testing

# Run tests
npm test

# Run 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

License

MIT © MVP Factory

Support


Extracted from Holy Habit project - Battle-tested OAuth implementation used in production.