aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/challenger-ui/src/hooks/session.ts11
-rw-r--r--packages/challenger-ui/src/pages/AnswerChallenge.tsx111
-rw-r--r--packages/challenger-ui/src/pages/AskChallenge.tsx56
-rw-r--r--packages/taler-util/src/http-client/challenger.ts4
-rw-r--r--packages/taler-util/src/http-client/types.ts40
5 files changed, 153 insertions, 69 deletions
diff --git a/packages/challenger-ui/src/hooks/session.ts b/packages/challenger-ui/src/hooks/session.ts
index 54eeb2fdc..4dc7e0dc1 100644
--- a/packages/challenger-ui/src/hooks/session.ts
+++ b/packages/challenger-ui/src/hooks/session.ts
@@ -41,7 +41,9 @@ export type SessionId = {
};
export type LastChallengeResponse = {
- attemptsLeft: number;
+ sendCodeLeft: number;
+ changeTargetLeft: number;
+ checkPinLeft: number;
nextSend: AbsoluteTime;
transmitted: boolean;
};
@@ -53,7 +55,9 @@ export type SessionState = SessionId & {
};
export const codecForLastChallengeResponse = (): Codec<LastChallengeResponse> =>
buildCodecForObject<LastChallengeResponse>()
- .property("attemptsLeft", codecForNumber())
+ .property("sendCodeLeft", codecForNumber())
+ .property("changeTargetLeft", codecForNumber())
+ .property("checkPinLeft", codecForNumber())
.property("nextSend", codecForAbsoluteTime)
.property("transmitted", codecForBoolean())
.build("LastChallengeResponse");
@@ -127,7 +131,8 @@ export function useSessionState(): SessionStateHandler {
const ls = state.lastStatus;
if (
ls.changes_left !== st.changes_left ||
- ls.fix_address !== st.fix_address || ls.last_address !== st.last_address
+ ls.fix_address !== st.fix_address ||
+ ls.last_address !== st.last_address
) {
update({
...state,
diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx b/packages/challenger-ui/src/pages/AnswerChallenge.tsx
index b5b3b74b0..2740e1bdb 100644
--- a/packages/challenger-ui/src/pages/AnswerChallenge.tsx
+++ b/packages/challenger-ui/src/pages/AnswerChallenge.tsx
@@ -17,6 +17,7 @@ import {
AbsoluteTime,
ChallengerApi,
HttpStatusCode,
+ TalerProtocolTimestamp,
assertUnreachable,
} from "@gnu-taler/taler-util";
import {
@@ -53,8 +54,7 @@ export function AnswerChallenge({
const { state, accepted, completed } = useSessionState();
const [notification, withErrorHandler] = useLocalNotificationHandler();
const [pin, setPin] = useState<string | undefined>();
- const [lastTryError, setLastTryError] =
- useState<ChallengerApi.InvalidPinResponse>();
+
const errors = undefinedIfEmpty({
pin: !pin ? i18n.str`Can't be empty` : undefined,
});
@@ -68,7 +68,9 @@ export function AnswerChallenge({
: state.lastStatus.last_address["email"];
const onSendAgain =
- !state || lastEmail === undefined
+ lastEmail === undefined ||
+ state?.lastStatus == undefined ||
+ state?.lastStatus.changes_left === 0
? undefined
: withErrorHandler(
async () => {
@@ -80,9 +82,11 @@ export function AnswerChallenge({
completed(new URL(ok.body.redirect_url));
} else {
accepted({
- attemptsLeft: ok.body.attempts_left,
+ changeTargetLeft: ok.body.attempts_left,
+ checkPinLeft: state.lastStatus?.auth_attempts_left ?? 0,
+ sendCodeLeft: state.lastStatus?.pin_transmissions_left ?? 0,
nextSend: AbsoluteTime.fromProtocolTimestamp(
- ok.body.next_tx_time,
+ ok.body.retransmission_time,
),
transmitted: ok.body.transmitted,
});
@@ -92,23 +96,23 @@ export function AnswerChallenge({
(fail) => {
switch (fail.case) {
case HttpStatusCode.BadRequest:
- return i18n.str``;
- case HttpStatusCode.Forbidden:
- return i18n.str``;
+ return i18n.str`The request was not accepted, try reloading the app.`;
case HttpStatusCode.NotFound:
- return i18n.str``;
+ return i18n.str`Challenge not found.`;
case HttpStatusCode.NotAcceptable:
- return i18n.str``;
+ return i18n.str`Server templates are missing due to misconfiguration.`;
case HttpStatusCode.TooManyRequests:
- return i18n.str``;
+ return i18n.str`There have been too many attempts to request challenge transmissions.`;
case HttpStatusCode.InternalServerError:
- return i18n.str``;
+ return i18n.str`Server is not able to respond due to internal problems.`;
}
},
);
const onCheck =
- errors !== undefined || (lastTryError && lastTryError.exhausted)
+ errors !== undefined ||
+ state?.lastStatus == undefined ||
+ state?.lastStatus.auth_attempts_left === 0
? undefined
: withErrorHandler(
async () => {
@@ -118,25 +122,34 @@ export function AnswerChallenge({
if (ok.body.type === "completed") {
completed(new URL(ok.body.redirect_url));
} else {
- setLastTryError(ok.body);
+ accepted({
+ changeTargetLeft: ok.body.addresses_left,
+ checkPinLeft: ok.body.auth_attempts_left,
+ sendCodeLeft: ok.body.pin_transmissions_left,
+ nextSend: AbsoluteTime.fromProtocolTimestamp(
+ state?.lastStatus?.retransmission_time ??
+ TalerProtocolTimestamp.now(),
+ ),
+ transmitted: state?.lastTry?.transmitted ?? false,
+ });
}
onComplete();
},
(fail) => {
switch (fail.case) {
case HttpStatusCode.BadRequest:
- return i18n.str`Invalid request`;
+ return i18n.str`The request was not accepted, try reloading the app.`;
case HttpStatusCode.Forbidden: {
- return i18n.str`Too many attemps where made`;
+ return i18n.str`Invalid pin.`;
}
case HttpStatusCode.NotFound:
- return i18n.str``;
+ return i18n.str`Challenge not found.`;
case HttpStatusCode.NotAcceptable:
- return i18n.str``;
+ return i18n.str`Server templates are missing due to misconfiguration.`;
case HttpStatusCode.TooManyRequests:
- return i18n.str``;
+ return i18n.str`There have been too many attempts to request challenge transmissions.`;
case HttpStatusCode.InternalServerError:
- return i18n.str``;
+ return i18n.str`Server is not able to respond due to internal problems.`;
default:
assertUnreachable(fail);
}
@@ -177,11 +190,11 @@ export function AnswerChallenge({
</Attention>
)}
</p>
- {!lastTryError ? undefined : (
+ {!state.lastStatus ? undefined : (
<p class="mt-2 text-lg leading-8 text-gray-600">
<i18n.Translate>
You can try another PIN but just{" "}
- {lastTryError.auth_attempts_left} times more.
+ {state.lastStatus.auth_attempts_left} times more.
</i18n.Translate>
</p>
)}
@@ -225,8 +238,21 @@ export function AnswerChallenge({
<p class="mt-3 text-sm leading-6 text-gray-400">
<i18n.Translate>
- You have {state.lastTry.attemptsLeft} attempts left.
+ We send the code {state.lastTry.checkPinLeft} more times.
</i18n.Translate>
+ {state.lastTry.checkPinLeft < 1 ? (
+ <i18n.Translate>
+ You can&#39;t check the PIN anymore.
+ </i18n.Translate>
+ ) : state.lastTry.checkPinLeft === 1 ? (
+ <i18n.Translate>
+ You can check the PIN one last time.
+ </i18n.Translate>
+ ) : (
+ <i18n.Translate>
+ You can check the PIN {state.lastTry.checkPinLeft} more times.
+ </i18n.Translate>
+ )}
</p>
</div>
@@ -243,12 +269,31 @@ export function AnswerChallenge({
<div class="mt-10 flex justify-between">
<div>
<a
+ data-disabled={!state.lastStatus || state.lastStatus.changes_left < 1}
href={routeAsk.url({ nonce })}
- class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
+ class="relative data-[disabled=true]:bg-gray-300 data-[disabled=true]:text-white data-[disabled=true]:cursor-default inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
>
<i18n.Translate>Change email</i18n.Translate>
</a>
- </div>
+ {state.lastStatus === undefined ? undefined :
+ <p class="mt-2 text-sm leading-6 text-gray-400">
+ {state.lastStatus.changes_left < 1 ? (
+ <i18n.Translate>
+ You can&#39;t change the email anymore.
+ </i18n.Translate>
+ ) : state.lastStatus.changes_left === 1 ? (
+ <i18n.Translate>
+ You can change the email one last time.
+ </i18n.Translate>
+ ) : (
+ <i18n.Translate>
+ You can change the email {state.lastStatus.changes_left}{" "}
+ more times.
+ </i18n.Translate>
+ )}
+ </p>
+ }
+ </div>
<div>
<Button
type="submit"
@@ -258,6 +303,22 @@ export function AnswerChallenge({
>
<i18n.Translate>Send code again</i18n.Translate>
</Button>
+ <p class="mt-2 text-sm leading-6 text-gray-400">
+ {state.lastTry.sendCodeLeft < 1 ? (
+ <i18n.Translate>
+ We can&#39;t send you the code anymore.
+ </i18n.Translate>
+ ) : state.lastTry.sendCodeLeft === 1 ? (
+ <i18n.Translate>
+ We can send the code one last time.
+ </i18n.Translate>
+ ) : (
+ <i18n.Translate>
+ We can send the code {state.lastTry.sendCodeLeft} more
+ times.
+ </i18n.Translate>
+ )}
+ </p>
</div>
</div>
</form>
diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx b/packages/challenger-ui/src/pages/AskChallenge.tsx
index cb0223d90..dc60562b7 100644
--- a/packages/challenger-ui/src/pages/AskChallenge.tsx
+++ b/packages/challenger-ui/src/pages/AskChallenge.tsx
@@ -48,13 +48,15 @@ export function AskChallenge({
focus,
}: Props): VNode {
const { state, accepted, completed } = useSessionState();
+ const { lib, config } = useChallengerApiContext();
+
const status = state?.lastStatus;
const prevEmail =
!status || !status.last_address ? undefined : status.last_address["email"];
- const regexEmail =
- !status || !status.restrictions ? undefined : status.restrictions["email"];
+ const regexEmail = !config.restrictions
+ ? undefined
+ : config.restrictions["email"];
- const { lib } = useChallengerApiContext();
const { i18n } = useTranslationContext();
const [notification, withErrorHandler] = useLocalNotificationHandler();
const [email, setEmail] = useState<string | undefined>();
@@ -91,8 +93,12 @@ export function AskChallenge({
completed(new URL(ok.body.redirect_url));
} else {
accepted({
- attemptsLeft: ok.body.attempts_left,
- nextSend: AbsoluteTime.fromProtocolTimestamp( ok.body.next_tx_time),
+ changeTargetLeft: ok.body.attempts_left,
+ checkPinLeft: state?.lastStatus?.auth_attempts_left ?? 0,
+ sendCodeLeft: state?.lastStatus?.pin_transmissions_left ?? 0,
+ nextSend: AbsoluteTime.fromProtocolTimestamp(
+ ok.body.retransmission_time,
+ ),
transmitted: ok.body.transmitted,
});
}
@@ -101,17 +107,15 @@ export function AskChallenge({
(fail) => {
switch (fail.case) {
case HttpStatusCode.BadRequest:
- return i18n.str``;
- case HttpStatusCode.Forbidden:
- return i18n.str``;
+ return i18n.str`The request was not accepted, try reloading the app.`;
case HttpStatusCode.NotFound:
- return i18n.str``;
+ return i18n.str`Challenge not found.`;
case HttpStatusCode.NotAcceptable:
- return i18n.str``;
+ return i18n.str`Server templates are missing due to misconfiguration.`;
case HttpStatusCode.TooManyRequests:
- return i18n.str``;
+ return i18n.str`There have been too many attempts to request challenge transmissions.`;
case HttpStatusCode.InternalServerError:
- return i18n.str``;
+ return i18n.str`Server is not able to respond due to internal problems.`;
}
},
);
@@ -122,7 +126,7 @@ export function AskChallenge({
return (
<Fragment>
- <LocalNotificationBanner notification={notification} />
+ <LocalNotificationBanner notification={notification} showDebug={true} />
<div class="isolate bg-white px-6 py-12">
<div class="mx-auto max-w-2xl text-center">
@@ -213,16 +217,22 @@ export function AskChallenge({
</div>
)}
- {!status.changes_left ? (
- <p class="mt-3 text-sm leading-6 text-gray-400">
- <i18n.Translate>No more changes left</i18n.Translate>
- </p>
- ) : (
- <p class="mt-3 text-sm leading-6 text-gray-400">
- <i18n.Translate>
- You can change your email address another{" "}
- {status.changes_left} times.
- </i18n.Translate>
+ {state.lastStatus === undefined ? undefined : (
+ <p class="mt-2 text-sm leading-6 text-gray-400">
+ {state.lastStatus.changes_left < 1 ? (
+ <i18n.Translate>
+ You can&#39;t change the email anymore.
+ </i18n.Translate>
+ ) : state.lastStatus.changes_left === 1 ? (
+ <i18n.Translate>
+ You can change the email one last time.
+ </i18n.Translate>
+ ) : (
+ <i18n.Translate>
+ You can change the email {state.lastStatus.changes_left}{" "}
+ more times.
+ </i18n.Translate>
+ )}
</p>
)}
</div>
diff --git a/packages/taler-util/src/http-client/challenger.ts b/packages/taler-util/src/http-client/challenger.ts
index e7b128b02..6a920749c 100644
--- a/packages/taler-util/src/http-client/challenger.ts
+++ b/packages/taler-util/src/http-client/challenger.ts
@@ -164,8 +164,6 @@ export class ChallengerHttpClient {
}
case HttpStatusCode.BadRequest:
return opKnownHttpFailure(resp.status, resp);
- case HttpStatusCode.Forbidden:
- return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotAcceptable:
@@ -205,7 +203,7 @@ export class ChallengerHttpClient {
case HttpStatusCode.BadRequest:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Forbidden:
- return opKnownHttpFailure(resp.status, resp);
+ return opKnownAlternativeFailure(resp, HttpStatusCode.Forbidden, codecForChallengeInvalidPinResponse());
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotAcceptable:
diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts
index a91e8cc71..3816b1598 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -1567,6 +1567,14 @@ export const codecForChallengerTermsOfServiceResponse =
.property("name", codecForConstString("challenger"))
.property("version", codecForString())
.property("implementation", codecOptional(codecForString()))
+ .property("restrictions", codecOptional(codecForMap(codecForAny())))
+ .property(
+ "address_type",
+ codecForEither(
+ codecForConstString("phone"),
+ codecForConstString("email"),
+ ),
+ )
.build("ChallengerApi.ChallengerTermsOfServiceResponse");
export const codecForChallengeSetupResponse =
@@ -1578,7 +1586,6 @@ export const codecForChallengeSetupResponse =
export const codecForChallengeStatus =
(): Codec<ChallengerApi.ChallengeStatus> =>
buildCodecForObject<ChallengerApi.ChallengeStatus>()
- .property("restrictions", codecOptional(codecForMap(codecForAny())))
.property("fix_address", codecForBoolean())
.property("solved", codecForBoolean())
.property("last_address", codecOptional(codecForMap(codecForAny())))
@@ -1600,10 +1607,10 @@ export const codecForChallengeCreateResponse =
(): Codec<ChallengerApi.ChallengeCreateResponse> =>
buildCodecForObject<ChallengerApi.ChallengeCreateResponse>()
.property("attempts_left", codecForNumber())
- .property("address", codecForAny())
.property("type", codecForConstString("created"))
+ .property("address", codecForAny())
.property("transmitted", codecForBoolean())
- .property("next_tx_time", codecForTimestamp)
+ .property("retransmission_time", codecForTimestamp)
.build("ChallengerApi.ChallengeCreateResponse");
export const codecForChallengeRedirect =
@@ -5389,6 +5396,19 @@ export namespace ChallengerApi {
// URN of the implementation (needed to interpret 'revision' in version).
// @since v0, may become mandatory in the future.
implementation?: string;
+
+ // Object; map of keys (names of the fields of the address
+ // to be entered by the user) to objects with a "regex" (string)
+ // containing an extended Posix regular expression for allowed
+ // address field values, and a "hint"/"hint_i18n" giving a
+ // human-readable explanation to display if the value entered
+ // by the user does not match the regex. Keys that are not mapped
+ // to such an object have no restriction on the value provided by
+ // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration.
+ restrictions: Record<string, Restriction> | undefined;
+
+ // @since v2.
+ address_type: "email" | "phone";
}
export interface ChallengeSetupResponse {
@@ -5403,16 +5423,6 @@ export namespace ChallengerApi {
}
export interface ChallengeStatus {
- // Object; map of keys (names of the fields of the address
- // to be entered by the user) to objects with a "regex" (string)
- // containing an extended Posix regular expression for allowed
- // address field values, and a "hint"/"hint_i18n" giving a
- // human-readable explanation to display if the value entered
- // by the user does not match the regex. Keys that are not mapped
- // to such an object have no restriction on the value provided by
- // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration.
- restrictions: Record<string, Restriction> | undefined;
-
// indicates if the given address cannot be changed anymore, the
// form should be read-only if set to true.
fix_address: boolean;
@@ -5425,7 +5435,7 @@ export namespace ChallengerApi {
// shown to the user
changes_left: Integer;
- // if the challenge has already been solved
+ // is the challenge already solved?
solved: boolean;
// when we would re-transmit the challenge the next
@@ -5470,7 +5480,7 @@ export namespace ChallengerApi {
// timestamp explaining when we would re-transmit the challenge the next
// time (at the earliest) if requested by the user
- next_tx_time: TalerProtocolTimestamp;
+ retransmission_time: TalerProtocolTimestamp;
}
export type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse;