TNOCS/node-auth

View on GitHub
src/lib/routes/authorize.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import { UNAUTHORIZED, FORBIDDEN } from 'http-status-codes';
import { Request, Response } from 'express';
import { Subject } from '../models/subject';
import { IPolicyStore } from '../authorize/policy-store';
import { PolicyDecisionPoint, initPDP } from '../authorize/pdp';
import { INodeAuthOptions } from '../models/options';
import { IPrivilegeRequest } from '../models/rule';
import { CRUD } from '../models/crud';
import { Action } from '../models/action';
import { ResponseMessage } from '../models/response-message';

export let policyStore: IPolicyStore;
let pdp: PolicyDecisionPoint;

function checkSubjectHasManagementPermission(subject: Subject, newPrivilege: IPrivilegeRequest, callback: (message: ResponseMessage) => void) {
  const pr = pdp.getPolicyResolver(newPrivilege.policySet);
  if (!pr) { callback({ success: false, message: 'Insufficient rights' }); }
  const permit = pr({ subject: subject, action: Action.Manage, resource: newPrivilege.resource });
  callback(permit ? { success: true } : { success: false, message: 'Insufficient rights' });
}

function getPolicyEditor(newPrivilege: IPrivilegeRequest) {
  const policy = newPrivilege.policy || -1;
  if (typeof policy === 'number') {
    const policySet = policyStore.getPolicySet(newPrivilege.policySet);
    if (policy >= policySet.policies.length) { return null; }
    // When the requested policy is -1, use the last one.
    const policyName = policySet.policies[policy < 0 ? policySet.policies.length - 1 : policy].name;
    return policyStore.getPolicyEditor(policyName);
  } else {
    return policyStore.getPolicyEditor(policy);
  }
}

function createPrivilege(newPrivilege: IPrivilegeRequest) {
  const policyEditor = getPolicyEditor(newPrivilege);
  if (!policyEditor) { return null; }
  return policyEditor('add', newPrivilege);
}

function updatePrivilege(newPrivilege: IPrivilegeRequest) {
  const policyEditor = getPolicyEditor(newPrivilege);
  if (!policyEditor) { return null; }
  return policyEditor('update', newPrivilege);
}

function deletePrivilege(newPrivilege: IPrivilegeRequest) {
  const policyEditor = getPolicyEditor(newPrivilege);
  if (!policyEditor) { return null; }
  return policyEditor('delete', newPrivilege);
}

function getPrivilegeRequest(req: Request, res: Response) {
  const newPrivilege: IPrivilegeRequest = req['body'];
  newPrivilege.policySet = newPrivilege.policySet || policyStore.getDefaultPolicySet().name;
  if (!newPrivilege || !(newPrivilege.subject || newPrivilege.action || newPrivilege.resource)) {
    res.status(FORBIDDEN).json({ success: false, message: 'Unknown body, expected { subject, action, resource } message.' });
    return null;
  }
  return newPrivilege;
}

/**
 * EXPORTED FUNCTIONS
 */

export function init(options: INodeAuthOptions) {
  if (!options.policyStore) { throw new Error('No PolicyStore defined! In case you do not turn of options.authorizations, you need to supply a policy store.'); }
  policyStore = options.policyStore;
  pdp = initPDP(policyStore);
}

export function getSubjectPrivileges(req: Request, res: Response) {
  const user: Subject = req['user'];
  if (!user) {
    res.status(FORBIDDEN).json({ success: false, message: 'Service only available for authenticated users.' });
  } else {
    res.json({ success: true, message: policyStore.getSubjectPrivileges(user) });
  }
}

export function getResourcePrivileges(req: Request, res: Response) {
  const subject: Subject = req['user'];
  if (!subject) {
    res.status(FORBIDDEN).json({ success: false, message: 'Service only available for authenticated users.' });
  } else {
    const newPrivilege = getPrivilegeRequest(req, res);
    if (!newPrivilege) { return; }
    checkSubjectHasManagementPermission(subject, newPrivilege, (msg: { success: boolean, message: string }) => {
      if (!msg.success) { res.status(UNAUTHORIZED).json({ success: false, message: msg.message }); }
    });
    res.json({ success: true, message: policyStore.getResourcePrivileges(newPrivilege.resource) });
  }
}

function crudPrivileges(change: CRUD, req: Request, res: Response, handler: (pr: IPrivilegeRequest) => (msg: ResponseMessage) => void) {
  const subject: Subject = req['user'];
  if (!subject) { return; }
  const newPrivilege = getPrivilegeRequest(req, res);
  if (!newPrivilege) { return; }
  if (change !== 'create' && !newPrivilege.hasOwnProperty('$loki')) {
    res.status(UNAUTHORIZED).json({ success: false, message: '$loki key is missing, original rule should be returned' });
    return;
  }
  checkSubjectHasManagementPermission(subject, newPrivilege, handler(newPrivilege));
}

export function createPrivileges(req: Request, res: Response) {
  const handler = (newPrivilegeReq: IPrivilegeRequest) => {
    return (msg: ResponseMessage) => {
      if (msg.success) {
        const ruleStatus = createPrivilege(newPrivilegeReq);
        if (ruleStatus) {
          res
            .status(ruleStatus.status) // rule === newPrivilegeReq ? NOT_MODIFIED :
            .json({ success: true, message: ruleStatus.rule });
        } else {
          res.status(UNAUTHORIZED).json(msg);
        }
      } else {
        res.status(UNAUTHORIZED).json(msg);
      }
    };
  };
  crudPrivileges('create', req, res, handler);
}

export function updatePrivileges(req: Request, res: Response) {
  const handler = (newPrivilege: IPrivilegeRequest) => {
    return (msg: ResponseMessage) => {
      if (msg.success) {
        const ruleStatus = updatePrivilege(newPrivilege);
        if (ruleStatus) {
          res.json({ success: true, message: ruleStatus.rule });
        } else {
          res.status(UNAUTHORIZED).json(msg);
        }
      } else {
        res.status(UNAUTHORIZED).json(msg);
      }
    };
  };
  crudPrivileges('update', req, res, handler);
}

export function deletePrivileges(req: Request, res: Response) {
  const handler = (newPrivilege: IPrivilegeRequest) => {
    return (msg: ResponseMessage) => {
      if (msg.success) {
        const ruleStatus = deletePrivilege(newPrivilege);
        if (ruleStatus) {
          res.status(ruleStatus.status).end();
        } else {
          res.status(UNAUTHORIZED).json(msg);
        }
      } else {
        res.status(UNAUTHORIZED).json(msg);
      }
    };
  };
  crudPrivileges('delete', req, res, handler);
}