@velas/account-agent
Version: 
sdk
190 lines (160 loc) • 5.39 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 enc       = new TextEncoder();
    const buffer    = enc.encode(string);
    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 dec       = new TextDecoder();
    const string     = dec.decode(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;