From e8c0df004ebbcc8131521df240ff5886280af053 Mon Sep 17 00:00:00 2001 From: Bram Date: Thu, 30 May 2024 10:48:20 +0200 Subject: [PATCH 1/5] Add removePasswordOnLogin setting --- src/Internal/Config/Default.elm | 16 +++++++ src/Internal/Config/Text.elm | 4 ++ src/Internal/Values/Settings.elm | 19 +++++++- src/Matrix/Settings.elm | 75 +++++++++++++++++++++++++------- 4 files changed, 98 insertions(+), 16 deletions(-) diff --git a/src/Internal/Config/Default.elm b/src/Internal/Config/Default.elm index 737bb96..53a5db2 100644 --- a/src/Internal/Config/Default.elm +++ b/src/Internal/Config/Default.elm @@ -1,6 +1,7 @@ module Internal.Config.Default exposing ( currentVersion, deviceName , syncTime + , removePasswordOnLogin ) {-| This module hosts all default settings and configurations that the Vault @@ -16,6 +17,11 @@ will assume until overriden by the user. @docs syncTime + +## Security + +@docs removePasswordOnLogin + -} @@ -52,3 +58,13 @@ The value is in miliseconds, so it is set at 30,000. syncTime : Int syncTime = 30 * 1000 + + +{-| Once the Matrix API has logged in successfully, it does not need to remember +the user's password. However, to keep the Vault logged in automatically, one may +choose to remember the password in order to get a new access token when an old +access token has expired. +-} +removePasswordOnLogin : Bool +removePasswordOnLogin = + True diff --git a/src/Internal/Config/Text.elm b/src/Internal/Config/Text.elm index 256fb13..a63e936 100644 --- a/src/Internal/Config/Text.elm +++ b/src/Internal/Config/Text.elm @@ -321,6 +321,7 @@ fields : , settings : { currentVersion : Desc , deviceName : Desc + , removePasswordOnLogin : Desc , syncTime : Desc } , timeline : @@ -501,6 +502,9 @@ fields = , deviceName = [ "Indicates the device name that is communicated to the Matrix API." ] + , removePasswordOnLogin = + [ "Remove the password as soon as a valid access token has been received." + ] , syncTime = [ "Indicates the frequency in miliseconds with which the Elm SDK should long-poll the /sync endpoint." ] diff --git a/src/Internal/Values/Settings.elm b/src/Internal/Values/Settings.elm index 4696b7a..eed039a 100644 --- a/src/Internal/Values/Settings.elm +++ b/src/Internal/Values/Settings.elm @@ -35,6 +35,7 @@ behave under the user's preferred settings. type alias Settings = { currentVersion : String , deviceName : String + , removePasswordOnLogin : Bool , syncTime : Int } @@ -43,7 +44,7 @@ type alias Settings = -} coder : Json.Coder Settings coder = - Json.object3 + Json.object4 { name = Text.docs.settings.name , description = Text.docs.settings.description , init = Settings @@ -66,6 +67,21 @@ coder = , defaultToString = identity } ) + (Json.field.optional.withDefault + { fieldName = "removePasswordOnLogin" + , toField = .removePasswordOnLogin + , description = Text.fields.settings.removePasswordOnLogin + , coder = Json.bool + , default = Tuple.pair Default.removePasswordOnLogin [] + , defaultToString = + \b -> + if b then + "true" + + else + "false" + } + ) (Json.field.optional.withDefault { fieldName = "syncTime" , toField = .syncTime @@ -97,5 +113,6 @@ init : Settings init = { currentVersion = Default.currentVersion , deviceName = Default.deviceName + , removePasswordOnLogin = Default.removePasswordOnLogin , syncTime = Default.syncTime } diff --git a/src/Matrix/Settings.elm b/src/Matrix/Settings.elm index bd0102d..7c84e3b 100644 --- a/src/Matrix/Settings.elm +++ b/src/Matrix/Settings.elm @@ -2,6 +2,8 @@ module Matrix.Settings exposing ( setAccessToken, removeAccessToken , getDeviceName, setDeviceName , getSyncTime, setSyncTime + , setPassword + , removePassword, removePasswordOnLogin ) {-| The Matrix Vault has lots of configurable variables that you rarely want to @@ -50,20 +52,39 @@ The value is in miliseconds, so it is set at 30,000. @docs getSyncTime, setSyncTime + +## Password + +When a Vault wants to access the Matrix API, it needs an access token. This can +either be provided directly, or the Vault can get one itself by using a password +to log in. + +@docs setPassword + +For security reasons, it is not possible to read whatever password is stored in +the Vault. An attacker with access to the memory might be able to find it, +however, so the Vault offers ways to remove the password from memory. + +@docs removePassword, removePasswordOnLogin + -} import Internal.Values.Envelope as Envelope import Types exposing (Vault(..)) -{-| Insert a suggested access token. +{-| Determine the device name. -} -setAccessToken : String -> Vault -> Vault -setAccessToken token (Vault vault) = - vault - |> Envelope.mapContext - (\c -> { c | suggestedAccessToken = Just token }) - |> Vault +getDeviceName : Vault -> String +getDeviceName (Vault vault) = + Envelope.extractSettings .deviceName vault + + +{-| Determine the sync timeout value. +-} +getSyncTime : Vault -> Int +getSyncTime (Vault vault) = + Envelope.extractSettings .syncTime vault {-| Remove an access token that has been inserted using the @@ -80,11 +101,32 @@ removeAccessToken (Vault vault) = |> Vault -{-| Determine the device name. +{-| Remove a password that is stored in the Matrix Vault. -} -getDeviceName : Vault -> String -getDeviceName (Vault vault) = - Envelope.extractSettings .deviceName vault +removePassword : Vault -> Vault +removePassword (Vault vault) = + vault + |> Envelope.mapContext + (\c -> { c | password = Nothing }) + |> Vault + + +{-| Remove password from the Vault as soon as a valid access token has been +received from the Matrix API. +-} +removePasswordOnLogin : Bool -> Vault -> Vault +removePasswordOnLogin b (Vault vault) = + Vault <| Envelope.mapSettings (\s -> { s | removePasswordOnLogin = b }) vault + + +{-| Insert a suggested access token. +-} +setAccessToken : String -> Vault -> Vault +setAccessToken token (Vault vault) = + vault + |> Envelope.mapContext + (\c -> { c | suggestedAccessToken = Just token }) + |> Vault {-| Override the device name. @@ -94,11 +136,14 @@ setDeviceName name (Vault vault) = Vault <| Envelope.mapSettings (\s -> { s | deviceName = name }) vault -{-| Determine the sync timeout value. +{-| Set a password for the given user. -} -getSyncTime : Vault -> Int -getSyncTime (Vault vault) = - Envelope.extractSettings .syncTime vault +setPassword : String -> Vault -> Vault +setPassword password (Vault vault) = + vault + |> Envelope.mapContext + (\c -> { c | password = Just password }) + |> Vault {-| Override the sync timeout value. From b465ad1f4781669f76cd3c4632347cb1f4b99795 Mon Sep 17 00:00:00 2001 From: Bram Date: Thu, 30 May 2024 10:52:48 +0200 Subject: [PATCH 2/5] Remove password after login, if necessary --- src/Internal/Api/LoginWithUsernameAndPassword/Api.elm | 1 + src/Internal/Values/Envelope.elm | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm b/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm index a839d2c..5ae8f29 100644 --- a/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm +++ b/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm @@ -190,6 +190,7 @@ loginWithUsernameAndPasswordV1 { username, password } = , refresh = out.refreshToken , value = out.accessToken } + , E.RemovePasswordIfNecessary , out.user |> Maybe.map (V.SetUser >> E.ContentUpdate) |> E.Optional diff --git a/src/Internal/Values/Envelope.elm b/src/Internal/Values/Envelope.elm index 9ecbef1..7823e62 100644 --- a/src/Internal/Values/Envelope.elm +++ b/src/Internal/Values/Envelope.elm @@ -78,6 +78,7 @@ type EnvelopeUpdate a | More (List (EnvelopeUpdate a)) | Optional (Maybe (EnvelopeUpdate a)) | RemoveAccessToken String + | RemovePasswordIfNecessary | SetAccessToken AccessToken | SetBaseUrl String | SetDeviceId String @@ -311,6 +312,13 @@ update updateContent eu ({ context } as data) = RemoveAccessToken token -> { data | context = { context | accessTokens = Hashdict.removeKey token context.accessTokens } } + RemovePasswordIfNecessary -> + if data.settings.removePasswordOnLogin then + { data | context = { context | password = Nothing } } + + else + data + SetAccessToken a -> { data | context = { context | accessTokens = Hashdict.insert a context.accessTokens } } From 994c99af1566a7922773d804c1684da784f60f74 Mon Sep 17 00:00:00 2001 From: Bram Date: Thu, 30 May 2024 13:53:56 +0200 Subject: [PATCH 3/5] Add RemovePasswordOnLogin feature --- index.html | 16499 ++++++++++++++++ .../Api/LoginWithUsernameAndPassword/Api.elm | 6 + src/Matrix.elm | 15 +- 3 files changed, 16515 insertions(+), 5 deletions(-) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..8d00860 --- /dev/null +++ b/index.html @@ -0,0 +1,16499 @@ + + + + + Main + + + + + +

+
+
+
+
+
\ No newline at end of file
diff --git a/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm b/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm
index 5ae8f29..17d121d 100644
--- a/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm
+++ b/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm
@@ -232,6 +232,7 @@ loginWithUsernameAndPasswordV2 { deviceId, initialDeviceDisplayName, username, p
                             , refresh = Nothing
                             , value = out.accessToken
                             }
+                        , E.RemovePasswordIfNecessary
                         , out.user
                             |> Maybe.map (V.SetUser >> E.ContentUpdate)
                             |> E.Optional
@@ -283,6 +284,7 @@ loginWithUsernameAndPasswordV3 { deviceId, initialDeviceDisplayName, username, p
                             , refresh = Nothing
                             , value = out.accessToken
                             }
+                        , E.RemovePasswordIfNecessary
                         , out.user
                             |> Maybe.map (V.SetUser >> E.ContentUpdate)
                             |> E.Optional
@@ -334,6 +336,7 @@ loginWithUsernameAndPasswordV4 { deviceId, initialDeviceDisplayName, username, p
                             , refresh = Nothing
                             , value = out.accessToken
                             }
+                        , E.RemovePasswordIfNecessary
                         , out.user
                             |> Maybe.map (V.SetUser >> E.ContentUpdate)
                             |> E.Optional
@@ -389,6 +392,7 @@ loginWithUsernameAndPasswordV5 { deviceId, initialDeviceDisplayName, username, p
                             , refresh = Nothing
                             , value = out.accessToken
                             }
+                        , E.RemovePasswordIfNecessary
                         , out.user
                             |> Maybe.map (V.SetUser >> E.ContentUpdate)
                             |> E.Optional
@@ -445,6 +449,7 @@ loginWithUsernameAndPasswordV6 { deviceId, enableRefreshToken, initialDeviceDisp
                             , refresh = out.refreshToken
                             , value = out.accessToken
                             }
+                        , E.RemovePasswordIfNecessary
                         , out.user
                             |> Maybe.map (V.SetUser >> E.ContentUpdate)
                             |> E.Optional
@@ -501,6 +506,7 @@ loginWithUsernameAndPasswordV7 { deviceId, enableRefreshToken, initialDeviceDisp
                             , refresh = out.refreshToken
                             , value = out.accessToken
                             }
+                        , E.RemovePasswordIfNecessary
                         , E.ContentUpdate (V.SetUser out.user)
                         , out.wellKnown
                             |> Maybe.map (.homeserver >> .baseUrl)
diff --git a/src/Matrix.elm b/src/Matrix.elm
index b9da8e7..0991107 100644
--- a/src/Matrix.elm
+++ b/src/Matrix.elm
@@ -57,6 +57,9 @@ type alias VaultUpdate =
     Types.VaultUpdate
 
 
+{-| Adds a custom access token to the Vault. This can be done if no password is
+provided or known.
+-}
 addAccessToken : String -> Vault -> Vault
 addAccessToken token (Vault vault) =
     Envelope.mapContext (\c -> { c | suggestedAccessToken = Just token }) vault
@@ -74,16 +77,18 @@ addAccessToken token (Vault vault) =
 
 -}
 fromUserId : String -> Maybe Vault
