Remove unsafe-inline from the Content-Security-Policy (closes #125)
check / check (push) Waiting to run
check / check (push) Waiting to run
script-src and style-src now allow only 'self'. The generator page's two inline onclick handlers, which selected the generated URL and copied it, move into internal/static/generator.js and are attached with addEventListener. The bundled Tailwind script, which built styles in the browser and injected them at runtime, is replaced by a small hand-written internal/static/style.css holding only the rules the login and generator pages use; the templates carry a few plain class names in place of Tailwind's. No build step. The pages keep their layout, not every pixel of it. Model: opus-5-5
This commit is contained in:
@@ -4,52 +4,47 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pixa - URL Generator</title>
|
||||
<script src="/static/tailwind.js"></script>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen">
|
||||
<div class="max-w-2xl mx-auto py-8 px-4">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-800">Pixa URL Generator</h1>
|
||||
<a href="/logout" class="text-sm text-gray-600 hover:text-gray-800 underline">
|
||||
<body>
|
||||
<div class="page">
|
||||
<header>
|
||||
<h1>Pixa URL Generator</h1>
|
||||
<a href="/logout">
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{if .GeneratedURL}}
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
|
||||
<h2 class="text-sm font-medium text-green-800 mb-2">Generated URL</h2>
|
||||
<div class="flex gap-2">
|
||||
<div class="result">
|
||||
<h2>Generated URL</h2>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value="{{.GeneratedURL}}"
|
||||
id="generated-url"
|
||||
class="flex-1 px-3 py-2 bg-white border border-green-300 rounded-md text-sm font-mono"
|
||||
onclick="this.select()"
|
||||
>
|
||||
<button
|
||||
onclick="navigator.clipboard.writeText(document.getElementById('generated-url').value)"
|
||||
class="px-3 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 text-sm"
|
||||
>
|
||||
<button id="copy-url">
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-green-600 mt-2">
|
||||
<p>
|
||||
Expires: {{.ExpiresAt}}
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Error}}
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6">
|
||||
<div class="error">
|
||||
{{.Error}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<form method="POST" action="/generate" class="bg-white rounded-lg shadow-md p-6 space-y-4">
|
||||
<form method="POST" action="/generate" class="card">
|
||||
{{ .CSRFField }}
|
||||
<div>
|
||||
<label for="url" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="url">
|
||||
Source URL
|
||||
</label>
|
||||
<input
|
||||
@@ -59,13 +54,12 @@
|
||||
required
|
||||
placeholder="https://example.com/image.jpg"
|
||||
value="{{.FormURL}}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="columns">
|
||||
<div>
|
||||
<label for="width" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="width">
|
||||
Width
|
||||
</label>
|
||||
<input
|
||||
@@ -76,11 +70,10 @@
|
||||
max="8192"
|
||||
value="{{if .FormWidth}}{{.FormWidth}}{{else}}0{{end}}"
|
||||
placeholder="0 = original"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label for="height" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="height">
|
||||
Height
|
||||
</label>
|
||||
<input
|
||||
@@ -91,21 +84,16 @@
|
||||
max="8192"
|
||||
value="{{if .FormHeight}}{{.FormHeight}}{{else}}0{{end}}"
|
||||
placeholder="0 = original"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="columns">
|
||||
<div>
|
||||
<label for="format" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="format">
|
||||
Format
|
||||
</label>
|
||||
<select
|
||||
id="format"
|
||||
name="format"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<select id="format" name="format">
|
||||
<option value="orig" {{if eq .FormFormat "orig"}}selected{{end}}>Original</option>
|
||||
<option value="jpeg" {{if eq .FormFormat "jpeg"}}selected{{end}}>JPEG</option>
|
||||
<option value="png" {{if eq .FormFormat "png"}}selected{{end}}>PNG</option>
|
||||
@@ -115,14 +103,10 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="quality" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="quality">
|
||||
Quality
|
||||
</label>
|
||||
<select
|
||||
id="quality"
|
||||
name="quality"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<select id="quality" name="quality">
|
||||
<option value="25" {{if eq .FormQuality "25"}}selected{{end}}>Potato</option>
|
||||
<option value="50" {{if eq .FormQuality "50"}}selected{{end}}>Low</option>
|
||||
<option value="70" {{if eq .FormQuality "70"}}selected{{end}}>Medium</option>
|
||||
@@ -132,16 +116,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="columns">
|
||||
<div>
|
||||
<label for="fit" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="fit">
|
||||
Fit Mode
|
||||
</label>
|
||||
<select
|
||||
id="fit"
|
||||
name="fit"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<select id="fit" name="fit">
|
||||
<option value="cover" {{if eq .FormFit "cover"}}selected{{end}}>Cover</option>
|
||||
<option value="contain" {{if eq .FormFit "contain"}}selected{{end}}>Contain</option>
|
||||
<option value="fill" {{if eq .FormFit "fill"}}selected{{end}}>Fill</option>
|
||||
@@ -150,14 +130,10 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="ttl" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="ttl">
|
||||
Expires In
|
||||
</label>
|
||||
<select
|
||||
id="ttl"
|
||||
name="ttl"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<select id="ttl" name="ttl">
|
||||
<option value="0" {{if or (eq .FormTTL "0") (eq .FormTTL "")}}selected{{end}}>Never</option>
|
||||
<option value="60" {{if eq .FormTTL "60"}}selected{{end}}>1 minute</option>
|
||||
<option value="3600" {{if eq .FormTTL "3600"}}selected{{end}}>1 hour</option>
|
||||
@@ -169,17 +145,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
|
||||
>
|
||||
<button type="submit">
|
||||
Generate Encrypted URL
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="text-xs text-gray-500 mt-4 text-center">
|
||||
<p class="note">
|
||||
Generated URLs are encrypted and cannot be modified. They will expire at the specified time.
|
||||
</p>
|
||||
</div>
|
||||
<script src="/static/generator.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user