baublet/w8mngr

View on GitHub
api/permissionsServices/createPermissionService.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { ServiceContainer } from "@baublet/service-container";

import { log } from "../config/log";
import { Context, contextService } from "../createContext";
import { errors } from "../helpers";

type PermissionFunction = (
  context: Context,
  ...args: any[]
) => Promise<Error | true> | Error | true;

type ArgsWithoutContext<T> = T extends [Context, ...infer R] ? R : never;

export function createPermissionService<
  T extends Record<string, PermissionFunction>
>(
  functions: T
): (services: ServiceContainer) => {
  assert: <TPermission extends keyof T>(
    permission: TPermission,
    ...args: ArgsWithoutContext<Parameters<T[TPermission]>>
  ) => Promise<void>;
  materializeToPermissionsObject: (args: {
    [K in keyof T]?: ArgsWithoutContext<Parameters<T[K]>>;
  }) => Promise<Record<string, () => Promise<boolean>>>;
} {
  return (services) => {
    const context = services.get(contextService);
    return {
      assert: async (permission, ...args) => {
        const value = await functions[permission].call(
          undefined,
          context,
          ...args
        );
        if (value instanceof Error) {
          throw value;
        }
      },
      materializeToPermissionsObject: async (args) => {
        const permissions = {} as Record<string, () => Promise<boolean>>;
        for (const permission in args) {
          const functionArgs: any[] = args[permission] || [];
          const permissionFunction = functions[permission];
          permissions[permission] = async () => {
            const value = await permissionFunction.call(
              undefined,
              context,
              // @ts-ignore -- :spin-think: not sure what gives
              ...functionArgs
            );
            if (value instanceof Error) {
              log("warn", "Permission function returned false", { value });
              return false;
            }
            return true;
          };
        }
        return permissions;
      },
    };
  };
}

export function requireAuth(context: Context) {
  if (!context.getCurrentUser()) {
    throw new errors.Unauthorized(context);
  }
  return true as const;
}