Skip to content

Commit 96f88f3

Browse files
authored
feat: support user session management (#1157)
Reviewed-by: @ben-pr-p Reviewed-by: @ajohn25 Reviewed-by: @hiemanshu
1 parent d506a62 commit 96f88f3

27 files changed

+738
-174
lines changed

__test__/lib/session.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const createSession = async (options: CreateSessionOptions) => {
2121
.post("/login-callback")
2222
.send({ authType: "login", email, password })
2323
.then((res) => {
24-
const setCookies: string[] = res.headers["set-cookie"];
24+
const setCookies: string[] = res.headers["set-cookie"] ?? [];
2525
const cookies = setCookies.reduce<Record<string, string>>(
2626
(acc, cookie) => {
2727
const [cookieName, cookieValue] = cookie.split(";")[0].split("=");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Ref: https://github.com/voxpelli/node-connect-pg-simple/blob/main/table.sql
2+
3+
exports.up = function up(knex) {
4+
return knex.schema.raw(`
5+
alter table public.user add column is_suspended boolean not null default false;
6+
7+
create table user_session (
8+
sid text primary key,
9+
sess json not null,
10+
expire timestamptz not null,
11+
user_id integer generated always as ((sess->'passport'->>'user')::integer) stored
12+
);
13+
14+
create index on user_session (expire);
15+
`);
16+
};
17+
18+
exports.down = function down(knex) {
19+
return knex.schema.raw(`
20+
drop table user_session;
21+
alter table public.user drop column is_suspended;
22+
`);
23+
};

package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
"camelcase-keys": "^4.1.0",
9797
"chart.js": "^2.9.4",
9898
"connect-datadog-graphql": "^0.0.11",
99-
"cookie-session": "^1.4.0",
99+
"connect-pg-simple": "^7.0.0",
100100
"css-loader": "^5.0.1",
101101
"dataloader": "^1.2.0",
102102
"dotenv": "^8.0.0",
@@ -106,6 +106,7 @@
106106
"express": "^4.17.3",
107107
"express-basic-auth": "^1.2.1",
108108
"express-rate-limit": "^6.3.0",
109+
"express-session": "^1.17.2",
109110
"express-winston": "^4.2.0",
110111
"fakeredis": "^2.0.0",
111112
"fast-csv": "^4.3.1",
@@ -198,6 +199,7 @@
198199
"@rewired/commitlint-circle": "^3.1.0",
199200
"@types/aphrodite": "^2.0.0",
200201
"@types/chart.js": "^2.9.32",
202+
"@types/connect-pg-simple": "^7.0.0",
201203
"@types/draft-js": "^0.10.45",
202204
"@types/express-rate-limit": "^6.0.0",
203205
"@types/faker": "^5.1.5",
@@ -210,15 +212,15 @@
210212
"@types/node-cron": "^2.0.3",
211213
"@types/nodemailer": "^6.4.0",
212214
"@types/passport": "^1.0.4",
213-
"@types/pg": "^7.14.3",
215+
"@types/pg": "^8.6.5",
214216
"@types/promise-retry": "^1.1.3",
215217
"@types/react": "^16.14.0",
216218
"@types/react-dom": "^16.9.11",
217219
"@types/react-router": "^5.1.5",
218220
"@types/react-router-dom": "^5.1.7",
219221
"@types/react-select": "^2.0.0",
220222
"@types/recompose": "^0.30.7",
221-
"@types/redis": "^2.8.28",
223+
"@types/redis": "^2.8.32",
222224
"@types/superagent": "^4.1.7",
223225
"@types/supertest": "^2.0.11",
224226
"@types/webpack": "^5.28.0",

schema-dump.sql

+31-1
Original file line numberDiff line numberDiff line change
@@ -3037,7 +3037,8 @@ CREATE TABLE public."user" (
30373037
assigned_cell text,
30383038
is_superadmin boolean,
30393039
terms boolean DEFAULT false,
3040-
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP
3040+
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
3041+
is_suspended boolean DEFAULT false NOT NULL
30413042
);
30423043

30433044

@@ -3142,6 +3143,20 @@ ALTER TABLE public.user_organization_id_seq OWNER TO postgres;
31423143
ALTER SEQUENCE public.user_organization_id_seq OWNED BY public.user_organization.id;
31433144

31443145

3146+
--
3147+
-- Name: user_session; Type: TABLE; Schema: public; Owner: postgres
3148+
--
3149+
3150+
CREATE TABLE public.user_session (
3151+
sid text NOT NULL,
3152+
sess json NOT NULL,
3153+
expire timestamp with time zone NOT NULL,
3154+
user_id integer GENERATED ALWAYS AS ((((sess -> 'passport'::text) ->> 'user'::text))::integer) STORED
3155+
);
3156+
3157+
3158+
ALTER TABLE public.user_session OWNER TO postgres;
3159+
31453160
--
31463161
-- Name: user_team; Type: TABLE; Schema: public; Owner: postgres
31473162
--
@@ -3950,6 +3965,14 @@ ALTER TABLE ONLY public."user"
39503965
ADD CONSTRAINT user_pkey PRIMARY KEY (id);
39513966

39523967

3968+
--
3969+
-- Name: user_session user_session_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
3970+
--
3971+
3972+
ALTER TABLE ONLY public.user_session
3973+
ADD CONSTRAINT user_session_pkey PRIMARY KEY (sid);
3974+
3975+
39533976
--
39543977
-- Name: user_team user_team_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
39553978
--
@@ -4464,6 +4487,13 @@ CREATE INDEX user_organization_organization_id_user_id_index ON public.user_orga
44644487
CREATE INDEX user_organization_user_id_index ON public.user_organization USING btree (user_id);
44654488

44664489

4490+
--
4491+
-- Name: user_session_expire_idx; Type: INDEX; Schema: public; Owner: postgres
4492+
--
4493+
4494+
CREATE INDEX user_session_expire_idx ON public.user_session USING btree (expire);
4495+
4496+
44674497
--
44684498
-- Name: assignment_request _500_assignment_request_updated_at; Type: TRIGGER; Schema: public; Owner: postgres
44694499
--

src/api/schema.ts

+2
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ const rootSchema = `
254254
editUser(organizationId: String!, userId: String!, userData:UserInput): User
255255
resetUserPassword(organizationId: String!, userId: String!): String!
256256
changeUserPassword(userId: String!, formData: UserPasswordChange): User
257+
setUserSuspended(userId: String!, isSuspended: Boolean!): User!
258+
clearUserSessions(userId: String!): User!
257259
updateTextingHours( organizationId: String!, textingHoursStart: Int!, textingHoursEnd: Int!): Organization
258260
updateTextingHoursEnforcement( organizationId: String!, textingHoursEnforced: Boolean!): Organization
259261
updateTextRequestFormSettings(organizationId: String!, textRequestFormEnabled: Boolean!, textRequestType: String!, textRequestMaxCount: Int!): Organization

src/api/user.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface User {
2525
assignment: Assignment;
2626
terms: boolean;
2727
isSuperadmin: boolean;
28+
isSuspended: boolean;
2829
}
2930

3031
export const schema = `
@@ -45,6 +46,7 @@ export const schema = `
4546
assignment(campaignId: String): Assignment,
4647
terms: Boolean
4748
isSuperadmin: Boolean!
49+
isSuspended: Boolean!
4850
}
4951
5052
type UsersList {

src/containers/AdminPeople/AdminPeople.tsx

+57
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,22 @@ export interface AdminPeopleMutations {
9999
resetUserPassword: string;
100100
}>
101101
>;
102+
setUserSuspended: (
103+
userId: string,
104+
isSuspended: boolean
105+
) => Promise<
106+
ApolloQueryResult<{
107+
id: string;
108+
isSuspended: boolean;
109+
}>
110+
>;
111+
clearUserSessions: (
112+
userId: string
113+
) => Promise<
114+
ApolloQueryResult<{
115+
id: string;
116+
}>
117+
>;
102118
removeUsers: () => Promise<
103119
ApolloQueryResult<{
104120
purgeOrganizationUsers: number;
@@ -321,6 +337,14 @@ class AdminPeople extends React.Component<
321337
}
322338
};
323339

340+
handleSetSuspended = async (userId: string, isSuspended: boolean) => {
341+
await this.props.mutations.setUserSuspended(userId, isSuspended);
342+
};
343+
344+
handleClearSessions = async (userId: string) => {
345+
await this.props.mutations.clearUserSessions(userId);
346+
};
347+
324348
ctx(): AdminPeopleContext {
325349
const { campaignId } = queryString.parse(this.props.location.search);
326350
return {
@@ -353,6 +377,9 @@ class AdminPeople extends React.Component<
353377
editAutoApprove: (autoApprove, userId) =>
354378
this.handleEditAutoApprove(autoApprove, userId),
355379
resetUserPassword: (userId) => this.handleResetPassword(userId),
380+
setSuspended: (userId, isSuspended) =>
381+
this.handleSetSuspended(userId, isSuspended),
382+
clearSessions: (userId) => this.handleClearSessions(userId),
356383
error: (message) => this.setState({ error: { message, seen: false } })
357384
};
358385
}
@@ -557,6 +584,36 @@ const mutations: MutationMap<AdminPeopleExtendedProps> = {
557584
}
558585
};
559586
},
587+
setUserSuspended: (_ownProps) => (userId: string, isSuspended: boolean) => {
588+
return {
589+
mutation: gql`
590+
mutation SetUserSuspended($userId: String!, $isSuspended: Boolean!) {
591+
setUserSuspended(userId: $userId, isSuspended: $isSuspended) {
592+
id
593+
isSuspended
594+
}
595+
}
596+
`,
597+
variables: {
598+
userId,
599+
isSuspended
600+
}
601+
};
602+
},
603+
clearUserSessions: (_ownProps) => (userId: string) => {
604+
return {
605+
mutation: gql`
606+
mutation ClearUserSessions($userId: String!) {
607+
clearUserSessions(userId: $userId) {
608+
id
609+
}
610+
}
611+
`,
612+
variables: {
613+
userId
614+
}
615+
};
616+
},
560617
removeUsers: (ownProps) => () => {
561618
return {
562619
mutation: gql`

0 commit comments

Comments
 (0)