Skip to content

Commit 56a4c79

Browse files
committed
WIP: consolidate the login code to the session store
WIP: create inflator and subsequent code cleanup.
1 parent 4bad6ca commit 56a4c79

File tree

8 files changed

+61
-59
lines changed

8 files changed

+61
-59
lines changed

src/common/models/session.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { ParseableUser, User } from 'src/common/models/users';
1+
import type { SessionUser, User } from 'src/common/models/users';
22
import { parseBoolean } from './parsers';
33

44
export interface SessionInfo {
5-
user: ParseableUser;
5+
user: SessionUser;
66
logged_in: boolean;
77
message: string;
88
}

src/common/models/users.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@ import { isNonNegInt, isValidUsername, isValidEmail }
66
import { Dictionary, Model } from 'src/common/models';
77
import { UserRole } from 'src/stores/permissions';
88

9-
export interface ParseableUser {
10-
user_id?: number;
11-
username?: string;
9+
// This defined the required fields of a user in the Session
10+
11+
export interface SessionUser {
12+
user_id: number;
13+
username: string;
1214
email?: string;
1315
first_name?: string;
1416
last_name?: string;
15-
is_admin?: boolean;
17+
is_admin: boolean;
1618
student_id?: string;
1719
}
1820

21+
export type ParseableUser = Partial<SessionUser>;
22+
1923
/**
2024
* @class User
2125
*/
@@ -34,15 +38,15 @@ export class User extends Model {
3438
get all_field_names(): string[] { return User.ALL_FIELDS; }
3539
get param_fields(): string[] { return []; }
3640

37-
constructor(params: ParseableUser = {}) {
41+
constructor(params: ParseableUser = { user_id: 0, username: '', is_admin: false }) {
3842
super();
3943
this.set(params);
4044
}
4145

4246
set(params: ParseableUser) {
4347
if (params.username) this.username = params.username;
44-
if (params.user_id != undefined) this.user_id = params.user_id;
45-
if (params.is_admin != undefined) this.is_admin = params.is_admin;
48+
if (params.user_id) this.user_id = params.user_id;
49+
if (params.is_admin) this.is_admin = params.is_admin;
4650
if (params.email) this.email = params.email;
4751
if (params.first_name) this.first_name = params.first_name;
4852
if (params.last_name) this.last_name = params.last_name;
@@ -72,7 +76,7 @@ export class User extends Model {
7276
set student_id(value: string) { this._student_id = value; }
7377

7478
clone() {
75-
return new User(this.toObject() as ParseableUser);
79+
return new User(this.toObject() as unknown as ParseableUser);
7680
}
7781

7882
// The email can be the empty string or valid email address.

src/components/common/Login.vue

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import { useRouter } from 'vue-router';
2525
import { ref } from 'vue';
2626
import { useI18n } from 'vue-i18n';
27-
import { checkPassword } from 'src/common/api-requests/session';
2827
import { useSessionStore } from 'src/stores/session';
2928
import { usePermissionStore } from 'src/stores/permissions';
3029
@@ -54,25 +53,17 @@ const login = async () => {
5453
username: username.value,
5554
password: password.value
5655
};
57-
const session_info = await checkPassword(username_info);
58-
59-
if (!session_info.logged_in || !session_info.user.user_id) {
60-
message.value = i18n.t('authentication.failure');
61-
} else {
62-
// success
63-
session.updateSessionInfo(session_info);
64-
65-
// permissions require access to user courses and respective roles
66-
await session.fetchUserCourses();
67-
await permission_store.fetchRoles();
68-
await permission_store.fetchRoutePermissions();
69-
56+
const login_successful = await session.login(username_info);
57+
if (login_successful) {
58+
const user = session.user;
7059
let forward = localStorage.getItem('afterLogin');
71-
forward ||= (session_info.user.is_admin) ?
60+
forward ||= (user.is_admin) ?
7261
'/admin' :
73-
`/users/${session_info.user.user_id}/courses`;
62+
`/users/${user.user_id}/courses`;
7463
localStorage.removeItem('afterLogin');
7564
void router.push(forward);
65+
} else {
66+
message.value = i18n.t('authentication.failure');
7667
}
7768
};
7869
</script>

src/stores/session.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
import { defineStore } from 'pinia';
44
import { api } from 'boot/axios';
5-
import { ParseableUser, User } from 'src/common/models/users';
6-
import type { SessionInfo } from 'src/common/models/session';
5+
import { SessionUser, User } from 'src/common/models/users';
6+
import { ParseableSessionInfo, parseSessionInfo, SessionInfo, UserPassword } from 'src/common/models/session';
77
import { ParseableUserCourse } from 'src/common/models/courses';
88
import { logger } from 'boot/logger';
99
import { ResponseError } from 'src/common/api-requests/errors';
1010

1111
import { useUserStore } from 'src/stores/users';
1212
import { useSettingsStore } from 'src/stores/settings';
1313
import { useProblemSetStore } from 'src/stores/problem_sets';
14-
import { UserRole } from 'src/stores/permissions';
14+
import { usePermissionStore, UserRole } from 'src/stores/permissions';
1515

1616
interface CourseInfo {
1717
course_name: string;
@@ -25,18 +25,20 @@ interface CourseInfo {
2525
export interface SessionState {
2626
logged_in: boolean;
2727
expiry: number;
28-
user: ParseableUser;
28+
user: SessionUser;
2929
course: CourseInfo;
3030
user_courses: ParseableUserCourse[];
3131
}
3232

33+
const logged_out_user = { username: 'logged_out', user_id: 0, is_admin: false };
34+
3335
export const useSessionStore = defineStore('session', {
3436
// Stores this in localStorage.
3537
persist: true,
3638
state: (): SessionState => ({
3739
logged_in: false,
3840
expiry: 0,
39-
user: { username: 'logged_out' },
41+
user: logged_out_user,
4042
course: {
4143
course_id: 0,
4244
role: '',
@@ -45,7 +47,7 @@ export const useSessionStore = defineStore('session', {
4547
user_courses: []
4648
}),
4749
getters: {
48-
full_name: (state): string => `${state.user?.first_name ?? ''} ${state.user?.last_name ?? ''}`,
50+
full_name: (state): string => `${state.user.first_name ?? ''} ${state.user.last_name ?? ''}`,
4951
getUser: (state): User => new User(state.user),
5052
},
5153
actions: {
@@ -63,7 +65,7 @@ export const useSessionStore = defineStore('session', {
6365
if (this.logged_in) {
6466
this.user = session_info.user;
6567
} else {
66-
this.user = new User({ username: 'logged_out' }).toObject();
68+
this.user = logged_out_user;
6769
}
6870
},
6971
setCourse(course_id: number): void {
@@ -102,9 +104,29 @@ export const useSessionStore = defineStore('session', {
102104
throw response.data as ResponseError;
103105
}
104106
},
107+
/**
108+
* Attempt to login to webwork3 with username/password. If successful, fetch
109+
* needed data (usercourses, roles, permissions).
110+
*/
111+
async login(user_pass: UserPassword): Promise<boolean> {
112+
const response = await api.post('login', user_pass);
113+
const session_info = parseSessionInfo(response.data as ParseableSessionInfo);
114+
if (!session_info.logged_in || !session_info.user.user_id) {
115+
return false;
116+
} else {
117+
// success
118+
this.updateSessionInfo(session_info);
119+
const permission_store = usePermissionStore();
120+
// permissions require access to user courses and respective roles
121+
await this.fetchUserCourses();
122+
await permission_store.fetchRoles();
123+
await permission_store.fetchRoutePermissions();
124+
return true;
125+
}
126+
},
105127
logout() {
106128
this.logged_in = false;
107-
this.user = new User({ username: 'logged_out' }).toObject();
129+
this.user = logged_out_user;
108130
this.course = { course_id: 0, role: '', course_name: '' };
109131
useProblemSetStore().clearAll();
110132
useSettingsStore().clearAll();

tests/stores/session.spec.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ import { api } from 'boot/axios';
1515

1616
import { getUser } from 'src/common/api-requests/user';
1717
import { useSessionStore } from 'src/stores/session';
18-
import { checkPassword } from 'src/common/api-requests/session';
1918

2019
import { Course, UserCourse } from 'src/common/models/courses';
2120
import { SessionInfo } from 'src/common/models/session';
22-
import { ParseableUser, User } from 'src/common/models/users';
21+
import { SessionUser, User } from 'src/common/models/users';
2322

2423
import { cleanIDs, loadCSV } from '../utils';
2524

@@ -30,7 +29,7 @@ describe('Session Store', () => {
3029
let lisa: User;
3130

3231
// session now just stores objects not models:
33-
const user: ParseableUser = {
32+
const user: SessionUser = {
3433
first_name: 'Homer',
3534
last_name: 'Simpson',
3635
user_id: 1234,
@@ -39,14 +38,10 @@ describe('Session Store', () => {
3938
is_admin: false,
4039
};
4140

42-
const logged_out: ParseableUser = {
41+
const logged_out: SessionUser = {
4342
username: 'logged_out',
44-
email: '',
45-
last_name: '',
46-
first_name: '',
4743
user_id: 0,
4844
is_admin: false,
49-
student_id: ''
5045
};
5146

5247
const session_info: SessionInfo = {
@@ -114,19 +109,12 @@ describe('Session Store', () => {
114109
});
115110

116111
test('Login as a user', async () => {
117-
// test logging in as lisa gives the proper courses.
118-
const session_info = await checkPassword({
119-
username: 'lisa', password: 'lisa'
120-
});
121-
expect(session_info.logged_in).toBe(true);
122-
expect(session_info.user).toStrictEqual(lisa.toObject());
123-
112+
// test logging in as lisa gives the proper courses.
124113
const session = useSessionStore();
125-
session.updateSessionInfo(session_info);
126-
114+
const logged_in = await session.login({ username: 'lisa', password: 'lisa' });
115+
expect(logged_in).toBe(true);
127116
expect(session.logged_in).toBe(true);
128117
expect(session.user).toStrictEqual(lisa.toObject());
129-
130118
});
131119

132120
// sort by course name and clean up the _id tags.
@@ -135,9 +123,8 @@ describe('Session Store', () => {
135123
a.course_name < b.course_name ? -1 : a.course_name > b.course_name ? 1 : 0));
136124
};
137125

138-
test('check user courses', async () => {
126+
test('check user courses', () => {
139127
const session_store = useSessionStore();
140-
await session_store.fetchUserCourses();
141128
expect(sortAndClean(session_store.user_courses.map(c => new UserCourse(c))))
142129
.toStrictEqual(sortAndClean(lisa_courses));
143130
});

tests/stores/set_problems.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { Dictionary, generic } from 'src/common/models';
2727

2828
import { loadCSV, cleanIDs } from '../utils';
2929
import { checkPassword } from 'src/common/api-requests/session';
30-
import { logger } from 'src/boot/logger';
3130

3231
const app = createApp({});
3332

tests/stores/user_sets.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import { createApp } from 'vue';
1212
import { createPinia, setActivePinia } from 'pinia';
1313
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
14-
import { api } from 'boot/axios';
1514

1615
import { useCourseStore } from 'src/stores/courses';
1716
import { useProblemSetStore } from 'src/stores/problem_sets';

tests/unit-tests/users.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,23 @@ describe('Testing User and CourseUsers', () => {
9696
describe('Testing for valid and invalid users.', () => {
9797

9898
test('setting invalid email', () => {
99-
const user = new User({ username: 'test' });
99+
const user = new User({ username: 'test', user_id: 10, is_admin: true });
100100
expect(user.isValid()).toBe(true);
101101

102102
user.email = 'bad@[email protected]';
103103
expect(user.isValid()).toBe(false);
104104
});
105105

106106
test('setting invalid user_id', () => {
107-
const user = new User({ username: 'test' });
107+
const user = new User({ username: 'test', user_id: 10, is_admin: true });
108108
expect(user.isValid()).toBe(true);
109109

110110
user.user_id = -15;
111111
expect(user.isValid()).toBe(false);
112112
});
113113

114114
test('setting invalid username', () => {
115-
const user = new User({ username: 'my username' });
115+
const user = new User({ username: 'my username', user_id: 10, is_admin: true });
116116
expect(user.isValid()).toBe(false);
117117
});
118118
});

0 commit comments

Comments
 (0)