git ssb

1+

Daan Patchwork / patchwork



Tree: 6df8f9822940a85660caf16ce3b5f32f30589248

Files: 6df8f9822940a85660caf16ce3b5f32f30589248 / lib / depject / page / html / render / mnemonic.js

6887 bytesRaw
1const fs = require("fs");
2const fsPath = require("path");
3const { computed, h, Value } = require("mutant");
4const nest = require("depnest");
5const ssbMnemonic = require("ssb-keys-mnemonic");
6const watch = require("mutant/watch");
7
8exports.needs = nest({
9 "message.html.markdown": "first",
10 "intl.sync.i18n": "first",
11 "keys.sync.load": "first",
12});
13
14exports.gives = nest("page.html.render");
15
16exports.create = function (api) {
17 return nest("page.html.render", function channel(path) {
18 const assetPath = fsPath.join(
19 __dirname,
20 "..",
21 "..",
22 "..",
23 "..",
24 "..",
25 "assets",
26 "mnemonic_warning.md"
27 );
28 const markdown = api.message.html.markdown
29 const warningText = fs.readFileSync(assetPath, "utf8");
30 const warningHtml = markdown(warningText);
31
32 const confirmationText = Value("");
33
34 if (path !== "/mnemonic") return;
35 const i18n = api.intl.sync.i18n;
36
37 const keys = api.keys.sync.load();
38 const words = ssbMnemonic.keysToWords(keys).split(" ");
39 const wordBatches = [];
40 const maxLen = words.reduce(
41 (currMax, currWord) =>
42 currWord.length > currMax ? currWord.length : currMax,
43 0
44 );
45 for (let i = 0; i < words.length; i = i + 4) {
46 const batchLine = words
47 .slice(i, i + 4)
48 .map((s) => s.padEnd(maxLen, " "))
49 .join(" ");
50 wordBatches.push(batchLine);
51 }
52 const mnemonic = wordBatches.join("\n");
53
54 const prepend = [
55 h("PageHeading", [h("h1", [h("strong", i18n("Key Export"))])]),
56 ];
57
58 const content = [h("section", warningHtml), h("hr")];
59
60 let showNextChallenge = Value(false)
61 const showFirstChallenge = showNextChallenge
62 content.push(
63 h(
64 "form",
65 {
66 style: {
67 margin: "1em auto",
68 },
69 action: "",
70 "ev-submit": (ev) => {
71 ev.preventDefault();
72 showFirstChallenge.set(true);
73 },
74 },
75 h(
76 "button",
77 {
78 disabled: showFirstChallenge,
79 style: {
80 margin: "1em auto",
81 "background-color": "#51c067",
82 color: "white",
83 },
84 },
85 "I still want to export my keys"
86 ),
87 )
88 );
89
90 function addChallenge(challenge, response) {
91 const showChalllenge = showNextChallenge;
92 const showNext = Value(false);
93 content.push(
94 h(
95 "section",
96 { hidden: computed(showChalllenge, (b) => !b) },
97 challenge
98 ),
99 h(
100 "form",
101 {
102 hidden: computed(showChalllenge, (b) => !b),
103 style: {
104 margin: "1em auto",
105 },
106 action: "",
107 "ev-submit": (ev) => {
108 ev.preventDefault();
109 if (confirmationText().toLowerCase() === response.toLowerCase()) {
110 showNext.set(true);
111 }
112 },
113 },
114 [
115 h("input", {
116 disabled: showNext,
117 hooks: [ValueHook(confirmationText), ScrollHook(showChalllenge)],
118 size: response.length + 1 < 30 ? 30 : response.length + 1,
119 "ev-paste": (ev) => {
120 ev.preventDefault();
121 },
122 }),
123 ]
124 )
125 );
126 showNextChallenge = showNext
127 }
128
129 addChallenge(
130 [
131 markdown('> ' + i18n("To confirm you understand the risks and responsibilities here, we will play a little game. Are you ready?")),
132 i18n("Type 'yes' and it return to continue"),
133 ],
134 i18n('yes'),
135 );
136 addChallenge(
137 markdown('> ' + i18n("This will be annoying and slow. That's intentional. It's a feature, not a bug. You really need to understand that we can't help you if this goes wrong. Type 'I understand' to confirm")),
138 i18n('I understand'),
139 );
140 addChallenge(
141 markdown('> ' + i18n("Good. You'll answer a bunch of questions. The prize for getting them right is one giant foot-gun. Aka: your key export\nLet's start easy: What's the name of the person posting the bird picture?")),
142 i18n('Carol'),
143 );
144 addChallenge(
145 markdown('> ' + i18n("Excellent job. And that's the same person as the one posting about #foffee, right?")),
146 i18n('no'),
147 );
148 addChallenge(
149 markdown('> ' + i18n("Well *someone* has been paying attention. Good catch! So, who was it then?")),
150 i18n('Alice'),
151 );
152 addChallenge(
153 markdown('> ' + i18n("You made it quite far into the text, that's good news!\nChange of gears: Will this procedure allow you to use the same identity on multiple two or more devices?")),
154 i18n('no'),
155 );
156 const sameAsChallenge = i18n('I understand that exporting my key will not allow me to use it on more than one device.')
157 addChallenge(
158 markdown('> ' + i18n("That's right. But let's be clear here. Please type this out:")+'\n> '+sameAsChallenge),
159 sameAsChallenge,
160 );
161 addChallenge(
162 markdown('> ' + i18n("At which step of the process does manyverse become the sole holder of your identity?")),
163 i18n('9'),
164 );
165 addChallenge(
166 markdown('> ' + i18n("Correct. Just after you import the key.\nIs it safe to post at that point then?")),
167 i18n('no'),
168 );
169 addChallenge(
170 markdown('> ' + i18n("Okay, looks like you're getting it. One last one then:\nIf any of this goes wrong, who can most likely help you?\n* The patchwork devs\n* The manyverse devs\n* Nobody")),
171 i18n('nobody'),
172 );
173
174 content.push(
175 h("hr", { hidden: computed(showNextChallenge, (b) => !b) }),
176 h("div", { hidden: computed(showNextChallenge, (b) => !b) }, [
177 h("p", i18n("Congrats, you made it. Here your mnemonic representation of your secret:")),
178 h("pre.mnemonic", {
179 hooks: [ScrollHook(showNextChallenge)],
180 }, mnemonic),
181 h(
182 "p",
183 i18n(
184 "Again: be very careful with it. Keep it secret, and don't use this key on multiple devices, including this one."
185 )
186 ),
187 ])
188 );
189
190 return h("Scroller", { style: { overflow: "auto" } }, [
191 h("div.wrapper", [
192 h(
193 "section.prepend",
194 h("PageHeading", [h("h1", [h("strong", i18n("Key Export"))])])
195 ),
196 h("section.content", content),
197 ]),
198 ]);
199 });
200};
201
202function ValueHook(obs) {
203 return function (element) {
204 element.value = obs();
205 element.oninput = function () {
206 obs.set(element.value.trim());
207 };
208 };
209}
210
211function ScrollHook(obs) {
212 return function(element) {
213 var scrolledOnce = false
214 watch(obs, (visible) => {
215 if (!scrolledOnce && visible) {
216 scrolledOnce = true
217 element.scrollIntoViewIfNeeded()
218 try {
219 element.focus()
220 } catch {}
221 }
222 })
223 }
224}
225

Built with git-ssb-web