Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions apps/dashboard-api/src/__tests__/loadProjectForAdmin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@ class AppError extends Error {
}
}

const mockAppError = AppError;

jest.mock('@urbackend/common', () => ({
AppError,
AppError: mockAppError,
Project: {
findOne: jest.fn()
},
getProjectAccessQuery: jest.fn((userId) => ({ $or: [{ owner: userId }, { "members.user": userId }] }))
}));

jest.mock('mongoose', () => ({
Types: {
ObjectId: {
isValid: jest.fn((value) => value === '507f1f77bcf86cd799439011')
}
}
}));

const { Project } = require('@urbackend/common');
const loadProjectForAdmin = require('../middlewares/loadProjectForAdmin');

Expand Down Expand Up @@ -43,13 +53,26 @@ describe('loadProjectForAdmin Middleware', () => {
expect(Project.findOne).not.toHaveBeenCalled();
});

it('should call next with AppError(400) if projectId is invalid', async () => {
req.params.projectId = 'bad-id';

await loadProjectForAdmin(req, res, next);

expect(next).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith(expect.any(AppError));
const error = next.mock.calls[0][0];
expect(error.statusCode).toBe(400);
expect(error.message).toBe("Invalid project ID format");
expect(Project.findOne).not.toHaveBeenCalled();
});

it('should call next with AppError(404) if project is not found', async () => {
req.params.projectId = 'proj123';
req.params.projectId = '507f1f77bcf86cd799439011';
Project.findOne.mockResolvedValueOnce(null);

await loadProjectForAdmin(req, res, next);

expect(Project.findOne).toHaveBeenCalledWith({ _id: 'proj123', $or: [{ owner: 'user123' }, { "members.user": 'user123' }] });
expect(Project.findOne).toHaveBeenCalledWith({ _id: '507f1f77bcf86cd799439011', $or: [{ owner: 'user123' }, { "members.user": 'user123' }] });
expect(next).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith(expect.any(AppError));
const error = next.mock.calls[0][0];
Expand All @@ -58,13 +81,13 @@ describe('loadProjectForAdmin Middleware', () => {
});

it('should set req.project and call next without error if project is found', async () => {
req.params.projectId = 'proj123';
const mockProject = { _id: 'proj123', name: 'Test Project' };
req.params.projectId = '507f1f77bcf86cd799439011';
const mockProject = { _id: '507f1f77bcf86cd799439011', name: 'Test Project' };
Project.findOne.mockResolvedValueOnce(mockProject);

await loadProjectForAdmin(req, res, next);

expect(Project.findOne).toHaveBeenCalledWith({ _id: 'proj123', $or: [{ owner: 'user123' }, { "members.user": 'user123' }] });
expect(Project.findOne).toHaveBeenCalledWith({ _id: '507f1f77bcf86cd799439011', $or: [{ owner: 'user123' }, { "members.user": 'user123' }] });
expect(req.project).toEqual(mockProject);
expect(next).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith();
Expand Down
14 changes: 9 additions & 5 deletions apps/dashboard-api/src/controllers/project.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,13 +453,17 @@ module.exports.getSingleProject = async (req, res) => {
await setProjectById(req.params.projectId, projectObj);
}

if (!getProjectRole(projectObj, req.user._id)) {
throw new AppError(403, "Access denied.");
}

Comment on lines -456 to -459

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why removed role check?
isnt this incorrect??

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please respond??
@Nitin-kumar-yadav1307

res.json(sanitizeProjectResponse(projectObj));
} catch (err) {
res.status(500).json({ error: err.message });
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
data: {},
message: err.message,
});
}

res.status(500).json({ success: false, data: {}, message: err.message });
}
};

Expand Down
5 changes: 5 additions & 0 deletions apps/dashboard-api/src/middlewares/loadProjectForAdmin.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// FUNCTION - LOAD PROJECT FOR ADMIN (MIDDLEWARE)

const { Project, AppError, getProjectAccessQuery } = require('@urbackend/common');
const mongoose = require('mongoose');

module.exports = async (req, res, next) => {
try {
const { projectId } = req.params;
if (!projectId) return next(new AppError(400, "Project ID is required"));

if (!mongoose.Types.ObjectId.isValid(projectId)) {
return next(new AppError(400, "Invalid project ID format"));
}

const project = await Project.findOne({
_id: projectId,
...getProjectAccessQuery(req.user._id),
Expand Down
46 changes: 38 additions & 8 deletions apps/web-dashboard/src/pages/Auth.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ import AddRecordDrawer from '../components/AddRecordDrawer';
import { PUBLIC_API_URL } from '../config';

export default function Auth() {
const unwrapApiResponse = (payload) => {
if (!payload || typeof payload !== 'object') {
return { ok: true, data: payload, message: '' };
}

if (Object.prototype.hasOwnProperty.call(payload, 'success')) {
return {
ok: payload.success !== false,
data: payload.data ?? payload.project ?? payload,
message: payload.message || payload.error || ''
};
}

return { ok: true, data: payload, message: '' };
};

const normalizeUsersResponse = (payload) => {
if (Array.isArray(payload)) return payload;
if (Array.isArray(payload?.items)) return payload.items;
Expand Down Expand Up @@ -87,25 +103,39 @@ export default function Auth() {
const fetchData = async () => {
try {
const projRes = await api.get(`/api/projects/${projectId}`);
const projectResult = unwrapApiResponse(projRes.data);

if (!projectResult.ok) {
throw new Error(projectResult.message || 'Failed to load auth details');
}

if (isMounted) {
setProject(projRes.data);
if (projRes.data.authProviders) setAuthProviders(projRes.data.authProviders);
if (projRes.data.isAuthEnabled) {
setProject(projectResult.data);
if (projectResult.data?.authProviders) setAuthProviders(projectResult.data.authProviders);
if (projectResult.data?.isAuthEnabled) {
const requestId = ++latestUsersRequestId.current;
const usersRes = await api.get(
`/api/projects/${projectId}/admin/users?page=${page}&limit=${limit}`
);

const usersResult = unwrapApiResponse(usersRes.data);

if (!usersResult.ok) {
throw new Error(usersResult.message || 'Failed to load auth users');
}

if (!isMounted || requestId !== latestUsersRequestId.current) return;
setUsers(normalizeUsersResponse(usersRes.data));
setUsers(normalizeUsersResponse(usersResult.data));
setTotalRecords(
usersRes.data?.data?.total ||
usersRes.data?.total ||
normalizeUsersResponse(usersRes.data).length
usersResult.data?.data?.total ||
usersResult.data?.total ||
normalizeUsersResponse(usersResult.data).length
);
}
}
} catch { toast.error("Failed to load auth details"); }
} catch (err) {
toast.error(err?.response?.data?.message || err?.response?.data?.error || err.message || "Failed to load auth details");
}
finally { if (isMounted) setLoading(false); }
};
fetchData();
Expand Down
10 changes: 9 additions & 1 deletion packages/common/src/middleware/loadProjectForAdmin.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
// FUNCTION - LOAD PROJECT FOR ADMIN (MIDDLEWARE)
const Project = require('../models/Project');
const AppError = require('../utils/AppError');
const { getProjectAccessQuery } = require('../utils/projectAccess');

module.exports = async (req, res, next) => {
try {
const { projectId } = req.params;
if (!projectId) return next(new AppError(400, "Project ID is required"));

const project = await Project.findOne({ _id: projectId, owner: req.user._id });
if (!mongoose.Types.ObjectId.isValid(projectId)) {
return next(new AppError(400, "Invalid project ID format"));
}

const project = await Project.findOne({
_id: projectId,
...getProjectAccessQuery(req.user._id),
Comment thread
yash-pouranik marked this conversation as resolved.
});
if (!project) {
return next(new AppError(404, "Project not found or access denied"));
}
Expand Down
Loading