Source

adminjs-hapijs/src/plugin.ts

import path from 'path';
import Boom from '@hapi/boom';
import inert from '@hapi/inert';
import Hapi from '@hapi/hapi';
import AdminJS, { AdminJSOptions, Router as AdminRouter } from 'adminjs';
import sessionAuth from './extensions/session-auth';
import info from './info';

/**
 * Plugin definition for Hapi.js framework.
 * @private
 */

export type AuthOptions = {
  /**
   * Function takes email and password as argument. Should return a logged-in user object or null/false.
   * If provided, the strategy is set to 'session'.
   */
  authenticate?: (email: string, password: string) => Promise<Record<string, any>> | null | false;
  /**
   * Auth strategy for Hapi routes.
   */
  strategy?: string;
  /**
   * This is the name of the cookie if strategy is set to 'session'.
   */
  cookieName?: string;
  /**
   * Cookie password for 'session' strategy.
   */
  cookiePassword?: string;
  /**
   * If cookie should be accessible only via https, defaults to false
   */
  isSecure: boolean;
  /**
   * Cookie options: https://github.com/hapijs/cookie
   */
  [key: string]: any;
};

export type ExtendedAdminJSOptions = AdminJSOptions & {
  /**
   * Should 'inert' be registered by the plugin. Defaults to true. If disabled, you snould register it yourself.
   */
  registerInert: boolean;
  /**
   * Authentication options. You can pass here options specified below and any other option
   * supported by https://github.com/hapijs/cookie
   */
  auth: AuthOptions;
};

/**
 * Actual method that Hapi uses under the hood when you call
 * server.register(plugin, options) method.
 * Options you give in Hapi are passed back to it.
 *
 * @memberof module:@adminjs/hapi
 * @example
 * const AdminJSPlugin = require('@adminjs/hapi')
 * const Hapi = require('@hapi/hapi')
 *
 * // see AdminJS documentation on database setup.
 * const yourDatabase = require('your-database-setup-file')
 *
 * const ADMIN = {
 *   email: 'text@example.com',
 *   password: 'password',
 * }
 *
 * const adminJsOptions = {
 *   resources: [yourDatabase],
 *   auth: {
 *     authenticate: (email, password) => {
 *       if (ADMIN.email === email && ADMIN.password === password) {
 *         return ADMIN
 *       }
 *       return null
 *     },
 *     strategy: 'session',
 *     cookieName: 'adminJsCookie',
 *     cookiePassword: process.env.COOKIE_PASSWORD || 'makesurepasswordissecure',
 *     isSecure: true, //only https requests
 *   },
 * }
 *
 * const server = Hapi.server({ port: process.env.PORT || 8080 })
 * const start = async () => {
 *   await server.register({
 *     plugin: AdminJSPlugin,
 *     options: adminJsOptions,
 *   })
 *
 *   await server.start()
 * }
 *
 * start()
 */
const register = async (server: Hapi, options: ExtendedAdminJSOptions) => {
  const { registerInert = true } = options;
  const { routes, assets } = AdminRouter;

  if (options.auth?.authenticate) {
    if (options.auth.strategy && options.auth.strategy !== 'session') {
      throw new Error(`When you give auth.authenticate as a parameter, auth strategy is set to 'session'.
                       Please remove auth.strategy from authentication parameters.
                      `);
    }
    options.auth.strategy = 'session';

    if (!options.auth.cookiePassword) {
      throw new Error('You have to give auth.cookiePassword parameter if you want to use authenticate function.');
    }
  }

  const admin = new AdminJS(options);
  await admin.initialize();

  if (options.auth?.authenticate) {
    await sessionAuth(server, admin);
  }

  routes.forEach((route) => {
    const opts =
      route.method === 'POST'
        ? {
            auth: options.auth?.strategy,
            payload: {
              allow: 'multipart/form-data',
              multipart: { output: 'stream' },
            },
          }
        : {
            auth: options.auth?.strategy,
          };

    server.route({
      method: route.method,
      path: `${admin.options.rootPath}${route.path}`,
      options: opts,
      handler: async (request, h) => {
        try {
          const loggedInUser = request.auth && request.auth.credentials;
          const controller = new route.Controller({ admin }, loggedInUser);
          const ret = await controller[route.action](request, h);
          const response = h.response(ret);
          if (route.contentType) {
            response.type(route.contentType);
          }
          return response;
        } catch (e) {
          if (e.statusCode >= 400 && e.statusCode < 500) {
            throw Boom.boomify(e, { statusCode: e.statusCode });
          } else {
            // eslint-disable-next-line no-console
            console.log(e);
            throw Boom.boomify(e);
          }
        }
      },
    });
  });

  if (registerInert && inert) {
    await server.register(inert);
  }

  assets.forEach((asset) => {
    server.route({
      method: 'GET',
      options: { auth: false },
      path: `${admin.options.rootPath}${asset.path}`,
      handler: {
        file: {
          path: path.resolve(asset.src),
          confine: false,
        },
      },
    });
  });

  return admin;
};

const AdminJSHapi = {
  name: info?.name ?? '@adminjs/hapi',
  version: info?.version ?? 5,
  register,
};

export default AdminJSHapi;