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' ;
9
2
import { 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' ;
18
4
19
5
/**
20
6
* The Guard can be wrapped around a Component that depends on a user being logged in.
@@ -23,353 +9,9 @@ import { CodeBlock } from './CodeBlock';
23
9
* Instructs them to save their secret somewhere safe
24
10
*/
25
11
export function Guard ( { children } : React . PropsWithChildren < any > ) : JSX . Element {
26
- const { dialogProps, show } = useDialog ( ) ;
27
12
const { agent } = useSettings ( ) ;
28
- const [ register , setRegister ] = useState ( true ) ;
29
13
30
14
if ( agent ) {
31
15
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 /> ;
57
17
}
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