@strands.gg/accui 1.2.0 → 1.2.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.
@@ -7426,15 +7426,38 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
7426
7426
  challenge: safeBufferConvert(challenge.challenge),
7427
7427
  pubKeyCredParams: challenge.pubKeyCredParams || [
7428
7428
  { type: "public-key", alg: -7 },
7429
- // ES256
7430
- { type: "public-key", alg: -257 }
7431
- // RS256
7429
+ // ES256 (ECDSA w/ SHA-256)
7430
+ { type: "public-key", alg: -35 },
7431
+ // ES384 (ECDSA w/ SHA-384)
7432
+ { type: "public-key", alg: -36 },
7433
+ // ES512 (ECDSA w/ SHA-512)
7434
+ { type: "public-key", alg: -257 },
7435
+ // RS256 (RSASSA-PKCS1-v1_5 w/ SHA-256)
7436
+ { type: "public-key", alg: -258 },
7437
+ // RS384 (RSASSA-PKCS1-v1_5 w/ SHA-384)
7438
+ { type: "public-key", alg: -259 },
7439
+ // RS512 (RSASSA-PKCS1-v1_5 w/ SHA-512)
7440
+ { type: "public-key", alg: -37 },
7441
+ // PS256 (RSASSA-PSS w/ SHA-256)
7442
+ { type: "public-key", alg: -38 },
7443
+ // PS384 (RSASSA-PSS w/ SHA-384)
7444
+ { type: "public-key", alg: -39 },
7445
+ // PS512 (RSASSA-PSS w/ SHA-512)
7446
+ { type: "public-key", alg: -8 }
7447
+ // EdDSA (Ed25519)
7432
7448
  ],
7433
- timeout: challenge.timeout || 6e4,
7449
+ timeout: challenge.timeout || 12e4,
7450
+ // Longer timeout for hardware keys
7434
7451
  authenticatorSelection: challenge.authenticatorSelection || {
7435
- userVerification: "preferred"
7452
+ authenticatorAttachment: "cross-platform",
7453
+ // Prefer external authenticators like YubiKey
7454
+ requireResidentKey: false,
7455
+ residentKey: "discouraged",
7456
+ userVerification: "discouraged"
7457
+ // YubiKeys work better with discouraged
7436
7458
  },
7437
- attestation: challenge.attestation || "none"
7459
+ attestation: challenge.attestation || "direct"
7460
+ // Better compatibility with hardware keys
7438
7461
  };
