feat: customer-level credentials shared across all nodes (item 2)
One credential set per customer (UUID + 32-byte root relay key + shortId)
lives on the customer record; node bindings no longer store credentials.
SS2022 user keys derive deterministically from the root key by method
length (16/24/32-byte prefix), so a single password serves every protocol,
matching 3x-ui's per-client credential model. Reality public keys stay a
node property and are shown per binding in the credentials pane.
- store: load-time convergence — first binding's set wins; a converged
password that is not a 32-byte root key is regenerated (uuid/shortId
preserved); bindings are stripped; idempotent, covered by tests
- deploy: the orchestrator stamps the customer's set onto a build copy;
subscriptions resolve the same set; the derivation applies in both the
inbound builder and the URI builder (verified live: Alice's N1 link is
byte-identical to the pre-change seed expectation, her N2 user key is
exactly the root key's 16-byte prefix)
- API: GET /api/customers/:id/credentials returns {credentials, bindings};
regenerate is now POST /api/customers/:id/credentials/regenerate and
rotates the whole set, rebuilding every deployed node of the customer
- UI: credentials pane shows the single shared set plus per-binding
node/protocol/Reality-public-key rows
- golden subscriptions fixture regenerated under one shared set (RF and
SS entries byte-identical to before; five entries audited by hand)
- golden baseline re-captured for the new shapes; difftest normalizer now
treats a base64 root key's prefix-truncation encodings and the Surge
'='-mangled variants as the same secret
This commit is contained in:
+19
-11
@@ -560,9 +560,13 @@ type Customer struct {
|
||||
ServerUsage map[string]*UsageSlot `json:"serverUsage,omitempty"`
|
||||
LastUsageAt *string `json:"lastUsageAt,omitempty"`
|
||||
LastOnlineAt *string `json:"lastOnlineAt,omitempty"`
|
||||
ExternalSources []ExtSource `json:"externalSources,omitempty"`
|
||||
ManualLinks []ManualLink `json:"manualLinks,omitempty"`
|
||||
Extra extraFields `json:"-"`
|
||||
// The customer's single credential set, shared by all of their node
|
||||
// bindings (round-2 item 2). Populated by the store-load migration for
|
||||
// legacy records and at creation for new ones.
|
||||
Credentials *Credentials `json:"credentials,omitempty"`
|
||||
ExternalSources []ExtSource `json:"externalSources,omitempty"`
|
||||
ManualLinks []ManualLink `json:"manualLinks,omitempty"`
|
||||
Extra extraFields `json:"-"`
|
||||
}
|
||||
|
||||
// ExtSource is one external subscription source on a customer
|
||||
@@ -781,14 +785,18 @@ func (c Credentials) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
type NodeClient struct {
|
||||
ID string `json:"id"`
|
||||
NodeID string `json:"nodeId"`
|
||||
CustomerID string `json:"customerId"`
|
||||
Credentials Credentials `json:"credentials"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
Extra extraFields `json:"-"`
|
||||
ID string `json:"id"`
|
||||
NodeID string `json:"nodeId"`
|
||||
CustomerID string `json:"customerId"`
|
||||
// Deprecated: credentials live on the Customer (round-2 item 2). This
|
||||
// field is only a migration input — the store-load migration converges
|
||||
// it into Customer.Credentials and clears it, so it disappears from
|
||||
// disk on the next save. Builders receive resolved copies instead.
|
||||
Credentials *Credentials `json:"credentials,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
Extra extraFields `json:"-"`
|
||||
}
|
||||
|
||||
func (n *NodeClient) UnmarshalJSON(raw []byte) error {
|
||||
|
||||
Reference in New Issue
Block a user