@agentuity/auth 0.0.96

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,110 @@
1
+ /**
2
+ * Clerk server-side authentication middleware for Hono.
3
+ *
4
+ * @module clerk/server
5
+ */
6
+ import { createClerkClient, verifyToken } from '@clerk/backend';
7
+ /**
8
+ * Create Hono middleware for Clerk authentication.
9
+ *
10
+ * This middleware:
11
+ * - Extracts and validates JWT tokens from Authorization header
12
+ * - Returns 401 if token is missing or invalid
13
+ * - Exposes authenticated user via c.var.auth
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { createMiddleware } from '@agentuity/auth/clerk';
18
+ *
19
+ * router.get('/api/profile', createMiddleware(), async (c) => {
20
+ * const user = await c.var.auth.requireUser();
21
+ * return c.json({ email: user.email });
22
+ * });
23
+ * ```
24
+ */
25
+ export function createMiddleware(options = {}) {
26
+ const secretKey = options.secretKey || process.env.CLERK_SECRET_KEY;
27
+ const publishableKey = options.publishableKey ||
28
+ process.env.AGENTUITY_PUBLIC_CLERK_PUBLISHABLE_KEY ||
29
+ process.env.CLERK_PUBLISHABLE_KEY;
30
+ if (!secretKey) {
31
+ console.error('[Clerk Auth] CLERK_SECRET_KEY is not set. Add it to your .env file or pass secretKey option to createMiddleware()');
32
+ throw new Error('Clerk secret key is required (set CLERK_SECRET_KEY or pass secretKey option)');
33
+ }
34
+ if (!publishableKey) {
35
+ console.warn('[Clerk Auth] AGENTUITY_PUBLIC_CLERK_PUBLISHABLE_KEY is not set. Token validation may fail. Add it to your .env file.');
36
+ }
37
+ // Create Clerk client instance
38
+ const clerkClient = createClerkClient({ secretKey });
39
+ return async (c, next) => {
40
+ const authHeader = c.req.header('Authorization');
41
+ if (!authHeader) {
42
+ return c.json({ error: 'Unauthorized' }, 401);
43
+ }
44
+ try {
45
+ // Extract token from Bearer header
46
+ let token;
47
+ if (options.getToken) {
48
+ token = options.getToken(authHeader);
49
+ }
50
+ else {
51
+ // Validate Authorization scheme is Bearer
52
+ if (!authHeader.match(/^Bearer\s+/i)) {
53
+ return c.json({ error: 'Unauthorized' }, 401);
54
+ }
55
+ token = authHeader.replace(/^Bearer\s+/i, '');
56
+ }
57
+ // Ensure token is not empty
58
+ if (!token || token.trim().length === 0) {
59
+ return c.json({ error: 'Unauthorized' }, 401);
60
+ }
61
+ // Verify token with Clerk (delegates validation to provider)
62
+ const payload = (await verifyToken(token, {
63
+ secretKey,
64
+ }));
65
+ // Validate payload has required subject claim
66
+ if (!payload.sub || typeof payload.sub !== 'string') {
67
+ throw new Error('Invalid token: missing or invalid subject claim');
68
+ }
69
+ // Memoize user fetch to avoid multiple API calls
70
+ let cachedUser = null;
71
+ // Create auth object with Clerk user and payload types
72
+ const auth = {
73
+ async requireUser() {
74
+ if (cachedUser) {
75
+ return cachedUser;
76
+ }
77
+ const user = await clerkClient.users.getUser(payload.sub);
78
+ cachedUser = mapClerkUserToAgentuityUser(user);
79
+ return cachedUser;
80
+ },
81
+ async getToken() {
82
+ return token;
83
+ },
84
+ raw: payload,
85
+ };
86
+ c.set('auth', auth);
87
+ await next();
88
+ }
89
+ catch (error) {
90
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
91
+ const errorCode = error && typeof error === 'object' && 'code' in error && typeof error.code === 'string'
92
+ ? error.code
93
+ : 'CLERK_AUTH_ERROR';
94
+ console.error(`[Clerk Auth] Authentication failed: ${errorCode} - ${errorMessage}`);
95
+ return c.json({ error: 'Unauthorized' }, 401);
96
+ }
97
+ };
98
+ }
99
+ /**
100
+ * Map Clerk User to AgentuityAuthUser.
101
+ */
102
+ function mapClerkUserToAgentuityUser(clerkUser) {
103
+ return {
104
+ id: clerkUser.id,
105
+ name: `${clerkUser.firstName || ''} ${clerkUser.lastName || ''}`.trim() || undefined,
106
+ email: clerkUser.emailAddresses[0]?.emailAddress,
107
+ raw: clerkUser,
108
+ };
109
+ }
110
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/clerk/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @agentuity/auth - Authentication helpers for identity providers
3
+ *
4
+ * Currently supports Clerk, with more providers coming soon.
5
+ *
6
+ * @example Clerk Integration
7
+ * ```typescript
8
+ * // Client-side (React)
9
+ * import { AgentuityClerk } from '@agentuity/auth/clerk';
10
+ *
11
+ * // Server-side (Hono)
12
+ * import { createMiddleware } from '@agentuity/auth/clerk';
13
+ * ```
14
+ *
15
+ * @module @agentuity/auth
16
+ */
17
+ export type { AgentuityAuthUser, AgentuityAuth } from './types';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @agentuity/auth - Authentication helpers for identity providers
3
+ *
4
+ * Currently supports Clerk, with more providers coming soon.
5
+ *
6
+ * @example Clerk Integration
7
+ * ```typescript
8
+ * // Client-side (React)
9
+ * import { AgentuityClerk } from '@agentuity/auth/clerk';
10
+ *
11
+ * // Server-side (Hono)
12
+ * import { createMiddleware } from '@agentuity/auth/clerk';
13
+ * ```
14
+ *
15
+ * @module @agentuity/auth
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Core authentication types shared across all providers.
3
+ *
4
+ * @module types
5
+ */
6
+ /**
7
+ * Generic authenticated user interface.
8
+ * All auth providers return this structure with provider-specific data in `raw`.
9
+ */
10
+ export interface AgentuityAuthUser<T = unknown> {
11
+ /** Unique user identifier from the auth provider */
12
+ id: string;
13
+ /** User's full name */
14
+ name?: string;
15
+ /** Primary email address */
16
+ email?: string;
17
+ /** Raw provider-specific user object for advanced use cases */
18
+ raw: T;
19
+ }
20
+ /**
21
+ * Generic authentication interface exposed on Hono context.
22
+ * All auth middleware implementations provide this interface.
23
+ */
24
+ export interface AgentuityAuth<TUser = unknown, TRaw = unknown> {
25
+ /** Get the authenticated user, throws if not authenticated */
26
+ requireUser(): Promise<AgentuityAuthUser<TUser>>;
27
+ /** Get the raw JWT token */
28
+ getToken(): Promise<string | null>;
29
+ /** Raw provider-specific auth object (e.g., JWT payload, session) */
30
+ raw: TRaw;
31
+ }
32
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO;IAC7C,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IAEX,uBAAuB;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,+DAA+D;IAC/D,GAAG,EAAE,CAAC,CAAC;CACP;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,GAAG,OAAO,EAAE,IAAI,GAAG,OAAO;IAC7D,8DAA8D;IAC9D,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;IAEjD,4BAA4B;IAC5B,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAEnC,qEAAqE;IACrE,GAAG,EAAE,IAAI,CAAC;CACV"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Core authentication types shared across all providers.
3
+ *
4
+ * @module types
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@agentuity/auth",
3
+ "version": "0.0.96",
4
+ "type": "module",
5
+ "description": "Authentication helpers for popular identity providers",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/agentuity/sdk.git",
9
+ "directory": "packages/auth"
10
+ },
11
+ "license": "Apache-2.0",
12
+ "exports": {
13
+ ".": "./src/index.ts",
14
+ "./clerk": "./src/clerk/index.ts"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc --build",
18
+ "clean": "rm -rf dist .tsbuildinfo",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "peerDependencies": {
22
+ "react": "^18.0.0 || ^19.0.0",
23
+ "@clerk/clerk-react": "^5.0.0",
24
+ "@clerk/backend": "^1.0.0",
25
+ "@agentuity/react": "0.0.96",
26
+ "@agentuity/runtime": "0.0.96",
27
+ "hono": "^4.0.0"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "@clerk/clerk-react": {
31
+ "optional": true
32
+ },
33
+ "@clerk/backend": {
34
+ "optional": true
35
+ }
36
+ },
37
+ "devDependencies": {
38
+ "@agentuity/react": "0.0.96",
39
+ "@agentuity/runtime": "0.0.96",
40
+ "@agentuity/test-utils": "0.0.96",
41
+ "@clerk/clerk-react": "^5.46.1",
42
+ "@clerk/backend": "^2.27.1",
43
+ "@types/react": "^18.3.18",
44
+ "hono": "^4.6.14",
45
+ "react": "^18.3.1",
46
+ "typescript": "^5.8.3"
47
+ }
48
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Clerk client-side authentication provider for React.
3
+ *
4
+ * @module clerk/client
5
+ */
6
+
7
+ import React, { useEffect } from 'react';
8
+ import type { useAuth as ClerkUseAuth } from '@clerk/clerk-react';
9
+ import { useAgentuity } from '@agentuity/react';
10
+
11
+ type UseAuth = typeof ClerkUseAuth;
12
+
13
+ export interface AgentuityClerkProps {
14
+ /** React children to render */
15
+ children: React.ReactNode;
16
+
17
+ /** Clerk's useAuth hook from @clerk/clerk-react */
18
+ useAuth: UseAuth;
19
+
20
+ /** Token refresh interval in milliseconds (default: 60000 = 1 minute) */
21
+ refreshInterval?: number;
22
+ }
23
+
24
+ /**
25
+ * Agentuity authentication provider for Clerk.
26
+ *
27
+ * This component integrates Clerk authentication with Agentuity's context,
28
+ * automatically injecting auth tokens into API calls via useAPI and useWebsocket.
29
+ *
30
+ * Must be a child of both ClerkProvider and AgentuityProvider.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * import { ClerkProvider, useAuth } from '@clerk/clerk-react';
35
+ * import { AgentuityProvider } from '@agentuity/react';
36
+ * import { AgentuityClerk } from '@agentuity/auth/clerk';
37
+ *
38
+ * <ClerkProvider publishableKey={key}>
39
+ * <AgentuityProvider>
40
+ * <AgentuityClerk useAuth={useAuth}>
41
+ * <App />
42
+ * </AgentuityClerk>
43
+ * </AgentuityProvider>
44
+ * </ClerkProvider>
45
+ * ```
46
+ */
47
+ export function AgentuityClerk({
48
+ children,
49
+ useAuth,
50
+ refreshInterval = 60000,
51
+ }: AgentuityClerkProps) {
52
+ const { getToken, isLoaded } = useAuth();
53
+ const { setAuthHeader, setAuthLoading } = useAgentuity();
54
+
55
+ // Fetch and update token in AgentuityContext
56
+ useEffect(() => {
57
+ if (!isLoaded || !setAuthHeader || !setAuthLoading) {
58
+ if (setAuthLoading) {
59
+ setAuthLoading(true);
60
+ }
61
+ return;
62
+ }
63
+
64
+ const fetchToken = async () => {
65
+ try {
66
+ setAuthLoading(true);
67
+ const token = await getToken();
68
+ setAuthHeader(token ? `Bearer ${token}` : null);
69
+ } catch (error) {
70
+ console.error('Failed to get Clerk token:', error);
71
+ setAuthHeader(null);
72
+ } finally {
73
+ setAuthLoading(false);
74
+ }
75
+ };
76
+
77
+ fetchToken();
78
+
79
+ // Clerk handles token expiry internally, we refresh periodically
80
+ const interval = setInterval(fetchToken, refreshInterval);
81
+ return () => clearInterval(interval);
82
+ }, [getToken, isLoaded, setAuthHeader, setAuthLoading, refreshInterval]);
83
+
84
+ // Render children directly - auth header is now in AgentuityContext
85
+ return <>{children}</>;
86
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Clerk authentication provider for Agentuity.
3
+ *
4
+ * Provides both client-side (React) and server-side (Hono) authentication.
5
+ *
6
+ * @example Client-side
7
+ * ```tsx
8
+ * import { ClerkProvider, useAuth } from '@clerk/clerk-react';
9
+ * import { AgentuityProvider } from '@agentuity/react';
10
+ * import { AgentuityClerk } from '@agentuity/auth/clerk';
11
+ *
12
+ * <ClerkProvider publishableKey={key}>
13
+ * <AgentuityProvider>
14
+ * <AgentuityClerk useAuth={useAuth}>
15
+ * <App />
16
+ * </AgentuityClerk>
17
+ * </AgentuityProvider>
18
+ * </ClerkProvider>
19
+ * ```
20
+ *
21
+ * @example Server-side
22
+ * ```typescript
23
+ * import { createMiddleware } from '@agentuity/auth/clerk';
24
+ *
25
+ * router.get('/api/profile', createMiddleware(), async (c) => {
26
+ * const user = await c.var.auth.requireUser();
27
+ * return c.json({ email: user.email });
28
+ * });
29
+ * ```
30
+ *
31
+ * @module clerk
32
+ */
33
+
34
+ export { AgentuityClerk } from './client';
35
+ export type { AgentuityClerkProps } from './client';
36
+ export { createMiddleware } from './server';
37
+ export type { ClerkMiddlewareOptions, ClerkJWTPayload } from './server';
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Clerk server-side authentication middleware for Hono.
3
+ *
4
+ * @module clerk/server
5
+ */
6
+
7
+ import type { MiddlewareHandler } from 'hono';
8
+ import { createClerkClient, verifyToken } from '@clerk/backend';
9
+ import type { User } from '@clerk/backend';
10
+ import type { AgentuityAuth, AgentuityAuthUser } from '../types';
11
+
12
+ /**
13
+ * Clerk JWT payload structure.
14
+ */
15
+ export interface ClerkJWTPayload {
16
+ /** Subject (user ID) */
17
+ sub: string;
18
+ /** Additional claims */
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ /**
23
+ * Options for Clerk middleware.
24
+ */
25
+ export interface ClerkMiddlewareOptions {
26
+ /** Clerk secret key (defaults to process.env.CLERK_SECRET_KEY) */
27
+ secretKey?: string;
28
+
29
+ /** Custom token extractor function */
30
+ getToken?: (authHeader: string) => string;
31
+
32
+ /** Clerk publishable key for token verification */
33
+ publishableKey?: string;
34
+ }
35
+
36
+ /**
37
+ * Create Hono middleware for Clerk authentication.
38
+ *
39
+ * This middleware:
40
+ * - Extracts and validates JWT tokens from Authorization header
41
+ * - Returns 401 if token is missing or invalid
42
+ * - Exposes authenticated user via c.var.auth
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * import { createMiddleware } from '@agentuity/auth/clerk';
47
+ *
48
+ * router.get('/api/profile', createMiddleware(), async (c) => {
49
+ * const user = await c.var.auth.requireUser();
50
+ * return c.json({ email: user.email });
51
+ * });
52
+ * ```
53
+ */
54
+ export function createMiddleware(options: ClerkMiddlewareOptions = {}): MiddlewareHandler {
55
+ const secretKey = options.secretKey || process.env.CLERK_SECRET_KEY;
56
+ const publishableKey =
57
+ options.publishableKey ||
58
+ process.env.AGENTUITY_PUBLIC_CLERK_PUBLISHABLE_KEY ||
59
+ process.env.CLERK_PUBLISHABLE_KEY;
60
+
61
+ if (!secretKey) {
62
+ console.error(
63
+ '[Clerk Auth] CLERK_SECRET_KEY is not set. Add it to your .env file or pass secretKey option to createMiddleware()'
64
+ );
65
+ throw new Error(
66
+ 'Clerk secret key is required (set CLERK_SECRET_KEY or pass secretKey option)'
67
+ );
68
+ }
69
+
70
+ if (!publishableKey) {
71
+ console.warn(
72
+ '[Clerk Auth] AGENTUITY_PUBLIC_CLERK_PUBLISHABLE_KEY is not set. Token validation may fail. Add it to your .env file.'
73
+ );
74
+ }
75
+
76
+ // Create Clerk client instance
77
+ const clerkClient = createClerkClient({ secretKey });
78
+
79
+ return async (c, next) => {
80
+ const authHeader = c.req.header('Authorization');
81
+
82
+ if (!authHeader) {
83
+ return c.json({ error: 'Unauthorized' }, 401);
84
+ }
85
+
86
+ try {
87
+ // Extract token from Bearer header
88
+ let token: string;
89
+ if (options.getToken) {
90
+ token = options.getToken(authHeader);
91
+ } else {
92
+ // Validate Authorization scheme is Bearer
93
+ if (!authHeader.match(/^Bearer\s+/i)) {
94
+ return c.json({ error: 'Unauthorized' }, 401);
95
+ }
96
+ token = authHeader.replace(/^Bearer\s+/i, '');
97
+ }
98
+
99
+ // Ensure token is not empty
100
+ if (!token || token.trim().length === 0) {
101
+ return c.json({ error: 'Unauthorized' }, 401);
102
+ }
103
+
104
+ // Verify token with Clerk (delegates validation to provider)
105
+ const payload = (await verifyToken(token, {
106
+ secretKey,
107
+ })) as ClerkJWTPayload;
108
+
109
+ // Validate payload has required subject claim
110
+ if (!payload.sub || typeof payload.sub !== 'string') {
111
+ throw new Error('Invalid token: missing or invalid subject claim');
112
+ }
113
+
114
+ // Memoize user fetch to avoid multiple API calls
115
+ let cachedUser: AgentuityAuthUser<User> | null = null;
116
+
117
+ // Create auth object with Clerk user and payload types
118
+ const auth: AgentuityAuth<User, ClerkJWTPayload> = {
119
+ async requireUser() {
120
+ if (cachedUser) {
121
+ return cachedUser;
122
+ }
123
+ const user = await clerkClient.users.getUser(payload.sub);
124
+ cachedUser = mapClerkUserToAgentuityUser(user);
125
+ return cachedUser;
126
+ },
127
+
128
+ async getToken() {
129
+ return token;
130
+ },
131
+
132
+ raw: payload,
133
+ };
134
+
135
+ c.set('auth', auth);
136
+ await next();
137
+ } catch (error) {
138
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
139
+ const errorCode =
140
+ error && typeof error === 'object' && 'code' in error && typeof error.code === 'string'
141
+ ? error.code
142
+ : 'CLERK_AUTH_ERROR';
143
+ console.error(`[Clerk Auth] Authentication failed: ${errorCode} - ${errorMessage}`);
144
+ return c.json({ error: 'Unauthorized' }, 401);
145
+ }
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Map Clerk User to AgentuityAuthUser.
151
+ */
152
+ function mapClerkUserToAgentuityUser(clerkUser: User): AgentuityAuthUser<User> {
153
+ return {
154
+ id: clerkUser.id,
155
+ name: `${clerkUser.firstName || ''} ${clerkUser.lastName || ''}`.trim() || undefined,
156
+ email: clerkUser.emailAddresses[0]?.emailAddress,
157
+ raw: clerkUser,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Augment Hono's context types to include auth.
163
+ */
164
+ declare module 'hono' {
165
+ interface ContextVariableMap {
166
+ auth: AgentuityAuth<User, ClerkJWTPayload>;
167
+ }
168
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @agentuity/auth - Authentication helpers for identity providers
3
+ *
4
+ * Currently supports Clerk, with more providers coming soon.
5
+ *
6
+ * @example Clerk Integration
7
+ * ```typescript
8
+ * // Client-side (React)
9
+ * import { AgentuityClerk } from '@agentuity/auth/clerk';
10
+ *
11
+ * // Server-side (Hono)
12
+ * import { createMiddleware } from '@agentuity/auth/clerk';
13
+ * ```
14
+ *
15
+ * @module @agentuity/auth
16
+ */
17
+
18
+ export type { AgentuityAuthUser, AgentuityAuth } from './types';
package/src/types.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Core authentication types shared across all providers.
3
+ *
4
+ * @module types
5
+ */
6
+
7
+ /**
8
+ * Generic authenticated user interface.
9
+ * All auth providers return this structure with provider-specific data in `raw`.
10
+ */
11
+ export interface AgentuityAuthUser<T = unknown> {
12
+ /** Unique user identifier from the auth provider */
13
+ id: string;
14
+
15
+ /** User's full name */
16
+ name?: string;
17
+
18
+ /** Primary email address */
19
+ email?: string;
20
+
21
+ /** Raw provider-specific user object for advanced use cases */
22
+ raw: T;
23
+ }
24
+
25
+ /**
26
+ * Generic authentication interface exposed on Hono context.
27
+ * All auth middleware implementations provide this interface.
28
+ */
29
+ export interface AgentuityAuth<TUser = unknown, TRaw = unknown> {
30
+ /** Get the authenticated user, throws if not authenticated */
31
+ requireUser(): Promise<AgentuityAuthUser<TUser>>;
32
+
33
+ /** Get the raw JWT token */
34
+ getToken(): Promise<string | null>;
35
+
36
+ /** Raw provider-specific auth object (e.g., JWT payload, session) */
37
+ raw: TRaw;
38
+ }
@@ -0,0 +1,21 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, test, expect } from 'bun:test';
3
+ import { AgentuityClerk } from '../src/clerk/client';
4
+
5
+ describe('AgentuityClerk', () => {
6
+ test('exports AgentuityClerk component', () => {
7
+ expect(AgentuityClerk).toBeDefined();
8
+ expect(typeof AgentuityClerk).toBe('function');
9
+ });
10
+
11
+ test('component props interface is correct', () => {
12
+ // Type test - will fail at compile time if interface changes
13
+ const validProps = {
14
+ children: null,
15
+ useAuth: (() => ({ getToken: async () => null, isLoaded: true })) as any,
16
+ refreshInterval: 60000,
17
+ };
18
+
19
+ expect(validProps).toBeDefined();
20
+ });
21
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test';
2
+ import { Hono } from 'hono';
3
+ import { createMiddleware } from '../src/clerk/server';
4
+
5
+ describe('Clerk server middleware', () => {
6
+ beforeEach(() => {
7
+ process.env.CLERK_SECRET_KEY = 'sk_test_secret';
8
+ });
9
+
10
+ test('returns 401 when Authorization header is missing', async () => {
11
+ const app = new Hono();
12
+ app.use('/protected', createMiddleware());
13
+ app.get('/protected', (c) => c.json({ success: true }));
14
+
15
+ const res = await app.request('/protected', {
16
+ method: 'GET',
17
+ });
18
+
19
+ expect(res.status).toBe(401);
20
+ const body = await res.json();
21
+ expect(body).toEqual({ error: 'Unauthorized' });
22
+ });
23
+
24
+ test('throws error when CLERK_SECRET_KEY is missing', () => {
25
+ delete process.env.CLERK_SECRET_KEY;
26
+
27
+ expect(() => createMiddleware()).toThrow('Clerk secret key is required');
28
+ });
29
+
30
+ test('creates middleware function', () => {
31
+ const middleware = createMiddleware();
32
+ expect(typeof middleware).toBe('function');
33
+ });
34
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "jsx": "react-jsx"
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": ["test/**/*"],
11
+ "references": [{ "path": "../react" }, { "path": "../runtime" }]
12
+ }