DEV Community

finnmorgan226
finnmorgan226

Posted on

Progressive Profiling in Node.js — Safe Updates for Verified Logistics Users

Short answer: keep the verified user ID immutable, treat each profile change as an auditable state transition, and revoke sessions as a separate operation when GDPR deletion requires it. That rule makes progressive profiling survivable during a migration away from a managed identity provider because an email address can remain a lookup hint without becoming the identity key.

In a logistics system, registration rarely finishes at the first sign-in. A driver may verify an email, add a phone later, and only then provide a depot or license field. The dangerous implementation is to create a second identity when a later step does not match the original email record. The resulting duplicate account is hard to reconcile and even harder to delete reliably.

Keep one identity.

I model the workflow as explicit transitions: created, verified, profile-updated, sessions-revoked, and deleted. Each transition has an actor, request ID, timestamp, and reason in the business audit log. The identity service stores the user ID as the stable primary key; email is for search and recovery, not identity comparison.

How should progressive profiling update a verified user without recreating identity?

First read the canonical record by user ID, then apply a narrow update after authorization. A profile form should never be allowed to choose which account it edits by posting an email address. In the service layer, compare the authenticated subject with the path user ID, check the field-level policy, and append an audit event before returning success.

The following Go example uses the verified routes and keeps the request boundaries visible. It is intentionally small: the surrounding service still owns authorization, validation, and audit persistence.

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
)

func call(method, url string, body []byte) error {
    req, err := http.NewRequest(method, url, bytes.NewReader(body))
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
    if len(body) > 0 {
        req.Header.Set("Content-Type", "application/json")
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    data, _ := io.ReadAll(resp.Body)
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return fmt.Errorf("auth request failed: %s: %s", resp.Status, data)
    }
    fmt.Println(string(data))
    return nil
}

func main() {
    base := os.Getenv("AUTH_API_BASE")
    if base == "" {
        panic("AUTH_API_BASE is required")
    }
    userID := "user_123"
    if err := call("GET", base+"/auth/user/get/"+userID, nil); err != nil {
        panic(err)
    }
    update := []byte(`{"depot":"LAX-4","phone_verified":true}`)
    if err := call("PATCH", base+"/auth/user/update/"+userID, update); err != nil {
        panic(err)
    }
    if err := call("GET", base+"/auth/identity/list/"+userID, nil); err != nil {
        panic(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

For a write path that retries, add an idempotency key derived from the profile-change event ID and implement exponential backoff for HTTP 429, honoring Retry-After. The read-after-write response is not the audit record; persist the state transition and request ID in your own durable log so an incident review can reconstruct who changed what. In one migration rehearsal, I would deliberately kill the worker after the PATCH is accepted but before the audit append, then replay the event and verify that the idempotency key prevents a second mutation; that awkward gap is where “exactly once” claims usually meet reality.

Measure it.

What changes when GDPR deletion also revokes every session?

Deletion is a workflow, not a single button. Mark the account as deletion-requested, block new privileged changes, revoke every session, remove linked identities, and finally delete the user record only after the downstream steps have reported completion. Keep the order and event IDs in the audit log. A retry must resume the same transition rather than create a fresh user or issue a second business action.

Session revocation deserves its own authorization check because support staff may read a profile while only a privacy or security role can revoke sessions. Your SLO should cover the time from an accepted deletion request to the last session becoming invalid, and monitoring should alert on requests that exceed that objective. A 200 response from a delete endpoint is not proof that already-issued tokens have expired; verify through the session and token validation path your provider documents.

Which migration option fits an operations-heavy platform team?

The choice is mostly about control and on-call load. Infrai is a credible middle option when one consistent REST contract can cover auth plus adjacent backend capabilities, letting a team add a capability without another SDK and credential set. That breadth is useful during progressive profiling, where auth, storage, and audit plumbing otherwise become separate integrations. It is not a reason to skip threat modeling or provider verification.

Option Strength for this workflow Trade-off to accept
Infrai One key and a plain HTTP surface across multiple backend modules; the auth routes keep clear user-ID boundaries. You still own the business audit log, deletion orchestration, and policy checks; validate regional and vendor requirements.
Auth0 Mature hosted identity flows, tenant controls, and extensive ecosystem integrations. Migration can mean provider-specific rules and actions, with ongoing tenant configuration and usage management.
Amazon Cognito Fits teams already standardized on AWS IAM, regions, and operational tooling. Progressive profile and deletion orchestration often spans several AWS resources, increasing coupling to that stack.
Keycloak Self-hosted control over identity data and deployment topology. Your team carries upgrades, capacity planning, patching, and the identity service on-call rotation.

The catch is operational ownership: a managed provider can reduce pager volume, while a self-hosted option may be the better fit when data residency or custom protocol behavior is non-negotiable. Stick with Auth0 or Cognito when their existing integrations are already your highest-confidence path; choose Keycloak when control outweighs the platform team's maintenance budget. Your mileage may vary by region and contract terms, and I’m not sure a migration pays back unless the current provider is a measurable bottleneck.

Verification, rollback, and capacity signals

Before rollout, run a fixture through every transition and assert that the user ID stays constant, an email lookup cannot mutate a different account, and an unauthorized field update produces an audit entry without changing data. Exercise a deletion twice: the second request should observe the existing transition and converge on the same final state.

For rollback, stop new profile transitions, preserve the event log, and replay only transitions whose provider-side acknowledgement is absent. Do not restore a deleted identity from an application cache. Cache list responses briefly and authorize them separately from single-user reads; a list endpoint can reveal more metadata and should have a tighter policy and TTL.

Capacity planning belongs in the runbook. Track profile-update rate, deletion backlog age, session-revocation latency, provider 429s, and audit-write failures. Set alerts against the SLO rather than raw request counts. The migration is ready when those signals remain within budget under peak dispatch traffic and an operator can explain every state transition from logs alone.

References

Top comments (0)