Skip to content

Commit

Permalink
Adding delete user endpoint (#350)
Browse files Browse the repository at this point in the history
* Adding delete user endpoint

* Updated changelog and jsons

* Updated according to new plugin interface + deleting users last

* Removed rid of delete user endpoint

* Added explanation about the deletion order in deletUser

* Updated explanation comment

* Checking if delete revoked sessions of 2nd user

Co-authored-by: Mihaly Lengyel <[email protected]>
  • Loading branch information
porcellus and Mihaly Lengyel authored Dec 20, 2021
1 parent 4d1bcf1 commit c381604
Show file tree
Hide file tree
Showing 15 changed files with 424 additions and 4 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.7.0] - 2021-11-28
## [3.7.0] - 2021-12-16

### Added

- Delete user endpoint

## [3.6.1] - 2021-11-15

Expand Down
3 changes: 2 additions & 1 deletion coreDriverInterfaceSupported.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"versions": [
"2.7",
"2.8",
"2.9"
"2.9",
"2.10"
]
}
2 changes: 1 addition & 1 deletion pluginInterfaceSupported.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_comment": "contains a list of plugin interfaces branch names that this core supports",
"versions": [
"2.9"
"2.10"
]
}
16 changes: 16 additions & 0 deletions src/main/java/io/supertokens/authRecipe/AuthRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,20 @@ public static UserPaginationContainer getUsers(Main main, Integer limit, String
System.arraycopy(users, 0, resultUsers, 0, maxLoop);
return new UserPaginationContainer(resultUsers, nextPaginationToken);
}

public static void deleteUser(Main main, String userId) throws StorageQueryException {
// We clean up the user last so that if anything before that throws an error, then that will throw a 500 to the
// developer. In this case, they expect that the user has not been deleted (which will be true). This is as
// opposed to deleting the user first, in which case if something later throws an error, then the user has
// actually been deleted already (which is not expected by the dev)

// For things created after the intial cleanup and before finishing the operation:
// - session: the session will expire anyway
// - email verification: email verification tokens can be created for any userId anyway

StorageLayer.getSessionStorage(main).deleteSessionsOfUser(userId);
StorageLayer.getEmailVerificationStorage(main).deleteEmailVerificationUserInfo(userId);
StorageLayer.getEmailPasswordStorage(main).deleteEmailPasswordUser(userId);
StorageLayer.getThirdPartyStorage(main).deleteThirdPartyUser(userId);
}
}
36 changes: 36 additions & 0 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,15 @@ public void createNewSession(String sessionHandle, String userId, String refresh
}
}

@Override
public void deleteSessionsOfUser(String userId) throws StorageQueryException {
try {
SessionQueries.deleteSessionsOfUser(this, userId);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int getNumberOfSessions() throws StorageQueryException {
try {
Expand Down Expand Up @@ -435,6 +444,15 @@ public void signUp(UserInfo userInfo)
}
}

@Override
public void deleteEmailPasswordUser(String userId) throws StorageQueryException {
try {
EmailPasswordQueries.deleteUser(this, userId);
} catch (StorageTransactionLogicException e) {
throw new StorageQueryException(e.actualException);
}
}

@Override
public UserInfo getUserInfoUsingId(String id) throws StorageQueryException {
try {
Expand Down Expand Up @@ -654,6 +672,15 @@ public void updateIsEmailVerified_Transaction(TransactionConnection con, String
}
}

@Override
public void deleteEmailVerificationUserInfo(String userId) throws StorageQueryException {
try {
EmailVerificationQueries.deleteUserInfo(this, userId);
} catch (StorageTransactionLogicException e) {
throw new StorageQueryException(e.actualException);
}
}

@Override
public EmailVerificationTokenInfo getEmailVerificationTokenInfo(String token) throws StorageQueryException {
try {
Expand Down Expand Up @@ -756,6 +783,15 @@ public void signUp(io.supertokens.pluginInterface.thirdparty.UserInfo userInfo)
}
}

@Override
public void deleteThirdPartyUser(String userId) throws StorageQueryException {
try {
ThirdPartyQueries.deleteUser(this, userId);
} catch (StorageTransactionLogicException e) {
throw new StorageQueryException(e.actualException);
}
}

@Override
public io.supertokens.pluginInterface.thirdparty.UserInfo getThirdPartyUserInfoUsingId(String thirdPartyId,
String thirdPartyUserId) throws StorageQueryException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,50 @@ public static void signUp(Start start, String userId, String email, String passw
});
}

public static void deleteUser(Start start, String userId)
throws StorageQueryException, StorageTransactionLogicException {
start.startTransaction(con -> {
Connection sqlCon = (Connection) con.getConnection();
try {
{
String QUERY = "DELETE FROM " + Config.getConfig(start).getUsersTable()
+ " WHERE user_id = ? AND recipe_id = ?";

try (PreparedStatement pst = sqlCon.prepareStatement(QUERY)) {
pst.setString(1, userId);
pst.setString(2, RECIPE_ID.EMAIL_PASSWORD.toString());
pst.executeUpdate();
}
}

{
String QUERY = "DELETE FROM " + Config.getConfig(start).getEmailPasswordUsersTable()
+ " WHERE user_id = ?";

try (PreparedStatement pst = sqlCon.prepareStatement(QUERY)) {
pst.setString(1, userId);
pst.executeUpdate();
}
}

{
// This will be done by cascading delete where it's supported
String QUERY = "DELETE FROM " + Config.getConfig(start).getPasswordResetTokensTable()
+ " WHERE user_id = ?";

try (PreparedStatement pst = sqlCon.prepareStatement(QUERY)) {
pst.setString(1, userId);
pst.executeUpdate();
}
}
sqlCon.commit();
} catch (SQLException throwables) {
throw new StorageTransactionLogicException(throwables);
}
return null;
});
}

public static UserInfo getUserInfoUsingId(Start start, String id) throws SQLException, StorageQueryException {
List<String> input = new ArrayList<>();
input.add(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.supertokens.pluginInterface.RowMapper;
import io.supertokens.pluginInterface.emailverification.EmailVerificationTokenInfo;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;

import java.sql.Connection;
import java.sql.PreparedStatement;
Expand Down Expand Up @@ -200,6 +201,38 @@ public static void unverifyEmail(Start start, String userId, String email) throw
}
}

public static void deleteUserInfo(Start start, String userId)
throws StorageQueryException, StorageTransactionLogicException {
start.startTransaction(con -> {
Connection sqlCon = (Connection) con.getConnection();
try {
{
String QUERY = "DELETE FROM " + Config.getConfig(start).getEmailVerificationTable()
+ " WHERE user_id = ?";
try (PreparedStatement pst = sqlCon.prepareStatement(QUERY)) {
pst.setString(1, userId);
pst.executeUpdate();
}
}

{
String QUERY = "DELETE FROM " + Config.getConfig(start).getEmailVerificationTokensTable()
+ " WHERE user_id = ?";

try (PreparedStatement pst = sqlCon.prepareStatement(QUERY)) {
pst.setString(1, userId);
pst.executeUpdate();
}
}

sqlCon.commit();
} catch (SQLException throwables) {
throw new StorageTransactionLogicException(throwables);
}
return null;
});
}

public static void revokeAllTokens(Start start, String userId, String email) throws SQLException {
String QUERY = "DELETE FROM " + Config.getConfig(start).getEmailVerificationTokensTable()
+ " WHERE user_id = ? AND email = ?";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ public static int deleteSession(Start start, String[] sessionHandles) throws SQL
}
}

