From 61b6668d67a7f69a2bef00468772287da8c3fbd7 Mon Sep 17 00:00:00 2001 From: Alec Charbonneau Date: Tue, 12 Dec 2023 11:26:16 -0800 Subject: [PATCH] add authToken class (#81) * add authToken class * make account optional * cleanup * remove un-needed account --- examples/sep24/sep24.ts | 2 +- src/walletSdk/Anchor/Sep24.ts | 4 +-- src/walletSdk/Anchor/Sep6.ts | 28 +++++++++--------- src/walletSdk/Anchor/index.ts | 10 +++---- src/walletSdk/Auth/index.ts | 7 ++--- src/walletSdk/Customer/index.ts | 32 +++++++++++++-------- src/walletSdk/Recovery/AccountRecover.ts | 2 +- src/walletSdk/Recovery/index.ts | 4 +-- src/walletSdk/Types/anchor.ts | 6 ++-- src/walletSdk/Types/auth.ts | 36 +++++++++++++++++++++++- src/walletSdk/Types/watcher.ts | 6 ++-- src/walletSdk/Watcher/getTransactions.ts | 9 +++--- src/walletSdk/Watcher/index.ts | 8 ++---- test/customer.test.ts | 2 +- test/sep6.test.ts | 2 -- test/wallet.test.ts | 12 +++++--- 16 files changed, 105 insertions(+), 65 deletions(-) diff --git a/examples/sep24/sep24.ts b/examples/sep24/sep24.ts index f80e87e..2aedc8f 100644 --- a/examples/sep24/sep24.ts +++ b/examples/sep24/sep24.ts @@ -88,7 +88,7 @@ const createAccount = async () => { }; // Create Deposit -let authToken: string; +let authToken: Types.AuthToken; export const runDeposit = async (anchor: Anchor, kp: SigningKeypair) => { console.log("\ncreating deposit ..."); const auth = await anchor.sep10(); diff --git a/src/walletSdk/Anchor/Sep24.ts b/src/walletSdk/Anchor/Sep24.ts index d80cf6d..5ddf066 100644 --- a/src/walletSdk/Anchor/Sep24.ts +++ b/src/walletSdk/Anchor/Sep24.ts @@ -87,7 +87,7 @@ export class Sep24 { * Initiates a withdrawal request. * @param {Sep24PostParams} params - The SEP-24 Post params. * @param {string} params.assetCode - The asset to withdraw. - * @param {string} params.authToken - Authentication token for the request. + * @param {AuthToken} params.authToken - Authentication token for the request. * @param {string} [params.lang] - The language for the request (defaults to the Anchor's language). * @param {ExtraFields} [params.extraFields] - Additional fields for the request. * @param {string} [params.withdrawalAccount] - The withdrawal account. @@ -157,7 +157,7 @@ export class Sep24 { { headers: { "Content-Type": "application/json", - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${authToken.token}`, }, }, ); diff --git a/src/walletSdk/Anchor/Sep6.ts b/src/walletSdk/Anchor/Sep6.ts index 6a5f27a..992504b 100644 --- a/src/walletSdk/Anchor/Sep6.ts +++ b/src/walletSdk/Anchor/Sep6.ts @@ -18,6 +18,7 @@ import { GetTransactionParams, GetTransactionsParams, WatcherSepType, + AuthToken, } from "../Types"; import { Watcher, @@ -76,7 +77,7 @@ export class Sep6 { * the anchor are given in the response. * * @param {object} options - The options for the deposit. - * @param {string} options.authToken - The authentication token. + * @param {AuthToken} options.authToken - The authentication token. * @param {Sep6DepositParams} options.params - The parameters for the deposit request. * * @returns {Promise} Sep6 deposit response, containing next steps if needed @@ -88,7 +89,7 @@ export class Sep6 { authToken, params, }: { - authToken: string; + authToken: AuthToken; params: Sep6DepositParams; }): Promise { return this.flow({ type: "deposit", authToken, params }); @@ -98,7 +99,7 @@ export class Sep6 { * Initiates a withdrawal using SEP-6. * * @param {object} options - The options for the withdrawal operation. - * @param {string} options.authToken - The authentication token. + * @param {AuthToken} options.authToken - The authentication token. * @param {Sep6WithdrawParams} options.params - The parameters for the withdrawal request. * * @returns {Promise} Sep6 withdraw response. @@ -107,7 +108,7 @@ export class Sep6 { authToken, params, }: { - authToken: string; + authToken: AuthToken; params: Sep6WithdrawParams; }): Promise { return this.flow({ type: "withdraw", authToken, params }); @@ -118,7 +119,7 @@ export class Sep6 { * that require an exchange. * * @param {object} options - The options for the deposit exchange. - * @param {string} options.authToken - The authentication token. + * @param {AuthToken} options.authToken - The authentication token. * @param {Sep6ExchangeParams} options.params - The parameters for the deposit request. * * @returns {Promise} Sep6 deposit response, containing next steps if needed @@ -130,7 +131,7 @@ export class Sep6 { authToken, params, }: { - authToken: string; + authToken: AuthToken; params: Sep6ExchangeParams; }): Promise { return this.flow({ type: "deposit-exchange", authToken, params }); @@ -141,7 +142,7 @@ export class Sep6 { * that require an exchange. * * @param {object} options - The options for the deposit exchange. - * @param {string} options.authToken - The authentication token. + * @param {AuthToken} options.authToken - The authentication token. * @param {Sep6ExchangeParams} options.params - The parameters for the deposit request. * * @returns {Promise} Sep6 withdraw response, containing next steps if needed @@ -153,7 +154,7 @@ export class Sep6 { authToken, params, }: { - authToken: string; + authToken: AuthToken; params: Sep6ExchangeParams; }): Promise { return this.flow({ type: "withdraw-exchange", authToken, params }); @@ -165,7 +166,7 @@ export class Sep6 { params, }: { type: "deposit" | "withdraw" | "deposit-exchange" | "withdraw-exchange"; - authToken: string; + authToken: AuthToken; params: Sep6DepositParams | Sep6WithdrawParams | Sep6ExchangeParams; }) { const { transferServer } = await this.anchor.sep1(); @@ -176,7 +177,7 @@ export class Sep6 { { headers: { "Content-Type": "application/json", - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${authToken.token}`, }, }, ); @@ -204,8 +205,6 @@ export class Sep6 { * @param {GetTransactionsParams} params - The Get Transactions params. * @param {AuthToken} params.authToken - The authentication token for the account authenticated with the anchor. * @param {string} params.assetCode - The target asset to query for. - * @param {string} params.account - The stellar account public key involved in the transactions. If the service requires SEP-10 - * authentication, this parameter must match the authenticated account. * @param {string} [params.noOlderThan] - The response should contain transactions starting on or after this date & time. * @param {string} [params.limit] - The response should contain at most 'limit' transactions. * @param {string} [params.kind] - The kind of transaction that is desired. E.g.: 'deposit', 'withdrawal', 'depo @@ -219,20 +218,19 @@ export class Sep6 { async getTransactionsForAsset({ authToken, assetCode, - account, noOlderThan, limit, kind, pagingId, lang = this.anchor.language, - }: GetTransactionsParams & { account: string }): Promise { + }: GetTransactionsParams): Promise { const toml = await this.anchor.sep1(); const transferServerEndpoint = toml.transferServer; // Let's convert all params to snake case for the API call const apiParams = camelToSnakeCaseObject({ assetCode, - account, + account: authToken.account, noOlderThan, limit, kind, diff --git a/src/walletSdk/Anchor/index.ts b/src/walletSdk/Anchor/index.ts index 27d7b3b..b41afc9 100644 --- a/src/walletSdk/Anchor/index.ts +++ b/src/walletSdk/Anchor/index.ts @@ -10,7 +10,7 @@ import { } from "../Exceptions"; import { Sep6 } from "./Sep6"; import { Sep24 } from "./Sep24"; -import { AnchorServiceInfo, TomlInfo } from "../Types"; +import { AnchorServiceInfo, TomlInfo, AuthToken } from "../Types"; import { parseToml } from "../Utils"; // Let's prevent exporting this constructor type as @@ -125,11 +125,11 @@ export class Anchor { /** * Create new customer object to handle customer records with the anchor using SEP-12. - * @param {string} authToken - The authentication token. + * @param {AuthToken} authToken - The authentication token. * @returns {Promise} - A Sep12 customer instance. * @throws {KYCServerNotFoundError} - If the KYC server information is not available. */ - async sep12(authToken: string): Promise { + async sep12(authToken: AuthToken): Promise { const tomlInfo = await this.sep1(); const kycServer = tomlInfo?.kycServer; if (!kycServer) { @@ -140,10 +140,10 @@ export class Anchor { /** * Create new customer object to handle customer records using the `sep12` method. - * @param {string} authToken - The authentication token. + * @param {AuthToken} authToken - The authentication token. * @returns {Promise} - A Customer instance. */ - async customer(authToken: string): Promise { + async customer(authToken: AuthToken): Promise { return this.sep12(authToken); } diff --git a/src/walletSdk/Auth/index.ts b/src/walletSdk/Auth/index.ts index 39c7a2e..671f640 100644 --- a/src/walletSdk/Auth/index.ts +++ b/src/walletSdk/Auth/index.ts @@ -147,17 +147,16 @@ export class Sep10 { throw new MissingTokenError(); } - const authToken: AuthToken = resp.data.token; - validateToken(authToken); + validateToken(resp.data.token); - return authToken; + return AuthToken.from(resp.data.token); } catch (e) { throw new ServerRequestFailedError(e); } } } -const validateToken = (token: AuthToken) => { +const validateToken = (token: string) => { const parsedToken = decode(token); if (!parsedToken) { throw new InvalidTokenError(); diff --git a/src/walletSdk/Customer/index.ts b/src/walletSdk/Customer/index.ts index 3e5025d..1029bda 100644 --- a/src/walletSdk/Customer/index.ts +++ b/src/walletSdk/Customer/index.ts @@ -7,6 +7,7 @@ import { GetCustomerResponse, AddCustomerResponse, AddCustomerParams, + AuthToken, } from "../Types"; /** @@ -24,17 +25,21 @@ export class Sep12 { /** * Creates a new instance of the Sep12 class. * @constructor - * @param {string} authToken - The authentication token for authenticating with the server. + * @param {AuthToken} authToken - The authentication token for authenticating with the server. * @param {string} baseUrl - The KYC url. * @param {AxiosInstance} httpClient - An Axios instance for making HTTP requests. */ - constructor(authToken: string, baseUrl: string, httpClient: AxiosInstance) { + constructor( + authToken: AuthToken, + baseUrl: string, + httpClient: AxiosInstance, + ) { this.authToken = authToken; this.baseUrl = baseUrl; this.httpClient = httpClient; this.headers = { "Content-Type": "application/json", - Authorization: `Bearer ${this.authToken}`, + Authorization: `Bearer ${this.authToken.token}`, }; } @@ -66,9 +71,9 @@ export class Sep12 { * Add a new customer. Customer info is given in sep9Info param. If it * is binary type (eg. Buffer of an image) include it in sep9BinaryInfo. * @param {AddCustomerParams} params - The parameters for adding a customer. - * @param {CustomerInfoMap} params.sep9Info - Customer information. What fields you should + * @param {CustomerInfoMap} [params.sep9Info] - Customer information. What fields you should * give is indicated by the anchor. - * @param {CustomerInfoMap} params.sep9BinaryInfo - Customer information that is in binary + * @param {CustomerInfoMap} [params.sep9BinaryInfo] - Customer information that is in binary * format (eg. Buffer of an image). * @param {string} [params.type] - The type of the customer. * @param {string} [params.memo] - A memo associated with the customer. @@ -106,9 +111,9 @@ export class Sep12 { * Updates an existing customer. Customer info is given in sep9Info param. If it * is binary type (eg. Buffer of an image) include it in sep9BinaryInfo. * @param {AddCustomerParams} params - The parameters for adding a customer. - * @param {CustomerInfoMap} params.sep9Info - Customer information. What fields you should + * @param {CustomerInfoMap} [params.sep9Info] - Customer information. What fields you should * give is indicated by the anchor. - * @param {CustomerInfoMap} params.sep9BinaryInfo - Customer information that is in binary + * @param {CustomerInfoMap} [params.sep9BinaryInfo] - Customer information that is in binary * format (eg. Buffer of an image). * @param {string} [params.id] - The id of the customer. * @param {string} [params.type] - The type of the customer. @@ -157,10 +162,13 @@ export class Sep12 { * @param {string} accountAddress - The account address of the customer to delete. * @param {string} [memo] - An optional memo for customer identification. */ - async delete(accountAddress: string, memo?: string) { - await this.httpClient.delete(`${this.baseUrl}/customer/${accountAddress}`, { - data: { memo }, - headers: this.headers, - }); + async delete(accountAddress?: string, memo?: string) { + await this.httpClient.delete( + `${this.baseUrl}/customer/${accountAddress || this.authToken.account}`, + { + data: { memo }, + headers: this.headers, + }, + ); } } diff --git a/src/walletSdk/Recovery/AccountRecover.ts b/src/walletSdk/Recovery/AccountRecover.ts index dee33a8..2b9d7b6 100644 --- a/src/walletSdk/Recovery/AccountRecover.ts +++ b/src/walletSdk/Recovery/AccountRecover.ts @@ -236,7 +236,7 @@ export abstract class AccountRecover { { headers: { "Content-Type": "application/json", - Authorization: `Bearer ${auth.authToken}`, + Authorization: `Bearer ${auth.authToken.token}`, }, }, ); diff --git a/src/walletSdk/Recovery/index.ts b/src/walletSdk/Recovery/index.ts index 7d7e325..f4ac10f 100644 --- a/src/walletSdk/Recovery/index.ts +++ b/src/walletSdk/Recovery/index.ts @@ -156,7 +156,7 @@ export class Recovery extends AccountRecover { const resp = await this.httpClient.get(requestUrl, { headers: { "Content-Type": "application/json", - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${authToken.token}`, }, }); @@ -288,7 +288,7 @@ export class Recovery extends AccountRecover { { headers: { "Content-Type": "application/json", - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${authToken.token}`, }, }, ); diff --git a/src/walletSdk/Types/anchor.ts b/src/walletSdk/Types/anchor.ts index 1c3adcd..9aef775 100644 --- a/src/walletSdk/Types/anchor.ts +++ b/src/walletSdk/Types/anchor.ts @@ -1,6 +1,8 @@ import { MemoType } from "stellar-sdk"; import { Optional } from "utility-types"; +import { AuthToken } from "./auth"; + export interface AnchorServiceInfo { deposit: AssetInfoMap; withdraw: AssetInfoMap; @@ -101,7 +103,7 @@ export interface Payment { } export type GetTransactionParams = { - authToken: string; + authToken: AuthToken; id?: string; stellarTransactionId?: string; externalTransactionId?: string; @@ -109,7 +111,7 @@ export type GetTransactionParams = { }; export type GetTransactionsParams = { - authToken: string; + authToken: AuthToken; assetCode: string; noOlderThan?: string; limit?: number; diff --git a/src/walletSdk/Types/auth.ts b/src/walletSdk/Types/auth.ts index 2c04e1f..9b69717 100644 --- a/src/walletSdk/Types/auth.ts +++ b/src/walletSdk/Types/auth.ts @@ -1,4 +1,6 @@ import { Transaction } from "stellar-sdk"; +import { decode } from "jws"; + import { WalletSigner } from "../Auth/WalletSigner"; import { AccountKeypair } from "../Horizon/Account"; @@ -9,7 +11,39 @@ export type AuthenticateParams = { clientDomain?: string; }; -export type AuthToken = string; +export class AuthToken { + public token: string; + public issuer: string; + public issuedAt: string; + public expiresAt: string; + public clientDomain: string; + private principalAccount: string; + + get account(): string { + return this.principalAccount.split(":")[0]; + } + + get memo(): string { + const split = this.principalAccount.split(":"); + if (split.length !== 2) { + return null; + } + return split[1]; + } + + static from = (str: string): AuthToken => { + const authToken = new AuthToken(); + + const decoded = decode(str); + authToken.issuer = decoded.payload.iss; + authToken.principalAccount = decoded.payload.sub; + authToken.issuedAt = decoded.payload.iat; + authToken.expiresAt = decoded.payload.exp; + authToken.clientDomain = decoded.payload.client_domain; + authToken.token = str; + return authToken; + }; +} export type ChallengeParams = { accountKp: AccountKeypair; diff --git a/src/walletSdk/Types/watcher.ts b/src/walletSdk/Types/watcher.ts index 8bcf6b2..e64664c 100644 --- a/src/walletSdk/Types/watcher.ts +++ b/src/walletSdk/Types/watcher.ts @@ -1,11 +1,11 @@ import { AnchorTransaction } from "./anchor"; +import { AuthToken } from "./auth"; export type WatchTransactionsParams = { - authToken: string; + authToken: AuthToken; assetCode: string; onMessage: (transaction: AnchorTransaction) => void; onError: (error: AnchorTransaction | Error) => void; - account?: string; watchlist?: string[]; timeout?: number; isRetry?: boolean; @@ -15,7 +15,7 @@ export type WatchTransactionsParams = { }; export type WatchTransactionParams = { - authToken: string; + authToken: AuthToken; assetCode: string; id: string; onMessage: (transaction: AnchorTransaction) => void; diff --git a/src/walletSdk/Watcher/getTransactions.ts b/src/walletSdk/Watcher/getTransactions.ts index b728ff3..b3fc53b 100644 --- a/src/walletSdk/Watcher/getTransactions.ts +++ b/src/walletSdk/Watcher/getTransactions.ts @@ -1,6 +1,7 @@ import { AxiosInstance } from "axios"; import queryString from "query-string"; +import { AuthToken } from "../Types"; import { ServerRequestFailedError, InvalidTransactionsResponseError, @@ -8,7 +9,7 @@ import { } from "../Exceptions"; export const _getTransactionsForAsset = async ( - authToken: string, + authToken: AuthToken, params: { [key: string]: string | number }, endpoint: string, client: AxiosInstance, @@ -18,7 +19,7 @@ export const _getTransactionsForAsset = async ( `${endpoint}/transactions?${queryString.stringify(params)}`, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${authToken.token}`, }, }, ); @@ -36,7 +37,7 @@ export const _getTransactionsForAsset = async ( }; export const _getTransactionBy = async ( - authToken: string, + authToken: AuthToken, params: { [key: string]: string | number }, endpoint: string, client: AxiosInstance, @@ -46,7 +47,7 @@ export const _getTransactionBy = async ( `${endpoint}/transaction?${queryString.stringify(params)}`, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${authToken.token}`, }, }, ); diff --git a/src/walletSdk/Watcher/index.ts b/src/walletSdk/Watcher/index.ts index 4fc31fa..fd88ee0 100644 --- a/src/walletSdk/Watcher/index.ts +++ b/src/walletSdk/Watcher/index.ts @@ -79,11 +79,10 @@ export class Watcher { * You may also provide an array of transaction ids, `watchlist`, and this * watcher will always react to transactions whose ids are in the watchlist. * @param {WatchTransactionsParams} params - The Watch Transactions params. - * @param {string} params.authToken - The authentication token used for authenticating with the anchor. + * @param {AuthToken} params.authToken - The authentication token used for authenticating with the anchor. * @param {string} params.assetCode - The asset code to filter transactions by. * @param {Function} params.onMessage - A callback function to handle incoming transaction messages. * @param {Function} params.onError - A callback function to handle errors during transaction streaming. - * @param {string} [params.account] - The stellar account public key involved in the transactions. * @param {Array} [params.watchlist=[]] - An optional array of specific transaction IDs to watch. * @param {number} [params.timeout=5000] - The timeout duration for the streaming connection (in milliseconds). * @param {boolean} [params.isRetry=false] - Indicates whether this is a retry attempt (optional). @@ -97,7 +96,6 @@ export class Watcher { assetCode, onMessage, onError, - account, watchlist = [], timeout = 5000, isRetry = false, @@ -108,7 +106,6 @@ export class Watcher { const allParams = { authToken, assetCode, - account, onMessage, onError, watchlist, @@ -155,7 +152,6 @@ export class Watcher { .getTransactionsForAsset({ authToken, assetCode, - account, lang, kind, noOlderThan, @@ -282,7 +278,7 @@ export class Watcher { * * onError - When there's a runtime error, or the transaction comes back as * no_market / too_small / too_large / error. * @param {WatchTransactionParams} params - The Watch Transaction params. - * @param {string} params.authToken - The authentication token used for authenticating with th anchor. + * @param {AuthToken} params.authToken - The authentication token used for authenticating with th anchor. * @param {string} params.assetCode - The asset code to filter transactions by. * @param {string} params.id - The id of the transaction to watch. * @param {Function} params.onMessage - A callback function to handle incoming transaction messages. diff --git a/test/customer.test.ts b/test/customer.test.ts index b796ae1..e2a0651 100644 --- a/test/customer.test.ts +++ b/test/customer.test.ts @@ -77,6 +77,6 @@ describe("Customer", () => { expect(Object.keys(resp.data.fields).length).toBe(0); // Delete - await sep12.delete(accountKp.publicKey); + await sep12.delete(); }, 20000); }); diff --git a/test/sep6.test.ts b/test/sep6.test.ts index a438f26..97c257c 100644 --- a/test/sep6.test.ts +++ b/test/sep6.test.ts @@ -158,7 +158,6 @@ describe("SEP-6", () => { let resp = await anchor.sep6().getTransactionsForAsset({ authToken, assetCode: "SRT", - account: accountKp.publicKey, }); expect(resp[0].id).toBeTruthy(); @@ -192,7 +191,6 @@ describe("SEP-6", () => { const { stop } = watcher.watchAllTransactions({ authToken, assetCode: "SRT", - account: accountKp.publicKey, onMessage, onError, timeout: 1, diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 1af85fc..841c779 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -11,7 +11,11 @@ import { import { DefaultClient } from "../src/walletSdk"; import { ServerRequestFailedError } from "../src/walletSdk/Exceptions"; import { Watcher } from "../src/walletSdk/Watcher"; -import { TransactionStatus, AnchorTransaction } from "../src/walletSdk/Types"; +import { + TransactionStatus, + AnchorTransaction, + AuthToken, +} from "../src/walletSdk/Types"; import { WalletSigner, DefaultSigner, @@ -64,7 +68,7 @@ describe("SEP-24 flow", () => { let anchor: Anchor; let accountKp: SigningKeypair; -let authToken: string; +let authToken: AuthToken; const makeTransaction = (eta: number, txStatus: TransactionStatus) => ({ kind: "deposit", id: "TEST", @@ -94,13 +98,13 @@ describe("Anchor", () => { let auth = await anchor.sep10(); authToken = await auth.authenticate({ accountKp }); expect(authToken).toBeTruthy(); - expect(typeof authToken).toBe("string"); + expect(authToken.account).toBeTruthy(); // alias auth = await anchor.auth(); authToken = await auth.authenticate({ accountKp }); expect(authToken).toBeTruthy(); - expect(typeof authToken).toBe("string"); + expect(authToken.account).toBeTruthy(); }); it("should be able to authenticate with client domain", async () => {