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

Feature/added GraphQl Quries in server to verify User Role and Its authorization So that it can be helpFul for client side. #3094

Merged
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
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
}

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");

Check warning on line 43 in src/resolvers/Query/verifyRole.ts

View check run for this annotation

Codecov / codecov/patch

src/resolvers/Query/verifyRole.ts#L43

Added line #L43 was not covered by tests
}
const decoded = jwt.verify(
token,
process.env.ACCESS_TOKEN_SECRET as string,
);
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
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,

Check warning on line 59 in src/resolvers/Query/verifyRole.ts

View check run for this annotation

Codecov / codecov/patch

src/resolvers/Query/verifyRole.ts#L59

Added line #L59 was not covered by tests
});
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>;
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
};


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'];
};
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

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>;
};
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

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
Loading