Merge pull request #25 from noordstar/4-transfer-api

Add password
pull/26/head
BramvdnHeuvel 2024-05-30 14:07:52 +02:00 committed by GitHub
commit acd4a07d5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 176 additions and 39 deletions

4
.gitignore vendored
View File

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

View File

@ -190,6 +190,7 @@ loginWithUsernameAndPasswordV1 { username, password } =
, refresh = out.refreshToken , refresh = out.refreshToken
, value = out.accessToken , value = out.accessToken
} }
, E.RemovePasswordIfNecessary
, out.user , out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate) |> Maybe.map (V.SetUser >> E.ContentUpdate)
|> E.Optional |> E.Optional
@ -231,6 +232,7 @@ loginWithUsernameAndPasswordV2 { deviceId, initialDeviceDisplayName, username, p
, refresh = Nothing , refresh = Nothing
, value = out.accessToken , value = out.accessToken
} }
, E.RemovePasswordIfNecessary
, out.user , out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate) |> Maybe.map (V.SetUser >> E.ContentUpdate)
|> E.Optional |> E.Optional
@ -282,6 +284,7 @@ loginWithUsernameAndPasswordV3 { deviceId, initialDeviceDisplayName, username, p
, refresh = Nothing , refresh = Nothing
, value = out.accessToken , value = out.accessToken
} }
, E.RemovePasswordIfNecessary
, out.user , out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate) |> Maybe.map (V.SetUser >> E.ContentUpdate)
|> E.Optional |> E.Optional
@ -333,6 +336,7 @@ loginWithUsernameAndPasswordV4 { deviceId, initialDeviceDisplayName, username, p
, refresh = Nothing , refresh = Nothing
, value = out.accessToken , value = out.accessToken
} }
, E.RemovePasswordIfNecessary
, out.user , out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate) |> Maybe.map (V.SetUser >> E.ContentUpdate)
|> E.Optional |> E.Optional
@ -388,6 +392,7 @@ loginWithUsernameAndPasswordV5 { deviceId, initialDeviceDisplayName, username, p
, refresh = Nothing , refresh = Nothing
, value = out.accessToken , value = out.accessToken
} }
, E.RemovePasswordIfNecessary
, out.user , out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate) |> Maybe.map (V.SetUser >> E.ContentUpdate)
|> E.Optional |> E.Optional
@ -444,6 +449,7 @@ loginWithUsernameAndPasswordV6 { deviceId, enableRefreshToken, initialDeviceDisp
, refresh = out.refreshToken , refresh = out.refreshToken
, value = out.accessToken , value = out.accessToken
} }
, E.RemovePasswordIfNecessary
, out.user , out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate) |> Maybe.map (V.SetUser >> E.ContentUpdate)
|> E.Optional |> E.Optional
@ -500,6 +506,7 @@ loginWithUsernameAndPasswordV7 { deviceId, enableRefreshToken, initialDeviceDisp
, refresh = out.refreshToken , refresh = out.refreshToken
, value = out.accessToken , value = out.accessToken
} }
, E.RemovePasswordIfNecessary
, E.ContentUpdate (V.SetUser out.user) , E.ContentUpdate (V.SetUser out.user)
, out.wellKnown , out.wellKnown
|> Maybe.map (.homeserver >> .baseUrl) |> Maybe.map (.homeserver >> .baseUrl)

View File

