Skip to content

Commit

Permalink
fix: Auth token not updated cause page redirect to login (#609)
Browse files Browse the repository at this point in the history
  • Loading branch information
sunnywx authored Dec 24, 2018
1 parent c483986 commit 90bf637
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 96 deletions.
12 changes: 6 additions & 6 deletions server/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ const authPages = [
];

module.exports = async (ctx, next) => {
const { cookies } = ctx;
const { cookies, url } = ctx;

// filter non-asset types
if (ctx.url.endsWith('.map')) {
if (url.endsWith('.map')) {
return;
}

debug('PAGE: %s', ctx.url);
debug('PAGE: %s', url);

/* eslint prefer-destructuring: off */
const page = (ctx.params.page || '').split('/')[0];
const needAuth = authPages.indexOf(page) > -1;
const needAuth = authPages.includes(page);

if (needAuth && !(cookies.get('user') && cookies.get('access_token'))) {
if (needAuth && !cookies.get('refresh_token')) {
// not login
ctx.redirect(`/login?url=${ctx.params.page}`);
ctx.redirect(`/login?redirect_url=${url}`);
}

await next();
Expand Down
76 changes: 35 additions & 41 deletions server/routes/api.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
const Router = require('koa-router');
const debug = require('debug')('app');

const agent = require('lib/request').default;
const logger = require('../logger');
const utils = require('../utils');

const router = new Router();
const header = {};

const authEndpoint = 'oauth2/token';

router.post('/api/*', async ctx => {
Expand All @@ -23,70 +22,65 @@ router.post('/api/*', async ctx => {

logger({ method, url, body });

const authToken = utils.getTokenGroupFromCtx(ctx);
const unAuthToken = utils.getTokenGroupFromCtx(ctx, 'un_auth');

const browserUrl = ctx.headers.referer;
// todo
const endUrl = browserUrl
.split('/')
.slice(3)
.join('/');
const usingNoAuthToken = endUrl === '' || endUrl.startsWith('apps') || body.isGlobalQuery;
delete body.isGlobalQuery;

// defalut special token params
const tokenData = {
grant_type: 'client_credentials',
const authParams = {
client_id: clientId,
client_secret: clientSecret,
scope: ''
};

// retrieve special token
if (usingNoAuthToken && !unAuthToken.access_token) {
const res = await agent.send('post', [apiServer, authEndpoint].join('/'), tokenData);

if (!res || !res.access_token) {
ctx.throw(401, 'Retrieve token failed');
}

utils.saveTokenResponseToCookie(ctx, res, 'un_auth');
// get current auth info from cookie
const prefix = usingNoAuthToken ? 'no_auth' : '';
const authInfo = utils.getTokenGroupFromCtx(ctx, prefix);
const {
token_type, access_token, refresh_token, expires_in
} = authInfo;

const payload = usingNoAuthToken
? Object.assign(authParams, {
grant_type: 'client_credentials'
})
: Object.assign(authParams, {
grant_type: 'refresh_token',
refresh_token
});

Object.assign(unAuthToken, res);
if (!usingNoAuthToken && !refresh_token) {
// need login
ctx.throw(401, 'refresh token expired');
}

const chooseToken = usingNoAuthToken ? unAuthToken : authToken;

// check if token expired, retrieve refresh token or special token
const { access_token, refresh_token } = chooseToken;
if (!access_token && refresh_token) {
// refresh token params
if (!usingNoAuthToken) {
tokenData.grant_type = 'refresh_token';
tokenData.refresh_token = chooseToken.refresh_token;
}

const res = await agent.send('post', [apiServer, authEndpoint].join('/'), tokenData);
if (!access_token || expires_in < Date.now()) {
const res = await agent.post([apiServer, authEndpoint].join('/'), payload);
debug(`Using refresh token to exchange auth info: %O`, res);

if (!res || !res.access_token) {
ctx.throw(401, 'Refresh token failed');
ctx.throw(401, 'Retrieve access token failed');
}

utils.saveTokenResponseToCookie(ctx, res, usingNoAuthToken ? 'un_auth' : '');
Object.assign(chooseToken, res);
utils.saveTokenResponseToCookie(ctx, res, prefix);
Object.assign(authInfo, res);
}

if (!chooseToken.access_token) {
if (!authInfo.access_token) {
ctx.throw(401, 'Unauthorized: invalid access token');
} else {
header.Authorization = `${chooseToken.token_type} ${chooseToken.access_token}`;
}

delete body.method;
delete body.method;

ctx.body = await agent.send(method, url, body, {
header
});
}
ctx.body = await agent.send(method, url, body, {
header: {
Authorization: `${authInfo.token_type} ${authInfo.access_token}`
}
});
});

module.exports = router;
6 changes: 3 additions & 3 deletions server/routes/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ router.get('/logout', ctx => {
'role',
'expires_in',

'un_auth_access_token',
'un_auth_expires_in',
'un_auth_token_type'
'no_auth_access_token',
'no_auth_expires_in',
'no_auth_token_type'
];
names.forEach(name => ctx.cookies.set(name, '', cookieOptions));
ctx.redirect('/login');
Expand Down
11 changes: 4 additions & 7 deletions server/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ const oauthResFields = [
];

const getTokenGroupFromCtx = (ctx, group = '') => oauthResFields.reduce((obj, prop) => {
if (group) {
prop = [group, prop].join('_');
}

obj[prop] = ctx.cookies.get(prop);
const key = group ? [group, prop].join('_') : prop;
obj[prop] = ctx.cookies.get(key);
return obj;
}, {});

Expand All @@ -30,9 +27,9 @@ const saveTokenResponseToCookie = (

without(oauthResFields, 'id_token').forEach(prop => {
const val = prop === 'expires_in' ? Date.now() + msExpireIn : token_res[prop];
// refresh_token cookie expires after 2 days
// refresh_token cookie expires after 2 weeks
if (prop === 'refresh_token') {
cookieOption.maxAge = 2 * 24 * 60 * 60 * 1000;
cookieOption.maxAge = 2 * 7 * 24 * 60 * 60 * 1000;
} else {
cookieOption.maxAge = msExpireIn;
}
Expand Down
4 changes: 0 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ class App extends React.Component {
// todo
const hasHeader = user.isNormal || !user.accessToken || user.role === 'user';

if (route.needAuth && !user.accessToken) {
return <Redirect to={`/login?url=${match.url}`} />;
}

if (route.noMatch) {
return <Redirect to="/" />;
}
Expand Down
12 changes: 11 additions & 1 deletion src/pages/Login/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@ import styles from './index.scss';

@translate()
@inject(({ rootStore }) => ({
rootStore,
store: rootStore.userStore,
user: rootStore.user
}))
@observer
export default class Login extends Component {
constructor(props) {
super(props);

// eslint-disable-next-line
if (props.rootStore.user.isLoggedIn()) {
props.history.replace('/');
}
}

handleSubmit = async params => {
const { store, user, history } = this.props;
const res = await store.oauth2Check(params);
Expand All @@ -27,7 +37,7 @@ export default class Login extends Component {
}
const defaultUrl = user.isDev ? '/dashboard/my/apps' : '/dashboard/apps';
if (!(res && res.err)) {
history.push(getUrlParam('url') || defaultUrl);
history.push(getUrlParam('redirect_url') || defaultUrl);
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/providers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default class UserProvider {
}

isLoggedIn() {
return Boolean(this.accessToken) && this.username;
return this.accessToken && this.username;
}

update(props = {}) {
Expand Down
18 changes: 1 addition & 17 deletions src/routes/renderRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,13 @@ import { withRouter } from 'react-router';
import Header from 'components/Header';
import Footer from 'components/Footer';
import RouteWrapper from './wrapper';
import { getCookie, setCookie } from '../utils';
import { getCookie } from '../utils';

const renderRoute = (match, route, store) => {
if (route.path === 'login') {
const names = [
'access_token',
'token_type',
'access_token_home',
'refresh_token',
'user',
'role'
];
names.forEach(name => setCookie(name, '', -1));
}

const user = store.user || {};
const role = getCookie('role');
const hasHeader = user.isNormal || !user.username || role === 'user';

if (route.needAuth && !user.username) {
return <Redirect to={`/login?url=${match.url}`} />;
}

if (route.noMatch) {
return <Redirect to="/" />;
}
Expand Down
13 changes: 0 additions & 13 deletions src/stores/Store.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { set } from 'mobx';
import agent from 'lib/request';
import _ from 'lodash';
import qs from 'query-string';

export default class Store {
constructor(initialState, branch) {
Expand Down Expand Up @@ -33,14 +32,6 @@ Store.prototype = {
this.notify(message, 'success');
},
error(message) {
// Can't get token will skip to the login page
if (message === 'Unauthorized') {
const { pathname } = location;
if (pathname && pathname !== '/') {
location.href = `/login?url=${pathname}`;
}
}

this.notify(message, 'error');
},
getValueFromEvent(val) {
Expand Down Expand Up @@ -87,10 +78,6 @@ Store.prototype = {
// forward to node backend
const res = await target.post(url, { method, ...params });

if (res && res.status >= 300 && res.status < 400) {
location.href = res.message || '/login';
}

// error handling
if (_.isObject(res) && _.has(res, 'err') && res.status >= 400) {
this.error(res.errDetail || res.err || 'internal error');
Expand Down
10 changes: 7 additions & 3 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ export function setCookie(name, value, time) {
}

export function getCookie(name) {
const re = new RegExp(`${name}=([^;]+)`);
const value = re.exec(document.cookie);
return value !== null ? decodeURIComponent(value[1]) : null;
const cookieMap = _.fromPairs(
document.cookie.split(';').map(item => item.trim().split('='))
);
if (cookieMap[name]) {
return decodeURIComponent(cookieMap[name]);
}
return null;
}

export function getPastTime(time) {
Expand Down

0 comments on commit 90bf637

Please sign in to comment.