• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperledger / identus-edge-agent-sdk-ts / 12281808968

11 Dec 2024 05:36PM UTC coverage: 72.637% (+0.08%) from 72.557%
12281808968

Pull #333

github

web-flow
Merge 1fca5ba01 into 00ddc08e4
Pull Request #333: feat(backup): introduce new schema to minimize backup length

1678 of 2519 branches covered (66.61%)

Branch coverage included in aggregate %.

25 of 26 new or added lines in 3 files covered. (96.15%)

1 existing line in 1 file now uncovered.

3541 of 4666 relevant lines covered (75.89%)

34.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.23
/src/edge-agent/Agent.Backup.ts
1
import Pako from "pako";
2
import * as Domain from "../domain";
3
import Agent from "./Agent";
4
import { Version } from "../domain/backup";
5
import { isObject, validateSafe } from "../utils";
6

7

8
/**
9
 * define Agent requirements for Backup
10
 */
11
type BackupAgent = Pick<Agent, "apollo" | "pluto" | "pollux" | "seed">;
12
type BackupExclude = "messages" | "mediators" | "link_secret";
13

14
type MasterKey = Domain.PrivateKey & Domain.ExportableKey.Common & Domain.ExportableKey.JWK & Domain.ExportableKey.PEM
15

16
export type BackupOptions = {
17
  version?: Version
18
  key?: MasterKey
19
  compress?: boolean
20
  excludes?: BackupExclude[]
21
}
22

23
export class AgentBackup {
24
  constructor(
25
    public readonly Agent: BackupAgent
49✔
26
  ) {}
27

28
  /**
29
 * Creates a JWE (JSON Web Encryption) containing the backup data stored in Pluto.
30
 * The data can optionally be encrypted using a custom master key, compressed, 
31
 * and filtered to exclude specified fields.
32
 * 
33
 * @param {BackupOptions} [options] - Optional settings for the backup.
34
 * @param {Version} [options.version] - Specifies the version of the backup data.
35
 * @param {MasterKey} [options.key] - Custom master key used for encrypting the backup.
36
 * @param {boolean} [options.compress] - If true, compresses the JWE using DEFLATE.
37
 * @param {BackupExclude[]} [options.excludes] - Keys to exclude from the backup data 
38
 * (e.g., "messages", "mediators", "link_secret"). Arrays are cleared, and strings are set to empty strings.
39
 * 
40
 * @returns {Promise<string>} - A promise that resolves to the JWE string.
41
 * 
42
 * @see restore - Method to restore data from a JWE string.
43
 */
44
  async createJWE(options?: BackupOptions): Promise<string> {
45
    await this.Agent.pollux.start();
6✔
46
    
47
    let backup = await this.Agent.pluto.backup(options?.version);
6✔
48
    if (options?.excludes && Array.isArray(options.excludes)) {
6✔
49
      backup = this.applyExclusions(backup, options.excludes);
2✔
50
    }
51
    const backupStr = options?.compress ? this.compress(JSON.stringify(backup)) : JSON.stringify(backup);
6✔
52
    const masterSk = options?.key ?? await this.masterSk();
6✔
53
    const jwk = masterSk.to.JWK();
6✔
54
    
55
    return this.Agent.pollux.jwe.JWE.encrypt(
6✔
56
      backupStr,
57
      JSON.stringify(jwk),
58
      'backup',
59
    );
60
  }
61

62
  /**
63
   * Decodes a JWE (JSON Web Encryption) string and restores the backup data to the store.
64
   * If the JWE is compressed (Base64-encoded), it will attempt to decompress it first.
65
   * 
66
   * @param {string} jwe - The JWE string containing the encrypted backup data.
67
   * @param {BackupOptions} [options] - Optional settings for the backup.
68
   * @param {Version} [options.version] - Specifies the version of the restore data.
69
   * @param {MasterKey} [options.key] - Custom master key used for decrypting the backup.
70
   * @param {boolean} [options.compress] - If true, compresses the JWE using INFLATE.
71
   * 
72
   * @returns {Promise<void>} - A promise that resolves when the data is successfully restored.
73
   * 
74
   * @see createJWE - Method to create a JWE from the stored backup data.
75
   */
76
  async restore(jwe: string, options?: BackupOptions) {
77
    await this.Agent.pollux.start();
6✔
78
    const masterSk = options?.key ?? await this.masterSk();
6✔
79

80
    const jwk = masterSk.to.JWK();
6✔
81
    const decoded = this.Agent.pollux.jwe.JWE.decrypt(
6✔
82
      jwe,
83
      'backup',
84
      JSON.stringify(jwk),
85
    );
86
    let jsonStr: string;
87
    if (options?.compress) {
6✔
88
      jsonStr = this.decompress(new TextDecoder().decode(decoded));
4✔
89
    } else {
90
      jsonStr = Buffer.from(decoded).toString();
2✔
91
    }    
92
    const json = JSON.parse(jsonStr);    
6✔
93
    const backup = this.parseBackupJson(json);    
6✔
94
    await this.Agent.pluto.restore(backup);
6✔
95
  }
96

97
  private parseBackupJson(json: unknown): Domain.Backup.Schema {
98
    if (isObject(json)) {
6!
99
      const version = json.version ?? Domain.Backup.defaultVersion;
6!
100
      switch (version) {
6✔
101
        case "0.0.1":
102
          if (validateSafe(json, Domain.Backup.v0_0_1)) {
6!
103
            return json;
6✔
104
          }
NEW
105
          break;
×
106
      }
107
    }
UNCOV
108
    throw new Domain.AgentError.BackupVersionError();
×
109
  }
110

111
  /**
112
 * Compresses a JSON object into a Base64-encoded string using DEFLATE.
113
 * 
114
 * - Uses `level: 9` for maximum compression and `strategy: Z_FILTERED` 
115
 *   (optimized for repetitive patterns, common in JSON data).
116
 * - Converts the JSON to a string, compresses it, and encodes it in Base64.
117
 * 
118
 * @param {unknown} json - The JSON object to compress.
119
 * @returns {string} - The Base64-encoded compressed string.
120
 */
121
  private compress(json: unknown): string {
122
    // Strategy 1 is 
123
    return Buffer.from(Pako.deflate(JSON.stringify(json), {level: 9, strategy: 1})).toString('base64');
4✔
124
  }
125

126
  /**
127
 * Decompresses a Base64-encoded string into its original JSON representation.
128
 * 
129
 * - Decodes the Base64 string to a binary buffer.
130
 * - Uses DEFLATE to decompress the data and converts it back to a JSON string.
131
 * - Parses and returns the JSON object.
132
 * 
133
 * @param {string} data - The Base64-encoded compressed string.
134
 * @returns {string} - The decompressed JSON string.
135
 */
136
  private decompress(data: string): string {
137
    const compressedData = Buffer.from(data, 'base64');
4✔
138
    return JSON.parse(Pako.inflate(compressedData, {to: 'string'}));
4✔
139
  }
140

141
  /**
142
   * create a JWK for the MasterKey (X25519)
143
   * @returns JWK
144
   */
145
  private async masterSk() {
146
    const masterKey = this.Agent.apollo.createPrivateKey({
8✔
147
      [Domain.KeyProperties.type]: Domain.KeyTypes.Curve25519,
148
      [Domain.KeyProperties.curve]: Domain.Curve.X25519,
149
      [Domain.KeyProperties.seed]: Buffer.from(this.Agent.seed.value).toString("hex"),
150
      [Domain.KeyProperties.derivationPath]: "m/0'/0'/0'"
151
    });
152
    if (!masterKey.isExportable()) {
8!
153
      throw new Domain.AgentError.KeyNotExportableError();
×
154
    }
155
    return masterKey;
8✔
156
  }
157
  /**
158
 * Modifies the backup object by applying exclusions.
159
 * Sets excluded array values to empty arrays and string values to empty strings.
160
 *
161
 * @param {Domain.Backup.Schema} backup - The backup object to be modified.
162
 * @param {BackupExclude[]} excludes - An array of keys to exclude from the backup.
163
 * @returns {Domain.Backup.Schema} The modified backup object.
164
 */
165
  private applyExclusions(backup: Domain.Backup.Schema, excludes: BackupExclude[]): Domain.Backup.Schema {
166
    const tmp = {...backup};
2✔
167
    for (const exclude of excludes) {
2✔
168
      switch (exclude) {
6✔
169
        case "messages":
170
        case "mediators":
171
          tmp[exclude] = [];
4✔
172
          break;
4✔
173
        case "link_secret":
174
          tmp[exclude] = undefined;
2✔
175
          break;
2✔
176
      }
177
    }
178
    return tmp;
2✔
179
  }
180
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc