@bandeira-tech/b3nd-web 0.3.0 → 0.3.2
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/dist/{chunk-4C4YY2X7.js → chunk-EF5ZUB4O.js} +193 -16
- package/dist/{chunk-IILPY4TK.js → chunk-FDJZ4P2M.js} +198 -48
- package/dist/{chunk-OXHGNAQJ.js → chunk-O53KW746.js} +25 -1
- package/dist/{chunk-LFUC4ETD.js → chunk-OY4CDOHY.js} +45 -0
- package/dist/{chunk-7U5JDFQW.js → chunk-PZFEKQ7F.js} +24 -0
- package/dist/{chunk-T43IWAQK.js → chunk-UUHVOWVI.js} +29 -0
- package/dist/{client-fsiY-9VW.d.ts → client-C4oQxiDu.d.ts} +102 -11
- package/dist/clients/http/mod.d.ts +3 -1
- package/dist/clients/http/mod.js +1 -1
- package/dist/clients/local-storage/mod.d.ts +2 -1
- package/dist/clients/local-storage/mod.js +1 -1
- package/dist/clients/memory/mod.d.ts +2 -1
- package/dist/clients/memory/mod.js +1 -1
- package/dist/clients/websocket/mod.d.ts +2 -1
- package/dist/clients/websocket/mod.js +1 -1
- package/dist/{core-BLTm0eu6.d.ts → core-ClnuubZw.d.ts} +16 -1
- package/dist/src/mod.web.d.ts +2 -2
- package/dist/src/mod.web.js +12 -12
- package/dist/{types-oQCx3U-_.d.ts → types-uuvn4oKw.d.ts} +36 -2
- package/dist/wallet/mod.d.ts +52 -12
- package/dist/wallet/mod.js +6 -4
- package/dist/wallet-server/adapters/browser.d.ts +3 -3
- package/dist/wallet-server/adapters/browser.js +3 -3
- package/dist/wallet-server/mod.d.ts +8 -5
- package/dist/wallet-server/mod.js +2 -2
- package/package.json +1 -1
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HttpClient
|
|
3
|
+
} from "./chunk-OY4CDOHY.js";
|
|
1
4
|
import {
|
|
2
5
|
createAuthenticatedMessage,
|
|
3
6
|
createSignedEncryptedMessage,
|
|
@@ -5,11 +8,9 @@ import {
|
|
|
5
8
|
decrypt,
|
|
6
9
|
encodeHex,
|
|
7
10
|
encrypt,
|
|
11
|
+
verify,
|
|
8
12
|
verifyPayload
|
|
9
13
|
} from "./chunk-JN75UL5C.js";
|
|
10
|
-
import {
|
|
11
|
-
HttpClient
|
|
12
|
-
} from "./chunk-LFUC4ETD.js";
|
|
13
14
|
|
|
14
15
|
// wallet-server/interfaces.ts
|
|
15
16
|
var defaultLogger = {
|
|
@@ -511,6 +512,89 @@ async function proxyRead(proxyClient, credentialClient, serverPublicKey, usernam
|
|
|
511
512
|
};
|
|
512
513
|
}
|
|
513
514
|
}
|
|
515
|
+
async function proxyReadMulti(proxyClient, credentialClient, serverPublicKey, username, serverEncryptionPrivateKeyPem, uris) {
|
|
516
|
+
if (uris.length > 50) {
|
|
517
|
+
return {
|
|
518
|
+
success: false,
|
|
519
|
+
results: [],
|
|
520
|
+
summary: { total: uris.length, succeeded: 0, failed: uris.length },
|
|
521
|
+
error: "Maximum 50 URIs per request"
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
try {
|
|
525
|
+
const [accountKey, userEncryptionKey] = await Promise.all([
|
|
526
|
+
loadUserAccountKey(
|
|
527
|
+
credentialClient,
|
|
528
|
+
serverPublicKey,
|
|
529
|
+
username,
|
|
530
|
+
serverEncryptionPrivateKeyPem
|
|
531
|
+
),
|
|
532
|
+
loadUserEncryptionKey(
|
|
533
|
+
credentialClient,
|
|
534
|
+
serverPublicKey,
|
|
535
|
+
username,
|
|
536
|
+
serverEncryptionPrivateKeyPem
|
|
537
|
+
)
|
|
538
|
+
]);
|
|
539
|
+
const resolvedUris = uris.map(
|
|
540
|
+
(uri) => uri.replace(/:key/g, accountKey.publicKeyHex)
|
|
541
|
+
);
|
|
542
|
+
const multiResult = await proxyClient.readMulti(resolvedUris);
|
|
543
|
+
const privateKey = await pemToCryptoKey(
|
|
544
|
+
userEncryptionKey.privateKeyPem,
|
|
545
|
+
"X25519"
|
|
546
|
+
);
|
|
547
|
+
const results = await Promise.all(
|
|
548
|
+
multiResult.results.map(async (item, i) => {
|
|
549
|
+
const originalUri = uris[i];
|
|
550
|
+
if (!item.success) {
|
|
551
|
+
return {
|
|
552
|
+
uri: originalUri,
|
|
553
|
+
success: false,
|
|
554
|
+
error: "error" in item ? item.error : "Read failed"
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
const result = {
|
|
558
|
+
uri: originalUri,
|
|
559
|
+
success: true,
|
|
560
|
+
record: "record" in item ? item.record : void 0
|
|
561
|
+
};
|
|
562
|
+
if (result.record?.data) {
|
|
563
|
+
try {
|
|
564
|
+
const data = result.record.data;
|
|
565
|
+
if (typeof data === "object" && data.payload && typeof data.payload === "object") {
|
|
566
|
+
const payload = data.payload;
|
|
567
|
+
if (payload.data && payload.nonce && payload.ephemeralPublicKey) {
|
|
568
|
+
const encryptedPayload = {
|
|
569
|
+
data: payload.data,
|
|
570
|
+
nonce: payload.nonce,
|
|
571
|
+
ephemeralPublicKey: payload.ephemeralPublicKey
|
|
572
|
+
};
|
|
573
|
+
const decrypted = await decrypt(encryptedPayload, privateKey);
|
|
574
|
+
return { ...result, decrypted };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
} catch {
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return result;
|
|
581
|
+
})
|
|
582
|
+
);
|
|
583
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
584
|
+
return {
|
|
585
|
+
success: succeeded > 0,
|
|
586
|
+
results,
|
|
587
|
+
summary: { total: uris.length, succeeded, failed: uris.length - succeeded }
|
|
588
|
+
};
|
|
589
|
+
} catch (error) {
|
|
590
|
+
return {
|
|
591
|
+
success: false,
|
|
592
|
+
results: [],
|
|
593
|
+
summary: { total: uris.length, succeeded: 0, failed: uris.length },
|
|
594
|
+
error: `Proxy read-multi failed: ${error instanceof Error ? error.message : String(error)}`
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
}
|
|
514
598
|
|
|
515
599
|
// wallet-server/auth.ts
|
|
516
600
|
function generateSalt() {
|
|
@@ -597,7 +681,7 @@ async function createUser(client, serverPublicKey, username, password, serverIde
|
|
|
597
681
|
);
|
|
598
682
|
return { salt, hash };
|
|
599
683
|
}
|
|
600
|
-
async function authenticateUser(client, serverPublicKey, username, password,
|
|
684
|
+
async function authenticateUser(client, serverPublicKey, username, password, serverEncryptionPrivateKeyPem, appScope, logger) {
|
|
601
685
|
const passwordPath = await deriveObfuscatedPath(
|
|
602
686
|
serverPublicKey,
|
|
603
687
|
username,
|
|
@@ -629,7 +713,6 @@ async function changePassword(client, serverPublicKey, username, oldPassword, ne
|
|
|
629
713
|
serverPublicKey,
|
|
630
714
|
username,
|
|
631
715
|
oldPassword,
|
|
632
|
-
serverIdentityPublicKeyHex,
|
|
633
716
|
serverEncryptionPrivateKeyPem,
|
|
634
717
|
appScope
|
|
635
718
|
);
|
|
@@ -1003,7 +1086,6 @@ var PasswordCredentialHandler = class {
|
|
|
1003
1086
|
context.serverPublicKey,
|
|
1004
1087
|
username,
|
|
1005
1088
|
password,
|
|
1006
|
-
context.serverIdentityPublicKeyHex,
|
|
1007
1089
|
context.serverEncryptionPrivateKeyPem,
|
|
1008
1090
|
context.appKey,
|
|
1009
1091
|
context.logger
|
|
@@ -1310,13 +1392,42 @@ var WalletServerCore = class {
|
|
|
1310
1392
|
async writeBootstrapState(path, state) {
|
|
1311
1393
|
await this.storage.writeTextFile(path, JSON.stringify(state, null, 2));
|
|
1312
1394
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1395
|
+
/**
|
|
1396
|
+
* Validate that a session is approved by the app.
|
|
1397
|
+
* Sessions are keypairs - the sessionPubkey is used directly as the identifier.
|
|
1398
|
+
* App approves sessions by writing 1 to mutable://accounts/{appKey}/sessions/{sessionPubkey}
|
|
1399
|
+
*
|
|
1400
|
+
* @param appKey - The app's public key
|
|
1401
|
+
* @param sessionPubkey - The session's public key (hex encoded)
|
|
1402
|
+
* @returns { valid: true } if approved, { valid: false, reason } if not
|
|
1403
|
+
*/
|
|
1404
|
+
async sessionExists(appKey, sessionPubkey) {
|
|
1405
|
+
const uri = `mutable://accounts/${appKey}/sessions/${sessionPubkey}`;
|
|
1318
1406
|
const res = await this.proxyClient.read(uri);
|
|
1319
|
-
|
|
1407
|
+
if (!res.success) {
|
|
1408
|
+
return { valid: false, reason: "session_not_approved" };
|
|
1409
|
+
}
|
|
1410
|
+
if (res.record?.data === 1) {
|
|
1411
|
+
return { valid: true };
|
|
1412
|
+
}
|
|
1413
|
+
if (res.record?.data === 0) {
|
|
1414
|
+
return { valid: false, reason: "session_revoked" };
|
|
1415
|
+
}
|
|
1416
|
+
return { valid: false, reason: "invalid_session_status" };
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Verify that a login request signature is valid for the given session pubkey.
|
|
1420
|
+
* The signature should be over the stringified login payload (without the signature field).
|
|
1421
|
+
* Uses SDK crypto for consistent verification across the codebase.
|
|
1422
|
+
*/
|
|
1423
|
+
async verifySessionSignature(sessionPubkey, signature, payload) {
|
|
1424
|
+
try {
|
|
1425
|
+
const { sessionSignature: _, ...signedPayload } = payload;
|
|
1426
|
+
return await verify(sessionPubkey, signature, signedPayload);
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
this.logger.error("Session signature verification failed:", error);
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1320
1431
|
}
|
|
1321
1432
|
createApp() {
|
|
1322
1433
|
const app = new Hono();
|
|
@@ -1418,12 +1529,33 @@ var WalletServerCore = class {
|
|
|
1418
1529
|
if (!appKey) {
|
|
1419
1530
|
return c.json({ success: false, error: "appKey is required" }, 400);
|
|
1420
1531
|
}
|
|
1532
|
+
if (!payload.sessionPubkey) {
|
|
1533
|
+
return c.json({ success: false, error: "sessionPubkey is required" }, 400);
|
|
1534
|
+
}
|
|
1535
|
+
if (!payload.sessionSignature) {
|
|
1536
|
+
return c.json({ success: false, error: "sessionSignature is required" }, 400);
|
|
1537
|
+
}
|
|
1421
1538
|
if (!payload.type) {
|
|
1422
1539
|
return c.json({
|
|
1423
1540
|
success: false,
|
|
1424
1541
|
error: `type is required. Supported: ${getSupportedCredentialTypes().join(", ")}`
|
|
1425
1542
|
}, 400);
|
|
1426
1543
|
}
|
|
1544
|
+
const signatureValid = await this.verifySessionSignature(
|
|
1545
|
+
payload.sessionPubkey,
|
|
1546
|
+
payload.sessionSignature,
|
|
1547
|
+
payload
|
|
1548
|
+
);
|
|
1549
|
+
if (!signatureValid) {
|
|
1550
|
+
return c.json({ success: false, error: "Invalid session signature" }, 401);
|
|
1551
|
+
}
|
|
1552
|
+
const sessionResult = await this.sessionExists(appKey, payload.sessionPubkey);
|
|
1553
|
+
if (!sessionResult.valid) {
|
|
1554
|
+
return c.json({
|
|
1555
|
+
success: false,
|
|
1556
|
+
error: sessionResult.reason === "session_revoked" ? "Session has been revoked" : sessionResult.reason === "session_not_approved" ? "Session not approved by app" : "Invalid session"
|
|
1557
|
+
}, 401);
|
|
1558
|
+
}
|
|
1427
1559
|
const handler = getCredentialHandler(payload.type);
|
|
1428
1560
|
let googleClientId;
|
|
1429
1561
|
if (payload.type === "google") {
|
|
@@ -1485,8 +1617,11 @@ var WalletServerCore = class {
|
|
|
1485
1617
|
if (!appKey) {
|
|
1486
1618
|
return c.json({ success: false, error: "appKey is required" }, 400);
|
|
1487
1619
|
}
|
|
1488
|
-
if (!payload.
|
|
1489
|
-
return c.json({ success: false, error: "
|
|
1620
|
+
if (!payload.sessionPubkey) {
|
|
1621
|
+
return c.json({ success: false, error: "sessionPubkey is required" }, 400);
|
|
1622
|
+
}
|
|
1623
|
+
if (!payload.sessionSignature) {
|
|
1624
|
+
return c.json({ success: false, error: "sessionSignature is required" }, 400);
|
|
1490
1625
|
}
|
|
1491
1626
|
if (!payload.type) {
|
|
1492
1627
|
return c.json({
|
|
@@ -1494,8 +1629,20 @@ var WalletServerCore = class {
|
|
|
1494
1629
|
error: `type is required. Supported: ${getSupportedCredentialTypes().join(", ")}`
|
|
1495
1630
|
}, 400);
|
|
1496
1631
|
}
|
|
1497
|
-
|
|
1498
|
-
|
|
1632
|
+
const signatureValid = await this.verifySessionSignature(
|
|
1633
|
+
payload.sessionPubkey,
|
|
1634
|
+
payload.sessionSignature,
|
|
1635
|
+
payload
|
|
1636
|
+
);
|
|
1637
|
+
if (!signatureValid) {
|
|
1638
|
+
return c.json({ success: false, error: "Invalid session signature" }, 401);
|
|
1639
|
+
}
|
|
1640
|
+
const sessionResult = await this.sessionExists(appKey, payload.sessionPubkey);
|
|
1641
|
+
if (!sessionResult.valid) {
|
|
1642
|
+
return c.json({
|
|
1643
|
+
success: false,
|
|
1644
|
+
error: sessionResult.reason === "session_revoked" ? "Session has been revoked" : sessionResult.reason === "session_not_approved" ? "Session not approved by app" : "Invalid session"
|
|
1645
|
+
}, 401);
|
|
1499
1646
|
}
|
|
1500
1647
|
const handler = getCredentialHandler(payload.type);
|
|
1501
1648
|
let googleClientId;
|
|
@@ -1751,6 +1898,36 @@ var WalletServerCore = class {
|
|
|
1751
1898
|
return c.json({ success: false, error: message }, 500);
|
|
1752
1899
|
}
|
|
1753
1900
|
});
|
|
1901
|
+
app.post("/api/v1/proxy/read-multi", async (c) => {
|
|
1902
|
+
try {
|
|
1903
|
+
const authHeader = c.req.header("Authorization");
|
|
1904
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
1905
|
+
return c.json({ success: false, error: "Authorization required" }, 401);
|
|
1906
|
+
}
|
|
1907
|
+
const token = authHeader.substring(7);
|
|
1908
|
+
const payload = await verifyJwt(token, this.config.jwtSecret);
|
|
1909
|
+
const body = await c.req.json();
|
|
1910
|
+
if (!Array.isArray(body.uris)) {
|
|
1911
|
+
return c.json({ success: false, error: "uris must be an array" }, 400);
|
|
1912
|
+
}
|
|
1913
|
+
const result = await proxyReadMulti(
|
|
1914
|
+
this.proxyClient,
|
|
1915
|
+
this.credentialClient,
|
|
1916
|
+
serverPublicKey,
|
|
1917
|
+
payload.username,
|
|
1918
|
+
serverEncryptionPrivateKeyPem,
|
|
1919
|
+
body.uris
|
|
1920
|
+
);
|
|
1921
|
+
return c.json(result);
|
|
1922
|
+
} catch (error) {
|
|
1923
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1924
|
+
if (message.includes("expired")) {
|
|
1925
|
+
return c.json({ success: false, error: message }, 401);
|
|
1926
|
+
}
|
|
1927
|
+
this.logger.error("Proxy read-multi error:", error);
|
|
1928
|
+
return c.json({ success: false, error: message }, 500);
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1754
1931
|
return app;
|
|
1755
1932
|
}
|
|
1756
1933
|
};
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MemoryClient,
|
|
3
|
-
createTestSchema
|
|
4
|
-
} from "./chunk-OXHGNAQJ.js";
|
|
5
1
|
import {
|
|
6
2
|
WalletServerCore
|
|
7
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EF5ZUB4O.js";
|
|
8
4
|
import {
|
|
9
5
|
exportPrivateKeyPem,
|
|
10
6
|
generateEncryptionKeyPair,
|
|
11
|
-
generateSigningKeyPair
|
|
7
|
+
generateSigningKeyPair,
|
|
8
|
+
signWithHex
|
|
12
9
|
} from "./chunk-JN75UL5C.js";
|
|
10
|
+
import {
|
|
11
|
+
MemoryClient,
|
|
12
|
+
createTestSchema
|
|
13
|
+
} from "./chunk-O53KW746.js";
|
|
13
14
|
|
|
14
15
|
// wallet/client.ts
|
|
15
16
|
var WalletClient = class {
|
|
@@ -143,19 +144,34 @@ var WalletClient = class {
|
|
|
143
144
|
throw new Error("Use resetPasswordWithToken(appKey, username, resetToken, newPassword)");
|
|
144
145
|
}
|
|
145
146
|
/**
|
|
146
|
-
* Sign up with
|
|
147
|
+
* Sign up with session keypair (scoped to an app)
|
|
148
|
+
*
|
|
149
|
+
* The session must be approved by the app beforehand:
|
|
150
|
+
* 1. Client writes request to: immutable://inbox/{appKey}/sessions/{sessionPubkey} = 1
|
|
151
|
+
* 2. App approves by writing: mutable://accounts/{appKey}/sessions/{sessionPubkey} = 1
|
|
152
|
+
* 3. Client calls this method with the session keypair
|
|
153
|
+
*
|
|
154
|
+
* @param appKey - The app's public key
|
|
155
|
+
* @param session - Session keypair (generated via generateSessionKeypair)
|
|
156
|
+
* @param credentials - User credentials (username/password)
|
|
147
157
|
*/
|
|
148
|
-
async signupWithToken(appKey,
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
async signupWithToken(appKey, session, credentials) {
|
|
159
|
+
if (!session?.publicKeyHex || !session?.privateKeyHex) {
|
|
160
|
+
throw new Error("session keypair is required");
|
|
161
|
+
}
|
|
162
|
+
const payloadToSign = {
|
|
163
|
+
sessionPubkey: session.publicKeyHex,
|
|
164
|
+
type: "password",
|
|
165
|
+
username: credentials.username,
|
|
166
|
+
password: credentials.password
|
|
167
|
+
};
|
|
168
|
+
const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
|
|
151
169
|
const response = await this.fetchImpl(this.buildAppKeyUrl("/auth/signup", appKey), {
|
|
152
170
|
method: "POST",
|
|
153
171
|
headers: { "Content-Type": "application/json" },
|
|
154
172
|
body: JSON.stringify({
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
username: credentials.username,
|
|
158
|
-
password: credentials.password
|
|
173
|
+
...payloadToSign,
|
|
174
|
+
sessionSignature
|
|
159
175
|
})
|
|
160
176
|
});
|
|
161
177
|
const data = await response.json();
|
|
@@ -165,21 +181,34 @@ var WalletClient = class {
|
|
|
165
181
|
return { username: data.username, token: data.token, expiresIn: data.expiresIn };
|
|
166
182
|
}
|
|
167
183
|
/**
|
|
168
|
-
* Login with
|
|
184
|
+
* Login with session keypair (scoped to an app)
|
|
185
|
+
*
|
|
186
|
+
* The session must be approved by the app beforehand:
|
|
187
|
+
* 1. Client writes request to: immutable://inbox/{appKey}/sessions/{sessionPubkey} = 1
|
|
188
|
+
* 2. App approves by writing: mutable://accounts/{appKey}/sessions/{sessionPubkey} = 1
|
|
189
|
+
* 3. Client calls this method with the session keypair
|
|
190
|
+
*
|
|
191
|
+
* @param appKey - The app's public key
|
|
192
|
+
* @param session - Session keypair (generated via generateSessionKeypair)
|
|
193
|
+
* @param credentials - User credentials (username/password)
|
|
169
194
|
*/
|
|
170
|
-
async loginWithTokenSession(appKey,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
195
|
+
async loginWithTokenSession(appKey, session, credentials) {
|
|
196
|
+
if (!session?.publicKeyHex || !session?.privateKeyHex) {
|
|
197
|
+
throw new Error("session keypair is required");
|
|
198
|
+
}
|
|
199
|
+
const payloadToSign = {
|
|
200
|
+
sessionPubkey: session.publicKeyHex,
|
|
201
|
+
type: "password",
|
|
202
|
+
username: credentials.username,
|
|
203
|
+
password: credentials.password
|
|
204
|
+
};
|
|
205
|
+
const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
|
|
174
206
|
const response = await this.fetchImpl(this.buildAppKeyUrl("/auth/login", appKey), {
|
|
175
207
|
method: "POST",
|
|
176
208
|
headers: { "Content-Type": "application/json" },
|
|
177
209
|
body: JSON.stringify({
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
type: "password",
|
|
181
|
-
username: credentials.username,
|
|
182
|
-
password: credentials.password
|
|
210
|
+
...payloadToSign,
|
|
211
|
+
sessionSignature
|
|
183
212
|
})
|
|
184
213
|
});
|
|
185
214
|
const data = await response.json();
|
|
@@ -299,6 +328,31 @@ var WalletClient = class {
|
|
|
299
328
|
const data = await response.json();
|
|
300
329
|
return data;
|
|
301
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Proxy multiple read requests through the wallet server
|
|
333
|
+
* Reads multiple URIs in a single request (max 50 URIs)
|
|
334
|
+
* The server decrypts encrypted data using user's encryption key
|
|
335
|
+
* Requires active authentication session
|
|
336
|
+
*
|
|
337
|
+
* @returns ProxyReadMultiResponse - check `success` for overall result, `results` for per-URI results
|
|
338
|
+
*/
|
|
339
|
+
async proxyReadMulti(request) {
|
|
340
|
+
if (!this.currentSession) {
|
|
341
|
+
throw new Error("Not authenticated. Please login first.");
|
|
342
|
+
}
|
|
343
|
+
const response = await this.fetchImpl(
|
|
344
|
+
`${this.walletServerUrl}${this.apiBasePath}/proxy/read-multi`,
|
|
345
|
+
{
|
|
346
|
+
method: "POST",
|
|
347
|
+
headers: {
|
|
348
|
+
"Content-Type": "application/json",
|
|
349
|
+
Authorization: `Bearer ${this.currentSession.token}`
|
|
350
|
+
},
|
|
351
|
+
body: JSON.stringify({ uris: request.uris })
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
return await response.json();
|
|
355
|
+
}
|
|
302
356
|
/**
|
|
303
357
|
* Convenience method: Get current user's public keys
|
|
304
358
|
* Requires active authentication session
|
|
@@ -356,22 +410,37 @@ var WalletClient = class {
|
|
|
356
410
|
};
|
|
357
411
|
}
|
|
358
412
|
/**
|
|
359
|
-
* Login with Google OAuth (scoped to app token and session)
|
|
413
|
+
* Login with Google OAuth (scoped to app token and session keypair)
|
|
360
414
|
* Returns session data with Google profile info - call setSession() to activate it
|
|
361
415
|
*
|
|
416
|
+
* The session must be approved by the app beforehand.
|
|
417
|
+
*
|
|
418
|
+
* @param appKey - The app's public key
|
|
362
419
|
* @param token - App token from app server
|
|
363
|
-
* @param session - Session
|
|
420
|
+
* @param session - Session keypair (generated via generateSessionKeypair)
|
|
364
421
|
* @param googleIdToken - Google ID token from Google Sign-In
|
|
365
422
|
* @returns GoogleAuthSession with username, JWT token, and Google profile info
|
|
366
423
|
*/
|
|
367
424
|
async loginWithGoogle(appKey, token, session, googleIdToken) {
|
|
368
425
|
if (!token) throw new Error("token is required");
|
|
369
|
-
if (!session
|
|
426
|
+
if (!session?.publicKeyHex || !session?.privateKeyHex) {
|
|
427
|
+
throw new Error("session keypair is required");
|
|
428
|
+
}
|
|
370
429
|
if (!googleIdToken) throw new Error("googleIdToken is required");
|
|
430
|
+
const payloadToSign = {
|
|
431
|
+
token,
|
|
432
|
+
sessionPubkey: session.publicKeyHex,
|
|
433
|
+
type: "google",
|
|
434
|
+
googleIdToken
|
|
435
|
+
};
|
|
436
|
+
const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
|
|
371
437
|
const response = await this.fetchImpl(this.buildAppKeyUrl("/auth/login", appKey), {
|
|
372
438
|
method: "POST",
|
|
373
439
|
headers: { "Content-Type": "application/json" },
|
|
374
|
-
body: JSON.stringify({
|
|
440
|
+
body: JSON.stringify({
|
|
441
|
+
...payloadToSign,
|
|
442
|
+
sessionSignature
|
|
443
|
+
})
|
|
375
444
|
});
|
|
376
445
|
const data = await response.json();
|
|
377
446
|
if (!response.ok || !data.success) {
|
|
@@ -387,6 +456,13 @@ var WalletClient = class {
|
|
|
387
456
|
};
|
|
388
457
|
}
|
|
389
458
|
};
|
|
459
|
+
async function generateSessionKeypair() {
|
|
460
|
+
const keyPair = await generateSigningKeyPair();
|
|
461
|
+
return {
|
|
462
|
+
publicKeyHex: keyPair.publicKeyHex,
|
|
463
|
+
privateKeyHex: keyPair.privateKeyHex
|
|
464
|
+
};
|
|
465
|
+
}
|
|
390
466
|
|
|
391
467
|
// wallet/memory-client.ts
|
|
392
468
|
async function generateTestServerKeys() {
|
|
@@ -541,14 +617,32 @@ var MemoryWalletClient = class _MemoryWalletClient {
|
|
|
541
617
|
async login(_credentials) {
|
|
542
618
|
throw new Error("Use loginWithTokenSession(appKey, session, credentials) \u2014 app token + session required");
|
|
543
619
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
620
|
+
/**
|
|
621
|
+
* Sign up with session keypair (scoped to an app)
|
|
622
|
+
*
|
|
623
|
+
* The session must be approved by the app beforehand:
|
|
624
|
+
* 1. Client writes request to: immutable://inbox/{appKey}/sessions/{sessionPubkey} = 1
|
|
625
|
+
* 2. App approves by writing: mutable://accounts/{appKey}/sessions/{sessionPubkey} = 1
|
|
626
|
+
* 3. Client calls this method with the session keypair
|
|
627
|
+
*
|
|
628
|
+
* @param appKey - The app's public key
|
|
629
|
+
* @param session - Session keypair (generated via generateSessionKeypair)
|
|
630
|
+
* @param credentials - User credentials (username/password)
|
|
631
|
+
*/
|
|
632
|
+
async signupWithToken(appKey, session, credentials) {
|
|
633
|
+
if (!session?.publicKeyHex || !session?.privateKeyHex) {
|
|
634
|
+
throw new Error("session keypair is required");
|
|
635
|
+
}
|
|
636
|
+
const payloadToSign = {
|
|
637
|
+
sessionPubkey: session.publicKeyHex,
|
|
549
638
|
type: "password",
|
|
550
639
|
username: credentials.username,
|
|
551
640
|
password: credentials.password
|
|
641
|
+
};
|
|
642
|
+
const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
|
|
643
|
+
const response = await this.request("POST", `/auth/signup/${appKey}`, {
|
|
644
|
+
...payloadToSign,
|
|
645
|
+
sessionSignature
|
|
552
646
|
});
|
|
553
647
|
const data = await response.json();
|
|
554
648
|
if (!response.ok || !data.success) {
|
|
@@ -560,16 +654,32 @@ var MemoryWalletClient = class _MemoryWalletClient {
|
|
|
560
654
|
expiresIn: data.expiresIn
|
|
561
655
|
};
|
|
562
656
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
657
|
+
/**
|
|
658
|
+
* Login with session keypair (scoped to an app)
|
|
659
|
+
*
|
|
660
|
+
* The session must be approved by the app beforehand:
|
|
661
|
+
* 1. Client writes request to: immutable://inbox/{appKey}/sessions/{sessionPubkey} = 1
|
|
662
|
+
* 2. App approves by writing: mutable://accounts/{appKey}/sessions/{sessionPubkey} = 1
|
|
663
|
+
* 3. Client calls this method with the session keypair
|
|
664
|
+
*
|
|
665
|
+
* @param appKey - The app's public key
|
|
666
|
+
* @param session - Session keypair (generated via generateSessionKeypair)
|
|
667
|
+
* @param credentials - User credentials (username/password)
|
|
668
|
+
*/
|
|
669
|
+
async loginWithTokenSession(appKey, session, credentials) {
|
|
670
|
+
if (!session?.publicKeyHex || !session?.privateKeyHex) {
|
|
671
|
+
throw new Error("session keypair is required");
|
|
672
|
+
}
|
|
673
|
+
const payloadToSign = {
|
|
674
|
+
sessionPubkey: session.publicKeyHex,
|
|
570
675
|
type: "password",
|
|
571
676
|
username: credentials.username,
|
|
572
677
|
password: credentials.password
|
|
678
|
+
};
|
|
679
|
+
const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
|
|
680
|
+
const response = await this.request("POST", `/auth/login/${appKey}`, {
|
|
681
|
+
...payloadToSign,
|
|
682
|
+
sessionSignature
|
|
573
683
|
});
|
|
574
684
|
const data = await response.json();
|
|
575
685
|
if (!response.ok || !data.success) {
|
|
@@ -675,6 +785,12 @@ var MemoryWalletClient = class _MemoryWalletClient {
|
|
|
675
785
|
const data = await response.json();
|
|
676
786
|
return data;
|
|
677
787
|
}
|
|
788
|
+
async proxyReadMulti(request) {
|
|
789
|
+
const response = await this.authRequest("POST", "/proxy/read-multi", {
|
|
790
|
+
uris: request.uris
|
|
791
|
+
});
|
|
792
|
+
return await response.json();
|
|
793
|
+
}
|
|
678
794
|
// ============================================================
|
|
679
795
|
// Google OAuth (for completeness - may not work without real Google)
|
|
680
796
|
// ============================================================
|
|
@@ -699,15 +815,34 @@ var MemoryWalletClient = class _MemoryWalletClient {
|
|
|
699
815
|
picture: data.picture
|
|
700
816
|
};
|
|
701
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Login with Google OAuth (scoped to app token and session keypair)
|
|
820
|
+
* Returns session data with Google profile info - call setSession() to activate it
|
|
821
|
+
*
|
|
822
|
+
* The session must be approved by the app beforehand.
|
|
823
|
+
*
|
|
824
|
+
* @param appKey - The app's public key
|
|
825
|
+
* @param token - App token from app server
|
|
826
|
+
* @param session - Session keypair (generated via generateSessionKeypair)
|
|
827
|
+
* @param googleIdToken - Google ID token from Google Sign-In
|
|
828
|
+
* @returns GoogleAuthSession with username, JWT token, and Google profile info
|
|
829
|
+
*/
|
|
702
830
|
async loginWithGoogle(appKey, token, session, googleIdToken) {
|
|
703
831
|
if (!token) throw new Error("token is required");
|
|
704
|
-
if (!session
|
|
832
|
+
if (!session?.publicKeyHex || !session?.privateKeyHex) {
|
|
833
|
+
throw new Error("session keypair is required");
|
|
834
|
+
}
|
|
705
835
|
if (!googleIdToken) throw new Error("googleIdToken is required");
|
|
706
|
-
const
|
|
836
|
+
const payloadToSign = {
|
|
707
837
|
token,
|
|
708
|
-
session,
|
|
838
|
+
sessionPubkey: session.publicKeyHex,
|
|
709
839
|
type: "google",
|
|
710
840
|
googleIdToken
|
|
841
|
+
};
|
|
842
|
+
const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
|
|
843
|
+
const response = await this.request("POST", `/auth/login/${appKey}`, {
|
|
844
|
+
...payloadToSign,
|
|
845
|
+
sessionSignature
|
|
711
846
|
});
|
|
712
847
|
const data = await response.json();
|
|
713
848
|
if (!response.ok || !data.success) {
|
|
@@ -755,16 +890,30 @@ async function createTestEnvironment(config = {}) {
|
|
|
755
890
|
wallet,
|
|
756
891
|
serverKeys,
|
|
757
892
|
async signupTestUser(appKey, username, password) {
|
|
758
|
-
const
|
|
893
|
+
const keypair = await generateSigningKeyPair();
|
|
894
|
+
const sessionKeypair = {
|
|
895
|
+
publicKeyHex: keypair.publicKeyHex,
|
|
896
|
+
privateKeyHex: keypair.privateKeyHex
|
|
897
|
+
};
|
|
898
|
+
const sessionUri = `mutable://accounts/${appKey}/sessions/${sessionKeypair.publicKeyHex}`;
|
|
899
|
+
await backend.write(sessionUri, 1);
|
|
900
|
+
const session = await wallet.signupWithToken(appKey, sessionKeypair, { username, password });
|
|
759
901
|
wallet.setSession(session);
|
|
760
902
|
const keys = await wallet.getPublicKeys(appKey);
|
|
761
|
-
return { session, keys };
|
|
903
|
+
return { session, keys, sessionKeypair };
|
|
762
904
|
},
|
|
763
|
-
async loginTestUser(appKey,
|
|
764
|
-
const
|
|
905
|
+
async loginTestUser(appKey, username, password) {
|
|
906
|
+
const keypair = await generateSigningKeyPair();
|
|
907
|
+
const sessionKeypair = {
|
|
908
|
+
publicKeyHex: keypair.publicKeyHex,
|
|
909
|
+
privateKeyHex: keypair.privateKeyHex
|
|
910
|
+
};
|
|
911
|
+
const sessionUri = `mutable://accounts/${appKey}/sessions/${sessionKeypair.publicKeyHex}`;
|
|
912
|
+
await backend.write(sessionUri, 1);
|
|
913
|
+
const session = await wallet.loginWithTokenSession(appKey, sessionKeypair, { username, password });
|
|
765
914
|
wallet.setSession(session);
|
|
766
915
|
const keys = await wallet.getPublicKeys(appKey);
|
|
767
|
-
return { session, keys };
|
|
916
|
+
return { session, keys, sessionKeypair };
|
|
768
917
|
},
|
|
769
918
|
async cleanup() {
|
|
770
919
|
await backend.cleanup();
|
|
@@ -775,6 +924,7 @@ async function createTestEnvironment(config = {}) {
|
|
|
775
924
|
|
|
776
925
|
export {
|
|
777
926
|
WalletClient,
|
|
927
|
+
generateSessionKeypair,
|
|
778
928
|
generateTestServerKeys,
|
|
779
929
|
MemoryWalletClient,
|
|
780
930
|
createTestEnvironment
|