JSPM

@sp-uvb/feathers

0.1.0
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 1
    • Score
      100M100P100Q28592F
    • License MIT

    FeathersJS hooks and services for Universal Verification Broker (UVB)

    Package Exports

    • @sp-uvb/feathers
    • @sp-uvb/feathers/vue

    Readme

    @sp-uvb/feathers

    FeathersJS hooks and services for Universal Verification Broker (UVB) authentication.

    Installation

    npm install @sp-uvb/feathers
    # or
    yarn add @sp-uvb/feathers
    # or
    pnpm add @sp-uvb/feathers

    Quick Start

    import { feathers } from '@feathersjs/feathers';
    import { authenticate, requireFactors } from '@sp-uvb/feathers';
    
    const app = feathers();
    
    // Apply authentication to all services
    app.hooks({
      before: {
        all: [authenticate({ tenantId: 'my-tenant' })],
      },
    });
    
    // Require specific factors for sensitive operations
    app.service('admin').hooks({
      before: {
        all: [requireFactors(['totp', 'webauthn'])],
      },
    });
    
    // Access session in service methods
    app.service('users').hooks({
      before: {
        find: [
          async (context) => {
            const session = context.params.uvbSession;
            console.log('User ID:', session?.userId);
            return context;
          },
        ],
      },
    });

    API

    authenticate(options)

    Feathers hook to authenticate requests with UVB.

    Options:

    • tenantId (required): Your UVB tenant ID
    • uvbUrl (optional): UVB server URL, defaults to http://localhost:8080
    • apiKey (optional): API key for server-to-server authentication
    • cookieName (optional): Cookie name for session token, defaults to uvb_session
    • excludeServices (optional): Array of service paths to exclude from authentication
    import { authenticate } from '@sp-uvb/feathers';
    
    // Global authentication
    app.hooks({
      before: {
        all: [
          authenticate({
            tenantId: 'my-tenant',
            uvbUrl: 'http://localhost:8080',
            excludeServices: ['authentication', 'health'],
          }),
        ],
      },
    });
    
    // Per-service authentication
    app.service('users').hooks({
      before: {
        all: [authenticate({ tenantId: 'my-tenant' })],
      },
    });

    Session Access:

    After authentication, context.params.uvbSession contains:

    interface UvbSession {
      userId: string;
      tenantId: string;
      sessionId: string;
      factorsVerified: string[];
      expiresAt: Date;
    }

    requireFactors(factors)

    Hook to require specific MFA factors.

    import { authenticate, requireFactors } from '@sp-uvb/feathers';
    
    app.service('admin').hooks({
      before: {
        all: [authenticate({ tenantId: 'my-tenant' }), requireFactors(['totp', 'webauthn'])],
      },
    });

    requireOwnership(ownerField)

    Hook to require user owns the resource.

    import { authenticate, requireOwnership } from '@sp-uvb/feathers';
    
    app.service('posts').hooks({
      before: {
        update: [
          authenticate({ tenantId: 'my-tenant' }),
          requireOwnership('userId'), // Checks if record.userId === session.userId
        ],
        remove: [authenticate({ tenantId: 'my-tenant' }), requireOwnership('userId')],
        create: [
          authenticate({ tenantId: 'my-tenant' }),
          requireOwnership('userId'), // Automatically sets record.userId = session.userId
        ],
      },
    });

    getSession(context)

    Helper to get UVB session from hook context.

    import { getSession } from '@sp-uvb/feathers';
    
    app.service('users').hooks({
      before: {
        find: [
          async (context) => {
            const session = getSession(context);
            if (session) {
              context.params.query = {
                ...context.params.query,
                userId: session.userId,
              };
            }
            return context;
          },
        ],
      },
    });

    UvbService

    Feathers service for validating and revoking UVB sessions.

    import { UvbService } from '@sp-uvb/feathers';
    
    // Register service
    app.use(
      'uvb',
      new UvbService({
        tenantId: 'my-tenant',
        uvbUrl: 'http://localhost:8080',
      })
    );
    
    // Validate session
    const session = await app.service('uvb').get(sessionToken);
    console.log('User ID:', session.userId);
    
    // Revoke session
    await app.service('uvb').remove(sessionId);

    Examples

    Protecting All Services Except Authentication

    import { authenticate } from '@sp-uvb/feathers';
    
    app.hooks({
      before: {
        all: [
          authenticate({
            tenantId: 'my-tenant',
            excludeServices: ['authentication', 'health', 'public'],
          }),
        ],
      },
    });

    Per-Method Authentication Requirements

    import { authenticate, requireFactors } from '@sp-uvb/feathers';
    
    app.service('users').hooks({
      before: {
        find: [authenticate({ tenantId: 'my-tenant' })],
        get: [authenticate({ tenantId: 'my-tenant' })],
        create: [authenticate({ tenantId: 'my-tenant' })],
        update: [authenticate({ tenantId: 'my-tenant' }), requireFactors(['totp'])],
        remove: [authenticate({ tenantId: 'my-tenant' }), requireFactors(['totp', 'webauthn'])],
      },
    });

    Filtering Results by User

    import { authenticate, getSession } from '@sp-uvb/feathers';
    
    app.service('posts').hooks({
      before: {
        find: [
          authenticate({ tenantId: 'my-tenant' }),
          async (context) => {
            const session = getSession(context);
            // Only show user's own posts
            context.params.query = {
              ...context.params.query,
              userId: session.userId,
            };
            return context;
          },
        ],
      },
    });

    Conditional MFA Requirements

    import { authenticate, requireFactors, getSession } from '@sp-uvb/feathers';
    
    app.service('transfers').hooks({
      before: {
        create: [
          authenticate({ tenantId: 'my-tenant' }),
          async (context) => {
            const amount = context.data.amount;
    
            // Require strong auth for large transfers
            if (amount > 10000) {
              await requireFactors(['totp', 'webauthn'])(context);
            }
    
            return context;
          },
        ],
      },
    });

    Admin-Only Operations

    import { authenticate, getSession } from '@sp-uvb/feathers';
    
    const requireAdmin = async (context) => {
      const session = getSession(context);
    
      if (!session) {
        throw new Error('No session found');
      }
    
      // Check if user has admin factor
      if (!session.factorsVerified.includes('admin-role')) {
        const error = new Error('Admin access required') as any;
        error.code = 403;
        throw error;
      }
    
      return context;
    };
    
    app.service('admin').hooks({
      before: {
        all: [authenticate({ tenantId: 'my-tenant' }), requireAdmin],
      },
    });

    Audit Logging with Session

    import { authenticate, getSession } from '@sp-uvb/feathers';
    
    app.service('logs').hooks({
      after: {
        create: [
          async (context) => {
            const session = getSession(context);
    
            await app.service('audit-logs').create({
              action: 'log_created',
              userId: session?.userId,
              timestamp: new Date(),
              data: context.result,
            });
    
            return context;
          },
        ],
      },
    });

    Using with Feathers Authentication

    import { authenticate as feathersAuth } from '@feathersjs/authentication';
    import { authenticate as uvbAuth } from '@sp-uvb/feathers';
    
    // Use Feathers auth for local auth endpoints
    app.service('authentication').hooks({
      before: {
        create: [feathersAuth('local')],
      },
    });
    
    // Use UVB auth for everything else
    app.hooks({
      before: {
        all: [
          uvbAuth({
            tenantId: 'my-tenant',
            excludeServices: ['authentication'],
          }),
        ],
      },
    });

    Combining Multiple Security Hooks

    import { authenticate, requireFactors, requireOwnership } from '@sp-uvb/feathers';
    
    app.service('private-docs').hooks({
      before: {
        all: [authenticate({ tenantId: 'my-tenant' })],
        find: [],
        get: [requireOwnership('userId')],
        create: [requireFactors(['email-verified']), requireOwnership('userId')],
        update: [requireFactors(['totp']), requireOwnership('userId')],
        remove: [requireFactors(['totp', 'webauthn']), requireOwnership('userId')],
      },
    });

    Custom Session Validation

    import { UvbService } from '@sp-uvb/feathers';
    
    // Register UVB service
    app.use('uvb', new UvbService({ tenantId: 'my-tenant' }));
    
    // Custom authentication hook
    const customAuth = async (context) => {
      const token = context.params.headers?.['x-custom-token'];
    
      if (!token) {
        throw new Error('No token provided');
      }
    
      try {
        const session = await app.service('uvb').get(token);
        context.params.uvbSession = session;
        return context;
      } catch (error) {
        throw new Error('Invalid token');
      }
    };
    
    app.service('custom').hooks({
      before: {
        all: [customAuth],
      },
    });

    TypeScript

    This package includes full TypeScript definitions:

    import type { UvbSession, UvbAuthenticateOptions } from '@sp-uvb/feathers';
    
    // Extend Feathers Params type (done automatically by the package)
    declare module '@feathersjs/feathers' {
      interface Params {
        uvbSession?: UvbSession;
      }
    }

    License

    MIT