feat(api): implement rate limiting and SSRF protection across endpoints
- Added rate limiting to `reaction-users`, `search`, and `image-proxy` APIs to prevent abuse. - Introduced SSRF protection in `image-proxy` to block requests to private IP ranges. - Enhanced `link-preview` to use `linkedom` for HTML parsing and improved meta tag extraction. - Refactored authentication checks in various pages to utilize middleware for cleaner code. - Improved JWT key loading with error handling and security warnings for production. - Updated `authFetch` utility to handle token refresh more efficiently with deduplication. - Enhanced rate limiting utility to trust proxy headers from known sources. - Numerous layout / design changes
This commit is contained in:
@@ -1,81 +1,442 @@
|
||||
/* Table and Dark Overrides */
|
||||
.p-datatable {
|
||||
table-layout: fixed !important;
|
||||
.trip-management-container {
|
||||
width: 100%;
|
||||
}
|
||||
.p-datatable td span.truncate {
|
||||
|
||||
.trip-management-container .table-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.trip-management-container .p-datatable {
|
||||
width: 100% !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.trip-management-container .p-datatable-wrapper {
|
||||
width: 100% !important;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.trip-management-container .p-datatable-table {
|
||||
width: 100% !important;
|
||||
table-layout: fixed !important;
|
||||
min-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Force header and body rows to fill width */
|
||||
.trip-management-container .p-datatable-thead,
|
||||
.trip-management-container .p-datatable-tbody {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.trip-management-container .p-datatable-thead > tr,
|
||||
.trip-management-container .p-datatable-tbody > tr {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Column widths - distribute across table */
|
||||
.trip-management-container .p-datatable-thead > tr > th,
|
||||
.trip-management-container .p-datatable-tbody > tr > td {
|
||||
/* Default: auto distribute */
|
||||
}
|
||||
|
||||
/* ID column - narrow */
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(1),
|
||||
.trip-management-container .p-datatable-tbody > tr > td:nth-child(1) {
|
||||
width: 10% !important;
|
||||
}
|
||||
|
||||
/* Target column - widest */
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(2),
|
||||
.trip-management-container .p-datatable-tbody > tr > td:nth-child(2) {
|
||||
width: 22% !important;
|
||||
}
|
||||
|
||||
/* Tracks column */
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(3),
|
||||
.trip-management-container .p-datatable-tbody > tr > td:nth-child(3) {
|
||||
width: 10% !important;
|
||||
}
|
||||
|
||||
/* Status column */
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(4),
|
||||
.trip-management-container .p-datatable-tbody > tr > td:nth-child(4) {
|
||||
width: 12% !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Progress column */
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(5),
|
||||
.trip-management-container .p-datatable-tbody > tr > td:nth-child(5) {
|
||||
width: 16% !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Quality column */
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(6),
|
||||
.trip-management-container .p-datatable-tbody > tr > td:nth-child(6) {
|
||||
width: 10% !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Tarball column - fills remaining */
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(7),
|
||||
.trip-management-container .p-datatable-tbody > tr > td:nth-child(7) {
|
||||
width: 20% !important;
|
||||
}
|
||||
|
||||
.trip-management-container .p-datatable td span.truncate {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Row hover cursor - indicate clickable */
|
||||
.trip-management-container .p-datatable-tbody > tr {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
/* Center-align headers for centered columns */
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(4),
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(5),
|
||||
.trip-management-container .p-datatable-thead > tr > th:nth-child(6) {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
/* Skeleton loading styles */
|
||||
.table-skeleton {
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
padding: 1rem 0.75rem;
|
||||
border-bottom: 1px solid rgba(128, 128, 128, 0.2);
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.skeleton-bar {
|
||||
height: 1rem;
|
||||
background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
border-radius: 0.25rem;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* Light mode skeleton */
|
||||
[data-theme="light"] .skeleton-bar {
|
||||
background: linear-gradient(90deg, #e5e5e5 25%, #f0f0f0 50%, #e5e5e5 75%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
/* Empty state styles */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 3rem;
|
||||
color: #6b7280;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-state-text {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: #9ca3af;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.empty-state-subtext {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* Dark Mode for Table */
|
||||
[data-theme="dark"] .p-datatable {
|
||||
background-color: #121212 !important;
|
||||
[data-theme="dark"] .trip-management-container .p-datatable {
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
[data-theme="dark"] .p-datatable-thead > tr > th {
|
||||
[data-theme="dark"] .trip-management-container .p-datatable-thead > tr > th {
|
||||
background-color: #1f1f1f !important;
|
||||
color: #e5e7eb !important;
|
||||
border-bottom: 1px solid #374151;
|
||||
}
|
||||
[data-theme="dark"] .p-datatable-tbody > tr {
|
||||
[data-theme="dark"] .trip-management-container .p-datatable-tbody > tr {
|
||||
background-color: #1a1a1a !important;
|
||||
border-bottom: 1px solid #374151;
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
[data-theme="dark"] .p-datatable-tbody > tr:nth-child(odd) {
|
||||
[data-theme="dark"] .trip-management-container .p-datatable-tbody > tr:nth-child(odd) {
|
||||
background-color: #222 !important;
|
||||
}
|
||||
[data-theme="dark"] .p-datatable-tbody > tr:hover {
|
||||
[data-theme="dark"] .trip-management-container .p-datatable-tbody > tr:hover {
|
||||
background-color: #333 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* Paginator Dark Mode */
|
||||
[data-theme="dark"] .p-paginator {
|
||||
[data-theme="dark"] .trip-management-container .p-paginator {
|
||||
background-color: #121212 !important;
|
||||
color: #e5e7eb !important;
|
||||
border-top: 1px solid #374151 !important;
|
||||
}
|
||||
[data-theme="dark"] .p-paginator .p-paginator-page,
|
||||
[data-theme="dark"] .p-paginator .p-paginator-next,
|
||||
[data-theme="dark"] .p-paginator .p-paginator-prev,
|
||||
[data-theme="dark"] .p-paginator .p-paginator-first,
|
||||
[data-theme="dark"] .p-paginator .p-paginator-last {
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-paginator-page,
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-paginator-next,
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-paginator-prev,
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-paginator-first,
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-paginator-last {
|
||||
color: #e5e7eb !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
[data-theme="dark"] .p-paginator .p-paginator-page:hover,
|
||||
[data-theme="dark"] .p-paginator .p-paginator-next:hover,
|
||||
[data-theme="dark"] .p-paginator .p-paginator-prev:hover {
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-paginator-page:hover,
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-paginator-next:hover,
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-paginator-prev:hover {
|
||||
background-color: #374151 !important;
|
||||
color: #fff !important;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
[data-theme="dark"] .p-paginator .p-highlight {
|
||||
[data-theme="dark"] .trip-management-container .p-paginator .p-highlight {
|
||||
background-color: #6b7280 !important;
|
||||
color: #fff !important;
|
||||
border-radius: 0.25rem !important;
|
||||
}
|
||||
|
||||
/* Dark Mode for PrimeReact Dialog */
|
||||
[data-theme="dark"] .p-dialog {
|
||||
/* Dark Mode for PrimeReact Dialog - rendered via portal so needs global selector */
|
||||
[data-theme="dark"] .p-dialog.dark\:bg-neutral-900 {
|
||||
background-color: #1a1a1a !important;
|
||||
color: #e5e7eb !important;
|
||||
border-color: #374151 !important;
|
||||
}
|
||||
[data-theme="dark"] .p-dialog .p-dialog-header {
|
||||
background-color: #121212 !important;
|
||||
[data-theme="dark"] .p-dialog.dark\:bg-neutral-900 .p-dialog-header {
|
||||
background-color: #171717 !important;
|
||||
color: #e5e7eb !important;
|
||||
border-bottom: 1px solid #374151 !important;
|
||||
}
|
||||
[data-theme="dark"] .p-dialog .p-dialog-content {
|
||||
[data-theme="dark"] .p-dialog.dark\:bg-neutral-900 .p-dialog-header .p-dialog-header-icon {
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
[data-theme="dark"] .p-dialog.dark\:bg-neutral-900 .p-dialog-header .p-dialog-header-icon:hover {
|
||||
background-color: #374151 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
[data-theme="dark"] .p-dialog.dark\:bg-neutral-900 .p-dialog-content {
|
||||
background-color: #1a1a1a !important;
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
[data-theme="dark"] .p-dialog .p-dialog-footer {
|
||||
background-color: #121212 !important;
|
||||
[data-theme="dark"] .p-dialog.dark\:bg-neutral-900 .p-dialog-footer {
|
||||
background-color: #171717 !important;
|
||||
border-top: 1px solid #374151 !important;
|
||||
}
|
||||
|
||||
/* Progress Bar Styles */
|
||||
.progress-bar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.progress-bar-track {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background-color: rgba(128, 128, 128, 0.2);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar-track-lg {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-bar-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
min-width: 2.5rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Container Styles */
|
||||
.trip-management-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.trip-management-container .overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.trip-request-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.trip-management-container {
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.trip-management-container h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* Stack filters on mobile */
|
||||
.trip-management-container .flex-wrap {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Make table horizontally scrollable */
|
||||
.trip-management-container .overflow-x-auto {
|
||||
margin: 0 -1rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Reduce column widths on mobile */
|
||||
.p-datatable-thead > tr > th,
|
||||
.p-datatable-tbody > tr > td {
|
||||
padding: 0.5rem 0.25rem !important;
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
/* Hide less important columns on small screens */
|
||||
.p-datatable .hide-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.trip-management-container {
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.progress-bar-track {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.progress-bar-text {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== MediaRequestForm Mobile Styles ===== */
|
||||
|
||||
/* Form container responsive */
|
||||
.trip-request-form {
|
||||
width: 100%;
|
||||
max-width: 48rem;
|
||||
margin: 2.5rem auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.trip-request-form {
|
||||
margin: 1rem auto;
|
||||
padding: 1rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.trip-request-form h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* Quality buttons stack on mobile */
|
||||
.trip-quality-buttons {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.trip-quality-buttons button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Accordion improvements for mobile */
|
||||
.p-accordion-header .p-accordion-header-link {
|
||||
padding: 0.75rem !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
.p-accordion-content {
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
/* Track list items more compact on mobile */
|
||||
.p-accordion-content li {
|
||||
padding: 0.5rem !important;
|
||||
font-size: 0.85rem !important;
|
||||
}
|
||||
|
||||
/* Album header info stacks */
|
||||
.album-header-info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start !important;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
/* Audio player controls smaller on mobile */
|
||||
.track-audio-controls button {
|
||||
padding: 0.25rem 0.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.trip-request-form {
|
||||
margin: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.trip-request-form h2 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
/* Input fields full width */
|
||||
.p-autocomplete,
|
||||
.p-autocomplete-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Smaller text in track listings */
|
||||
.p-accordion-content li span {
|
||||
font-size: 0.75rem !important;
|
||||
}
|
||||
|
||||
/* Submit button full width */
|
||||
.trip-submit-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user