summaryrefslogtreecommitdiff
path: root/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx
blob: 89017aa6fe7a5411f80cc8af7027c67bb822c556 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { ChallengeFeedbackStatus, ChallengeInfo } from "anastasis-core";
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../components/AsyncButton";
import { TextInput } from "../../../components/fields/TextInput";
import { useAnastasisContext } from "../../../context/anastasis";
import { AnastasisClientFrame } from "../index";
import { SolveOverviewFeedbackDisplay } from "../SolveScreen";
import { AuthMethodSolveProps } from "./index";

export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
  const [answer, setAnswer] = useState("");
  const [expanded, setExpanded] = useState(false);

  const reducer = useAnastasisContext();
  if (!reducer) {
    return (
      <AnastasisClientFrame hideNav title="Recovery problem">
        <div>no reducer in context</div>
      </AnastasisClientFrame>
    );
  }
  if (
    !reducer.currentReducerState ||
    reducer.currentReducerState.recovery_state === undefined
  ) {
    return (
      <AnastasisClientFrame hideNav title="Recovery problem">
        <div>invalid state</div>
      </AnastasisClientFrame>
    );
  }

  if (!reducer.currentReducerState.recovery_information) {
    return (
      <AnastasisClientFrame
        hideNext="Recovery document not found"
        title="Recovery problem"
      >
        <div>no recovery information found</div>
      </AnastasisClientFrame>
    );
  }
  if (!reducer.currentReducerState.selected_challenge_uuid) {
    return (
      <AnastasisClientFrame hideNav title="Recovery problem">
        <div>invalid state</div>
        <div
          style={{
            marginTop: "2em",
            display: "flex",
            justifyContent: "space-between",
          }}
        >
          <button class="button" onClick={() => reducer.back()}>
            Back
          </button>
        </div>
      </AnastasisClientFrame>
    );
  }

  const chArr = reducer.currentReducerState.recovery_information.challenges;
  const challengeFeedback =
    reducer.currentReducerState.challenge_feedback ?? {};
  const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
  const challenges: {
    [uuid: string]: ChallengeInfo;
  } = {};
  for (const ch of chArr) {
    challenges[ch.uuid] = ch;
  }
  const selectedChallenge = challenges[selectedUuid];
  const feedback = challengeFeedback[selectedUuid];

  async function onNext(): Promise<void> {
    return reducer?.transition("solve_challenge", { answer });
  }
  function onCancel(): void {
    reducer?.back();
  }

  const shouldHideConfirm =
    feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded ||
    feedback?.state === ChallengeFeedbackStatus.Redirect ||
    feedback?.state === ChallengeFeedbackStatus.Unsupported ||
    feedback?.state === ChallengeFeedbackStatus.TruthUnknown;

  return (
    <AnastasisClientFrame hideNav title="Email challenge">
      <SolveOverviewFeedbackDisplay feedback={feedback} />
      <p>
        An email has been sent to "<b>{selectedChallenge.instructions}</b>". The
        message has and identification code and recovery code that starts with "
        <b>A-</b>". Wait the message to arrive and the enter the recovery code
        below.
      </p>
      {!expanded ? (
        <p>
          The identification code in the email should start with "
          {selectedUuid.substring(0, 10)}"
          <span
            class="icon has-tooltip-top"
            data-tooltip="click to expand"
            onClick={() => setExpanded((e) => !e)}
          >
            <i class="mdi mdi-information" />
          </span>
        </p>
      ) : (
        <p>
          The identification code in the email is "{selectedUuid}"
          <span
            class="icon has-tooltip-top"
            data-tooltip="click to show less code"
            onClick={() => setExpanded((e) => !e)}
          >
            <i class="mdi mdi-information" />
          </span>
        </p>
      )}
      <TextInput
        label="Answer"
        grabFocus
        onConfirm={onNext}
        bind={[answer, setAnswer]}
        placeholder="A-1234567812345678"
      />

      <div
        style={{
          marginTop: "2em",
          display: "flex",
          justifyContent: "space-between",
        }}
      >
        <button class="button" onClick={onCancel}>
          Cancel
        </button>
        {!shouldHideConfirm && (
          <AsyncButton class="button is-info" onClick={onNext}>
            Confirm
          </AsyncButton>
        )}
      </div>
    </AnastasisClientFrame>
  );
}