aboutsummaryrefslogtreecommitdiff
path: root/userapi
diff options
context:
space:
mode:
authorLoïck Bonniot <git@lesterpig.com>2020-10-02 18:18:20 +0200
committerGitHub <noreply@github.com>2020-10-02 17:18:20 +0100
commit4e8c484618f5bfd5b7349cc2afd102cd5feb3861 (patch)
tree915618d3127f7ead8c5cce5a5814ae861841498a /userapi
parent279044cd90722ba98b018b0aa277285113454822 (diff)
Implement account deactivation (#1455)
* Implement account deactivation See #610 Signed-off-by: Loïck Bonniot <git@lesterpig.com> * Rename 'is_active' to 'is_deactivated' Signed-off-by: Loïck Bonniot <git@lesterpig.com> Co-authored-by: Kegsay <kegan@matrix.org>
Diffstat (limited to 'userapi')
-rw-r--r--userapi/api/api.go11
-rw-r--r--userapi/internal/api.go7
-rw-r--r--userapi/inthttp/client.go19
-rw-r--r--userapi/inthttp/server.go13
-rw-r--r--userapi/storage/accounts/interface.go1
-rw-r--r--userapi/storage/accounts/postgres/accounts_table.go20
-rw-r--r--userapi/storage/accounts/postgres/deltas/20200929203058_is_active.sql9
-rw-r--r--userapi/storage/accounts/postgres/storage.go5
-rw-r--r--userapi/storage/accounts/sqlite3/accounts_table.go20
-rw-r--r--userapi/storage/accounts/sqlite3/deltas/20200929203058_is_active.sql38
-rw-r--r--userapi/storage/accounts/sqlite3/storage.go5
11 files changed, 139 insertions, 9 deletions
diff --git a/userapi/api/api.go b/userapi/api/api.go
index 3baaa100..d384c5b1 100644
--- a/userapi/api/api.go
+++ b/userapi/api/api.go
@@ -30,6 +30,7 @@ type UserInternalAPI interface {
PerformDeviceCreation(ctx context.Context, req *PerformDeviceCreationRequest, res *PerformDeviceCreationResponse) error
PerformDeviceDeletion(ctx context.Context, req *PerformDeviceDeletionRequest, res *PerformDeviceDeletionResponse) error
PerformDeviceUpdate(ctx context.Context, req *PerformDeviceUpdateRequest, res *PerformDeviceUpdateResponse) error
+ PerformAccountDeactivation(ctx context.Context, req *PerformAccountDeactivationRequest, res *PerformAccountDeactivationResponse) error
QueryProfile(ctx context.Context, req *QueryProfileRequest, res *QueryProfileResponse) error
QueryAccessToken(ctx context.Context, req *QueryAccessTokenRequest, res *QueryAccessTokenResponse) error
QueryDevices(ctx context.Context, req *QueryDevicesRequest, res *QueryDevicesResponse) error
@@ -199,6 +200,16 @@ type PerformDeviceCreationResponse struct {
Device *Device
}
+// PerformAccountDeactivationRequest is the request for PerformAccountDeactivation
+type PerformAccountDeactivationRequest struct {
+ Localpart string
+}
+
+// PerformAccountDeactivationResponse is the response for PerformAccountDeactivation
+type PerformAccountDeactivationResponse struct {
+ AccountDeactivated bool
+}
+
// Device represents a client's device (mobile, web, etc)
type Device struct {
ID string
diff --git a/userapi/internal/api.go b/userapi/internal/api.go
index 461c548c..ec828439 100644
--- a/userapi/internal/api.go
+++ b/userapi/internal/api.go
@@ -388,3 +388,10 @@ func (a *UserInternalAPI) queryAppServiceToken(ctx context.Context, token, appSe
dev.UserID = appService.SenderLocalpart
return &dev, nil
}
+
+// PerformAccountDeactivation deactivates the user's account, removing all ability for the user to login again.
+func (a *UserInternalAPI) PerformAccountDeactivation(ctx context.Context, req *api.PerformAccountDeactivationRequest, res *api.PerformAccountDeactivationResponse) error {
+ err := a.AccountDB.DeactivateAccount(ctx, req.Localpart)
+ res.AccountDeactivated = err == nil
+ return err
+}
diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go
index 6dcaf756..4d9dcc41 100644
--- a/userapi/inthttp/client.go
+++ b/userapi/inthttp/client.go
@@ -28,11 +28,12 @@ import (
const (
InputAccountDataPath = "/userapi/inputAccountData"
- PerformDeviceCreationPath = "/userapi/performDeviceCreation"
- PerformAccountCreationPath = "/userapi/performAccountCreation"
- PerformPasswordUpdatePath = "/userapi/performPasswordUpdate"
- PerformDeviceDeletionPath = "/userapi/performDeviceDeletion"
- PerformDeviceUpdatePath = "/userapi/performDeviceUpdate"
+ PerformDeviceCreationPath = "/userapi/performDeviceCreation"
+ PerformAccountCreationPath = "/userapi/performAccountCreation"
+ PerformPasswordUpdatePath = "/userapi/performPasswordUpdate"
+ PerformDeviceDeletionPath = "/userapi/performDeviceDeletion"
+ PerformDeviceUpdatePath = "/userapi/performDeviceUpdate"
+ PerformAccountDeactivationPath = "/userapi/performAccountDeactivation"
QueryProfilePath = "/userapi/queryProfile"
QueryAccessTokenPath = "/userapi/queryAccessToken"
@@ -126,6 +127,14 @@ func (h *httpUserInternalAPI) PerformDeviceUpdate(ctx context.Context, req *api.
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res)
}
+func (h *httpUserInternalAPI) PerformAccountDeactivation(ctx context.Context, req *api.PerformAccountDeactivationRequest, res *api.PerformAccountDeactivationResponse) error {
+ span, ctx := opentracing.StartSpanFromContext(ctx, "PerformAccountDeactivation")
+ defer span.Finish()
+
+ apiURL := h.apiURL + PerformAccountDeactivationPath
+ return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res)
+}
+
func (h *httpUserInternalAPI) QueryProfile(
ctx context.Context,
request *api.QueryProfileRequest,
diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go
index d2674678..e24aad3a 100644
--- a/userapi/inthttp/server.go
+++ b/userapi/inthttp/server.go
@@ -91,6 +91,19 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) {
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}),
)
+ internalAPIMux.Handle(PerformAccountDeactivationPath,
+ httputil.MakeInternalAPI("performAccountDeactivation", func(req *http.Request) util.JSONResponse {
+ request := api.PerformAccountDeactivationRequest{}
+ response := api.PerformAccountDeactivationResponse{}
+ if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
+ return util.MessageResponse(http.StatusBadRequest, err.Error())
+ }
+ if err := s.PerformAccountDeactivation(req.Context(), &request, &response); err != nil {
+ return util.ErrorResponse(err)
+ }
+ return util.JSONResponse{Code: http.StatusOK, JSON: &response}
+ }),
+ )
internalAPIMux.Handle(QueryProfilePath,
httputil.MakeInternalAPI("queryProfile", func(req *http.Request) util.JSONResponse {
request := api.QueryProfileRequest{}
diff --git a/userapi/storage/accounts/interface.go b/userapi/storage/accounts/interface.go
index 49446f11..c86b2c39 100644
--- a/userapi/storage/accounts/interface.go
+++ b/userapi/storage/accounts/interface.go
@@ -51,6 +51,7 @@ type Database interface {
CheckAccountAvailability(ctx context.Context, localpart string) (bool, error)
GetAccountByLocalpart(ctx context.Context, localpart string) (*api.Account, error)
SearchProfiles(ctx context.Context, searchString string, limit int) ([]authtypes.Profile, error)
+ DeactivateAccount(ctx context.Context, localpart string) (err error)
}
// Err3PIDInUse is the error returned when trying to save an association involving
diff --git a/userapi/storage/accounts/postgres/accounts_table.go b/userapi/storage/accounts/postgres/accounts_table.go
index 7500e1e8..254da84c 100644
--- a/userapi/storage/accounts/postgres/accounts_table.go
+++ b/userapi/storage/accounts/postgres/accounts_table.go
@@ -37,7 +37,9 @@ CREATE TABLE IF NOT EXISTS account_accounts (
-- The password hash for this account. Can be NULL if this is a passwordless account.
password_hash TEXT,
-- Identifies which application service this account belongs to, if any.
- appservice_id TEXT
+ appservice_id TEXT,
+ -- If the account is currently active
+ is_deactivated BOOLEAN DEFAULT FALSE
-- TODO:
-- is_guest, is_admin, upgraded_ts, devices, any email reset stuff?
);
@@ -51,11 +53,14 @@ const insertAccountSQL = "" +
const updatePasswordSQL = "" +
"UPDATE account_accounts SET password_hash = $1 WHERE localpart = $2"
+const deactivateAccountSQL = "" +
+ "UPDATE account_accounts SET is_deactivated = TRUE WHERE localpart = $1"
+
const selectAccountByLocalpartSQL = "" +
"SELECT localpart, appservice_id FROM account_accounts WHERE localpart = $1"
const selectPasswordHashSQL = "" +
- "SELECT password_hash FROM account_accounts WHERE localpart = $1"
+ "SELECT password_hash FROM account_accounts WHERE localpart = $1 AND is_deactivated = FALSE"
const selectNewNumericLocalpartSQL = "" +
"SELECT nextval('numeric_username_seq')"
@@ -63,6 +68,7 @@ const selectNewNumericLocalpartSQL = "" +
type accountsStatements struct {
insertAccountStmt *sql.Stmt
updatePasswordStmt *sql.Stmt
+ deactivateAccountStmt *sql.Stmt
selectAccountByLocalpartStmt *sql.Stmt
selectPasswordHashStmt *sql.Stmt
selectNewNumericLocalpartStmt *sql.Stmt
@@ -80,6 +86,9 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server
if s.updatePasswordStmt, err = db.Prepare(updatePasswordSQL); err != nil {
return
}
+ if s.deactivateAccountStmt, err = db.Prepare(deactivateAccountSQL); err != nil {
+ return
+ }
if s.selectAccountByLocalpartStmt, err = db.Prepare(selectAccountByLocalpartSQL); err != nil {
return
}
@@ -127,6 +136,13 @@ func (s *accountsStatements) updatePassword(
return
}
+func (s *accountsStatements) deactivateAccount(
+ ctx context.Context, localpart string,
+) (err error) {
+ _, err = s.deactivateAccountStmt.ExecContext(ctx, localpart)
+ return
+}
+
func (s *accountsStatements) selectPasswordHash(
ctx context.Context, localpart string,
) (hash string, err error) {
diff --git a/userapi/storage/accounts/postgres/deltas/20200929203058_is_active.sql b/userapi/storage/accounts/postgres/deltas/20200929203058_is_active.sql
new file mode 100644
index 00000000..32e6e166
--- /dev/null
+++ b/userapi/storage/accounts/postgres/deltas/20200929203058_is_active.sql
@@ -0,0 +1,9 @@
+-- +goose Up
+-- +goose StatementBegin
+ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS is_deactivated BOOLEAN DEFAULT FALSE;
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+ALTER TABLE account_accounts DROP COLUMN is_deactivated;
+-- +goose StatementEnd
diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go
index 8b9ebef8..2230f7e7 100644
--- a/userapi/storage/accounts/postgres/storage.go
+++ b/userapi/storage/accounts/postgres/storage.go
@@ -317,3 +317,8 @@ func (d *Database) SearchProfiles(ctx context.Context, searchString string, limi
) ([]authtypes.Profile, error) {
return d.profiles.selectProfilesBySearch(ctx, searchString, limit)
}
+
+// DeactivateAccount deactivates the user's account, removing all ability for the user to login again.
+func (d *Database) DeactivateAccount(ctx context.Context, localpart string) (err error) {
+ return d.accounts.deactivateAccount(ctx, localpart)
+}
diff --git a/userapi/storage/accounts/sqlite3/accounts_table.go b/userapi/storage/accounts/sqlite3/accounts_table.go
index 2d935fb6..d0ea8a8b 100644
--- a/userapi/storage/accounts/sqlite3/accounts_table.go
+++ b/userapi/storage/accounts/sqlite3/accounts_table.go
@@ -37,7 +37,9 @@ CREATE TABLE IF NOT EXISTS account_accounts (
-- The password hash for this account. Can be NULL if this is a passwordless account.
password_hash TEXT,
-- Identifies which application service this account belongs to, if any.
- appservice_id TEXT
+ appservice_id TEXT,
+ -- If the account is currently active
+ is_deactivated BOOLEAN DEFAULT 0
-- TODO:
-- is_guest, is_admin, upgraded_ts, devices, any email reset stuff?
);
@@ -49,11 +51,14 @@ const insertAccountSQL = "" +
const updatePasswordSQL = "" +
"UPDATE account_accounts SET password_hash = $1 WHERE localpart = $2"
+const deactivateAccountSQL = "" +
+ "UPDATE account_accounts SET is_deactivated = 1 WHERE localpart = $1"
+
const selectAccountByLocalpartSQL = "" +
"SELECT localpart, appservice_id FROM account_accounts WHERE localpart = $1"
const selectPasswordHashSQL = "" +
- "SELECT password_hash FROM account_accounts WHERE localpart = $1"
+ "SELECT password_hash FROM account_accounts WHERE localpart = $1 AND is_deactivated = 0"
const selectNewNumericLocalpartSQL = "" +
"SELECT COUNT(localpart) FROM account_accounts"
@@ -62,6 +67,7 @@ type accountsStatements struct {
db *sql.DB
insertAccountStmt *sql.Stmt
updatePasswordStmt *sql.Stmt
+ deactivateAccountStmt *sql.Stmt
selectAccountByLocalpartStmt *sql.Stmt
selectPasswordHashStmt *sql.Stmt
selectNewNumericLocalpartStmt *sql.Stmt
@@ -81,6 +87,9 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server
if s.updatePasswordStmt, err = db.Prepare(updatePasswordSQL); err != nil {
return
}
+ if s.deactivateAccountStmt, err = db.Prepare(deactivateAccountSQL); err != nil {
+ return
+ }
if s.selectAccountByLocalpartStmt, err = db.Prepare(selectAccountByLocalpartSQL); err != nil {
return
}
@@ -128,6 +137,13 @@ func (s *accountsStatements) updatePassword(
return
}
+func (s *accountsStatements) deactivateAccount(
+ ctx context.Context, localpart string,
+) (err error) {
+ _, err = s.deactivateAccountStmt.ExecContext(ctx, localpart)
+ return
+}
+
func (s *accountsStatements) selectPasswordHash(
ctx context.Context, localpart string,
) (hash string, err error) {
diff --git a/userapi/storage/accounts/sqlite3/deltas/20200929203058_is_active.sql b/userapi/storage/accounts/sqlite3/deltas/20200929203058_is_active.sql
new file mode 100644
index 00000000..51e9bae3
--- /dev/null
+++ b/userapi/storage/accounts/sqlite3/deltas/20200929203058_is_active.sql
@@ -0,0 +1,38 @@
+-- +goose Up
+-- +goose StatementBegin
+ALTER TABLE account_accounts RENAME TO account_accounts_tmp;
+CREATE TABLE account_accounts (
+ localpart TEXT NOT NULL PRIMARY KEY,
+ created_ts BIGINT NOT NULL,
+ password_hash TEXT,
+ appservice_id TEXT,
+ is_deactivated BOOLEAN DEFAULT 0
+);
+INSERT
+ INTO account_accounts (
+ localpart, created_ts, password_hash, appservice_id
+ ) SELECT
+ localpart, created_ts, password_hash, appservice_id
+ FROM account_accounts_tmp
+;
+DROP TABLE account_accounts_tmp;
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+ALTER TABLE account_accounts RENAME TO account_accounts_tmp;
+CREATE TABLE account_accounts (
+ localpart TEXT NOT NULL PRIMARY KEY,
+ created_ts BIGINT NOT NULL,
+ password_hash TEXT,
+ appservice_id TEXT
+);
+INSERT
+ INTO account_accounts (
+ localpart, created_ts, password_hash, appservice_id
+ ) SELECT
+ localpart, created_ts, password_hash, appservice_id
+ FROM account_accounts_tmp
+;
+DROP TABLE account_accounts_tmp;
+-- +goose StatementEnd
diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go
index 4b66304c..7a2830a9 100644
--- a/userapi/storage/accounts/sqlite3/storage.go
+++ b/userapi/storage/accounts/sqlite3/storage.go
@@ -359,3 +359,8 @@ func (d *Database) SearchProfiles(ctx context.Context, searchString string, limi
) ([]authtypes.Profile, error) {
return d.profiles.selectProfilesBySearch(ctx, searchString, limit)
}
+
+// DeactivateAccount deactivates the user's account, removing all ability for the user to login again.
+func (d *Database) DeactivateAccount(ctx context.Context, localpart string) (err error) {
+ return d.accounts.deactivateAccount(ctx, localpart)
+}