better select secret screen

This commit is contained in:
Sebastian 2022-06-09 16:11:49 -03:00
parent 78b056a0b1
commit 3ebb1d1815
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
16 changed files with 218 additions and 59 deletions

View File

@ -50,11 +50,14 @@ bundle ui-dev
if [ "WATCH" == "$1" ]; then if [ "WATCH" == "$1" ]; then
echo watch mode echo watch mode
echo Writting any file in the src directory will trigger a browser reload.
echo Be sure that the watcher server is running.
inotifywait -e close_write -r src -q -m | while read line; do inotifywait -e close_write -r src -q -m | while read line; do
DATE=$(date) echo $(date) $line
echo $DATE $line
build_js src/main.ts build_js src/main.ts
bundle ui-dev bundle ui-dev
./watch/send_reload.sh #CONTENT=$(echo 'alert("hola")' | base64)
./watch/send.sh '{"type":"RELOAD"}'
#./watch/send.sh '{"type":"UPDATE","content":"'$CONTENT'"}'
done; done;
fi fi

View File

@ -23,13 +23,13 @@
<body> <body>
<div id="container" class="anastasis-container"></div> <div id="container" class="anastasis-container"></div>
<script type="application/javascript"> <script id="code" type="application/javascript">
ANASTASIS_SCRIPT_CONTENT; ANASTASIS_SCRIPT_CONTENT;
</script> </script>
<script type="application/javascript"> <script type="application/javascript">
function setupLiveReload(port) { function setupLiveReload() {
const socketPath = `ws://localhost:${port}/socket`; const socketPath = `ws://localhost:8003/socket`;
console.log("connecting to ", socketPath)
const ws = new WebSocket(socketPath); const ws = new WebSocket(socketPath);
ws.onmessage = (message) => { ws.onmessage = (message) => {
const event = JSON.parse(message.data); const event = JSON.parse(message.data);
@ -39,12 +39,27 @@
if (event.type === "RELOAD") { if (event.type === "RELOAD") {
window.location.reload(); window.location.reload();
} }
if (event.type === "UPDATE") {
document.body.removeChild(document.getElementById("container"))
const d = document.createElement('div')
d.setAttribute('id',"container")
d.setAttribute('class',"anastasis-container");
document.body.appendChild(d)
const s = document.createElement('script')
s.setAttribute('id',"code")
s.setAttribute('type',"application/javascript");
s.textContent = atob(event.content)
document.body.appendChild(s)
}
}; };
ws.onerror = (error) => { ws.onerror = (error) => {
console.error(error); console.error(error);
}; };
ws.onclose = (e) => {
setTimeout(setupLiveReload, 500)
};
} }
setupLiveReload(8003); setupLiveReload();
</script> </script>
</body> </body>
</html> </html>

View File

