@drmhse/authos-vue 0.2.7 → 0.8.0

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.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import { AUTH_OS_INJECTION_KEY, useAuthOS } from './chunk-YED35B36.mjs';
2
- export { AUTH_OS_INJECTION_KEY, useAuthOS } from './chunk-YED35B36.mjs';
3
- import './chunk-6DZX6EAA.mjs';
4
- import { defineComponent, reactive, provide, onMounted, onUnmounted, h, ref, computed, inject, nextTick } from 'vue';
5
- import { SsoClient, BrowserStorage, MemoryStorage, SsoApiError } from '@drmhse/sso-sdk';
6
- export { AuthErrorCodes, BrowserStorage, MemoryStorage, SsoApiError } from '@drmhse/sso-sdk';
7
-
8
- // src/styles.ts
9
- var AUTHOS_STYLES = `
1
+ import { n as AUTH_OS_INJECTION_KEY, t as useAuthOS } from "./useAuthOS-DEOIm_-6.mjs";
2
+ import { computed, defineComponent, h, inject, nextTick, onMounted, onUnmounted, provide, reactive, ref } from "vue";
3
+ import { AuthErrorCodes, BrowserStorage, BrowserStorage as BrowserStorage$1, MemoryStorage, MemoryStorage as MemoryStorage$1, SsoApiError, SsoApiError as SsoApiError$1, SsoClient } from "@drmhse/sso-sdk";
4
+ //#region src/styles.ts
5
+ /**
6
+ * Built-in styles for AuthOS Vue components.
7
+ * Provides polished default styling with CSS custom properties for theming.
8
+ */
9
+ const AUTHOS_STYLES = `
10
10
  /* ==========================================================================
11
11
  AuthOS Component Styles
12
12
  CSS Variables + Default Theme
@@ -435,1204 +435,1287 @@ var AUTHOS_STYLES = `
435
435
  opacity: 0.6;
436
436
  }
437
437
  `;
438
- var stylesInjected = false;
438
+ /**
439
+ * Injects the AuthOS styles into the document head.
440
+ * Only injects once, even if called multiple times.
441
+ */
442
+ let stylesInjected = false;
439
443
  function injectStyles() {
440
- if (stylesInjected) return;
441
- if (typeof document === "undefined") return;
442
- const styleElement = document.createElement("style");
443
- styleElement.setAttribute("data-authos-styles", "");
444
- styleElement.textContent = AUTHOS_STYLES;
445
- document.head.appendChild(styleElement);
446
- stylesInjected = true;
444
+ if (stylesInjected) return;
445
+ if (typeof document === "undefined") return;
446
+ const styleElement = document.createElement("style");
447
+ styleElement.setAttribute("data-authos-styles", "");
448
+ styleElement.textContent = AUTHOS_STYLES;
449
+ document.head.appendChild(styleElement);
450
+ stylesInjected = true;
447
451
  }
452
+ /**
453
+ * Applies custom CSS variable overrides for theming.
454
+ */
448
455
  function applyVariables(variables) {
449
- if (typeof document === "undefined") return;
450
- const root = document.documentElement;
451
- for (const [key, value] of Object.entries(variables)) {
452
- const cssVar = `--authos-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`;
453
- root.style.setProperty(cssVar, value);
454
- }
456
+ if (typeof document === "undefined") return;
457
+ const root = document.documentElement;
458
+ for (const [key, value] of Object.entries(variables)) {
459
+ const cssVar = `--authos-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`;
460
+ root.style.setProperty(cssVar, value);
461
+ }
455
462
  }
456
-
457
- // src/plugin.ts
463
+ //#endregion
464
+ //#region src/plugin.ts
458
465
  function createAuthOS(options) {
459
- const getStorage = () => {
460
- if (options.storage) return options.storage;
461
- try {
462
- if (typeof window !== "undefined" && window.localStorage) {
463
- return new BrowserStorage();
464
- }
465
- } catch {
466
- }
467
- return new MemoryStorage();
468
- };
469
- const client = new SsoClient({
470
- baseURL: options.baseURL,
471
- storage: getStorage(),
472
- token: options.initialToken
473
- // Pass initial token if provided
474
- });
475
- let hasSetInitialToken = false;
476
- const setInitialToken = async () => {
477
- if (options.initialToken && !hasSetInitialToken) {
478
- await client.setSession({ access_token: options.initialToken });
479
- hasSetInitialToken = true;
480
- }
481
- };
482
- const state = reactive({
483
- user: null,
484
- isAuthenticated: false,
485
- isLoading: !options.initialToken,
486
- // Skip loading if we have initial token
487
- currentOrganization: null,
488
- organizations: []
489
- });
490
- const context = {
491
- client,
492
- state,
493
- options
494
- };
495
- return {
496
- install(app) {
497
- injectStyles();
498
- if (options.appearance?.variables) {
499
- applyVariables(options.appearance.variables);
500
- }
501
- if (options.org && !options.service) {
502
- console.warn(
503
- '[AuthOS] You provided "org" but not "service". OAuth flows may not work correctly.'
504
- );
505
- }
506
- if (!options.org && options.service) {
507
- console.warn(
508
- '[AuthOS] You provided "service" but not "org". OAuth flows may not work correctly.'
509
- );
510
- }
511
- nextTick(() => {
512
- setInitialToken();
513
- });
514
- client.onAuthStateChange(async (isAuthenticated) => {
515
- state.isAuthenticated = isAuthenticated;
516
- state.isLoading = false;
517
- if (isAuthenticated) {
518
- try {
519
- const profile = await client.user.getProfile();
520
- state.user = profile;
521
- } catch {
522
- state.user = null;
523
- }
524
- try {
525
- const orgs = await client.organizations.list();
526
- state.organizations = orgs;
527
- if (orgs.length > 0 && !state.currentOrganization) {
528
- state.currentOrganization = orgs[0];
529
- }
530
- } catch {
531
- state.organizations = [];
532
- }
533
- } else {
534
- state.user = null;
535
- state.currentOrganization = null;
536
- state.organizations = [];
537
- }
538
- });
539
- app.provide(AUTH_OS_INJECTION_KEY, context);
540
- app.config.globalProperties.$authOS = context;
541
- }
542
- };
466
+ const getStorage = () => {
467
+ if (options.storage) return options.storage;
468
+ try {
469
+ if (typeof window !== "undefined" && window.localStorage) return new BrowserStorage$1();
470
+ } catch {}
471
+ return new MemoryStorage$1();
472
+ };
473
+ const client = new SsoClient({
474
+ baseURL: options.baseURL,
475
+ storage: getStorage(),
476
+ token: options.initialToken
477
+ });
478
+ let hasSetInitialToken = false;
479
+ const setInitialToken = async () => {
480
+ if (options.initialToken && !hasSetInitialToken) {
481
+ await client.setSession({ access_token: options.initialToken });
482
+ hasSetInitialToken = true;
483
+ }
484
+ };
485
+ const state = reactive({
486
+ user: null,
487
+ isAuthenticated: false,
488
+ isLoading: !options.initialToken,
489
+ currentOrganization: null,
490
+ organizations: []
491
+ });
492
+ const context = {
493
+ client,
494
+ state,
495
+ options
496
+ };
497
+ return { install(app) {
498
+ injectStyles();
499
+ if (options.appearance?.variables) applyVariables(options.appearance.variables);
500
+ if (options.org && !options.service) console.warn("[AuthOS] You provided \"org\" but not \"service\". OAuth flows may not work correctly.");
501
+ if (!options.org && options.service) console.warn("[AuthOS] You provided \"service\" but not \"org\". OAuth flows may not work correctly.");
502
+ nextTick(() => {
503
+ setInitialToken();
504
+ });
505
+ client.onAuthStateChange(async (isAuthenticated) => {
506
+ state.isAuthenticated = isAuthenticated;
507
+ state.isLoading = false;
508
+ if (isAuthenticated) {
509
+ try {
510
+ state.user = await client.user.getProfile();
511
+ } catch {
512
+ state.user = null;
513
+ }
514
+ try {
515
+ const orgs = await client.organizations.list();
516
+ state.organizations = orgs;
517
+ if (orgs.length > 0 && !state.currentOrganization) state.currentOrganization = orgs[0];
518
+ } catch {
519
+ state.organizations = [];
520
+ }
521
+ } else {
522
+ state.user = null;
523
+ state.currentOrganization = null;
524
+ state.organizations = [];
525
+ }
526
+ });
527
+ app.provide(AUTH_OS_INJECTION_KEY, context);
528
+ app.config.globalProperties.$authOS = context;
529
+ } };
543
530
  }
531
+ //#endregion
532
+ //#region src/composables/useUser.ts
544
533
  function useUser() {
545
- const context = inject(AUTH_OS_INJECTION_KEY);
546
- if (!context) {
547
- return {
548
- user: ref(null),
549
- isLoading: computed(() => false)
550
- };
551
- }
552
- const user = computed(() => context.state.user);
553
- const isLoading = computed(() => context.state.isLoading);
554
- return {
555
- user,
556
- isLoading
557
- };
534
+ const context = inject(AUTH_OS_INJECTION_KEY);
535
+ if (!context) return {
536
+ user: ref(null),
537
+ isLoading: computed(() => false)
538
+ };
539
+ return {
540
+ user: computed(() => context.state.user),
541
+ isLoading: computed(() => context.state.isLoading)
542
+ };
558
543
  }
544
+ //#endregion
545
+ //#region src/composables/useOrganization.ts
546
+ /**
547
+ * Composable to access the current organization context and switch between organizations.
548
+ *
549
+ * When switching organizations, this calls the backend to issue new JWT tokens
550
+ * with the organization context, enabling seamless organization switching without
551
+ * requiring re-authentication.
552
+ *
553
+ * @returns The current organization and a function to switch organizations
554
+ *
555
+ * @example
556
+ * ```vue
557
+ * <script setup>
558
+ * import { useOrganization } from '@drmhse/authos-vue';
559
+ *
560
+ * const { currentOrganization, switchOrganization, isSwitching } = useOrganization();
561
+ * <\/script>
562
+ * ```
563
+ */
559
564
  function useOrganization() {
560
- const context = inject(AUTH_OS_INJECTION_KEY);
561
- if (!context) {
562
- return {
563
- currentOrganization: ref(null),
564
- organizations: ref([]),
565
- switchOrganization: async () => null,
566
- isSwitching: ref(false)
567
- };
568
- }
569
- const currentOrganization = computed(() => context.state.currentOrganization);
570
- const organizations = computed(() => context.state.organizations);
571
- const isSwitching = ref(false);
572
- async function switchOrganization(slug) {
573
- if (!context) return;
574
- isSwitching.value = true;
575
- try {
576
- const result = await context.client.organizations.select(slug);
577
- await context.client.setSession({
578
- access_token: result.access_token,
579
- refresh_token: result.refresh_token
580
- });
581
- const orgResponse = await context.client.organizations.get(slug);
582
- context.state.currentOrganization = orgResponse;
583
- return orgResponse;
584
- } finally {
585
- isSwitching.value = false;
586
- }
587
- }
588
- return {
589
- currentOrganization,
590
- organizations,
591
- switchOrganization,
592
- isSwitching
593
- };
565
+ const context = inject(AUTH_OS_INJECTION_KEY);
566
+ if (!context) return {
567
+ currentOrganization: ref(null),
568
+ organizations: ref([]),
569
+ switchOrganization: async () => null,
570
+ isSwitching: ref(false)
571
+ };
572
+ const currentOrganization = computed(() => context.state.currentOrganization);
573
+ const organizations = computed(() => context.state.organizations);
574
+ const isSwitching = ref(false);
575
+ async function switchOrganization(slug) {
576
+ if (!context) return;
577
+ isSwitching.value = true;
578
+ try {
579
+ const result = await context.client.organizations.select(slug);
580
+ await context.client.setSession({
581
+ access_token: result.access_token,
582
+ refresh_token: result.refresh_token
583
+ });
584
+ const orgResponse = await context.client.organizations.get(slug);
585
+ context.state.currentOrganization = orgResponse;
586
+ return orgResponse;
587
+ } finally {
588
+ isSwitching.value = false;
589
+ }
590
+ }
591
+ return {
592
+ currentOrganization,
593
+ organizations,
594
+ switchOrganization,
595
+ isSwitching
596
+ };
594
597
  }
