Socket
Socket
Sign inDemoInstall

@musakui/fedi

Package Overview
Dependencies
0
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.2 to 0.0.3

lib/activitypub/index.js

186

lib/hs/core.js

@@ -9,4 +9,4 @@ const HS2019 = 'hs2019'

const numericFields = new Set([
'created',
'expires',
'created',
'expires',
])

@@ -18,9 +18,9 @@

const getSigInfo = (valid) => {
const created = algorithm === HS2019
? Math.floor(new Date() / 1000)
: null
return {
created,
expires: (created && valid) ? (created + valid) : null,
}
const created = algorithm === HS2019
? Math.floor(new Date() / 1000)
: null
return {
created,
expires: (created && valid) ? (created + valid) : null,
}
}

@@ -39,27 +39,27 @@

export const createString = (req, signHeaders = ['host']) => {
const info = getSigInfo()
const toSign = [
TARGET,
info?.created ? CREATED : '',
info?.expires ? EXPIRES : '',
req.headers?.date ? 'date' : '',
req.headers?.digest ? 'digest' : '',
...signHeaders.map((i) => i.toLowerCase()),
].filter((i) => i)
const info = getSigInfo()
const toSign = [
TARGET,
info?.created ? CREATED : '',
info?.expires ? EXPIRES : '',
req.headers?.date ? 'date' : '',
req.headers?.digest ? 'digest' : '',
...signHeaders.map((i) => i.toLowerCase()),
].filter((i) => i)
const url = new URL(req.url)
const head = new Map([
[TARGET, `${req.method.toLowerCase()} ${url.pathname}`],
[CREATED, info?.created],
[EXPIRES, info?.expires],
...Object.entries({
host: url.host,
...req.headers
}).map(([k, v]) => [k.toLowerCase(), v]),
])
const url = new URL(req.url)
const head = new Map([
[TARGET, `${req.method.toLowerCase()} ${url.pathname}`],
[CREATED, info?.created],
[EXPIRES, info?.expires],
...Object.entries({
host: url.host,
...req.headers
}).map(([k, v]) => [k.toLowerCase(), v]),
])
return [
{ headers: toSign.join(' '), ...info },
toSign.map((h) => `${h}: ${head.get(h)}`).join('\n'),
]
return [
{ headers: toSign.join(' '), ...info },
toSign.map((h) => `${h}: ${head.get(h)}`).join('\n'),
]
}

@@ -73,10 +73,10 @@

export const getHeader = (fields) => Object.entries({
algorithm,
keyId,
...fields
algorithm,
keyId,
...fields
}).map(([k, v]) => {
if (!v) return null
return numericFields.has(k)
? `${k}=${v}`
: `${k}="${v}"`
if (!v) return null
return numericFields.has(k)
? `${k}=${v}`
: `${k}="${v}"`
}).filter((i) => i).join(',')

@@ -99,64 +99,64 @@

export const parse = (req) => {
if (!req.headers) throw new Error('no headers on request')
if (!req.headers.signature) throw new Error('missing signature header')
if (!req.headers) throw new Error('no headers on request')
if (!req.headers.signature) throw new Error('missing signature header')
const now = new Date()
const now = new Date()
if (req.headers.date) {
const hd = new Date(req.headers.date)
if (isNaN(hd)) throw new Error(`invalid date header: ${req.headers.date}`)
const dt = Math.abs(now - hd)
if (dt > 1e5) throw new Error(`date too far: ${dt}`)
}
if (req.headers.date) {
const hd = new Date(req.headers.date)
if (isNaN(hd)) throw new Error(`invalid date header: ${req.headers.date}`)
const dt = Math.abs(now - hd)
if (dt > 1e5) throw new Error(`date too far: ${dt}`)
}
/**
* @type {SignatureField}
*/
const sig = Object.fromEntries(req.headers.signature.split(',').map((s) => {
const m = SIG_REGEX.exec(s.trim())
return m ? [m[1], m[2] || parseFloat(m[3])] : ['', '']
}))
/**
* @type {SignatureField}
*/
const sig = Object.fromEntries(req.headers.signature.split(',').map((s) => {
const m = SIG_REGEX.exec(s.trim())
return m ? [m[1], m[2] || parseFloat(m[3])] : ['', '']
}))
if (!sig.keyId || !sig.signature) {
throw new Error(`invalid signature header: ${req.headers.signature}`)
}
if (!sig.keyId || !sig.signature) {
throw new Error(`invalid signature header: ${req.headers.signature}`)
}
if (sig.headers === '') throw new Error('empty headers field')
if (sig.headers === '') throw new Error('empty headers field')
if (sig.created) {
const created = new Date(sig.created * 1000)
if (isNaN(created)) throw new Error(`invalid created: ${sig.created}`)
if (created > now) throw new Error(`created in future: ${created}`)
}
if (sig.created) {
const created = new Date(sig.created * 1000)
if (isNaN(created)) throw new Error(`invalid created: ${sig.created}`)
if (created > now) throw new Error(`created in future: ${created}`)
}
if (sig.expires) {
const expires = new Date(sig.expires * 1000)
if (isNaN(expires)) throw new Error(`invalid expires: ${sig.expires}`)
if (expires < now) throw new Error(`signature expired: ${expires}`)
}
if (sig.expires) {
const expires = new Date(sig.expires * 1000)
if (isNaN(expires)) throw new Error(`invalid expires: ${sig.expires}`)
if (expires < now) throw new Error(`signature expired: ${expires}`)
}
const algo = sig.algorithm || HS2019
const isHs = algo === HS2019
const head = sig.headers ? sig.headers.split(' ') : [CREATED]
const algo = sig.algorithm || HS2019
const isHs = algo === HS2019
const head = sig.headers ? sig.headers.split(' ') : [CREATED]
const getValue = (h) => {
switch (h) {
case TARGET:
return `${req.method.toLowerCase()} ${req.path}`
case CREATED:
if (isHs && parseInt(sig.created)) return sig.created
break
case EXPIRES:
if (isHs && parseInt(sig.expires)) return sig.expires
break
default:
return req.headers[h]
}
throw new Error(`invalid field: ${h}`)
}
const getValue = (h) => {
switch (h) {
case TARGET:
return `${req.method.toLowerCase()} ${req.path}`
case CREATED:
if (isHs && parseInt(sig.created)) return sig.created
break
case EXPIRES:
if (isHs && parseInt(sig.expires)) return sig.expires
break
default:
return req.headers[h]
}
throw new Error(`invalid field: ${h}`)
}
return {
...sig,
data: head.map((h) => `${h}: ${getValue(h)}`).join('\n'),
}
return {
...sig,
data: head.map((h) => `${h}: ${getValue(h)}`).join('\n'),
}
}

@@ -168,3 +168,3 @@

export const useAlgorithm = (algo) => {
algorithm = algo
algorithm = algo
}

@@ -176,3 +176,3 @@

export const useKeyId = (id) => {
keyId = id
keyId = id
}

@@ -8,52 +8,55 @@ import * as core from './core.js'

const toB64 = (buf) => {
const arr = Array.from(new Uint8Array(buf))
return btoa(arr.reduce((s, c) => s + String.fromCharCode(c), ''))
const arr = Array.from(new Uint8Array(buf))
return btoa(arr.reduce((s, c) => s + String.fromCharCode(c), ''))
}
const getDigest = async (data, algo = 'SHA-256') => {
const dg = await crypto.subtle.digest(algo, encoder.encode(data))
return `${algo}=${toB64(dg)}`
const dg = await crypto.subtle.digest(algo, encoder.encode(data))
return `${algo}=${toB64(dg)}`
}
export const signRequest = async (req) => {
if (!core.keyId || !sign) throw new Error('key not set')
const [info, data] = core.createString(req)
const signature = core.getHeader({ ...info, signature: await sign(data) })
return {
...req,
headers: {
...req.headers,
signature,
},
}
if (!core.keyId || !sign) throw new Error('key not set')
const [info, data] = core.createString(req)
const signature = core.getHeader({ ...info, signature: await sign(data) })
return {
...req,
headers: {
...req.headers,
signature,
},
}
}
export const sendRequest = async (req) => {
const signed = await signRequest({
...req,
method: req.body ? 'POST' : 'GET',
headers: {
date: (new Date()).toUTCString(),
...(req.body ? { digest: await getDigest(req.body) } : {}),
...req.headers,
},
})
const signed = await signRequest({
...req,
method: req.body ? 'POST' : 'GET',
headers: {
date: (new Date()).toUTCString(),
...(req.body ? { digest: await getDigest(req.body) } : {}),
...req.headers,
},
})
const resp = await fetch(req.url, signed)
try {
return await resp.json()
} catch (err) {
return await resp.text()
}
const resp = await fetch(req.url, signed)
if (!resp.ok) {
throw new Error(`${resp.status} ${resp.statusText}`)
}
try {
return await resp.json()
} catch (err) {
return await resp.text()
}
}
export const useKey = (id, key) => {
core.useKeyId(id)
// TODO: implement browser signing
sign = (data) => ''
core.useKeyId(id)
// TODO: implement browser signing
sign = (data) => ''
}
export {
algorithm,
useAlgorithm,
algorithm,
useAlgorithm,
} from './core.js'

@@ -19,14 +19,14 @@ import { createSign, createVerify, createHash } from 'crypto'

const getDigest = (data, algo = S256) => {
let hasher = null
switch (algo) {
case S256:
hasher = createHash('sha256')
break
case 'SHA-512':
hasher = createHash('sha512')
break
default:
return undefined
}
return `${algo}=${hasher.update(data).digest(B64)}`
let hasher = null
switch (algo) {
case S256:
hasher = createHash('sha256')
break
case 'SHA-512':
hasher = createHash('sha512')
break
default:
return undefined
}
return `${algo}=${hasher.update(data).digest(B64)}`
}

@@ -38,12 +38,12 @@

