on master help sign in
Merge pull request #488 from urbit/lf/auth-token

Merge #433 into master

commit25610be4f15f6bdee168304b58fb28543484e22f authorFang <git@fang.io> dateMon 13 Jul 2020, 18:58 (6 years ago) parent141dac494d4cef
11 files changed+253−36
src/indigo-react/indigo.css+22 src/lib/networkCode.js+1−1 src/lib/useInviter.js+8−6 src/lib/walletgen.js+32−6 src/store/lib/useInvitesStore.js+2−1 src/store/lib/usePointStore.js+6−1 src/store/wallet.js+1−1 src/views/Invite/Cohort.js+178−17 src/views/Login/Ledger.js+1−1 src/views/Login/Metamask.js+1−1 src/views/Login/Trezor.js+1−1
src/indigo-react/indigo.css+22
@@ -3629,6 +3629,28 @@ caption,
36293629/*******************************************************************************
36303630 Margin
36313631*******************************************************************************/
3632+.m-auto {
3633+ margin: auto; }
3634+
3635+.mt-auto {
3636+ margin-top: auto; }
3637+
3638+.mb-auto {
3639+ margin-bottom: auto; }
3640+
3641+.ml-auto {
3642+ margin-left: auto; }
3643+
3644+.mr-auto {
3645+ margin-right: auto; }
3646+
3647+.mv-auto {
3648+ margin-top: auto;
3649+ margin-bottom: auto; }
3650+
3651+.mh-auto {
3652+ margin-left: auto;
3653+ margin-right: auto; }
36323654.m0 {
36333655 margin: 0px; }
36343656
src/lib/networkCode.js+1−1
@@ -18,7 +18,7 @@ function buf2hex(buf) {
1818 .toString('hex');
1919}
2020
21−function shas(buf, salt) {
21+export function shas(buf, salt) {
2222 return shax(xor(salt, shax(buf)));
2323}
2424
src/lib/useInviter.js+8−6
@@ -35,10 +35,11 @@ const STATUS = {
3535
3636const useInviter = () => {
3737 const { contracts, web3, networkType } = useNetwork();
38− const { wallet, walletType, walletHdPath } = useWallet();
38+ const { wallet, walletType, walletHdPath, authToken } = useWallet();
3939 const { pointCursor } = usePointCursor();
4040 const { syncInvites } = usePointCache();
4141 const { gasPrice } = useGasPrice();
42+
4243 const point = need.point(pointCursor);
4344
4445 const [needFunds, setNeedFunds] = useState(null);
@@ -65,7 +66,8 @@ const useInviter = () => {
6566 const _contracts = contracts.getOrElse(null);
6667 const _web3 = web3.getOrElse(null);
6768 const _wallet = wallet.getOrElse(null);
68− if (!_contracts || !_web3 || !_wallet) {
69+ const _authToken = authToken.getOrElse(null);
70+ if (!_contracts || !_web3 || !_wallet || !_authToken) {
6971 // not using need because we want a custom error
7072 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
7173 }
@@ -104,10 +106,10 @@ const useInviter = () => {
104106 try {
105107 const planet = planets[i];
106108
107− const { ticket, owner } = await wg.generateTemporaryTicketAndWallet(
108− MIN_PLANET
109− // we're always giving planets, so generate a ticket of the correct size
110− );
109+ const {
110+ ticket,
111+ owner,
112+ } = await wg.generateTemporaryDeterministicWallet(planet, _authToken);
111113
112114 const inviteTx = azimuth.delegatedSending.sendPoint(
113115 _contracts,
src/lib/walletgen.js+32−6
@@ -2,6 +2,7 @@ import ob from 'urbit-ob';
22import kg from 'urbit-key-generation';
33import * as more from 'more-entropy';
44import { chunk, flatMap, zipWith } from 'lodash';
5+import { shax, shas } from 'lib/networkCode';
56
67import {
78 MIN_STAR,
@@ -15,14 +16,16 @@ import {
1516
1617const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8;
1718
19+const getTicketBitSize = point =>
20+ point < MIN_STAR
21+ ? GALAXY_ENTROPY_BITS
22+ : point < MIN_PLANET
23+ ? STAR_ENTROPY_BITS
24+ : PLANET_ENTROPY_BITS;
25+
1826// returns a promise for a ticket string
1927export const makeTicket = point => {
20− const bits =
21− point < MIN_STAR
22− ? GALAXY_ENTROPY_BITS
23− : point < MIN_PLANET
24− ? STAR_ENTROPY_BITS
25− : PLANET_ENTROPY_BITS;
28+ const bits = getTicketBitSize(point);
2629
2730 const bytes = bits / 8;
2831 const some = new Uint8Array(bytes);
@@ -44,6 +47,22 @@ export const makeTicket = point => {
4447 });
4548};
4649
50+export const makeDeterministicTicket = (point, seed) => {
51+ const bits = getTicketBitSize(point);
52+
53+ const bytes = bits / 8;
54+
55+ const pointSalt = Buffer.concat([
56+ Buffer.from(point.toString()),
57+ Buffer.from('invites'),
58+ ]);
59+ const entropy = shas(Buffer.from(seed, 'hex'), pointSalt);
60+
61+ const buf = entropy.slice(0, bytes);
62+ const patq = ob.hex2patq(buf.toString('hex'));
63+ return patq;
64+};
65+
4766// return a wallet object
4867export const generateWallet = async (point, ticket, boot) => {
4968 const config = {
@@ -78,3 +97,10 @@ export const generateTemporaryTicketAndWallet = async point => {
7897
7998 return { ticket, owner };
8099};
100+
101+export const generateTemporaryDeterministicWallet = async (point, seed) => {
102+ const ticket = makeDeterministicTicket(point, seed);
103+ const owner = await generateOwnershipWallet(ZOD, ticket);
104+
105+ return { ticket, owner };
106+};
src/store/lib/useInvitesStore.js+2−1
@@ -51,7 +51,8 @@ export default function useInvitesStore() {
5151 active: !!active,
5252 }))
5353 .partition('active')
54− .map(arr => arr.map(x => x.point));
54+ .map(arr => arr.map(x => x.point))
55+ .value();
5556
5657 addToInvitesCache({
5758 [point]: {
src/store/lib/usePointStore.js+6−1
@@ -27,7 +27,12 @@ export default function usePointStore() {
2727 );
2828
2929 // sync all of the on-chain info required to display a foreign point
30− const syncForeignPoint = useCallback(async point => {}, []);
30+ const syncForeignPoint = useCallback(
31+ async point => {
32+ syncDetails(point);
33+ },
34+ [syncDetails]
35+ );
3136
3237 // sync all of the on-chain info required for a point that the user owns
3338 const syncOwnedPoint = useCallback(
src/store/wallet.js+1−1
@@ -73,7 +73,7 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
7373 web3: _web3,
7474 });
7575
76− setAuthToken(token);
76+ setAuthToken(Just(token));
7777 })();
7878 }, [wallet, walletType, walletHdPath, web3]);
7979
src/views/Invite/Cohort.js+178−17
@@ -1,11 +1,13 @@
11import React, { useMemo, useEffect, useCallback, useState } from 'react';
22import { Grid, Flex, Button } from 'indigo-react';
33import * as ob from 'urbit-ob';
4−import { Just } from 'folktale/maybe';
4+import { Just, Nothing } from 'folktale/maybe';
55import cn from 'classnames';
6+import { delegatedSending } from 'azimuth-js';
67
78import { usePointCursor } from 'store/pointCursor';
89import { usePointCache } from 'store/pointCache';
10+import { useWallet } from 'store/wallet';
911
1012import View from 'components/View';
1113import Tabs from 'components/Tabs';
@@ -18,10 +20,14 @@ import Crumbs from 'components/Crumbs';
1820import { useLocalRouter } from 'lib/LocalRouter';
1921import useInvites from 'lib/useInvites';
2022import * as need from 'lib/need';
23+import * as wg from 'lib/walletgen';
2124
2225import Inviter from 'views/Invite/Inviter';
2326
2427import { ReactComponent as SearchIcon } from 'assets/search.svg';
28+import CopyButton from 'components/CopyButton';
29+import { useSyncForeignPoints } from 'lib/useSyncPoints';
30+import { useNetwork } from 'store/network';
2531import NavHeader from 'components/NavHeader';
2632
2733function SearchInput({ className, value, onChange }) {
@@ -42,7 +48,7 @@ function SearchInput({ className, value, onChange }) {
4248 );
4349}
4450
45−function CohortMember({ point, pending = false, className }) {
51+function CohortMember({ point, pending = false, className, ...rest }) {
4652 const { sentInvites } = useInvites(point);
4753 const patp = useMemo(() => ob.patp(point), [point]);
4854 const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
@@ -54,7 +60,7 @@ function CohortMember({ point, pending = false, className }) {
5460 );
5561
5662 return (
57− <Flex justify="between" className={cn('b1 b-gray2', className)}>
63+ <Flex justify="between" className={cn('b1 b-gray2', className)} {...rest}>
5864 <Flex.Item className="w9 h9">
5965 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
6066 </Flex.Item>
@@ -73,7 +79,13 @@ function CohortMember({ point, pending = false, className }) {
7379 </Flex>
7480 );
7581}
76−function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
82+function CohortList({
83+ acceptedPoints,
84+ pendingPoints,
85+ className,
86+ onlyPending,
87+ onSelectInvite,
88+}) {
7789 const { syncInvites } = usePointCache();
7890
7991 const [query, setQuery] = useState('');
@@ -98,7 +110,12 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
98110 }),
99111 [query]
100112 );
101−
113+ const selectInvite = useCallback(
114+ point => () => {
115+ onSelectInvite(point);
116+ },
117+ [onSelectInvite]
118+ );
102119 const _pendingPoints = filterPoints(pendingPoints);
103120 const _acceptedPoints = filterPoints(acceptedPoints);
104121
@@ -115,6 +132,7 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
115132 half={(idx % 2) + 1}
116133 as={CohortMember}
117134 point={p}
135+ onClick={selectInvite(p)}
118136 />
119137 ))}
120138 {_pendingPoints.map((p, idx) => (
@@ -124,6 +142,7 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
124142 as={CohortMember}
125143 point={p}
126144 pending
145+ onClick={selectInvite(p)}
127146 />
128147 ))}
129148 </>
@@ -139,12 +158,132 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
139158 );
140159}
141160
161+function hideIf(hider, hidden) {
162+ return hider ? '●●●●●●●' : hidden;
163+}
164+
165+function CohortMemberExpanded({ point, className, ...rest }) {
166+ const { sentInvites, acceptedInvites } = useInvites(point);
167+ const { getDetails } = usePointCache();
168+ const { authToken } = useWallet();
169+ const { contracts } = useNetwork();
170+ const details = getDetails(point);
171+ const { active } = details.getOrElse({});
172+ const patp = useMemo(() => ob.patp(point), [point]);
173+ const colors = useMemo(
174+ () => (!active ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF']),
175+ [active]
176+ );
177+
178+ const [code, setCode] = useState(Nothing());
179+ const [codeVisible, setCodeVisible] = useState(false);
180+
181+ useEffect(() => {
182+ const fetchWallet = async () => {
183+ const _authToken = authToken.getOrElse(null);
184+ const _details = details.getOrElse(null);
185+ const _contracts = contracts.getOrElse(null);
186+ if (
187+ !_authToken ||
188+ !_details ||
189+ _details.active ||
190+ !_contracts ||
191+ !codeVisible
192+ ) {
193+ setCode(Nothing());
194+ return;
195+ }
196+ const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
197+ point,
198+ _authToken
199+ );
200+
201+ if (owner.keys.address !== _details.transferProxy) {
202+ setCode(Just(null));
203+ } else {
204+ setCode(Just(ticket));
205+ }
206+ };
207+ fetchWallet();
208+ }, [codeVisible]);
209+
210+ useEffect(() => {
211+ setCodeVisible(false);
212+ }, [point]);
213+
214+ return (
215+ <Grid justify="between" className={cn('b1 b-gray2', className)} {...rest}>
216+ <Grid.Item rows={[1, 4]} cols={[1, 4]} className="h10 w10">
217+ <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
218+ </Grid.Item>
219+
220+ <Grid.Item cols={[4, 13]} className="mono mt-auto ">
221+ {patp}
222+ </Grid.Item>
223+
224+ {!active && (
225+ <>
226+ <Grid.Item
227+ cols={[4, 13]}
228+ className="mt-auto mb1 f6 gray4"
229+ as={Flex}
230+ col>
231+ Invite Code
232+ </Grid.Item>
233+ <Grid.Item cols={[4, 10]} className="mb-auto mt1 f6 gray4">
234+ {(code.getOrElse(true) === null &&
235+ 'This invite code cannot be recovered') ||
236+ hideIf(!codeVisible, matchBlinky(code))}
237+ </Grid.Item>
238+ {!codeVisible && (
239+ <Grid.Item
240+ cols={[10, 13]}
241+ className="mh-auto f6 underline pointer"
242+ onClick={() => setCodeVisible(true)}>
243+ Show
244+ </Grid.Item>
245+ )}
246+ {codeVisible && code.getOrElse('') !== null && (
247+ <Grid.Item
248+ className="mh-auto f6 t-right"
249+ cols={[10, 13]}
250+ as={CopyButton}
251+ text={code.getOrElse('')}
252+ />
253+ )}
254+ </>
255+ )}
256+
257+ {active && (
258+ <>
259+ <Grid.Item
260+ cols={[4, 13]}
261+ className="mt-auto mb1 f6 gray4"
262+ as={Flex}
263+ col>
264+ {matchBlinky(sentInvites)} invites sent
265+ </Grid.Item>
266+ <Grid.Item
267+ cols={[4, 13]}
268+ className="mb-auto mt1 f6 gray4"
269+ as={Flex}
270+ col>
271+ {matchBlinky(acceptedInvites)} invites accepted
272+ </Grid.Item>
273+ </>
274+ )}
275+ </Grid>
276+ );
277+}
278+
142279export default function InviteCohort() {
143280 const { pop } = useLocalRouter();
144281
145282 const { pointCursor } = usePointCursor();
146283 const point = need.point(pointCursor);
147284
285+ const { syncDetails } = usePointCache();
286+
148287 const {
149288 acceptedInvites,
150289 acceptedPoints,
@@ -164,6 +303,15 @@ export default function InviteCohort() {
164303 .map(a => sentInvites.getOrElse(0) + a)
165304 .getOrElse(0);
166305
306+ const [selectedInvite, _setSelectedInvite] = useState();
307+
308+ useSyncForeignPoints([..._acceptedPoints, ..._pendingPoints]);
309+
310+ const setSelectedInvite = useCallback(
311+ p => _setSelectedInvite(old => (old === p ? null : p)),
312+ []
313+ );
314+
167315 return (
168316 <View pop={pop} inset>
169317 <NavHeader>
@@ -208,18 +356,31 @@ export default function InviteCohort() {
208356 )}
209357 {showInviteForm && <Inviter />}
210358
211− <Grid.Item
212− full
213− center
214− as={Tabs}
215− views={VIEWS}
216− options={OPTIONS}
217− currentTab={tab}
218− onTabChange={setTab}
219− acceptedPoints={_acceptedPoints}
220− pendingPoints={_pendingPoints}
221− onlyPending={tab === NAMES.PENDING}
222− />
359+ {true && (
360+ <Grid.Item
361+ full
362+ center
363+ as={Tabs}
364+ views={VIEWS}
365+ options={OPTIONS}
366+ currentTab={tab}
367+ onTabChange={setTab}
368+ acceptedPoints={_acceptedPoints}
369+ pendingPoints={_pendingPoints}
370+ onlyPending={tab === NAMES.PENDING}
371+ onSelectInvite={setSelectedInvite}
372+ />
373+ )}
374+ {selectedInvite && (
375+ <Grid.Item
376+ full
377+ center
378+ as={CohortMemberExpanded}
379+ pending
380+ point={selectedInvite}
381+ onBack={() => setSelectedInvite(null)}
382+ />
383+ )}
223384 </Grid>
224385 </View>
225386 );
src/views/Login/Ledger.js+1−1
@@ -78,7 +78,7 @@ export default function Ledger({ className, goHome }) {
7878 walletHdPath: addHdPrefix(values.hdpath),
7979 });
8080
81− setAuthToken(authToken);
81+ setAuthToken(Just(authToken));
8282 setWallet(Just(hd));
8383 setWalletHdPath(addHdPrefix(values.hdpath));
8484 } catch (error) {
src/views/Login/Metamask.js+1−1
@@ -33,7 +33,7 @@ export default function Metamask({ className, goHome }) {
3333 web3: _web3,
3434 });
3535
36− setAuthToken(authToken);
36+ setAuthToken(Just(authToken));
3737 setWallet(Just(wallet));
3838 setWalletType(WALLET_TYPES.METAMASK);
3939 } catch (e) {
src/views/Login/Trezor.js+1−1
@@ -74,7 +74,7 @@ export default function Trezor({ className, goHome }) {
7474 walletHdPath: values.hdPath,
7575 });
7676
77− setAuthToken(authToken);
77+ setAuthToken(Just(authToken));
7878 setWallet(Just(hd));
7979 setWalletHdPath(values.hdPath);
8080 },