Merge branch 'dev' into feat/assignment-page
This commit is contained in:
commit
9dcc1091cc
20 changed files with 148 additions and 53 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -738,3 +738,5 @@ flycheck_*.el
|
||||||
/network-security.data
|
/network-security.data
|
||||||
|
|
||||||
docs/.venv
|
docs/.venv
|
||||||
|
idp_data/h2/keycloakdb.mv.db
|
||||||
|
idp_data/h2/keycloakdb.trace.db
|
||||||
|
|
|
@ -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 { envVars, getEnvVar } from '../util/envVars.js';
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
interface FrontendIdpConfig {
|
interface FrontendIdpConfig {
|
||||||
authority: string;
|
authority: string;
|
||||||
|
@ -15,6 +21,8 @@ interface FrontendAuthConfig {
|
||||||
const SCOPE = 'openid profile email';
|
const SCOPE = 'openid profile email';
|
||||||
const RESPONSE_TYPE = 'code';
|
const RESPONSE_TYPE = 'code';
|
||||||
|
|
||||||
|
const logger = getLogger();
|
||||||
|
|
||||||
export function getFrontendAuthConfig(): FrontendAuthConfig {
|
export function getFrontendAuthConfig(): FrontendAuthConfig {
|
||||||
return {
|
return {
|
||||||
student: {
|
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.
|
* Exceptions which are associated with a HTTP error code.
|
||||||
*/
|
*/
|
||||||
export abstract class ExceptionWithHttpState extends Error {
|
export abstract class ExceptionWithHttpState extends Error implements HasStatusCode {
|
||||||
constructor(
|
constructor(
|
||||||
public status: number,
|
public status: number,
|
||||||
public error: string
|
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({
|
const verifyJwtToken = expressjwt({
|
||||||
secret: async (_: express.Request, token: jwt.Jwt | undefined) => {
|
secret: async (_: express.Request, token: jwt.Jwt | undefined) => {
|
||||||
if (!token?.payload || !(token.payload as JwtPayload).iss) {
|
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 issuer = (token.payload as JwtPayload).iss;
|
||||||
|
|
||||||
const idpConfig = Object.values(idpConfigs).find((config) => config.issuer === issuer);
|
const idpConfig = Object.values(idpConfigs).find((config) => config.issuer === issuer);
|
||||||
if (!idpConfig) {
|
if (!idpConfig) {
|
||||||
throw new Error('Issuer not accepted.');
|
throw new UnauthorizedException('Issuer not accepted.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const signingKey = await idpConfig.jwksClient.getSigningKey(token.header.kid);
|
const signingKey = await idpConfig.jwksClient.getSigningKey(token.header.kid);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import { getLogger, Logger } from '../../logging/initalize.js';
|
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();
|
const logger: Logger = getLogger();
|
||||||
|
|
||||||
export function errorHandler(err: unknown, _req: Request, res: Response, _: NextFunction): void {
|
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})`);
|
logger.warn(`An error occurred while handling a request: ${err} (-> HTTP ${err.status})`);
|
||||||
res.status(err.status).json(err);
|
res.status(err.status).json(err);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import express from 'express';
|
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';
|
import { authenticatedOnly, studentsOnly, teachersOnly } from '../middleware/auth/auth.js';
|
||||||
const router = express.Router();
|
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!' });
|
res.json({ message: 'If you see this, you should be a teacher!' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/hello', authenticatedOnly, postHelloHandler);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -63,6 +63,16 @@ export async function createStudent(userData: StudentDTO): Promise<StudentDTO> {
|
||||||
|
|
||||||
const newStudent = mapToStudent(userData);
|
const newStudent = mapToStudent(userData);
|
||||||
await studentRepository.save(newStudent, { preventOverwrite: true });
|
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;
|
return userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,9 +62,19 @@ export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO> {
|
||||||
|
|
||||||
const newTeacher = mapToTeacher(userData);
|
const newTeacher = mapToTeacher(userData);
|
||||||
await teacherRepository.save(newTeacher, { preventOverwrite: true });
|
await teacherRepository.save(newTeacher, { preventOverwrite: true });
|
||||||
|
|
||||||
return mapToTeacherDTO(newTeacher);
|
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> {
|
export async function deleteTeacher(username: string): Promise<TeacherDTO> {
|
||||||
const teacherRepository: TeacherRepository = getTeacherRepository();
|
const teacherRepository: TeacherRepository = getTeacherRepository();
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const showMenuBar = computed(() => (route.meta as RouteMeta).requiresAuth && auth.authState.user);
|
const showMenuBar = computed(() => (route.meta as RouteMeta).requiresAuth && auth.authState.user);
|
||||||
|
|
||||||
auth.loadUser().catch((_error) => {
|
|
||||||
// TODO Could not load user!
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
import auth from "@/services/auth/auth-service.ts";
|
import auth from "@/services/auth/auth-service.ts";
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
const role = auth.authState.activeRole;
|
const role = auth.authState.activeRole;
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const name = ref(auth.authState.user!.profile.name!);
|
const name = ref(auth.authState.user!.profile.name!);
|
||||||
const initials: string = name.value
|
const initials: string = name.value
|
||||||
|
|
|
@ -88,6 +88,9 @@
|
||||||
"sent": "sent",
|
"sent": "sent",
|
||||||
"failed": "fehlgeschlagen",
|
"failed": "fehlgeschlagen",
|
||||||
"wrong": "etwas ist schief gelaufen",
|
"wrong": "etwas ist schief gelaufen",
|
||||||
|
"created": "erstellt",
|
||||||
|
"callbackLoading": "Sie werden angemeldet...",
|
||||||
|
"loginUnexpectedError": "Anmeldung fehlgeschlagen",
|
||||||
"submitSolution": "Lösung einreichen",
|
"submitSolution": "Lösung einreichen",
|
||||||
"submitNewSolution": "Neue Lösung einreichen",
|
"submitNewSolution": "Neue Lösung einreichen",
|
||||||
"markAsDone": "Als fertig markieren",
|
"markAsDone": "Als fertig markieren",
|
||||||
|
@ -107,8 +110,8 @@
|
||||||
"created": "erstellt",
|
"created": "erstellt",
|
||||||
"remove": "entfernen",
|
"remove": "entfernen",
|
||||||
"students": "Studenten",
|
"students": "Studenten",
|
||||||
"classJoinRequests": "Anfragen verbinden",
|
"classJoinRequests": "Beitrittsanfragen",
|
||||||
"reject": "zurückweisen",
|
"reject": "ablehnen",
|
||||||
"areusure": "Sind Sie sicher?",
|
"areusure": "Sind Sie sicher?",
|
||||||
"yes": "ja",
|
"yes": "ja",
|
||||||
"teachers": "Lehrer",
|
"teachers": "Lehrer",
|
||||||
|
@ -117,4 +120,3 @@
|
||||||
"enterUsername": "Geben Sie den Benutzernamen der Lehrkraft ein, die Sie einladen möchten",
|
"enterUsername": "Geben Sie den Benutzernamen der Lehrkraft ein, die Sie einladen möchten",
|
||||||
"username": "Nutzername",
|
"username": "Nutzername",
|
||||||
"invite": "einladen"
|
"invite": "einladen"
|
||||||
}
|
|
||||||
|
|
|
@ -88,6 +88,9 @@
|
||||||
"sent": "sent",
|
"sent": "sent",
|
||||||
"failed": "failed",
|
"failed": "failed",
|
||||||
"wrong": "something went wrong",
|
"wrong": "something went wrong",
|
||||||
|
"created": "created",
|
||||||
|
"callbackLoading": "You are being logged in...",
|
||||||
|
"loginUnexpectedError": "Login failed",
|
||||||
"submitSolution": "Submit solution",
|
"submitSolution": "Submit solution",
|
||||||
"submitNewSolution": "Submit new solution",
|
"submitNewSolution": "Submit new solution",
|
||||||
"markAsDone": "Mark as completed",
|
"markAsDone": "Mark as completed",
|
||||||
|
|
|
@ -89,6 +89,8 @@
|
||||||
"failed": "échoué",
|
"failed": "échoué",
|
||||||
"wrong": "quelque chose n'a pas fonctionné",
|
"wrong": "quelque chose n'a pas fonctionné",
|
||||||
"created": "créé",
|
"created": "créé",
|
||||||
|
"callbackLoading": "Vous serez connecté...",
|
||||||
|
"loginUnexpectedError": "La connexion a échoué",
|
||||||
"submitSolution": "Soumettre la solution",
|
"submitSolution": "Soumettre la solution",
|
||||||
"submitNewSolution": "Soumettre une nouvelle solution",
|
"submitNewSolution": "Soumettre une nouvelle solution",
|
||||||
"markAsDone": "Marquer comme terminé",
|
"markAsDone": "Marquer comme terminé",
|
||||||
|
|
|
@ -89,6 +89,8 @@
|
||||||
"failed": "mislukt",
|
"failed": "mislukt",
|
||||||
"wrong": "er ging iets verkeerd",
|
"wrong": "er ging iets verkeerd",
|
||||||
"created": "gecreëerd",
|
"created": "gecreëerd",
|
||||||
|
"callbackLoading": "Je wordt ingelogd...",
|
||||||
|
"loginUnexpectedError": "Inloggen mislukt",
|
||||||
"submitSolution": "Oplossing indienen",
|
"submitSolution": "Oplossing indienen",
|
||||||
"submitNewSolution": "Nieuwe oplossing indienen",
|
"submitNewSolution": "Nieuwe oplossing indienen",
|
||||||
"markAsDone": "Markeren als afgewerkt",
|
"markAsDone": "Markeren als afgewerkt",
|
||||||
|
|
|
@ -12,9 +12,11 @@ import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import { aliases, mdi } from "vuetify/iconsets/mdi";
|
import { aliases, mdi } from "vuetify/iconsets/mdi";
|
||||||
import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query";
|
import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query";
|
||||||
|
import authService from "./services/auth/auth-service.ts";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
await authService.loadUser();
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
const link = document.createElement("link");
|
const link = document.createElement("link");
|
||||||
|
|
|
@ -142,9 +142,8 @@ const router = createRouter({
|
||||||
router.beforeEach(async (to, _from, next) => {
|
router.beforeEach(async (to, _from, next) => {
|
||||||
// Verify if user is logged in before accessing certain routes
|
// Verify if user is logged in before accessing certain routes
|
||||||
if (to.meta.requiresAuth) {
|
if (to.meta.requiresAuth) {
|
||||||
if (!authState.isLoggedIn.value) {
|
if (!authService.isLoggedIn.value) {
|
||||||
//Next("/login");
|
next("/login");
|
||||||
next();
|
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ async function getUserManagers(): Promise<UserManagersForRoles> {
|
||||||
const authState = reactive<AuthState>({
|
const authState = reactive<AuthState>({
|
||||||
user: null,
|
user: null,
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
activeRole: authStorage.getActiveRole() || null,
|
activeRole: authStorage.getActiveRole() ?? null,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,18 +38,38 @@ async function loadUser(): Promise<User | null> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const user = await (await getUserManagers())[activeRole].getUser();
|
const user = await (await getUserManagers())[activeRole].getUser();
|
||||||
authState.user = user;
|
setUserAuthInfo(user);
|
||||||
authState.accessToken = user?.access_token || null;
|
authState.activeRole = activeRole ?? null;
|
||||||
authState.activeRole = activeRole || null;
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoggedIn = computed(() => authState.user !== null);
|
const isLoggedIn = computed(() => authState.user !== null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all the cached information about the current authentication.
|
||||||
|
*/
|
||||||
|
function clearAuthState(): void {
|
||||||
|
authStorage.deleteActiveRole();
|
||||||
|
authState.accessToken = null;
|
||||||
|
authState.user = null;
|
||||||
|
authState.activeRole = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the information about the currently logged-in user in the cache.
|
||||||
|
*/
|
||||||
|
function setUserAuthInfo(newUser: User | null): void {
|
||||||
|
authState.user = newUser;
|
||||||
|
authState.accessToken = newUser?.access_token ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect the user to the login page where he/she can choose whether to log in as a student or teacher.
|
* Redirect the user to the login page where he/she can choose whether to log in as a student or teacher.
|
||||||
*/
|
*/
|
||||||
async function initiateLogin(): Promise<void> {
|
async function initiateLogin(): Promise<void> {
|
||||||
|
if (isLoggedIn.value) {
|
||||||
|
clearAuthState();
|
||||||
|
}
|
||||||
await router.push(loginRoute);
|
await router.push(loginRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +92,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 apiClient.post("/auth/hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,14 +101,14 @@ async function handleLoginCallback(): Promise<void> {
|
||||||
async function renewToken(): Promise<User | null> {
|
async function renewToken(): Promise<User | null> {
|
||||||
const activeRole = authStorage.getActiveRole();
|
const activeRole = authStorage.getActiveRole();
|
||||||
if (!activeRole) {
|
if (!activeRole) {
|
||||||
// FIXME console.log("Can't renew the token: Not logged in!");
|
|
||||||
await initiateLogin();
|
await initiateLogin();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await (await getUserManagers())[activeRole].signinSilent();
|
const userManagerForRole = (await getUserManagers())[activeRole];
|
||||||
} catch (_error) {
|
const user = await userManagerForRole.signinSilent();
|
||||||
// FIXME console.log("Can't renew the token: " + error);
|
setUserAuthInfo(user);
|
||||||
|
} catch (_error: unknown) {
|
||||||
await initiateLogin();
|
await initiateLogin();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -101,6 +122,7 @@ async function logout(): Promise<void> {
|
||||||
if (activeRole) {
|
if (activeRole) {
|
||||||
await (await getUserManagers())[activeRole].signoutRedirect();
|
await (await getUserManagers())[activeRole].signoutRedirect();
|
||||||
authStorage.deleteActiveRole();
|
authStorage.deleteActiveRole();
|
||||||
|
clearAuthState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,13 +141,15 @@ apiClient.interceptors.request.use(
|
||||||
// Registering interceptor to refresh the token when a request failed because it was expired.
|
// Registering interceptor to refresh the token when a request failed because it was expired.
|
||||||
apiClient.interceptors.response.use(
|
apiClient.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
async (error: AxiosError<{ message?: string }>) => {
|
async (error: AxiosError<{ message?: string; inner?: { message?: string } }>) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
if (error.response.data.message === "token_expired") {
|
// If the user should already be logged in, his token is probably just expired.
|
||||||
// FIXME console.log("Access token expired, trying to refresh...");
|
if (isLoggedIn.value) {
|
||||||
await renewToken();
|
await renewToken();
|
||||||
return apiClient(error.config!); // Retry the request
|
return apiClient(error.config!); // Retry the request
|
||||||
} // Apparently, the user got a 401 because he was not logged in yet at all. Redirect him to login.
|
}
|
||||||
|
|
||||||
|
// Apparently, the user got a 401 because he was not logged in yet at all. Redirect him to login.
|
||||||
await initiateLogin();
|
await initiateLogin();
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { onMounted, ref, type Ref } from "vue";
|
import { onMounted, ref, type Ref } from "vue";
|
||||||
import auth from "../services/auth/auth-service.ts";
|
import auth from "../services/auth/auth-service.ts";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const errorMessage: Ref<string | null> = ref(null);
|
const errorMessage: Ref<string | null> = ref(null);
|
||||||
|
@ -12,14 +15,34 @@
|
||||||
await auth.handleLoginCallback();
|
await auth.handleLoginCallback();
|
||||||
await router.replace("/user"); // Redirect to theme page
|
await router.replace("/user"); // Redirect to theme page
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = `OIDC callback error: ${error}`;
|
errorMessage.value = `${t("loginUnexpectedError")}: ${error}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<p v-if="!errorMessage">Logging you in...</p>
|
<div class="callback">
|
||||||
<p v-else>{{ errorMessage }}</p>
|
<div
|
||||||
|
class="callback-loading"
|
||||||
|
v-if="!errorMessage"
|
||||||
|
>
|
||||||
|
<v-progress-circular indeterminate></v-progress-circular>
|
||||||
|
<p>{{ t("callbackLoading") }}</p>
|
||||||
|
</div>
|
||||||
|
<v-alert
|
||||||
|
icon="mdi-alert-circle"
|
||||||
|
type="error"
|
||||||
|
variant="elevated"
|
||||||
|
v-if="errorMessage"
|
||||||
|
>
|
||||||
|
{{ errorMessage }}
|
||||||
|
</v-alert>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.callback {
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue