Browse Source

WIP: bundled up for use with importScripts

master
Matthew Raymer 11 months ago
parent
commit
b56b1c5f93
  1. 2
      dist/custom-module-bundle.js
  2. 416
      src/custom-module-entry.js

2
dist/custom-module-bundle.js

File diff suppressed because one or more lines are too long

416
src/custom-module-entry.js

@ -4,9 +4,417 @@ const util = require("tweetnacl-util");
const secp256k1 = require('@noble/curves/secp256k1');
const sha256$1 = require('@noble/hashes/sha256');
function fromString(str, encoding = 'utf8') {
if (encoding === 'utf8') {
return new TextEncoder().encode(str);
} else if (encoding === 'base16') {
if (str.length % 2 !== 0) {
throw new Error('Invalid hex string length.');
}
let bytes = new Uint8Array(str.length / 2);
for (let i = 0; i < str.length; i += 2) {
bytes[i/2] = parseInt(str.substring(i, i + 2), 16);
}
return bytes;
} else if (encoding === 'base64url') {
str = str.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) {
str += '=';
}
return new Uint8Array(Buffer.from(str, 'base64'));
} else {
throw new Error(`Unsupported encoding "${encoding}"`);
}
}
/**
* Convert a Uint8Array to a string with the given encoding.
*
* @param {Uint8Array} byteArray - The Uint8Array to convert.
* @param {string} [encoding='utf8'] - The desired encoding ('utf8', 'base16', 'base64url').
* @returns {string} - The encoded string.
* @throws {Error} - Throws an error if the encoding is unsupported.
*/
function toString(byteArray, encoding = 'utf8') {
switch (encoding) {
case 'utf8':
return decodeUTF8(byteArray);
case 'base16':
return toBase16(byteArray);
case 'base64url':
return toBase64Url(byteArray);
default:
throw new Error(`Unsupported encoding "${encoding}"`);
}
}
/**
* Decode a Uint8Array as a UTF-8 string.
*
* @param {Uint8Array} byteArray
* @returns {string}
*/
function decodeUTF8(byteArray) {
return new TextDecoder().decode(byteArray);
}
/**
* Convert a Uint8Array to a base16 (hex) encoded string.
*
* @param {Uint8Array} byteArray
* @returns {string}
*/
function toBase16(byteArray) {
return Array.from(byteArray).map(byte => byte.toString(16).padStart(2, '0')).join('');
}
/**
* Convert a Uint8Array to a base64url encoded string.
*
* @param {Uint8Array} byteArray
* @returns {string}
*/
function toBase64Url(byteArray) {
const base64 = Buffer.from(byteArray).toString('base64');
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
const u8a = { toString, fromString };
function sha256(payload) {
const data = typeof payload === 'string' ? u8a.fromString(payload) : payload;
return sha256$1.sha256(data);
}
async function accessToken(identifier) {
const did = identifier['did'];
const privateKeyHex = identifier['keys'][0]['privateKeyHex'];
const signer = await SimpleSigner(privateKeyHex);
const nowEpoch = Math.floor(Date.now() / 1000);
const endEpoch = nowEpoch + 60; // add one minute
const tokenPayload = { exp: endEpoch, iat: nowEpoch, iss: did };
const alg = undefined; // defaults to 'ES256K', more standardized but harder to verify vs ES256K-R
const jwt = await createJWT(tokenPayload, {
alg,
issuer: did,
signer,
});
return jwt;
}
async function createJWT(payload, options, header = {}) {
const { issuer, signer, alg, expiresIn, canonicalize } = options;
if (!signer) throw new Error('missing_signer: No Signer functionality has been configured');
if (!issuer) throw new Error('missing_issuer: No issuing DID has been configured');
if (!header.typ) header.typ = 'JWT';
if (!header.alg) header.alg = alg;
const timestamps = {
iat: Math.floor(Date.now() / 1000),
exp: undefined,
};
if (expiresIn) {
if (typeof expiresIn === 'number') {
timestamps.exp = (payload.nbf || timestamps.iat) + Math.floor(expiresIn);
} else {
throw new Error('invalid_argument: JWT expiresIn is not a number');
}
}
const fullPayload = { ...timestamps, ...payload, iss: issuer };
return createJWS(fullPayload, signer, header, { canonicalize });
}
const defaultAlg = 'ES256K'
async function createJWS(payload, signer, header = {}, options = {}) {
if (!header.alg) header.alg = defaultAlg;
const encodedPayload = typeof payload === 'string' ? payload : encodeSection(payload, options.canonicalize);
const signingInput = [encodeSection(header, options.canonicalize), encodedPayload].join('.');
const jwtSigner = ES256KSignerAlg(false);
const signature = await jwtSigner(signingInput, signer);
// JWS Compact Serialization
// https://www.rfc-editor.org/rfc/rfc7515#section-7.1
return [signingInput, signature].join('.');
}
function canonicalizeData (object) {
if (typeof object === 'number' && isNaN(object)) {
throw new Error('NaN is not allowed');
}
if (typeof object === 'number' && !isFinite(object)) {
throw new Error('Infinity is not allowed');
}
if (object === null || typeof object !== 'object') {
return JSON.stringify(object);
}
if (object.toJSON instanceof Function) {
return serialize(object.toJSON());
}
if (Array.isArray(object)) {
const values = object.reduce((t, cv, ci) => {
const comma = ci === 0 ? '' : ',';
const value = cv === undefined || typeof cv === 'symbol' ? null : cv;
return `${t}${comma}${serialize(value)}`;
}, '');
return `[${values}]`;
}
const values = Object.keys(object).sort().reduce((t, cv) => {
if (object[cv] === undefined ||
typeof object[cv] === 'symbol') {
return t;
}
const comma = t.length === 0 ? '' : ',';
return `${t}${comma}${serialize(cv)}:${serialize(object[cv])}`;
}, '');
return `{${values}}`;
};
function encodeSection(data, shouldCanonicalize = false) {
if (shouldCanonicalize) {
return encodeBase64url(canonicalizeData(data));
} else {
return encodeBase64url(JSON.stringify(data));
}
}
function encodeBase64url(s) {
return bytesToBase64url(u8a.fromString(s))
}
function instanceOfEcdsaSignature(object) {
return typeof object === 'object' && 'r' in object && 's' in object;
}
function ES256KSignerAlg(recoverable) {
return async function sign(payload, signer) {
const signature = await signer(payload);
if (instanceOfEcdsaSignature(signature)) {
return toJose(signature, recoverable);
} else {
if (recoverable && typeof fromJose(signature).recoveryParam === 'undefined') {
throw new Error(`not_supported: ES256K-R not supported when signer doesn't provide a recovery param`);
}
return signature;
}
};
}
function leftpad(data, size = 64) {
if (data.length === size) return data;
return '0'.repeat(size - data.length) + data;
}
async function SimpleSigner(hexPrivateKey) {
const signer = await ES256KSigner(hexToBytes(hexPrivateKey), true);
return async (data) => {
const signature = (await signer(data));
return fromJose(signature);
};
}
function hexToBytes(s, minLength) {
let input = s.startsWith('0x') ? s.substring(2) : s;
if (input.length % 2 !== 0) {
input = `0${input}`;
}
if (minLength) {
const paddedLength = Math.max(input.length, minLength * 2);
input = input.padStart(paddedLength, '00');
}
return u8a.fromString(input.toLowerCase(), 'base16');
}
function ES256KSigner(privateKey, recoverable = false) {
const privateKeyBytes = privateKey;
if (privateKeyBytes.length !== 32) {
throw new Error(`bad_key: Invalid private key format. Expecting 32 bytes, but got ${privateKeyBytes.length}`);
}
return async function(data) {
const signature = secp256k1.secp256k1.sign(sha256(data), privateKeyBytes);
console.log(signature)
return toJose({
r: leftpad(signature.r.toString(16)),
s: leftpad(signature.s.toString(16)),
recoveryParam: signature.recovery,
}, recoverable);
}
}
function toJose(signature, recoverable) {
const { r, s, recoveryParam } = signature;
const jose = new Uint8Array(recoverable ? 65 : 64);
jose.set(u8a.fromString(r, 'base16'), 0);
jose.set(u8a.fromString(s, 'base16'), 32);
if (recoverable) {
if (typeof recoveryParam === 'undefined') {
throw new Error('Signer did not return a recoveryParam');
}
jose[64] = recoveryParam;
}
return bytesToBase64url(jose);
}
function bytesToBase64url(b) {
return u8a.toString(b, 'base64url');
}
function base64ToBytes(s) {
const inputBase64Url = s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
return u8a.fromString(inputBase64Url, 'base64url')
}
function bytesToHex(b) {
return u8a.toString(b, 'base16')
}
function fromJose(signature) {
const signatureBytes = base64ToBytes(signature);
if (signatureBytes.length < 64 || signatureBytes.length > 65) {
throw new TypeError(`Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`,);
}
const r = bytesToHex(signatureBytes.slice(0, 32));
const s = bytesToHex(signatureBytes.slice(32, 64));
const recoveryParam = signatureBytes.length === 65 ? signatureBytes[64] : undefined;
return { r, s, recoveryParam };
}
async function getSettingById(id) {
return new Promise((resolve, reject) => {
let openRequest = indexedDB.open('TimeSafari');
openRequest.onupgradeneeded = (event) => {
// Handle database setup if necessary
let db = event.target.result;
if (!db.objectStoreNames.contains('settings')) {
db.createObjectStore('settings', { keyPath: 'id' });
}
};
openRequest.onsuccess = (event) => {
let db = event.target.result;
let transaction = db.transaction('settings', 'readonly');
let objectStore = transaction.objectStore('settings');
let getRequest = objectStore.get(id);
getRequest.onsuccess = () => resolve(getRequest.result);
getRequest.onerror = () => reject(getRequest.error);
};
openRequest.onerror = () => reject(openRequest.error);
});
}
function getAllAccounts() {
return new Promise((resolve, reject) => {
let openRequest = indexedDB.open('TimeSafariAccounts');
openRequest.onupgradeneeded = (event) => {
// Handle database setup if necessary
let db = event.target.result;
if (!db.objectStoreNames.contains('accounts')) {
db.createObjectStore('accounts', { keyPath: 'id' });
}
};
openRequest.onsuccess = (event) => {
let db = event.target.result;
let transaction = db.transaction('accounts', 'readonly');
let objectStore = transaction.objectStore('accounts');
let getAllRequest = objectStore.getAll();
getAllRequest.onsuccess = () => resolve(getAllRequest.result);
getAllRequest.onerror = () => reject(getAllRequest.error);
};
openRequest.onerror = () => reject(openRequest.error);
});
}
// Usage with async/await:
async function fetchAllAccounts() {
try {
const accounts = await getAllAccounts();
console.log('Accounts:', accounts);
return accounts;
// Further processing with 'accounts' if necessary
} catch (error) {
console.error('Could not get accounts:', error);
}
}
async function makeNotifications() {
const secret = localStorage.getItem('secret');
const secretUint8Array = util.decodeBase64(secret);
const setting = await getSettingById(1);
const accounts = await fetchAllAccounts();
let results = [];
for (var i = 0; i < accounts.length; i++) {
let account = accounts[i];
let did = account['did'];
let publicKeyHex = account['publicKeyHex'];
let identity = account['identity'];
const messageWithNonceAsUint8Array = util.decodeBase64(identity);
const nonce = messageWithNonceAsUint8Array.slice(0, 24);
const message = messageWithNonceAsUint8Array.slice(24, identity.length);
const decrypted = nacl.secretbox.open(message, nonce, secretUint8Array);
const decoder = new TextDecoder('utf-8');
const msg = decoder.decode(decrypted);
const identifier = JSON.parse(JSON.parse(msg));
const headers = {
"Content-Type": "application/json",
};
headers["Authorization"] = "Bearer " + await accessToken(identifier);
results.push({ "headers": headers })
}
return { "headers": headers }
}
module.exports = {
nacl,
util,
secp256k1,
sha256: sha256$1
makeNotifications
};

Loading…
Cancel
Save