Make Neltify CMS work with Netlify Identity Widget
When Netlify CMS uses the git-gateway backend, it will check for a window.netlifyIdentity object and use that to handle the whole auth flow. This also sets defaults for the git-gateway endpoint, that means it can be used in templates with zero configuration and fit with a one-click deploy to Netlify approach. Netlify Identity itself is based on our open-source GoTrue microservice, and Netlify's Git Gateway project is completely open-source as well. The git-gateway backend will work with Netlify without any setup, but can also be configured to work with any selfhosted GoTrue and Git Gateway instances.
This commit is contained in:
parent
36f2596a9e
commit
aaa9d2ab95
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "netlify-cms",
|
"name": "netlify-cms",
|
||||||
"version": "0.5.0-beta.1",
|
"version": "0.5.0-beta.5",
|
||||||
"description": "Netlify CMS lets content editors work on structured content stored in git",
|
"description": "Netlify CMS lets content editors work on structured content stored in git",
|
||||||
"main": "dist/cms.js",
|
"main": "dist/cms.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -68,6 +68,7 @@
|
|||||||
"exports-loader": "^0.6.4",
|
"exports-loader": "^0.6.4",
|
||||||
"extract-text-webpack-plugin": "^2.1.2",
|
"extract-text-webpack-plugin": "^2.1.2",
|
||||||
"file-loader": "^0.11.2",
|
"file-loader": "^0.11.2",
|
||||||
|
"gotrue-js": "^0.9.3",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"imports-loader": "^0.7.1",
|
"imports-loader": "^0.7.1",
|
||||||
"jest": "^20.0.4",
|
"jest": "^20.0.4",
|
||||||
@ -101,7 +102,6 @@
|
|||||||
"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",
|
||||||
@ -115,6 +115,7 @@
|
|||||||
"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",
|
||||||
|
"node-sass": "^3.10.0",
|
||||||
"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",
|
||||||
|
@ -77,7 +77,8 @@ export function logoutUser() {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const backend = currentBackend(state.config);
|
const backend = currentBackend(state.config);
|
||||||
backend.logout();
|
Promise.resolve(backend.logout()).then(() => {
|
||||||
dispatch(logout());
|
dispatch(logout());
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { attempt, isError } from 'lodash';
|
import { attempt, isError } from 'lodash';
|
||||||
import TestRepoBackend from "./test-repo/implementation";
|
import TestRepoBackend from "./test-repo/implementation";
|
||||||
import GitHubBackend from "./github/implementation";
|
import GitHubBackend from "./github/implementation";
|
||||||
import NetlifyAuthBackend from "./netlify-auth/implementation";
|
import GitGatewayBackend from "./git-gateway/implementation";
|
||||||
import { resolveFormat } from "../formats/formats";
|
import { resolveFormat } from "../formats/formats";
|
||||||
import { selectListMethod, selectEntrySlug, selectEntryPath, selectAllowNewEntries, selectFolderEntryExtension } from "../reducers/collections";
|
import { selectListMethod, selectEntrySlug, selectEntryPath, selectAllowNewEntries, selectFolderEntryExtension } from "../reducers/collections";
|
||||||
import { createEntry } from "../valueObjects/Entry";
|
import { createEntry } from "../valueObjects/Entry";
|
||||||
@ -36,12 +36,12 @@ const slugFormatter = (template = "{{slug}}", entryData) => {
|
|||||||
const identifier = identifiers.find(ident => ident !== undefined);
|
const identifier = identifiers.find(ident => ident !== undefined);
|
||||||
|
|
||||||
if (identifier === undefined) {
|
if (identifier === undefined) {
|
||||||
throw new Error("Collection must have a field name that is a valid entry identifier");
|
throw new Error("Collection must have a field name that is a valid entry identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
return identifier;
|
return identifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
return template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => {
|
return template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => {
|
||||||
switch (field) {
|
switch (field) {
|
||||||
case "year":
|
case "year":
|
||||||
@ -88,11 +88,11 @@ class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
if (this.authStore) {
|
return Promise.resolve(this.implementation.logout()).then(() => {
|
||||||
this.authStore.logout();
|
if (this.authStore) {
|
||||||
} else {
|
this.authStore.logout();
|
||||||
throw new Error("User isn't authenticated.");
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getToken = () => this.implementation.getToken();
|
getToken = () => this.implementation.getToken();
|
||||||
@ -292,8 +292,8 @@ export function resolveBackend(config) {
|
|||||||
return new Backend(new TestRepoBackend(config), authStore);
|
return new Backend(new TestRepoBackend(config), authStore);
|
||||||
case "github":
|
case "github":
|
||||||
return new Backend(new GitHubBackend(config), authStore);
|
return new Backend(new GitHubBackend(config), authStore);
|
||||||
case "netlify-auth":
|
case "git-gateway":
|
||||||
return new Backend(new NetlifyAuthBackend(config), authStore);
|
return new Backend(new GitGatewayBackend(config), authStore);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Backend not found: ${ name }`);
|
throw new Error(`Backend not found: ${ name }`);
|
||||||
}
|
}
|
||||||
|
@ -5,23 +5,44 @@ import { Card, Icon } from "../../components/UI";
|
|||||||
import logo from "./netlify_logo.svg";
|
import logo from "./netlify_logo.svg";
|
||||||
import styles from "./AuthenticationPage.css";
|
import styles from "./AuthenticationPage.css";
|
||||||
|
|
||||||
|
let component = null;
|
||||||
|
|
||||||
|
if (window.netlifyIdentity) {
|
||||||
|
window.netlifyIdentity.on('login', (user) => {
|
||||||
|
component && component.handleIdentityLogin(user);
|
||||||
|
});
|
||||||
|
window.netlifyIdentity.on('logout', () => {
|
||||||
|
component && component.handleIdentityLogout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export default class AuthenticationPage extends React.Component {
|
export default class AuthenticationPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.identity && !this.state.user) {
|
component = this;
|
||||||
this.identity.on('login', (user) => {
|
if (!this.loggedIn && window.netlifyIdentity && window.netlifyIdentity.currentUser()) {
|
||||||
this.props.onLogin(user);
|
this.props.onLogin(window.netlifyIdentity.currentUser());
|
||||||
this.identity.close();
|
|
||||||
});
|
|
||||||
this.identity.on('signup', (user) => {
|
|
||||||
this.props.onLogin(user);
|
|
||||||
this.identity.close();
|
|
||||||
});
|
|
||||||
this.identity.open();
|
|
||||||
}
|
}
|
||||||
|
if (window.netlifyIdentity && !window.netlifyIdentity.currentUser()) {
|
||||||
|
window.netlifyIdentity.open('login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
component = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIdentityLogin = (user) => {
|
||||||
|
console.log('logging in ', user);
|
||||||
|
this.props.onLogin(user);
|
||||||
|
window.netlifyIdentity.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIdentityLogout = () => {
|
||||||
|
window.netlifyIdentity.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -64,6 +85,10 @@ export default class AuthenticationPage extends React.Component {
|
|||||||
const { errors } = this.state;
|
const { errors } = this.state;
|
||||||
const { error } = this.props;
|
const { error } = this.props;
|
||||||
|
|
||||||
|
if (window.netlifyIdentity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.root}>
|
<section className={styles.root}>
|
||||||
<Card className={styles.card}>
|
<Card className={styles.card}>
|
@ -11,38 +11,40 @@ const localHosts = {
|
|||||||
'127.0.0.1': true,
|
'127.0.0.1': true,
|
||||||
'0.0.0.0': true
|
'0.0.0.0': true
|
||||||
}
|
}
|
||||||
|
const defaults = {
|
||||||
|
identity: '/.netlify/identity',
|
||||||
|
gateway: '/.netlify/git/github'
|
||||||
|
}
|
||||||
|
|
||||||
function getEndpoint(endpoint, netlifySiteURL) {
|
function getEndpoint(endpoint, netlifySiteURL) {
|
||||||
if (localHosts[document.location.host] && netlifySiteURL && endpoint.match(/^\/\.netlify\//)) {
|
if (localHosts[document.location.host.split(":").shift()] && netlifySiteURL && endpoint.match(/^\/\.netlify\//)) {
|
||||||
const parts = [netlifySiteURL];
|
const parts = [];
|
||||||
if (!netlifySiteURL.match(/\/$/)) { parts.push("/"); }
|
if (netlifySiteURL) {
|
||||||
parts.push(endpoint);
|
parts.push(netlifySiteURL);
|
||||||
|
if (!netlifySiteURL.match(/\/$/)) { parts.push("/"); }
|
||||||
|
}
|
||||||
|
parts.push(endpoint.replace(/^\//, ''));
|
||||||
return parts.join("");
|
return parts.join("");
|
||||||
}
|
}
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NetlifyAuth extends GitHubBackend {
|
export default class GitGateway extends GitHubBackend {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
super(config, true);
|
super(config, true);
|
||||||
if (config.getIn(["backend", "auth_url"]) == null) { throw new Error("The NetlifyAuth backend needs an \"auth_url\" in the backend configuration."); }
|
|
||||||
|
|
||||||
if (config.getIn(["backend", "github_proxy_url"]) == null) {
|
|
||||||
throw new Error("The NetlifyAuth backend needs an \"github_proxy_url\" in the backend configuration.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.accept_roles = (config.getIn(["backend", "accept_roles"]) || List()).toArray();
|
this.accept_roles = (config.getIn(["backend", "accept_roles"]) || List()).toArray();
|
||||||
|
|
||||||
const netlifySiteURL = localStorage.getItem("netlifySiteURL");
|
const netlifySiteURL = localStorage.getItem("netlifySiteURL");
|
||||||
const APIUrl = getEndpoint(config.getIn(["backend", "auth_url"]), netlifySiteURL);
|
const APIUrl = getEndpoint(config.getIn(["backend", "identity_url"], defaults.identity), netlifySiteURL);
|
||||||
this.github_proxy_url = getEndpoint(config.getIn(["backend", "github_proxy_url"]), netlifySiteURL);
|
this.github_proxy_url = getEndpoint(config.getIn(["backend", "gateway_url"], defaults.gateway), netlifySiteURL);
|
||||||
this.authClient = new GoTrue({APIUrl});
|
this.authClient = window.netlifyIdentity ? window.netlifyIdentity.gotrue : new GoTrue({APIUrl});
|
||||||
|
|
||||||
AuthenticationPage.authClient = this.authClient;
|
AuthenticationPage.authClient = this.authClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser() {
|
setUser() {
|
||||||
const user = this.authClient.currentUser();
|
const user = this.authClient && this.authClient.currentUser();
|
||||||
if (!user) return Promise.reject();
|
if (!user) return Promise.reject();
|
||||||
return this.authenticate(user);
|
return this.authenticate(user);
|
||||||
}
|
}
|
||||||
@ -74,6 +76,14 @@ export default class NetlifyAuth extends GitHubBackend {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
if (window.netlifyIdentity) {
|
||||||
|
return window.netlifyIdentity.logout();
|
||||||
|
}
|
||||||
|
const user = this.authClient.currentUser();
|
||||||
|
return user && user.logout();
|
||||||
|
}
|
||||||
|
|
||||||
getToken() {
|
getToken() {
|
||||||
return this.tokenPromise();
|
return this.tokenPromise();
|
||||||
}
|
}
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -42,6 +42,11 @@ export default class GitHub {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.token = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
getToken() {
|
getToken() {
|
||||||
return Promise.resolve(this.token);
|
return Promise.resolve(this.token);
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
import Input from "react-toolbox/lib/input";
|
import Input from "react-toolbox/lib/input";
|
||||||
import Button from "react-toolbox/lib/button";
|
import Button from "react-toolbox/lib/button";
|
||||||
import { Card, Icon } from "../../components/UI";
|
import { Card, Icon } from "../../components/UI";
|
||||||
import logo from "../netlify-auth/netlify_logo.svg";
|
import logo from "../git-gateway/netlify_logo.svg";
|
||||||
import styles from "../netlify-auth/AuthenticationPage.css";
|
import styles from "../git-gateway/AuthenticationPage.css";
|
||||||
|
|
||||||
export default class AuthenticationPage extends React.Component {
|
export default class AuthenticationPage extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -36,6 +36,10 @@ export default class TestRepo {
|
|||||||
return Promise.resolve({ email: state.email, name: nameFromEmail(state.email) });
|
return Promise.resolve({ email: state.email, name: nameFromEmail(state.email) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logotu() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getToken() {
|
getToken() {
|
||||||
return Promise.resolve('');
|
return Promise.resolve('');
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user