Skip to content
Snippets Groups Projects
Commit 8f1a2874 authored by Cam Saül's avatar Cam Saül Committed by Kyle Doherty
Browse files

Google auth v1 :lock: (#2818)

* Google Auth :lock: [wip] [ci skip]

* "No Metabase account exists for this Google account" screen :yum:

* Single Sign On Settings Page [WIP] :yum:

* Google Auth v1 :lock:

* update login layout

* add setup instructions

* add google logo for google account signups

* clean up sso form layout

* Merge branch 'master' into google-auth [ci skip]

* Fix Google Auth button not showing up after initial load :lock:

* Fix bug when creating session for user with no last_login :lock:

* Organize google auth backend code a bit :lock:

* Lots of tests for Google Auth :lock_with_ink_pen:

* Add Save Button to Google Auth settings page :lock:

* Use onChange instead of onBlurChange :lock:

* Make "save changes" button say "changes saved" after save :lock_with_ink_pen:

* adjust user settings for managed accounts

* Ensure we clear GA creds if GA login fails

* tweak password reset email

* owned google sign in button

* use var

* Cleanup some random legacy Angular junk

* Keep Redux and AppState currentUser in sync, fixes login issue

* render() should have side-effects! Fixes $digest errors on logout

* Test fixes :wrench:

* fix checkbox

* cleanup

* remove unused import
parent d400635c
Branches
Tags
No related merge requests found
Showing
with 371 additions and 15 deletions
......@@ -8,6 +8,7 @@ import ForgotPasswordApp from "metabase/auth/containers/ForgotPasswordApp.jsx";
import LoginApp from "metabase/auth/containers/LoginApp.jsx";
import LogoutApp from "metabase/auth/containers/LogoutApp.jsx";
import PasswordResetApp from "metabase/auth/containers/PasswordResetApp.jsx";
import GoogleNoAccount from "metabase/auth/components/GoogleNoAccount.jsx";
// main app containers
import DashboardApp from "metabase/dashboard/containers/DashboardApp.jsx";
......@@ -72,9 +73,10 @@ export default class Routes extends Component {
</Route>
<Route path="/auth/forgot_password" component={ForgotPasswordApp} />
<Route path="/auth/login" component={this._forwardProps(LoginApp, ["onChangeLocation"])} />
<Route path="/auth/login" component={this._forwardProps(LoginApp, ["onChangeLocation", "setSessionFn"])} />
<Route path="/auth/logout" component={this._forwardProps(LogoutApp, ["onChangeLocation"])} />
<Route path="/auth/reset_password/:token" component={this._forwardProps(PasswordResetApp, ["onChangeLocation"])} />
<Route path="/auth/google_no_mb_account" component={GoogleNoAccount} />
<Route path="/card/:cardId" component={this._forwardProps(QueryBuilder, ["onChangeLocation", "broadcastEventFn", "updateUrl"])} />
......
......@@ -67,7 +67,7 @@ export default class DatabaseEditApp extends Component {
{ /* Sidebar Actions */ }
{ database && database.id != null &&
<div className="Grid-cell Cell--1of3" ng-if="database.id">
<div className="Grid-cell Cell--1of3">
<div className="Actions bordered rounded shadowed">
<h3>Actions</h3>
<div className="Actions-group">
......
......@@ -8,6 +8,8 @@ import Modal from "metabase/components/Modal.jsx";
import ModalContent from "metabase/components/ModalContent.jsx";
import PasswordReveal from "metabase/components/PasswordReveal.jsx";
import UserAvatar from "metabase/components/UserAvatar.jsx";
import Icon from "metabase/components/Icon.jsx";
import Tooltip from "metabase/components/Tooltip.jsx";
import EditUserForm from "./EditUserForm.jsx";
import UserActionsSelect from "./UserActionsSelect.jsx";
......@@ -364,6 +366,7 @@ export default class AdminPeople extends Component {
<thead>
<tr>
<th>Name</th>
<th></th>
<th>Email</th>
<th>Role</th>
<th>Last Seen</th>
......@@ -374,6 +377,12 @@ export default class AdminPeople extends Component {
{ users.map(user =>
<tr>
<td><span className="text-white inline-block"><UserAvatar background={(user.is_superuser) ? "bg-purple" : "bg-brand"} user={user} /></span> <span className="ml2 text-bold">{user.common_name}</span></td>
<td>
{user.google_auth ?
<Tooltip tooltip="Signed up via Google">
<Icon name='google' />
</Tooltip> : null}
</td>
<td>{user.email}</td>
<td>
<UserRoleSelect
......
import React, { Component, PropTypes } from "react";
import cx from "classnames";
import _ from "underscore";
import Input from "metabase/components/Input.jsx";
export default class SettingsSingleSignOnForm extends Component {
constructor(props, context) {
super(props, context);
this.updateClientID = this.updateClientID.bind(this);
this.updateDomain = this.updateDomain.bind(this);
this.onCheckboxClicked = this.onCheckboxClicked.bind(this),
this.saveChanges = this.saveChanges.bind(this),
this.clientIDChanged = this.clientIDChanged.bind(this),
this.domainChanged = this.domainChanged.bind(this)
}
static propTypes = {
elements: PropTypes.array,
updateSetting: PropTypes.func.isRequired
};
componentWillMount() {
let { elements } = this.props,
clientID = _.findWhere(elements, {key: 'google-auth-client-id'}),
domain = _.findWhere(elements, {key: 'google-auth-auto-create-accounts-domain'});
this.setState({
clientID: clientID,
domain: domain,
clientIDValue: clientID.value,
domainValue: domain.value,
recentlySaved: false
});
}
updateClientID(newValue) {
if (newValue === this.state.clientIDValue) return;
this.setState({
clientIDValue: newValue && newValue.length ? newValue : null,
recentlySaved: false
});
}
updateDomain(newValue) {
if (newValue === this.state.domain.value) return;
this.setState({
domainValue: newValue && newValue.length ? newValue : null,
recentlySaved: false
});
}
clientIDChanged() {
return this.state.clientID.value !== this.state.clientIDValue;
}
domainChanged() {
return this.state.domain.value !== this.state.domainValue;
}
saveChanges() {
let { clientID, clientIDValue, domain, domainValue } = this.state;
if (this.clientIDChanged()) {
this.props.updateSetting(clientID, clientIDValue);
this.setState({
clientID: {
value: clientIDValue
},
recentlySaved: true
});
}
if (this.domainChanged()) {
this.props.updateSetting(domain, domainValue);
this.setState({
domain: {
value: domainValue
},
recentlySaved: true
});
}
}
onCheckboxClicked() {
// if domain is present, clear it out; otherwise if there's no domain try to set it back to what it was
this.setState({
domainValue: this.state.domainValue ? null : this.state.domain.value,
recentlySaved: false
});
}
render() {
let hasChanges = this.domainChanged() || this.clientIDChanged(),
hasClientID = this.state.clientIDValue;
return (
<form noValidate>
<div className="px2"
style={{maxWidth: "585px"}}>
<h2>Sign in with Google</h2>
<p className="text-grey-4">
Allows users with existing Metabase accounts to login with a Google account that matches their email address in addition to their Metabase username and password.
</p>
<p className="text-grey-4">
To allow users to sign in with Google you'll need to give Metabase a Google Developers console application client ID. It only takes a few steps and instructions on how to create a key can be found <a className="link" href="https://developers.google.com/identity/sign-in/web/devconsole-project" target="_blank">here.</a>
</p>
<Input
className="SettingsInput AdminInput bordered rounded h3"
type="text"
value={this.state.clientIDValue}
placeholder="Your Google client ID"
onChange={(event) => this.updateClientID(event.target.value)}
/>
<div className="py3">
<div className="flex align-center">
<p className="text-grey-4">Allow users to sign up on their own if their Google account email address is from:</p>
</div>
<div className="mt1 bordered rounded inline-block">
<div className="inline-block px2 h2">@</div>
<Input
className="SettingsInput inline-block AdminInput h3 border-left"
type="text"
value={this.state.domainValue}
onChange={(event) => this.updateDomain(event.target.value)}
disabled={!hasClientID}
/>
</div>
</div>
<button className={cx("Button mr2", {"Button--primary": hasChanges})}
disabled={!hasChanges}
onClick={this.saveChanges}>
{this.state.recentlySaved ? "Changes saved!" : "Save Changes"}
</button>
</div>
</form>
);
}
}
......@@ -8,6 +8,7 @@ import SettingsEmailForm from "../components/SettingsEmailForm.jsx";
import SettingsSlackForm from "../components/SettingsSlackForm.jsx";
import SettingsSetupList from "../components/SettingsSetupList.jsx";
import SettingsUpdatesForm from "../components/SettingsUpdatesForm.jsx";
import SettingsSingleSignOnForm from "../components/SettingsSingleSignOnForm.jsx";
import _ from "underscore";
import cx from 'classnames';
......@@ -119,6 +120,15 @@ export default class SettingsEditorApp extends Component {
/>
</div>
);
} else if (section.name === "Single Sign On") {
return (
<div className="px2">
<SettingsSingleSignOnForm
elements={section.settings}
updateSetting={this.updateSetting}
/>
</div>
);
} else {
let settings = section.settings.map((setting, index) => {
return <SettingsSetting key={setting.key} setting={setting} updateSetting={this.updateSetting} handleChangeEvent={this.handleChangeEvent} autoFocus={index === 0}/>
......
......@@ -126,6 +126,17 @@ const SECTIONS = [
type: "boolean"
}
]
},
{
name: "Single Sign On",
settings: [
{
key: "google-auth-client-id"
},
{
key: "google-auth-auto-create-accounts-domain"
}
]
}
];
......
......@@ -54,7 +54,7 @@ import * as setup from "metabase/setup/reducers";
/* user */
import * as user from "metabase/user/reducers";
import { currentUser } from "metabase/user";
import { currentUser, setUser } from "metabase/user";
import { registerAnalyticsClickListener } from "metabase/lib/analytics";
import { serializeCardForUrl, cleanCopyCard, urlForCardState } from "metabase/lib/card";
......@@ -239,6 +239,14 @@ angular.module('metabase', [
};
$scope.store = createStoreWithAngularScope($scope, $location, reducers, {currentUser: AppState.model.currentUser});
// ANGULAR_HACK™: this seems like the easiest way to keep the redux store up to date with the currentUser :-/
let userSyncTimer = setInterval(() => {
if ($scope.store.getState().currentUser !== AppState.model.currentUser) {
$scope.store.dispatch(setUser(AppState.model.currentUser));
}
}, 250);
$scope.$on("$destroy", () => clearInterval(userSyncTimer));
// HACK: prevent reloading controllers as the URL changes
let route = $route.current;
$scope.$on('$locationChangeSuccess', function (event) {
......
......@@ -4,9 +4,11 @@ import { handleActions, combineReducers, AngularResourceProxy, createThunkAction
import MetabaseCookies from "metabase/lib/cookies";
import MetabaseUtils from "metabase/lib/utils";
import { clearGoogleAuthCredentials } from "metabase/lib/auth";
// resource wrappers
const SessionApi = new AngularResourceProxy("Session", ["create", "delete", "reset_password"]);
const SessionApi = new AngularResourceProxy("Session", ["create", "createWithGoogleAuth", "delete", "reset_password"]);
// login
......@@ -33,6 +35,34 @@ export const login = createThunkAction("AUTH_LOGIN", function(credentials, onCha
};
});
// login Google
export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googleUser, onChangeLocation) {
return async function(dispatch, getState) {
try {
let newSession = await SessionApi.createWithGoogleAuth({
token: googleUser.getAuthResponse().id_token
});
// since we succeeded, lets set the session cookie
MetabaseCookies.setSessionCookie(newSession.id);
// TODO: redirect after login (carry user to intended destination)
// this is ridiculously stupid. we have to wait (300ms) for the cookie to actually be set in the browser :(
setTimeout(() => onChangeLocation("/"), 300);
} catch (error) {
clearGoogleAuthCredentials();
// If we see a 428 ("Precondition Required") that means we need to show the "No Metabase account exists for this Google Account" page
if (error.status === 428) {
onChangeLocation('/auth/google_no_mb_account');
} else {
return error;
}
}
};
});
// logout
export const logout = createThunkAction("AUTH_LOGOUT", function(onChangeLocation) {
return async function(dispatch, getState) {
......@@ -84,7 +114,8 @@ export const passwordReset = createThunkAction("AUTH_PASSWORD_RESET", function(t
// reducers
const loginError = handleActions({
["AUTH_LOGIN"]: { next: (state, { payload }) => payload ? payload : null }
["AUTH_LOGIN"]: { next: (state, { payload }) => payload ? payload : null },
["AUTH_LOGIN_GOOGLE"]: { next: (state, { payload }) => payload ? payload : null }
}, null);
const resetSuccess = handleActions({
......
......@@ -10,7 +10,7 @@ export default class AuthScene extends Component {
render() {
return (
<section className="brand-scene absolute bottom left right hide sm-show">
<section className="z1 brand-scene absolute bottom left right hide sm-show">
<div className="brand-boat-container">
<svg className="brand-boat" width="27px" height="28px" viewBox="0 0 27 28">
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd" ref="HACK_fill_rule_1">
......
import React from 'react'
const BackToLogin = () =>
<a className="link block" href="/auth/login">Back to login</a>
export default BackToLogin;
import React from "react";
import AuthScene from "./AuthScene.jsx";
import LogoIcon from "metabase/components/LogoIcon.jsx";
import BackToLogin from "./BackToLogin.jsx"
const GoogleNoAccount = () =>
<div className="full-height bg-white flex flex-column flex-full md-layout-centered">
<div className="wrapper">
<div className="Login-wrapper Grid Grid--full md-Grid--1of2">
<div className="Grid-cell flex layout-centered text-brand">
<LogoIcon className="Logo my4 sm-my0" width={66} height={85} />
</div>
<div className="Grid-cell text-centered bordered rounded shadowed p4">
<h3 className="mt4 mb2">No Metabase account exists for this Google account.</h3>
<p className="mb4 ml-auto mr-auto" style={{maxWidth: 360}}>
You'll need an administrator to create a Metabase account before
you can use Google to log in.
</p>
<BackToLogin />
</div>
</div>
</div>
<AuthScene />
</div>
export default GoogleNoAccount;
import React, { Component, PropTypes } from "react";
import Icon from "metabase/components/Icon";
import { capitalize } from 'humanize'
const propTypes = {
provider: PropTypes.string.isRequired
};
class SSOLoginButton extends Component {
render () {
const { provider } = this.props
return (
<div className="relative z2 bg-white p2 cursor-pointer shadow-hover text-centered sm-text-left rounded block sm-inline-block bordered shadowed">
<div className="flex align-center">
<Icon className="mr1" name={provider} />
<h4>{`Sign in with ${capitalize(provider)}`}</h4>
</div>
</div>
)
}
}
SSOLoginButton.proptypes = propTypes
export default SSOLoginButton;
......@@ -5,6 +5,7 @@ import _ from "underscore";
import cx from "classnames";
import AuthScene from "../components/AuthScene.jsx";
import BackToLogin from "../components/BackToLogin.jsx"
import FormField from "metabase/components/form/FormField.jsx";
import FormLabel from "metabase/components/form/FormLabel.jsx";
import FormMessage from "metabase/components/form/FormMessage.jsx";
......@@ -55,7 +56,7 @@ export default class ForgotPasswordApp extends Component {
<div className="Grid-cell">
<div className="text-centered bordered rounded shadowed p4">
<h3 className="my4">Please contact an administrator to have them reset your password</h3>
<a className="link block mb4" href="/auth/login">Back to login</a>
<BackToLogin />
</div>
</div>
:
......
import React, { Component, PropTypes } from "react";
import { findDOMNode } from "react-dom";
import { connect } from "react-redux";
import cx from "classnames";
import AuthScene from "../components/AuthScene.jsx";
import SSOLoginButton from "../components/SSOLoginButton.jsx";
import FormField from "metabase/components/form/FormField.jsx";
import FormLabel from "metabase/components/form/FormLabel.jsx";
import FormMessage from "metabase/components/form/FormMessage.jsx";
import LogoIcon from "metabase/components/LogoIcon.jsx";
import Settings from "metabase/lib/settings.js";
import * as authActions from "../auth";
......@@ -51,13 +54,49 @@ export default class LoginApp extends Component {
}
componentDidMount() {
this.validateForm();
const { loginGoogle, onChangeLocation } = this.props;
let ssoLoginButton = findDOMNode(this.refs.ssoLoginButton);
function attachGoogleAuth() {
// if gapi isn't loaded yet then wait 100ms and check again. Keep doing this until we're ready
if (!window.gapi) {
window.setTimeout(attachGoogleAuth, 100);
return;
}
try {
window.gapi.load('auth2', () => {
let auth2 = window.gapi.auth2.init({
client_id: Settings.get('google_auth_client_id'),
cookiepolicy: 'single_host_origin',
});
auth2.attachClickHandler(ssoLoginButton, {},
(googleUser) => loginGoogle(googleUser, onChangeLocation),
(error) => console.error('There was an error logging in', error)
);
})
} catch (error) {
console.error('Error attaching Google Auth handler: ', error);
}
}
attachGoogleAuth();
}
componentDidUpdate() {
this.validateForm();
}
componentWillReceiveProps(newProps) {
const { user, onChangeLocation } = newProps;
// if we already have a user then we shouldn't be logging in
if (user) {
onChangeLocation("/");
}
}
onChange(fieldName, fieldValue) {
this.setState({ credentials: { ...this.state.credentials, [fieldName]: fieldValue }});
}
......@@ -72,22 +111,28 @@ export default class LoginApp extends Component {
}
render() {
if (this.props.user) {
// if we already have a user then we shouldn't be logging in
this.props.onChangeLocation("/");
}
const { loginError } = this.props;
return (
<div className="full-height full bg-white flex flex-column flex-full md-layout-centered">
<div className="Login-wrapper wrapper Grid Grid--full md-Grid--1of2">
<div className="Login-wrapper wrapper Grid Grid--full md-Grid--1of2 relative z2">
<div className="Grid-cell flex layout-centered text-brand">
<LogoIcon className="Logo my4 sm-my0" width={66} height={85} />
</div>
<div className="Login-content Grid-cell">
<form className="Form-new bg-white bordered rounded shadowed" name="form" onSubmit={(e) => this.formSubmitted(e)} noValidate>
<h3 className="Login-header Form-offset mb3">Sign in to Metabase</h3>
<h3 className="Login-header Form-offset">Sign in to Metabase</h3>
{ Settings.ssoEnabled() &&
<div className="mx4 mb4 py3 border-bottom relative">
<SSOLoginButton provider='google' ref="ssoLoginButton"/>
{/*<div className="g-signin2 ml1 relative z2" id="g-signin2"></div>*/}
<div className="mx1 absolute text-centered left right" style={{ bottom: -8 }}>
<span className="text-bold px3 py2 text-grey-3 bg-white">OR</span>
</div>
</div>
}
<FormMessage formError={loginError && loginError.data.message ? loginError : null} ></FormMessage>
......@@ -105,7 +150,7 @@ export default class LoginApp extends Component {
<div className="Form-field">
<ul className="Form-offset">
<input name="remember" type="checkbox" ng-init="remember_me = true" checked /> <label className="inline-block">Remember Me:</label>
<input name="remember" type="checkbox" defaultChecked /> <label className="inline-block">Remember Me:</label>
</ul>
</div>
......
......@@ -40,6 +40,10 @@
.inline-block,
:local(.inline-block) { display: inline-block; }
@media screen and (--breakpoint-min-sm) {
.sm-inline-block { display: inline-block; }
}
.table { display: table; }
.full, :local(.full) { width: 100%; }
......
:root {
--shadow-color: rgba(0, 0, 0, .08);
--shadow-hover-color: rgba(0, 0, 0, .12);
}
.shadowed, :local(.shadowed) {
box-shadow: 0 2px 2px var(--shadow-color);
}
.shadow-hover:hover {
box-shadow: 0 2px 2px var(--shadow-hover-color);
transition: box-shadow 300ms linear;
}
......@@ -71,6 +71,9 @@ export var ICON_PATHS = {
folder: "M3.96901618e-15,5.41206355 L0.00949677904,29 L31.8821132,29 L31.8821132,10.8928571 L18.2224205,10.8928571 L15.0267944,5.41206355 L3.96901618e-15,5.41206355 Z M16.8832349,5.42402804 L16.8832349,4.52140947 C16.8832349,3.68115822 17.5639241,3 18.4024298,3 L27.7543992,3 L30.36417,3 C31.2031259,3 31.8832341,3.67669375 31.8832341,4.51317691 L31.8832341,7.86669975 L31.8832349,8.5999999 L18.793039,8.5999999 L16.8832349,5.42402804 Z",
gear: 'M14 0 H18 L19 6 L20.707 6.707 L26 3.293 L28.707 6 L25.293 11.293 L26 13 L32 14 V18 L26 19 L25.293 20.707 L28.707 26 L26 28.707 L20.707 25.293 L19 26 L18 32 L14 32 L13 26 L11.293 25.293 L6 28.707 L3.293 26 L6.707 20.707 L6 19 L0 18 L0 14 L6 13 L6.707 11.293 L3.293 6 L6 3.293 L11.293 6.707 L13 6 L14 0 z M16 10 A6 6 0 0 0 16 22 A6 6 0 0 0 16 10',
grid: 'M2 2 L10 2 L10 10 L2 10z M12 2 L20 2 L20 10 L12 10z M22 2 L30 2 L30 10 L22 10z M2 12 L10 12 L10 20 L2 20z M12 12 L20 12 L20 20 L12 20z M22 12 L30 12 L30 20 L22 20z M2 22 L10 22 L10 30 L2 30z M12 22 L20 22 L20 30 L12 30z M22 22 L30 22 L30 30 L22 30z',
google: {
svg: '<g fill="none" fill-rule="evenodd"><path d="M16 32c4.32 0 7.947-1.422 10.596-3.876l-5.05-3.91c-1.35.942-3.164 1.6-5.546 1.6-4.231 0-7.822-2.792-9.102-6.65l-5.174 4.018C4.356 28.41 9.742 32 16 32z" fill="#34A853"/><path d="M6.898 19.164A9.85 9.85 0 0 1 6.364 16c0-1.102.196-2.169.516-3.164L1.707 8.818A16.014 16.014 0 0 0 0 16c0 2.578.622 5.013 1.707 7.182l5.19-4.018z" fill="#FBBC05"/><path d="M31.36 16.356c0-1.316-.107-2.276-.338-3.272H16v5.938h8.818c-.178 1.476-1.138 3.698-3.271 5.191l5.049 3.911c3.022-2.79 4.764-6.897 4.764-11.768z" fill="#4285F4"/><path d="M16 6.187c3.004 0 5.031 1.297 6.187 2.382l4.515-4.409C23.93 1.582 20.32 0 16 0 9.742 0 4.338 3.591 1.707 8.818l5.173 4.018c1.298-3.858 4.889-6.65 9.12-6.65z" fill="#EA4335"/></g>'
},
history: {
path: "M35,16.5 C35,25.0368108 28.12452,32 19.69524,32 C15.4568,32 11.59542,30.2352712 8.8642,27.421691 L11.2187,25.0368108 C13.38484,27.1827209 16.3986,28.5659239 19.69524,28.5659239 C26.28818,28.5659239 31.60918,23.1770449 31.60918,16.5 C31.60918,9.82295508 26.28818,4.43373173 19.69524,4.43373173 C13.90266,4.43373173 9.05256,8.48761496 7.96932,14.0197383 L11.78378,13.4474497 L6.36826,21.1258275 L1,13.4474497 L4.57884,13.9718754 C5.709,6.57998623 12.0194,1 19.69524,1 C28.12452,1 35,7.91532634 35,16.5 Z M21.2212723,10.8189082 L21.1121095,17.4229437 L23.8197636,19.9536905 C25.5447498,21.5044111 23.0137118,23.8063921 21.4895987,22.3922785 L18.2842405,19.4460113 C17.9625843,19.1048495 17.7475101,18.7575877 17.7628492,18.3548451 L17.789933,10.8461204 C17.7725216,8.69014788 21.1413368,8.68773723 21.2212723,10.8189082 Z",
attrs: { viewBox: "0 0 36 33" }
......
/*global gapi*/
/// clear out Google Auth credentials in browser if present
export function clearGoogleAuthCredentials() {
let googleAuth = typeof gapi !== 'undefined' && gapi && gapi.auth2 ? gapi.auth2.getAuthInstance() : undefined;
if (!googleAuth) return;
try {
googleAuth.signOut().then(function() {
console.log('Cleared Google Auth credentials.');
});
} catch (error) {
console.error('Problem clearing Google Auth credentials', error);
}
}
import { clearGoogleAuthCredentials } from "metabase/lib/auth";
export const METABASE_SESSION_COOKIE = 'metabase.SESSION_ID';
var mb_cookies = {};
......@@ -29,8 +32,9 @@ var MetabaseCookies = {
} else {
sessionId = mb_cookies.ipCookie(METABASE_SESSION_COOKIE);
// delete the current session cookie
// delete the current session cookie and Google Auth creds
mb_cookies.ipCookie.remove(METABASE_SESSION_COOKIE);
clearGoogleAuthCredentials();
// send a logout notification event
mb_cookies.scope.$broadcast('appstate:logout', sessionId);
......
......@@ -35,6 +35,10 @@ const MetabaseSettings = {
return (mb_settings.setup_token !== undefined && mb_settings.setup_token !== null);
},
ssoEnabled: function() {
return mb_settings.google_auth_client_id != null;
},
newVersionAvailable: function(settings) {
let versionInfo = _.findWhere(settings, {key: "version-info"}),
currentVersion = MetabaseSettings.get("version").tag;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment