openmates 0.11.0-alpha.8 → 0.11.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2YHQ7NP4.js → chunk-QJE5DL63.js} +81 -10
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -91,6 +91,16 @@ async function decryptWithAesGcmCombined(encryptedWithIvB64, rawKeyBytes) {
|
|
|
91
91
|
return null;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
async function deriveEmailEncryptionKeyB64(email, emailSaltB64) {
|
|
95
|
+
const encoder = new TextEncoder();
|
|
96
|
+
const emailBytes = encoder.encode(email);
|
|
97
|
+
const saltBytes = base64ToBytes(emailSaltB64);
|
|
98
|
+
const combined = new Uint8Array(emailBytes.length + saltBytes.length);
|
|
99
|
+
combined.set(emailBytes);
|
|
100
|
+
combined.set(saltBytes, emailBytes.length);
|
|
101
|
+
const hashBuffer = await cryptoApi.subtle.digest("SHA-256", toArrayBuffer(combined));
|
|
102
|
+
return bytesToBase64(new Uint8Array(hashBuffer));
|
|
103
|
+
}
|
|
94
104
|
async function decryptBytesWithAesGcm(encryptedWithIvB64, rawKeyBytes) {
|
|
95
105
|
try {
|
|
96
106
|
const combined = base64ToBytes(encryptedWithIvB64);
|
|
@@ -554,6 +564,18 @@ function saveSession(session) {
|
|
|
554
564
|
} else if (result.type === "plaintext") {
|
|
555
565
|
onDisk.masterKeyExportedB64 = session.masterKeyExportedB64;
|
|
556
566
|
}
|
|
567
|
+
if (session.emailEncryptionKeyB64) {
|
|
568
|
+
const emailKeyResult = storeMasterKey(
|
|
569
|
+
session.emailEncryptionKeyB64,
|
|
570
|
+
`${session.hashedEmail}:email`
|
|
571
|
+
);
|
|
572
|
+
onDisk.emailEncryptionKeyStorage = emailKeyResult.type;
|
|
573
|
+
if (emailKeyResult.type === "encrypted") {
|
|
574
|
+
onDisk.emailEncryptionKeyEncrypted = emailKeyResult.encryptedData;
|
|
575
|
+
} else if (emailKeyResult.type === "plaintext") {
|
|
576
|
+
onDisk.emailEncryptionKeyB64 = session.emailEncryptionKeyB64;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
557
579
|
writeJsonFile(filePath, onDisk);
|
|
558
580
|
if (result.type !== "plaintext") {
|
|
559
581
|
process.stderr.write("Decrypting data...\n");
|
|
@@ -564,17 +586,19 @@ function loadSession() {
|
|
|
564
586
|
const onDisk = readJsonFile(filePath);
|
|
565
587
|
if (!onDisk) return null;
|
|
566
588
|
let masterKey = null;
|
|
589
|
+
let emailEncryptionKey = null;
|
|
567
590
|
if (!onDisk.masterKeyStorage) {
|
|
568
591
|
masterKey = onDisk.masterKeyExportedB64 ?? null;
|
|
569
592
|
if (masterKey) {
|
|
570
|
-
|
|
593
|
+
emailEncryptionKey = getEmailEncryptionKeyFromDisk(onDisk);
|
|
594
|
+
const session = buildSession(onDisk, masterKey, emailEncryptionKey);
|
|
571
595
|
try {
|
|
572
596
|
saveSession(session);
|
|
573
597
|
process.stderr.write("Decrypting data...\n");
|
|
574
598
|
} catch {
|
|
575
599
|
}
|
|
576
600
|
}
|
|
577
|
-
return masterKey ? buildSession(onDisk, masterKey) : null;
|
|
601
|
+
return masterKey ? buildSession(onDisk, masterKey, getEmailEncryptionKeyFromDisk(onDisk)) : null;
|
|
578
602
|
}
|
|
579
603
|
switch (onDisk.masterKeyStorage) {
|
|
580
604
|
case "keychain":
|
|
@@ -598,7 +622,7 @@ function loadSession() {
|
|
|
598
622
|
);
|
|
599
623
|
return null;
|
|
600
624
|
}
|
|
601
|
-
return buildSession(onDisk, masterKey);
|
|
625
|
+
return buildSession(onDisk, masterKey, getEmailEncryptionKeyFromDisk(onDisk));
|
|
602
626
|
}
|
|
603
627
|
function clearSession() {
|
|
604
628
|
const filePath = join(ensureStateDir(), "session.json");
|
|
@@ -606,17 +630,36 @@ function clearSession() {
|
|
|
606
630
|
if (onDisk?.masterKeyStorage) {
|
|
607
631
|
deleteMasterKey(onDisk.masterKeyStorage, onDisk.hashedEmail);
|
|
608
632
|
}
|
|
633
|
+
if (onDisk?.emailEncryptionKeyStorage) {
|
|
634
|
+
deleteMasterKey(onDisk.emailEncryptionKeyStorage, `${onDisk.hashedEmail}:email`);
|
|
635
|
+
}
|
|
609
636
|
if (existsSync2(filePath)) {
|
|
610
637
|
rmSync(filePath);
|
|
611
638
|
}
|
|
612
639
|
}
|
|
613
|
-
function
|
|
640
|
+
function getEmailEncryptionKeyFromDisk(onDisk) {
|
|
641
|
+
if (!onDisk.emailEncryptionKeyStorage) return onDisk.emailEncryptionKeyB64 ?? null;
|
|
642
|
+
switch (onDisk.emailEncryptionKeyStorage) {
|
|
643
|
+
case "keychain":
|
|
644
|
+
return retrieveMasterKey("keychain", `${onDisk.hashedEmail}:email`);
|
|
645
|
+
case "encrypted":
|
|
646
|
+
return retrieveMasterKey(
|
|
647
|
+
"encrypted",
|
|
648
|
+
`${onDisk.hashedEmail}:email`,
|
|
649
|
+
onDisk.emailEncryptionKeyEncrypted
|
|
650
|
+
);
|
|
651
|
+
case "plaintext":
|
|
652
|
+
return onDisk.emailEncryptionKeyB64 ?? null;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function buildSession(onDisk, masterKey, emailEncryptionKey) {
|
|
614
656
|
return {
|
|
615
657
|
apiUrl: onDisk.apiUrl,
|
|
616
658
|
sessionId: onDisk.sessionId,
|
|
617
659
|
wsToken: onDisk.wsToken,
|
|
618
660
|
cookies: onDisk.cookies,
|
|
619
661
|
masterKeyExportedB64: masterKey,
|
|
662
|
+
emailEncryptionKeyB64: emailEncryptionKey,
|
|
620
663
|
hashedEmail: onDisk.hashedEmail,
|
|
621
664
|
userEmailSalt: onDisk.userEmailSalt,
|
|
622
665
|
createdAt: onDisk.createdAt,
|
|
@@ -1828,6 +1871,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
1828
1871
|
authorizerDeviceName: complete.data.authorizer_device_name ?? null,
|
|
1829
1872
|
autoLogoutMinutes: complete.data.auto_logout_minutes ?? null
|
|
1830
1873
|
};
|
|
1874
|
+
await this.hydrateEmailEncryptionKey(session);
|
|
1831
1875
|
saveSession(session);
|
|
1832
1876
|
}
|
|
1833
1877
|
async whoAmI() {
|
|
@@ -2811,8 +2855,9 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2811
2855
|
`CreditNote_${invoiceId}.pdf`
|
|
2812
2856
|
);
|
|
2813
2857
|
}
|
|
2814
|
-
async requestRefund(invoiceId
|
|
2815
|
-
this.requireSession();
|
|
2858
|
+
async requestRefund(invoiceId) {
|
|
2859
|
+
const session = this.requireSession();
|
|
2860
|
+
const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
|
|
2816
2861
|
const response = await this.http.post(
|
|
2817
2862
|
"/v1/payments/refund",
|
|
2818
2863
|
{ invoice_id: invoiceId, email_encryption_key: emailEncryptionKey },
|
|
@@ -3380,6 +3425,34 @@ Required: ${schema.required.join(", ")}`
|
|
|
3380
3425
|
data: response.data
|
|
3381
3426
|
};
|
|
3382
3427
|
}
|
|
3428
|
+
async ensureEmailEncryptionKey(session) {
|
|
3429
|
+
if (session.emailEncryptionKeyB64) return session.emailEncryptionKeyB64;
|
|
3430
|
+
await this.hydrateEmailEncryptionKey(session);
|
|
3431
|
+
if (session.emailEncryptionKeyB64) return session.emailEncryptionKeyB64;
|
|
3432
|
+
throw new Error(
|
|
3433
|
+
"Email encryption key is missing. Run `openmates login` again to refresh your local encryption keys."
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
async hydrateEmailEncryptionKey(session) {
|
|
3437
|
+
const response = await this.http.post(
|
|
3438
|
+
"/v1/auth/session",
|
|
3439
|
+
{ session_id: session.sessionId },
|
|
3440
|
+
this.getCliRequestHeaders()
|
|
3441
|
+
);
|
|
3442
|
+
const encryptedEmail = response.data.user?.encrypted_email_with_master_key;
|
|
3443
|
+
if (!response.ok || !response.data.success || typeof encryptedEmail !== "string") {
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
const email = await decryptWithAesGcmCombined(
|
|
3447
|
+
encryptedEmail,
|
|
3448
|
+
base64ToBytes(session.masterKeyExportedB64)
|
|
3449
|
+
);
|
|
3450
|
+
if (!email) return;
|
|
3451
|
+
session.emailEncryptionKeyB64 = await deriveEmailEncryptionKeyB64(
|
|
3452
|
+
email,
|
|
3453
|
+
session.userEmailSalt
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3383
3456
|
requireSession() {
|
|
3384
3457
|
if (!this.session) {
|
|
3385
3458
|
throw new Error("Not logged in. Run `openmates login`.");
|
|
@@ -7761,7 +7834,7 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
|
7761
7834
|
{ path: ["billing", "invoices", "list"], description: "List invoices", examples: ["openmates settings billing invoices list --json"] },
|
|
7762
7835
|
{ path: ["billing", "invoices", "download"], description: "Download an invoice PDF", examples: ["openmates settings billing invoices download <invoice-id> --output ./invoices"] },
|
|
7763
7836
|
{ path: ["billing", "invoices", "credit-note"], description: "Download a credit note PDF", examples: ["openmates settings billing invoices credit-note <invoice-id> --output ./invoices"] },
|
|
7764
|
-
{ path: ["billing", "invoices", "refund"], description: "Request a refund for an invoice", examples: ["openmates settings billing invoices refund <invoice-id> --
|
|
7837
|
+
{ path: ["billing", "invoices", "refund"], description: "Request a refund for an invoice", examples: ["openmates settings billing invoices refund <invoice-id> --yes"] },
|
|
7765
7838
|
{ path: ["billing", "gift-card", "redeem"], description: "Redeem a gift card", examples: ["openmates settings billing gift-card redeem ABCD-1234"] },
|
|
7766
7839
|
{ path: ["billing", "gift-card", "list"], description: "List redeemed gift cards", examples: ["openmates settings billing gift-card list"] },
|
|
7767
7840
|
{ path: ["billing", "auto-topup", "low-balance", "set"], description: "Configure low-balance auto top-up", examples: ["openmates settings billing auto-topup low-balance set --enabled true --amount 1000 --currency eur --email you@example.com"] },
|
|
@@ -8196,11 +8269,9 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
8196
8269
|
}
|
|
8197
8270
|
if (matches(tokens, ["billing", "invoices", "refund"])) {
|
|
8198
8271
|
const invoiceId = rest[2];
|
|
8199
|
-
const emailEncryptionKey = typeof flags["email-encryption-key"] === "string" ? flags["email-encryption-key"] : void 0;
|
|
8200
8272
|
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
8201
|
-
if (!emailEncryptionKey) throw new Error("Missing --email-encryption-key. The backend requires the encrypted email key for refund requests.");
|
|
8202
8273
|
if (flags.yes !== true) await confirmOrExit(`Request refund for invoice ${invoiceId}? [y/N] `);
|
|
8203
|
-
await printSettingsMutationResult(client.requestRefund(invoiceId
|
|
8274
|
+
await printSettingsMutationResult(client.requestRefund(invoiceId), flags);
|
|
8204
8275
|
return;
|
|
8205
8276
|
}
|
|
8206
8277
|
if (matches(tokens, ["billing", "gift-card", "redeem"]) || subcommand === "gift-card" && rest[0] === "redeem") {
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ interface OpenMatesSession {
|
|
|
7
7
|
wsToken: string | null;
|
|
8
8
|
cookies: Record<string, string>;
|
|
9
9
|
masterKeyExportedB64: string;
|
|
10
|
+
emailEncryptionKeyB64?: string | null;
|
|
10
11
|
hashedEmail: string;
|
|
11
12
|
userEmailSalt: string;
|
|
12
13
|
createdAt: number;
|
|
@@ -489,7 +490,7 @@ declare class OpenMatesClient {
|
|
|
489
490
|
}>;
|
|
490
491
|
downloadInvoice(invoiceId: string): Promise<DownloadedDocument>;
|
|
491
492
|
downloadCreditNote(invoiceId: string): Promise<DownloadedDocument>;
|
|
492
|
-
requestRefund(invoiceId: string
|
|
493
|
+
requestRefund(invoiceId: string): Promise<unknown>;
|
|
493
494
|
updateUsername(username: string): Promise<unknown>;
|
|
494
495
|
updateProfileImage(filePath: string): Promise<unknown>;
|
|
495
496
|
getNewsletterCategories(): Promise<unknown>;
|
|
@@ -637,6 +638,8 @@ declare class OpenMatesClient {
|
|
|
637
638
|
buildMentionContext(): Promise<MentionContext>;
|
|
638
639
|
private normalizePath;
|
|
639
640
|
private downloadPaymentPdf;
|
|
641
|
+
private ensureEmailEncryptionKey;
|
|
642
|
+
private hydrateEmailEncryptionKey;
|
|
640
643
|
private requireSession;
|
|
641
644
|
private getMasterKeyBytes;
|
|
642
645
|
private getValidSessionFromDisk;
|
package/dist/index.js
CHANGED