Skip to content

Commit 38e53c5

Browse files
Merge pull request #349 from Nitin-kumar-yadav1307/bug/auth-access
fix: allow team members to access auth page and improve auth API response handling
2 parents 9967891 + 107d251 commit 38e53c5

5 files changed

Lines changed: 90 additions & 20 deletions

File tree

apps/dashboard-api/src/__tests__/loadProjectForAdmin.test.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@ class AppError extends Error {
77
}
88
}
99

10+
const mockAppError = AppError;
11+
1012
jest.mock('@urbackend/common', () => ({
11-
AppError,
13+
AppError: mockAppError,
1214
Project: {
1315
findOne: jest.fn()
1416
},
1517
getProjectAccessQuery: jest.fn((userId) => ({ $or: [{ owner: userId }, { "members.user": userId }] }))
1618
}));
1719

20+
jest.mock('mongoose', () => ({
21+
Types: {
22+
ObjectId: {
23+
isValid: jest.fn((value) => value === '507f1f77bcf86cd799439011')
24+
}
25+
}
26+
}));
27+
1828
const { Project } = require('@urbackend/common');
1929
const loadProjectForAdmin = require('../middlewares/loadProjectForAdmin');
2030

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

56+
it('should call next with AppError(400) if projectId is invalid', async () => {
57+
req.params.projectId = 'bad-id';
58+
59+
await loadProjectForAdmin(req, res, next);
60+
61+
expect(next).toHaveBeenCalledTimes(1);
62+
expect(next).toHaveBeenCalledWith(expect.any(AppError));
63+
const error = next.mock.calls[0][0];
64+
expect(error.statusCode).toBe(400);
65+
expect(error.message).toBe("Invalid project ID format");
66+
expect(Project.findOne).not.toHaveBeenCalled();
67+
});
68+
4669
it('should call next with AppError(404) if project is not found', async () => {
47-
req.params.projectId = 'proj123';
70+
req.params.projectId = '507f1f77bcf86cd799439011';
4871
Project.findOne.mockResolvedValueOnce(null);
4972

5073
await loadProjectForAdmin(req, res, next);
5174

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

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

6588
await loadProjectForAdmin(req, res, next);
6689

67-
expect(Project.findOne).toHaveBeenCalledWith({ _id: 'proj123', $or: [{ owner: 'user123' }, { "members.user": 'user123' }] });
90+
expect(Project.findOne).toHaveBeenCalledWith({ _id: '507f1f77bcf86cd799439011', $or: [{ owner: 'user123' }, { "members.user": 'user123' }] });
6891
expect(req.project).toEqual(mockProject);
6992
expect(next).toHaveBeenCalledTimes(1);
7093
expect(next).toHaveBeenCalledWith();

apps/dashboard-api/src/controllers/project.controller.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -453,13 +453,17 @@ module.exports.getSingleProject = async (req, res) => {
453453
await setProjectById(req.params.projectId, projectObj);
454454
}
455455

456-
if (!getProjectRole(projectObj, req.user._id)) {
457-
throw new AppError(403, "Access denied.");
458-
}
459-
460456
res.json(sanitizeProjectResponse(projectObj));
461457
} catch (err) {
462-
res.status(500).json({ error: err.message });
458+
if (err instanceof AppError) {
459+
return res.status(err.statusCode).json({
460+
success: false,
461+
data: {},
462+
message: err.message,
463+
});
464+
}
465+
466+
res.status(500).json({ success: false, data: {}, message: err.message });
463467
}
464468
};
465469

apps/dashboard-api/src/middlewares/loadProjectForAdmin.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
// FUNCTION - LOAD PROJECT FOR ADMIN (MIDDLEWARE)
22

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

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

