sforkowany z mirror/soapbox
Support Ethereum external login
rodzic
012a7f8d89
commit
f051b70b10
|
@ -18,6 +18,7 @@ import KVStore from 'soapbox/storage/kv_store';
|
||||||
import { getLoggedInAccount, parseBaseURL } from 'soapbox/utils/auth';
|
import { getLoggedInAccount, parseBaseURL } from 'soapbox/utils/auth';
|
||||||
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 { getQuirks } from 'soapbox/utils/quirks';
|
||||||
import { isStandalone } from 'soapbox/utils/state';
|
import { isStandalone } from 'soapbox/utils/state';
|
||||||
|
|
||||||
import api, { baseClient } from '../api';
|
import api, { baseClient } from '../api';
|
||||||
|
@ -62,6 +63,10 @@ function createAppAndToken() {
|
||||||
|
|
||||||
function createAuthApp() {
|
function createAuthApp() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
// Mitra: skip creating the app
|
||||||
|
const quirks = getQuirks(getState().get('instance'));
|
||||||
|
if (quirks.skipsAppCreation) return dispatch(noOp());
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
client_name: sourceCode.displayName,
|
client_name: sourceCode.displayName,
|
||||||
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
|
@ -213,10 +218,13 @@ export function logIn(intl, username, password) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ethereumLogin() {
|
export function ethereumLogin(instance, baseURL) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
instance = (instance || getState().get('instance'));
|
||||||
|
|
||||||
const { ethereum } = window;
|
const { ethereum } = window;
|
||||||
const loginMessage = getState().getIn(['instance', 'login_message']);
|
const { scopes } = getFeatures(instance);
|
||||||
|
const loginMessage = instance.get('login_message');
|
||||||
|
|
||||||
return ethereum.request({ method: 'eth_requestAccounts' }).then(walletAddresses => {
|
return ethereum.request({ method: 'eth_requestAccounts' }).then(walletAddresses => {
|
||||||
const [walletAddress] = walletAddresses;
|
const [walletAddress] = walletAddresses;
|
||||||
|
@ -227,15 +235,14 @@ export function ethereumLogin() {
|
||||||
wallet_address: walletAddress.toLowerCase(),
|
wallet_address: walletAddress.toLowerCase(),
|
||||||
password: signature,
|
password: signature,
|
||||||
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
scope: getScopes(getState()),
|
scope: scopes,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: skips app creation
|
// Note: skips app creation
|
||||||
// TODO: add to quirks.js for Mitra
|
// TODO: add to quirks.js for Mitra
|
||||||
return dispatch(obtainOAuthToken(params)).then(token => {
|
return dispatch(obtainOAuthToken(params, baseURL))
|
||||||
dispatch(authLoggedIn(token));
|
.then(token => dispatch(authLoggedIn(token)))
|
||||||
return dispatch(verifyCredentials(token.access_token));
|
.then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL)));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,11 +9,12 @@
|
||||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||||
|
|
||||||
import { createApp } from 'soapbox/actions/apps';
|
import { createApp } from 'soapbox/actions/apps';
|
||||||
import { authLoggedIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
import { authLoggedIn, verifyCredentials, switchAccount, ethereumLogin } from 'soapbox/actions/auth';
|
||||||
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
||||||
import { parseBaseURL } from 'soapbox/utils/auth';
|
import { parseBaseURL } from 'soapbox/utils/auth';
|
||||||
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 { getQuirks } from 'soapbox/utils/quirks';
|
||||||
|
|
||||||
import { baseClient } from '../api';
|
import { baseClient } from '../api';
|
||||||
|
|
||||||
|
@ -32,11 +33,8 @@ const fetchExternalInstance = baseURL => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createAppAndRedirect(host) {
|
function createExternalApp(instance, baseURL) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const baseURL = parseBaseURL(host) || parseBaseURL(`https://${host}`);
|
|
||||||
|
|
||||||
return fetchExternalInstance(baseURL).then(instance => {
|
|
||||||
const { scopes } = getFeatures(instance);
|
const { scopes } = getFeatures(instance);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
|
@ -46,7 +44,15 @@ export function createAppAndRedirect(host) {
|
||||||
scopes,
|
scopes,
|
||||||
};
|
};
|
||||||
|
|
||||||
return dispatch(createApp(params, baseURL)).then(app => {
|
return dispatch(createApp(params, baseURL));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function externalAuthorize(instance, baseURL) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { scopes } = getFeatures(instance);
|
||||||
|
|
||||||
|
return dispatch(createExternalApp(instance, baseURL)).then(app => {
|
||||||
const { client_id, redirect_uri } = app;
|
const { client_id, redirect_uri } = app;
|
||||||
|
|
||||||
const query = new URLSearchParams({
|
const query = new URLSearchParams({
|
||||||
|
@ -62,6 +68,30 @@ export function createAppAndRedirect(host) {
|
||||||
|
|
||||||
window.location.href = `${baseURL}/oauth/authorize?${query.toString()}`;
|
window.location.href = `${baseURL}/oauth/authorize?${query.toString()}`;
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function externalEthereumLogin(instance, baseURL) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
return dispatch(ethereumLogin(instance, baseURL))
|
||||||
|
.then(account => dispatch(switchAccount(account.id)))
|
||||||
|
.then(() => window.location.href = '/');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function externalLogin(host) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const baseURL = parseBaseURL(host) || parseBaseURL(`https://${host}`);
|
||||||
|
|
||||||
|
return fetchExternalInstance(baseURL).then(instance => {
|
||||||
|
const features = getFeatures(instance);
|
||||||
|
const quirks = getQuirks(instance);
|
||||||
|
|
||||||
|
if (features.ethereumLogin && quirks.ethereumLoginOnly) {
|
||||||
|
return dispatch(externalEthereumLogin(instance, baseURL));
|
||||||
|
} else {
|
||||||
|
return dispatch(externalAuthorize(instance, baseURL));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,25 +4,18 @@ import { injectIntl } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { ethereumLogin } from 'soapbox/actions/auth';
|
|
||||||
import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
||||||
import { fetchInstance } from 'soapbox/actions/instance';
|
import { fetchInstance } from 'soapbox/actions/instance';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
|
||||||
import { isStandalone } from 'soapbox/utils/state';
|
import { isStandalone } from 'soapbox/utils/state';
|
||||||
|
|
||||||
import LoginForm from './login_form';
|
import LoginForm from './login_form';
|
||||||
import OtpAuthForm from './otp_auth_form';
|
import OtpAuthForm from './otp_auth_form';
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => ({
|
||||||
const instance = state.get('instance');
|
|
||||||
|
|
||||||
return {
|
|
||||||
me: state.get('me'),
|
me: state.get('me'),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
standalone: isStandalone(state),
|
standalone: isStandalone(state),
|
||||||
features: getFeatures(instance),
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
|
@ -69,18 +62,8 @@ class LoginPage extends ImmutablePureComponent {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEthereumLogin = e => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
|
|
||||||
dispatch(ethereumLogin())
|
|
||||||
.then(() => this.setState({ shouldRedirect: true }))
|
|
||||||
.catch(console.error);
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { standalone, features } = this.props;
|
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 (standalone) return <Redirect to='/auth/external' />;
|
||||||
|
@ -89,11 +72,7 @@ class LoginPage extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (mfa_auth_needed) return <OtpAuthForm mfa_token={mfa_token} />;
|
if (mfa_auth_needed) return <OtpAuthForm mfa_token={mfa_token} />;
|
||||||
|
|
||||||
if (features.ethereumLogin) {
|
|
||||||
return <button onClick={this.handleEthereumLogin}>Log in with Ethereum</button>;
|
|
||||||
} else {
|
|
||||||
return <LoginForm handleSubmit={this.handleSubmit} isLoading={isLoading} />;
|
return <LoginForm handleSubmit={this.handleSubmit} isLoading={isLoading} />;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { createAppAndRedirect, loginWithCode } from 'soapbox/actions/external_auth';
|
import { externalLogin, loginWithCode } from 'soapbox/actions/external_auth';
|
||||||
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
||||||
import { SimpleForm, FieldsGroup, TextInput } from 'soapbox/features/forms';
|
import { SimpleForm, FieldsGroup, TextInput } from 'soapbox/features/forms';
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class ExternalLoginForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
|
|
||||||
dispatch(createAppAndRedirect(host))
|
dispatch(externalLogin(host))
|
||||||
.then(() => this.setState({ isLoading: false }))
|
.then(() => this.setState({ isLoading: false }))
|
||||||
.catch(() => this.setState({ isLoading: false }));
|
.catch(() => this.setState({ isLoading: false }));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import { parseVersion } from './features';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { parseVersion, PLEROMA, MITRA } from './features';
|
||||||
|
|
||||||
// For solving bugs between API implementations
|
// For solving bugs between API implementations
|
||||||
export const getQuirks = instance => {
|
export const getQuirks = createSelector([
|
||||||
const v = parseVersion(instance.get('version'));
|
instance => parseVersion(instance.get('version')),
|
||||||
|
], (v) => {
|
||||||
return {
|
return {
|
||||||
invertedPagination: v.software === 'Pleroma',
|
invertedPagination: v.software === PLEROMA,
|
||||||
|
skipsAppCreation: v.software === MITRA,
|
||||||
|
ethereumLoginOnly: v.software === MITRA,
|
||||||
};
|
};
|
||||||
};
|
});
|
||||||
|
|
||||||
export const getNextLinkName = getState =>
|
export const getNextLinkName = getState =>
|
||||||
getQuirks(getState().get('instance')).invertedPagination ? 'prev' : 'next';
|
getQuirks(getState().get('instance')).invertedPagination ? 'prev' : 'next';
|
||||||
|
|
Ładowanie…
Reference in New Issue