598
+ //#endregion
599
+ //#region src/composables/usePermission.ts
600
+ /**
601
+ * Check if the current user has a specific permission.
602
+ *
603
+ * @param permission The permission to check
604
+ * @returns A computed ref that is true if the user has the permission
605
+ *
606
+ * @example
607
+ * ```vue
608
+ * <script setup>
609
+ * import { usePermission } from '@drmhse/authos-vue';
610
+ *
611
+ * const canAccessAdmin = usePermission('admin:access');
612
+ * <\/script>
613
+ *
614
+ * <template>
615
+ * <button v-if="canAccessAdmin">Admin Panel</button>
616
+ * </template>
617
+ * ```
618
+ */
595
619
  function usePermission(permission) {
596
- const { user } = useUser();
597
- return computed(() => {
598
- if (!user.value || !permission) return false;
599
- return user.value.permissions?.includes(permission) ?? false;
600
- });
620
+ const { user } = useUser();
621
+ return computed(() => {
622
+ if (!user.value || !permission) return false;
623
+ return user.value.permissions?.includes(permission) ?? false;
624
+ });
601
625
  }
626
+ /**
627
+ * Check if the current user has any of the specified permissions.
628
+ *
629
+ * @param permissions The permissions to check
630
+ * @returns A computed ref that is true if the user has any of the permissions
631
+ *
632
+ * @example
633
+ * ```vue
634
+ * <script setup>
635
+ * import { useAnyPermission } from '@drmhse/authos-vue';
636
+ *
637
+ * const canAccessReports = useAnyPermission(['reports:read', 'admin:access']);
638
+ * <\/script>
639
+ * ```
640
+ */
602
641
  function useAnyPermission(permissions) {
603
- const { user } = useUser();
604
- return computed(() => {
605
- if (!user.value || permissions.length === 0) return false;
606
- return permissions.some((p) => user.value?.permissions?.includes(p));
607
- });
642
+ const { user } = useUser();
643
+ return computed(() => {
644
+ if (!user.value || permissions.length === 0) return false;
645
+ return permissions.some((p) => user.value?.permissions?.includes(p));
646
+ });
608
647
  }
648
+ /**
649
+ * Check if the current user has all of the specified permissions.
650
+ *
651
+ * @param permissions The permissions to check
652
+ * @returns A computed ref that is true if the user has all of the permissions
653
+ *
654
+ * @example
655
+ * ```vue
656
+ * <script setup>
657
+ * import { useAllPermissions } from '@drmhse/authos-vue';
658
+ *
659
+ * const canManageBilling = useAllPermissions(['billing:read', 'billing:write']);
660
+ * <\/script>
661
+ * ```
662
+ */
609
663
  function useAllPermissions(permissions) {
610
- const { user } = useUser();
611
- return computed(() => {
612
- if (!user.value || permissions.length === 0) return false;
613
- return permissions.every((p) => user.value?.permissions?.includes(p));
614
- });
664
+ const { user } = useUser();
665
+ return computed(() => {
666
+ if (!user.value || permissions.length === 0) return false;
667
+ return permissions.every((p) => user.value?.permissions?.includes(p));
668
+ });
615
669
  }
