Merge pull request #579 from netlify/fixes-to-netlify-auth-and-asset-setup

Small fixes for the netlify-auth and asset store setup
This commit is contained in:
Shawn Erquhart 2017-09-06 15:01:14 -04:00 committed by GitHub
commit 85e0a156fd
6 changed files with 145 additions and 95 deletions

View File

@ -101,6 +101,7 @@
"dateformat": "^1.0.12", "dateformat": "^1.0.12",
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"fuzzy": "^0.1.1", "fuzzy": "^0.1.1",
"gotrue-js": "^0.9.3",
"history": "^2.1.2", "history": "^2.1.2",
"immutability-helper": "^2.0.0", "immutability-helper": "^2.0.0",
"immutable": "^3.7.6", "immutable": "^3.7.6",
@ -114,7 +115,6 @@
"mdast-util-definitions": "^1.2.2", "mdast-util-definitions": "^1.2.2",
"mdast-util-to-string": "^1.0.4", "mdast-util-to-string": "^1.0.4",
"moment": "^2.11.2", "moment": "^2.11.2",
"netlify-auth-js": "^0.5.5",
"normalize.css": "^4.2.0", "normalize.css": "^4.2.0",
"preliminaries": "1.1.0", "preliminaries": "1.1.0",
"preliminaries-parser-toml": "1.1.0", "preliminaries-parser-toml": "1.1.0",

View File

