From 47e6a33a4505e13ba4edf37ff1f8fbdadb279ee3 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 16 Sep 2015 15:49:12 -0400 Subject: Implement OAuth2 service provider functionality. --- web/react/components/authorize.jsx | 72 +++++++ web/react/components/popover_list_members.jsx | 2 +- web/react/components/register_app_modal.jsx | 249 +++++++++++++++++++++++ web/react/components/user_settings.jsx | 10 + web/react/components/user_settings_developer.jsx | 93 +++++++++ web/react/components/user_settings_modal.jsx | 11 +- web/react/pages/authorize.jsx | 21 ++ web/react/pages/channel.jsx | 6 + web/react/utils/client.jsx | 33 +++ web/sass-files/sass/partials/_signup.scss | 15 ++ web/templates/authorize.html | 26 +++ web/templates/channel.html | 1 + web/web.go | 204 ++++++++++++++++++- web/web_test.go | 134 +++++++++++- 14 files changed, 864 insertions(+), 13 deletions(-) create mode 100644 web/react/components/authorize.jsx create mode 100644 web/react/components/register_app_modal.jsx create mode 100644 web/react/components/user_settings_developer.jsx create mode 100644 web/react/pages/authorize.jsx create mode 100644 web/templates/authorize.html (limited to 'web') diff --git a/web/react/components/authorize.jsx b/web/react/components/authorize.jsx new file mode 100644 index 000000000..dd4479ad4 --- /dev/null +++ b/web/react/components/authorize.jsx @@ -0,0 +1,72 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../utils/client.jsx'); + +export default class Authorize extends React.Component { + constructor(props) { + super(props); + + this.handleAllow = this.handleAllow.bind(this); + this.handleDeny = this.handleDeny.bind(this); + + this.state = {}; + } + handleAllow() { + const responseType = this.props.responseType; + const clientId = this.props.clientId; + const redirectUri = this.props.redirectUri; + const state = this.props.state; + const scope = this.props.scope; + + Client.allowOAuth2(responseType, clientId, redirectUri, state, scope, + (data) => { + if (data.redirect) { + window.location.replace(data.redirect); + } + }, + () => {} + ); + } + handleDeny() { + window.location.replace(this.props.redirectUri + '?error=access_denied'); + } + render() { + return ( +
+
+

{'An application would like to connect to your '}{this.props.teamName}{' account'}

+ +
+
+ +
+ + +
+
+ ); + } +} + +Authorize.propTypes = { + appName: React.PropTypes.string, + teamName: React.PropTypes.string, + responseType: React.PropTypes.string, + clientId: React.PropTypes.string, + redirectUri: React.PropTypes.string, + state: React.PropTypes.string, + scope: React.PropTypes.string +}; diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx index fb9522afb..ec873dd00 100644 --- a/web/react/components/popover_list_members.jsx +++ b/web/react/components/popover_list_members.jsx @@ -25,7 +25,7 @@ export default class PopoverListMembers extends React.Component { $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true}); $('body').on('click', function onClick(e) { - if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) { + if (e.target.parentNode && $(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) { $('#member_popover').popover('hide'); } }); diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx new file mode 100644 index 000000000..3dd5c094e --- /dev/null +++ b/web/react/components/register_app_modal.jsx @@ -0,0 +1,249 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../utils/client.jsx'); + +export default class RegisterAppModal extends React.Component { + constructor() { + super(); + + this.register = this.register.bind(this); + this.onHide = this.onHide.bind(this); + this.save = this.save.bind(this); + + this.state = {clientId: '', clientSecret: '', saved: false}; + } + componentDidMount() { + $(React.findDOMNode(this)).on('hide.bs.modal', this.onHide); + } + register() { + var state = this.state; + state.serverError = null; + + var app = {}; + + var name = this.refs.name.getDOMNode().value; + if (!name || name.length === 0) { + state.nameError = 'Application name must be filled in.'; + this.setState(state); + return; + } + state.nameError = null; + app.name = name; + + var homepage = this.refs.homepage.getDOMNode().value; + if (!homepage || homepage.length === 0) { + state.homepageError = 'Homepage must be filled in.'; + this.setState(state); + return; + } + state.homepageError = null; + app.homepage = homepage; + + var desc = this.refs.desc.getDOMNode().value; + app.description = desc; + + var rawCallbacks = this.refs.callback.getDOMNode().value.trim(); + if (!rawCallbacks || rawCallbacks.length === 0) { + state.callbackError = 'At least one callback URL must be filled in.'; + this.setState(state); + return; + } + state.callbackError = null; + app.callback_urls = rawCallbacks.split('\n'); + + Client.registerOAuthApp(app, + (data) => { + state.clientId = data.id; + state.clientSecret = data.client_secret; + this.setState(state); + }, + (err) => { + state.serverError = err.message; + this.setState(state); + } + ); + } + onHide(e) { + if (!this.state.saved && this.state.clientId !== '') { + e.preventDefault(); + return; + } + + this.setState({clientId: '', clientSecret: '', saved: false}); + } + save() { + this.setState({saved: this.refs.save.getDOMNode().checked}); + } + render() { + var nameError; + if (this.state.nameError) { + nameError =
; + } + var homepageError; + if (this.state.homepageError) { + homepageError =
; + } + var callbackError; + if (this.state.callbackError) { + callbackError =
; + } + var serverError; + if (this.state.serverError) { + serverError =
; + } + + var body = ''; + if (this.state.clientId === '') { + body = ( +
+

{'Register a New Application'}

+
+ +
+ + {nameError} +
+
+
+ +
+ + {homepageError} +
+
+
+ +
+ +
+
+
+ +
+