Files
TimeTracker/templates/projects/edit.html
T
Dries Peeters 9b7aa3a938 security: Add CSRF token protection to all POST forms" -m " Complete CSRF protection implementation across the entire application. Fixed 31 HTML forms and 4 JavaScript dynamic form generators that were missing CSRF tokens.
Affected modules: Projects, Clients, Tasks, Invoices, Comments, Admin, Search

- All HTML forms now include csrf_token hidden input
- JavaScript forms retrieve token from meta tag in base.html
- API endpoints properly exempted for JSON operations
- 58 POST forms + 4 dynamic JS forms now protected

Security impact: HIGH - Closes critical CSRF vulnerability
Files modified: 20 templates
2025-10-11 09:01:58 +02:00

147 lines
8.2 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ _('Edit Project') }} - {{ app_name }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('projects.list_projects') }}">{{ _('Projects') }}</a></li>
<li class="breadcrumb-item"><a href="{{ url_for('projects.view_project', project_id=project.id) }}">{{ project.name }}</a></li>
<li class="breadcrumb-item active">{{ _('Edit') }}</li>
</ol>
</nav>
<h1 class="h3 mb-0">
<i class="fas fa-project-diagram text-primary"></i> {{ _('Edit Project') }}
</h1>
</div>
<div>
<a href="{{ url_for('projects.view_project', project_id=project.id) }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> {{ _('Back to Project') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-info-circle"></i> {{ _('Project Information') }}
</h5>
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('projects.edit_project', project_id=project.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label for="name" class="form-label">{{ _('Project Name') }} *</label>
<input type="text" class="form-control" id="name" name="name" required value="{{ request.form.get('name', project.name) }}">
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="client_id" class="form-label">{{ _('Client') }} *</label>
<select class="form-control" id="client_id" name="client_id" required>
<option value="">{{ _('Select a client...') }}</option>
{% for client in clients %}
<option value="{{ client.id }}"
{% if request.form.get('client_id', project.client_id) == client.id %}selected{% endif %}
data-default-rate="{{ client.default_hourly_rate or '' }}">
{{ client.name }}
</option>
{% endfor %}
</select>
<div class="form-text">
<a href="{{ url_for('clients.create_client') }}" class="text-decoration-none">
<i class="fas fa-plus"></i> {{ _('Create new client') }}
</a>
</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="description" class="form-label">{{ _('Description') }}</label>
<textarea class="form-control" id="description" name="description" rows="3">{{ request.form.get('description', project.description or '') }}</textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="billable" name="billable" {% if (request.form and request.form.get('billable')) or (not request.form and project.billable) %}checked{% endif %}>
<label class="form-check-label" for="billable">{{ _('Billable') }}</label>
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="hourly_rate" class="form-label">{{ _('Hourly Rate') }}</label>
<input type="number" step="0.01" min="0" class="form-control" id="hourly_rate" name="hourly_rate" value="{{ request.form.get('hourly_rate', project.hourly_rate or '') }}" placeholder="e.g. 75.00">
<div class="form-text">{{ _('Leave empty for non-billable projects') }}</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="billing_ref" class="form-label">{{ _('Billing Reference') }}</label>
<input type="text" class="form-control" id="billing_ref" name="billing_ref" value="{{ request.form.get('billing_ref', project.billing_ref or '') }}" placeholder="Optional">
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="budget_amount" class="form-label">{{ _('Budget Amount') }}</label>
<input type="number" step="0.01" min="0" class="form-control" id="budget_amount" name="budget_amount" value="{{ request.form.get('budget_amount', project.budget_amount or '') }}" placeholder="{{ _('e.g. 10000.00') }}">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="budget_threshold_percent" class="form-label">{{ _('Alert Threshold (%)') }}</label>
<input type="number" step="1" min="0" max="100" class="form-control" id="budget_threshold_percent" name="budget_threshold_percent" value="{{ request.form.get('budget_threshold_percent', project.budget_threshold_percent or 80) }}" placeholder="80">
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<a href="{{ url_for('projects.view_project', project_id=project.id) }}" class="btn btn-secondary">
<i class="fas fa-times"></i> {{ _('Cancel') }}
</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> {{ _('Save Changes') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const clientSelect = document.getElementById('client_id');
const hourlyRateInput = document.getElementById('hourly_rate');
clientSelect.addEventListener('change', function() {
const selectedOption = this.options[this.selectedIndex];
const defaultRate = selectedOption.getAttribute('data-default-rate');
if (defaultRate && !hourlyRateInput.value) {
hourlyRateInput.value = defaultRate;
}
});
});
</script>
{% endblock %}