@ -1,6 +1,7 @@
module Internal.Config.Default exposing module Internal.Config.Default exposing
( currentVersion, deviceName ( currentVersion, deviceName
, syncTime , syncTime
, removePasswordOnLogin
) )
{-| This module hosts all default settings and configurations that the Vault {-| This module hosts all default settings and configurations that the Vault
@ -16,6 +17,11 @@ will assume until overriden by the user.
@docs syncTime @docs syncTime
## Security
@docs removePasswordOnLogin
-} -}
@ -52,3 +58,13 @@ The value is in miliseconds, so it is set at 30,000.
syncTime : Int syncTime : Int
syncTime = syncTime =
30 * 1000 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 : , settings :
{ currentVersion : Desc { currentVersion : Desc
, deviceName : Desc , deviceName : Desc
, removePasswordOnLogin : Desc
, syncTime : Desc , syncTime : Desc
} }
, timeline : , timeline :
@ -501,6 +502,9 @@ fields =
, deviceName = , deviceName =
[ "Indicates the device name that is communicated to the Matrix API." [ "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 = , syncTime =
[ "Indicates the frequency in miliseconds with which the Elm SDK should long-poll the /sync endpoint." [ "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)) | More (List (EnvelopeUpdate a))
| Optional (Maybe (EnvelopeUpdate a)) | Optional (Maybe (EnvelopeUpdate a))
| RemoveAccessToken String | RemoveAccessToken String
| RemovePasswordIfNecessary
| SetAccessToken AccessToken | SetAccessToken AccessToken
| SetBaseUrl String | SetBaseUrl String
| SetDeviceId String | SetDeviceId String
@ -311,6 +312,13 @@ update updateContent eu ({ context } as data) =
RemoveAccessToken token -> RemoveAccessToken token ->
{ data | context = { context | accessTokens = Hashdict.removeKey token context.accessTokens } } { data | context = { context | accessTokens = Hashdict.removeKey token context.accessTokens } }
RemovePasswordIfNecessary ->
if data.settings.removePasswordOnLogin then
{ data | context = { context | password = Nothing } }
else
data
SetAccessToken a -> SetAccessToken a ->
{ data | context = { context | accessTokens = Hashdict.insert a context.accessTokens } } { 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 = type alias Settings =
{ currentVersion : String { currentVersion : String
, deviceName : String , deviceName : String
, removePasswordOnLogin : Bool
, syncTime : Int , syncTime : Int
} }
@ -43,7 +44,7 @@ type alias Settings =
-} -}
coder : Json.Coder Settings coder : Json.Coder Settings
coder = coder =
Json.object3 Json.object4
{ name = Text.docs.settings.name { name = Text.docs.settings.name
, description = Text.docs.settings.description , description = Text.docs.settings.description
, init = Settings , init = Settings
@ -66,6 +67,21 @@ coder =
, defaultToString = identity , 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 (Json.field.optional.withDefault
{ fieldName = "syncTime" { fieldName = "syncTime"
, toField = .syncTime , toField = .syncTime
@ -97,5 +113,6 @@ init : Settings
init = init =
{ currentVersion = Default.currentVersion { currentVersion = Default.currentVersion
, deviceName = Default.deviceName , deviceName = Default.deviceName
, removePasswordOnLogin = Default.removePasswordOnLogin
, syncTime = Default.syncTime , syncTime = Default.syncTime
} }

View File

@ -45,7 +45,7 @@ import Internal.Values.User as User exposing (User)
type alias Vault = type alias Vault =
{ accountData : Dict String Json.Value { accountData : Dict String Json.Value
, rooms : Hashdict Room , rooms : Hashdict Room
, user : User , user : Maybe User
} }
@ -81,7 +81,7 @@ coder =
, coder = Hashdict.coder .roomId Room.coder , coder = Hashdict.coder .roomId Room.coder
} }
) )
(Json.field.required (Json.field.optional.value
{ fieldName = "user" { fieldName = "user"
, toField = .user , toField = .user
, description = Text.fields.vault.user , description = Text.fields.vault.user
@ -106,11 +106,11 @@ getAccountData key vault =
{-| Initiate a new Vault type. {-| Initiate a new Vault type.
-} -}
init : User -> Vault init : Maybe User -> Vault
init user = init mUser =
{ accountData = Dict.empty { accountData = Dict.empty
, rooms = Hashdict.empty .roomId , rooms = Hashdict.empty .roomId
, user = user , user = mUser
} }
@ -156,4 +156,4 @@ update vu vault =
setAccountData key value vault setAccountData key value vault
SetUser user -> SetUser user ->
{ vault | user = user } { vault | user = Just user }

View File

@ -1,5 +1,5 @@
module Matrix exposing module Matrix exposing
( Vault, fromUserId ( Vault, fromUserId, fromUsername
, VaultUpdate, update , VaultUpdate, update
, addAccessToken, sendMessageEvent , addAccessToken, sendMessageEvent
) )
@ -19,7 +19,7 @@ support a monolithic public registry. (:
## Vault ## Vault
@docs Vault, fromUserId @docs Vault, fromUserId, fromUsername
## Keeping the Vault up-to-date ## Keeping the Vault up-to-date
@ -57,6 +57,9 @@ type alias VaultUpdate =
Types.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 : String -> Vault -> Vault
addAccessToken token (Vault vault) = addAccessToken token (Vault vault) =
Envelope.mapContext (\c -> { c | suggestedAccessToken = Just token }) vault Envelope.mapContext (\c -> { c | suggestedAccessToken = Just token }) vault
@ -74,16 +77,39 @@ addAccessToken token (Vault vault) =
-} -}
fromUserId : String -> Maybe Vault fromUserId : String -> Maybe Vault
fromUserId = fromUserId uid =
User.fromString uid
>> Maybe.map |> User.fromString
|> Maybe.map
(\u -> (\u ->
Envelope.init Envelope.init
{ serverName = "https://" ++ User.domain u { 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. {-| Send a message event to a room.
@ -94,8 +120,18 @@ exists and the user is able to send a message to, and instead just sends the
request to the Matrix API. request to the Matrix API.
-} -}
sendMessageEvent : Vault -> { content : E.Value, eventType : String, roomId : String, toMsg : VaultUpdate -> msg, transactionId : String } -> Cmd msg sendMessageEvent :
sendMessageEvent (Vault vault) data = { 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 Api.sendMessageEvent vault
{ content = data.content { content = data.content
, eventType = data.eventType , eventType = data.eventType

View File

@ -2,6 +2,8 @@ module Matrix.Settings exposing
( setAccessToken, removeAccessToken ( setAccessToken, removeAccessToken
, getDeviceName, setDeviceName , getDeviceName, setDeviceName
, getSyncTime, setSyncTime , getSyncTime, setSyncTime
, setPassword
, removePassword, removePasswordOnLogin
) )
{-| The Matrix Vault has lots of configurable variables that you rarely want to {-| 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 @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 Internal.Values.Envelope as Envelope
import Types exposing (Vault(..)) import Types exposing (Vault(..))
{-| Insert a suggested access token. {-| Determine the device name.
-} -}
setAccessToken : String -> Vault -> Vault getDeviceName : Vault -> String
setAccessToken token (Vault vault) = getDeviceName (Vault vault) =
vault Envelope.extractSettings .deviceName vault
|> Envelope.mapContext
(\c -> { c | suggestedAccessToken = Just token })
|> 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 {-| Remove an access token that has been inserted using the
@ -80,11 +101,32 @@ removeAccessToken (Vault vault) =
|> Vault |> Vault
{-| Determine the device name. {-| Remove a password that is stored in the Matrix Vault.
-} -}
getDeviceName : Vault -> String removePassword : Vault -> Vault
getDeviceName (Vault vault) = removePassword (Vault vault) =
Envelope.extractSettings .deviceName 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. {-| Override the device name.
@ -94,11 +136,14 @@ setDeviceName name (Vault vault) =
Vault <| Envelope.mapSettings (\s -> { s | deviceName = name }) vault Vault <| Envelope.mapSettings (\s -> { s | deviceName = name }) vault
{-| Determine the sync timeout value. {-| Set a password for the given user.
-} -}
getSyncTime : Vault -> Int setPassword : String -> Vault -> Vault
getSyncTime (Vault vault) = setPassword password (Vault vault) =
Envelope.extractSettings .syncTime vault vault
|> Envelope.mapContext
(\c -> { c | password = Just password })
|> Vault
{-| Override the sync timeout value. {-| Override the sync timeout value.

View File

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