Skip to content

Commit

Permalink
Add account deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
DJAndries committed Nov 13, 2024
1 parent 1d33e91 commit 5f30730
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 7 deletions.
27 changes: 26 additions & 1 deletion controllers/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,12 @@ func NewAccountsController(opaqueService *services.OpaqueService, jwtService *se
}
}

func (ac *AccountsController) Router(verificationMiddleware func(http.Handler) http.Handler) chi.Router {
func (ac *AccountsController) Router(verificationMiddleware func(http.Handler) http.Handler, authMiddleware func(http.Handler) http.Handler) chi.Router {
r := chi.NewRouter()

r.With(verificationMiddleware).Post("/password/init", ac.SetupPasswordInit)
r.With(verificationMiddleware).Post("/password/finalize", ac.SetupPasswordFinalize)
r.With(authMiddleware).Delete("/", ac.DeleteAccount)

return r
}
Expand Down Expand Up @@ -308,3 +309,27 @@ func (ac *AccountsController) SetupPasswordFinalize(w http.ResponseWriter, r *ht
AuthToken: authToken,
})
}

// @Summary Delete account
// @Description Deletes the authenticated account and all associated data
// @Tags Accounts
// @Produce json
// @Param Authorization header string true "Bearer + auth token"
// @Param Brave-Key header string false "Brave services key (if one is configured)"
// @Success 204 "No Content"
// @Failure 401 {object} util.ErrorResponse
// @Failure 403 {object} util.ErrorResponse
// @Failure 500 {object} util.ErrorResponse
// @Router /v2/accounts [delete]
func (ac *AccountsController) DeleteAccount(w http.ResponseWriter, r *http.Request) {
session := r.Context().Value(middleware.ContextSession).(*datastore.Session)

// Delete the account with all associated data
if err := ac.ds.DeleteAccount(session.AccountID); err != nil {
util.RenderErrorResponse(w, r, http.StatusInternalServerError, err)
return
}

render.Status(r, http.StatusNoContent)
render.NoContent(w, r)
}
9 changes: 9 additions & 0 deletions datastore/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,12 @@ func (d *Datastore) UpdateOpaqueRegistration(accountID uuid.UUID, oprfSeedID int

return nil
}

