diff --git a/packages/engine-request/package.json b/packages/engine-request/package.json index f5650c011..6d8ef55c4 100644 --- a/packages/engine-request/package.json +++ b/packages/engine-request/package.json @@ -4,7 +4,6 @@ "dependencies": { "tslib": "^2.3.0", "uuid": "^9.0.1", - "zod": "^3.22.4", - "jest": "^29.4.1" + "zod": "^3.22.4" } } diff --git a/packages/engine-request/src/lib/builders/__test__/request-builder.spec.ts b/packages/engine-request/src/lib/builders/__test__/request-builder.spec.ts index 339c3b189..eb5a2e812 100644 --- a/packages/engine-request/src/lib/builders/__test__/request-builder.spec.ts +++ b/packages/engine-request/src/lib/builders/__test__/request-builder.spec.ts @@ -1,8 +1,5 @@ // builders.test.ts -import { - Eip712TypedData, - TransactionRequest -} from '@narval/policy-engine-shared' +import { Eip712TypedData, TransactionRequest } from '@narval/policy-engine-shared' import { v4 } from 'uuid' import { buildRequest } from '../request-builder' diff --git a/packages/engine-request/src/lib/builders/evaluation-request.ts b/packages/engine-request/src/lib/builders/evaluation-request.ts index 828a367f7..f2d7ab9f1 100644 --- a/packages/engine-request/src/lib/builders/evaluation-request.ts +++ b/packages/engine-request/src/lib/builders/evaluation-request.ts @@ -1,9 +1,9 @@ import { Feed, JwtString, Prices } from '@narval/policy-engine-shared' export default class EvaluationRequestBuilder { - approvals?: JwtString[] - prices?: Prices - feeds?: Feed[] + private approvals?: JwtString[] + private prices?: Prices + private feeds?: Feed[] setApprovals(approvals: JwtString[]) { this.approvals = approvals diff --git a/packages/engine-request/src/lib/builders/organization-request.ts b/packages/engine-request/src/lib/builders/organization-request.ts index 1711ec0c6..641b7d5a8 100644 --- a/packages/engine-request/src/lib/builders/organization-request.ts +++ b/packages/engine-request/src/lib/builders/organization-request.ts @@ -1,9 +1,8 @@ import { OrganizationAction } from '../domain' -import EvaluationRequestBuilder from './evaluation-request' -export default class OrganizationBuilder extends EvaluationRequestBuilder { - action: OrganizationAction | null = null - nonce: string +export default class OrganizationBuilder { + private action: OrganizationAction | null = null + private nonce: string setAction(action: OrganizationAction) { this.action = action diff --git a/packages/engine-request/src/lib/builders/request-builder.ts b/packages/engine-request/src/lib/builders/request-builder.ts index 7c2471836..ea7278554 100644 --- a/packages/engine-request/src/lib/builders/request-builder.ts +++ b/packages/engine-request/src/lib/builders/request-builder.ts @@ -2,6 +2,17 @@ import { Category } from '../domain' import OrganizationBuilder from './organization-request' import WalletRequestBuilder from './wallet-request' +/** + * Creates a request builder for either Wallet or Organization categories. + * @param category - The category of the request to build. Either 'wallet' or 'organization'. + * @returns A builder instance specific to the provided category, either WalletRequestBuilder or OrganizationBuilder. + * @example + * // For wallet-related requests + * const walletBuilder = buildRequest('wallet'); + * + * // For organization-related requests + * const organizationBuilder = buildRequest('organization'); + */ export function buildRequest(category: 'wallet'): WalletRequestBuilder export function buildRequest(category: 'organization'): OrganizationBuilder export function buildRequest(category: Category): OrganizationBuilder | WalletRequestBuilder { diff --git a/packages/engine-request/src/lib/builders/wallet-request.ts b/packages/engine-request/src/lib/builders/wallet-request.ts index f1c14ff77..c4f3d77c1 100644 --- a/packages/engine-request/src/lib/builders/wallet-request.ts +++ b/packages/engine-request/src/lib/builders/wallet-request.ts @@ -8,13 +8,28 @@ import { } from '@narval/policy-engine-shared' import { v4 } from 'uuid' import { BuildResponse, WalletAction } from '../domain' -import EvaluationRequestBuilder from './evaluation-request' - -export default class WalletRequestBuilder extends EvaluationRequestBuilder { - action: WalletAction - resourceId: string - nonce: string +/** + * Builder class for creating wallet-related requests, such as signing transactions, raw data, messages, or typed data. + */ +export default class WalletRequestBuilder { + private action: WalletAction + private resourceId: string + private nonce: string + + /** + * Mandatory. Sets the action to be performed by the request. + * @param action - The specific wallet action to perform. + * @returns The builder instance specific to the action for method chaining. + * @example + * const request = buildRequest('wallet') + * .setAction('signTransaction') + * .setTransactionRequest(transactionRequest) + * @example + * const request = buildRequest('wallet') + * .setAction('signRaw') + * .setRawMessage(rawMessage) + */ setAction(action: 'signTransaction'): SignTransactionBuilder setAction(action: 'signRaw'): SignRawBuilder setAction(action: 'signMessage'): SignMessageBuilder @@ -34,11 +49,32 @@ export default class WalletRequestBuilder extends EvaluationRequestBuilder { } } + /** + * Optional. Sets the unique identifier for the transaction nonce. + * @default v4() + * @optional + * @param nonce - The nonce value as a string. + * @returns The builder instance for method chaining. + */ setNonce(nonce: string) { this.nonce = nonce return this } + protected getNonce() { + return this.nonce + } + + protected getResourceId() { + return this.resourceId + } + + /** + * Mandatory. Sets the resource identifier involved in the request, usually the wallet address. + * @param resourceId - The Ethereum wallet address as a string. + * @mandatory + * @returns The builder instance for method chaining. + */ setResourceId(resourceId: string) { this.resourceId = resourceId return this @@ -53,12 +89,16 @@ class SignTransactionBuilder extends WalletRequestBuilder { return this } + /** + * Constructs and returns the final request object based on the previously provided configurations. + * @returns {BuildResponse} The final request object if all required configurations are valid, otherwise returns an error object. + */ build(): BuildResponse { - const nonce = this.nonce || v4() + const nonce = this.getNonce() || v4() const request = { action: WalletAction.SIGN_TRANSACTION, nonce, - resourceId: this.resourceId, + resourceId: this.getResourceId(), transactionRequest: this.transactionRequest } const res = SignTransactionAction.safeParse(request) @@ -88,12 +128,16 @@ class SignRawBuilder extends WalletRequestBuilder { return this } + /** + * Constructs and returns the final request object based on the previously provided configurations. + * @returns {BuildResponse} The final request object if all required configurations are valid, otherwise returns an error object. + */ build(): BuildResponse { - const nonce = this.nonce || v4() + const nonce = this.getNonce() || v4() const request = { action: WalletAction.SIGN_RAW, nonce, - resourceId: this.resourceId, + resourceId: this.getResourceId(), rawMessage: this.rawMessage } const res = SignRawAction.safeParse(request) @@ -117,12 +161,16 @@ class SignMessageBuilder extends WalletRequestBuilder { return this } + /** + * Constructs and returns the final request object based on the previously provided configurations. + * @returns {BuildResponse} The final request object if all required configurations are valid, otherwise returns an error object. + */ build(): BuildResponse { - const nonce = this.nonce || v4() + const nonce = this.getNonce() || v4() const request = { action: WalletAction.SIGN_MESSAGE, nonce, - resourceId: this.resourceId, + resourceId: this.getResourceId(), message: this.message } const res = SignMessageAction.safeParse(request) @@ -146,12 +194,16 @@ class SignTypedDataBuilder extends WalletRequestBuilder { return this } + /** + * Constructs and returns the final request object based on the previously provided configurations. + * @returns {BuildResponse} The final request object if all required configurations are valid, otherwise returns an error object. + */ build(): BuildResponse { - const nonce = this.nonce || v4() + const nonce = this.getNonce() || v4() const request = { action: WalletAction.SIGN_TYPED_DATA, nonce, - resourceId: this.resourceId, + resourceId: this.getResourceId(), typedData: this.typedData } const res = SignTypedDataAction.safeParse(request) diff --git a/packages/engine-request/src/lib/engine-request.ts b/packages/engine-request/src/lib/engine-request.ts index 19fb43a13..ddbd11b0a 100644 --- a/packages/engine-request/src/lib/engine-request.ts +++ b/packages/engine-request/src/lib/engine-request.ts @@ -1,3 +1,79 @@ -export function engineRequest(): string { - return 'engine-request' +import { EvaluationRequest, EvaluationResponse, JwtString, Request } from '@narval/policy-engine-shared' +import { Jwk, Payload, SigningAlg, hash, signJwt } from '@narval/signature' +import axios from 'axios' + +export type SignConfig = { + principalCredential: Jwk + orgId: string + signingAlg?: SigningAlg + signer?: (payload: string) => Promise +} + +export type BuildEngineRequestInput = { + request: Request + signingConfig: SignConfig +} + +const buildPayloadFromRequest = (request: Request, jwk: Jwk, orgId: string): Payload => { + return { + requestHash: hash(request), + sub: jwk.kid, + iss: orgId, + iat: new Date().getTime() + } +} + +export const signPayload = async ({ payload, jwk }: { payload: Payload; jwk: Jwk }): Promise => { + const jwt = await signJwt(payload, jwk) + return jwt +} + +export const signPayloadSafe = async ({ + payload, + jwk +}: { + payload: Payload + jwk: Jwk +}): Promise<{ success: boolean; jwt: JwtString | null; error?: unknown }> => { + try { + const jwt = await signJwt(payload, jwk) + return { success: true, jwt } + } catch (error) { + return { success: false, jwt: null, error } + } +} + +export const signEngineRequest = async ({ request, signingConfig }: BuildEngineRequestInput): Promise => { + const { principalCredential, signingAlg, signer } = signingConfig + const payload = buildPayloadFromRequest(request, principalCredential, signingConfig.orgId) + const authentication = signer + ? await signJwt(payload, principalCredential, { alg: signingAlg }, signer) + : await signJwt(payload, principalCredential, { alg: signingAlg }) + return authentication +} + +export const signEngineRequestSafe = async ({ + request, + signingConfig +}: BuildEngineRequestInput): Promise<{ success: boolean; jwt: JwtString | null; error?: unknown }> => { + try { + const authentication = await signEngineRequest({ request, signingConfig }) + return { success: true, jwt: authentication } + } catch (error) { + return { success: false, jwt: null, error } + } +} + +export const sendEvaluationRequest = async ( + evaluationRequest: EvaluationRequest, + engineUrl: string +): Promise => { + const response = await axios.post(engineUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(evaluationRequest) + }) + return response.data }