Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #2338 from nocodb/fix/insufficient-session-expiration
fix: insufficient session expiration
  • Loading branch information
pranavxc committed Jun 13, 2022
2 parents a18f5dd + 5f08ecb commit c9b5111
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 19 deletions.
4 changes: 3 additions & 1 deletion packages/nc-gui/pages/user/settings/index.vue
Expand Up @@ -227,8 +227,10 @@ export default {
newPassword: this.passwordDetails.newPassword
}
)
this.$toast.success('Password changed successfully.').goAway(3000)
this.$toast.success('Password changed successfully. Please login again.').goAway(3000)
this.$refs.formType[0].reset()
await this.$store.dispatch('users/ActSignOut')
this.$router.push('/user/authentication/signin')
} catch (e) {
this.$toast
.error(await this._extractSdkResponseErrorMsg(e))
Expand Down
2 changes: 1 addition & 1 deletion packages/nc-gui/plugins/axiosInterceptor.js
Expand Up @@ -77,7 +77,7 @@ export default ({ store, $axios, redirect, $toast, route, app }) => {
redirect('/')
} else {
$toast.clear()
$toast.info('Token expired please login to continue', {
$toast.info('Token Expired. Please login again.', {
position: 'bottom-center'
}).goAway(5000)
redirect('/user/authentication/signin')
Expand Down
12 changes: 10 additions & 2 deletions packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
Expand Up @@ -53,7 +53,8 @@ export function initStrategies(router): void {
firstname,
lastname,
isAuthorized,
isPublicBase
isPublicBase,
token_version
},
done
) {
Expand All @@ -72,7 +73,8 @@ export function initStrategies(router): void {
provider,
firstname,
lastname,
roles
roles,
token_version
});
});

Expand Down Expand Up @@ -100,11 +102,17 @@ export function initStrategies(router): void {
);

if (cachedVal) {
if (cachedVal.token_version !== jwtPayload.token_version) {
return done(new Error('Token Expired. Please login again.'));
}
return done(null, cachedVal);
}

User.getByEmail(jwtPayload?.email)
.then(async user => {
if (user.token_version !== jwtPayload.token_version) {
return done(new Error('Token Expired. Please login again.'));
}
if (req.ncProjectId) {
// this.xcMeta
// .metaGet(req.ncProjectId, null, 'nc_projects_users', {
Expand Down
43 changes: 34 additions & 9 deletions packages/nocodb/src/lib/meta/api/userApi/userApis.ts
Expand Up @@ -71,7 +71,8 @@ export async function signup(req: Request, res: Response<TableType>) {
password,
email_verification_token,
invite_token: null,
invite_token_expires: null
invite_token_expires: null,
email: user.email
});
} else {
NcError.badRequest('User already exist');
Expand All @@ -95,14 +96,17 @@ export async function signup(req: Request, res: Response<TableType>) {
}
}

const token_version = randomTokenString();

await User.insert({
firstname,
lastname,
email,
salt,
password,
email_verification_token,
roles
roles,
token_version
});
}
user = await User.getByEmail(email);
Expand All @@ -126,7 +130,8 @@ export async function signup(req: Request, res: Response<TableType>) {
await promisify((req as any).login.bind(req))(user);
const refreshToken = randomTokenString();
await User.update(user.id, {
refresh_token: refreshToken
refresh_token: refreshToken,
email: user.email
});

setTokenCookie(res, refreshToken);
Expand All @@ -148,7 +153,8 @@ export async function signup(req: Request, res: Response<TableType>) {
firstname: user.firstname,
lastname: user.lastname,
id: user.id,
roles: user.roles
roles: user.roles,
token_version: user.token_version
},
Noco.getConfig().auth.jwt.secret,
Noco.getConfig().auth.jwt.options
Expand Down Expand Up @@ -178,8 +184,15 @@ async function successfulSignIn({
await promisify((req as any).login.bind(req))(user);
const refreshToken = randomTokenString();

let token_version = user.token_version;
if (!token_version) {
token_version = randomTokenString();
}

await User.update(user.id, {
refresh_token: refreshToken
refresh_token: refreshToken,
email: user.email,
token_version
});
setTokenCookie(res, refreshToken);

Expand All @@ -198,7 +211,8 @@ async function successfulSignIn({
firstname: user.firstname,
lastname: user.lastname,
id: user.id,
roles: user.roles
roles: user.roles,
token_version
},

Noco.getConfig().auth.jwt.secret,
Expand Down Expand Up @@ -249,6 +263,7 @@ async function googleSignin(req, res, next) {
function randomTokenString(): string {
return crypto.randomBytes(40).toString('hex');
}

function setTokenCookie(res, token): void {
// create http only cookie with refresh token that expires in 7 days
const cookieOptions = {
Expand Down Expand Up @@ -285,7 +300,8 @@ async function passwordChange(req: Request<any, any>, res): Promise<any> {
await User.update(user.id, {
salt,
password,
email: user.email
email: user.email,
token_version: null
});

Audit.insert({
Expand All @@ -311,8 +327,10 @@ async function passwordForgot(req: Request<any, any>, res): Promise<any> {
if (user) {
const token = uuidv4();
await User.update(user.id, {
email: user.email,
reset_password_token: token,
reset_password_expires: new Date(Date.now() + 60 * 60 * 1000)
reset_password_expires: new Date(Date.now() + 60 * 60 * 1000),
token_version: null
});
try {
const template = (await import('./ui/emailTemplates/forgotPassword'))
Expand Down Expand Up @@ -363,6 +381,9 @@ async function tokenValidate(req, res): Promise<any> {
if (user.reset_password_expires < new Date()) {
NcError.badRequest('Password reset url expired');
}
if (!user.token_version) {
NcError.badRequest('Token Expired. Please login again.');
}
res.json(true);
}

Expand All @@ -389,8 +410,10 @@ async function passwordReset(req, res): Promise<any> {
await User.update(user.id, {
salt,
password,
email: user.email,
reset_password_expires: null,
reset_password_token: ''
reset_password_token: '',
token_version: null
});

Audit.insert({
Expand All @@ -416,6 +439,7 @@ async function emailVerification(req, res): Promise<any> {
}

await User.update(user.id, {
email: user.email,
email_verification_token: '',
email_verified: true
});
Expand Down Expand Up @@ -446,6 +470,7 @@ async function refreshToken(req, res): Promise<any> {
const refreshToken = randomTokenString();

await User.update(user.id, {
email: user.email,
refresh_token: refreshToken
});

Expand Down
3 changes: 2 additions & 1 deletion packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts
Expand Up @@ -2,10 +2,11 @@ import projectAcl from '../../utils/projectAcl';
import { NextFunction, Request, Response } from 'express';
import catchError, { NcError } from './catchError';
import extractProjectIdAndAuthenticate from './extractProjectIdAndAuthenticate';

export default function(handlerFn, permissionName) {
return [
extractProjectIdAndAuthenticate,
catchError(function authMiddleware(req, _res, next) {
catchError(async function authMiddleware(req, _res, next) {
const roles = req?.session?.passport?.user?.roles;
if (
!(
Expand Down
6 changes: 5 additions & 1 deletion packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts
Expand Up @@ -4,6 +4,7 @@ import * as nc_013_sync_source from './v2/nc_013_sync_source';
import * as nc_014_alter_column_data_types from './v2/nc_014_alter_column_data_types';
import * as nc_015_add_meta_col_in_column_table from './v2/nc_015_add_meta_col_in_column_table';
import * as nc_016_alter_hooklog_payload_types from './v2/nc_016_alter_hooklog_payload_types';
import * as nc_017_add_user_token_version_column from './v2/nc_017_add_user_token_version_column';

// Create a custom migration source class
export default class XcMigrationSourcev2 {
Expand All @@ -18,7 +19,8 @@ export default class XcMigrationSourcev2 {
'nc_013_sync_source',
'nc_014_alter_column_data_types',
'nc_015_add_meta_col_in_column_table',
'nc_016_alter_hooklog_payload_types'
'nc_016_alter_hooklog_payload_types',
'nc_017_add_user_token_version_column'
]);
}

Expand All @@ -40,6 +42,8 @@ export default class XcMigrationSourcev2 {
return nc_015_add_meta_col_in_column_table;
case 'nc_016_alter_hooklog_payload_types':
return nc_016_alter_hooklog_payload_types;
case 'nc_017_add_user_token_version_column':
return nc_017_add_user_token_version_column;
}
}
}
Expand Down
@@ -0,0 +1,37 @@
import Knex from 'knex';

const up = async (knex: Knex) => {
await knex.schema.alterTable('nc_users_v2', table => {
table.string('token_version');
});
};

const down = async knex => {
await knex.schema.alterTable('nc_users_v2', table => {
table.dropColumns('token_version');
});
};

export { up, down };

/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Wing-Kam Wong <wingkwong.code@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
7 changes: 5 additions & 2 deletions packages/nocodb/src/lib/models/User.ts
Expand Up @@ -22,6 +22,7 @@ export default class User implements UserType {
email_verification_token?: string;
email_verified: boolean;
roles?: string;
token_version?: string;

constructor(data: User) {
Object.assign(this, data);
Expand All @@ -43,7 +44,8 @@ export default class User implements UserType {
'reset_password_token',
'email_verification_token',
'email_verified',
'roles'
'roles',
'token_version'
]);
const { id } = await ncMeta.metaInsert2(
null,
Expand Down Expand Up @@ -71,7 +73,8 @@ export default class User implements UserType {
'reset_password_token',
'email_verification_token',
'email_verified',
'roles'
'roles',
'token_version'
]);
// get existing cache
const keys = [
Expand Down
6 changes: 4 additions & 2 deletions packages/nocodb/src/lib/v1-legacy/rest/RestAuthCtrl.ts
Expand Up @@ -42,7 +42,8 @@ passport.serializeUser(function(
firstname,
lastname,
isAuthorized,
isPublicBase
isPublicBase,
token_version
},
done
) {
Expand All @@ -61,7 +62,8 @@ passport.serializeUser(function(
provider,
firstname,
lastname,
roles
roles,
token_version
});
});

Expand Down

0 comments on commit c9b5111

Please sign in to comment.