[PATCH damus-api v1 1/2] Add UUID map to the dump script

3 views
Skip to first unread message

Daniel D’Aquino

unread,
Jul 1, 2024, 4:59:23 PM (4 days ago) Jul 1
to pat...@damus.io, Daniel D’Aquino
This is needed to aid customer support requests where locating users'
UUIDs is relevant

Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
---
scripts/dump.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/scripts/dump.js b/scripts/dump.js
index 12b6629..e0a9e6b 100644
--- a/scripts/dump.js
+++ b/scripts/dump.js
@@ -7,10 +7,11 @@ function go() {
const db = lmdb.open({ path: db_path })
const accounts = db.openDB('accounts')
const pubkeys_to_user_ids = db.openDB('pubkeys_to_user_ids')
+ const pubkeys_to_user_uuids = db.openDB('pubkeys_to_user_uuids')
const invoices = db.openDB('invoices')
const sessions = db.openDB('checkout_sessions')
const data = {}
- const datas = {accounts:{},pubkeys_to_user_ids:{},checkout_sessions:{}}
+ const datas = {accounts:{},pubkeys_to_user_ids:{},checkout_sessions:{}, pubkeys_to_user_uuids:{}}

for (const { key, value } of accounts.getRange()) {
datas.accounts[key] = value
@@ -18,6 +19,9 @@ function go() {
for (const { key, value } of pubkeys_to_user_ids.getRange()) {
datas.pubkeys_to_user_ids[key] = value
}
+ for(const { key, value } of pubkeys_to_user_uuids.getRange()) {
+ datas.pubkeys_to_user_uuids[key] = value
+ }
for (const { key, value } of sessions.getRange()) {
datas.checkout_sessions[key] = value
}

base-commit: 2b5ce631f8755aa7b588056d8ea8f74b8af3eefb
--
2.44.0


Daniel D’Aquino

unread,
Jul 1, 2024, 4:59:32 PM (4 days ago) Jul 1
to pat...@damus.io, Daniel D’Aquino
This commit adds lookup functions that allows transaction histories to
be found using the Order ID from the customer. The Order ID is a string
that gets included in each email receipt and can be used in customer
support situations.

Currently the function is not exposed in any external APIs. It can only
be used interactively on a Node.js console.

From the perspective of the API and the server functionality, this commit should be a no-op.

-------
Testing
-------

Damus API: This commit
Coverage:
- All automated tests are passing (with no `.env`)
- Typescript type check has no regressions
- New function was tested by using it via Node.js console to help solve a real customer support request

Closes: https://github.com/damus-io/api/issues/9
Changelog-Added: Add Order ID lookup functions to aid customer support
Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
---
src/app_store_receipt_verifier.js | 127 +++++++++++++++++++++++-------
1 file changed, 99 insertions(+), 28 deletions(-)

diff --git a/src/app_store_receipt_verifier.js b/src/app_store_receipt_verifier.js
index fddcbfc..eabfc0f 100644
--- a/src/app_store_receipt_verifier.js
+++ b/src/app_store_receipt_verifier.js
@@ -12,6 +12,20 @@ const { current_time } = require("./utils")
const fs = require('fs')
const debug = require('debug')('api:iap')

+const DEFAULT_ROOT_CA_DIR = './apple-root-ca'
+/**
+ * Mock transaction history for testing purposes.
+ * @type {Transaction[]}
+ */
+const MOCK_TRANSACTION_HISTORY = [{
+ type: "iap",
+ id: "1",
+ start_date: current_time(),
+ end_date: current_time() + 60 * 60 * 24 * 30,
+ purchased_date: current_time(),
+ duration: null
+}];
+
/**
* Verifies the receipt data and returns the expiry date if the receipt is valid.
*
@@ -25,18 +39,11 @@ async function verify_receipt(receipt_data, authenticated_account_token) {
// Mocking logic for testing purposes
if (process.env.MOCK_VERIFY_RECEIPT == "true") {
debug("Mocking verify_receipt with expiry date 30 days from now");
- return [{
- type: "iap",
- id: "1",
- start_date: current_time(),
- end_date: current_time() + 60 * 60 * 24 * 30,
- purchased_date: current_time(),
- duration: null
- }]
+ return MOCK_TRANSACTION_HISTORY;
}

// Setup the environment and client
- const rootCaDir = process.env.IAP_ROOT_CA_DIR || './apple-root-ca'
+ const rootCaDir = process.env.IAP_ROOT_CA_DIR || DEFAULT_ROOT_CA_DIR;
const bundleId = process.env.IAP_BUNDLE_ID;
const environment = getAppStoreEnvironmentFromEnv();
const client = createAppStoreServerAPIClientFromEnv();
@@ -65,18 +72,11 @@ async function verify_transaction_id(transaction_id, authenticated_account_token
// Mocking logic for testing purposes
if (process.env.MOCK_VERIFY_RECEIPT == "true") {
debug("Mocking verify_receipt with expiry date 30 days from now");
- return [{
- type: "iap",
- id: "1",
- start_date: current_time(),
- end_date: current_time() + 60 * 60 * 24 * 30,
- purchased_date: current_time(),
- duration: null
- }];
+ return MOCK_TRANSACTION_HISTORY;
}

// Setup the environment and client
- const rootCaDir = process.env.IAP_ROOT_CA_DIR || './apple-root-ca'
+ const rootCaDir = process.env.IAP_ROOT_CA_DIR || DEFAULT_ROOT_CA_DIR;
const bundleId = process.env.IAP_BUNDLE_ID;
const environment = getAppStoreEnvironmentFromEnv();
const client = createAppStoreServerAPIClientFromEnv();
@@ -89,6 +89,35 @@ async function verify_transaction_id(transaction_id, authenticated_account_token
}


+/**
+ * Looks up the order id and returns the verified transaction history if valid
+ *
+ * @param {string} order_id - The order id to lookup
+ *
+ * @returns {Promise<Transaction[]|null>} The validated transactions
+ */
+async function lookup_order_id(order_id) {
+ debug("Looking up order id '%d'", order_id);
+ // Mocking logic for testing purposes
+ if (process.env.MOCK_VERIFY_RECEIPT == "true") {
+ debug("Mocking verify_receipt with expiry date 30 days from now");
+ return MOCK_TRANSACTION_HISTORY;
+ }
+
+ // Setup the environment and client
+ const rootCaDir = process.env.IAP_ROOT_CA_DIR || DEFAULT_ROOT_CA_DIR;
+ const bundleId = process.env.IAP_BUNDLE_ID;
+ const environment = getAppStoreEnvironmentFromEnv();
+ const client = createAppStoreServerAPIClientFromEnv();
+
+ // If the transaction ID is present, fetch the transaction history, verify the transactions, and return the latest expiry date
+ if (order_id != null) {
+ return await fetchValidatedTransactionsFromOrderId(client, order_id, rootCaDir, environment, bundleId, undefined);
+ }
+ return Promise.resolve(null);
+}
+
+
/**
* Fetches transaction history with the App Store API, verifies the transactions, and returns formatted transactions.
* It also verifies if the transaction belongs to the account who made the request.
@@ -114,17 +143,59 @@ async function fetchValidatedTransactions(client, transactionId, rootCaDir, envi
return null;
}
return validDecodedTransactions.map((decodedTransaction) => {
- return {
- type: "iap",
- id: decodedTransaction.transactionId,
- start_date: decodedTransaction.purchaseDate / 1000,
- end_date: decodedTransaction.expiresDate / 1000,
- purchased_date: decodedTransaction.purchaseDate / 1000,
- duration: null
- }
+ return transformDecodedTransactionToTransaction(decodedTransaction)
})
}

+/**
+ * Fetches transaction history associated with an Order ID with the App Store API, verifies the transactions, and returns formatted transactions.
+ * An Order ID is a unique identifier for a transaction that is generated by the App Store and is included in receipt emails, so it is useful for customer support.
+ *
+ * @param {AppStoreServerAPIClient} client - The App Store API client.
+ * @param {string} orderId - The order ID to fetch history for.
+ * @param {string} rootCaDir - The directory containing Apple root CA certificates for verification.
+ * @param {Environment} environment - The App Store environment.
+ * @param {string} bundleId - The bundle ID of the app.
+ * @param {string | undefined} authenticatedAccountToken - The UUID account token of the user who is authenticated in this request. If undefined, UUID authentication will be skipped.
+ *
+ * @returns {Promise<Transaction[]|null>} The validated transactions
+*/
+async function fetchValidatedTransactionsFromOrderId(client, orderId, rootCaDir, environment, bundleId, authenticatedAccountToken) {
+ const transactions = await client.lookUpOrderId(orderId);
+ debug("[Order ID: %s] Fetched transaction history; Found %d transactions", orderId, transactions.signedTransactions);
+ const rootCAs = readCertificateFiles(rootCaDir);
+ const decodedTransactions = await verifyAndDecodeTransactions(transactions.signedTransactions, rootCAs, environment, bundleId);
+ debug("[Order ID: %s] Verified and decoded %d transactions", orderId, decodedTransactions.length);
+ const validDecodedTransactions = authenticatedAccountToken == undefined ? decodedTransactions : filterTransactionsThatBelongToAccount(decodedTransactions, authenticatedAccountToken);
+ if (authenticatedAccountToken != undefined) {
+ debug("[Account token: %s] Filtered transactions that belong to the account UUID. Found %d matching transactions", authenticatedAccountToken, validDecodedTransactions.length);
+ }
+ if (validDecodedTransactions.length === 0) {
+ return null;
+ }
+ return validDecodedTransactions.map((decodedTransaction) => {
+ return transformDecodedTransactionToTransaction(decodedTransaction)
+ })
+}
+
+/**
+ * Transforms a JWSTransactionDecodedPayload into a Transaction object
+ *
+ * @param {JWSTransactionDecodedPayload} decodedTransaction - The decoded transaction to transform.
+ * @returns {Transaction} The transformed transaction.
+ *
+ */
+function transformDecodedTransactionToTransaction(decodedTransaction) {
+ return {
+ type: "iap",
+ id: decodedTransaction.transactionId,
+ start_date: decodedTransaction.purchaseDate / 1000,
+ end_date: decodedTransaction.expiresDate / 1000,
+ purchased_date: decodedTransaction.purchaseDate / 1000,
+ duration: null
+ }
+}
+
/**
* Filters out transactions that do not belong to the authorized account token.
*
@@ -168,7 +239,7 @@ function readCertificateFiles(directory) {
* @param {Buffer[]} rootCAs - Apple root CA certificate contents for verification.
* @param {Environment} environment - The App Store environment.
* @param {string} bundleId - The bundle ID of the app.
- * @returns {Promise<Object[]>} The decoded transactions.
+ * @returns {Promise<JWSTransactionDecodedPayload[]>} The decoded transactions.
*/
async function verifyAndDecodeTransactions(transactions, rootCAs, environment, bundleId) {
const verifier = new SignedDataVerifier(rootCAs, true, environment, bundleId);
@@ -254,5 +325,5 @@ function getAppStoreEnvironmentFromEnv() {
}

module.exports = {
- verify_receipt, verify_transaction_id
+ verify_receipt, verify_transaction_id, lookup_order_id
};
--
2.44.0


William Casarin

unread,
Jul 1, 2024, 10:42:48 PM (4 days ago) Jul 1
to Daniel D’Aquino, pat...@damus.io

On Mon, Jul 01, 2024 at 08:59:26PM +0000, Daniel D’Aquino wrote:
>This commit adds lookup functions that allows transaction histories to
>be found using the Order ID from the customer. The Order ID is a string
>that gets included in each email receipt and can be used in customer
>support situations.
>
>Currently the function is not exposed in any external APIs. It can only
>be used interactively on a Node.js console.
>
>From the perspective of the API and the server functionality, this commit should be a no-op.
>
>-------
>Testing
>-------
>
>Damus API: This commit
>Coverage:
>- All automated tests are passing (with no `.env`)
>- Typescript type check has no regressions
>- New function was tested by using it via Node.js console to help solve a real customer support request
>
>Closes: https://github.com/damus-io/api/issues/9
>Changelog-Added: Add Order ID lookup functions to aid customer support
>Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
>---

Reviewed-by: William Casarin <jb...@jb55.com>

Daniel D'Aquino

unread,
Jul 3, 2024, 2:17:29 PM (2 days ago) Jul 3
to William Casarin, sembene_truestar via patches


> On Jul 1, 2024, at 19:42, William Casarin <jb...@jb55.com> wrote:
>
>
> On Mon, Jul 01, 2024 at 08:59:26PM +0000, Daniel D’Aquino wrote:
>> This commit adds lookup functions that allows transaction histories to
>> be found using the Order ID from the customer. The Order ID is a string
>> that gets included in each email receipt and can be used in customer
>> support situations.
>>
>> Currently the function is not exposed in any external APIs. It can only
>> be used interactively on a Node.js console.
>>
>>
>> -------
>> Testing
>> -------
>>
>> Damus API: This commit
>> Coverage:
>> - All automated tests are passing (with no `.env`)
>> - Typescript type check has no regressions
>> - New function was tested by using it via Node.js console to help solve a real customer support request
>>
>> Closes: https://github.com/damus-io/api/issues/9
>> Changelog-Added: Add Order ID lookup functions to aid customer support
>> Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
>> ---
>
> Reviewed-by: William Casarin <jb...@jb55.com>

Thanks! Pushed to 790210dd0db58c5846c7ec96c0243f84388947b4
> To unsubscribe from this group and stop receiving emails from it, send an email to patches+u...@damus.io.
>


Reply all
Reply to author
Forward
0 new messages