feat: implement unauthorized error handling and login popup
Some checks failed
Deploy Miniplan / build (push) Failing after 42s
Some checks failed
Deploy Miniplan / build (push) Failing after 42s
This commit is contained in:
@@ -10,6 +10,6 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dropzone-vue3": "^1.0.2",
|
"dropzone-vue3": "^1.0.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ fun Payload.mid() = getClaim("id").asInt()
|
|||||||
|
|
||||||
|
|
||||||
object Security {
|
object Security {
|
||||||
fun DEFAULT_EXPIRY() = Date(System.currentTimeMillis() + 1000 * 60 * 60);
|
fun DEFAULT_EXPIRY() = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 14);
|
||||||
|
|
||||||
suspend fun authenticateUser(application: Application, username: String, password: String): Ministrant? {
|
suspend fun authenticateUser(application: Application, username: String, password: String): Ministrant? {
|
||||||
println("Username $username password $password")
|
println("Username $username password $password")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.walamana.views
|
|||||||
import de.walamana.models.GottesdienstGroup
|
import de.walamana.models.GottesdienstGroup
|
||||||
import de.walamana.models.GottesdienstGroupDao
|
import de.walamana.models.GottesdienstGroupDao
|
||||||
import de.walamana.models.GottesdienstGroups
|
import de.walamana.models.GottesdienstGroups
|
||||||
|
import de.walamana.plugins.getJWTEnvironment
|
||||||
import de.walamana.service.PlanDao
|
import de.walamana.service.PlanDao
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.auth.*
|
import io.ktor.server.auth.*
|
||||||
@@ -14,6 +15,7 @@ import io.ktor.server.util.*
|
|||||||
|
|
||||||
fun Route.configurePlanRoutes() {
|
fun Route.configurePlanRoutes() {
|
||||||
route("/plan") {
|
route("/plan") {
|
||||||
|
authenticate {
|
||||||
get {
|
get {
|
||||||
val principal = call.principal<JWTPrincipal>()
|
val principal = call.principal<JWTPrincipal>()
|
||||||
val isAnonymous = principal == null
|
val isAnonymous = principal == null
|
||||||
@@ -22,6 +24,7 @@ fun Route.configurePlanRoutes() {
|
|||||||
call.respond(plan)
|
call.respond(plan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
route("/groups") {
|
route("/groups") {
|
||||||
get {
|
get {
|
||||||
val offset = call.parameters.get("offset")?.toLong() ?: 0
|
val offset = call.parameters.get("offset")?.toLong() ?: 0
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import LoginPanel from "@/components/LoginPanel.vue";
|
|||||||
import {onMounted, ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
import {Auth} from "@/services/auth";
|
import {Auth} from "@/services/auth";
|
||||||
import DialogHost from "@/components/dialog/DialogHost.vue";
|
import DialogHost from "@/components/dialog/DialogHost.vue";
|
||||||
|
import {LoginService} from "@/services/LoginService";
|
||||||
|
|
||||||
let showPopup = ref(false)
|
let showPopup = ref(false)
|
||||||
let loggedIn = ref(false)
|
let loggedIn = ref(false)
|
||||||
@@ -25,6 +26,10 @@ onMounted(() => {
|
|||||||
Auth.checkForToken()
|
Auth.checkForToken()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
LoginService.subject.subscribe(() => {
|
||||||
|
showPopup.value = true
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ async function attemptLogin() {
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<img src="./../assets/minis_logo.png" style="height: 80px"/>
|
||||||
<span class="title">Anmelden</span>
|
<span class="title">Anmelden</span>
|
||||||
|
<span class="subtitle">Um den Miniplan einsehen zu können, logge dich bitte ein.</span>
|
||||||
<Input class="input" v-model:value="username" label="Nutzername"/>
|
<Input class="input" v-model:value="username" label="Nutzername"/>
|
||||||
<Input class="input" v-model:value="password" label="Passwort" type="password"/>
|
<Input class="input" v-model:value="password" label="Passwort" type="password"/>
|
||||||
<span v-if="invalid" style="color: red; text-align: center; margin-bottom: 10px">Benutzername oder<br> Passwort falsch.</span>
|
<span v-if="invalid" style="color: red; text-align: center; margin-bottom: 10px">Benutzername oder<br> Passwort falsch.</span>
|
||||||
@@ -69,11 +71,22 @@ async function attemptLogin() {
|
|||||||
.title{
|
.title{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
max-width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.5;
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
button{
|
button{
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
|||||||
9
public/src/services/LoginService.ts
Normal file
9
public/src/services/LoginService.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
|
||||||
|
export namespace LoginService {
|
||||||
|
export const subject = new Subject<void>();
|
||||||
|
|
||||||
|
export function showLoginPopup() {
|
||||||
|
subject.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,11 +30,21 @@ function getToken(): string | null {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UnauthorizedError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("UnauthorizedError");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export namespace API {
|
export namespace API {
|
||||||
|
|
||||||
export async function getPlan(id: number) {
|
export async function getPlan(id: number) {
|
||||||
return api(`/plan?id=${id}`).then(res => res.json())
|
return api(`/plan?id=${id}`).then(res => {
|
||||||
.then(data => {
|
if(res.status == 401) {
|
||||||
|
throw new UnauthorizedError()
|
||||||
|
}
|
||||||
|
return res.json()
|
||||||
|
}).then(data => {
|
||||||
return {
|
return {
|
||||||
gottesdienste: data.gottesdienste,
|
gottesdienste: data.gottesdienste,
|
||||||
ministranten: data.ministranten,
|
ministranten: data.ministranten,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import {computed, onMounted, reactive, ref, toRaw, watch} from "vue";
|
import {computed, onMounted, reactive, ref, toRaw, watch} from "vue";
|
||||||
import TablePlan from "@/components/TablePlan.vue";
|
import TablePlan from "@/components/TablePlan.vue";
|
||||||
import {API} from "@/services/api";
|
import {API, UnauthorizedError} from "@/services/api";
|
||||||
import type {Gottesdienst, GottesdienstGroup, Mark, SimplifiedMinistrant} from "@/models/models";
|
import type {Gottesdienst, GottesdienstGroup, Mark, SimplifiedMinistrant} from "@/models/models";
|
||||||
import MobilePlan from "@/components/MobilePlan.vue";
|
import MobilePlan from "@/components/MobilePlan.vue";
|
||||||
import PlanActionBar from "@/components/PlanActionBar.vue";
|
import PlanActionBar from "@/components/PlanActionBar.vue";
|
||||||
@@ -17,6 +17,7 @@ import {useRoute, useRouter} from "vue-router";
|
|||||||
import debounce from "underscore/modules/debounce.js"
|
import debounce from "underscore/modules/debounce.js"
|
||||||
import SavingIndicator from "@/components/SavingIndicator.vue";
|
import SavingIndicator from "@/components/SavingIndicator.vue";
|
||||||
import ImportZelebrationsplanDialog from "@/components/dialog/ImportZelebrationsplanDialog.vue";
|
import ImportZelebrationsplanDialog from "@/components/dialog/ImportZelebrationsplanDialog.vue";
|
||||||
|
import {LoginService} from "@/services/LoginService";
|
||||||
|
|
||||||
const MAX_WIDTH_MOBILE = 600;
|
const MAX_WIDTH_MOBILE = 600;
|
||||||
|
|
||||||
@@ -56,11 +57,23 @@ watch(() => route.params.id, async (value, oldValue) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function loadPlan() {
|
async function loadPlan() {
|
||||||
|
try {
|
||||||
const { ministranten, gottesdienste, marks} = await API.getPlan(planId.value)
|
const { ministranten, gottesdienste, marks} = await API.getPlan(planId.value)
|
||||||
plan.ministranten = ministranten
|
plan.ministranten = ministranten
|
||||||
plan.gottesdienste = gottesdienste
|
plan.gottesdienste = gottesdienste
|
||||||
plan.marks = marks
|
plan.marks = marks
|
||||||
Auth.checkForToken()
|
Auth.checkForToken()
|
||||||
|
} catch (e: any) {
|
||||||
|
if(e instanceof UnauthorizedError) {
|
||||||
|
LoginService.showLoginPopup()
|
||||||
|
let subscription = Auth.loggedInSubject.subscribe((loggedIn) => {
|
||||||
|
subscription.unsubscribe()
|
||||||
|
if(loggedIn) {
|
||||||
|
loadPlan()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Auth.loggedInSubject.subscribe(async (loggedIn) => {
|
Auth.loggedInSubject.subscribe(async (loggedIn) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user