public static void deleteSessionsOfUser(Start start, String userId) throws SQLException {
String QUERY = "DELETE FROM " + Config.getConfig(start).getSessionInfoTable() + " WHERE user_id = ?";

try (Connection con = ConnectionPool.getConnection(start);
PreparedStatement pst = con.prepareStatement(QUERY.toString())) {
pst.setString(1, userId);
pst.executeUpdate();
}
}

public static String[] getAllSessionHandlesForUser(Start start, String userId) throws SQLException {
String QUERY = "SELECT session_handle FROM " + Config.getConfig(start).getSessionInfoTable()
+ " WHERE user_id = ?";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,38 @@ public static void signUp(Start start, io.supertokens.pluginInterface.thirdparty
});
}

public static void deleteUser(Start start, String userId)
throws StorageQueryException, StorageTransactionLogicException {
start.startTransaction(con -> {
Connection sqlCon = (Connection) con.getConnection();
try {
{
String QUERY = "DELETE FROM " + Config.getConfig(start).getUsersTable()
+ " WHERE user_id = ? AND recipe_id = ?";
try (PreparedStatement pst = sqlCon.prepareStatement(QUERY)) {
pst.setString(1, userId);
pst.setString(2, RECIPE_ID.THIRD_PARTY.toString());
pst.executeUpdate();
}
}

{
String QUERY = "DELETE FROM " + Config.getConfig(start).getThirdPartyUsersTable()
+ " WHERE user_id = ? ";
try (PreparedStatement pst = sqlCon.prepareStatement(QUERY)) {
pst.setString(1, userId);
pst.executeUpdate();
}
}

sqlCon.commit();
} catch (SQLException throwables) {
throw new StorageTransactionLogicException(throwables);
}
return null;
});
}

public static UserInfo getThirdPartyUserInfoUsingId(Start start, String userId)
throws SQLException, StorageQueryException {
List<String> input = new ArrayList<>();
Expand Down
1 change: 1 addition & 0 deletions src/main/java/io/supertokens/webserver/Webserver.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ private void setupRoutes() throws Exception {
addAPI(new TelemetryAPI(main));
addAPI(new UsersCountAPI(main));
addAPI(new UsersAPI(main));
addAPI(new DeleteUserAPI(main));
addAPI(new RevokeAllTokensForUserAPI(main));
addAPI(new UnverifyEmailAPI(main));
addAPI(new JWTSigningAPI(main));
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/supertokens/webserver/WebserverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ public abstract class WebserverAPI extends HttpServlet {
supportedVersions.add("2.7");
supportedVersions.add("2.8");
supportedVersions.add("2.9");
supportedVersions.add("2.10");
}

public static String getLatestCDIVersion() {
return "2.9";
return "2.10";
}

public WebserverAPI(Main main, String rid) {
Expand Down
60 changes: 60 additions & 0 deletions src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package io.supertokens.webserver.api.core;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.JsonObject;

import io.supertokens.Main;
import io.supertokens.authRecipe.AuthRecipe;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.webserver.InputParser;
import io.supertokens.webserver.WebserverAPI;

public class DeleteUserAPI extends WebserverAPI {

private static final long serialVersionUID = -2225750492558064634L;

public DeleteUserAPI(Main main) {
super(main, "");
}

@Override
public String getPath() {
return "/user/remove";
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
JsonObject input = InputParser.parseJsonObjectOrThrowError(req);
String userId = InputParser.parseStringOrThrowError(input, "userId", false);
try {
AuthRecipe.deleteUser(super.main, userId);
JsonObject result = new JsonObject();
result.addProperty("status", "OK");
super.sendJsonResponse(200, result, resp);
} catch (StorageQueryException e) {
throw new ServletException(e);
}
}
}
Loading

0 comments on commit c381604

Please sign in to comment.