@wipcomputer/wip-ldm-os 0.4.85-alpha.6 → 0.4.85-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -17,15 +17,15 @@ function assertNotContains(needle, label) {
|
|
|
17
17
|
|
|
18
18
|
assertContains('const ACCOUNT_TENANT_PREFIX = "acct:";', "account tenant prefix");
|
|
19
19
|
assertContains('const LEGACY_API_KEY_TENANT_PREFIX = "key:";', "legacy key tenant prefix");
|
|
20
|
-
assertContains('const RESERVED_AGENT_HANDLES = new Set([', "reserved handle set");
|
|
21
|
-
assertContains('"parker-smoke-test",', "reserved parker smoke handle");
|
|
22
20
|
assertContains('function accountTenantIdForUserId(userId)', "account tenant helper");
|
|
23
21
|
assertContains('function identityForApiKey(key)', "api key identity helper");
|
|
24
22
|
assertContains('return identityForApiKey(key);', "http auth uses identity helper");
|
|
25
23
|
assertContains("const agentId = accountTenantIdForUserId(stored.userId);", "registration uses immutable account tenant");
|
|
24
|
+
assertContains("function sanitizeDisplayLabel(raw)", "display label sanitizer");
|
|
25
|
+
assertContains('replace(/[\\u0000-\\u001f\\u007f]/g, "").replace(/\\s+/g, " ").trim().slice(0, 64)', "display label sanitizer preserves label semantics");
|
|
26
|
+
assertContains("const displayLabel = sanitizeDisplayLabel(body?.displayName || body?.username);", "registration treats entered name as display label");
|
|
27
|
+
assertContains("displayLabel,", "registration challenge stores display label");
|
|
26
28
|
assertContains("await saveApiKey(apiKey, agentId, { handle: credentialLabel });", "registration stores handle separately");
|
|
27
|
-
assertContains('json(res, 409, { error: "reserved_handle"', "reserved handle rejected");
|
|
28
|
-
assertContains('json(res, 409, { error: "handle_taken"', "duplicate handle rejected");
|
|
29
29
|
assertContains("p.handle = identity.handle;", "pair stores display handle separately");
|
|
30
30
|
assertContains("handle: identity.handle,", "relay metadata returns display handle");
|
|
31
31
|
assertContains("codexDaemons.has(identity.agentId)", "daemon presence uses tenant id");
|
|
@@ -37,6 +37,10 @@ assertContains("const key = codexRelayKey(identity.agentId, threadId);", "web ws
|
|
|
37
37
|
assertContains("const daemonWs = codexDaemons.get(identity.agentId);", "web sends to tenant daemon");
|
|
38
38
|
assertNotContains("const agentId = stored.username || (\"passkey-\"", "registration must not use chosen handle as tenant");
|
|
39
39
|
assertNotContains("const existingKey = Object.entries(API_KEYS).find(([k, v]) => v === agentId);", "oauth must not reuse chosen handle as tenant");
|
|
40
|
+
assertNotContains("function isUsernameTaken", "display labels must not be globally unique usernames");
|
|
41
|
+
assertNotContains("function sanitizeUsername", "display labels must not be modeled as usernames");
|
|
42
|
+
assertNotContains('json(res, 409, { error: "reserved_handle"', "display labels must not be blocked as reserved security handles");
|
|
43
|
+
assertNotContains('json(res, 409, { error: "handle_taken"', "duplicate display labels must be allowed");
|
|
40
44
|
|
|
41
45
|
function legacyTenantIdForApiKey(key) {
|
|
42
46
|
return "key:" + createHash("sha256").update(key).digest("base64url").slice(0, 32);
|
|
@@ -47,11 +51,16 @@ function accountTenantIdForUserId(userId) {
|
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
const chosenHandle = "parker-smoke-test";
|
|
54
|
+
const sharedDisplayLabel = "Parker";
|
|
50
55
|
const accountA = accountTenantIdForUserId("user-a");
|
|
51
56
|
const accountB = accountTenantIdForUserId("user-b");
|
|
57
|
+
const threadId = "thread-019dfa";
|
|
52
58
|
if (accountA === accountB) {
|
|
53
59
|
throw new Error("different user ids collapsed to one account tenant");
|
|
54
60
|
}
|
|
61
|
+
if (`${sharedDisplayLabel}:${threadId}` === `${accountA}:${threadId}` || `${sharedDisplayLabel}:${threadId}` === `${accountB}:${threadId}`) {
|
|
62
|
+
throw new Error("display label was used as a relay route key");
|
|
63
|
+
}
|
|
55
64
|
|
|
56
65
|
const legacyA = legacyTenantIdForApiKey("ck-a");
|
|
57
66
|
const legacyB = legacyTenantIdForApiKey("ck-b");
|
|
@@ -59,7 +68,6 @@ if (legacyA === legacyB) {
|
|
|
59
68
|
throw new Error("legacy API-key tenants collided");
|
|
60
69
|
}
|
|
61
70
|
|
|
62
|
-
const threadId = "thread-019dfa";
|
|
63
71
|
const webKeyA = `${accountA}:${threadId}`;
|
|
64
72
|
const webKeyB = `${accountB}:${threadId}`;
|
|
65
73
|
if (webKeyA === webKeyB) {
|
|
@@ -110,9 +110,6 @@ const API_KEY_HANDLES = {};
|
|
|
110
110
|
const ACCOUNT_TENANT_PREFIX = "acct:";
|
|
111
111
|
const LEGACY_API_KEY_TENANT_PREFIX = "key:";
|
|
112
112
|
const OAUTH_API_KEY_TENANT_PREFIX = "oauth:";
|
|
113
|
-
const RESERVED_AGENT_HANDLES = new Set([
|
|
114
|
-
"parker-smoke-test",
|
|
115
|
-
]);
|
|
116
113
|
|
|
117
114
|
function isInternalTenantId(id) {
|
|
118
115
|
return typeof id === "string"
|
|
@@ -519,21 +516,12 @@ function esc(s) {
|
|
|
519
516
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
520
517
|
}
|
|
521
518
|
|
|
522
|
-
function
|
|
519
|
+
function sanitizeDisplayLabel(raw) {
|
|
523
520
|
if (!raw || typeof raw !== "string") return null;
|
|
524
|
-
const cleaned = raw.
|
|
521
|
+
const cleaned = raw.replace(/[\u0000-\u001f\u007f]/g, "").replace(/\s+/g, " ").trim().slice(0, 64);
|
|
525
522
|
return cleaned.length > 0 ? cleaned : null;
|
|
526
523
|
}
|
|
527
524
|
|
|
528
|
-
async function isUsernameTaken(username) {
|
|
529
|
-
if (!username) return false;
|
|
530
|
-
if (usePrisma) {
|
|
531
|
-
const existing = await prisma.user.findFirst({ where: { name: username } });
|
|
532
|
-
return !!existing;
|
|
533
|
-
}
|
|
534
|
-
return passkeys.some((entry) => entry.handle === username || entry.agentId === username);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
525
|
// ---------- Session cleanup ----------
|
|
538
526
|
|
|
539
527
|
function touchSession(sid) {
|
|
@@ -655,22 +643,17 @@ async function handleRegisterOptions(req, res) {
|
|
|
655
643
|
let body;
|
|
656
644
|
try { body = await readBody(req); } catch { body = {}; }
|
|
657
645
|
|
|
658
|
-
// Accept
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
if (username && await isUsernameTaken(username)) {
|
|
665
|
-
json(res, 409, { error: "handle_taken", error_description: "This handle is already in use." });
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
646
|
+
// Accept the existing `username` field for wire compatibility, but
|
|
647
|
+
// treat it only as a display label for the passkey prompt. It is not
|
|
648
|
+
// a public username, account handle, or relay tenant boundary.
|
|
649
|
+
// Duplicate display labels are allowed.
|
|
650
|
+
const displayLabel = sanitizeDisplayLabel(body?.displayName || body?.username);
|
|
668
651
|
|
|
669
652
|
const userId = randomBytes(16);
|
|
670
653
|
const userIdB64 = userId.toString("base64url");
|
|
671
654
|
|
|
672
|
-
const userName =
|
|
673
|
-
const displayName =
|
|
655
|
+
const userName = displayLabel || ("user-" + userIdB64.slice(0, 8));
|
|
656
|
+
const displayName = displayLabel || "Memory Crystal User";
|
|
674
657
|
|
|
675
658
|
let options;
|
|
676
659
|
try {
|
|
@@ -699,7 +682,7 @@ async function handleRegisterOptions(req, res) {
|
|
|
699
682
|
challenge: options.challenge,
|
|
700
683
|
type: "registration",
|
|
701
684
|
userId: userIdB64,
|
|
702
|
-
|
|
685
|
+
displayLabel,
|
|
703
686
|
expires: Date.now() + 120000,
|
|
704
687
|
};
|
|
705
688
|
|
|
@@ -752,8 +735,8 @@ async function handleRegisterVerify(req, res) {
|
|
|
752
735
|
|
|
753
736
|
const { credential: cred, credentialDeviceType, credentialBackedUp } = verification.registrationInfo;
|
|
754
737
|
|
|
755
|
-
// Internal tenancy is the immutable WebAuthn user id. The
|
|
756
|
-
//
|
|
738
|
+
// Internal tenancy is the immutable WebAuthn user id. The user-entered
|
|
739
|
+
// display label is metadata only and never owns a relay namespace.
|
|
757
740
|
const agentId = accountTenantIdForUserId(stored.userId);
|
|
758
741
|
// credentialLabel matches the userName passed to
|
|
759
742
|
// generateRegistrationOptions in handleRegisterOptions, which is what
|
|
@@ -761,7 +744,7 @@ async function handleRegisterVerify(req, res) {
|
|
|
761
744
|
// welcome view should display this, not agentId. Auth semantics are
|
|
762
745
|
// unchanged; only the user-facing label is aligned with the saved
|
|
763
746
|
// credential.
|
|
764
|
-
const credentialLabel = stored.
|
|
747
|
+
const credentialLabel = stored.displayLabel || ("user-" + stored.userId.slice(0, 8));
|
|
765
748
|
const apiKey = generateApiKey();
|
|
766
749
|
|
|
767
750
|
const entry = {
|
|
@@ -2784,8 +2767,8 @@ async function handleCodexPairComplete(req, res) {
|
|
|
2784
2767
|
p.agentId = identity.agentId;
|
|
2785
2768
|
p.handle = identity.handle;
|
|
2786
2769
|
// Phase 2.5: register the daemon's E2EE public key against the
|
|
2787
|
-
// authenticated
|
|
2788
|
-
//
|
|
2770
|
+
// authenticated immutable tenant id. The display handle is returned
|
|
2771
|
+
// as metadata only.
|
|
2789
2772
|
if (p.daemon_public_key) {
|
|
2790
2773
|
codexDaemonPubkeys.set(identity.agentId, {
|
|
2791
2774
|
pubkey: p.daemon_public_key,
|