1- import {
2- Dialog ,
3- DialogActions ,
4- DialogContent ,
5- DialogTitle ,
6- useDialog ,
7- } from './Dialog' ;
8- import React , { FormEvent , useCallback , useEffect , useState } from 'react' ;
1+ import React from 'react' ;
92import { useSettings } from '../helpers/AppSettings' ;
10- import { Button , ButtonInput } from './Button' ;
11- import { Agent , nameRegex , useRegister , useServerURL } from '@tomic/react' ;
12- import { FaEyeSlash , FaEye , FaCog } from 'react-icons/fa' ;
13- import Field from './forms/Field' ;
14- import { InputWrapper , InputStyled } from './forms/InputStyles' ;
15- import { Row } from './Row' ;
16- import { ErrorLook } from './ErrorLook' ;
17- import { CodeBlock } from './CodeBlock' ;
3+ import { RegisterSignIn } from './RegisterSignIn' ;
184
195/**
206 * The Guard can be wrapped around a Component that depends on a user being logged in.
@@ -23,353 +9,9 @@ import { CodeBlock } from './CodeBlock';
239 * Instructs them to save their secret somewhere safe
2410 */
2511export function Guard ( { children } : React . PropsWithChildren < any > ) : JSX . Element {
26- const { dialogProps, show } = useDialog ( ) ;
2712 const { agent } = useSettings ( ) ;
28- const [ register , setRegister ] = useState ( true ) ;
2913
3014 if ( agent ) {
3115 return < > { children } </ > ;
32- } else
33- return (
34- < >
35- < Row >
36- < Button
37- onClick = { ( ) => {
38- setRegister ( true ) ;
39- show ( ) ;
40- } }
41- >
42- Register
43- </ Button >
44- < Button
45- subtle
46- onClick = { ( ) => {
47- setRegister ( false ) ;
48- show ( ) ;
49- } }
50- >
51- Sign In
52- </ Button >
53- </ Row >
54- < Dialog { ...dialogProps } > { register ? < Register /> : < SignIn /> } </ Dialog >
55- </ >
56- ) ;
16+ } else return < RegisterSignIn /> ;
5717}
58-
59- function Register ( ) {
60- const [ name , setName ] = useState ( '' ) ;
61- const [ secret , setSecret ] = useState ( '' ) ;
62- const [ driveURL , setDriveURL ] = useState ( '' ) ;
63- const [ newAgent , setNewAgent ] = useState < Agent | undefined > ( undefined ) ;
64- const [ serverUrlStr ] = useServerURL ( ) ;
65- const [ err , setErr ] = useState < Error | undefined > ( undefined ) ;
66- const register = useRegister ( ) ;
67- const { setAgent } = useSettings ( ) ;
68-
69- const serverUrl = new URL ( serverUrlStr ) ;
70- serverUrl . host = `${ name } .${ serverUrl . host } ` ;
71-
72- useEffect ( ( ) => {
73- // check regex of name, set error
74- if ( ! name . match ( nameRegex ) ) {
75- setErr ( new Error ( 'Name must be lowercase and only contain numbers' ) ) ;
76- } else {
77- setErr ( undefined ) ;
78- }
79- } , [ name ] ) ;
80-
81- const handleSubmit = useCallback (
82- async ( event : FormEvent ) => {
83- event . preventDefault ( ) ;
84-
85- if ( ! name ) {
86- setErr ( new Error ( 'Name is required' ) ) ;
87-
88- return ;
89- }
90-
91- try {
92- const { driveURL : newDriveURL , agent } = await register ( name ) ;
93- setDriveURL ( newDriveURL ) ;
94- setSecret ( agent . buildSecret ( ) ) ;
95- setNewAgent ( agent ) ;
96- } catch ( er ) {
97- setErr ( er ) ;
98- }
99- } ,
100- [ name ] ,
101- ) ;
102-
103- const handleSaveAgent = useCallback ( ( ) => {
104- setAgent ( newAgent ) ;
105- } , [ newAgent ] ) ;
106-
107- if ( driveURL ) {
108- return (
109- < >
110- < DialogTitle >
111- < h1 > Save your Passphrase, { name } </ h1 >
112- </ DialogTitle >
113- < DialogContent >
114- < p >
115- Your Passphrase is like your password. Never share it with anyone.
116- Use a password manager to store it securely. You will need this to
117- log in next!
118- </ p >
119- < CodeBlock content = { secret } wrapContent />
120- </ DialogContent >
121- < DialogActions >
122- < Button onClick = { handleSaveAgent } > Continue here</ Button >
123- < a href = { driveURL } target = '_blank' rel = 'noreferrer' >
124- Open my new Drive!
125- </ a >
126- </ DialogActions >
127- </ >
128- ) ;
129- }
130-
131- return (
132- < >
133- < DialogTitle >
134- < h1 > Register</ h1 >
135- </ DialogTitle >
136- < DialogContent >
137- < form onSubmit = { handleSubmit } id = 'register-form' >
138- < Field
139- label = 'Unique username'
140- helper = 'Becomes a part of your URL, e.g. `example.atomicdata.dev`'
141- >
142- < InputWrapper >
143- < InputStyled
144- autoFocus = { true }
145- pattern = { nameRegex }
146- type = { 'text' }
147- required
148- value = { name }
149- onChange = { e => {
150- setName ( e . target . value ) ;
151- } }
152- />
153- </ InputWrapper >
154- </ Field >
155- { ! err && name ?. length > 0 && < code > { serverUrl . toString ( ) } </ code > }
156- { name && err && < ErrorLook > { err . message } </ ErrorLook > }
157- </ form >
158- </ DialogContent >
159- < DialogActions >
160- < Button
161- type = 'submit'
162- form = 'register-form'
163- disabled = { ! name || ! ! err }
164- onClick = { handleSubmit }
165- >
166- Create
167- </ Button >
168- </ DialogActions >
169- </ >
170- ) ;
171- }
172-
173- function SignIn ( ) {
174- return (
175- < >
176- < DialogTitle >
177- < h1 > Sign in </ h1 >
178- </ DialogTitle >
179- < DialogContent >
180- < SettingsAgent />
181- < p > Lost your passphrase?</ p >
182- </ DialogContent >
183- </ >
184- ) ;
185- }
186-
187- export const SettingsAgent : React . FunctionComponent = ( ) => {
188- const { agent, setAgent } = useSettings ( ) ;
189- const [ subject , setSubject ] = useState < string | undefined > ( undefined ) ;
190- const [ privateKey , setPrivateKey ] = useState < string | undefined > ( undefined ) ;
191- const [ error , setError ] = useState < Error | undefined > ( undefined ) ;
192- const [ showPrivateKey , setShowPrivateKey ] = useState ( false ) ;
193- const [ advanced , setAdvanced ] = useState ( false ) ;
194- const [ secret , setSecret ] = useState < string | undefined > ( undefined ) ;
195-
196- // When there is an agent, set the advanced values
197- // Otherwise, reset the secret value
198- React . useEffect ( ( ) => {
199- if ( agent !== undefined ) {
200- fillAdvanced ( ) ;
201- } else {
202- setSecret ( '' ) ;
203- }
204- } , [ agent ] ) ;
205-
206- // When the key or subject changes, update the secret
207- React . useEffect ( ( ) => {
208- renewSecret ( ) ;
209- } , [ subject , privateKey ] ) ;
210-
211- function renewSecret ( ) {
212- if ( agent ) {
213- setSecret ( agent . buildSecret ( ) ) ;
214- }
215- }
216-
217- function fillAdvanced ( ) {
218- try {
219- if ( ! agent ) {
220- throw new Error ( 'No agent set' ) ;
221- }
222-
223- setSubject ( agent . subject ) ;
224- setPrivateKey ( agent . privateKey ) ;
225- } catch ( e ) {
226- const err = new Error ( 'Cannot fill subject and privatekey fields.' + e ) ;
227- setError ( err ) ;
228- setSubject ( '' ) ;
229- }
230- }
231-
232- function setAgentIfChanged ( oldAgent : Agent | undefined , newAgent : Agent ) {
233- if ( JSON . stringify ( oldAgent ) !== JSON . stringify ( newAgent ) ) {
234- setAgent ( newAgent ) ;
235- }
236- }
237-
238- /** Called when the secret or the subject is updated manually */
239- async function handleUpdateSubjectAndKey ( ) {
240- renewSecret ( ) ;
241- setError ( undefined ) ;
242-
243- try {
244- const newAgent = new Agent ( privateKey ! , subject ) ;
245- await newAgent . getPublicKey ( ) ;
246- await newAgent . verifyPublicKeyWithServer ( ) ;
247-
248- setAgentIfChanged ( agent , newAgent ) ;
249- } catch ( e ) {
250- const err = new Error ( 'Invalid Agent' + e ) ;
251- setError ( err ) ;
252- }
253- }
254-
255- function handleCopy ( ) {
256- secret && navigator . clipboard . writeText ( secret ) ;
257- }
258-
259- /** When the Secret updates, parse it and try if the */
260- async function handleUpdateSecret ( updateSecret : string ) {
261- setSecret ( updateSecret ) ;
262-
263- if ( updateSecret === '' ) {
264- setSecret ( '' ) ;
265- setError ( undefined ) ;
266-
267- return ;
268- }
269-
270- setError ( undefined ) ;
271-
272- try {
273- const newAgent = Agent . fromSecret ( updateSecret ) ;
274- setAgentIfChanged ( agent , newAgent ) ;
275- setPrivateKey ( newAgent . privateKey ) ;
276- setSubject ( newAgent . subject ) ;
277- // This will fail and throw if the agent is not public, which is by default
278- // await newAgent.checkPublicKey();
279- } catch ( e ) {
280- const err = new Error ( 'Invalid secret. ' + e ) ;
281- setError ( err ) ;
282- }
283- }
284-
285- return (
286- < form >
287- < Field
288- label = { agent ? 'Passphrase' : 'Enter your Passphrase' }
289- helper = {
290- "The Agent Passphrase is a secret, long string of characters that encodes both the Subject and the Private Key. You can think of it as a combined username + password. Store it safely, and don't share it with others."
291- }
292- error = { error }
293- >
294- < InputWrapper >
295- < InputStyled
296- value = { secret }
297- onChange = { e => handleUpdateSecret ( e . target . value ) }
298- type = { showPrivateKey ? 'text' : 'password' }
299- disabled = { agent !== undefined }
300- name = 'secret'
301- id = 'current-password'
302- autoComplete = 'current-password'
303- spellCheck = 'false'
304- placeholder = 'Paste your Passphrase'
305- />
306- < ButtonInput
307- type = 'button'
308- title = { showPrivateKey ? 'Hide secret' : 'Show secret' }
309- onClick = { ( ) => setShowPrivateKey ( ! showPrivateKey ) }
310- >
311- { showPrivateKey ? < FaEyeSlash /> : < FaEye /> }
312- </ ButtonInput >
313- < ButtonInput
314- type = 'button'
315- title = { advanced ? 'Hide advanced' : 'Show advanced' }
316- onClick = { ( ) => setAdvanced ( ! advanced ) }
317- >
318- < FaCog />
319- </ ButtonInput >
320- { agent && (
321- < ButtonInput type = 'button' onClick = { handleCopy } >
322- copy
323- </ ButtonInput >
324- ) }
325- </ InputWrapper >
326- </ Field >
327- { advanced ? (
328- < React . Fragment >
329- < Field
330- label = 'Subject URL'
331- helper = {
332- 'The link to your Agent, e.g. https://atomicdata.dev/agents/someAgent'
333- }
334- >
335- < InputWrapper >
336- < InputStyled
337- disabled = { agent !== undefined }
338- value = { subject }
339- onChange = { e => {
340- setSubject ( e . target . value ) ;
341- handleUpdateSubjectAndKey ( ) ;
342- } }
343- />
344- </ InputWrapper >
345- </ Field >
346- < Field
347- label = 'Private Key'
348- helper = {
349- 'The private key of the Agent, which is a Base64 encoded string.'
350- }
351- >
352- < InputWrapper >
353- < InputStyled
354- disabled = { agent !== undefined }
355- type = { showPrivateKey ? 'text' : 'password' }
356- value = { privateKey }
357- onChange = { e => {
358- setPrivateKey ( e . target . value ) ;
359- handleUpdateSubjectAndKey ( ) ;
360- } }
361- />
362- < ButtonInput
363- type = 'button'
364- title = { showPrivateKey ? 'Hide private key' : 'Show private key' }
365- onClick = { ( ) => setShowPrivateKey ( ! showPrivateKey ) }
366- >
367- { showPrivateKey ? < FaEyeSlash /> : < FaEye /> }
368- </ ButtonInput >
369- </ InputWrapper >
370- </ Field >
371- </ React . Fragment >
372- ) : null }
373- </ form >
374- ) ;
375- } ;
0 commit comments