trigger redux auth state update based on custom auth header

This commit is contained in:
Aaron 2019-02-15 21:07:41 -06:00
parent 30557e3b9c
commit 00dedfbfc0
No known key found for this signature in database
GPG Key ID: 3B5B7597106F0A0E
6 changed files with 65 additions and 7 deletions

View File

@ -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

View File

@ -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):

View File

@ -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) {

View File

@ -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();

View File

@ -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,
};

View File

@ -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 };
}