All files / src AuthDirective.ts

73.52% Statements 25/34
35.71% Branches 5/14
75% Functions 6/8
73.52% Lines 25/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 851x 1x                   11x 11x       3x 3x             2x 2x         11x 1x   10x   10x   10x 7x 7x 7x 6x       6x   6x 1x     5x 5x 5x 5x                                                         1x  
import { SchemaDirectiveVisitor } from "graphql-tools";
import {
  Action,
  AuthenticationError,
  AuthorizationError,
} from "@the-neon/core";
import Authorizer from "./Authorizer";
 
class AuthDirective extends SchemaDirectiveVisitor {
  authorizer: Authorizer;
  constructor(config: any, authorizer: Authorizer) {
    super(config);
    this.authorizer = authorizer;
  }
 
  public visitObject(type: any): void {
    this.ensureFieldsWrapped(type);
    type._requiredAuthRoles = this["args"]?.roles;
  }
 
  // Visitor methods for nested types like fields and arguments
  // also receive a details object that provides information about
  // the parent and grandparent types.
  public visitFieldDefinition(field, details) {
    this.ensureFieldsWrapped(details.objectType);
    field._requiredAuthRoles = this["args"]?.roles;
  }
 
  public ensureFieldsWrapped(objectType) {
    // Mark the GraphQLObjectType object to avoid re-wrapping:
    if (objectType._authFieldsWrapped) {
      return;
    }
    objectType._authFieldsWrapped = true;
 
    const fields = objectType.getFields();
 
    Object.keys(fields).forEach((fieldName) => {
      const field = fields[fieldName];
      const { resolve } = field;
      field.resolve = async function (...args) {
        const authorizer = (this as any).authorizer;
        // Get the required Role from the field first, falling back
        // to the objectType if no Role is required by the field:
        const requiredRoles: string[] =
          field._requiredAuthRoles || objectType._requiredAuthRoles;
 
        if (!requiredRoles) {
          return resolve.apply(this, args);
        }
 
        const context = args[2];
        const user = await authorizer?.getAuthenticatedUser();
        if (!user) {
          throw new AuthenticationError();
        }
 
        let isAllowed = user.isOwner;
 
        Iif (!isAllowed && requiredRoles[0] && user.permissions) {
          isAllowed = JSON.parse(
            Buffer.from(requiredRoles[0], "base64").toString()
          )
            .map(
              (permission) =>
                permission.entity === "any" ||
                (user.permissions[permission.entity] &
                  +Action[permission.action]) !==
                  0
            )
            .reduce((a, b) => a && b, true);
        }
 
        Iif (isAllowed) {
          context.user = user;
          return resolve.apply(this, args);
        }
        throw new AuthorizationError();
      };
    });
  }
}
 
export default AuthDirective;