@drmhse/authos-vue 0.1.3 → 0.1.5

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.js CHANGED
@@ -21,7 +21,7 @@ function createAuthOS(options) {
21
21
  return new ssoSdk.MemoryStorage();
22
22
  };
23
23
  const client = new ssoSdk.SsoClient({
24
- baseURL: options.baseUrl,
24
+ baseURL: options.baseURL,
25
25
  storage: getStorage(),
26
26
  token: options.initialToken
27
27
  // Pass initial token if provided
@@ -43,7 +43,8 @@ function createAuthOS(options) {
43
43
  });
44
44
  const context = {
45
45
  client,
46
- state
46
+ state,
47
+ options
47
48
  };
48
49
  return {
49
50
  install(app) {
@@ -83,12 +84,16 @@ function createAuthOS(options) {
83
84
  function useAuthOS() {
84
85
  const context = vue.inject(AUTH_OS_INJECTION_KEY);
85
86
  if (!context) {
87
+ const defaultOptions = {
88
+ baseURL: "http://localhost:3001"
89
+ };
86
90
  const defaultClient = new ssoSdk.SsoClient({
87
- baseURL: "http://localhost:3001",
91
+ baseURL: defaultOptions.baseURL,
88
92
  storage: new ssoSdk.MemoryStorage()
89
93
  });
90
94
  return {
91
95
  client: defaultClient,
96
+ options: defaultOptions,
92
97
  isLoading: vue.computed(() => false),
93
98
  isAuthenticated: vue.computed(() => false)
94
99
  };
@@ -97,6 +102,7 @@ function useAuthOS() {
97
102
  const isAuthenticated = vue.computed(() => context.state.isAuthenticated);
98
103
  return {
99
104
  client: context.client,
105
+ options: context.options,
100
106
  isLoading,
101
107
  isAuthenticated
102
108
  };
@@ -133,6 +139,11 @@ function useOrganization() {
133
139
  if (!context) return;
134
140
  isSwitching.value = true;
135
141
  try {
142
+ const result = await context.client.organizations.select(slug);
143
+ await context.client.setSession({
144
+ access_token: result.access_token,
145
+ refresh_token: result.refresh_token
146
+ });
136
147
  const orgResponse = await context.client.organizations.get(slug);
137
148
  context.state.currentOrganization = orgResponse;
138
149
  return orgResponse;
@@ -147,13 +158,46 @@ function useOrganization() {
147
158
  isSwitching
148
159
  };
149
160
  }
161
+ function usePermission(permission) {
162
+ const { user } = useUser();
163
+ return vue.computed(() => {
164
+ if (!user.value || !permission) return false;
165
+ return user.value.permissions?.includes(permission) ?? false;
166
+ });
167
+ }
168
+ function useAnyPermission(permissions) {
169
+ const { user } = useUser();
170
+ return vue.computed(() => {
171
+ if (!user.value || permissions.length === 0) return false;
172
+ return permissions.some((p) => user.value?.permissions?.includes(p));
173
+ });
174
+ }
175
+ function useAllPermissions(permissions) {
176
+ const { user } = useUser();
177
+ return vue.computed(() => {
178
+ if (!user.value || permissions.length === 0) return false;
179
+ return permissions.every((p) => user.value?.permissions?.includes(p));
180
+ });
181
+ }
150
182
  var AuthOSProvider = vue.defineComponent({
151
183
  name: "AuthOSProvider",
152
184
  props: {
153
- baseUrl: {
185
+ baseURL: {
154
186
  type: String,
155
187
  required: true
156
188
  },
189
+ org: {
190
+ type: String,
191
+ default: void 0
192
+ },
193
+ service: {
194
+ type: String,
195
+ default: void 0
196
+ },
197
+ redirectUri: {
198
+ type: String,
199
+ default: void 0
200
+ },
157
201
  storage: {
158
202
  type: Object,
159
203
  default: void 0
@@ -170,7 +214,7 @@ var AuthOSProvider = vue.defineComponent({
170
214
  return new ssoSdk.MemoryStorage();
171
215
  };
172
216
  const client = props.client ?? new ssoSdk.SsoClient({
173
- baseURL: props.baseUrl,
217
+ baseURL: props.baseURL,
174
218
  storage: getStorage()
175
219
  });
176
220
  const state = vue.reactive({
@@ -180,7 +224,13 @@ var AuthOSProvider = vue.defineComponent({
180
224
  currentOrganization: null,
181
225
  organizations: []
182
226
  });
183
- const context = { client, state };
227
+ const options = {
228
+ baseURL: props.baseURL,
229
+ org: props.org,
230
+ service: props.service,
231
+ redirectUri: props.redirectUri
232
+ };
233
+ const context = { client, state, options };
184
234
  vue.provide(AUTH_OS_INJECTION_KEY, context);
185
235
  let unsubscribe;
186
236
  vue.onMounted(() => {
@@ -231,7 +281,7 @@ var SignIn = vue.defineComponent({
231
281
  },
232
282
  emits: ["success", "error"],
233
283
  setup(props, { slots, emit }) {
234
- const { client } = useAuthOS();
284
+ const { client, options } = useAuthOS();
235
285
  const email = vue.ref("");
236
286
  const password = vue.ref("");
237
287
  const mfaCode = vue.ref("");
@@ -246,7 +296,9 @@ var SignIn = vue.defineComponent({
246
296
  if (step.value === "credentials") {
247
297
  const result = await client.auth.login({
248
298
  email: email.value,
249
- password: password.value
299
+ password: password.value,
300
+ org_slug: options.org,
301
+ service_slug: options.service
250
302
  });
251
303
  if (result.expires_in === MFA_PREAUTH_EXPIRY) {
252
304
  preauthToken.value = result.access_token;
@@ -325,6 +377,16 @@ var SignUp = vue.defineComponent({
325
377
  onError: {
326
378
  type: Function,
327
379
  default: void 0
380
+ },
381
+ /** Organization slug for tenant context */
382
+ orgSlug: {
383
+ type: String,
384
+ default: void 0
385
+ },
386
+ /** Service slug for tenant attribution (used with orgSlug) */
387
+ serviceSlug: {
388
+ type: String,
389
+ default: void 0
328
390
  }
329
391
  },
330
392
  emits: ["success", "error"],
@@ -340,7 +402,9 @@ var SignUp = vue.defineComponent({
340
402
  try {
341
403
  await client.auth.register({
342
404
  email: email.value,
343
- password: password.value
405
+ password: password.value,
406
+ org_slug: props.orgSlug,
407
+ service_slug: props.serviceSlug
344
408
  });
345
409
  emit("success");
346
410
  props.onSuccess?.();
@@ -545,6 +609,297 @@ var Protect = vue.defineComponent({
545
609
  };
546
610
  }
547
611
  });
612
+ var PROVIDER_NAMES = {
613
+ github: "GitHub",
614
+ google: "Google",
615
+ microsoft: "Microsoft"
616
+ };
617
+ var OAuthButton = vue.defineComponent({
618
+ name: "OAuthButton",
619
+ props: {
620
+ provider: {
621
+ type: String,
622
+ required: true
623
+ },
624
+ disabled: {
625
+ type: Boolean,
626
+ default: false
627
+ }
628
+ },
629
+ emits: ["redirect"],
630
+ setup(props, { slots, emit }) {
631
+ const { client, options } = useAuthOS();
632
+ const isConfigured = vue.computed(() => !!(options.org && options.service));
633
+ const providerName = vue.computed(() => PROVIDER_NAMES[props.provider]);
634
+ function handleClick() {
635
+ if (!options.org || !options.service) {
636
+ console.error(
637
+ `[AuthOS] OAuth login requires "org" and "service" in createAuthOS options.
638
+ Current options: { org: ${options.org ? `"${options.org}"` : "undefined"}, service: ${options.service ? `"${options.service}"` : "undefined"} }
639
+
640
+ Example:
641
+ app.use(createAuthOS({
642
+ baseURL: "${options.baseURL}",
643
+ org: "your-org-slug",
644
+ service: "your-service-slug",
645
+ }));
646
+
647
+ See: https://docs.authos.dev/vue/oauth-setup`
648
+ );
649
+ return;
650
+ }
651
+ const redirectUri = options.redirectUri ?? (typeof window !== "undefined" ? window.location.origin + "/callback" : void 0);
652
+ const url = client.auth.getLoginUrl(props.provider, {
653
+ org: options.org,
654
+ service: options.service,
655
+ redirect_uri: redirectUri
656
+ });
657
+ emit("redirect");
658
+ window.location.href = url;
659
+ }
660
+ return () => {
661
+ const slotProps = {
662
+ provider: props.provider,
663
+ providerName: providerName.value,
664
+ isConfigured: isConfigured.value,
665
+ disabled: props.disabled,
666
+ handleClick
667
+ };
668
+ if (slots.default) {
669
+ return slots.default(slotProps);
670
+ }
671
+ return vue.h(
672
+ "button",
673
+ {
674
+ type: "button",
675
+ onClick: handleClick,
676
+ disabled: props.disabled || !isConfigured.value,
677
+ "data-authos-oauth": "",
678
+ "data-provider": props.provider
679
+ },
680
+ `Continue with ${providerName.value}`
681
+ );
682
+ };
683
+ }
684
+ });
685
+ var SignedIn = vue.defineComponent({
686
+ name: "SignedIn",
687
+ setup(_, { slots }) {
688
+ const { isAuthenticated, isLoading } = useAuthOS();
689
+ return () => {
690
+ if (isLoading.value) {
691
+ return null;
692
+ }
693
+ if (!isAuthenticated.value) {
694
+ return null;
695
+ }
696
+ return slots.default?.();
697
+ };
698
+ }
699
+ });
700
+ var SignedOut = vue.defineComponent({
701
+ name: "SignedOut",
702
+ setup(_, { slots }) {
703
+ const { isAuthenticated, isLoading } = useAuthOS();
704
+ return () => {
705
+ if (isLoading.value) {
706
+ return null;
707
+ }
708
+ if (isAuthenticated.value) {
709
+ return null;
710
+ }
711
+ return slots.default?.();
712
+ };
713
+ }
714
+ });
715
+ var MagicLinkSignIn = vue.defineComponent({
716
+ name: "MagicLinkSignIn",
717
+ props: {
718
+ showPasswordSignIn: {
719
+ type: Boolean,
720
+ default: true
721
+ }
722
+ },
723
+ emits: ["success", "error"],
724
+ setup(props, { emit, slots }) {
725
+ const { client } = useAuthOS();
726
+ const email = vue.ref("");
727
+ const isLoading = vue.ref(false);
728
+ const error = vue.ref(null);
729
+ const isSent = vue.ref(false);
730
+ async function handleSubmit() {
731
+ error.value = null;
732
+ isLoading.value = true;
733
+ try {
734
+ await client.magicLinks.request({ email: email.value });
735
+ isSent.value = true;
736
+ emit("success");
737
+ } catch (err) {
738
+ const message = err instanceof Error ? err.message : "Failed to send magic link";
739
+ error.value = message;
740
+ emit("error", err);
741
+ } finally {
742
+ isLoading.value = false;
743
+ }
744
+ }
745
+ const slotProps = vue.computed(() => ({
746
+ email: email.value,
747
+ isLoading: isLoading.value,
748
+ error: error.value,
749
+ isSent: isSent.value,
750
+ updateEmail: (val) => {
751
+ email.value = val;
752
+ },
753
+ submit: handleSubmit,
754
+ reset: () => {
755
+ isSent.value = false;
756
+ }
757
+ }));
758
+ return () => {
759
+ if (slots.default) {
760
+ return slots.default(slotProps.value);
761
+ }
762
+ if (isSent.value) {
763
+ return vue.h("div", { "data-authos-magic-link": "", "data-state": "sent" }, [
764
+ vue.h("div", { "data-authos-success": "" }, [
765
+ vue.h("p", "Check your email!"),
766
+ vue.h("p", ["We sent a login link to ", vue.h("strong", email.value)])
767
+ ]),
768
+ vue.h("button", {
769
+ type: "button",
770
+ onClick: () => {
771
+ isSent.value = false;
772
+ },
773
+ "data-authos-back": ""
774
+ }, "Use a different email")
775
+ ]);
776
+ }
777
+ return vue.h("form", {
778
+ onSubmit: (e) => {
779
+ e.preventDefault();
780
+ handleSubmit();
781
+ },
782
+ "data-authos-magic-link": "",
783
+ "data-state": "form"
784
+ }, [
785
+ vue.h("div", { "data-authos-field": "email" }, [
786
+ vue.h("label", { for: "authos-magic-email" }, "Email"),
787
+ vue.h("input", {
788
+ id: "authos-magic-email",
789
+ type: "email",
790
+ autocomplete: "email",
791
+ value: email.value,
792
+ onInput: (e) => {
793
+ email.value = e.target.value;
794
+ },
795
+ placeholder: "Enter your email",
796
+ required: true,
797
+ disabled: isLoading.value
798
+ })
799
+ ]),
800
+ error.value ? vue.h("div", { "data-authos-error": "" }, error.value) : null,
801
+ vue.h("button", {
802
+ type: "submit",
803
+ disabled: isLoading.value,
804
+ "data-authos-submit": ""
805
+ }, isLoading.value ? "Sending..." : "Send Magic Link"),
806
+ props.showPasswordSignIn ? vue.h("div", { "data-authos-signin-prompt": "" }, [
807
+ vue.h("a", { href: "/signin", "data-authos-link": "signin" }, "Sign in with password")
808
+ ]) : null
809
+ ]);
810
+ };
811
+ }
812
+ });
813
+ var PasskeySignIn = vue.defineComponent({
814
+ name: "PasskeySignIn",
815
+ props: {
816
+ showPasswordSignIn: {
817
+ type: Boolean,
818
+ default: true
819
+ }
820
+ },
821
+ emits: ["success", "error"],
822
+ setup(props, { emit, slots }) {
823
+ const { client } = useAuthOS();
824
+ const email = vue.ref("");
825
+ const isLoading = vue.ref(false);
826
+ const error = vue.ref(null);
827
+ const isSupported = vue.ref(true);
828
+ vue.onMounted(() => {
829
+ isSupported.value = client.passkeys.isSupported();
830
+ });
831
+ async function handleSubmit() {
832
+ error.value = null;
833
+ isLoading.value = true;
834
+ try {
835
+ await client.passkeys.login(email.value);
836
+ emit("success");
837
+ } catch (err) {
838
+ const message = err instanceof Error ? err.message : "Passkey authentication failed";
839
+ error.value = message;
840
+ emit("error", err);
841
+ } finally {
842
+ isLoading.value = false;
843
+ }
844
+ }
845
+ const slotProps = vue.computed(() => ({
846
+ email: email.value,
847
+ isLoading: isLoading.value,
848
+ error: error.value,
849
+ isSupported: isSupported.value,
850
+ updateEmail: (val) => {
851
+ email.value = val;
852
+ },
853
+ submit: handleSubmit
854
+ }));
855
+ return () => {
856
+ if (slots.default) {
857
+ return slots.default(slotProps.value);
858
+ }
859
+ if (!isSupported.value) {
860
+ return vue.h("div", {
861
+ "data-authos-passkey": "",
862
+ "data-state": "unsupported"
863
+ }, [
864
+ vue.h("div", { "data-authos-error": "" }, "Passkeys are not supported in this browser."),
865
+ props.showPasswordSignIn ? vue.h("a", { href: "/signin", "data-authos-link": "signin" }, "Sign in with password") : null
866
+ ]);
867
+ }
868
+ return vue.h("form", {
869
+ onSubmit: (e) => {
870
+ e.preventDefault();
871
+ handleSubmit();
872
+ },
873
+ "data-authos-passkey": ""
874
+ }, [
875
+ vue.h("div", { "data-authos-field": "email" }, [
876
+ vue.h("label", { for: "authos-passkey-email" }, "Email"),
877
+ vue.h("input", {
878
+ id: "authos-passkey-email",
879
+ type: "email",
880
+ autocomplete: "email webauthn",
881
+ value: email.value,
882
+ onInput: (e) => {
883
+ email.value = e.target.value;
884
+ },
885
+ placeholder: "Enter your email",
886
+ required: true,
887
+ disabled: isLoading.value
888
+ })
889
+ ]),
890
+ error.value ? vue.h("div", { "data-authos-error": "" }, error.value) : null,
891
+ vue.h("button", {
892
+ type: "submit",
893
+ disabled: isLoading.value,
894
+ "data-authos-submit": ""
895
+ }, isLoading.value ? "Authenticating..." : "Sign in with Passkey"),
896
+ props.showPasswordSignIn ? vue.h("div", { "data-authos-signin-prompt": "" }, [
897
+ vue.h("a", { href: "/signin", "data-authos-link": "signin" }, "Sign in with password")
898
+ ]) : null
899
+ ]);
900
+ };
901
+ }
902
+ });
548
903
 
549
904
  Object.defineProperty(exports, "AuthErrorCodes", {
550
905
  enumerable: true,
@@ -564,12 +919,20 @@ Object.defineProperty(exports, "SsoApiError", {
564
919
  });
565
920
  exports.AUTH_OS_INJECTION_KEY = AUTH_OS_INJECTION_KEY;
566
921
  exports.AuthOSProvider = AuthOSProvider;
922
+ exports.MagicLinkSignIn = MagicLinkSignIn;
923
+ exports.OAuthButton = OAuthButton;
567
924
  exports.OrganizationSwitcher = OrganizationSwitcher;
925
+ exports.PasskeySignIn = PasskeySignIn;
568
926
  exports.Protect = Protect;
569
927
  exports.SignIn = SignIn;
570
928
  exports.SignUp = SignUp;
929
+ exports.SignedIn = SignedIn;
930
+ exports.SignedOut = SignedOut;
571
931
  exports.UserButton = UserButton;
572
932
  exports.createAuthOS = createAuthOS;
933
+ exports.useAllPermissions = useAllPermissions;
934
+ exports.useAnyPermission = useAnyPermission;
573
935
  exports.useAuthOS = useAuthOS;
574
936
  exports.useOrganization = useOrganization;
937
+ exports.usePermission = usePermission;
575
938
  exports.useUser = useUser;