Skip to content

Commit

Permalink
Feature/added GraphQl Quries in server to verify User Role and Its au…
Browse files Browse the repository at this point in the history
…thorization So that it can be helpFul for client side. (#3094)

* added graphql Queries named VerifyRole Response

* modified verifyRole.ts

* Update src/resolvers/Query/verifyRole.ts

line-30

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/typeDefs/types.ts

line -86

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update schema.graphql

line - 1618

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/resolvers/Query/verifyRole.ts

line - 37

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/resolvers/Query/verifyRole.ts

line-44

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/resolvers/Query/verifyRole.ts

line-52

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* suggestion from ai and fixed the failing test

* fixed build issue

* added test cases for verifyRole Query

* added test cases for falling line

* fixed failed test cases

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
PurnenduMIshra129th and coderabbitai[bot] authored Feb 1, 2025
1 parent 8ab6bdd commit 595feaa
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 1 deletion.
10 changes: 10 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,7 @@ type Query {
users(first: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [UserData]
usersConnection(first: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [UserData]!
venue(id: ID!): Venue
verifyRole: VerifyRoleResponse
}

input RecaptchaVerification {
Expand Down Expand Up @@ -2164,6 +2165,15 @@ input VenueWhereInput {
name_starts_with: String
}

"""Response type for verifying user roles and their authorization status."""
type VerifyRoleResponse {
"""Whether the user is authorized for the requested action."""
isAuthorized: Boolean!

"""The role of the user (e.g., 'ADMIN', 'USER', etc.)."""
role: String!
}

type VolunteerMembership {
_id: ID!
createdAt: DateTime!
Expand Down
2 changes: 2 additions & 0 deletions src/resolvers/Query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { eventsAttendedByUser } from "./eventsAttendedByUser";
import { getRecurringEvents } from "./getRecurringEvents";
import { getVolunteerMembership } from "./getVolunteerMembership";
import { getVolunteerRanks } from "./getVolunteerRanks";
import { verifyRole } from "./verifyRole";

export const Query: QueryResolvers = {
actionItemsByEvent,
Expand Down Expand Up @@ -117,4 +118,5 @@ export const Query: QueryResolvers = {
eventsAttendedByUser,
getVolunteerMembership,
getVolunteerRanks,
verifyRole,
};
94 changes: 94 additions & 0 deletions src/resolvers/Query/verifyRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { QueryResolvers } from "../../types/generatedGraphQLTypes";
import jwt from "jsonwebtoken";
import type { InterfaceAppUserProfile } from "../../models/AppUserProfile";
import { AppUserProfile } from "../../models/AppUserProfile";
import type { Request } from "express";
import type { InterfaceJwtTokenPayload } from "../../utilities";
/**
* This query verifies the user's role based on the provided JWT token.
* @param _ - Unused parent parameter (as this is a root-level query).
* @param __ - Unused arguments parameter (as this query does not require input arguments).
* @param context - Contains the Express `Request` object, which includes the Authorization header.
* @returns An object containing:
* - `role`: The user's role, either "admin" or "user".
* - `isAuthorized`: A boolean indicating whether the token is valid.
*
* @remarks
* - Extracts the token from the `Authorization` header.
* - Decodes and verifies the token using `jwt.verify()`.
* - Fetches the user profile from the database using `userId` from the decoded token.
* - Determines the role (`admin` if `isSuperAdmin` is `true`, otherwise `user`).
* - Returns the role and authorization status.
*/

export const verifyRole: QueryResolvers["verifyRole"] = async (
_: unknown,
args: unknown,
{ req }: { req: Request },
) => {
try {
// Extract token from the Authorization header
const authHeader = req.headers.authorization;
if (!authHeader) {
return { role: "", isAuthorized: false };
}
const token = authHeader.startsWith("Bearer ")
? authHeader.split(" ")[1]
: authHeader;
if (!token) {
return { role: "", isAuthorized: false };
}
// Verify token
if (!process.env.ACCESS_TOKEN_SECRET) {
throw new Error("ACCESS_TOKEN_SECRET is not defined");
}
const decoded = jwt.verify(
token,
process.env.ACCESS_TOKEN_SECRET as string,
);
const decodedToken = decoded as InterfaceJwtTokenPayload;
if (!decodedToken.userId) {
throw new Error("Invalid token: userId is missing");
}
const appUserProfile: InterfaceAppUserProfile | null =
await AppUserProfile.findOne({
userId: decodedToken.userId,
appLanguageCode: process.env.DEFAULT_LANGUAGE_CODE || "en",
tokenVersion: process.env.TOKEN_VERSION
? parseInt(process.env.TOKEN_VERSION)
: 0,
});
if (appUserProfile == null || appUserProfile == undefined) {
throw new Error("User profile not found");
}

let role = "user"; // Default role
if (appUserProfile) {
if (appUserProfile.isSuperAdmin) {
role = "superAdmin";
} else if (
appUserProfile.adminFor &&
appUserProfile.adminFor.length > 0
) {
role = "admin";
}
}
return {
role: role,
isAuthorized: true,
};
} catch (error) {
// Log sanitized error for debugging
console.error(
"Token verification failed:",
error instanceof Error ? error.message : "Unknown error",
);
// Return specific error status
const isJwtError = error instanceof jwt.JsonWebTokenError;
return {
role: "",
isAuthorized: false,
error: isJwtError ? "Invalid token" : "Authentication failed",
};
}
};
2 changes: 2 additions & 0 deletions src/typeDefs/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,7 @@ export const queries = gql`
venue(id: ID!): Venue
eventsAttendedByUser(id: ID, orderBy: EventOrderByInput): [Event]
verifyRole: VerifyRoleResponse
}
`;
14 changes: 14 additions & 0 deletions src/typeDefs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,4 +842,18 @@ export const types = gql`
messageContent: String!
messageId: ID!
}
"""
Response type for verifying user roles and their authorization status.
"""
type VerifyRoleResponse {
"""
The role of the user (e.g., 'ADMIN', 'USER', etc.).
"""
role: String!
"""
Whether the user is authorized for the requested action.
"""
isAuthorized: Boolean!
}
`;
20 changes: 20 additions & 0 deletions src/types/generatedGraphQLTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2407,6 +2407,7 @@ export type Query = {
users?: Maybe<Array<Maybe<UserData>>>;
usersConnection: Array<Maybe<UserData>>;
venue?: Maybe<Venue>;
verifyRole?: Maybe<VerifyRoleResponse>;
};


Expand Down Expand Up @@ -3345,6 +3346,15 @@ export type VenueWhereInput = {
name_starts_with?: InputMaybe<Scalars['String']['input']>;
};

/** Response type for verifying user roles and their authorization status. */
export type VerifyRoleResponse = {
__typename?: 'VerifyRoleResponse';
/** Whether the user is authorized for the requested action. */
isAuthorized: Scalars['Boolean']['output'];
/** The role of the user (e.g., 'ADMIN', 'USER', etc.). */
role: Scalars['String']['output'];
};

export type VolunteerMembership = {
__typename?: 'VolunteerMembership';
_id: Scalars['ID']['output'];
Expand Down Expand Up @@ -3725,6 +3735,7 @@ export type ResolversTypes = {
VenueInput: VenueInput;
VenueOrderByInput: VenueOrderByInput;
VenueWhereInput: VenueWhereInput;
VerifyRoleResponse: ResolverTypeWrapper<VerifyRoleResponse>;
VolunteerMembership: ResolverTypeWrapper<InterfaceVolunteerMembershipModel>;
VolunteerMembershipInput: VolunteerMembershipInput;
VolunteerMembershipOrderByInput: VolunteerMembershipOrderByInput;
Expand Down Expand Up @@ -3936,6 +3947,7 @@ export type ResolversParentTypes = {
Venue: InterfaceVenueModel;
VenueInput: VenueInput;
VenueWhereInput: VenueWhereInput;
VerifyRoleResponse: VerifyRoleResponse;
VolunteerMembership: InterfaceVolunteerMembershipModel;
VolunteerMembershipInput: VolunteerMembershipInput;
VolunteerMembershipWhereInput: VolunteerMembershipWhereInput;
Expand Down Expand Up @@ -4903,6 +4915,7 @@ export type QueryResolvers<ContextType = any, ParentType extends ResolversParent
users?: Resolver<Maybe<Array<Maybe<ResolversTypes['UserData']>>>, ParentType, ContextType, Partial<QueryUsersArgs>>;
usersConnection?: Resolver<Array<Maybe<ResolversTypes['UserData']>>, ParentType, ContextType, Partial<QueryUsersConnectionArgs>>;
venue?: Resolver<Maybe<ResolversTypes['Venue']>, ParentType, ContextType, RequireFields<QueryVenueArgs, 'id'>>;
verifyRole?: Resolver<Maybe<ResolversTypes['VerifyRoleResponse']>, ParentType, ContextType>;
};

export type RecurrenceRuleResolvers<ContextType = any, ParentType extends ResolversParentTypes['RecurrenceRule'] = ResolversParentTypes['RecurrenceRule']> = {
Expand Down Expand Up @@ -5102,6 +5115,12 @@ export type VenueResolvers<ContextType = any, ParentType extends ResolversParent
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type VerifyRoleResponseResolvers<ContextType = any, ParentType extends ResolversParentTypes['VerifyRoleResponse'] = ResolversParentTypes['VerifyRoleResponse']> = {
isAuthorized?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
role?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type VolunteerMembershipResolvers<ContextType = any, ParentType extends ResolversParentTypes['VolunteerMembership'] = ResolversParentTypes['VolunteerMembership']> = {
_id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
Expand Down Expand Up @@ -5233,6 +5252,7 @@ export type Resolvers<ContextType = any> = {
UsersConnection?: UsersConnectionResolvers<ContextType>;
UsersConnectionEdge?: UsersConnectionEdgeResolvers<ContextType>;
Venue?: VenueResolvers<ContextType>;
VerifyRoleResponse?: VerifyRoleResponseResolvers<ContextType>;
VolunteerMembership?: VolunteerMembershipResolvers<ContextType>;
VolunteerRank?: VolunteerRankResolvers<ContextType>;
};
Expand Down
2 changes: 1 addition & 1 deletion tests/resolvers/Query/getVolunteerRanks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe("resolvers -> Query -> getVolunteerRanks", () => {
{},
)) as unknown as VolunteerRank[];

expect(volunteerRanks[0].hoursVolunteered).toEqual(6);
expect(volunteerRanks[0].hoursVolunteered).toEqual(2);
expect(volunteerRanks[0].user._id).toEqual(testUser1?._id);
expect(volunteerRanks[0].rank).toEqual(1);
});
Expand Down
Loading

0 comments on commit 595feaa

Please sign in to comment.