@velas/account-agent
Version:
sdk
188 lines (158 loc) • 5.32 kB
JavaScript
import jwt from 'jsonwebtoken';
import jwtDecode from 'jwt-decode';
import jwkToPem from 'jwk-to-pem';
import nacl from 'tweetnacl';
import bs58 from 'bs58';
import ttl from './ttl';
function strToUint8(str) {
return new TextEncoder().encode(str);
};
function utf8ToBinaryString(str) {
const escstr = encodeURIComponent(str);
const binstr = escstr.replace(/%([0-9A-F]{2})/g, (match, p1) => {
return String.fromCharCode(parseInt(p1, 16));
});
return binstr;
};
function binToUrlBase64(bin) {
return btoa(bin)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+/g, '');
};
function strToUrlBase64(str) {
return binToUrlBase64(utf8ToBinaryString(str));
};
function uint8ToUrlBase64(uint8) {
let bin = '';
uint8.forEach((code) => {
bin += String.fromCharCode(code);
});
return binToUrlBase64(bin);
};
const head = {
"RSASSA-PKCS1-v1_5": {
alg: 'RS256',
typ: 'JWT',
},
"ECDSA": {
alg: 'ES256',
typ: 'JWT',
},
"ED25519": {
alg: 'ED25519',
typ: 'JWT',
},
};
function pem(str) {
return `-----BEGIN PUBLIC KEY-----\n${str}\n-----END PUBLIC KEY-----`
};
function jwkToBase64PubKey(jwk) {
return jwkToPem(jwk)
.replace('-----BEGIN PUBLIC KEY-----\n', '')
.replace('\n-----END PUBLIC KEY-----\n', '')
.replace( /[\r\n]+/gm, '' );
};
function KeyStorage({ KeyStorageHandler, issuer }) {
this.handler = new KeyStorageHandler({
jwkToBase64PubKey,
});
this.issuer = issuer;
};
KeyStorage.prototype.signWithKey = function(id, data) {
return this.handler.signWithKey(id, data)
};
KeyStorage.prototype.parametersToJWT = async function(base64PubKey, body = {}) {
try {
const algorithm = await this.handler.getAlgorithm(base64PubKey);
const jwtHead = strToUrlBase64(JSON.stringify(head[algorithm]));
const jwtBody = strToUrlBase64(JSON.stringify(body));
const signature = await this.handler.signWithKey(base64PubKey, strToUint8(`${jwtHead}.${jwtBody}`));
const jwtSignature = uint8ToUrlBase64(new Uint8Array(signature))
return `${jwtHead}.${jwtBody}.${jwtSignature}`;
} catch (e) {
throw new Error('jwt token generation error');
};
};
KeyStorage.prototype.validateJWT = async function({access_token}) {
try {
const { alg } = jwtDecode(access_token, {header: true});
const { iss } = jwtDecode(access_token);
if (!alg || !iss) throw new Error('access_token payload is not valid');
return jwt.verify(access_token, pem(iss));
} catch(e) {
throw new Error("Invalid access_token");
};
};
KeyStorage.prototype.updateKeyWithAccount = function(id, account) {
return this.handler.updateKey(id, {
expires: new Date().getTime() + ttl.interaction,
account,
});
};
KeyStorage.prototype.destroy = function(id) {
return this.handler.destroy(id);
};
KeyStorage.prototype.extract = function(id) {
return this.handler.extract(id);
};
KeyStorage.prototype.encryptWithExchangeKey = async function(JWKPubKey, data) {
const string = JSON.stringify(data);
const buffer = Buffer.from(string, "utf-8");
const encrypted = await this.handler.encryptWithAsymmetricEncryptionKey(JWKPubKey, buffer);
return bs58.encode(Buffer.from(encrypted));
};
KeyStorage.prototype.decryptWithExchangeKey = async function(id, data) {
const buffer = bs58.decode(data)
const decrypted = await this.handler.decryptWithAsymmetricEncryptionKey(id, buffer);
const bytes = new Buffer.from(decrypted);
const string = bytes.toString('utf8');
return JSON.parse(string);
};
KeyStorage.prototype.uploadOperationalKey = function({
keys,
account,
} = {}) {
const pair = keys || nacl.sign.keyPair();
return this.handler.uploadKey({
id: bs58.encode(pair.publicKey),
type: 'symmetric-encryption-key',
issuer: this.issuer,
expires: new Date().getTime() + ttl.interaction,
encryptSecret: pair.secretKey,
account,
});
};
KeyStorage.prototype.uploadAgentKey = async function() {
const keys = await this.handler.loadAllKeys();
if (!keys.agent) {
return this.handler.uploadKey({
id: 'agent',
type: 'jwt-signing-key',
issuer: this.issuer,
expires: new Date().getTime() + new Date().getTime(),
});
};
return keys.agent.pubKey;
};
KeyStorage.prototype.uploadExchangeKey = function() {
return this.handler.uploadKey({
id: 'exchange',
type: 'asymmetric-encryption-key',
issuer: this.issuer,
expires: new Date().getTime() + ttl.interaction,
});
};
KeyStorage.prototype.removeExpiredItems = async function() {
let keys = await this.handler.loadAllKeys();
for (var i in keys) {
const key = keys[i];
if (key.issuer && key.issuer === this.issuer) {
if (!key.account && key.expires && key.expires < new Date().getTime()) {
await this.handler.destroy(i);
};
};
};
return await this.handler.loadAllKeys() || {};
};
export default KeyStorage;