diff --git a/TODO.txt b/TODO.txt index 22898f5..6551ff9 100644 --- a/TODO.txt +++ b/TODO.txt @@ -11,7 +11,7 @@ Key: Milestone: v0.2.0 ----------------- -[ ] Abstract /email/requestToken and /msidsn/requestToken +[!] Abstract /email/requestToken and /msidsn/requestToken - Call it RouteRequestToken - Reference it all the places it's needed - Finish implementing JSON parsing @@ -22,8 +22,8 @@ Milestone: v0.2.0 [x] User login [x] Logout all -[ ] Documentation - [ ] User functions +[~] Documentation + [x] User functions [ ] Json functions [ ] Db functions [ ] Uia (move docs from Matrix) @@ -64,6 +64,7 @@ Milestone: v0.3.0 registration is disabled. [ ] 4: Account management [ ] Deactivate + [ ] Make sure UserLogin() fails if user is deactivated. [ ] Change password [ ] Whoami [ ] 9: User Data diff --git a/man/man3/User.3 b/man/man3/User.3 index b7b45ac..ee26ce2 100644 --- a/man/man3/User.3 +++ b/man/man3/User.3 @@ -1,4 +1,4 @@ -.Dd $Mdocdate: February 14 2023 $ +.Dd $Mdocdate: March 6 2023 $ .Dt USER 3 .Os Telodendria Project .Sh NAME @@ -26,14 +26,55 @@ .Fn UserGetName "User *" .Ft int .Fn UserCheckPassword "User *" "char *" +.Ft int +.Fn UserSetPassword "User *" "char *" +.Ft int +.Fn UserDeactivate "User *" +.Ft HashMap * +.Fn UserGetDevices "User *" +.Ft UserAccessToken * +.Fn UserGenerateAccessToken "User *" "char *" "int" +.Ft int +.Fn UserAccessTokenSave "Db *" "UserAccessToken *" +.Ft void +.Fn UserAccessTokenFree "UserAccessToken *" +.Ft int +.Fn UserDeleteToken "User *" "char *" +.Ft int +.Fn UserDeleteTokens "User *" +.Ft UserId * +.Fn UserIdParse "char *" "char *" +.Ft void +.Fn UserIdFree "UserId *" .Sh DESCRIPTION -.Pp The .Nm API provides a wrapper over the database and offers an easy way for managing local users. It supports all of the locking mechanisms that the database does, and provides features for authenticating local users, among other tasks. .Pp +.Bd -literal -offset indent +typedef struct UserLoginInfo +{ + UserAccessToken *accessToken; + char *refreshToken; +} UserLoginInfo; + +typedef struct UserAccessToken +{ + char *user; + char *string; + char *deviceId; + long lifetime; +} UserAccessToken; + +typedef struct UserId +{ + char *localpart; + char *server; +} UserId; +.Ed +.Pp .Fn UserValidate takes a localpart and domain as separate parameters and validates it against the rules of the Matrix specification. The reason the domain is required is because @@ -83,14 +124,58 @@ it's necessary to know the localpart of a user. takes a password and verifies it against a user object. Telodendria does not store passwords in plain text, so this function hashes the password and and checks it against what's stored in the database. +.Pp +.Fn UserSetPassword +resets the given user's password by hashing a plain text password and +storing it in the database. +.Pp +.Fn UserDeactivate +deactivates a user such that it can no longer be used to log in, but +the username is still taken. This is to prevent future users from +pretending to be previous users of a given localpart. +.Pp +.Fn UserGetDevices +fetches the devices that belong to the user, in JSON format, +identical to what's stored in the database. In fact, this JSON is +still linked to the database, so it should not be freed with +.Fn JsonFree . +.Pp +.Fn UserAccessTokenGenerate , +.Fn UserAccessTokenSave , +and +.Fn UserAccessTokenFree +are used for managing individual access tokens on a user. They +operate on the UserAccessToken structure. +.Fn UserAccessTokenGenerate +takes the user localpart to generate the token for, the device ID, +for the token, and a boolean value indicating whether or not the token +should expire. +.Fn UserAccessTokenSave +writes the access token to the database. +.Pp +.Fn UserDeleteToken +and +.Fn UserDeleteTokens +delete a specific access token/refresh token pair, or all the access +and refresh tokens for a given user, respectively. +.Pp +.Fn UserIdParse +parses either a localpart or a fully-qualified Matrix ID. +.Fn UserIdFree +frees the result of this parsing. .Sh RETURN VALUES .Pp .Fn UserValidate , .Fn UserHistoricalValidate , .Fn UserExists , .Fn UserUnlock , +.Fn UserCheckPassword , +.Fn UserSetPassword , +.Fn UserDeactivate , +.Fn UserAccessTokenSave , +.Fn UserDeleteToken , and -.Fn UserCheckPassword +.Fn UserDeleteTokens all return a boolean value. Non-zero values indicate success, and zero values indicate failure. .Pp @@ -106,18 +191,29 @@ by the given user pointer. This pointer should not be freed by the caller , as i is used internally and will be freed when the user is unlocked. .Pp .Fn UserLogin -returns a UserLoginInfo struct, which is defined as follows: -.Bd -literal -offset indent -typedef struct UserLoginInfo -{ - char *accessToken; - char *refreshToken; - char *deviceId; - long tokenLifetime; -} UserLoginInfo; -.Ed -.Pp +returns a UserLoginInfo struct, or +.Dv NULL +if something goes wrong. All this information should be returned to the client that is logging in. If the client doesn't support refresh tokens, then refreshToken will be NULL. +.Pp +.Fn UserGetDevices +returns a JSON object that is linked to the database, or NULL if +there was an error. The result should not be freed with +.Fn JsonFree +because it is still directly attached to the database. This object +is an exact representation of what is stored on the disk. +.Pp +.Fn UserAccessTokenGenerate +generates an access token structure that should be freed when it is +no longer needed, or +.Dv NULL +if there was a memory error. +.Pp +.Fn UserIdParse +returns a UserId structure that should be freed when it is no longer +needed, or +.Dv NULL +if there was a memory error. .Sh SEE ALSO .Xr Db 3 diff --git a/src/Routes/RouteLogin.c b/src/Routes/RouteLogin.c index 6bf49ae..ec442fd 100644 --- a/src/Routes/RouteLogin.c +++ b/src/Routes/RouteLogin.c @@ -160,7 +160,7 @@ ROUTE_IMPL(RouteLogin, args) break; } - userId = UserParseId(JsonValueAsString(val), + userId = UserIdParse(JsonValueAsString(val), args->matrixArgs->config->serverName); if (!userId) { @@ -292,7 +292,7 @@ ROUTE_IMPL(RouteLogin, args) break; } - UserFreeId(userId); + UserIdFree(userId); JsonFree(request); return response; } diff --git a/src/Routes/RouteRefresh.c b/src/Routes/RouteRefresh.c index 84fc727..a6af79a 100644 --- a/src/Routes/RouteRefresh.c +++ b/src/Routes/RouteRefresh.c @@ -134,7 +134,7 @@ ROUTE_IMPL(RouteRefresh, args) /* Generate a new access token associated with the device and user. */ deviceId = JsonValueAsString(HashMapGet(DbJson(oAtRef), "device")); - newAccessToken = UserGenerateAccessToken(user, deviceId, 1); + newAccessToken = UserAccessTokenGenerate(user, deviceId, 1); UserAccessTokenSave(db, newAccessToken); /* Replace old access token in User */ diff --git a/src/Uia.c b/src/Uia.c index a8031dd..71575ef 100644 --- a/src/Uia.c +++ b/src/Uia.c @@ -212,7 +212,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db, char *authType; Array *completed; Array *possibleNext; - int remaining[16]; /* There should never be more than this many stages in a flow, right? */ + int remaining[16]; /* There should never be more than + * this many stages in a flow, + * right? */ size_t i; DbRef *dbRef; @@ -353,7 +355,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db, } type = JsonValueAsString(HashMapGet(identifier, "type")); - userId = UserParseId(JsonValueAsString(HashMapGet(identifier, "user")), + userId = UserIdParse(JsonValueAsString(HashMapGet(identifier, "user")), config->serverName); if (!type || strcmp(type, "m.id.user") != 0 @@ -361,7 +363,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db, { HttpResponseStatus(context, HTTP_UNAUTHORIZED); ret = BuildResponse(flows, db, response, session, dbRef); - UserFreeId(userId); + UserIdFree(userId); goto finish; } @@ -370,7 +372,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db, { HttpResponseStatus(context, HTTP_UNAUTHORIZED); ret = BuildResponse(flows, db, response, session, dbRef); - UserFreeId(userId); + UserIdFree(userId); goto finish; } @@ -378,12 +380,12 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db, { HttpResponseStatus(context, HTTP_UNAUTHORIZED); ret = BuildResponse(flows, db, response, session, dbRef); - UserFreeId(userId); + UserIdFree(userId); UserUnlock(user); goto finish; } - UserFreeId(userId); + UserIdFree(userId); UserUnlock(user); } else if (strcmp(authType, "m.login.registration_token") == 0) diff --git a/src/User.c b/src/User.c index 376ceab..8700808 100644 --- a/src/User.c +++ b/src/User.c @@ -269,7 +269,7 @@ UserLogin(User * user, char *password, char *deviceId, char *deviceDisplayName, } /* Generate an access token */ - result->accessToken = UserGenerateAccessToken(user, deviceId, withRefresh); + result->accessToken = UserAccessTokenGenerate(user, deviceId, withRefresh); UserAccessTokenSave(user->db, result->accessToken); if (withRefresh) @@ -444,7 +444,7 @@ UserGetDevices(User * user) } UserAccessToken * -UserGenerateAccessToken(User * user, char *deviceId, int withRefresh) +UserAccessTokenGenerate(User * user, char *deviceId, int withRefresh) { UserAccessToken *token; @@ -641,7 +641,7 @@ UserDeleteTokens(User * user) } UserId * -UserParseId(char *id, char *defaultServer) +UserIdParse(char *id, char *defaultServer) { UserId *userId; @@ -693,13 +693,19 @@ UserParseId(char *id, char *defaultServer) userId->server = StrDuplicate(defaultServer); } + if (!UserHistoricalValidate(userId->localpart, userId->server)) + { + UserIdFree(userId); + userId = NULL; + } + finish: Free(id); return userId; } void -UserFreeId(UserId * id) +UserIdFree(UserId * id) { if (id) { diff --git a/src/include/User.h b/src/include/User.h index 591e917..7ee6a86 100644 --- a/src/include/User.h +++ b/src/include/User.h @@ -88,7 +88,7 @@ extern HashMap * UserGetDevices(User *); extern UserAccessToken * - UserGenerateAccessToken(User *, char *, int); + UserAccessTokenGenerate(User *, char *, int); extern int UserAccessTokenSave(Db *, UserAccessToken *); @@ -103,9 +103,9 @@ extern int UserDeleteTokens(User *); extern UserId * - UserParseId(char *, char *); + UserIdParse(char *, char *); extern void - UserFreeId(UserId *); + UserIdFree(UserId *); #endif /* TELODENDRIA_USER_H */