feat(ip): protect persona 'formula' — secret fields only super_admin sees/edits

IP protection so casual copying yields inferior results:
- SECRET_PERSONA_FIELDS (pains/objections/negotiation_levers/opener/tolerance +
  pain rootCause/resolutionConditions): only super_admin can view/edit them.
- list_personas/get_persona/update_persona/get_group strip these for role=admin (and
  hide sales_kit + pain-fit report from admins too).
- update_persona rejects admin attempts to set secret fields (403).
- PersonaForm hides the 'การขาย' recipe section for non-super-admin (shows locked note);
  auth.isSuperAdmin getter added.
Rebuilt dist. Added test_ip_protection.
This commit is contained in:
Macky
2026-08-09 07:22:50 +07:00
parent a04bc2add8
commit e1d61e1e1e
29 changed files with 156 additions and 39 deletions

View File

@@ -36,8 +36,8 @@
<textarea v-model="d.communication_style"></textarea>
</div>
<!-- Section: การขาย -->
<div class="sec">
<!-- Section: การขาย (secret super_admin only) -->
<div v-if="auth.isSuperAdmin" class="sec">
<h4>🎯 การขาย</h4>
<label>เปาหมาย</label><textarea v-model="d.goal"></textarea>
<label>กรอบเวลาในการตดสนใจ</label><input v-model="d.decision_timeline" />
@@ -50,6 +50,12 @@
<label>องทางสำหรบลกคาเปดบทสนทนา (opener)</label>
<textarea v-model="d.opener"></textarea>
</div>
<div v-else class="sec locked">
<h4>🔒 วนสตรการขาย</h4>
<p class="muted">ดการแกไข เฉพาะผแลระดบสงเทานนท/แกได (เพอรกษาความไดเปรยบ)</p>
<label>เปาหมาย</label><textarea v-model="d.goal"></textarea>
<label>กรอบเวลาในการตดสนใจ</label><input v-model="d.decision_timeline" />
</div>
<!-- Section: องทาง/โหมด -->
<div class="sec">
@@ -62,7 +68,7 @@
<select v-model="d.initiation_mode"><option value="customer">ลูกค้าทักก่อน</option><option value="seller">เราต้องเริ่มขาย (เชิงรุก)</option></select>
</div>
</div>
<label class="muted" v-if="d.special">พิเศษ: {{ d.special }}</label>
<label class="muted" v-if="auth.isSuperAdmin && d.special">พิเศษ: {{ d.special }}</label>
</div>
<div class="row actions">
@@ -74,6 +80,7 @@
<script setup>
import { reactive, ref, watch } from 'vue'
import { auth } from '../store/auth'
const props = defineProps({ persona: { type: Object, required: true } })
const emit = defineEmits(['save', 'cancel'])
@@ -126,4 +133,6 @@ function save() {
label { font-size: 13px; color: var(--muted); display: block; margin: 10px 0 4px; }
input, select, textarea { width: 100%; }
.actions { margin-top: 20px; }
.locked { background: #fffaf5; border: 1px dashed #d6c7a1; border-radius: 12px; padding: 12px 14px; }
.locked h4 { color: #b45309; }
</style>