A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactor authentication state

+1208 -1008
+8 -16
src/Applications/UI.elm
··· 29 29 import UI.Alfred.State as Alfred 30 30 import UI.Alien as Alien 31 31 import UI.Audio.State as Audio 32 - import UI.Authentication as Authentication 33 - import UI.Authentication.ContextMenu as Authentication 34 32 import UI.Authentication.State as Authentication 35 33 import UI.Backdrop as Backdrop 36 34 import UI.Common.State as Common ··· 235 233 -- Authentication 236 234 ----------------------------------------- 237 235 AuthenticationBootFailure a -> 238 - Authentication.authenticationBootFailure a 236 + Authentication.bootFailure a 239 237 240 238 MissingSecretKey a -> 241 239 Authentication.missingSecretKey a ··· 397 395 User.saveEnclosedUserData 398 396 399 397 ----------------------------------------- 400 - -- 🦉 Adjunct 398 + -- ⚗️ Adjunct 401 399 ----------------------------------------- 402 400 KeyboardMsg a -> 403 401 Adjunct.keyboardInput a ··· 412 410 Other.setIsOnline a 413 411 414 412 ----------------------------------------- 415 - -- Children (TODO) 413 + -- 🦉 Nested 416 414 ----------------------------------------- 417 - AuthenticationMsg sub -> 418 - \model -> 419 - Return3.wieldNested 420 - Reply.translate 421 - { mapCmd = AuthenticationMsg 422 - , mapModel = \child -> { model | authentication = child } 423 - , update = Authentication.update 424 - } 425 - { model = model.authentication 426 - , msg = sub 427 - } 415 + AuthenticationMsg a -> 416 + Authentication.update a 428 417 418 + ----------------------------------------- 419 + -- Children (TODO) 420 + ----------------------------------------- 429 421 QueueMsg sub -> 430 422 \model -> 431 423 Return3.wieldNested
+1 -1
src/Applications/UI/Adjunct.elm
··· 5 5 import Return 6 6 import Return.Ext as Return 7 7 import UI.Alfred.State as Alfred 8 - import UI.Authentication as Authentication 8 + import UI.Authentication.Types as Authentication 9 9 import UI.Interface.State exposing (hideOverlay) 10 10 import UI.Reply as Reply exposing (Reply) 11 11 import UI.Types as UI exposing (..)
+1 -1
src/Applications/UI/Alien.elm
··· 4 4 import Common exposing (Switch(..)) 5 5 import Json.Decode 6 6 import Notifications 7 - import UI.Authentication as Authentication 7 + import UI.Authentication.Types as Authentication 8 8 import UI.Sources as Sources 9 9 import UI.Tracks as Tracks 10 10 import UI.Types exposing (..)
-973
src/Applications/UI/Authentication.elm
··· 1 - module UI.Authentication exposing (Model(..), Msg(..), extractMethod, initialModel, update, view) 2 - 3 - import Alien 4 - import Base64 5 - import Binary 6 - import Chunky exposing (..) 7 - import Color 8 - import Color.Ext as Color 9 - import Common exposing (Switch(..)) 10 - import Conditional exposing (..) 11 - import Css.Classes as C 12 - import Html exposing (Html, a, button, text) 13 - import Html.Attributes exposing (attribute, href, placeholder, src, style, target, title, value, width) 14 - import Html.Events exposing (onClick, onSubmit) 15 - import Html.Events.Extra exposing (onClickStopPropagation) 16 - import Html.Events.Extra.Mouse as Mouse 17 - import Http 18 - import Json.Encode 19 - import Markdown 20 - import Material.Icons as Icons 21 - import Material.Icons.Types exposing (Coloring(..)) 22 - import Maybe.Extra as Maybe 23 - import Return3 exposing (..) 24 - import SHA 25 - import String.Ext as String 26 - import Svg exposing (Svg) 27 - import UI.Kit 28 - import UI.Ports as Ports 29 - import UI.Reply exposing (Reply(..)) 30 - import UI.Svg.Elements 31 - import Url exposing (Url) 32 - import Url.Ext as Url 33 - import User.Layer exposing (..) 34 - 35 - 36 - 37 - -- ⛩ 38 - 39 - 40 - minimumPassphraseLength = 41 - 16 42 - 43 - 44 - passphraseLengthErrorMessage = 45 - "Your passphrase should be atleast *16 characters* long." 46 - 47 - 48 - 49 - -- 🌳 50 - 51 - 52 - type Model 53 - = Authenticated Method 54 - | InputScreen Method Question 55 - | NewEncryptionKeyScreen Method (Maybe String) 56 - | UpdateEncryptionKeyScreen Method (Maybe String) 57 - | Unauthenticated 58 - | Welcome 59 - 60 - 61 - type alias Question = 62 - { placeholder : String 63 - , question : String 64 - , value : String 65 - } 66 - 67 - 68 - initialModel : Url -> Model 69 - initialModel url = 70 - case Url.action url of 71 - [ "authenticate", "dropbox" ] -> 72 - url.fragment 73 - |> Maybe.map (String.split "&") 74 - |> Maybe.map (List.filter <| String.startsWith "access_token=") 75 - |> Maybe.andThen List.head 76 - |> Maybe.withDefault "" 77 - |> String.replace "access_token=" "" 78 - |> (\t -> 79 - NewEncryptionKeyScreen 80 - (Dropbox { token = t }) 81 - Nothing 82 - ) 83 - 84 - [ "authenticate", "remotestorage", encodedUserAddress ] -> 85 - let 86 - userAddress = 87 - encodedUserAddress 88 - |> Url.percentDecode 89 - |> Maybe.andThen (Base64.decode >> Result.toMaybe) 90 - |> Maybe.withDefault encodedUserAddress 91 - in 92 - url.fragment 93 - |> Maybe.map (String.split "&") 94 - |> Maybe.map (List.filter <| String.startsWith "access_token=") 95 - |> Maybe.andThen List.head 96 - |> Maybe.withDefault "" 97 - |> String.replace "access_token=" "" 98 - |> (\t -> 99 - NewEncryptionKeyScreen 100 - (RemoteStorage { userAddress = userAddress, token = t }) 101 - Nothing 102 - ) 103 - 104 - _ -> 105 - Welcome 106 - 107 - 108 - extractMethod : Model -> Maybe Method 109 - extractMethod model = 110 - case model of 111 - Authenticated method -> 112 - Just method 113 - 114 - InputScreen method _ -> 115 - Just method 116 - 117 - NewEncryptionKeyScreen method _ -> 118 - Just method 119 - 120 - UpdateEncryptionKeyScreen method _ -> 121 - Just method 122 - 123 - Unauthenticated -> 124 - Nothing 125 - 126 - Welcome -> 127 - Nothing 128 - 129 - 130 - 131 - -- 📣 132 - 133 - 134 - type Msg 135 - = Bypass 136 - | Cancel 137 - | GetStarted 138 - | ShowMoreOptions Mouse.Event 139 - | SignIn Method 140 - | SignInWithPassphrase Method String 141 - | SignedIn Method 142 - | TriggerExternalAuth Method String 143 - ----------------------------------------- 144 - -- Encryption 145 - ----------------------------------------- 146 - | KeepPassphraseInMemory String 147 - | RemoveEncryptionKey Method 148 - | ShowNewEncryptionKeyScreen Method 149 - | ShowUpdateEncryptionKeyScreen Method 150 - | UpdateEncryptionKey Method String 151 - ----------------------------------------- 152 - -- IPFS 153 - ----------------------------------------- 154 - | PingIpfs 155 - | PingIpfsCallback (Result Http.Error ()) 156 - | PingOtherIpfs String 157 - | PingOtherIpfsCallback String (Result Http.Error ()) 158 - ----------------------------------------- 159 - -- More Input 160 - ----------------------------------------- 161 - | AskForInput Method Question 162 - | Input String 163 - | ConfirmInput 164 - ----------------------------------------- 165 - -- Textile 166 - ----------------------------------------- 167 - | PingTextile 168 - | PingTextileCallback (Result Http.Error ()) 169 - | PingOtherTextile String 170 - | PingOtherTextileCallback String (Result Http.Error ()) 171 - 172 - 173 - update : Msg -> Model -> Return Model Msg Reply 174 - update msg model = 175 - case msg of 176 - Bypass -> 177 - return model 178 - 179 - Cancel -> 180 - ( case model of 181 - Authenticated method -> 182 - Authenticated method 183 - 184 - InputScreen _ _ -> 185 - Unauthenticated 186 - 187 - NewEncryptionKeyScreen _ _ -> 188 - Unauthenticated 189 - 190 - UpdateEncryptionKeyScreen method _ -> 191 - Authenticated method 192 - 193 - Unauthenticated -> 194 - Welcome 195 - 196 - Welcome -> 197 - Welcome 198 - -- 199 - , Cmd.none 200 - , [ ForceTracksRerender ] 201 - ) 202 - 203 - GetStarted -> 204 - return Unauthenticated 205 - 206 - ShowMoreOptions mouseEvent -> 207 - ( model 208 - , Cmd.none 209 - , ( mouseEvent.clientPos 210 - , mouseEvent.offsetPos 211 - ) 212 - |> (\( ( a, b ), ( c, d ) ) -> 213 - { x = a - c + 15 214 - , y = b - d + 12 215 - } 216 - ) 217 - |> ShowMoreAuthenticationOptions 218 - |> List.singleton 219 - ) 220 - 221 - SignIn method -> 222 - ( model 223 - -- 224 - , [ ( "method", encodeMethod method ) 225 - , ( "passphrase", Json.Encode.null ) 226 - ] 227 - |> Json.Encode.object 228 - |> Alien.broadcast Alien.SignIn 229 - |> Ports.toBrain 230 - -- 231 - , [ ToggleLoadingScreen On ] 232 - ) 233 - 234 - SignInWithPassphrase method passphrase -> 235 - if String.length passphrase < minimumPassphraseLength then 236 - addReply 237 - (ShowErrorNotification passphraseLengthErrorMessage) 238 - (return model) 239 - 240 - else 241 - ( model 242 - -- 243 - , [ ( "method", encodeMethod method ) 244 - , ( "passphrase", Json.Encode.string <| hashPassphrase passphrase ) 245 - ] 246 - |> Json.Encode.object 247 - |> Alien.broadcast Alien.SignIn 248 - |> Ports.toBrain 249 - -- 250 - , [ ToggleLoadingScreen On ] 251 - ) 252 - 253 - SignedIn method -> 254 - return (Authenticated method) 255 - 256 - TriggerExternalAuth method string -> 257 - returnReplyWithModel model (ExternalAuth method string) 258 - 259 - ----------------------------------------- 260 - -- Encryption 261 - ----------------------------------------- 262 - KeepPassphraseInMemory passphrase -> 263 - case model of 264 - NewEncryptionKeyScreen method _ -> 265 - return (NewEncryptionKeyScreen method <| Just passphrase) 266 - 267 - UpdateEncryptionKeyScreen method _ -> 268 - return (UpdateEncryptionKeyScreen method <| Just passphrase) 269 - 270 - _ -> 271 - return model 272 - 273 - RemoveEncryptionKey method -> 274 - Alien.RemoveEncryptionKey 275 - |> Alien.trigger 276 - |> Ports.toBrain 277 - |> returnCommandWithModel (Authenticated method) 278 - |> addReply ForceTracksRerender 279 - |> addReply (ShowSuccessNotification "Saving data without encryption ...") 280 - 281 - ShowNewEncryptionKeyScreen method -> 282 - return (NewEncryptionKeyScreen method Nothing) 283 - 284 - ShowUpdateEncryptionKeyScreen method -> 285 - return (UpdateEncryptionKeyScreen method Nothing) 286 - 287 - UpdateEncryptionKey method passphrase -> 288 - if String.length passphrase < minimumPassphraseLength then 289 - addReply 290 - (ShowErrorNotification passphraseLengthErrorMessage) 291 - (return model) 292 - 293 - else 294 - passphrase 295 - |> hashPassphrase 296 - |> Json.Encode.string 297 - |> Alien.broadcast Alien.UpdateEncryptionKey 298 - |> Ports.toBrain 299 - |> returnCommandWithModel (Authenticated method) 300 - |> addReply ForceTracksRerender 301 - |> addReply (ShowSuccessNotification "Encrypting data with new passphrase ...") 302 - 303 - ----------------------------------------- 304 - -- IPFS 305 - ----------------------------------------- 306 - PingIpfs -> 307 - { url = "//localhost:5001/api/v0/id" 308 - , expect = Http.expectWhatever PingIpfsCallback 309 - } 310 - |> Http.get 311 - |> returnCommandWithModel model 312 - 313 - PingIpfsCallback (Ok _) -> 314 - { apiOrigin = "//localhost:5001" } 315 - |> Ipfs 316 - |> ShowNewEncryptionKeyScreen 317 - |> updateWithModel model 318 - 319 - PingIpfsCallback (Err _) -> 320 - { placeholder = "//localhost:5001" 321 - , question = """ 322 - Where's your IPFS API located?<br /> 323 - <span class="font-normal text-white-60"> 324 - You can find this address on the IPFS Web UI.<br /> 325 - Most likely you'll also need to setup CORS.<br /> 326 - You can find the instructions for that 327 - <a href="about#CORS__IPFS" target="_blank" class="border-b border-current-color font-semibold inline-block leading-tight">here</a>. 328 - </span> 329 - """ 330 - , value = "//localhost:5001" 331 - } 332 - |> AskForInput (Ipfs { apiOrigin = "" }) 333 - |> updateWithModel model 334 - 335 - PingOtherIpfs origin -> 336 - { url = origin ++ "/api/v0/id" 337 - , expect = Http.expectWhatever (PingOtherIpfsCallback origin) 338 - } 339 - |> Http.get 340 - |> returnCommandWithModel model 341 - 342 - PingOtherIpfsCallback origin (Ok _) -> 343 - { apiOrigin = origin } 344 - |> Ipfs 345 - |> ShowNewEncryptionKeyScreen 346 - |> updateWithModel model 347 - 348 - PingOtherIpfsCallback origin (Err _) -> 349 - "Can't reach this IPFS API, maybe it's offline? Or I don't have access?" 350 - |> ShowErrorNotification 351 - |> returnReplyWithModel model 352 - 353 - ----------------------------------------- 354 - -- More Input 355 - ----------------------------------------- 356 - AskForInput method opts -> 357 - { placeholder = opts.placeholder 358 - , question = opts.question 359 - , value = opts.value 360 - } 361 - |> InputScreen method 362 - |> return 363 - 364 - Input string -> 365 - case model of 366 - InputScreen method opts -> 367 - return (InputScreen method { opts | value = string }) 368 - 369 - m -> 370 - return m 371 - 372 - ConfirmInput -> 373 - case model of 374 - InputScreen (Ipfs i) { value } -> 375 - value 376 - |> String.chopEnd "/" 377 - |> PingOtherIpfs 378 - |> updateWithModel model 379 - 380 - InputScreen (RemoteStorage r) { value } -> 381 - addReply 382 - (ExternalAuth (RemoteStorage r) value) 383 - (return model) 384 - 385 - InputScreen (Textile t) { value } -> 386 - value 387 - |> String.chopEnd "/" 388 - |> PingOtherTextile 389 - |> updateWithModel model 390 - 391 - _ -> 392 - return model 393 - 394 - ----------------------------------------- 395 - -- Textile 396 - ----------------------------------------- 397 - PingTextile -> 398 - { url = "//localhost:40600/api/v0/summary" 399 - , expect = Http.expectWhatever PingTextileCallback 400 - } 401 - |> Http.get 402 - |> returnCommandWithModel model 403 - 404 - PingTextileCallback (Ok _) -> 405 - { apiOrigin = "//localhost:40600" } 406 - |> Textile 407 - |> SignIn 408 - |> updateWithModel model 409 - 410 - PingTextileCallback (Err _) -> 411 - { placeholder = "//localhost:40600" 412 - , question = """ 413 - Where's your Textile API located?<br /> 414 - <span class="font-normal text-white-60"> 415 - You might need to do some CORS configuration.<br /> 416 - You can find the instructions for that 417 - <a href="about#CORS__Textile" target="_blank" class="border-b border-current-color font-semibold inline-block leading-tight">here</a>.<br /> 418 - You can't connect to a HTTP server while on HTTPS. 419 - </span> 420 - """ 421 - , value = "//localhost:40600" 422 - } 423 - |> AskForInput (Textile { apiOrigin = "" }) 424 - |> updateWithModel model 425 - 426 - PingOtherTextile origin -> 427 - { url = origin ++ "/api/v0/summary" 428 - , expect = Http.expectWhatever (PingOtherTextileCallback origin) 429 - } 430 - |> Http.get 431 - |> returnCommandWithModel model 432 - 433 - PingOtherTextileCallback origin (Ok _) -> 434 - { apiOrigin = origin } 435 - |> Textile 436 - |> SignIn 437 - |> updateWithModel model 438 - 439 - PingOtherTextileCallback origin (Err _) -> 440 - "Can't reach this Textile API, maybe it's offline? Or I don't have access?" 441 - |> ShowErrorNotification 442 - |> returnReplyWithModel model 443 - 444 - 445 - updateWithModel : Model -> Msg -> Return Model Msg Reply 446 - updateWithModel model msg = 447 - update msg model 448 - 449 - 450 - hashPassphrase : String -> String 451 - hashPassphrase phrase = 452 - phrase 453 - |> Binary.fromStringAsUtf8 454 - |> SHA.sha256 455 - |> Binary.toHex 456 - |> String.toLower 457 - 458 - 459 - 460 - -- 🗺 461 - 462 - 463 - view : Model -> Html Msg 464 - view model = 465 - chunk 466 - [ C.flex 467 - , C.flex_col 468 - , C.h_full 469 - , C.items_center 470 - ] 471 - [ brick 472 - [ style "height" "42%" ] 473 - [ C.flex 474 - , C.items_center 475 - , C.pb_8 476 - 477 - -- 478 - , C.md__pb_0 479 - ] 480 - [ -- Logo 481 - ------- 482 - chunk 483 - [ C.py_5, C.relative ] 484 - [ slab 485 - Html.img 486 - [ onClick Cancel 487 - , src "images/diffuse-light.svg" 488 - , width 190 489 - 490 - -- 491 - , case model of 492 - Welcome -> 493 - title "Diffuse" 494 - 495 - _ -> 496 - title "Go back" 497 - ] 498 - [ case model of 499 - Welcome -> 500 - C.cursor_default 501 - 502 - _ -> 503 - C.cursor_pointer 504 - ] 505 - [] 506 - 507 - -- Speech bubble 508 - ---------------- 509 - , case model of 510 - InputScreen _ { question } -> 511 - question 512 - |> String.lines 513 - |> List.map String.trimLeft 514 - |> String.join "\n" 515 - |> Markdown.toHtmlWith 516 - { githubFlavored = Nothing 517 - , defaultHighlighting = Nothing 518 - , sanitize = False 519 - , smartypants = True 520 - } 521 - [] 522 - |> speechBubble 523 - 524 - NewEncryptionKeyScreen _ _ -> 525 - [ text "I need a passphrase to encrypt your personal data." 526 - , lineBreak 527 - , inline 528 - [ C.font_normal, C.text_white_60 ] 529 - [ text "This'll prevent other people from reading your data." ] 530 - ] 531 - |> chunk [] 532 - |> speechBubble 533 - 534 - UpdateEncryptionKeyScreen _ _ -> 535 - [ text "I need a new passphrase to encrypt your personal data." 536 - , lineBreak 537 - , inline 538 - [ C.font_normal, C.text_white_60 ] 539 - [ text "This'll prevent other people from reading your data." ] 540 - ] 541 - |> chunk [] 542 - |> speechBubble 543 - 544 - Welcome -> 545 - [ text "Diffuse plays music" 546 - , inline [ C.not_italic, C.font_normal, C.mr_px ] [ text " ♫ " ] 547 - , inline [ C.font_normal, C.text_white_60 ] 548 - [ text "from your Dropbox," 549 - , lineBreak 550 - , text "IPFS node, Amazon S3 bucket, or any other" 551 - , lineBreak 552 - , text "cloud/distributed storage service you use." 553 - ] 554 - ] 555 - |> chunk [] 556 - |> speechBubble 557 - 558 - _ -> 559 - [ text "Where would you like to keep your personal data?" 560 - , lineBreak 561 - , inline 562 - [ C.font_normal, C.text_white_60 ] 563 - [ text "That's things like your favourites, your playlists, etc." 564 - , lineBreak 565 - , text "After this you'll be able add some music ♫" 566 - ] 567 - ] 568 - |> chunk [] 569 - |> speechBubble 570 - ] 571 - ] 572 - 573 - ----------------------------------------- 574 - -- Content 575 - ----------------------------------------- 576 - , case model of 577 - InputScreen method opts -> 578 - inputScreen opts 579 - 580 - NewEncryptionKeyScreen method pass -> 581 - encryptionKeyScreen 582 - { withEncryption = SignInWithPassphrase method (Maybe.withDefault "" pass) 583 - , withoutEncryption = SignIn method 584 - } 585 - 586 - UpdateEncryptionKeyScreen method pass -> 587 - encryptionKeyScreen 588 - { withEncryption = UpdateEncryptionKey method (Maybe.withDefault "" pass) 589 - , withoutEncryption = RemoveEncryptionKey method 590 - } 591 - 592 - Unauthenticated -> 593 - choicesScreen 594 - 595 - Authenticated _ -> 596 - choicesScreen 597 - 598 - Welcome -> 599 - welcomeScreen 600 - 601 - ----------------------------------------- 602 - -- Link to about page 603 - ----------------------------------------- 604 - , chunk 605 - [ C.antialiased 606 - , C.font_semibold 607 - , C.flex 608 - , C.flex_grow 609 - , C.items_end 610 - , C.leading_snug 611 - , C.pb_8 612 - , C.pt_3 613 - , C.text_sm 614 - ] 615 - [ slab 616 - a 617 - [ href "about" ] 618 - [ C.border_b 619 - , C.border_white_60 620 - , C.italic 621 - , C.no_underline 622 - , C.text_white_60 623 - ] 624 - [ text "More info" ] 625 - ] 626 - ] 627 - 628 - 629 - 630 - -- WELCOME 631 - 632 - 633 - welcomeScreen : Html Msg 634 - welcomeScreen = 635 - chunk 636 - [ C.mt_3 637 - , C.relative 638 - , C.z_10 639 - ] 640 - [ UI.Kit.buttonWithColor 641 - UI.Kit.Blank 642 - UI.Kit.Filled 643 - GetStarted 644 - (slab 645 - Html.span 646 - [ style "font-size" "13px" 647 - , style "letter-spacing" "0.25em" 648 - ] 649 - [ C.align_middle 650 - , C.inline_block 651 - , C.pt_px 652 - ] 653 - [ text "SIGN IN" ] 654 - ) 655 - ] 656 - 657 - 658 - 659 - -- CHOICES 660 - 661 - 662 - choicesScreen : Html Msg 663 - choicesScreen = 664 - chunk 665 - [ C.bg_white 666 - , C.rounded 667 - , C.px_4 668 - , C.py_2 669 - , C.relative 670 - , C.z_10 671 - 672 - -- Dark mode 673 - ------------ 674 - , C.dark__bg_darkest_hour 675 - ] 676 - [ choiceButton 677 - { action = ShowNewEncryptionKeyScreen Local 678 - , icon = Icons.web 679 - , infoLink = Nothing 680 - , label = "My Browser" 681 - , outOfOrder = False 682 - } 683 - , choiceButton 684 - { action = TriggerExternalAuth Blockstack "" 685 - , icon = \_ _ -> Svg.map never UI.Svg.Elements.blockstackLogo 686 - , infoLink = Just "https://blockstack.org" 687 - , label = "Blockstack" 688 - , outOfOrder = False 689 - } 690 - , choiceButton 691 - { action = TriggerExternalAuth (Dropbox { token = "" }) "" 692 - , icon = \_ _ -> Svg.map never UI.Svg.Elements.dropboxLogo 693 - , infoLink = Just "https://dropbox.com/" 694 - , label = "Dropbox" 695 - , outOfOrder = False 696 - } 697 - , choiceButton 698 - { action = 699 - AskForInput 700 - (RemoteStorage { userAddress = "", token = "" }) 701 - { placeholder = "example@5apps.com" 702 - , question = """ 703 - What's your user address? 704 - <span class="font-normal text-white-60"> 705 - <br />The format's 706 - <span class="font-semibold">username@server.domain</span> 707 - </span> 708 - """ 709 - , value = "" 710 - } 711 - , icon = \_ _ -> Svg.map never UI.Svg.Elements.remoteStorageLogo 712 - , infoLink = Just "https://remotestorage.io/" 713 - , label = "RemoteStorage" 714 - , outOfOrder = False 715 - } 716 - 717 - -- More options 718 - --------------- 719 - , chunk 720 - [ C.pb_px, C.pt_4, C.text_center ] 721 - [ slab 722 - Html.span 723 - [ title "More options" 724 - , Mouse.onClick ShowMoreOptions 725 - ] 726 - [ C.inline_block, C.px_1, C.cursor_pointer, C.leading_none ] 727 - [ chunk 728 - [ C.pointer_events_none ] 729 - [ Icons.more_horiz 22 Inherit ] 730 - ] 731 - ] 732 - ] 733 - 734 - 735 - choiceButton : 736 - { action : msg 737 - , icon : Int -> Coloring -> Svg msg 738 - , infoLink : Maybe String 739 - , label : String 740 - , outOfOrder : Bool 741 - } 742 - -> Html msg 743 - choiceButton { action, icon, infoLink, label, outOfOrder } = 744 - chunk 745 - [ C.border_b 746 - , C.border_gray_300 747 - , C.relative 748 - 749 - -- 750 - , C.last__border_b_0 751 - 752 - -- Dark mode 753 - ------------ 754 - , C.dark__border_base01 755 - ] 756 - [ ----------------------------------------- 757 - -- Button 758 - ----------------------------------------- 759 - slab 760 - button 761 - [ onClick action ] 762 - [ C.bg_transparent 763 - , C.cursor_pointer 764 - , C.flex 765 - , C.items_center 766 - , C.leading_none 767 - , C.min_w_tiny 768 - , C.outline_none 769 - , C.px_2 770 - , C.py_4 771 - , C.text_left 772 - , C.text_sm 773 - ] 774 - [ chunk 775 - [ C.flex 776 - , C.items_center 777 - 778 - -- 779 - , ifThenElse outOfOrder C.opacity_20 C.opacity_100 780 - ] 781 - [ inline 782 - [ C.inline_flex, C.mr_4 ] 783 - [ icon 16 Inherit ] 784 - , text label 785 - ] 786 - ] 787 - 788 - ----------------------------------------- 789 - -- Info icon 790 - ----------------------------------------- 791 - , case infoLink of 792 - Just link -> 793 - slab 794 - Html.a 795 - [ style "left" "100%" 796 - , style "top" "50%" 797 - , style "transform" "translateY(-50%)" 798 - 799 - -- 800 - , href link 801 - , target "_blank" 802 - , title ("Learn more about " ++ label) 803 - ] 804 - [ C.absolute 805 - , C.cursor_pointer 806 - , C.duration_100 807 - , C.leading_none 808 - , C.ml_4 809 - , C.minus_translate_y_half 810 - , C.opacity_40 811 - , C.pl_4 812 - , C.text_white 813 - , C.transition 814 - , C.transform 815 - 816 - -- 817 - , C.hocus__opacity_100 818 - ] 819 - [ Icons.help 17 Inherit ] 820 - 821 - Nothing -> 822 - nothing 823 - ] 824 - 825 - 826 - 827 - -- ENCRYPTION KEY 828 - 829 - 830 - encryptionKeyScreen : { withEncryption : Msg, withoutEncryption : Msg } -> Html Msg 831 - encryptionKeyScreen { withEncryption, withoutEncryption } = 832 - slab 833 - Html.form 834 - [ onSubmit withEncryption ] 835 - [ C.flex 836 - , C.flex_col 837 - , C.max_w_xs 838 - , C.px_3 839 - , C.w_screen 840 - 841 - -- 842 - , C.sm__px_0 843 - ] 844 - [ UI.Kit.textArea 845 - [ attribute "autocapitalize" "none" 846 - , attribute "autocomplete" "off" 847 - , attribute "autocorrect" "off" 848 - , attribute "rows" "4" 849 - , attribute "spellcheck" "false" 850 - 851 - -- 852 - , placeholder "anQLS9Usw24gxUi11IgVBg76z8SCWZgLKkoWIeJ1ClVmBHLRlaiA0CtvONVAMGritbgd3U45cPTxrhFU0WXaOAa8pVt186KyEccfUNyAq97" 853 - 854 - -- 855 - , Html.Events.onInput KeepPassphraseInMemory 856 - ] 857 - , UI.Kit.button 858 - UI.Kit.Filled 859 - Bypass 860 - (text "Continue") 861 - , brick 862 - [ onClickStopPropagation withoutEncryption ] 863 - [ C.cursor_pointer 864 - , C.flex 865 - , C.items_center 866 - , C.justify_center 867 - , C.leading_snug 868 - , C.mt_3 869 - , C.opacity_50 870 - , C.text_white 871 - , C.text_xs 872 - ] 873 - [ inline [ C.inline_block, C.leading_none, C.mr_2 ] [ Icons.warning 13 Inherit ] 874 - , text "Continue without encryption" 875 - ] 876 - ] 877 - 878 - 879 - 880 - -- INPUT SCREEN 881 - 882 - 883 - inputScreen : Question -> Html Msg 884 - inputScreen question = 885 - slab 886 - Html.form 887 - [ onSubmit ConfirmInput ] 888 - [ C.flex 889 - , C.flex_col 890 - , C.max_w_xs 891 - , C.px_3 892 - , C.w_screen 893 - 894 - -- 895 - , C.sm__px_0 896 - ] 897 - [ UI.Kit.textFieldAlt 898 - [ attribute "autocapitalize" "off" 899 - , placeholder question.placeholder 900 - , Html.Events.onInput Input 901 - , value question.value 902 - ] 903 - , UI.Kit.button 904 - UI.Kit.Filled 905 - Bypass 906 - (text "Continue") 907 - ] 908 - 909 - 910 - 911 - -- SPEECH BUBBLE 912 - 913 - 914 - speechBubble : Html msg -> Html msg 915 - speechBubble contents = 916 - chunk 917 - [ C.absolute 918 - , C.antialiased 919 - , C.bg_background 920 - , C.border_b 921 - , C.border_transparent 922 - , C.font_semibold 923 - , C.italic 924 - , C.leading_snug 925 - , C.left_half 926 - , C.max_w_screen 927 - , C.minus_translate_x_half 928 - , C.px_4 929 - , C.py_2 930 - , C.rounded 931 - , C.text_center 932 - , C.text_sm 933 - , C.text_white 934 - , C.top_full 935 - , C.transform 936 - , C.whitespace_no_wrap 937 - 938 - -- Dark mode 939 - ------------ 940 - , C.dark__bg_darkest_hour 941 - , C.dark__text_gray_600 942 - ] 943 - [ contents 944 - 945 - -- 946 - , brick 947 - speechBubbleArrowStyles 948 - [ C.absolute 949 - , C.h_0 950 - , C.left_half 951 - , C.minus_translate_x_half 952 - , C.minus_translate_y_full 953 - , C.top_0 954 - , C.transform 955 - , C.w_0 956 - ] 957 - [] 958 - ] 959 - 960 - 961 - 962 - -- 🖼 963 - 964 - 965 - speechBubbleArrowStyles : List (Html.Attribute msg) 966 - speechBubbleArrowStyles = 967 - let 968 - color = 969 - Color.toCssString UI.Kit.colors.background 970 - in 971 - [ style "border-color" ("transparent transparent " ++ color ++ " transparent") 972 - , style "border-width" "0 6px 5px 6px" 973 - ]
+30
src/Applications/UI/Authentication/Common.elm
··· 1 + module UI.Authentication.Common exposing (..) 2 + 3 + import UI.Authentication.Types exposing (..) 4 + import User.Layer exposing (Method) 5 + 6 + 7 + 8 + -- 🛠 9 + 10 + 11 + extractMethod : State -> Maybe Method 12 + extractMethod state = 13 + case state of 14 + Authenticated method -> 15 + Just method 16 + 17 + InputScreen method _ -> 18 + Just method 19 + 20 + NewEncryptionKeyScreen method _ -> 21 + Just method 22 + 23 + UpdateEncryptionKeyScreen method _ -> 24 + Just method 25 + 26 + Unauthenticated -> 27 + Nothing 28 + 29 + Welcome -> 30 + Nothing
+525 -3
src/Applications/UI/Authentication/State.elm
··· 1 1 module UI.Authentication.State exposing (..) 2 2 3 + import Alien 4 + import Base64 5 + import Binary 3 6 import Browser.Navigation as Nav 7 + import Chunky exposing (..) 4 8 import Common exposing (Switch(..)) 9 + import Conditional exposing (..) 10 + import Html exposing (a) 11 + import Html.Attributes exposing (placeholder, value) 12 + import Html.Events.Extra.Mouse as Mouse 5 13 import Http 6 14 import Json.Decode as Json 15 + import Json.Encode 16 + import Lens.Ext as Lens 17 + import Management 18 + import Material.Icons.Types exposing (Coloring(..)) 19 + import Maybe.Extra as Maybe 20 + import Monocle.Lens as Lens exposing (Lens) 7 21 import Notifications 8 22 import Return exposing (andThen, return) 9 23 import Return.Ext as Return 24 + import SHA 25 + import String.Ext as String 26 + import UI.Authentication.Types as Authentication exposing (..) 10 27 import UI.Common.State as Common exposing (showNotification, showNotificationWithModel) 28 + import UI.Interface.State as Interface 29 + import UI.Ports as Ports 11 30 import UI.Reply as Reply exposing (Reply(..)) 12 31 import UI.Reply.Translate as Reply 13 - import UI.Types as UI exposing (Manager, Msg(..)) 32 + import UI.Types as UI exposing (..) 33 + import Url exposing (Url) 34 + import Url.Ext as Url 35 + import User.Layer exposing (..) 14 36 import User.Layer.Methods.RemoteStorage as RemoteStorage 37 + 38 + 39 + 40 + -- ⛩ 41 + 42 + 43 + minimumPassphraseLength = 44 + 16 45 + 46 + 47 + passphraseLengthErrorMessage = 48 + "Your passphrase should be atleast *16 characters* long." 49 + 50 + 51 + 52 + -- 🌳 53 + 54 + 55 + initialModel : Url -> Authentication.State 56 + initialModel url = 57 + case Url.action url of 58 + [ "authenticate", "dropbox" ] -> 59 + url.fragment 60 + |> Maybe.map (String.split "&") 61 + |> Maybe.map (List.filter <| String.startsWith "access_token=") 62 + |> Maybe.andThen List.head 63 + |> Maybe.withDefault "" 64 + |> String.replace "access_token=" "" 65 + |> (\t -> 66 + NewEncryptionKeyScreen 67 + (Dropbox { token = t }) 68 + Nothing 69 + ) 70 + 71 + [ "authenticate", "remotestorage", encodedUserAddress ] -> 72 + let 73 + userAddress = 74 + encodedUserAddress 75 + |> Url.percentDecode 76 + |> Maybe.andThen (Base64.decode >> Result.toMaybe) 77 + |> Maybe.withDefault encodedUserAddress 78 + in 79 + url.fragment 80 + |> Maybe.map (String.split "&") 81 + |> Maybe.map (List.filter <| String.startsWith "access_token=") 82 + |> Maybe.andThen List.head 83 + |> Maybe.withDefault "" 84 + |> String.replace "access_token=" "" 85 + |> (\t -> 86 + NewEncryptionKeyScreen 87 + (RemoteStorage { userAddress = userAddress, token = t }) 88 + Nothing 89 + ) 90 + 91 + _ -> 92 + Welcome 93 + 94 + 95 + lens : Lens UI.Model Authentication.State 96 + lens = 97 + { get = .authentication 98 + , set = \a m -> { m | authentication = a } 99 + } 100 + 101 + 102 + 103 + -- 📣 104 + 105 + 106 + update : Authentication.Msg -> Manager 107 + update msg = 108 + case msg of 109 + Authentication.Bypass -> 110 + Return.singleton 111 + 112 + CancelFlow -> 113 + cancelFlow 114 + 115 + GetStarted -> 116 + startFlow 117 + 118 + ShowMoreOptions a -> 119 + showMoreOptions a 120 + 121 + SignIn a -> 122 + signIn a 123 + 124 + SignInWithPassphrase a b -> 125 + signInWithPassphrase a b 126 + 127 + SignedIn a -> 128 + signedIn a 129 + 130 + TriggerExternalAuth a b -> 131 + externalAuth a b 132 + 133 + ----------------------------------------- 134 + -- Encryption 135 + ----------------------------------------- 136 + KeepPassphraseInMemory a -> 137 + keepPassphraseInMemory a 138 + 139 + RemoveEncryptionKey a -> 140 + removeEncryptionKey a 141 + 142 + ShowNewEncryptionKeyScreen a -> 143 + showNewEncryptionKeyScreen a 144 + 145 + Authentication.ShowUpdateEncryptionKeyScreen a -> 146 + showUpdateEncryptionKeyScreen a 147 + 148 + UpdateEncryptionKey a b -> 149 + updateEncryptionKey a b 150 + 151 + ----------------------------------------- 152 + -- IPFS 153 + ----------------------------------------- 154 + PingIpfs -> 155 + pingIpfs 156 + 157 + PingIpfsCallback a -> 158 + pingIpfsCallback a 159 + 160 + PingOtherIpfs a -> 161 + pingOtherIpfs a 162 + 163 + PingOtherIpfsCallback a b -> 164 + pingOtherIpfsCallback a b 165 + 166 + ----------------------------------------- 167 + -- More Input 168 + ----------------------------------------- 169 + AskForInput a b -> 170 + askForInput a b 171 + 172 + Input a -> 173 + input a 174 + 175 + ConfirmInput -> 176 + confirmInput 177 + 178 + ----------------------------------------- 179 + -- Textile 180 + ----------------------------------------- 181 + PingTextile -> 182 + pingTextile 183 + 184 + PingTextileCallback a -> 185 + pingTextileCallback a 186 + 187 + PingOtherTextile a -> 188 + pingOtherTextile a 189 + 190 + PingOtherTextileCallback a b -> 191 + pingOtherTextileCallback a b 192 + 193 + 194 + organize : Organizer Authentication.State -> Manager 195 + organize = 196 + Management.organize lens 197 + 198 + 199 + replaceState : Authentication.State -> Manager 200 + replaceState state = 201 + lens.set state >> Return.singleton 15 202 16 203 17 204 18 205 -- 🔱 19 206 20 207 21 - authenticationBootFailure : String -> Manager 22 - authenticationBootFailure err model = 208 + bootFailure : String -> Manager 209 + bootFailure err model = 23 210 model 24 211 |> showNotification (Notifications.error err) 25 212 |> andThen (Reply.translate LoadDefaultBackdrop) 26 213 27 214 215 + cancelFlow : Manager 216 + cancelFlow model = 217 + (\state -> 218 + case state of 219 + Authenticated method -> 220 + Authenticated method 221 + 222 + InputScreen _ _ -> 223 + Unauthenticated 224 + 225 + NewEncryptionKeyScreen _ _ -> 226 + Unauthenticated 227 + 228 + UpdateEncryptionKeyScreen method _ -> 229 + Authenticated method 230 + 231 + Unauthenticated -> 232 + Welcome 233 + 234 + Welcome -> 235 + Welcome 236 + ) 237 + |> Lens.adjust lens model 238 + |> Return.singleton 239 + |> Return.andThen (Reply.translate Reply.ForceTracksRerender) 240 + 241 + 242 + externalAuth : Method -> String -> Manager 243 + externalAuth method string model = 244 + string 245 + |> Reply.ExternalAuth method 246 + |> Reply.translateWithModel model 247 + 248 + 28 249 missingSecretKey : Json.Value -> Manager 29 250 missingSecretKey _ model = 30 251 "There seems to be existing data that's encrypted, I will need the passphrase (ie. encryption key) to continue." ··· 75 296 RemoteStorage.webfingerError 76 297 |> Notifications.error 77 298 |> showNotificationWithModel model 299 + 300 + 301 + showMoreOptions : Mouse.Event -> Manager 302 + showMoreOptions mouseEvent = 303 + ( mouseEvent.clientPos 304 + , mouseEvent.offsetPos 305 + ) 306 + |> (\( ( a, b ), ( c, d ) ) -> 307 + { x = a - c + 15 308 + , y = b - d + 12 309 + } 310 + ) 311 + |> ShowMoreAuthenticationOptions 312 + |> Reply 313 + |> Return.performance 314 + 315 + 316 + signedIn : Method -> Manager 317 + signedIn method = 318 + replaceState (Authenticated method) 319 + 320 + 321 + signIn : Method -> Manager 322 + signIn method model = 323 + [ ( "method", encodeMethod method ) 324 + , ( "passphrase", Json.Encode.null ) 325 + ] 326 + |> Json.Encode.object 327 + |> Alien.broadcast Alien.SignIn 328 + |> Ports.toBrain 329 + -- 330 + |> Return.return model 331 + |> Return.andThen (Interface.toggleLoadingScreen On) 332 + 333 + 334 + signInWithPassphrase : Method -> String -> Manager 335 + signInWithPassphrase method passphrase model = 336 + if String.length passphrase < minimumPassphraseLength then 337 + passphraseLengthErrorMessage 338 + |> Notifications.error 339 + |> Common.showNotificationWithModel model 340 + 341 + else 342 + [ ( "method", encodeMethod method ) 343 + , ( "passphrase", Json.Encode.string <| hashPassphrase passphrase ) 344 + ] 345 + |> Json.Encode.object 346 + |> Alien.broadcast Alien.SignIn 347 + |> Ports.toBrain 348 + -- 349 + |> Return.return model 350 + |> Return.andThen (Interface.toggleLoadingScreen On) 351 + 352 + 353 + startFlow : Manager 354 + startFlow = 355 + replaceState Unauthenticated 356 + 357 + 358 + 359 + -- ENCRYPTION 360 + 361 + 362 + keepPassphraseInMemory : String -> Manager 363 + keepPassphraseInMemory passphrase model = 364 + (\state -> 365 + case state of 366 + NewEncryptionKeyScreen method _ -> 367 + NewEncryptionKeyScreen method (Just passphrase) 368 + 369 + UpdateEncryptionKeyScreen method _ -> 370 + UpdateEncryptionKeyScreen method (Just passphrase) 371 + 372 + s -> 373 + s 374 + ) 375 + |> Lens.adjust lens model 376 + |> Return.singleton 377 + 378 + 379 + removeEncryptionKey : Method -> Manager 380 + removeEncryptionKey method model = 381 + Alien.RemoveEncryptionKey 382 + |> Alien.trigger 383 + |> Ports.toBrain 384 + -- 385 + |> Return.return (lens.set (Authenticated method) model) 386 + |> Return.andThen (Common.showNotification <| Notifications.success "Saving data without encryption ...") 387 + |> Return.andThen (Reply.translate ForceTracksRerender) 388 + 389 + 390 + showNewEncryptionKeyScreen : Method -> Manager 391 + showNewEncryptionKeyScreen method = 392 + replaceState (NewEncryptionKeyScreen method Nothing) 393 + 394 + 395 + showUpdateEncryptionKeyScreen : Method -> Manager 396 + showUpdateEncryptionKeyScreen method = 397 + replaceState (UpdateEncryptionKeyScreen method Nothing) 398 + 399 + 400 + updateEncryptionKey : Method -> String -> Manager 401 + updateEncryptionKey method passphrase model = 402 + if String.length passphrase < minimumPassphraseLength then 403 + passphraseLengthErrorMessage 404 + |> Notifications.error 405 + |> Common.showNotificationWithModel model 406 + 407 + else 408 + passphrase 409 + |> hashPassphrase 410 + |> Json.Encode.string 411 + |> Alien.broadcast Alien.UpdateEncryptionKey 412 + |> Ports.toBrain 413 + -- 414 + |> Return.return (lens.set (Authenticated method) model) 415 + |> Return.andThen (Common.showNotification <| Notifications.success "Encrypting data with new passphrase ...") 416 + |> Return.andThen (Reply.translate ForceTracksRerender) 417 + 418 + 419 + 420 + -- IPFS 421 + 422 + 423 + pingIpfs : Manager 424 + pingIpfs model = 425 + { url = "//localhost:5001/api/v0/id" 426 + , expect = Http.expectWhatever (AuthenticationMsg << PingIpfsCallback) 427 + } 428 + |> Http.get 429 + |> return model 430 + 431 + 432 + pingIpfsCallback : Result Http.Error () -> Manager 433 + pingIpfsCallback result = 434 + case result of 435 + Ok _ -> 436 + { apiOrigin = "//localhost:5001" } 437 + |> Ipfs 438 + |> showNewEncryptionKeyScreen 439 + 440 + Err _ -> 441 + askForInput 442 + (Ipfs { apiOrigin = "" }) 443 + { placeholder = "//localhost:5001" 444 + , question = """ 445 + Where's your IPFS API located?<br /> 446 + <span class="font-normal text-white-60"> 447 + You can find this address on the IPFS Web UI.<br /> 448 + Most likely you'll also need to setup CORS.<br /> 449 + You can find the instructions for that 450 + <a href="about#CORS__IPFS" target="_blank" class="border-b border-current-color font-semibold inline-block leading-tight">here</a>. 451 + </span> 452 + """ 453 + , value = "//localhost:5001" 454 + } 455 + 456 + 457 + pingOtherIpfs : String -> Manager 458 + pingOtherIpfs origin model = 459 + { url = origin ++ "/api/v0/id" 460 + , expect = Http.expectWhatever (AuthenticationMsg << PingOtherIpfsCallback origin) 461 + } 462 + |> Http.get 463 + |> return model 464 + 465 + 466 + pingOtherIpfsCallback : String -> Result Http.Error () -> Manager 467 + pingOtherIpfsCallback origin result = 468 + case result of 469 + Ok _ -> 470 + { apiOrigin = origin } 471 + |> Ipfs 472 + |> showNewEncryptionKeyScreen 473 + 474 + Err _ -> 475 + "Can't reach this IPFS API, maybe it's offline? Or I don't have access?" 476 + |> Notifications.error 477 + |> Common.showNotification 478 + 479 + 480 + 481 + -- MORE INPUT 482 + 483 + 484 + askForInput : Method -> Question -> Manager 485 + askForInput method q = 486 + { placeholder = q.placeholder 487 + , question = q.question 488 + , value = q.value 489 + } 490 + |> InputScreen method 491 + |> replaceState 492 + 493 + 494 + input : String -> Manager 495 + input string model = 496 + (\state -> 497 + case state of 498 + InputScreen method opts -> 499 + InputScreen method { opts | value = string } 500 + 501 + s -> 502 + s 503 + ) 504 + |> Lens.adjust lens model 505 + |> Return.singleton 506 + 507 + 508 + confirmInput : Manager 509 + confirmInput model = 510 + case lens.get model of 511 + InputScreen (Ipfs i) { value } -> 512 + pingOtherIpfs (String.chopEnd "/" value) model 513 + 514 + InputScreen (RemoteStorage r) { value } -> 515 + -- TODO: 516 + -- addReply 517 + -- (ExternalAuth (RemoteStorage r) value) 518 + -- (return model) 519 + Return.singleton model 520 + 521 + InputScreen (Textile t) { value } -> 522 + pingOtherTextile (String.chopEnd "/" value) model 523 + 524 + _ -> 525 + Return.singleton model 526 + 527 + 528 + 529 + -- TEXTILE 530 + 531 + 532 + pingTextile : Manager 533 + pingTextile model = 534 + { url = "//localhost:40600/api/v0/summary" 535 + , expect = Http.expectWhatever (AuthenticationMsg << PingTextileCallback) 536 + } 537 + |> Http.get 538 + |> return model 539 + 540 + 541 + pingTextileCallback : Result Http.Error () -> Manager 542 + pingTextileCallback result = 543 + case result of 544 + Ok _ -> 545 + { apiOrigin = "//localhost:40600" } 546 + |> Textile 547 + |> signIn 548 + 549 + Err _ -> 550 + askForInput 551 + (Textile { apiOrigin = "" }) 552 + { placeholder = "//localhost:40600" 553 + , question = """ 554 + Where's your Textile API located?<br /> 555 + <span class="font-normal text-white-60"> 556 + You might need to do some CORS configuration.<br /> 557 + You can find the instructions for that 558 + <a href="about#CORS__Textile" target="_blank" class="border-b border-current-color font-semibold inline-block leading-tight">here</a>.<br /> 559 + You can't connect to a HTTP server while on HTTPS. 560 + </span> 561 + """ 562 + , value = "//localhost:40600" 563 + } 564 + 565 + 566 + pingOtherTextile : String -> Manager 567 + pingOtherTextile origin model = 568 + { url = origin ++ "/api/v0/summary" 569 + , expect = Http.expectWhatever (AuthenticationMsg << PingOtherTextileCallback origin) 570 + } 571 + |> Http.get 572 + |> return model 573 + 574 + 575 + pingOtherTextileCallback : String -> Result Http.Error () -> Manager 576 + pingOtherTextileCallback origin result = 577 + case result of 578 + Ok _ -> 579 + { apiOrigin = origin } 580 + |> Textile 581 + |> signIn 582 + 583 + Err _ -> 584 + "Can't reach this Textile API, maybe it's offline? Or I don't have access?" 585 + |> Notifications.error 586 + |> Common.showNotification 587 + 588 + 589 + 590 + -- 🛠 591 + 592 + 593 + hashPassphrase : String -> String 594 + hashPassphrase phrase = 595 + phrase 596 + |> Binary.fromStringAsUtf8 597 + |> SHA.sha256 598 + |> Binary.toHex 599 + |> String.toLower
+68
src/Applications/UI/Authentication/Types.elm
··· 1 + module UI.Authentication.Types exposing (Msg(..), Question, State(..)) 2 + 3 + import Html.Events.Extra.Mouse as Mouse 4 + import Http 5 + import User.Layer exposing (Method(..)) 6 + 7 + 8 + 9 + -- 🌳 10 + 11 + 12 + type State 13 + = Authenticated Method 14 + | InputScreen Method Question 15 + | NewEncryptionKeyScreen Method (Maybe String) 16 + | UpdateEncryptionKeyScreen Method (Maybe String) 17 + | Unauthenticated 18 + | Welcome 19 + 20 + 21 + type alias Question = 22 + { placeholder : String 23 + , question : String 24 + , value : String 25 + } 26 + 27 + 28 + 29 + -- 📣 30 + 31 + 32 + type Msg 33 + = Bypass 34 + | CancelFlow 35 + | GetStarted 36 + | ShowMoreOptions Mouse.Event 37 + | SignIn Method 38 + | SignInWithPassphrase Method String 39 + | SignedIn Method 40 + | TriggerExternalAuth Method String 41 + ----------------------------------------- 42 + -- Encryption 43 + ----------------------------------------- 44 + | KeepPassphraseInMemory String 45 + | RemoveEncryptionKey Method 46 + | ShowNewEncryptionKeyScreen Method 47 + | ShowUpdateEncryptionKeyScreen Method 48 + | UpdateEncryptionKey Method String 49 + ----------------------------------------- 50 + -- IPFS 51 + ----------------------------------------- 52 + | PingIpfs 53 + | PingIpfsCallback (Result Http.Error ()) 54 + | PingOtherIpfs String 55 + | PingOtherIpfsCallback String (Result Http.Error ()) 56 + ----------------------------------------- 57 + -- More Input 58 + ----------------------------------------- 59 + | AskForInput Method Question 60 + | Input String 61 + | ConfirmInput 62 + ----------------------------------------- 63 + -- Textile 64 + ----------------------------------------- 65 + | PingTextile 66 + | PingTextileCallback (Result Http.Error ()) 67 + | PingOtherTextile String 68 + | PingOtherTextileCallback String (Result Http.Error ())
+549
src/Applications/UI/Authentication/View.elm
··· 1 + module UI.Authentication.View exposing (view) 2 + 3 + import Chunky exposing (..) 4 + import Color 5 + import Color.Ext as Color 6 + import Common exposing (Switch(..)) 7 + import Conditional exposing (..) 8 + import Css.Classes as C 9 + import Html exposing (Html, a, button, text) 10 + import Html.Attributes exposing (attribute, href, placeholder, src, style, target, title, value, width) 11 + import Html.Events exposing (onClick, onSubmit) 12 + import Html.Events.Extra exposing (onClickStopPropagation) 13 + import Html.Events.Extra.Mouse as Mouse 14 + import Html.Lazy as Lazy 15 + import Markdown 16 + import Material.Icons as Icons 17 + import Material.Icons.Types exposing (Coloring(..)) 18 + import Maybe.Extra as Maybe 19 + import Return3 exposing (..) 20 + import String.Ext as String 21 + import Svg exposing (Svg) 22 + import UI.Authentication.Types as Authentication exposing (..) 23 + import UI.Kit 24 + import UI.Reply exposing (Reply(..)) 25 + import UI.Svg.Elements 26 + import UI.Types as UI exposing (..) 27 + import User.Layer exposing (..) 28 + 29 + 30 + 31 + -- 🗺 32 + 33 + 34 + view : Model -> Html UI.Msg 35 + view = 36 + Html.map AuthenticationMsg << Lazy.lazy view_ << .authentication 37 + 38 + 39 + view_ : State -> Html Authentication.Msg 40 + view_ state = 41 + chunk 42 + [ C.flex 43 + , C.flex_col 44 + , C.h_full 45 + , C.items_center 46 + ] 47 + [ brick 48 + [ style "height" "42%" ] 49 + [ C.flex 50 + , C.items_center 51 + , C.pb_8 52 + 53 + -- 54 + , C.md__pb_0 55 + ] 56 + [ -- Logo 57 + ------- 58 + chunk 59 + [ C.py_5, C.relative ] 60 + [ slab 61 + Html.img 62 + [ onClick CancelFlow 63 + , src "images/diffuse-light.svg" 64 + , width 190 65 + 66 + -- 67 + , case state of 68 + Welcome -> 69 + title "Diffuse" 70 + 71 + _ -> 72 + title "Go back" 73 + ] 74 + [ case state of 75 + Welcome -> 76 + C.cursor_default 77 + 78 + _ -> 79 + C.cursor_pointer 80 + ] 81 + [] 82 + 83 + -- Speech bubble 84 + ---------------- 85 + , case state of 86 + InputScreen _ { question } -> 87 + question 88 + |> String.lines 89 + |> List.map String.trimLeft 90 + |> String.join "\n" 91 + |> Markdown.toHtmlWith 92 + { githubFlavored = Nothing 93 + , defaultHighlighting = Nothing 94 + , sanitize = False 95 + , smartypants = True 96 + } 97 + [] 98 + |> speechBubble 99 + 100 + NewEncryptionKeyScreen _ _ -> 101 + [ text "I need a passphrase to encrypt your personal data." 102 + , lineBreak 103 + , inline 104 + [ C.font_normal, C.text_white_60 ] 105 + [ text "This'll prevent other people from reading your data." ] 106 + ] 107 + |> chunk [] 108 + |> speechBubble 109 + 110 + UpdateEncryptionKeyScreen _ _ -> 111 + [ text "I need a new passphrase to encrypt your personal data." 112 + , lineBreak 113 + , inline 114 + [ C.font_normal, C.text_white_60 ] 115 + [ text "This'll prevent other people from reading your data." ] 116 + ] 117 + |> chunk [] 118 + |> speechBubble 119 + 120 + Welcome -> 121 + [ text "Diffuse plays music" 122 + , inline [ C.not_italic, C.font_normal, C.mr_px ] [ text " ♫ " ] 123 + , inline [ C.font_normal, C.text_white_60 ] 124 + [ text "from your Dropbox," 125 + , lineBreak 126 + , text "IPFS node, Amazon S3 bucket, or any other" 127 + , lineBreak 128 + , text "cloud/distributed storage service you use." 129 + ] 130 + ] 131 + |> chunk [] 132 + |> speechBubble 133 + 134 + _ -> 135 + [ text "Where would you like to keep your personal data?" 136 + , lineBreak 137 + , inline 138 + [ C.font_normal, C.text_white_60 ] 139 + [ text "That's things like your favourites, your playlists, etc." 140 + , lineBreak 141 + , text "After this you'll be able add some music ♫" 142 + ] 143 + ] 144 + |> chunk [] 145 + |> speechBubble 146 + ] 147 + ] 148 + 149 + ----------------------------------------- 150 + -- Content 151 + ----------------------------------------- 152 + , case state of 153 + InputScreen method opts -> 154 + inputScreen opts 155 + 156 + NewEncryptionKeyScreen method pass -> 157 + encryptionKeyScreen 158 + { withEncryption = SignInWithPassphrase method (Maybe.withDefault "" pass) 159 + , withoutEncryption = SignIn method 160 + } 161 + 162 + UpdateEncryptionKeyScreen method pass -> 163 + encryptionKeyScreen 164 + { withEncryption = UpdateEncryptionKey method (Maybe.withDefault "" pass) 165 + , withoutEncryption = RemoveEncryptionKey method 166 + } 167 + 168 + Unauthenticated -> 169 + choicesScreen 170 + 171 + Authenticated _ -> 172 + choicesScreen 173 + 174 + Welcome -> 175 + welcomeScreen 176 + 177 + ----------------------------------------- 178 + -- Link to about page 179 + ----------------------------------------- 180 + , chunk 181 + [ C.antialiased 182 + , C.font_semibold 183 + , C.flex 184 + , C.flex_grow 185 + , C.items_end 186 + , C.leading_snug 187 + , C.pb_8 188 + , C.pt_3 189 + , C.text_sm 190 + ] 191 + [ slab 192 + a 193 + [ href "about" ] 194 + [ C.border_b 195 + , C.border_white_60 196 + , C.italic 197 + , C.no_underline 198 + , C.text_white_60 199 + ] 200 + [ text "More info" ] 201 + ] 202 + ] 203 + 204 + 205 + 206 + -- WELCOME 207 + 208 + 209 + welcomeScreen : Html Authentication.Msg 210 + welcomeScreen = 211 + chunk 212 + [ C.mt_3 213 + , C.relative 214 + , C.z_10 215 + ] 216 + [ UI.Kit.buttonWithColor 217 + UI.Kit.Blank 218 + UI.Kit.Filled 219 + GetStarted 220 + (slab 221 + Html.span 222 + [ style "font-size" "13px" 223 + , style "letter-spacing" "0.25em" 224 + ] 225 + [ C.align_middle 226 + , C.inline_block 227 + , C.pt_px 228 + ] 229 + [ text "SIGN IN" ] 230 + ) 231 + ] 232 + 233 + 234 + 235 + -- CHOICES 236 + 237 + 238 + choicesScreen : Html Authentication.Msg 239 + choicesScreen = 240 + chunk 241 + [ C.bg_white 242 + , C.rounded 243 + , C.px_4 244 + , C.py_2 245 + , C.relative 246 + , C.z_10 247 + 248 + -- Dark mode 249 + ------------ 250 + , C.dark__bg_darkest_hour 251 + ] 252 + [ choiceButton 253 + { action = ShowNewEncryptionKeyScreen Local 254 + , icon = Icons.web 255 + , infoLink = Nothing 256 + , label = "My Browser" 257 + , outOfOrder = False 258 + } 259 + , choiceButton 260 + { action = TriggerExternalAuth Blockstack "" 261 + , icon = \_ _ -> Svg.map never UI.Svg.Elements.blockstackLogo 262 + , infoLink = Just "https://blockstack.org" 263 + , label = "Blockstack" 264 + , outOfOrder = False 265 + } 266 + , choiceButton 267 + { action = TriggerExternalAuth (Dropbox { token = "" }) "" 268 + , icon = \_ _ -> Svg.map never UI.Svg.Elements.dropboxLogo 269 + , infoLink = Just "https://dropbox.com/" 270 + , label = "Dropbox" 271 + , outOfOrder = False 272 + } 273 + , choiceButton 274 + { action = 275 + AskForInput 276 + (RemoteStorage { userAddress = "", token = "" }) 277 + { placeholder = "example@5apps.com" 278 + , question = """ 279 + What's your user address? 280 + <span class="font-normal text-white-60"> 281 + <br />The format's 282 + <span class="font-semibold">username@server.domain</span> 283 + </span> 284 + """ 285 + , value = "" 286 + } 287 + , icon = \_ _ -> Svg.map never UI.Svg.Elements.remoteStorageLogo 288 + , infoLink = Just "https://remotestorage.io/" 289 + , label = "RemoteStorage" 290 + , outOfOrder = False 291 + } 292 + 293 + -- More options 294 + --------------- 295 + , chunk 296 + [ C.pb_px, C.pt_4, C.text_center ] 297 + [ slab 298 + Html.span 299 + [ title "More options" 300 + , Mouse.onClick ShowMoreOptions 301 + ] 302 + [ C.inline_block, C.px_1, C.cursor_pointer, C.leading_none ] 303 + [ chunk 304 + [ C.pointer_events_none ] 305 + [ Icons.more_horiz 22 Inherit ] 306 + ] 307 + ] 308 + ] 309 + 310 + 311 + choiceButton : 312 + { action : msg 313 + , icon : Int -> Coloring -> Svg msg 314 + , infoLink : Maybe String 315 + , label : String 316 + , outOfOrder : Bool 317 + } 318 + -> Html msg 319 + choiceButton { action, icon, infoLink, label, outOfOrder } = 320 + chunk 321 + [ C.border_b 322 + , C.border_gray_300 323 + , C.relative 324 + 325 + -- 326 + , C.last__border_b_0 327 + 328 + -- Dark mode 329 + ------------ 330 + , C.dark__border_base01 331 + ] 332 + [ ----------------------------------------- 333 + -- Button 334 + ----------------------------------------- 335 + slab 336 + button 337 + [ onClick action ] 338 + [ C.bg_transparent 339 + , C.cursor_pointer 340 + , C.flex 341 + , C.items_center 342 + , C.leading_none 343 + , C.min_w_tiny 344 + , C.outline_none 345 + , C.px_2 346 + , C.py_4 347 + , C.text_left 348 + , C.text_sm 349 + ] 350 + [ chunk 351 + [ C.flex 352 + , C.items_center 353 + 354 + -- 355 + , ifThenElse outOfOrder C.opacity_20 C.opacity_100 356 + ] 357 + [ inline 358 + [ C.inline_flex, C.mr_4 ] 359 + [ icon 16 Inherit ] 360 + , text label 361 + ] 362 + ] 363 + 364 + ----------------------------------------- 365 + -- Info icon 366 + ----------------------------------------- 367 + , case infoLink of 368 + Just link -> 369 + slab 370 + Html.a 371 + [ style "left" "100%" 372 + , style "top" "50%" 373 + , style "transform" "translateY(-50%)" 374 + 375 + -- 376 + , href link 377 + , target "_blank" 378 + , title ("Learn more about " ++ label) 379 + ] 380 + [ C.absolute 381 + , C.cursor_pointer 382 + , C.duration_100 383 + , C.leading_none 384 + , C.ml_4 385 + , C.minus_translate_y_half 386 + , C.opacity_40 387 + , C.pl_4 388 + , C.text_white 389 + , C.transition 390 + , C.transform 391 + 392 + -- 393 + , C.hocus__opacity_100 394 + ] 395 + [ Icons.help 17 Inherit ] 396 + 397 + Nothing -> 398 + nothing 399 + ] 400 + 401 + 402 + 403 + -- ENCRYPTION KEY 404 + 405 + 406 + encryptionKeyScreen : { withEncryption : Authentication.Msg, withoutEncryption : Authentication.Msg } -> Html Authentication.Msg 407 + encryptionKeyScreen { withEncryption, withoutEncryption } = 408 + slab 409 + Html.form 410 + [ onSubmit withEncryption ] 411 + [ C.flex 412 + , C.flex_col 413 + , C.max_w_xs 414 + , C.px_3 415 + , C.w_screen 416 + 417 + -- 418 + , C.sm__px_0 419 + ] 420 + [ UI.Kit.textArea 421 + [ attribute "autocapitalize" "none" 422 + , attribute "autocomplete" "off" 423 + , attribute "autocorrect" "off" 424 + , attribute "rows" "4" 425 + , attribute "spellcheck" "false" 426 + 427 + -- 428 + , placeholder "anQLS9Usw24gxUi11IgVBg76z8SCWZgLKkoWIeJ1ClVmBHLRlaiA0CtvONVAMGritbgd3U45cPTxrhFU0WXaOAa8pVt186KyEccfUNyAq97" 429 + 430 + -- 431 + , Html.Events.onInput KeepPassphraseInMemory 432 + ] 433 + , UI.Kit.button 434 + UI.Kit.Filled 435 + Authentication.Bypass 436 + (text "Continue") 437 + , brick 438 + [ onClickStopPropagation withoutEncryption ] 439 + [ C.cursor_pointer 440 + , C.flex 441 + , C.items_center 442 + , C.justify_center 443 + , C.leading_snug 444 + , C.mt_3 445 + , C.opacity_50 446 + , C.text_white 447 + , C.text_xs 448 + ] 449 + [ inline [ C.inline_block, C.leading_none, C.mr_2 ] [ Icons.warning 13 Inherit ] 450 + , text "Continue without encryption" 451 + ] 452 + ] 453 + 454 + 455 + 456 + -- INPUT SCREEN 457 + 458 + 459 + inputScreen : Question -> Html Authentication.Msg 460 + inputScreen question = 461 + slab 462 + Html.form 463 + [ onSubmit ConfirmInput ] 464 + [ C.flex 465 + , C.flex_col 466 + , C.max_w_xs 467 + , C.px_3 468 + , C.w_screen 469 + 470 + -- 471 + , C.sm__px_0 472 + ] 473 + [ UI.Kit.textFieldAlt 474 + [ attribute "autocapitalize" "off" 475 + , placeholder question.placeholder 476 + , Html.Events.onInput Input 477 + , value question.value 478 + ] 479 + , UI.Kit.button 480 + UI.Kit.Filled 481 + Authentication.Bypass 482 + (text "Continue") 483 + ] 484 + 485 + 486 + 487 + -- SPEECH BUBBLE 488 + 489 + 490 + speechBubble : Html msg -> Html msg 491 + speechBubble contents = 492 + chunk 493 + [ C.absolute 494 + , C.antialiased 495 + , C.bg_background 496 + , C.border_b 497 + , C.border_transparent 498 + , C.font_semibold 499 + , C.italic 500 + , C.leading_snug 501 + , C.left_half 502 + , C.max_w_screen 503 + , C.minus_translate_x_half 504 + , C.px_4 505 + , C.py_2 506 + , C.rounded 507 + , C.text_center 508 + , C.text_sm 509 + , C.text_white 510 + , C.top_full 511 + , C.transform 512 + , C.whitespace_no_wrap 513 + 514 + -- Dark mode 515 + ------------ 516 + , C.dark__bg_darkest_hour 517 + , C.dark__text_gray_600 518 + ] 519 + [ contents 520 + 521 + -- 522 + , brick 523 + speechBubbleArrowStyles 524 + [ C.absolute 525 + , C.h_0 526 + , C.left_half 527 + , C.minus_translate_x_half 528 + , C.minus_translate_y_full 529 + , C.top_0 530 + , C.transform 531 + , C.w_0 532 + ] 533 + [] 534 + ] 535 + 536 + 537 + 538 + -- 🖼 539 + 540 + 541 + speechBubbleArrowStyles : List (Html.Attribute msg) 542 + speechBubbleArrowStyles = 543 + let 544 + color = 545 + Color.toCssString UI.Kit.colors.background 546 + in 547 + [ style "border-color" ("transparent transparent " ++ color ++ " transparent") 548 + , style "border-width" "0 6px 5px 6px" 549 + ]
+1 -1
src/Applications/UI/Other/State.elm
··· 7 7 import Return 8 8 import Return.Ext as Return 9 9 import Time 10 - import UI.Authentication as Authentication 10 + import UI.Authentication.Types as Authentication 11 11 import UI.Common.State as Common 12 12 import UI.Ports as Ports 13 13 import UI.Sources.State as Sources
+1 -1
src/Applications/UI/Reply/Translate.elm
··· 30 30 import Tracks 31 31 import Tracks.Encoding as Tracks 32 32 import UI.Audio.State as Audio 33 - import UI.Authentication as Authentication 34 33 import UI.Authentication.ContextMenu as Authentication 34 + import UI.Authentication.Types as Authentication 35 35 import UI.Backdrop as Backdrop 36 36 import UI.Common.State exposing (showNotification, showNotificationWithModel) 37 37 import UI.Demo as Demo
+12 -6
src/Applications/UI/Types.elm
··· 29 29 import Time 30 30 import Tracks 31 31 import Tracks.Encoding as Tracks 32 - import UI.Authentication as Authentication 33 - import UI.Authentication.ContextMenu as Authentication 32 + import UI.Authentication.Types as Authentication 34 33 import UI.Notifications 35 34 import UI.Page as Page exposing (Page) 36 35 import UI.Queue as Queue ··· 128 127 , playlistToActivate : Maybe String 129 128 130 129 ----------------------------------------- 130 + -- 🦉 Nested 131 + ----------------------------------------- 132 + , authentication : Authentication.State 133 + 134 + ----------------------------------------- 131 135 -- Children (TODO) 132 136 ----------------------------------------- 133 - , authentication : Authentication.Model 134 137 , queue : Queue.Model 135 138 , sources : Sources.Model 136 139 , tracks : Tracks.Model ··· 235 238 | LoadHypaethralUserData Json.Decode.Value 236 239 | SaveEnclosedUserData 237 240 ----------------------------------------- 238 - -- 🦉 Adjunct 241 + -- ⚗️ Adjunct 239 242 ----------------------------------------- 240 243 | KeyboardMsg Keyboard.Msg 241 244 ----------------------------------------- 242 - -- 📭 Et Cetera 245 + -- 📭 Other 243 246 ----------------------------------------- 244 247 | SetCurrentTime Time.Posix 245 248 | SetIsOnline Bool 246 249 ----------------------------------------- 247 - -- Children (TODO) 250 + -- 🦉 Nested 248 251 ----------------------------------------- 249 252 | AuthenticationMsg Authentication.Msg 253 + ----------------------------------------- 254 + -- Children (TODO) 255 + ----------------------------------------- 250 256 | QueueMsg Queue.Msg 251 257 | SourcesMsg Sources.Msg 252 258 | TracksMsg Tracks.Msg
+4 -6
src/Applications/UI/View.elm
··· 24 24 import Tracks 25 25 import Tracks.Encoding as Tracks 26 26 import UI.Alfred.View as Alfred 27 - import UI.Authentication as Authentication 27 + import UI.Authentication.Common as Authentication 28 28 import UI.Authentication.ContextMenu as Authentication 29 + import UI.Authentication.Types as Authentication 30 + import UI.Authentication.View as Authentication 29 31 import UI.Backdrop as Backdrop 30 32 import UI.Console 31 33 import UI.ContextMenu ··· 137 139 content opts (defaultScreen model) 138 140 139 141 ( False, _ ) -> 140 - model.authentication 141 - |> Lazy.lazy Authentication.view 142 - |> Html.map AuthenticationMsg 143 - |> List.singleton 144 - |> content opts 142 + content opts [ Authentication.view model ] 145 143 ] 146 144 147 145
+8
src/Library/Lens/Ext.elm
··· 1 + module Lens.Ext exposing (..) 2 + 3 + import Monocle.Lens as Lens exposing (Lens) 4 + 5 + 6 + adjust : Lens a b -> a -> (b -> b) -> a 7 + adjust lens a fn = 8 + Lens.modify lens fn a