@drmhse/authos-vue 0.1.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.js ADDED
@@ -0,0 +1,562 @@
1
+ 'use strict';
2
+
3
+ var vue = require('vue');
4
+ var ssoSdk = require('@drmhse/sso-sdk');
5
+
6
+ // src/plugin.ts
7
+
8
+ // src/types.ts
9
+ var AUTH_OS_INJECTION_KEY = /* @__PURE__ */ Symbol("authOS");
10
+
11
+ // src/plugin.ts
12
+ function createAuthOS(options) {
13
+ const getStorage = () => {
14
+ if (options.storage) return options.storage;
15
+ try {
16
+ if (typeof window !== "undefined" && window.localStorage) {
17
+ return new ssoSdk.BrowserStorage();
18
+ }
19
+ } catch {
20
+ }
21
+ return new ssoSdk.MemoryStorage();
22
+ };
23
+ const client = new ssoSdk.SsoClient({
24
+ baseURL: options.baseUrl,
25
+ storage: getStorage()
26
+ });
27
+ const state = vue.reactive({
28
+ user: null,
29
+ isAuthenticated: false,
30
+ isLoading: true,
31
+ currentOrganization: null,
32
+ organizations: []
33
+ });
34
+ const context = {
35
+ client,
36
+ state
37
+ };
38
+ return {
39
+ install(app) {
40
+ client.onAuthStateChange(async (isAuthenticated) => {
41
+ state.isAuthenticated = isAuthenticated;
42
+ state.isLoading = false;
43
+ if (isAuthenticated) {
44
+ try {
45
+ const profile = await client.user.getProfile();
46
+ state.user = profile;
47
+ } catch {
48
+ state.user = null;
49
+ }
50
+ try {
51
+ const orgs = await client.organizations.list();
52
+ state.organizations = orgs;
53
+ if (orgs.length > 0 && !state.currentOrganization) {
54
+ state.currentOrganization = orgs[0];
55
+ }
56
+ } catch {
57
+ state.organizations = [];
58
+ }
59
+ } else {
60
+ state.user = null;
61
+ state.currentOrganization = null;
62
+ state.organizations = [];
63
+ }
64
+ });
65
+ app.provide(AUTH_OS_INJECTION_KEY, context);
66
+ app.config.globalProperties.$authOS = context;
67
+ }
68
+ };
69
+ }
70
+ function useAuthOS() {
71
+ const context = vue.inject(AUTH_OS_INJECTION_KEY);
72
+ if (!context) {
73
+ const defaultClient = new ssoSdk.SsoClient({
74
+ baseURL: "http://localhost:3001",
75
+ storage: new ssoSdk.MemoryStorage()
76
+ });
77
+ return {
78
+ client: defaultClient,
79
+ isLoading: vue.computed(() => false),
80
+ isAuthenticated: vue.computed(() => false)
81
+ };
82
+ }
83
+ const isLoading = vue.computed(() => context.state.isLoading);
84
+ const isAuthenticated = vue.computed(() => context.state.isAuthenticated);
85
+ return {
86
+ client: context.client,
87
+ isLoading,
88
+ isAuthenticated
89
+ };
90
+ }
91
+ function useUser() {
92
+ const context = vue.inject(AUTH_OS_INJECTION_KEY);
93
+ if (!context) {
94
+ return {
95
+ user: vue.ref(null),
96
+ isLoading: vue.computed(() => false)
97
+ };
98
+ }
99
+ const user = vue.computed(() => context.state.user);
100
+ const isLoading = vue.computed(() => context.state.isLoading);
101
+ return {
102
+ user,
103
+ isLoading
104
+ };
105
+ }
106
+ function useOrganization() {
107
+ const context = vue.inject(AUTH_OS_INJECTION_KEY);
108
+ if (!context) {
109
+ return {
110
+ currentOrganization: vue.ref(null),
111
+ organizations: vue.ref([]),
112
+ switchOrganization: async () => null,
113
+ isSwitching: vue.ref(false)
114
+ };
115
+ }
116
+ const currentOrganization = vue.computed(() => context.state.currentOrganization);
117
+ const organizations = vue.computed(() => context.state.organizations);
118
+ const isSwitching = vue.ref(false);
119
+ async function switchOrganization(slug) {
120
+ if (!context) return;
121
+ isSwitching.value = true;
122
+ try {
123
+ const orgResponse = await context.client.organizations.get(slug);
124
+ context.state.currentOrganization = orgResponse;
125
+ return orgResponse;
126
+ } finally {
127
+ isSwitching.value = false;
128
+ }
129
+ }
130
+ return {
131
+ currentOrganization,
132
+ organizations,
133
+ switchOrganization,
134
+ isSwitching
135
+ };
136
+ }
137
+ var AuthOSProvider = vue.defineComponent({
138
+ name: "AuthOSProvider",
139
+ props: {
140
+ baseUrl: {
141
+ type: String,
142
+ required: true
143
+ },
144
+ storage: {
145
+ type: Object,
146
+ default: void 0
147
+ },
148
+ client: {
149
+ type: Object,
150
+ default: void 0
151
+ }
152
+ },
153
+ setup(props, { slots }) {
154
+ const getStorage = () => {
155
+ if (props.storage) return props.storage;
156
+ if (typeof window !== "undefined") return new ssoSdk.BrowserStorage();
157
+ return new ssoSdk.MemoryStorage();
158
+ };
159
+ const client = props.client ?? new ssoSdk.SsoClient({
160
+ baseURL: props.baseUrl,
161
+ storage: getStorage()
162
+ });
163
+ const state = vue.reactive({
164
+ user: null,
165
+ isAuthenticated: false,
166
+ isLoading: true,
167
+ currentOrganization: null,
168
+ organizations: []
169
+ });
170
+ const context = { client, state };
171
+ vue.provide(AUTH_OS_INJECTION_KEY, context);
172
+ let unsubscribe;
173
+ vue.onMounted(() => {
174
+ unsubscribe = client.onAuthStateChange(async (isAuthenticated) => {
175
+ state.isAuthenticated = isAuthenticated;
176
+ state.isLoading = false;
177
+ if (isAuthenticated) {
178
+ try {
179
+ const profile = await client.user.getProfile();
180
+ state.user = profile;
181
+ } catch {
182
+ state.user = null;
183
+ }
184
+ try {
185
+ const orgs = await client.organizations.list();
186
+ state.organizations = orgs;
187
+ if (orgs.length > 0 && !state.currentOrganization) {
188
+ state.currentOrganization = orgs[0];
189
+ }
190
+ } catch {
191
+ state.organizations = [];
192
+ }
193
+ } else {
194
+ state.user = null;
195
+ state.currentOrganization = null;
196
+ state.organizations = [];
197
+ }
198
+ });
199
+ });
200
+ vue.onUnmounted(() => {
201
+ unsubscribe?.();
202
+ });
203
+ return () => slots.default ? slots.default() : vue.h("div");
204
+ }
205
+ });
206
+ var MFA_PREAUTH_EXPIRY = 300;
207
+ var SignIn = vue.defineComponent({
208
+ name: "SignIn",
209
+ props: {
210
+ onSuccess: {
211
+ type: Function,
212
+ default: void 0
213
+ },
214
+ onError: {
215
+ type: Function,
216
+ default: void 0
217
+ }
218
+ },
219
+ emits: ["success", "error"],
220
+ setup(props, { slots, emit }) {
221
+ const { client } = useAuthOS();
222
+ const email = vue.ref("");
223
+ const password = vue.ref("");
224
+ const mfaCode = vue.ref("");
225
+ const preauthToken = vue.ref("");
226
+ const step = vue.ref("credentials");
227
+ const error = vue.ref(null);
228
+ const isSubmitting = vue.ref(false);
229
+ async function submit() {
230
+ error.value = null;
231
+ isSubmitting.value = true;
232
+ try {
233
+ if (step.value === "credentials") {
234
+ const result = await client.auth.login({
235
+ email: email.value,
236
+ password: password.value
237
+ });
238
+ if (result.expires_in === MFA_PREAUTH_EXPIRY) {
239
+ preauthToken.value = result.access_token;
240
+ step.value = "mfa";
241
+ } else {
242
+ emit("success");
243
+ props.onSuccess?.();
244
+ }
245
+ } else {
246
+ await client.auth.verifyMfa(preauthToken.value, mfaCode.value);
247
+ emit("success");
248
+ props.onSuccess?.();
249
+ }
250
+ } catch (err) {
251
+ const message = err instanceof ssoSdk.SsoApiError ? err.message : "Login failed";
252
+ error.value = message;
253
+ const e = err instanceof Error ? err : new Error(message);
254
+ emit("error", e);
255
+ props.onError?.(e);
256
+ } finally {
257
+ isSubmitting.value = false;
258
+ }
259
+ }
260
+ return () => {
261
+ const slotProps = {
262
+ email: email.value,
263
+ password: password.value,
264
+ mfaCode: mfaCode.value,
265
+ step: step.value,
266
+ error: error.value,
267
+ isSubmitting: isSubmitting.value,
268
+ updateEmail: (v) => email.value = v,
269
+ updatePassword: (v) => password.value = v,
270
+ updateMfaCode: (v) => mfaCode.value = v,
271
+ submit
272
+ };
273
+ if (slots.default) {
274
+ return slots.default(slotProps);
275
+ }
276
+ return vue.h("form", { onSubmit: (e) => {
277
+ e.preventDefault();
278
+ submit();
279
+ } }, [
280
+ step.value === "credentials" ? [
281
+ vue.h("input", {
282
+ type: "email",
283
+ value: email.value,
284
+ placeholder: "Email",
285
+ onInput: (e) => email.value = e.target.value
286
+ }),
287
+ vue.h("input", {
288
+ type: "password",
289
+ value: password.value,
290
+ placeholder: "Password",
291
+ onInput: (e) => password.value = e.target.value
292
+ })
293
+ ] : vue.h("input", {
294
+ type: "text",
295
+ value: mfaCode.value,
296
+ placeholder: "MFA Code",
297
+ onInput: (e) => mfaCode.value = e.target.value
298
+ }),
299
+ error.value && vue.h("p", { style: "color: red" }, error.value),
300
+ vue.h("button", { type: "submit", disabled: isSubmitting.value }, isSubmitting.value ? "Signing in..." : "Sign In")
301
+ ]);
302
+ };
303
+ }
304
+ });
305
+ var SignUp = vue.defineComponent({
306
+ name: "SignUp",
307
+ props: {
308
+ onSuccess: {
309
+ type: Function,
310
+ default: void 0
311
+ },
312
+ onError: {
313
+ type: Function,
314
+ default: void 0
315
+ }
316
+ },
317
+ emits: ["success", "error"],
318
+ setup(props, { slots, emit }) {
319
+ const { client } = useAuthOS();
320
+ const email = vue.ref("");
321
+ const password = vue.ref("");
322
+ const error = vue.ref(null);
323
+ const isSubmitting = vue.ref(false);
324
+ async function submit() {
325
+ error.value = null;
326
+ isSubmitting.value = true;
327
+ try {
328
+ await client.auth.register({
329
+ email: email.value,
330
+ password: password.value
331
+ });
332
+ emit("success");
333
+ props.onSuccess?.();
334
+ } catch (err) {
335
+ const message = err instanceof ssoSdk.SsoApiError ? err.message : "Registration failed";
336
+ error.value = message;
337
+ const e = err instanceof Error ? err : new Error(message);
338
+ emit("error", e);
339
+ props.onError?.(e);
340
+ } finally {
341
+ isSubmitting.value = false;
342
+ }
343
+ }
344
+ return () => {
345
+ const slotProps = {
346
+ email: email.value,
347
+ password: password.value,
348
+ error: error.value,
349
+ isSubmitting: isSubmitting.value,
350
+ updateEmail: (v) => email.value = v,
351
+ updatePassword: (v) => password.value = v,
352
+ submit
353
+ };
354
+ if (slots.default) {
355
+ return slots.default(slotProps);
356
+ }
357
+ return vue.h("form", { onSubmit: (e) => {
358
+ e.preventDefault();
359
+ submit();
360
+ } }, [
361
+ vue.h("input", {
362
+ type: "email",
363
+ value: email.value,
364
+ placeholder: "Email",
365
+ onInput: (e) => email.value = e.target.value
366
+ }),
367
+ vue.h("input", {
368
+ type: "password",
369
+ value: password.value,
370
+ placeholder: "Password",
371
+ onInput: (e) => password.value = e.target.value
372
+ }),
373
+ error.value && vue.h("p", { style: "color: red" }, error.value),
374
+ vue.h("button", { type: "submit", disabled: isSubmitting.value }, isSubmitting.value ? "Creating account..." : "Sign Up")
375
+ ]);
376
+ };
377
+ }
378
+ });
379
+ var OrganizationSwitcher = vue.defineComponent({
380
+ name: "OrganizationSwitcher",
381
+ props: {
382
+ onSwitch: {
383
+ type: Function,
384
+ default: void 0
385
+ }
386
+ },
387
+ emits: ["switch"],
388
+ setup(props, { slots, emit }) {
389
+ const { currentOrganization, organizations, switchOrganization, isSwitching } = useOrganization();
390
+ async function switchTo(slug) {
391
+ await switchOrganization(slug);
392
+ const org = organizations.value.find((o) => o.organization.slug === slug);
393
+ if (org) {
394
+ emit("switch", org);
395
+ props.onSwitch?.(org);
396
+ }
397
+ }
398
+ return () => {
399
+ const slotProps = {
400
+ currentOrganization: currentOrganization.value,
401
+ organizations: organizations.value,
402
+ isSwitching: isSwitching.value,
403
+ switchTo
404
+ };
405
+ if (slots.default) {
406
+ return slots.default(slotProps);
407
+ }
408
+ return vue.h(
409
+ "div",
410
+ { "data-authos-orgswitcher": "", "data-state": organizations.value.length > 0 ? "ready" : "empty" },
411
+ organizations.value.length > 0 ? vue.h(
412
+ "select",
413
+ {
414
+ value: currentOrganization.value?.organization.slug ?? "",
415
+ disabled: isSwitching.value,
416
+ onChange: (e) => switchTo(e.target.value)
417
+ },
418
+ organizations.value.map(
419
+ (org) => vue.h("option", { key: org.organization.id, value: org.organization.slug }, org.organization.name)
420
+ )
421
+ ) : "No organizations"
422
+ );
423
+ };
424
+ }
425
+ });
426
+ var UserButton = vue.defineComponent({
427
+ name: "UserButton",
428
+ props: {
429
+ onLogout: {
430
+ type: Function,
431
+ default: void 0
432
+ }
433
+ },
434
+ emits: ["logout"],
435
+ setup(props, { slots, emit }) {
436
+ const { user, isLoading } = useUser();
437
+ const { client } = useAuthOS();
438
+ const isLoggingOut = vue.ref(false);
439
+ async function logout() {
440
+ isLoggingOut.value = true;
441
+ try {
442
+ await client.auth.logout();
443
+ emit("logout");
444
+ props.onLogout?.();
445
+ } finally {
446
+ isLoggingOut.value = false;
447
+ }
448
+ }
449
+ return () => {
450
+ const slotProps = {
451
+ user: user.value,
452
+ isLoading: isLoading.value,
453
+ isLoggingOut: isLoggingOut.value,
454
+ logout
455
+ };
456
+ if (slots.default) {
457
+ return slots.default(slotProps);
458
+ }
459
+ if (isLoading.value) {
460
+ return vue.h("div", { "data-authos-userbutton": "", "data-state": "loading" }, "Loading...");
461
+ }
462
+ if (!user.value) {
463
+ return vue.h("div", { "data-authos-userbutton": "", "data-state": "signed-out" }, "Not signed in");
464
+ }
465
+ return vue.h("div", { "data-authos-userbutton": "", "data-state": "signed-in", style: "display: flex; align-items: center; gap: 8px;" }, [
466
+ vue.h("span", user.value.email),
467
+ vue.h(
468
+ "button",
469
+ {
470
+ onClick: logout,
471
+ disabled: isLoggingOut.value
472
+ },
473
+ isLoggingOut.value ? "Logging out..." : "Logout"
474
+ )
475
+ ]);
476
+ };
477
+ }
478
+ });
479
+ var Protect = vue.defineComponent({
480
+ name: "Protect",
481
+ props: {
482
+ permission: {
483
+ type: String,
484
+ default: void 0
485
+ },
486
+ permissions: {
487
+ type: Array,
488
+ default: void 0
489
+ },
490
+ requireAll: {
491
+ type: Boolean,
492
+ default: false
493
+ },
494
+ fallback: {
495
+ type: [Object, Function],
496
+ default: void 0
497
+ }
498
+ },
499
+ setup(props, { slots }) {
500
+ const { user, isLoading } = useUser();
501
+ const hasAccess = vue.computed(() => {
502
+ if (isLoading.value || !user.value) {
503
+ return false;
504
+ }
505
+ const userPermissions = user.value.permissions ?? [];
506
+ if (props.permission) {
507
+ return userPermissions.includes(props.permission);
508
+ }
509
+ if (props.permissions && props.permissions.length > 0) {
510
+ if (props.requireAll) {
511
+ return props.permissions.every((p) => userPermissions.includes(p));
512
+ }
513
+ return props.permissions.some((p) => userPermissions.includes(p));
514
+ }
515
+ return true;
516
+ });
517
+ return () => {
518
+ if (isLoading.value) {
519
+ return vue.h("div", { "data-authos-protect": "", "data-state": "loading" });
520
+ }
521
+ if (hasAccess.value) {
522
+ return vue.h("div", { "data-authos-protect": "", "data-state": "allowed" }, slots.default?.());
523
+ }
524
+ if (props.fallback) {
525
+ const fallbackContent = typeof props.fallback === "function" ? props.fallback() : props.fallback;
526
+ return vue.h("div", { "data-authos-protect": "", "data-state": "denied" }, [fallbackContent]);
527
+ }
528
+ if (slots.fallback) {
529
+ return vue.h("div", { "data-authos-protect": "", "data-state": "denied" }, slots.fallback());
530
+ }
531
+ return vue.h("div", { "data-authos-protect": "", "data-state": "denied" });
532
+ };
533
+ }
534
+ });
535
+
536
+ Object.defineProperty(exports, "AuthErrorCodes", {
537
+ enumerable: true,
538
+ get: function () { return ssoSdk.AuthErrorCodes; }
539
+ });
540
+ Object.defineProperty(exports, "BrowserStorage", {
541
+ enumerable: true,
542
+ get: function () { return ssoSdk.BrowserStorage; }
543
+ });
544
+ Object.defineProperty(exports, "MemoryStorage", {
545
+ enumerable: true,
546
+ get: function () { return ssoSdk.MemoryStorage; }
547
+ });
548
+ Object.defineProperty(exports, "SsoApiError", {
549
+ enumerable: true,
550
+ get: function () { return ssoSdk.SsoApiError; }
551
+ });
552
+ exports.AUTH_OS_INJECTION_KEY = AUTH_OS_INJECTION_KEY;
553
+ exports.AuthOSProvider = AuthOSProvider;
554
+ exports.OrganizationSwitcher = OrganizationSwitcher;
555
+ exports.Protect = Protect;
556
+ exports.SignIn = SignIn;
557
+ exports.SignUp = SignUp;
558
+ exports.UserButton = UserButton;
559
+ exports.createAuthOS = createAuthOS;
560
+ exports.useAuthOS = useAuthOS;
561
+ exports.useOrganization = useOrganization;
562
+ exports.useUser = useUser;