anastasis: use new truth API
This commit is contained in:
parent
a30a547ac5
commit
f33d9dad47
@ -867,6 +867,51 @@ async function pollChallenges(
|
||||
return state;
|
||||
}
|
||||
|
||||
async function getResponseHash(
|
||||
truth: EscrowMethod,
|
||||
solveRequest: ActionArgsSolveChallengeRequest,
|
||||
): Promise<string> {
|
||||
let respHash: string;
|
||||
switch (truth.escrow_type) {
|
||||
case ChallengeType.Question: {
|
||||
if ("answer" in solveRequest) {
|
||||
respHash = await secureAnswerHash(
|
||||
solveRequest.answer,
|
||||
truth.uuid,
|
||||
truth.truth_salt,
|
||||
);
|
||||
} else {
|
||||
throw Error("unsupported answer request");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ChallengeType.Email:
|
||||
case ChallengeType.Sms:
|
||||
case ChallengeType.Post:
|
||||
case ChallengeType.Iban:
|
||||
case ChallengeType.Totp: {
|
||||
if ("answer" in solveRequest) {
|
||||
const s = solveRequest.answer.trim().replace(/^A-/, "");
|
||||
let pin: number;
|
||||
try {
|
||||
pin = Number.parseInt(s);
|
||||
} catch (e) {
|
||||
throw Error("invalid pin format");
|
||||
}
|
||||
respHash = await pinAnswerHash(pin);
|
||||
} else if ("pin" in solveRequest) {
|
||||
respHash = await pinAnswerHash(solveRequest.pin);
|
||||
} else {
|
||||
throw Error("unsupported answer request");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error(`unsupported challenge type "${truth.escrow_type}""`);
|
||||
}
|
||||
return respHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a truth, optionally with a challenge solution
|
||||
* provided by the user.
|
||||
@ -874,61 +919,26 @@ async function pollChallenges(
|
||||
async function requestTruth(
|
||||
state: ReducerStateRecovery,
|
||||
truth: EscrowMethod,
|
||||
solveRequest?: ActionArgsSolveChallengeRequest,
|
||||
solveRequest: ActionArgsSolveChallengeRequest,
|
||||
): Promise<ReducerStateRecovery | ReducerStateError> {
|
||||
const url = new URL(`/truth/${truth.uuid}`, truth.url);
|
||||
const url = new URL(`/truth/${truth.uuid}/solve`, truth.url);
|
||||
|
||||
if (solveRequest) {
|
||||
logger.info(`handling solve request ${j2s(solveRequest)}`);
|
||||
let respHash: string;
|
||||
switch (truth.escrow_type) {
|
||||
case ChallengeType.Question: {
|
||||
if ("answer" in solveRequest) {
|
||||
respHash = await secureAnswerHash(
|
||||
solveRequest.answer,
|
||||
truth.uuid,
|
||||
truth.truth_salt,
|
||||
);
|
||||
} else {
|
||||
throw Error("unsupported answer request");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ChallengeType.Email:
|
||||
case ChallengeType.Sms:
|
||||
case ChallengeType.Post:
|
||||
case ChallengeType.Iban:
|
||||
case ChallengeType.Totp: {
|
||||
if ("answer" in solveRequest) {
|
||||
const s = solveRequest.answer.trim().replace(/^A-/, "");
|
||||
let pin: number;
|
||||
try {
|
||||
pin = Number.parseInt(s);
|
||||
} catch (e) {
|
||||
throw Error("invalid pin format");
|
||||
}
|
||||
respHash = await pinAnswerHash(pin);
|
||||
} else if ("pin" in solveRequest) {
|
||||
respHash = await pinAnswerHash(solveRequest.pin);
|
||||
} else {
|
||||
throw Error("unsupported answer request");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error(`unsupported challenge type "${truth.escrow_type}""`);
|
||||
}
|
||||
url.searchParams.set("response", respHash);
|
||||
}
|
||||
const hresp = await getResponseHash(truth, solveRequest);
|
||||
|
||||
const resp = await fetch(url.href, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Anastasis-Truth-Decryption-Key": truth.truth_key,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
truth_decryption_key: truth.truth_key,
|
||||
h_response: hresp,
|
||||
}),
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`got GET /truth response from ${truth.url}, http status ${resp.status}`,
|
||||
`got POST /truth/.../solve response from ${truth.url}, http status ${resp.status}`,
|
||||
);
|
||||
|
||||
if (resp.status === HttpStatusCode.Ok) {
|
||||
@ -975,66 +985,6 @@ async function requestTruth(
|
||||
return tryRecoverSecret(newState);
|
||||
}
|
||||
|
||||
if (resp.status === HttpStatusCode.Forbidden) {
|
||||
const body = await resp.json();
|
||||
if (
|
||||
body.code === TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
recovery_state: RecoveryStates.ChallengeSolving,
|
||||
challenge_feedback: {
|
||||
...state.challenge_feedback,
|
||||
[truth.uuid]: {
|
||||
state: ChallengeFeedbackStatus.Pending,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
recovery_state: RecoveryStates.ChallengeSolving,
|
||||
challenge_feedback: {
|
||||
...state.challenge_feedback,
|
||||
[truth.uuid]: {
|
||||
state: ChallengeFeedbackStatus.Message,
|
||||
message: body.hint ?? "Challenge should be solved",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (resp.status === HttpStatusCode.Accepted) {
|
||||
const body = await resp.json();
|
||||
logger.info(`got body ${j2s(body)}`);
|
||||
if (body.method === "iban") {
|
||||
const b = body as IbanExternalAuthResponse;
|
||||
return {
|
||||
...state,
|
||||
recovery_state: RecoveryStates.ChallengeSolving,
|
||||
challenge_feedback: {
|
||||
...state.challenge_feedback,
|
||||
[truth.uuid]: {
|
||||
state: ChallengeFeedbackStatus.AuthIban,
|
||||
answer_code: b.answer_code,
|
||||
business_name: b.details.business_name,
|
||||
challenge_amount: b.details.challenge_amount,
|
||||
credit_iban: b.details.credit_iban,
|
||||
wire_transfer_subject: b.details.wire_transfer_subject,
|
||||
details: b.details,
|
||||
method: "iban",
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
|
||||
hint: "unknown external authentication method",
|
||||
http_status: resp.status,
|
||||
} as ReducerStateError;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
|
||||
hint: "got unexpected /truth/ response status",
|
||||
@ -1053,6 +1003,7 @@ async function solveChallenge(
|
||||
if (!truth) {
|
||||
throw Error("truth for challenge not found");
|
||||
}
|
||||
|
||||
return requestTruth(state, truth, ta);
|
||||
}
|
||||
|
||||
@ -1096,7 +1047,58 @@ async function selectChallenge(
|
||||
throw "truth for challenge not found";
|
||||
}
|
||||
|
||||
return requestTruth({ ...state, selected_challenge_uuid: ta.uuid }, truth);
|
||||
const url = new URL(`/truth/${truth.uuid}/challenge`, truth.url);
|
||||
|
||||
if (truth.escrow_type === ChallengeType.Question) {
|
||||
return {
|
||||
...state,
|
||||
recovery_state: RecoveryStates.ChallengeSolving,
|
||||
selected_challenge_uuid: truth.uuid,
|
||||
challenge_feedback: {
|
||||
...state.challenge_feedback,
|
||||
[truth.uuid]: {
|
||||
state: ChallengeFeedbackStatus.Pending,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const resp = await fetch(url.href, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
truth_decryption_key: truth.truth_key,
|
||||
}),
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`got GET /truth/.../challenge response from ${truth.url}, http status ${resp.status}`,
|
||||
);
|
||||
|
||||
if (resp.status === HttpStatusCode.Ok) {
|
||||
return {
|
||||
...state,
|
||||
recovery_state: RecoveryStates.ChallengeSolving,
|
||||
selected_challenge_uuid: truth.uuid,
|
||||
challenge_feedback: {
|
||||
...state.challenge_feedback,
|
||||
[truth.uuid]: {
|
||||
state: ChallengeFeedbackStatus.Pending,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: look at response, include in challenge_feedback!
|
||||
|
||||
return {
|
||||
code: TalerErrorCode.ANASTASIS_TRUTH_CHALLENGE_FAILED,
|
||||
hint: "got unexpected /truth/.../challenge response status",
|
||||
http_status: resp.status,
|
||||
} as ReducerStateError;
|
||||
}
|
||||
|
||||
async function backupSelectContinent(
|
||||
@ -1140,15 +1142,15 @@ interface TransitionImpl<S, T> {
|
||||
handler: (s: S, args: T) => Promise<S | ReducerStateError>;
|
||||
}
|
||||
|
||||
interface Transition<S, T> {
|
||||
[x: string]: TransitionImpl<S, T>;
|
||||
interface Transition<S> {
|
||||
[x: string]: TransitionImpl<S, any>;
|
||||
}
|
||||
|
||||
function transition<S, T>(
|
||||
action: string,
|
||||
argCodec: Codec<T>,
|
||||
handler: (s: S, args: T) => Promise<S | ReducerStateError>,
|
||||
): Transition<S, T> {
|
||||
): Transition<S> {
|
||||
return {
|
||||
[action]: {
|
||||
argCodec,
|
||||
@ -1160,7 +1162,7 @@ function transition<S, T>(
|
||||
function transitionBackupJump(
|
||||
action: string,
|
||||
st: BackupStates,
|
||||
): Transition<ReducerStateBackup, void> {
|
||||
): Transition<ReducerStateBackup> {
|
||||
return {
|
||||
[action]: {
|
||||
argCodec: codecForAny(),
|
||||
@ -1172,7 +1174,7 @@ function transitionBackupJump(
|
||||
function transitionRecoveryJump(
|
||||
action: string,
|
||||
st: RecoveryStates,
|
||||
): Transition<ReducerStateRecovery, void> {
|
||||
): Transition<ReducerStateRecovery> {
|
||||
return {
|
||||
[action]: {
|
||||
argCodec: codecForAny(),
|
||||
@ -1440,7 +1442,7 @@ async function updateSecretExpiration(
|
||||
|
||||
const backupTransitions: Record<
|
||||
BackupStates,
|
||||
Transition<ReducerStateBackup, any>
|
||||
Transition<ReducerStateBackup>
|
||||
> = {
|
||||
[BackupStates.ContinentSelecting]: {
|
||||
...transition(
|
||||
@ -1511,7 +1513,7 @@ const backupTransitions: Record<
|
||||
|
||||
const recoveryTransitions: Record<
|
||||
RecoveryStates,
|
||||
Transition<ReducerStateRecovery, any>
|
||||
Transition<ReducerStateRecovery>
|
||||
> = {
|
||||
[RecoveryStates.ContinentSelecting]: {
|
||||
...transition(
|
||||
|
@ -6,9 +6,7 @@ import {
|
||||
codecForNumber,
|
||||
codecForString,
|
||||
codecForTimestamp,
|
||||
Duration,
|
||||
TalerProtocolTimestamp,
|
||||
AbsoluteTime,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { ChallengeFeedback } from "./challenge-feedback-types.js";
|
||||
import { KeyShare } from "./crypto.js";
|
||||
|
@ -18,7 +18,7 @@
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
import { useState } from "preact/hooks";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
// import { cancelPendingRequest } from "./backend";
|
||||
|
||||
export interface Options {
|
||||
@ -34,6 +34,17 @@ export interface AsyncOperationApi<T> {
|
||||
error: string | undefined;
|
||||
}
|
||||
|
||||
export function useIsMounted() {
|
||||
const isMountedRef = useRef(true);
|
||||
const isMounted = useCallback(() => isMountedRef.current, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => void (isMountedRef.current = false);
|
||||
}, []);
|
||||
|
||||
return isMounted;
|
||||
}
|
||||
|
||||
export function useAsync<T>(
|
||||
fn?: (...args: any) => Promise<T>,
|
||||
{ slowTolerance: tooLong }: Options = { slowTolerance: 1000 },
|
||||
@ -42,11 +53,15 @@ export function useAsync<T>(
|
||||
const [isLoading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<any>(undefined);
|
||||
const [isSlow, setSlow] = useState(false);
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const request = async (...args: any) => {
|
||||
if (!fn) return;
|
||||
setLoading(true);
|
||||
const handler = setTimeout(() => {
|
||||
if (!isMounted()) {
|
||||
return;
|
||||
}
|
||||
setSlow(true);
|
||||
}, tooLong);
|
||||
|
||||
@ -54,6 +69,10 @@ export function useAsync<T>(
|
||||
console.log("calling async", args);
|
||||
const result = await fn(...args);
|
||||
console.log("async back", result);
|
||||
if (!isMounted()) {
|
||||
// Possibly calling fn(...) resulted in the component being unmounted.
|
||||
return;
|
||||
}
|
||||
setData(result);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
|
@ -220,6 +220,13 @@ export enum TalerErrorCode {
|
||||
*/
|
||||
GENERIC_FAILED_COMPUTE_JSON_HASH = 61,
|
||||
|
||||
/**
|
||||
* The service could not compute an amount.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
GENERIC_FAILED_COMPUTE_AMOUNT = 62,
|
||||
|
||||
/**
|
||||
* The HTTP server had insufficient memory to parse the request.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
|
||||
@ -395,6 +402,20 @@ export enum TalerErrorCode {
|
||||
*/
|
||||
EXCHANGE_GENERIC_CLOCK_SKEW = 1020,
|
||||
|
||||
/**
|
||||
* The specified amount for the coin is higher than the value of the denomination of the coin.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE = 1021,
|
||||
|
||||
/**
|
||||
* The exchange was not properly configured with global fees.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_GENERIC_GLOBAL_FEES_MISSING = 1022,
|
||||
|
||||
/**
|
||||
* The exchange did not find information about the specified transaction in the database.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
|
||||
@ -1081,6 +1102,83 @@ export enum TalerErrorCode {
|
||||
*/
|
||||
EXCHANGE_MANAGEMENT_GLOBAL_FEE_SIGNATURE_INVALID = 1817,
|
||||
|
||||
/**
|
||||
* The purse was previously created with different meta data.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA = 1850,
|
||||
|
||||
/**
|
||||
* The purse was previously created with a different contract.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_CREATE_CONFLICTING_CONTRACT_STORED = 1851,
|
||||
|
||||
/**
|
||||
* A coin signature for a deposit into the purse is invalid.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_CREATE_COIN_SIGNATURE_INVALID = 1852,
|
||||
|
||||
/**
|
||||
* The purse expiration time is in the past.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW = 1853,
|
||||
|
||||
/**
|
||||
* The purse expiration time is "never".
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER = 1854,
|
||||
|
||||
/**
|
||||
* The purse signature over the purse meta data is invalid.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID = 1855,
|
||||
|
||||
/**
|
||||
* The signature over the encrypted contract is invalid.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID = 1856,
|
||||
|
||||
/**
|
||||
* The signature from the exchange over the confirmation is invalid.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID = 1857,
|
||||
|
||||
/**
|
||||
* The coin was previously deposited with different meta data.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA = 1858,
|
||||
|
||||
/**
|
||||
* The encrypted contract was previously uploaded with different meta data.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA = 1859,
|
||||
|
||||
/**
|
||||
* The deposited amount is less than the purse fee.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE = 1860,
|
||||
|
||||
/**
|
||||
* The auditor signature over the denomination meta data is invalid.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
|
||||
|
Loading…
Reference in New Issue
Block a user