One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links 馃搮 calendar.xyehr.cn
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 97 lines 2.2 kB view raw
1import { 2 createHash, 3 createPrivateKey, 4 generateKeyPairSync, 5 randomUUID, 6 sign, 7} from 'crypto' 8 9export interface DpopPublicJwk { 10 kty: string 11 crv: string 12 x: string 13 y: string 14} 15 16function toBase64Url(input: Buffer | string) { 17 return Buffer.from(input).toString('base64url') 18} 19 20function jwkThumbprint(publicJwk: DpopPublicJwk) { 21 const canonical = JSON.stringify({ 22 crv: publicJwk.crv, 23 kty: publicJwk.kty, 24 x: publicJwk.x, 25 y: publicJwk.y, 26 }) 27 28 return createHash('sha256').update(canonical, 'utf8').digest('base64url') 29} 30 31export function generateDpopKeyMaterial() { 32 const { privateKey, publicKey } = generateKeyPairSync('ec', { 33 namedCurve: 'P-256', 34 }) 35 const publicJwk = publicKey.export({ format: 'jwk' }) as DpopPublicJwk 36 const privateKeyPem = privateKey 37 .export({ type: 'pkcs8', format: 'pem' }) 38 .toString() 39 40 if (!publicJwk?.kty || !publicJwk?.crv || !publicJwk?.x || !publicJwk?.y) { 41 throw new Error('Failed to generate DPoP key material') 42 } 43 44 return { 45 publicJwk, 46 privateKeyPem, 47 jkt: jwkThumbprint(publicJwk), 48 } 49} 50 51export function createDpopProof(params: { 52 htu: string 53 htm: string 54 privateKeyPem: string 55 publicJwk: DpopPublicJwk 56 accessToken?: string 57 nonce?: string 58}) { 59 const header = { 60 typ: 'dpop+jwt', 61 alg: 'ES256', 62 jwk: { 63 kty: params.publicJwk.kty, 64 crv: params.publicJwk.crv, 65 x: params.publicJwk.x, 66 y: params.publicJwk.y, 67 }, 68 } 69 70 const payload: Record<string, string | number> = { 71 jti: randomUUID(), 72 iat: Math.floor(Date.now() / 1000), 73 htm: params.htm.toUpperCase(), 74 htu: params.htu, 75 } 76 77 if (params.accessToken) { 78 payload.ath = createHash('sha256') 79 .update(params.accessToken, 'utf8') 80 .digest('base64url') 81 } 82 83 if (params.nonce) { 84 payload.nonce = params.nonce 85 } 86 87 const encodedHeader = toBase64Url(JSON.stringify(header)) 88 const encodedPayload = toBase64Url(JSON.stringify(payload)) 89 const signingInput = `${encodedHeader}.${encodedPayload}` 90 91 const signature = sign('sha256', Buffer.from(signingInput, 'utf8'), { 92 key: createPrivateKey(params.privateKeyPem), 93 dsaEncoding: 'ieee-p1363', 94 }) 95 96 return `${signingInput}.${toBase64Url(signature)}` 97}