@ -6,12 +6,31 @@ import logo from "./netlify_logo.svg";
import styles from "./AuthenticationPage.css"; import styles from "./AuthenticationPage.css";
export default class AuthenticationPage extends React.Component { export default class AuthenticationPage extends React.Component {
constructor(props) {
super(props);
this.identity = window.netlifyIdentity;
this.state = {user: this.identity && this.identity.gotrue && this.identity.gotrue.currentUser()};
}
componentDidMount() {
if (this.identity && !this.state.user) {
this.identity.on('login', (user) => {
this.props.onLogin(user);
this.identity.close();
});
this.identity.on('signup', (user) => {
this.props.onLogin(user);
this.identity.close();
});
this.identity.open();
}
}
static propTypes = { static propTypes = {
onLogin: React.PropTypes.func.isRequired, onLogin: React.PropTypes.func.isRequired,
}; };
state = { username: "", password: "", errors: {} }; state = { email: "", password: "", errors: {} };
handleChange = (name, value) => { handleChange = (name, value) => {
this.setState({ ...this.state, [name]: value }); this.setState({ ...this.state, [name]: value });
@ -20,13 +39,13 @@ export default class AuthenticationPage extends React.Component {
handleLogin = (e) => { handleLogin = (e) => {
e.preventDefault(); e.preventDefault();
const { username, password } = this.state; const { email, password } = this.state;
const errors = {}; const errors = {};
if (!username) { if (!email) {
errors.username = 'Make sure to enter your user name'; errors.email = 'Make sure to enter your email.';
} }
if (!password) { if (!password) {
errors.password = 'Please enter your password'; errors.password = 'Please enter your password.';
} }
if (Object.keys(errors).length > 0) { if (Object.keys(errors).length > 0) {
@ -34,11 +53,11 @@ export default class AuthenticationPage extends React.Component {
return; return;
} }
AuthenticationPage.authClient.login(this.state.username, this.state.password, true) AuthenticationPage.authClient.login(this.state.email, this.state.password, true)
.then((user) => { .then((user) => {
this.props.onLogin(user); this.props.onLogin(user);
}) })
.catch((error) => { .catch((error) => {
this.setState({ errors: { server: error.description || error.msg || error }, loggingIn: false }); this.setState({ errors: { server: error.description || error.msg || error }, loggingIn: false });
}); });
}; };
@ -46,39 +65,41 @@ export default class AuthenticationPage extends React.Component {
render() { render() {
const { errors } = this.state; const { errors } = this.state;
const { error } = this.props; const { error } = this.props;
return ( return (
<section className={styles.root}> <section className={styles.root}>
<Card className={styles.card}> <Card className={styles.card}>
<img src={logo} width={100} role="presentation" /> <form onSubmit={this.handleLogin}>
{error && <p> <img src={logo} width={100} role="presentation" />
<span className={styles.errorMsg}>{error}</span> {error && <p>
</p>} <span className={styles.errorMsg}>{error}</span>
{errors.server && <p> </p>}
<span className={styles.errorMsg}>{errors.server}</span> {errors.server && <p>
</p>} <span className={styles.errorMsg}>{errors.server}</span>
<Input </p>}
type="text" <Input
label="Username" type="text"
name="username" label="Email"
value={this.state.username} name="email"
error={errors.username} value={this.state.email}
onChange={this.handleChange.bind(this, "username")} // eslint-disable-line error={errors.email}
/> onChange={this.handleChange.bind(this, "email")} // eslint-disable-line
<Input />
type="password" <Input
label="Password" type="password"
name="password" label="Password"
value={this.state.password} name="password"
error={errors.password} value={this.state.password}
onChange={this.handleChange.bind(this, "password")} // eslint-disable-line error={errors.password}
/> onChange={this.handleChange.bind(this, "password")} // eslint-disable-line
<Button />
className={styles.button} <Button
raised className={styles.button}
onClick={this.handleLogin} raised
> >
<Icon type="login" /> Login <Icon type="login" /> Login
</Button> </Button>
</form>
</Card> </Card>
</section> </section>
); );

View File

@ -1,10 +1,27 @@
import NetlifyAuthClient from "netlify-auth-js"; import GoTrue from "gotrue-js";
import jwtDecode from 'jwt-decode'; import jwtDecode from 'jwt-decode';
import {List} from 'immutable';
import { get, pick, intersection } from "lodash"; import { get, pick, intersection } from "lodash";
import GitHubBackend from "../github/implementation"; import GitHubBackend from "../github/implementation";
import API from "./API"; import API from "./API";
import AuthenticationPage from "./AuthenticationPage"; import AuthenticationPage from "./AuthenticationPage";
const localHosts = {
localhost: true,
'127.0.0.1': true,
'0.0.0.0': true
}
function getEndpoint(endpoint, netlifySiteURL) {
if (localHosts[document.location.host] && netlifySiteURL && endpoint.match(/^\/\.netlify\//)) {
const parts = [netlifySiteURL];
if (!netlifySiteURL.match(/\/$/)) { parts.push("/"); }
parts.push(endpoint);
return parts.join("");
}
return endpoint;
}
export default class NetlifyAuth extends GitHubBackend { export default class NetlifyAuth extends GitHubBackend {
constructor(config) { constructor(config) {
super(config, true); super(config, true);
@ -13,16 +30,13 @@ export default class NetlifyAuth extends GitHubBackend {
if (config.getIn(["backend", "github_proxy_url"]) == null) { if (config.getIn(["backend", "github_proxy_url"]) == null) {
throw new Error("The NetlifyAuth backend needs an \"github_proxy_url\" in the backend configuration."); throw new Error("The NetlifyAuth backend needs an \"github_proxy_url\" in the backend configuration.");
} }
this.github_proxy_url = config.getIn(["backend", "github_proxy_url"]);
if (config.getIn(["backend", "accept_roles"]) == null) { this.accept_roles = (config.getIn(["backend", "accept_roles"]) || List()).toArray();
throw new Error("The NetlifyAuth backend needs an \"accept_roles\" in the backend configuration.");
}
this.accept_roles = config.getIn(["backend", "accept_roles"]).toArray();
this.authClient = new NetlifyAuthClient({ const netlifySiteURL = localStorage.getItem("netlifySiteURL");
APIUrl: config.getIn(["backend", "auth_url"]), const APIUrl = getEndpoint(config.getIn(["backend", "auth_url"]), netlifySiteURL);
}); this.github_proxy_url = getEndpoint(config.getIn(["backend", "github_proxy_url"]), netlifySiteURL);
this.authClient = new GoTrue({APIUrl});
AuthenticationPage.authClient = this.authClient; AuthenticationPage.authClient = this.authClient;
} }
@ -37,10 +51,14 @@ export default class NetlifyAuth extends GitHubBackend {
this.tokenPromise = user.jwt.bind(user); this.tokenPromise = user.jwt.bind(user);
return this.tokenPromise() return this.tokenPromise()
.then((token) => { .then((token) => {
let validRole = true;
if (this.accept_roles && this.accept_roles.length > 0) {
validRole = intersection(userRoles, this.accept_roles).length > 0;
}
const userRoles = get(jwtDecode(token), 'app_metadata.roles', []); const userRoles = get(jwtDecode(token), 'app_metadata.roles', []);
if (intersection(userRoles, this.accept_roles).length > 0) { if (validRole) {
const userData = { const userData = {
name: `${ user.user_metadata.firstname } ${ user.user_metadata.lastname }`, name: user.user_metadata.name,
email: user.email, email: user.email,
metadata: user.user_metadata, metadata: user.user_metadata,
}; };
@ -51,9 +69,9 @@ export default class NetlifyAuth extends GitHubBackend {
}); });
return userData; return userData;
} else { } else {
throw new Error("User is not authorized"); throw new Error("You don't have sufficient permissions to access Netlify CMS");
} }
}); });
} }
getToken() { getToken() {

View File

@ -5,7 +5,7 @@ export default class AssetStore {
throw 'The AssetStore integration needs the getSignedFormURL in the integration configuration.'; throw 'The AssetStore integration needs the getSignedFormURL in the integration configuration.';
} }
this.getToken = getToken; this.getToken = getToken;
this.shouldConfirmUpload = config.get('shouldConfirmUpload', false); this.shouldConfirmUpload = config.get('shouldConfirmUpload', false);
this.getSignedFormURL = config.get('getSignedFormURL'); this.getSignedFormURL = config.get('getSignedFormURL');
} }
@ -69,9 +69,11 @@ export default class AssetStore {
upload(file, privateUpload = false) { upload(file, privateUpload = false) {
const fileData = { const fileData = {
name: file.name, name: file.name,
size: file.size, size: file.size
content_type: file.type,
}; };
if (file.type) {
fileData.content_type = file.type;
}
if (privateUpload) { if (privateUpload) {
fileData.visibility = 'private'; fileData.visibility = 'private';
@ -91,7 +93,7 @@ export default class AssetStore {
const formFields = response.form.fields; const formFields = response.form.fields;
const assetID = response.asset.id; const assetID = response.asset.id;
const assetURL = response.asset.url; const assetURL = response.asset.url;
const formData = new FormData(); const formData = new FormData();
Object.keys(formFields).forEach(key => formData.append(key, formFields[key])); Object.keys(formFields).forEach(key => formData.append(key, formFields[key]));
formData.append('file', file, file.name); formData.append('file', file, file.name);
@ -100,11 +102,10 @@ export default class AssetStore {
method: 'POST', method: 'POST',
body: formData, body: formData,
}) })
.then(() => { .then(() => {
if (this.shouldConfirmUpload) this.confirmRequest(assetID); if (this.shouldConfirmUpload) this.confirmRequest(assetID);
return { success: true, assetURL }; return { success: true, assetURL };
}); });
}); });
} }
} }

View File

@ -1,3 +1,5 @@
import url from 'url';
function getUrl(url, direct) { function getUrl(url, direct) {
return `${ direct ? '/#' : '' }${ url }`; return `${ direct ? '/#' : '' }${ url }`;
} }
@ -9,3 +11,32 @@ export function getCollectionUrl(collectionName, direct) {
export function getNewEntryUrl(collectionName, direct) { export function getNewEntryUrl(collectionName, direct) {
return getUrl(`/collections/${ collectionName }/entries/new`, direct); return getUrl(`/collections/${ collectionName }/entries/new`, direct);
} }
export function urlize(string) {
const sanitized = makePathSanitized(string);
const parsedURL = url.parse(sanitized);
return url.format(parsedURL);
}
function makePathSanitized(string) {
return makePath(string.toLowerCase());
}
function makePath(string) {
return unicodeSanitize(string).trim().replace(/[\s]/g, '-').replace(/-+/g, '-');
}
function unicodeSanitize(string) {
let target = [];
const runes = string.split('');
for (let i=0; i < string.length; i++) {
const r = runes[i];
if (r == '%' && i+2 < string.length && string.substr(i+1, 2).match(/^[0-9a-f]+$/)) {
target = target.concat([r, runes[i+1], runes[i+2]]);
} else if (r.match(/[\w .\/\\_#\+-]/u)) {
target.push(r);
}
}
return target.join('');
}

View File

@ -2331,20 +2331,13 @@ dns-txt@^2.0.2:
dependencies: dependencies:
buffer-indexof "^1.0.0" buffer-indexof "^1.0.0"
doctrine@1.3.x: doctrine@1.3.x, doctrine@^1.2.2:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.3.0.tgz#13e75682b55518424276f7c173783456ef913d26" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.3.0.tgz#13e75682b55518424276f7c173783456ef913d26"
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
isarray "^1.0.0" isarray "^1.0.0"
doctrine@^1.2.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
dependencies:
esutils "^2.0.2"
isarray "^1.0.0"
doctrine@^2.0.0: doctrine@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
@ -2402,20 +2395,13 @@ domhandler@^2.3.0:
dependencies: dependencies:
domelementtype "1" domelementtype "1"
domutils@1.5.1: domutils@1.5.1, domutils@^1.5.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
dependencies: dependencies:
dom-serializer "0" dom-serializer "0"
domelementtype "1" domelementtype "1"
domutils@^1.5.1:
version "1.6.2"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff"
dependencies:
dom-serializer "0"
domelementtype "1"
dot-prop@^4.1.0: dot-prop@^4.1.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
@ -3481,6 +3467,13 @@ got@^6.7.1:
unzip-response "^2.0.1" unzip-response "^2.0.1"
url-parse-lax "^1.0.0" url-parse-lax "^1.0.0"
gotrue-js@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/gotrue-js/-/gotrue-js-0.9.3.tgz#5f8d4de45b10fd9e27b5739fe13e7568b9e9720b"
dependencies:
micro-api-client "^2.0.0"
prettier "^1.6.1"
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4: graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4:
version "4.1.11" version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
@ -3801,14 +3794,10 @@ hyphenate-style-name@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b" resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b"
iconv-lite@0.4.13: iconv-lite@0.4.13, iconv-lite@~0.4.13:
version "0.4.13" version "0.4.13"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
iconv-lite@~0.4.13:
version "0.4.18"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
icss-replace-symbols@^1.1.0: icss-replace-symbols@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@ -5348,7 +5337,7 @@ minimatch@3.0.3:
dependencies: dependencies:
brace-expansion "^1.0.0" brace-expansion "^1.0.0"
minimist@0.0.8: minimist@0.0.8, minimist@~0.0.1:
version "0.0.8" version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
@ -5356,10 +5345,6 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
mixin-object@^2.0.1: mixin-object@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
@ -5421,12 +5406,6 @@ negotiator@0.6.1:
version "0.6.1" version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
netlify-auth-js@^0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/netlify-auth-js/-/netlify-auth-js-0.5.5.tgz#8753018407c08757b941df9c1e8dd5fc99c4ad29"
dependencies:
micro-api-client "^2.0.0"
node-emoji@^1.0.3: node-emoji@^1.0.3:
version "1.8.1" version "1.8.1"
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826"
@ -6721,6 +6700,10 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.6.1.tgz#850f411a3116226193e32ea5acfc21c0f9a76d7d"
pretty-format@^20.0.3: pretty-format@^20.0.3:
version "20.0.3" version "20.0.3"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.3.tgz#020e350a560a1fe1a98dc3beb6ccffb386de8b14" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.3.tgz#020e350a560a1fe1a98dc3beb6ccffb386de8b14"
@ -9106,11 +9089,7 @@ whatwg-encoding@^1.0.1:
dependencies: dependencies:
iconv-lite "0.4.13" iconv-lite "0.4.13"
whatwg-fetch@>=0.10.0: whatwg-fetch@>=0.10.0, whatwg-fetch@^1.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
whatwg-fetch@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz#ac3c9d39f320c6dce5339969d054ef43dd333319" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz#ac3c9d39f320c6dce5339969d054ef43dd333319"