616
- var AuthOSProvider = defineComponent({
617
- name: "AuthOSProvider",
618
- props: {
619
- baseURL: {
620
- type: String,
621
- required: true
622
- },
623
- org: {
624
- type: String,
625
- default: void 0
626
- },
627
- service: {
628
- type: String,
629
- default: void 0
630
- },
631
- redirectUri: {
632
- type: String,
633
- default: void 0
634
- },
635
- storage: {
636
- type: Object,
637
- default: void 0
638
- },
639
- client: {
640
- type: Object,
641
- default: void 0
642
- }
643
- },
644
- setup(props, { slots }) {
645
- const getStorage = () => {
646
- if (props.storage) return props.storage;
647
- if (typeof window !== "undefined") return new BrowserStorage();
648
- return new MemoryStorage();
649
- };
650
- const client = props.client ?? new SsoClient({
651
- baseURL: props.baseURL,
652
- storage: getStorage()
653
- });
654
- const state = reactive({
655
- user: null,
656
- isAuthenticated: false,
657
- isLoading: true,
658
- currentOrganization: null,
659
- organizations: []
660
- });
661
- const options = {
662
- baseURL: props.baseURL,
663
- org: props.org,
664
- service: props.service,
665
- redirectUri: props.redirectUri
666
- };
667
- const context = { client, state, options };
668
- provide(AUTH_OS_INJECTION_KEY, context);
669
- let unsubscribe;
670
- onMounted(() => {
671
- unsubscribe = client.onAuthStateChange(async (isAuthenticated) => {
672
- state.isAuthenticated = isAuthenticated;
673
- state.isLoading = false;
674
- if (isAuthenticated) {
675
- try {
676
- const profile = await client.user.getProfile();
677
- state.user = profile;
678
- } catch {
679
- state.user = null;
680
- }
681
- try {
682
- const orgs = await client.organizations.list();
683
- state.organizations = orgs;
684
- if (orgs.length > 0 && !state.currentOrganization) {
685
- state.currentOrganization = orgs[0];
686
- }
687
- } catch {
688
- state.organizations = [];
689
- }
690
- } else {
691
- state.user = null;
692
- state.currentOrganization = null;
693
- state.organizations = [];
694
- }
695
- });
696
- });
697
- onUnmounted(() => {
698
- unsubscribe?.();
699
- });
700
- return () => slots.default ? slots.default() : h("div");
701
- }
670
+ //#endregion
671
+ //#region src/components/AuthOSProvider.ts
672
+ const AuthOSProvider = defineComponent({
673
+ name: "AuthOSProvider",
674
+ props: {
675
+ baseURL: {
676
+ type: String,
677
+ required: true
678
+ },
679
+ org: {
680
+ type: String,
681
+ default: void 0
682
+ },
683
+ service: {
684
+ type: String,
685
+ default: void 0
686
+ },
687
+ redirectUri: {
688
+ type: String,
689
+ default: void 0
690
+ },
691
+ storage: {
692
+ type: Object,
693
+ default: void 0
694
+ },
695
+ client: {
696
+ type: Object,
697
+ default: void 0
698
+ }
699
+ },
700
+ setup(props, { slots }) {
701
+ const getStorage = () => {
702
+ if (props.storage) return props.storage;
703
+ if (typeof window !== "undefined") return new BrowserStorage$1();
704
+ return new MemoryStorage$1();
705
+ };
706
+ const client = props.client ?? new SsoClient({
707
+ baseURL: props.baseURL,
708
+ storage: getStorage()
709
+ });
710
+ const state = reactive({
711
+ user: null,
712
+ isAuthenticated: false,
713
+ isLoading: true,
714
+ currentOrganization: null,
715
+ organizations: []
716
+ });
717
+ provide(AUTH_OS_INJECTION_KEY, {
718
+ client,
719
+ state,
720
+ options: {
721
+ baseURL: props.baseURL,
722
+ org: props.org,
723
+ service: props.service,
724
+ redirectUri: props.redirectUri
725
+ }
726
+ });
727
+ let unsubscribe;
728
+ onMounted(() => {
729
+ unsubscribe = client.onAuthStateChange(async (isAuthenticated) => {
730
+ state.isAuthenticated = isAuthenticated;
731
+ state.isLoading = false;
732
+ if (isAuthenticated) {
733
+ try {
734
+ state.user = await client.user.getProfile();
735
+ } catch {
736
+ state.user = null;
737
+ }
738
+ try {
739
+ const orgs = await client.organizations.list();
740
+ state.organizations = orgs;
741
+ if (orgs.length > 0 && !state.currentOrganization) state.currentOrganization = orgs[0];
742
+ } catch {
743
+ state.organizations = [];
744
+ }
745
+ } else {
746
+ state.user = null;
747
+ state.currentOrganization = null;
748
+ state.organizations = [];
749
+ }
750
+ });
751
+ });
752
+ onUnmounted(() => {
753
+ unsubscribe?.();
754
+ });
755
+ return () => slots.default ? slots.default() : h("div");
756
+ }
702
757
  });
703
- var MFA_PREAUTH_EXPIRY = 300;
704
- var SignIn = defineComponent({
705
- name: "SignIn",
706
- props: {
707
- onSuccess: {
708
- type: Function,
709
- default: void 0
710
- },
711
- onError: {
712
- type: Function,
713
- default: void 0
714
- }
715
- },
716
- emits: ["success", "error"],
717
- setup(props, { slots, emit }) {
718
- const { client, options } = useAuthOS();
719
- const email = ref("");
720
- const password = ref("");
721
- const mfaCode = ref("");
722
- const preauthToken = ref("");
723
- const step = ref("credentials");
724
- const error = ref(null);
725
- const isSubmitting = ref(false);
726
- async function submit() {
727
- error.value = null;
728
- isSubmitting.value = true;
729
- try {
730
- if (step.value === "credentials") {
731
- const result = await client.auth.login({
732
- email: email.value,
733
- password: password.value,
734
- org_slug: options.org,
735
- service_slug: options.service
736
- });
737
- if (result.expires_in === MFA_PREAUTH_EXPIRY) {
738
- preauthToken.value = result.access_token;
739
- step.value = "mfa";
740
- } else {
741
- emit("success");
742
- props.onSuccess?.();
743
- }
744
- } else {
745
- await client.auth.verifyMfa(preauthToken.value, mfaCode.value);
746
- emit("success");
747
- props.onSuccess?.();
748
- }
749
- } catch (err) {
750
- const message = err instanceof SsoApiError ? err.message : "Login failed";
751
- error.value = message;
752
- const e = err instanceof Error ? err : new Error(message);
753
- emit("error", e);
754
- props.onError?.(e);
755
- } finally {
756
- isSubmitting.value = false;
757
- }
758
- }
759
- return () => {
760
- const slotProps = {
761
- email: email.value,
762
- password: password.value,
763
- mfaCode: mfaCode.value,
764
- step: step.value,
765
- error: error.value,
766
- isSubmitting: isSubmitting.value,
767
- updateEmail: (v) => email.value = v,
768
- updatePassword: (v) => password.value = v,
769
- updateMfaCode: (v) => mfaCode.value = v,
770
- submit
771
- };
772
- if (slots.default) {
773
- return slots.default(slotProps);
774
- }
775
- if (step.value === "mfa") {
776
- return h("div", { "data-authos-signin": "", "data-state": "mfa" }, [
777
- h("form", { onSubmit: (e) => {
778
- e.preventDefault();
779
- submit();
780
- } }, [
781
- h("div", { "data-authos-field": "mfa-code" }, [
782
- h("label", { for: "authos-mfa-code" }, "Verification Code"),
783
- h("input", {
784
- id: "authos-mfa-code",
785
- type: "text",
786
- inputMode: "numeric",
787
- autocomplete: "one-time-code",
788
- value: mfaCode.value,
789
- placeholder: "Enter 6-digit code",
790
- required: true,
791
- disabled: isSubmitting.value,
792
- onInput: (e) => mfaCode.value = e.target.value
793
- })
794
- ]),
795
- error.value && h("div", { "data-authos-error": "" }, error.value),
796
- h("button", {
797
- type: "submit",
798
- disabled: isSubmitting.value,
799
- "data-authos-submit": ""
800
- }, isSubmitting.value ? "Verifying..." : "Verify"),
801
- h("button", {
802
- type: "button",
803
- "data-authos-back": "",
804
- onClick: () => {
805
- step.value = "credentials";
806
- mfaCode.value = "";
807
- preauthToken.value = "";
808
- error.value = null;
809
- }
810
- }, "Back to login")
811
- ])
812
- ]);
813
- }
814
- return h("div", { "data-authos-signin": "", "data-state": "credentials" }, [
815
- h("form", { onSubmit: (e) => {
816
- e.preventDefault();
817
- submit();
818
- } }, [
819
- h("div", { "data-authos-field": "email" }, [
820
- h("label", { for: "authos-email" }, "Email"),
821
- h("input", {
822
- id: "authos-email",
823
- type: "email",
824
- autocomplete: "email",
825
- value: email.value,
826
- placeholder: "Enter your email",
827
- required: true,
828
- disabled: isSubmitting.value,
829
- onInput: (e) => email.value = e.target.value
830
- })
831
- ]),
832
- h("div", { "data-authos-field": "password" }, [
833
- h("label", { for: "authos-password" }, "Password"),
834
- h("input", {
835
- id: "authos-password",
836
- type: "password",
837
- autocomplete: "current-password",
838
- value: password.value,
839
- placeholder: "Enter your password",
840
- required: true,
841
- disabled: isSubmitting.value,
842
- onInput: (e) => password.value = e.target.value
843
- })
844
- ]),
845
- error.value && h("div", { "data-authos-error": "" }, error.value),
846
- h("button", {
847
- type: "submit",
848
- disabled: isSubmitting.value,
849
- "data-authos-submit": ""
850
- }, isSubmitting.value ? "Signing in..." : "Sign In")
851
- ])
852
- ]);
853
- };
854
- }
758
+ //#endregion
759
+ //#region src/components/SignIn.ts
760
+ const MFA_PREAUTH_EXPIRY = 300;
761
+ const SignIn = defineComponent({
762
+ name: "SignIn",
763
+ props: {
764
+ onSuccess: {
765
+ type: Function,
766
+ default: void 0
767
+ },
768
+ onError: {
769
+ type: Function,
770
+ default: void 0
771
+ }
772
+ },
773
+ emits: ["success", "error"],
774
+ setup(props, { slots, emit }) {
775
+ const { client, options } = useAuthOS();
776
+ const email = ref("");
777
+ const password = ref("");
778
+ const mfaCode = ref("");
779
+ const preauthToken = ref("");
780
+ const step = ref("credentials");
781
+ const error = ref(null);
782
+ const isSubmitting = ref(false);
783
+ async function submit() {
784
+ error.value = null;
785
+ isSubmitting.value = true;
786
+ try {
787
+ if (step.value === "credentials") {
788
+ const result = await client.auth.login({
789
+ email: email.value,
790
+ password: password.value,
791
+ org_slug: options.org,
792
+ service_slug: options.service
793
+ });
794
+ if (result.expires_in === MFA_PREAUTH_EXPIRY) {
795
+ preauthToken.value = result.access_token;
796
+ step.value = "mfa";
797
+ } else {
798
+ emit("success");
799
+ props.onSuccess?.();
800
+ }
801
+ } else {
802
+ await client.auth.verifyMfa(preauthToken.value, mfaCode.value);
803
+ emit("success");
804
+ props.onSuccess?.();
805
+ }
806
+ } catch (err) {
807
+ const message = err instanceof SsoApiError$1 ? err.message : "Login failed";
808
+ error.value = message;
809
+ const e = err instanceof Error ? err : new Error(message);
810
+ emit("error", e);
811
+ props.onError?.(e);
812
+ } finally {
813
+ isSubmitting.value = false;
814
+ }
815
+ }
816
+ return () => {
817
+ const slotProps = {
818
+ email: email.value,
819
+ password: password.value,
820
+ mfaCode: mfaCode.value,
821
+ step: step.value,
822
+ error: error.value,
823
+ isSubmitting: isSubmitting.value,
824
+ updateEmail: (v) => email.value = v,
825
+ updatePassword: (v) => password.value = v,
826
+ updateMfaCode: (v) => mfaCode.value = v,
827
+ submit
828
+ };
829
+ if (slots.default) return slots.default(slotProps);
830
+ if (step.value === "mfa") return h("div", {
831
+ "data-authos-signin": "",
832
+ "data-state": "mfa"
833
+ }, [h("form", { onSubmit: (e) => {
834
+ e.preventDefault();
835
+ submit();
836
+ } }, [
837
+ h("div", { "data-authos-field": "mfa-code" }, [h("label", { for: "authos-mfa-code" }, "Verification Code"), h("input", {
838
+ id: "authos-mfa-code",
839
+ type: "text",
840
+ inputMode: "numeric",
841
+ autocomplete: "one-time-code",
842
+ value: mfaCode.value,
843
+ placeholder: "Enter 6-digit code",
844
+ required: true,
845
+ disabled: isSubmitting.value,
846
+ onInput: (e) => mfaCode.value = e.target.value
847
+ })]),
848
+ error.value && h("div", { "data-authos-error": "" }, error.value),
849
+ h("button", {
850
+ type: "submit",
851
+ disabled: isSubmitting.value,
852
+ "data-authos-submit": ""
853
+ }, isSubmitting.value ? "Verifying..." : "Verify"),
854
+ h("button", {
855
+ type: "button",
856
+ "data-authos-back": "",
857
+ onClick: () => {
858
+ step.value = "credentials";
859
+ mfaCode.value = "";
860
+ preauthToken.value = "";
861
+ error.value = null;
862
+ }
863
+ }, "Back to login")
864
+ ])]);
865
+ return h("div", {
866
+ "data-authos-signin": "",
867
+ "data-state": "credentials"
868
+ }, [h("form", { onSubmit: (e) => {
869
+ e.preventDefault();
870
+ submit();
871
+ } }, [
872
+ h("div", { "data-authos-field": "email" }, [h("label", { for: "authos-email" }, "Email"), h("input", {
873
+ id: "authos-email",
874
+ type: "email",
875
+ autocomplete: "email",
876
+ value: email.value,
877
+ placeholder: "Enter your email",
878
+ required: true,
879
+ disabled: isSubmitting.value,
880
+ onInput: (e) => email.value = e.target.value
881
+ })]),
882
+ h("div", { "data-authos-field": "password" }, [h("label", { for: "authos-password" }, "Password"), h("input", {
883
+ id: "authos-password",
884
+ type: "password",
885
+ autocomplete: "current-password",
886
+ value: password.value,
887
+ placeholder: "Enter your password",
888
+ required: true,
889
+ disabled: isSubmitting.value,
890
+ onInput: (e) => password.value = e.target.value
891
+ })]),
892
+ error.value && h("div", { "data-authos-error": "" }, error.value),
893
+ h("button", {
894
+ type: "submit",
895
+ disabled: isSubmitting.value,
896
+ "data-authos-submit": ""
897
+ }, isSubmitting.value ? "Signing in..." : "Sign In")
898
+ ])]);
899
+ };
900
+ }
855
901
  });
856
- var PROVIDER_NAMES = {
857
- github: "GitHub",
858
- google: "Google",
859
- microsoft: "Microsoft"
902
+ //#endregion
903
+ //#region src/components/OAuthButton.ts
904
+ /**
905
+ * Human-readable provider names for button labels
906
+ */
907
+ const PROVIDER_NAMES = {
908
+ github: "GitHub",
909
+ google: "Google",
910
+ microsoft: "Microsoft"
860
911
  };
912
+ /**
913
+ * SVG icons for each OAuth provider
914
+ */
861
915
  function getProviderIcon(provider) {
862
- switch (provider) {
863
- case "github":
864
- return h("svg", {
865
- xmlns: "http://www.w3.org/2000/svg",
866
- viewBox: "0 0 24 24",
867
- fill: "currentColor",
868
- "aria-hidden": "true"
869
- }, [
870
- h("path", {
871
- d: "M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"
872
- })
873
- ]);
874
- case "google":
875
- return h("svg", {
876
- xmlns: "http://www.w3.org/2000/svg",
877
- viewBox: "0 0 24 24",
878
- "aria-hidden": "true"
879
- }, [
880
- h("path", {
881
- fill: "#4285F4",
882
- d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
883
- }),
884
- h("path", {
885
- fill: "#34A853",
886
- d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
887
- }),
888
- h("path", {
889
- fill: "#FBBC05",
890
- d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
891
- }),
892
- h("path", {
893
- fill: "#EA4335",
894
- d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
895
- })
896
- ]);
897
- case "microsoft":
898
- return h("svg", {
899
- xmlns: "http://www.w3.org/2000/svg",
900
- viewBox: "0 0 23 23",
901
- "aria-hidden": "true"
902
- }, [
903
- h("path", { fill: "#f35325", d: "M1 1h10v10H1z" }),
904
- h("path", { fill: "#81bc06", d: "M12 1h10v10H12z" }),
905
- h("path", { fill: "#05a6f0", d: "M1 12h10v10H1z" }),
906
- h("path", { fill: "#ffba08", d: "M12 12h10v10H12z" })
907
- ]);
908
- }
916
+ switch (provider) {
917
+ case "github": return h("svg", {
918
+ xmlns: "http://www.w3.org/2000/svg",
919
+ viewBox: "0 0 24 24",
920
+ fill: "currentColor",
921
+ "aria-hidden": "true"
922
+ }, [h("path", { d: "M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z" })]);
923
+ case "google": return h("svg", {
924
+ xmlns: "http://www.w3.org/2000/svg",
925
+ viewBox: "0 0 24 24",
926
+ "aria-hidden": "true"
927
+ }, [
928
+ h("path", {
929
+ fill: "#4285F4",
930
+ d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
931
+ }),
932
+ h("path", {
933
+ fill: "#34A853",
934
+ d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
935
+ }),
936
+ h("path", {
937
+ fill: "#FBBC05",
938
+ d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
939
+ }),
940
+ h("path", {
941
+ fill: "#EA4335",
942
+ d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
943
+ })
944
+ ]);
945
+ case "microsoft": return h("svg", {
946
+ xmlns: "http://www.w3.org/2000/svg",
947
+ viewBox: "0 0 23 23",
948
+ "aria-hidden": "true"
949
+ }, [
950
+ h("path", {
951
+ fill: "#f35325",
952
+ d: "M1 1h10v10H1z"
953
+ }),
954
+ h("path", {
955
+ fill: "#81bc06",
956
+ d: "M12 1h10v10H12z"
957
+ }),
958
+ h("path", {
959
+ fill: "#05a6f0",
960
+ d: "M1 12h10v10H1z"
961
+ }),
962
+ h("path", {
963
+ fill: "#ffba08",
964
+ d: "M12 12h10v10H12z"
965
+ })
966
+ ]);
967
+ }
909
968
  }
910
- var OAuthButton = defineComponent({
911
- name: "OAuthButton",
912
- props: {
913
- provider: {
914
- type: String,
915
- required: true
916
- },
917
- disabled: {
918
- type: Boolean,
919
- default: false
920
- }
921
- },
922
- emits: ["redirect"],
923
- setup(props, { slots, emit }) {
924
- const { client, options } = useAuthOS();
925
- const isConfigured = computed(() => !!(options.org && options.service));
926
- const providerName = computed(() => PROVIDER_NAMES[props.provider]);
927
- function handleClick() {
928
- if (!options.org || !options.service) {
929
- console.error(
930
- `[AuthOS] OAuth login requires "org" and "service" in createAuthOS options.
931
- Current options: { org: ${options.org ? `"${options.org}"` : "undefined"}, service: ${options.service ? `"${options.service}"` : "undefined"} }
932
-
933
- Example:
934
- app.use(createAuthOS({
935
- baseURL: "${options.baseURL}",
936
- org: "your-org-slug",
937
- service: "your-service-slug",
938
- }));
939
-
940
- See: https://docs.authos.dev/vue/oauth-setup`
941
- );
942
- return;
943
- }
944
- const redirectUri = options.redirectUri ?? (typeof window !== "undefined" ? window.location.origin + "/callback" : void 0);
945
- const url = client.auth.getLoginUrl(props.provider, {
946
- org: options.org,
947
- service: options.service,
948
- redirect_uri: redirectUri
949
- });
950
- emit("redirect");
951
- window.location.href = url;
952
- }
953
- return () => {
954
- const slotProps = {
955
- provider: props.provider,
956
- providerName: providerName.value,
957
- isConfigured: isConfigured.value,
958
- disabled: props.disabled,
959
- handleClick
960
- };
961
- if (slots.default) {
962
- return slots.default(slotProps);
963
- }
964
- return h(
965
- "button",
966
- {
967
- type: "button",
968
- onClick: handleClick,
969
- disabled: props.disabled || !isConfigured.value,
970
- "data-authos-oauth": "",
971
- "data-provider": props.provider
972
- },
973
- [
974
- getProviderIcon(props.provider),
975
- h("span", `Continue with ${providerName.value}`)
976
- ]
977
- );
978
- };
979
- }
969
+ /**
970
+ * OAuth login button for a specific provider.
971
+ * Redirects the user to the OAuth provider's login page.
972
+ *
973
+ * Requires `org` and `service` to be configured in createAuthOS options.
974
+ *
975
+ * @example
976
+ * ```vue
977
+ * <script setup>
978
+ * import { OAuthButton } from '@drmhse/authos-vue';
979
+ * <\/script>
980
+ *
981
+ * <template>
982
+ * <OAuthButton provider="github" />
983
+ * <OAuthButton provider="google">Sign in with Google</OAuthButton>
984
+ * </template>
985
+ * ```
986
+ */
987
+ const OAuthButton = defineComponent({
988
+ name: "OAuthButton",
989
+ props: {
990
+ provider: {
991
+ type: String,
992
+ required: true
993
+ },
994
+ disabled: {
995
+ type: Boolean,
996
+ default: false
997
+ }
998
+ },
999
+ emits: ["redirect"],
1000
+ setup(props, { slots, emit }) {
1001
+ const { client, options } = useAuthOS();
1002
+ const isConfigured = computed(() => !!(options.org && options.service));
1003
+ const providerName = computed(() => PROVIDER_NAMES[props.provider]);
1004
+ function handleClick() {
1005
+ if (!options.org || !options.service) {
1006
+ console.error(`[AuthOS] OAuth login requires "org" and "service" in createAuthOS options.\nCurrent options: { org: ${options.org ? `"${options.org}"` : "undefined"}, service: ${options.service ? `"${options.service}"` : "undefined"} }\n\nExample:\n app.use(createAuthOS({\n baseURL: "${options.baseURL}",\n org: "your-org-slug",\n service: "your-service-slug",\n }));\n\nSee: https://docs.authos.dev/vue/oauth-setup`);
1007
+ return;
1008
+ }
1009
+ const redirectUri = options.redirectUri ?? (typeof window !== "undefined" ? window.location.origin + "/callback" : void 0);
1010
+ const url = client.auth.getLoginUrl(props.provider, {
1011
+ org: options.org,
1012
+ service: options.service,
1013
+ redirect_uri: redirectUri
1014
+ });
1015
+ emit("redirect");
1016
+ window.location.href = url;
1017
+ }
1018
+ return () => {
1019
+ const slotProps = {
1020
+ provider: props.provider,
1021
+ providerName: providerName.value,
1022
+ isConfigured: isConfigured.value,
1023
+ disabled: props.disabled,
1024
+ handleClick
1025
+ };
1026
+ if (slots.default) return slots.default(slotProps);
1027
+ return h("button", {
1028
+ type: "button",
1029
+ onClick: handleClick,
1030
+ disabled: props.disabled || !isConfigured.value,
1031
+ "data-authos-oauth": "",
1032
+ "data-provider": props.provider
1033
+ }, [getProviderIcon(props.provider), h("span", `Continue with ${providerName.value}`)]);
1034
+ };
1035
+ }
980
1036
  });
981
-
982
- // src/components/SignUp.ts
983
- var SignUp = defineComponent({
984
- name: "SignUp",
985
- props: {
986
- onSuccess: {
987
- type: Function,
988
- default: void 0
989
- },
990
- onError: {
991
- type: Function,
992
- default: void 0
993
- },
994
- /** Organization slug for tenant context */
995
- orgSlug: {
996
- type: String,
997
- default: void 0
998
- },
999
- /** Service slug for tenant attribution (used with orgSlug) */
1000
- serviceSlug: {
1001
- type: String,
1002
- default: void 0
1003
- },
1004
- /** List of OAuth providers to display buttons for */
1005
- providers: {
1006
- type: [Array, Boolean],
1007
- default: false
1008
- },
1009
- /** Show divider between OAuth and email form */
1010
- showDivider: {
1011
- type: Boolean,
1012
- default: true
1013
- },
1014
- /** Show sign in link */
1015
- showSignIn: {
1016
- type: Boolean,
1017
- default: true
1018
- }
1019
- },
1020
- emits: ["success", "error"],
1021
- setup(props, { slots, emit }) {
1022
- const { client, options } = useAuthOS();
1023
- const email = ref("");
1024
- const password = ref("");
1025
- const confirmPassword = ref("");
1026
- const error = ref(null);
1027
- const isSubmitting = ref(false);
1028
- const isSuccess = ref(false);
1029
- const hasOAuthConfig = !!(options.org && options.service);
1030
- const oauthProviders = Array.isArray(props.providers) ? props.providers : [];
1031
- async function submit() {
1032
- error.value = null;
1033
- if (password.value !== confirmPassword.value) {
1034
- error.value = "Passwords do not match";
1035
- return;
1036
- }
1037
- if (password.value.length < 8) {
1038
- error.value = "Password must be at least 8 characters";
1039
- return;
1040
- }
1041
- isSubmitting.value = true;
1042
- try {
1043
- await client.auth.register({
1044
- email: email.value,
1045
- password: password.value,
1046
- org_slug: props.orgSlug ?? options.org,
1047
- service_slug: props.serviceSlug ?? options.service
1048
- });
1049
- isSuccess.value = true;
1050
- emit("success");
1051
- props.onSuccess?.();
1052
- } catch (err) {
1053
- const message = err instanceof SsoApiError ? err.message : "Registration failed";
1054
- error.value = message;
1055
- const e = err instanceof Error ? err : new Error(message);
1056
- emit("error", e);
1057
- props.onError?.(e);
1058
- } finally {
1059
- isSubmitting.value = false;
1060
- }
1061
- }
1062
- return () => {
1063
- const slotProps = {
1064
- email: email.value,
1065
- password: password.value,
1066
- error: error.value,
1067
- isSubmitting: isSubmitting.value,
1068
- updateEmail: (v) => email.value = v,
1069
- updatePassword: (v) => password.value = v,
1070
- submit
1071
- };
1072
- if (slots.default) {
1073
- return slots.default(slotProps);
1074
- }
1075
- if (isSuccess.value) {
1076
- return h("div", { "data-authos-signup": "", "data-state": "success" }, [
1077
- h("div", { "data-authos-success": "" }, [
1078
- h("h2", "Check your email"),
1079
- h("p", `We've sent a verification link to ${email.value}. Please click the link to verify your account.`)
1080
- ])
1081
- ]);
1082
- }
1083
- return h("div", { "data-authos-signup": "", "data-state": "credentials" }, [
1084
- // OAuth Section
1085
- oauthProviders.length > 0 && h("div", { "data-authos-oauth-section": "" }, [
1086
- oauthProviders.map(
1087
- (provider) => h(OAuthButton, {
1088
- key: provider,
1089
- provider,
1090
- disabled: isSubmitting.value || !hasOAuthConfig
1091
- })
1092
- ),
1093
- !hasOAuthConfig && h("p", {
1094
- "data-authos-oauth-warning": "",
1095
- style: { color: "orange", fontSize: "0.875rem" }
1096
- }, "OAuth requires org and service in plugin options")
1097
- ]),
1098
- // Divider
1099
- oauthProviders.length > 0 && props.showDivider && h("div", { "data-authos-divider": "" }, [
1100
- h("span", "or")
1101
- ]),
1102
- h("form", { onSubmit: (e) => {
1103
- e.preventDefault();
1104
- submit();
1105
- } }, [
1106
- h("div", { "data-authos-field": "email" }, [
1107
- h("label", { for: "authos-signup-email" }, "Email"),
1108
- h("input", {
1109
- id: "authos-signup-email",
1110
- type: "email",
1111
- autocomplete: "email",
1112
- value: email.value,
1113
- placeholder: "Enter your email",
1114
- required: true,
1115
- disabled: isSubmitting.value,
1116
- onInput: (e) => email.value = e.target.value
1117
- })
1118
- ]),
1119
- h("div", { "data-authos-field": "password" }, [
1120
- h("label", { for: "authos-signup-password" }, "Password"),
1121
- h("input", {
1122
- id: "authos-signup-password",
1123
- type: "password",
1124
- autocomplete: "new-password",
1125
- value: password.value,
1126
- placeholder: "Create a password",
1127
- required: true,
1128
- disabled: isSubmitting.value,
1129
- onInput: (e) => password.value = e.target.value
1130
- })
1131
- ]),
1132
- // Confirm Password
1133
- h("div", { "data-authos-field": "confirm-password" }, [
1134
- h("label", { for: "authos-signup-confirm" }, "Confirm Password"),
1135
- h("input", {
1136
- id: "authos-signup-confirm",
1137
- type: "password",
1138
- autocomplete: "new-password",
1139
- value: confirmPassword.value,
1140
- placeholder: "Confirm your password",
1141
- required: true,
1142
- disabled: isSubmitting.value,
1143
- onInput: (e) => confirmPassword.value = e.target.value
1144
- })
1145
- ]),
1146
- error.value && h("div", { "data-authos-error": "" }, error.value),
1147
- h("button", {
1148
- type: "submit",
1149
- disabled: isSubmitting.value,
1150
- "data-authos-submit": ""
1151
- }, isSubmitting.value ? "Creating account..." : "Sign Up"),
1152
- // Sign In Link
1153
- props.showSignIn && h("div", { "data-authos-signin-prompt": "" }, [
1154
- "Already have an account? ",
1155
- h("a", { href: "/signin", "data-authos-link": "signin" }, "Sign in")
1156
- ])
1157
- ])
1158
- ]);
1159
- };
1160
- }
1037
+ //#endregion
1038
+ //#region src/components/SignUp.ts
1039
+ const SignUp = defineComponent({
1040
+ name: "SignUp",
1041
+ props: {
1042
+ onSuccess: {
1043
+ type: Function,
1044
+ default: void 0
1045
+ },
1046
+ onError: {
1047
+ type: Function,
1048
+ default: void 0
1049
+ },
1050
+ /** Organization slug for tenant context */
1051
+ orgSlug: {
1052
+ type: String,
1053
+ default: void 0
1054
+ },
1055
+ /** Service slug for tenant attribution (used with orgSlug) */
1056
+ serviceSlug: {
1057
+ type: String,
1058
+ default: void 0
1059
+ },
1060
+ /** List of OAuth providers to display buttons for */
1061
+ providers: {
1062
+ type: [Array, Boolean],
1063
+ default: false
1064
+ },
1065
+ /** Show divider between OAuth and email form */
1066
+ showDivider: {
1067
+ type: Boolean,
1068
+ default: true
1069
+ },
1070
+ /** Show sign in link */
1071
+ showSignIn: {
1072
+ type: Boolean,
1073
+ default: true
1074
+ }
1075
+ },
1076
+ emits: ["success", "error"],
1077
+ setup(props, { slots, emit }) {
1078
+ const { client, options } = useAuthOS();
1079
+ const email = ref("");
1080
+ const password = ref("");
1081
+ const confirmPassword = ref("");
1082
+ const error = ref(null);
1083
+ const isSubmitting = ref(false);
1084
+ const isSuccess = ref(false);
1085
+ const hasOAuthConfig = !!(options.org && options.service);
1086
+ const oauthProviders = Array.isArray(props.providers) ? props.providers : [];
1087
+ async function submit() {
1088
+ error.value = null;
1089
+ if (password.value !== confirmPassword.value) {
1090
+ error.value = "Passwords do not match";
1091
+ return;
1092
+ }
1093
+ if (password.value.length < 8) {
1094
+ error.value = "Password must be at least 8 characters";
1095
+ return;
1096
+ }
1097
+ isSubmitting.value = true;
1098
+ try {
1099
+ await client.auth.register({
1100
+ email: email.value,
1101
+ password: password.value,
1102
+ org_slug: props.orgSlug ?? options.org,
1103
+ service_slug: props.serviceSlug ?? options.service
1104
+ });
1105
+ isSuccess.value = true;
1106
+ emit("success");
1107
+ props.onSuccess?.();
1108
+ } catch (err) {
1109
+ const message = err instanceof SsoApiError$1 ? err.message : "Registration failed";
1110
+ error.value = message;
1111
+ const e = err instanceof Error ? err : new Error(message);
1112
+ emit("error", e);
1113
+ props.onError?.(e);
1114
+ } finally {
1115
+ isSubmitting.value = false;
1116
+ }
1117
+ }
1118
+ return () => {
1119
+ const slotProps = {
1120
+ email: email.value,
1121
+ password: password.value,
1122
+ error: error.value,
1123
+ isSubmitting: isSubmitting.value,
1124
+ updateEmail: (v) => email.value = v,
1125
+ updatePassword: (v) => password.value = v,
1126
+ submit
1127
+ };
1128
+ if (slots.default) return slots.default(slotProps);
1129
+ if (isSuccess.value) return h("div", {
1130
+ "data-authos-signup": "",
1131
+ "data-state": "success"
1132
+ }, [h("div", { "data-authos-success": "" }, [h("h2", "Check your email"), h("p", `We've sent a verification link to ${email.value}. Please click the link to verify your account.`)])]);
1133
+ return h("div", {
1134
+ "data-authos-signup": "",
1135
+ "data-state": "credentials"
1136
+ }, [
1137
+ oauthProviders.length > 0 && h("div", { "data-authos-oauth-section": "" }, [oauthProviders.map((provider) => h(OAuthButton, {
1138
+ key: provider,
1139
+ provider,
1140
+ disabled: isSubmitting.value || !hasOAuthConfig
1141
+ })), !hasOAuthConfig && h("p", {
1142
+ "data-authos-oauth-warning": "",
1143
+ style: {
1144
+ color: "orange",
1145
+ fontSize: "0.875rem"
1146
+ }
1147
+ }, "OAuth requires org and service in plugin options")]),
1148
+ oauthProviders.length > 0 && props.showDivider && h("div", { "data-authos-divider": "" }, [h("span", "or")]),
1149
+ h("form", { onSubmit: (e) => {
1150
+ e.preventDefault();
1151
+ submit();
1152
+ } }, [
1153
+ h("div", { "data-authos-field": "email" }, [h("label", { for: "authos-signup-email" }, "Email"), h("input", {
1154
+ id: "authos-signup-email",
1155
+ type: "email",
1156
+ autocomplete: "email",
1157
+ value: email.value,
1158
+ placeholder: "Enter your email",
1159
+ required: true,
1160
+ disabled: isSubmitting.value,
1161
+ onInput: (e) => email.value = e.target.value
1162
+ })]),
1163
+ h("div", { "data-authos-field": "password" }, [h("label", { for: "authos-signup-password" }, "Password"), h("input", {
1164
+ id: "authos-signup-password",
1165
+ type: "password",
1166
+ autocomplete: "new-password",
1167
+ value: password.value,
1168
+ placeholder: "Create a password",
1169
+ required: true,
1170
+ disabled: isSubmitting.value,
1171
+ onInput: (e) => password.value = e.target.value
1172
+ })]),
1173
+ h("div", { "data-authos-field": "confirm-password" }, [h("label", { for: "authos-signup-confirm" }, "Confirm Password"), h("input", {
1174
+ id: "authos-signup-confirm",
1175
+ type: "password",
1176
+ autocomplete: "new-password",
1177
+ value: confirmPassword.value,
1178
+ placeholder: "Confirm your password",
1179
+ required: true,
1180
+ disabled: isSubmitting.value,
1181
+ onInput: (e) => confirmPassword.value = e.target.value
1182
+ })]),
1183
+ error.value && h("div", { "data-authos-error": "" }, error.value),
1184
+ h("button", {
1185
+ type: "submit",
1186
+ disabled: isSubmitting.value,
1187
+ "data-authos-submit": ""
1188
+ }, isSubmitting.value ? "Creating account..." : "Sign Up"),
1189
+ props.showSignIn && h("div", { "data-authos-signin-prompt": "" }, ["Already have an account? ", h("a", {
1190
+ href: "/signin",
1191
+ "data-authos-link": "signin"
1192
+ }, "Sign in")])
1193
+ ])
1194
+ ]);
1195
+ };
1196
+ }
1161
1197
  });
1162
- var OrganizationSwitcher = defineComponent({
1163
- name: "OrganizationSwitcher",
1164
- props: {
1165
- onSwitch: {
1166
- type: Function,
1167
- default: void 0
1168
- }
1169
- },
1170
- emits: ["switch"],
1171
- setup(props, { slots, emit }) {
1172
- const { currentOrganization, organizations, switchOrganization, isSwitching } = useOrganization();
1173
- async function switchTo(slug) {
1174
- await switchOrganization(slug);
1175
- const org = organizations.value.find((o) => o.organization.slug === slug);
1176
- if (org) {
1177
- emit("switch", org);
1178
- props.onSwitch?.(org);
1179
- }
1180
- }
1181
- return () => {
1182
- const slotProps = {
1183
- currentOrganization: currentOrganization.value,
1184
- organizations: organizations.value,
1185
- isSwitching: isSwitching.value,
1186
- switchTo
1187
- };
1188
- if (slots.default) {
1189
- return slots.default(slotProps);
1190
- }
1191
- return h(
1192
- "div",
1193
- { "data-authos-orgswitcher": "", "data-state": organizations.value.length > 0 ? "ready" : "empty" },
1194
- organizations.value.length > 0 ? h(
1195
- "select",
1196
- {
1197
- value: currentOrganization.value?.organization.slug ?? "",
1198
- disabled: isSwitching.value,
1199
- onChange: (e) => switchTo(e.target.value)
1200
- },
1201
- organizations.value.map(
1202
- (org) => h("option", { key: org.organization.id, value: org.organization.slug }, org.organization.name)
1203
- )
1204
- ) : "No organizations"
1205
- );
1206
- };
1207
- }
1198
+ //#endregion
1199
+ //#region src/components/OrganizationSwitcher.ts
1200
+ const OrganizationSwitcher = defineComponent({
1201
+ name: "OrganizationSwitcher",
1202
+ props: { onSwitch: {
1203
+ type: Function,
1204
+ default: void 0
1205
+ } },
1206
+ emits: ["switch"],
1207
+ setup(props, { slots, emit }) {
1208
+ const { currentOrganization, organizations, switchOrganization, isSwitching } = useOrganization();
1209
+ async function switchTo(slug) {
1210
+ await switchOrganization(slug);
1211
+ const org = organizations.value.find((o) => o.organization.slug === slug);
1212
+ if (org) {
1213
+ emit("switch", org);
1214
+ props.onSwitch?.(org);
1215
+ }
1216
+ }
1217
+ return () => {
1218
+ const slotProps = {
1219
+ currentOrganization: currentOrganization.value,
1220
+ organizations: organizations.value,
1221
+ isSwitching: isSwitching.value,
1222
+ switchTo
1223
+ };
1224
+ if (slots.default) return slots.default(slotProps);
1225
+ return h("div", {
1226
+ "data-authos-orgswitcher": "",
1227
+ "data-state": organizations.value.length > 0 ? "ready" : "empty"
1228
+ }, organizations.value.length > 0 ? h("select", {
1229
+ value: currentOrganization.value?.organization.slug ?? "",
1230
+ disabled: isSwitching.value,
1231
+ onChange: (e) => switchTo(e.target.value)
1232
+ }, organizations.value.map((org) => h("option", {
1233
+ key: org.organization.id,
1234
+ value: org.organization.slug
1235
+ }, org.organization.name))) : "No organizations");
1236
+ };
1237
+ }
1208
1238
  });
1209
- var UserButton = defineComponent({
1210
- name: "UserButton",
1211
- props: {
1212
- onLogout: {
1213
- type: Function,
1214
- default: void 0
1215
- }
1216
- },
1217
- emits: ["logout"],
1218
- setup(props, { slots, emit }) {
1219
- const { user, isLoading } = useUser();
1220
- const { client } = useAuthOS();
1221
- const isLoggingOut = ref(false);
1222
- async function logout() {
1223
- isLoggingOut.value = true;
1224
- try {
1225
- await client.auth.logout();
1226
- emit("logout");
1227
- props.onLogout?.();
1228
- } finally {
1229
- isLoggingOut.value = false;
1230
- }
1231
- }
1232
- return () => {
1233
- const slotProps = {
1234
- user: user.value,
1235
- isLoading: isLoading.value,
1236
- isLoggingOut: isLoggingOut.value,
1237
- logout
1238
- };
1239
- if (slots.default) {
1240
- return slots.default(slotProps);
1241
- }
1242
- if (isLoading.value) {
1243
- return h("div", { "data-authos-userbutton": "", "data-state": "loading" }, "Loading...");
1244
- }
1245
- if (!user.value) {
1246
- return h("div", { "data-authos-userbutton": "", "data-state": "signed-out" }, "Not signed in");
1247
- }
1248
- const getInitials = (email) => {
1249
- const name = email.split("@")[0];
1250
- return name.substring(0, 2).toUpperCase();
1251
- };
1252
- return h("div", { "data-authos-userbutton": "", "data-state": "signed-in" }, [
1253
- h("div", { "data-authos-avatar": "" }, getInitials(user.value.email)),
1254
- h("span", { "data-authos-email": "" }, user.value.email),
1255
- h(
1256
- "button",
1257
- {
1258
- "data-authos-logout": "",
1259
- onClick: logout,
1260
- disabled: isLoggingOut.value
1261
- },
1262
- isLoggingOut.value ? "Logging out..." : "Logout"
1263
- )
1264
- ]);
1265
- };
1266
- }
1239
+ //#endregion
1240
+ //#region src/components/UserButton.ts
1241
+ const UserButton = defineComponent({
1242
+ name: "UserButton",
1243
+ props: { onLogout: {
1244
+ type: Function,
1245
+ default: void 0
1246
+ } },
1247
+ emits: ["logout"],
1248
+ setup(props, { slots, emit }) {
1249
+ const { user, isLoading } = useUser();
1250
+ const { client } = useAuthOS();
1251
+ const isLoggingOut = ref(false);
1252
+ async function logout() {
1253
+ isLoggingOut.value = true;
1254
+ try {
1255
+ await client.auth.logout();
1256
+ emit("logout");
1257
+ props.onLogout?.();
1258
+ } finally {
1259
+ isLoggingOut.value = false;
1260
+ }
1261
+ }
1262
+ return () => {
1263
+ const slotProps = {
1264
+ user: user.value,
1265
+ isLoading: isLoading.value,
1266
+ isLoggingOut: isLoggingOut.value,
1267
+ logout
1268
+ };
1269
+ if (slots.default) return slots.default(slotProps);
1270
+ if (isLoading.value) return h("div", {
1271
+ "data-authos-userbutton": "",
1272
+ "data-state": "loading"
1273
+ }, "Loading...");
1274
+ if (!user.value) return h("div", {
1275
+ "data-authos-userbutton": "",
1276
+ "data-state": "signed-out"
1277
+ }, "Not signed in");
1278
+ const getInitials = (email) => {
1279
+ return email.split("@")[0].substring(0, 2).toUpperCase();
1280
+ };
1281
+ return h("div", {
1282
+ "data-authos-userbutton": "",
1283
+ "data-state": "signed-in"
1284
+ }, [
1285
+ h("div", { "data-authos-avatar": "" }, getInitials(user.value.email)),
1286
+ h("span", { "data-authos-email": "" }, user.value.email),
1287
+ h("button", {
1288
+ "data-authos-logout": "",
1289
+ onClick: logout,
1290
+ disabled: isLoggingOut.value
1291
+ }, isLoggingOut.value ? "Logging out..." : "Logout")
1292
+ ]);
1293
+ };
1294
+ }
1267
1295
  });
1268
- var Protect = defineComponent({
1269
- name: "Protect",
1270
- props: {
1271
- permission: {
1272
- type: String,
1273
- default: void 0
1274
- },
1275
- permissions: {
1276
- type: Array,
1277
- default: void 0
1278
- },
1279
- requireAll: {
1280
- type: Boolean,
1281
- default: false
1282
- },
1283
- fallback: {
1284
- type: [Object, Function],
1285
- default: void 0
1286
- }
1287
- },
1288
- setup(props, { slots }) {
1289
- const { user, isLoading } = useUser();
1290
- const hasAccess = computed(() => {
1291
- if (isLoading.value || !user.value) {
1292
- return false;
1293
- }
1294
- const userPermissions = user.value.permissions ?? [];
1295
- if (props.permission) {
1296
- return userPermissions.includes(props.permission);
1297
- }
1298
- if (props.permissions && props.permissions.length > 0) {
1299
- if (props.requireAll) {
1300
- return props.permissions.every((p) => userPermissions.includes(p));
1301
- }
1302
- return props.permissions.some((p) => userPermissions.includes(p));
1303
- }
1304
- return true;
1305
- });
1306
- return () => {
1307
- if (isLoading.value) {
1308
- return h("div", { "data-authos-protect": "", "data-state": "loading" });
1309
- }
1310
- if (hasAccess.value) {
1311
- return h("div", { "data-authos-protect": "", "data-state": "allowed" }, slots.default?.());
1312
- }
1313
- if (props.fallback) {
1314
- const fallbackContent = typeof props.fallback === "function" ? props.fallback() : props.fallback;
1315
- return h("div", { "data-authos-protect": "", "data-state": "denied" }, [fallbackContent]);
1316
- }
1317
- if (slots.fallback) {
1318
- return h("div", { "data-authos-protect": "", "data-state": "denied" }, slots.fallback());
1319
- }
1320
- return h("div", { "data-authos-protect": "", "data-state": "denied" });
1321
- };
1322
- }
1296
+ //#endregion
1297
+ //#region src/components/Protect.ts
1298
+ const Protect = defineComponent({
1299
+ name: "Protect",
1300
+ props: {
1301
+ permission: {
1302
+ type: String,
1303
+ default: void 0
1304
+ },
1305
+ permissions: {
1306
+ type: Array,
1307
+ default: void 0
1308
+ },
1309
+ requireAll: {
1310
+ type: Boolean,
1311
+ default: false
1312
+ },
1313
+ fallback: {
1314
+ type: [Object, Function],
1315
+ default: void 0
1316
+ }
1317
+ },
1318
+ setup(props, { slots }) {
1319
+ const { user, isLoading } = useUser();
1320
+ const hasAccess = computed(() => {
1321
+ if (isLoading.value || !user.value) return false;
1322
+ const userPermissions = user.value.permissions ?? [];
1323
+ if (props.permission) return userPermissions.includes(props.permission);
1324
+ if (props.permissions && props.permissions.length > 0) {
1325
+ if (props.requireAll) return props.permissions.every((p) => userPermissions.includes(p));
1326
+ return props.permissions.some((p) => userPermissions.includes(p));
1327
+ }
1328
+ return true;
1329
+ });
1330
+ return () => {
1331
+ if (isLoading.value) return h("div", {
1332
+ "data-authos-protect": "",
1333
+ "data-state": "loading"
1334
+ });
1335
+ if (hasAccess.value) return h("div", {
1336
+ "data-authos-protect": "",
1337
+ "data-state": "allowed"
1338
+ }, slots.default?.());
1339
+ if (props.fallback) return h("div", {
1340
+ "data-authos-protect": "",
1341
+ "data-state": "denied"
1342
+ }, [typeof props.fallback === "function" ? props.fallback() : props.fallback]);
1343
+ if (slots.fallback) return h("div", {
1344
+ "data-authos-protect": "",
1345
+ "data-state": "denied"
1346
+ }, slots.fallback());
1347
+ return h("div", {
1348
+ "data-authos-protect": "",
1349
+ "data-state": "denied"
1350
+ });
1351
+ };
1352
+ }
1323
1353
  });
1324
- var SignedIn = defineComponent({
1325
- name: "SignedIn",
1326
- setup(_, { slots }) {
1327
- const { isAuthenticated, isLoading } = useAuthOS();
1328
- return () => {
1329
- if (isLoading.value) {
1330
- return null;
1331
- }
1332
- if (!isAuthenticated.value) {
1333
- return null;
1334
- }
1335
- return slots.default?.();
1336
- };
1337
- }
1354
+ //#endregion
1355
+ //#region src/components/SignedIn.ts
1356
+ /**
1357
+ * Renders children only when the user is authenticated.
1358
+ * Use with SignedOut to create conditional UI based on auth state.
1359
+ *
1360
+ * @example
1361
+ * ```vue
1362
+ * <script setup>
1363
+ * import { SignedIn, SignedOut, UserButton, SignIn } from '@drmhse/authos-vue';
1364
+ * <\/script>
1365
+ *
1366
+ * <template>
1367
+ * <header>
1368
+ * <SignedIn>
1369
+ * <UserButton />
1370
+ * </SignedIn>
1371
+ * <SignedOut>
1372
+ * <SignIn />
1373
+ * </SignedOut>
1374
+ * </header>
1375
+ * </template>
1376
+ * ```
1377
+ */
1378
+ const SignedIn = defineComponent({
1379
+ name: "SignedIn",
1380
+ setup(_, { slots }) {
1381
+ const { isAuthenticated, isLoading } = useAuthOS();
1382
+ return () => {
1383
+ if (isLoading.value) return null;
1384
+ if (!isAuthenticated.value) return null;
1385
+ return slots.default?.();
1386
+ };
1387
+ }
1338
1388
  });
1339
- var SignedOut = defineComponent({
1340
- name: "SignedOut",
1341
- setup(_, { slots }) {
1342
- const { isAuthenticated, isLoading } = useAuthOS();
1343
- return () => {
1344
- if (isLoading.value) {
1345
- return null;
1346
- }
1347
- if (isAuthenticated.value) {
1348
- return null;
1349
- }
1350
- return slots.default?.();
1351
- };
1352
- }
1389
+ //#endregion
1390
+ //#region src/components/SignedOut.ts
1391
+ /**
1392
+ * Renders children only when the user is NOT authenticated.
1393
+ * Use with SignedIn to create conditional UI based on auth state.
1394
+ *
1395
+ * @example
1396
+ * ```vue
1397
+ * <script setup>
1398
+ * import { SignedIn, SignedOut, UserButton, SignIn } from '@drmhse/authos-vue';
1399
+ * <\/script>
1400
+ *
1401
+ * <template>
1402
+ * <header>
1403
+ * <SignedIn>
1404
+ * <UserButton />
1405
+ * </SignedIn>
1406
+ * <SignedOut>
1407
+ * <SignIn />
1408
+ * </SignedOut>
1409
+ * </header>
1410
+ * </template>
1411
+ * ```
1412
+ */
1413
+ const SignedOut = defineComponent({
1414
+ name: "SignedOut",
1415
+ setup(_, { slots }) {
1416
+ const { isAuthenticated, isLoading } = useAuthOS();
1417
+ return () => {
1418
+ if (isLoading.value) return null;
1419
+ if (isAuthenticated.value) return null;
1420
+ return slots.default?.();
1421
+ };
1422
+ }
1353
1423
  });
1354
- var MagicLinkSignIn = defineComponent({
1355
- name: "MagicLinkSignIn",
1356
- props: {
1357
- showPasswordSignIn: {
1358
- type: Boolean,
1359
- default: true
1360
- }
1361
- },
1362
- emits: ["success", "error"],
1363
- setup(props, { emit, slots }) {
1364
- const { client } = useAuthOS();
1365
- const email = ref("");
1366
- const isLoading = ref(false);
1367
- const error = ref(null);
1368
- const isSent = ref(false);
1369
- async function handleSubmit() {
1370
- error.value = null;
1371
- isLoading.value = true;
1372
- try {
1373
- await client.magicLinks.request({ email: email.value });
1374
- isSent.value = true;
1375
- emit("success");
1376
- } catch (err) {
1377
- const message = err instanceof Error ? err.message : "Failed to send magic link";
1378
- error.value = message;
1379
- emit("error", err);
1380
- } finally {
1381
- isLoading.value = false;
1382
- }
1383
- }
1384
- const slotProps = computed(() => ({
1385
- email: email.value,
1386
- isLoading: isLoading.value,
1387
- error: error.value,
1388
- isSent: isSent.value,
1389
- updateEmail: (val) => {
1390
- email.value = val;
1391
- },
1392
- submit: handleSubmit,
1393
- reset: () => {
1394
- isSent.value = false;
1395
- }
1396
- }));
1397
- return () => {
1398
- if (slots.default) {
1399
- return slots.default(slotProps.value);
1400
- }
1401
- if (isSent.value) {
1402
- return h("div", { "data-authos-magic-link": "", "data-state": "sent" }, [
1403
- h("div", { "data-authos-success": "" }, [
1404
- h("p", "Check your email!"),
1405
- h("p", ["We sent a login link to ", h("strong", email.value)])
1406
- ]),
1407
- h("button", {
1408
- type: "button",
1409
- onClick: () => {
1410
- isSent.value = false;
1411
- },
1412
- "data-authos-back": ""
1413
- }, "Use a different email")
1414
- ]);
1415
- }
1416
- return h("form", {
1417
- onSubmit: (e) => {
1418
- e.preventDefault();
1419
- handleSubmit();
1420
- },
1421
- "data-authos-magic-link": "",
1422
- "data-state": "form"
1423
- }, [
1424
- h("div", { "data-authos-field": "email" }, [
1425
- h("label", { for: "authos-magic-email" }, "Email"),
1426
- h("input", {
1427
- id: "authos-magic-email",
1428
- type: "email",
1429
- autocomplete: "email",
1430
- value: email.value,
1431
- onInput: (e) => {
1432
- email.value = e.target.value;
1433
- },
1434
- placeholder: "Enter your email",
1435
- required: true,
1436
- disabled: isLoading.value
1437
- })
1438
- ]),
1439
- error.value ? h("div", { "data-authos-error": "" }, error.value) : null,
1440
- h("button", {
1441
- type: "submit",
1442
- disabled: isLoading.value,
1443
- "data-authos-submit": ""
1444
- }, isLoading.value ? "Sending..." : "Send Magic Link"),
1445
- props.showPasswordSignIn ? h("div", { "data-authos-signin-prompt": "" }, [
1446
- h("a", { href: "/signin", "data-authos-link": "signin" }, "Sign in with password")
1447
- ]) : null
1448
- ]);
1449
- };
1450
- }
1424
+ //#endregion
1425
+ //#region src/components/MagicLinkSignIn.ts
1426
+ /**
1427
+ * Passwordless sign-in via magic link (email).
1428
+ *
1429
+ * Sends a one-time login link to the user's email address.
1430
+ *
1431
+ * @example
1432
+ * ```vue
1433
+ * <script setup>
1434
+ * import { MagicLinkSignIn } from '@drmhse/authos-vue';
1435
+ * <\/script>
1436
+ *
1437
+ * <template>
1438
+ * <MagicLinkSignIn @success="() => alert('Check your email!')" />
1439
+ * </template>
1440
+ * ```
1441
+ */
1442
+ const MagicLinkSignIn = defineComponent({
1443
+ name: "MagicLinkSignIn",
1444
+ props: { showPasswordSignIn: {
1445
+ type: Boolean,
1446
+ default: true
1447
+ } },
1448
+ emits: ["success", "error"],
1449
+ setup(props, { emit, slots }) {
1450
+ const { client } = useAuthOS();
1451
+ const email = ref("");
1452
+ const isLoading = ref(false);
1453
+ const error = ref(null);
1454
+ const isSent = ref(false);
1455
+ async function handleSubmit() {
1456
+ error.value = null;
1457
+ isLoading.value = true;
1458
+ try {
1459
+ await client.magicLinks.request({ email: email.value });
1460
+ isSent.value = true;
1461
+ emit("success");
1462
+ } catch (err) {
1463
+ error.value = err instanceof Error ? err.message : "Failed to send magic link";
1464
+ emit("error", err);
1465
+ } finally {
1466
+ isLoading.value = false;
1467
+ }
1468
+ }
1469
+ const slotProps = computed(() => ({
1470
+ email: email.value,
1471
+ isLoading: isLoading.value,
1472
+ error: error.value,
1473
+ isSent: isSent.value,
1474
+ updateEmail: (val) => {
1475
+ email.value = val;
1476
+ },
1477
+ submit: handleSubmit,
1478
+ reset: () => {
1479
+ isSent.value = false;
1480
+ }
1481
+ }));
1482
+ return () => {
1483
+ if (slots.default) return slots.default(slotProps.value);
1484
+ if (isSent.value) return h("div", {
1485
+ "data-authos-magic-link": "",
1486
+ "data-state": "sent"
1487
+ }, [h("div", { "data-authos-success": "" }, [h("p", "Check your email!"), h("p", ["We sent a login link to ", h("strong", email.value)])]), h("button", {
1488
+ type: "button",
1489
+ onClick: () => {
1490
+ isSent.value = false;
1491
+ },
1492
+ "data-authos-back": ""
1493
+ }, "Use a different email")]);
1494
+ return h("form", {
1495
+ onSubmit: (e) => {
1496
+ e.preventDefault();
1497
+ handleSubmit();
1498
+ },
1499
+ "data-authos-magic-link": "",
1500
+ "data-state": "form"
1501
+ }, [
1502
+ h("div", { "data-authos-field": "email" }, [h("label", { for: "authos-magic-email" }, "Email"), h("input", {
1503
+ id: "authos-magic-email",
1504
+ type: "email",
1505
+ autocomplete: "email",
1506
+ value: email.value,
1507
+ onInput: (e) => {
1508
+ email.value = e.target.value;
1509
+ },
1510
+ placeholder: "Enter your email",
1511
+ required: true,
1512
+ disabled: isLoading.value
1513
+ })]),
1514
+ error.value ? h("div", { "data-authos-error": "" }, error.value) : null,
1515
+ h("button", {
1516
+ type: "submit",
1517
+ disabled: isLoading.value,
1518
+ "data-authos-submit": ""
1519
+ }, isLoading.value ? "Sending..." : "Send Magic Link"),
1520
+ props.showPasswordSignIn ? h("div", { "data-authos-signin-prompt": "" }, [h("a", {
1521
+ href: "/signin",
1522
+ "data-authos-link": "signin"
1523
+ }, "Sign in with password")]) : null
1524
+ ]);
1525
+ };
1526
+ }
1451
1527
  });
1452
- var PasskeySignIn = defineComponent({
1453
- name: "PasskeySignIn",
1454
- props: {
1455
- showPasswordSignIn: {
1456
- type: Boolean,
1457
- default: true
1458
- },
1459
- orgSlug: {
1460
- type: String,
1461
- default: void 0
1462
- },
1463
- serviceSlug: {
1464
- type: String,
1465
- default: void 0
1466
- },
1467
- redirectUri: {
1468
- type: String,
1469
- default: void 0
1470
- },
1471
- state: {
1472
- type: String,
1473
- default: void 0
1474
- }
1475
- },
1476
- emits: ["success", "error"],
1477
- setup(props, { emit, slots }) {
1478
- const { client, options } = useAuthOS();
1479
- const email = ref("");
1480
- const isLoading = ref(false);
1481
- const error = ref(null);
1482
- const isSupported = ref(true);
1483
- onMounted(() => {
1484
- isSupported.value = client.passkeys.isSupported();
1485
- });
1486
- async function handleSubmit() {
1487
- error.value = null;
1488
- isLoading.value = true;
1489
- try {
1490
- const org = props.orgSlug ?? options.org;
1491
- const service = props.serviceSlug ?? options.service;
1492
- const passkeyContext = org && service ? {
1493
- org_slug: org,
1494
- service_slug: service,
1495
- redirect_uri: props.redirectUri ?? options.redirectUri,
1496
- state: props.state
1497
- } : void 0;
1498
- await client.passkeys.login(email.value, passkeyContext);
1499
- emit("success");
1500
- } catch (err) {
1501
- const message = err instanceof Error ? err.message : "Passkey authentication failed";
1502
- error.value = message;
1503
- emit("error", err);
1504
- } finally {
1505
- isLoading.value = false;
1506
- }
1507
- }
1508
- const slotProps = computed(() => ({
1509
- email: email.value,
1510
- isLoading: isLoading.value,
1511
- error: error.value,
1512
- isSupported: isSupported.value,
1513
- updateEmail: (val) => {
1514
- email.value = val;
1515
- },
1516
- submit: handleSubmit
1517
- }));
1518
- return () => {
1519
- if (slots.default) {
1520
- return slots.default(slotProps.value);
1521
- }
1522
- if (!isSupported.value) {
1523
- return h("div", {
1524
- "data-authos-passkey": "",
1525
- "data-state": "unsupported"
1526
- }, [
1527
- h("div", { "data-authos-error": "" }, "Passkeys are not supported in this browser."),
1528
- props.showPasswordSignIn ? h("a", { href: "/signin", "data-authos-link": "signin" }, "Sign in with password") : null
1529
- ]);
1530
- }
1531
- return h("form", {
1532
- onSubmit: (e) => {
1533
- e.preventDefault();
1534
- handleSubmit();
1535
- },
1536
- "data-authos-passkey": ""
1537
- }, [
1538
- h("div", { "data-authos-field": "email" }, [
1539
- h("label", { for: "authos-passkey-email" }, "Email"),
1540
- h("input", {
1541
- id: "authos-passkey-email",
1542
- type: "email",
1543
- autocomplete: "email webauthn",
1544
- value: email.value,
1545
- onInput: (e) => {
1546
- email.value = e.target.value;
1547
- },
1548
- placeholder: "Enter your email",
1549
- required: true,
1550
- disabled: isLoading.value
1551
- })
1552
- ]),
1553
- error.value ? h("div", { "data-authos-error": "" }, error.value) : null,
1554
- h("button", {
1555
- type: "submit",
1556
- disabled: isLoading.value,
1557
- "data-authos-submit": ""
1558
- }, isLoading.value ? "Authenticating..." : "Sign in with Passkey"),
1559
- props.showPasswordSignIn ? h("div", { "data-authos-signin-prompt": "" }, [
1560
- h("a", { href: "/signin", "data-authos-link": "signin" }, "Sign in with password")
1561
- ]) : null
1562
- ]);
1563
- };
1564
- }
1528
+ //#endregion
1529
+ //#region src/components/PasskeySignIn.ts
1530
+ /**
1531
+ * Passkey (WebAuthn) sign-in component.
1532
+ *
1533
+ * Uses the Web Authentication API for passwordless authentication
1534
+ * via biometrics, security keys, or platform authenticators.
1535
+ *
1536
+ * @example
1537
+ * ```vue
1538
+ * <script setup>
1539
+ * import { PasskeySignIn } from '@drmhse/authos-vue';
1540
+ * <\/script>
1541
+ *
1542
+ * <template>
1543
+ * <PasskeySignIn @success="() => router.push('/dashboard')" />
1544
+ * </template>
1545
+ * ```
1546
+ */
1547
+ const PasskeySignIn = defineComponent({
1548
+ name: "PasskeySignIn",
1549
+ props: {
1550
+ showPasswordSignIn: {
1551
+ type: Boolean,
1552
+ default: true
1553
+ },
1554
+ orgSlug: {
1555
+ type: String,
1556
+ default: void 0
1557
+ },
1558
+ serviceSlug: {
1559
+ type: String,
1560
+ default: void 0
1561
+ },
1562
+ redirectUri: {
1563
+ type: String,
1564
+ default: void 0
1565
+ },
1566
+ state: {
1567
+ type: String,
1568
+ default: void 0
1569
+ }
1570
+ },
1571
+ emits: ["success", "error"],
1572
+ setup(props, { emit, slots }) {
1573
+ const { client, options } = useAuthOS();
1574
+ const email = ref("");
1575
+ const isLoading = ref(false);
1576
+ const error = ref(null);
1577
+ const isSupported = ref(true);
1578
+ onMounted(() => {
1579
+ isSupported.value = client.passkeys.isSupported();
1580
+ });
1581
+ async function handleSubmit() {
1582
+ error.value = null;
1583
+ isLoading.value = true;
1584
+ try {
1585
+ const org = props.orgSlug ?? options.org;
1586
+ const service = props.serviceSlug ?? options.service;
1587
+ const passkeyContext = org && service ? {
1588
+ org_slug: org,
1589
+ service_slug: service,
1590
+ redirect_uri: props.redirectUri ?? options.redirectUri,
1591
+ state: props.state
1592
+ } : void 0;
1593
+ await client.passkeys.login(email.value, passkeyContext);
1594
+ emit("success");
1595
+ } catch (err) {
1596
+ error.value = err instanceof Error ? err.message : "Passkey authentication failed";
1597
+ emit("error", err);
1598
+ } finally {
1599
+ isLoading.value = false;
1600
+ }
1601
+ }
1602
+ const slotProps = computed(() => ({
1603
+ email: email.value,
1604
+ isLoading: isLoading.value,
1605
+ error: error.value,
1606
+ isSupported: isSupported.value,
1607
+ updateEmail: (val) => {
1608
+ email.value = val;
1609
+ },
1610
+ submit: handleSubmit
1611
+ }));
1612
+ return () => {
1613
+ if (slots.default) return slots.default(slotProps.value);
1614
+ if (!isSupported.value) return h("div", {
1615
+ "data-authos-passkey": "",
1616
+ "data-state": "unsupported"
1617
+ }, [h("div", { "data-authos-error": "" }, "Passkeys are not supported in this browser."), props.showPasswordSignIn ? h("a", {
1618
+ href: "/signin",
1619
+ "data-authos-link": "signin"
1620
+ }, "Sign in with password") : null]);
1621
+ return h("form", {
1622
+ onSubmit: (e) => {
1623
+ e.preventDefault();
1624
+ handleSubmit();
1625
+ },
1626
+ "data-authos-passkey": ""
1627
+ }, [
1628
+ h("div", { "data-authos-field": "email" }, [h("label", { for: "authos-passkey-email" }, "Email"), h("input", {
1629
+ id: "authos-passkey-email",
1630
+ type: "email",
1631
+ autocomplete: "email webauthn",
1632
+ value: email.value,
1633
+ onInput: (e) => {
1634
+ email.value = e.target.value;
1635
+ },
1636
+ placeholder: "Enter your email",
1637
+ required: true,
1638
+ disabled: isLoading.value
1639
+ })]),
1640
+ error.value ? h("div", { "data-authos-error": "" }, error.value) : null,
1641
+ h("button", {
1642
+ type: "submit",
1643
+ disabled: isLoading.value,
1644
+ "data-authos-submit": ""
1645
+ }, isLoading.value ? "Authenticating..." : "Sign in with Passkey"),
1646
+ props.showPasswordSignIn ? h("div", { "data-authos-signin-prompt": "" }, [h("a", {
1647
+ href: "/signin",
1648
+ "data-authos-link": "signin"
1649
+ }, "Sign in with password")]) : null
1650
+ ]);
1651
+ };
1652
+ }
1565
1653
  });
1566
- var Callback = defineComponent({
1567
- name: "Callback",
1568
- props: {
1569
- onSuccess: {
1570
- type: Function,
1571
- default: void 0
1572
- },
1573
- onError: {
1574
- type: Function,
1575
- default: void 0
1576
- }
1577
- },
1578
- emits: ["success", "error"],
1579
- setup(props, { slots, emit }) {
1580
- const { client } = useAuthOS();
1581
- const error = ref(null);
1582
- onMounted(async () => {
1583
- if (!client) {
1584
- error.value = "AuthOS client not initialized";
1585
- return;
1586
- }
1587
- const hashParams = new URLSearchParams(window.location.hash.substring(1));
1588
- const queryParams = new URLSearchParams(window.location.search);
1589
- const accessToken = hashParams.get("access_token") || queryParams.get("access_token");
1590
- const refreshToken = hashParams.get("refresh_token") || queryParams.get("refresh_token");
1591
- const errorParam = hashParams.get("error") || queryParams.get("error");
1592
- const errorDescription = hashParams.get("error_description") || queryParams.get("error_description");
1593
- if (errorParam) {
1594
- const msg = errorDescription || errorParam;
1595
- error.value = msg;
1596
- const e = new Error(msg);
1597
- emit("error", e);
1598
- props.onError?.(e);
1599
- return;
1600
- }
1601
- if (accessToken) {
1602
- try {
1603
- await client.setSession({
1604
- access_token: accessToken,
1605
- refresh_token: refreshToken || void 0
1606
- });
1607
- emit("success");
1608
- props.onSuccess?.();
1609
- } catch (err) {
1610
- const message = err.message || "Failed to set session";
1611
- error.value = message;
1612
- const e = err instanceof Error ? err : new Error(message);
1613
- emit("error", e);
1614
- props.onError?.(e);
1615
- }
1616
- } else {
1617
- const message = "No authentication tokens found in callback URL.";
1618
- error.value = message;
1619
- const e = new Error(message);
1620
- emit("error", e);
1621
- props.onError?.(e);
1622
- }
1623
- });
1624
- return () => {
1625
- const slotProps = {
1626
- error: error.value
1627
- };
1628
- if (slots.default) {
1629
- return slots.default(slotProps);
1630
- }
1631
- return h("div", { "data-authos-callback": "" }, [
1632
- error.value ? h("div", { "data-authos-error": "" }, error.value) : h("div", { "data-authos-loading": "" }, "Completing sign in...")
1633
- ]);
1634
- };
1635
- }
1654
+ //#endregion
1655
+ //#region src/components/Callback.ts
1656
+ const Callback = defineComponent({
1657
+ name: "Callback",
1658
+ props: {
1659
+ onSuccess: {
1660
+ type: Function,
1661
+ default: void 0
1662
+ },
1663
+ onError: {
1664
+ type: Function,
1665
+ default: void 0
1666
+ }
1667
+ },
1668
+ emits: ["success", "error"],
1669
+ setup(props, { slots, emit }) {
1670
+ const { client } = useAuthOS();
1671
+ const error = ref(null);
1672
+ onMounted(async () => {
1673
+ if (!client) {
1674
+ error.value = "AuthOS client not initialized";
1675
+ return;
1676
+ }
1677
+ const hashParams = new URLSearchParams(window.location.hash.substring(1));
1678
+ const queryParams = new URLSearchParams(window.location.search);
1679
+ const accessToken = hashParams.get("access_token") || queryParams.get("access_token");
1680
+ const refreshToken = hashParams.get("refresh_token") || queryParams.get("refresh_token");
1681
+ const errorParam = hashParams.get("error") || queryParams.get("error");
1682
+ const errorDescription = hashParams.get("error_description") || queryParams.get("error_description");
1683
+ if (errorParam) {
1684
+ const msg = errorDescription || errorParam;
1685
+ error.value = msg;
1686
+ const e = new Error(msg);
1687
+ emit("error", e);
1688
+ props.onError?.(e);
1689
+ return;
1690
+ }
1691
+ if (accessToken) try {
1692
+ await client.setSession({
1693
+ access_token: accessToken,
1694
+ refresh_token: refreshToken || void 0
1695
+ });
1696
+ emit("success");
1697
+ props.onSuccess?.();
1698
+ } catch (err) {
1699
+ const message = err.message || "Failed to set session";
1700
+ error.value = message;
1701
+ const e = err instanceof Error ? err : new Error(message);
1702
+ emit("error", e);
1703
+ props.onError?.(e);
1704
+ }
1705
+ else {
1706
+ const message = "No authentication tokens found in callback URL.";
1707
+ error.value = message;
1708
+ const e = /* @__PURE__ */ new Error(message);
1709
+ emit("error", e);
1710
+ props.onError?.(e);
1711
+ }
1712
+ });
1713
+ return () => {
1714
+ const slotProps = { error: error.value };
1715
+ if (slots.default) return slots.default(slotProps);
1716
+ return h("div", { "data-authos-callback": "" }, [error.value ? h("div", { "data-authos-error": "" }, error.value) : h("div", { "data-authos-loading": "" }, "Completing sign in...")]);
1717
+ };
1718
+ }
1636
1719
  });
1637
-
1638
- export { AuthOSProvider, Callback, MagicLinkSignIn, OAuthButton, OrganizationSwitcher, PasskeySignIn, Protect, SignIn, SignUp, SignedIn, SignedOut, UserButton, createAuthOS, useAllPermissions, useAnyPermission, useOrganization, usePermission, useUser };
1720
+ //#endregion
1721
+ export { AUTH_OS_INJECTION_KEY, AuthErrorCodes, AuthOSProvider, BrowserStorage, Callback, MagicLinkSignIn, MemoryStorage, OAuthButton, OrganizationSwitcher, PasskeySignIn, Protect, SignIn, SignUp, SignedIn, SignedOut, SsoApiError, UserButton, createAuthOS, useAllPermissions, useAnyPermission, useAuthOS, useOrganization, usePermission, useUser };