Merge branch 'standalone-improvements' into 'develop'
Standalone: add external accounts, refresh on logout See merge request soapbox-pub/soapbox-fe!700
This commit is contained in:
commit
402a45b495
|
@ -18,6 +18,7 @@ import { createApp } from 'soapbox/actions/apps';
|
||||||
import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth';
|
import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth';
|
||||||
import sourceCode from 'soapbox/utils/code';
|
import sourceCode from 'soapbox/utils/code';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
import { isStandalone } from 'soapbox/utils/state';
|
||||||
|
|
||||||
export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT';
|
export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT';
|
||||||
|
|
||||||
|
@ -177,6 +178,7 @@ export function logOut(intl) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const account = getLoggedInAccount(state);
|
const account = getLoggedInAccount(state);
|
||||||
|
const standalone = isStandalone(state);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
client_id: state.getIn(['auth', 'app', 'client_id']),
|
client_id: state.getIn(['auth', 'app', 'client_id']),
|
||||||
|
@ -185,7 +187,7 @@ export function logOut(intl) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return dispatch(revokeOAuthToken(params)).finally(() => {
|
return dispatch(revokeOAuthToken(params)).finally(() => {
|
||||||
dispatch({ type: AUTH_LOGGED_OUT, account });
|
dispatch({ type: AUTH_LOGGED_OUT, account, standalone });
|
||||||
dispatch(snackbar.success(intl.formatMessage(messages.loggedOut)));
|
dispatch(snackbar.success(intl.formatMessage(messages.loggedOut)));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import { baseClient } from '../api';
|
import { baseClient } from '../api';
|
||||||
import { createApp } from 'soapbox/actions/apps';
|
import { createApp } from 'soapbox/actions/apps';
|
||||||
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
||||||
import { authLoggedIn, verifyCredentials } from 'soapbox/actions/auth';
|
import { authLoggedIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
||||||
import { parseBaseURL } from 'soapbox/utils/auth';
|
import { parseBaseURL } from 'soapbox/utils/auth';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
import sourceCode from 'soapbox/utils/code';
|
import sourceCode from 'soapbox/utils/code';
|
||||||
|
@ -73,6 +73,7 @@ export function loginWithCode(code) {
|
||||||
return dispatch(obtainOAuthToken(params, baseURL))
|
return dispatch(obtainOAuthToken(params, baseURL))
|
||||||
.then(token => dispatch(authLoggedIn(token)))
|
.then(token => dispatch(authLoggedIn(token)))
|
||||||
.then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL)))
|
.then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL)))
|
||||||
|
.then(account => dispatch(switchAccount(account.id)))
|
||||||
.then(() => window.location.href = '/');
|
.then(() => window.location.href = '/');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,129 +1,5 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<LoginPage /> renders correctly on load 1`] = `
|
exports[`<LoginPage /> renders correctly on load 1`] = `null`;
|
||||||
<form
|
|
||||||
className="simple_form new_user"
|
|
||||||
method="post"
|
|
||||||
onSubmit={[Function]}
|
|
||||||
>
|
|
||||||
<fieldset
|
|
||||||
disabled={false}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="fields-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="input email user_email"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label="Username"
|
|
||||||
autoComplete="off"
|
|
||||||
className="string email"
|
|
||||||
name="username"
|
|
||||||
placeholder="Username"
|
|
||||||
required={true}
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="input password user_password"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label="Password"
|
|
||||||
autoComplete="off"
|
|
||||||
className="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="Password"
|
|
||||||
required={true}
|
|
||||||
type="password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className="hint subtle-hint"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/auth/reset_password"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
Trouble logging in?
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div
|
|
||||||
className="actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn button button-primary"
|
|
||||||
name="button"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Log in
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<LoginPage /> renders correctly on load 2`] = `
|
exports[`<LoginPage /> renders correctly on load 2`] = `null`;
|
||||||
<form
|
|
||||||
className="simple_form new_user"
|
|
||||||
method="post"
|
|
||||||
onSubmit={[Function]}
|
|
||||||
>
|
|
||||||
<fieldset
|
|
||||||
disabled={false}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="fields-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="input email user_email"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label="Username"
|
|
||||||
autoComplete="off"
|
|
||||||
className="string email"
|
|
||||||
name="username"
|
|
||||||
placeholder="Username"
|
|
||||||
required={true}
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="input password user_password"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label="Password"
|
|
||||||
autoComplete="off"
|
|
||||||
className="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="Password"
|
|
||||||
required={true}
|
|
||||||
type="password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className="hint subtle-hint"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/auth/reset_password"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
Trouble logging in?
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div
|
|
||||||
className="actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn button button-primary"
|
|
||||||
name="button"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Log in
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
|
|
|
@ -6,10 +6,12 @@ import { injectIntl } from 'react-intl';
|
||||||
import LoginForm from './login_form';
|
import LoginForm from './login_form';
|
||||||
import OtpAuthForm from './otp_auth_form';
|
import OtpAuthForm from './otp_auth_form';
|
||||||
import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
||||||
|
import { isStandalone } from 'soapbox/utils/state';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
me: state.get('me'),
|
me: state.get('me'),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
standalone: isStandalone(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
|
@ -55,8 +57,11 @@ class LoginPage extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { standalone } = this.props;
|
||||||
const { isLoading, mfa_auth_needed, mfa_token, shouldRedirect } = this.state;
|
const { isLoading, mfa_auth_needed, mfa_token, shouldRedirect } = this.state;
|
||||||
|
|
||||||
|
if (standalone) return <Redirect to='/auth/external' />;
|
||||||
|
|
||||||
if (shouldRedirect) return <Redirect to='/' />;
|
if (shouldRedirect) return <Redirect to='/' />;
|
||||||
|
|
||||||
if (mfa_auth_needed) return <OtpAuthForm mfa_token={mfa_token} />;
|
if (mfa_auth_needed) return <OtpAuthForm mfa_token={mfa_token} />;
|
||||||
|
|
|
@ -9,19 +9,7 @@ import Footer from './components/footer';
|
||||||
import LandingPage from '../landing_page';
|
import LandingPage from '../landing_page';
|
||||||
import AboutPage from '../about';
|
import AboutPage from '../about';
|
||||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
import { isPrerendered } from 'soapbox/precheck';
|
import { isStandalone } from 'soapbox/utils/state';
|
||||||
|
|
||||||
const validInstance = state => {
|
|
||||||
const v = state.getIn(['instance', 'version']);
|
|
||||||
return v && typeof v === 'string' && v !== '0.0.0';
|
|
||||||
};
|
|
||||||
|
|
||||||
const isStandalone = state => {
|
|
||||||
const hasInstance = validInstance(state);
|
|
||||||
const instanceFetchFailed = state.getIn(['meta', 'instance_fetch_failed']);
|
|
||||||
|
|
||||||
return !isPrerendered && !hasInstance && instanceFetchFailed;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
soapbox: getSoapboxConfig(state),
|
soapbox: getSoapboxConfig(state),
|
||||||
|
|
|
@ -261,7 +261,10 @@ const userSwitched = (oldState, state) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const maybeReload = (oldState, state, action) => {
|
const maybeReload = (oldState, state, action) => {
|
||||||
if (userSwitched(oldState, state)) {
|
const loggedOutStandalone = action.type === AUTH_LOGGED_OUT && action.standalone;
|
||||||
|
const switched = userSwitched(oldState, state);
|
||||||
|
|
||||||
|
if (switched || loggedOutStandalone) {
|
||||||
reload(state);
|
reload(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
|
/**
|
||||||
|
* State: general Redux state utility functions.
|
||||||
|
* @module soapbox/utils/state
|
||||||
|
*/
|
||||||
|
|
||||||
import { getSoapboxConfig } from'soapbox/actions/soapbox';
|
import { getSoapboxConfig } from'soapbox/actions/soapbox';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { isPrerendered } from 'soapbox/precheck';
|
||||||
|
import { isURL } from 'soapbox/utils/auth';
|
||||||
|
import { BACKEND_URL } from 'soapbox/build_config';
|
||||||
|
|
||||||
export const displayFqn = state => {
|
export const displayFqn = state => {
|
||||||
const soapbox = getSoapboxConfig(state);
|
const soapbox = getSoapboxConfig(state);
|
||||||
|
@ -8,3 +17,15 @@ export const displayFqn = state => {
|
||||||
export const federationRestrictionsDisclosed = state => {
|
export const federationRestrictionsDisclosed = state => {
|
||||||
return state.hasIn(['instance', 'pleroma', 'metadata', 'federation', 'mrf_policies']);
|
return state.hasIn(['instance', 'pleroma', 'metadata', 'federation', 'mrf_policies']);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether Soapbox FE is running in standalone mode.
|
||||||
|
* Standalone mode runs separately from any backend and can login anywhere.
|
||||||
|
* @param {object} state
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isStandalone = state => createSelector([
|
||||||
|
state => state.getIn(['meta', 'instance_fetch_failed'], false),
|
||||||
|
], instanceFetchFailed => {
|
||||||
|
return isURL(BACKEND_URL) ? false : (!isPrerendered && instanceFetchFailed);
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue