Merge branch 'custom-auth' into 'next'
Next: Let a custom auth app be embedded in the build See merge request soapbox-pub/soapbox-fe!1208
This commit is contained in:
commit
5f8c7c8db6
3 changed files with 50 additions and 7 deletions
|
@ -14,6 +14,7 @@ import { createApp } from 'soapbox/actions/apps';
|
||||||
import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me';
|
import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me';
|
||||||
import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth';
|
import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
|
import { custom } from 'soapbox/custom';
|
||||||
import KVStore from 'soapbox/storage/kv_store';
|
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';
|
||||||
|
@ -39,12 +40,14 @@ export const AUTH_ACCOUNT_REMEMBER_REQUEST = 'AUTH_ACCOUNT_REMEMBER_REQUEST';
|
||||||
export const AUTH_ACCOUNT_REMEMBER_SUCCESS = 'AUTH_ACCOUNT_REMEMBER_SUCCESS';
|
export const AUTH_ACCOUNT_REMEMBER_SUCCESS = 'AUTH_ACCOUNT_REMEMBER_SUCCESS';
|
||||||
export const AUTH_ACCOUNT_REMEMBER_FAIL = 'AUTH_ACCOUNT_REMEMBER_FAIL';
|
export const AUTH_ACCOUNT_REMEMBER_FAIL = 'AUTH_ACCOUNT_REMEMBER_FAIL';
|
||||||
|
|
||||||
|
const customApp = custom('app');
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
loggedOut: { id: 'auth.logged_out', defaultMessage: 'Logged out.' },
|
loggedOut: { id: 'auth.logged_out', defaultMessage: 'Logged out.' },
|
||||||
invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' },
|
invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const noOp = () => () => new Promise(f => f());
|
const noOp = () => new Promise(f => f());
|
||||||
|
|
||||||
const getScopes = state => {
|
const getScopes = state => {
|
||||||
const instance = state.get('instance');
|
const instance = state.get('instance');
|
||||||
|
@ -54,12 +57,23 @@ const getScopes = state => {
|
||||||
|
|
||||||
function createAppAndToken() {
|
function createAppAndToken() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
return dispatch(createAuthApp()).then(() => {
|
return dispatch(getAuthApp()).then(() => {
|
||||||
return dispatch(createAppToken());
|
return dispatch(createAppToken());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Create an auth app, or use it from build config */
|
||||||
|
function getAuthApp() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
if (customApp?.client_secret) {
|
||||||
|
return noOp().then(() => dispatch({ type: AUTH_APP_CREATED, app: customApp }));
|
||||||
|
} else {
|
||||||
|
return dispatch(createAuthApp());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createAuthApp() {
|
function createAuthApp() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const params = {
|
const params = {
|
||||||
|
@ -117,7 +131,7 @@ export function refreshUserToken() {
|
||||||
const refreshToken = getState().getIn(['auth', 'user', 'refresh_token']);
|
const refreshToken = getState().getIn(['auth', 'user', 'refresh_token']);
|
||||||
const app = getState().getIn(['auth', 'app']);
|
const app = getState().getIn(['auth', 'app']);
|
||||||
|
|
||||||
if (!refreshToken) return dispatch(noOp());
|
if (!refreshToken) return dispatch(noOp);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
client_id: app.get('client_id'),
|
client_id: app.get('client_id'),
|
||||||
|
@ -200,7 +214,7 @@ export function loadCredentials(token, accountUrl) {
|
||||||
|
|
||||||
export function logIn(intl, username, password) {
|
export function logIn(intl, username, password) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
return dispatch(createAuthApp()).then(() => {
|
return dispatch(getAuthApp()).then(() => {
|
||||||
return dispatch(createUserToken(username, password));
|
return dispatch(createUserToken(username, password));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error.response.data.error === 'mfa_required') {
|
if (error.response.data.error === 'mfa_required') {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* Functions for dealing with custom build configuration.
|
* Functions for dealing with custom build configuration.
|
||||||
*/
|
*/
|
||||||
import { NODE_ENV } from 'soapbox/build_config';
|
import * as BuildConfig from 'soapbox/build_config';
|
||||||
|
|
||||||
/** Require a custom JSON file if it exists */
|
/** Require a custom JSON file if it exists */
|
||||||
export const custom = (filename, fallback = {}) => {
|
export const custom = (filename: string, fallback: any = {}): any => {
|
||||||
if (NODE_ENV === 'test') return fallback;
|
if (BuildConfig.NODE_ENV === 'test') return fallback;
|
||||||
|
|
||||||
|
// @ts-ignore: yes it does
|
||||||
const context = require.context('custom', false, /\.json$/);
|
const context = require.context('custom', false, /\.json$/);
|
||||||
const path = `./${filename}.json`;
|
const path = `./${filename}.json`;
|
||||||
|
|
|
@ -38,6 +38,34 @@ For example:
|
||||||
|
|
||||||
See `app/soapbox/utils/features.js` for the full list of features.
|
See `app/soapbox/utils/features.js` for the full list of features.
|
||||||
|
|
||||||
|
### Embedded app (`custom/app.json`)
|
||||||
|
|
||||||
|
By default, Soapbox will create a new OAuth app every time a user tries to register or log in.
|
||||||
|
This is usually the desired behavior, as it works "out of the box" without any additional configuration, and it is resistant to tampering and subtle client bugs.
|
||||||
|
However, some larger servers may wish to skip this step for performance reasons.
|
||||||
|
|
||||||
|
If an app is supplied in `custom/app.json`, it will be used for authorization.
|
||||||
|
The full app entity must be provided, for example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"client_id": "cf5yI6ffXH1UcDkEApEIrtHpwCi5Tv9xmju8IKdMAkE",
|
||||||
|
"client_secret": "vHmSDpm6BJGUvR4_qWzmqWjfHcSYlZumxpFfohRwNNQ",
|
||||||
|
"id": "7132",
|
||||||
|
"name": "Soapbox FE",
|
||||||
|
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
|
||||||
|
"website": "https://soapbox.pub/",
|
||||||
|
"vapid_key": "BLElLQVJVmY_e4F5JoYxI5jXiVOYNsJ9p-amkykc9NcI-jwa9T1Y2GIbDqbY-HqC6ayPkfW4K4o9vgBFKYmkuS4"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is crucial that the app has the expected scopes.
|
||||||
|
You can obtain one with the following curl command (replace `MY_DOMAIN`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"client_name": "Soapbox FE", "redirect_uris": "urn:ietf:wg:oauth:2.0:oob", "scopes": "read write follow push admin", "website": "https://soapbox.pub/"}' "https://MY_DOMAIN.com/api/v1/apps"
|
||||||
|
```
|
||||||
|
|
||||||
### Custom files (`custom/instance/*`)
|
### Custom files (`custom/instance/*`)
|
||||||
|
|
||||||
You can place arbitrary files of any type in the `custom/instance/` directory.
|
You can place arbitrary files of any type in the `custom/instance/` directory.
|
||||||
|
|
Loading…
Reference in a new issue