Files
safetynet/packages/core/branding.mjs
T

8 lines
1.3 KiB
JavaScript
Raw Normal View History

import {z} from 'zod';
export function validCompanyLogo(value){
if(typeof value!=='string'||!/^data:image\/png;base64,[A-Za-z0-9+/]+={0,2}$/.test(value))return false;
const raw=value.slice(22),bytes=Buffer.from(raw,'base64');
return bytes.length>=33&&bytes.length<=128*1024&&bytes.toString('base64')===raw&&bytes.subarray(0,8).equals(Buffer.from('89504e470d0a1a0a','hex'))&&bytes.readUInt32BE(8)===13&&bytes.subarray(12,16).toString()==='IHDR'&&bytes.readUInt32BE(16)>0&&bytes.readUInt32BE(16)<=1024&&bytes.readUInt32BE(20)>0&&bytes.readUInt32BE(20)<=1024;
}
export const BrandSchema=z.object({name:z.string().trim().min(1).max(40),color:z.string().regex(/^#[0-9a-f]{6}$/i),logoDataUrl:z.string().max(180000).refine(validCompanyLogo,'Company logo must be a bounded PNG, at most 1024 × 1024').optional()}).strict();
export const StartPageSchema=z.object({title:z.string().max(100).default('Welcome'),subtitle:z.string().max(240).default(''),newsEnabled:z.boolean().default(false),feedUrl:z.string().max(2048).refine(v=>{try{const u=new URL(v);return u.protocol==='https:'&&!u.username&&!u.password&&!u.hash;}catch{return false;}},'Company feed requires HTTPS without credentials or fragment').optional()}).strict().prefault({}).superRefine((p,c)=>{if(p.newsEnabled&&!p.feedUrl)c.addIssue({code:'custom',path:['feedUrl'],message:'Enabled company news requires a feed URL'});});