trigger redux auth state update based on custom auth header
This commit is contained in:
parent
30557e3b9c
commit
00dedfbfc0
|
@ -9,7 +9,7 @@ from grant import commands, proposal, user, comment, milestone, admin, email, bl
|
|||
from grant.extensions import bcrypt, migrate, db, ma, security
|
||||
from grant.settings import SENTRY_RELEASE, ENV
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
from grant.utils.auth import AuthException, handle_auth_error
|
||||
from grant.utils.auth import AuthException, handle_auth_error, get_authed_user
|
||||
|
||||
|
||||
def create_app(config_objects=["grant.settings"]):
|
||||
|
@ -33,6 +33,11 @@ def create_app(config_objects=["grant.settings"]):
|
|||
# NOTE: testing mode does not honor this handler, and instead returns the generic 500 response
|
||||
app.register_error_handler(AuthException, handle_auth_error)
|
||||
|
||||
@app.after_request
|
||||
def grantio_authed(response):
|
||||
response.headers["X-Grantio-Authed"] = 'yes' if get_authed_user() else 'no'
|
||||
return response
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
@ -46,7 +51,7 @@ def register_extensions(app):
|
|||
security.init_app(app, datastore=user_datastore, register_blueprint=False)
|
||||
|
||||
# supports_credentials for session cookies
|
||||
CORS(app, supports_credentials=True)
|
||||
CORS(app, supports_credentials=True, expose_headers='X-Grantio-Authed')
|
||||
SSLify(app)
|
||||
return None
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ def handle_auth_error(e):
|
|||
|
||||
|
||||
def get_authed_user():
|
||||
return current_user if current_user.is_authenticated else None
|
||||
return current_user if current_user.is_authenticated and not current_user.banned else None
|
||||
|
||||
|
||||
def throw_on_banned(user):
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import axios from 'axios';
|
||||
import { getStoreRef } from 'store/configure';
|
||||
import { checkUser } from 'modules/auth/actions';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.BACKEND_URL,
|
||||
|
@ -7,9 +9,24 @@ const instance = axios.create({
|
|||
withCredentials: true,
|
||||
});
|
||||
|
||||
let lastAuthed = null as null | string;
|
||||
|
||||
instance.interceptors.response.use(
|
||||
// Do nothing to responses
|
||||
res => res,
|
||||
// - watch for changes to auth header and trigger checkUser action if it changes
|
||||
// - this allows for external authorization events to be registered in this context
|
||||
// - external auth events include login/logout in another tab, or
|
||||
// the user getting banned
|
||||
res => {
|
||||
const authed = res.headers['x-grantio-authed'];
|
||||
if (lastAuthed !== null && lastAuthed !== authed) {
|
||||
const store = getStoreRef();
|
||||
if (store) {
|
||||
store.dispatch<any>(checkUser());
|
||||
}
|
||||
}
|
||||
lastAuthed = authed;
|
||||
return res;
|
||||
},
|
||||
// Try to parse error message if possible
|
||||
err => {
|
||||
if (err.response && err.response.data) {
|
||||
|
|
|
@ -8,8 +8,11 @@ import {
|
|||
authUser as apiAuthUser,
|
||||
logoutUser,
|
||||
} from 'api/api';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { User } from 'types';
|
||||
|
||||
type GetState = () => AppState;
|
||||
|
||||
function setSentryScope(user: User) {
|
||||
Sentry.configureScope(scope => {
|
||||
scope.setUser({
|
||||
|
@ -20,7 +23,17 @@ function setSentryScope(user: User) {
|
|||
|
||||
// check if user has authenticated session
|
||||
export function checkUser() {
|
||||
return async (dispatch: Dispatch<any>) => {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
const state = getState();
|
||||
if (state.auth.isAuthingUser || state.auth.isLoggingOut) {
|
||||
// this happens when axios calls checkUser upon seeing a change in the
|
||||
// custom auth-header, this call will not be ignored on other tabs not
|
||||
// initiating the authentication related behaviors
|
||||
console.info(
|
||||
'ignoring checkUser action b/c we are currently authing or logging out',
|
||||
);
|
||||
return;
|
||||
}
|
||||
dispatch({ type: types.CHECK_USER_PENDING });
|
||||
try {
|
||||
const res = await checkUserAuth();
|
||||
|
|
|
@ -13,6 +13,8 @@ export interface AuthState {
|
|||
isCheckingUser: boolean;
|
||||
hasCheckedUser: boolean;
|
||||
|
||||
isLoggingOut: boolean;
|
||||
|
||||
isCreatingUser: boolean;
|
||||
createUserError: string | null;
|
||||
|
||||
|
@ -35,6 +37,8 @@ export const INITIAL_STATE: AuthState = {
|
|||
isCheckingUser: false,
|
||||
hasCheckedUser: false,
|
||||
|
||||
isLoggingOut: false,
|
||||
|
||||
authSignature: null,
|
||||
authSignatureAddress: null,
|
||||
isSigningAuth: false,
|
||||
|
@ -81,6 +85,7 @@ export default function createReducer(
|
|||
case types.CHECK_USER_REJECTED:
|
||||
return {
|
||||
...state,
|
||||
user: null,
|
||||
isCheckingUser: false,
|
||||
hasCheckedUser: true,
|
||||
};
|
||||
|
@ -135,9 +140,22 @@ export default function createReducer(
|
|||
signAuthError: action.payload,
|
||||
};
|
||||
|
||||
case types.LOGOUT_PENDING:
|
||||
return {
|
||||
...state,
|
||||
isLoggingOut: true,
|
||||
user: null,
|
||||
};
|
||||
case types.LOGOUT_FULFILLED:
|
||||
return {
|
||||
...state,
|
||||
isLoggingOut: false,
|
||||
user: null,
|
||||
};
|
||||
case types.LOGOUT_REJECTED:
|
||||
return {
|
||||
...state,
|
||||
isLoggingOut: false,
|
||||
user: null,
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,11 @@ const bindMiddleware = (middleware: MiddleWare[]) => {
|
|||
return composeWithDevTools(applyMiddleware(...middleware));
|
||||
};
|
||||
|
||||
let storeRef = null as null | Store<AppState>;
|
||||
export function getStoreRef() {
|
||||
return storeRef;
|
||||
}
|
||||
|
||||
export function configureStore(initialState: Partial<AppState> = combineInitialState) {
|
||||
const store: Store<AppState> = createStore(
|
||||
rootReducer,
|
||||
|
@ -44,6 +49,6 @@ export function configureStore(initialState: Partial<AppState> = combineInitialS
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
storeRef = store;
|
||||
return { store };
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue