feat: Mechanisme voor automatische aanmaak en update van accounts aangemaakt.
This commit is contained in:
parent
6cb8a1b98f
commit
9339eca9cf
9 changed files with 84 additions and 23 deletions
|
@ -1,4 +1,9 @@
|
||||||
import { envVars, getEnvVar } from '../util/envVars.js';
|
import { envVars, getEnvVar } from '../util/envVars.js';
|
||||||
|
import {AuthenticatedRequest} from "../middleware/auth/authenticated-request";
|
||||||
|
import {createStudent} from "../services/students";
|
||||||
|
import {AuthenticationInfo} from "../middleware/auth/authentication-info";
|
||||||
|
import {Request, Response} from "express";
|
||||||
|
import {createTeacher} from "../services/teachers";
|
||||||
|
|
||||||
interface FrontendIdpConfig {
|
interface FrontendIdpConfig {
|
||||||
authority: string;
|
authority: string;
|
||||||
|
@ -15,7 +20,7 @@ interface FrontendAuthConfig {
|
||||||
const SCOPE = 'openid profile email';
|
const SCOPE = 'openid profile email';
|
||||||
const RESPONSE_TYPE = 'code';
|
const RESPONSE_TYPE = 'code';
|
||||||
|
|
||||||
export function getFrontendAuthConfig(): FrontendAuthConfig {
|
function getFrontendAuthConfig(): FrontendAuthConfig {
|
||||||
return {
|
return {
|
||||||
student: {
|
student: {
|
||||||
authority: getEnvVar(envVars.IdpStudentUrl),
|
authority: getEnvVar(envVars.IdpStudentUrl),
|
||||||
|
@ -31,3 +36,26 @@ export function getFrontendAuthConfig(): FrontendAuthConfig {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function handleGetFrontendAuthConfig(_req: Request, res: Response): void {
|
||||||
|
res.json(getFrontendAuthConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleHello(req: AuthenticatedRequest) {
|
||||||
|
const auth: AuthenticationInfo = req.auth!;
|
||||||
|
if (auth.accountType === "teacher") {
|
||||||
|
await createTeacher({
|
||||||
|
id: auth.username,
|
||||||
|
username: auth.username,
|
||||||
|
firstName: auth.firstName ?? "",
|
||||||
|
lastName: auth.lastName ?? "",
|
||||||
|
}, true);
|
||||||
|
} else {
|
||||||
|
await createStudent({
|
||||||
|
id: auth.username,
|
||||||
|
username: auth.username,
|
||||||
|
firstName: auth.firstName ?? "",
|
||||||
|
lastName: auth.lastName ?? "",
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,3 +36,8 @@ export const studentsOnly = authorize((auth) => auth.accountType === 'student');
|
||||||
* Middleware which rejects requests from unauthenticated users or users that aren't teachers.
|
* Middleware which rejects requests from unauthenticated users or users that aren't teachers.
|
||||||
*/
|
*/
|
||||||
export const teachersOnly = authorize((auth) => auth.accountType === 'teacher');
|
export const teachersOnly = authorize((auth) => auth.accountType === 'teacher');
|
||||||
|
/**
|
||||||
|
* Middleware which is to be used on requests no normal user should be able to execute.
|
||||||
|
* Since there is no concept of administrator accounts yet, currently, those requests will always be blocked.
|
||||||
|
*/
|
||||||
|
export const adminOnly = authorize(() => false);
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import {authorize} from "./auth-checks";
|
import {authorize} from "./auth-checks";
|
||||||
import {AuthenticationInfo} from "../authentication-info";
|
import {AuthenticationInfo} from "../authentication-info";
|
||||||
import {AuthenticatedRequest} from "../authenticated-request";
|
import {AuthenticatedRequest} from "../authenticated-request";
|
||||||
import {getClassesByTeacher} from "../../../services/teachers";
|
import {getClass} from "../../../services/classes";
|
||||||
|
|
||||||
|
async function teaches(teacherUsername: string, classId: string) {
|
||||||
|
const clazz = await getClass(classId);
|
||||||
|
return clazz != null && teacherUsername in clazz.teachers;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To be used on a request with path parameters username and classId.
|
* To be used on a request with path parameters username and classId.
|
||||||
|
@ -13,10 +18,18 @@ export const onlyAllowStudentHimselfAndTeachersOfClass = authorize(
|
||||||
if (req.params.username === auth.username) {
|
if (req.params.username === auth.username) {
|
||||||
return true;
|
return true;
|
||||||
} else if (auth.accountType === "teacher") {
|
} else if (auth.accountType === "teacher") {
|
||||||
const classes: string[] = (await getClassesByTeacher(auth.username, false)) as string[];
|
return teaches(auth.username, req.params.classId);
|
||||||
return req.params.classId in classes;
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only let the request pass through if its path parameter "username" is the username of the currently logged-in
|
||||||
|
* teacher and the path parameter "classId" refers to a class the teacher teaches.
|
||||||
|
*/
|
||||||
|
export const onlyAllowTeacherOfClass = authorize(
|
||||||
|
async (auth: AuthenticationInfo, req: AuthenticatedRequest) =>
|
||||||
|
req.params.username === auth.username && teaches(auth.username, req.params.classId),
|
||||||
|
);
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { getFrontendAuthConfig } from '../controllers/auth.js';
|
import {handleGetFrontendAuthConfig, handleHello} from '../controllers/auth.js';
|
||||||
import {authenticatedOnly, studentsOnly, teachersOnly} from "../middleware/auth/checks/auth-checks";
|
import {authenticatedOnly, studentsOnly, teachersOnly} from "../middleware/auth/checks/auth-checks";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Returns auth configuration for frontend
|
// Returns auth configuration for frontend
|
||||||
router.get('/config', (_req, res) => {
|
router.get('/config', handleGetFrontendAuthConfig);
|
||||||
res.json(getFrontendAuthConfig());
|
|
||||||
});
|
// This endpoint is called by the client when the user has just logged in.
|
||||||
|
// It creates or updates the user entity based on the authentication data the endpoint was called with.
|
||||||
|
router.post('/hello', authenticatedOnly, handleHello);
|
||||||
|
|
||||||
router.get('/testAuthenticatedOnly', authenticatedOnly, (_req, res) => {
|
router.get('/testAuthenticatedOnly', authenticatedOnly, (_req, res) => {
|
||||||
/* #swagger.security = [{ "student": [ ] }, { "teacher": [ ] }] */
|
/* #swagger.security = [{ "student": [ ] }, { "teacher": [ ] }] */
|
||||||
|
|
|
@ -12,13 +12,16 @@ import {
|
||||||
} from '../controllers/students.js';
|
} from '../controllers/students.js';
|
||||||
import joinRequestRouter from './student-join-requests.js';
|
import joinRequestRouter from './student-join-requests.js';
|
||||||
import {onlyAllowUserHimself} from "../middleware/auth/checks/user-auth-checks";
|
import {onlyAllowUserHimself} from "../middleware/auth/checks/user-auth-checks";
|
||||||
|
import {adminOnly} from "../middleware/auth/checks/auth-checks";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Root endpoint used to search objects
|
// Root endpoint used to search objects
|
||||||
router.get('/', getAllStudentsHandler);
|
router.get('/', adminOnly, getAllStudentsHandler);
|
||||||
|
|
||||||
router.post('/', createStudentHandler);
|
// Users will be created automatically when some resource is created for them. Therefore, this endpoint
|
||||||
|
// can only be used by an administrator.
|
||||||
|
router.post('/', adminOnly, createStudentHandler);
|
||||||
|
|
||||||
router.delete('/:username', onlyAllowUserHimself, deleteStudentHandler);
|
router.delete('/:username', onlyAllowUserHimself, deleteStudentHandler);
|
||||||
|
|
||||||
|
|
|
@ -10,26 +10,29 @@ import {
|
||||||
getTeacherStudentHandler,
|
getTeacherStudentHandler,
|
||||||
updateStudentJoinRequestHandler,
|
updateStudentJoinRequestHandler,
|
||||||
} from '../controllers/teachers.js';
|
} from '../controllers/teachers.js';
|
||||||
|
import {adminOnly} from "../middleware/auth/checks/auth-checks";
|
||||||
|
import {onlyAllowUserHimself} from "../middleware/auth/checks/user-auth-checks";
|
||||||
|
import {onlyAllowTeacherOfClass} from "../middleware/auth/checks/class-auth-checks";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Root endpoint used to search objects
|
// Root endpoint used to search objects
|
||||||
router.get('/', getAllTeachersHandler);
|
router.get('/', adminOnly, getAllTeachersHandler);
|
||||||
|
|
||||||
router.post('/', createTeacherHandler);
|
router.post('/', adminOnly, createTeacherHandler);
|
||||||
|
|
||||||
router.get('/:username', getTeacherHandler);
|
router.get('/:username', onlyAllowUserHimself, getTeacherHandler);
|
||||||
|
|
||||||
router.delete('/:username', deleteTeacherHandler);
|
router.delete('/:username', onlyAllowUserHimself, deleteTeacherHandler);
|
||||||
|
|
||||||
router.get('/:username/classes', getTeacherClassHandler);
|
router.get('/:username/classes', onlyAllowUserHimself, getTeacherClassHandler);
|
||||||
|
|
||||||
router.get('/:username/students', getTeacherStudentHandler);
|
router.get('/:username/students', onlyAllowUserHimself, getTeacherStudentHandler);
|
||||||
|
|
||||||
router.get('/:username/questions', getTeacherQuestionHandler);
|
router.get('/:username/questions', onlyAllowUserHimself, getTeacherQuestionHandler);
|
||||||
|
|
||||||
router.get('/:username/joinRequests/:classId', getStudentJoinRequestHandler);
|
router.get('/:username/joinRequests/:classId', onlyAllowTeacherOfClass, getStudentJoinRequestHandler);
|
||||||
|
|
||||||
router.put('/:username/joinRequests/:classId/:studentUsername', updateStudentJoinRequestHandler);
|
router.put('/:username/joinRequests/:classId/:studentUsername', onlyAllowTeacherOfClass, updateStudentJoinRequestHandler);
|
||||||
|
|
||||||
// Invitations to other classes a teacher received
|
// Invitations to other classes a teacher received
|
||||||
router.get('/:id/invitations', (_req, res) => {
|
router.get('/:id/invitations', (_req, res) => {
|
||||||
|
|
|
@ -52,11 +52,11 @@ export async function getStudent(username: string): Promise<StudentDTO> {
|
||||||
return mapToStudentDTO(user);
|
return mapToStudentDTO(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createStudent(userData: StudentDTO): Promise<StudentDTO> {
|
export async function createStudent(userData: StudentDTO, allowUpdate: boolean = false): Promise<StudentDTO> {
|
||||||
const studentRepository = getStudentRepository();
|
const studentRepository = getStudentRepository();
|
||||||
|
|
||||||
const newStudent = mapToStudent(userData);
|
const newStudent = mapToStudent(userData);
|
||||||
await studentRepository.save(newStudent, { preventOverwrite: true });
|
await studentRepository.save(newStudent, { preventOverwrite: !allowUpdate });
|
||||||
return userData;
|
return userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,11 +56,11 @@ export async function getTeacher(username: string): Promise<TeacherDTO> {
|
||||||
return mapToTeacherDTO(user);
|
return mapToTeacherDTO(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO> {
|
export async function createTeacher(userData: TeacherDTO, update?: boolean): Promise<TeacherDTO> {
|
||||||
const teacherRepository: TeacherRepository = getTeacherRepository();
|
const teacherRepository: TeacherRepository = getTeacherRepository();
|
||||||
|
|
||||||
const newTeacher = mapToTeacher(userData);
|
const newTeacher = mapToTeacher(userData);
|
||||||
await teacherRepository.save(newTeacher, { preventOverwrite: true });
|
await teacherRepository.save(newTeacher, { preventOverwrite: !update });
|
||||||
return mapToTeacherDTO(newTeacher);
|
return mapToTeacherDTO(newTeacher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,10 @@ const authState = reactive<AuthState>({
|
||||||
activeRole: authStorage.getActiveRole() || null,
|
activeRole: authStorage.getActiveRole() || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function sendHello(): Promise<void> {
|
||||||
|
return apiClient.post("/auth/hello");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the information about who is currently logged in from the IDP.
|
* Load the information about who is currently logged in from the IDP.
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +45,8 @@ async function loadUser(): Promise<User | null> {
|
||||||
authState.user = user;
|
authState.user = user;
|
||||||
authState.accessToken = user?.access_token || null;
|
authState.accessToken = user?.access_token || null;
|
||||||
authState.activeRole = activeRole || null;
|
authState.activeRole = activeRole || null;
|
||||||
|
|
||||||
|
await sendHello();
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +78,7 @@ async function handleLoginCallback(): Promise<void> {
|
||||||
throw new Error("Login callback received, but the user is not logging in!");
|
throw new Error("Login callback received, but the user is not logging in!");
|
||||||
}
|
}
|
||||||
authState.user = (await (await getUserManagers())[activeRole].signinCallback()) || null;
|
authState.user = (await (await getUserManagers())[activeRole].signinCallback()) || null;
|
||||||
|
await sendHello();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue