imaginerio/narratives

View on GitHub
lists/User.js

Summary

Maintainability
A
2 hrs
Test Coverage
/* eslint-disable no-use-before-define */
const { Text, Select, Relationship, Checkbox, Password } = require('@keystonejs/fields');
const uuid = require('uuid').v4;

const { sendEmail } = require('../server/email');

// Access control functions
const userIsAdmin = ({ authentication: { item: user } }) => Boolean(user && user.isAdmin);
const userOwnsItem = ({ existingItem: { id }, authentication: { item: user } }) => {
  if (!user) {
    return false;
  }
  return id === user.id;
};

const userIsAdminOrOwner = auth => {
  const isAdmin = access.userIsAdmin(auth);
  const isOwner = access.userOwnsItem(auth);
  return isAdmin || isOwner;
};

const access = { userIsAdmin, userOwnsItem, userIsAdminOrOwner };

module.exports = {
  fields: {
    name: {
      type: Text,
      access: {
        read: true,
        update: access.userIsAdminOrOwner,
        create: true,
        delete: access.userIsAdminOrOwner,
      },
    },
    email: {
      type: Text,
      isUnique: true,
      isRequired: true,
      access: {
        read: true,
        update: access.userIsAdminOrOwner,
        create: true,
        delete: access.userIsAdminOrOwner,
      },
    },
    institution: {
      type: Text,
      access: {
        read: true,
        update: access.userIsAdminOrOwner,
        create: true,
        delete: access.userIsAdminOrOwner,
      },
    },
    language: {
      type: Select,
      options: [
        { value: 'en', label: 'English' },
        { value: 'pt', label: 'Portuguese' },
      ],
      access: {
        read: true,
        update: access.userIsAdminOrOwner,
        create: true,
        delete: access.userIsAdminOrOwner,
      },
    },
    verified: {
      type: Checkbox,
      access: {
        read: true,
        update: true,
        create: true,
        delete: true,
      },
    },
    verifyId: {
      type: Text,
      defaultValue: uuid(),
      access: {
        read: true,
        update: access.userIsAdmin,
        create: access.userIsAdmin,
        delete: access.userIsAdmin,
      },
    },
    resetId: {
      type: Text,
      access: {
        read: true,
        update: access.userIsAdminOrOwner,
        create: true,
        delete: true,
      },
    },
    isAdmin: {
      type: Checkbox,
      // Field-level access controls
      // Here, we set more restrictive field access so a non-admin cannot make themselves admin.
      access: {
        read: access.userIsAdminOrOwner,
        update: access.userIsAdmin,
        create: access.userIsAdmin,
        delete: access.userIsAdmin,
      },
    },
    password: {
      type: Password,
      access: {
        read: access.userIsAdminOrOwner,
        update: access.userIsAdminOrOwner,
        create: true,
        delete: access.userIsAdmin,
      },
    },
    termsAccepted: {
      type: Checkbox,
      access: {
        read: access.userIsAdminOrOwner,
        update: access.userIsAdminOrOwner,
        create: access.userIsAdminOrOwner,
        delete: access.userIsAdminOrOwner,
      },
    },
    privacyAccepted: {
      type: Checkbox,
      access: {
        read: access.userIsAdminOrOwner,
        update: access.userIsAdminOrOwner,
        create: access.userIsAdminOrOwner,
        delete: access.userIsAdminOrOwner,
      },
    },
    projects: {
      type: Relationship,
      ref: 'Project.user',
      many: true,
      access: {
        read: access.userIsAdminOrOwner,
        update: access.userIsAdminOrOwner,
        create: access.userIsAdmin,
        delete: access.userIsAdmin,
      },
    },
  },
  hooks: {
    afterChange: async ({ operation, updatedItem, context }) => {
      if (operation === 'create') {
        const {
          req: { protocol, hostname },
        } = context;
        sendEmail({
          to: updatedItem.email,
          key: updatedItem.verifyId,
          host: `${protocol}://${hostname}`,
          lang: updatedItem.language,
        });
      }
    },
  },
};