@ -45,7 +45,6 @@ export function FileButton(props: Props): VNode {
if (!f || f.length != 1) { if (!f || f.length != 1) {
return props.onChange(undefined); return props.onChange(undefined);
} }
console.log(f);
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
setSizeError(true); setSizeError(true);
return props.onChange(undefined); return props.onChange(undefined);

View File

@ -75,7 +75,6 @@ export function FileInput(props: FileInputProps): VNode {
if (!f || f.length != 1) { if (!f || f.length != 1) {
return props.onChange(undefined); return props.onChange(undefined);
} }
console.log(f);
if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) {
setSizeError(true); setSizeError(true);
return props.onChange(undefined); return props.onChange(undefined);

View File

@ -66,9 +66,7 @@ export function useAsync<T>(
}, tooLong); }, tooLong);
try { try {
console.log("calling async", args);
const result = await fn(...args); const result = await fn(...args);
console.log("async back", result);
if (!isMounted()) { if (!isMounted()) {
// Possibly calling fn(...) resulted in the component being unmounted. // Possibly calling fn(...) resulted in the component being unmounted.
return; return;

View File

@ -177,7 +177,6 @@ function getStateFromStorage(): any {
if (s === "undefined") { if (s === "undefined") {
state = undefined; state = undefined;
} else if (s) { } else if (s) {
console.log("restoring state from", s);
state = JSON.parse(s); state = JSON.parse(s);
} }
} catch (e) { } catch (e) {
@ -225,7 +224,6 @@ export function useAnastasisReducer(): AnastasisReducerApi {
if (Object.keys(updates).length === 0) { if (Object.keys(updates).length === 0) {
return; return;
} }
console.log("got provider updates", updates);
const rs2 = reducerState; const rs2 = reducerState;
if (rs2.reducer_type !== "backup" && rs2.reducer_type !== "recovery") { if (rs2.reducer_type !== "backup" && rs2.reducer_type !== "recovery") {
return; return;
@ -248,19 +246,15 @@ export function useAnastasisReducer(): AnastasisReducerApi {
}; };
async function doTransition(action: string, args: any): Promise<void> { async function doTransition(action: string, args: any): Promise<void> {
console.log("reducing with", action, args);
let s: ReducerState; let s: ReducerState;
if (remoteReducer) { if (remoteReducer) {
s = await reduceStateRemote(anastasisState.reducerState, action, args); s = await reduceStateRemote(anastasisState.reducerState, action, args);
} else { } else {
s = await reduceAction(anastasisState.reducerState!, action, args); s = await reduceAction(anastasisState.reducerState!, action, args);
} }
console.log("got response from reducer", s);
if (s.reducer_type === "error") { if (s.reducer_type === "error") {
console.log("response is an error");
setAnastasisState({ ...anastasisState, currentError: s }); setAnastasisState({ ...anastasisState, currentError: s });
} else { } else {
console.log("response is a new state");
setAnastasisState({ setAnastasisState({
...anastasisState, ...anastasisState,
currentError: undefined, currentError: undefined,
@ -387,7 +381,6 @@ export function useAnastasisReducer(): AnastasisReducerApi {
console.log("exception during reducer transaction", e); console.log("exception during reducer transaction", e);
} }
const s = txHandle.transactionState; const s = txHandle.transactionState;
console.log("transaction finished, new state", s);
if (s.reducer_type === "error") { if (s.reducer_type === "error") {
setAnastasisState({ setAnastasisState({
...anastasisState, ...anastasisState,
@ -413,7 +406,6 @@ class ReducerTxImpl implements ReducerTransactionHandle {
} else { } else {
s = await reduceAction(this.transactionState, action, args); s = await reduceAction(this.transactionState, action, args);
} }
console.log("making transition in transaction", action);
this.transactionState = s; this.transactionState = s;
// Abort transaction as soon as we transition into an error state. // Abort transaction as soon as we transition into an error state.
if (this.transactionState.reducer_type === "error") { if (this.transactionState.reducer_type === "error") {

View File

@ -13,7 +13,11 @@
You should have received a copy of the GNU Affero General Public License along with You should have received a copy of the GNU Affero General Public License along with
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { AuthenticationProviderStatusOk } from "@gnu-taler/anastasis-core"; import {
AuthenticationProviderStatus,
AuthenticationProviderStatusError,
AuthenticationProviderStatusOk,
} from "@gnu-taler/anastasis-core";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
import { TextInput } from "../../components/fields/TextInput.js"; import { TextInput } from "../../components/fields/TextInput.js";
@ -38,7 +42,6 @@ async function testProvider(
"This provider doesn't have authentication method. Check the provider URL", "This provider doesn't have authentication method. Check the provider URL",
); );
} }
console.log("expected", expectedMethodType);
if (!expectedMethodType) { if (!expectedMethodType) {
return; return;
} }
@ -68,8 +71,10 @@ export function AddingProviderScreen({ providerType, onCancel }: Props): VNode {
const reducer = useAnastasisContext(); const reducer = useAnastasisContext();
const [providerURL, setProviderURL] = useState(""); const [providerURL, setProviderURL] = useState("");
const [error, setError] = useState<string | undefined>(); const [error, setError] = useState<string | undefined>();
const [testing, setTesting] = useState(false); const [testing, setTesting] = useState(false);
const providerLabel = providerType const providerLabel = providerType
? authMethods[providerType].label ? authMethods[providerType].label
: undefined; : undefined;
@ -81,19 +86,32 @@ export function AddingProviderScreen({ providerType, onCancel }: Props): VNode {
!reducer.currentReducerState.authentication_providers !reducer.currentReducerState.authentication_providers
? {} ? {}
: reducer.currentReducerState.authentication_providers; : reducer.currentReducerState.authentication_providers;
const authProviders = Object.keys(allAuthProviders).filter((provUrl) => {
const p = allAuthProviders[provUrl];
if (!providerLabel) {
return p && "currency" in p;
} else {
return (
p &&
"currency" in p &&
p.methods.findIndex((m) => m.type === providerType) !== -1
);
}
});
const authProvidersByStatus = Object.keys(allAuthProviders).reduce(
(prev, url) => {
const p = allAuthProviders[url];
if (
providerLabel &&
p.status === "ok" &&
p.methods.findIndex((m) => m.type === providerType) !== -1
) {
return prev;
}
const others = prev[p.status] ? prev[p.status] : [];
others.push({ ...p, url });
return {
...prev,
[p.status]: others,
};
},
{} as Record<
AuthenticationProviderStatus["status"],
(AuthenticationProviderStatus & { url: string })[]
>,
);
const authProviders = authProvidersByStatus["ok"].map((p) => p.url);
console.log("rodos", allAuthProviders);
//FIXME: move this timeout logic into a hook //FIXME: move this timeout logic into a hook
const timeout = useRef<number | undefined>(undefined); const timeout = useRef<number | undefined>(undefined);
useEffect(() => { useEffect(() => {
@ -211,6 +229,17 @@ export function AddingProviderScreen({ providerType, onCancel }: Props): VNode {
<TableRow key={k} url={k} info={p} onDelete={deleteProvider} /> <TableRow key={k} url={k} info={p} onDelete={deleteProvider} />
); );
})} })}
{authProvidersByStatus["error"]?.map((k) => {
const p = k as AuthenticationProviderStatusError;
return (
<TableRowError
key={k}
url={k.url}
info={p}
onDelete={deleteProvider}
/>
);
})}
</div> </div>
</AnastasisClientFrame> </AnastasisClientFrame>
); );
@ -277,3 +306,62 @@ function TableRow({
</div> </div>
); );
} }
function TableRowError({
url,
info,
onDelete,
}: {
onDelete: (s: string) => void;
url: string;
info: AuthenticationProviderStatusError;
}): VNode {
const [status, setStatus] = useState("checking");
useEffect(function () {
testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url)
.then(function () {
setStatus("responding");
})
.catch(function () {
setStatus("failed to contact");
});
});
return (
<div
class="box"
style={{ display: "flex", justifyContent: "space-between" }}
>
<div>
<div class="subtitle">{url}</div>
<dl>
<dt>
<b>Error</b>
</dt>
<dd>{info.hint}</dd>
<dt>
<b>Code</b>
</dt>
<dd>{info.code}</dd>
<dt>
<b>Status</b>
</dt>
<dd>{status}</dd>
</dl>
</div>
<div
class="block"
style={{
marginTop: "auto",
marginBottom: "auto",
display: "flex",
justifyContent: "space-between",
flexDirection: "column",
}}
>
<button class="button is-danger" onClick={() => onDelete(url)}>
Remove
</button>
</div>
</div>
);
}

View File

@ -91,20 +91,64 @@ export function SecretSelectionScreen(): VNode {
title="Recovery: Select secret" title="Recovery: Select secret"
hideNext="Please select version to recover" hideNext="Please select version to recover"
> >
<p>Found versions:</p> <div class="columns">
{policies.map((version, i) => ( <div class="column">
<div key={i}> <p class="block">Found versions:</p>
{version.policy_hash} / {version.secret_name} {policies.map((version, i) => (
<button <div key={i} class="box">
onClick={async () => { <div
await reducer.transition("select_version", version); class="block"
}} style={{ display: "flex", justifyContent: "space-between" }}
> >
Recover <div
</button> style={{
display: "flex",
flexDirection: "column",
}}
>
<div style={{ display: "flex", alignItems: "center" }}>
<b>Name:</b>&nbsp;<span>{version.secret_name}</span>
</div>
<div style={{ display: "flex", alignItems: "center" }}>
<b>Id:</b>&nbsp;
<span
class="icon has-tooltip-top"
data-tooltip={version.policy_hash
.match(/(.{22})/g)
?.join("\n")}
>
<i class="mdi mdi-information" />
</span>
<span>{version.policy_hash.substring(0, 22)}...</span>
</div>
</div>
<div>
<AsyncButton
class="button"
onClick={() =>
reducer.transition("select_version", version)
}
>
Recover
</AsyncButton>
</div>
</div>
</div>
))}
</div> </div>
))} <div class="column">
<button>Load older versions</button> <p>
Secret found, you can select another version or continue to the
challenges solving
</p>
<p class="block">
<a onClick={() => setManageProvider(true)}>
Manage recovery providers
</a>
</p>
</div>
</div>
</AnastasisClientFrame> </AnastasisClientFrame>
); );
} }

View File

@ -85,7 +85,6 @@ export function AuthMethodTotpSolve(props: AuthMethodSolveProps): VNode {
const feedback = challengeFeedback[selectedUuid]; const feedback = challengeFeedback[selectedUuid];
async function onNext(): Promise<void> { async function onNext(): Promise<void> {
console.log(`sending TOTP code '${answerCode}'`);
return reducer?.transition("solve_challenge", { return reducer?.transition("solve_challenge", {
answer: answerCode, answer: answerCode,
}); });

View File

@ -146,7 +146,7 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
const handleKeyPress = ( const handleKeyPress = (
e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>, e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>,
): void => { ): void => {
console.log("Got key press", e.key); // console.log("Got key press", e.key);
// FIXME: By default, "next" action should be executed here // FIXME: By default, "next" action should be executed here
}; };
@ -227,7 +227,6 @@ function AnastasisClientImpl(): VNode {
if (!state) { if (!state) {
return <StartScreen />; return <StartScreen />;
} }
console.log("state", reducer.currentReducerState);
if ( if (
(state.reducer_type === "backup" && (state.reducer_type === "backup" &&

View File

@ -381,4 +381,7 @@ function setupLiveReload(port: number, onReload: () => void): void {
ws.onerror = (error) => { ws.onerror = (error) => {
console.error(error); console.error(error);
}; };
ws.onclose = (e) => {
console.log("disconnected", e);
};
} }

View File

@ -14,4 +14,5 @@ done
cat watch/web_socket_server.reply | sed 's/$'"/`echo \\\r`/" | envsubst '$WS_ACCEPT' cat watch/web_socket_server.reply | sed 's/$'"/`echo \\\r`/" | envsubst '$WS_ACCEPT'
socat UNIX-RECV:./send_signal STDOUT tail -n 0 -F /tmp/send_signal 2> /dev/null

View File

@ -0,0 +1,12 @@
#!/bin/bash
#https://datatracker.ietf.org/doc/html/rfc6455#page-65
COMMAND=$1
LEN=$(printf '%x\n' ${#COMMAND})
#text command
OPCODE=81
cat <(echo -n $OPCODE$LEN | xxd -r -p) <(echo -n $COMMAND) >> /tmp/send_signal

View File

@ -0,0 +1,14 @@
#!/bin/bash
#https://datatracker.ietf.org/doc/html/rfc6455#page-65
CONTENT=$( cat $1 | base64 -w 0 )
COMMAND='{"type":"UPDATE","'$CONTENT'"}'
LEN=$(printf '%0*x\n' 4 ${#COMMAND})
echo $LEN
LEN=00000138
#text command
OPCODE=81
cat <(echo -n $OPCODE$LEN | xxd -r -p) <(echo -n $COMMAND) >> /tmp/send_signal

View File

@ -1,7 +0,0 @@
#!/bin/bash
COMMAND='{"type":"RELOAD"}'
LEN=$(printf '%x\n' ${#COMMAND})
OPCODE=81
cat <(echo -n $OPCODE$LEN | xxd -r -p) <(echo -n $COMMAND) | socat - UNIX-SEND:./send_signal

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/bin/bash
socat TCP-LISTEN:8003,fork EXEC:"./watch/reply.sh" socat TCP-LISTEN:8003,fork,reuseaddr,keepalive EXEC:"./watch/reply.sh"