-fromUserId =
-    User.fromString
-        >> Maybe.map
+fromUserId uid =
+    uid
+        |> User.fromString
+        |> Maybe.map
             (\u ->
                 Envelope.init
                     { serverName = "https://" ++ User.domain u
-                    , content = Internal.init u
+                    , content = Internal.init (Just u)
                     }
+                    |> Envelope.mapContext (\c -> { c | username = Just uid })
             )
-        >> Maybe.map Vault
+        |> Maybe.map Vault
 
 
 {-| Send a message event to a room.

From 85d767414de168333f969c3644f10758dec98864 Mon Sep 17 00:00:00 2001
From: Bram 
Date: Thu, 30 May 2024 13:54:30 +0200
Subject: [PATCH 4/5] Make username optional

---
 src/Internal/Values/Vault.elm | 12 ++++----
 src/Matrix.elm                | 53 +++++++++++++++++++++++++++--------
 tests/Test/Values/Vault.elm   |  2 +-
 3 files changed, 49 insertions(+), 18 deletions(-)

diff --git a/src/Internal/Values/Vault.elm b/src/Internal/Values/Vault.elm
index 5153553..2725744 100644
--- a/src/Internal/Values/Vault.elm
+++ b/src/Internal/Values/Vault.elm
@@ -45,7 +45,7 @@ import Internal.Values.User as User exposing (User)
 type alias Vault =
     { accountData : Dict String Json.Value
     , rooms : Hashdict Room
-    , user : User
+    , user : Maybe User
     }
 
 
@@ -81,7 +81,7 @@ coder =
             , coder = Hashdict.coder .roomId Room.coder
             }
         )
