From 3f7efa2c7907e656bfcd297bd90b76ff8895413b Mon Sep 17 00:00:00 2001 From: Nathan Lie Date: Wed, 24 Sep 2025 15:08:04 -0700 Subject: [PATCH 1/5] feat(localenv): expose subject during consent in mock-ase --- .../Grant Request Outgoing Payment.bru | 8 ++++++ .../app/lib/apiClient.ts | 5 +++- .../app/lib/types.ts | 5 ++++ .../app/routes/mock-idp._index.tsx | 27 ++++++++++++++----- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/bruno/collections/Rafiki/Examples/Open Payments/Grant Request Outgoing Payment.bru b/bruno/collections/Rafiki/Examples/Open Payments/Grant Request Outgoing Payment.bru index 3c4b667956..ba48792c47 100644 --- a/bruno/collections/Rafiki/Examples/Open Payments/Grant Request Outgoing Payment.bru +++ b/bruno/collections/Rafiki/Examples/Open Payments/Grant Request Outgoing Payment.bru @@ -26,6 +26,14 @@ body:json { } ] }, + "subject": { + "sub_ids": [ + { + "id": "{{senderWalletAddress}}", + "format": "uri" + } + ] + }, "client": "{{clientWalletAddress}}", "interact": { "start": [ diff --git a/localenv/mock-account-servicing-entity/app/lib/apiClient.ts b/localenv/mock-account-servicing-entity/app/lib/apiClient.ts index 57a5f04db1..26226261a9 100644 --- a/localenv/mock-account-servicing-entity/app/lib/apiClient.ts +++ b/localenv/mock-account-servicing-entity/app/lib/apiClient.ts @@ -50,7 +50,10 @@ export class ApiClient { if (response.status === 200) { return { isFailure: false, - payload: response.data.access, + payload: { + access: response.data.access, + subject: response.data.subject + }, contextUpdates: { grant: response.data } diff --git a/localenv/mock-account-servicing-entity/app/lib/types.ts b/localenv/mock-account-servicing-entity/app/lib/types.ts index 5a8350be2a..cb54affa08 100644 --- a/localenv/mock-account-servicing-entity/app/lib/types.ts +++ b/localenv/mock-account-servicing-entity/app/lib/types.ts @@ -28,6 +28,11 @@ export interface Access { limits?: AccessLimit } +export interface SubjectId { + id: string + format: string +} + export type InstanceConfig = { name: string logo: string diff --git a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx index 4cc6eca429..c4dd0291c6 100644 --- a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx +++ b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx @@ -8,7 +8,7 @@ import type { Dispatch, SetStateAction } from 'react' import { useEffect, useState } from 'react' import { Button } from '~/components' import { ApiClient } from '~/lib/apiClient' -import type { Access, InstanceConfig } from '~/lib/types' +import type { Access, InstanceConfig, SubjectId } from '~/lib/types' import { CONFIG } from '~/lib/parse_config.server' interface ConsentScreenContext { @@ -20,6 +20,7 @@ interface ConsentScreenContext { returnUrl: string accesses: Array | null outgoingPaymentAccess: Access | null + subjectId: SubjectId | null price: GrantAmount | null costToUser: GrantAmount | null errors: Array @@ -51,7 +52,8 @@ function ConsentScreenBody({ costToUser, interactId, nonce, - returnUrl + returnUrl, + subjectId }: { _thirdPartyUri: string thirdPartyName: string @@ -60,6 +62,7 @@ function ConsentScreenBody({ interactId: string nonce: string returnUrl: string + subjectId: SubjectId | null }) { const chooseConsent = (accept: boolean) => { const href = new URL(returnUrl) @@ -80,11 +83,18 @@ function ConsentScreenBody({

)} +
+
+ {subjectId && ( +

You are being asked to confirm ownership of {subjectId.id}.

+ )} +
+
{costToUser && (

- This will cost you {costToUser.currencyDisplayCode}{' '} + You will be charged {costToUser.currencyDisplayCode}{' '} {costToUser.amount.toFixed(2)}

)} @@ -238,6 +248,7 @@ export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) { //TODO returnUrl: 'http://localhost:3030/mock-idp/consent?interactid=demo-interact-id&nonce=demo-interact-nonce', accesses: null, outgoingPaymentAccess: null, + subjectId: null, price: null, costToUser: null, errors: new Array() @@ -292,14 +303,14 @@ export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) { ...ctx, errors: response.errors.map((e) => new Error(e)) }) - } else if (!response.payload) { + } else if (!response.payload.access && !response.payload.subject) { setCtx({ ...ctx, - errors: [new Error('no accesses in grant')] + errors: [new Error('no accesses or subjects in grant')] }) } else { const outgoingPaymentAccess = - response.payload.find( + response.payload.access.find( // eslint-disable-next-line @typescript-eslint/no-explicit-any (p: Record) => p.type === 'outgoing-payment' ) || null @@ -344,7 +355,8 @@ export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) { ) setCtx({ ...ctx, - accesses: response.payload, + accesses: response.payload.access, + subjectId: response.payload.subject.sub_ids[0], outgoingPaymentAccess: outgoingPaymentAccess, thirdPartyName: ctx.thirdPartyName, thirdPartyUri: ctx.thirdPartyUri, @@ -438,6 +450,7 @@ export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) { interactId={ctx.interactId} nonce={ctx.nonce} returnUrl={ctx.returnUrl} + subjectId={ctx.subjectId} /> )} From 8747fc7de65ec291a64cb0f33282c165d0849cf2 Mon Sep 17 00:00:00 2001 From: Nathan Lie Date: Mon, 29 Sep 2025 13:58:29 -0700 Subject: [PATCH 2/5] feat: include client name in subject grant line --- .../app/routes/mock-idp._index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx index c4dd0291c6..d9f1b35944 100644 --- a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx +++ b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx @@ -86,7 +86,10 @@ function ConsentScreenBody({
{subjectId && ( -

You are being asked to confirm ownership of {subjectId.id}.

+

+ {thirdPartyName} is asking you to confirm ownership of{' '} + {subjectId.id}. +

)}
From 54c331d56e9d0d245a2799c76c270492de683ed3 Mon Sep 17 00:00:00 2001 From: Cozmin Ungureanu Date: Wed, 1 Oct 2025 17:03:16 +0300 Subject: [PATCH 3/5] fix(mase): grantId not being retrieved --- .../app/lib/apiClient.ts | 4 ++- .../app/routes/mock-idp._index.tsx | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/localenv/mock-account-servicing-entity/app/lib/apiClient.ts b/localenv/mock-account-servicing-entity/app/lib/apiClient.ts index 26226261a9..028f19bf19 100644 --- a/localenv/mock-account-servicing-entity/app/lib/apiClient.ts +++ b/localenv/mock-account-servicing-entity/app/lib/apiClient.ts @@ -52,7 +52,9 @@ export class ApiClient { isFailure: false, payload: { access: response.data.access, - subject: response.data.subject + subject: response.data.subject, + grantId: response.data.grantId, + state: response.data.state }, contextUpdates: { grant: response.data diff --git a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx index d9f1b35944..561476c340 100644 --- a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx +++ b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx @@ -48,6 +48,7 @@ export function loader() { function ConsentScreenBody({ _thirdPartyUri, thirdPartyName, + accesses, price, costToUser, interactId, @@ -57,6 +58,7 @@ function ConsentScreenBody({ }: { _thirdPartyUri: string thirdPartyName: string + accesses: Access[] | null price: GrantAmount | null costToUser: GrantAmount | null interactId: string @@ -75,14 +77,6 @@ function ConsentScreenBody({ return ( <>
-
- {price && ( -

- {thirdPartyName} wants to send {price.currencyDisplayCode}{' '} - {price.amount.toFixed(2)} to its account. -

- )} -
{subjectId && ( @@ -93,6 +87,16 @@ function ConsentScreenBody({ )}
+
+
+ {price && ( +

+ {thirdPartyName} wants to send {price.currencyDisplayCode}{' '} + {price.amount.toFixed(2)} to its account. +

+ )} +
+
{costToUser && ( @@ -105,7 +109,7 @@ function ConsentScreenBody({
- {!price && !costToUser && ( + {accesses?.length && !price && !costToUser && (

{thirdPartyName} is requesting grant for an unlimited amount

@@ -320,7 +324,7 @@ export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) { const returnUrlObject = new URL(ctx.returnUrl) returnUrlObject.searchParams.append( 'grantId', - outgoingPaymentAccess.grantId + response.payload.grantId ) returnUrlObject.searchParams.append( 'thirdPartyName', @@ -448,6 +452,7 @@ export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) { Date: Thu, 2 Oct 2025 12:09:58 +0300 Subject: [PATCH 4/5] fix(mase): consent and confirmation texts --- .../app/routes/mock-idp._index.tsx | 20 +++++---- .../app/routes/mock-idp.consent.tsx | 42 ++++++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx index 561476c340..9849ba2922 100644 --- a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx +++ b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx @@ -109,7 +109,7 @@ function ConsentScreenBody({
- {accesses?.length && !price && !costToUser && ( + {(accesses?.length ?? 0 > 0) && !price && !costToUser && (

{thirdPartyName} is requesting grant for an unlimited amount

@@ -352,13 +352,19 @@ export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) { outgoingPaymentAccess?.limits?.receiveAmount?.assetScale ?? null ) + if (outgoingPaymentAccess) { + returnUrlObject.searchParams.append( + 'amountType', + outgoingPaymentAccess.limits?.receiveAmount + ? AmountType.RECEIVE + : outgoingPaymentAccess.limits?.debitAmount + ? AmountType.DEBIT + : AmountType.UNLIMITED + ) + } returnUrlObject.searchParams.append( - 'amountType', - outgoingPaymentAccess?.limits?.receiveAmount - ? AmountType.RECEIVE - : outgoingPaymentAccess?.limits?.debitAmount - ? AmountType.DEBIT - : AmountType.UNLIMITED + 'subjectId', + response.payload.subject.sub_ids[0]?.id ?? null ) setCtx({ ...ctx, diff --git a/localenv/mock-account-servicing-entity/app/routes/mock-idp.consent.tsx b/localenv/mock-account-servicing-entity/app/routes/mock-idp.consent.tsx index 63e6f5e979..86670e2736 100644 --- a/localenv/mock-account-servicing-entity/app/routes/mock-idp.consent.tsx +++ b/localenv/mock-account-servicing-entity/app/routes/mock-idp.consent.tsx @@ -27,7 +27,8 @@ function AuthorizedView({ interactId, nonce, authServerDomain, - amountType + amountType, + subjectId }: { thirdPartyName: string currencyDisplayCode: string @@ -36,21 +37,30 @@ function AuthorizedView({ nonce: string authServerDomain: string amountType: string + subjectId: string }) { - let message = `You gave ${thirdPartyName} permission to ` - switch (amountType) { - case AmountType.RECEIVE: - message += `receive ${currencyDisplayCode} ${amount.toFixed(2)} in your account.` - break - case AmountType.DEBIT: - message += `send ${currencyDisplayCode} ${amount.toFixed(2)} out of your account.` - break - case AmountType.UNLIMITED: - message += 'have unlimited access to your account.' - break - default: - message = 'Type of authorization is missing' + let message = '' + if (amountType) { + message += `You gave ${thirdPartyName} permission to ` + switch (amountType) { + case AmountType.RECEIVE: + message += `receive ${currencyDisplayCode} ${amount.toFixed(2)} in your account.` + break + case AmountType.DEBIT: + message += `send ${currencyDisplayCode} ${amount.toFixed(2)} out of your account.` + break + case AmountType.UNLIMITED: + message += 'have unlimited access to your account.' + break + default: + message = 'Type of authorization is missing' + } + } + if (subjectId) { + message += message.length > 0 ? ' Also, you ' : 'You ' + message += `confirmed ${thirdPartyName} your ownership of ${subjectId}.` } + return (
@@ -132,7 +142,8 @@ export default function Consent() { amount: Number(queryParams.get('amountValue')) / Math.pow(10, Number(queryParams.get('amountScale'))), - amountType: queryParams.get('amountType') + amountType: queryParams.get('amountType'), + subjectId: queryParams.get('subjectId') }) useEffect(() => { @@ -187,6 +198,7 @@ export default function Consent() { nonce={ctx.nonce} authServerDomain={authServerDomain} amountType={ctx.amountType || ''} + subjectId={ctx.subjectId || ''} /> ) : ( Date: Thu, 9 Oct 2025 12:41:20 -0700 Subject: [PATCH 5/5] fix: handle subject-only grants properly --- .../Grant Request Outgoing Payment.bru | 8 --- .../Continuation Request.bru | 33 +++++++++++ .../Get sender wallet address.bru | 58 +++++++++++++++++++ .../Grant Request for Subject Information.bru | 52 +++++++++++++++++ .../folder.bru | 4 ++ .../app/routes/mock-idp._index.tsx | 7 ++- 6 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Continuation Request.bru create mode 100644 bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Get sender wallet address.bru create mode 100644 bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Grant Request for Subject Information.bru create mode 100644 bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/folder.bru diff --git a/bruno/collections/Rafiki/Examples/Open Payments/Grant Request Outgoing Payment.bru b/bruno/collections/Rafiki/Examples/Open Payments/Grant Request Outgoing Payment.bru index ba48792c47..3c4b667956 100644 --- a/bruno/collections/Rafiki/Examples/Open Payments/Grant Request Outgoing Payment.bru +++ b/bruno/collections/Rafiki/Examples/Open Payments/Grant Request Outgoing Payment.bru @@ -26,14 +26,6 @@ body:json { } ] }, - "subject": { - "sub_ids": [ - { - "id": "{{senderWalletAddress}}", - "format": "uri" - } - ] - }, "client": "{{clientWalletAddress}}", "interact": { "start": [ diff --git a/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Continuation Request.bru b/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Continuation Request.bru new file mode 100644 index 0000000000..9859237b18 --- /dev/null +++ b/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Continuation Request.bru @@ -0,0 +1,33 @@ +meta { + name: Continuation Request + type: http + seq: 8 +} + +post { + url: {{senderOpenPaymentsContinuationUri}} + body: json + auth: none +} + +headers { + Authorization: GNAP {{continueToken}} +} + +script:pre-request { + const scripts = require('./scripts'); + + await scripts.addSignatureHeaders(); +} + +script:post-response { + const scripts = require('./scripts'); + + scripts.storeTokenDetails(); +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Get sender wallet address.bru b/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Get sender wallet address.bru new file mode 100644 index 0000000000..118456a8f7 --- /dev/null +++ b/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Get sender wallet address.bru @@ -0,0 +1,58 @@ +meta { + name: Get sender wallet address + type: http + seq: 1 +} + +get { + url: {{senderWalletAddress}} + body: none + auth: none +} + +headers { + Accept: application/json +} + +script:pre-request { + const scripts = require('./scripts'); + + scripts.addHostHeader("senderOpenPaymentsHost"); +} + +script:post-response { + const url = require('url') + + if (res.getStatus() !== 200) { + return + } + + const body = res.getBody() + bru.setEnvVar("senderAssetCode", body?.assetCode) + bru.setEnvVar("senderAssetScale", body?.assetScale) + + const authUrl = url.parse(body?.authServer) + if ( + authUrl.hostname.includes('cloud-nine-wallet') || + authUrl.hostname.includes('happy-life-bank') + ){ + const port = authUrl.hostname.includes('cloud-nine-wallet')? authUrl.port: Number(authUrl.port) + 1000 + bru.setEnvVar("senderOpenPaymentsAuthHost", authUrl.protocol + '//localhost:' + port + authUrl.path); + } else { + bru.setEnvVar("senderOpenPaymentsAuthHost", body?.authServer); + } + + const resourceUrl = url.parse(body?.resourceServer) + if (resourceUrl.hostname.includes('cloud-nine-wallet') || resourceUrl.hostname.includes('happy-life-bank')) { + const port = resourceUrl.hostname.includes('happy-life-bank') ? bru.getEnvVar('happyLifeOpenPaymentsPort') : bru.getEnvVar('cloudNineOpenPaymentsPort') + bru.setEnvVar("senderOpenPaymentsHost", 'http://localhost:' + port + resourceUrl.path); + } else { + bru.setEnvVar("senderOpenPaymentsHost", body?.resourceServer); + } +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Grant Request for Subject Information.bru b/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Grant Request for Subject Information.bru new file mode 100644 index 0000000000..a7bc2f19c2 --- /dev/null +++ b/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/Grant Request for Subject Information.bru @@ -0,0 +1,52 @@ +meta { + name: Grant Request for Subject Information + type: http + seq: 7 +} + +post { + url: {{senderOpenPaymentsAuthHost}} + body: json + auth: none +} + +body:json { + { + "subject": { + "sub_ids": [ + { + "id": "{{senderWalletAddress}}", + "format": "uri" + } + ] + }, + "client": "{{clientWalletAddress}}", + "interact": { + "start": [ + "redirect" + ] + } + } + +} + +script:pre-request { + const scripts = require('./scripts'); + + await scripts.addSignatureHeaders(); +} + +script:post-response { + const scripts = require('./scripts'); + + scripts.storeTokenDetails(); + + const body = res.getBody() + bru.setEnvVar("senderOpenPaymentsContinuationUri", body?.continue.uri) +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/folder.bru b/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/folder.bru new file mode 100644 index 0000000000..6520ef80ca --- /dev/null +++ b/bruno/collections/Rafiki/Examples/Vailidating Wallet Address Ownership with Open Payments/folder.bru @@ -0,0 +1,4 @@ +meta { + name: Vailidating Wallet Address Ownership with Open Payments + seq: 6 +} diff --git a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx index 9849ba2922..3a4fe62ace 100644 --- a/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx +++ b/localenv/mock-account-servicing-entity/app/routes/mock-idp._index.tsx @@ -109,11 +109,14 @@ function ConsentScreenBody({
- {(accesses?.length ?? 0 > 0) && !price && !costToUser && ( + {accesses?.length && + accesses.length > 0 && + !price && + !costToUser ? (

{thirdPartyName} is requesting grant for an unlimited amount

- )} + ) : undefined}