From 098b5be274bb8dfd5bfeea37982ec01bfe88d3a6 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 18 May 2020 16:39:40 -0400 Subject: [PATCH] flattens user into collections --- demo/payload.config.js | 6 +- package.json | 2 +- .../users.spec.js => auth/auth.spec.js} | 10 +- src/{users => auth}/baseFields.js | 0 src/auth/default.js | 14 ++ src/{users => auth}/executePolicy.js | 0 .../graphql/resolvers/forgotPassword.js | 4 +- .../graphql/resolvers/index.js | 0 src/{users => auth}/graphql/resolvers/init.js | 0 .../graphql/resolvers/login.js | 4 +- src/{users => auth}/graphql/resolvers/me.js | 0 .../graphql/resolvers/policies.js | 3 +- .../graphql/resolvers/refresh.js | 4 +- .../graphql/resolvers/register.js | 4 +- .../graphql/resolvers/resetPassword.js | 4 +- .../graphql/resolvers/update.js | 0 .../operations/forgotPassword.js | 16 +- src/{users => auth}/operations/index.js | 0 src/{users => auth}/operations/init.js | 0 src/{users => auth}/operations/login.js | 19 +- src/{users => auth}/operations/policies.js | 5 +- src/{users => auth}/operations/refresh.js | 8 +- src/{users => auth}/operations/register.js | 17 +- .../operations/registerFirstUser.js | 8 +- .../operations/resetPassword.js | 25 ++- src/{users => auth}/operations/update.js | 2 +- .../requestHandlers/forgotPassword.js | 2 +- src/{users => auth}/requestHandlers/index.js | 0 src/{users => auth}/requestHandlers/init.js | 0 src/{users => auth}/requestHandlers/login.js | 6 +- src/{users => auth}/requestHandlers/me.js | 0 .../requestHandlers/policies.js | 1 + .../requestHandlers/refresh.js | 5 +- .../requestHandlers/register.js | 6 +- .../requestHandlers/registerFirstUser.js | 6 +- .../requestHandlers/resetPassword.js | 6 +- src/{users => auth}/requestHandlers/update.js | 0 src/{users => auth}/routes.js | 36 ++-- src/{users => auth}/strategies/apiKey.js | 0 src/{users => auth}/strategies/jwt.js | 6 +- src/collections/buildSchema.js | 4 +- .../graphql/{register.js => init.js} | 111 +++++++++- src/collections/init.js | 42 +++- src/collections/operations/create.js | 2 +- src/collections/operations/delete.js | 2 +- src/collections/operations/find.js | 2 +- src/collections/operations/findByID.js | 2 +- src/collections/operations/update.js | 2 +- .../requestHandlers/collections.spec.js | 8 +- src/errors/AuthenticationError.js | 2 +- src/globals/graphql/{register.js => init.js} | 0 src/globals/operations/findOne.js | 2 +- src/globals/operations/update.js | 2 +- src/graphql/index.js | 15 +- src/graphql/schema/buildObjectType.js | 10 +- src/index.js | 7 +- src/tests/credentials.js | 2 +- src/tests/globalSetup.js | 2 +- src/users/graphql/init.js | 191 ------------------ src/users/init.js | 35 ---- src/users/sanitize.js | 46 ----- src/utilities/sanitizeConfig.js | 11 + src/utilities/secureConfig.js | 4 +- yarn.lock | 19 +- 64 files changed, 321 insertions(+), 431 deletions(-) rename src/{users/users.spec.js => auth/auth.spec.js} (89%) rename src/{users => auth}/baseFields.js (100%) create mode 100644 src/auth/default.js rename src/{users => auth}/executePolicy.js (100%) rename src/{users => auth}/graphql/resolvers/forgotPassword.js (71%) rename src/{users => auth}/graphql/resolvers/index.js (100%) rename src/{users => auth}/graphql/resolvers/init.js (100%) rename src/{users => auth}/graphql/resolvers/login.js (77%) rename src/{users => auth}/graphql/resolvers/me.js (100%) rename src/{users => auth}/graphql/resolvers/policies.js (70%) rename src/{users => auth}/graphql/resolvers/refresh.js (77%) rename src/{users => auth}/graphql/resolvers/register.js (84%) rename src/{users => auth}/graphql/resolvers/resetPassword.js (81%) rename src/{users => auth}/graphql/resolvers/update.js (100%) rename src/{users => auth}/operations/forgotPassword.js (80%) rename src/{users => auth}/operations/index.js (100%) rename src/{users => auth}/operations/init.js (100%) rename src/{users => auth}/operations/login.js (74%) rename src/{users => auth}/operations/policies.js (88%) rename src/{users => auth}/operations/refresh.js (83%) rename src/{users => auth}/operations/register.js (80%) rename src/{users => auth}/operations/registerFirstUser.js (87%) rename src/{users => auth}/operations/resetPassword.js (73%) rename src/{users => auth}/operations/update.js (96%) rename src/{users => auth}/requestHandlers/forgotPassword.js (94%) rename src/{users => auth}/requestHandlers/index.js (100%) rename src/{users => auth}/requestHandlers/init.js (100%) rename src/{users => auth}/requestHandlers/login.js (81%) rename src/{users => auth}/requestHandlers/me.js (100%) rename src/{users => auth}/requestHandlers/policies.js (94%) rename src/{users => auth}/requestHandlers/refresh.js (84%) rename src/{users => auth}/requestHandlers/register.js (79%) rename src/{users => auth}/requestHandlers/registerFirstUser.js (79%) rename src/{users => auth}/requestHandlers/resetPassword.js (81%) rename src/{users => auth}/requestHandlers/update.js (100%) rename src/{users => auth}/routes.js (56%) rename src/{users => auth}/strategies/apiKey.js (100%) rename src/{users => auth}/strategies/jwt.js (72%) rename src/collections/graphql/{register.js => init.js} (52%) rename src/globals/graphql/{register.js => init.js} (100%) delete mode 100644 src/users/graphql/init.js delete mode 100644 src/users/init.js delete mode 100644 src/users/sanitize.js diff --git a/demo/payload.config.js b/demo/payload.config.js index 14cfffe891..eda897b1d7 100644 --- a/demo/payload.config.js +++ b/demo/payload.config.js @@ -12,7 +12,11 @@ const Header = require('./globals/Header'); const Footer = require('./globals/Footer'); module.exports = { - // disableAdmin: true, + secret: 'SECRET_KEY', + admin: { + user: 'users', + disable: false, + }, collections: [ Page, Category, diff --git a/package.json b/package.json index 0faacb4293..dc97917877 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "passport-headerapikey": "^1.2.1", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", - "passport-local-mongoose": "^5.0.1", + "passport-local-mongoose": "^6.0.1", "postcss-preset-env": "6.0.6", "prop-types": "^15.7.2", "qs": "^6.9.1", diff --git a/src/users/users.spec.js b/src/auth/auth.spec.js similarity index 89% rename from src/users/users.spec.js rename to src/auth/auth.spec.js index 8adc0dacdb..1ad1c5c03b 100644 --- a/src/users/users.spec.js +++ b/src/auth/auth.spec.js @@ -15,7 +15,7 @@ let token = null; describe('Users REST API', () => { it('should prevent registering a first user', async () => { - const response = await fetch(`${url}/api/first-register`, { + const response = await fetch(`${url}/api/users/first-register`, { body: JSON.stringify({ username: 'thisuser@shouldbeprevented.com', password: 'get-out', @@ -30,7 +30,7 @@ describe('Users REST API', () => { }); it('should login a user successfully', async () => { - const response = await fetch(`${url}/api/login`, { + const response = await fetch(`${url}/api/users/login`, { body: JSON.stringify({ username, password, @@ -50,7 +50,7 @@ describe('Users REST API', () => { }); it('should return a logged in user from /me', async () => { - const response = await fetch(`${url}/api/me`, { + const response = await fetch(`${url}/api/users/me`, { headers: { Authorization: `JWT ${token}`, }, @@ -63,7 +63,7 @@ describe('Users REST API', () => { }); it('should refresh a token and reset its expiration', async () => { - const response = await fetch(`${url}/api/refresh-token`, { + const response = await fetch(`${url}/api/users/refresh-token`, { method: 'post', headers: { Authorization: `JWT ${token}`, @@ -81,7 +81,7 @@ describe('Users REST API', () => { it('should allow forgot-password by email', async () => { // TODO: figure out how to spy on payload instance functions // const mailSpy = jest.spyOn(payload, 'sendEmail'); - const response = await fetch(`${url}/api/forgot-password`, { + const response = await fetch(`${url}/api/users/forgot-password`, { method: 'post', body: JSON.stringify({ username, diff --git a/src/users/baseFields.js b/src/auth/baseFields.js similarity index 100% rename from src/users/baseFields.js rename to src/auth/baseFields.js diff --git a/src/auth/default.js b/src/auth/default.js new file mode 100644 index 0000000000..1a360ff6dc --- /dev/null +++ b/src/auth/default.js @@ -0,0 +1,14 @@ +const defaultUser = { + slug: 'users', + labels: { + singular: 'User', + plural: 'Users', + }, + useAsTitle: 'username', + auth: { + tokenExpiration: 7200, + }, + timestamps: true, +}; + +module.exports = defaultUser; diff --git a/src/users/executePolicy.js b/src/auth/executePolicy.js similarity index 100% rename from src/users/executePolicy.js rename to src/auth/executePolicy.js diff --git a/src/users/graphql/resolvers/forgotPassword.js b/src/auth/graphql/resolvers/forgotPassword.js similarity index 71% rename from src/users/graphql/resolvers/forgotPassword.js rename to src/auth/graphql/resolvers/forgotPassword.js index 191586b43d..418b2f303e 100644 --- a/src/users/graphql/resolvers/forgotPassword.js +++ b/src/auth/graphql/resolvers/forgotPassword.js @@ -1,10 +1,10 @@ /* eslint-disable no-param-reassign */ const { forgotPassword } = require('../../operations'); -const forgotPasswordResolver = (config, Model, email) => async (_, args, context) => { +const forgotPasswordResolver = (config, collection, email) => async (_, args, context) => { const options = { config, - Model, + collection, data: args, email, req: context, diff --git a/src/users/graphql/resolvers/index.js b/src/auth/graphql/resolvers/index.js similarity index 100% rename from src/users/graphql/resolvers/index.js rename to src/auth/graphql/resolvers/index.js diff --git a/src/users/graphql/resolvers/init.js b/src/auth/graphql/resolvers/init.js similarity index 100% rename from src/users/graphql/resolvers/init.js rename to src/auth/graphql/resolvers/init.js diff --git a/src/users/graphql/resolvers/login.js b/src/auth/graphql/resolvers/login.js similarity index 77% rename from src/users/graphql/resolvers/login.js rename to src/auth/graphql/resolvers/login.js index d1e36ce1b9..c5f0050f7b 100644 --- a/src/users/graphql/resolvers/login.js +++ b/src/auth/graphql/resolvers/login.js @@ -1,9 +1,9 @@ /* eslint-disable no-param-reassign */ const { login } = require('../../operations'); -const loginResolver = ({ Model, config }) => async (_, args, context) => { +const loginResolver = (config, collection) => async (_, args, context) => { const options = { - Model, + collection, config, data: { username: args.username, diff --git a/src/users/graphql/resolvers/me.js b/src/auth/graphql/resolvers/me.js similarity index 100% rename from src/users/graphql/resolvers/me.js rename to src/auth/graphql/resolvers/me.js diff --git a/src/users/graphql/resolvers/policies.js b/src/auth/graphql/resolvers/policies.js similarity index 70% rename from src/users/graphql/resolvers/policies.js rename to src/auth/graphql/resolvers/policies.js index 6ef96a0c13..faa9577aa2 100644 --- a/src/users/graphql/resolvers/policies.js +++ b/src/auth/graphql/resolvers/policies.js @@ -1,8 +1,9 @@ const { policies } = require('../../operations'); -const policyResolver = config => async (_, __, context) => { +const policyResolver = (config, collection) => async (_, __, context) => { const options = { config, + collection, req: context, }; diff --git a/src/users/graphql/resolvers/refresh.js b/src/auth/graphql/resolvers/refresh.js similarity index 77% rename from src/users/graphql/resolvers/refresh.js rename to src/auth/graphql/resolvers/refresh.js index 1af3dfa289..cdea2b3a02 100644 --- a/src/users/graphql/resolvers/refresh.js +++ b/src/auth/graphql/resolvers/refresh.js @@ -1,10 +1,10 @@ /* eslint-disable no-param-reassign */ const { refresh } = require('../../operations'); -const refreshResolver = ({ Model, config }) => async (_, __, context) => { +const refreshResolver = (config, collection) => async (_, __, context) => { const options = { config, - Model, + collection, authorization: context.headers.authorization, req: context, }; diff --git a/src/users/graphql/resolvers/register.js b/src/auth/graphql/resolvers/register.js similarity index 84% rename from src/users/graphql/resolvers/register.js rename to src/auth/graphql/resolvers/register.js index f20195a534..ca160ad7a0 100644 --- a/src/users/graphql/resolvers/register.js +++ b/src/auth/graphql/resolvers/register.js @@ -1,10 +1,10 @@ /* eslint-disable no-param-reassign */ const { register } = require('../../operations'); -const registerResolver = ({ Model, config }) => async (_, args, context) => { +const registerResolver = (config, collection) => async (_, args, context) => { const options = { config, - Model, + collection, data: args.data, depth: 0, req: context, diff --git a/src/users/graphql/resolvers/resetPassword.js b/src/auth/graphql/resolvers/resetPassword.js similarity index 81% rename from src/users/graphql/resolvers/resetPassword.js rename to src/auth/graphql/resolvers/resetPassword.js index 93144f2adb..ff9662814c 100644 --- a/src/users/graphql/resolvers/resetPassword.js +++ b/src/auth/graphql/resolvers/resetPassword.js @@ -1,12 +1,12 @@ /* eslint-disable no-param-reassign */ const { resetPassword } = require('../../operations'); -const resetPasswordResolver = ({ Model, config }) => async (_, args, context) => { +const resetPasswordResolver = (config, collection) => async (_, args, context) => { if (args.locale) context.locale = args.locale; if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale; const options = { - Model, + collection, config, data: args, req: context, diff --git a/src/users/graphql/resolvers/update.js b/src/auth/graphql/resolvers/update.js similarity index 100% rename from src/users/graphql/resolvers/update.js rename to src/auth/graphql/resolvers/update.js diff --git a/src/users/operations/forgotPassword.js b/src/auth/operations/forgotPassword.js similarity index 80% rename from src/users/operations/forgotPassword.js rename to src/auth/operations/forgotPassword.js index 2e48a41ae0..0e037ee665 100644 --- a/src/users/operations/forgotPassword.js +++ b/src/auth/operations/forgotPassword.js @@ -13,10 +13,10 @@ const forgotPassword = async (args) => { // 1. Execute before login hook // ///////////////////////////////////// - const beforeForgotPasswordHook = args.config.User.hooks && args.config.User.hooks.beforeForgotPassword; + const { beforeForgotPassword } = args.collection.config.hooks; - if (typeof beforeForgotPasswordHook === 'function') { - options = await beforeForgotPasswordHook(options); + if (typeof beforeForgotPassword === 'function') { + options = await beforeForgotPassword(options); } // ///////////////////////////////////// @@ -24,7 +24,9 @@ const forgotPassword = async (args) => { // ///////////////////////////////////// const { - Model, + collection: { + Model, + }, config, data, email, @@ -60,10 +62,10 @@ const forgotPassword = async (args) => { // 3. Execute after forgot password hook // ///////////////////////////////////// - const afterForgotPasswordHook = args.config.User.hooks && args.config.User.hooks.afterForgotPassword; + const { afterForgotPassword } = args.req.collection.config.hooks; - if (typeof afterForgotPasswordHook === 'function') { - await afterForgotPasswordHook(options); + if (typeof afterForgotPassword === 'function') { + await afterForgotPassword(options); } } catch (error) { throw error; diff --git a/src/users/operations/index.js b/src/auth/operations/index.js similarity index 100% rename from src/users/operations/index.js rename to src/auth/operations/index.js diff --git a/src/users/operations/init.js b/src/auth/operations/init.js similarity index 100% rename from src/users/operations/init.js rename to src/auth/operations/init.js diff --git a/src/users/operations/login.js b/src/auth/operations/login.js similarity index 74% rename from src/users/operations/login.js rename to src/auth/operations/login.js index cf19c89426..b576d6aba1 100644 --- a/src/users/operations/login.js +++ b/src/auth/operations/login.js @@ -1,5 +1,5 @@ const jwt = require('jsonwebtoken'); -const { Forbidden, AuthenticationError } = require('../../errors'); +const { Unauthorized, AuthenticationError } = require('../../errors'); const login = async (args) => { try { @@ -11,7 +11,7 @@ const login = async (args) => { // 1. Execute before login hook // ///////////////////////////////////// - const beforeLoginHook = args.config.hooks && args.config.hooks.beforeLogin; + const beforeLoginHook = args.collection.config.hooks.beforeLogin; if (typeof beforeLoginHook === 'function') { options = await beforeLoginHook(options); @@ -22,7 +22,10 @@ const login = async (args) => { // ///////////////////////////////////// const { - Model, + collection: { + Model, + config: collectionConfig, + }, config, data, } = options; @@ -31,7 +34,7 @@ const login = async (args) => { const user = await Model.findByUsername(username); - if (!user) throw new Forbidden(); + if (!user) throw new AuthenticationError(); const authResult = await user.authenticate(password); @@ -39,7 +42,7 @@ const login = async (args) => { throw new AuthenticationError(); } - const fieldsToSign = config.fields.reduce((signedFields, field) => { + const fieldsToSign = collectionConfig.fields.reduce((signedFields, field) => { if (field.saveToJWT) { return { ...signedFields, @@ -53,9 +56,9 @@ const login = async (args) => { const token = jwt.sign( fieldsToSign, - config.auth.secretKey, + config.secret, { - expiresIn: config.auth.tokenExpiration, + expiresIn: collectionConfig.auth.tokenExpiration, }, ); @@ -63,7 +66,7 @@ const login = async (args) => { // 3. Execute after login hook // ///////////////////////////////////// - const afterLoginHook = args.config && args.config.hooks && args.config.hooks.afterLogin; + const afterLoginHook = args.collection.config.hooks.afterLogin; if (typeof afterLoginHook === 'function') { await afterLoginHook({ ...options, token, user }); diff --git a/src/users/operations/policies.js b/src/auth/operations/policies.js similarity index 88% rename from src/users/operations/policies.js rename to src/auth/operations/policies.js index b25749070c..2a32701817 100644 --- a/src/users/operations/policies.js +++ b/src/auth/operations/policies.js @@ -4,6 +4,9 @@ const policies = async (args) => { try { const { config, + collection: { + config: collectionConfig, + }, req, req: { user }, } = args; @@ -34,7 +37,7 @@ const policies = async (args) => { }; const policyResults = { - canAccessAdmin: config.User.policies && config.User.policies.admin ? config.User.policies.admin(args) : isLoggedIn, + canAccessAdmin: collectionConfig.policies.admin ? collectionConfig.policies.admin(args) : isLoggedIn, }; config.collections.forEach((collection) => { diff --git a/src/users/operations/refresh.js b/src/auth/operations/refresh.js similarity index 83% rename from src/users/operations/refresh.js rename to src/auth/operations/refresh.js index 6b5abaabab..d00ecf30fb 100644 --- a/src/users/operations/refresh.js +++ b/src/auth/operations/refresh.js @@ -10,7 +10,7 @@ const refresh = async (args) => { // 1. Execute before refresh hook // ///////////////////////////////////// - const { beforeRefresh } = args.config.hooks; + const { beforeRefresh } = args.collection.config.hooks; if (typeof beforeRefresh === 'function') { options = await beforeRefresh(options); @@ -20,9 +20,9 @@ const refresh = async (args) => { // 2. Perform refresh // ///////////////////////////////////// - const secret = options.config.auth.secretKey; + const { secret } = options.config; const opts = {}; - opts.expiresIn = options.config.auth.tokenExpiration; + opts.expiresIn = options.collection.config.auth.tokenExpiration; const token = options.authorization.replace('JWT ', ''); const payload = jwt.verify(token, secret, {}); @@ -34,7 +34,7 @@ const refresh = async (args) => { // 3. Execute after login hook // ///////////////////////////////////// - const { afterRefresh } = args.config.hooks; + const { afterRefresh } = args.collection.config.hooks; if (typeof afterRefresh === 'function') { await afterRefresh(options, refreshedToken); diff --git a/src/users/operations/register.js b/src/auth/operations/register.js similarity index 80% rename from src/users/operations/register.js rename to src/auth/operations/register.js index 394e6cbdc9..844c311873 100644 --- a/src/users/operations/register.js +++ b/src/auth/operations/register.js @@ -1,5 +1,5 @@ const passport = require('passport'); -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../executePolicy'); const executeFieldHooks = require('../../fields/executeHooks'); const { validateCreate } = require('../../fields/validateCreate'); @@ -10,7 +10,7 @@ const register = async (args) => { // ///////////////////////////////////// if (!args.overridePolicy) { - await executePolicy(args, args.config.policies.register); + await executePolicy(args, args.collection.config.policies.register); } let options = { ...args }; @@ -19,19 +19,19 @@ const register = async (args) => { // 2. Validate incoming data // ///////////////////////////////////// - await validateCreate(args.data, args.config.fields); + await validateCreate(args.data, args.collection.config.fields); // ///////////////////////////////////// // 3. Execute before register field-level hooks // ///////////////////////////////////// - options.data = await executeFieldHooks(options, args.config.fields, args.data, 'beforeCreate'); + options.data = await executeFieldHooks(options, args.collection.config.fields, args.data, 'beforeCreate'); // ///////////////////////////////////// // 4. Execute before register hook // ///////////////////////////////////// - const { beforeRegister } = args.config.hooks; + const { beforeRegister } = args.collection.config.hooks; if (typeof beforeRegister === 'function') { options = await beforeRegister(options); @@ -42,7 +42,9 @@ const register = async (args) => { // ///////////////////////////////////// const { - Model, + collection: { + Model, + }, data, req: { locale, @@ -69,7 +71,7 @@ const register = async (args) => { // 6. Execute after register hook // ///////////////////////////////////// - const afterRegister = args.config.hooks; + const afterRegister = args.collection.config.hooks; if (typeof afterRegister === 'function') { result = await afterRegister(options, result); @@ -78,6 +80,7 @@ const register = async (args) => { // ///////////////////////////////////// // 7. Return user // ///////////////////////////////////// + return result.toJSON({ virtuals: true }); } catch (error) { throw error; diff --git a/src/users/operations/registerFirstUser.js b/src/auth/operations/registerFirstUser.js similarity index 87% rename from src/users/operations/registerFirstUser.js rename to src/auth/operations/registerFirstUser.js index d74f2984d6..f37bb50a23 100644 --- a/src/users/operations/registerFirstUser.js +++ b/src/auth/operations/registerFirstUser.js @@ -4,7 +4,7 @@ const { Forbidden } = require('../../errors'); const registerFirstUser = async (args) => { try { - const count = await args.Model.countDocuments({}); + const count = await args.collection.Model.countDocuments({}); if (count >= 1) throw new Forbidden(); @@ -16,7 +16,7 @@ const registerFirstUser = async (args) => { // 1. Execute before register first user hook // ///////////////////////////////////// - const { beforeRegister } = args.config.hooks; + const { beforeRegister } = args.collection.config.hooks; if (typeof beforeRegister === 'function') { options = await beforeRegister(options); @@ -36,7 +36,9 @@ const registerFirstUser = async (args) => { // 3. Log in new user // ///////////////////////////////////// - const token = await login(options); + const token = await login({ + ...options, + }); result = { ...result, diff --git a/src/users/operations/resetPassword.js b/src/auth/operations/resetPassword.js similarity index 73% rename from src/users/operations/resetPassword.js rename to src/auth/operations/resetPassword.js index 371a37db55..b0f40e27bd 100644 --- a/src/users/operations/resetPassword.js +++ b/src/auth/operations/resetPassword.js @@ -14,10 +14,10 @@ const resetPassword = async (args) => { // 1. Execute before reset password hook // ///////////////////////////////////// - const beforeResetPasswordHook = args.config.hooks && args.config.hooks.beforeResetPassword; + const { beforeResetPassword } = args.collection.config.hooks; - if (typeof beforeResetPasswordHook === 'function') { - options = await beforeResetPasswordHook(options); + if (typeof beforeResetPassword === 'function') { + options = await beforeResetPassword(options); } // ///////////////////////////////////// @@ -25,7 +25,10 @@ const resetPassword = async (args) => { // ///////////////////////////////////// const { - Model, + collection: { + Model, + config: collectionConfig, + }, config, data, } = options; @@ -48,7 +51,7 @@ const resetPassword = async (args) => { await user.authenticate(data.password); - const fieldsToSign = config.fields.reduce((signedFields, field) => { + const fieldsToSign = collectionConfig.fields.reduce((signedFields, field) => { if (field.saveToJWT) { return { ...signedFields, @@ -57,14 +60,14 @@ const resetPassword = async (args) => { } return signedFields; }, { - [usernameField]: username, + username, }); const token = jwt.sign( fieldsToSign, - config.auth.secretKey, + config.secret, { - expiresIn: config.auth.tokenExpiration, + expiresIn: collectionConfig.auth.tokenExpiration, }, ); @@ -72,10 +75,10 @@ const resetPassword = async (args) => { // 3. Execute after reset password hook // ///////////////////////////////////// - const afterResetPasswordHook = args.config.hooks && args.config.hooks.afterResetPassword; + const { afterResetPassword } = collectionConfig.hooks; - if (typeof afterResetPasswordHook === 'function') { - await afterResetPasswordHook(options, user); + if (typeof afterResetPassword === 'function') { + await afterResetPassword(options, user); } // ///////////////////////////////////// diff --git a/src/users/operations/update.js b/src/auth/operations/update.js similarity index 96% rename from src/users/operations/update.js rename to src/auth/operations/update.js index 95b9b00fbe..9008c7597f 100644 --- a/src/users/operations/update.js +++ b/src/auth/operations/update.js @@ -1,6 +1,6 @@ const { NotFound } = require('../../errors'); -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../executePolicy'); const update = async (args) => { try { diff --git a/src/users/requestHandlers/forgotPassword.js b/src/auth/requestHandlers/forgotPassword.js similarity index 94% rename from src/users/requestHandlers/forgotPassword.js rename to src/auth/requestHandlers/forgotPassword.js index c4dcd040c9..96eda790fb 100644 --- a/src/users/requestHandlers/forgotPassword.js +++ b/src/auth/requestHandlers/forgotPassword.js @@ -6,7 +6,7 @@ const forgotPasswordHandler = (config, email) => async (req, res) => { try { await forgotPassword({ req, - Model: req.collection.Model, + collection: req.collection, config, data: req.body, email, diff --git a/src/users/requestHandlers/index.js b/src/auth/requestHandlers/index.js similarity index 100% rename from src/users/requestHandlers/index.js rename to src/auth/requestHandlers/index.js diff --git a/src/users/requestHandlers/init.js b/src/auth/requestHandlers/init.js similarity index 100% rename from src/users/requestHandlers/init.js rename to src/auth/requestHandlers/init.js diff --git a/src/users/requestHandlers/login.js b/src/auth/requestHandlers/login.js similarity index 81% rename from src/users/requestHandlers/login.js rename to src/auth/requestHandlers/login.js index cee4953bce..bbaea52263 100644 --- a/src/users/requestHandlers/login.js +++ b/src/auth/requestHandlers/login.js @@ -2,12 +2,12 @@ const httpStatus = require('http-status'); const formatErrorResponse = require('../../express/responses/formatError'); const { login } = require('../operations'); -const loginHandler = async (req, res) => { +const loginHandler = config => async (req, res) => { try { const token = await login({ req, - Model: req.collection.Model, - config: req.collection.config, + collection: req.collection, + config, data: req.body, }); diff --git a/src/users/requestHandlers/me.js b/src/auth/requestHandlers/me.js similarity index 100% rename from src/users/requestHandlers/me.js rename to src/auth/requestHandlers/me.js diff --git a/src/users/requestHandlers/policies.js b/src/auth/requestHandlers/policies.js similarity index 94% rename from src/users/requestHandlers/policies.js rename to src/auth/requestHandlers/policies.js index 783f62fda4..6c156da398 100644 --- a/src/users/requestHandlers/policies.js +++ b/src/auth/requestHandlers/policies.js @@ -7,6 +7,7 @@ const policiesHandler = config => async (req, res) => { const policyResults = await policies({ req, config, + collection: req.collection, }); return res.status(httpStatus.OK) diff --git a/src/users/requestHandlers/refresh.js b/src/auth/requestHandlers/refresh.js similarity index 84% rename from src/users/requestHandlers/refresh.js rename to src/auth/requestHandlers/refresh.js index c11532b125..bf6d6c6192 100644 --- a/src/users/requestHandlers/refresh.js +++ b/src/auth/requestHandlers/refresh.js @@ -2,11 +2,12 @@ const httpStatus = require('http-status'); const formatErrorResponse = require('../../express/responses/formatError'); const { refresh } = require('../operations'); -const refreshHandler = async (req, res) => { +const refreshHandler = config => async (req, res) => { try { const refreshedToken = await refresh({ req, - config: req.collection.config, + collection: req.collection, + config, authorization: req.headers.authorization, }); diff --git a/src/users/requestHandlers/register.js b/src/auth/requestHandlers/register.js similarity index 79% rename from src/users/requestHandlers/register.js rename to src/auth/requestHandlers/register.js index 9e2c8d873b..214d1745d8 100644 --- a/src/users/requestHandlers/register.js +++ b/src/auth/requestHandlers/register.js @@ -2,13 +2,13 @@ const httpStatus = require('http-status'); const formatErrorResponse = require('../../express/responses/formatError'); const { register } = require('../operations'); -const registerHandler = async (req, res) => { +const registerHandler = config => async (req, res) => { try { const user = await register({ + config, + collection: req.collection, req, data: req.body, - Model: req.collection.Model, - config: req.collection.config, }); return res.status(201).json(user); diff --git a/src/users/requestHandlers/registerFirstUser.js b/src/auth/requestHandlers/registerFirstUser.js similarity index 79% rename from src/users/requestHandlers/registerFirstUser.js rename to src/auth/requestHandlers/registerFirstUser.js index cb583963d6..a53c214537 100644 --- a/src/users/requestHandlers/registerFirstUser.js +++ b/src/auth/requestHandlers/registerFirstUser.js @@ -2,12 +2,12 @@ const httpStatus = require('http-status'); const formatErrorResponse = require('../../express/responses/formatError'); const { registerFirstUser } = require('../operations'); -const registerFirstUserHandler = async (req, res) => { +const registerFirstUserHandler = config => async (req, res) => { try { const firstUser = await registerFirstUser({ req, - Model: req.collection.Model, - config: req.collection.config, + config, + collection: req.collection, data: req.body, }); diff --git a/src/users/requestHandlers/resetPassword.js b/src/auth/requestHandlers/resetPassword.js similarity index 81% rename from src/users/requestHandlers/resetPassword.js rename to src/auth/requestHandlers/resetPassword.js index 8cb6d49b4a..5406f3a334 100644 --- a/src/users/requestHandlers/resetPassword.js +++ b/src/auth/requestHandlers/resetPassword.js @@ -2,12 +2,12 @@ const httpStatus = require('http-status'); const formatErrorResponse = require('../../express/responses/formatError'); const { resetPassword } = require('../operations'); -const resetPasswordHandler = async (req, res) => { +const resetPasswordHandler = config => async (req, res) => { try { const token = await resetPassword({ req, - Model: req.collection.Model, - config: req.collection.config, + collection: req.collection, + config, data: req.body, }); diff --git a/src/users/requestHandlers/update.js b/src/auth/requestHandlers/update.js similarity index 100% rename from src/users/requestHandlers/update.js rename to src/auth/requestHandlers/update.js diff --git a/src/users/routes.js b/src/auth/routes.js similarity index 56% rename from src/users/routes.js rename to src/auth/routes.js index a2dc42e483..507496ea99 100644 --- a/src/users/routes.js +++ b/src/auth/routes.js @@ -22,51 +22,53 @@ const { const router = express.Router(); -const authRoutes = (User, config, sendEmail) => { +const authRoutes = (collection, config, sendEmail) => { + const { slug } = collection.config; + router.all('*', - bindCollectionMiddleware(User)); + bindCollectionMiddleware(collection)); router - .route('/init') + .route(`/${slug}/init`) .get(init); router - .route('/login') - .post(login); + .route(`/${slug}/login`) + .post(login(config)); router - .route('/refresh-token') - .post(refresh); + .route(`/${slug}/refresh-token`) + .post(refresh(config)); router - .route('/me') + .route(`/${slug}/me`) .get(me); router - .route('/policies') + .route(`/${slug}/policies`) .get(policies(config)); router - .route('/first-register') - .post(registerFirstUser); + .route(`/${slug}/first-register`) + .post(registerFirstUser(config)); router - .route('/forgot-password') + .route(`/${slug}/forgot-password`) .post(forgotPassword(config, sendEmail)); router - .route('/reset-password') + .route(`${slug}/reset-password`) .post(resetPassword); router - .route(`/${User.config.slug}/register`) - .post(register); + .route(`/${slug}/register`) + .post(register(config)); router - .route(`/${User.config.slug}`) + .route(`/${slug}`) .get(find); - router.route(`/${User.config.slug}/:id`) + router.route(`/${slug}/:id`) .get(findByID) .put(update) .delete(deleteHandler); diff --git a/src/users/strategies/apiKey.js b/src/auth/strategies/apiKey.js similarity index 100% rename from src/users/strategies/apiKey.js rename to src/auth/strategies/apiKey.js diff --git a/src/users/strategies/jwt.js b/src/auth/strategies/jwt.js similarity index 72% rename from src/users/strategies/jwt.js rename to src/auth/strategies/jwt.js index 03552db136..a4bb42eedb 100644 --- a/src/users/strategies/jwt.js +++ b/src/auth/strategies/jwt.js @@ -3,14 +3,14 @@ const passportJwt = require('passport-jwt'); const JwtStrategy = passportJwt.Strategy; const { ExtractJwt } = passportJwt; -module.exports = ({ Model, config }) => { +module.exports = (config, Model) => { const opts = {}; opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('JWT'); - opts.secretOrKey = config.auth.secretKey; + opts.secretOrKey = config.secret; return new JwtStrategy(opts, async (token, done) => { try { - const user = await Model.findByUsername(token.email); + const user = await Model.findByUsername(token.username); return done(null, user); } catch (err) { return done(null, false); diff --git a/src/collections/buildSchema.js b/src/collections/buildSchema.js index 3f71203793..4571697150 100644 --- a/src/collections/buildSchema.js +++ b/src/collections/buildSchema.js @@ -1,4 +1,3 @@ -const mongooseHidden = require('mongoose-hidden'); const paginate = require('mongoose-paginate-v2'); const autopopulate = require('mongoose-autopopulate'); const buildQueryPlugin = require('../mongoose/buildQuery'); @@ -11,8 +10,7 @@ const buildCollectionSchema = (collection, config, schemaOptions = {}) => { schema.plugin(paginate) .plugin(buildQueryPlugin) .plugin(localizationPlugin, config.localization) - .plugin(autopopulate) - .plugin(mongooseHidden()); + .plugin(autopopulate); return schema; }; diff --git a/src/collections/graphql/register.js b/src/collections/graphql/init.js similarity index 52% rename from src/collections/graphql/register.js rename to src/collections/graphql/init.js index cd797a7dca..5db5935bd6 100644 --- a/src/collections/graphql/register.js +++ b/src/collections/graphql/init.js @@ -1,13 +1,20 @@ const { GraphQLString, + GraphQLBoolean, GraphQLNonNull, GraphQLInt, } = require('graphql'); const formatName = require('../../graphql/utilities/formatName'); + const { create, find, findByID, deleteResolver, update, } = require('./resolvers'); + +const { + login, me, init, refresh, register, forgotPassword, resetPassword, policies, +} = require('../../auth/graphql/resolvers'); + const buildPaginatedListType = require('../../graphql/schema/buildPaginatedListType'); function registerCollections() { @@ -19,10 +26,12 @@ function registerCollections() { singular, plural, }, - fields, + fields: initialFields, }, } = collection; + const fields = [...initialFields]; + const singularLabel = formatName(singular); let pluralLabel = formatName(plural); @@ -53,6 +62,14 @@ function registerCollections() { singularLabel, ); + if (collection.auth) { + fields.push({ + name: 'password', + type: 'text', + required: true, + }); + } + collection.graphQL.mutationInputType = new GraphQLNonNull(this.buildMutationInputType( singularLabel, fields, @@ -93,14 +110,6 @@ function registerCollections() { resolve: find(collection), }; - this.Mutation.fields[`create${singularLabel}`] = { - type: collection.graphQL.type, - args: { - data: { type: collection.graphQL.mutationInputType }, - }, - resolve: create(collection), - }; - this.Mutation.fields[`update${singularLabel}`] = { type: collection.graphQL.type, args: { @@ -117,6 +126,90 @@ function registerCollections() { }, resolve: deleteResolver(collection), }; + + if (collection.auth) { + collection.graphQL.jwt = this.buildObjectType( + 'JWT', + collection.config.fields.reduce((jwtFields, potentialField) => { + if (potentialField.saveToJWT) { + return [ + ...jwtFields, + potentialField, + ]; + } + + return jwtFields; + }, [ + { + name: 'username', + type: 'text', + required: true, + }, + ]), + ); + + this.Query.fields[`policies${singularLabel}`] = { + type: this.buildPoliciesType(), + resolve: policies(this.config, collection), + }; + + this.Query.fields[`me${singularLabel}`] = { + type: collection.graphQL.jwt, + resolve: me, + }; + + this.Query.fields[`initialized${singularLabel}`] = { + type: GraphQLBoolean, + resolve: init(collection), + }; + + this.Mutation.fields[`login${singularLabel}`] = { + type: GraphQLString, + args: { + username: { type: GraphQLString }, + password: { type: GraphQLString }, + }, + resolve: login(this.config, collection), + }; + + this.Mutation.fields[`register${singularLabel}`] = { + type: collection.graphQL.type, + args: { + data: { type: collection.graphQL.mutationInputType }, + }, + resolve: register(this.config, collection), + }; + + this.Mutation.fields[`forgotPassword${singularLabel}`] = { + type: new GraphQLNonNull(GraphQLBoolean), + args: { + username: { type: new GraphQLNonNull(GraphQLString) }, + }, + resolve: forgotPassword(this.config, collection.Model, this.sendEmail), + }; + + this.Mutation.fields[`resetPassword${singularLabel}`] = { + type: GraphQLString, + args: { + token: { type: GraphQLString }, + password: { type: GraphQLString }, + }, + resolve: resetPassword(collection), + }; + + this.Mutation.fields[`refreshToken${singularLabel}`] = { + type: GraphQLString, + resolve: refresh(this.config, collection), + }; + } else { + this.Mutation.fields[`create${singularLabel}`] = { + type: collection.graphQL.type, + args: { + data: { type: collection.graphQL.mutationInputType }, + }, + resolve: create(collection), + }; + } }); } diff --git a/src/collections/init.js b/src/collections/init.js index c49b28358f..ca94236231 100644 --- a/src/collections/init.js +++ b/src/collections/init.js @@ -1,7 +1,21 @@ const mongoose = require('mongoose'); +const mongooseHidden = require('mongoose-hidden')({ + hidden: { + salt: true, hash: true, _id: true, __v: true, + }, + applyRecursively: true, +}); +const passport = require('passport'); +const passportLocalMongoose = require('passport-local-mongoose'); +const LocalStrategy = require('passport-local').Strategy; +const AnonymousStrategy = require('passport-anonymous'); +const jwtStrategy = require('../auth/strategies/jwt'); +const apiKeyStrategy = require('../auth/strategies/apiKey'); const collectionRoutes = require('./routes'); const buildSchema = require('./buildSchema'); const sanitize = require('./sanitize'); +const baseAuthFields = require('../auth/baseFields'); +const authRoutes = require('../auth/routes'); function registerCollections() { this.config.collections.forEach((collection) => { @@ -89,14 +103,40 @@ function registerCollections() { ]; } + if (collection.auth) { + formattedCollection.fields = [ + ...formattedCollection.fields, + ...baseAuthFields, + ]; + } + const schema = buildSchema(formattedCollection, this.config); + if (collection.auth) { + schema.plugin(passportLocalMongoose); + } + + schema.plugin(mongooseHidden); + this.collections[formattedCollection.slug] = { Model: mongoose.model(formattedCollection.slug, schema), config: sanitize(this.collections, formattedCollection), }; - this.router.use(collectionRoutes(this.collections[formattedCollection.slug])); + if (collection.auth) { + const AuthCollection = this.collections[formattedCollection.slug]; + + passport.use(new LocalStrategy(AuthCollection.Model.authenticate())); + passport.use(apiKeyStrategy(AuthCollection)); + passport.use(jwtStrategy(this.config, AuthCollection.Model)); + passport.serializeUser(AuthCollection.Model.serializeUser()); + passport.deserializeUser(AuthCollection.Model.deserializeUser()); + passport.use(new AnonymousStrategy.Strategy()); + + this.router.use(authRoutes(AuthCollection, this.config, this.sendEmail)); + } else { + this.router.use(collectionRoutes(this.collections[formattedCollection.slug])); + } }); } diff --git a/src/collections/operations/create.js b/src/collections/operations/create.js index 3fc9fc50e5..f581274449 100644 --- a/src/collections/operations/create.js +++ b/src/collections/operations/create.js @@ -1,6 +1,6 @@ const mkdirp = require('mkdirp'); -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../../auth/executePolicy'); const executeFieldHooks = require('../../fields/executeHooks'); const { validateCreate } = require('../../fields/validateCreate'); diff --git a/src/collections/operations/delete.js b/src/collections/operations/delete.js index dd21355884..e4e00777b2 100644 --- a/src/collections/operations/delete.js +++ b/src/collections/operations/delete.js @@ -1,6 +1,6 @@ const fs = require('fs'); const { NotFound, Forbidden } = require('../../errors'); -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../../auth/executePolicy'); const deleteQuery = async (args) => { try { diff --git a/src/collections/operations/find.js b/src/collections/operations/find.js index bca58d7352..b573bf37a3 100644 --- a/src/collections/operations/find.js +++ b/src/collections/operations/find.js @@ -1,5 +1,5 @@ const merge = require('lodash.merge'); -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../../auth/executePolicy'); const executeFieldHooks = require('../../fields/executeHooks'); const find = async (args) => { diff --git a/src/collections/operations/findByID.js b/src/collections/operations/findByID.js index f9e77eab0e..4dbad5bfb0 100644 --- a/src/collections/operations/findByID.js +++ b/src/collections/operations/findByID.js @@ -1,5 +1,5 @@ const { Forbidden, NotFound } = require('../../errors'); -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../../auth/executePolicy'); const executeFieldHooks = require('../../fields/executeHooks'); const findByID = async (args) => { diff --git a/src/collections/operations/update.js b/src/collections/operations/update.js index 2ad6a4f956..60d10710e2 100644 --- a/src/collections/operations/update.js +++ b/src/collections/operations/update.js @@ -1,4 +1,4 @@ -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../../auth/executePolicy'); const executeFieldHooks = require('../../fields/executeHooks'); const { NotFound, Forbidden } = require('../../errors'); const validate = require('../../fields/validateUpdate'); diff --git a/src/collections/requestHandlers/collections.spec.js b/src/collections/requestHandlers/collections.spec.js index 4ea48dfc95..f885bddf6d 100644 --- a/src/collections/requestHandlers/collections.spec.js +++ b/src/collections/requestHandlers/collections.spec.js @@ -5,7 +5,7 @@ require('isomorphic-fetch'); const faker = require('faker'); const config = require('../../../demo/payload.config'); -const { email, password } = require('../../tests/credentials'); +const { username, password } = require('../../tests/credentials'); const url = config.serverURL; @@ -16,9 +16,9 @@ const englishPostDesc = faker.lorem.lines(2); const spanishPostDesc = faker.lorem.lines(2); beforeAll(async (done) => { - const response = await fetch(`${url}/api/login`, { + const response = await fetch(`${url}/api/users/login`, { body: JSON.stringify({ - email, + username, password, }), headers: { @@ -261,7 +261,7 @@ describe('Collections - REST', () => { it('should include metadata', async () => { const desc = 'metadataDesc'; for (let i = 0; i < 12; i += 1) { - // eslint-disable-next-line no-await-in-loop + // eslint-disable-next-line no-await-in-loop await createPost(null, desc); } diff --git a/src/errors/AuthenticationError.js b/src/errors/AuthenticationError.js index 79c0b18a91..552e697740 100644 --- a/src/errors/AuthenticationError.js +++ b/src/errors/AuthenticationError.js @@ -3,7 +3,7 @@ const APIError = require('./APIError'); class AuthenticationError extends APIError { constructor() { - super('The username or password provided is incorrect.', httpStatus.BAD_REQUEST); + super('The username or password provided is incorrect.', httpStatus.UNAUTHORIZED); } } diff --git a/src/globals/graphql/register.js b/src/globals/graphql/init.js similarity index 100% rename from src/globals/graphql/register.js rename to src/globals/graphql/init.js diff --git a/src/globals/operations/findOne.js b/src/globals/operations/findOne.js index 06ec225814..ec4b27483e 100644 --- a/src/globals/operations/findOne.js +++ b/src/globals/operations/findOne.js @@ -1,4 +1,4 @@ -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../../auth/executePolicy'); const { NotFound } = require('../../errors'); const executeFieldHooks = require('../../fields/executeHooks'); diff --git a/src/globals/operations/update.js b/src/globals/operations/update.js index 0eeb8ad198..a4a93947d5 100644 --- a/src/globals/operations/update.js +++ b/src/globals/operations/update.js @@ -1,4 +1,4 @@ -const executePolicy = require('../../users/executePolicy'); +const executePolicy = require('../../auth/executePolicy'); const executeFieldHooks = require('../../fields/executeHooks'); const update = async (args) => { diff --git a/src/graphql/index.js b/src/graphql/index.js index 553c479d70..574009c316 100644 --- a/src/graphql/index.js +++ b/src/graphql/index.js @@ -7,9 +7,8 @@ const buildBlockType = require('./schema/buildBlockType'); const buildPoliciesType = require('./schema/buildPoliciesType'); const buildLocaleInputType = require('./schema/buildLocaleInputType'); const buildFallbackLocaleInputType = require('./schema/buildFallbackLocaleInputType'); -const registerCollections = require('../collections/graphql/register'); -const registerGlobals = require('../globals/graphql/register'); -const initUser = require('../users/graphql/init'); +const initCollections = require('../collections/graphql/init'); +const initGlobals = require('../globals/graphql/init'); const buildWhereInputType = require('./schema/buildWhereInputType'); class GraphQL { @@ -32,15 +31,13 @@ class GraphQL { this.buildWhereInputType = buildWhereInputType; this.buildObjectType = buildObjectType.bind(this); this.buildPoliciesType = buildPoliciesType.bind(this); - this.registerCollections = registerCollections.bind(this); - this.initUser = initUser.bind(this); - this.registerGlobals = registerGlobals.bind(this); + this.initCollections = initCollections.bind(this); + this.initGlobals = initGlobals.bind(this); } init() { - this.registerCollections(); - this.initUser(); - this.registerGlobals(); + this.initCollections(); + this.initGlobals(); const query = new GraphQLObjectType(this.Query); const mutation = new GraphQLObjectType(this.Mutation); diff --git a/src/graphql/schema/buildObjectType.js b/src/graphql/schema/buildObjectType.js index b8a2277240..a72577ba29 100644 --- a/src/graphql/schema/buildObjectType.js +++ b/src/graphql/schema/buildObjectType.js @@ -83,8 +83,6 @@ function buildObjectType(name, fields, parentName, baseFields = {}) { return this.types.blockTypes[data.blockType]; }, }); - } else if (relationTo === this.config.User.slug) { - ({ type } = this.User.graphQL); } else { ({ type } = this.collections[relationTo].graphQL); } @@ -203,13 +201,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) { ), }; } else { - let whereFields; - - if (relationTo === this.config.User.slug) { - whereFields = this.User.config.fields; - } else { - whereFields = this.collections[relationTo].config.fields; - } + const whereFields = this.collections[relationTo].config.fields; relationship.args.where = { type: this.buildWhereInputType( diff --git a/src/index.js b/src/index.js index bbf1924226..12ba17179f 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,6 @@ const connectMongoose = require('./mongoose/connect'); const expressMiddleware = require('./express/middleware'); const createAuthHeaderFromCookie = require('./express/middleware/createAuthHeaderFromCookie'); const initWebpack = require('./webpack/init'); -const initUser = require('./users/init'); const initCollections = require('./collections/init'); const initGlobals = require('./globals/init'); const initStatic = require('./express/static'); @@ -24,7 +23,6 @@ class Payload { this.router = express.Router(); this.collections = {}; - this.initUser = initUser.bind(this); this.initCollections = initCollections.bind(this); this.initGlobals = initGlobals.bind(this); this.buildEmail = buildEmail.bind(this); @@ -39,9 +37,6 @@ class Payload { connectMongoose(this.config.mongoURL); this.router.use(...expressMiddleware(this.config)); - // Register and bind User - this.initUser(); - // Register collections this.initCollections(); @@ -49,7 +44,7 @@ class Payload { this.initGlobals(); // Enable client - if (!this.config.disableAdmin && process.env.NODE_ENV !== 'test') { + if (!this.config.admin.disable && process.env.NODE_ENV !== 'test') { this.express.use(initWebpack(this.config)); } diff --git a/src/tests/credentials.js b/src/tests/credentials.js index de0cd02c87..c3ec1e0540 100644 --- a/src/tests/credentials.js +++ b/src/tests/credentials.js @@ -1,2 +1,2 @@ -exports.email = 'test@test.com'; +exports.username = 'test@test.com'; exports.password = 'test123'; diff --git a/src/tests/globalSetup.js b/src/tests/globalSetup.js index c9040ba41b..274c2d1281 100644 --- a/src/tests/globalSetup.js +++ b/src/tests/globalSetup.js @@ -8,7 +8,7 @@ const url = config.serverURL; const globalSetup = async () => { global.PAYLOAD_SERVER = server.start(); - const response = await fetch(`${url}/api/first-register`, { + const response = await fetch(`${url}/api/users/first-register`, { body: JSON.stringify({ username, password, diff --git a/src/users/graphql/init.js b/src/users/graphql/init.js deleted file mode 100644 index ef66bce352..0000000000 --- a/src/users/graphql/init.js +++ /dev/null @@ -1,191 +0,0 @@ -const { - GraphQLString, - GraphQLBoolean, - GraphQLNonNull, - GraphQLInt, -} = require('graphql'); - -const formatName = require('../../graphql/utilities/formatName'); -const buildPaginatedListType = require('../../graphql/schema/buildPaginatedListType'); - -const { - find, findByID, deleteResolver, -} = require('../../collections/graphql/resolvers'); - -const { - login, me, init, refresh, register, update, forgotPassword, resetPassword, policies, -} = require('./resolvers'); - -function registerUser() { - const { - config: { - labels: { - singular, - plural, - }, - fields, - }, - } = this.User; - - const singularLabel = formatName(singular); - const pluralLabel = formatName(plural); - - this.User.graphQL = {}; - - this.User.graphQL.type = this.buildObjectType( - singularLabel, - fields, - singularLabel, - { - id: { type: GraphQLString }, - }, - ); - - this.User.graphQL.whereInputType = this.buildWhereInputType( - singularLabel, - fields, - singularLabel, - ); - - const mutationFields = [ - ...fields, - { - name: 'password', - type: 'text', - required: true, - }, - ]; - - this.User.graphQL.mutationInputType = new GraphQLNonNull(this.buildMutationInputType( - singularLabel, - mutationFields, - singularLabel, - )); - - this.User.graphQL.updateMutationInputType = this.buildMutationInputType( - `${singularLabel}Update`, - mutationFields.map((field) => { - return { - ...field, - required: false, - }; - }), - `${singularLabel}Update`, - ); - - this.User.graphQL.jwt = this.buildObjectType( - 'JWT', - this.User.config.fields.reduce((jwtFields, potentialField) => { - if (potentialField.saveToJWT) { - return [ - ...jwtFields, - potentialField, - ]; - } - - return jwtFields; - }, [ - { - name: 'username', - type: 'text', - required: true, - }, - ]), - ); - - this.Query.fields.Policies = { - type: this.buildPoliciesType(), - resolve: policies(this.config), - }; - - this.Query.fields[singularLabel] = { - type: this.User.graphQL.type, - args: { - id: { type: GraphQLString }, - locale: { type: this.types.localeInputType }, - fallbackLocale: { type: this.types.fallbackLocaleInputType }, - }, - resolve: findByID(this.User), - }; - - this.Query.fields[pluralLabel] = { - type: buildPaginatedListType(pluralLabel, this.User.graphQL.type), - args: { - where: { type: this.User.graphQL.whereInputType }, - locale: { type: this.types.localeInputType }, - fallbackLocale: { type: this.types.fallbackLocaleInputType }, - page: { type: GraphQLInt }, - limit: { type: GraphQLInt }, - sort: { type: GraphQLString }, - }, - resolve: find(this.User), - }; - - this.Query.fields.Me = { - type: this.User.graphQL.jwt, - resolve: me, - }; - - this.Query.fields.Initialized = { - type: GraphQLBoolean, - resolve: init(this.User), - }; - - this.Mutation.fields[`update${singularLabel}`] = { - type: this.User.graphQL.type, - args: { - id: { type: new GraphQLNonNull(GraphQLString) }, - data: { type: this.User.graphQL.updateMutationInputType }, - }, - resolve: update(this.User), - }; - - this.Mutation.fields[`delete${singularLabel}`] = { - type: this.User.graphQL.type, - args: { - id: { type: new GraphQLNonNull(GraphQLString) }, - }, - resolve: deleteResolver(this.User), - }; - - this.Mutation.fields.login = { - type: GraphQLString, - args: { - username: { type: GraphQLString }, - password: { type: GraphQLString }, - }, - resolve: login(this.User), - }; - - this.Mutation.fields.register = { - type: this.User.graphQL.type, - args: { - data: { type: this.User.graphQL.mutationInputType }, - }, - resolve: register(this.User), - }; - - this.Mutation.fields.forgotPassword = { - type: new GraphQLNonNull(GraphQLBoolean), - args: { - username: { type: new GraphQLNonNull(GraphQLString) }, - }, - resolve: forgotPassword(this.config, this.User.Model, this.sendEmail), - }; - - this.Mutation.fields.resetPassword = { - type: GraphQLString, - args: { - token: { type: GraphQLString }, - password: { type: GraphQLString }, - }, - resolve: resetPassword(this.User), - }; - - this.Mutation.fields.refreshToken = { - type: GraphQLString, - resolve: refresh(this.User), - }; -} - -module.exports = registerUser; diff --git a/src/users/init.js b/src/users/init.js deleted file mode 100644 index 27cb93f8e2..0000000000 --- a/src/users/init.js +++ /dev/null @@ -1,35 +0,0 @@ -const mongoose = require('mongoose'); -const passport = require('passport'); -const AnonymousStrategy = require('passport-anonymous'); -const LocalStrategy = require('passport-local').Strategy; -const passportLocalMongoose = require('passport-local-mongoose'); -const jwtStrategy = require('./strategies/jwt'); -const apiKeyStrategy = require('./strategies/apiKey'); -const authRoutes = require('./routes'); -const buildCollectionSchema = require('../collections/buildSchema'); -const baseUserFields = require('./baseFields'); -const sanitize = require('./sanitize'); - -function initUser() { - this.config.User.fields.push(...baseUserFields); - this.config.User = sanitize(this.config.User); - const userSchema = buildCollectionSchema(this.config.User, this.config); - userSchema.plugin(passportLocalMongoose); - - this.User = { - config: this.config.User, - Model: mongoose.model(this.config.User.slug, userSchema), - }; - - passport.use(new LocalStrategy(this.User.Model.authenticate())); - passport.use(this.User.Model.createStrategy()); - passport.use(apiKeyStrategy(this.User)); - passport.use(jwtStrategy(this.User)); - passport.serializeUser(this.User.Model.serializeUser()); - passport.deserializeUser(this.User.Model.deserializeUser()); - passport.use(new AnonymousStrategy.Strategy()); - - this.router.use(authRoutes(this.User, this.config, this.sendEmail)); -} - -module.exports = initUser; diff --git a/src/users/sanitize.js b/src/users/sanitize.js deleted file mode 100644 index 4d6646f9b2..0000000000 --- a/src/users/sanitize.js +++ /dev/null @@ -1,46 +0,0 @@ -const { MissingCollectionLabel } = require('../errors'); -const sanitizeFields = require('../fields/sanitize'); - -const sanitizeUserCollection = (userConfig) => { - // ///////////////////////////////// - // Ensure config is valid - // ///////////////////////////////// - - if (!userConfig.labels.singular) { - throw new MissingCollectionLabel(userConfig); - } - - // ///////////////////////////////// - // Make copy of user config - // ///////////////////////////////// - - const sanitizedUserConfig = { ...userConfig }; - - // ///////////////////////////////// - // Ensure that user has required object structure - // ///////////////////////////////// - - if (!sanitizedUserConfig.hooks) sanitizedUserConfig.hooks = {}; - if (!sanitizedUserConfig.policies) sanitizedUserConfig.policies = {}; - - // ///////////////////////////////// - // Sanitize fields - // ///////////////////////////////// - - sanitizedUserConfig.fields = sanitizeFields(userConfig.fields); - - // ///////////////////////////////// - // Hidden fields - // ///////////////////////////////// - - const hiddenFields = Array.isArray(sanitizedUserConfig.hidden) ? sanitizedUserConfig.hidden : []; - sanitizedUserConfig.hidden = [ - ...hiddenFields, - 'resetPasswordToken', - 'resetPasswordExpiration', - ]; - - return sanitizedUserConfig; -}; - -module.exports = sanitizeUserCollection; diff --git a/src/utilities/sanitizeConfig.js b/src/utilities/sanitizeConfig.js index 3c26dd1497..69126b9b0e 100644 --- a/src/utilities/sanitizeConfig.js +++ b/src/utilities/sanitizeConfig.js @@ -1,12 +1,23 @@ +const defaultUser = require('../auth/default'); + const sanitizeConfig = (config) => { const sanitizedConfig = { ...config }; + sanitizedConfig.admin = config.admin || {}; + + if (!sanitizedConfig.admin.user) { + sanitizedConfig.admin.user = config.admin.user || 'users'; + + sanitizedConfig.collections.push(defaultUser); + } + sanitizedConfig.routes = { admin: (config.routes && config.routes.admin) ? config.routes.admin : '/admin', api: (config.routes && config.routes.api) ? config.routes.api : '/api', graphQL: (config.routes && config.routes.graphQL) ? config.routes.graphQL : '/graphql', graphQLPlayground: (config.routes && config.routes.graphQLPlayground) ? config.routes.graphQLPlayground : '/graphql-playground', }; + sanitizedConfig.components = { ...(config.components || {}) }; return sanitizedConfig; diff --git a/src/utilities/secureConfig.js b/src/utilities/secureConfig.js index a8ca466930..b0f4b0d4de 100644 --- a/src/utilities/secureConfig.js +++ b/src/utilities/secureConfig.js @@ -19,7 +19,7 @@ const recursivelySecure = (object) => { const secureConfig = (insecureConfig) => { const config = deepCopyObject(insecureConfig); - delete config.User.auth.secretKey; + delete config.secret; recursivelySecure(config); @@ -27,8 +27,6 @@ const secureConfig = (insecureConfig) => { config.collections[i] = recursivelySecure(collection); }); - config.User = recursivelySecure(config.User); - return config; }; diff --git a/yarn.lock b/yarn.lock index 2869273c01..2ac3e61b49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3273,7 +3273,7 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" -debug@^3.1.0, debug@^3.2.6: +debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -7611,16 +7611,15 @@ passport-jwt@^4.0.0: jsonwebtoken "^8.2.0" passport-strategy "^1.0.0" -passport-local-mongoose@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/passport-local-mongoose/-/passport-local-mongoose-5.0.1.tgz#7b45ae6bcb6e8a7be26a4a95a7787e4d14130fc7" - integrity sha512-VUY5DgBdpjt1tjunJJ1EXV5b2nhMDkXJuhTjyiK660IgIp7kONMyWEe9tGHf8I9tZudXuTF+47JNQLIzU+Hjbw== +passport-local-mongoose@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/passport-local-mongoose/-/passport-local-mongoose-6.0.1.tgz#4980b1435c2dc97cc4094bbb6612999b1640fe2a" + integrity sha512-Jc/ImrVnG7o7aTIqrAznt2CxVxy5M2gAxc3erVZyPVUVfiwgvrhlzA9c5fswValA12H2C9mm1AjqGDz+491TDg== dependencies: - debug "^3.1.0" generaterr "^1.5.0" passport-local "^1.0.0" - scmp "^2.0.0" - semver "^5.5.0" + scmp "^2.1.0" + semver "^7.1.1" passport-local@^1.0.0: version "1.0.0" @@ -9442,7 +9441,7 @@ schema-utils@^2.6.5: ajv "^6.12.0" ajv-keywords "^3.4.1" -scmp@^2.0.0: +scmp@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/scmp/-/scmp-2.1.0.tgz#37b8e197c425bdeb570ab91cc356b311a11f9c9a" integrity sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q== @@ -9477,7 +9476,7 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.1.3: +semver@^7.1.1, semver@^7.1.3: version "7.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==