func (d *Datastore) DeleteAccount(accountID uuid.UUID) error {
result := d.db.Delete(&Account{}, "id = ?", accountID)
if result.Error != nil {
return fmt.Errorf("error deleting account: %w", result.Error)
}

return nil
}
2 changes: 1 addition & 1 deletion datastore/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (d *Datastore) ListSessions(accountID uuid.UUID, minSessionVersion *int) ([
if minSessionVersion == nil {
minSessionVersion = &d.minSessionVersion
}
if err := d.db.Where("account_id = ? AND version >= ? AND expires_at IS NULL", accountID, *minSessionVersion).Find(&sessions).Error; err != nil {
if err := d.db.Where("account_id = ? AND version >= ?", accountID, *minSessionVersion).Find(&sessions).Error; err != nil {
return nil, fmt.Errorf("failed to list sessions: %w", err)
}

Expand Down
50 changes: 50 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,56 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/v2/accounts": {
"delete": {
"description": "Deletes the authenticated account and all associated data",
"produces": [
"application/json"
],
"tags": [
"Accounts"
],
"summary": "Delete account",
"parameters": [
{
"type": "string",
"description": "Bearer + auth token",
"name": "Authorization",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Brave services key (if one is configured)",
"name": "Brave-Key",
"in": "header"
}
],
"responses": {
"204": {
"description": "No Content"
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/util.ErrorResponse"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/util.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/util.ErrorResponse"
}
}
}
}
},
"/v2/accounts/password/finalize": {
"post": {
"description": "Complete the password setup process and return auth token.\nEither ` + "`" + `publicKey` + "`" + `, ` + "`" + `maskingKey` + "`" + ` and ` + "`" + `envelope` + "`" + ` must be provided together,\nor ` + "`" + `serializedRecord` + "`" + ` must be provided.",
Expand Down
50 changes: 50 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,56 @@
"contact": {}
},
"paths": {
"/v2/accounts": {
"delete": {
"description": "Deletes the authenticated account and all associated data",
"produces": [
"application/json"
],
"tags": [
"Accounts"
],
"summary": "Delete account",
"parameters": [
{
"type": "string",
"description": "Bearer + auth token",
"name": "Authorization",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Brave services key (if one is configured)",
"name": "Brave-Key",
"in": "header"
}
],
"responses": {
"204": {
"description": "No Content"
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/util.ErrorResponse"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/util.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/util.ErrorResponse"
}
}
}
}
},
"/v2/accounts/password/finalize": {
"post": {
"description": "Complete the password setup process and return auth token.\nEither `publicKey`, `maskingKey` and `envelope` must be provided together,\nor `serializedRecord` must be provided.",
Expand Down
33 changes: 33 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,39 @@ info:
contact: {}
title: Brave Accounts Service
paths:
/v2/accounts:
delete:
description: Deletes the authenticated account and all associated data
parameters:
- description: Bearer + auth token
in: header
name: Authorization
required: true
type: string
- description: Brave services key (if one is configured)
in: header
name: Brave-Key
type: string
produces:
- application/json
responses:
"204":
description: No Content
"401":
description: Unauthorized
schema:
$ref: '#/definitions/util.ErrorResponse'
"403":
description: Forbidden
schema:
$ref: '#/definitions/util.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/util.ErrorResponse'
summary: Delete account
tags:
- Accounts
/v2/accounts/password/finalize:
post:
consumes:
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func main() {
r.Route("/v2", func(r chi.Router) {
r.With(servicesKeyMiddleware).Mount("/auth", authController.Router(authMiddleware))
if passwordAuthEnabled {
r.With(servicesKeyMiddleware).Mount("/accounts", accountsController.Router(verificationMiddleware))
r.With(servicesKeyMiddleware).Mount("/accounts", accountsController.Router(verificationMiddleware, authMiddleware))
}
r.Mount("/verify", verificationController.Router(verificationMiddleware, servicesKeyMiddleware, debugEndpointsEnabled))
r.With(servicesKeyMiddleware).Mount("/sessions", sessionsController.Router(authMiddleware))
Expand Down
6 changes: 3 additions & 3 deletions migrations/20241021231751_init.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ CREATE TABLE accounts (

CREATE TABLE ake_states (
id UUID PRIMARY KEY,
account_id UUID REFERENCES accounts(id),
account_id UUID REFERENCES accounts(id) ON DELETE CASCADE,
oprf_seed_id INT REFERENCES oprf_seeds(id),
state BYTEA NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
Expand All @@ -36,7 +36,7 @@ CREATE TABLE registration_states (

CREATE TABLE sessions (
id UUID PRIMARY KEY,
account_id UUID NOT NULL REFERENCES accounts(id),
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
user_agent TEXT NOT NULL,
version SMALLINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
Expand All @@ -54,7 +54,7 @@ CREATE TABLE verifications (
CREATE INDEX ON verifications (email);

CREATE TABLE user_keys (
account_id UUID NOT NULL REFERENCES accounts(id),
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
name TEXT NOT NULL,
encrypted_key BYTEA NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
Expand Down
6 changes: 5 additions & 1 deletion misc/test-client-rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ fn post_request(
bearer_token: Option<&str>,
body: HashMap<&str, Value>,
) -> HashMap<String, String> {
let client = reqwest::blocking::Client::new();
// add user agent of some sort.
let client = reqwest::blocking::Client::builder()
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
.build()
.expect("Failed to create HTTP client");
let mut request_builder = client.post(url).json(&body);

// Add authorization header if bearer token is provided
Expand Down

0 comments on commit 5f30730

Please sign in to comment.