PolicyTab-_cLEoCYi.js raw

   1  import{r as o,i as r,b as g,t as i,j as e,B as x,n as F}from"./index-DfKg850Q.js";const V=`{
   2    "kind": {
   3      "whitelist": [0, 1, 3, 6, 7, 10002],
   4      "blacklist": []
   5    },
   6    "global": {
   7      "description": "Global rules applied to all events",
   8      "size_limit": 65536,
   9      "max_age_of_event": 86400,
  10      "max_age_event_in_future": 300
  11    },
  12    "rules": {
  13      "1": {
  14        "description": "Kind 1 (short text notes)",
  15        "content_limit": 8192,
  16        "write_allow_follows": true
  17      },
  18      "30023": {
  19        "description": "Long-form articles",
  20        "content_limit": 100000,
  21        "tag_validation": {
  22          "d": "^[a-z0-9-]{1,64}$",
  23          "t": "^[a-z0-9-]{1,32}$"
  24        }
  25      }
  26    },
  27    "default_policy": "allow",
  28    "policy_admins": ["<your-hex-pubkey>"],
  29    "policy_follow_whitelist_enabled": true
  30  }`;function M(n){if(!n)return null;if(/^[0-9a-fA-F]{64}$/.test(n))return n.toLowerCase();if(n.startsWith("npub1"))try{const{type:d,data:a}=F.decode(n);if(d==="npub"&&typeof a=="string")return a}catch{return null}return null}function D(n){return`${n.substring(0,16)}...${n.substring(n.length-8)}`}function q(){const[n,d]=o.useState(""),[a,c]=o.useState(!1),[v,w]=o.useState(!1),[_,J]=o.useState(!1),[k,P]=o.useState(""),[S,p]=o.useState([]),[b,E]=o.useState([]),[y,C]=o.useState(""),j=!!r.pubkey,f=o.useMemo(()=>{try{if(n)return JSON.parse(n).policy_admins||[]}catch{}return[]},[n]);o.useEffect(()=>{g.loadPolicyConfig().then(s=>{w(!!s.enabled)}).catch(()=>w(!1)),j&&g.fetchUserRole().then(s=>{P(s),A()}).catch(()=>P(""))},[j]),o.useEffect(()=>{r.pubkey&&f.length>0&&J(f.includes(r.pubkey))},[f]);const A=o.useCallback(async()=>{try{const s=await g.loadPolicy();s&&Object.keys(s).length>0&&d(JSON.stringify(s,null,2))}catch{}},[]),$=o.useCallback(async()=>{c(!0),p([]);try{const s=await r.fetchEvents(r.currentRelays,{kinds:[12345],limit:1});if(s&&s.length>0){let t=s[0].content;try{t=JSON.stringify(JSON.parse(t),null,2)}catch{}d(t),i.success("Policy loaded from relay event")}else{const t=await g.loadPolicy();t&&Object.keys(t).length>0?(d(JSON.stringify(t,null,2)),i.success("Policy loaded from file")):(d(""),i.info("No policy configuration found"))}}catch(s){i.error(`Error loading policy: ${s instanceof Error?s.message:String(s)}`)}finally{c(!1)}},[]),N=o.useCallback(()=>{const s=[];if(!n.trim())return p(["Policy JSON is empty"]),i.error("Validation failed"),!1;let t;try{t=JSON.parse(n)}catch(l){return p([`JSON parse error: ${l instanceof Error?l.message:String(l)}`]),i.error("Invalid JSON syntax"),!1}if(typeof t!="object"||t===null)return p(["Policy must be a JSON object"]),i.error("Validation failed"),!1;if(t.policy_admins)if(!Array.isArray(t.policy_admins))s.push("policy_admins must be an array");else for(const l of t.policy_admins)(typeof l!="string"||!/^[0-9a-fA-F]{64}$/.test(l))&&s.push(`Invalid policy_admin pubkey: ${l}`);if(t.rules){if(typeof t.rules!="object")s.push("rules must be an object");else for(const[l,m]of Object.entries(t.rules))if(/^\d+$/.test(l)||s.push(`Invalid kind number: ${l}`),m.tag_validation&&typeof m.tag_validation=="object")for(const[h,u]of Object.entries(m.tag_validation))try{new RegExp(u)}catch{s.push(`Invalid regex for tag '${h}': ${u}`)}}return t.default_policy&&!["allow","deny"].includes(t.default_policy)&&s.push("default_policy must be 'allow' or 'deny'"),p(s),s.length>0?(i.error("Validation failed - see errors below"),!1):(i.success("Validation passed"),!0)},[n]),R=o.useCallback(async()=>{if(N()){c(!0);try{if(!r.signer){i.error("No signer available. Please log in.");return}const t=await r.signer.signEvent({kind:12345,created_at:Math.floor(Date.now()/1e3),tags:[],content:n});await r.publishEvent(r.currentRelays,t),i.success("Policy updated and published")}catch(t){i.error(`Error saving policy: ${t instanceof Error?t.message:String(t)}`)}finally{c(!1)}}},[n,N]),z=o.useCallback(()=>{try{const s=JSON.parse(n);d(JSON.stringify(s,null,2)),i.success("JSON formatted")}catch(s){i.error(`Cannot format: ${s instanceof Error?s.message:String(s)}`)}},[n]),I=o.useCallback(async()=>{c(!0),E([]);try{let s=[];try{s=JSON.parse(n||"{}").policy_admins||[]}catch{i.error("Cannot parse policy JSON to get admins"),c(!1);return}if(s.length===0){i.warning("No policy admins configured"),c(!1);return}const t=await r.fetchEvents(r.currentRelays,{kinds:[3],authors:s,limit:s.length}),l=new Set;for(const h of t)if(h.tags)for(const u of h.tags)u[0]==="p"&&u[1]&&u[1].length===64&&l.add(u[1]);const m=Array.from(l);E(m),i.success(`Loaded ${m.length} follows from ${t.length} admin(s)`)}catch(s){i.error(`Error loading follows: ${s instanceof Error?s.message:String(s)}`)}finally{c(!1)}},[n]),O=o.useCallback(()=>{const s=y.trim();if(!s){i.error("Please enter a pubkey");return}const t=M(s);if(!t||t.length!==64){i.error("Invalid pubkey format. Use hex (64 chars) or npub");return}try{const l=JSON.parse(n||"{}");if(l.policy_admins||(l.policy_admins=[]),l.policy_admins.includes(t)){i.warning("Admin already in list");return}l.policy_admins.push(t),d(JSON.stringify(l,null,2)),C(""),i.info("Admin added - click 'Save & Publish' to apply")}catch(l){i.error(`Error adding admin: ${l instanceof Error?l.message:String(l)}`)}},[y,n]),L=o.useCallback(s=>{try{const t=JSON.parse(n||"{}");t.policy_admins&&(t.policy_admins=t.policy_admins.filter(l=>l!==s),d(JSON.stringify(t,null,2)),i.info("Admin removed - click 'Save & Publish' to apply"))}catch(t){i.error(`Error removing admin: ${t instanceof Error?t.message:String(t)}`)}},[n]);return j?k!=="owner"&&!_?e.jsxs("div",{className:"p-4 w-full",children:[e.jsx("h2",{className:"text-2xl font-semibold mb-4",children:"Policy Configuration"}),e.jsxs("div",{className:"text-center py-8 rounded-lg border bg-card space-y-2",children:[e.jsx("p",{className:"text-muted-foreground",children:"Policy configuration requires owner or policy admin permissions."}),e.jsxs("p",{className:"text-muted-foreground",children:["To become a policy admin, ask an existing policy admin to add your pubkey to the"," ",e.jsx("code",{className:"bg-muted px-1.5 py-0.5 rounded text-xs font-mono",children:"policy_admins"})," ","list."]}),e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Current user role: ",e.jsx("span",{className:"font-semibold",children:k||"none"})]})]})]}):e.jsxs("div",{className:"p-4 space-y-4 w-full",children:[e.jsx("h2",{className:"text-2xl font-semibold",children:"Policy Configuration"}),e.jsxs("div",{className:"rounded-lg border bg-card p-4 space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-2",children:[e.jsx("h3",{className:"text-lg font-semibold",children:"Policy Editor"}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:`px-3 py-1 rounded-full text-xs font-semibold ${v?"bg-green-600 text-white":"bg-destructive text-destructive-foreground"}`,children:v?"Policy Enabled":"Policy Disabled"}),_&&e.jsx("span",{className:"px-3 py-1 rounded-full text-xs font-semibold bg-primary text-primary-foreground",children:"Policy Admin"})]})]}),e.jsxs("div",{className:"rounded-md bg-muted/50 border p-3 text-sm space-y-1",children:[e.jsx("p",{children:`Edit the policy JSON below and click "Save & Publish" to update the relay's policy configuration. Changes are applied immediately after validation.`}),e.jsx("p",{className:"text-muted-foreground text-xs",children:"Policy updates are published as kind 12345 events and require policy admin permissions."})]}),e.jsx("textarea",{className:"w-full h-96 rounded-md border bg-background p-3 font-mono text-sm leading-relaxed resize-y focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50 disabled:cursor-not-allowed",value:n,onChange:s=>d(s.target.value),placeholder:"Loading policy configuration...",disabled:a,spellCheck:!1}),S.length>0&&e.jsxs("div",{className:"rounded-md bg-destructive/10 border border-destructive p-3",children:[e.jsx("h4",{className:"text-sm font-semibold text-destructive mb-1",children:"Validation Errors:"}),e.jsx("ul",{className:"list-disc pl-5 text-sm text-destructive space-y-0.5",children:S.map((s,t)=>e.jsx("li",{children:s},t))})]}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(x,{variant:"outline",size:"sm",onClick:$,disabled:a,children:"Load Current"}),e.jsx(x,{variant:"secondary",size:"sm",onClick:z,disabled:a,children:"Format JSON"}),e.jsx(x,{variant:"outline",size:"sm",onClick:()=>{N()},disabled:a,className:"border-yellow-500/50 text-yellow-500 hover:bg-yellow-500/10",children:"Validate"}),e.jsx(x,{size:"sm",onClick:R,disabled:a,children:"Save & Publish"})]})]}),e.jsxs("div",{className:"rounded-lg border bg-card p-4 space-y-3",children:[e.jsx("h3",{className:"text-lg font-semibold",children:"Policy Administrators"}),e.jsxs("div",{className:"rounded-md bg-muted/50 border p-3 text-sm space-y-1",children:[e.jsxs("p",{children:["Policy admins can update the relay's policy configuration via kind 12345 events. Their follows get whitelisted if"," ",e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"policy_follow_whitelist_enabled"})," ","is true in the policy."]}),e.jsx("p",{className:"text-muted-foreground text-xs",children:'Note: Policy admins are separate from relay admins (ORLY_ADMINS). Changes here update the JSON editor - click "Save & Publish" to apply.'})]}),e.jsx("div",{className:"space-y-2",children:f.length===0?e.jsx("p",{className:"text-center py-3 text-muted-foreground italic text-sm",children:"No policy admins configured"}):f.map(s=>e.jsxs("div",{className:"flex items-center justify-between rounded-md border bg-background px-3 py-2",children:[e.jsx("span",{className:"font-mono text-sm",title:s,children:D(s)}),e.jsx("button",{onClick:()=>L(s),disabled:a,title:"Remove admin",className:"w-6 h-6 rounded-full bg-destructive text-destructive-foreground text-xs flex items-center justify-center hover:brightness-90 disabled:opacity-50 disabled:cursor-not-allowed",children:"X"})]},s))}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("input",{type:"text",placeholder:"npub or hex pubkey",value:y,onChange:s=>C(s.target.value),onKeyDown:s=>s.key==="Enter"&&O(),disabled:a,className:"flex-1 rounded-md border bg-background px-3 py-2 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"}),e.jsx(x,{size:"sm",onClick:O,disabled:a||!y.trim(),children:"+ Add Admin"})]})]}),e.jsxs("div",{className:"rounded-lg border bg-card p-4 space-y-3",children:[e.jsx("h3",{className:"text-lg font-semibold",children:"Policy Follow Whitelist"}),e.jsx("div",{className:"rounded-md bg-muted/50 border p-3 text-sm",children:e.jsxs("p",{children:["Pubkeys followed by policy admins (kind 3 events). These get automatic read+write access when rules have"," ",e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"write_allow_follows: true"}),"."]})}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("span",{className:"text-sm font-semibold",children:[b.length," pubkey(s) in whitelist"]}),e.jsx(x,{variant:"outline",size:"sm",onClick:I,disabled:a,children:"Refresh Follows"})]}),e.jsx("div",{className:"max-h-72 overflow-y-auto rounded-md border bg-background",children:b.length===0?e.jsx("p",{className:"text-center py-4 text-sm text-muted-foreground italic",children:'No follows loaded. Click "Refresh Follows" to load from database.'}):e.jsx("div",{className:"grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2 p-3",children:b.map(s=>e.jsxs("div",{title:s,className:"px-2 py-1.5 rounded-md border bg-card font-mono text-xs truncate",children:[s.substring(0,12),"...",s.substring(s.length-6)]},s))})})]}),e.jsxs("div",{className:"rounded-lg border bg-card p-4 space-y-3",children:[e.jsx("h3",{className:"text-lg font-semibold",children:"Policy Reference"}),e.jsxs("div",{className:"space-y-3 text-sm",children:[e.jsxs("div",{children:[e.jsx("h4",{className:"font-semibold mb-1",children:"Structure Overview"}),e.jsxs("ul",{className:"list-disc pl-5 space-y-0.5 text-muted-foreground",children:[e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"kind.whitelist"})," ","- Only allow these event kinds (takes precedence)"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"kind.blacklist"})," ","- Deny these event kinds (if no whitelist)"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"global"})," - Rules applied to all events"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"rules"})," - Per-kind rules (keyed by kind number as string)"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"default_policy"})," ",'- "allow" or "deny" when no rules match']}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"policy_admins"})," ","- Hex pubkeys that can update policy"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"policy_follow_whitelist_enabled"})," ","- Enable follow-based access"]})]})]}),e.jsxs("div",{children:[e.jsx("h4",{className:"font-semibold mb-1",children:"Rule Fields"}),e.jsxs("ul",{className:"list-disc pl-5 space-y-0.5 text-muted-foreground",children:[e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"description"})," ","- Human-readable rule description"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"write_allow"})," ","/"," ",e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"write_deny"})," ","- Pubkey lists for write access"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"read_allow"})," ","/"," ",e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"read_deny"})," ","- Pubkey lists for read access"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"write_allow_follows"})," ","- Grant access to policy admin follows"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"size_limit"})," ","- Max total event size in bytes"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"content_limit"})," ","- Max content field size in bytes"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"max_expiry"})," ","- Max expiry offset in seconds"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"max_age_of_event"})," ","- Max age of created_at in seconds"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"max_age_event_in_future"})," ","- Max future offset in seconds"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"must_have_tags"})," ",'- Required tag letters (e.g., ["d", "t"])']}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"tag_validation"})," ","- Regex patterns for tag values"]}),e.jsxs("li",{children:[e.jsx("code",{className:"bg-muted px-1 py-0.5 rounded text-xs font-mono",children:"script"})," - Path to external validation script"]})]})]}),e.jsxs("div",{children:[e.jsx("h4",{className:"font-semibold mb-1",children:"Example Policy"}),e.jsx("pre",{className:"rounded-md border bg-background p-3 font-mono text-xs leading-snug overflow-x-auto whitespace-pre",children:V})]})]})]})]}):e.jsxs("div",{className:"p-4 w-full",children:[e.jsx("h2",{className:"text-2xl font-semibold mb-4",children:"Policy Configuration"}),e.jsx("div",{className:"text-center py-8 rounded-lg border bg-card",children:e.jsx("p",{className:"text-muted-foreground",children:"Please log in to access policy configuration."})})]})}export{q as default};
  31