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

session management feature for merge #2556

Merged
merged 6 commits into from
Oct 13, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Core features include:

`talawa` is based on the original `quito` code created by the [Palisadoes Foundation](http://www.palisadoes.org) as part of its annual Calico Challenge program. Calico provides paid summer internships for Jamaican university students to work on selected open source projects. They are mentored by software professionals and receive stipends based on the completion of predefined milestones. Calico was started in 2015. Visit [The Palisadoes Foundation's website](http://www.palisadoes.org/) for more details on its origin and activities.

## Table of Contents
## Table of Contents

<!-- toc -->

Expand Down
1 change: 1 addition & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"invalid.fileType": "Invalid file type",
"invalid.refreshToken": "Invalid refresh token",
"invalid.credentials": "Invalid credentials",
"invalid.timeoutRange": "Invalid timeout range",
"registrant.alreadyExist": "Already registered for the event",
"member.notFound": "Member not found",
"registrant.alreadyUnregistered": "Already unregistered for the event",
Expand Down
1 change: 1 addition & 0 deletions locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"invalid.fileType": "Type de fichier non valide",
"invalid.refreshToken": "Jeton d'actualisation non valide",
"invalid.credentials": "Informations d'identification non valides",
"invalid.timeoutRange": "Plage de temps d'attente non valide",
"registrant.alreadyExist": "Déjà inscrit à l'événement",
"member.notFound": "Membre introuvable",
"registrant.alreadyUnregistered": "Déjà non inscrit à l'événement",
Expand Down
1 change: 1 addition & 0 deletions locales/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"invalid.fileType": "अमान्य फ़ाइल प्रकार",
"invalid.refreshToken": "अमान्य रीफ़्रेश टोकन",
"invalid.credentials": "अवैध प्रत्यय पत्र",
"invalid.timeoutRange": "अमान्य टाइमआउट रेंज",
"registrant.alreadyExist": "घटना के लिए पहले से पंजीकृत",
"member.notFound": "सदस्य अनुपस्थित",
"registrant.alreadyUnregistered": "घटना के लिए पहले से ही अपंजीकृत",
Expand Down
1 change: 1 addition & 0 deletions locales/sp.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"invalid.fileType": "Tipo de archivo no válido",
"invalid.refreshToken": "Token de actualización no válido",
"invalid.credentials": "Credenciales no válidas",
"invalid.timeoutRange": "Rango de tiempo de espera no válido",
"registrant.alreadyExist": "Ya inscrito para el evento",
"member.notFound": "Miembro no encontrado",
"registrant.alreadyUnregistered": "Ya no está registrado para el evento",
Expand Down
1 change: 1 addition & 0 deletions locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"invalid.fileType": "無效的文件類型",
"invalid.refreshToken": "無效的刷新令牌",
"invalid.credentials": "無效的憑據",
"invalid.timeoutRange": "无效的超时范围",
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved
"registrant.alreadyExist": "已经报名参加活动",
"member.notFound": "未找到成员",
"registrant.alreadyUnregistered": "已取消注册该活动",
Expand Down
2 changes: 2 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ type Community {
logoUrl: String
name: String!
socialMediaUrls: SocialMediaUrls
timeout: Int
websiteLink: String
}

Expand Down Expand Up @@ -1192,6 +1193,7 @@ type Mutation {
updateOrganization(data: UpdateOrganizationInput, file: String, id: ID!): Organization!
updatePluginStatus(id: ID!, orgId: ID!): Plugin!
updatePost(data: PostUpdateInput, id: ID!): Post!
updateSessionTimeout(timeout: Int!): Boolean!
updateUserPassword(data: UpdateUserPasswordInput!): UserData!
updateUserProfile(data: UpdateUserInput, file: String): User!
updateUserRoleInOrganization(organizationId: ID!, role: String!, userId: ID!): Organization!
Expand Down
25 changes: 25 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export const AGENDA_CATEGORY_NOT_FOUND_ERROR = Object.freeze({
PARAM: "agendaCategory",
});

export const APP_USER_PROFILE_NOT_FOUND_ERROR = Object.freeze({
DESC: "appUserProfile not found",
CODE: "appUserProfile.notFound",
MESSAGE: "appUserProfile.notFound",
PARAM: "appUserProfile",
});

export const BASE_RECURRING_EVENT_NOT_FOUND = Object.freeze({
DESC: "Base Recurring Event not found",
CODE: "baseRecurringEvent.notFound",
Expand All @@ -57,6 +64,13 @@ export const CHAT_NOT_FOUND_ERROR = Object.freeze({
PARAM: "chat",
});

export const COMMUNITY_NOT_FOUND_ERROR = Object.freeze({
DESC: "Community not found",
CODE: "community.notFound",
MESSAGE: "community.notFound",
PARAM: "community",
});

export const VENUE_ALREADY_EXISTS_ERROR = Object.freeze({
DESC: "Venue already exists",
CODE: "venue.alreadyExists",
Expand Down Expand Up @@ -144,6 +158,14 @@ export const FUND_NOT_FOUND_ERROR = Object.freeze({
export const INVALID_OTP = "Invalid OTP";

export const IN_PRODUCTION = process.env.NODE_ENV === "production";

export const INVALID_TIMEOUT_RANGE = Object.freeze({
DESC: "Timeout should be in the range of 15 to 60 minutes.",
CODE: "invalid.timeoutRange",
MESSAGE: "invalid.timeoutRange",
PARAM: "timeout",
});

export const MEMBER_NOT_FOUND_ERROR = Object.freeze({
DESC: "Member not found",
CODE: "member.notFound",
Expand Down Expand Up @@ -696,6 +718,9 @@ export const PRELOGIN_IMAGERY_FIELD_EMPTY = Object.freeze({
PARAM: "preLoginImagery.empty",
});

export const MINIMUM_TIMEOUT_MINUTES = 15;
export const MAXIMUM_TIMEOUT_MINUTES = 60;

export const MAXIMUM_FETCH_LIMIT = 100;

export const MAXIMUM_IMAGE_SIZE_LIMIT_KB = 20000;
Expand Down
10 changes: 10 additions & 0 deletions src/models/Community.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface InterfaceCommunity {
slack: string;
reddit: string;
}; // Object containing various social media URLs for the community.
timeout: number;
}

/**
Expand All @@ -35,6 +36,9 @@ export interface InterfaceCommunity {
* @param youTube - YouTube URL.
* @param slack - Slack URL.
* @param reddit - Reddit URL.
* @param websiteLink - Community website URL.
* @param name - Community name.
* @param timeout - Timeout duration in minutes (default is 30 minutes).
*/
const communitySchema = new Schema({
name: {
Expand Down Expand Up @@ -73,6 +77,12 @@ const communitySchema = new Schema({
type: String,
},
},
timeout: {
type: Number,
default: 30,
min: [15, "Timeout should be at least 15 minutes."],
max: [60, "Timeout should not exceed 60 minutes."],
},
});

/**
Expand Down
2 changes: 2 additions & 0 deletions src/resolvers/Mutation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ import { updateLanguage } from "./updateLanguage";
import { updateOrganization } from "./updateOrganization";
import { updatePluginStatus } from "./updatePluginStatus";
import { updatePost } from "./updatePost";
import { updateSessionTimeout } from "./updateSessionTimeout";
import { updateUserPassword } from "./updateUserPassword";
import { updateUserProfile } from "./updateUserProfile";
import { updateUserRoleInOrganization } from "./updateUserRoleInOrganization";
Expand Down Expand Up @@ -236,6 +237,7 @@ export const Mutation: MutationResolvers = {
updateLanguage,
updateOrganization,
updatePluginStatus,
updateSessionTimeout,
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved
updateUserProfile,
updateUserPassword,
updateUserTag,
Expand Down
30 changes: 22 additions & 8 deletions src/resolvers/Mutation/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
createRefreshToken,
} from "../../utilities";
/**
* This function enables login.
* This function enables login. (note: only works when using the last resort SuperAdmin credentials)
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved
* @param _parent - parent of current request
* @param args - payload provided with the request
* @remarks The following checks are done:
Expand Down Expand Up @@ -55,29 +55,43 @@ export const login: MutationResolvers["login"] = async (_parent, args) => {
}
let appUserProfile: InterfaceAppUserProfile | null =
await AppUserProfile.findOne({
userId: user._id.toString(),
userId: user._id,
appLanguageCode: "en",
tokenVersion: 0,
}).lean();

if (!appUserProfile) {
appUserProfile = await AppUserProfile.create({
userId: user._id.toString(),
userId: user._id,
appLanguageCode: "en",
tokenVersion: 0,
isSuperAdmin: false,
});
await User.updateOne(

user = await User.findOneAndUpdate(
{
_id: user._id.toString(),
_id: user._id,
},
{
appUserProfileId: appUserProfile?._id?.toString(),
appUserProfileId: appUserProfile?._id,
},
{ new: true, lean: true },
);

// user = await User.findOne({
// email: args.data.email.toLowerCase(),
// }).lean();

if (!user) {
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved
}

const accessToken = createAccessToken(
const accessToken = await createAccessToken(
user,
appUserProfile as InterfaceAppUserProfile,
);
Expand All @@ -104,7 +118,7 @@ export const login: MutationResolvers["login"] = async (_parent, args) => {
// );
await AppUserProfile.findOneAndUpdate(
{
user: user._id,
_id: user.appUserProfileId,
},
{
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved
isSuperAdmin: true,
Expand Down
88 changes: 88 additions & 0 deletions src/resolvers/Mutation/updateSessionTimeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { InterfaceAppUserProfile } from "../../models";
import { User, AppUserProfile } from "../../models";
import {
COMMUNITY_NOT_FOUND_ERROR,
INVALID_TIMEOUT_RANGE,
USER_NOT_FOUND_ERROR,
APP_USER_PROFILE_NOT_FOUND_ERROR,
MINIMUM_TIMEOUT_MINUTES,
MAXIMUM_TIMEOUT_MINUTES,
} from "../../constants";
import type { MutationResolvers } from "../../types/generatedGraphQLTypes";
import { errors, requestContext } from "../../libraries";
import { superAdminCheck } from "../../utilities";
import { Community } from "../../models/Community";

/**
* This function updates the session timeout and can only be performed by superadmin users.
* @param _parent - parent of the current request
* @param args - payload provided with the request, including organizationId and timeout
* @param context - context of the entire application, containing user information
* @returns - A message true if the organization timeout is updated successfully
* @throws - NotFoundError: If the user, appuserprofile or organization is not found
* @throws - ValidationError: If the user is not an admin or superadmin, or if the timeout is outside the valid range
* @throws - InternalServerError: If there is an error updating the organization timeout
*/
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved

export const updateSessionTimeout: MutationResolvers["updateSessionTimeout"] =
async (_parent, args, context) => {
const userId = context.userId;
const user = await User.findById(userId).lean();

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

//const appuserprofile: InterfaceAppUserProfile | null = await AppUserProfile.findOne({userId: userId}).lean();
const appuserprofile: InterfaceAppUserProfile | null =
await AppUserProfile.findById(user.appUserProfileId).lean(); //more appropriate since it shows the link between the user and the userprofile

if (!appuserprofile) {
throw new errors.NotFoundError(
requestContext.translate(APP_USER_PROFILE_NOT_FOUND_ERROR.MESSAGE),
APP_USER_PROFILE_NOT_FOUND_ERROR.CODE,
APP_USER_PROFILE_NOT_FOUND_ERROR.PARAM,
);
}

superAdminCheck(appuserprofile);

const community = await Community.findOne().lean();

if (!community) {
throw new errors.NotFoundError(
requestContext.translate(COMMUNITY_NOT_FOUND_ERROR.MESSAGE),
COMMUNITY_NOT_FOUND_ERROR.CODE,
COMMUNITY_NOT_FOUND_ERROR.PARAM,
);
}

if (
args.timeout < MINIMUM_TIMEOUT_MINUTES ||
args.timeout > MAXIMUM_TIMEOUT_MINUTES ||
args.timeout % 5 !== 0
) {
throw new errors.ValidationError(
[
{
message: requestContext.translate(INVALID_TIMEOUT_RANGE.MESSAGE),
code: INVALID_TIMEOUT_RANGE.CODE,
param: INVALID_TIMEOUT_RANGE.PARAM,
},
],
INVALID_TIMEOUT_RANGE.MESSAGE,
);
}
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved

await Community.findByIdAndUpdate(
community._id,
{ timeout: args.timeout },
{ new: true },
);
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved

return true;
JordanCampbell1 marked this conversation as resolved.
Show resolved Hide resolved
};
2 changes: 1 addition & 1 deletion src/setup/superAdmin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function askForSuperAdminEmail(): Promise<string> {
name: "email",
message:
"Enter the email which you wish to assign as the Super Admin of last resort :",
validate: (input: string) =>
validate: (input: string): boolean | string =>
isValidEmail(input) || "Invalid email. Please try again.",
},
]);
Expand Down
2 changes: 2 additions & 0 deletions src/typeDefs/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ export const mutations = gql`

updatePluginStatus(id: ID!, orgId: ID!): Plugin!

updateSessionTimeout(timeout: Int!): Boolean! @auth

updateUserTag(input: UpdateUserTagInput!): UserTag @auth

updateUserProfile(data: UpdateUserInput, file: String): User! @auth
Expand Down
1 change: 1 addition & 0 deletions src/typeDefs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export const types = gql`
logoUrl: String
websiteLink: String
socialMediaUrls: SocialMediaUrls
timeout: Int
}
type CreateAdminPayload {
user: AppUserProfile
Expand Down
9 changes: 9 additions & 0 deletions src/types/generatedGraphQLTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export type Community = {
logoUrl?: Maybe<Scalars['String']['output']>;
name: Scalars['String']['output'];
socialMediaUrls?: Maybe<SocialMediaUrls>;
timeout?: Maybe<Scalars['Int']['output']>;
websiteLink?: Maybe<Scalars['String']['output']>;
};

Expand Down Expand Up @@ -1283,6 +1284,7 @@ export type Mutation = {
updateOrganization: Organization;
updatePluginStatus: Plugin;
updatePost: Post;
updateSessionTimeout: Scalars['Boolean']['output'];
updateUserPassword: UserData;
updateUserProfile: User;
updateUserRoleInOrganization: Organization;
Expand Down Expand Up @@ -1918,6 +1920,11 @@ export type MutationUpdatePostArgs = {
};


export type MutationUpdateSessionTimeoutArgs = {
timeout: Scalars['Int']['input'];
};


export type MutationUpdateUserPasswordArgs = {
data: UpdateUserPasswordInput;
};
Expand Down Expand Up @@ -3911,6 +3918,7 @@ export type CommunityResolvers<ContextType = any, ParentType extends ResolversPa
logoUrl?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
socialMediaUrls?: Resolver<Maybe<ResolversTypes['SocialMediaUrls']>, ParentType, ContextType>;
timeout?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
websiteLink?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
Expand Down Expand Up @@ -4428,6 +4436,7 @@ export type MutationResolvers<ContextType = any, ParentType extends ResolversPar
updateOrganization?: Resolver<ResolversTypes['Organization'], ParentType, ContextType, RequireFields<MutationUpdateOrganizationArgs, 'id'>>;
updatePluginStatus?: Resolver<ResolversTypes['Plugin'], ParentType, ContextType, RequireFields<MutationUpdatePluginStatusArgs, 'id' | 'orgId'>>;
updatePost?: Resolver<ResolversTypes['Post'], ParentType, ContextType, RequireFields<MutationUpdatePostArgs, 'id'>>;
updateSessionTimeout?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationUpdateSessionTimeoutArgs, 'timeout'>>;
updateUserPassword?: Resolver<ResolversTypes['UserData'], ParentType, ContextType, RequireFields<MutationUpdateUserPasswordArgs, 'data'>>;
updateUserProfile?: Resolver<ResolversTypes['User'], ParentType, ContextType, Partial<MutationUpdateUserProfileArgs>>;
updateUserRoleInOrganization?: Resolver<ResolversTypes['Organization'], ParentType, ContextType, RequireFields<MutationUpdateUserRoleInOrganizationArgs, 'organizationId' | 'role' | 'userId'>>;
Expand Down
Loading