forked from lda/telodendria
Document User API
This commit is contained in:
parent
e73cea64b7
commit
0ac21d430a
7 changed files with 138 additions and 33 deletions
7
TODO.txt
7
TODO.txt
|
@ -11,7 +11,7 @@ Key:
|
||||||
Milestone: v0.2.0
|
Milestone: v0.2.0
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
[ ] Abstract /email/requestToken and /msidsn/requestToken
|
[!] Abstract /email/requestToken and /msidsn/requestToken
|
||||||
- Call it RouteRequestToken
|
- Call it RouteRequestToken
|
||||||
- Reference it all the places it's needed
|
- Reference it all the places it's needed
|
||||||
- Finish implementing JSON parsing
|
- Finish implementing JSON parsing
|
||||||
|
@ -22,8 +22,8 @@ Milestone: v0.2.0
|
||||||
[x] User login
|
[x] User login
|
||||||
[x] Logout all
|
[x] Logout all
|
||||||
|
|
||||||
[ ] Documentation
|
[~] Documentation
|
||||||
[ ] User functions
|
[x] User functions
|
||||||
[ ] Json functions
|
[ ] Json functions
|
||||||
[ ] Db functions
|
[ ] Db functions
|
||||||
[ ] Uia (move docs from Matrix)
|
[ ] Uia (move docs from Matrix)
|
||||||
|
@ -64,6 +64,7 @@ Milestone: v0.3.0
|
||||||
registration is disabled.
|
registration is disabled.
|
||||||
[ ] 4: Account management
|
[ ] 4: Account management
|
||||||
[ ] Deactivate
|
[ ] Deactivate
|
||||||
|
[ ] Make sure UserLogin() fails if user is deactivated.
|
||||||
[ ] Change password
|
[ ] Change password
|
||||||
[ ] Whoami
|
[ ] Whoami
|
||||||
[ ] 9: User Data
|
[ ] 9: User Data
|
||||||
|
|
124
man/man3/User.3
124
man/man3/User.3
|
@ -1,4 +1,4 @@
|
||||||
.Dd $Mdocdate: February 14 2023 $
|
.Dd $Mdocdate: March 6 2023 $
|
||||||
.Dt USER 3
|
.Dt USER 3
|
||||||
.Os Telodendria Project
|
.Os Telodendria Project
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
@ -26,14 +26,55 @@
|
||||||
.Fn UserGetName "User *"
|
.Fn UserGetName "User *"
|
||||||
.Ft int
|
.Ft int
|
||||||
.Fn UserCheckPassword "User *" "char *"
|
.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
|
.Sh DESCRIPTION
|
||||||
.Pp
|
|
||||||
The
|
The
|
||||||
.Nm
|
.Nm
|
||||||
API provides a wrapper over the database and offers an easy way for managing
|
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,
|
local users. It supports all of the locking mechanisms that the database does,
|
||||||
and provides features for authenticating local users, among other tasks.
|
and provides features for authenticating local users, among other tasks.
|
||||||
.Pp
|
.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
|
.Fn UserValidate
|
||||||
takes a localpart and domain as separate parameters and validates it against the
|
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
|
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
|
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
|
store passwords in plain text, so this function hashes the password and and
|
||||||
checks it against what's stored in the database.
|
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
|
.Sh RETURN VALUES
|
||||||
.Pp
|
.Pp
|
||||||
.Fn UserValidate ,
|
.Fn UserValidate ,
|
||||||
.Fn UserHistoricalValidate ,
|
.Fn UserHistoricalValidate ,
|
||||||
.Fn UserExists ,
|
.Fn UserExists ,
|
||||||
.Fn UserUnlock ,
|
.Fn UserUnlock ,
|
||||||
|
.Fn UserCheckPassword ,
|
||||||
|
.Fn UserSetPassword ,
|
||||||
|
.Fn UserDeactivate ,
|
||||||
|
.Fn UserAccessTokenSave ,
|
||||||
|
.Fn UserDeleteToken ,
|
||||||
and
|
and
|
||||||
.Fn UserCheckPassword
|
.Fn UserDeleteTokens
|
||||||
all return a boolean value. Non-zero values indicate success, and zero values
|
all return a boolean value. Non-zero values indicate success, and zero values
|
||||||
indicate failure.
|
indicate failure.
|
||||||
.Pp
|
.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.
|
is used internally and will be freed when the user is unlocked.
|
||||||
.Pp
|
.Pp
|
||||||
.Fn UserLogin
|
.Fn UserLogin
|
||||||
returns a UserLoginInfo struct, which is defined as follows:
|
returns a UserLoginInfo struct, or
|
||||||
.Bd -literal -offset indent
|
.Dv NULL
|
||||||
typedef struct UserLoginInfo
|
if something goes wrong.
|
||||||
{
|
|
||||||
char *accessToken;
|
|
||||||
char *refreshToken;
|
|
||||||
char *deviceId;
|
|
||||||
long tokenLifetime;
|
|
||||||
} UserLoginInfo;
|
|
||||||
.Ed
|
|
||||||
.Pp
|
|
||||||
All this information should be returned to the client that is logging in. If the
|
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.
|
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
|
.Sh SEE ALSO
|
||||||
.Xr Db 3
|
.Xr Db 3
|
||||||
|
|
|
@ -160,7 +160,7 @@ ROUTE_IMPL(RouteLogin, args)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
userId = UserParseId(JsonValueAsString(val),
|
userId = UserIdParse(JsonValueAsString(val),
|
||||||
args->matrixArgs->config->serverName);
|
args->matrixArgs->config->serverName);
|
||||||
if (!userId)
|
if (!userId)
|
||||||
{
|
{
|
||||||
|
@ -292,7 +292,7 @@ ROUTE_IMPL(RouteLogin, args)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserFreeId(userId);
|
UserIdFree(userId);
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ ROUTE_IMPL(RouteRefresh, args)
|
||||||
|
|
||||||
/* Generate a new access token associated with the device and user. */
|
/* Generate a new access token associated with the device and user. */
|
||||||
deviceId = JsonValueAsString(HashMapGet(DbJson(oAtRef), "device"));
|
deviceId = JsonValueAsString(HashMapGet(DbJson(oAtRef), "device"));
|
||||||
newAccessToken = UserGenerateAccessToken(user, deviceId, 1);
|
newAccessToken = UserAccessTokenGenerate(user, deviceId, 1);
|
||||||
UserAccessTokenSave(db, newAccessToken);
|
UserAccessTokenSave(db, newAccessToken);
|
||||||
|
|
||||||
/* Replace old access token in User */
|
/* Replace old access token in User */
|
||||||
|
|
14
src/Uia.c
14
src/Uia.c
|
@ -212,7 +212,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
char *authType;
|
char *authType;
|
||||||
Array *completed;
|
Array *completed;
|
||||||
Array *possibleNext;
|
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;
|
size_t i;
|
||||||
|
|
||||||
DbRef *dbRef;
|
DbRef *dbRef;
|
||||||
|
@ -353,7 +355,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
}
|
}
|
||||||
|
|
||||||
type = JsonValueAsString(HashMapGet(identifier, "type"));
|
type = JsonValueAsString(HashMapGet(identifier, "type"));
|
||||||
userId = UserParseId(JsonValueAsString(HashMapGet(identifier, "user")),
|
userId = UserIdParse(JsonValueAsString(HashMapGet(identifier, "user")),
|
||||||
config->serverName);
|
config->serverName);
|
||||||
|
|
||||||
if (!type || strcmp(type, "m.id.user") != 0
|
if (!type || strcmp(type, "m.id.user") != 0
|
||||||
|
@ -361,7 +363,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
{
|
{
|
||||||
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
||||||
ret = BuildResponse(flows, db, response, session, dbRef);
|
ret = BuildResponse(flows, db, response, session, dbRef);
|
||||||
UserFreeId(userId);
|
UserIdFree(userId);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,7 +372,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
{
|
{
|
||||||
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
||||||
ret = BuildResponse(flows, db, response, session, dbRef);
|
ret = BuildResponse(flows, db, response, session, dbRef);
|
||||||
UserFreeId(userId);
|
UserIdFree(userId);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,12 +380,12 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
{
|
{
|
||||||
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
||||||
ret = BuildResponse(flows, db, response, session, dbRef);
|
ret = BuildResponse(flows, db, response, session, dbRef);
|
||||||
UserFreeId(userId);
|
UserIdFree(userId);
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserFreeId(userId);
|
UserIdFree(userId);
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
}
|
}
|
||||||
else if (strcmp(authType, "m.login.registration_token") == 0)
|
else if (strcmp(authType, "m.login.registration_token") == 0)
|
||||||
|
|
14
src/User.c
14
src/User.c
|
@ -269,7 +269,7 @@ UserLogin(User * user, char *password, char *deviceId, char *deviceDisplayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Generate an access token */
|
/* Generate an access token */
|
||||||
result->accessToken = UserGenerateAccessToken(user, deviceId, withRefresh);
|
result->accessToken = UserAccessTokenGenerate(user, deviceId, withRefresh);
|
||||||
UserAccessTokenSave(user->db, result->accessToken);
|
UserAccessTokenSave(user->db, result->accessToken);
|
||||||
|
|
||||||
if (withRefresh)
|
if (withRefresh)
|
||||||
|
@ -444,7 +444,7 @@ UserGetDevices(User * user)
|
||||||
}
|
}
|
||||||
|
|
||||||
UserAccessToken *
|
UserAccessToken *
|
||||||
UserGenerateAccessToken(User * user, char *deviceId, int withRefresh)
|
UserAccessTokenGenerate(User * user, char *deviceId, int withRefresh)
|
||||||
{
|
{
|
||||||
UserAccessToken *token;
|
UserAccessToken *token;
|
||||||
|
|
||||||
|
@ -641,7 +641,7 @@ UserDeleteTokens(User * user)
|
||||||
}
|
}
|
||||||
|
|
||||||
UserId *
|
UserId *
|
||||||
UserParseId(char *id, char *defaultServer)
|
UserIdParse(char *id, char *defaultServer)
|
||||||
{
|
{
|
||||||
UserId *userId;
|
UserId *userId;
|
||||||
|
|
||||||
|
@ -693,13 +693,19 @@ UserParseId(char *id, char *defaultServer)
|
||||||
userId->server = StrDuplicate(defaultServer);
|
userId->server = StrDuplicate(defaultServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!UserHistoricalValidate(userId->localpart, userId->server))
|
||||||
|
{
|
||||||
|
UserIdFree(userId);
|
||||||
|
userId = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
Free(id);
|
Free(id);
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
UserFreeId(UserId * id)
|
UserIdFree(UserId * id)
|
||||||
{
|
{
|
||||||
if (id)
|
if (id)
|
||||||
{
|
{
|
||||||
|
|
|
@ -88,7 +88,7 @@ extern HashMap *
|
||||||
UserGetDevices(User *);
|
UserGetDevices(User *);
|
||||||
|
|
||||||
extern UserAccessToken *
|
extern UserAccessToken *
|
||||||
UserGenerateAccessToken(User *, char *, int);
|
UserAccessTokenGenerate(User *, char *, int);
|
||||||
|
|
||||||
extern int
|
extern int
|
||||||
UserAccessTokenSave(Db *, UserAccessToken *);
|
UserAccessTokenSave(Db *, UserAccessToken *);
|
||||||
|
@ -103,9 +103,9 @@ extern int
|
||||||
UserDeleteTokens(User *);
|
UserDeleteTokens(User *);
|
||||||
|
|
||||||
extern UserId *
|
extern UserId *
|
||||||
UserParseId(char *, char *);
|
UserIdParse(char *, char *);
|
||||||
|
|
||||||
extern void
|
extern void
|
||||||
UserFreeId(UserId *);
|
UserIdFree(UserId *);
|
||||||
|
|
||||||
#endif /* TELODENDRIA_USER_H */
|
#endif /* TELODENDRIA_USER_H */
|
||||||
|
|
Loading…
Reference in a new issue