Merge branch 'dev' into feat/assignment-page
This commit is contained in:
commit
9dcc1091cc
20 changed files with 148 additions and 53 deletions
|
@ -1,21 +0,0 @@
|
|||
PORT=3000
|
||||
DWENGO_DB_HOST=db
|
||||
DWENGO_DB_PORT=5432
|
||||
DWENGO_DB_USERNAME=postgres
|
||||
DWENGO_DB_PASSWORD=postgres
|
||||
DWENGO_DB_UPDATE=false
|
||||
|
||||
DWENGO_AUTH_STUDENT_URL=http://localhost/idp/realms/student
|
||||
DWENGO_AUTH_STUDENT_CLIENT_ID=dwengo
|
||||
DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://idp:7080/idp/realms/student/protocol/openid-connect/certs
|
||||
DWENGO_AUTH_TEACHER_URL=http://localhost/idp/realms/teacher
|
||||
DWENGO_AUTH_TEACHER_CLIENT_ID=dwengo
|
||||
DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://idp:7080/idp/realms/teacher/protocol/openid-connect/certs
|
||||
|
||||
# Allow Vite dev-server to access the backend (for testing purposes). Don't forget to remove this in production!
|
||||
#DWENGO_CORS_ALLOWED_ORIGINS=http://localhost/,127.0.0.1:80,http://127.0.0.1,http://localhost:80,http://127.0.0.1:80,localhost
|
||||
DWENGO_CORS_ALLOWED_ORIGINS=http://localhost/*,http://idp:7080,https://idp:7080
|
||||
|
||||
# Logging and monitoring
|
||||
|
||||
LOKI_HOST=http://logging:3102
|
|
@ -1,4 +1,10 @@
|
|||
import { UnauthorizedException } from '../exceptions/unauthorized-exception.js';
|
||||
import { getLogger } from '../logging/initalize.js';
|
||||
import { AuthenticatedRequest } from '../middleware/auth/authenticated-request.js';
|
||||
import { createOrUpdateStudent } from '../services/students.js';
|
||||
import { createOrUpdateTeacher } from '../services/teachers.js';
|
||||
import { envVars, getEnvVar } from '../util/envVars.js';
|
||||
import { Response } from 'express';
|
||||
|
||||
interface FrontendIdpConfig {
|
||||
authority: string;
|
||||
|
@ -15,6 +21,8 @@ interface FrontendAuthConfig {
|
|||
const SCOPE = 'openid profile email';
|
||||
const RESPONSE_TYPE = 'code';
|
||||
|
||||
const logger = getLogger();
|
||||
|
||||
export function getFrontendAuthConfig(): FrontendAuthConfig {
|
||||
return {
|
||||
student: {
|
||||
|
@ -31,3 +39,24 @@ export function getFrontendAuthConfig(): FrontendAuthConfig {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function postHelloHandler(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
const auth = req.auth;
|
||||
if (!auth) {
|
||||
throw new UnauthorizedException('Cannot say hello when not authenticated.');
|
||||
}
|
||||
const userData = {
|
||||
id: auth.username,
|
||||
username: auth.username,
|
||||
firstName: auth.firstName ?? '',
|
||||
lastName: auth.lastName ?? '',
|
||||
};
|
||||
if (auth.accountType === 'student') {
|
||||
await createOrUpdateStudent(userData);
|
||||
logger.debug(`Synchronized student ${userData.username} with IDP`);
|
||||
} else {
|
||||
await createOrUpdateTeacher(userData);
|
||||
logger.debug(`Synchronized teacher ${userData.username} with IDP`);
|
||||
}
|
||||
res.status(200).send({ message: 'Welcome!' });
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { HasStatusCode } from './has-status-code';
|
||||
|
||||
/**
|
||||
* Exceptions which are associated with a HTTP error code.
|
||||
*/
|
||||
export abstract class ExceptionWithHttpState extends Error {
|
||||
export abstract class ExceptionWithHttpState extends Error implements HasStatusCode {
|
||||
constructor(
|
||||
public status: number,
|
||||
public error: string
|
||||
|
|
6
backend/src/exceptions/has-status-code.ts
Normal file
6
backend/src/exceptions/has-status-code.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface HasStatusCode {
|
||||
status: number;
|
||||
}
|
||||
export function hasStatusCode(err: unknown): err is HasStatusCode {
|
||||
return typeof err === 'object' && err !== null && 'status' in err && typeof (err as HasStatusCode)?.status === 'number';
|
||||
}
|
|
@ -48,14 +48,14 @@ const idpConfigs = {
|
|||
const verifyJwtToken = expressjwt({
|
||||
secret: async (_: express.Request, token: jwt.Jwt | undefined) => {
|
||||
if (!token?.payload || !(token.payload as JwtPayload).iss) {
|
||||
throw new Error('Invalid token');
|
||||
throw new UnauthorizedException('Invalid token.');
|
||||
}
|
||||
|
||||
const issuer = (token.payload as JwtPayload).iss;
|
||||
|
||||
const idpConfig = Object.values(idpConfigs).find((config) => config.issuer === issuer);
|
||||
if (!idpConfig) {
|
||||
throw new Error('Issuer not accepted.');
|
||||
throw new UnauthorizedException('Issuer not accepted.');
|
||||
}
|
||||
|
||||
const signingKey = await idpConfig.jwksClient.getSigningKey(token.header.kid);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { NextFunction, Request, Response } from 'express';
|
||||
import { getLogger, Logger } from '../../logging/initalize.js';
|
||||
import { ExceptionWithHttpState } from '../../exceptions/exception-with-http-state.js';
|
||||
import { hasStatusCode } from '../../exceptions/has-status-code.js';
|
||||
|
||||
const logger: Logger = getLogger();
|
||||
|
||||
export function errorHandler(err: unknown, _req: Request, res: Response, _: NextFunction): void {
|
||||
if (err instanceof ExceptionWithHttpState) {
|
||||
if (hasStatusCode(err)) {
|
||||
logger.warn(`An error occurred while handling a request: ${err} (-> HTTP ${err.status})`);
|
||||
res.status(err.status).json(err);
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import express from 'express';
|
||||
import { getFrontendAuthConfig } from '../controllers/auth.js';
|
||||
import { getFrontendAuthConfig, postHelloHandler } from '../controllers/auth.js';
|
||||
import { authenticatedOnly, studentsOnly, teachersOnly } from '../middleware/auth/auth.js';
|
||||
const router = express.Router();
|
||||
|
||||
|
@ -23,4 +23,6 @@ router.get('/testTeachersOnly', teachersOnly, (_req, res) => {
|
|||
res.json({ message: 'If you see this, you should be a teacher!' });
|
||||
});
|
||||
|
||||
router.post('/hello', authenticatedOnly, postHelloHandler);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -63,6 +63,16 @@ export async function createStudent(userData: StudentDTO): Promise<StudentDTO> {
|
|||
|
||||
const newStudent = mapToStudent(userData);
|
||||
await studentRepository.save(newStudent, { preventOverwrite: true });
|
||||
|
||||
return userData;
|
||||
}
|
||||
|
||||
export async function createOrUpdateStudent(userData: StudentDTO): Promise<StudentDTO> {
|
||||
await getStudentRepository().upsert({
|
||||
username: userData.username,
|
||||
firstName: userData.firstName,
|
||||
lastName: userData.lastName,
|
||||
});
|
||||
return userData;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,9 +62,19 @@ export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO> {
|
|||
|
||||
const newTeacher = mapToTeacher(userData);
|
||||
await teacherRepository.save(newTeacher, { preventOverwrite: true });
|
||||
|
||||
return mapToTeacherDTO(newTeacher);
|
||||
}
|
||||
|
||||
export async function createOrUpdateTeacher(userData: TeacherDTO): Promise<TeacherDTO> {
|
||||
await getTeacherRepository().upsert({
|
||||
username: userData.username,
|
||||
firstName: userData.firstName,
|
||||
lastName: userData.lastName,
|
||||
});
|
||||
return userData;
|
||||
}
|
||||
|
||||
export async function deleteTeacher(username: string): Promise<TeacherDTO> {
|
||||
const teacherRepository: TeacherRepository = getTeacherRepository();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue