feat(account): deletion orchestration + step-up + gateway edge
Step-up: email accounts confirm with a mailed code (purpose=delete, no deeplink —
ConfirmByToken refuses a delete token so a stray click can't delete); platform-only
accounts type a fixed phrase (anti-impulse). Endpoints /user/delete/{request,confirm};
the confirm orchestration resigns active games, drops all-robot games, tombstones +
anonymizes the account (freeing its creds), and revokes its sessions — the tombstone is
the point of no return, the rest best-effort. Gateway account.delete.{request,confirm}
ops + fbs AccountDeleteConfirm/AccountDeleteRequestResult + branded ru/en delete email.
Integration tests cover the step-up (code + no-email) and the orchestration pieces.
This commit is contained in:
@@ -5,6 +5,8 @@ package inttest
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -133,3 +135,47 @@ func TestDropAllRobotGames(t *testing.T) {
|
||||
t.Errorf("the human game should be kept, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteStepUpEmail: an email account's delete code verifies (wrong code rejected, no
|
||||
// deeplink in the mail); a platform-only account has no email and cannot request a code.
|
||||
func TestDeleteStepUpEmail(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := account.NewStore(testDB)
|
||||
mailer := &capturingMailer{}
|
||||
svc := account.NewEmailService(store, mailer, "https://erudit-game.ru")
|
||||
|
||||
acc, err := store.ProvisionByIdentity(ctx, account.KindTelegram, "tg-"+uuid.NewString())
|
||||
if err != nil {
|
||||
t.Fatalf("provision: %v", err)
|
||||
}
|
||||
if err := store.AttachIdentity(ctx, acc.ID, account.KindEmail, "del-"+uuid.NewString()+"@example.com", true); err != nil {
|
||||
t.Fatalf("attach email: %v", err)
|
||||
}
|
||||
if has, err := svc.HasEmail(ctx, acc.ID); err != nil || !has {
|
||||
t.Fatalf("HasEmail = (%v, %v), want true", has, err)
|
||||
}
|
||||
if err := svc.RequestDeleteCode(ctx, acc.ID); err != nil {
|
||||
t.Fatalf("request delete code: %v", err)
|
||||
}
|
||||
if strings.Contains(mailer.lastBody, "/confirm/") {
|
||||
t.Error("a delete email must not carry a one-tap deeplink")
|
||||
}
|
||||
code := sixDigit.FindString(mailer.lastBody)
|
||||
if err := svc.VerifyDeleteCode(ctx, acc.ID, "000000"); err == nil {
|
||||
t.Error("a wrong delete code must be rejected")
|
||||
}
|
||||
if err := svc.VerifyDeleteCode(ctx, acc.ID, code); err != nil {
|
||||
t.Fatalf("verify correct delete code: %v", err)
|
||||
}
|
||||
|
||||
noEmail, err := store.ProvisionByIdentity(ctx, account.KindTelegram, "tg-"+uuid.NewString())
|
||||
if err != nil {
|
||||
t.Fatalf("provision no-email: %v", err)
|
||||
}
|
||||
if has, _ := svc.HasEmail(ctx, noEmail.ID); has {
|
||||
t.Error("HasEmail must be false for a platform-only account")
|
||||
}
|
||||
if err := svc.RequestDeleteCode(ctx, noEmail.ID); !errors.Is(err, account.ErrNoEmail) {
|
||||
t.Errorf("request delete for no-email account = %v, want ErrNoEmail", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user