Initial commit: Imperva WAF demo lab
Flight-booking app scaffolded from the source running on the GCP host, plus the four-screen frontend and three OWASP attack endpoints (SQLi, reflected XSS, path traversal) built and locally verified against a live uvicorn/vite dev pair. Includes deploy.sh (local build -> ship over ssh -> swap on host) and docs/ carried over from the handoff.
This commit is contained in:
commit
4d60fdd139
28 files changed
+2609
No files matched your search
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { downloadBoardingPass } from '../api.js'
|
||||
|
||||
const file = ref('BP-1001.txt')
|
||||
const content = ref('')
|
||||
const error = ref('')
|
||||
|
||||
async function runDownload() {
|
||||
error.value = ''
|
||||
content.value = ''
|
||||
const { ok, status, text } = await downloadBoardingPass(file.value)
|
||||
if (!ok) {
|
||||
error.value = `Request failed (${status})`
|
||||
return
|
||||
}
|
||||
content.value = text
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="panel">
|
||||
<h2>Boarding Pass Download</h2>
|
||||
<div class="field-row">
|
||||
<input
|
||||
type="text"
|
||||
v-model="file"
|
||||
placeholder="Boarding pass filename (e.g. BP-1001.txt)"
|
||||
@keyup.enter="runDownload"
|
||||
/>
|
||||
<button class="primary" @click="runDownload">Download</button>
|
||||
</div>
|
||||
|
||||
<p class="status error" v-if="error">{{ error }}</p>
|
||||
<pre class="result" v-if="content">{{ content }}</pre>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { searchFlights } from '../api.js'
|
||||
|
||||
const term = ref('')
|
||||
const queryEcho = ref('')
|
||||
const results = ref([])
|
||||
const searched = ref(false)
|
||||
|
||||
async function runSearch() {
|
||||
const { body } = await searchFlights(term.value)
|
||||
queryEcho.value = body?.query_echo ?? ''
|
||||
results.value = body?.results ?? []
|
||||
searched.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="panel">
|
||||
<h2>Flight Search</h2>
|
||||
<div class="field-row">
|
||||
<input
|
||||
type="text"
|
||||
v-model="term"
|
||||
placeholder="Search destinations (e.g. Mars)"
|
||||
@keyup.enter="runSearch"
|
||||
/>
|
||||
<button class="primary" @click="runSearch">Search</button>
|
||||
</div>
|
||||
|
||||
<!-- Intentionally unescaped: the API echoes the raw search term back
|
||||
verbatim and this renders it as HTML, not text. -->
|
||||
<p class="echo" v-if="searched" v-html="queryEcho"></p>
|
||||
|
||||
<table v-if="results.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Flight</th>
|
||||
<th>Origin</th>
|
||||
<th>Destination</th>
|
||||
<th>Departs</th>
|
||||
<th>Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="f in results" :key="f.id">
|
||||
<td>{{ f.id }}</td>
|
||||
<td>{{ f.origin }}</td>
|
||||
<td>{{ f.destination }}</td>
|
||||
<td>{{ f.depart }}</td>
|
||||
<td>${{ f.price.toLocaleString() }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { login } from '../api.js'
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const message = ref('')
|
||||
const success = ref(false)
|
||||
|
||||
async function runLogin() {
|
||||
const { body } = await login(username.value, password.value)
|
||||
success.value = body?.status === 'authenticated'
|
||||
message.value = success.value
|
||||
? `Authenticated. Token: ${body.token}`
|
||||
: (body?.message ?? 'Login failed')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="panel">
|
||||
<h2>Login</h2>
|
||||
<div class="field-row">
|
||||
<input type="text" v-model="username" placeholder="Username" />
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<input type="password" v-model="password" placeholder="Password" @keyup.enter="runLogin" />
|
||||
<button class="primary" @click="runLogin">Log in</button>
|
||||
</div>
|
||||
|
||||
<p class="status" :class="success ? 'ok' : 'error'" v-if="message">{{ message }}</p>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { searchUsers } from '../api.js'
|
||||
|
||||
const q = ref('')
|
||||
const results = ref([])
|
||||
const error = ref('')
|
||||
|
||||
async function runSearch() {
|
||||
error.value = ''
|
||||
const { ok, body } = await searchUsers(q.value)
|
||||
if (!ok) {
|
||||
error.value = body?.detail ?? 'Search failed'
|
||||
results.value = []
|
||||
return
|
||||
}
|
||||
results.value = body ?? []
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="panel">
|
||||
<h2>Passenger / Admin Lookup</h2>
|
||||
<div class="field-row">
|
||||
<input
|
||||
type="text"
|
||||
v-model="q"
|
||||
placeholder="Search by username"
|
||||
@keyup.enter="runSearch"
|
||||
/>
|
||||
<button class="primary" @click="runSearch">Search</button>
|
||||
</div>
|
||||
|
||||
<p class="status error" v-if="error">{{ error }}</p>
|
||||
|
||||
<table v-if="results.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="u in results" :key="u.id">
|
||||
<td>{{ u.id }}</td>
|
||||
<td>{{ u.username }}</td>
|
||||
<td>{{ u.email }}</td>
|
||||
<td>{{ u.role }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
Reference in new issue
Block a user