@phantom/embedded-provider-core 0.1.8 → 0.1.10
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/index.d.mts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +186 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -10,6 +10,8 @@ interface Keypair {
|
|
|
10
10
|
interface StamperInfo {
|
|
11
11
|
keyId: string;
|
|
12
12
|
publicKey: string;
|
|
13
|
+
createdAt?: number;
|
|
14
|
+
authenticatorId?: string;
|
|
13
15
|
}
|
|
14
16
|
interface Session {
|
|
15
17
|
sessionId: string;
|
|
@@ -22,6 +24,11 @@ interface Session {
|
|
|
22
24
|
status: "pending" | "completed" | "failed";
|
|
23
25
|
createdAt: number;
|
|
24
26
|
lastUsed: number;
|
|
27
|
+
authenticatorCreatedAt: number;
|
|
28
|
+
authenticatorExpiresAt: number;
|
|
29
|
+
lastRenewalAttempt?: number;
|
|
30
|
+
username: string;
|
|
31
|
+
accountDerivationIndex?: number;
|
|
25
32
|
}
|
|
26
33
|
interface EmbeddedStorage {
|
|
27
34
|
getSession(): Promise<Session | null>;
|
|
@@ -53,6 +60,7 @@ interface AuthResult {
|
|
|
53
60
|
walletId: string;
|
|
54
61
|
provider?: string;
|
|
55
62
|
userInfo?: Record<string, any>;
|
|
63
|
+
accountDerivationIndex?: number;
|
|
56
64
|
}
|
|
57
65
|
interface PhantomConnectOptions {
|
|
58
66
|
organizationId: string;
|
|
@@ -168,6 +176,8 @@ declare class EmbeddedProvider {
|
|
|
168
176
|
private handleJWTAuth;
|
|
169
177
|
private handleRedirectAuth;
|
|
170
178
|
private completeAuthConnection;
|
|
179
|
+
private ensureValidAuthenticator;
|
|
180
|
+
private renewAuthenticator;
|
|
171
181
|
private initializeClientFromSession;
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -179,4 +189,20 @@ declare function generateSessionId(): string;
|
|
|
179
189
|
|
|
180
190
|
declare function retryWithBackoff<T>(operation: () => Promise<T>, operationName: string, logger: DebugLogger, maxRetries?: number, baseDelay?: number): Promise<T>;
|
|
181
191
|
|
|
182
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Constants for authenticator lifecycle management
|
|
194
|
+
*/
|
|
195
|
+
/**
|
|
196
|
+
* How long an authenticator is valid before it expires (in milliseconds)
|
|
197
|
+
* Default: 7 days
|
|
198
|
+
* For testing: Use smaller values like 5 * 60 * 1000 (5 minutes)
|
|
199
|
+
*/
|
|
200
|
+
declare const AUTHENTICATOR_EXPIRATION_TIME_MS: number;
|
|
201
|
+
/**
|
|
202
|
+
* How long before expiration should we attempt to renew the authenticator (in milliseconds)
|
|
203
|
+
* Default: 2 days before expiration
|
|
204
|
+
* For testing: Use smaller values like 2 * 60 * 1000 (2 minutes)
|
|
205
|
+
*/
|
|
206
|
+
declare const AUTHENTICATOR_RENEWAL_WINDOW_MS: number;
|
|
207
|
+
|
|
208
|
+
export { AUTHENTICATOR_EXPIRATION_TIME_MS, AUTHENTICATOR_RENEWAL_WINDOW_MS, AuthOptions, AuthProvider, AuthResult, ConnectResult, DebugLogger, EmbeddedProvider, EmbeddedProviderConfig, EmbeddedProviderEvent, EmbeddedStorage, EventCallback, JWTAuth, JWTAuthOptions, Keypair, PhantomConnectOptions, PlatformAdapter, Session, SignAndSendTransactionParams, SignMessageParams, SignMessageResult, SignedTransaction, StamperInfo, URLParamsAccessor, WalletAddress, generateSessionId, retryWithBackoff };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ interface Keypair {
|
|
|
10
10
|
interface StamperInfo {
|
|
11
11
|
keyId: string;
|
|
12
12
|
publicKey: string;
|
|
13
|
+
createdAt?: number;
|
|
14
|
+
authenticatorId?: string;
|
|
13
15
|
}
|
|
14
16
|
interface Session {
|
|
15
17
|
sessionId: string;
|
|
@@ -22,6 +24,11 @@ interface Session {
|
|
|
22
24
|
status: "pending" | "completed" | "failed";
|
|
23
25
|
createdAt: number;
|
|
24
26
|
lastUsed: number;
|
|
27
|
+
authenticatorCreatedAt: number;
|
|
28
|
+
authenticatorExpiresAt: number;
|
|
29
|
+
lastRenewalAttempt?: number;
|
|
30
|
+
username: string;
|
|
31
|
+
accountDerivationIndex?: number;
|
|
25
32
|
}
|
|
26
33
|
interface EmbeddedStorage {
|
|
27
34
|
getSession(): Promise<Session | null>;
|
|
@@ -53,6 +60,7 @@ interface AuthResult {
|
|
|
53
60
|
walletId: string;
|
|
54
61
|
provider?: string;
|
|
55
62
|
userInfo?: Record<string, any>;
|
|
63
|
+
accountDerivationIndex?: number;
|
|
56
64
|
}
|
|
57
65
|
interface PhantomConnectOptions {
|
|
58
66
|
organizationId: string;
|
|
@@ -168,6 +176,8 @@ declare class EmbeddedProvider {
|
|
|
168
176
|
private handleJWTAuth;
|
|
169
177
|
private handleRedirectAuth;
|
|
170
178
|
private completeAuthConnection;
|
|
179
|
+
private ensureValidAuthenticator;
|
|
180
|
+
private renewAuthenticator;
|
|
171
181
|
private initializeClientFromSession;
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -179,4 +189,20 @@ declare function generateSessionId(): string;
|
|
|
179
189
|
|
|
180
190
|
declare function retryWithBackoff<T>(operation: () => Promise<T>, operationName: string, logger: DebugLogger, maxRetries?: number, baseDelay?: number): Promise<T>;
|
|
181
191
|
|
|
182
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Constants for authenticator lifecycle management
|
|
194
|
+
*/
|
|
195
|
+
/**
|
|
196
|
+
* How long an authenticator is valid before it expires (in milliseconds)
|
|
197
|
+
* Default: 7 days
|
|
198
|
+
* For testing: Use smaller values like 5 * 60 * 1000 (5 minutes)
|
|
199
|
+
*/
|
|
200
|
+
declare const AUTHENTICATOR_EXPIRATION_TIME_MS: number;
|
|
201
|
+
/**
|
|
202
|
+
* How long before expiration should we attempt to renew the authenticator (in milliseconds)
|
|
203
|
+
* Default: 2 days before expiration
|
|
204
|
+
* For testing: Use smaller values like 2 * 60 * 1000 (2 minutes)
|
|
205
|
+
*/
|
|
206
|
+
declare const AUTHENTICATOR_RENEWAL_WINDOW_MS: number;
|
|
207
|
+
|
|
208
|
+
export { AUTHENTICATOR_EXPIRATION_TIME_MS, AUTHENTICATOR_RENEWAL_WINDOW_MS, AuthOptions, AuthProvider, AuthResult, ConnectResult, DebugLogger, EmbeddedProvider, EmbeddedProviderConfig, EmbeddedProviderEvent, EmbeddedStorage, EventCallback, JWTAuth, JWTAuthOptions, Keypair, PhantomConnectOptions, PlatformAdapter, Session, SignAndSendTransactionParams, SignMessageParams, SignMessageResult, SignedTransaction, StamperInfo, URLParamsAccessor, WalletAddress, generateSessionId, retryWithBackoff };
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
AUTHENTICATOR_EXPIRATION_TIME_MS: () => AUTHENTICATOR_EXPIRATION_TIME_MS,
|
|
34
|
+
AUTHENTICATOR_RENEWAL_WINDOW_MS: () => AUTHENTICATOR_RENEWAL_WINDOW_MS,
|
|
33
35
|
EmbeddedProvider: () => EmbeddedProvider,
|
|
34
36
|
JWTAuth: () => JWTAuth,
|
|
35
37
|
generateSessionId: () => generateSessionId,
|
|
@@ -43,6 +45,10 @@ var import_base64url = require("@phantom/base64url");
|
|
|
43
45
|
var import_bs58 = __toESM(require("bs58"));
|
|
44
46
|
var import_parsers = require("@phantom/parsers");
|
|
45
47
|
|
|
48
|
+
// src/constants.ts
|
|
49
|
+
var AUTHENTICATOR_EXPIRATION_TIME_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
50
|
+
var AUTHENTICATOR_RENEWAL_WINDOW_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
51
|
+
|
|
46
52
|
// src/auth/jwt-auth.ts
|
|
47
53
|
var JWTAuth = class {
|
|
48
54
|
async authenticate(options) {
|
|
@@ -209,14 +215,17 @@ var EmbeddedProvider = class {
|
|
|
209
215
|
}
|
|
210
216
|
}
|
|
211
217
|
async getAndFilterWalletAddresses(walletId) {
|
|
218
|
+
const session = await this.storage.getSession();
|
|
219
|
+
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
212
220
|
const addresses = await retryWithBackoff(
|
|
213
|
-
() => this.client.getWalletAddresses(walletId),
|
|
221
|
+
() => this.client.getWalletAddresses(walletId, void 0, derivationIndex),
|
|
214
222
|
"getWalletAddresses",
|
|
215
223
|
this.logger
|
|
216
224
|
).catch(async (error) => {
|
|
217
225
|
this.logger.error("EMBEDDED_PROVIDER", "getWalletAddresses failed after retries, disconnecting", {
|
|
218
226
|
walletId,
|
|
219
|
-
error: error.message
|
|
227
|
+
error: error.message,
|
|
228
|
+
derivationIndex
|
|
220
229
|
});
|
|
221
230
|
await this.storage.clearSession();
|
|
222
231
|
this.client = null;
|
|
@@ -257,6 +266,14 @@ var EmbeddedProvider = class {
|
|
|
257
266
|
return null;
|
|
258
267
|
}
|
|
259
268
|
}
|
|
269
|
+
if (session.status === "completed" && !this.isSessionValid(session)) {
|
|
270
|
+
this.logger.warn("EMBEDDED_PROVIDER", "Session invalid due to authenticator expiration", {
|
|
271
|
+
sessionId: session.sessionId,
|
|
272
|
+
authenticatorExpiresAt: session.authenticatorExpiresAt
|
|
273
|
+
});
|
|
274
|
+
await this.storage.clearSession();
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
260
277
|
return session;
|
|
261
278
|
}
|
|
262
279
|
/*
|
|
@@ -291,6 +308,7 @@ var EmbeddedProvider = class {
|
|
|
291
308
|
walletId: this.walletId,
|
|
292
309
|
addressCount: this.addresses.length
|
|
293
310
|
});
|
|
311
|
+
await this.ensureValidAuthenticator();
|
|
294
312
|
const result = {
|
|
295
313
|
walletId: this.walletId,
|
|
296
314
|
addresses: this.addresses,
|
|
@@ -320,8 +338,9 @@ var EmbeddedProvider = class {
|
|
|
320
338
|
}
|
|
321
339
|
}
|
|
322
340
|
/*
|
|
323
|
-
* We use this method to validate if a session is still valid
|
|
324
|
-
* This checks session status,
|
|
341
|
+
* We use this method to validate if a session is still valid.
|
|
342
|
+
* This checks session status, required fields, and authenticator expiration.
|
|
343
|
+
* Sessions never expire by age - only authenticators expire.
|
|
325
344
|
*/
|
|
326
345
|
isSessionValid(session) {
|
|
327
346
|
if (!session) {
|
|
@@ -339,20 +358,23 @@ var EmbeddedProvider = class {
|
|
|
339
358
|
this.logger.log("EMBEDDED_PROVIDER", "Session not completed", { status: session.status });
|
|
340
359
|
return false;
|
|
341
360
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
361
|
+
if (!session.authenticatorExpiresAt) {
|
|
362
|
+
this.logger.log("EMBEDDED_PROVIDER", "Session invalid - missing authenticator timing", {
|
|
363
|
+
sessionId: session.sessionId
|
|
364
|
+
});
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
if (Date.now() >= session.authenticatorExpiresAt) {
|
|
368
|
+
this.logger.log("EMBEDDED_PROVIDER", "Authenticator expired, session invalid", {
|
|
369
|
+
authenticatorExpiresAt: new Date(session.authenticatorExpiresAt).toISOString(),
|
|
370
|
+
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
349
371
|
});
|
|
350
372
|
return false;
|
|
351
373
|
}
|
|
352
374
|
this.logger.log("EMBEDDED_PROVIDER", "Session is valid", {
|
|
353
375
|
sessionId: session.sessionId,
|
|
354
376
|
walletId: session.walletId,
|
|
355
|
-
|
|
377
|
+
authenticatorExpires: new Date(session.authenticatorExpiresAt).toISOString()
|
|
356
378
|
});
|
|
357
379
|
return true;
|
|
358
380
|
}
|
|
@@ -421,9 +443,11 @@ var EmbeddedProvider = class {
|
|
|
421
443
|
platform: platformName
|
|
422
444
|
});
|
|
423
445
|
const base64urlPublicKey = (0, import_base64url.base64urlEncode)(import_bs58.default.decode(stamperInfo.publicKey));
|
|
446
|
+
const expiresAtMs = Date.now() + AUTHENTICATOR_EXPIRATION_TIME_MS;
|
|
447
|
+
const username = `user-${shortPubKey}`;
|
|
424
448
|
const { organizationId } = await tempClient.createOrganization(organizationName, [
|
|
425
449
|
{
|
|
426
|
-
username
|
|
450
|
+
username,
|
|
427
451
|
role: "ADMIN",
|
|
428
452
|
authenticators: [
|
|
429
453
|
{
|
|
@@ -431,12 +455,14 @@ var EmbeddedProvider = class {
|
|
|
431
455
|
authenticatorKind: "keypair",
|
|
432
456
|
publicKey: base64urlPublicKey,
|
|
433
457
|
algorithm: "Ed25519"
|
|
458
|
+
// Commented for now until KMS supports fully expirable organizations
|
|
459
|
+
// expiresAtMs: expiresAtMs,
|
|
434
460
|
}
|
|
435
461
|
]
|
|
436
462
|
}
|
|
437
463
|
]);
|
|
438
464
|
this.logger.info("EMBEDDED_PROVIDER", "Organization created", { organizationId });
|
|
439
|
-
return { organizationId, stamperInfo };
|
|
465
|
+
return { organizationId, stamperInfo, expiresAtMs, username };
|
|
440
466
|
}
|
|
441
467
|
async connect(authOptions) {
|
|
442
468
|
try {
|
|
@@ -465,8 +491,8 @@ var EmbeddedProvider = class {
|
|
|
465
491
|
}
|
|
466
492
|
this.validateAuthOptions(authOptions);
|
|
467
493
|
this.logger.info("EMBEDDED_PROVIDER", "No existing connection, creating new auth flow");
|
|
468
|
-
const { organizationId, stamperInfo } = await this.createOrganizationAndStamper();
|
|
469
|
-
const session = await this.handleAuthFlow(organizationId, stamperInfo, authOptions);
|
|
494
|
+
const { organizationId, stamperInfo, expiresAtMs, username } = await this.createOrganizationAndStamper();
|
|
495
|
+
const session = await this.handleAuthFlow(organizationId, stamperInfo, authOptions, expiresAtMs, username);
|
|
470
496
|
if (!session) {
|
|
471
497
|
return {
|
|
472
498
|
addresses: [],
|
|
@@ -478,6 +504,7 @@ var EmbeddedProvider = class {
|
|
|
478
504
|
await this.storage.saveSession(session);
|
|
479
505
|
}
|
|
480
506
|
await this.initializeClientFromSession(session);
|
|
507
|
+
await this.ensureValidAuthenticator();
|
|
481
508
|
const result = {
|
|
482
509
|
walletId: this.walletId,
|
|
483
510
|
addresses: this.addresses,
|
|
@@ -543,15 +570,19 @@ var EmbeddedProvider = class {
|
|
|
543
570
|
if (!this.client || !this.walletId) {
|
|
544
571
|
throw new Error("Not connected");
|
|
545
572
|
}
|
|
573
|
+
await this.ensureValidAuthenticator();
|
|
546
574
|
this.logger.info("EMBEDDED_PROVIDER", "Signing message", {
|
|
547
575
|
walletId: this.walletId,
|
|
548
576
|
message: params.message
|
|
549
577
|
});
|
|
550
578
|
const parsedMessage = (0, import_parsers.parseMessage)(params.message);
|
|
579
|
+
const session = await this.storage.getSession();
|
|
580
|
+
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
551
581
|
const rawResponse = await this.client.signMessage({
|
|
552
582
|
walletId: this.walletId,
|
|
553
583
|
message: parsedMessage.base64url,
|
|
554
|
-
networkId: params.networkId
|
|
584
|
+
networkId: params.networkId,
|
|
585
|
+
derivationIndex
|
|
555
586
|
});
|
|
556
587
|
this.logger.info("EMBEDDED_PROVIDER", "Message signed successfully", {
|
|
557
588
|
walletId: this.walletId,
|
|
@@ -563,19 +594,24 @@ var EmbeddedProvider = class {
|
|
|
563
594
|
if (!this.client || !this.walletId) {
|
|
564
595
|
throw new Error("Not connected");
|
|
565
596
|
}
|
|
597
|
+
await this.ensureValidAuthenticator();
|
|
566
598
|
this.logger.info("EMBEDDED_PROVIDER", "Signing and sending transaction", {
|
|
567
599
|
walletId: this.walletId,
|
|
568
600
|
networkId: params.networkId
|
|
569
601
|
});
|
|
570
602
|
const parsedTransaction = await (0, import_parsers.parseTransaction)(params.transaction, params.networkId);
|
|
603
|
+
const session = await this.storage.getSession();
|
|
604
|
+
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
571
605
|
this.logger.log("EMBEDDED_PROVIDER", "Parsed transaction for signing", {
|
|
572
606
|
walletId: this.walletId,
|
|
573
|
-
transaction: parsedTransaction
|
|
607
|
+
transaction: parsedTransaction,
|
|
608
|
+
derivationIndex
|
|
574
609
|
});
|
|
575
610
|
const rawResponse = await this.client.signAndSendTransaction({
|
|
576
611
|
walletId: this.walletId,
|
|
577
612
|
transaction: parsedTransaction.base64url,
|
|
578
|
-
networkId: params.networkId
|
|
613
|
+
networkId: params.networkId,
|
|
614
|
+
derivationIndex
|
|
579
615
|
});
|
|
580
616
|
this.logger.info("EMBEDDED_PROVIDER", "Transaction signed and sent successfully", {
|
|
581
617
|
walletId: this.walletId,
|
|
@@ -596,20 +632,20 @@ var EmbeddedProvider = class {
|
|
|
596
632
|
* It handles app-wallet creation directly or routes to JWT/redirect authentication for user-wallets.
|
|
597
633
|
* Returns null for redirect flows since they don't complete synchronously.
|
|
598
634
|
*/
|
|
599
|
-
async handleAuthFlow(organizationId, stamperInfo, authOptions) {
|
|
635
|
+
async handleAuthFlow(organizationId, stamperInfo, authOptions, expiresAtMs, username) {
|
|
600
636
|
if (this.config.embeddedWalletType === "user-wallet") {
|
|
601
637
|
this.logger.info("EMBEDDED_PROVIDER", "Creating user-wallet, routing authentication", {
|
|
602
638
|
authProvider: authOptions?.provider || "phantom-connect"
|
|
603
639
|
});
|
|
604
640
|
if (authOptions?.provider === "jwt") {
|
|
605
|
-
return await this.handleJWTAuth(organizationId, stamperInfo, authOptions);
|
|
641
|
+
return await this.handleJWTAuth(organizationId, stamperInfo, authOptions, expiresAtMs, username);
|
|
606
642
|
} else {
|
|
607
643
|
this.logger.info("EMBEDDED_PROVIDER", "Starting redirect-based authentication flow", {
|
|
608
644
|
organizationId,
|
|
609
645
|
parentOrganizationId: this.config.organizationId,
|
|
610
646
|
provider: authOptions?.provider
|
|
611
647
|
});
|
|
612
|
-
return await this.handleRedirectAuth(organizationId, stamperInfo, authOptions);
|
|
648
|
+
return await this.handleRedirectAuth(organizationId, stamperInfo, authOptions, username);
|
|
613
649
|
}
|
|
614
650
|
} else {
|
|
615
651
|
this.logger.info("EMBEDDED_PROVIDER", "Creating app-wallet", {
|
|
@@ -632,9 +668,15 @@ var EmbeddedProvider = class {
|
|
|
632
668
|
stamperInfo,
|
|
633
669
|
authProvider: "app-wallet",
|
|
634
670
|
userInfo: { embeddedWalletType: this.config.embeddedWalletType },
|
|
671
|
+
accountDerivationIndex: 0,
|
|
672
|
+
// App wallets default to index 0
|
|
635
673
|
status: "completed",
|
|
636
674
|
createdAt: now,
|
|
637
|
-
lastUsed: now
|
|
675
|
+
lastUsed: now,
|
|
676
|
+
authenticatorCreatedAt: now,
|
|
677
|
+
authenticatorExpiresAt: expiresAtMs,
|
|
678
|
+
lastRenewalAttempt: void 0,
|
|
679
|
+
username
|
|
638
680
|
};
|
|
639
681
|
await this.storage.saveSession(session);
|
|
640
682
|
this.logger.info("EMBEDDED_PROVIDER", "App-wallet created successfully", { walletId, organizationId });
|
|
@@ -645,7 +687,7 @@ var EmbeddedProvider = class {
|
|
|
645
687
|
* We use this method to handle JWT-based authentication for user-wallets.
|
|
646
688
|
* It authenticates using the provided JWT token and creates a completed session.
|
|
647
689
|
*/
|
|
648
|
-
async handleJWTAuth(organizationId, stamperInfo, authOptions) {
|
|
690
|
+
async handleJWTAuth(organizationId, stamperInfo, authOptions, expiresAtMs, username) {
|
|
649
691
|
this.logger.info("EMBEDDED_PROVIDER", "Using JWT authentication flow");
|
|
650
692
|
if (!authOptions.jwtToken) {
|
|
651
693
|
this.logger.error("EMBEDDED_PROVIDER", "JWT token missing for JWT authentication");
|
|
@@ -668,9 +710,14 @@ var EmbeddedProvider = class {
|
|
|
668
710
|
stamperInfo,
|
|
669
711
|
authProvider: authResult.provider,
|
|
670
712
|
userInfo: authResult.userInfo,
|
|
713
|
+
accountDerivationIndex: authResult.accountDerivationIndex,
|
|
671
714
|
status: "completed",
|
|
672
715
|
createdAt: now,
|
|
673
|
-
lastUsed: now
|
|
716
|
+
lastUsed: now,
|
|
717
|
+
authenticatorCreatedAt: now,
|
|
718
|
+
authenticatorExpiresAt: expiresAtMs,
|
|
719
|
+
lastRenewalAttempt: void 0,
|
|
720
|
+
username
|
|
674
721
|
};
|
|
675
722
|
this.logger.log("EMBEDDED_PROVIDER", "Saving JWT session");
|
|
676
723
|
await this.storage.saveSession(session);
|
|
@@ -681,7 +728,7 @@ var EmbeddedProvider = class {
|
|
|
681
728
|
* It saves a temporary session before redirecting to prevent losing state during the redirect flow.
|
|
682
729
|
* Session timestamp is updated before redirect to prevent race conditions.
|
|
683
730
|
*/
|
|
684
|
-
async handleRedirectAuth(organizationId, stamperInfo, authOptions) {
|
|
731
|
+
async handleRedirectAuth(organizationId, stamperInfo, authOptions, username) {
|
|
685
732
|
this.logger.info("EMBEDDED_PROVIDER", "Using Phantom Connect authentication flow (redirect-based)", {
|
|
686
733
|
provider: authOptions?.provider,
|
|
687
734
|
hasRedirectUrl: !!this.config.authOptions?.redirectUrl,
|
|
@@ -697,9 +744,15 @@ var EmbeddedProvider = class {
|
|
|
697
744
|
stamperInfo,
|
|
698
745
|
authProvider: "phantom-connect",
|
|
699
746
|
userInfo: { provider: authOptions?.provider },
|
|
747
|
+
accountDerivationIndex: void 0,
|
|
748
|
+
// Will be set when redirect completes
|
|
700
749
|
status: "pending",
|
|
701
750
|
createdAt: now,
|
|
702
|
-
lastUsed: now
|
|
751
|
+
lastUsed: now,
|
|
752
|
+
authenticatorCreatedAt: now,
|
|
753
|
+
authenticatorExpiresAt: now + AUTHENTICATOR_EXPIRATION_TIME_MS,
|
|
754
|
+
lastRenewalAttempt: void 0,
|
|
755
|
+
username: username || `user-${stamperInfo.keyId.substring(0, 8)}`
|
|
703
756
|
};
|
|
704
757
|
this.logger.log("EMBEDDED_PROVIDER", "Saving temporary session before redirect", {
|
|
705
758
|
sessionId: tempSession.sessionId,
|
|
@@ -731,6 +784,7 @@ var EmbeddedProvider = class {
|
|
|
731
784
|
});
|
|
732
785
|
tempSession.walletId = authResult.walletId;
|
|
733
786
|
tempSession.authProvider = authResult.provider || tempSession.authProvider;
|
|
787
|
+
tempSession.accountDerivationIndex = authResult.accountDerivationIndex;
|
|
734
788
|
tempSession.status = "completed";
|
|
735
789
|
tempSession.lastUsed = Date.now();
|
|
736
790
|
await this.storage.saveSession(tempSession);
|
|
@@ -746,16 +800,119 @@ var EmbeddedProvider = class {
|
|
|
746
800
|
}
|
|
747
801
|
session.walletId = authResult.walletId;
|
|
748
802
|
session.authProvider = authResult.provider || session.authProvider;
|
|
803
|
+
session.accountDerivationIndex = authResult.accountDerivationIndex;
|
|
749
804
|
session.status = "completed";
|
|
750
805
|
session.lastUsed = Date.now();
|
|
751
806
|
await this.storage.saveSession(session);
|
|
752
807
|
await this.initializeClientFromSession(session);
|
|
808
|
+
await this.ensureValidAuthenticator();
|
|
753
809
|
return {
|
|
754
810
|
walletId: this.walletId,
|
|
755
811
|
addresses: this.addresses,
|
|
756
812
|
status: "completed"
|
|
757
813
|
};
|
|
758
814
|
}
|
|
815
|
+
/*
|
|
816
|
+
* Ensures the authenticator is valid and performs renewal if needed.
|
|
817
|
+
* The renewal of the authenticator can only happen meanwhile the previous authenticator is still valid.
|
|
818
|
+
*/
|
|
819
|
+
async ensureValidAuthenticator() {
|
|
820
|
+
const session = await this.storage.getSession();
|
|
821
|
+
if (!session) {
|
|
822
|
+
throw new Error("No active session found");
|
|
823
|
+
}
|
|
824
|
+
const now = Date.now();
|
|
825
|
+
if (!session.authenticatorExpiresAt) {
|
|
826
|
+
this.logger.warn("EMBEDDED_PROVIDER", "Session missing authenticator timing - treating as invalid session");
|
|
827
|
+
await this.disconnect();
|
|
828
|
+
throw new Error("Invalid session - missing authenticator timing");
|
|
829
|
+
}
|
|
830
|
+
const timeUntilExpiry = session.authenticatorExpiresAt - now;
|
|
831
|
+
this.logger.log("EMBEDDED_PROVIDER", "Checking authenticator expiration", {
|
|
832
|
+
expiresAt: new Date(session.authenticatorExpiresAt).toISOString(),
|
|
833
|
+
timeUntilExpiry
|
|
834
|
+
});
|
|
835
|
+
if (timeUntilExpiry <= 0) {
|
|
836
|
+
this.logger.error("EMBEDDED_PROVIDER", "Authenticator has expired, disconnecting");
|
|
837
|
+
await this.disconnect();
|
|
838
|
+
throw new Error("Authenticator expired");
|
|
839
|
+
}
|
|
840
|
+
const renewalWindow = AUTHENTICATOR_RENEWAL_WINDOW_MS;
|
|
841
|
+
if (timeUntilExpiry <= renewalWindow) {
|
|
842
|
+
this.logger.info("EMBEDDED_PROVIDER", "Authenticator needs renewal", {
|
|
843
|
+
expiresAt: new Date(session.authenticatorExpiresAt).toISOString(),
|
|
844
|
+
timeUntilExpiry,
|
|
845
|
+
renewalWindow
|
|
846
|
+
});
|
|
847
|
+
try {
|
|
848
|
+
await this.renewAuthenticator(session);
|
|
849
|
+
this.logger.info("EMBEDDED_PROVIDER", "Authenticator renewed successfully");
|
|
850
|
+
} catch (error) {
|
|
851
|
+
this.logger.error("EMBEDDED_PROVIDER", "Failed to renew authenticator", {
|
|
852
|
+
error: error instanceof Error ? error.message : String(error)
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
/*
|
|
858
|
+
* We use this method to perform silent authenticator renewal.
|
|
859
|
+
* It generates a new keypair, creates a new authenticator, and switches to it.
|
|
860
|
+
*/
|
|
861
|
+
async renewAuthenticator(session) {
|
|
862
|
+
if (!this.client) {
|
|
863
|
+
throw new Error("Client not initialized");
|
|
864
|
+
}
|
|
865
|
+
this.logger.info("EMBEDDED_PROVIDER", "Starting authenticator renewal");
|
|
866
|
+
try {
|
|
867
|
+
const newKeyInfo = await this.stamper.rotateKeyPair();
|
|
868
|
+
this.logger.log("EMBEDDED_PROVIDER", "Generated new keypair for renewal", {
|
|
869
|
+
newKeyId: newKeyInfo.keyId,
|
|
870
|
+
newPublicKey: newKeyInfo.publicKey
|
|
871
|
+
});
|
|
872
|
+
const base64urlPublicKey = (0, import_base64url.base64urlEncode)(import_bs58.default.decode(newKeyInfo.publicKey));
|
|
873
|
+
const expiresAtMs = Date.now() + AUTHENTICATOR_EXPIRATION_TIME_MS;
|
|
874
|
+
let authenticatorResult;
|
|
875
|
+
try {
|
|
876
|
+
authenticatorResult = await this.client.createAuthenticator({
|
|
877
|
+
organizationId: session.organizationId,
|
|
878
|
+
username: session.username,
|
|
879
|
+
authenticatorName: `auth-${newKeyInfo.keyId.substring(0, 8)}`,
|
|
880
|
+
authenticator: {
|
|
881
|
+
authenticatorName: `auth-${newKeyInfo.keyId.substring(0, 8)}`,
|
|
882
|
+
authenticatorKind: "keypair",
|
|
883
|
+
publicKey: base64urlPublicKey,
|
|
884
|
+
algorithm: "Ed25519"
|
|
885
|
+
// Commented for now until KMS supports fully expiring organizations
|
|
886
|
+
// expiresAtMs: expiresAtMs,
|
|
887
|
+
},
|
|
888
|
+
replaceExpirable: true
|
|
889
|
+
});
|
|
890
|
+
} catch (error) {
|
|
891
|
+
this.logger.error("EMBEDDED_PROVIDER", "Failed to create new authenticator", {
|
|
892
|
+
error: error instanceof Error ? error.message : String(error)
|
|
893
|
+
});
|
|
894
|
+
await this.stamper.rollbackRotation();
|
|
895
|
+
throw new Error(`Failed to create new authenticator: ${error instanceof Error ? error.message : String(error)}`);
|
|
896
|
+
}
|
|
897
|
+
this.logger.info("EMBEDDED_PROVIDER", "Created new authenticator", {
|
|
898
|
+
authenticatorId: authenticatorResult.id
|
|
899
|
+
});
|
|
900
|
+
await this.stamper.commitRotation(authenticatorResult.id || "unknown");
|
|
901
|
+
const now = Date.now();
|
|
902
|
+
session.stamperInfo = newKeyInfo;
|
|
903
|
+
session.authenticatorCreatedAt = now;
|
|
904
|
+
session.authenticatorExpiresAt = expiresAtMs;
|
|
905
|
+
session.lastRenewalAttempt = now;
|
|
906
|
+
await this.storage.saveSession(session);
|
|
907
|
+
this.logger.info("EMBEDDED_PROVIDER", "Authenticator renewal completed successfully", {
|
|
908
|
+
newKeyId: newKeyInfo.keyId,
|
|
909
|
+
expiresAt: new Date(expiresAtMs).toISOString()
|
|
910
|
+
});
|
|
911
|
+
} catch (error) {
|
|
912
|
+
await this.stamper.rollbackRotation();
|
|
913
|
+
throw error;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
759
916
|
/*
|
|
760
917
|
* We use this method to initialize the PhantomClient and fetch wallet addresses from a completed session.
|
|
761
918
|
* This is the final step that sets up the provider's client state and retrieves available addresses.
|
|
@@ -781,6 +938,8 @@ var EmbeddedProvider = class {
|
|
|
781
938
|
};
|
|
782
939
|
// Annotate the CommonJS export names for ESM import in node:
|
|
783
940
|
0 && (module.exports = {
|
|
941
|
+
AUTHENTICATOR_EXPIRATION_TIME_MS,
|
|
942
|
+
AUTHENTICATOR_RENEWAL_WINDOW_MS,
|
|
784
943
|
EmbeddedProvider,
|
|
785
944
|
JWTAuth,
|
|
786
945
|
generateSessionId,
|