@agentuity/auth 0.1.8 → 0.1.10
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/agentuity/config.d.ts +36 -26
- package/dist/agentuity/config.d.ts.map +1 -1
- package/dist/agentuity/config.js +4 -2
- package/dist/agentuity/config.js.map +1 -1
- package/package.json +28 -8
- package/AGENTS.md +0 -117
- package/src/agentuity/config.ts +0 -401
- package/src/agentuity/plugins/api-key.ts +0 -158
- package/src/agentuity/plugins/index.ts +0 -35
- package/src/agentuity/plugins/jwt.ts +0 -30
- package/src/agentuity/plugins/organization.ts +0 -345
- package/src/agentuity/react.tsx +0 -366
- package/src/agentuity/server.ts +0 -734
- package/src/agentuity/types.ts +0 -201
- package/src/index.ts +0 -86
- package/src/schema.ts +0 -270
- package/src/types.ts +0 -30
- package/test/agentuity/config.test.ts +0 -621
- package/test/agentuity/server.test.ts +0 -537
- package/test/schema.test.ts +0 -147
- package/tsconfig.json +0 -13
- package/tsconfig.test.json +0 -11
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -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
|
-
}
|
package/src/agentuity/react.tsx
DELETED
|
@@ -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
|
-
}
|