fix: extract middleware

Signed-off-by: mertmit <mertmit99@gmail.com>
This commit is contained in:
mertmit
2026-01-10 13:49:13 +03:00
parent 84e45952ac
commit c1d409a82b

View File

@@ -37,6 +37,7 @@ import {
Source,
SyncSource,
View,
Widget,
Workspace,
} from '~/models';
import rolePermissions, {
@@ -92,8 +93,303 @@ const getApiVersionFromUrl = (url: string) => {
@Injectable()
export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
async use(req, res, next): Promise<any> {
const { params, query } = req;
req.ncApiVersion = getApiVersionFromUrl(req.route.path);
req.ncSocketId = req.headers['xc-socket-id'];
const bypassContext = {
workspace_id: RootScopes.BYPASS,
base_id: RootScopes.BYPASS,
api_version: req.ncApiVersion,
socket_id: req.ncSocketId,
};
// this is a special route for ws operations we pass 'nc' as base id
const isInternalApi = !!req.path?.startsWith('/api/v2/internal');
const isInternalWorkspaceScope = isInternalApi && params.baseId === 'nc';
const baseId = params.baseId || params.baseName;
if (!isInternalWorkspaceScope && baseId) {
const base = await Base.get(bypassContext, baseId);
if (!base) {
NcError.get(bypassContext).baseNotFound(baseId);
}
const context = {
workspace_id: base.fk_workspace_id,
base_id: base.id,
api_version: req.ncApiVersion,
socket_id: req.ncSocketId,
};
req.ncBaseId = base.id;
req.ncWorkspaceId = base.fk_workspace_id;
req.context = {
workspace_id: req.ncWorkspaceId,
base_id: req.ncBaseId,
api_version: req.ncApiVersion,
socket_id: req.ncSocketId,
nc_site_url: req.ncSiteUrl,
};
const mcpTokenId = params.mcpTokenId || query.mcpTokenId;
const integrationId = params.integrationId || query.integrationId;
const tableId =
params.tableId || params.modelId || params.tableName || query.tableId;
const viewId = params.viewId || params.viewName || query.viewId;
const formViewId = params.formViewId || query.formViewId;
const gridViewId = params.gridViewId || query.gridViewId;
const kanbanViewId = params.kanbanViewId || query.kanbanViewId;
const galleryViewId = params.galleryViewId || query.galleryViewId;
const calendarViewId = params.calendarViewId || query.calendarViewId;
const publicDataUuid = params.publicDataUuid || query.publicDataUuid;
const sharedViewUuid = params.sharedViewUuid || query.sharedViewUuid;
const sharedBaseUuid = params.sharedBaseUuid || query.sharedBaseUuid;
const hookId = params.hookId || query.hookId;
const rowColorConditionId =
params.rowColorConditionId || query.rowColorConditionId;
const gridViewColumnId =
params.gridViewColumnId || query.gridViewColumnId;
const formViewColumnId =
params.formViewColumnId || query.formViewColumnId;
const galleryViewColumnId =
params.galleryViewColumnId || query.galleryViewColumnId;
const columnId = params.columnId || query.columnId;
const filterId = params.filterId || query.filterId;
const filterParentId = params.filterParentId || query.filterParentId;
const widgetId = params.widgetId || query.widgetId;
const sortId = params.sortId || query.sortId;
const syncId = params.syncId || query.syncId;
const extensionId = params.extensionId || query.extensionId;
if (mcpTokenId) {
const mcpToken = await MCPToken.get(
{
workspace_id: RootScopes.FULL_BYPASS,
base_id: RootScopes.FULL_BYPASS,
},
mcpTokenId,
);
if (!mcpToken) {
NcError.get(context).genericNotFound('MCPToken', mcpTokenId);
}
req.ncBaseId = mcpToken.base_id;
req.ncWorkspaceId = mcpToken.fk_workspace_id;
} else if (integrationId) {
const integration = await Integration.get(context, integrationId);
if (!integration) {
NcError.get(context).integrationNotFound(integrationId);
}
} else if (tableId) {
const model = await Model.get(context, tableId);
if (!model) {
NcError.get(context).tableNotFound(tableId);
}
req.ncSourceId = model.source_id;
} else if (viewId) {
const view =
(await View.get(context, viewId)) ||
(await Model.get(context, viewId));
if (!view) {
NcError.get(context).viewNotFound(viewId);
}
req.ncSourceId = view.source_id;
} else if (
formViewId ||
gridViewId ||
kanbanViewId ||
galleryViewId ||
calendarViewId
) {
const view = await View.get(
context,
formViewId ||
gridViewId ||
kanbanViewId ||
galleryViewId ||
calendarViewId,
);
if (!view) {
NcError.get(context).viewNotFound(
formViewId ||
gridViewId ||
kanbanViewId ||
galleryViewId ||
calendarViewId,
);
}
req.ncSourceId = view.source_id;
} else if (publicDataUuid) {
const view = await View.getByUUID(context, publicDataUuid);
if (!view) {
NcError.get(context).viewNotFound(publicDataUuid);
}
req.ncSourceId = view?.source_id;
} else if (sharedViewUuid) {
const view = await View.getByUUID(context, sharedViewUuid);
if (!view) {
NcError.get(context).viewNotFound(sharedViewUuid);
}
req.ncSourceId = view.source_id;
} else if (sharedBaseUuid) {
const base = await Base.getByUuid(context, sharedBaseUuid);
if (!base) {
NcError.get(context).baseNotFound(sharedBaseUuid);
}
} else if (hookId) {
const hook = await Hook.get(context, hookId);
if (!hook) {
NcError.get(context).genericNotFound('Webhook', hookId);
}
req.ncSourceId = hook.source_id;
} else if (rowColorConditionId) {
const rowColorCondition = await RowColorCondition.getById(
context,
rowColorConditionId,
);
if (!rowColorCondition) {
NcError.get(context).genericNotFound(
'Row color condition',
rowColorConditionId,
);
}
const view = await View.get(context, rowColorCondition.fk_view_id);
req.ncSourceId = view.source_id;
} else if (gridViewColumnId) {
const gridViewColumn = await GridViewColumn.get(
context,
gridViewColumnId,
);
if (!gridViewColumn) {
NcError.get(context).fieldNotFound(gridViewColumnId);
}
req.ncSourceId = gridViewColumn?.source_id;
} else if (formViewColumnId) {
const formViewColumn = await FormViewColumn.get(
context,
formViewColumnId,
);
if (!formViewColumn) {
NcError.get(context).fieldNotFound(formViewColumnId);
}
req.ncSourceId = formViewColumn.source_id;
} else if (galleryViewColumnId) {
const galleryViewColumn = await GalleryViewColumn.get(
context,
galleryViewColumnId,
);
if (!galleryViewColumn) {
NcError.get(context).fieldNotFound(galleryViewColumnId);
}
req.ncSourceId = galleryViewColumn.source_id;
} else if (columnId) {
const column = await Column.get(context, { colId: columnId });
if (!column) {
NcError.get(context).fieldNotFound(columnId);
}
req.ncSourceId = column.source_id;
} else if (filterId) {
const filter = await Filter.get(context, filterId);
if (!filter) {
NcError.genericNotFound('Filter', filterId);
}
req.ncSourceId = filter.source_id;
} else if (filterParentId) {
const filter = await Filter.get(context, filterParentId);
if (!filter) {
NcError.genericNotFound('Filter', filterParentId);
}
req.ncSourceId = filter.source_id;
} else if (widgetId) {
const widget = await Widget.get(context, widgetId);
if (!widget) {
NcError.genericNotFound('Widget', widgetId);
}
} else if (sortId) {
const sort = await Sort.get(context, sortId);
if (!sort) {
NcError.genericNotFound('Sort', sortId);
}
req.ncSourceId = sort.source_id;
} else if (syncId) {
const syncSource = await SyncSource.get(context, syncId);
if (!syncSource) {
NcError.genericNotFound('Sync Source', syncId);
}
req.ncSourceId = syncSource.source_id;
} else if (extensionId) {
const extension = await Extension.get(context, extensionId);
if (!extension) {
NcError.genericNotFound('Extension', extensionId);
}
}
} else {
await this.legacyExtractIds(req);
}
await this.additionalValidation({ req, res, next });
next();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
await this.use(
context.switchToHttp().getRequest(),
context.switchToHttp().getResponse(),
() => {},
);
return true;
}
// additional validation logic which can be overridden
protected async additionalValidation(_param: {
next: any;
res: any;
req: any;
}) {
// do nothing
}
protected async legacyExtractIds(req) {
const { params } = req;
let view;
let tableIdToCheck: string | null = null; // Store table ID for permission check at the end
const context = {
workspace_id: RootScopes.BYPASS,
@@ -169,6 +465,9 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = model.base_id;
req.ncSourceId = model.source_id;
// Extract table ID for permission check at the end
tableIdToCheck = model.id;
} else if (params.viewId) {
view =
(await View.get(context, params.viewId)) ||
@@ -180,6 +479,9 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = view.base_id;
req.ncSourceId = view.source_id;
// Extract table ID for permission check at the end
tableIdToCheck = view?.fk_model_id;
} else if (
params.formViewId ||
params.gridViewId ||
@@ -242,6 +544,9 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = hook.base_id;
req.ncSourceId = hook.source_id;
// Extract table ID for permission check at the end
tableIdToCheck = hook.fk_model_id;
} else if (params.rowColorConditionId) {
const rowColorCondition = await RowColorCondition.getById(
context,
@@ -258,6 +563,9 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
view = await View.get(context, rowColorCondition.fk_view_id);
req.ncSourceId = view.source_id;
// Extract table ID for permission check at the end
tableIdToCheck = view?.fk_model_id;
} else if (params.gridViewColumnId) {
const gridViewColumn = await GridViewColumn.get(
context,
@@ -274,6 +582,11 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = gridViewColumn?.base_id;
req.ncSourceId = gridViewColumn?.source_id;
// Extract table ID for permission check at the end
if (view?.fk_model_id) {
tableIdToCheck = view.fk_model_id;
}
} else if (params.formViewColumnId) {
const formViewColumn = await FormViewColumn.get(
context,
@@ -290,6 +603,11 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = formViewColumn.base_id;
req.ncSourceId = formViewColumn.source_id;
// Extract table ID for permission check at the end
if (view?.fk_model_id) {
tableIdToCheck = view.fk_model_id;
}
} else if (params.galleryViewColumnId) {
const galleryViewColumn = await GalleryViewColumn.get(
context,
@@ -306,6 +624,11 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = galleryViewColumn.base_id;
req.ncSourceId = galleryViewColumn.source_id;
// Extract table ID for permission check at the end
if (view?.fk_model_id) {
tableIdToCheck = view.fk_model_id;
}
} else if (params.columnId) {
const column = await Column.get(context, { colId: params.columnId });
@@ -315,6 +638,9 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = column.base_id;
req.ncSourceId = column.source_id;
// Extract table ID for permission check at the end
tableIdToCheck = column.fk_model_id;
} else if (params.filterId) {
const filter = await Filter.get(context, params.filterId);
@@ -328,6 +654,11 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = filter.base_id;
req.ncSourceId = filter.source_id;
// Extract table ID for permission check at the end
if (view?.fk_model_id) {
tableIdToCheck = view.fk_model_id;
}
} else if (params.filterParentId) {
const filter = await Filter.get(context, params.filterParentId);
@@ -341,6 +672,19 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = filter.base_id;
req.ncSourceId = filter.source_id;
// Extract table ID for permission check at the end
if (view?.fk_model_id) {
tableIdToCheck = view.fk_model_id;
}
} else if (params.widgetId) {
const widget = await Widget.get(context, params.widgetId);
if (!widget) {
NcError.genericNotFound('Widget', params.widgetId);
}
req.ncBaseId = widget.base_id;
} else if (params.sortId) {
const sort = await Sort.get(context, params.sortId);
@@ -354,6 +698,11 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = sort.base_id;
req.ncSourceId = sort.source_id;
// Extract table ID for permission check at the end
if (view?.fk_model_id) {
tableIdToCheck = view.fk_model_id;
}
} else if (params.syncId) {
const syncSource = await SyncSource.get(context, req.params.syncId);
@@ -410,6 +759,9 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = model.base_id;
req.ncSourceId = model.source_id;
// Extract table ID for permission check at the end
tableIdToCheck = model.id;
} else if (
[
'/api/v1/db/meta/comment/:commentId',
@@ -430,6 +782,9 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = audit.base_id;
req.ncSourceId = audit.source_id;
// Extract table ID for permission check at the end
tableIdToCheck = audit.fk_model_id;
}
// extract base id from query params only if it's userMe endpoint
else if (
@@ -543,31 +898,15 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
is_api_token: req.user?.is_api_token,
};
// Load and cache permissions in context to avoid multiple fetches
if (req.ncBaseId && !isInternalWorkspaceScope) {
req.permissions = await Permission.list(req.context, req.ncBaseId);
}
await this.additionalValidation({ req, res, next });
next();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
await this.use(
context.switchToHttp().getRequest(),
context.switchToHttp().getResponse(),
() => {},
);
return true;
}
// additional validation logic which can be overridden
protected async additionalValidation(_param: {
next: any;
res: any;
req: any;
}) {
// do nothing
// Store table ID to check in context for ACL middleware to perform table visibility check
if (tableIdToCheck) {
req.context.ncTableId = tableIdToCheck;
}
}
}