fix(security): complete security and delivery compliance remediation

This commit is contained in:
2026-07-28 17:56:28 +08:00
parent 5b07ec6df0
commit 81ab82e9ba
32 changed files with 798 additions and 94 deletions
+7 -3
View File
@@ -1,10 +1,14 @@
# Stage 1: Build
FROM node:18-alpine AS build
ARG NODE_BASE_IMAGE=node:20-alpine
ARG NGINX_BASE_IMAGE=nginx:alpine
FROM ${NODE_BASE_IMAGE} AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
ARG NPM_CONFIG_REGISTRY
RUN if [ -n "$NPM_CONFIG_REGISTRY" ]; then npm config set registry "$NPM_CONFIG_REGISTRY"; fi \
&& npm ci
COPY . .
@@ -12,7 +16,7 @@ COPY . .
RUN npm run build
# Stage 2: Serve with nginx
FROM nginx:alpine AS serve
FROM ${NGINX_BASE_IMAGE} AS serve
# Remove default nginx config
RUN rm /etc/nginx/conf.d/default.conf
+29
View File
@@ -20,6 +20,35 @@ server {
access_log off;
}
# Keep the browser and API on the same origin in production. This must be
# evaluated before the SPA fallback, otherwise /api/* returns index.html.
location /api/ {
proxy_pass http://backend:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_connect_timeout 15s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
location /docs {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /openapi.json {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# SPA fallback - all routes serve index.html
location / {
try_files $uri $uri/ /index.html;
+21 -2
View File
@@ -19,15 +19,34 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { marked } from 'marked'
import { marked, Renderer } from 'marked'
import request from '../utils/request'
const content = ref('')
const loading = ref(true)
const safeLink = (href) => {
try {
const url = new URL(href, window.location.origin)
return ['http:', 'https:', 'mailto:'].includes(url.protocol) ? url.href : ''
} catch {
return ''
}
}
const safeRenderer = new Renderer()
safeRenderer.html = () => ''
safeRenderer.image = ({ text }) => text
safeRenderer.link = function ({ href, tokens }) {
const text = this.parser.parseInline(tokens)
const safeHref = safeLink(href)
if (!safeHref) return text
return `<a href="${safeHref}" rel="noopener noreferrer" target="_blank">${text}</a>`
}
const renderedContent = computed(() => {
if (!content.value) return ''
return marked.parse(content.value)
return marked.parse(content.value, { renderer: safeRenderer })
})
onMounted(async () => {
+1 -18
View File
@@ -8,8 +8,6 @@
<div v-else-if="error">
<p>登录失败: {{ error }}</p>
<el-button @click="$router.push('/login')">返回登录</el-button>
<el-button type="info" @click="showDetails = !showDetails">详细信息</el-button>
<pre v-if="showDetails" class="error-details">{{ errorDetails }}</pre>
</div>
</el-card>
</div>
@@ -26,8 +24,6 @@ const router = useRouter()
const authStore = useAuthStore()
const loading = ref(true)
const error = ref('')
const errorDetails = ref('')
const showDetails = ref(false)
onMounted(async () => {
const code = new URLSearchParams(window.location.search).get('code')
@@ -44,8 +40,7 @@ onMounted(async () => {
authStore.setToken(data.access_token)
router.push('/dashboard')
} catch (err) {
error.value = err.message || '登录失败'
errorDetails.value = err.response?.data?.detail || JSON.stringify(err, null, 2)
error.value = err.response?.data?.detail || '登录失败,请稍后重试'
loading.value = false
}
})
@@ -58,16 +53,4 @@ onMounted(async () => {
align-items: center;
height: 100vh;
}
.error-details {
margin-top: 12px;
padding: 12px;
background: #f5f5f5;
border-radius: 4px;
font-size: 12px;
text-align: left;
white-space: pre-wrap;
word-break: break-all;
max-height: 200px;
overflow-y: auto;
}
</style>