@agentuity/auth 0.1.8 → 0.1.9

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.
@@ -1,345 +0,0 @@
1
- /**
2
- * Organization plugin API types for @agentuity/auth.
3
- *
4
- * Server-side API methods for organization management provided by BetterAuth's
5
- * organization plugin. Includes multi-tenancy support, member management,
6
- * invitations, and access control.
7
- *
8
- * @see https://better-auth.com/docs/plugins/organization
9
- * @module agentuity/plugins/organization
10
- */
11
-
12
- /**
13
- * Organization data returned from API calls.
14
- */
15
- export interface Organization {
16
- id: string;
17
- name: string;
18
- slug: string;
19
- logo?: string | null;
20
- metadata?: Record<string, unknown> | null;
21
- createdAt?: Date;
22
- }
23
-
24
- /**
25
- * Member data within an organization.
26
- */
27
- export interface OrganizationMember {
28
- id: string;
29
- userId: string;
30
- organizationId: string;
31
- role: string;
32
- createdAt?: Date;
33
- user?: {
34
- id: string;
35
- name?: string | null;
36
- email: string;
37
- image?: string | null;
38
- };
39
- }
40
-
41
- /**
42
- * Invitation data for organization invites.
43
- */
44
- export interface OrganizationInvitation {
45
- id: string;
46
- email: string;
47
- role: string;
48
- organizationId: string;
49
- inviterId: string;
50
- status: 'pending' | 'accepted' | 'rejected' | 'canceled';
51
- expiresAt: Date;
52
- createdAt?: Date;
53
- organization?: Organization;
54
- inviter?: {
55
- id: string;
56
- name?: string | null;
57
- email: string;
58
- };
59
- }
60
-
61
- /**
62
- * Server-side API methods for organization management.
63
- *
64
- * These methods are added by the BetterAuth organization plugin and provide
65
- * multi-tenancy support including creating organizations, managing members,
66
- * and handling invitations.
67
- *
68
- * @see https://better-auth.com/docs/plugins/organization
69
- */
70
- export interface OrganizationApiMethods {
71
- // =========================================================================
72
- // Organization CRUD
73
- // =========================================================================
74
-
75
- /**
76
- * Create a new organization.
77
- *
78
- * The creator becomes the owner by default. If session headers are provided,
79
- * the organization is created for the authenticated user. If `userId` is
80
- * provided without headers (server-side only), it creates for that user.
81
- */
82
- createOrganization: (params: {
83
- body: {
84
- name: string;
85
- slug: string;
86
- logo?: string;
87
- metadata?: Record<string, unknown>;
88
- userId?: string;
89
- keepCurrentActiveOrganization?: boolean;
90
- };
91
- headers?: Headers;
92
- }) => Promise<Organization>;
93
-
94
- /**
95
- * List all organizations the user is a member of.
96
- */
97
- listOrganizations: (params: { headers?: Headers }) => Promise<Organization[]>;
98
-
99
- /**
100
- * Get full organization details including members.
101
- *
102
- * By default uses the active organization. Pass `organizationId` or
103
- * `organizationSlug` to get a specific organization.
104
- */
105
- getFullOrganization: (params: {
106
- query?: {
107
- organizationId?: string;
108
- organizationSlug?: string;
109
- membersLimit?: number;
110
- };
111
- headers?: Headers;
112
- }) => Promise<
113
- | (Organization & {
114
- members?: OrganizationMember[];
115
- })
116
- | null
117
- >;
118
-
119
- /**
120
- * Set the active organization for the current session.
121
- *
122
- * Pass `organizationId: null` to unset the active organization.
123
- */
124
- setActiveOrganization: (params: {
125
- body: { organizationId: string | null; organizationSlug?: string };
126
- headers?: Headers;
127
- }) => Promise<Organization | null>;
128
-
129
- /**
130
- * Update organization details.
131
- *
132
- * Requires appropriate permissions (typically owner or admin role).
133
- */
134
- updateOrganization: (params: {
135
- body: {
136
- organizationId?: string;
137
- data: {
138
- name?: string;
139
- slug?: string;
140
- logo?: string | null;
141
- metadata?: Record<string, unknown> | null;
142
- };
143
- };
144
- headers?: Headers;
145
- }) => Promise<Organization>;
146
-
147
- /**
148
- * Delete an organization.
149
- *
150
- * Requires owner role. All members, invitations, and organization data
151
- * will be removed.
152
- */
153
- deleteOrganization: (params: {
154
- body: { organizationId: string };
155
- headers?: Headers;
156
- }) => Promise<{ success: boolean }>;
157
-
158
- /**
159
- * Check if an organization slug is available.
160
- */
161
- checkOrganizationSlug: (params: {
162
- body: { slug: string };
163
- headers?: Headers;
164
- }) => Promise<{ status: boolean }>;
165
-
166
- // =========================================================================
167
- // Invitation Management
168
- // =========================================================================
169
-
170
- /**
171
- * Create an invitation to join an organization.
172
- *
173
- * Sends an invitation email to the specified address. The user can then
174
- * accept or reject the invitation.
175
- */
176
- createInvitation: (params: {
177
- body: {
178
- email: string;
179
- role: string | string[];
180
- organizationId?: string;
181
- resend?: boolean;
182
- teamId?: string;
183
- };
184
- headers?: Headers;
185
- }) => Promise<OrganizationInvitation>;
186
-
187
- /**
188
- * Get details of a specific invitation.
189
- */
190
- getInvitation: (params: {
191
- query: { id: string };
192
- headers?: Headers;
193
- }) => Promise<OrganizationInvitation | null>;
194
-
195
- /**
196
- * List all invitations for an organization.
197
- *
198
- * Defaults to the active organization if `organizationId` is not provided.
199
- */
200
- listInvitations: (params: {
201
- query?: { organizationId?: string };
202
- headers?: Headers;
203
- }) => Promise<OrganizationInvitation[]>;
204
-
205
- /**
206
- * List all pending invitations for the current user.
207
- *
208
- * On the server, you can pass `email` to query for a specific user's invitations.
209
- */
210
- listUserInvitations: (params: {
211
- query?: { email?: string };
212
- headers?: Headers;
213
- }) => Promise<OrganizationInvitation[]>;
214
-
215
- /**
216
- * Accept an invitation to join an organization.
217
- *
218
- * The user must be authenticated. After accepting, they become a member
219
- * of the organization with the role specified in the invitation.
220
- */
221
- acceptInvitation: (params: {
222
- body: { invitationId: string };
223
- headers?: Headers;
224
- }) => Promise<{ success: boolean; member?: OrganizationMember }>;
225
-
226
- /**
227
- * Reject an invitation to join an organization.
228
- */
229
- rejectInvitation: (params: {
230
- body: { invitationId: string };
231
- headers?: Headers;
232
- }) => Promise<{ success: boolean }>;
233
-
234
- /**
235
- * Cancel a pending invitation.
236
- *
237
- * Typically used by organization admins to revoke an invitation.
238
- */
239
- cancelInvitation: (params: {
240
- body: { invitationId: string };
241
- headers?: Headers;
242
- }) => Promise<{ success: boolean }>;
243
-
244
- // =========================================================================
245
- // Member Management
246
- // =========================================================================
247
-
248
- /**
249
- * List all members of an organization.
250
- *
251
- * Supports pagination, sorting, and filtering.
252
- */
253
- listMembers: (params: {
254
- query?: {
255
- organizationId?: string;
256
- limit?: number;
257
- offset?: number;
258
- sortBy?: string;
259
- sortDirection?: 'asc' | 'desc';
260
- filterField?: string;
261
- filterOperator?: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains';
262
- filterValue?: string;
263
- };
264
- headers?: Headers;
265
- }) => Promise<OrganizationMember[]>;
266
-
267
- /**
268
- * Add a member directly to an organization (server-only).
269
- *
270
- * Unlike invitations, this immediately adds the user as a member.
271
- * Typically used for admin scripts, migrations, or automated onboarding.
272
- */
273
- addMember: (params: {
274
- body: {
275
- userId?: string | null;
276
- role: string | string[];
277
- organizationId?: string;
278
- teamId?: string;
279
- };
280
- headers?: Headers;
281
- }) => Promise<OrganizationMember>;
282
-
283
- /**
284
- * Remove a member from an organization.
285
- */
286
- removeMember: (params: {
287
- body: { memberIdOrEmail: string; organizationId?: string };
288
- headers?: Headers;
289
- }) => Promise<{ success: boolean }>;
290
-
291
- /**
292
- * Update a member's role in an organization.
293
- */
294
- updateMemberRole: (params: {
295
- body: {
296
- memberId: string;
297
- role: string | string[];
298
- organizationId?: string;
299
- };
300
- headers?: Headers;
301
- }) => Promise<{ success: boolean }>;
302
-
303
- /**
304
- * Get the current user's member record in the active organization.
305
- */
306
- getActiveMember: (params: { headers?: Headers }) => Promise<OrganizationMember | null>;
307
-
308
- /**
309
- * Get the current user's role in the active organization.
310
- */
311
- getActiveMemberRole: (params: { headers?: Headers }) => Promise<{ role: string | null }>;
312
-
313
- /**
314
- * Leave an organization.
315
- *
316
- * The current user will be removed from the organization.
317
- */
318
- leaveOrganization: (params: {
319
- body: { organizationId: string };
320
- headers?: Headers;
321
- }) => Promise<{ success: boolean }>;
322
-
323
- // =========================================================================
324
- // Access Control / Permissions
325
- // =========================================================================
326
-
327
- /**
328
- * Check if the current user has specific permissions.
329
- *
330
- * Works with BetterAuth's access control system. The permissions object
331
- * maps resources to arrays of required actions.
332
- *
333
- * @example
334
- * ```typescript
335
- * const result = await auth.api.hasPermission({
336
- * body: { permissions: { project: ['create', 'update'] } },
337
- * headers,
338
- * });
339
- * ```
340
- */
341
- hasPermission: (params: {
342
- body: { permissions: Record<string, string[]> };
343
- headers?: Headers;
344
- }) => Promise<{ success: boolean }>;
345
- }
@@ -1,366 +0,0 @@
1
- /**
2
- * Auth React integration for @agentuity/auth.
3
- *
4
- * All React-specific code for auth.
5
- * Import from '@agentuity/auth/react' for React components and hooks.
6
- *
7
- * @module agentuity/react
8
- */
9
-
10
- import React, { useEffect, createContext, useContext, useState, useMemo } from 'react';
11
- import { createAuthClient as createBetterAuthClient } from 'better-auth/react';
12
- import { organizationClient, apiKeyClient } from 'better-auth/client/plugins';
13
- import { useAuth as useAgentuityReactAuth, useAnalytics } from '@agentuity/react';
14
- import type { BetterAuthClientPlugin } from 'better-auth/client';
15
-
16
- import type { AuthSession, AuthUser } from './types';
17
-
18
- // =============================================================================
19
- // Auth Client Factory
20
- // =============================================================================
21
-
22
- /**
23
- * Options for creating the auth client.
24
- *
25
- * @typeParam TPlugins - Array of BetterAuth client plugins for type inference
26
- */
27
- export interface AuthClientOptions<
28
- TPlugins extends BetterAuthClientPlugin[] = BetterAuthClientPlugin[],
29
- > {
30
- /**
31
- * Base URL for auth API requests.
32
- * Defaults to `window.location.origin` in browser environments.
33
- */
34
- baseURL?: string;
35
-
36
- /**
37
- * Base path for auth endpoints.
38
- * Defaults to '/api/auth' (Agentuity convention).
39
- */
40
- basePath?: string;
41
-
42
- /**
43
- * Skip default plugins (organizationClient, apiKeyClient).
44
- * Use this if you want full control over plugins.
45
- */
46
- skipDefaultPlugins?: boolean;
47
-
48
- /**
49
- * Additional plugins to include.
50
- * These are added after the default plugins (unless skipDefaultPlugins is true).
51
- *
52
- * Plugin types are inferred for full type safety.
53
- */
54
- plugins?: TPlugins;
55
- }
56
-
57
- /**
58
- * Get the default client plugins for auth.
59
- *
60
- * These mirror the server-side plugins:
61
- * - organizationClient: Multi-tenancy support
62
- * - apiKeyClient: Programmatic API key management
63
- *
64
- * Note: jwt() and bearer() are server-only plugins.
65
- */
66
- export function getDefaultClientPlugins() {
67
- return [organizationClient(), apiKeyClient()];
68
- }
69
-
70
- /**
71
- * Create a pre-configured Auth client.
72
- *
73
- * This factory provides sensible defaults for Agentuity projects:
74
- * - Uses `/api/auth` as the default base path
75
- * - Automatically uses `window.location.origin` as base URL in browsers
76
- * - Includes organization and API key plugins by default
77
- *
78
- * @example Basic usage (zero config)
79
- * ```typescript
80
- * import { createAuthClient } from '@agentuity/auth/react';
81
- *
82
- * export const authClient = createAuthClient();
83
- * export const { signIn, signUp, signOut, useSession, getSession } = authClient;
84
- * ```
85
- *
86
- * @example With custom base path
87
- * ```typescript
88
- * export const authClient = createAuthClient({
89
- * basePath: '/auth', // If mounted at /auth instead of /api/auth
90
- * });
91
- * ```
92
- *
93
- * @example With additional plugins
94
- * ```typescript
95
- * import { twoFactorClient } from 'better-auth/client/plugins';
96
- *
97
- * export const authClient = createAuthClient({
98
- * plugins: [twoFactorClient()],
99
- * });
100
- * ```
101
- *
102
- * @example With custom plugins only (no defaults)
103
- * ```typescript
104
- * import { organizationClient } from 'better-auth/client/plugins';
105
- *
106
- * export const authClient = createAuthClient({
107
- * skipDefaultPlugins: true,
108
- * plugins: [organizationClient()],
109
- * });
110
- * ```
111
- */
112
- export function createAuthClient<TPlugins extends BetterAuthClientPlugin[] = []>(
113
- options?: AuthClientOptions<TPlugins>
114
- ): ReturnType<typeof createBetterAuthClient<{ plugins: TPlugins }>> {
115
- const baseURL =
116
- options?.baseURL ?? (typeof window !== 'undefined' ? window.location.origin : '');
117
- const basePath = options?.basePath ?? '/api/auth';
118
-
119
- const defaultPlugins = options?.skipDefaultPlugins ? [] : getDefaultClientPlugins();
120
- const userPlugins = options?.plugins ?? [];
121
-
122
- // Merge default plugins with user plugins
123
- // We pass through the full options to preserve type inference
124
- // The return type preserves plugin type inference via the generic parameter
125
- return createBetterAuthClient({
126
- ...options,
127
- baseURL,
128
- basePath,
129
- plugins: [...defaultPlugins, ...userPlugins],
130
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
- }) as any;
132
- }
133
-
134
- /**
135
- * Type helper for the auth client return type.
136
- */
137
- export type AuthClient = ReturnType<typeof createAuthClient>;
138
-
139
- // =============================================================================
140
- // React Provider and Hooks
141
- // =============================================================================
142
-
143
- /**
144
- * Context value for Auth.
145
- */
146
- export interface AuthContextValue {
147
- /** The auth client instance */
148
- authClient: AuthClient;
149
- /** Current authenticated user, or null if not signed in */
150
- user: AuthUser | null;
151
- /** Current session object (if available) */
152
- session: AuthSession | null;
153
- /** Whether the auth state is still loading */
154
- isPending: boolean;
155
- /** Any error that occurred while fetching auth state */
156
- error: Error | null;
157
- /** Whether the user is authenticated */
158
- isAuthenticated: boolean;
159
- }
160
-
161
- const AuthContext = createContext<AuthContextValue | null>(null);
162
-
163
- export interface AuthProviderProps {
164
- /** React children to render */
165
- children: React.ReactNode;
166
-
167
- /**
168
- * The auth client instance created with createAuthClient().
169
- * Required for session management.
170
- */
171
- authClient: AuthClient;
172
-
173
- /**
174
- * Token refresh interval in milliseconds.
175
- *
176
- * **Default:** `3600000` (1 hour)
177
- *
178
- * Controls how frequently the auth state is refreshed by polling the session endpoint.
179
- * A longer interval reduces server load and API calls, but means auth state changes
180
- * (like session expiration or revocation) may not be detected for up to the interval duration.
181
- *
182
- * **Security Implications:**
183
- * - Longer intervals mean staler auth state: revoked sessions or permission changes
184
- * may not be detected until the next refresh cycle (up to the interval duration)
185
- * - Shorter intervals provide fresher state but increase server load and API calls
186
- * - Consider your security requirements when choosing an interval
187
- *
188
- * **Recommended Intervals:**
189
- * - `30000` - `60000` (30s - 1m): High-security applications requiring near-real-time
190
- * detection of session revocation or permission changes
191
- * - `300000` - `900000` (5m - 15m): Sensitive features (admin panels, financial operations)
192
- * where timely detection of auth changes is important
193
- * - `3600000` (1h): Typical applications where occasional staleness is acceptable
194
- *
195
- * **Override Example:**
196
- * ```tsx
197
- * // High-security: refresh every 30 seconds
198
- * <AuthProvider authClient={authClient} refreshInterval={30000}>
199
- *
200
- * // Sensitive features: refresh every 5 minutes
201
- * <AuthProvider authClient={authClient} refreshInterval={300000}>
202
- *
203
- * // Default: refresh every hour
204
- * <AuthProvider authClient={authClient}>
205
- * ```
206
- */
207
- refreshInterval?: number;
208
-
209
- /**
210
- * Override the token endpoint path.
211
- * Defaults to '/token' (relative to the auth client's basePath).
212
- * Set to `false` to disable token fetching entirely.
213
- *
214
- * @example Custom token endpoint
215
- * ```tsx
216
- * <AuthProvider authClient={authClient} tokenEndpoint="/api/custom/jwt">
217
- * ```
218
- *
219
- * @example Disable token fetching
220
- * ```tsx
221
- * <AuthProvider authClient={authClient} tokenEndpoint={false}>
222
- * ```
223
- */
224
- tokenEndpoint?: string | false;
225
- }
226
-
227
- /**
228
- * Auth provider component.
229
- *
230
- * This component integrates Auth with Agentuity's React context,
231
- * automatically injecting auth tokens into API calls via useAgent and useWebsocket.
232
- *
233
- * Must be a child of AgentuityProvider.
234
- *
235
- * @example
236
- * ```tsx
237
- * import { AgentuityProvider } from '@agentuity/react';
238
- * import { createAuthClient, AuthProvider } from '@agentuity/auth/react';
239
- *
240
- * const authClient = createAuthClient();
241
- *
242
- * <AgentuityProvider>
243
- * <AuthProvider authClient={authClient}>
244
- * <App />
245
- * </AuthProvider>
246
- * </AgentuityProvider>
247
- * ```
248
- */
249
- export function AuthProvider({
250
- children,
251
- authClient,
252
- refreshInterval = 3600000,
253
- tokenEndpoint = '/token',
254
- }: AuthProviderProps) {
255
- const { setAuthHeader, setAuthLoading } = useAgentuityReactAuth();
256
- const { identify } = useAnalytics();
257
- const [user, setUser] = useState<AuthUser | null>(null);
258
- const [session, setSession] = useState<AuthSession | null>(null);
259
- const [isPending, setIsPending] = useState(true);
260
- const [error, setError] = useState<Error | null>(null);
261
-
262
- useEffect(() => {
263
- if (!setAuthHeader || !setAuthLoading) return;
264
-
265
- const fetchAuthState = async () => {
266
- try {
267
- setAuthLoading(true);
268
- setIsPending(true);
269
- setError(null);
270
-
271
- // Use the auth client's getSession method
272
- const result = await authClient.getSession();
273
-
274
- if (result.data?.user) {
275
- const authUser = result.data.user as AuthUser;
276
- setUser(authUser);
277
- setSession((result.data.session as AuthSession) ?? null);
278
-
279
- // Identify user for analytics
280
- identify(authUser.id, {
281
- email: authUser.email || '',
282
- name: authUser.name || '',
283
- });
284
-
285
- // Get the JWT token for API calls (unless disabled)
286
- if (tokenEndpoint !== false) {
287
- try {
288
- const tokenResult = await authClient.$fetch(tokenEndpoint, { method: 'GET' });
289
- const tokenData = tokenResult.data as { token?: string } | undefined;
290
- if (tokenData?.token) {
291
- setAuthHeader(`Bearer ${tokenData.token}`);
292
- } else {
293
- setAuthHeader(null);
294
- }
295
- } catch {
296
- // Token endpoint might not exist, that's okay
297
- setAuthHeader(null);
298
- }
299
- } else {
300
- setAuthHeader(null);
301
- }
302
- } else {
303
- setUser(null);
304
- setSession(null);
305
- setAuthHeader(null);
306
- }
307
- } catch (err) {
308
- console.error('[AuthProvider] Failed to get auth state:', err);
309
- setError(err instanceof Error ? err : new Error('Failed to get auth state'));
310
- setUser(null);
311
- setSession(null);
312
- setAuthHeader(null);
313
- } finally {
314
- setAuthLoading(false);
315
- setIsPending(false);
316
- }
317
- };
318
-
319
- fetchAuthState();
320
-
321
- const interval = setInterval(fetchAuthState, refreshInterval);
322
- return () => clearInterval(interval);
323
- }, [authClient, refreshInterval, tokenEndpoint, setAuthHeader, setAuthLoading]);
324
-
325
- const contextValue = useMemo(
326
- () => ({
327
- authClient,
328
- user,
329
- session,
330
- isPending,
331
- error,
332
- isAuthenticated: !isPending && user !== null,
333
- }),
334
- [authClient, user, session, isPending, error]
335
- );
336
-
337
- return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
338
- }
339
-
340
- /**
341
- * Hook to access Auth state.
342
- *
343
- * This hook provides access to the current user and session.
344
- * Must be used within an AuthProvider.
345
- *
346
- * @example
347
- * ```tsx
348
- * import { useAuth } from '@agentuity/auth/react';
349
- *
350
- * function Profile() {
351
- * const { user, session, isPending, isAuthenticated } = useAuth();
352
- *
353
- * if (isPending) return <div>Loading...</div>;
354
- * if (!isAuthenticated) return <div>Not signed in</div>;
355
- *
356
- * return <div>Welcome, {user.name}!</div>;
357
- * }
358
- * ```
359
- */
360
- export function useAuth(): AuthContextValue {
361
- const context = useContext(AuthContext);
362
- if (!context) {
363
- throw new Error('useAuth must be used within an AuthProvider');
364
- }
365
- return context;
366
- }