export const signRequest = (req) => {
if (!sign) throw new Error('key not set')
const [info, data] = core.createString(req)
const signature = core.getHeader({ ...info, signature: sign(data) })
return {
...req,
headers: {
...req.headers,
signature,
},
}
if (!sign) throw new Error('key not set')
const [info, data] = core.createString(req)
const signature = core.getHeader({ ...info, signature: sign(data) })
return {
...req,
headers: {
...req.headers,
signature,
},
}
}

@@ -55,13 +55,13 @@

export const sendRequest = async (req) => {
if (!myFetch) throw new Error('fetch not set')
if (!myFetch) throw new Error('fetch not set')
return await myFetch(req.url, signRequest({
...req,
method: req.body ? 'POST' : 'GET',
headers: {
date: (new Date()).toUTCString(),
...(req.body ? { digest: getDigest(req.body) } : {}),
...req.headers,
},
}))
return await myFetch(req.url, signRequest({
...req,
method: req.body ? 'POST' : 'GET',
headers: {
date: (new Date()).toUTCString(),
...(req.body ? { digest: getDigest(req.body) } : {}),
...req.headers,
},
}))
}

@@ -74,14 +74,14 @@

export const getKey = async (id, accept = appJSON) => {
const headers = { accept }
try {
const resp = await myFetch(id, { headers })
return await resp.json()
} catch (err) {
// some sites require signed GET
const resp = await sendRequest({
url: id,
headers,
})
return await resp.json()
}
const headers = { accept }
try {
const resp = await myFetch(id, { headers })
return await resp.json()
} catch (err) {
// some sites require signed GET
const resp = await sendRequest({
url: id,
headers,
})
return await resp.json()
}
}

@@ -94,23 +94,23 @@

export const verifyRequest = async (req) => {
if (req.body && req.headers.digest) {
const dg = req.headers.digest
const ha = dg.split('=')[0]
if (dg !== getDigest(req.body, ha)) throw new Error(`invalid digest: ${dg}`)
}
if (req.body && req.headers.digest) {
const dg = req.headers.digest
const ha = dg.split('=')[0]
if (dg !== getDigest(req.body, ha)) throw new Error(`invalid digest: ${dg}`)
}
const sig = core.parse(req)
const { publicKey: pk, ...rest } = await getKey(sig.keyId)
const sig = core.parse(req)
const { publicKey: pk, ...rest } = await getKey(sig.keyId)
if (!pk || !pk.publicKeyPem) {
throw new Error(`failed to get key: ${sig.keyId}`)
}
if (!pk || !pk.publicKeyPem) {
throw new Error(`failed to get key: ${sig.keyId}`)
}
if (pk.id && pk.id !== sig.keyId) {
throw new Error(`invalid keyId: ${sig.keyId} != ${pk.id}`)
}
if (pk.id && pk.id !== sig.keyId) {
throw new Error(`invalid keyId: ${sig.keyId} != ${pk.id}`)
}
const veri = createVerify(sig.algorithm).update(sig.data)
if (veri.verify(pk.publicKeyPem, sig.signature, B64)) return rest
const veri = createVerify(sig.algorithm).update(sig.data)
if (veri.verify(pk.publicKeyPem, sig.signature, B64)) return rest
throw new Error('verification failed')
throw new Error('verification failed')
}

@@ -124,12 +124,12 @@

export const useKey = (id, key, algo = 'sha256') => {
core.useKeyId(id)
sign = (data) => createSign(algo).update(data).sign(key).toString(B64)
core.useKeyId(id)
sign = (data) => createSign(algo).update(data).sign(key).toString(B64)
}
export const useFetch = (fn) => {
myFetch = fn
myFetch = fn
}
try {
myFetch = fetch
myFetch = fetch
} catch (err) {

@@ -136,0 +136,0 @@ }

import * as Keys from './keys.js'
import * as Signatures from './hs/index.js'
import * as WebFinger from './webfinger.js'
import * as ActivityPub from './activitypub/index.js'
import * as ActivityStreams from './activitystreams/index.js'
export {
Keys,
Signatures,
Keys,
WebFinger,
ActivityPub,
ActivityStreams,
}
{
"name": "@musakui/fedi",
"description": "tools for the fediverse",
"keywords": [
"ActivityPub"
],
"author": "musakui",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/musakui/fedi"
},
"version": "0.0.2",
"type": "module",
"files": [
"lib"
],
"exports": {
"./hs": {
"node": "./lib/hs/node.js",
"default": "./lib/hs/index.js"
},
".": "./lib/index.js"
},
"scripts": {
},
"devDependencies": {
}
"name": "@musakui/fedi",
"description": "tools for the fediverse",
"keywords": [
"ActivityPub"
],
"author": "musakui",
"license": "MIT",
"repository": "github:musakui/fedi",
"version": "0.0.3",
"type": "module",
"files": [
"lib"
],
"exports": {
"./activitypub": "./lib/activitypub/index.js",
"./activitystreams": "./lib/activitystreams/index.js",
"./hs": {
"node": "./lib/hs/node.js",
"default": "./lib/hs/index.js"
},
"./webfinger": "./lib/webfinger.js",
".": "./lib/index.js"
},
"scripts": {
},
"devDependencies": {
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc