@xenterprises/nuxt-x-auth-better 0.3.1 → 0.4.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.
@@ -0,0 +1,532 @@
1
+ import { computed } from 'vue';
2
+ import type { Organization, OrganizationMember, OrganizationInvitation, OrganizationTeam, PermissionCheck } from './types';
3
+
4
+ export function useOrganization() {
5
+ const config = useAppConfig();
6
+ // The authClient singleton is created with organizationClient(), but its type
7
+ // is not inferred through useXAuth's module-level singleton. Cast to any is
8
+ // intentional: the plugin is guaranteed at runtime and the composable owns
9
+ // all org-specific client calls.
10
+ const { authClient } = useXAuth() as { authClient: any };
11
+ const toast = useToast();
12
+
13
+ const orgConfig = computed(() => config.xAuth?.organization || {});
14
+ const isEnabled = computed(() =>
15
+ config.xAuth?.features?.organization !== false && orgConfig.value?.enabled !== false
16
+ );
17
+ const teamsEnabled = computed(() =>
18
+ isEnabled.value && orgConfig.value?.teams !== false
19
+ );
20
+
21
+ const listQuery = authClient.useListOrganizations();
22
+ const activeQuery = authClient.useActiveOrganization();
23
+ const activeMemberQuery = authClient.organization.useActiveMember ? authClient.organization.useActiveMember() : null;
24
+ const activeMemberRoleQuery = authClient.organization.useActiveMemberRole ? authClient.organization.useActiveMemberRole() : null;
25
+
26
+ const organizations = computed<Organization[]>(() => listQuery.value.data || []);
27
+ const organizationsLoading = computed(() => listQuery.value.isPending);
28
+ const organizationsError = computed(() => listQuery.value.error || null);
29
+
30
+ const activeOrganization = computed<Organization | null>(() => activeQuery.value.data || null);
31
+ const activeLoading = computed(() => activeQuery.value.isPending);
32
+ const activeError = computed(() => activeQuery.value.error || null);
33
+
34
+ const activeMember = computed<OrganizationMember | null>(() => activeMemberQuery?.value?.data || null);
35
+ const activeMemberRole = computed<string | null>(() => activeMemberRoleQuery?.value?.data?.role || null);
36
+
37
+ const members = computed<OrganizationMember[]>(() => activeQuery.value.data?.members || []);
38
+ const invitations = computed<OrganizationInvitation[]>(() => activeQuery.value.data?.invitations || []);
39
+ const teams = computed<OrganizationTeam[]>(() => activeQuery.value.data?.teams || []);
40
+
41
+ const showSuccess = (title: string, description?: string) => {
42
+ toast.add({
43
+ title,
44
+ description,
45
+ icon: 'i-lucide-check-circle',
46
+ color: 'success',
47
+ });
48
+ };
49
+
50
+ const showError = (title: string, description?: string) => {
51
+ toast.add({
52
+ title,
53
+ description,
54
+ icon: 'i-lucide-alert-circle',
55
+ color: 'error',
56
+ });
57
+ };
58
+
59
+ const withErrorHandling = async <T>(
60
+ title: string,
61
+ fn: () => Promise<{ data?: T | null; error?: { message?: string } | null }>,
62
+ success?: { title?: string; description?: string }
63
+ ): Promise<T | null | { error: string }> => {
64
+ try {
65
+ const result = await fn();
66
+ if (result.error) {
67
+ showError(title, result.error.message || 'An error occurred.');
68
+ return { error: result.error.message || 'An error occurred.' };
69
+ }
70
+ if (success?.title) {
71
+ showSuccess(success.title, success.description);
72
+ }
73
+ return result.data || null;
74
+ } catch (err: any) {
75
+ showError(title, err.message || 'An unexpected error occurred.');
76
+ return { error: err.message || 'An unexpected error occurred.' };
77
+ }
78
+ };
79
+
80
+ const ensureEnabled = (label: string): null | { error: string } => {
81
+ if (!isEnabled.value) {
82
+ showError(`${label} Failed`, 'Organizations are not enabled.');
83
+ return { error: 'Organizations are not enabled.' };
84
+ }
85
+ return null;
86
+ };
87
+
88
+ const createOrganization = async (name: string, slug?: string, logo?: string) => {
89
+ const disabled = ensureEnabled('Create Organization');
90
+ if (disabled) return disabled;
91
+
92
+ return withErrorHandling(
93
+ 'Create Organization',
94
+ () => authClient.organization.create({
95
+ name,
96
+ slug,
97
+ logo,
98
+ }),
99
+ { title: 'Organization Created', description: `${name} has been created.` }
100
+ );
101
+ };
102
+
103
+ const updateOrganization = async (data: { organizationId?: string; name?: string; slug?: string; logo?: string }) => {
104
+ const disabled = ensureEnabled('Update Organization');
105
+ if (disabled) return disabled;
106
+
107
+ return withErrorHandling(
108
+ 'Update Organization',
109
+ () => authClient.organization.update(data),
110
+ { title: 'Organization Updated' }
111
+ );
112
+ };
113
+
114
+ const deleteOrganization = async (organizationId?: string) => {
115
+ const disabled = ensureEnabled('Delete Organization');
116
+ if (disabled) return disabled;
117
+
118
+ return withErrorHandling(
119
+ 'Delete Organization',
120
+ () => authClient.organization.delete({ organizationId }),
121
+ { title: 'Organization Deleted' }
122
+ );
123
+ };
124
+
125
+ const setActiveOrganization = async (organizationId?: string | null, organizationSlug?: string) => {
126
+ const disabled = ensureEnabled('Set Active Organization');
127
+ if (disabled) return disabled;
128
+
129
+ return withErrorHandling(
130
+ 'Set Active Organization',
131
+ () => authClient.organization.setActive({ organizationId: organizationId || null, organizationSlug }),
132
+ { title: 'Active Organization Updated' }
133
+ );
134
+ };
135
+
136
+ const getFullOrganization = async (organizationId?: string, organizationSlug?: string) => {
137
+ const disabled = ensureEnabled('Get Organization');
138
+ if (disabled) return disabled;
139
+
140
+ return withErrorHandling(
141
+ 'Get Organization',
142
+ () => authClient.organization.getFullOrganization({ organizationId, organizationSlug })
143
+ );
144
+ };
145
+
146
+ const checkSlug = async (slug: string) => {
147
+ const disabled = ensureEnabled('Check Slug');
148
+ if (disabled) return disabled;
149
+
150
+ return withErrorHandling(
151
+ 'Check Slug',
152
+ () => authClient.organization.checkSlug({ slug })
153
+ );
154
+ };
155
+
156
+ const listMembers = async (organizationId?: string) => {
157
+ const disabled = ensureEnabled('List Members');
158
+ if (disabled) return disabled;
159
+
160
+ return withErrorHandling(
161
+ 'List Members',
162
+ () => authClient.organization.listMembers({ organizationId })
163
+ );
164
+ };
165
+
166
+ const updateMemberRole = async (memberId: string, role: string | string[], organizationId?: string) => {
167
+ const disabled = ensureEnabled('Update Member Role');
168
+ if (disabled) return disabled;
169
+
170
+ return withErrorHandling(
171
+ 'Update Member Role',
172
+ () => authClient.organization.updateMemberRole({ memberId, role, organizationId }),
173
+ { title: 'Member Role Updated' }
174
+ );
175
+ };
176
+
177
+ const removeMember = async (memberId: string, organizationId?: string) => {
178
+ const disabled = ensureEnabled('Remove Member');
179
+ if (disabled) return disabled;
180
+
181
+ return withErrorHandling(
182
+ 'Remove Member',
183
+ () => authClient.organization.removeMember({ memberId, organizationId }),
184
+ { title: 'Member Removed' }
185
+ );
186
+ };
187
+
188
+ const leaveOrganization = async () => {
189
+ const disabled = ensureEnabled('Leave Organization');
190
+ if (disabled) return disabled;
191
+
192
+ return withErrorHandling(
193
+ 'Leave Organization',
194
+ () => authClient.organization.leave(),
195
+ { title: 'Left Organization' }
196
+ );
197
+ };
198
+
199
+ const inviteMember = async (email: string, role: string | string[], organizationId?: string) => {
200
+ const disabled = ensureEnabled('Invite Member');
201
+ if (disabled) return disabled;
202
+
203
+ return withErrorHandling(
204
+ 'Invite Member',
205
+ () => authClient.organization.inviteMember({ email, role, organizationId }),
206
+ { title: 'Invitation Sent', description: `An invitation has been sent to ${email}.` }
207
+ );
208
+ };
209
+
210
+ const cancelInvitation = async (invitationId: string) => {
211
+ const disabled = ensureEnabled('Cancel Invitation');
212
+ if (disabled) return disabled;
213
+
214
+ return withErrorHandling(
215
+ 'Cancel Invitation',
216
+ () => authClient.organization.cancelInvitation({ invitationId }),
217
+ { title: 'Invitation Canceled' }
218
+ );
219
+ };
220
+
221
+ const acceptInvitation = async (invitationId: string) => {
222
+ const disabled = ensureEnabled('Accept Invitation');
223
+ if (disabled) return disabled;
224
+
225
+ return withErrorHandling(
226
+ 'Accept Invitation',
227
+ () => authClient.organization.acceptInvitation({ invitationId }),
228
+ { title: 'Invitation Accepted' }
229
+ );
230
+ };
231
+
232
+ const rejectInvitation = async (invitationId: string) => {
233
+ const disabled = ensureEnabled('Reject Invitation');
234
+ if (disabled) return disabled;
235
+
236
+ return withErrorHandling(
237
+ 'Reject Invitation',
238
+ () => authClient.organization.rejectInvitation({ invitationId }),
239
+ { title: 'Invitation Rejected' }
240
+ );
241
+ };
242
+
243
+ const getInvitation = async (invitationId: string) => {
244
+ const disabled = ensureEnabled('Get Invitation');
245
+ if (disabled) return disabled;
246
+
247
+ return withErrorHandling(
248
+ 'Get Invitation',
249
+ () => authClient.organization.getInvitation({ invitationId })
250
+ );
251
+ };
252
+
253
+ const listInvitations = async (organizationId?: string) => {
254
+ const disabled = ensureEnabled('List Invitations');
255
+ if (disabled) return disabled;
256
+
257
+ return withErrorHandling(
258
+ 'List Invitations',
259
+ () => authClient.organization.listInvitations({ organizationId })
260
+ );
261
+ };
262
+
263
+ const listUserInvitations = async () => {
264
+ const disabled = ensureEnabled('List User Invitations');
265
+ if (disabled) return disabled;
266
+
267
+ return withErrorHandling(
268
+ 'List User Invitations',
269
+ () => authClient.organization.listUserInvitations()
270
+ );
271
+ };
272
+
273
+ const listTeams = async (organizationId?: string) => {
274
+ const disabled = ensureEnabled('List Teams');
275
+ if (disabled) return disabled;
276
+
277
+ if (!teamsEnabled.value) {
278
+ showError('List Teams Failed', 'Teams are not enabled.');
279
+ return { error: 'Teams are not enabled.' };
280
+ }
281
+
282
+ return withErrorHandling(
283
+ 'List Teams',
284
+ () => authClient.organization.listTeams({ organizationId })
285
+ );
286
+ };
287
+
288
+ const createTeam = async (name: string, organizationId?: string) => {
289
+ const disabled = ensureEnabled('Create Team');
290
+ if (disabled) return disabled;
291
+
292
+ if (!teamsEnabled.value) {
293
+ showError('Create Team Failed', 'Teams are not enabled.');
294
+ return { error: 'Teams are not enabled.' };
295
+ }
296
+
297
+ return withErrorHandling(
298
+ 'Create Team',
299
+ () => authClient.organization.createTeam({ name, organizationId }),
300
+ { title: 'Team Created' }
301
+ );
302
+ };
303
+
304
+ const updateTeam = async (teamId: string, data: { name?: string }, organizationId?: string) => {
305
+ const disabled = ensureEnabled('Update Team');
306
+ if (disabled) return disabled;
307
+
308
+ if (!teamsEnabled.value) {
309
+ showError('Update Team Failed', 'Teams are not enabled.');
310
+ return { error: 'Teams are not enabled.' };
311
+ }
312
+
313
+ return withErrorHandling(
314
+ 'Update Team',
315
+ () => authClient.organization.updateTeam({ teamId, ...data, organizationId }),
316
+ { title: 'Team Updated' }
317
+ );
318
+ };
319
+
320
+ const deleteTeam = async (teamId: string, organizationId?: string) => {
321
+ const disabled = ensureEnabled('Delete Team');
322
+ if (disabled) return disabled;
323
+
324
+ if (!teamsEnabled.value) {
325
+ showError('Delete Team Failed', 'Teams are not enabled.');
326
+ return { error: 'Teams are not enabled.' };
327
+ }
328
+
329
+ return withErrorHandling(
330
+ 'Delete Team',
331
+ () => authClient.organization.deleteTeam({ teamId, organizationId }),
332
+ { title: 'Team Deleted' }
333
+ );
334
+ };
335
+
336
+ const setActiveTeam = async (teamId: string) => {
337
+ const disabled = ensureEnabled('Set Active Team');
338
+ if (disabled) return disabled;
339
+
340
+ if (!teamsEnabled.value) {
341
+ showError('Set Active Team Failed', 'Teams are not enabled.');
342
+ return { error: 'Teams are not enabled.' };
343
+ }
344
+
345
+ return withErrorHandling(
346
+ 'Set Active Team',
347
+ () => authClient.organization.setActiveTeam({ teamId }),
348
+ { title: 'Active Team Updated' }
349
+ );
350
+ };
351
+
352
+ const listTeamMembers = async (teamId: string) => {
353
+ const disabled = ensureEnabled('List Team Members');
354
+ if (disabled) return disabled;
355
+
356
+ if (!teamsEnabled.value) {
357
+ showError('List Team Members Failed', 'Teams are not enabled.');
358
+ return { error: 'Teams are not enabled.' };
359
+ }
360
+
361
+ return withErrorHandling(
362
+ 'List Team Members',
363
+ () => authClient.organization.listTeamMembers({ teamId })
364
+ );
365
+ };
366
+
367
+ const addTeamMember = async (teamId: string, memberId: string, organizationId?: string) => {
368
+ const disabled = ensureEnabled('Add Team Member');
369
+ if (disabled) return disabled;
370
+
371
+ if (!teamsEnabled.value) {
372
+ showError('Add Team Member Failed', 'Teams are not enabled.');
373
+ return { error: 'Teams are not enabled.' };
374
+ }
375
+
376
+ return withErrorHandling(
377
+ 'Add Team Member',
378
+ () => authClient.organization.addTeamMember({ teamId, memberId, organizationId }),
379
+ { title: 'Team Member Added' }
380
+ );
381
+ };
382
+
383
+ const removeTeamMember = async (teamId: string, memberId: string) => {
384
+ const disabled = ensureEnabled('Remove Team Member');
385
+ if (disabled) return disabled;
386
+
387
+ if (!teamsEnabled.value) {
388
+ showError('Remove Team Member Failed', 'Teams are not enabled.');
389
+ return { error: 'Teams are not enabled.' };
390
+ }
391
+
392
+ return withErrorHandling(
393
+ 'Remove Team Member',
394
+ () => authClient.organization.removeTeamMember({ teamId, memberId }),
395
+ { title: 'Team Member Removed' }
396
+ );
397
+ };
398
+
399
+ const listUserTeams = async () => {
400
+ const disabled = ensureEnabled('List User Teams');
401
+ if (disabled) return disabled;
402
+
403
+ if (!teamsEnabled.value) {
404
+ showError('List User Teams Failed', 'Teams are not enabled.');
405
+ return { error: 'Teams are not enabled.' };
406
+ }
407
+
408
+ return withErrorHandling(
409
+ 'List User Teams',
410
+ () => authClient.organization.listUserTeams()
411
+ );
412
+ };
413
+
414
+ const hasPermission = (permissionCheck: PermissionCheck): boolean => {
415
+ if (!isEnabled.value) return false;
416
+ try {
417
+ return authClient.organization.hasPermission(permissionCheck);
418
+ } catch (err: any) {
419
+ return false;
420
+ }
421
+ };
422
+
423
+ const checkRolePermission = (role: string, permissions: Record<string, string[]>): boolean => {
424
+ if (!isEnabled.value) return false;
425
+ try {
426
+ return authClient.organization.checkRolePermission({ role, permissions });
427
+ } catch (err: any) {
428
+ return false;
429
+ }
430
+ };
431
+
432
+ const getDefaultPermissions = (role: string): Record<string, string[]> => {
433
+ switch (role) {
434
+ case 'owner':
435
+ return {
436
+ organization: ['update', 'delete'],
437
+ member: ['create', 'update', 'delete'],
438
+ invitation: ['create', 'cancel'],
439
+ team: ['create', 'update', 'delete'],
440
+ };
441
+ case 'admin':
442
+ return {
443
+ organization: ['update'],
444
+ member: ['create', 'update', 'delete'],
445
+ invitation: ['create', 'cancel'],
446
+ team: ['create', 'update', 'delete'],
447
+ };
448
+ case 'member':
449
+ default:
450
+ return {
451
+ organization: [],
452
+ member: [],
453
+ invitation: [],
454
+ team: [],
455
+ };
456
+ }
457
+ };
458
+
459
+ const canCreateOrganization = computed(() =>
460
+ orgConfig.value?.allowUserToCreateOrganization !== false
461
+ );
462
+
463
+ const isOrgAdmin = computed(() => {
464
+ const role = activeMemberRole.value || activeMember.value?.role || '';
465
+ return role === 'owner' || role === 'admin' || role.split(',').includes('owner') || role.split(',').includes('admin');
466
+ });
467
+
468
+ const isOrgOwner = computed(() => {
469
+ const role = activeMemberRole.value || activeMember.value?.role || '';
470
+ return role === 'owner' || role.split(',').includes('owner');
471
+ });
472
+
473
+ return {
474
+ isEnabled,
475
+ teamsEnabled,
476
+ config: orgConfig,
477
+
478
+ organizations,
479
+ organizationsLoading,
480
+ organizationsError,
481
+
482
+ activeOrganization,
483
+ activeLoading,
484
+ activeError,
485
+
486
+ activeMember,
487
+ activeMemberRole,
488
+ members,
489
+ invitations,
490
+ teams,
491
+
492
+ canCreateOrganization,
493
+ isOrgAdmin,
494
+ isOrgOwner,
495
+
496
+ createOrganization,
497
+ updateOrganization,
498
+ deleteOrganization,
499
+ setActiveOrganization,
500
+ getFullOrganization,
501
+ checkSlug,
502
+
503
+ listMembers,
504
+ updateMemberRole,
505
+ removeMember,
506
+ leaveOrganization,
507
+
508
+ inviteMember,
509
+ cancelInvitation,
510
+ acceptInvitation,
511
+ rejectInvitation,
512
+ getInvitation,
513
+ listInvitations,
514
+ listUserInvitations,
515
+
516
+ listTeams,
517
+ createTeam,
518
+ updateTeam,
519
+ deleteTeam,
520
+ setActiveTeam,
521
+ listTeamMembers,
522
+ addTeamMember,
523
+ removeTeamMember,
524
+ listUserTeams,
525
+
526
+ hasPermission,
527
+ checkRolePermission,
528
+ getDefaultPermissions,
529
+
530
+ authClient,
531
+ };
532
+ }
@@ -1,5 +1,6 @@
1
1
  import { computed, ref } from 'vue';
2
2
  import { createAuthClient } from 'better-auth/vue';
3
+ import { organizationClient } from 'better-auth/client/plugins';
3
4
  import type { AuthUser, AuthConfig } from './types';
4
5
 
5
6
  let authClientInstance: ReturnType<typeof createAuthClient> | null = null;
@@ -28,6 +29,13 @@ export function useXAuth() {
28
29
  fetchOptions: {
29
30
  credentials: 'include',
30
31
  },
32
+ plugins: [
33
+ organizationClient({
34
+ teams: {
35
+ enabled: true,
36
+ },
37
+ }),
38
+ ],
31
39
  });
32
40
  }
33
41
 
@@ -47,7 +55,6 @@ export function useXAuth() {
47
55
  description,
48
56
  icon: 'i-lucide-check-circle',
49
57
  color: 'success',
50
- timeout: 2000,
51
58
  });
52
59
  };
53
60
 
@@ -57,7 +64,6 @@ export function useXAuth() {
57
64
  description,
58
65
  icon: 'i-lucide-alert-circle',
59
66
  color: 'error',
60
- timeout: 3000,
61
67
  });
62
68
  };
63
69
 
@@ -102,7 +108,7 @@ export function useXAuth() {
102
108
  const result = await authClient.signUp.email({
103
109
  email,
104
110
  password,
105
- name: name || email.split('@')[0],
111
+ name: name ?? (email.split('@')[0] || email),
106
112
  });
107
113
 
108
114
  if (result.error) {
@@ -157,7 +163,7 @@ export function useXAuth() {
157
163
  }
158
164
 
159
165
  try {
160
- const result = await authClient.resetPassword({
166
+ const result = await authClient.requestPasswordReset({
161
167
  email,
162
168
  redirectTo: `${getOrigin()}/auth/handler/password-reset`,
163
169
  });
@@ -257,7 +263,7 @@ export function useXAuth() {
257
263
  const getToken = async (): Promise<string | null> => {
258
264
  try {
259
265
  const session = await authClient.getSession();
260
- return session.data?.session?.accessToken || null;
266
+ return session.data?.session?.token || null;
261
267
  } catch {
262
268
  return null;
263
269
  }