anastasis-core: make truth/policy payments compatible with C reducer

This commit is contained in:
Florian Dold 2021-11-05 07:29:26 +01:00
parent 34d2e4703d
commit e42c282e67
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
3 changed files with 85 additions and 15 deletions

View File

@ -11,8 +11,9 @@ import {
stringToBytes, stringToBytes,
secretbox_open, secretbox_open,
hash, hash,
Logger,
j2s,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { gzipSync } from "fflate";
import { argon2id } from "hash-wasm"; import { argon2id } from "hash-wasm";
export type Flavor<T, FlavorT extends string> = T & { export type Flavor<T, FlavorT extends string> = T & {

View File

@ -16,6 +16,7 @@ import {
HttpStatusCode, HttpStatusCode,
j2s, j2s,
Logger, Logger,
parsePayUri,
stringToBytes, stringToBytes,
TalerErrorCode, TalerErrorCode,
TalerSignaturePurpose, TalerSignaturePurpose,
@ -328,7 +329,6 @@ async function getTruthValue(
* Compress the recovery document and add a size header. * Compress the recovery document and add a size header.
*/ */
async function compressRecoveryDoc(rd: any): Promise<Uint8Array> { async function compressRecoveryDoc(rd: any): Promise<Uint8Array> {
logger.info(`recovery document: ${j2s(rd)}`);
const docBytes = stringToBytes(JSON.stringify(rd)); const docBytes = stringToBytes(JSON.stringify(rd));
const sizeHeaderBuf = new ArrayBuffer(4); const sizeHeaderBuf = new ArrayBuffer(4);
const dvbuf = new DataView(sizeHeaderBuf); const dvbuf = new DataView(sizeHeaderBuf);
@ -457,6 +457,8 @@ async function uploadSecret(
const rd = recoveryData.recovery_document; const rd = recoveryData.recovery_document;
const truthPayUris: string[] = []; const truthPayUris: string[] = [];
const truthPaySecrets: Record<string, string> = {};
const userIdCache: Record<string, UserIdentifier> = {}; const userIdCache: Record<string, UserIdentifier> = {};
const getUserIdCaching = async (providerUrl: string) => { const getUserIdCaching = async (providerUrl: string) => {
let userId = userIdCache[providerUrl]; let userId = userIdCache[providerUrl];
@ -483,9 +485,8 @@ async function uploadSecret(
tm.truth_key, tm.truth_key,
truthValue, truthValue,
); );
logger.info(`uploading to ${meth.provider}`); logger.info(`uploading truth to ${meth.provider}`);
const userId = await getUserIdCaching(meth.provider); const userId = await getUserIdCaching(meth.provider);
// FIXME: check that the question salt is okay here, looks weird.
const encryptedKeyShare = await encryptKeyshare( const encryptedKeyShare = await encryptKeyshare(
tm.key_share, tm.key_share,
userId, userId,
@ -500,10 +501,21 @@ async function uploadSecret(
type: authMethod.type, type: authMethod.type,
truth_mime: authMethod.mime_type, truth_mime: authMethod.mime_type,
}; };
const resp = await fetch(new URL(`truth/${tm.uuid}`, meth.provider).href, { const reqUrl = new URL(`truth/${tm.uuid}`, meth.provider);
const paySecret = (state.truth_upload_payment_secrets ?? {})[meth.provider];
if (paySecret) {
// FIXME: Get this from the params
reqUrl.searchParams.set("timeout_ms", "500");
}
const resp = await fetch(reqUrl.href, {
method: "POST", method: "POST",
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
...(paySecret
? {
"Anastasis-Payment-Identifier": paySecret,
}
: {}),
}, },
body: JSON.stringify(tur), body: JSON.stringify(tur),
}); });
@ -520,6 +532,14 @@ async function uploadSecret(
}; };
} }
truthPayUris.push(talerPayUri); truthPayUris.push(talerPayUri);
const parsedUri = parsePayUri(talerPayUri);
if (!parsedUri) {
return {
code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE,
hint: `payment requested, but no taler://pay URI given`,
};
}
truthPaySecrets[meth.provider] = parsedUri.orderId;
continue; continue;
} }
return { return {
@ -532,6 +552,7 @@ async function uploadSecret(
return { return {
...state, ...state,
backup_state: BackupStates.TruthsPaying, backup_state: BackupStates.TruthsPaying,
truth_upload_payment_secrets: truthPaySecrets,
payments: truthPayUris, payments: truthPayUris,
}; };
} }
@ -539,6 +560,8 @@ async function uploadSecret(
const successDetails: SuccessDetails = {}; const successDetails: SuccessDetails = {};
const policyPayUris: string[] = []; const policyPayUris: string[] = [];
const policyPayUriMap: Record<string, string> = {};
//const policyPaySecrets: Record<string, string> = {};
for (const prov of state.policy_providers!) { for (const prov of state.policy_providers!) {
const userId = await getUserIdCaching(prov.provider_url); const userId = await getUserIdCaching(prov.provider_url);
@ -553,17 +576,33 @@ async function uploadSecret(
.put(bodyHash) .put(bodyHash)
.build(); .build();
const sig = eddsaSign(sigPS, decodeCrock(acctKeypair.priv)); const sig = eddsaSign(sigPS, decodeCrock(acctKeypair.priv));
const resp = await fetch( const talerPayUri = state.policy_payment_requests?.find(
new URL(`policy/${acctKeypair.pub}`, prov.provider_url).href, (x) => x.provider === prov.provider_url,
{ )?.payto;
method: "POST", let paySecret: string | undefined;
headers: { if (talerPayUri) {
"Anastasis-Policy-Signature": encodeCrock(sig), paySecret = parsePayUri(talerPayUri)!.orderId;
"If-None-Match": encodeCrock(bodyHash), }
}, const reqUrl = new URL(`policy/${acctKeypair.pub}`, prov.provider_url);
body: decodeCrock(encRecoveryDoc), if (paySecret) {
// FIXME: Get this from the params
reqUrl.searchParams.set("timeout_ms", "500");
}
logger.info(`uploading policy to ${prov.provider_url}`);
const resp = await fetch(reqUrl.href, {
method: "POST",
headers: {
"Anastasis-Policy-Signature": encodeCrock(sig),
"If-None-Match": encodeCrock(bodyHash),
...(paySecret
? {
"Anastasis-Payment-Identifier": paySecret,
}
: {}),
}, },
); body: decodeCrock(encRecoveryDoc),
});
logger.info(`got response for policy upload (http status ${resp.status})`);
if (resp.status === HttpStatusCode.NoContent) { if (resp.status === HttpStatusCode.NoContent) {
let policyVersion = 0; let policyVersion = 0;
let policyExpiration: Timestamp = { t_ms: 0 }; let policyExpiration: Timestamp = { t_ms: 0 };
@ -592,6 +631,14 @@ async function uploadSecret(
}; };
} }
policyPayUris.push(talerPayUri); policyPayUris.push(talerPayUri);
const parsedUri = parsePayUri(talerPayUri);
if (!parsedUri) {
return {
code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE,
hint: `payment requested, but no taler://pay URI given`,
};
}
policyPayUriMap[prov.provider_url] = talerPayUri;
continue; continue;
} }
return { return {
@ -605,9 +652,17 @@ async function uploadSecret(
...state, ...state,
backup_state: BackupStates.PoliciesPaying, backup_state: BackupStates.PoliciesPaying,
payments: policyPayUris, payments: policyPayUris,
policy_payment_requests: Object.keys(policyPayUriMap).map((x) => {
return {
payto: policyPayUriMap[x],
provider: x,
};
}),
}; };
} }
logger.info("backup finished");
return { return {
...state, ...state,
core_secret: undefined, core_secret: undefined,
@ -766,6 +821,7 @@ async function requestTruth(
const url = new URL(`/truth/${truth.uuid}`, truth.url); const url = new URL(`/truth/${truth.uuid}`, truth.url);
if (solveRequest) { if (solveRequest) {
logger.info(`handling solve request ${j2s(solveRequest)}`);
let respHash: string; let respHash: string;
switch (truth.escrow_type) { switch (truth.escrow_type) {
case ChallengeType.Question: { case ChallengeType.Question: {

View File

@ -87,10 +87,19 @@ export interface ReducerStateBackup {
* Currently requested payments. * Currently requested payments.
* *
* List of taler://pay URIs. * List of taler://pay URIs.
*
* FIXME: There should be more information in this,
* including the provider and amount.
*/ */
payments?: string[]; payments?: string[];
/**
* FIXME: Why is this not a map from provider to payto?
*/
policy_payment_requests?: { policy_payment_requests?: {
/**
* FIXME: This is not a payto URI, right?!
*/
payto: string; payto: string;
provider: string; provider: string;
}[]; }[];
@ -100,6 +109,10 @@ export interface ReducerStateBackup {
expiration?: Timestamp; expiration?: Timestamp;
upload_fees?: { fee: AmountString }[]; upload_fees?: { fee: AmountString }[];
// FIXME: The payment secrets and pay URIs should
// probably be consolidated into a single field.
truth_upload_payment_secrets?: Record<string, string>;
} }
export interface AuthMethod { export interface AuthMethod {