Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict user data access in User query #2797

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
821bb0f
Restrict user data access in User query
NishantSinghhhhh Dec 27, 2024
32108df
fixes
NishantSinghhhhh Dec 27, 2024
9456c06
Merge branch 'develop' into bug-user-query
NishantSinghhhhh Dec 27, 2024
f647e5c
fixes
NishantSinghhhhh Dec 27, 2024
5c6c7db
Merge branch 'bug-user-query' of github.com:NishantSinghhhhh/talawa-a…
NishantSinghhhhh Dec 27, 2024
d1f98e6
fixes for tests
NishantSinghhhhh Dec 27, 2024
092e48b
Rename node-version to .node-version
NishantSinghhhhh Dec 27, 2024
ab07073
Update users.ts
NishantSinghhhhh Dec 27, 2024
27abe1d
fixes for tests
NishantSinghhhhh Dec 27, 2024
42c10a6
fixes for failing tests
NishantSinghhhhh Dec 27, 2024
610b7e1
Merge branch 'bug-user-query' of github.com:NishantSinghhhhh/talawa-a…
NishantSinghhhhh Dec 27, 2024
0eb3351
full test coverage
NishantSinghhhhh Dec 28, 2024
f7390de
full test coverage
NishantSinghhhhh Dec 28, 2024
7d5c8f6
fixes
NishantSinghhhhh Dec 28, 2024
3f95a91
Merge branch 'develop' into bug-user-query
NishantSinghhhhh Dec 28, 2024
52ce28c
Merge branch 'develop' into bug-user-query
NishantSinghhhhh Dec 28, 2024
f2278af
fixes
NishantSinghhhhh Dec 28, 2024
cdc6557
removing unnessary changes
NishantSinghhhhh Dec 28, 2024
96155d7
adding tests
NishantSinghhhhh Dec 28, 2024
26b730d
code-Rabbit's changes
NishantSinghhhhh Dec 29, 2024
36b955f
rabbit's changes
NishantSinghhhhh Dec 29, 2024
a916d5b
Merge branch 'develop' into bug-user-query
NishantSinghhhhh Dec 29, 2024
5b9c650
adding tests
NishantSinghhhhh Dec 29, 2024
daefc59
fixes
NishantSinghhhhh Dec 29, 2024
1ed6306
Merge branch 'develop' into bug-user-query
NishantSinghhhhh Dec 30, 2024
8834a77
removed tests and added tests with full coverage
NishantSinghhhhh Dec 30, 2024
9b837fa
Merge branch 'bug-user-query' of github.com:NishantSinghhhhh/talawa-a…
NishantSinghhhhh Dec 30, 2024
0d2f42d
linting users
NishantSinghhhhh Dec 30, 2024
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
41 changes: 36 additions & 5 deletions src/resolvers/Query/user.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,62 @@
import { USER_NOT_FOUND_ERROR } from "../../constants";
import { errors } from "../../libraries";
import type { InterfaceAppUserProfile, InterfaceUser } from "../../models";
import { AppUserProfile, User } from "../../models";
import { AppUserProfile, User, Organization } from "../../models";
import type { QueryResolvers } from "../../types/generatedGraphQLTypes";
/**
* This query fetch the user from the database.
*
* This function ensure that users can only query their own data and not access details of other users , protecting sensitive data.
*
* @param _parent-
* @param args - An object that contains `id` for the user.
* @param context-
* @returns An object that contains user data. If the user is not found then it throws a `NotFoundError` error.
*/
export const user: QueryResolvers["user"] = async (_parent, args, context) => {
// Check if the current user exists in the system
const currentUserExists = !!(await User.exists({
_id: context.userId,
}));

if (currentUserExists === false) {
if (!currentUserExists) {
throw new errors.NotFoundError(
USER_NOT_FOUND_ERROR.DESC,
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}

const [userOrganization, superAdminProfile] = await Promise.all([
Organization.exists({
members: args.id,
admins: context.userId,
}),
AppUserProfile.exists({
userId: context.userId,
isSuperAdmin: true,
}),
]);

if (!userOrganization && context.userId !== args.id && !superAdminProfile) {
throw new errors.UnauthorizedError(
"Access denied. Only the user themselves, organization admins, or super admins can view this profile.",
);
}

// Fetch the user data from the database based on the provided ID (args.id)
const user: InterfaceUser = (await User.findById(
args.id,
).lean()) as InterfaceUser;

if (!user) {
throw new errors.NotFoundError(
USER_NOT_FOUND_ERROR.DESC,
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}

const user: InterfaceUser = (await User.findOne({
_id: args.id,
}).lean()) as InterfaceUser;
const userAppProfile: InterfaceAppUserProfile = (await AppUserProfile.findOne(
{
userId: user._id,
Expand Down
203 changes: 203 additions & 0 deletions tests/resolvers/User/userAccess.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { USER_NOT_FOUND_ERROR, BASE_URL } from "../../../src/constants";
import { user as userResolver } from "../../../src/resolvers/Query/user";
import { User, Organization, AppUserProfile } from "../../../src/models";
import { connect, disconnect } from "../../helpers/db";
import type { TestUserType } from "../../helpers/userAndOrg";
import { createTestUser } from "../../helpers/userAndOrg";
import { beforeAll, afterAll, describe, it, expect } from "vitest";
import type mongoose from "mongoose";
import { Types } from "mongoose";
import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge";
import { deleteUserFromCache } from "../../../src/services/UserCache/deleteUserFromCache";

let testUser: TestUserType;
let anotherTestUser: TestUserType;
let adminUser: TestUserType;
let superAdminUser: TestUserType;
let MONGOOSE_INSTANCE: typeof mongoose;

beforeAll(async () => {
MONGOOSE_INSTANCE = await connect();
await deleteUserFromCache(testUser?.id);
NishantSinghhhhh marked this conversation as resolved.
Show resolved Hide resolved
const pledges = await FundraisingCampaignPledge.find({
_id: new Types.ObjectId(),
}).lean();
console.log(pledges);
try {
testUser = await createTestUser();
if (!testUser?.id) throw new Error("Failed to create test user");
anotherTestUser = await createTestUser();
if (!anotherTestUser?.id)
throw new Error("Failed to create another test user");
adminUser = await createTestUser();
if (!adminUser?.id) throw new Error("Failed to create admin user");
superAdminUser = await createTestUser();
if (!superAdminUser?.id)
throw new Error("Failed to create super admin user");

// Make sure we're using the correct ObjectId for the member
const org = await Organization.create({
creatorId: adminUser?.id,
// Ensure we're using the MongoDB _id, not the string id
members: [anotherTestUser?._id],
admins: [adminUser?.id],
name: "Test Organization",
description: "A test organization for user query testing",
});

// Verify the member was added correctly
await Organization.findByIdAndUpdate(
org._id,
{ $addToSet: { members: anotherTestUser?._id } },
{ new: true },
);

if (!org) throw new Error("Failed to create organization");

const profile = await AppUserProfile.create({
userId: superAdminUser?.id,
isSuperAdmin: true,
});
if (!profile) throw new Error("Failed to create super admin profile");
} catch (error) {
console.error("Failed to set up test data:", error);
throw error;
}
});

afterAll(async () => {
await Promise.all([
User.deleteMany({
_id: {
$in: [
testUser?.id,
anotherTestUser?.id,
adminUser?.id,
superAdminUser?.id,
],
},
}),
Organization.deleteMany({}),
AppUserProfile.deleteMany({ userId: superAdminUser?.id }),
]);
await disconnect(MONGOOSE_INSTANCE);
});

describe("user Query", () => {
// Test case 1: Invalid user ID scenario
it("throws error if user doesn't exist", async () => {
expect.assertions(2);
const args = {
id: new Types.ObjectId().toString(),
};

const context = {
userId: new Types.ObjectId().toString(),
};

try {
await userResolver?.({}, args, context);
} catch (error: unknown) {
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.DESC);
}
});

// Test case 2: Unauthorized access scenario
it("throws unauthorized error when trying to access another user's data", async () => {
expect.assertions(1);
const args = {
id: anotherTestUser?.id,
};

const context = {
userId: testUser?.id,
};

try {
await userResolver?.({}, args, context);
} catch (error: unknown) {
expect((error as Error).message).toEqual(
"Access denied. Only the user themselves, organization admins, or super admins can view this profile.",
);
}
});

// Test case 3: Admin access scenario
// Test case 3: Admin access scenario
it("allows an admin to access another user's data within the same organization", async () => {
expect.assertions(2);
const args = {
id: anotherTestUser?.id,
};

const context = {
userId: adminUser?.id,
apiRootURL: BASE_URL,
};

const org = await Organization.findOne({ admins: adminUser?.id });

// Convert ObjectIds to strings for comparison
const memberIds = org?.members.map((id) => id.toString());
const testUserId = anotherTestUser?._id?.toString();

expect(memberIds).toContain(testUserId);

const result = await userResolver?.({}, args, context);

const user = await User.findById(anotherTestUser?._id).lean();

expect(result?.user).toEqual({
...user,
organizationsBlockedBy: [],
image: user?.image ? `${BASE_URL}${user.image}` : null,
});
});

// Test case 4: SuperAdmin access scenario
it("allows a super admin to access any user's data", async () => {
expect.assertions(1);
const args = {
id: anotherTestUser?.id,
};

const context = {
userId: superAdminUser?.id,
apiRootURL: BASE_URL,
};

const result = await userResolver?.({}, args, context);

const user = await User.findById(anotherTestUser?._id).lean();

expect(result?.user).toEqual({
...user,
organizationsBlockedBy: [],
image: user?.image ? `${BASE_URL}${user.image}` : null,
});
});

// Test case 5: Successful access to own profile
it("successfully returns user data when accessing own profile", async () => {
expect.assertions(1);
const args = {
id: testUser?.id,
};

const context = {
userId: testUser?.id,
apiRootURL: BASE_URL,
};

const result = await userResolver?.({}, args, context);

const user = await User.findById(testUser?._id).lean();

expect(result?.user).toEqual({
...user,
organizationsBlockedBy: [],
image: user?.image ? `${BASE_URL}${user.image}` : null,
});
});
});
Loading