@strands.gg/accui 2.1.4 → 2.1.6
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.ts +1 -7
- package/dist/js/auth-components-BJhPF4dS.js +8195 -0
- package/dist/js/auth-components-BJhPF4dS.js.br +0 -0
- package/dist/js/auth-components-BJhPF4dS.js.gz +0 -0
- package/dist/js/auth-components-CXT-W3fG.js +1 -0
- package/dist/js/auth-components-CXT-W3fG.js.br +0 -0
- package/dist/js/auth-components-CXT-W3fG.js.gz +0 -0
- package/dist/js/composables-BqIRpDGP.js +1609 -0
- package/dist/js/composables-BqIRpDGP.js.br +0 -0
- package/dist/js/composables-BqIRpDGP.js.gz +0 -0
- package/dist/js/composables-BqLSXNon.js +1 -0
- package/dist/js/composables-BqLSXNon.js.br +0 -0
- package/dist/js/composables-BqLSXNon.js.gz +0 -0
- package/dist/js/icons-CQ3z-CUv.js +398 -0
- package/dist/js/icons-CQ3z-CUv.js.br +0 -0
- package/dist/js/icons-CQ3z-CUv.js.gz +0 -0
- package/dist/js/icons-MGGFfB13.js +1 -0
- package/dist/js/index-87OecKtg.js +1 -0
- package/dist/js/index-B113vWue.js +106 -0
- package/dist/js/nuxt/module-BA8ZjBpp.js +1 -0
- package/dist/{nuxt/module.es.js → js/nuxt/module-YfdoaPCb.js} +2 -0
- package/dist/{nuxt/runtime/composables/useAuthenticatedFetch.es.js → js/nuxt/runtime/composables/useAuthenticatedFetch-DToDCO6a.js} +1 -1
- package/dist/js/nuxt/runtime/composables/useAuthenticatedFetch-qYkd06Dm.js +1 -0
- package/dist/{nuxt/runtime/composables/useStrandsAuth.es.js → js/nuxt/runtime/composables/useStrandsAuth-8b_bomlH.js} +1 -1
- package/dist/js/nuxt/runtime/composables/useStrandsAuth-Dn15YEQw.js +1 -0
- package/dist/{nuxt/runtime/middleware/auth.global.es.js → js/nuxt/runtime/middleware/auth.global-_2xq-SvK.js} +1 -1
- package/dist/js/nuxt/runtime/middleware/auth.global-dZpk-3-I.js +1 -0
- package/dist/js/nuxt/runtime/plugin.client-D7S6ulTg.js +1 -0
- package/dist/{nuxt/runtime/plugin.client.es.js → js/nuxt/runtime/plugin.client-KK_pCEfA.js} +2 -4
- package/dist/js/nuxt/runtime/plugin.server-BpSAN5Gh.js +1 -0
- package/dist/{nuxt/runtime/plugin.server.es.js → js/nuxt/runtime/plugin.server-CGdeJOoz.js} +1 -1
- package/dist/js/nuxt/runtime/plugins/auth-interceptor.client-BQu0eR16.js +1 -0
- package/dist/{nuxt/runtime/plugins/auth-interceptor.client.es.js → js/nuxt/runtime/plugins/auth-interceptor.client-DIa4pC59.js} +1 -1
- package/dist/js/nuxt-DqhAWeo-.js +1 -0
- package/dist/{nuxt.es.js → js/nuxt-MUpTW0D9.js} +3 -3
- package/dist/{nuxt-v4/module.es.js → js/nuxt-v4/module-CCvkwSrU.js} +2 -0
- package/dist/js/nuxt-v4/module-CDtxGAn8.js +1 -0
- package/dist/{nuxt-v4/runtime/composables/useAuthenticatedFetch.es.js → js/nuxt-v4/runtime/composables/useAuthenticatedFetch-I0BKB43E.js} +1 -1
- package/dist/js/nuxt-v4/runtime/composables/useAuthenticatedFetch-fAO6ieYd.js +1 -0
- package/dist/{nuxt-v4/runtime/composables/useStrandsAuth.es.js → js/nuxt-v4/runtime/composables/useStrandsAuth-BZe8CTx0.js} +1 -1
- package/dist/js/nuxt-v4/runtime/composables/useStrandsAuth-DPGVMZ-e.js +1 -0
- package/dist/js/nuxt-v4/runtime/middleware/auth.global-B0pyByUu.js +1 -0
- package/dist/{nuxt-v4/runtime/middleware/auth.global.es.js → js/nuxt-v4/runtime/middleware/auth.global-C_saJaMU.js} +1 -1
- package/dist/{nuxt-v4/runtime/plugin.client.es.js → js/nuxt-v4/runtime/plugin.client-CxLmYQfu.js} +2 -4
- package/dist/js/nuxt-v4/runtime/plugin.client-DGO53i-e.js +1 -0
- package/dist/{nuxt-v4/runtime/plugin.server.es.js → js/nuxt-v4/runtime/plugin.server-BM6HYyiZ.js} +1 -1
- package/dist/js/nuxt-v4/runtime/plugin.server-D_dUMHYo.js +1 -0
- package/dist/js/nuxt-v4/runtime/plugins/auth-interceptor.client-CUNOTFBp.js +1 -0
- package/dist/{nuxt-v4/runtime/plugins/auth-interceptor.client.es.js → js/nuxt-v4/runtime/plugins/auth-interceptor.client-Qht8QSLW.js} +1 -1
- package/dist/{nuxt-v4.es.js → js/nuxt-v4-C5JN4uyM.js} +3 -3
- package/dist/js/nuxt-v4-DuwGUuhX.js +1 -0
- package/dist/js/ui-components-BC1UK39i.js +5204 -0
- package/dist/js/ui-components-BC1UK39i.js.br +0 -0
- package/dist/js/ui-components-BC1UK39i.js.gz +0 -0
- package/dist/js/ui-components-CocLXkUI.js +1 -0
- package/dist/js/ui-components-CocLXkUI.js.br +0 -0
- package/dist/js/ui-components-CocLXkUI.js.gz +0 -0
- package/dist/js/utils-B9zcSW52.js +1 -0
- package/dist/js/utils-B9zcSW52.js.br +0 -0
- package/dist/js/utils-B9zcSW52.js.gz +0 -0
- package/dist/js/utils-BZHeJkZf.js +1080 -0
- package/dist/js/utils-BZHeJkZf.js.br +0 -0
- package/dist/js/utils-BZHeJkZf.js.gz +0 -0
- package/dist/nuxt/module.d.ts +1 -6
- package/dist/nuxt/runtime/composables/useAuthenticatedFetch.d.ts +1 -21
- package/dist/nuxt/runtime/composables/useStrandsAuth.d.ts +1 -143
- package/dist/nuxt/runtime/middleware/auth.global.d.ts +1 -4
- package/dist/nuxt/runtime/plugin.client.d.ts +1 -4
- package/dist/nuxt/runtime/plugin.server.d.ts +1 -4
- package/dist/nuxt/runtime/plugins/auth-interceptor.client.d.ts +1 -4
- package/dist/nuxt-v4/module.d.ts +1 -6
- package/dist/nuxt-v4/runtime/composables/useAuthenticatedFetch.d.ts +1 -21
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.d.ts +1 -28
- package/dist/nuxt-v4/runtime/middleware/auth.global.d.ts +1 -4
- package/dist/nuxt-v4/runtime/plugin.client.d.ts +1 -4
- package/dist/nuxt-v4/runtime/plugin.server.d.ts +1 -4
- package/dist/nuxt-v4/runtime/plugins/auth-interceptor.client.d.ts +1 -4
- package/dist/nuxt-v4.d.ts +1 -5
- package/dist/nuxt.d.ts +1 -5
- package/dist/robots.txt +4 -0
- package/dist/sitemap.xml +9 -0
- package/dist/styles/accui-DF7zRGFD.css +1 -0
- package/dist/styles/accui-DF7zRGFD.css.br +0 -0
- package/dist/styles/accui-DF7zRGFD.css.gz +0 -0
- package/package.json +15 -1
- package/dist/accui.css +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/nuxt/module.cjs.js +0 -1
- package/dist/nuxt/module.cjs.js.map +0 -1
- package/dist/nuxt/module.d.ts.map +0 -1
- package/dist/nuxt/module.es.js.map +0 -1
- package/dist/nuxt/runtime/composables/useAuthenticatedFetch.cjs.js +0 -1
- package/dist/nuxt/runtime/composables/useAuthenticatedFetch.cjs.js.map +0 -1
- package/dist/nuxt/runtime/composables/useAuthenticatedFetch.d.ts.map +0 -1
- package/dist/nuxt/runtime/composables/useAuthenticatedFetch.es.js.map +0 -1
- package/dist/nuxt/runtime/composables/useStrandsAuth.cjs.js +0 -1
- package/dist/nuxt/runtime/composables/useStrandsAuth.cjs.js.map +0 -1
- package/dist/nuxt/runtime/composables/useStrandsAuth.d.ts.map +0 -1
- package/dist/nuxt/runtime/composables/useStrandsAuth.es.js.map +0 -1
- package/dist/nuxt/runtime/middleware/auth.d.ts +0 -8
- package/dist/nuxt/runtime/middleware/auth.d.ts.map +0 -1
- package/dist/nuxt/runtime/middleware/auth.global.cjs.js +0 -1
- package/dist/nuxt/runtime/middleware/auth.global.cjs.js.map +0 -1
- package/dist/nuxt/runtime/middleware/auth.global.d.ts.map +0 -1
- package/dist/nuxt/runtime/middleware/auth.global.es.js.map +0 -1
- package/dist/nuxt/runtime/middleware/guest.d.ts +0 -8
- package/dist/nuxt/runtime/middleware/guest.d.ts.map +0 -1
- package/dist/nuxt/runtime/plugin.client.cjs.js +0 -1
- package/dist/nuxt/runtime/plugin.client.cjs.js.map +0 -1
- package/dist/nuxt/runtime/plugin.client.d.ts.map +0 -1
- package/dist/nuxt/runtime/plugin.client.es.js.map +0 -1
- package/dist/nuxt/runtime/plugin.server.cjs.js +0 -1
- package/dist/nuxt/runtime/plugin.server.cjs.js.map +0 -1
- package/dist/nuxt/runtime/plugin.server.d.ts.map +0 -1
- package/dist/nuxt/runtime/plugin.server.es.js.map +0 -1
- package/dist/nuxt/runtime/plugins/auth-interceptor.client.cjs.js +0 -1
- package/dist/nuxt/runtime/plugins/auth-interceptor.client.cjs.js.map +0 -1
- package/dist/nuxt/runtime/plugins/auth-interceptor.client.d.ts.map +0 -1
- package/dist/nuxt/runtime/plugins/auth-interceptor.client.es.js.map +0 -1
- package/dist/nuxt/types.d.ts +0 -46
- package/dist/nuxt/types.d.ts.map +0 -1
- package/dist/nuxt-v4/module.cjs.js +0 -1
- package/dist/nuxt-v4/module.cjs.js.map +0 -1
- package/dist/nuxt-v4/module.d.ts.map +0 -1
- package/dist/nuxt-v4/module.es.js.map +0 -1
- package/dist/nuxt-v4/runtime/composables/useAuthenticatedFetch.cjs.js +0 -1
- package/dist/nuxt-v4/runtime/composables/useAuthenticatedFetch.cjs.js.map +0 -1
- package/dist/nuxt-v4/runtime/composables/useAuthenticatedFetch.d.ts.map +0 -1
- package/dist/nuxt-v4/runtime/composables/useAuthenticatedFetch.es.js.map +0 -1
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.cjs.js +0 -1
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.cjs.js.map +0 -1
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.d.ts.map +0 -1
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.es.js.map +0 -1
- package/dist/nuxt-v4/runtime/middleware/auth.global.cjs.js +0 -1
- package/dist/nuxt-v4/runtime/middleware/auth.global.cjs.js.map +0 -1
- package/dist/nuxt-v4/runtime/middleware/auth.global.d.ts.map +0 -1
- package/dist/nuxt-v4/runtime/middleware/auth.global.es.js.map +0 -1
- package/dist/nuxt-v4/runtime/plugin.client.cjs.js +0 -1
- package/dist/nuxt-v4/runtime/plugin.client.cjs.js.map +0 -1
- package/dist/nuxt-v4/runtime/plugin.client.d.ts.map +0 -1
- package/dist/nuxt-v4/runtime/plugin.client.es.js.map +0 -1
- package/dist/nuxt-v4/runtime/plugin.server.cjs.js +0 -1
- package/dist/nuxt-v4/runtime/plugin.server.cjs.js.map +0 -1
- package/dist/nuxt-v4/runtime/plugin.server.d.ts.map +0 -1
- package/dist/nuxt-v4/runtime/plugin.server.es.js.map +0 -1
- package/dist/nuxt-v4/runtime/plugins/auth-interceptor.client.cjs.js +0 -1
- package/dist/nuxt-v4/runtime/plugins/auth-interceptor.client.cjs.js.map +0 -1
- package/dist/nuxt-v4/runtime/plugins/auth-interceptor.client.d.ts.map +0 -1
- package/dist/nuxt-v4/runtime/plugins/auth-interceptor.client.es.js.map +0 -1
- package/dist/nuxt-v4/types.d.ts +0 -64
- package/dist/nuxt-v4/types.d.ts.map +0 -1
- package/dist/nuxt-v4.cjs.js +0 -1
- package/dist/nuxt-v4.cjs.js.map +0 -1
- package/dist/nuxt-v4.d.ts.map +0 -1
- package/dist/nuxt-v4.es.js.map +0 -1
- package/dist/nuxt.cjs.js +0 -1
- package/dist/nuxt.cjs.js.map +0 -1
- package/dist/nuxt.d.ts.map +0 -1
- package/dist/nuxt.es.js.map +0 -1
- package/dist/shared/defaults.d.ts +0 -3
- package/dist/shared/defaults.d.ts.map +0 -1
- package/dist/strands-auth-ui.cjs.js +0 -1
- package/dist/strands-auth-ui.cjs.js.map +0 -1
- package/dist/strands-auth-ui.es.js +0 -10561
- package/dist/strands-auth-ui.es.js.map +0 -1
- package/dist/types/index.d.ts +0 -237
- package/dist/types/index.d.ts.map +0 -1
- package/dist/useStrandsAuth-CTlaiFqK.cjs +0 -1
- package/dist/useStrandsAuth-CTlaiFqK.cjs.map +0 -1
- package/dist/useStrandsAuth-Cev-PTun.js +0 -899
- package/dist/useStrandsAuth-Cev-PTun.js.map +0 -1
- package/dist/useStrandsConfig-Cxb360Os.js +0 -179
- package/dist/useStrandsConfig-Cxb360Os.js.map +0 -1
- package/dist/useStrandsConfig-Z9_36OcV.cjs +0 -1
- package/dist/useStrandsConfig-Z9_36OcV.cjs.map +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/slots.d.ts +0 -2
- package/dist/utils/slots.d.ts.map +0 -1
- package/dist/utils/validation.d.ts +0 -13
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/vue/components/SignedIn.vue.d.ts +0 -55
- package/dist/vue/components/SignedIn.vue.d.ts.map +0 -1
- package/dist/vue/components/SignedOut.vue.d.ts +0 -55
- package/dist/vue/components/SignedOut.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsAuth.vue.d.ts +0 -26
- package/dist/vue/components/StrandsAuth.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsBackupCodesModal.vue.d.ts +0 -13
- package/dist/vue/components/StrandsBackupCodesModal.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsCompleteSignUp.vue.d.ts +0 -22
- package/dist/vue/components/StrandsCompleteSignUp.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsConfigProvider.vue.d.ts +0 -23
- package/dist/vue/components/StrandsConfigProvider.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsConfirmModal.vue.d.ts +0 -23
- package/dist/vue/components/StrandsConfirmModal.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsEmailMfaSetupModal.vue.d.ts +0 -13
- package/dist/vue/components/StrandsEmailMfaSetupModal.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsHardwareKeySetupModal.vue.d.ts +0 -16
- package/dist/vue/components/StrandsHardwareKeySetupModal.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsLogo.vue.d.ts +0 -9
- package/dist/vue/components/StrandsLogo.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsMFASetup.vue.d.ts +0 -17
- package/dist/vue/components/StrandsMFASetup.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsMfaModal.vue.d.ts +0 -13
- package/dist/vue/components/StrandsMfaModal.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsMfaVerification.vue.d.ts +0 -18
- package/dist/vue/components/StrandsMfaVerification.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsPasswordReset.vue.d.ts +0 -16
- package/dist/vue/components/StrandsPasswordReset.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsSecuredFooter.vue.d.ts +0 -23
- package/dist/vue/components/StrandsSecuredFooter.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsSessionsModal.vue.d.ts +0 -15
- package/dist/vue/components/StrandsSessionsModal.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsSettingsModal.vue.d.ts +0 -19
- package/dist/vue/components/StrandsSettingsModal.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsSignIn.vue.d.ts +0 -22
- package/dist/vue/components/StrandsSignIn.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsSignUp.vue.d.ts +0 -20
- package/dist/vue/components/StrandsSignUp.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsTotpSetupModal.vue.d.ts +0 -13
- package/dist/vue/components/StrandsTotpSetupModal.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsUserButton.vue.d.ts +0 -31
- package/dist/vue/components/StrandsUserButton.vue.d.ts.map +0 -1
- package/dist/vue/components/StrandsUserProfile.vue.d.ts +0 -27
- package/dist/vue/components/StrandsUserProfile.vue.d.ts.map +0 -1
- package/dist/vue/components/SvgIcon.vue.d.ts +0 -18
- package/dist/vue/components/SvgIcon.vue.d.ts.map +0 -1
- package/dist/vue/components/VirtualList.vue.d.ts +0 -38
- package/dist/vue/components/VirtualList.vue.d.ts.map +0 -1
- package/dist/vue/components/icons/IconGithub.vue.d.ts +0 -4
- package/dist/vue/components/icons/IconGithub.vue.d.ts.map +0 -1
- package/dist/vue/components/icons/IconGoogle.vue.d.ts +0 -4
- package/dist/vue/components/icons/IconGoogle.vue.d.ts.map +0 -1
- package/dist/vue/components/icons/index.d.ts +0 -3
- package/dist/vue/components/icons/index.d.ts.map +0 -1
- package/dist/vue/components/index.d.ts +0 -27
- package/dist/vue/components/index.d.ts.map +0 -1
- package/dist/vue/composables/useAuthenticatedFetch.d.ts +0 -21
- package/dist/vue/composables/useAuthenticatedFetch.d.ts.map +0 -1
- package/dist/vue/composables/useOAuthProviders.d.ts +0 -75
- package/dist/vue/composables/useOAuthProviders.d.ts.map +0 -1
- package/dist/vue/composables/useStrandsAuth.d.ts +0 -131
- package/dist/vue/composables/useStrandsAuth.d.ts.map +0 -1
- package/dist/vue/composables/useStrandsConfig.d.ts +0 -12
- package/dist/vue/composables/useStrandsConfig.d.ts.map +0 -1
- package/dist/vue/composables/useStrandsMfa.d.ts +0 -39
- package/dist/vue/composables/useStrandsMfa.d.ts.map +0 -1
- package/dist/vue/index.d.ts +0 -13
- package/dist/vue/index.d.ts.map +0 -1
- package/dist/vue/plugins/StrandsUIPlugin.d.ts +0 -20
- package/dist/vue/plugins/StrandsUIPlugin.d.ts.map +0 -1
- package/dist/vue/ui/UiAlert.vue.d.ts +0 -32
- package/dist/vue/ui/UiAlert.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiAvatarEditor.vue.d.ts +0 -26
- package/dist/vue/ui/UiAvatarEditor.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiButton.vue.d.ts +0 -55
- package/dist/vue/ui/UiButton.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiCard.vue.d.ts +0 -30
- package/dist/vue/ui/UiCard.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiInput.vue.d.ts +0 -49
- package/dist/vue/ui/UiInput.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiLevelProgress.vue.d.ts +0 -20
- package/dist/vue/ui/UiLevelProgress.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiLink.vue.d.ts +0 -43
- package/dist/vue/ui/UiLink.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiLoader.vue.d.ts +0 -16
- package/dist/vue/ui/UiLoader.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiModal.vue.d.ts +0 -34
- package/dist/vue/ui/UiModal.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiTabs.vue.d.ts +0 -18
- package/dist/vue/ui/UiTabs.vue.d.ts.map +0 -1
- package/dist/vue/ui/UiToggle.vue.d.ts +0 -16
- package/dist/vue/ui/UiToggle.vue.d.ts.map +0 -1
- package/dist/vue/ui/index.d.ts +0 -30
- package/dist/vue/ui/index.d.ts.map +0 -1
- package/dist/vue/utils/contrast.d.ts +0 -80
- package/dist/vue/utils/contrast.d.ts.map +0 -1
- package/dist/vue/utils/debounce.d.ts +0 -13
- package/dist/vue/utils/debounce.d.ts.map +0 -1
- package/dist/vue/utils/fontPreloader.d.ts +0 -20
- package/dist/vue/utils/fontPreloader.d.ts.map +0 -1
- package/dist/vue/utils/iconProps.d.ts +0 -10
- package/dist/vue/utils/iconProps.d.ts.map +0 -1
- package/dist/vue/utils/lazyComponents.d.ts +0 -3
- package/dist/vue/utils/lazyComponents.d.ts.map +0 -1
- package/dist/vue/utils/levels.d.ts +0 -28
- package/dist/vue/utils/levels.d.ts.map +0 -1
- package/dist/vue/utils/performanceInit.d.ts +0 -41
- package/dist/vue/utils/performanceInit.d.ts.map +0 -1
- package/dist/vue/utils/requestCache.d.ts +0 -50
- package/dist/vue/utils/requestCache.d.ts.map +0 -1
- package/dist/vue/utils/sounds.d.ts +0 -57
- package/dist/vue/utils/sounds.d.ts.map +0 -1
|
@@ -0,0 +1,1609 @@
|
|
|
1
|
+
import { computed, ref, onMounted, watch, inject, provide, onUnmounted } from "vue";
|
|
2
|
+
import { u as useRequestCache, d as debouncedSetItem } from "./utils-BZHeJkZf.js";
|
|
3
|
+
const currentTheme = ref("system");
|
|
4
|
+
const systemPrefersDark = ref(false);
|
|
5
|
+
const STORAGE_KEY = "strands-ui-theme";
|
|
6
|
+
function getSystemPreference() {
|
|
7
|
+
if (typeof window === "undefined") return false;
|
|
8
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
9
|
+
}
|
|
10
|
+
function getStoredTheme() {
|
|
11
|
+
if (typeof window === "undefined") return "system";
|
|
12
|
+
try {
|
|
13
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
14
|
+
if (stored && ["light", "dark", "system"].includes(stored)) {
|
|
15
|
+
return stored;
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.warn("Failed to read theme preference from localStorage:", error);
|
|
19
|
+
}
|
|
20
|
+
return "system";
|
|
21
|
+
}
|
|
22
|
+
function setStoredTheme(theme) {
|
|
23
|
+
if (typeof window === "undefined") return;
|
|
24
|
+
try {
|
|
25
|
+
localStorage.setItem(STORAGE_KEY, theme);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.warn("Failed to save theme preference to localStorage:", error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function applyTheme(isDark) {
|
|
31
|
+
if (typeof document === "undefined") return;
|
|
32
|
+
if (isDark) {
|
|
33
|
+
document.documentElement.setAttribute("data-theme", "dark");
|
|
34
|
+
document.documentElement.classList.add("dark");
|
|
35
|
+
} else {
|
|
36
|
+
document.documentElement.setAttribute("data-theme", "light");
|
|
37
|
+
document.documentElement.classList.remove("dark");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function useDarkMode() {
|
|
41
|
+
const isDark = computed(() => {
|
|
42
|
+
if (currentTheme.value === "dark") return true;
|
|
43
|
+
if (currentTheme.value === "light") return false;
|
|
44
|
+
return systemPrefersDark.value;
|
|
45
|
+
});
|
|
46
|
+
const themeLabel = computed(() => {
|
|
47
|
+
switch (currentTheme.value) {
|
|
48
|
+
case "light":
|
|
49
|
+
return "Light";
|
|
50
|
+
case "dark":
|
|
51
|
+
return "Dark";
|
|
52
|
+
case "system":
|
|
53
|
+
return "System";
|
|
54
|
+
default:
|
|
55
|
+
return "System";
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
function setTheme(theme) {
|
|
59
|
+
currentTheme.value = theme;
|
|
60
|
+
setStoredTheme(theme);
|
|
61
|
+
}
|
|
62
|
+
function toggle() {
|
|
63
|
+
const newTheme = isDark.value ? "light" : "dark";
|
|
64
|
+
setTheme(newTheme);
|
|
65
|
+
}
|
|
66
|
+
function cycleTheme() {
|
|
67
|
+
switch (currentTheme.value) {
|
|
68
|
+
case "light":
|
|
69
|
+
setTheme("dark");
|
|
70
|
+
break;
|
|
71
|
+
case "dark":
|
|
72
|
+
setTheme("system");
|
|
73
|
+
break;
|
|
74
|
+
case "system":
|
|
75
|
+
setTheme("light");
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
setTheme("light");
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function initialize() {
|
|
83
|
+
if (typeof window === "undefined") return;
|
|
84
|
+
systemPrefersDark.value = getSystemPreference();
|
|
85
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
86
|
+
const handleSystemThemeChange = (e) => {
|
|
87
|
+
systemPrefersDark.value = e.matches;
|
|
88
|
+
};
|
|
89
|
+
if (mediaQuery.addEventListener) {
|
|
90
|
+
mediaQuery.addEventListener("change", handleSystemThemeChange);
|
|
91
|
+
} else {
|
|
92
|
+
mediaQuery.addListener(handleSystemThemeChange);
|
|
93
|
+
}
|
|
94
|
+
currentTheme.value = getStoredTheme();
|
|
95
|
+
applyTheme(isDark.value);
|
|
96
|
+
watch(isDark, (newIsDark) => {
|
|
97
|
+
applyTheme(newIsDark);
|
|
98
|
+
}, { immediate: false });
|
|
99
|
+
}
|
|
100
|
+
onMounted(() => {
|
|
101
|
+
initialize();
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
// State
|
|
105
|
+
currentTheme: computed(() => currentTheme.value),
|
|
106
|
+
isDark,
|
|
107
|
+
themeLabel,
|
|
108
|
+
// Actions
|
|
109
|
+
setTheme,
|
|
110
|
+
toggle,
|
|
111
|
+
cycleTheme,
|
|
112
|
+
initialize,
|
|
113
|
+
// Theme options for UI
|
|
114
|
+
themeOptions: [
|
|
115
|
+
{ value: "light", label: "Light", icon: "sun" },
|
|
116
|
+
{ value: "dark", label: "Dark", icon: "moon" },
|
|
117
|
+
{ value: "system", label: "System", icon: "monitor" }
|
|
118
|
+
]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
let globalInstance = null;
|
|
122
|
+
function useGlobalDarkMode() {
|
|
123
|
+
if (!globalInstance) {
|
|
124
|
+
globalInstance = useDarkMode();
|
|
125
|
+
if (typeof window !== "undefined") {
|
|
126
|
+
globalInstance.initialize();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return globalInstance;
|
|
130
|
+
}
|
|
131
|
+
const STRANDS_AUTH_DEFAULTS = {
|
|
132
|
+
baseUrl: "https://your-api.example.com",
|
|
133
|
+
accentColor: "#EA00A8",
|
|
134
|
+
redirectUrl: "/",
|
|
135
|
+
onSignInUrl: "/dashboard",
|
|
136
|
+
onSignOutUrl: "/",
|
|
137
|
+
autoRefresh: true,
|
|
138
|
+
refreshInterval: 4,
|
|
139
|
+
protectedRoutes: [],
|
|
140
|
+
guestOnlyRoutes: ["/auth", "/login", "/register"],
|
|
141
|
+
devMode: false,
|
|
142
|
+
styles: true,
|
|
143
|
+
endpoints: {},
|
|
144
|
+
useSquircle: true
|
|
145
|
+
};
|
|
146
|
+
const DEFAULT_ENDPOINTS = {
|
|
147
|
+
signIn: "/api/v1/auth/sign-in",
|
|
148
|
+
signUp: "/api/v1/auth/sign-up",
|
|
149
|
+
signOut: "/api/v1/auth/sign-out",
|
|
150
|
+
refresh: "/api/v1/auth/refresh",
|
|
151
|
+
passwordReset: "/api/v1/auth/password-reset",
|
|
152
|
+
passwordResetConfirm: "/api/v1/auth/password-reset/confirm",
|
|
153
|
+
completeRegistration: "/api/v1/auth/complete-registration",
|
|
154
|
+
profile: "/api/v1/user/profile",
|
|
155
|
+
verifyEmail: "/api/v1/auth/verify-email",
|
|
156
|
+
oauthProviders: "/api/v1/oauth/providers",
|
|
157
|
+
oauthProvider: "/api/v1/oauth/providers/{provider_id}",
|
|
158
|
+
changeEmail: "/api/v1/user/change-email",
|
|
159
|
+
avatar: "/api/v1/user/avatar",
|
|
160
|
+
settings: "/api/v1/user/settings",
|
|
161
|
+
// Username endpoints
|
|
162
|
+
changeUsername: "/api/v1/user/username",
|
|
163
|
+
usernameCooldown: "/api/v1/user/username/cooldown",
|
|
164
|
+
checkUsernameAvailability: "/api/v1/username/{username}/available",
|
|
165
|
+
// MFA endpoints
|
|
166
|
+
mfaDevices: "/api/v1/mfa/devices",
|
|
167
|
+
mfaTotpSetup: "/api/v1/mfa/totp/setup",
|
|
168
|
+
mfaTotpVerify: "/api/v1/mfa/totp/verify",
|
|
169
|
+
mfaEmailSetup: "/api/v1/mfa/email/setup",
|
|
170
|
+
mfaEmailSend: "/api/v1/mfa/email/send",
|
|
171
|
+
mfaEmailVerify: "/api/v1/mfa/email/verify",
|
|
172
|
+
mfaDeviceDisable: "/api/v1/mfa/device/disable",
|
|
173
|
+
mfaBackupCodes: "/api/v1/mfa/backup-codes/regenerate",
|
|
174
|
+
// Hardware key endpoints
|
|
175
|
+
mfaHardwareStartRegistration: "/api/v1/mfa/hardware/start-registration",
|
|
176
|
+
mfaHardwareCompleteRegistration: "/api/v1/mfa/hardware/complete-registration",
|
|
177
|
+
// MFA sign-in specific endpoints
|
|
178
|
+
mfaSigninSendEmail: "/api/v1/auth/mfa/email/send",
|
|
179
|
+
mfaSigninVerify: "/api/v1/auth/mfa/verify",
|
|
180
|
+
mfaBackupCodeVerify: "/api/v1/auth/mfa/backup-code/verify",
|
|
181
|
+
mfaWebAuthnChallenge: "/api/v1/auth/mfa/webauthn/challenge",
|
|
182
|
+
// Session management endpoints
|
|
183
|
+
sessions: "/api/v1/sessions",
|
|
184
|
+
sessionsStats: "/api/v1/sessions/stats",
|
|
185
|
+
sessionRevoke: "/api/v1/sessions/{session_id}/revoke",
|
|
186
|
+
sessionsRevokeAll: "/api/v1/sessions/revoke-all"
|
|
187
|
+
};
|
|
188
|
+
const STRANDS_CONFIG_KEY = Symbol("strands-config");
|
|
189
|
+
const globalConfig = ref(null);
|
|
190
|
+
function generateColorShades(accentColor) {
|
|
191
|
+
if (typeof window === "undefined" || !document.documentElement) return;
|
|
192
|
+
document.documentElement.style.setProperty("--strands-accent", accentColor);
|
|
193
|
+
document.documentElement.style.setProperty("--accui-strands-accent", accentColor);
|
|
194
|
+
document.documentElement.style.setProperty("--accui-strands-50", `color-mix(in srgb, ${accentColor} 10%, white)`);
|
|
195
|
+
document.documentElement.style.setProperty("--accui-strands-100", `color-mix(in srgb, ${accentColor} 20%, white)`);
|
|
196
|
+
document.documentElement.style.setProperty("--accui-strands-200", `color-mix(in srgb, ${accentColor} 30%, white)`);
|
|
197
|
+
document.documentElement.style.setProperty("--accui-strands-300", `color-mix(in srgb, ${accentColor} 40%, white)`);
|
|
198
|
+
document.documentElement.style.setProperty("--accui-strands-400", `color-mix(in srgb, ${accentColor} 70%, white)`);
|
|
199
|
+
document.documentElement.style.setProperty("--accui-strands-500", accentColor);
|
|
200
|
+
document.documentElement.style.setProperty("--accui-strands-600", `color-mix(in srgb, ${accentColor} 85%, black)`);
|
|
201
|
+
document.documentElement.style.setProperty("--accui-strands-700", `color-mix(in srgb, ${accentColor} 70%, black)`);
|
|
202
|
+
document.documentElement.style.setProperty("--accui-strands-800", `color-mix(in srgb, ${accentColor} 55%, black)`);
|
|
203
|
+
document.documentElement.style.setProperty("--accui-strands-900", `color-mix(in srgb, ${accentColor} 40%, black)`);
|
|
204
|
+
document.documentElement.style.setProperty("--accui-strands-950", `color-mix(in srgb, ${accentColor} 25%, black)`);
|
|
205
|
+
}
|
|
206
|
+
function provideStrandsConfig(config) {
|
|
207
|
+
globalConfig.value = config;
|
|
208
|
+
if (typeof window !== "undefined" && document.documentElement) {
|
|
209
|
+
const useSquircle = config.useSquircle !== void 0 ? config.useSquircle : true;
|
|
210
|
+
document.documentElement.style.setProperty("--strands-allow-squircle", useSquircle ? "1" : "0");
|
|
211
|
+
if (config.accentColor) {
|
|
212
|
+
generateColorShades(config.accentColor);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
provide(STRANDS_CONFIG_KEY, config);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.warn("[Strands Auth] Could not provide config via Vue provide/inject. Config available via global state only.", error);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function useStrandsConfig(fallbackConfig) {
|
|
222
|
+
const injectedConfig = inject(STRANDS_CONFIG_KEY, null);
|
|
223
|
+
let nuxtConfig = null;
|
|
224
|
+
try {
|
|
225
|
+
if (typeof window !== "undefined") {
|
|
226
|
+
if (window.__STRANDS_CONFIG__) {
|
|
227
|
+
nuxtConfig = window.__STRANDS_CONFIG__;
|
|
228
|
+
} else if (window.__NUXT__) {
|
|
229
|
+
const nuxtData = window.__NUXT__;
|
|
230
|
+
nuxtConfig = nuxtData?.config?.public?.strandsAuth || nuxtData?.public?.strandsAuth || nuxtData?.strandsAuth;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error("[Strands Auth] Error accessing runtime configuration:", error);
|
|
235
|
+
}
|
|
236
|
+
const activeConfig = computed(() => {
|
|
237
|
+
const config = {
|
|
238
|
+
...STRANDS_AUTH_DEFAULTS,
|
|
239
|
+
...fallbackConfig || {},
|
|
240
|
+
...injectedConfig || {},
|
|
241
|
+
...globalConfig.value || {},
|
|
242
|
+
...nuxtConfig || {}
|
|
243
|
+
};
|
|
244
|
+
if (config.baseUrl === STRANDS_AUTH_DEFAULTS.baseUrl && typeof window === "undefined") {
|
|
245
|
+
console.warn("[Strands Auth] No baseUrl configured for SSR. Please provide a baseUrl in your strandsAuth configuration.");
|
|
246
|
+
}
|
|
247
|
+
if (typeof window !== "undefined" && document.documentElement) {
|
|
248
|
+
const useSquircle = config.useSquircle !== void 0 ? config.useSquircle : true;
|
|
249
|
+
document.documentElement.style.setProperty("--strands-allow-squircle", useSquircle ? "1" : "0");
|
|
250
|
+
if (config.accentColor) {
|
|
251
|
+
document.documentElement.style.setProperty("--strands-accent", config.accentColor);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return config;
|
|
255
|
+
});
|
|
256
|
+
const endpoints = computed(() => {
|
|
257
|
+
const config = activeConfig.value;
|
|
258
|
+
const customEndpoints = config.endpoints || {};
|
|
259
|
+
return {
|
|
260
|
+
signIn: customEndpoints.signIn || DEFAULT_ENDPOINTS.signIn,
|
|
261
|
+
signUp: customEndpoints.signUp || DEFAULT_ENDPOINTS.signUp,
|
|
262
|
+
signOut: customEndpoints.signOut || DEFAULT_ENDPOINTS.signOut,
|
|
263
|
+
refresh: customEndpoints.refresh || DEFAULT_ENDPOINTS.refresh,
|
|
264
|
+
passwordReset: customEndpoints.passwordReset || DEFAULT_ENDPOINTS.passwordReset,
|
|
265
|
+
passwordResetConfirm: customEndpoints.passwordResetConfirm || DEFAULT_ENDPOINTS.passwordResetConfirm,
|
|
266
|
+
completeRegistration: customEndpoints.completeRegistration || DEFAULT_ENDPOINTS.completeRegistration,
|
|
267
|
+
profile: customEndpoints.profile || DEFAULT_ENDPOINTS.profile,
|
|
268
|
+
verifyEmail: customEndpoints.verifyEmail || DEFAULT_ENDPOINTS.verifyEmail,
|
|
269
|
+
oauthProviders: customEndpoints.oauthProviders || DEFAULT_ENDPOINTS.oauthProviders,
|
|
270
|
+
oauthProvider: customEndpoints.oauthProvider || DEFAULT_ENDPOINTS.oauthProvider,
|
|
271
|
+
changeEmail: customEndpoints.changeEmail || DEFAULT_ENDPOINTS.changeEmail,
|
|
272
|
+
avatar: customEndpoints.avatar || DEFAULT_ENDPOINTS.avatar,
|
|
273
|
+
settings: customEndpoints.settings || DEFAULT_ENDPOINTS.settings,
|
|
274
|
+
// Username endpoints
|
|
275
|
+
changeUsername: customEndpoints.changeUsername || DEFAULT_ENDPOINTS.changeUsername,
|
|
276
|
+
usernameCooldown: customEndpoints.usernameCooldown || DEFAULT_ENDPOINTS.usernameCooldown,
|
|
277
|
+
checkUsernameAvailability: customEndpoints.checkUsernameAvailability || DEFAULT_ENDPOINTS.checkUsernameAvailability,
|
|
278
|
+
// MFA endpoints
|
|
279
|
+
mfaDevices: customEndpoints.mfaDevices || DEFAULT_ENDPOINTS.mfaDevices,
|
|
280
|
+
mfaTotpSetup: customEndpoints.mfaTotpSetup || DEFAULT_ENDPOINTS.mfaTotpSetup,
|
|
281
|
+
mfaTotpVerify: customEndpoints.mfaTotpVerify || DEFAULT_ENDPOINTS.mfaTotpVerify,
|
|
282
|
+
mfaEmailSetup: customEndpoints.mfaEmailSetup || DEFAULT_ENDPOINTS.mfaEmailSetup,
|
|
283
|
+
mfaEmailSend: customEndpoints.mfaEmailSend || DEFAULT_ENDPOINTS.mfaEmailSend,
|
|
284
|
+
mfaEmailVerify: customEndpoints.mfaEmailVerify || DEFAULT_ENDPOINTS.mfaEmailVerify,
|
|
285
|
+
mfaDeviceDisable: customEndpoints.mfaDeviceDisable || DEFAULT_ENDPOINTS.mfaDeviceDisable,
|
|
286
|
+
mfaBackupCodes: customEndpoints.mfaBackupCodes || DEFAULT_ENDPOINTS.mfaBackupCodes,
|
|
287
|
+
// Hardware key endpoints
|
|
288
|
+
mfaHardwareStartRegistration: customEndpoints.mfaHardwareStartRegistration || DEFAULT_ENDPOINTS.mfaHardwareStartRegistration,
|
|
289
|
+
mfaHardwareCompleteRegistration: customEndpoints.mfaHardwareCompleteRegistration || DEFAULT_ENDPOINTS.mfaHardwareCompleteRegistration,
|
|
290
|
+
// MFA sign-in specific endpoints
|
|
291
|
+
mfaSigninSendEmail: customEndpoints.mfaSigninSendEmail || DEFAULT_ENDPOINTS.mfaSigninSendEmail,
|
|
292
|
+
mfaSigninVerify: customEndpoints.mfaSigninVerify || DEFAULT_ENDPOINTS.mfaSigninVerify,
|
|
293
|
+
mfaBackupCodeVerify: customEndpoints.mfaBackupCodeVerify || DEFAULT_ENDPOINTS.mfaBackupCodeVerify,
|
|
294
|
+
mfaWebAuthnChallenge: customEndpoints.mfaWebAuthnChallenge || DEFAULT_ENDPOINTS.mfaWebAuthnChallenge,
|
|
295
|
+
// Session management endpoints
|
|
296
|
+
sessions: customEndpoints.sessions || DEFAULT_ENDPOINTS.sessions,
|
|
297
|
+
sessionsStats: customEndpoints.sessionsStats || DEFAULT_ENDPOINTS.sessionsStats,
|
|
298
|
+
sessionRevoke: customEndpoints.sessionRevoke || DEFAULT_ENDPOINTS.sessionRevoke,
|
|
299
|
+
sessionsRevokeAll: customEndpoints.sessionsRevokeAll || DEFAULT_ENDPOINTS.sessionsRevokeAll
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
const getUrl = (endpoint) => {
|
|
303
|
+
const config = activeConfig.value;
|
|
304
|
+
if (!config.baseUrl) {
|
|
305
|
+
throw new Error("Base URL is required in configuration");
|
|
306
|
+
}
|
|
307
|
+
let endpointPath;
|
|
308
|
+
if (typeof endpoint === "string" && endpoint in endpoints.value) {
|
|
309
|
+
endpointPath = endpoints.value[endpoint];
|
|
310
|
+
} else if (typeof endpoint === "string") {
|
|
311
|
+
endpointPath = endpoint;
|
|
312
|
+
} else {
|
|
313
|
+
endpointPath = endpoints.value[endpoint];
|
|
314
|
+
}
|
|
315
|
+
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
316
|
+
const path = endpointPath.startsWith("/") ? endpointPath : `/${endpointPath}`;
|
|
317
|
+
console.debug(`[Strands Auth] Constructing URL for endpoint "${endpoint}": ${baseUrl}${path}`);
|
|
318
|
+
return `${baseUrl}${path}`;
|
|
319
|
+
};
|
|
320
|
+
const getSupportEmail = () => {
|
|
321
|
+
const config = activeConfig.value;
|
|
322
|
+
return config.supportEmail || null;
|
|
323
|
+
};
|
|
324
|
+
return {
|
|
325
|
+
config: activeConfig,
|
|
326
|
+
endpoints,
|
|
327
|
+
getUrl,
|
|
328
|
+
getSupportEmail
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function setStrandsConfig(config) {
|
|
332
|
+
globalConfig.value = config;
|
|
333
|
+
if (typeof window !== "undefined" && document.documentElement) {
|
|
334
|
+
const useSquircle = config.useSquircle !== void 0 ? config.useSquircle : true;
|
|
335
|
+
document.documentElement.style.setProperty("--strands-allow-squircle", useSquircle ? "1" : "0");
|
|
336
|
+
if (config.accentColor) {
|
|
337
|
+
generateColorShades(config.accentColor);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const mapApiUserToFrontend = (apiUser) => {
|
|
342
|
+
return {
|
|
343
|
+
id: apiUser.id,
|
|
344
|
+
email: apiUser.email,
|
|
345
|
+
firstName: apiUser.first_name || apiUser.firstName || "",
|
|
346
|
+
lastName: apiUser.last_name || apiUser.lastName || "",
|
|
347
|
+
avatar: apiUser.avatar_url || apiUser.avatar,
|
|
348
|
+
mfaEnabled: apiUser.mfa_enabled ?? apiUser.mfaEnabled ?? false,
|
|
349
|
+
emailVerified: apiUser.email_verified ?? apiUser.emailVerified ?? false,
|
|
350
|
+
passwordUpdatedAt: apiUser.password_updated_at || apiUser.passwordUpdatedAt,
|
|
351
|
+
settings: apiUser.settings || {},
|
|
352
|
+
xp: apiUser.xp || 0,
|
|
353
|
+
level: apiUser.level || 1,
|
|
354
|
+
next_level_xp: apiUser.next_level_xp || apiUser.next_level_xp || 4,
|
|
355
|
+
username: apiUser.username,
|
|
356
|
+
usernameLastChangedAt: apiUser.username_last_changed_at || apiUser.usernameLastChangedAt,
|
|
357
|
+
createdAt: apiUser.created_at || apiUser.createdAt,
|
|
358
|
+
updatedAt: apiUser.updated_at || apiUser.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
359
|
+
};
|
|
360
|
+
};
|
|
361
|
+
const globalState = {
|
|
362
|
+
currentUser: ref(null),
|
|
363
|
+
currentSession: ref(null),
|
|
364
|
+
loadingStates: ref({
|
|
365
|
+
initializing: true,
|
|
366
|
+
signingIn: false,
|
|
367
|
+
signingUp: false,
|
|
368
|
+
signingOut: false,
|
|
369
|
+
refreshingToken: false,
|
|
370
|
+
sendingMfaEmail: false,
|
|
371
|
+
verifyingMfa: false,
|
|
372
|
+
loadingProfile: false
|
|
373
|
+
}),
|
|
374
|
+
isInitialized: ref(false),
|
|
375
|
+
mfaRequired: ref(false),
|
|
376
|
+
mfaSessionId: ref(null),
|
|
377
|
+
availableMfaMethods: ref([])
|
|
378
|
+
};
|
|
379
|
+
let refreshTimer = null;
|
|
380
|
+
let refreshPromise = null;
|
|
381
|
+
function useStrandsAuth() {
|
|
382
|
+
const { getUrl } = useStrandsConfig();
|
|
383
|
+
const { fetch: cachedFetch, clear: clearCache, invalidate } = useRequestCache();
|
|
384
|
+
const { currentUser, currentSession, loadingStates, isInitialized, mfaRequired, mfaSessionId, availableMfaMethods } = globalState;
|
|
385
|
+
const isInitializing = computed(() => loadingStates.value.initializing);
|
|
386
|
+
const isSigningIn = computed(() => loadingStates.value.signingIn);
|
|
387
|
+
const isSigningUp = computed(() => loadingStates.value.signingUp);
|
|
388
|
+
const isSigningOut = computed(() => loadingStates.value.signingOut);
|
|
389
|
+
const isRefreshingToken = computed(() => loadingStates.value.refreshingToken);
|
|
390
|
+
const isSendingMfaEmail = computed(() => loadingStates.value.sendingMfaEmail);
|
|
391
|
+
const isVerifyingMfa = computed(() => loadingStates.value.verifyingMfa);
|
|
392
|
+
computed(() => loadingStates.value.loadingProfile);
|
|
393
|
+
const loading2 = computed(
|
|
394
|
+
() => loadingStates.value.signingIn || loadingStates.value.signingUp || loadingStates.value.signingOut || loadingStates.value.refreshingToken || loadingStates.value.sendingMfaEmail || loadingStates.value.verifyingMfa || loadingStates.value.loadingProfile
|
|
395
|
+
);
|
|
396
|
+
const isLoading = computed(() => loadingStates.value.initializing || loading2.value);
|
|
397
|
+
const loadingMessage = computed(() => {
|
|
398
|
+
const states = loadingStates.value;
|
|
399
|
+
if (states.initializing) return "Checking authentication...";
|
|
400
|
+
if (states.signingIn) return "Signing you in...";
|
|
401
|
+
if (states.signingUp) return "Creating your account...";
|
|
402
|
+
if (states.signingOut) return "Signing you out...";
|
|
403
|
+
if (states.refreshingToken) return "Refreshing session...";
|
|
404
|
+
if (states.sendingMfaEmail) return "Sending verification code...";
|
|
405
|
+
if (states.verifyingMfa) return "Verifying code...";
|
|
406
|
+
if (states.loadingProfile) return "Loading profile...";
|
|
407
|
+
return "Loading...";
|
|
408
|
+
});
|
|
409
|
+
const getAuthHeaders = () => {
|
|
410
|
+
const headers = {};
|
|
411
|
+
if (currentSession.value?.accessToken) {
|
|
412
|
+
headers["Authorization"] = `Bearer ${currentSession.value.accessToken}`;
|
|
413
|
+
}
|
|
414
|
+
if (currentSession.value?.refreshToken) {
|
|
415
|
+
headers["x-refresh-token"] = currentSession.value.refreshToken;
|
|
416
|
+
}
|
|
417
|
+
return headers;
|
|
418
|
+
};
|
|
419
|
+
const completeHardwareKeyRegistration = async (deviceId, credential, accessToken) => {
|
|
420
|
+
const response = await fetch(getUrl("mfaHardwareCompleteRegistration"), {
|
|
421
|
+
method: "POST",
|
|
422
|
+
headers: {
|
|
423
|
+
"Content-Type": "application/json",
|
|
424
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
425
|
+
},
|
|
426
|
+
body: JSON.stringify({
|
|
427
|
+
device_id: deviceId,
|
|
428
|
+
credential
|
|
429
|
+
})
|
|
430
|
+
});
|
|
431
|
+
if (!response.ok) {
|
|
432
|
+
const errorText = await response.text();
|
|
433
|
+
let errorMessage = "Failed to complete hardware key registration";
|
|
434
|
+
try {
|
|
435
|
+
const errorData = JSON.parse(errorText);
|
|
436
|
+
errorMessage = errorData.message || errorData.error || errorText;
|
|
437
|
+
} catch {
|
|
438
|
+
errorMessage = errorText || "Failed to complete hardware key registration";
|
|
439
|
+
}
|
|
440
|
+
throw new Error(errorMessage);
|
|
441
|
+
}
|
|
442
|
+
return response.json();
|
|
443
|
+
};
|
|
444
|
+
const registerHardwareKey = async (deviceName, accessToken, deviceType = "hardware") => {
|
|
445
|
+
const response = await fetch(getUrl("mfaHardwareStartRegistration"), {
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: {
|
|
448
|
+
"Content-Type": "application/json",
|
|
449
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
450
|
+
},
|
|
451
|
+
body: JSON.stringify({
|
|
452
|
+
device_name: deviceName,
|
|
453
|
+
device_type: deviceType
|
|
454
|
+
})
|
|
455
|
+
});
|
|
456
|
+
if (!response.ok) {
|
|
457
|
+
const errorText = await response.text();
|
|
458
|
+
let errorMessage = "Failed to start hardware key registration";
|
|
459
|
+
try {
|
|
460
|
+
const errorData = JSON.parse(errorText);
|
|
461
|
+
errorMessage = errorData.message || errorData.error || errorText;
|
|
462
|
+
} catch {
|
|
463
|
+
errorMessage = errorText || "Failed to start hardware key registration";
|
|
464
|
+
}
|
|
465
|
+
throw new Error(errorMessage);
|
|
466
|
+
}
|
|
467
|
+
return response.json();
|
|
468
|
+
};
|
|
469
|
+
const isAuthenticated = computed(() => currentUser.value !== null);
|
|
470
|
+
const signIn = async (credentials) => {
|
|
471
|
+
loadingStates.value.signingIn = true;
|
|
472
|
+
try {
|
|
473
|
+
mfaRequired.value = false;
|
|
474
|
+
mfaSessionId.value = null;
|
|
475
|
+
availableMfaMethods.value = [];
|
|
476
|
+
const headers = {
|
|
477
|
+
"Content-Type": "application/json"
|
|
478
|
+
};
|
|
479
|
+
if (typeof window !== "undefined" && window.location) {
|
|
480
|
+
headers["Origin"] = window.location.origin;
|
|
481
|
+
}
|
|
482
|
+
const response = await fetch(getUrl("signIn"), {
|
|
483
|
+
method: "POST",
|
|
484
|
+
headers,
|
|
485
|
+
body: JSON.stringify(credentials)
|
|
486
|
+
});
|
|
487
|
+
if (!response.ok) {
|
|
488
|
+
if (response.status === 401) {
|
|
489
|
+
throw new Error("Invalid email or password");
|
|
490
|
+
} else if (response.status === 403) {
|
|
491
|
+
throw new Error("Please verify your email address before signing in");
|
|
492
|
+
} else {
|
|
493
|
+
throw new Error(`Sign in failed: ${response.status} ${response.statusText}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const authData = await response.json();
|
|
497
|
+
if (authData.mfa_required) {
|
|
498
|
+
mfaRequired.value = true;
|
|
499
|
+
mfaSessionId.value = authData.mfa_session_id || null;
|
|
500
|
+
const methods = (authData.available_mfa_methods || []).map((method) => {
|
|
501
|
+
let fallbackName = `${method.device_type.charAt(0).toUpperCase() + method.device_type.slice(1)} Authentication`;
|
|
502
|
+
if (method.device_type === "hardware") {
|
|
503
|
+
fallbackName = method.device_name || "Security Key";
|
|
504
|
+
} else if (method.device_type === "totp") {
|
|
505
|
+
fallbackName = method.device_name || "Authenticator App";
|
|
506
|
+
} else if (method.device_type === "email") {
|
|
507
|
+
fallbackName = method.device_name || "Email Verification";
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
id: method.device_id,
|
|
511
|
+
device_type: method.device_type,
|
|
512
|
+
device_name: method.device_name || fallbackName,
|
|
513
|
+
is_active: true,
|
|
514
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
515
|
+
last_used_at: method.last_used_at,
|
|
516
|
+
// Pass through additional metadata if available
|
|
517
|
+
credential_id: method.credential_id,
|
|
518
|
+
device_info: method.device_info
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
availableMfaMethods.value = methods;
|
|
522
|
+
loadingStates.value.signingIn = false;
|
|
523
|
+
return authData;
|
|
524
|
+
}
|
|
525
|
+
await setAuthData(authData);
|
|
526
|
+
return authData;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
throw error;
|
|
529
|
+
} finally {
|
|
530
|
+
loadingStates.value.signingIn = false;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
const signUp = async (userData) => {
|
|
534
|
+
loadingStates.value.signingUp = true;
|
|
535
|
+
try {
|
|
536
|
+
throw new Error("Sign up not implemented - please integrate with auth SDK");
|
|
537
|
+
} finally {
|
|
538
|
+
loadingStates.value.signingUp = false;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
const signOut = async () => {
|
|
542
|
+
loadingStates.value.signingOut = true;
|
|
543
|
+
try {
|
|
544
|
+
stopTokenRefreshTimer();
|
|
545
|
+
refreshPromise = null;
|
|
546
|
+
clearCache();
|
|
547
|
+
currentUser.value = null;
|
|
548
|
+
currentSession.value = null;
|
|
549
|
+
mfaRequired.value = false;
|
|
550
|
+
mfaSessionId.value = null;
|
|
551
|
+
availableMfaMethods.value = [];
|
|
552
|
+
if (typeof window !== "undefined") {
|
|
553
|
+
localStorage.removeItem("strands_auth_session");
|
|
554
|
+
localStorage.removeItem("strands_auth_user");
|
|
555
|
+
}
|
|
556
|
+
} finally {
|
|
557
|
+
loadingStates.value.signingOut = false;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
const refreshToken = async () => {
|
|
561
|
+
if (!currentSession.value?.refreshToken) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
if (refreshPromise) {
|
|
565
|
+
console.log("Token refresh already in progress, waiting for completion...");
|
|
566
|
+
return await refreshPromise;
|
|
567
|
+
}
|
|
568
|
+
refreshPromise = (async () => {
|
|
569
|
+
loadingStates.value.refreshingToken = true;
|
|
570
|
+
try {
|
|
571
|
+
const response = await fetch(getUrl("refresh"), {
|
|
572
|
+
method: "POST",
|
|
573
|
+
headers: {
|
|
574
|
+
"Content-Type": "application/json"
|
|
575
|
+
},
|
|
576
|
+
body: JSON.stringify({
|
|
577
|
+
refresh_token: currentSession.value.refreshToken
|
|
578
|
+
})
|
|
579
|
+
});
|
|
580
|
+
if (!response.ok) {
|
|
581
|
+
if (response.status === 401) {
|
|
582
|
+
await signOut();
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
|
|
586
|
+
}
|
|
587
|
+
const tokenData = await response.json();
|
|
588
|
+
if (tokenData.user) {
|
|
589
|
+
currentUser.value = mapApiUserToFrontend(tokenData.user);
|
|
590
|
+
if (currentUser.value && typeof window !== "undefined") {
|
|
591
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const newSession = {
|
|
595
|
+
accessToken: tokenData.access_token,
|
|
596
|
+
refreshToken: tokenData.refresh_token,
|
|
597
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1e3),
|
|
598
|
+
// 5 minutes from now
|
|
599
|
+
userId: tokenData.user?.id || currentUser.value?.id
|
|
600
|
+
};
|
|
601
|
+
currentSession.value = newSession;
|
|
602
|
+
if (typeof window !== "undefined") {
|
|
603
|
+
localStorage.setItem("strands_auth_session", JSON.stringify(newSession));
|
|
604
|
+
}
|
|
605
|
+
startTokenRefreshTimer();
|
|
606
|
+
invalidate(`sessions:${currentSession.value.accessToken.slice(0, 20)}`);
|
|
607
|
+
return true;
|
|
608
|
+
} catch (error) {
|
|
609
|
+
await signOut();
|
|
610
|
+
return false;
|
|
611
|
+
} finally {
|
|
612
|
+
loadingStates.value.refreshingToken = false;
|
|
613
|
+
}
|
|
614
|
+
})();
|
|
615
|
+
const result = await refreshPromise;
|
|
616
|
+
refreshPromise = null;
|
|
617
|
+
return result;
|
|
618
|
+
};
|
|
619
|
+
const fetchProfile = async () => {
|
|
620
|
+
const cacheKey = `profile:${currentSession.value.accessToken.slice(0, 20)}`;
|
|
621
|
+
loadingStates.value.loadingProfile = true;
|
|
622
|
+
try {
|
|
623
|
+
return await cachedFetch(cacheKey, async () => {
|
|
624
|
+
const response = await fetch(getUrl("profile"), {
|
|
625
|
+
method: "GET",
|
|
626
|
+
headers: {
|
|
627
|
+
"Content-Type": "application/json",
|
|
628
|
+
"Authorization": `Bearer ${currentSession.value?.accessToken}`
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
if (!response.ok) {
|
|
632
|
+
if (response.status === 401) {
|
|
633
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
634
|
+
} else {
|
|
635
|
+
throw new Error(`Failed to fetch profile: ${response.status} ${response.statusText}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const userData = await response.json();
|
|
639
|
+
currentUser.value = mapApiUserToFrontend(userData);
|
|
640
|
+
if (currentUser.value && typeof window !== "undefined") {
|
|
641
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
642
|
+
}
|
|
643
|
+
return currentUser.value;
|
|
644
|
+
});
|
|
645
|
+
} finally {
|
|
646
|
+
loadingStates.value.loadingProfile = false;
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
const updateProfile = async (profileData) => {
|
|
650
|
+
loadingStates.value.loadingProfile = true;
|
|
651
|
+
try {
|
|
652
|
+
const response = await fetch(getUrl("profile"), {
|
|
653
|
+
method: "POST",
|
|
654
|
+
headers: {
|
|
655
|
+
"Content-Type": "application/json",
|
|
656
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
657
|
+
},
|
|
658
|
+
body: JSON.stringify({
|
|
659
|
+
first_name: profileData.firstName,
|
|
660
|
+
last_name: profileData.lastName
|
|
661
|
+
})
|
|
662
|
+
});
|
|
663
|
+
if (!response.ok) {
|
|
664
|
+
if (response.status === 401) {
|
|
665
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
666
|
+
} else {
|
|
667
|
+
throw new Error(`Profile update failed: ${response.status} ${response.statusText}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const updatedUserData = await response.json();
|
|
671
|
+
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
672
|
+
if (currentUser.value) {
|
|
673
|
+
debouncedSetItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
674
|
+
}
|
|
675
|
+
return currentUser.value;
|
|
676
|
+
} finally {
|
|
677
|
+
loadingStates.value.loadingProfile = false;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const updateUserSettings = async (settings) => {
|
|
681
|
+
loadingStates.value.loadingProfile = true;
|
|
682
|
+
try {
|
|
683
|
+
const response = await fetch(getUrl("settings"), {
|
|
684
|
+
method: "POST",
|
|
685
|
+
headers: {
|
|
686
|
+
"Content-Type": "application/json",
|
|
687
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
688
|
+
},
|
|
689
|
+
body: JSON.stringify({
|
|
690
|
+
settings
|
|
691
|
+
})
|
|
692
|
+
});
|
|
693
|
+
if (!response.ok) {
|
|
694
|
+
if (response.status === 401) {
|
|
695
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
696
|
+
} else {
|
|
697
|
+
throw new Error(`Settings update failed: ${response.status} ${response.statusText}`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const updatedUserData = await response.json();
|
|
701
|
+
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
702
|
+
if (currentUser.value) {
|
|
703
|
+
debouncedSetItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
704
|
+
}
|
|
705
|
+
return currentUser.value;
|
|
706
|
+
} finally {
|
|
707
|
+
loadingStates.value.loadingProfile = false;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
const changeEmail = async (newEmail, password) => {
|
|
711
|
+
loadingStates.value.loadingProfile = true;
|
|
712
|
+
try {
|
|
713
|
+
const response = await fetch(getUrl("changeEmail"), {
|
|
714
|
+
method: "POST",
|
|
715
|
+
headers: {
|
|
716
|
+
"Content-Type": "application/json",
|
|
717
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
718
|
+
},
|
|
719
|
+
body: JSON.stringify({
|
|
720
|
+
new_email: newEmail,
|
|
721
|
+
password
|
|
722
|
+
})
|
|
723
|
+
});
|
|
724
|
+
if (!response.ok) {
|
|
725
|
+
if (response.status === 401) {
|
|
726
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
727
|
+
} else {
|
|
728
|
+
const errorData = await response.json().catch(() => ({}));
|
|
729
|
+
throw new Error(errorData.message || `Email change failed: ${response.status} ${response.statusText}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const result = await response.json();
|
|
733
|
+
if (currentUser.value) {
|
|
734
|
+
currentUser.value = {
|
|
735
|
+
...currentUser.value,
|
|
736
|
+
email: newEmail,
|
|
737
|
+
emailVerified: false,
|
|
738
|
+
// Email needs to be verified again
|
|
739
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
740
|
+
};
|
|
741
|
+
if (typeof window !== "undefined") {
|
|
742
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return result;
|
|
746
|
+
} finally {
|
|
747
|
+
loadingStates.value.loadingProfile = false;
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
const verifyMfa = async (deviceId, code, isBackupCode = false) => {
|
|
751
|
+
if (!mfaSessionId.value) {
|
|
752
|
+
throw new Error("No MFA session available");
|
|
753
|
+
}
|
|
754
|
+
loadingStates.value.verifyingMfa = true;
|
|
755
|
+
try {
|
|
756
|
+
const endpoint = isBackupCode ? getUrl("mfaBackupCodeVerify") : getUrl("mfaSigninVerify");
|
|
757
|
+
const body = isBackupCode ? { mfa_session_id: mfaSessionId.value, backup_code: code } : { mfa_session_id: mfaSessionId.value, device_id: deviceId, code };
|
|
758
|
+
const response = await fetch(endpoint, {
|
|
759
|
+
method: "POST",
|
|
760
|
+
headers: { "Content-Type": "application/json" },
|
|
761
|
+
body: JSON.stringify(body)
|
|
762
|
+
});
|
|
763
|
+
if (!response.ok) {
|
|
764
|
+
const errorText = await response.text();
|
|
765
|
+
let errorMessage = "MFA verification failed";
|
|
766
|
+
try {
|
|
767
|
+
const errorData = JSON.parse(errorText);
|
|
768
|
+
errorMessage = errorData.message || errorData.error || errorText;
|
|
769
|
+
} catch {
|
|
770
|
+
errorMessage = errorText || "MFA verification failed";
|
|
771
|
+
}
|
|
772
|
+
throw new Error(errorMessage);
|
|
773
|
+
}
|
|
774
|
+
const authData = await response.json();
|
|
775
|
+
mfaRequired.value = false;
|
|
776
|
+
mfaSessionId.value = null;
|
|
777
|
+
availableMfaMethods.value = [];
|
|
778
|
+
await setAuthData(authData);
|
|
779
|
+
return authData;
|
|
780
|
+
} finally {
|
|
781
|
+
loadingStates.value.verifyingMfa = false;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
const sendMfaEmailCode = async (deviceId) => {
|
|
785
|
+
if (!mfaSessionId.value) {
|
|
786
|
+
throw new Error("No MFA session available");
|
|
787
|
+
}
|
|
788
|
+
loadingStates.value.sendingMfaEmail = true;
|
|
789
|
+
try {
|
|
790
|
+
const response = await fetch(getUrl("mfaSigninSendEmail"), {
|
|
791
|
+
method: "POST",
|
|
792
|
+
headers: { "Content-Type": "application/json" },
|
|
793
|
+
body: JSON.stringify({
|
|
794
|
+
mfa_session_id: mfaSessionId.value,
|
|
795
|
+
device_id: deviceId
|
|
796
|
+
})
|
|
797
|
+
});
|
|
798
|
+
if (!response.ok) {
|
|
799
|
+
const errorText = await response.text();
|
|
800
|
+
let errorMessage = "Failed to send MFA email code";
|
|
801
|
+
try {
|
|
802
|
+
const errorData = JSON.parse(errorText);
|
|
803
|
+
errorMessage = errorData.message || errorData.error || errorText;
|
|
804
|
+
} catch {
|
|
805
|
+
errorMessage = errorText || "Failed to send MFA email code";
|
|
806
|
+
}
|
|
807
|
+
throw new Error(errorMessage);
|
|
808
|
+
}
|
|
809
|
+
const result = await response.json();
|
|
810
|
+
return result;
|
|
811
|
+
} finally {
|
|
812
|
+
loadingStates.value.sendingMfaEmail = false;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
const getMfaWebAuthnChallenge = async (deviceId) => {
|
|
816
|
+
if (!mfaSessionId.value) {
|
|
817
|
+
throw new Error("No MFA session available");
|
|
818
|
+
}
|
|
819
|
+
const response = await fetch(getUrl("mfaWebAuthnChallenge"), {
|
|
820
|
+
method: "POST",
|
|
821
|
+
headers: { "Content-Type": "application/json" },
|
|
822
|
+
body: JSON.stringify({
|
|
823
|
+
mfa_session_id: mfaSessionId.value,
|
|
824
|
+
device_id: deviceId
|
|
825
|
+
})
|
|
826
|
+
});
|
|
827
|
+
if (!response.ok) {
|
|
828
|
+
const errorText = await response.text();
|
|
829
|
+
let errorMessage = "Failed to get WebAuthn challenge";
|
|
830
|
+
try {
|
|
831
|
+
const errorData = JSON.parse(errorText);
|
|
832
|
+
errorMessage = errorData.message || errorData.error || errorText;
|
|
833
|
+
} catch {
|
|
834
|
+
errorMessage = errorText || errorMessage;
|
|
835
|
+
}
|
|
836
|
+
throw new Error(errorMessage);
|
|
837
|
+
}
|
|
838
|
+
return response.json();
|
|
839
|
+
};
|
|
840
|
+
const setAuthData = async (authResponse) => {
|
|
841
|
+
try {
|
|
842
|
+
if (authResponse.user) {
|
|
843
|
+
currentUser.value = mapApiUserToFrontend(authResponse.user);
|
|
844
|
+
}
|
|
845
|
+
const session = {
|
|
846
|
+
accessToken: authResponse.access_token,
|
|
847
|
+
refreshToken: authResponse.refresh_token,
|
|
848
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1e3),
|
|
849
|
+
// 5 minutes from now (matching API token expiry)
|
|
850
|
+
userId: currentUser.value?.id || authResponse.user?.id
|
|
851
|
+
};
|
|
852
|
+
currentSession.value = session;
|
|
853
|
+
if (typeof window !== "undefined") {
|
|
854
|
+
localStorage.setItem("strands_auth_session", JSON.stringify(session));
|
|
855
|
+
if (currentUser.value) {
|
|
856
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
startTokenRefreshTimer();
|
|
860
|
+
} catch (error) {
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
const startTokenRefreshTimer = () => {
|
|
864
|
+
if (refreshTimer) {
|
|
865
|
+
clearTimeout(refreshTimer);
|
|
866
|
+
}
|
|
867
|
+
if (!currentSession.value) return;
|
|
868
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const now = /* @__PURE__ */ new Date();
|
|
872
|
+
const expiresAt = currentSession.value.expiresAt;
|
|
873
|
+
const timeUntilRefresh = expiresAt.getTime() - now.getTime() - 1 * 60 * 1e3;
|
|
874
|
+
if (timeUntilRefresh <= 0) {
|
|
875
|
+
refreshToken();
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
refreshTimer = setTimeout(async () => {
|
|
879
|
+
if (typeof document === "undefined" || document.visibilityState === "visible") {
|
|
880
|
+
const success = await refreshToken();
|
|
881
|
+
if (success) {
|
|
882
|
+
startTokenRefreshTimer();
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}, timeUntilRefresh);
|
|
886
|
+
};
|
|
887
|
+
const stopTokenRefreshTimer = () => {
|
|
888
|
+
if (refreshTimer) {
|
|
889
|
+
clearTimeout(refreshTimer);
|
|
890
|
+
refreshTimer = null;
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
const initialize = async () => {
|
|
894
|
+
if (isInitialized.value) return;
|
|
895
|
+
loadingStates.value.initializing = true;
|
|
896
|
+
try {
|
|
897
|
+
if (typeof window !== "undefined") {
|
|
898
|
+
const storedSession = localStorage.getItem("strands_auth_session");
|
|
899
|
+
const storedUser = localStorage.getItem("strands_auth_user");
|
|
900
|
+
if (storedSession && storedUser) {
|
|
901
|
+
try {
|
|
902
|
+
const session = JSON.parse(storedSession);
|
|
903
|
+
const user = JSON.parse(storedUser);
|
|
904
|
+
session.expiresAt = new Date(session.expiresAt);
|
|
905
|
+
if (session.expiresAt > /* @__PURE__ */ new Date()) {
|
|
906
|
+
currentSession.value = session;
|
|
907
|
+
currentUser.value = user;
|
|
908
|
+
startTokenRefreshTimer();
|
|
909
|
+
} else {
|
|
910
|
+
localStorage.removeItem("strands_auth_session");
|
|
911
|
+
localStorage.removeItem("strands_auth_user");
|
|
912
|
+
}
|
|
913
|
+
} catch (error) {
|
|
914
|
+
localStorage.removeItem("strands_auth_session");
|
|
915
|
+
localStorage.removeItem("strands_auth_user");
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
isInitialized.value = true;
|
|
920
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
921
|
+
} catch (error) {
|
|
922
|
+
} finally {
|
|
923
|
+
loadingStates.value.initializing = false;
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
const changeUsername = async (newUsername) => {
|
|
927
|
+
loadingStates.value.loadingProfile = true;
|
|
928
|
+
try {
|
|
929
|
+
const response = await fetch(getUrl("changeUsername"), {
|
|
930
|
+
method: "POST",
|
|
931
|
+
headers: {
|
|
932
|
+
"Content-Type": "application/json",
|
|
933
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
934
|
+
},
|
|
935
|
+
body: JSON.stringify({
|
|
936
|
+
username: newUsername
|
|
937
|
+
})
|
|
938
|
+
});
|
|
939
|
+
if (!response.ok) {
|
|
940
|
+
const errorData = await response.json().catch(() => ({}));
|
|
941
|
+
if (response.status === 409) {
|
|
942
|
+
throw new Error("Username is already taken");
|
|
943
|
+
} else if (errorData.cooldown_end) {
|
|
944
|
+
throw new Error(`You can only change your username once every 30 days. You can change it again on ${new Date(errorData.cooldown_end).toLocaleDateString()}`);
|
|
945
|
+
}
|
|
946
|
+
throw new Error(errorData.message || `Username change failed: ${response.status} ${response.statusText}`);
|
|
947
|
+
}
|
|
948
|
+
const result = await response.json();
|
|
949
|
+
if (currentUser.value) {
|
|
950
|
+
currentUser.value = {
|
|
951
|
+
...currentUser.value,
|
|
952
|
+
username: newUsername,
|
|
953
|
+
usernameLastChangedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
954
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
955
|
+
};
|
|
956
|
+
if (typeof window !== "undefined") {
|
|
957
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return result;
|
|
961
|
+
} finally {
|
|
962
|
+
loadingStates.value.loadingProfile = false;
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
const getUsernameCooldown = async () => {
|
|
966
|
+
const response = await fetch(getUrl("usernameCooldown"), {
|
|
967
|
+
method: "GET",
|
|
968
|
+
headers: {
|
|
969
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
if (!response.ok) {
|
|
973
|
+
throw new Error(`Failed to get username cooldown: ${response.status} ${response.statusText}`);
|
|
974
|
+
}
|
|
975
|
+
return response.json();
|
|
976
|
+
};
|
|
977
|
+
const checkUsernameAvailability = async (username) => {
|
|
978
|
+
const url = getUrl("checkUsernameAvailability").replace("{username}", encodeURIComponent(username));
|
|
979
|
+
const response = await fetch(url, {
|
|
980
|
+
method: "GET",
|
|
981
|
+
headers: {
|
|
982
|
+
"Content-Type": "application/json"
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
if (!response.ok) {
|
|
986
|
+
throw new Error(`Failed to check username availability: ${response.status} ${response.statusText}`);
|
|
987
|
+
}
|
|
988
|
+
return response.json();
|
|
989
|
+
};
|
|
990
|
+
const getUserSessions = async () => {
|
|
991
|
+
const cacheKey = `sessions:${currentSession.value?.accessToken?.slice(0, 20) || "no-token"}`;
|
|
992
|
+
try {
|
|
993
|
+
return await cachedFetch(cacheKey, async () => {
|
|
994
|
+
const headers = getAuthHeaders();
|
|
995
|
+
const response = await fetch(getUrl("sessions"), {
|
|
996
|
+
method: "GET",
|
|
997
|
+
headers
|
|
998
|
+
});
|
|
999
|
+
if (!response.ok) {
|
|
1000
|
+
await response.text();
|
|
1001
|
+
throw new Error(`Failed to get user sessions: ${response.status} ${response.statusText}`);
|
|
1002
|
+
}
|
|
1003
|
+
return response.json();
|
|
1004
|
+
}, 2 * 60 * 1e3);
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
throw error;
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
const getSessionStats = async () => {
|
|
1010
|
+
const response = await fetch(getUrl("sessionsStats"), {
|
|
1011
|
+
method: "GET",
|
|
1012
|
+
headers: getAuthHeaders()
|
|
1013
|
+
});
|
|
1014
|
+
if (!response.ok) {
|
|
1015
|
+
throw new Error(`Failed to get session stats: ${response.status} ${response.statusText}`);
|
|
1016
|
+
}
|
|
1017
|
+
return response.json();
|
|
1018
|
+
};
|
|
1019
|
+
const revokeSession = async (sessionId) => {
|
|
1020
|
+
const url = getUrl("sessionRevoke").replace("{session_id}", encodeURIComponent(sessionId));
|
|
1021
|
+
const response = await fetch(url, {
|
|
1022
|
+
method: "POST",
|
|
1023
|
+
headers: getAuthHeaders()
|
|
1024
|
+
});
|
|
1025
|
+
if (!response.ok) {
|
|
1026
|
+
throw new Error(`Failed to revoke session: ${response.status} ${response.statusText}`);
|
|
1027
|
+
}
|
|
1028
|
+
return response.status === 200;
|
|
1029
|
+
};
|
|
1030
|
+
const revokeAllOtherSessions = async () => {
|
|
1031
|
+
const response = await fetch(getUrl("sessionsRevokeAll"), {
|
|
1032
|
+
method: "POST",
|
|
1033
|
+
headers: getAuthHeaders()
|
|
1034
|
+
});
|
|
1035
|
+
if (!response.ok) {
|
|
1036
|
+
throw new Error(`Failed to revoke all other sessions: ${response.status} ${response.statusText}`);
|
|
1037
|
+
}
|
|
1038
|
+
return response.status === 200;
|
|
1039
|
+
};
|
|
1040
|
+
if (typeof document !== "undefined") {
|
|
1041
|
+
document.addEventListener("visibilitychange", () => {
|
|
1042
|
+
if (document.visibilityState === "visible" && currentSession.value) {
|
|
1043
|
+
startTokenRefreshTimer();
|
|
1044
|
+
} else if (document.visibilityState === "hidden") {
|
|
1045
|
+
stopTokenRefreshTimer();
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
const cleanup = () => {
|
|
1050
|
+
stopTokenRefreshTimer();
|
|
1051
|
+
clearCache();
|
|
1052
|
+
if (typeof document !== "undefined") {
|
|
1053
|
+
document.removeEventListener("visibilitychange", () => {
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
try {
|
|
1058
|
+
onUnmounted(cleanup);
|
|
1059
|
+
} catch (e) {
|
|
1060
|
+
}
|
|
1061
|
+
if (!isInitialized.value) {
|
|
1062
|
+
initialize();
|
|
1063
|
+
}
|
|
1064
|
+
return {
|
|
1065
|
+
// State
|
|
1066
|
+
user: computed(() => currentUser.value),
|
|
1067
|
+
currentUser: computed(() => currentUser.value),
|
|
1068
|
+
currentSession: computed(() => currentSession.value),
|
|
1069
|
+
isAuthenticated,
|
|
1070
|
+
isLoading: computed(() => isLoading.value || !isInitialized.value),
|
|
1071
|
+
loading: computed(() => loading2.value),
|
|
1072
|
+
loadingMessage,
|
|
1073
|
+
// Specific loading states
|
|
1074
|
+
isInitializing,
|
|
1075
|
+
isSigningIn,
|
|
1076
|
+
isSigningUp,
|
|
1077
|
+
isSigningOut,
|
|
1078
|
+
isRefreshingToken,
|
|
1079
|
+
isSendingMfaEmail,
|
|
1080
|
+
isVerifyingMfa,
|
|
1081
|
+
// MFA State
|
|
1082
|
+
mfaRequired: computed(() => mfaRequired.value),
|
|
1083
|
+
mfaSessionId: computed(() => mfaSessionId.value),
|
|
1084
|
+
availableMfaMethods: computed(() => availableMfaMethods.value),
|
|
1085
|
+
// Methods
|
|
1086
|
+
signIn,
|
|
1087
|
+
signUp,
|
|
1088
|
+
signOut,
|
|
1089
|
+
refreshToken,
|
|
1090
|
+
fetchProfile,
|
|
1091
|
+
updateProfile,
|
|
1092
|
+
updateUserSettings,
|
|
1093
|
+
changeEmail,
|
|
1094
|
+
changeUsername,
|
|
1095
|
+
getUsernameCooldown,
|
|
1096
|
+
checkUsernameAvailability,
|
|
1097
|
+
// Session management
|
|
1098
|
+
getUserSessions,
|
|
1099
|
+
getSessionStats,
|
|
1100
|
+
revokeSession,
|
|
1101
|
+
revokeAllOtherSessions,
|
|
1102
|
+
initialize,
|
|
1103
|
+
setAuthData,
|
|
1104
|
+
verifyMfa,
|
|
1105
|
+
sendMfaEmailCode,
|
|
1106
|
+
getMfaWebAuthnChallenge,
|
|
1107
|
+
registerHardwareKey,
|
|
1108
|
+
completeHardwareKeyRegistration,
|
|
1109
|
+
// Token management
|
|
1110
|
+
startTokenRefreshTimer,
|
|
1111
|
+
stopTokenRefreshTimer,
|
|
1112
|
+
getAuthHeaders,
|
|
1113
|
+
// Force re-initialization (useful for testing or navigation)
|
|
1114
|
+
forceReInit: () => {
|
|
1115
|
+
isInitialized.value = false;
|
|
1116
|
+
loadingStates.value.initializing = true;
|
|
1117
|
+
initialize();
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
const mfaDevices = ref([]);
|
|
1122
|
+
const mfaEnabled = ref(false);
|
|
1123
|
+
const loading = ref(false);
|
|
1124
|
+
function useStrandsMfa() {
|
|
1125
|
+
const { getUrl } = useStrandsConfig();
|
|
1126
|
+
const { currentSession } = useStrandsAuth();
|
|
1127
|
+
const hasMfaDevices = computed(() => mfaDevices.value.length > 0);
|
|
1128
|
+
const activeMfaDevices = computed(
|
|
1129
|
+
() => mfaDevices.value.filter(
|
|
1130
|
+
(d) => d.is_active && d.device_type !== "hardware" && d.device_type !== "passkey"
|
|
1131
|
+
)
|
|
1132
|
+
);
|
|
1133
|
+
const makeAuthenticatedRequest = async (url, options = {}) => {
|
|
1134
|
+
const headers = {
|
|
1135
|
+
"Content-Type": "application/json",
|
|
1136
|
+
...options.headers
|
|
1137
|
+
};
|
|
1138
|
+
if (currentSession.value?.accessToken) {
|
|
1139
|
+
headers["Authorization"] = `Bearer ${currentSession.value.accessToken}`;
|
|
1140
|
+
}
|
|
1141
|
+
const response = await fetch(url, {
|
|
1142
|
+
...options,
|
|
1143
|
+
headers
|
|
1144
|
+
});
|
|
1145
|
+
if (!response.ok) {
|
|
1146
|
+
const errorText = await response.text();
|
|
1147
|
+
let errorMessage = `Request failed: ${response.status} ${response.statusText}`;
|
|
1148
|
+
try {
|
|
1149
|
+
const errorData = JSON.parse(errorText);
|
|
1150
|
+
errorMessage = errorData.message || errorMessage;
|
|
1151
|
+
} catch {
|
|
1152
|
+
if (errorText) {
|
|
1153
|
+
errorMessage = errorText;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
throw new Error(errorMessage);
|
|
1157
|
+
}
|
|
1158
|
+
return response.json();
|
|
1159
|
+
};
|
|
1160
|
+
const fetchMfaDevices = async () => {
|
|
1161
|
+
loading.value = true;
|
|
1162
|
+
try {
|
|
1163
|
+
const response = await makeAuthenticatedRequest(getUrl("mfaDevices"), {
|
|
1164
|
+
method: "GET"
|
|
1165
|
+
});
|
|
1166
|
+
mfaDevices.value = response.devices || [];
|
|
1167
|
+
mfaEnabled.value = response.mfa_enabled || false;
|
|
1168
|
+
return response;
|
|
1169
|
+
} finally {
|
|
1170
|
+
loading.value = false;
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
const setupTotp = async (deviceName) => {
|
|
1174
|
+
loading.value = true;
|
|
1175
|
+
try {
|
|
1176
|
+
const response = await makeAuthenticatedRequest(getUrl("mfaTotpSetup"), {
|
|
1177
|
+
method: "POST",
|
|
1178
|
+
body: JSON.stringify({ device_name: deviceName })
|
|
1179
|
+
});
|
|
1180
|
+
await fetchMfaDevices();
|
|
1181
|
+
return response;
|
|
1182
|
+
} finally {
|
|
1183
|
+
loading.value = false;
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
const verifyTotpSetup = async (deviceId, totpCode) => {
|
|
1187
|
+
loading.value = true;
|
|
1188
|
+
try {
|
|
1189
|
+
await makeAuthenticatedRequest(getUrl("mfaTotpVerify"), {
|
|
1190
|
+
method: "POST",
|
|
1191
|
+
body: JSON.stringify({
|
|
1192
|
+
device_id: deviceId,
|
|
1193
|
+
totp_code: totpCode
|
|
1194
|
+
})
|
|
1195
|
+
});
|
|
1196
|
+
await fetchMfaDevices();
|
|
1197
|
+
} finally {
|
|
1198
|
+
loading.value = false;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
const setupEmailMfa = async (deviceName) => {
|
|
1202
|
+
loading.value = true;
|
|
1203
|
+
try {
|
|
1204
|
+
const response = await makeAuthenticatedRequest(getUrl("mfaEmailSetup"), {
|
|
1205
|
+
method: "POST",
|
|
1206
|
+
body: JSON.stringify({ device_name: deviceName })
|
|
1207
|
+
});
|
|
1208
|
+
await fetchMfaDevices();
|
|
1209
|
+
return response;
|
|
1210
|
+
} finally {
|
|
1211
|
+
loading.value = false;
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
const sendEmailMfaCode = async (deviceId) => {
|
|
1215
|
+
loading.value = true;
|
|
1216
|
+
try {
|
|
1217
|
+
await makeAuthenticatedRequest(getUrl("mfaEmailSend"), {
|
|
1218
|
+
method: "POST",
|
|
1219
|
+
body: JSON.stringify({ device_id: deviceId })
|
|
1220
|
+
});
|
|
1221
|
+
} finally {
|
|
1222
|
+
loading.value = false;
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
const verifyEmailMfaCode = async (deviceId, code) => {
|
|
1226
|
+
loading.value = true;
|
|
1227
|
+
try {
|
|
1228
|
+
const response = await makeAuthenticatedRequest(getUrl("mfaEmailVerify"), {
|
|
1229
|
+
method: "POST",
|
|
1230
|
+
body: JSON.stringify({
|
|
1231
|
+
device_id: deviceId,
|
|
1232
|
+
code
|
|
1233
|
+
})
|
|
1234
|
+
});
|
|
1235
|
+
return response.verified || false;
|
|
1236
|
+
} finally {
|
|
1237
|
+
loading.value = false;
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
const disableMfaDevice = async (deviceId) => {
|
|
1241
|
+
loading.value = true;
|
|
1242
|
+
try {
|
|
1243
|
+
await makeAuthenticatedRequest(getUrl("mfaDeviceDisable"), {
|
|
1244
|
+
method: "POST",
|
|
1245
|
+
body: JSON.stringify({ device_id: deviceId })
|
|
1246
|
+
});
|
|
1247
|
+
await fetchMfaDevices();
|
|
1248
|
+
} finally {
|
|
1249
|
+
loading.value = false;
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
const regenerateBackupCodes = async (deviceId) => {
|
|
1253
|
+
loading.value = true;
|
|
1254
|
+
try {
|
|
1255
|
+
const response = await makeAuthenticatedRequest(getUrl("mfaBackupCodes"), {
|
|
1256
|
+
method: "POST",
|
|
1257
|
+
body: JSON.stringify({ device_id: deviceId })
|
|
1258
|
+
});
|
|
1259
|
+
return response;
|
|
1260
|
+
} finally {
|
|
1261
|
+
loading.value = false;
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
const getDeviceTypeIcon = (deviceType) => {
|
|
1265
|
+
switch (deviceType) {
|
|
1266
|
+
case "totp":
|
|
1267
|
+
return "📱";
|
|
1268
|
+
// Authenticator app
|
|
1269
|
+
case "email":
|
|
1270
|
+
return "📧";
|
|
1271
|
+
// Email
|
|
1272
|
+
case "hardware":
|
|
1273
|
+
return "🔑";
|
|
1274
|
+
// Hardware key
|
|
1275
|
+
case "passkey":
|
|
1276
|
+
return "🔐";
|
|
1277
|
+
// Passkey
|
|
1278
|
+
default:
|
|
1279
|
+
return "🔒";
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
const getDeviceTypeName = (deviceType) => {
|
|
1283
|
+
switch (deviceType) {
|
|
1284
|
+
case "totp":
|
|
1285
|
+
return "Authenticator App";
|
|
1286
|
+
case "email":
|
|
1287
|
+
return "Email Verification";
|
|
1288
|
+
case "hardware":
|
|
1289
|
+
return "Hardware Key";
|
|
1290
|
+
case "passkey":
|
|
1291
|
+
return "Passkey";
|
|
1292
|
+
default:
|
|
1293
|
+
return "Unknown";
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
const formatLastUsed = (lastUsedAt) => {
|
|
1297
|
+
if (!lastUsedAt) return "Never";
|
|
1298
|
+
const date = new Date(lastUsedAt);
|
|
1299
|
+
const now = /* @__PURE__ */ new Date();
|
|
1300
|
+
const diffMs = now.getTime() - date.getTime();
|
|
1301
|
+
const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
1302
|
+
if (diffDays === 0) return "Today";
|
|
1303
|
+
if (diffDays === 1) return "Yesterday";
|
|
1304
|
+
if (diffDays < 30) return `${diffDays} days ago`;
|
|
1305
|
+
return date.toLocaleDateString();
|
|
1306
|
+
};
|
|
1307
|
+
return {
|
|
1308
|
+
// State
|
|
1309
|
+
mfaDevices: computed(() => mfaDevices.value),
|
|
1310
|
+
mfaEnabled: computed(() => mfaEnabled.value),
|
|
1311
|
+
loading: computed(() => loading.value),
|
|
1312
|
+
hasMfaDevices,
|
|
1313
|
+
activeMfaDevices,
|
|
1314
|
+
// Methods
|
|
1315
|
+
fetchMfaDevices,
|
|
1316
|
+
setupTotp,
|
|
1317
|
+
verifyTotpSetup,
|
|
1318
|
+
setupEmailMfa,
|
|
1319
|
+
sendEmailMfaCode,
|
|
1320
|
+
verifyEmailMfaCode,
|
|
1321
|
+
disableMfaDevice,
|
|
1322
|
+
regenerateBackupCodes,
|
|
1323
|
+
// Helper methods
|
|
1324
|
+
getDeviceTypeIcon,
|
|
1325
|
+
getDeviceTypeName,
|
|
1326
|
+
formatLastUsed
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
const providersCache = /* @__PURE__ */ new Map();
|
|
1330
|
+
const CACHE_DURATION = 5 * 60 * 1e3;
|
|
1331
|
+
function useOAuthProviders(options = {}) {
|
|
1332
|
+
const { getUrl, config } = useStrandsConfig();
|
|
1333
|
+
const providers = ref([]);
|
|
1334
|
+
const loading2 = ref(false);
|
|
1335
|
+
const error = ref(null);
|
|
1336
|
+
const enabledProviders = computed(
|
|
1337
|
+
() => providers.value.filter((provider) => provider.enabled)
|
|
1338
|
+
);
|
|
1339
|
+
const fetchProviders = async () => {
|
|
1340
|
+
const cacheKey = JSON.stringify(options);
|
|
1341
|
+
const cached = providersCache.get(cacheKey);
|
|
1342
|
+
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
|
|
1343
|
+
providers.value = cached.data;
|
|
1344
|
+
return cached.data;
|
|
1345
|
+
}
|
|
1346
|
+
loading2.value = true;
|
|
1347
|
+
error.value = null;
|
|
1348
|
+
try {
|
|
1349
|
+
let url = getUrl("oauthProviders");
|
|
1350
|
+
const redirectUrl = options.redirectUrl || config.value?.oauth2RedirectUrl;
|
|
1351
|
+
if (redirectUrl) {
|
|
1352
|
+
const params = new URLSearchParams();
|
|
1353
|
+
let absoluteRedirectUrl = redirectUrl;
|
|
1354
|
+
if (redirectUrl.startsWith("/")) {
|
|
1355
|
+
const currentOrigin = window.location.origin;
|
|
1356
|
+
absoluteRedirectUrl = `${currentOrigin}${redirectUrl}`;
|
|
1357
|
+
}
|
|
1358
|
+
params.append("redirect_url", absoluteRedirectUrl);
|
|
1359
|
+
url = `${url}?${params.toString()}`;
|
|
1360
|
+
}
|
|
1361
|
+
const response = await fetch(url, {
|
|
1362
|
+
method: "GET",
|
|
1363
|
+
headers: {
|
|
1364
|
+
"Content-Type": "application/json"
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
if (!response.ok) {
|
|
1368
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1369
|
+
throw new Error(errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
1370
|
+
}
|
|
1371
|
+
const result = await response.json();
|
|
1372
|
+
providers.value = result.providers || [];
|
|
1373
|
+
providersCache.set(cacheKey, {
|
|
1374
|
+
data: providers.value,
|
|
1375
|
+
timestamp: Date.now()
|
|
1376
|
+
});
|
|
1377
|
+
} catch (err) {
|
|
1378
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch OAuth providers";
|
|
1379
|
+
error.value = errorMessage;
|
|
1380
|
+
} finally {
|
|
1381
|
+
loading2.value = false;
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
const getProviderAuthUrl = async (providerId, customOptions) => {
|
|
1385
|
+
const mergedOptions = { ...options, ...customOptions };
|
|
1386
|
+
const params = new URLSearchParams();
|
|
1387
|
+
if (mergedOptions.redirectUrl) {
|
|
1388
|
+
let absoluteRedirectUrl = mergedOptions.redirectUrl;
|
|
1389
|
+
if (mergedOptions.redirectUrl.startsWith("/")) {
|
|
1390
|
+
const currentOrigin = window.location.origin;
|
|
1391
|
+
absoluteRedirectUrl = `${currentOrigin}${mergedOptions.redirectUrl}`;
|
|
1392
|
+
}
|
|
1393
|
+
params.append("redirect_url", absoluteRedirectUrl);
|
|
1394
|
+
}
|
|
1395
|
+
if (mergedOptions.scopes && mergedOptions.scopes.length > 0) {
|
|
1396
|
+
params.append("scopes", mergedOptions.scopes.join(","));
|
|
1397
|
+
}
|
|
1398
|
+
const queryString = params.toString();
|
|
1399
|
+
const providerUrl = getUrl("oauthProvider").replace("{provider_id}", providerId);
|
|
1400
|
+
const fullUrl = queryString ? `${providerUrl}?${queryString}` : providerUrl;
|
|
1401
|
+
try {
|
|
1402
|
+
const response = await fetch(fullUrl, {
|
|
1403
|
+
method: "GET",
|
|
1404
|
+
headers: {
|
|
1405
|
+
"Content-Type": "application/json"
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
if (!response.ok) {
|
|
1409
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1410
|
+
throw new Error(errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
1411
|
+
}
|
|
1412
|
+
const result = await response.json();
|
|
1413
|
+
if (!result.success) {
|
|
1414
|
+
throw new Error(result.error?.message || "Failed to get OAuth auth URL");
|
|
1415
|
+
}
|
|
1416
|
+
return result.data.authUrl;
|
|
1417
|
+
} catch (err) {
|
|
1418
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to get OAuth auth URL";
|
|
1419
|
+
throw new Error(errorMessage);
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
const redirectToProvider = async (providerId, customOptions) => {
|
|
1423
|
+
try {
|
|
1424
|
+
const provider = providers.value.find((p) => p.id === providerId);
|
|
1425
|
+
if (!provider) {
|
|
1426
|
+
throw new Error(`OAuth provider '${providerId}' not found`);
|
|
1427
|
+
}
|
|
1428
|
+
if (!provider.auth_url) {
|
|
1429
|
+
throw new Error(`No auth URL configured for provider '${providerId}'`);
|
|
1430
|
+
}
|
|
1431
|
+
window.location.href = provider.auth_url;
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
error.value = err instanceof Error ? err.message : "Failed to redirect to OAuth provider";
|
|
1434
|
+
throw err;
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
const getProviderById = (providerId) => {
|
|
1438
|
+
return providers.value.find((provider) => provider.id === providerId);
|
|
1439
|
+
};
|
|
1440
|
+
const getProviderIcon = (provider) => {
|
|
1441
|
+
if (provider.iconUrl) {
|
|
1442
|
+
return provider.iconUrl;
|
|
1443
|
+
}
|
|
1444
|
+
switch (provider.id.toLowerCase()) {
|
|
1445
|
+
case "google":
|
|
1446
|
+
return "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9IiM0Mjg1RjQiIGQ9Ik0yMi41NiAxMi4yNWMwLS43OC0uMDctMS41My0uMi0yLjI1SDEydjQuMjZoNS45MmMtLjI2IDEuMzctMS4wNCAyLjUzLTIuMjEgMy4zMXYyLjc3aDMuNTdjMi4wOC0xLjkyIDMuMjgtNC43NCAzLjI4LTguMDl6Ii8+PHBhdGggZmlsbD0iIzM0QTg1MyIgZD0iTTEyIDIzYzIuOTcgMCA1LjQ2LS45OCA3LjI4LTIuNjZsLTMuNTctMi43N2MtLjk4LjY2LTIuMjMgMS4wNi0zLjcxIDEuMDYtMi44NiAwLTUuMjktMS45My02LjE2LTQuNTNIMi4xOHYyLjg0QzMuOTkgMjAuNTMgNy43IDIzIDEyIDIzeiIvPjxwYXRoIGZpbGw9IiNGQkJDMDUiIGQ9Ik01Ljg0IDE0LjA5Yy0uMjItLjY2LS4zNS0xLjM2LS4zNS0yLjA5cy4xMy0xLjQzLjM1LTIuMDlWNy4wN0gyLjE4QzEuNDMgOC41NSAxIDEwLjIyIDEgMTJzLjQzIDMuNDUgMS4xOCA0LjkzbDIuODUtMi4yMi44MS0uNjJ6Ii8+PHBhdGggZmlsbD0iI0VBNDMzNSIgZD0iTTEyIDUuMzhjMS42MiAwIDMuMDYuNTYgNC4yMSAxLjY0bDMuMTUtMy4xNUMxNy40NSAyLjA5IDE0Ljk3IDEgMTIgMSA3LjcgMSAzLjk5IDMuNDcgMi4xOCA3LjA3bDMuNjYgMi44NGMuODctMi42IDMuMy00LjUzIDYuMTYtNC41M3oiLz48L3N2Zz4=";
|
|
1447
|
+
case "github":
|
|
1448
|
+
return "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJjdXJyZW50Q29sb3IiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEyIDBoLTEyYy02LjYyNiAwLTEyIDUuMzczLTEyIDEyIDAgNS4zMDIgMy40MzggOS44IDguMjA3IDExLjM4Ny41OTkuMTExLjc5My0uMjYxLjc5My0uNTc3di0yLjIzNGMtMy4zMzguNzI2LTQuMDMzLTEuNDE2LTQuMDMzLTEuNDE2LS41NDYtMS4zODctMS4zMzMtMS43NTYtMS4zMzMtMS43NTYtMS4wODktLjc0NS4wODMtLjcyOS4wODMtLjcyOSAxLjIwNS4wODQgMS44MzkgMS4yMzcgMS44MzkgMS4yMzcgMS4wNyAxLjgzNCAyLjgwNyAxLjMwNCAzLjQ5Mi45OTcuMTA3LS43NzUuNDE4LTEuMzA1Ljc2Mi0xLjYwNC0yLjY2NS0uMzA1LTUuNDY3LTEuMzM0LTUuNDY3LTUuOTMxIDAtMS4zMTEuNDY5LTIuMzgxIDEuMjM2LTMuMjIxLS4xMjQtLjMwMy0uNTM1LTEuNTI0LjExNy0zLjE3NiAwIDAgMS4wMDgtLjMyMiAzLjMwMSAxLjIzLjk1Ny0uMjY2IDEuOTgzLS4zOTkgMy4wMDMtLjQwNCAxLjAyLjAwNSAyLjA0Ny4xMzggMy4wMDYuNDA0IDIuMjkxLTEuNTUyIDMuMjk3LTEuMjMgMy4yOTctMS4yMy42NTMgMS42NTMuMjQyIDIuODc0LjExOCAzLjE3Ni43Ny44NCAxLjIzNSAxLjkxMSAxLjIzNSAzLjIyMSAwIDQuNjA5LTIuODA3IDUuNjI0LTUuNDc5IDUuOTIxLjQzLjM3Mi44MjMgMS4xMDIuODIzIDIuMjIydjMuMjkzYzAgLjMxOS4xOTIuNjk0LjgwMS41NzYgNC43NjUtMS41ODkgOC4xOTktNi4wODYgOC4xOTktMTEuMzg2IDAtNi42MjctNS4zNzMtMTItMTItMTJ6Ii8+PC9zdmc+";
|
|
1449
|
+
case "microsoft":
|
|
1450
|
+
case "azure":
|
|
1451
|
+
return "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiPjxyZWN0IGZpbGw9IiNmMjUwMjIiIHdpZHRoPSIxMSIgaGVpZ2h0PSIxMSIvPjxyZWN0IGZpbGw9IiM3ZmJhMDAiIHg9IjEzIiB3aWR0aD0iMTEiIGhlaWdodD0iMTEiLz48cmVjdCBmaWxsPSIjMDBhNGVmIiB5PSIxMyIgd2lkdGg9IjExIiBoZWlnaHQ9IjExIi8+PHJlY3QgZmlsbD0iI2ZmYjkwMCIgeD0iMTMiIHk9IjEzIiB3aWR0aD0iMTEiIGhlaWdodD0iMTEiLz48L3N2Zz4=";
|
|
1452
|
+
case "discord":
|
|
1453
|
+
return "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiM1ODY1RjIiPjxwYXRoIGQ9Ik0yMC4zMTcgNC4zNjk2QTE5Ljc5MTMgMTkuNzkxMyAwIDAgMCAxOC4yMDU4IDMuMzY5NmMtLjk2NDIuMzU2NC0xLjk5NTguNjE1NC0zLjA1OC43NzNhMTQuNzI0NCAxNC43MjQ0IDAgMCAwLTYuMjk1NCAwIDEzLjU0OTQgMTMuNTQ5NCAwIDAgMC0zLjA1OC0uNzczIDEzLjY5MyAxMy42OTMgMCAwIDAtNS4wNjc2LTEuMjc5NCAyMC4xOTg0IDIwLjE5ODQgMCAwIDAtMi42NDk3IDkuMTE2MyAyMC4yODE4IDIwLjI4MTggMCAwIDAgMi4zNzU4IDIuNjNjMS4zMzIyLjEyNTggMi42NjUuMTcyMiA0LjA0NC4xNzIyaDEuMzE1NGMxLjM3OSAwIDIuNzExOC0uMDQ2NCA0LjA0NC0uMTcyMmEyMC4yODE4IDIwLjI4MTggMCAwIDAgMi4zNzU4LTIuNjMgMjAuMTk4NCAyMC4xOTg0IDAgMCAwIDIuNjQ5Ny05LjExNjNBMTkuNzkxMyAxOS43OTEzIDAgMCAwIDIwLjMxNyA0LjM2OTZ6TTE4LjE5IDEyQzE3LjY3IDE0LjQ5IDEzLjk5IDE2IDEyIDE2UzYuMzMgMTQuNDkgNS44MSAxMmMuNTItMi40OSA0LjE5LTQgNi4xOS00UzE3LjY3IDkuNTEgMTguMTkgMTJ6Ii8+PC9zdmc+";
|
|
1454
|
+
default:
|
|
1455
|
+
return "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9ImN1cnJlbnRDb2xvciI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiLz48dGV4dCB4PSIxMiIgeT0iMTciIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IndoaXRlIiBmb250LXNpemU9IjEyIj57cHJvdmlkZXIuaWQuY2hhckF0KDApLnRvVXBwZXJDYXNlKCl9PC90ZXh0Pjwvc3ZnPg==";
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
return {
|
|
1459
|
+
providers: computed(() => providers.value),
|
|
1460
|
+
enabledProviders,
|
|
1461
|
+
loading: computed(() => loading2.value),
|
|
1462
|
+
error: computed(() => error.value),
|
|
1463
|
+
fetchProviders,
|
|
1464
|
+
getProviderAuthUrl,
|
|
1465
|
+
redirectToProvider,
|
|
1466
|
+
getProviderById,
|
|
1467
|
+
getProviderIcon
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
function useAuthenticatedFetch() {
|
|
1471
|
+
const { config } = useStrandsConfig();
|
|
1472
|
+
const { currentSession, refreshToken, getAuthHeaders } = useStrandsAuth();
|
|
1473
|
+
const authenticatedFetch = async (url, options = {}) => {
|
|
1474
|
+
const {
|
|
1475
|
+
autoRefresh = true,
|
|
1476
|
+
requireAuth = true,
|
|
1477
|
+
baseURL,
|
|
1478
|
+
...fetchOptions
|
|
1479
|
+
} = options;
|
|
1480
|
+
if (requireAuth && !currentSession.value?.accessToken) {
|
|
1481
|
+
throw new Error("User is not authenticated");
|
|
1482
|
+
}
|
|
1483
|
+
let fullUrl = url;
|
|
1484
|
+
const resolvedBaseURL = baseURL || config.value.baseUrl;
|
|
1485
|
+
if (resolvedBaseURL && typeof url === "string" && !url.startsWith("http")) {
|
|
1486
|
+
fullUrl = new URL(url, resolvedBaseURL).toString();
|
|
1487
|
+
}
|
|
1488
|
+
const headers = new Headers(fetchOptions.headers);
|
|
1489
|
+
if (currentSession.value?.accessToken) {
|
|
1490
|
+
try {
|
|
1491
|
+
const authHeaders = getAuthHeaders();
|
|
1492
|
+
Object.entries(authHeaders).forEach(([key, value]) => {
|
|
1493
|
+
headers.set(key, value);
|
|
1494
|
+
});
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
console.warn("[Strands Auth] Failed to get auth headers:", error);
|
|
1497
|
+
if (requireAuth) {
|
|
1498
|
+
throw error;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
const enhancedOptions = {
|
|
1503
|
+
...fetchOptions,
|
|
1504
|
+
headers
|
|
1505
|
+
};
|
|
1506
|
+
let response = await fetch(fullUrl, enhancedOptions);
|
|
1507
|
+
if (response.status === 401 && autoRefresh && currentSession.value?.refreshToken) {
|
|
1508
|
+
console.log("[Strands Auth] Request failed with 401, attempting token refresh...");
|
|
1509
|
+
try {
|
|
1510
|
+
const refreshed = await refreshToken();
|
|
1511
|
+
if (refreshed && currentSession.value?.accessToken) {
|
|
1512
|
+
const newAuthHeaders = getAuthHeaders();
|
|
1513
|
+
Object.entries(newAuthHeaders).forEach(([key, value]) => {
|
|
1514
|
+
headers.set(key, value);
|
|
1515
|
+
});
|
|
1516
|
+
console.log("[Strands Auth] Retrying request with refreshed token");
|
|
1517
|
+
response = await fetch(fullUrl, { ...enhancedOptions, headers });
|
|
1518
|
+
}
|
|
1519
|
+
} catch (refreshError) {
|
|
1520
|
+
console.error("[Strands Auth] Token refresh failed:", refreshError);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return response;
|
|
1524
|
+
};
|
|
1525
|
+
const get = (url, options) => {
|
|
1526
|
+
return authenticatedFetch(url, { ...options, method: "GET" });
|
|
1527
|
+
};
|
|
1528
|
+
const post = (url, body, options) => {
|
|
1529
|
+
const headers = new Headers(options?.headers);
|
|
1530
|
+
if (body && typeof body === "object" && !headers.has("Content-Type")) {
|
|
1531
|
+
headers.set("Content-Type", "application/json");
|
|
1532
|
+
}
|
|
1533
|
+
return authenticatedFetch(url, {
|
|
1534
|
+
...options,
|
|
1535
|
+
method: "POST",
|
|
1536
|
+
headers,
|
|
1537
|
+
body: typeof body === "object" ? JSON.stringify(body) : body
|
|
1538
|
+
});
|
|
1539
|
+
};
|
|
1540
|
+
const put = (url, body, options) => {
|
|
1541
|
+
const headers = new Headers(options?.headers);
|
|
1542
|
+
if (body && typeof body === "object" && !headers.has("Content-Type")) {
|
|
1543
|
+
headers.set("Content-Type", "application/json");
|
|
1544
|
+
}
|
|
1545
|
+
return authenticatedFetch(url, {
|
|
1546
|
+
...options,
|
|
1547
|
+
method: "PUT",
|
|
1548
|
+
headers,
|
|
1549
|
+
body: typeof body === "object" ? JSON.stringify(body) : body
|
|
1550
|
+
});
|
|
1551
|
+
};
|
|
1552
|
+
const del = (url, options) => {
|
|
1553
|
+
return authenticatedFetch(url, { ...options, method: "DELETE" });
|
|
1554
|
+
};
|
|
1555
|
+
const patch = (url, body, options) => {
|
|
1556
|
+
const headers = new Headers(options?.headers);
|
|
1557
|
+
if (body && typeof body === "object" && !headers.has("Content-Type")) {
|
|
1558
|
+
headers.set("Content-Type", "application/json");
|
|
1559
|
+
}
|
|
1560
|
+
return authenticatedFetch(url, {
|
|
1561
|
+
...options,
|
|
1562
|
+
method: "PATCH",
|
|
1563
|
+
headers,
|
|
1564
|
+
body: typeof body === "object" ? JSON.stringify(body) : body
|
|
1565
|
+
});
|
|
1566
|
+
};
|
|
1567
|
+
return {
|
|
1568
|
+
authenticatedFetch,
|
|
1569
|
+
get,
|
|
1570
|
+
post,
|
|
1571
|
+
put,
|
|
1572
|
+
delete: del,
|
|
1573
|
+
patch
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
const $authFetch = {
|
|
1577
|
+
get: async (url, options) => {
|
|
1578
|
+
const { get } = useAuthenticatedFetch();
|
|
1579
|
+
return get(url, options);
|
|
1580
|
+
},
|
|
1581
|
+
post: async (url, body, options) => {
|
|
1582
|
+
const { post } = useAuthenticatedFetch();
|
|
1583
|
+
return post(url, body, options);
|
|
1584
|
+
},
|
|
1585
|
+
put: async (url, body, options) => {
|
|
1586
|
+
const { put } = useAuthenticatedFetch();
|
|
1587
|
+
return put(url, body, options);
|
|
1588
|
+
},
|
|
1589
|
+
delete: async (url, options) => {
|
|
1590
|
+
const { delete: del } = useAuthenticatedFetch();
|
|
1591
|
+
return del(url, options);
|
|
1592
|
+
},
|
|
1593
|
+
patch: async (url, body, options) => {
|
|
1594
|
+
const { patch } = useAuthenticatedFetch();
|
|
1595
|
+
return patch(url, body, options);
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
export {
|
|
1599
|
+
$authFetch as $,
|
|
1600
|
+
STRANDS_AUTH_DEFAULTS as S,
|
|
1601
|
+
useStrandsMfa as a,
|
|
1602
|
+
useStrandsAuth as b,
|
|
1603
|
+
useOAuthProviders as c,
|
|
1604
|
+
useGlobalDarkMode as d,
|
|
1605
|
+
useAuthenticatedFetch as e,
|
|
1606
|
+
provideStrandsConfig as p,
|
|
1607
|
+
setStrandsConfig as s,
|
|
1608
|
+
useStrandsConfig as u
|
|
1609
|
+
};
|