Compare commits

...

5 Commits

Author SHA1 Message Date
Bram 1de9566e1d 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.
2024-05-30 14:02:20 +02:00
Bram 85d767414d Make username optional 2024-05-30 13:54:30 +02:00
Bram 994c99af15 Add RemovePasswordOnLogin feature 2024-05-30 13:53:56 +02:00
Bram b465ad1f47 Remove password after login, if necessary 2024-05-30 10:52:48 +02:00
Bram e8c0df004e Add removePasswordOnLogin setting 2024-05-30 10:48:20 +02:00
10 changed files with 176 additions and 39 deletions

4
.gitignore vendored
View File

@ -4,3 +4,7 @@ elm-stuff
repl-temp-*
# VScode settings
.vscode/
# Elm output
index.html
elm.js

View File

@ -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
@ -231,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
@ -282,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
@ -333,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
@ -388,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
@ -444,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
@ -500,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)

View File

@ -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

View File

@ -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."
]

View File

@ -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 } }

View File

@ -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
}

View File

@ -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 }

View File

@ -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
@ -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,39 @@ 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
{-| 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.
@ -94,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.

View File

@ -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.

View File

@ -19,4 +19,4 @@ vault =
|> Fuzz.map Dict.fromList
)
(TestHashdict.fuzzer .roomId TestRoom.fuzzer)
TestUser.fuzzer
(Fuzz.maybe TestUser.fuzzer)