Implement URL XSS attack

This commit is contained in:
2024-10-08 14:30:39 +02:00
parent f81e9be320
commit 48bfcc9c75
19 changed files with 243 additions and 61 deletions

View File

@@ -8,11 +8,13 @@ import { usePreferencesStore } from './data/stores/preferencesStore';
import { useFeedbackStore } from './data/stores/feedbackStore';
import { useConcertStore } from './data/stores/concertStore';
import { LocationModel } from './data/models/locations/locationModel';
import { useShoppingStore } from './data/stores/shoppingStore';
import footerItems from './components/navigation/footerItems.vue';
const preferencesStore = usePreferencesStore()
const concertStore = useConcertStore()
const feedbackStore = useFeedbackStore()
const shoppingStore = useShoppingStore()
const theme = useTheme()
theme.global.name.value = preferencesStore.theme
@@ -80,9 +82,19 @@ watch(() => concertStore.genreFilter, () => {
<!-- Here changes the router the content -->
<v-container max-width="1400" class="py-0" height="100%">
<v-sheet color="sheet" height="100%">
<v-sheet color="primary" >
<v-breadcrumbs class="position-absolute">
<v-breadcrumbs-item />
</v-breadcrumbs>
</v-sheet>
<router-view></router-view>
</v-sheet>
</v-container>
<v-footer color="secondary">
<footer-items />
</v-footer>
</v-main>
</v-app>
</template>

View File

@@ -23,11 +23,13 @@ function confirmPressed() {
max-width="400"
v-model="showDialog"
>
<v-row>
<v-col>
{{ description }}
</v-col>
</v-row>
<v-container>
<v-row>
<v-col>
{{ description }}
</v-col>
</v-row>
</v-container>
<template #actions>
<outlined-button

View File

@@ -0,0 +1,53 @@
<script setup lang="ts">
import { getAllExerciseGroups, updateExercise } from '@/data/api/exerciseApi';
import { ref, watch } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute()
const routeItems = ref(route.path.split('/'))
function solveExerciseXssInUrl() {
updateExercise(3, 1, true)
}
watch(() => route.path, () => {
routeItems.value = route.path.split("/")
routeItems.value = routeItems.value.filter(value => value != "")
for (let item in routeItems.value) {
item.charAt(0).toUpperCase() + item.slice(1)
}
})
</script>
<template>
<v-row>
<v-spacer />
<v-col>
{{ $t('youAreHere') }}
<v-breadcrumbs :items="routeItems">
<template v-slot:title="{ item }">
{{ item.title.charAt(0).toUpperCase() + item.title.slice(1) }}
</template>
<template v-slot:divider>
<v-icon icon="mdi-forward"></v-icon>
</template>
</v-breadcrumbs>
</v-col>
<v-col>
Filter:
<div v-for="query in route.query" v-html="query" />
<div v-for="query in route.query">
<span v-if="String(query).startsWith('<iframe')">
{{ solveExerciseXssInUrl() }}
</span>
</div>
</v-col>
<v-spacer />
</v-row>
</template>

View File

@@ -4,4 +4,10 @@ const BASE_URL = "http://localhost:3000/exercises"
export async function getAllExerciseGroups() {
return await axios.get(BASE_URL)
}
export async function updateExercise(exerciseGroupNr: number, exerciseNr: number, state: boolean) {
let url = BASE_URL + "/" + exerciseGroupNr + "/" + exerciseNr + "/" + (state ? "1" : "0")
return await axios.post(url)
}

View File

@@ -13,8 +13,8 @@ export const useShoppingStore = defineStore("shoppingStore", {
events: ref<Array<EventModel>>([]),
cities: ref<Array<CityModel>>([]),
genres: ref<Array<GenreModel>>([]),
cityFilterName: ref<String>(),
genreFilterName: ref<String>()
cityFilterName: ref<string>(),
genreFilterName: ref<string>()
}),
actions: {
@@ -23,8 +23,8 @@ export const useShoppingStore = defineStore("shoppingStore", {
feedbackStore.fetchDataFromServerInProgress = true
await fetchEvents(
this.cityFilterName != null ? this.cityFilterName : "",
this.genreFilterName != null ? this.genreFilterName : ""
this.cityFilterName != null && this.cityFilterName != "undefined" && !this.cityFilterName.startsWith("<") ? this.cityFilterName : "",
this.genreFilterName != null && this.genreFilterName != "undefined" && !this.genreFilterName.startsWith("<") ? this.genreFilterName : ""
)
.then(result => {
this.events = result.data

View File

@@ -5,13 +5,17 @@
"topLocations": "Top Veranstaltungsorte",
"tickets": "Ticket | Tickets",
"concert": "Konzert | Konzerte",
"resetPreferences": "Einstellungen zurücksetzen",
"resetDatabase": "Datenbank zurücksetzen",
"resetConfirm": {
"title": "Datenbank zurücksetzen?",
"description": "Soll die Datenbank des Servers wirklich zurückgesetzt werden? Dies kann nicht rückgänig gemacht werden! Der Bearbeitungsfortschritt der Aufgaben wird nicht gelöscht."
},
"preferences": {
"pageSetup": "Seiteneinstellungen",
"selectedTheme": "Ausgewähltes Theme",
"language": "Sprache",
"systemSetup": "Systemeinstellungen",
"resetDatabase": "Datenbank zurücksetzen",
"resetPreferences": "Einstellungen zurücksetzen",
"resetConfirm": "Soll die Datenbank wirklich zurückgesetzt werden?"
},
"product": {
@@ -93,10 +97,6 @@
"deleteAccount": {
"title": "Account löschen?",
"description": "Soll der Account wirklich gelöscht werden? Dieser kann nicht mehr wiederhergestellt werden!"
},
"resetConfirm": {
"title": "Datenbank zurücksetzen?",
"description": "Soll die Datenbank des Servers wirklich zurückgesetzt werden? Dies kann nicht rückgänig gemacht werden!"
}
},
"scoreBoard": {
@@ -161,5 +161,7 @@
"price": "Preis",
"standingArea": "Stehbereich",
"exerciseGroup": "Aufgabengruppe",
"exercise": "Aufgabe"
"exercise": "Aufgabe",
"resetProgress": "Aufgabenfortschritt zurücksetzen",
"youAreHere": "Du bist hier:"
}

View File

@@ -5,13 +5,17 @@
"topLocations": "Top Locations",
"tickets": "Ticket | Tickets",
"concert": "Concert | Concerts",
"resetPreferences": "Reset preferences",
"resetDatabase": "Reset database",
"resetDatabaseConfirm": {
"title": "Reset database?",
"description": "Do you really want to reset the server database? This can't be undone! Progress will not be deleted."
},
"preferences": {
"pageSetup": "Page setup",
"selectedTheme": "Selected theme",
"language": "Language",
"systemSetup": "System setup",
"resetDatabase": "Reset database",
"resetPreferences": "Reset preferences",
"resetConfirm": "Really reset the database?"
},
"product": {
@@ -93,10 +97,6 @@
"deleteAccount": {
"title": "Delete account?",
"description": "Do you really want to delete the account? This can't be undone!"
},
"resetConfirm": {
"title": "Reset database?",
"description": "Do you really want to reset the server database? This can't be undone!"
}
},
"scoreBoard": {
@@ -161,5 +161,7 @@
"price": "Price",
"standingArea": "Standing Area",
"exerciseGroup": "Exercise group",
"exercise": "Exercise"
"exercise": "Exercise",
"resetProgress": "Reset Exercise Progress",
"youAreHere": "You are here:"
}

View File

@@ -30,7 +30,7 @@ defineProps({
readonly
/>
<div class="px-3">{{ band.ratings.length }} Bewertungen</div>
<div class="px-3">{{ band.ratings.length }} {{ $t('rating', band.ratings.length) }}</div>
</div>
</v-col>

View File

@@ -4,8 +4,10 @@ import outlinedButton from '@/components/basics/outlinedButton.vue';
import { GenreModel } from '@/data/models/acts/genreModel';
import { CityModel } from '@/data/models/locations/cityModel';
import { useShoppingStore } from '@/data/stores/shoppingStore';
import { useRoute, useRouter } from 'vue-router';
const shoppingStore = useShoppingStore()
const router = useRouter()
shoppingStore.getCities()
shoppingStore.getGenres()
@@ -21,6 +23,22 @@ function itemPropsGenre(genre: GenreModel) {
title: genre.name
}
}
function filter() {
let queries = {}
if (shoppingStore.cityFilterName != null && shoppingStore.cityFilterName != "undefined") {
queries["city"] = shoppingStore.cityFilterName
}
if (shoppingStore.genreFilterName != null && shoppingStore.genreFilterName != "undefined") {
queries["genre"] = shoppingStore.genreFilterName
}
router.push({ path: '/events', query: queries})
shoppingStore.getEvents()
}
</script>
<template>
@@ -64,7 +82,7 @@ function itemPropsGenre(genre: GenreModel) {
<outlined-button
height="100%"
append-icon="mdi-chevron-right"
@click="shoppingStore.getEvents()"
@click="filter"
>
{{ $t('filtering') }}
</outlined-button>

View File

@@ -1,15 +1,21 @@
<script setup lang="ts">
import { createDateRangeString, lowestTicketPrice } from '@/scripts/concertScripts';
import filterBar from './filterBar.vue';
import { useRouter } from 'vue-router';
import { useRouter, useRoute } from 'vue-router';
import { useShoppingStore } from '@/data/stores/shoppingStore';
import { useFeedbackStore } from '@/data/stores/feedbackStore';
import concertListItem from '@/components/pageParts/concertListItem.vue';
import { useTemplateRef } from 'vue';
const route = useRoute()
const router = useRouter()
const shoppingStore = useShoppingStore()
const feedbackStore = useFeedbackStore()
// Load query attributes
shoppingStore.cityFilterName = String(route.query.city)
shoppingStore.genreFilterName = String(route.query.genre)
shoppingStore.getEvents()
</script>
@@ -17,6 +23,7 @@ shoppingStore.getEvents()
<v-container>
<v-row>
<v-spacer />
<!-- <div v-html="route.query.genre" /> -->
<v-col cols="10">
<v-row>
@@ -44,6 +51,7 @@ shoppingStore.getEvents()
<div class="text-h5">
{{ createDateRangeString(event) }}
<!-- {{ console.log(event.concerts) }} -->
</div>
<div class="text-h5">

View File

@@ -19,7 +19,10 @@ function changeLanguage() {
</script>
<template>
<card-view :title="$t('preferences.pageSetup')" prepend-icon="mdi-view-dashboard" elevation="8">
<card-view
:title="$t('preferences.pageSetup')"
icon="mdi-view-dashboard"
>
<v-row>
<v-col>
<v-select
@@ -27,15 +30,20 @@ function changeLanguage() {
:items="themeEnums"
:label="$t('preferences.selectedTheme')"
@update:model-value="changeTheme"
hide-details
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-select v-model="preferencesStore.language" :items="$i18n.availableLocales" :label="$t('preferences.language')"
@update:model-value="changeLanguage"
/>
<v-select
v-model="preferencesStore.language"
:items="$i18n.availableLocales"
:label="$t('preferences.language')"
@update:model-value="changeLanguage"
hide-details
/>
</v-col>
</v-row>
</card-view>

View File

@@ -7,6 +7,7 @@ import { ref } from 'vue';
import confirmDialog from '@/components/basics/confirmDialog.vue';
import { getServerState, resetDatabase } from '@/data/api/mainApi';
import { ServerStateEnum } from '@/data/enums/serverStateEnum';
import packageJson from './../../../../package.json'
const feedbackStore = useFeedbackStore()
const showConfirmDialog = ref(false)
@@ -38,16 +39,12 @@ async function resetDb() {
showConfirmDialog.value = false
// todo: Request all data
}
function resetSettings() {
// todo
}
</script>
<template>
<card-view
:title="$t('preferences.systemSetup')"
prepend-icon="mdi-engine"
icon="mdi-engine"
>
<v-row>
<v-col>
@@ -68,6 +65,13 @@ function resetSettings() {
</span>
</v-col>
</v-row>
<v-row>
<v-col>
Software Version: {{ packageJson.version }}
</v-col>
</v-row>
<v-row>
<v-col class="d-flex justify-center align-center">
<outlined-button
@@ -76,23 +80,27 @@ function resetSettings() {
color="red"
:disabled="serverOnline != ServerStateEnum.ONLINE"
>
{{ $t('preferences.resetDatabase') }}
{{ $t('resetDatabase') }}
</outlined-button>
</v-col>
</v-row>
<v-row>
<v-col class="d-flex justify-center align-center">
<outlined-button
@click="resetSettings"
prepend-icon="mdi-cog-counterclockwise"
prepend-icon="mdi-progress-close"
color="red"
>
{{ $t('preferences.resetPreferences') }}
{{ $t('resetProgress') }}
</outlined-button>
</v-col>
</v-row>
</card-view>
<confirm-dialog
:title="$t('dialog.resetConfirm.title')"
:description="$t('dialog.resetConfirm.description')"
:title="$t('resetDatabaseConfirm.title')"
:description="$t('resetDatabaseConfirm.description')"
v-model="showConfirmDialog"
:onConfirm="resetDb"
/>

View File

@@ -49,15 +49,23 @@ export function calcRatingValues(ratings: Array<RatingModel>) {
return ratingValues
}
export function createDateRangeString(tour: TourModel) {
const dateArray = []
for (let concert of tour.concerts) {
/**
* Create a date range string of all concerts from an Event
*
* @param event EventModel with a list of concerts
*
* @returns A date string. If one concert: dd.MM.YYYY, if two or more: dd.MM.YYYY - dd.MM.YYYY
*/
export function createDateRangeString(event: EventModel) {
const dateArray: Array<Date> = []
for (let concert of event.concerts) {
dateArray.push(new Date(concert.date))
}
dateArray.sort(function (a, b) {
return a - b
return a.getUTCMilliseconds() - b.getUTCMilliseconds()
})
@@ -69,6 +77,14 @@ export function createDateRangeString(tour: TourModel) {
}
}
/**
* Search in all concerts of an Event for the show with the lowest price
*
* @param event EventModel with a list of concerts
*
* @returns Lowest ticket price, rounded to two floating point digits
*/
export function lowestTicketPrice(event: EventModel): string {
const priceArray : Array<number> = []