diff --git a/browser/app/js/browser/Login.js b/browser/app/js/browser/Login.js
index 92ae308b9..2bbf1d81c 100644
--- a/browser/app/js/browser/Login.js
+++ b/browser/app/js/browser/Login.js
@@ -16,16 +16,13 @@
import React from "react"
import { connect } from "react-redux"
-import classNames from "classnames"
import logo from "../../img/logo.svg"
import Alert from "../alert/Alert"
import * as actionsAlert from "../alert/actions"
import InputGroup from "./InputGroup"
import web from "../web"
import { Redirect, Link } from "react-router-dom"
-import qs from "query-string"
-import storage from "local-storage-fallback"
-import history from "../history"
+import OpenIDLoginButton from './OpenIDLoginButton'
export class Login extends React.Component {
constructor(props) {
@@ -33,7 +30,8 @@ export class Login extends React.Component {
this.state = {
accessKey: "",
secretKey: "",
- discoveryDoc: {}
+ discoveryDoc: {},
+ clientId: ""
}
}
@@ -88,8 +86,9 @@ export class Login extends React.Component {
}
componentDidMount() {
- web.GetDiscoveryDoc().then(({ DiscoveryDoc }) => {
+ web.GetDiscoveryDoc().then(({ DiscoveryDoc, clientId }) => {
this.setState({
+ clientId,
discoveryDoc: DiscoveryDoc
})
})
@@ -107,6 +106,8 @@ export class Login extends React.Component {
let alertBox =
// Make sure you don't show a fading out alert box on the initial web-page load.
if (!alert.message) alertBox = ""
+
+ const showOpenID = Boolean(this.state.discoveryDoc && this.state.discoveryDoc.authorization_endpoint)
return (
{alertBox}
@@ -139,13 +140,24 @@ export class Login extends React.Component {
- {this.state.discoveryDoc &&
- this.state.discoveryDoc.authorization_endpoint && (
+ {showOpenID && (
or
-
- Log in with OpenID
-
+ {
+ this.state.clientId ? (
+
+ Log in with OpenID
+
+ ) : (
+
+ Log in with OpenID
+
+ )
+ }
)}
diff --git a/browser/app/js/browser/OpenIDLogin.js b/browser/app/js/browser/OpenIDLogin.js
index 3c6544762..71dd04bad 100644
--- a/browser/app/js/browser/OpenIDLogin.js
+++ b/browser/app/js/browser/OpenIDLogin.js
@@ -26,6 +26,7 @@ import qs from "query-string"
import { getRandomString } from "../utils"
import storage from "local-storage-fallback"
import jwtDecode from "jwt-decode"
+import { buildOpenIDAuthURL, OPEN_ID_NONCE_KEY } from './utils'
export class OpenIDLogin extends React.Component {
constructor(props) {
@@ -58,20 +59,17 @@ export class OpenIDLogin extends React.Component {
if (this.state.discoveryDoc && this.state.discoveryDoc.authorization_endpoint) {
const redirectURI = window.location.href.split("#")[0]
- var params = new URLSearchParams()
- params.set("response_type", "id_token")
- params.set("scope", "openid")
- params.set("client_id", this.state.clientID)
- params.set("redirect_uri", redirectURI)
// Store nonce in localstorage to check again after the redirect
const nonce = getRandomString(16)
- params.set("nonce", nonce)
- storage.setItem("openIDKey", nonce)
-
- const authURL = `${
- this.state.discoveryDoc.authorization_endpoint
- }?${params.toString()}`
+ storage.setItem(OPEN_ID_NONCE_KEY, nonce)
+
+ const authURL = buildOpenIDAuthURL(
+ this.state.discoveryDoc.authorization_endpoint,
+ redirectURI,
+ this.state.clientID,
+ nonce
+ )
window.location = authURL
}
}
@@ -99,13 +97,13 @@ export class OpenIDLogin extends React.Component {
if (values.id_token) {
// Check nonce on the token to prevent replay attacks
const tokenJSON = jwtDecode(values.id_token)
- if (storage.getItem("openIDKey") !== tokenJSON.nonce) {
+ if (storage.getItem(OPEN_ID_NONCE_KEY) !== tokenJSON.nonce) {
this.props.showAlert("danger", "Invalid auth token")
return
}
web.LoginSTS({ token: values.id_token }).then(() => {
- storage.removeItem("openIDKey")
+ storage.removeItem(OPEN_ID_NONCE_KEY)
this.forceUpdate()
return
})
diff --git a/browser/app/js/browser/OpenIDLoginButton.js b/browser/app/js/browser/OpenIDLoginButton.js
new file mode 100644
index 000000000..97b0f85a7
--- /dev/null
+++ b/browser/app/js/browser/OpenIDLoginButton.js
@@ -0,0 +1,57 @@
+/*
+ * MinIO Cloud Storage (C) 2019 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from "react"
+import { getRandomString } from "../utils"
+import storage from "local-storage-fallback"
+import { buildOpenIDAuthURL, OPEN_ID_NONCE_KEY } from './utils'
+
+export class OpenIDLoginButton extends React.Component {
+ constructor(props) {
+ super(props)
+ this.handleClick = this.handleClick.bind(this)
+ }
+
+ handleClick(event) {
+ event.stopPropagation()
+ const { authorizationEndpoint, clientId } = this.props
+
+ let redirectURI = window.location.href.split("#")[0]
+ if (redirectURI.endsWith('/')) {
+ redirectURI += 'openid'
+ } else {
+ redirectURI += '/openid'
+ }
+
+ // Store nonce in localstorage to check again after the redirect
+ const nonce = getRandomString(16)
+ storage.setItem(OPEN_ID_NONCE_KEY, nonce)
+
+ const authURL = buildOpenIDAuthURL(authorizationEndpoint, redirectURI, clientId, nonce)
+ window.location = authURL
+ }
+
+ render() {
+ const { children, className } = this.props
+ return (
+
+ {children}
+
+ )
+ }
+}
+
+export default OpenIDLoginButton
diff --git a/browser/app/js/browser/utils.js b/browser/app/js/browser/utils.js
new file mode 100644
index 000000000..cbab2ee20
--- /dev/null
+++ b/browser/app/js/browser/utils.js
@@ -0,0 +1,29 @@
+/*
+ * MinIO Cloud Storage (C) 2019 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const OPEN_ID_NONCE_KEY = 'openIDKey'
+
+export const buildOpenIDAuthURL = (authorizationEndpoint, redirectURI, clientID, nonce) => {
+ const params = new URLSearchParams()
+ params.set("response_type", "id_token")
+ params.set("scope", "openid")
+ params.set("client_id", clientID)
+ params.set("redirect_uri", redirectURI)
+ params.set("nonce", nonce)
+
+ return `${authorizationEndpoint}?${params.toString()}`
+}
+
diff --git a/browser/app/less/inc/login.less b/browser/app/less/inc/login.less
index ccaa487e0..0a5ace5ab 100644
--- a/browser/app/less/inc/login.less
+++ b/browser/app/less/inc/login.less
@@ -101,14 +101,15 @@
.openid-btn {
display: inline-block;
+ color: @link-color;
margin-top: 30px;
border-width: 1px;
border-style: solid;
opacity: 0.6;
font-size: 14px;
&:hover {
- color: @link-color;
opacity: 1;
+ cursor: pointer;
}
}
@@ -118,7 +119,7 @@
align-items: center;
color:grey;
}
-
+
.or:after,
.or:before {
content: "";
@@ -136,4 +137,4 @@ input:-webkit-autofill {
-webkit-box-shadow:0 0 0 50px #002a37 inset !important;
-webkit-text-fill-color: @white !important;
caret-color: white;
-}
\ No newline at end of file
+}
diff --git a/browser/ui-assets.go b/browser/ui-assets.go
index ecdc35fee..0911b7651 100644
--- a/browser/ui-assets.go
+++ b/browser/ui-assets.go
@@ -1,4 +1,4 @@
-// Code generated by go-bindata.
+// Package browser Code generated by go-bindata. (@generated) DO NOT EDIT.
// sources:
// production/chrome.png
// production/favicon-16x16.png
@@ -6,17 +6,14 @@
// production/favicon-96x96.png
// production/firefox.png
// production/index.html
-// production/index_bundle-2019-10-28T16-27-23Z.js
+// production/index_bundle-2019-11-28T10-38-13Z.js
// production/loader.css
// production/logo.svg
// production/safari.png
-// DO NOT EDIT!
-
package browser
import (
"fmt"
- "github.com/elazarl/go-bindata-assetfs"
"io/ioutil"
"os"
"path/filepath"
@@ -36,21 +33,32 @@ type bindataFileInfo struct {
modTime time.Time
}
+// Name return file name
func (fi bindataFileInfo) Name() string {
return fi.name
}
+
+// Size return file size
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
+
+// Mode return file mode
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
+
+// Mode return file modify time
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
+
+// IsDir return file whether a directory
func (fi bindataFileInfo) IsDir() bool {
- return false
+ return fi.mode&os.ModeDir != 0
}
+
+// Sys return file is sys mode
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
@@ -67,7 +75,7 @@ func productionChromePng() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "production/chrome.png", size: 3726, mode: os.FileMode(420), modTime: time.Unix(1572280054, 0)}
+ info := bindataFileInfo{name: "production/chrome.png", size: 3726, mode: os.FileMode(420), modTime: time.Unix(1574937525, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -84,7 +92,7 @@ func productionFavicon16x16Png() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "production/favicon-16x16.png", size: 14906, mode: os.FileMode(420), modTime: time.Unix(1572280054, 0)}
+ info := bindataFileInfo{name: "production/favicon-16x16.png", size: 14906, mode: os.FileMode(420), modTime: time.Unix(1574937525, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -101,7 +109,7 @@ func productionFavicon32x32Png() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "production/favicon-32x32.png", size: 16066, mode: os.FileMode(420), modTime: time.Unix(1572280054, 0)}
+ info := bindataFileInfo{name: "production/favicon-32x32.png", size: 16066, mode: os.FileMode(420), modTime: time.Unix(1574937525, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -118,7 +126,7 @@ func productionFavicon96x96Png() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "production/favicon-96x96.png", size: 17029, mode: os.FileMode(420), modTime: time.Unix(1572280054, 0)}
+ info := bindataFileInfo{name: "production/favicon-96x96.png", size: 17029, mode: os.FileMode(420), modTime: time.Unix(1574937525, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -135,7 +143,7 @@ func productionFirefoxPng() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "production/firefox.png", size: 4795, mode: os.FileMode(420), modTime: time.Unix(1572280054, 0)}
+ info := bindataFileInfo{name: "production/firefox.png", size: 4795, mode: os.FileMode(420), modTime: time.Unix(1574937525, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -195,8 +203,8 @@ var _productionIndexHTML = []byte(`
-
-
+
+