commit 344cdaef8fa1f75c859f40a0a847d8a39cdf3d1a
parent cf65329a025237308e6a28cd321bac7f05769a22
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date: Thu, 13 Nov 2025 13:50:28 +0100
wallet-core: support paid mailbox registrations (API)
Diffstat:
6 files changed, 95 insertions(+), 25 deletions(-)
diff --git a/packages/taler-util/src/http-client/mailbox.ts b/packages/taler-util/src/http-client/mailbox.ts
@@ -31,7 +31,7 @@ import {
opKnownAlternativeHttpFailure,
opKnownHttpFailure,
opUnknownHttpFailure,
- MailboxMetadata as MailboxMetadata,
+ MailboxMetadata,
codecForTalerMailboxMetadata,
opSuccessFromHttp,
MailboxRegisterRequest,
@@ -41,6 +41,8 @@ import {
MailboxConfiguration,
eddsaGetPublic,
EddsaSignatureString,
+ MailboxRegisterResult,
+ OperationAlternative,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -266,10 +268,10 @@ export class TalerMailboxInstanceHttpClient {
/**
* https://docs.taler.net/core/api-mailbox.html#post--register
*/
- async updateRegistration(req: MailboxRegisterRequest) : Promise<
- | OperationOk<void>
+ async registerMailbox(req: MailboxRegisterRequest) : Promise<
+ | OperationOk<MailboxRegisterResult>
| OperationFail<HttpStatusCode.Forbidden>
- | OperationFail<HttpStatusCode.PaymentRequired>
+ | OperationAlternative<HttpStatusCode.PaymentRequired, MailboxRegisterResult>
>{
const url = new URL(`register`, this.baseUrl);
@@ -281,13 +283,13 @@ export class TalerMailboxInstanceHttpClient {
switch (resp.status) {
case HttpStatusCode.NoContent: {
- return opEmptySuccess();
+ return opFixedSuccess({status: "ok"} as MailboxRegisterResult);
}
case HttpStatusCode.Forbidden: {
return opKnownHttpFailure(resp.status, resp);
}
case HttpStatusCode.PaymentRequired: {
- return opKnownHttpFailure(resp.status, resp);
+ return { type: "fail", case: resp.status, body: { status: "payment-required", talerUri: resp.headers.get("Taler")} as MailboxRegisterResult};
}
default:
return opUnknownHttpFailure(resp);
diff --git a/packages/taler-util/src/types-taler-mailbox.ts b/packages/taler-util/src/types-taler-mailbox.ts
@@ -20,7 +20,9 @@ import { codecForAmountString } from "./amounts.js";
import {
Codec,
buildCodecForObject,
+ buildCodecForUnion,
codecForNumber,
+ codecOptional,
} from "./codec.js";
import {
codecForString,
@@ -85,7 +87,44 @@ export interface TalerMailboxConfigResponse {
monthly_fee: AmountString;
}
-export const codecForTalerMailboxMessageKeysUpdateRequest =
+export type MailboxRegisterResult =
+ | MailboxRegisterOk
+ | MailboxRegisterPaymentRequired;
+
+export interface MailboxRegisterOk {
+ status: "ok";
+}
+
+export interface MailboxRegisterPaymentRequired {
+ status: "payment-required";
+ talerUri?: string;
+}
+
+export const codecForMailboxRegisterOk = (): Codec<MailboxRegisterOk> =>
+ buildCodecForObject<MailboxRegisterOk>()
+ .property("status", codecForConstString("ok"))
+ .build("MailboxRegisterOk");
+
+export const codecForMailboxRegisterPaymentRequired =
+ (): Codec<MailboxRegisterPaymentRequired> =>
+ buildCodecForObject<MailboxRegisterPaymentRequired>()
+ .property("status", codecForConstString("payment-required"))
+ .property("talerUri", codecOptional(codecForString()))
+ .build("MailboxRegisterPaymentRequired");
+
+export const codecForMailboxRegisterResult =
+ (): Codec<MailboxRegisterResult> =>
+ buildCodecForUnion<MailboxRegisterResult>()
+ .discriminateOn("status")
+ .alternative("ok", codecForMailboxRegisterOk())
+ .alternative(
+ "payment-required",
+ codecForMailboxRegisterPaymentRequired(),
+ )
+ .build("MailboxRegisterResult");
+
+
+export const codecForTalerMailboxRegisterRequest =
(): Codec<MailboxRegisterRequest> =>
buildCodecForObject<MailboxRegisterRequest>()
.property("mailbox_metadata", codecForTalerMailboxMetadata())
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -52,7 +52,7 @@ import { PaytoString, codecForPaytoString } from "./payto.js";
import { QrCodeSpec } from "./qr.js";
import { AgeCommitmentProof, HpkeSecretKey } from "./taler-crypto.js";
import { TalerErrorCode } from "./taler-error-codes.js";
-import { TemplateParams } from "./taleruri.js";
+import { TalerUri, TalerUriString, TemplateParams } from "./taleruri.js";
import {
AbsoluteTime,
DurationUnitSpec,
@@ -1376,6 +1376,7 @@ export interface MailboxConfiguration {
privateKey: EddsaPrivateKeyString;
privateEncryptionKey: string;
expiration: Timestamp;
+ payUri?: TalerUri;
}
export const codecForMailboxConfiguration = (): Codec<MailboxConfiguration> =>
diff --git a/packages/taler-wallet-core/src/mailbox.ts b/packages/taler-wallet-core/src/mailbox.ts
@@ -45,10 +45,23 @@ import {
hpkeSecretKeyGetPublic,
MailboxMetadata,
MailboxRegisterRequest,
+ MailboxRegisterResult,
eddsaSign,
Duration,
AbsoluteTime,
TalerSignaturePurpose,
+ HttpStatusCode,
+ Codec,
+ buildCodecForUnion,
+ buildCodecForObject,
+ codecForConstString,
+ codecOptional,
+ codecForString,
+ opKnownFailure,
+ TalerUris,
+ parseTalerUri,
+ Paytos,
+ succeedOrValue,
} from "@gnu-taler/taler-util";
import {
WalletExecutionContext,
@@ -125,16 +138,14 @@ export async function listMailboxMessages(
return { messages: messages };
}
+
/**
- * Update mailbox keys
- * Does not actually update key material in
- * mailbox configuration, only updates service
- * with new key info
+ * Register or update mailbox
*/
-export async function updateMailboxKeys(
+export async function registerMailbox(
wex: WalletExecutionContext,
mailboxConf: MailboxConfiguration,
-): Promise<void> {
+): Promise<MailboxRegisterResult> {
const privateSigningKey = decodeCrock(mailboxConf.privateKey);
const signingKey = eddsaGetPublic(privateSigningKey);
const encryptionKey = hpkeSecretKeyGetPublic(decodeCrock(mailboxConf.privateEncryptionKey));
@@ -169,8 +180,15 @@ export async function updateMailboxKeys(
};
const mailboxClient = new TalerMailboxInstanceHttpClient(mailboxConf.mailboxBaseUrl, wex.http);
- succeedOrThrow(await mailboxClient.updateRegistration(req));
- return;
+ const resp = await mailboxClient.registerMailbox(req);
+ switch (resp.case) {
+ case "ok":
+ return resp.body;
+ case HttpStatusCode.Forbidden:
+ throw Error("Access to Mailbox API unauthorized");
+ case HttpStatusCode.PaymentRequired:
+ return resp.body;
+ }
}
/**
@@ -194,9 +212,10 @@ export async function getMailbox(
/**
- * Initialize mailbox.
+ * Create new mailbox configuration locally and
+ * try to register it.
*/
-export async function initMailbox(
+export async function createNewMailbox(
wex: WalletExecutionContext,
mailboxBaseUrl: string,
): Promise<MailboxConfiguration> {
@@ -205,13 +224,20 @@ export async function initMailbox(
const privKey = encodeCrock(keys.eddsaPriv);
const nowInAYear = AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({
years: 1}));
- const mailboxConf: MailboxConfiguration = {
+ const mailboxConf: MailboxConfiguration = {
mailboxBaseUrl: mailboxBaseUrl,
privateKey: privKey,
privateEncryptionKey: hpkeKey,
expiration: AbsoluteTime.toProtocolTimestamp(nowInAYear)
};
- await updateMailboxKeys(wex, mailboxConf);
+
+ const resp = await registerMailbox(wex, mailboxConf);
+ if (resp.status == "payment-required") {
+ if (!resp.talerUri) {
+ throw Error("payment required to register mailbox but no Taler URI given");
+ }
+ mailboxConf.payUri = succeedOrValue(TalerUris.fromString(resp.talerUri), undefined);
+ }
await wex.db.runReadWriteTx(
{
storeNames: ["mailboxConfigurations"],
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -329,7 +329,7 @@ import {
listMailboxMessages,
addMailboxMessage,
deleteMailboxMessage,
- initMailbox,
+ createNewMailbox,
sendTalerUriMessage,
getMailbox,
refreshMailbox
@@ -1963,7 +1963,7 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
},
[WalletApiOperation.InitializeMailbox]: {
codec: codecForString(),
- handler: initMailbox,
+ handler: createNewMailbox,
},
[WalletApiOperation.GetMailbox]: {
codec: codecForString(),
diff --git a/packages/taler-wallet-webextension/src/wallet/Mailbox.tsx b/packages/taler-wallet-webextension/src/wallet/Mailbox.tsx
@@ -26,10 +26,8 @@ import {
sha512,
MailboxMessageRecord,
TranslatedString,
- succeedOrThrow,
succeedOrValue,
TalerProtocolTimestamp,
- AbsoluteTime,
TalerUris,
TalerUriAction,
TalerUri
@@ -120,10 +118,14 @@ export function MailboxPage({
/>
);
}
-
const onInitializeMailbox = async () => {
try {
await api.wallet.call(WalletApiOperation.InitializeMailbox, mailboxBaseUrl);
+ // FIXME the returned mailbox may have a payto URI set
+ // In that case we need to display the option to pay AND
+ // Properly check on reload if we can clear the property (and store the
+ // updated mailbox struct)
+ // https://bugs.gnunet.org/view.php?id=10605
state?.retry();
} catch (err) {
setError(i18n.str`Unexpected error when trying to initialize mailbox: ${err}`);