diff --git a/elm.json b/elm.json index b911374..a16af88 100644 --- a/elm.json +++ b/elm.json @@ -27,10 +27,12 @@ "Internal.Values.Settings", "Internal.Values.StateManager", "Internal.Values.Timeline", + "Internal.Values.User", "Internal.Values.Vault", "Matrix", "Matrix.Event", "Matrix.Settings", + "Matrix.User", "Types" ], "elm-version": "0.19.0 <= v < 0.20.0", diff --git a/src/Internal/Config/Text.elm b/src/Internal/Config/Text.elm index 1d128cc..bb6e933 100644 --- a/src/Internal/Config/Text.elm +++ b/src/Internal/Config/Text.elm @@ -1,10 +1,9 @@ module Internal.Config.Text exposing - ( docs, failures, fields, mappings, logs + ( docs, failures, fields, mappings, logs, parses , accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid , versionsFoundLocally, versionsReceived, versionsFailedToDecode , unsupportedVersionForEndpoint , decodedDictSize, invalidHashInHashdict, invalidHashInMashdict, leakingValueFound - , parses ) {-| Throughout the Elm SDK, there are lots of pieces of text being used for @@ -28,7 +27,7 @@ You should only do this if you know what you're doing. ## Type documentation -@docs docs, failures, fields, mappings, logs +@docs docs, failures, fields, mappings, logs, parses ## API Authentication @@ -487,23 +486,6 @@ leakingValueFound leaking_value = "Found leaking value : " ++ leaking_value -parses : - { reservedIPs : - { ipv6Toipv4 : String - , multicast : String - , futureUse : String - , unspecified : String - } - } -parses = - { reservedIPs = - { ipv6Toipv4 = "Detected a reserved ip address that is formerly used as an IPv6 to IPv4 relay. It is unlikely that this IP Address is real." - , multicast = "Detected a reserved ip address that is used for multicasting. It is unlikely that this IP Address is real." - , futureUse = "Detected a reserves ip address that is reserved for future use. It is unlikely that this IP Address is real if you're running a recent version of the Elm SDK." - , unspecified = "This is an unspecified ip address. It is unlikely that this IP Address is real and someone might try to break something." - } - } - {-| These logs might appear during a process where something unexpected has happened. Most of these unexpected results, are taken account of by the Elm SDK, but logged so that the programmer can do something about it. @@ -533,6 +515,28 @@ mappings = } +{-| Logs for issues that might be found while parsing strings into meaningful data. +-} +parses : + { historicalUserId : String -> String + , reservedIPs : + { ipv6Toipv4 : String + , multicast : String + , futureUse : String + , unspecified : String + } + } +parses = + { historicalUserId = \name -> "Found a historical username `" ++ name ++ "`." + , reservedIPs = + { ipv6Toipv4 = "Detected a reserved ip address that is formerly used as an IPv6 to IPv4 relay. It is unlikely that this IP Address is real." + , multicast = "Detected a reserved ip address that is used for multicasting. It is unlikely that this IP Address is real." + , futureUse = "Detected a reserves ip address that is reserved for future use. It is unlikely that this IP Address is real if you're running a recent version of the Elm SDK." + , unspecified = "This is an unspecified ip address. It is unlikely that this IP Address is real and someone might try to break something." + } + } + + {-| The Matrix homeserver can specify how it wishes to communicate, and the Elm SDK aims to communicate accordingly. This may fail in some scenarios, however, in which case it will throw this error. diff --git a/src/Internal/Values/User.elm b/src/Internal/Values/User.elm new file mode 100644 index 0000000..c39ebfc --- /dev/null +++ b/src/Internal/Values/User.elm @@ -0,0 +1,73 @@ +module Internal.Values.User exposing + ( User, toString, fromString + , localpart, domain + ) + +{-| The Matrix user is uniquely identified by their identifier. This User type +helps identify and safely handle these strings to transform them into meaningful +data types. + + +## User + +@docs User, toString, fromString + + +## Divide + +Matrix users are identified by their unique ID. In the Matrix API, this is a +string that looks as follows: + + @alice:example.org + \---/ \---------/ + | | + | | + localpart domain + +Since the username is safely parsed, one can get these parts of the username. + +@docs localpart, domain + +-} + +import Internal.Grammar.ServerName as ServerName +import Internal.Grammar.UserId as UserId + + +{-| The Matrix user represents a user across multiple Matrix rooms. +-} +type alias User = + UserId.UserID + + +{-| The domain represents the Matrix homeserver controlling this user. It also +offers other Matrix homeservers an indication of where to look if you wish to +send a message to this user. +-} +domain : User -> String +domain = + .domain >> ServerName.toString + + +{-| Parse a string and convert it into a User, if formatted properly. +-} +fromString : String -> Maybe User +fromString = + UserId.fromString + + +{-| The localpart is similar to a username, in the sense that every user has +their own localpart. The localpart is not unique across multiple servers, +however! There can be a user @alice:example.com and a user @alice:example.org in +a room at the same time. +-} +localpart : User -> String +localpart = + .localpart + + +{-| Convert a user into its unique identifier string value. +-} +toString : User -> String +toString = + UserId.toString diff --git a/src/Matrix/User.elm b/src/Matrix/User.elm new file mode 100644 index 0000000..595e1a9 --- /dev/null +++ b/src/Matrix/User.elm @@ -0,0 +1,147 @@ +module Matrix.User exposing + ( User, toString + , localpart, domain + , get + ) + +{-| Matrix users are identified by their unique ID. In the Matrix API, this is a +string that looks as follows: + + @alice:example.org + \---/ \---------/ + | | + | | + localpart domain + +Since it is very easy to abuse Matrix user IDs to sneak in arbitrary values, +the Elm SDK parses them and makes sure they are safe. As a result, you might +need this module to get the right information from a user! + + +## User + +@docs User, toString + + +## Info + +Sometimes, you are more interested in the username itself. These functions can +help you decipher, disambiguate and categorize users based on their username. + +@docs localpart, domain + + +## Manipulate + +@docs get + +-} + +import Internal.Values.Envelope as Envelope +import Internal.Values.User as Internal +import Types exposing (User(..)) + + +{-| The User type represents a Matrix user. + +It contains information like: + + - Their username on Matrix + - The server that hosts their account + - Access tokens needed to talk to the server + +It does **NOT** contain information like: + + - Their nickname + - Their profile picture + - Your private room with them + +You can get all that information by looking it up in the [Vault](Matrix#Vault). + +**Note:** Please do not store this user type as a variable in your model! You +should always maintain a single source of truth in Elm, and the User type +contains various credentials and API tokens that might expire if you don't +update them from the Vault. + +If you need to remember specific users, you can best compare their identifying +string using [toString](Matrix-User#toString) or you can use +[get](Matrix-User#get) with the Vault to get the user type. + +-} +type alias User = + Types.User + + +{-| The domain is the name of the server that the user connects to. Server names +are case-sensitive, so if the strings are equal, the users are on the same +server! + +As a result, you can use the user domain for: + + - When multiple users in a room have the same localpart on different servers + - Finding other users from a potentially malicious homeserver + - Counting homeservers in a room + +See the following examples: + + domain (get vault "@alice:example.org") -- "example.org" + + domain (get vault "@bob:127.0.0.1") -- "127.0.0.1" + + domain (get vault "@charlie:[2001:db8::]") -- "[2001:db8::]" + +-} +domain : User -> String +domain (User user) = + Envelope.extract Internal.domain user + + +{-| Get a specific user by their unique identifier. + +The Vault is needed as an input because the `User` type also stores various +credentials needed to talk to the Matrix API. + + get vault "@alice:example.org" -- Just (User "alice" "example.org") + + get vault "@bob:127.0.0.1" -- Just (User "bob" "127.0.0.1") + + get vault "@charlie:[2001:db8::]" -- Just (User "charlie" "2001:db8::") + + get vault "@evil:#mp#ss#bl#.c#m" -- Nothing + + get vault "" -- Nothing + +-} +get : Types.Vault -> String -> Maybe User +get (Types.Vault vault) username = + Envelope.mapMaybe (\_ -> Internal.fromString username) vault + |> Maybe.map Types.User + + +{-| The localpart is the user's unique username. Every homeserver has their own +username registry, so you might occasionally find distinct users with the same +localpart. + +The localpart is often used as a user's name in a room if they haven't set up +a custom name. + +See the following examples: + + localpart (get vault "@alice:example.org") -- "alice" + + localpart (get vault "@bob:127.0.0.1") -- "bob" + + localpart (get vault "@charlie:[2001:db8::]") -- "charlie" + +-} +localpart : User -> String +localpart (User user) = + Envelope.extract Internal.localpart user + + +{-| Get the uniquely identifying string for this user. Since the strings are +case-sensitive, you can run a simple string comparison to compare usernames. +-} +toString : User -> String +toString (User user) = + Envelope.extract Internal.toString user diff --git a/src/Types.elm b/src/Types.elm index 242bea7..aef9df0 100644 --- a/src/Types.elm +++ b/src/Types.elm @@ -1,4 +1,4 @@ -module Types exposing (Vault(..), Event(..)) +module Types exposing (Vault(..), Event(..), User(..)) {-| The Elm SDK uses a lot of records and values that are easy to manipulate. Yet, the [Elm design guidelines](https://package.elm-lang.org/help/design-guidelines#keep-tags-and-record-constructors-secret) @@ -12,12 +12,13 @@ access their content directly. The opaque types are placed in a central module so all exposed modules can safely access all exposed data types without risking to create circular imports. -@docs Vault, Event +@docs Vault, Event, User -} import Internal.Values.Envelope as Envelope import Internal.Values.Event as Event +import Internal.Values.User as User import Internal.Values.Vault as Vault @@ -27,6 +28,12 @@ type Event = Event (Envelope.Envelope Event.Event) +{-| Opaque type for Matrix User +-} +type User + = User (Envelope.Envelope User.User) + + {-| Opaque type for Matrix Vault -} type Vault