first commit
This commit is contained in:
commit
ff482ebe23
251 files changed
+19126
No files matched your search
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
export const BookmarkSchema=z.object({id:z.string().min(1).max(80).regex(/^[a-zA-Z0-9_-]+$/),title:z.string().trim().min(1).max(120).refine(v=>!/[\u0000-\u001f\u007f]/.test(v)),url:z.string().max(2048).url().refine(v=>{try{const u=new URL(v);return ['https:','http:'].includes(u.protocol)&&!u.username&&!u.password&&!u.search&&!u.hash;}catch{return false;}},'Bookmarks require HTTP(S) without credentials, query or fragment').transform(v=>new URL(v).href).refine(v=>v.length<=2048,'Canonical bookmark URL exceeds limit'),folder:z.string().trim().max(80).refine(v=>!/[\u0000-\u001f\u007f]/.test(v)).default('')}).strict();
|
||||
export const BookmarkItemsSchema=z.array(BookmarkSchema).max(100).refine(items=>new Set(items.map(i=>i.id)).size===items.length,'Bookmark IDs must be unique').refine(items=>Buffer.byteLength(JSON.stringify(items))<=250000,'Bookmark collection exceeds byte budget');
|
||||
export const PersonalBookmarksSchema=z.object({revision:z.number().int().nonnegative().safe().default(0),items:BookmarkItemsSchema.default([])}).strict().prefault({});
|
||||
export const BookmarksSchema=z.object({managed:BookmarkItemsSchema.default([]),allowUserAdd:z.boolean().default(true),allowUserEdit:z.boolean().default(true),allowUserDelete:z.boolean().default(true),adminReadUserBookmarks:z.boolean().default(true),personal:PersonalBookmarksSchema}).strict().prefault({}).superRefine((v,ctx)=>{const ids=new Set(v.managed.map(i=>i.id));if(v.personal.items.some(i=>ids.has(i.id)))ctx.addIssue({code:'custom',message:'Managed and personal bookmark IDs must be distinct'});});
|
||||
import { createPrivateKey,createPublicKey,sign,verify } from 'node:crypto';
|
||||
export const BookmarkSnapshotSchema=z.object({deviceId:z.string().uuid(),policyVersion:z.number().int().positive(),revision:z.number().int().nonnegative().safe(),expiresAt:z.string().datetime(),items:BookmarkItemsSchema}).strict();
|
||||
export function signBookmarkSnapshot(payload,privateKey){payload=BookmarkSnapshotSchema.parse(payload);return {payload,signature:sign(null,Buffer.from('SafetyNet:bookmarks:v1\n'+JSON.stringify(payload)),createPrivateKey(privateKey)).toString('base64')};}
|
||||
export function verifyBookmarkSnapshot(envelope,publicKey,{deviceId,policyVersion,minimumRevision=0,now=Date.now()}){
|
||||
if(!envelope||typeof envelope.signature!=='string'||envelope.signature.length>200||Buffer.byteLength(JSON.stringify(envelope))>300000)throw Error('Invalid bookmark snapshot');
|
||||
const payload=BookmarkSnapshotSchema.parse(envelope.payload);
|
||||
if(JSON.stringify(payload)!==JSON.stringify(envelope.payload)||!verify(null,Buffer.from('SafetyNet:bookmarks:v1\n'+JSON.stringify(payload)),createPublicKey(publicKey),Buffer.from(envelope.signature,'base64')))throw Error('Invalid bookmark signature');
|
||||
if(payload.deviceId!==deviceId||payload.policyVersion!==policyVersion||payload.revision<minimumRevision||Date.parse(payload.expiresAt)<=now)throw Error('Bookmark snapshot binding, expiry or rollback rejected');
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user