Implement URL XSS attack
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Request, Response, NextFunction, Router } from 'express'
|
||||
import { deleteAllTables, prepopulateDatabase } from '../scripts/databaseHelper'
|
||||
import { deleteAllTables, deleteExerciseProgressTables, prepopulateDatabase, prepopulateExerciseDatabase } from '../scripts/databaseHelper'
|
||||
|
||||
export const api = Router()
|
||||
|
||||
@@ -15,5 +15,13 @@ api.get("/resetdatabase", async (req: Request, res: Response, next: NextFunction
|
||||
await prepopulateDatabase()
|
||||
|
||||
// Step 3: Send status back
|
||||
res.status(200).send()
|
||||
})
|
||||
|
||||
api.get("/resetExerciseProgress", async (req: Request, res: Response, next: NextFunction) => {
|
||||
deleteExerciseProgressTables()
|
||||
|
||||
await prepopulateExerciseDatabase()
|
||||
|
||||
res.status(200).send()
|
||||
})
|
||||
@@ -43,6 +43,7 @@ events.get("/", async (req: Request, res: Response) => {
|
||||
include: [
|
||||
{
|
||||
model: Concert,
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: Location,
|
||||
|
||||
@@ -20,4 +20,23 @@ exercises.get("/", (req: Request, res: Response) => {
|
||||
).then(result => {
|
||||
res.status(200).json(result)
|
||||
})
|
||||
})
|
||||
|
||||
exercises.post("/:groupNr/:exerciseNr/:state", (req: Request, res: Response) => {
|
||||
console.log(req.params.groupNr)
|
||||
ExerciseGroup.findOne({
|
||||
where: { groupNr: req.params.groupNr }
|
||||
})
|
||||
.then(group => {
|
||||
Exercise.findOne({
|
||||
where: {
|
||||
exerciseNr: req.params.exerciseNr,
|
||||
exerciseGroupId: group.id
|
||||
}
|
||||
})
|
||||
.then(exercise => {
|
||||
exercise.update({ solved: req.params.state == "1"})
|
||||
res.status(200).send()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -42,6 +42,7 @@ export function deleteAllTables() {
|
||||
Band.destroy({ truncate: true })
|
||||
Event.destroy({ truncate: true })
|
||||
|
||||
City.destroy({ truncate: true })
|
||||
Location.destroy({ truncate: true })
|
||||
Concert.destroy({ truncate: true })
|
||||
SeatGroup.destroy({ truncate: true })
|
||||
@@ -52,11 +53,26 @@ export function deleteAllTables() {
|
||||
Payment.destroy({ truncate: true })
|
||||
Account.destroy({ truncate: true })
|
||||
AccountRole.destroy({ truncate: true})
|
||||
}
|
||||
|
||||
export function deleteExerciseProgressTables() {
|
||||
Exercise.destroy({truncate: true})
|
||||
ExerciseGroup.destroy({truncate: true})
|
||||
}
|
||||
|
||||
export async function prepopulateExerciseDatabase() {
|
||||
for (let exerciseGroup of exercises.data) {
|
||||
ExerciseGroup.create(exerciseGroup)
|
||||
.then(async dataset => {
|
||||
for (let exercise of exerciseGroup.exercises) {
|
||||
exercise["exerciseGroupId"] = dataset.id
|
||||
|
||||
await Exercise.create(exercise)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert default datasets in the database tables
|
||||
*/
|
||||
@@ -195,15 +211,4 @@ export async function prepopulateDatabase() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for (let exerciseGroup of exercises.data) {
|
||||
ExerciseGroup.create(exerciseGroup)
|
||||
.then(async dataset => {
|
||||
for (let exercise of exerciseGroup.exercises) {
|
||||
exercise["exerciseGroupId"] = dataset.id
|
||||
|
||||
await Exercise.create(exercise)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
53
software/src/components/navigation/footerItems.vue
Normal file
53
software/src/components/navigation/footerItems.vue
Normal 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>
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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:"
|
||||
}
|
||||
|
||||
@@ -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:"
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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> = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user