fix(meeting): Task 16 修复 code-reviewer 审计 P0 四项 + 落盘审查报告
code-reviewer 子代理(docs/reviews/2026-04-23-phase2e-2-code-review.md)对 Phase 2e-2 整体做全栈审计,暴露 P0 × 4 / P1 × 8 / P2 × 8 / Nit × 11。本次先修 P0: - P0-1 / P0-2 媒体资源归属越权: 新增 assertOwnsResource(roomCode, userID, kind, id) helper,用 resourceTrackKey Redis Set SIsMember 校验 transport/producer/consumer 归属;统一接入 OnTransportConnect / OnProduceStart / OnConsumeStart / OnConsumeResume / OnProducerClose 五个信令入口, 一次封死同类横向越权漏洞(Redis 查询失败时 fail-closed,避免抖动开口子)。 新增 service.ErrResourceNotOwned 错误常量。 - P0-3 CreateRoom Router 失败吞错: 旧实现 mediaErr != nil 仅 logs.Warn 继续返回成功 → zombie 房间 + 入会必败 + 一人一会议名额被占。改为 fail-closed 补偿:LeaveRoom + MarkEnded(system_error) + 返回新 service.ErrMediaServiceUnavailable,controller.handleError 映射为 500。 - P0-4 会议密码明文走 URL: join.vue onNext 旧逻辑把密码拼 ?password=xxx,浏览器历史 / DevTools 留痕。 新增 meetingStore.draftJoinPayload 内存态草稿,join.vue 只跳转 ?mode=join&code=xxx,preview.vue onLoad 读取后立即 draftJoinPayload = null 清空, 邀请链接里的 password 参数一律忽略。 验证: - cd backend/go-service && go build ./... 通过 - cd frontend && npm run build:h5 通过 - 仅剩 1 个预先存在 unusedparams warning,与本次改动无关 P1 / P2 / Nit 清单保留在审查报告,按顺序继续处理。 Made-with: Cursor
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
<!--
|
||||
加入会议表单页(Task 10)
|
||||
加入会议表单页(Task 10;Task 16 P0-4 修复密码明文走 URL 的问题)
|
||||
|
||||
数据流:
|
||||
用户输入会议号(可粘贴带 / 不带连字符的 9 位数字)+ 可选密码 → 点击"下一步"
|
||||
→ uni.navigateTo('/pages/meeting/preview?mode=join&code=XXX-XXX-XXX&password=xxx')
|
||||
→ 预览页选定设备后真正调用 joinAndEnter
|
||||
→ 把 { code, password } 写入 meetingStore.draftJoinPayload
|
||||
→ uni.navigateTo('/pages/meeting/preview?mode=join&code=XXX-XXX-XXX') // URL 不再带密码
|
||||
→ 预览页从 draftJoinPayload 读密码并立即清空,再调用 joinAndEnter
|
||||
|
||||
邀请链接支持:
|
||||
邀请链接支持(设计 §2.2.1):
|
||||
- URL 参数 code=XXX-XXX-XXX(必传时直接回填)
|
||||
- URL 参数 password=xxx(可选,从邀请短链来)
|
||||
- 参考设计 §2.2.1:邀请链接形如 /#/pages/meeting/join?code=XXX-XXX-XXX
|
||||
- 严禁 URL 参数 password=xxx(旧实现已下线;邀请链接走 token 机制,password 只在当前页面表单内)
|
||||
-->
|
||||
<template>
|
||||
<view class="page">
|
||||
@@ -53,7 +53,9 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useMeetingStore } from '@/store/meeting'
|
||||
|
||||
const meetingStore = useMeetingStore()
|
||||
const rawCodeInput = ref('')
|
||||
const form = reactive({ password: '' })
|
||||
|
||||
@@ -93,17 +95,15 @@ onLoad((query) => {
|
||||
const d = digitsOf(query.code)
|
||||
if (d.length === 9) rawCodeInput.value = formatCode(d)
|
||||
}
|
||||
if (query?.password) {
|
||||
form.password = String(query.password)
|
||||
}
|
||||
// P0-4:邀请链接只允许带 code / token,不再接受 password query;历史 URL 里若仍有 password 一律忽略
|
||||
})
|
||||
|
||||
const onNext = () => {
|
||||
if (!isFormValid.value) return
|
||||
const code = formattedCode.value
|
||||
const passwordQuery = form.password ? `&password=${encodeURIComponent(form.password)}` : ''
|
||||
meetingStore.draftJoinPayload = { code, password: form.password || '' }
|
||||
uni.navigateTo({
|
||||
url: `/pages/meeting/preview?mode=join&code=${code}${passwordQuery}`
|
||||
url: `/pages/meeting/preview?mode=join&code=${code}`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -404,7 +404,17 @@ const onJoin = async () => {
|
||||
onLoad(async (query) => {
|
||||
mode.value = query?.mode === 'join' ? 'join' : 'create'
|
||||
joinCode.value = query?.code || ''
|
||||
joinPassword.value = query?.password ? decodeURIComponent(query.password) : ''
|
||||
// P0-4:密码不再从 URL query 读取,改为从 meetingStore.draftJoinPayload 一次性消费
|
||||
// 防止 URL 历史 / DevTools 留痕;读取后立即清空草稿避免二次写回
|
||||
if (mode.value === 'join') {
|
||||
const draft = meetingStore.draftJoinPayload
|
||||
if (draft && (!joinCode.value || draft.code === joinCode.value)) {
|
||||
joinPassword.value = draft.password || ''
|
||||
meetingStore.draftJoinPayload = null
|
||||
} else {
|
||||
joinPassword.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 先请求权限并预览,默认拿到第一个设备
|
||||
await startPreview()
|
||||
|
||||
Reference in New Issue
Block a user