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.

Add very rudimentary support for RemoteStorage

+432 -93
+1
Makefile
··· 64 64 @echo "> Downloading & minifying dependencies" 65 65 @mkdir -p $(VENDOR_DIR) 66 66 @curl https://unpkg.com/lunr@2.3.6/lunr.js -o $(VENDOR_DIR)/lunr.js 67 + @curl https://unpkg.com/remotestoragejs@1.2.2/release/remotestorage.js -o $(VENDOR_DIR)/remotestorage.min.js 67 68 @curl https://unpkg.com/fast-text-encoding@1.0.0/text.min.js -o $(VENDOR_DIR)/text-encoding-polyfill.min.js 68 69 @curl https://unpkg.com/tachyons@4.11.1/css/tachyons.min.css -o $(VENDOR_DIR)/tachyons.min.css 69 70
+6
src/Applications/Brain.elm
··· 345 345 Just Alien.AuthMethod -> 346 346 AuthenticationMsg (Authentication.MethodRetrieved event.data) 347 347 348 + Just Alien.AuthRemoteStorage -> 349 + AuthenticationMsg (Authentication.HypaethralDataRetrieved event.data) 350 + 348 351 Just Alien.FabricateSecretKey -> 349 352 AuthenticationMsg Authentication.SecretKeyFabricated 350 353 ··· 416 419 417 420 Just Alien.AuthIpfs -> 418 421 report Alien.AuthIpfs "Something went wrong regarding the IPFS storage. Maybe you used the wrong passphrase, or your IPFS node is offline?" 422 + 423 + Just Alien.AuthRemoteStorage -> 424 + report Alien.AuthRemoteStorage "I couldn't decrypt your data, maybe you used the wrong passphrase?" 419 425 420 426 Just tag -> 421 427 report tag err
+26 -9
src/Applications/Brain/Authentication.elm
··· 25 25 import Brain.Reply exposing (Reply(..)) 26 26 import Conditional exposing (..) 27 27 import Json.Decode as Decode 28 - import Json.Encode as J 28 + import Json.Encode as Json 29 29 import Replying exposing (R3D3, do) 30 30 31 31 ··· 59 59 60 60 61 61 type Msg 62 - = PerformSignIn J.Value 62 + = PerformSignIn Json.Value 63 63 | PerformSignOut 64 64 -- 0. Secret Key 65 65 | FabricateSecretKey String 66 66 | SecretKeyFabricated 67 67 -- 1. Method 68 68 | RetrieveMethod 69 - | MethodRetrieved J.Value 69 + | MethodRetrieved Json.Value 70 70 -- 2. Data 71 71 | RetrieveHypaethralData 72 - | HypaethralDataRetrieved J.Value 72 + | HypaethralDataRetrieved Json.Value 73 73 -- x. Data 74 74 | RetrieveEnclosedData 75 - | EnclosedDataRetrieved J.Value 76 - | SaveEnclosedData J.Value 77 - | SaveHypaethralData J.Value 75 + | EnclosedDataRetrieved Json.Value 76 + | SaveEnclosedData Json.Value 77 + | SaveHypaethralData Json.Value 78 78 79 79 80 80 update : Msg -> Model -> R3D3 Model Msg Reply ··· 129 129 FabricateSecretKey passphrase -> 130 130 ( model 131 131 , passphrase 132 - |> J.string 132 + |> Json.string 133 133 |> Alien.broadcast Alien.FabricateSecretKey 134 134 |> Ports.fabricateSecretKey 135 135 , Nothing ··· 182 182 Just Local -> 183 183 Ports.requestCache (Alien.trigger Alien.AuthAnonymous) 184 184 185 + Just (RemoteStorage { userAddress, token }) -> 186 + [ ( "token", Json.string token ) 187 + , ( "userAddress", Json.string userAddress ) 188 + ] 189 + |> Json.object 190 + |> Alien.broadcast Alien.AuthRemoteStorage 191 + |> Ports.requestRemoteStorage 192 + 185 193 -- ✋ 186 194 Nothing -> 187 195 Cmd.none ··· 239 247 Just Local -> 240 248 Ports.toCache (Alien.broadcast Alien.AuthAnonymous json) 241 249 250 + Just (RemoteStorage { userAddress, token }) -> 251 + [ ( "data", json ) 252 + , ( "token", Json.string token ) 253 + , ( "userAddress", Json.string userAddress ) 254 + ] 255 + |> Json.object 256 + |> Alien.broadcast Alien.AuthRemoteStorage 257 + |> Ports.toRemoteStorage 258 + 242 259 -- ✋ 243 260 Nothing -> 244 261 Cmd.none ··· 251 268 252 269 253 270 type Termination 254 - = Authenticated Method J.Value 271 + = Authenticated Method Json.Value 255 272 | NotAuthenticated 256 273 257 274
+7 -1
src/Applications/Brain/Ports.elm
··· 1 - port module Brain.Ports exposing (fabricateSecretKey, fromAlien, receiveSearchResults, receiveTags, removeCache, removeIpfs, requestCache, requestIpfs, requestSearch, requestTags, toCache, toIpfs, toUI, updateSearchIndex) 1 + port module Brain.Ports exposing (fabricateSecretKey, fromAlien, receiveSearchResults, receiveTags, removeCache, removeIpfs, requestCache, requestIpfs, requestRemoteStorage, requestSearch, requestTags, toCache, toIpfs, toRemoteStorage, toUI, updateSearchIndex) 2 2 3 3 import Alien 4 4 import Json.Encode as Json ··· 24 24 port requestIpfs : Alien.Event -> Cmd msg 25 25 26 26 27 + port requestRemoteStorage : Alien.Event -> Cmd msg 28 + 29 + 27 30 port requestSearch : String -> Cmd msg 28 31 29 32 ··· 31 34 32 35 33 36 port toCache : Alien.Event -> Cmd msg 37 + 38 + 39 + port toRemoteStorage : Alien.Event -> Cmd msg 34 40 35 41 36 42 port toIpfs : Alien.Event -> Cmd msg
+29 -2
src/Applications/UI.elm
··· 2 2 3 3 import Alien 4 4 import Authentication 5 + import Authentication.RemoteStorage 5 6 import Browser 6 7 import Browser.Navigation as Nav 7 8 import Chunky exposing (..) ··· 105 106 106 107 -- Children 107 108 ----------- 108 - , authentication = Authentication.initialModel 109 + , authentication = Authentication.initialModel url 109 110 , backdrop = Backdrop.initialModel 110 111 , equalizer = Equalizer.initialModel 111 112 , queue = Queue.initialModel ··· 190 191 R2.withCmd (Ports.unstall ()) model 191 192 192 193 ----------------------------------------- 194 + -- Authentication 195 + ----------------------------------------- 196 + Core.ExternalAuth (Authentication.RemoteStorage _) input -> 197 + -- TODO: 198 + -- 1. Make request to RemoteStorage.webfingerAddress 199 + -- 2. If proper response -> Valid RemoteStorage 200 + -- If not -> Not a RemoteStorage -> Show notification 201 + -- 3. Extract oauth address from webfinger response 202 + let 203 + origin = 204 + Common.urlOrigin model.url 205 + in 206 + input 207 + |> Authentication.RemoteStorage.parseUserAddress 208 + |> Maybe.map (Authentication.RemoteStorage.oauthAddress { origin = origin }) 209 + |> Maybe.map Nav.load 210 + |> Maybe.withDefault Cmd.none 211 + |> Tuple.pair model 212 + 213 + Core.ExternalAuth _ _ -> 214 + R2.withNoCmd model 215 + 216 + ----------------------------------------- 193 217 -- Brain 194 218 ----------------------------------------- 195 219 Core.ProcessSources -> ··· 278 302 |> Ports.toBrain 279 303 in 280 304 { model 281 - | authentication = Authentication.initialModel 305 + | authentication = Authentication.initialModel model.url 282 306 , sources = Sources.initialModel 283 307 , tracks = Tracks.initialModel 284 308 } ··· 518 542 519 543 Reply.DismissNotification opts -> 520 544 Core.DismissNotification opts 545 + 546 + Reply.ExternalAuth a b -> 547 + Core.ExternalAuth a b 521 548 522 549 Reply.FillQueue -> 523 550 Core.FillQueue
+178 -72
src/Applications/UI/Authentication.elm
··· 2 2 3 3 import Alien 4 4 import Authentication exposing (Method(..)) 5 + import Base64 5 6 import Chunky exposing (..) 6 7 import Color exposing (Color) 7 8 import Color.Ext as Color ··· 24 25 import UI.Ports as Ports 25 26 import UI.Reply exposing (Reply(..)) 26 27 import UI.Svg.Elements 28 + import Url exposing (Url) 27 29 28 30 29 31 ··· 44 46 45 47 type Model 46 48 = Authenticated Method 49 + | InputScreen Method { input : String, placeholder : String, question : String } 47 50 | NewEncryptionKeyScreen Method (Maybe String) 48 51 | UpdateEncryptionKeyScreen Method (Maybe String) 49 52 | Unauthenticated 50 53 51 54 52 - initialModel : Model 53 - initialModel = 54 - Unauthenticated 55 + initialModel : Url -> Model 56 + initialModel url = 57 + case String.split "/" (String.dropLeft 1 url.path) of 58 + [ "authenticate", "remotestorage", encodedUserAddress ] -> 59 + let 60 + userAddress = 61 + encodedUserAddress 62 + |> Url.percentDecode 63 + |> Maybe.andThen (Base64.decode >> Result.toMaybe) 64 + |> Maybe.withDefault encodedUserAddress 65 + in 66 + url.fragment 67 + |> Maybe.map (String.split "&") 68 + |> Maybe.map (List.filter <| String.startsWith "access_token=") 69 + |> Maybe.andThen List.head 70 + |> Maybe.withDefault "" 71 + |> String.replace "access_token=" "" 72 + |> (\t -> 73 + NewEncryptionKeyScreen 74 + (RemoteStorage { userAddress = userAddress, token = t }) 75 + Nothing 76 + ) 77 + 78 + _ -> 79 + Unauthenticated 55 80 56 81 57 82 extractMethod : Model -> Maybe Method ··· 60 85 Authenticated method -> 61 86 Just method 62 87 88 + InputScreen method _ -> 89 + Just method 90 + 63 91 NewEncryptionKeyScreen method _ -> 64 92 Just method 65 93 ··· 76 104 77 105 type Msg 78 106 = Bypass 79 - | HideEncryptionKeyScreen 80 - | KeepPassphraseInMemory String 81 - | ShowNewEncryptionKeyScreen Method 82 - | ShowUpdateEncryptionKeyScreen Method 107 + | Cancel 83 108 | SignIn Method 84 109 | SignInWithPassphrase Method String 85 110 | SignedIn Method 111 + ----------------------------------------- 112 + -- Encryption 113 + ----------------------------------------- 114 + | KeepPassphraseInMemory String 115 + | ShowNewEncryptionKeyScreen Method 116 + | ShowUpdateEncryptionKeyScreen Method 86 117 | UpdateEncryptionKey Method String 118 + ----------------------------------------- 119 + -- More Input 120 + ----------------------------------------- 121 + | AskForInput Method { placeholder : String, question : String } 122 + | Input String 123 + | ConfirmInput 87 124 88 125 89 126 update : Msg -> Model -> R3D3 Model Msg Reply ··· 92 129 Bypass -> 93 130 R3.withNothing model 94 131 95 - HideEncryptionKeyScreen -> 132 + Cancel -> 96 133 case model of 97 134 Authenticated method -> 98 135 R3.withNothing (Authenticated method) 136 + 137 + InputScreen _ _ -> 138 + R3.withNothing Unauthenticated 99 139 100 140 NewEncryptionKeyScreen _ _ -> 101 141 R3.withNothing Unauthenticated ··· 106 146 Unauthenticated -> 107 147 R3.withNothing Unauthenticated 108 148 109 - KeepPassphraseInMemory passphrase -> 110 - case model of 111 - NewEncryptionKeyScreen method _ -> 112 - R3.withNothing (NewEncryptionKeyScreen method <| Just passphrase) 113 - 114 - UpdateEncryptionKeyScreen method _ -> 115 - R3.withNothing (UpdateEncryptionKeyScreen method <| Just passphrase) 116 - 117 - _ -> 118 - R3.withNothing model 119 - 120 - ShowNewEncryptionKeyScreen method -> 121 - R3.withNothing (NewEncryptionKeyScreen method Nothing) 122 - 123 - ShowUpdateEncryptionKeyScreen method -> 124 - R3.withNothing (UpdateEncryptionKeyScreen method Nothing) 125 - 126 149 SignIn method -> 127 150 ( Unauthenticated 128 - , signIn method 151 + -- 152 + , [ ( "method", Authentication.encodeMethod method ) 153 + , ( "passphrase", Json.Encode.null ) 154 + ] 155 + |> Json.Encode.object 156 + |> Alien.broadcast Alien.SignIn 157 + |> Ports.toBrain 158 + -- 129 159 , Just [ ToggleLoadingScreen On ] 130 160 ) 131 161 ··· 138 168 139 169 else 140 170 ( Unauthenticated 141 - , signInWithPassphrase method passphrase 171 + -- 172 + , [ ( "method", Authentication.encodeMethod method ) 173 + , ( "passphrase", Json.Encode.string <| Crypto.Hash.sha256 passphrase ) 174 + ] 175 + |> Json.Encode.object 176 + |> Alien.broadcast Alien.SignIn 177 + |> Ports.toBrain 178 + -- 142 179 , Just [ ToggleLoadingScreen On ] 143 180 ) 144 181 145 182 SignedIn method -> 146 183 R3.withNothing (Authenticated method) 147 184 185 + ----------------------------------------- 186 + -- Encryption 187 + ----------------------------------------- 188 + KeepPassphraseInMemory passphrase -> 189 + case model of 190 + NewEncryptionKeyScreen method _ -> 191 + R3.withNothing (NewEncryptionKeyScreen method <| Just passphrase) 192 + 193 + UpdateEncryptionKeyScreen method _ -> 194 + R3.withNothing (UpdateEncryptionKeyScreen method <| Just passphrase) 195 + 196 + _ -> 197 + R3.withNothing model 198 + 199 + ShowNewEncryptionKeyScreen method -> 200 + R3.withNothing (NewEncryptionKeyScreen method Nothing) 201 + 202 + ShowUpdateEncryptionKeyScreen method -> 203 + R3.withNothing (UpdateEncryptionKeyScreen method Nothing) 204 + 148 205 UpdateEncryptionKey method passphrase -> 149 206 if String.length passphrase < minimumPassphraseLength then 150 207 ( model ··· 162 219 , Nothing 163 220 ) 164 221 222 + ----------------------------------------- 223 + -- More Input 224 + ----------------------------------------- 225 + AskForInput method opts -> 226 + { input = "" 227 + , placeholder = opts.placeholder 228 + , question = opts.question 229 + } 230 + |> InputScreen method 231 + |> R3.withNothing 165 232 166 - signIn : Method -> Cmd Msg 167 - signIn method = 168 - [ ( "method", Authentication.encodeMethod method ) 169 - , ( "passphrase", Json.Encode.null ) 170 - ] 171 - |> Json.Encode.object 172 - |> Alien.broadcast Alien.SignIn 173 - |> Ports.toBrain 233 + Input string -> 234 + case model of 235 + InputScreen method opts -> 236 + R3.withNothing (InputScreen method { opts | input = string }) 174 237 238 + m -> 239 + R3.withNothing m 175 240 176 - signInWithPassphrase : Method -> String -> Cmd Msg 177 - signInWithPassphrase method passphrase = 178 - [ ( "method", Authentication.encodeMethod method ) 179 - , ( "passphrase", Json.Encode.string <| Crypto.Hash.sha256 passphrase ) 180 - ] 181 - |> Json.Encode.object 182 - |> Alien.broadcast Alien.SignIn 183 - |> Ports.toBrain 241 + ConfirmInput -> 242 + case model of 243 + InputScreen method { input } -> 244 + ( model 245 + , Cmd.none 246 + , Just [ ExternalAuth method input ] 247 + ) 248 + 249 + _ -> 250 + R3.withNothing model 184 251 185 252 186 253 ··· 203 270 [ chunk 204 271 [ T.relative ] 205 272 [ img 206 - [ onClick HideEncryptionKeyScreen 273 + [ onClick Cancel 207 274 , src "/images/diffuse-light.svg" 208 275 , width 190 209 276 210 277 -- 211 278 , case model of 212 - NewEncryptionKeyScreen _ _ -> 213 - style "cursor" "pointer" 214 - 215 - UpdateEncryptionKeyScreen _ _ -> 216 - style "cursor" "pointer" 279 + Unauthenticated -> 280 + style "cursor" "default" 217 281 218 282 _ -> 219 - style "cursor" "default" 283 + style "cursor" "pointer" 220 284 ] 221 285 [] 222 286 223 287 -- Speech bubble 224 288 ---------------- 225 289 , case model of 290 + InputScreen _ { question } -> 291 + question 292 + |> text 293 + |> speechBubble 294 + 226 295 NewEncryptionKeyScreen _ _ -> 227 296 [ text "I need a passphrase" 228 297 , lineBreak ··· 253 322 -- Content 254 323 ----------------------------------------- 255 324 , case model of 325 + InputScreen method opts -> 326 + inputScreen opts 327 + 256 328 NewEncryptionKeyScreen method Nothing -> 257 329 encryptionKeyScreen Bypass 258 330 ··· 298 370 -- CHOICES 299 371 300 372 301 - encryptionKeyScreen : Msg -> Html Msg 302 - encryptionKeyScreen msg = 303 - chunk 304 - [ T.flex 305 - , T.flex_column 306 - ] 307 - [ UI.Kit.textArea 308 - [ attribute "autocomplete" "off" 309 - , attribute "autocorrect" "off" 310 - , attribute "spellcheck" "false" 311 - , placeholder "anQLS9Usw24gxUi11IgVBg76z8SCWZgLKkoWIeJ1ClVmBHLRlaiA0CtvONVAMGritbgd3U45cPTxrhFU0WXaOAa8pVt186KyEccfUNyAq97" 312 - , Html.Styled.Events.onInput KeepPassphraseInMemory 313 - ] 314 - , UI.Kit.button 315 - UI.Kit.Normal 316 - msg 317 - (text "Continue") 318 - ] 319 - 320 - 321 373 choicesScreen : Html Msg 322 374 choicesScreen = 323 375 chunk ··· 348 400 , outOfOrder = False 349 401 } 350 402 , choiceButton 351 - { action = Bypass 403 + { action = 404 + AskForInput 405 + (Authentication.RemoteStorage { userAddress = "", token = "" }) 406 + { placeholder = "username@5apps.com" 407 + , question = "What's your user address?" 408 + } 352 409 , icon = \_ _ -> Svg.map never UI.Svg.Elements.remoteStorageLogo 353 410 , isLast = False 354 411 , label = "RemoteStorage" 355 - , outOfOrder = True 412 + , outOfOrder = False 356 413 } 357 414 , choiceButton 358 415 { action = Bypass ··· 408 465 409 466 410 467 -- ENCRYPTION KEY 468 + 469 + 470 + encryptionKeyScreen : Msg -> Html Msg 471 + encryptionKeyScreen msg = 472 + slab 473 + Html.form 474 + [ onSubmit msg ] 475 + [ T.flex 476 + , T.flex_column 477 + ] 478 + [ UI.Kit.textArea 479 + [ attribute "autocomplete" "off" 480 + , attribute "autocorrect" "off" 481 + , attribute "spellcheck" "false" 482 + , placeholder "anQLS9Usw24gxUi11IgVBg76z8SCWZgLKkoWIeJ1ClVmBHLRlaiA0CtvONVAMGritbgd3U45cPTxrhFU0WXaOAa8pVt186KyEccfUNyAq97" 483 + , Html.Styled.Events.onInput KeepPassphraseInMemory 484 + ] 485 + , UI.Kit.button 486 + UI.Kit.Normal 487 + Bypass 488 + (text "Continue") 489 + ] 490 + 491 + 492 + 493 + -- INPUT SCREEN 494 + 495 + 496 + inputScreen : { question : String, input : String, placeholder : String } -> Html Msg 497 + inputScreen opts = 498 + slab 499 + Html.form 500 + [ onSubmit ConfirmInput ] 501 + [ T.flex 502 + , T.flex_column 503 + ] 504 + [ UI.Kit.textFieldAlt 505 + [ placeholder opts.placeholder 506 + , Html.Styled.Events.onInput Input 507 + ] 508 + , UI.Kit.button 509 + UI.Kit.Normal 510 + Bypass 511 + (text "Continue") 512 + ] 513 + 514 + 515 + 516 + -- SPEECH BUBBLE 411 517 412 518 413 519 speechBubble : Html msg -> Html msg
+5
src/Applications/UI/Core.elm
··· 1 1 module UI.Core exposing (Flags, Model, Msg(..)) 2 2 3 3 import Alien 4 + import Authentication 4 5 import Browser 5 6 import Browser.Navigation as Nav 6 7 import Common exposing (Switch(..)) ··· 93 94 | SetAudioIsLoading Bool 94 95 | SetAudioIsPlaying Bool 95 96 | Unstall 97 + ----------------------------------------- 98 + -- Authentication 99 + ----------------------------------------- 100 + | ExternalAuth Authentication.Method String 96 101 ----------------------------------------- 97 102 -- Brain 98 103 -----------------------------------------
+29 -1
src/Applications/UI/Kit.elm
··· 1 - module UI.Kit exposing (ButtonType(..), button, buttonFocus, buttonLink, buttonWithColor, buttonWithOptions, canister, centeredContent, checkbox, colorKit, colors, defaultFontFamilies, h1, h2, h3, headerFontFamilies, inlineIcon, inputFocus, insulationWidth, intro, label, link, logoBackdrop, navFocus, receptacle, select, textArea, textButton, textField, textFocus, vessel) 1 + module UI.Kit exposing (ButtonType(..), button, buttonFocus, buttonLink, buttonWithColor, buttonWithOptions, canister, centeredContent, checkbox, colorKit, colors, defaultFontFamilies, h1, h2, h3, headerFontFamilies, inlineIcon, inputFocus, insulationWidth, intro, label, link, logoBackdrop, navFocus, receptacle, select, textArea, textButton, textField, textFieldAlt, textFocus, vessel) 2 2 3 3 import Chunky exposing (..) 4 4 import Color ··· 473 473 [] 474 474 475 475 476 + textFieldAlt : List (Html.Attribute msg) -> Html msg 477 + textFieldAlt attributes = 478 + slab 479 + Html.input 480 + (css textFieldAltStyles :: attributes) 481 + [ T.bn 482 + , T.bg_white 483 + , T.br2 484 + , T.db 485 + , T.f6 486 + , T.lh_copy 487 + , T.mb3 488 + , T.pa3 489 + , T.w_100 490 + ] 491 + [] 492 + 493 + 476 494 vessel : List (Html msg) -> Html msg 477 495 vessel = 478 496 brick ··· 607 625 , Css.color (Color.toElmCssColor colors.text) 608 626 , Css.maxWidth (px maxInputWidth) 609 627 , inputFocus 628 + ] 629 + 630 + 631 + textFieldAltStyles : List Css.Style 632 + textFieldAltStyles = 633 + [ Css.color (Color.toElmCssColor colors.text) 634 + , Css.maxWidth (Css.vw 87.5) 635 + , Css.resize Css.none 636 + , Css.width (px 292) 637 + , textAreaFocus 610 638 ] 611 639 612 640
+2
src/Applications/UI/Reply.elm
··· 1 1 module UI.Reply exposing (Reply(..)) 2 2 3 3 import Alien 4 + import Authentication 4 5 import Common exposing (Switch(..)) 5 6 import Coordinates exposing (Coordinates) 6 7 import Json.Decode as Json ··· 19 20 = ActiveQueueItemChanged (Maybe Queue.Item) 20 21 | AddSourceToCollection Source 21 22 | DismissNotification { id : Int } 23 + | ExternalAuth Authentication.Method String 22 24 | FillQueue 23 25 | GoToPage Page 24 26 | InsertDemo
+3
src/Applications/UI/Settings.elm
··· 67 67 Just Local -> 68 68 text "in this browser." 69 69 70 + Just (RemoteStorage _) -> 71 + text "on a RemoteStorage server." 72 + 70 73 Nothing -> 71 74 text "on nothing, wtf?" 72 75 , lineBreak
+69 -5
src/Javascript/Workers/brain.js
··· 50 50 }) 51 51 52 52 53 + function getSecretKey() { 54 + return getFromIndex({ key: SECRET_KEY_LOCATION }) 55 + } 56 + 57 + 53 58 54 59 // Cache 55 60 // ----- ··· 63 68 const dataPromise = (_ => { 64 69 if (event.tag == "AUTH_ANONYMOUS") { 65 70 return getFromIndex({ key: event.tag }) 66 - .then(r => getFromIndex({ key: SECRET_KEY_LOCATION }).then(s => [r, s])) 71 + .then(r => getSecretKey().then(s => [r, s])) 67 72 .then(([r, s]) => r ? decrypt(s, r) : null) 68 73 .then(d => d ? JSON.parse(d) : null) 69 74 ··· 94 99 if (key == "AUTH_ANONYMOUS") { 95 100 const json = JSON.stringify(data) 96 101 97 - return getFromIndex({ key: SECRET_KEY_LOCATION }) 102 + return getSecretKey() 98 103 .then(secretKey => encrypt(secretKey, json)) 99 104 .then(encryptedData => setInIndex({ key: key, data: encryptedData })) 100 105 ··· 124 129 125 130 app.ports.requestIpfs.subscribe(event => { 126 131 const path = ipfsFilePath(event.tag) 127 - const secretKeyPromise = getFromIndex({ key: SECRET_KEY_LOCATION }) 128 132 129 133 fetch("http://localhost:5001/api/v0/files/read?arg=" + path) 130 134 .then(r => r.ok ? r.text() : r.json()) 131 - .then(r => secretKeyPromise.then(s => [r, s])) 135 + .then(r => getSecretKey().then(s => [r, s])) 132 136 .then(([r, s]) => r.Code === 0 ? {} : decrypt(s, r)) 133 137 .then(data => { 134 138 app.ports.fromAlien.send({ ··· 153 157 truncate: true 154 158 }).toString() 155 159 156 - getFromIndex({ key: SECRET_KEY_LOCATION }) 160 + getSecretKey() 157 161 .then(secretKey => encrypt(secretKey, json)) 158 162 .then(data => { 159 163 const formData = new FormData() ··· 168 172 }).catch( 169 173 reportError(event) 170 174 175 + ) 176 + }) 177 + 178 + 179 + 180 + // Remote Storage 181 + // -------------- 182 + 183 + let rs 184 + let rsClient 185 + 186 + 187 + function remoteStorage(event) { 188 + if (!rs) { 189 + importScripts("/vendor/remotestorage.min.js") 190 + 191 + rs = new RemoteStorage({ cache: false }) 192 + rs.access.claim("diffuse", "rw") 193 + 194 + rsClient = rs.scope("/diffuse-v2/") 195 + 196 + return new Promise(resolve => { 197 + rs.on("connected", resolve) 198 + rs.connect(event.data.userAddress, event.data.token) 199 + }) 200 + 201 + } else { 202 + return Promise.resolve() 203 + 204 + } 205 + } 206 + 207 + 208 + app.ports.requestRemoteStorage.subscribe(event => { 209 + remoteStorage(event) 210 + .then(_ => rsClient.getFile("diffuse.json")) 211 + .then(r => getSecretKey().then(s => [r.data, s])) 212 + .then(([r, s]) => r ? decrypt(s, r) : null) 213 + .then(data => { 214 + app.ports.fromAlien.send({ 215 + tag: event.tag, 216 + data: typeof data === "string" ? JSON.parse(data) : data, 217 + error: null 218 + }) 219 + }) 220 + .catch( 221 + reportError(event) 222 + ) 223 + }) 224 + 225 + 226 + app.ports.toRemoteStorage.subscribe(event => { 227 + const json = JSON.stringify(event.data.data) 228 + 229 + remoteStorage(event) 230 + .then(_ => getSecretKey()) 231 + .then(secretKey => encrypt(secretKey, json)) 232 + .then(data => rsClient.storeFile("application/json", "diffuse.json", data)) 233 + .catch( 234 + reportError(event) 171 235 ) 172 236 }) 173 237
+7
src/Library/Alien.elm
··· 24 24 | AuthEnclosedData 25 25 | AuthIpfs 26 26 | AuthMethod 27 + | AuthRemoteStorage 27 28 | AuthSecretKey 28 29 | FabricateSecretKey 29 30 | SearchTracks ··· 92 93 93 94 AuthMethod -> 94 95 "AUTH_METHOD" 96 + 97 + AuthRemoteStorage -> 98 + "AUTH_REMOTE_STORAGE" 95 99 96 100 AuthSecretKey -> 97 101 "AUTH_SECRET_KEY" ··· 180 184 181 185 "AUTH_METHOD" -> 182 186 Just AuthMethod 187 + 188 + "AUTH_REMOTE_STORAGE" -> 189 + Just AuthRemoteStorage 183 190 184 191 "AUTH_SECRET_KEY" -> 185 192 Just AuthSecretKey
+20 -3
src/Library/Authentication.elm
··· 19 19 type Method 20 20 = Ipfs 21 21 | Local 22 + | RemoteStorage { userAddress : String, token : String } 22 23 23 24 24 25 type alias EnclosedUserData = ··· 69 70 Local -> 70 71 "LOCAL" 71 72 73 + RemoteStorage { userAddress, token } -> 74 + String.join 75 + methodSeparator 76 + [ "REMOTE_STORAGE" 77 + , userAddress 78 + , token 79 + ] 80 + 72 81 73 82 methodFromString : String -> Maybe Method 74 83 methodFromString string = 75 - case string of 76 - "IPFS" -> 84 + case String.split methodSeparator string of 85 + [ "IPFS" ] -> 77 86 Just Ipfs 78 87 79 - "LOCAL" -> 88 + [ "LOCAL" ] -> 80 89 Just Local 90 + 91 + [ "REMOTE_STORAGE", u, t ] -> 92 + Just (RemoteStorage { userAddress = u, token = t }) 81 93 82 94 _ -> 83 95 Nothing 96 + 97 + 98 + methodSeparator : String 99 + methodSeparator = 100 + "___" 84 101 85 102 86 103
+50
src/Library/Authentication/RemoteStorage.elm
··· 1 + module Authentication.RemoteStorage exposing (RemoteStorage, oauthAddress, parseUserAddress) 2 + 3 + import Base64 4 + import Url 5 + 6 + 7 + 8 + -- 🌳 9 + 10 + 11 + type alias RemoteStorage = 12 + { host : String 13 + , username : String 14 + } 15 + 16 + 17 + 18 + -- 🔱 19 + 20 + 21 + parseUserAddress : String -> Maybe RemoteStorage 22 + parseUserAddress str = 23 + case String.split "@" str of 24 + [ u, h ] -> 25 + Just { host = h, username = u } 26 + 27 + _ -> 28 + Nothing 29 + 30 + 31 + oauthAddress : { origin : String } -> RemoteStorage -> String 32 + oauthAddress { origin } { host, username } = 33 + let 34 + ua = 35 + (username ++ "@" ++ host) 36 + |> Base64.encode 37 + |> Url.percentEncode 38 + in 39 + String.concat 40 + [ "https://" ++ host ++ "/rs/oauth/" ++ username 41 + , "?redirect_uri=" ++ Url.percentEncode (origin ++ "/authenticate/remotestorage/" ++ ua) 42 + , "&client_id=" ++ Url.percentEncode origin 43 + , "&scope=" ++ Url.percentEncode "diffuse-v2:rw" 44 + , "&response_type=token" 45 + ] 46 + 47 + 48 + webfingerAddress : RemoteStorage -> String 49 + webfingerAddress { host, username } = 50 + "https://" ++ host ++ "/.well-known/webfinger?resource=acct:" ++ username