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:
@@ -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>
|
||||
|
||||
@@ -12,6 +12,9 @@ export const auth = reactive({
|
||||
get isAdmin() {
|
||||
return this.role === 'admin' || this.role === 'super_admin'
|
||||
},
|
||||
get isSuperAdmin() {
|
||||
return this.role === 'super_admin'
|
||||
},
|
||||
async load() {
|
||||
if (!this.token) return null
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user