fix(auth): delete the Kratos identity on account deletion
Account deletion removed all local data but left the Ory Kratos
identity intact — an orphaned identity that can still authenticate.
Close the gap:
- kratos.Client gains the admin API: NewClient(publicURL, adminURL)
and DeleteIdentity (DELETE /admin/identities/{id}; a 404 is treated
as success so a retry after a partial failure is idempotent).
- AuthService.DeleteAccount deletes the Kratos identity FIRST; if that
call fails it aborts before touching local data, so the operation is
retryable rather than partially applied.
- KRATOS_ADMIN_URL config (default http://kratos:4434) + router wiring.
- kratos NetworkPolicy split: the api pods may now reach the admin API
:4434 (Traefik still reaches only the public API :4433).
- kratos CORS: allow_credentials + OPTIONS so the web browser flows
(ory_kratos_session cookie) work; origins stay an explicit allowlist.
- Regression tests: identity teardown happens, and a Kratos failure
aborts the deletion instead of orphaning local data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// Package kratos is a thin client for the Ory Kratos public API. honeyDue
|
||||
// Package kratos is a thin client for the Ory Kratos APIs. honeyDue
|
||||
// delegates all identity concerns (credentials, sessions, verification,
|
||||
// recovery, social sign-in) to Kratos; this client only validates sessions.
|
||||
// recovery, social sign-in) to Kratos; this client validates sessions
|
||||
// against the public API and deletes identities via the admin API.
|
||||
package kratos
|
||||
|
||||
import (
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -17,17 +19,21 @@ import (
|
||||
// or inactive — the caller should respond 401.
|
||||
var ErrUnauthorized = errors.New("kratos: session invalid or inactive")
|
||||
|
||||
// Client talks to the Ory Kratos public API.
|
||||
// Client talks to the Ory Kratos public and admin APIs.
|
||||
type Client struct {
|
||||
publicURL string
|
||||
adminURL string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
// NewClient builds a Kratos client for the given public-API base URL
|
||||
// (e.g. http://kratos:4433 in-cluster).
|
||||
func NewClient(publicURL string) *Client {
|
||||
// NewClient builds a Kratos client. publicURL is the public-API base used for
|
||||
// session validation (e.g. http://kratos:4433 in-cluster); adminURL is the
|
||||
// admin-API base used for identity management (e.g. http://kratos:4434).
|
||||
// Either may be empty when the corresponding API is unused.
|
||||
func NewClient(publicURL, adminURL string) *Client {
|
||||
return &Client{
|
||||
publicURL: strings.TrimRight(publicURL, "/"),
|
||||
adminURL: strings.TrimRight(adminURL, "/"),
|
||||
http: &http.Client{Timeout: 5 * time.Second},
|
||||
}
|
||||
}
|
||||
@@ -105,3 +111,35 @@ func (c *Client) Whoami(ctx context.Context, sessionToken, cookie string) (*Sess
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// DeleteIdentity permanently removes a Kratos identity by its UUID via the
|
||||
// admin API (DELETE /admin/identities/{id}). A 404 is treated as success —
|
||||
// the identity is already gone, which is the desired end state, so the call
|
||||
// is idempotent across retries. Called when a honeyDue account is deleted so
|
||||
// no orphaned, still-loginable identity is left behind.
|
||||
func (c *Client) DeleteIdentity(ctx context.Context, identityID string) error {
|
||||
if c.adminURL == "" {
|
||||
return errors.New("kratos: admin URL not configured")
|
||||
}
|
||||
if identityID == "" {
|
||||
return errors.New("kratos: empty identity id")
|
||||
}
|
||||
endpoint := c.adminURL + "/admin/identities/" + url.PathEscape(identityID)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("kratos delete identity: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNoContent, http.StatusNotFound:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("kratos delete identity: unexpected status %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user