Browse Source

Merge pull request #2338 from nocodb/fix/insufficient-session-expiration

fix: insufficient session expiration
pull/2345/head
Pranav C 2 years ago committed by GitHub
parent
commit
c9b5111b25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui/pages/user/settings/index.vue
  2. 2
      packages/nc-gui/plugins/axiosInterceptor.js
  3. 12
      packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
  4. 43
      packages/nocodb/src/lib/meta/api/userApi/userApis.ts
  5. 3
      packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts
  6. 6
      packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts
  7. 37
      packages/nocodb/src/lib/migrations/v2/nc_017_add_user_token_version_column.ts
  8. 7
      packages/nocodb/src/lib/models/User.ts
  9. 6
      packages/nocodb/src/lib/v1-legacy/rest/RestAuthCtrl.ts

4
packages/nc-gui/pages/user/settings/index.vue

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

2
packages/nc-gui/plugins/axiosInterceptor.js

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

12
packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts

@ -53,7 +53,8 @@ export function initStrategies(router): void {
firstname,
lastname,
isAuthorized,
isPublicBase
isPublicBase,
token_version
},
done
) {
@ -72,7 +73,8 @@ export function initStrategies(router): void {
provider,
firstname,
lastname,
roles
roles,
token_version
});
});
@ -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', {

43
packages/nocodb/src/lib/meta/api/userApi/userApis.ts

@ -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');
@ -95,6 +96,8 @@ export async function signup(req: Request, res: Response<TableType>) {
}
}
const token_version = randomTokenString();
await User.insert({
firstname,
lastname,
@ -102,7 +105,8 @@ export async function signup(req: Request, res: Response<TableType>) {
salt,
password,
email_verification_token,
roles
roles,
token_version
});
}
user = await User.getByEmail(email);
@ -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);
@ -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
@ -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);
@ -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,
@ -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 = {
@ -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({
@ -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'))
@ -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);
}
@ -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({
@ -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
});
@ -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
});

3
packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts

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

6
packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts

@ -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 {
@ -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'
]);
}
@ -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;
}
}
}

37
packages/nocodb/src/lib/migrations/v2/nc_017_add_user_token_version_column.ts

@ -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
packages/nocodb/src/lib/models/User.ts

@ -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);
@ -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,
@ -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 = [

6
packages/nocodb/src/lib/v1-legacy/rest/RestAuthCtrl.ts

@ -42,7 +42,8 @@ passport.serializeUser(function(
firstname,
lastname,
isAuthorized,
isPublicBase
isPublicBase,
token_version
},
done
) {
@ -61,7 +62,8 @@ passport.serializeUser(function(
provider,
firstname,
lastname,
roles
roles,
token_version
});
});

Loading…
Cancel
Save