Skip to content

Commit

Permalink
Add implicit OAuth for GitLab.
Browse files Browse the repository at this point in the history
  • Loading branch information
tech4him1 authored and Benaiah committed May 9, 2018
1 parent d7bedc9 commit ac0b0da
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 31 deletions.
34 changes: 26 additions & 8 deletions src/backends/gitlab/AuthenticationPage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import Authenticator from 'Lib/netlify-auth';
import NetlifyAuthenticator from 'Lib/netlify-auth';
import ImplicitAuthenticator from 'Lib/implicit-oauth';
import { Icon } from 'UI';

export default class AuthenticationPage extends React.Component {
Expand All @@ -11,15 +12,32 @@ export default class AuthenticationPage extends React.Component {

state = {};

componentDidMount() {
const authType = this.props.config.getIn(['backend', 'auth_type']);
if (authType === "implicit") {
this.auth = new ImplicitAuthenticator({
auth_url: this.props.config.getIn(['backend', 'auth_url'], 'https://gitlab.com/oauth/authorize'),
appid: this.props.config.getIn(['backend', 'appid']),
});
// Complete implicit authentication if we were redirected back to from the provider.
this.auth.completeAuth((err, data) => {
if (err) {
this.setState({ loginError: err.toString() });
return;
}
this.props.onLogin(data);
});
} else {
this.auth = new NetlifyAuthenticator({
base_url: this.props.base_url,
site_id: (document.location.host.split(':')[0] === 'localhost') ? 'cms.netlify.com' : this.props.siteId
});
}
}

handleLogin = (e) => {
e.preventDefault();
const cfg = {
base_url: this.props.base_url,
site_id: (document.location.host.split(':')[0] === 'localhost') ? 'cms.netlify.com' : this.props.siteId
};
const auth = new Authenticator(cfg);

auth.authenticate({ provider: 'gitlab', scope: 'api' }, (err, data) => {
this.auth.authenticate({ provider: 'gitlab', scope: 'api read_user' }, (err, data) => {
if (err) {
this.setState({ loginError: err.toString() });
return;
Expand Down
71 changes: 71 additions & 0 deletions src/lib/implicit-oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Map } from 'immutable';
import { randomStr } from 'Lib/randomGenerator';
import history from 'Routing/history';

function createNonce() {
const nonce = randomStr();
window.sessionStorage.setItem("netlify-cms-auth", JSON.stringify({ nonce }));
return nonce;
}

function validateNonce(check) {
const auth = window.sessionStorage.getItem("netlify-cms-auth");
const valid = auth && JSON.parse(auth).nonce;
window.localStorage.removeItem("netlify-cms-auth");
return (check === valid);
}

export default class ImplicitAuthenticator {
constructor(config = {}) {
this.auth_url = config.auth_url;
this.appid = config.appid;
}

authenticate(options, cb) {
if (
document.location.protocol !== "https:"
// TODO: Is insecure localhost a bad idea as well? I don't think it is, since you are not actually
// sending the token over the internet in this case, assuming the auth URL is secure.
&& (document.location.hostname !== "localhost" && document.location.hostname !== "127.0.0.1")
) {
return cb(new Error("Cannot authenticate over insecure protocol!"));
}

const authURL = new URL(this.auth_url);
authURL.searchParams.set('client_id', this.appid);
authURL.searchParams.set('redirect_uri', document.location.origin + document.location.pathname);
authURL.searchParams.set('response_type', 'token');
authURL.searchParams.set('scope', options.scope);
authURL.searchParams.set('state', createNonce());

document.location.assign(authURL.href);
}

/**
* Complete authentication if we were redirected back to from the provider.
*/
completeAuth(cb) {
const hashParams = new URLSearchParams(document.location.hash.replace(/^#?\/?/, ''));
if (!hashParams.has("access_token") && !hashParams.has("error")) {
return;
}
// Remove tokens from hash so that token does not remain in browser history.
history.replace('/');

const params = Map(hashParams.entries());

const validNonce = validateNonce(params.get('state'));
if (!validNonce) {
return cb(new Error("Invalid nonce"));
}

if (params.has('error')) {
return cb(new Error(`${ params.get('error') }: ${ params.get('error_description') }`));
}

if (params.has('access_token')) {
const { access_token: token, ...data } = params.toJS();
cb(null, { token, ...data });
}
}
}
33 changes: 10 additions & 23 deletions src/lib/randomGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,17 @@
* Random number generator
*/

let rng;

if (window.crypto && crypto.getRandomValues) {
// WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
// Moderately fast, high quality
const _rnds32 = new Uint32Array(1);
rng = function whatwgRNG() {
crypto.getRandomValues(_rnds32);
return _rnds32[0];
};
const padNumber = (num, base) => {
const padLen = (32 / Math.sqrt(base));
const str = num.toString(base);
return (('0' * padLen) + str).slice(-padLen);
}

if (!rng) {
// Math.random()-based (RNG)
// If no Crypto available, use Math.random().
rng = function() {
const r = Math.random() * 0x100000000;
const _rnds = r >>> 0;
return _rnds;
};
}
export function randomStr(len = 256) {
const _rnds = new Uint32Array(Math.ceil(len / 32));
window.crypto.getRandomValues(_rnds);

export function randomStr() {
return rng().toString(36);
}
const str = _rnds.reduce((agg, val) => (agg + padNumber(val, 16)), '');

export default rng;
return str.slice(-len);
}

0 comments on commit ac0b0da

Please sign in to comment.