From 9b21e2460a0c0ed7e14c42641c7e76114578d58b Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Tue, 28 Mar 2023 01:17:47 +0000 Subject: [PATCH] Accept #67: Add the password modification endpoint. --- TODO.txt | 2 +- src/Routes/RouteChangePwd.c | 146 ++++++++++++++++++++++++++++++++++++ src/Routes/RouteLogout.c | 2 +- src/Routes/RouteMatrix.c | 4 + src/User.c | 12 ++- src/include/Routes.h | 3 +- src/include/User.h | 2 +- 7 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 src/Routes/RouteChangePwd.c diff --git a/TODO.txt b/TODO.txt index c6106a8..cab30e3 100644 --- a/TODO.txt +++ b/TODO.txt @@ -79,7 +79,7 @@ Milestone: v0.3.0 [~] 4: Account management [~] Deactivate [x] Make sure UserLogin() fails if user is deactivated. - [ ] Change password + [x] Change password [x] Whoami [ ] 9: User Data [ ] 5: Capabilities negotiation diff --git a/src/Routes/RouteChangePwd.c b/src/Routes/RouteChangePwd.c new file mode 100644 index 0000000..8ae8c7a --- /dev/null +++ b/src/Routes/RouteChangePwd.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include + +#include + +#include +#include +#include +#include +#include +#include + +static Array * +PasswordFlow(void) +{ + Array *ret = ArrayCreate(); + + if (!ret) + { + return NULL; + } + ArrayAdd(ret, UiaStageBuild("m.login.password", NULL)); + return ret; +} + +ROUTE_IMPL(RouteChangePwd, args) +{ + Db *db = args->matrixArgs->db; + + User *user = NULL; + + HashMap *request = NULL; + HashMap *response = NULL; + HashMap *devices = NULL; + + JsonValue *val = NULL; + + Array *uiaFlows = NULL; + + int uiaResult; + int logoutDevices = 1; + + char *token; + char *newPassword; + char *key; + + if (MATRIX_PATH_PARTS(args->path) != 0 || + HttpRequestMethodGet(args->context) != HTTP_POST) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + return MatrixErrorCreate(M_UNRECOGNIZED); + } + + response = MatrixGetAccessToken(args->context, &token); + if (response) + { + JsonFree(request); + return response; + } + + request = JsonDecode(HttpServerStream(args->context)); + if (!request) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + return MatrixErrorCreate(M_NOT_JSON); + } + + uiaFlows = ArrayCreate(); + ArrayAdd(uiaFlows, PasswordFlow()); + uiaResult = UiaComplete(uiaFlows, args->context, + args->matrixArgs->db, request, &response, + args->matrixArgs->config); + UiaFlowsFree(uiaFlows); + + if (uiaResult < 0) + { + JsonFree(request); + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + return MatrixErrorCreate(M_UNKNOWN); + } + else if (!uiaResult) + { + JsonFree(request); + return response; + } + + newPassword = JsonValueAsString(HashMapGet(request, "new_password")); + if (!newPassword) + { + JsonFree(request); + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + return MatrixErrorCreate(M_BAD_JSON); + } + + val = HashMapGet(request, "logout_devices"); + if (val) + { + logoutDevices = JsonValueAsBoolean(val); + } + + /* Let's authenticate the user */ + user = UserAuthenticate(db, token); + + if (!user) + { + JsonFree(request); + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + return MatrixErrorCreate(M_UNKNOWN_TOKEN); + } + + UserSetPassword(user, newPassword); + + /* We might want to logout all extra devices */ + if (logoutDevices) + { + /* Deletes all tokens except the passed token */ + UserDeleteTokens(user, token); + } + + UserUnlock(user); + JsonFree(request); + response = HashMapCreate(); + return response; +} diff --git a/src/Routes/RouteLogout.c b/src/Routes/RouteLogout.c index 39b3e4a..3aaf307 100644 --- a/src/Routes/RouteLogout.c +++ b/src/Routes/RouteLogout.c @@ -79,7 +79,7 @@ ROUTE_IMPL(RouteLogout, args) } Free(pathPart); - if (!UserDeleteTokens(user)) + if (!UserDeleteTokens(user, NULL)) { /* If we can't delete all of our tokens, then something is * wrong. */ diff --git a/src/Routes/RouteMatrix.c b/src/Routes/RouteMatrix.c index 7320812..08c107c 100644 --- a/src/Routes/RouteMatrix.c +++ b/src/Routes/RouteMatrix.c @@ -94,6 +94,10 @@ ROUTE_IMPL(RouteMatrix, args) { response = RouteWhoami(args); } + else if (MATRIX_PATH_EQUALS(pathPart, "password")) + { + response = RouteChangePwd(args); + } else { HttpResponseStatus(args->context, HTTP_NOT_FOUND); diff --git a/src/User.c b/src/User.c index 2df884b..02a89f3 100644 --- a/src/User.c +++ b/src/User.c @@ -615,7 +615,7 @@ UserDeleteToken(User * user, char *token) } int -UserDeleteTokens(User * user) +UserDeleteTokens(User * user, char *exempt) { HashMap *devices; char *deviceId; @@ -638,6 +638,11 @@ UserDeleteTokens(User * user) char *accessToken = JsonValueAsString(HashMapGet(device, "accessToken")); char *refreshToken = JsonValueAsString(HashMapGet(device, "refreshToken")); + if (exempt && (strcmp(accessToken, exempt) == 0)) + { + continue; + } + if (accessToken) { DbDelete(user->db, 3, "tokens", "access", accessToken); @@ -647,10 +652,9 @@ UserDeleteTokens(User * user) { DbDelete(user->db, 3, "tokens", "refresh", refreshToken); } - } - JsonValueFree(HashMapDelete(DbJson(user->ref), "devices")); - HashMapSet(DbJson(user->ref), "devices", JsonValueObject(HashMapCreate())); + JsonValueFree(HashMapDelete(devices, deviceId)); + } return 1; } diff --git a/src/include/Routes.h b/src/include/Routes.h index 4156a93..cb5422b 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -65,7 +65,8 @@ ROUTE(RouteLogin); /* /_matrix/client/(r0|v3)/login */ ROUTE(RouteLogout); /* /_matrix/client/(r0|v3)/logout */ ROUTE(RouteRegister); /* /_matrix/client/(r0|v3)/register */ ROUTE(RouteRefresh); /* /_matrix/client/(r0|v3)/refresh */ -ROUTE(RouteWhoami); /* /_matrix/client/(r0|v3)/whoami */ +ROUTE(RouteWhoami); /* /_matrix/client/(r0|v3)/account/whoami */ +ROUTE(RouteChangePwd); /* /_matrix/client/(r0|v3)/account/password */ ROUTE(RouteTokenValid); /* /_matrix/client/v1/register/m.logi * n.registration_token/validity */ diff --git a/src/include/User.h b/src/include/User.h index d14f7d9..84b89b1 100644 --- a/src/include/User.h +++ b/src/include/User.h @@ -103,7 +103,7 @@ extern int UserDeleteToken(User *, char *); extern int - UserDeleteTokens(User *); + UserDeleteTokens(User *, char *); extern UserId * UserIdParse(char *, char *);