11+
if (!mongoose.Types.ObjectId.isValid(projectId)) {
12+
return next(new AppError(400, "Invalid project ID format"));
13+
}
14+
1015
const project = await Project.findOne({
1116
_id: projectId,
1217
...getProjectAccessQuery(req.user._id),

apps/web-dashboard/src/pages/Auth.jsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ import AddRecordDrawer from '../components/AddRecordDrawer';
1515
import { PUBLIC_API_URL } from '../config';
1616

1717
export default function Auth() {
18+
const unwrapApiResponse = (payload) => {
19+
if (!payload || typeof payload !== 'object') {
20+
return { ok: true, data: payload, message: '' };
21+
}
22+
23+
if (Object.prototype.hasOwnProperty.call(payload, 'success')) {
24+
return {
25+
ok: payload.success !== false,
26+
data: payload.data ?? payload.project ?? payload,
27+
message: payload.message || payload.error || ''
28+
};
29+
}
30+
31+
return { ok: true, data: payload, message: '' };
32+
};
33+
1834
const normalizeUsersResponse = (payload) => {
1935
if (Array.isArray(payload)) return payload;
2036
if (Array.isArray(payload?.items)) return payload.items;
@@ -87,25 +103,39 @@ export default function Auth() {
87103
const fetchData = async () => {
88104
try {
89105
const projRes = await api.get(`/api/projects/${projectId}`);
106+
const projectResult = unwrapApiResponse(projRes.data);
107+
108+
if (!projectResult.ok) {
109+
throw new Error(projectResult.message || 'Failed to load auth details');
110+
}
111+
90112
if (isMounted) {
91-
setProject(projRes.data);
92-
if (projRes.data.authProviders) setAuthProviders(projRes.data.authProviders);
93-
if (projRes.data.isAuthEnabled) {
113+
setProject(projectResult.data);
114+
if (projectResult.data?.authProviders) setAuthProviders(projectResult.data.authProviders);
115+
if (projectResult.data?.isAuthEnabled) {
94116
const requestId = ++latestUsersRequestId.current;
95117
const usersRes = await api.get(
96118
`/api/projects/${projectId}/admin/users?page=${page}&limit=${limit}`
97119
);
98120

121+
const usersResult = unwrapApiResponse(usersRes.data);
122+
123+
if (!usersResult.ok) {
124+
throw new Error(usersResult.message || 'Failed to load auth users');
125+
}
126+
99127
if (!isMounted || requestId !== latestUsersRequestId.current) return;
100-
setUsers(normalizeUsersResponse(usersRes.data));
128+
setUsers(normalizeUsersResponse(usersResult.data));
101129
setTotalRecords(
102-
usersRes.data?.data?.total ||
103-
usersRes.data?.total ||
104-
normalizeUsersResponse(usersRes.data).length
130+
usersResult.data?.data?.total ||
131+
usersResult.data?.total ||
132+
normalizeUsersResponse(usersResult.data).length
105133
);
106134
}
107135
}
108-
} catch { toast.error("Failed to load auth details"); }
136+
} catch (err) {
137+
toast.error(err?.response?.data?.message || err?.response?.data?.error || err.message || "Failed to load auth details");
138+
}
109139
finally { if (isMounted) setLoading(false); }
110140
};
111141
fetchData();

packages/common/src/middleware/loadProjectForAdmin.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
// FUNCTION - LOAD PROJECT FOR ADMIN (MIDDLEWARE)
22
const Project = require('../models/Project');
33
const AppError = require('../utils/AppError');
4+
const { getProjectAccessQuery } = require('../utils/projectAccess');
45

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

10-
const project = await Project.findOne({ _id: projectId, owner: req.user._id });
11+
if (!mongoose.Types.ObjectId.isValid(projectId)) {
12+
return next(new AppError(400, "Invalid project ID format"));
13+
}
14+
15+
const project = await Project.findOne({
16+
_id: projectId,
17+
...getProjectAccessQuery(req.user._id),
18+
});
1119
if (!project) {
1220
return next(new AppError(404, "Project not found or access denied"));
1321
}

0 commit comments

Comments
 (0)