brings frontend up to speed with flattened users

This commit is contained in:
James
2020-05-18 17:11:34 -04:00
parent 1603243da3
commit 17d25c8d2f
20 changed files with 89 additions and 101 deletions

View File

@@ -1,13 +1,12 @@
require('isomorphic-fetch');
const faker = require('faker');
const { username, password } = require('../tests/credentials');
const { email, password } = require('../tests/credentials');
/**
* @jest-environment node
*/
const config = require('../../demo/payload.config');
const { payload } = require('../../demo/server');
const url = config.serverURL;
@@ -17,7 +16,7 @@ describe('Users REST API', () => {
it('should prevent registering a first user', async () => {
const response = await fetch(`${url}/api/users/first-register`, {
body: JSON.stringify({
username: 'thisuser@shouldbeprevented.com',
email: 'thisuser@shouldbeprevented.com',
password: 'get-out',
}),
headers: {
@@ -32,7 +31,7 @@ describe('Users REST API', () => {
it('should login a user successfully', async () => {
const response = await fetch(`${url}/api/users/login`, {
body: JSON.stringify({
username,
email,
password,
}),
headers: {
@@ -59,7 +58,7 @@ describe('Users REST API', () => {
const data = await response.json();
expect(response.status).toBe(200);
expect(data.username).not.toBeNull();
expect(data.email).not.toBeNull();
});
it('should refresh a token and reset its expiration', async () => {
@@ -84,7 +83,7 @@ describe('Users REST API', () => {
const response = await fetch(`${url}/api/users/forgot-password`, {
method: 'post',
body: JSON.stringify({
username,
email,
}),
headers: {
'Content-Type': 'application/json',
@@ -100,7 +99,7 @@ describe('Users REST API', () => {
it('should allow a user to be created', async () => {
const response = await fetch(`${url}/api/users/register`, {
body: JSON.stringify({
username: `${faker.name.firstName()}@test.com`,
email: `${faker.name.firstName()}@test.com`,
password,
roles: ['editor'],
}),
@@ -114,7 +113,7 @@ describe('Users REST API', () => {
const data = await response.json();
expect(response.status).toBe(201);
expect(data).toHaveProperty('username');
expect(data).toHaveProperty('email');
expect(data).toHaveProperty('roles');
expect(data).toHaveProperty('createdAt');
});

View File

@@ -4,7 +4,7 @@ const defaultUser = {
singular: 'User',
plural: 'Users',
},
useAsTitle: 'username',
useAsTitle: 'email',
auth: {
tokenExpiration: 7200,
},

View File

@@ -6,7 +6,7 @@ const loginResolver = (config, collection) => async (_, args, context) => {
collection,
config,
data: {
username: args.username,
email: args.email,
password: args.password,
},
req: context,

View File

@@ -3,8 +3,8 @@ const { APIError } = require('../../errors');
const forgotPassword = async (args) => {
try {
if (!Object.prototype.hasOwnProperty.call(args.data, 'username')) {
throw new APIError('Missing username.');
if (!Object.prototype.hasOwnProperty.call(args.data, 'email')) {
throw new APIError('Missing email.');
}
let options = { ...args };
@@ -35,7 +35,7 @@ const forgotPassword = async (args) => {
let token = await crypto.randomBytes(20);
token = token.toString('hex');
const user = await Model.findOne({ username: data.username });
const user = await Model.findOne({ email: data.email });
if (!user) return;
@@ -53,7 +53,7 @@ const forgotPassword = async (args) => {
email({
from: `"${config.email.fromName}" <${config.email.fromAddress}>`,
to: data.username,
to: data.email,
subject: 'Password Reset',
html,
});

View File

@@ -30,9 +30,9 @@ const login = async (args) => {
data,
} = options;
const { username, password } = data;
const { email, password } = data;
const user = await Model.findByUsername(username);
const user = await Model.findByUsername(email);
if (!user) throw new AuthenticationError();
@@ -51,7 +51,7 @@ const login = async (args) => {
}
return signedFields;
}, {
username,
email,
});
const token = jwt.sign(

View File

@@ -33,7 +33,7 @@ const resetPassword = async (args) => {
data,
} = options;
const { username } = data;
const { email } = data;
const user = await Model.findOne({
resetPasswordToken: data.token,
@@ -60,7 +60,7 @@ const resetPassword = async (args) => {
}
return signedFields;
}, {
username,
email,
});
const token = jwt.sign(

View File

@@ -10,7 +10,7 @@ module.exports = (config, Model) => {
return new JwtStrategy(opts, async (token, done) => {
try {
const user = await Model.findByUsername(token.username);
const user = await Model.findByUsername(token.email);
return done(null, user);
} catch (err) {
return done(null, false);

View File

@@ -20,7 +20,7 @@ import Unauthorized from './views/Unauthorized';
import Loading from './elements/Loading';
const {
routes, collections, User, globals,
admin: { user: userSlug }, routes, collections, User, globals,
} = PAYLOAD_CONFIG;
const Routes = () => {
@@ -28,7 +28,7 @@ const Routes = () => {
const { user, permissions: { canAccessAdmin } } = useUser();
useEffect(() => {
requests.get(`${routes.api}/init`).then(res => res.json().then((data) => {
requests.get(`${routes.api}/${userSlug}/init`).then(res => res.json().then((data) => {
if (data && 'initialized' in data) {
setInitialized(data.initialized);
}

View File

@@ -10,7 +10,7 @@ import { requests } from '../../api';
import StayLoggedInModal from '../modals/StayLoggedIn';
import useThrottledEffect from '../../hooks/useThrottledEffect';
const { serverURL, routes: { admin, api } } = PAYLOAD_CONFIG;
const { admin: { user: userSlug }, serverURL, routes: { admin, api } } = PAYLOAD_CONFIG;
const cookies = new Cookies();
const Context = createContext({});
@@ -35,7 +35,7 @@ const UserProvider = ({ children }) => {
if (decodedToken?.exp > (Date.now() / 1000)) {
setTimeout(async () => {
const request = await requests.post(`${serverURL}${api}/refresh-token`);
const request = await requests.post(`${serverURL}${api}/${userSlug}/refresh-token`);
if (request.status === 200) {
const json = await request.json();
@@ -63,7 +63,6 @@ const UserProvider = ({ children }) => {
}, 15000, [location, refreshToken]);
// When token changes, set cookie, decode and set user
// Get new policies
useEffect(() => {
if (token) {
const decoded = jwt.decode(token);
@@ -72,22 +71,12 @@ const UserProvider = ({ children }) => {
cookies.set('token', token, { path: '/' });
}
}
async function getPermissions() {
const request = await requests.get(`${serverURL}${api}/policies`);
if (request.status === 200) {
const json = await request.json();
setPermissions(json);
}
}
getPermissions();
}, [token]);
// When user changes, get new policies
useEffect(() => {
async function getPermissions() {
const request = await requests.get(`${serverURL}${api}/policies`);
const request = await requests.get(`${serverURL}${api}/${userSlug}/policies`);
if (request.status === 200) {
const json = await request.json();

View File

@@ -1,12 +1,14 @@
import React from 'react';
import { useLocation, NavLink, Link } from 'react-router-dom';
import { NavLink, Link } from 'react-router-dom';
import { useUser } from '../../data/User';
import Chevron from '../../icons/Chevron';
import './index.scss';
const {
User,
admin: {
user: userSlug,
},
collections,
globals,
routes: {
@@ -14,8 +16,9 @@ const {
},
} = PAYLOAD_CONFIG;
const userConfig = collections.find(collection => collection.slug === userSlug);
const Sidebar = () => {
const location = useLocation();
const { permissions } = useUser();
return (
@@ -46,15 +49,6 @@ const Sidebar = () => {
return null;
})}
{permissions?.[User.slug].read.permission && (
<NavLink
activeClassName="active"
to={`${admin}/${User.slug}`}
>
<Chevron />
{User.labels.singular}
</NavLink>
)}
</nav>
<span className="label">Globals</span>
<nav>

View File

@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import MinimalTemplate from '../../templates/Minimal';
import StatusList, { useStatusList } from '../../elements/Status';
import Form from '../../forms/Form';
import RenderFields from '../../forms/RenderFields';
@@ -10,14 +11,11 @@ import { useUser } from '../../data/User';
import './index.scss';
const { serverURL, routes: { admin, api } } = PAYLOAD_CONFIG;
const {
admin: { user: userSlug }, collections, serverURL, routes: { admin, api },
} = PAYLOAD_CONFIG;
const passwordField = {
name: 'password',
label: 'Password',
type: 'password',
required: true,
};
const userConfig = collections.find(collection => collection.slug === userSlug);
const baseClass = 'create-first-user';
@@ -42,34 +40,39 @@ const CreateFirstUser = (props) => {
});
};
const fields = [...config.User.fields];
if (config.User.auth.passwordIndex) {
fields.splice(config.User.auth.passwordIndex, 0, passwordField);
} else {
fields.push(passwordField);
}
const fields = [
{
name: 'email',
label: 'Email Address',
type: 'email',
required: true,
}, {
name: 'password',
label: 'Password',
type: 'password',
required: true,
},
...userConfig.fields,
];
return (
<div className={baseClass}>
<div className={`${baseClass}__wrap`}>
<h1>Welcome to Payload</h1>
<p>To begin, create your first user.</p>
<StatusList />
<Form
handleAjaxResponse={handleAjaxResponse}
disableSuccessStatus
method="POST"
action={`${serverURL}${api}/first-register`}
>
<RenderFields
fieldSchema={fields}
fieldTypes={fieldTypes}
/>
<FormSubmit>Create</FormSubmit>
</Form>
</div>
</div>
<MinimalTemplate className={baseClass}>
<h1>Welcome to Payload</h1>
<p>To begin, create your first user.</p>
<StatusList />
<Form
handleAjaxResponse={handleAjaxResponse}
disableSuccessStatus
method="POST"
action={`${serverURL}${api}/${userSlug}/first-register`}
>
<RenderFields
fieldSchema={fields}
fieldTypes={fieldTypes}
/>
<FormSubmit>Create</FormSubmit>
</Form>
</MinimalTemplate>
);
};

View File

@@ -13,6 +13,7 @@ import './index.scss';
const baseClass = 'forgot-password';
const {
admin: { user: userSlug },
serverURL,
routes: {
admin,
@@ -86,10 +87,10 @@ const ForgotPassword = () => {
novalidate
handleAjaxResponse={handleAjaxResponse}
method="POST"
action={`${serverURL}${api}/forgot-password`}
action={`${serverURL}${api}/${userSlug}/forgot-password`}
>
<h1>Forgot Password</h1>
<p>Please enter your username below. You will receive an email message with instructions on how to reset your password.</p>
<p>Please enter your email below. You will receive an email message with instructions on how to reset your password.</p>
<Email
label="Email Address"
name="email"

View File

@@ -14,7 +14,7 @@ import './index.scss';
const baseClass = 'login';
const { serverURL, routes: { admin, api } } = PAYLOAD_CONFIG;
const { admin: { user: userSlug }, serverURL, routes: { admin, api } } = PAYLOAD_CONFIG;
const Login = () => {
const { addStatus } = useStatusList();
@@ -30,7 +30,7 @@ const Login = () => {
} else {
addStatus({
type: 'error',
message: 'The username or password you have entered is invalid.',
message: 'The email address or password you have entered is invalid.',
});
}
});
@@ -70,7 +70,7 @@ const Login = () => {
<Form
handleAjaxResponse={handleAjaxResponse}
method="POST"
action={`${serverURL}${api}/login`}
action={`${serverURL}${api}/${userSlug}/login`}
redirect={admin}
>
<Email

View File

@@ -12,7 +12,7 @@ import HiddenInput from '../../forms/field-types/HiddenInput';
const baseClass = 'reset-password';
const { serverURL, routes: { admin, api } } = PAYLOAD_CONFIG;
const { admin: { user: userSlug }, serverURL, routes: { admin, api } } = PAYLOAD_CONFIG;
const ResetPassword = () => {
const { token } = useParams();
@@ -61,7 +61,7 @@ const ResetPassword = () => {
<Form
handleAjaxResponse={handleAjaxResponse}
method="POST"
action={`${serverURL}${api}/reset-password`}
action={`${serverURL}${api}/${userSlug}/reset-password`}
redirect={admin}
>
<Password

View File

@@ -141,8 +141,8 @@ function registerCollections() {
return jwtFields;
}, [
{
name: 'username',
type: 'text',
name: 'email',
type: 'email',
required: true,
},
]),
@@ -166,7 +166,7 @@ function registerCollections() {
this.Mutation.fields[`login${singularLabel}`] = {
type: GraphQLString,
args: {
username: { type: GraphQLString },
email: { type: GraphQLString },
password: { type: GraphQLString },
},
resolve: login(this.config, collection),
@@ -183,7 +183,7 @@ function registerCollections() {
this.Mutation.fields[`forgotPassword${singularLabel}`] = {
type: new GraphQLNonNull(GraphQLBoolean),
args: {
username: { type: new GraphQLNonNull(GraphQLString) },
email: { type: new GraphQLNonNull(GraphQLString) },
},
resolve: forgotPassword(this.config, collection.Model, this.sendEmail),
};

View File

@@ -18,7 +18,7 @@ const baseAuthFields = require('../auth/baseFields');
const authRoutes = require('../auth/routes');
function registerCollections() {
this.config.collections.forEach((collection) => {
this.config.collections.map((collection) => {
const formattedCollection = { ...collection };
if (collection.upload) {
@@ -105,15 +105,15 @@ function registerCollections() {
if (collection.auth) {
formattedCollection.fields = [
...formattedCollection.fields,
...baseAuthFields,
...formattedCollection.fields,
];
}
const schema = buildSchema(formattedCollection, this.config);
if (collection.auth) {
schema.plugin(passportLocalMongoose);
schema.plugin(passportLocalMongoose, { usernameField: 'email' });
}
schema.plugin(mongooseHidden);
@@ -137,6 +137,8 @@ function registerCollections() {
} else {
this.router.use(collectionRoutes(this.collections[formattedCollection.slug]));
}
return formattedCollection;
});
}

View File

@@ -5,7 +5,7 @@
require('isomorphic-fetch');
const faker = require('faker');
const config = require('../../../demo/payload.config');
const { username, password } = require('../../tests/credentials');
const { email, password } = require('../../tests/credentials');
const url = config.serverURL;
@@ -18,7 +18,7 @@ const spanishPostDesc = faker.lorem.lines(2);
beforeAll(async (done) => {
const response = await fetch(`${url}/api/users/login`, {
body: JSON.stringify({
username,
email,
password,
}),
headers: {

View File

@@ -3,7 +3,7 @@ const APIError = require('./APIError');
class AuthenticationError extends APIError {
constructor() {
super('The username or password provided is incorrect.', httpStatus.UNAUTHORIZED);
super('The email or password provided is incorrect.', httpStatus.UNAUTHORIZED);
}
}

View File

@@ -1,2 +1,2 @@
exports.username = 'test@test.com';
exports.email = 'test@test.com';
exports.password = 'test123';

View File

@@ -1,7 +1,7 @@
require('isomorphic-fetch');
const server = require('../../demo/server');
const config = require('../../demo/payload.config');
const { username, password } = require('./credentials');
const { email, password } = require('./credentials');
const url = config.serverURL;
@@ -10,7 +10,7 @@ const globalSetup = async () => {
const response = await fetch(`${url}/api/users/first-register`, {
body: JSON.stringify({
username,
email,
password,
roles: ['admin', 'user'],
}),