-        (Json.field.required
+        (Json.field.optional.value
             { fieldName = "user"
             , toField = .user
             , description = Text.fields.vault.user
@@ -106,11 +106,11 @@ getAccountData key vault =
 
 {-| Initiate a new Vault type.
 -}
-init : User -> Vault
-init user =
+init : Maybe User -> Vault
+init mUser =
     { accountData = Dict.empty
     , rooms = Hashdict.empty .roomId
-    , user = user
+    , user = mUser
     }
 
 
@@ -156,4 +156,4 @@ update vu vault =
             setAccountData key value vault
 
         SetUser user ->
-            { vault | user = user }
+            { vault | user = Just user }
diff --git a/src/Matrix.elm b/src/Matrix.elm
index 0991107..f59136a 100644
--- a/src/Matrix.elm
+++ b/src/Matrix.elm
@@ -1,5 +1,5 @@
 module Matrix exposing
-    ( Vault, fromUserId
+    ( Vault, fromUserId, fromUsername
     , VaultUpdate, update
     , addAccessToken, sendMessageEvent
     )
@@ -19,7 +19,7 @@ support a monolithic public registry. (:
 
 ## Vault
 
-@docs Vault, fromUserId
+@docs Vault, fromUserId, fromUsername
 
 
 ## Keeping the Vault up-to-date
@@ -91,6 +91,27 @@ fromUserId uid =
         |> Maybe.map Vault
 
 
+{-| Using a username and an address, create a Vault.
+
+The username can either be the localpart or the full Matrix ID. For example,
+you can either insert `alice` or `@alice:example.org`.
+
+-}
+fromUsername : { username : String, host : String, port_ : Maybe Int } -> Vault
+fromUsername { username, host, port_ } =
+    { serverName =
+        port_
+            |> Maybe.map String.fromInt
+            |> Maybe.map ((++) ":")
+            |> Maybe.withDefault ""
+            |> (++) host
+    , content = Internal.init (User.fromString username)
+    }
+        |> Envelope.init
+        |> Envelope.mapContext (\c -> { c | username = Just username })
+        |> Vault
+
+
 {-| Send a message event to a room.
 
 This function can be used in a scenario where the user does not want to sync
@@ -99,15 +120,25 @@ exists and the user is able to send a message to, and instead just sends the
 request to the Matrix API.
 
 -}
-sendMessageEvent : Vault -> { content : E.Value, eventType : String, roomId : String, toMsg : VaultUpdate -> msg, transactionId : String } -> Cmd msg
-sendMessageEvent (Vault vault) data =
-    Api.sendMessageEvent vault
-        { content = data.content
-        , eventType = data.eventType
-        , roomId = data.roomId
-        , toMsg = Types.VaultUpdate >> data.toMsg
-        , transactionId = data.transactionId
-        }
+sendMessageEvent :
+    { content : E.Value
+    , eventType : String
+    , roomId : String
+    , toMsg : VaultUpdate -> msg
+    , transactionId : String
+    , vault : Vault
+    }
+    -> Cmd msg
+sendMessageEvent data =
+    case data.vault of
+        Vault vault ->
+            Api.sendMessageEvent vault
+                { content = data.content
+                , eventType = data.eventType
+                , roomId = data.roomId
+                , toMsg = Types.VaultUpdate >> data.toMsg
+                , transactionId = data.transactionId
+                }
 
 
 {-| Using new VaultUpdate information, update the Vault accordingly.
diff --git a/tests/Test/Values/Vault.elm b/tests/Test/Values/Vault.elm
index 96922a8..3982791 100644
--- a/tests/Test/Values/Vault.elm
+++ b/tests/Test/Values/Vault.elm
@@ -19,4 +19,4 @@ vault =
             |> Fuzz.map Dict.fromList
         )
         (TestHashdict.fuzzer .roomId TestRoom.fuzzer)
-        TestUser.fuzzer
+        (Fuzz.maybe TestUser.fuzzer)

From 1de9566e1dd1eeb08476682b29ed2c3327bc8257 Mon Sep 17 00:00:00 2001
From: Bram 
Date: Thu, 30 May 2024 14:02:20 +0200
Subject: [PATCH 5/5] SECURITY: Remove Elm output

Yes, this index.html file contains credentials. For this reason, the password has been reset and the software should no longer work.
---
 .gitignore |     4 +
 index.html | 16499 ---------------------------------------------------
 2 files changed, 4 insertions(+), 16499 deletions(-)
 delete mode 100644 index.html

diff --git a/.gitignore b/.gitignore
index 9cf9e69..ec38022 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,7 @@ elm-stuff
 repl-temp-*
 # VScode settings
 .vscode/
+
+# Elm output
+index.html
+elm.js
diff --git a/index.html b/index.html
deleted file mode 100644
index 8d00860..0000000
--- a/index.html
+++ /dev/null
@@ -1,16499 +0,0 @@
-
-
-
-  
-  Main
-  
-
-
-
-
-

-
-
-
-
-
\ No newline at end of file