1598646021
On unlink (RemoveIdentity, reason=unlink) and email change (replaceEmailIdentity, reason=change) write the outgoing credential to retained_identities before removing the live identities row — so the legal dossier survives while the (kind, external_id) frees for reuse. Same transaction, so the dossier and live state cannot diverge. Integration tests cover both reasons.
44 lines
1.6 KiB
Go
44 lines
1.6 KiB
Go
package account
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"scrabble/backend/internal/postgres/jet/backend/table"
|
|
)
|
|
|
|
// Reasons recorded on a retained_identities row: what detached the credential from its
|
|
// account. The row is written just before the live identities row is removed, preserving
|
|
// the legal dossier (which email/vk/tg was linked, and when) even as the identity frees
|
|
// for reuse. See docs/ARCHITECTURE.md §9.1.
|
|
const (
|
|
retainUnlink = "unlink"
|
|
retainChange = "change"
|
|
retainDelete = "delete"
|
|
)
|
|
|
|
// retainIdentityTx appends a retention-journal row for one identity being detached, inside
|
|
// tx. linkedAt is the identity's original creation time; detached_at defaults to now(). It
|
|
// must run in the same transaction as the identity removal, so the dossier and the live
|
|
// state can never diverge.
|
|
func retainIdentityTx(ctx context.Context, tx *sql.Tx, accountID uuid.UUID, kind, externalID string, confirmed bool, linkedAt time.Time, reason string) error {
|
|
id, err := uuid.NewV7()
|
|
if err != nil {
|
|
return fmt.Errorf("account: new retained id: %w", err)
|
|
}
|
|
ins := table.RetainedIdentities.INSERT(
|
|
table.RetainedIdentities.RetainedID, table.RetainedIdentities.AccountID,
|
|
table.RetainedIdentities.Kind, table.RetainedIdentities.ExternalID,
|
|
table.RetainedIdentities.Confirmed, table.RetainedIdentities.LinkedAt,
|
|
table.RetainedIdentities.Reason,
|
|
).VALUES(id, accountID, kind, externalID, confirmed, linkedAt, reason)
|
|
if _, err := ins.ExecContext(ctx, tx); err != nil {
|
|
return fmt.Errorf("account: retain identity (%s, %s): %w", kind, externalID, err)
|
|
}
|
|
return nil
|
|
}
|