diff --git a/packages/nocodb/src/helpers/initAdminFromEnv.ts b/packages/nocodb/src/helpers/initAdminFromEnv.ts index 4f68d8db28..89b69e55a0 100644 --- a/packages/nocodb/src/helpers/initAdminFromEnv.ts +++ b/packages/nocodb/src/helpers/initAdminFromEnv.ts @@ -185,7 +185,6 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) { password, email_verification_token, token_version: randomTokenString(), - refresh_token: null, }, ncMeta, ); @@ -199,7 +198,6 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) { password, email_verification_token, token_version: randomTokenString(), - refresh_token: null, }, ncMeta, ); diff --git a/packages/nocodb/src/meta/meta.service.ts b/packages/nocodb/src/meta/meta.service.ts index 608e9eb666..ee564124c2 100644 --- a/packages/nocodb/src/meta/meta.service.ts +++ b/packages/nocodb/src/meta/meta.service.ts @@ -228,7 +228,7 @@ export class MetaService { case MetaTable.USERS: prefix = 'us'; break; - case MetaTable.ORGS: + case MetaTable.ORGS_OLD: prefix = 'org'; break; case MetaTable.TEAMS: diff --git a/packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts b/packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts index 384429d5f5..8702674989 100644 --- a/packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts +++ b/packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts @@ -29,6 +29,7 @@ import * as nc_039_sqlite_alter_column_types from '~/meta/migrations/v2/nc_039_s import * as nc_040_form_view_alter_column_types from '~/meta/migrations/v2/nc_040_form_view_alter_column_types'; import * as nc_041_calendar_view from '~/meta/migrations/v2/nc_041_calendar_view'; import * as nc_042_user_block from '~/meta/migrations/v2/nc_042_user_block'; +import * as nc_043_user_refresh_token from '~/meta/migrations/v2/nc_043_user_refresh_token'; // Create a custom migration source class export default class XcMigrationSourcev2 { @@ -69,6 +70,7 @@ export default class XcMigrationSourcev2 { 'nc_040_form_view_alter_column_types', 'nc_041_calendar_view', 'nc_042_user_block', + 'nc_043_user_refresh_token', ]); } @@ -140,6 +142,8 @@ export default class XcMigrationSourcev2 { return nc_041_calendar_view; case 'nc_042_user_block': return nc_042_user_block; + case 'nc_043_user_refresh_token': + return nc_043_user_refresh_token; } } } diff --git a/packages/nocodb/src/meta/migrations/v2/nc_011.ts b/packages/nocodb/src/meta/migrations/v2/nc_011.ts index 7aca8fb30e..126b69318b 100644 --- a/packages/nocodb/src/meta/migrations/v2/nc_011.ts +++ b/packages/nocodb/src/meta/migrations/v2/nc_011.ts @@ -700,7 +700,7 @@ const up = async (knex) => { table.timestamps(true, true); }); - await knex.schema.createTable(MetaTable.ORGS, (table) => { + await knex.schema.createTable(MetaTable.ORGS_OLD, (table) => { table.string('id', 20).primary().notNullable(); table.string('title'); @@ -712,13 +712,13 @@ const up = async (knex) => { table.string('title'); table.string('org_id', 20); - table.foreign('org_id').references(`${MetaTable.ORGS}.id`); + table.foreign('org_id').references(`${MetaTable.ORGS_OLD}.id`); table.timestamps(true, true); }); await knex.schema.createTable(MetaTable.TEAM_USERS, (table) => { table.string('org_id', 20); - table.foreign('org_id').references(`${MetaTable.ORGS}.id`); + table.foreign('org_id').references(`${MetaTable.ORGS_OLD}.id`); table.string('user_id', 20); table.foreign('user_id').references(`${MetaTable.USERS}.id`); table.timestamps(true, true); diff --git a/packages/nocodb/src/meta/migrations/v2/nc_032_cleanup.ts b/packages/nocodb/src/meta/migrations/v2/nc_032_cleanup.ts index d9b8b72486..dde8c9c645 100644 --- a/packages/nocodb/src/meta/migrations/v2/nc_032_cleanup.ts +++ b/packages/nocodb/src/meta/migrations/v2/nc_032_cleanup.ts @@ -7,11 +7,11 @@ const up = async (knex: Knex) => { await knex.schema.dropTable(MetaTable.TEAMS); - await knex.schema.dropTable(MetaTable.ORGS); + await knex.schema.dropTable(MetaTable.ORGS_OLD); }; const down = async (knex: Knex) => { - await knex.schema.createTable(MetaTable.ORGS, (table) => { + await knex.schema.createTable(MetaTable.ORGS_OLD, (table) => { table.string('id', 20).primary().notNullable(); table.string('title'); @@ -23,13 +23,13 @@ const down = async (knex: Knex) => { table.string('title'); table.string('org_id', 20); - table.foreign('org_id').references(`${MetaTable.ORGS}.id`); + table.foreign('org_id').references(`${MetaTable.ORGS_OLD}.id`); table.timestamps(true, true); }); await knex.schema.createTable(MetaTable.TEAM_USERS, (table) => { table.string('org_id', 20); - table.foreign('org_id').references(`${MetaTable.ORGS}.id`); + table.foreign('org_id').references(`${MetaTable.ORGS_OLD}.id`); table.string('user_id', 20); table.foreign('user_id').references(`${MetaTable.USERS}.id`); table.timestamps(true, true); diff --git a/packages/nocodb/src/meta/migrations/v2/nc_043_user_refresh_token.ts b/packages/nocodb/src/meta/migrations/v2/nc_043_user_refresh_token.ts new file mode 100644 index 0000000000..1274c96a04 --- /dev/null +++ b/packages/nocodb/src/meta/migrations/v2/nc_043_user_refresh_token.ts @@ -0,0 +1,27 @@ +import type { Knex } from 'knex'; +import { MetaTable } from '~/utils/globals'; + +const up = async (knex: Knex) => { + await knex.schema.alterTable(MetaTable.USERS, (table) => { + table.dropColumn('refresh_token'); + }); + + await knex.schema.createTable(MetaTable.USER_REFRESH_TOKENS, (table) => { + table.string('fk_user_id', 20).index(); + table.string('token', 255).index(); + table.text('meta'); + + table.timestamp('expires_at'); + + table.timestamps(true, true); + }); +}; + +const down = async (knex: Knex) => { + await knex.schema.dropTable(MetaTable.USER_REFRESH_TOKENS); + await knex.schema.alterTable(MetaTable.USERS, (table) => { + table.string('refresh_token', 255); + }); +}; + +export { up, down }; diff --git a/packages/nocodb/src/models/User.ts b/packages/nocodb/src/models/User.ts index 6eba62d5eb..be336424e3 100644 --- a/packages/nocodb/src/models/User.ts +++ b/packages/nocodb/src/models/User.ts @@ -11,6 +11,7 @@ import { } from '~/utils/globals'; import { Base, BaseUser } from '~/models'; import { sanitiseUserObj } from '~/utils'; +import UserRefreshToken from '~/models/UserRefreshToken'; export default class User implements UserType { id: string; @@ -184,9 +185,21 @@ export default class User implements UserType { } static async getByRefreshToken(refresh_token, ncMeta = Noco.ncMeta) { - return await ncMeta.metaGet2(null, null, MetaTable.USERS, { + const userRefreshToken = await UserRefreshToken.getByToken( refresh_token, - }); + ncMeta, + ); + + if(!userRefreshToken){ + return null; + } + + return await ncMeta.metaGet2( + null, + null, + MetaTable.USERS, + userRefreshToken.fk_user_id, + ); } public static async list( @@ -262,6 +275,7 @@ export default class User implements UserType { args: { user?: User; baseId?: string; + orgId?: string; }, ncMeta = Noco.ncMeta, ) { diff --git a/packages/nocodb/src/models/UserRefreshToken.ts b/packages/nocodb/src/models/UserRefreshToken.ts new file mode 100644 index 0000000000..69b699a3a3 --- /dev/null +++ b/packages/nocodb/src/models/UserRefreshToken.ts @@ -0,0 +1,111 @@ +import dayjs from 'dayjs'; +import Noco from '~/Noco'; +import { extractProps } from '~/helpers/extractProps'; +import { MetaTable } from '~/utils/globals'; +import { parseMetaProp, stringifyMetaProp } from '~/utils/modelUtils'; + +export default class UserRefreshToken { + fk_user_id: string; + token: string; + expires_at: any; + meta?: any; + created_at?: any; + updated_at?: any; + + public static async insert( + syncLog: Partial, + ncMeta = Noco.ncMeta, + ) { + // clear old invalid tokens before inserting new one + // todo: verify the populated sql query + await ncMeta.metaDelete( + null, + null, + MetaTable.USER_REFRESH_TOKENS, + { + fk_user_id: syncLog.fk_user_id, + }, + { + expires_at: { + lt: dayjs().toDate(), + }, + }, + ); + + const insertObj = extractProps(syncLog, [ + 'fk_user_id', + 'token', + 'expires_at', + 'meta', + ]); + + // set default expiry as 90 days if missing + if (!('expires_at' in insertObj)) { + insertObj.expires_at = dayjs().add(90, 'day').toDate(); + } + + if ('meta' in insertObj) { + insertObj.meta = stringifyMetaProp(insertObj); + } + + await ncMeta.metaInsert2( + null, + null, + MetaTable.USER_REFRESH_TOKENS, + insertObj, + true, + ); + return insertObj; + } + + static async updateOldToken( + oldToken: string, + newToken: string, + ncMeta = Noco.ncMeta, + ) { + return await ncMeta.metaUpdate( + null, + null, + MetaTable.USER_REFRESH_TOKENS, + { + token: oldToken, + expires_at: dayjs().add(90, 'day').toDate(), + }, + { + token: newToken, + }, + ); + } + + static async deleteToken(token: string, ncMeta = Noco.ncMeta) { + return await ncMeta.metaDelete(null, null, MetaTable.USER_REFRESH_TOKENS, { + token, + }); + } + + static async deleteAllUserToken(userId: string, ncMeta = Noco.ncMeta) { + return await ncMeta.metaDelete(null, null, MetaTable.USER_REFRESH_TOKENS, { + fk_user_id: userId, + }); + } + + static async getByToken( + token: string, + ncMeta = Noco.ncMeta, + ): Promise { + const userToken = await ncMeta.metaGet2( + null, + null, + MetaTable.USER_REFRESH_TOKENS, + { + token, + }, + ); + + if (!userToken) return null; + + userToken.meta = parseMetaProp(userToken); + + return userToken; + } +} diff --git a/packages/nocodb/src/services/users/users.service.ts b/packages/nocodb/src/services/users/users.service.ts index e3817c078f..ff18357967 100644 --- a/packages/nocodb/src/services/users/users.service.ts +++ b/packages/nocodb/src/services/users/users.service.ts @@ -27,6 +27,7 @@ import NcPluginMgrv2 from '~/helpers/NcPluginMgrv2'; import { NcError } from '~/helpers/catchError'; import { BasesService } from '~/services/bases.service'; import { extractProps } from '~/helpers/extractProps'; +import UserRefreshToken from "~/models/UserRefreshToken"; @Injectable() export class UsersService { @@ -380,10 +381,15 @@ export class UsersService { const refreshToken = randomTokenString(); - await User.update(user.id, { - email: user.email, - refresh_token: refreshToken, - }); + // await User.update(user.id, { + // email: user.email, + // refresh_token: refreshToken, + // }); + + await UserRefreshToken.insert({ + token: refreshToken, + fk_user_id: user.id + }) setTokenCookie(param.res, refreshToken); @@ -495,10 +501,15 @@ export class UsersService { const refreshToken = randomTokenString(); - await User.update(user.id, { - refresh_token: refreshToken, - email: user.email, - }); + // await User.update(user.id, { + // refresh_token: refreshToken, + // email: user.email, + // }); + + await UserRefreshToken.insert({ + token: refreshToken, + fk_user_id: user.id + }) setTokenCookie(param.res, refreshToken); @@ -531,10 +542,12 @@ export class UsersService { this.clearCookie(param); const user = (param.req as any).user; if (user?.id) { - await User.update(user.id, { - refresh_token: null, - token_version: randomTokenString(), - }); + // await User.update(user.id, { + // refresh_token: null, + // token_version: randomTokenString(), + // }); + // todo: clear only token present in cookie + await UserRefreshToken.deleteAllUserToken(user.id); } return { msg: 'Signed out successfully' }; } catch (e) { @@ -572,10 +585,17 @@ export class UsersService { } await User.update(user.id, { - refresh_token: refreshToken, - email: user.email, + // refresh_token: refreshToken, + // email: user.email, token_version: user['token_version'], }); + + await UserRefreshToken.insert({ + token: refreshToken, + fk_user_id: user.id, + meta: req.user?.extra, + }); + setTokenCookie(res, refreshToken); } } diff --git a/packages/nocodb/src/utils/globals.ts b/packages/nocodb/src/utils/globals.ts index 039b99155c..ff286fa015 100644 --- a/packages/nocodb/src/utils/globals.ts +++ b/packages/nocodb/src/utils/globals.ts @@ -29,7 +29,7 @@ export enum MetaTable { KANBAN_VIEW = 'nc_kanban_view_v2', KANBAN_VIEW_COLUMNS = 'nc_kanban_view_columns_v2', USERS = 'nc_users_v2', - ORGS = 'nc_orgs_v2', + ORGS_OLD = 'nc_orgs_v2', TEAMS = 'nc_teams_v2', TEAM_USERS = 'nc_team_users_v2', VIEWS = 'nc_views_v2', @@ -46,6 +46,7 @@ export enum MetaTable { MAP_VIEW_COLUMNS = 'nc_map_view_columns_v2', STORE = 'nc_store', NOTIFICATION = 'notification', + USER_REFRESH_TOKENS = 'nc_user_refresh_tokens', } export enum MetaTableOldV2 { @@ -60,7 +61,7 @@ export const orderedMetaTables = [ MetaTable.AUDIT, MetaTable.TEAM_USERS, MetaTable.TEAMS, - MetaTable.ORGS, + MetaTable.ORGS_OLD, MetaTable.PROJECT_USERS, MetaTable.USERS, MetaTable.MAP_VIEW, @@ -151,7 +152,7 @@ export enum CacheScope { MAP_VIEW_COLUMN = 'mapViewColumn', KANBAN_VIEW_COLUMN = 'kanbanViewColumn', USER = 'user', - ORGS = 'orgs', + ORGS_OLD = 'orgs', TEAM = 'team', TEAM_USER = 'teamUser', VIEW = 'view',