scaffolds RenderFields, moves Init into Routes

This commit is contained in:
James
2020-01-19 15:04:31 -05:00
parent a1494e62b0
commit b6231925bc
26 changed files with 350 additions and 235 deletions

View File

@@ -1,23 +0,0 @@
const HttpStatus = require('http-status');
/**
* authorize a request by comparing the current user with one or more roles
* @param roles
* @returns {Function}
*/
const checkRoleMiddleware = (...roles) => {
return (req, res, next) => {
if (!req.user) {
res.status(HttpStatus.UNAUTHORIZED)
.send('Not Authorized');
} else if (!roles.some(role => role === req.user.role)) {
res.status(HttpStatus.FORBIDDEN)
.send('Role not authorized.');
} else {
next();
}
};
};
module.exports = checkRoleMiddleware;

View File

@@ -13,9 +13,13 @@ export const loadPolicy = (policy) => {
passport.authenticate(['jwt', 'anonymous'], { session: false }),
(req, res, next) => {
if (policy) {
policy(req, res, next);
} else {
requireAuth(req, res);
if (!policy(req.user)) {
return res.status(HttpStatus.FORBIDDEN)
.send('Role not authorized.');
}
return next();
}
requireAuth(req, res);
}];
};

View File

@@ -17,8 +17,8 @@ export default User => ({
const error = new APIError('Authentication error', httpStatus.UNAUTHORIZED);
return next(error);
}
passport.authenticate('local')(req, res, () => {
res.json({ email: user.email, role: user.role, createdAt: user.createdAt });
return passport.authenticate('local')(req, res, () => {
return res.json({ email: user.email, role: user.role, createdAt: user.createdAt });
});
});
},
@@ -35,7 +35,7 @@ export default User => ({
User.findByUsername(email, (err, user) => {
if (err || !user) return res.status(401).json({ message: 'Auth Failed' });
user.authenticate(password, (authErr, model, passwordError) => {
return user.authenticate(password, (authErr, model, passwordError) => {
if (authErr || passwordError) return res.status(401).json({ message: 'Auth Failed' });
const opts = {};
@@ -73,6 +73,6 @@ export default User => ({
next(error);
}
next();
return next();
},
});

View File

@@ -2,7 +2,6 @@ import express from 'express';
import passport from 'passport';
import authRequestHandlers from './requestHandlers';
import authValidate from './validate';
import checkRoleMiddleware from './checkRoleMiddleware';
import passwordResetRoutes from './passwordResets/routes';
const router = express.Router();
@@ -17,12 +16,6 @@ const authRoutes = (userConfig, User) => {
.route('/me')
.post(passport.authenticate(userConfig.auth.strategy, { session: false }), auth.me);
userConfig.roles.forEach((role) => {
router
.route(`/role/${role}`)
.get(passport.authenticate(userConfig.auth.strategy, { session: false }), checkRoleMiddleware(role), auth.me);
});
if (userConfig.auth.passwordResets) {
router.use('', passwordResetRoutes(userConfig.email, User));
}

View File

@@ -15,7 +15,7 @@ const requests = {
headers: {
...setJWT()
}
}).then(res => res.body);
});
},
post: (url, body) =>
@@ -25,7 +25,7 @@ const requests = {
headers: {
...setJWT()
},
}).then(res => res.body),
}),
put: (url, body) =>
fetch(`${url}`, {
@@ -34,7 +34,7 @@ const requests = {
headers: {
...setJWT()
},
}).then(res => res.body),
}),
};
export default {

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import Cookies from 'universal-cookie';
import {
Route, Switch, withRouter, Redirect,
@@ -7,102 +7,127 @@ import config from 'payload-config';
import DefaultTemplate from './layout/DefaultTemplate';
import Dashboard from './views/Dashboard';
import Login from './views/Login';
import CreateFirstUser from './views/CreateFirstUser';
import CreateUser from './views/CreateUser';
import MediaLibrary from './views/MediaLibrary';
import Edit from './views/collections/Edit';
import List from './views/collections/List';
import { requests } from '../api';
const cookies = new Cookies();
const Routes = () => {
const [initialized, setInitialized] = useState(null);
useEffect(() => {
requests.get('/init').then(res => res.json().then((data) => {
if (data && 'initialized' in data) {
setInitialized(data.initialized);
}
}));
}, []);
return (
<Route
path="/admin"
render={({ match }) => {
return (
<Switch>
<Route
path={`${match.url}/login`}
render={routeProps => <Login {...routeProps} />}
/>
<Route
path={`${match.url}/forgot`}
component={() => { return <h1>Forgot Password</h1>; }}
/>
<Route
render={() => {
if (cookies.get('token')) {
return (
<DefaultTemplate>
<Route
path={`${match.url}/media-library`}
component={MediaLibrary}
/>
<Route
path={`${match.url}/create-user`}
component={CreateUser}
/>
<Route
path={`${match.url}/`}
exact
component={Dashboard}
/>
if (initialized === false) {
return (
<Switch>
<Route path={`${match.url}/create-first-user`}>
<CreateFirstUser />
</Route>
<Route>
<Redirect to="/admin/create-first-user" />
</Route>
</Switch>
);
} if (initialized === true) {
return (
<Switch>
<Route path={`${match.url}/login`}>
<Login />
</Route>
<Route path={`${match.url}/forgot`}>
<h1>Forgot Password</h1>
</Route>
<Route
render={() => {
if (cookies.get('token')) {
return (
<DefaultTemplate>
<Route
path={`${match.url}/media-library`}
component={MediaLibrary}
/>
<Route
path={`${match.url}/create-user`}
component={CreateUser}
/>
<Route
path={`${match.url}/`}
exact
component={Dashboard}
/>
{config.collections.map((collection) => {
const components = collection.components ? collection.components : {};
return (
<Switch key={collection.slug}>
<Route
path={`${match.url}/collections/${collection.slug}/create`}
exact
render={(routeProps) => {
return (
<Edit
{...routeProps}
collection={collection}
/>
);
}}
/>
{config.collections.map((collection) => {
const components = collection.components ? collection.components : {};
return (
<Switch key={collection.slug}>
<Route
path={`${match.url}/collections/${collection.slug}/create`}
exact
render={(routeProps) => {
return (
<Edit
{...routeProps}
collection={collection}
/>
);
}}
/>
<Route
path={`${match.url}/collections/${collection.slug}/:id`}
exact
render={(routeProps) => {
return (
<Edit
{...routeProps}
collection={collection}
/>
);
}}
/>
<Route
path={`${match.url}/collections/${collection.slug}/:id`}
exact
render={(routeProps) => {
return (
<Edit
{...routeProps}
collection={collection}
/>
);
}}
/>
<Route
path={`${match.url}/collections/${collection.slug}`}
exact
render={(routeProps) => {
const ListComponent = components.List ? components.List : List;
return (
<ListComponent
{...routeProps}
collection={collection}
/>
);
}}
/>
<Route
path={`${match.url}/collections/${collection.slug}`}
exact
render={(routeProps) => {
const ListComponent = components.List ? components.List : List;
return (
<ListComponent
{...routeProps}
collection={collection}
/>
);
}}
/>
</Switch>
);
})}
</DefaultTemplate>
);
}
return <Redirect to="/admin/login" />;
}}
/>
</Switch>
);
</Switch>
);
})}
</DefaultTemplate>
);
}
return <Redirect to="/admin/login" />;
}}
/>
</Switch>
);
}
return null;
}}
/>
);

View File

@@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import fieldTypes from '../field-types';
const RenderFields = ({ fields }) => {
if (fields) {
return fields.map((field, i) => {
return field.name + i;
});
}
return null;
};
RenderFields.propTypes = {
fields: PropTypes.arrayOf(
PropTypes.shape({}),
).isRequired,
};
export default RenderFields;

View File

@@ -1,32 +1,28 @@
import React, { Component } from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import FormContext from '../Form/Context';
import Button from '../../controls/Button';
import './index.scss';
class FormSubmit extends Component {
render() {
return (
<div className="form-submit">
<Button disabled={this.props.context.processing ? 'disabled' : ''}>
{this.props.children}
</Button>
</div>
);
}
}
const baseClass = 'form-submit';
const ContextFormSubmit = (props) => {
const FormSubmit = ({ children }) => {
const formContext = useContext(FormContext);
return (
<FormContext.Consumer>
{context => (
<FormSubmit
{...props}
context={context}
/>
)}
</FormContext.Consumer>
<div className={baseClass}>
<Button disabled={formContext.processing ? 'disabled' : ''}>
{children}
</Button>
</div>
);
};
export default ContextFormSubmit;
FormSubmit.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
export default FormSubmit;

View File

@@ -1,12 +1,28 @@
import React from 'react';
import { Section } from 'payload/components';
import PropTypes from 'prop-types';
import Section from '../../../layout/Section';
const Group = props => {
const Group = ({ label, children }) => {
return (
<Section heading={props.label} className="field-group">
{props.children}
<Section
heading={label}
className="field-group"
>
{children}
</Section>
);
};
Group.defaultProps = {
label: '',
};
Group.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
label: PropTypes.string,
};
export default Group;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { fieldType } from 'payload/components';
import fieldType from '../fieldType';
import './index.scss';

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { fieldType, UploadMedia } from 'payload/components';
import fieldType from '../fieldType';
import UploadMedia from '../../../modules/UploadMedia';
import './index.scss';

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Section } from 'payload/components';
import Section from '../../../layout/Section';
class Repeater extends Component {
constructor(props) {

View File

@@ -1,28 +1,70 @@
import React from 'react';
import { fieldType } from 'payload/components';
import PropTypes from 'prop-types';
import fieldType from '../fieldType';
import './index.scss';
const error = 'Please fill in the textarea';
const errorMessage = 'Please fill in the textarea';
const validate = value => value.length > 0;
const Textarea = props => {
const Textarea = (props) => {
const {
className,
style,
error,
label,
value,
onChange,
disabled,
placeholder,
id,
name,
} = props;
return (
<div className={props.className} style={props.style}>
{props.error}
{props.label}
<div
className={className}
style={style}
>
{error}
{label}
<textarea
value={props.value || ''}
onChange={props.onChange}
disabled={props.disabled}
placeholder={props.placeholder}
type={props.type}
id={props.id ? props.id : props.name}
name={props.name}>
</textarea>
value={value || ''}
onChange={onChange}
disabled={disabled}
placeholder={placeholder}
id={id || name}
name={name}
/>
</div>
);
}
};
export default fieldType(Textarea, 'textarea', validate, error);
Textarea.defaultProps = {
className: null,
style: {},
error: null,
label: null,
value: '',
onChange: null,
disabled: null,
placeholder: null,
id: null,
name: 'textarea',
};
Textarea.propTypes = {
className: PropTypes.string,
style: PropTypes.shape({}),
error: PropTypes.node,
label: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
disabled: PropTypes.string,
placeholder: PropTypes.string,
id: PropTypes.string,
name: PropTypes.string,
};
export default fieldType(Textarea, 'textarea', validate, errorMessage);

View File

@@ -0,0 +1,8 @@
export { default as Email } from './Email';
export { default as Group } from './Group';
export { default as HiddenInput } from './HiddenInput';
export { default as Input } from './Input';
export { default as Media } from './Media';
export { default as Password } from './Password';
export { default as Repeater } from './Repeater';
export { default as Textarea } from './Textarea';

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import Init from './utilities/Init';
import { SearchParamsProvider } from './utilities/SearchParams';
import { LocaleProvider } from './utilities/Locale';
import { StatusListProvider } from './modules/Status';
@@ -11,17 +10,15 @@ import '../scss/app.scss';
const Index = () => {
return (
<Init>
<Router>
<SearchParamsProvider>
<LocaleProvider>
<StatusListProvider>
<Routes />
</StatusListProvider>
</LocaleProvider>
</SearchParamsProvider>
</Router>
</Init>
<Router>
<SearchParamsProvider>
<LocaleProvider>
<StatusListProvider>
<Routes />
</StatusListProvider>
</LocaleProvider>
</SearchParamsProvider>
</Router>
);
};

View File

@@ -1,8 +1,8 @@
import React, { Component } from 'react';
import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import { Button } from 'payload/components';
import api from 'payload/api';
import Button from '../../controls/Button';
import api from '../../../api';
import './index.scss';

View File

@@ -1,26 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
const Init = ({ children }) => {
const [initialized, setInitialized] = useState(false);
if (initialized) {
return (
{ children }
);
}
return (
<h1>Not initialized</h1>
);
};
Init.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
export default Init;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import Cookies from 'universal-cookie';
import config from 'payload-config';
import ContentBlock from '../../layout/ContentBlock';
import Form from '../../forms/Form';
import RenderFields from '../../forms/RenderFields';
import FormSubmit from '../../forms/Submit';
import './index.scss';
const cookies = new Cookies();
const handleAjaxResponse = (res) => {
cookies.set('token', res.token, { path: '/' });
};
const baseClass = 'create-first-user';
const CreateFirstUser = () => {
return (
<ContentBlock className={baseClass}>
<div className={`${baseClass}__wrap`}>
<h1>Welcome to Payload</h1>
<p>To begin, create your first user.</p>
<Form
handleAjaxResponse={handleAjaxResponse}
method="POST"
action="/first-user"
>
<RenderFields fields={config.user.fields} />
<FormSubmit>Create</FormSubmit>
</Form>
</div>
</ContentBlock>
);
};
export default CreateFirstUser;

View File

@@ -0,0 +1,16 @@
@import '../../../scss/styles';
.create-first-user {
display: flex;
align-items: center;
flex-wrap: wrap;
min-height: 100vh;
&__wrap {
margin: 0 auto base(1);
svg {
width: 100%;
}
}
}

View File

@@ -15,13 +15,15 @@ const handleAjaxResponse = (res) => {
cookies.set('token', res.token, { path: '/' });
};
const baseClass = 'login';
const Login = () => {
return (
<ContentBlock
className="login"
className={baseClass}
width="narrow"
>
<div className="wrap">
<div className={`${baseClass}__wrap`}>
<Form
handleAjaxResponse={handleAjaxResponse}
method="POST"

View File

@@ -6,9 +6,8 @@
flex-wrap: wrap;
min-height: 100vh;
.logo-wrap {
&__wrap {
margin: 0 auto base(1);
max-width: 200px;
svg {
width: 100%;

View File

@@ -62,7 +62,7 @@ class Payload {
registerUser = () => {
this.config.user.fields.push(...baseUserFields);
const userSchema = buildCollectionSchema(this.config.user, this.config);
userSchema.plugin(passportLocalMongoose, { usernameField: 'email' });
userSchema.plugin(passportLocalMongoose, { usernameField: this.config.user.useAsUsername });
this.User = mongoose.model(this.config.user.labels.singular, userSchema);
initUserAuth(this.User, this.config, this.router);