@phantom/embedded-provider-core 1.0.0-beta.10 → 1.0.0-beta.11

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 CHANGED
@@ -29,7 +29,7 @@ interface Session {
29
29
  authenticatorCreatedAt: number;
30
30
  authenticatorExpiresAt: number;
31
31
  lastRenewalAttempt?: number;
32
- username: string;
32
+ username?: string;
33
33
  accountDerivationIndex?: number;
34
34
  }
35
35
  interface EmbeddedStorage {
@@ -60,12 +60,14 @@ interface URLParamsAccessor {
60
60
 
61
61
  interface AuthResult {
62
62
  walletId: string;
63
+ organizationId: string;
63
64
  provider?: string;
64
65
  userInfo?: Record<string, any>;
65
- accountDerivationIndex?: number;
66
+ accountDerivationIndex: number;
67
+ expiresInMs: number;
66
68
  }
67
69
  interface PhantomConnectOptions {
68
- organizationId: string;
70
+ publicKey: string;
69
71
  appId: string;
70
72
  provider?: "google" | "apple";
71
73
  redirectUrl?: string;
@@ -75,7 +77,7 @@ interface PhantomConnectOptions {
75
77
  }
76
78
  interface JWTAuthOptions {
77
79
  appId: string;
78
- organizationId: string;
80
+ publicKey: string;
79
81
  jwtToken: string;
80
82
  customAuthData?: Record<string, any>;
81
83
  }
@@ -171,7 +173,8 @@ declare class EmbeddedProvider {
171
173
  private validateAuthOptions;
172
174
  private isSessionValid;
173
175
  autoConnect(): Promise<void>;
174
- private createOrganizationAndStamper;
176
+ private initializeStamper;
177
+ private createOrganizationForAppWallet;
175
178
  connect(authOptions?: AuthOptions): Promise<ConnectResult>;
176
179
  disconnect(): Promise<void>;
177
180
  signMessage(params: SignMessageParams): Promise<ParsedSignatureResult>;
package/dist/index.d.ts CHANGED
@@ -29,7 +29,7 @@ interface Session {
29
29
  authenticatorCreatedAt: number;
30
30
  authenticatorExpiresAt: number;
31
31
  lastRenewalAttempt?: number;
32
- username: string;
32
+ username?: string;
33
33
  accountDerivationIndex?: number;
34
34
  }
35
35
  interface EmbeddedStorage {
@@ -60,12 +60,14 @@ interface URLParamsAccessor {
60
60
 
61
61
  interface AuthResult {
62
62
  walletId: string;
63
+ organizationId: string;
63
64
  provider?: string;
64
65
  userInfo?: Record<string, any>;
65
- accountDerivationIndex?: number;
66
+ accountDerivationIndex: number;
67
+ expiresInMs: number;
66
68
  }
67
69
  interface PhantomConnectOptions {
68
- organizationId: string;
70
+ publicKey: string;
69
71
  appId: string;
70
72
  provider?: "google" | "apple";
71
73
  redirectUrl?: string;
@@ -75,7 +77,7 @@ interface PhantomConnectOptions {
75
77
  }
76
78
  interface JWTAuthOptions {
77
79
  appId: string;
78
- organizationId: string;
80
+ publicKey: string;
79
81
  jwtToken: string;
80
82
  customAuthData?: Record<string, any>;
81
83
  }
@@ -171,7 +173,8 @@ declare class EmbeddedProvider {
171
173
  private validateAuthOptions;
172
174
  private isSessionValid;
173
175
  autoConnect(): Promise<void>;
174
- private createOrganizationAndStamper;
176
+ private initializeStamper;
177
+ private createOrganizationForAppWallet;
175
178
  connect(authOptions?: AuthOptions): Promise<ConnectResult>;
176
179
  disconnect(): Promise<void>;
177
180
  signMessage(params: SignMessageParams): Promise<ParsedSignatureResult>;
package/dist/index.js CHANGED
@@ -72,6 +72,7 @@ var JWTAuth = class {
72
72
  },
73
73
  body: JSON.stringify({
74
74
  appId: options.appId,
75
+ publicKey: options.publicKey,
75
76
  customAuthData: options.customAuthData
76
77
  })
77
78
  });
@@ -112,10 +113,19 @@ var JWTAuth = class {
112
113
  if (!result.walletId) {
113
114
  throw new Error("Invalid JWT authentication response: missing walletId");
114
115
  }
116
+ if (!result.organizationId) {
117
+ throw new Error("Invalid JWT authentication response: missing organizationId");
118
+ }
119
+ if (!result.expiresInMs) {
120
+ throw new Error("Invalid JWT authentication response: missing expiresInMs");
121
+ }
115
122
  return {
116
123
  walletId: result.walletId,
124
+ organizationId: result.organizationId,
117
125
  provider: "jwt",
118
- userInfo: result.userInfo || {}
126
+ userInfo: result.userInfo || {},
127
+ expiresInMs: result.expiresInMs,
128
+ accountDerivationIndex: result.accountDerivationIndex || 0
119
129
  };
120
130
  } catch (error) {
121
131
  if (error instanceof TypeError && error.message.includes("fetch")) {
@@ -794,34 +804,39 @@ var EmbeddedProvider = class {
794
804
  * We use this method to initialize the stamper and create an organization for new sessions.
795
805
  * This is the first step when no existing session is found and we need to set up a new wallet.
796
806
  */
797
- async createOrganizationAndStamper() {
807
+ async initializeStamper() {
798
808
  this.logger.log("EMBEDDED_PROVIDER", "Initializing stamper");
799
- const stamperInfo = await this.stamper.init();
800
- this.logger.log("EMBEDDED_PROVIDER", "Stamper initialized", {
809
+ await this.stamper.init();
810
+ this.logger.log("EMBEDDED_PROVIDER", "Resetting keypair to avoid conflicts with existing keypairs");
811
+ const stamperInfo = await this.stamper.resetKeyPair();
812
+ this.logger.log("EMBEDDED_PROVIDER", "Stamper initialized with fresh keypair", {
801
813
  publicKey: stamperInfo.publicKey,
802
814
  keyId: stamperInfo.keyId,
803
815
  algorithm: this.stamper.algorithm
804
816
  });
805
- this.logger.log("EMBEDDED_PROVIDER", "Creating temporary PhantomClient");
806
- const tempClient = new import_client.PhantomClient(
807
- {
808
- apiBaseUrl: this.config.apiBaseUrl,
809
- headers: {
810
- ...this.platform.analyticsHeaders || {}
811
- }
812
- },
813
- this.stamper
814
- );
817
+ const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
818
+ this.logger.info("EMBEDDED_PROVIDER", "Stamper ready for auth flow with fresh keypair", {
819
+ publicKey: stamperInfo.publicKey,
820
+ keyId: stamperInfo.keyId
821
+ });
822
+ return { stamperInfo, expiresInMs };
823
+ }
824
+ async createOrganizationForAppWallet(stamperInfo, expiresInMs) {
825
+ const tempClient = new import_client.PhantomClient({
826
+ apiBaseUrl: this.config.apiBaseUrl,
827
+ headers: {
828
+ ...this.platform.analyticsHeaders || {}
829
+ }
830
+ });
815
831
  const platformName = this.platform.name || "unknown";
816
832
  const shortPubKey = stamperInfo.publicKey.slice(0, 8);
817
833
  const organizationName = `${this.config.appId.substring(0, 8)}-${platformName}-${shortPubKey}`;
818
- this.logger.log("EMBEDDED_PROVIDER", "Creating organization", {
834
+ this.logger.log("EMBEDDED_PROVIDER", "Creating organization for app-wallet", {
819
835
  organizationName,
820
836
  publicKey: stamperInfo.publicKey,
821
837
  platform: platformName
822
838
  });
823
839
  const base64urlPublicKey = (0, import_base64url2.base64urlEncode)(import_bs582.default.decode(stamperInfo.publicKey));
824
- const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
825
840
  const username = `user-${(0, import_utils.randomUUID)()}`;
826
841
  const { organizationId } = await tempClient.createOrganization(organizationName, [
827
842
  {
@@ -832,15 +847,14 @@ var EmbeddedProvider = class {
832
847
  authenticatorName: `auth-${shortPubKey}`,
833
848
  authenticatorKind: "keypair",
834
849
  publicKey: base64urlPublicKey,
835
- algorithm: "Ed25519"
836
- // Commented for now until KMS supports fully expirable organizations
837
- // expiresInMs: expiresInMs,
850
+ algorithm: "Ed25519",
851
+ expiresInMs
838
852
  }
839
853
  ]
840
854
  }
841
855
  ]);
842
- this.logger.info("EMBEDDED_PROVIDER", "Organization created", { organizationId });
843
- return { organizationId, stamperInfo, expiresInMs, username };
856
+ this.logger.info("EMBEDDED_PROVIDER", "Organization created for app-wallet", { organizationId });
857
+ return organizationId;
844
858
  }
845
859
  async connect(authOptions) {
846
860
  try {
@@ -869,8 +883,8 @@ var EmbeddedProvider = class {
869
883
  }
870
884
  this.validateAuthOptions(authOptions);
871
885
  this.logger.info("EMBEDDED_PROVIDER", "No existing connection, creating new auth flow");
872
- const { organizationId, stamperInfo, expiresInMs, username } = await this.createOrganizationAndStamper();
873
- const session = await this.handleAuthFlow(organizationId, stamperInfo, authOptions, expiresInMs, username);
886
+ const { stamperInfo, expiresInMs } = await this.initializeStamper();
887
+ const session = await this.handleAuthFlow(stamperInfo.publicKey, stamperInfo, authOptions, expiresInMs);
874
888
  if (!session) {
875
889
  return {
876
890
  addresses: [],
@@ -1042,24 +1056,25 @@ var EmbeddedProvider = class {
1042
1056
  * It handles app-wallet creation directly or routes to JWT/redirect authentication for user-wallets.
1043
1057
  * Returns null for redirect flows since they don't complete synchronously.
1044
1058
  */
1045
- async handleAuthFlow(organizationId, stamperInfo, authOptions, expiresInMs, username) {
1059
+ async handleAuthFlow(publicKey, stamperInfo, authOptions, expiresInMs) {
1046
1060
  if (this.config.embeddedWalletType === "user-wallet") {
1047
1061
  this.logger.info("EMBEDDED_PROVIDER", "Creating user-wallet, routing authentication", {
1048
1062
  authProvider: authOptions?.provider || "phantom-connect"
1049
1063
  });
1050
1064
  if (authOptions?.provider === "jwt") {
1051
- return await this.handleJWTAuth(organizationId, stamperInfo, authOptions, expiresInMs, username);
1065
+ return await this.handleJWTAuth(publicKey, stamperInfo, authOptions, expiresInMs);
1052
1066
  } else {
1053
1067
  this.logger.info("EMBEDDED_PROVIDER", "Starting redirect-based authentication flow", {
1054
- organizationId,
1068
+ publicKey,
1055
1069
  provider: authOptions?.provider
1056
1070
  });
1057
- return await this.handleRedirectAuth(organizationId, stamperInfo, authOptions, username);
1071
+ return await this.handleRedirectAuth(publicKey, stamperInfo, authOptions);
1058
1072
  }
1059
1073
  } else {
1060
1074
  this.logger.info("EMBEDDED_PROVIDER", "Creating app-wallet", {
1061
- organizationId
1075
+ publicKey
1062
1076
  });
1077
+ const organizationId = await this.createOrganizationForAppWallet(stamperInfo, expiresInMs);
1063
1078
  const tempClient = new import_client.PhantomClient(
1064
1079
  {
1065
1080
  apiBaseUrl: this.config.apiBaseUrl,
@@ -1088,8 +1103,7 @@ var EmbeddedProvider = class {
1088
1103
  lastUsed: now,
1089
1104
  authenticatorCreatedAt: now,
1090
1105
  authenticatorExpiresAt: Date.now() + expiresInMs,
1091
- lastRenewalAttempt: void 0,
1092
- username
1106
+ lastRenewalAttempt: void 0
1093
1107
  };
1094
1108
  await this.storage.saveSession(session);
1095
1109
  this.logger.info("EMBEDDED_PROVIDER", "App-wallet created successfully", { walletId, organizationId });
@@ -1100,7 +1114,7 @@ var EmbeddedProvider = class {
1100
1114
  * We use this method to handle JWT-based authentication for user-wallets.
1101
1115
  * It authenticates using the provided JWT token and creates a completed session.
1102
1116
  */
1103
- async handleJWTAuth(organizationId, stamperInfo, authOptions, expiresInMs, username) {
1117
+ async handleJWTAuth(publicKey, stamperInfo, authOptions, localExpiresInMs) {
1104
1118
  this.logger.info("EMBEDDED_PROVIDER", "Using JWT authentication flow");
1105
1119
  if (!authOptions.jwtToken) {
1106
1120
  this.logger.error("EMBEDDED_PROVIDER", "JWT token missing for JWT authentication");
@@ -1108,13 +1122,20 @@ var EmbeddedProvider = class {
1108
1122
  }
1109
1123
  this.logger.log("EMBEDDED_PROVIDER", "Starting JWT authentication");
1110
1124
  const authResult = await this.jwtAuth.authenticate({
1111
- organizationId,
1125
+ publicKey,
1112
1126
  appId: this.config.appId,
1113
1127
  jwtToken: authOptions.jwtToken,
1114
1128
  customAuthData: authOptions.customAuthData
1115
1129
  });
1116
1130
  const walletId = authResult.walletId;
1117
- this.logger.info("EMBEDDED_PROVIDER", "JWT authentication completed", { walletId });
1131
+ const organizationId = authResult.organizationId;
1132
+ const expiresInMs = authResult.expiresInMs > 0 ? authResult.expiresInMs : localExpiresInMs;
1133
+ this.logger.info("EMBEDDED_PROVIDER", "JWT authentication completed", {
1134
+ walletId,
1135
+ organizationId,
1136
+ expiresInMs,
1137
+ source: authResult.expiresInMs ? "server" : "local"
1138
+ });
1118
1139
  const now = Date.now();
1119
1140
  const session = {
1120
1141
  sessionId: generateSessionId(),
@@ -1130,8 +1151,7 @@ var EmbeddedProvider = class {
1130
1151
  lastUsed: now,
1131
1152
  authenticatorCreatedAt: now,
1132
1153
  authenticatorExpiresAt: Date.now() + expiresInMs,
1133
- lastRenewalAttempt: void 0,
1134
- username
1154
+ lastRenewalAttempt: void 0
1135
1155
  };
1136
1156
  this.logger.log("EMBEDDED_PROVIDER", "Saving JWT session");
1137
1157
  await this.storage.saveSession(session);
@@ -1142,7 +1162,7 @@ var EmbeddedProvider = class {
1142
1162
  * It saves a temporary session before redirecting to prevent losing state during the redirect flow.
1143
1163
  * Session timestamp is updated before redirect to prevent race conditions.
1144
1164
  */
1145
- async handleRedirectAuth(organizationId, stamperInfo, authOptions, username) {
1165
+ async handleRedirectAuth(publicKey, stamperInfo, authOptions) {
1146
1166
  this.logger.info("EMBEDDED_PROVIDER", "Using Phantom Connect authentication flow (redirect-based)", {
1147
1167
  provider: authOptions?.provider,
1148
1168
  hasRedirectUrl: !!this.config.authOptions.redirectUrl,
@@ -1152,9 +1172,10 @@ var EmbeddedProvider = class {
1152
1172
  const sessionId = generateSessionId();
1153
1173
  const tempSession = {
1154
1174
  sessionId,
1155
- walletId: `temp-${now}`,
1175
+ walletId: `temp-wallet-${now}`,
1176
+ // Temporary ID, will be updated after redirect
1177
+ organizationId: `temp-org-${now}`,
1156
1178
  // Temporary ID, will be updated after redirect
1157
- organizationId,
1158
1179
  appId: this.config.appId,
1159
1180
  stamperInfo,
1160
1181
  authProvider: "phantom-connect",
@@ -1166,8 +1187,7 @@ var EmbeddedProvider = class {
1166
1187
  lastUsed: now,
1167
1188
  authenticatorCreatedAt: now,
1168
1189
  authenticatorExpiresAt: now + AUTHENTICATOR_EXPIRATION_TIME_MS,
1169
- lastRenewalAttempt: void 0,
1170
- username: username || `user-${(0, import_utils.randomUUID)()}`
1190
+ lastRenewalAttempt: void 0
1171
1191
  };
1172
1192
  this.logger.log("EMBEDDED_PROVIDER", "Saving temporary session before redirect", {
1173
1193
  sessionId: tempSession.sessionId,
@@ -1176,13 +1196,13 @@ var EmbeddedProvider = class {
1176
1196
  tempSession.lastUsed = Date.now();
1177
1197
  await this.storage.saveSession(tempSession);
1178
1198
  this.logger.info("EMBEDDED_PROVIDER", "Starting Phantom Connect redirect", {
1179
- organizationId,
1199
+ publicKey,
1180
1200
  appId: this.config.appId,
1181
1201
  provider: authOptions?.provider,
1182
1202
  authUrl: this.config.authOptions.authUrl
1183
1203
  });
1184
1204
  const authResult = await this.authProvider.authenticate({
1185
- organizationId,
1205
+ publicKey,
1186
1206
  appId: this.config.appId,
1187
1207
  provider: authOptions?.provider,
1188
1208
  redirectUrl: this.config.authOptions.redirectUrl,
@@ -1193,13 +1213,24 @@ var EmbeddedProvider = class {
1193
1213
  if (authResult && "walletId" in authResult) {
1194
1214
  this.logger.info("EMBEDDED_PROVIDER", "Authentication completed after redirect", {
1195
1215
  walletId: authResult.walletId,
1216
+ organizationId: authResult.organizationId,
1196
1217
  provider: authResult.provider
1197
1218
  });
1198
1219
  tempSession.walletId = authResult.walletId;
1220
+ tempSession.organizationId = authResult.organizationId;
1199
1221
  tempSession.authProvider = authResult.provider || tempSession.authProvider;
1200
1222
  tempSession.accountDerivationIndex = authResult.accountDerivationIndex;
1201
1223
  tempSession.status = "completed";
1202
1224
  tempSession.lastUsed = Date.now();
1225
+ if (authResult.expiresInMs > 0) {
1226
+ const now2 = Date.now();
1227
+ tempSession.authenticatorCreatedAt = now2;
1228
+ tempSession.authenticatorExpiresAt = now2 + authResult.expiresInMs;
1229
+ this.logger.log("EMBEDDED_PROVIDER", "Updated authenticator expiration from immediate auth response", {
1230
+ expiresInMs: authResult.expiresInMs,
1231
+ expiresAt: new Date(tempSession.authenticatorExpiresAt).toISOString()
1232
+ });
1233
+ }
1203
1234
  await this.storage.saveSession(tempSession);
1204
1235
  return tempSession;
1205
1236
  }
@@ -1213,9 +1244,19 @@ var EmbeddedProvider = class {
1213
1244
  }
1214
1245
  session.walletId = authResult.walletId;
1215
1246
  session.authProvider = authResult.provider || session.authProvider;
1247
+ session.organizationId = authResult.organizationId;
1216
1248
  session.accountDerivationIndex = authResult.accountDerivationIndex;
1217
1249
  session.status = "completed";
1218
1250
  session.lastUsed = Date.now();
1251
+ if (authResult.expiresInMs > 0) {
1252
+ const now = Date.now();
1253
+ session.authenticatorCreatedAt = now;
1254
+ session.authenticatorExpiresAt = now + authResult.expiresInMs;
1255
+ this.logger.log("EMBEDDED_PROVIDER", "Updated authenticator expiration from auth response", {
1256
+ expiresInMs: authResult.expiresInMs,
1257
+ expiresAt: new Date(session.authenticatorExpiresAt).toISOString()
1258
+ });
1259
+ }
1219
1260
  await this.storage.saveSession(session);
1220
1261
  await this.initializeClientFromSession(session);
1221
1262
  await this.ensureValidAuthenticator();
@@ -1269,13 +1310,13 @@ var EmbeddedProvider = class {
1269
1310
  }
1270
1311
  /*
1271
1312
  * We use this method to perform silent authenticator renewal.
1272
- * It generates a new keypair, creates a new authenticator, and switches to it.
1313
+ * It generates a new keypair, adds a new user to the organization with the new keypair, and switches to it.
1273
1314
  */
1274
1315
  async renewAuthenticator(session) {
1275
1316
  if (!this.client) {
1276
1317
  throw new Error("Client not initialized");
1277
1318
  }
1278
- this.logger.info("EMBEDDED_PROVIDER", "Starting authenticator renewal");
1319
+ this.logger.info("EMBEDDED_PROVIDER", "Starting authenticator renewal using addUserToOrganization");
1279
1320
  try {
1280
1321
  const newKeyInfo = await this.stamper.rotateKeyPair();
1281
1322
  this.logger.log("EMBEDDED_PROVIDER", "Generated new keypair for renewal", {
@@ -1284,44 +1325,54 @@ var EmbeddedProvider = class {
1284
1325
  });
1285
1326
  const base64urlPublicKey = (0, import_base64url2.base64urlEncode)(import_bs582.default.decode(newKeyInfo.publicKey));
1286
1327
  const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
1287
- let authenticatorResult;
1328
+ const shortKeyId = newKeyInfo.keyId.substring(0, 8);
1329
+ const newUsername = `user-${shortKeyId}`;
1288
1330
  try {
1289
- authenticatorResult = await this.client.createAuthenticator({
1331
+ await this.client.addUserToOrganization({
1290
1332
  organizationId: session.organizationId,
1291
- username: session.username,
1292
- authenticatorName: `auth-${newKeyInfo.keyId.substring(0, 8)}`,
1293
- authenticator: {
1294
- authenticatorName: `auth-${newKeyInfo.keyId.substring(0, 8)}`,
1295
- authenticatorKind: "keypair",
1296
- publicKey: base64urlPublicKey,
1297
- algorithm: "Ed25519"
1298
- // Commented for now until KMS supports fully expiring organizations
1299
- // expiresInMs: expiresInMs,
1333
+ user: {
1334
+ username: newUsername,
1335
+ role: "ADMIN",
1336
+ // Use ADMIN role like original users
1337
+ authenticators: [
1338
+ {
1339
+ authenticatorName: `auth-${shortKeyId}`,
1340
+ authenticatorKind: "keypair",
1341
+ publicKey: base64urlPublicKey,
1342
+ algorithm: "Ed25519"
1343
+ }
1344
+ ],
1345
+ traits: {
1346
+ appId: this.config.appId
1347
+ }
1300
1348
  },
1349
+ expiresInMs,
1301
1350
  replaceExpirable: true
1351
+ // Replace oldest expirable user if at limit
1302
1352
  });
1303
1353
  } catch (error) {
1304
- this.logger.error("EMBEDDED_PROVIDER", "Failed to create new authenticator", {
1354
+ this.logger.error("EMBEDDED_PROVIDER", "Failed to add new user to organization", {
1305
1355
  error: error instanceof Error ? error.message : String(error)
1306
1356
  });
1307
1357
  await this.stamper.rollbackRotation();
1308
1358
  throw new Error(
1309
- `Failed to create new authenticator: ${error instanceof Error ? error.message : String(error)}`
1359
+ `Failed to add new user to organization: ${error instanceof Error ? error.message : String(error)}`
1310
1360
  );
1311
1361
  }
1312
- this.logger.info("EMBEDDED_PROVIDER", "Created new authenticator", {
1313
- authenticatorId: authenticatorResult.id
1362
+ this.logger.info("EMBEDDED_PROVIDER", "Added new user to organization successfully", {
1363
+ username: newUsername
1314
1364
  });
1315
- await this.stamper.commitRotation(authenticatorResult.id || "unknown");
1365
+ await this.stamper.commitRotation(newKeyInfo.keyId);
1316
1366
  const now = Date.now();
1317
1367
  session.stamperInfo = newKeyInfo;
1318
1368
  session.authenticatorCreatedAt = now;
1319
- session.authenticatorExpiresAt = Date.now() + expiresInMs;
1369
+ session.authenticatorExpiresAt = now + expiresInMs;
1320
1370
  session.lastRenewalAttempt = now;
1321
1371
  await this.storage.saveSession(session);
1322
1372
  this.logger.info("EMBEDDED_PROVIDER", "Authenticator renewal completed successfully", {
1323
1373
  newKeyId: newKeyInfo.keyId,
1324
- expiresAt: new Date(Date.now() + expiresInMs).toISOString()
1374
+ newUsername,
1375
+ expiresAt: new Date(session.authenticatorExpiresAt).toISOString()
1325
1376
  });
1326
1377
  } catch (error) {
1327
1378
  await this.stamper.rollbackRotation();