brings frontend up to speed with flattened users
This commit is contained in:
@@ -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');
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ const defaultUser = {
|
||||
singular: 'User',
|
||||
plural: 'Users',
|
||||
},
|
||||
useAsTitle: 'username',
|
||||
useAsTitle: 'email',
|
||||
auth: {
|
||||
tokenExpiration: 7200,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
exports.username = 'test@test.com';
|
||||
exports.email = 'test@test.com';
|
||||
exports.password = 'test123';
|
||||
|
||||
@@ -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'],
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user