7439
7462
  if (challenge.excludeCredentials && Array.isArray(challenge.excludeCredentials) && challenge.excludeCredentials.length > 0) {
7440
7463
  cleanChallenge.excludeCredentials = challenge.excludeCredentials.map((cred) => ({
@@ -7464,6 +7487,16 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
7464
7487
  if (!window.navigator.credentials || !window.PublicKeyCredential) {
7465
7488
  throw new Error("WebAuthn is not supported in this browser");
7466
7489
  }
7490
+ try {
7491
+ const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable?.();
7492
+ console.log("Platform authenticator available:", available);
7493
+ if (PublicKeyCredential.isConditionalMediationAvailable) {
7494
+ const conditionalMediationAvailable = await PublicKeyCredential.isConditionalMediationAvailable();
7495
+ console.log("Conditional mediation available:", conditionalMediationAvailable);
7496
+ }
7497
+ } catch (checkError) {
7498
+ console.warn("Could not check authenticator availability:", checkError);
7499
+ }
7467
7500
  if (!currentSession.value?.accessToken) {
7468
7501
  throw new Error("Not authenticated. Please sign in again.");
7469
7502
  }
@@ -7530,7 +7563,26 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
7530
7563
  }
7531
7564
  } catch (error) {
7532
7565
  console.error("Hardware key registration error:", error);
7533
- errorMessage.value = error instanceof Error ? error.message : "Hardware key registration failed";
7566
+ let userErrorMessage = "Hardware key registration failed";
7567
+ if (error instanceof Error) {
7568
+ const errorMsg = error.message.toLowerCase();
7569
+ if (errorMsg.includes("not supported") || errorMsg.includes("webauthn")) {
7570
+ userErrorMessage = "WebAuthn is not supported in this browser. Please use Chrome, Firefox, Safari, or Edge.";
7571
+ } else if (errorMsg.includes("cancelled") || errorMsg.includes("aborted")) {
7572
+ userErrorMessage = "Hardware key registration was cancelled. Please try again and touch your key when prompted.";
7573
+ } else if (errorMsg.includes("timeout")) {
7574
+ userErrorMessage = "Hardware key registration timed out. Please ensure your key is connected and try again.";
7575
+ } else if (errorMsg.includes("not allowed") || errorMsg.includes("invalid state")) {
7576
+ userErrorMessage = "This hardware key may already be registered or cannot be used. Try a different key or contact support.";
7577
+ } else if (errorMsg.includes("can't be used") || errorMsg.includes("newer or different")) {
7578
+ userErrorMessage = "Your hardware key is not compatible. Please ensure you have a FIDO2/WebAuthn compatible key like YubiKey 5 Series, and that it's properly connected.";
7579
+ } else if (errorMsg.includes("user verification") || errorMsg.includes("pin")) {
7580
+ userErrorMessage = "Hardware key verification failed. If your key has a PIN, please enter it when prompted.";
7581
+ } else {
7582
+ userErrorMessage = error.message;
7583
+ }
7584
+ }
7585
+ errorMessage.value = userErrorMessage;
7534
7586
  step.value = 5;
7535
7587
  } finally {
7536
7588
  internalLoading.value = false;
@@ -9198,6 +9250,7 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
9198
9250
  const emit = __emit;
9199
9251
  const { getSupportEmail, config: strandsConfig, getUrl } = useStrandsConfig(props.config);
9200
9252
  const { fetchProfile, updateProfile, changeEmail, currentUser: authUser, currentSession, isAuthenticated, refreshToken, signOut } = useStrandsAuth();
9253
+ const { mfaDevices: mfaDevices2, mfaEnabled: mfaEnabled2, activeMfaDevices, fetchMfaDevices } = useStrandsMfa();
9201
9254
  const internalUser = ref(null);
9202
9255
  const fetchingProfile = ref(false);
9203
9256
  const currentUser = computed(() => {
@@ -9275,6 +9328,54 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
9275
9328
  const years = Math.floor(diffDays / 365);
9276
9329
  return years === 1 ? "1 year ago" : `${years} years ago`;
9277
9330
  });
9331
+ const deviceTypeCounts = computed(() => {
9332
+ const devices = activeMfaDevices.value || [];
9333
+ const counts = {
9334
+ totp: 0,
9335
+ email: 0,
9336
+ hardware: 0,
9337
+ passkey: 0
9338
+ };
9339
+ devices.forEach((device) => {
9340
+ if (device.device_type in counts) {
9341
+ counts[device.device_type]++;
9342
+ }
9343
+ });
9344
+ return counts;
9345
+ });
9346
+ const mfaDeviceChips = computed(() => {
9347
+ const chips = [];
9348
+ const counts = deviceTypeCounts.value;
9349
+ if (counts.totp > 0) {
9350
+ chips.push({
9351
+ type: "totp",
9352
+ count: counts.totp,
9353
+ label: counts.totp === 1 ? "Auth app" : "Auth apps",
9354
+ icon: Smartphone,
9355
+ color: "bg-blue-50 text-blue-700 border-blue-200"
9356
+ });
9357
+ }
9358
+ if (counts.email > 0) {
9359
+ chips.push({
9360
+ type: "email",
9361
+ count: counts.email,
9362
+ label: "Email",
9363
+ icon: Mail,
9364
+ color: "bg-green-50 text-green-700 border-green-200"
9365
+ });
9366
+ }
9367
+ if (counts.hardware > 0 || counts.passkey > 0) {
9368
+ const totalHardware = counts.hardware + counts.passkey;
9369
+ chips.push({
9370
+ type: "hardware",
9371
+ count: totalHardware,
9372
+ label: totalHardware === 1 ? "Key" : "Keys",
9373
+ icon: KeyRound,
9374
+ color: "bg-purple-50 text-purple-700 border-purple-200"
9375
+ });
9376
+ }
9377
+ return chips;
9378
+ });
9278
9379
  const getInitials2 = (firstName, lastName) => {
9279
9380
  if (!firstName && !lastName) return "U";
9280
9381
  return `${firstName?.[0] || ""}${lastName?.[0] || ""}`.toUpperCase();
@@ -9284,6 +9385,9 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
9284
9385
  fetchingProfile.value = true;
9285
9386
  try {
9286
9387
  await fetchProfile();
9388
+ if (isAuthenticated.value) {
9389
+ await fetchMfaDevices();
9390
+ }
9287
9391
  } catch (err) {
9288
9392
  const errorMsg = err instanceof Error ? err.message : "Failed to load profile";
9289
9393
  errorMessage.value = errorMsg;
@@ -9498,6 +9602,7 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
9498
9602
  const handleMfaUpdated = async () => {
9499
9603
  try {
9500
9604
  await fetchProfile();
9605
+ await fetchMfaDevices();
9501
9606
  successMessage.value = "MFA settings updated successfully";
9502
9607
  emit("mfa-toggle", currentUser.value?.mfaEnabled || false);
9503
9608
  } catch (err) {
@@ -9535,7 +9640,7 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
9535
9640
  signingOut.value = false;
9536
9641
  }
9537
9642
  };
9538
- const __returned__ = { props, emit, getSupportEmail, strandsConfig, getUrl, fetchProfile, updateProfile, changeEmail, authUser, currentSession, isAuthenticated, refreshToken, signOut, internalUser, fetchingProfile, currentUser, loading: loading2, uploading, signingOut, showPasswordChange, showEmailChange, emailChangeLoading, showMfaModal, successMessage, errorMessage, activeSessions, fileInputRef, form, emailChangeForm, passwordForm, errors, hasChanges, isPasswordFormValid, isEmailChangeFormValid, passwordLastUpdated, getInitials: getInitials2, fetchUserProfile, clearMessages, handleUpdateProfile, handlePasswordChange, handleEmailChange, triggerFileUpload, handleFileSelect, uploadAvatar, handleMfaUpdated, handleCancel, handleSignOut, get StrandsUiButton() {
9643
+ const __returned__ = { props, emit, getSupportEmail, strandsConfig, getUrl, fetchProfile, updateProfile, changeEmail, authUser, currentSession, isAuthenticated, refreshToken, signOut, mfaDevices: mfaDevices2, mfaEnabled: mfaEnabled2, activeMfaDevices, fetchMfaDevices, internalUser, fetchingProfile, currentUser, loading: loading2, uploading, signingOut, showPasswordChange, showEmailChange, emailChangeLoading, showMfaModal, successMessage, errorMessage, activeSessions, fileInputRef, form, emailChangeForm, passwordForm, errors, hasChanges, isPasswordFormValid, isEmailChangeFormValid, passwordLastUpdated, deviceTypeCounts, mfaDeviceChips, getInitials: getInitials2, fetchUserProfile, clearMessages, handleUpdateProfile, handlePasswordChange, handleEmailChange, triggerFileUpload, handleFileSelect, uploadAvatar, handleMfaUpdated, handleCancel, handleSignOut, get StrandsUiButton() {
9539
9644
  return StrandsUiButton;
9540
9645
  }, get StrandsUiInput() {
9541
9646
  return StrandsUiInput;
@@ -9588,31 +9693,37 @@ const _hoisted_21 = {
9588
9693
  class: "space-y-3 overflow-hidden"
9589
9694
  };
9590
9695
  const _hoisted_22 = { class: "p-4 bg-gray-50 rounded-xl" };
9591
- const _hoisted_23 = { class: "flex items-center justify-between gap-2" };
9592
- const _hoisted_24 = { class: "text-sm text-gray-600" };
9593
- const _hoisted_25 = { class: "p-4 bg-gray-50 rounded-xl" };
9594
- const _hoisted_26 = { class: "flex items-center justify-between gap-2" };
9595
- const _hoisted_27 = { class: "text-sm text-gray-600" };
9596
- const _hoisted_28 = {
9696
+ const _hoisted_23 = { class: "flex items-start justify-between gap-4" };
9697
+ const _hoisted_24 = { class: "flex-1" };
9698
+ const _hoisted_25 = { class: "text-sm text-gray-600 mt-1" };
9699
+ const _hoisted_26 = {
9700
+ key: 0,
9701
+ class: "flex flex-wrap gap-2 mt-3"
9702
+ };
9703
+ const _hoisted_27 = { class: "ml-1" };
9704
+ const _hoisted_28 = { class: "p-4 bg-gray-50 rounded-xl" };
9705
+ const _hoisted_29 = { class: "flex items-center justify-between gap-2" };
9706
+ const _hoisted_30 = { class: "text-sm text-gray-600" };
9707
+ const _hoisted_31 = {
9597
9708
  key: 0,
9598
9709
  class: "flex flex-col sm:flex-row gap-3 pt-6 border-t border-gray-200 animate-slide-up"
9599
9710
  };
9600
- const _hoisted_29 = {
9711
+ const _hoisted_32 = {
9601
9712
  key: 0,
9602
9713
  class: "mt-6 animate-fade-in"
9603
9714
  };
9604
- const _hoisted_30 = { class: "alert-success" };
9605
- const _hoisted_31 = { class: "flex items-start gap-3" };
9606
- const _hoisted_32 = { class: "font-medium" };
9607
- const _hoisted_33 = {
9715
+ const _hoisted_33 = { class: "alert-success" };
9716
+ const _hoisted_34 = { class: "flex items-start gap-3" };
9717
+ const _hoisted_35 = { class: "font-medium" };
9718
+ const _hoisted_36 = {
9608
9719
  key: 1,
9609
9720
  class: "mt-6 animate-fade-in"
9610
9721
  };
9611
- const _hoisted_34 = { class: "alert-error" };
9612
- const _hoisted_35 = { class: "flex items-start gap-3" };
9613
- const _hoisted_36 = { class: "font-medium" };
9614
- const _hoisted_37 = { key: 0 };
9615
- const _hoisted_38 = { class: "text-gray-400 text-sm" };
9722
+ const _hoisted_37 = { class: "alert-error" };
9723
+ const _hoisted_38 = { class: "flex items-start gap-3" };
9724
+ const _hoisted_39 = { class: "font-medium" };
9725
+ const _hoisted_40 = { key: 0 };
9726
+ const _hoisted_41 = { class: "text-gray-400 text-sm" };
9616
9727
  function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
9617
9728
  return openBlock(), createElementBlock("div", _hoisted_1$5, [
9618
9729
  createElementVNode("div", _hoisted_2$4, [
@@ -9909,7 +10020,7 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
9909
10020
  createCommentVNode(" Two-Factor Authentication "),
9910
10021
  createElementVNode("div", _hoisted_22, [
9911
10022
  createElementVNode("div", _hoisted_23, [
9912
- createElementVNode("div", null, [
10023
+ createElementVNode("div", _hoisted_24, [
9913
10024
  _cache[17] || (_cache[17] = createElementVNode(
9914
10025
  "h4",
9915
10026
  { class: "font-medium text-gray-900" },
@@ -9919,16 +10030,60 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
9919
10030
  )),
9920
10031
  createElementVNode(
9921
10032
  "p",
9922
- _hoisted_24,
10033
+ _hoisted_25,
9923
10034
  toDisplayString($setup.currentUser?.mfaEnabled ? "Enabled" : "Add extra security to your account"),
9924
10035
  1
9925
10036
  /* TEXT */
9926
- )
10037
+ ),
10038
+ createCommentVNode(" Device Type Chips "),
10039
+ $setup.mfaDeviceChips.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_26, [
10040
+ (openBlock(true), createElementBlock(
10041
+ Fragment,
10042
+ null,
10043
+ renderList($setup.mfaDeviceChips, (chip) => {
10044
+ return openBlock(), createElementBlock(
10045
+ "div",
10046
+ {
10047
+ key: chip.type,
10048
+ class: normalizeClass([
10049
+ "inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-medium border transition-colors",
10050
+ chip.color
10051
+ ])
10052
+ },
10053
+ [
10054
+ (openBlock(), createBlock(resolveDynamicComponent(chip.icon), {
10055
+ size: 12,
10056
+ class: "mr-1.5"
10057
+ })),
10058
+ createElementVNode(
10059
+ "span",
10060
+ null,
10061
+ toDisplayString(chip.count),
10062
+ 1
10063
+ /* TEXT */
10064
+ ),
10065
+ createElementVNode(
10066
+ "span",
10067
+ _hoisted_27,
10068
+ toDisplayString(chip.label),
10069
+ 1
10070
+ /* TEXT */
10071
+ )
10072
+ ],
10073
+ 2
10074
+ /* CLASS */
10075
+ );
10076
+ }),
10077
+ 128
10078
+ /* KEYED_FRAGMENT */
10079
+ ))
10080
+ ])) : createCommentVNode("v-if", true)
9927
10081
  ]),
9928
10082
  createVNode($setup["StrandsUiButton"], {
9929
10083
  variant: $setup.currentUser?.mfaEnabled ? "secondary" : "primary",
9930
10084
  size: "sm",
9931
- onClick: _cache[9] || (_cache[9] = ($event) => $setup.showMfaModal = true)
10085
+ onClick: _cache[9] || (_cache[9] = ($event) => $setup.showMfaModal = true),
10086
+ class: "flex-shrink-0"
9932
10087
  }, {
9933
10088
  default: withCtx(() => [
9934
10089
  createTextVNode(
@@ -9943,8 +10098,8 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
9943
10098
  ])
9944
10099
  ]),
9945
10100
  createCommentVNode(" Active Sessions "),
9946
- createElementVNode("div", _hoisted_25, [
9947
- createElementVNode("div", _hoisted_26, [
10101
+ createElementVNode("div", _hoisted_28, [
10102
+ createElementVNode("div", _hoisted_29, [
9948
10103
  createElementVNode("div", null, [
9949
10104
  _cache[18] || (_cache[18] = createElementVNode(
9950
10105
  "h4",
@@ -9955,7 +10110,7 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
9955
10110
  )),
9956
10111
  createElementVNode(
9957
10112
  "p",
9958
- _hoisted_27,
10113
+ _hoisted_30,
9959
10114
  toDisplayString($setup.activeSessions.length) + " active device(s)",
9960
10115
  1
9961
10116
  /* TEXT */
@@ -9982,7 +10137,7 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
9982
10137
  ])
9983
10138
  ]),
9984
10139
  createCommentVNode(" Action Buttons - Only show when changes are made "),
9985
- $setup.hasChanges ? (openBlock(), createElementBlock("div", _hoisted_28, [
10140
+ $setup.hasChanges ? (openBlock(), createElementBlock("div", _hoisted_31, [
9986
10141
  createVNode($setup["StrandsUiButton"], {
9987
10142
  type: "submit",
9988
10143
  variant: "primary",
@@ -10020,9 +10175,9 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
10020
10175
  /* NEED_HYDRATION */
10021
10176
  ),
10022
10177
  createCommentVNode(" Success/Error Messages "),
10023
- $setup.successMessage ? (openBlock(), createElementBlock("div", _hoisted_29, [
10024
- createElementVNode("div", _hoisted_30, [
10025
- createElementVNode("div", _hoisted_31, [
10178
+ $setup.successMessage ? (openBlock(), createElementBlock("div", _hoisted_32, [
10179
+ createElementVNode("div", _hoisted_33, [
10180
+ createElementVNode("div", _hoisted_34, [
10026
10181
  _cache[22] || (_cache[22] = createElementVNode(
10027
10182
  "svg",
10028
10183
  {
@@ -10042,7 +10197,7 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
10042
10197
  )),
10043
10198
  createElementVNode(
10044
10199
  "p",
10045
- _hoisted_32,
10200
+ _hoisted_35,
10046
10201
  toDisplayString($setup.successMessage),
10047
10202
  1
10048
10203
  /* TEXT */
@@ -10050,9 +10205,9 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
10050
10205
  ])
10051
10206
  ])
10052
10207
  ])) : createCommentVNode("v-if", true),
10053
- $setup.errorMessage ? (openBlock(), createElementBlock("div", _hoisted_33, [
10054
- createElementVNode("div", _hoisted_34, [
10055
- createElementVNode("div", _hoisted_35, [
10208
+ $setup.errorMessage ? (openBlock(), createElementBlock("div", _hoisted_36, [
10209
+ createElementVNode("div", _hoisted_37, [
10210
+ createElementVNode("div", _hoisted_38, [
10056
10211
  _cache[23] || (_cache[23] = createElementVNode(
10057
10212
  "svg",
10058
10213
  {
@@ -10072,7 +10227,7 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
10072
10227
  )),
10073
10228
  createElementVNode(
10074
10229
  "p",
10075
- _hoisted_36,
10230
+ _hoisted_39,
10076
10231
  toDisplayString($setup.errorMessage),
10077
10232
  1
10078
10233
  /* TEXT */
@@ -10103,8 +10258,8 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
10103
10258
  /* STABLE */
10104
10259
  }, 8, ["disabled"]),
10105
10260
  createCommentVNode(" Need help "),
10106
- $setup.getSupportEmail() ? (openBlock(), createElementBlock("div", _hoisted_37, [
10107
- createElementVNode("p", _hoisted_38, [
10261
+ $setup.getSupportEmail() ? (openBlock(), createElementBlock("div", _hoisted_40, [
10262
+ createElementVNode("p", _hoisted_41, [
10108
10263
  _cache[25] || (_cache[25] = createTextVNode(
10109
10264
  " Need help? ",
10110
10265
  -1