1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct EnhancedSdkConfig {
12 pub base_url: String,
14 pub version: String,
16 pub typescript: bool,
18 pub include_rbac: bool,
20 pub include_conditional_permissions: bool,
22 pub include_audit: bool,
24 pub client_name: String,
26}
27
28impl Default for EnhancedSdkConfig {
29 fn default() -> Self {
30 Self {
31 base_url: "https://api.example.com".to_string(),
32 version: "v1".to_string(),
33 typescript: true,
34 include_rbac: true,
35 include_conditional_permissions: true,
36 include_audit: true,
37 client_name: "AuthFrameworkClient".to_string(),
38 }
39 }
40}
41
42pub struct JsSdkGenerator {
44 config: EnhancedSdkConfig,
45}
46
47impl JsSdkGenerator {
48 pub fn new(config: EnhancedSdkConfig) -> Self {
50 Self { config }
51 }
52
53 pub fn generate_sdk(&self) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
55 let mut files = HashMap::new();
56
57 files.insert("client.ts".to_string(), self.generate_base_client()?);
59
60 if self.config.typescript {
62 files.insert("types.ts".to_string(), self.generate_types()?);
63 }
64
65 if self.config.include_rbac {
67 files.insert("rbac.ts".to_string(), self.generate_rbac_module()?);
68 }
69
70 if self.config.include_conditional_permissions {
72 files.insert(
73 "conditional.ts".to_string(),
74 self.generate_conditional_module()?,
75 );
76 }
77
78 if self.config.include_audit {
80 files.insert("audit.ts".to_string(), self.generate_audit_module()?);
81 }
82
83 files.insert("utils.ts".to_string(), self.generate_utils()?);
85
86 files.insert("index.ts".to_string(), self.generate_index()?);
88
89 files.insert("package.json".to_string(), self.generate_package_json()?);
91
92 files.insert("README.md".to_string(), self.generate_readme()?);
94
95 Ok(files)
96 }
97
98 fn generate_base_client(&self) -> Result<String, Box<dyn std::error::Error>> {
100 let client_code = format!(
101 r#"
102/**
103 * Enhanced {} with RBAC Support
104 * Generated by AuthFramework SDK Generator
105 */
106
107export interface ClientConfig {{
108 baseUrl: string;
109 apiKey?: string;
110 accessToken?: string;
111 timeout?: number;
112 retryAttempts?: number;
113}}
114
115export interface ApiResponse<T> {{
116 success: boolean;
117 data?: T;
118 error?: string;
119 message?: string;
120}}
121
122export class HttpError extends Error {{
123 constructor(
124 public status: number,
125 public statusText: string,
126 public body?: any
127 ) {{
128 super(`HTTP ${{status}}: ${{statusText}}`);
129 this.name = 'HttpError';
130 }}
131}}
132
133export class {} {{
134 private baseUrl: string;
135 private headers: Record<string, string> = {{}};
136 private timeout: number;
137 private retryAttempts: number;
138
139 constructor(config: ClientConfig) {{
140 this.baseUrl = config.baseUrl.replace(/\/$/, '');
141 this.timeout = config.timeout || 30000;
142 this.retryAttempts = config.retryAttempts || 3;
143
144 if (config.apiKey) {{
145 this.headers['X-API-Key'] = config.apiKey;
146 }}
147
148 if (config.accessToken) {{
149 this.headers['Authorization'] = `Bearer ${{config.accessToken}}`;
150 }}
151
152 this.headers['Content-Type'] = 'application/json';
153 }}
154
155 /**
156 * Set authentication token
157 */
158 setAccessToken(token: string): void {{
159 this.headers['Authorization'] = `Bearer ${{token}}`;
160 }}
161
162 /**
163 * Clear authentication token
164 */
165 clearAccessToken(): void {{
166 delete this.headers['Authorization'];
167 }}
168
169 /**
170 * Set API key
171 */
172 setApiKey(apiKey: string): void {{
173 this.headers['X-API-Key'] = apiKey;
174 }}
175
176 /**
177 * Make HTTP request with retry logic
178 */
179 private async makeRequest<T>(
180 method: string,
181 path: string,
182 body?: any,
183 headers?: Record<string, string>
184 ): Promise<ApiResponse<T>> {{
185 const url = `${{this.baseUrl}}${{path}}`;
186 const requestHeaders = {{ ...this.headers, ...headers }};
187
188 let lastError: Error | null = null;
189
190 for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {{
191 try {{
192 const controller = new AbortController();
193 const timeoutId = setTimeout(() => controller.abort(), this.timeout);
194
195 const response = await fetch(url, {{
196 method,
197 headers: requestHeaders,
198 body: body ? JSON.stringify(body) : undefined,
199 signal: controller.signal,
200 }});
201
202 clearTimeout(timeoutId);
203
204 const responseData = await response.json();
205
206 if (!response.ok) {{
207 throw new HttpError(response.status, response.statusText, responseData);
208 }}
209
210 return responseData as ApiResponse<T>;
211 }} catch (error) {{
212 lastError = error as Error;
213
214 // Don't retry on client errors (4xx)
215 if (error instanceof HttpError && error.status >= 400 && error.status < 500) {{
216 throw error;
217 }}
218
219 // Wait before retry (exponential backoff)
220 if (attempt < this.retryAttempts) {{
221 await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
222 }}
223 }}
224 }}
225
226 throw lastError || new Error('Request failed after all retries');
227 }}
228
229 /**
230 * GET request
231 */
232 async get<T>(path: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
233 return this.makeRequest<T>('GET', path, undefined, headers);
234 }}
235
236 /**
237 * POST request
238 */
239 async post<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
240 return this.makeRequest<T>('POST', path, body, headers);
241 }}
242
243 /**
244 * PUT request
245 */
246 async put<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
247 return this.makeRequest<T>('PUT', path, body, headers);
248 }}
249
250 /**
251 * DELETE request
252 */
253 async delete<T>(path: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
254 return this.makeRequest<T>('DELETE', path, undefined, headers);
255 }}
256
257 /**
258 * PATCH request
259 */
260 async patch<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
261 return this.makeRequest<T>('PATCH', path, body, headers);
262 }}
263}}
264"#,
265 self.config.client_name, self.config.client_name
266 );
267
268 Ok(client_code)
269 }
270
271 fn generate_types(&self) -> Result<String, Box<dyn std::error::Error>> {
273 let types_code = r#"
274/**
275 * TypeScript type definitions for AuthFramework RBAC
276 */
277
278export interface Role {
279 id: string;
280 name: string;
281 description?: string;
282 parent_id?: string;
283 permissions: string[];
284 created_at: string;
285 updated_at: string;
286}
287
288export interface Permission {
289 id: string;
290 action: string;
291 resource: string;
292 conditions?: Record<string, string>;
293 created_at: string;
294}
295
296export interface UserRole {
297 role_id: string;
298 role_name: string;
299 assigned_at: string;
300 assigned_by?: string;
301 expires_at?: string;
302}
303
304export interface UserRolesResponse {
305 user_id: string;
306 roles: UserRole[];
307 effective_permissions: string[];
308}
309
310export interface CreateRoleRequest {
311 name: string;
312 description?: string;
313 parent_id?: string;
314 permissions?: string[];
315}
316
317export interface UpdateRoleRequest {
318 name?: string;
319 description?: string;
320 parent_id?: string;
321}
322
323export interface AssignRoleRequest {
324 role_id: string;
325 expires_at?: string;
326 reason?: string;
327}
328
329export interface BulkAssignRequest {
330 assignments: BulkAssignment[];
331}
332
333export interface BulkAssignment {
334 user_id: string;
335 role_id: string;
336 expires_at?: string;
337}
338
339export interface ElevateRoleRequest {
340 target_role: string;
341 duration_minutes?: number;
342 justification: string;
343}
344
345export interface PermissionCheckRequest {
346 action: string;
347 resource: string;
348 context?: Record<string, string>;
349}
350
351export interface PermissionCheckResponse {
352 granted: boolean;
353 reason: string;
354 required_roles: string[];
355 missing_permissions: string[];
356}
357
358export interface AuditEntry {
359 id: string;
360 user_id?: string;
361 action: string;
362 resource?: string;
363 result: string;
364 context: Record<string, string>;
365 timestamp: string;
366}
367
368export interface AuditLogResponse {
369 entries: AuditEntry[];
370 total_count: number;
371 page: number;
372 per_page: number;
373}
374
375export interface AuditQuery {
376 user_id?: string;
377 action?: string;
378 resource?: string;
379 start_time?: string;
380 end_time?: string;
381 page?: number;
382 per_page?: number;
383}
384
385export interface ConditionalContext {
386 time_of_day?: 'business_hours' | 'after_hours' | 'weekend' | 'holiday';
387 day_type?: 'weekday' | 'weekend' | 'holiday';
388 device_type?: 'desktop' | 'mobile' | 'tablet' | 'unknown';
389 connection_type?: 'direct' | 'vpn' | 'proxy' | 'tor' | 'corporate' | 'unknown';
390 security_level?: 'low' | 'medium' | 'high' | 'critical';
391 risk_score?: number;
392 ip_address?: string;
393 user_agent?: string;
394 custom_attributes?: Record<string, string>;
395}
396
397export interface RoleListQuery {
398 page?: number;
399 per_page?: number;
400 parent_id?: string;
401 include_permissions?: boolean;
402}
403
404export type TimeOfDay = 'business_hours' | 'after_hours' | 'weekend' | 'holiday';
405export type DayType = 'weekday' | 'weekend' | 'holiday';
406export type DeviceType = 'desktop' | 'mobile' | 'tablet' | 'unknown';
407export type ConnectionType = 'direct' | 'vpn' | 'proxy' | 'tor' | 'corporate' | 'unknown';
408export type SecurityLevel = 'low' | 'medium' | 'high' | 'critical';
409"#;
410
411 Ok(types_code.to_string())
412 }
413
414 fn generate_rbac_module(&self) -> Result<String, Box<dyn std::error::Error>> {
416 let rbac_code = format!(
417 r#"
418/**
419 * RBAC (Role-Based Access Control) Module
420 * Provides comprehensive role and permission management
421 */
422
423import {{ {} }} from './client';
424import {{
425 Role, CreateRoleRequest, UpdateRoleRequest, UserRolesResponse,
426 AssignRoleRequest, BulkAssignRequest, ElevateRoleRequest,
427 PermissionCheckRequest, PermissionCheckResponse, RoleListQuery,
428 ApiResponse
429}} from './types';
430
431export class RbacManager {{
432 constructor(private client: {}) {{}}
433
434 // ============================================================================
435 // ROLE MANAGEMENT
436 // ============================================================================
437
438 /**
439 * Create a new role
440 */
441 async createRole(request: CreateRoleRequest): Promise<ApiResponse<Role>> {{
442 return this.client.post<Role>('/{}/rbac/roles', request);
443 }}
444
445 /**
446 * Get role by ID
447 */
448 async getRole(roleId: string): Promise<ApiResponse<Role>> {{
449 return this.client.get<Role>(`/{}/rbac/roles/${{roleId}}`);
450 }}
451
452 /**
453 * List all roles with pagination
454 */
455 async listRoles(query?: RoleListQuery): Promise<ApiResponse<Role[]>> {{
456 const params = new URLSearchParams();
457 if (query?.page) params.append('page', query.page.toString());
458 if (query?.per_page) params.append('per_page', query.per_page.toString());
459 if (query?.parent_id) params.append('parent_id', query.parent_id);
460 if (query?.include_permissions) params.append('include_permissions', query.include_permissions.toString());
461
462 const queryString = params.toString();
463 const path = queryString ? `/{}/rbac/roles?${{queryString}}` : '/{}/rbac/roles';
464
465 return this.client.get<Role[]>(path);
466 }}
467
468 /**
469 * Update an existing role
470 */
471 async updateRole(roleId: string, request: UpdateRoleRequest): Promise<ApiResponse<Role>> {{
472 return this.client.put<Role>(`/{}/rbac/roles/${{roleId}}`, request);
473 }}
474
475 /**
476 * Delete a role
477 */
478 async deleteRole(roleId: string): Promise<ApiResponse<void>> {{
479 return this.client.delete<void>(`/{}/rbac/roles/${{roleId}}`);
480 }}
481
482 // ============================================================================
483 // USER ROLE ASSIGNMENTS
484 // ============================================================================
485
486 /**
487 * Assign role to user
488 */
489 async assignUserRole(userId: string, request: AssignRoleRequest): Promise<ApiResponse<void>> {{
490 return this.client.post<void>(`/{}/rbac/users/${{userId}}/roles`, request);
491 }}
492
493 /**
494 * Revoke role from user
495 */
496 async revokeUserRole(userId: string, roleId: string): Promise<ApiResponse<void>> {{
497 return this.client.delete<void>(`/{}/rbac/users/${{userId}}/roles/${{roleId}}`);
498 }}
499
500 /**
501 * Get user's roles and effective permissions
502 */
503 async getUserRoles(userId: string): Promise<ApiResponse<UserRolesResponse>> {{
504 return this.client.get<UserRolesResponse>(`/{}/rbac/users/${{userId}}/roles`);
505 }}
506
507 /**
508 * Bulk assign roles to multiple users
509 */
510 async bulkAssignRoles(request: BulkAssignRequest): Promise<ApiResponse<void>> {{
511 return this.client.post<void>('/{}/rbac/bulk/assign', request);
512 }}
513
514 // ============================================================================
515 // PERMISSION CHECKING
516 // ============================================================================
517
518 /**
519 * Check if current user has permission
520 */
521 async checkPermission(request: PermissionCheckRequest): Promise<ApiResponse<PermissionCheckResponse>> {{
522 return this.client.post<PermissionCheckResponse>('/{}/rbac/check-permission', request);
523 }}
524
525 /**
526 * Quick permission check (returns boolean)
527 */
528 async hasPermission(action: string, resource: string, context?: Record<string, string>): Promise<boolean> {{
529 try {{
530 const response = await this.checkPermission({{ action, resource, context }});
531 return response.data?.granted || false;
532 }} catch (error) {{
533 console.error('Permission check failed:', error);
534 return false;
535 }}
536 }}
537
538 /**
539 * Request role elevation
540 */
541 async elevateRole(request: ElevateRoleRequest): Promise<ApiResponse<void>> {{
542 return this.client.post<void>('/{}/rbac/elevate', request);
543 }}
544
545 // ============================================================================
546 // CONVENIENCE METHODS
547 // ============================================================================
548
549 /**
550 * Check if user has any of the specified roles
551 */
552 async userHasAnyRole(userId: string, roleNames: string[]): Promise<boolean> {{
553 try {{
554 const response = await this.getUserRoles(userId);
555 if (!response.data) return false;
556
557 const userRoleNames = response.data.roles.map(r => r.role_name);
558 return roleNames.some(role => userRoleNames.includes(role));
559 }} catch (error) {{
560 console.error('Role check failed:', error);
561 return false;
562 }}
563 }}
564
565 /**
566 * Check if user has all of the specified roles
567 */
568 async userHasAllRoles(userId: string, roleNames: string[]): Promise<boolean> {{
569 try {{
570 const response = await this.getUserRoles(userId);
571 if (!response.data) return false;
572
573 const userRoleNames = response.data.roles.map(r => r.role_name);
574 return roleNames.every(role => userRoleNames.includes(role));
575 }} catch (error) {{
576 console.error('Role check failed:', error);
577 return false;
578 }}
579 }}
580
581 /**
582 * Get role hierarchy (parent-child relationships)
583 */
584 async getRoleHierarchy(): Promise<Record<string, string[]>> {{
585 try {{
586 const response = await this.listRoles({{ include_permissions: false }});
587 if (!response.data) return {{}};
588
589 const hierarchy: Record<string, string[]> = {{}};
590
591 for (const role of response.data) {{
592 if (role.parent_id) {{
593 if (!hierarchy[role.parent_id]) {{
594 hierarchy[role.parent_id] = [];
595 }}
596 hierarchy[role.parent_id].push(role.id);
597 }}
598 }}
599
600 return hierarchy;
601 }} catch (error) {{
602 console.error('Failed to build role hierarchy:', error);
603 return {{}};
604 }}
605 }}
606
607 /**
608 * Get all child roles for a given parent role
609 */
610 async getChildRoles(parentRoleId: string): Promise<Role[]> {{
611 try {{
612 const response = await this.listRoles({{ parent_id: parentRoleId }});
613 return response.data || [];
614 }} catch (error) {{
615 console.error('Failed to get child roles:', error);
616 return [];
617 }}
618 }}
619}}
620"#,
621 self.config.client_name,
622 self.config.client_name,
623 self.config.version,
624 self.config.version,
625 self.config.version,
626 self.config.version,
627 self.config.version,
628 self.config.version,
629 self.config.version,
630 self.config.version,
631 self.config.version,
632 self.config.version,
633 self.config.version,
634 self.config.version
635 );
636
637 Ok(rbac_code)
638 }
639
640 fn generate_conditional_module(&self) -> Result<String, Box<dyn std::error::Error>> {
642 let conditional_code = r#"
643/**
644 * Conditional Permissions Module
645 * Provides context-aware permission checking based on time, location, device, etc.
646 */
647
648import { ConditionalContext, PermissionCheckRequest, PermissionCheckResponse, ApiResponse } from './types';
649
650export class ConditionalPermissions {
651 constructor(private rbacManager: any) {}
652
653 /**
654 * Build context from current browser/environment
655 */
656 buildContext(): ConditionalContext {
657 const context: ConditionalContext = {};
658
659 // Time-based context
660 const now = new Date();
661 const hour = now.getHours();
662 const dayOfWeek = now.getDay(); // 0 = Sunday, 6 = Saturday
663
664 if (hour >= 9 && hour < 17 && dayOfWeek >= 1 && dayOfWeek <= 5) {
665 context.time_of_day = 'business_hours';
666 context.day_type = 'weekday';
667 } else if (dayOfWeek === 0 || dayOfWeek === 6) {
668 context.time_of_day = 'weekend';
669 context.day_type = 'weekend';
670 } else {
671 context.time_of_day = 'after_hours';
672 context.day_type = 'weekday';
673 }
674
675 // Device detection
676 if (navigator.userAgent) {
677 const ua = navigator.userAgent.toLowerCase();
678 if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) {
679 context.device_type = 'mobile';
680 } else if (ua.includes('tablet') || ua.includes('ipad')) {
681 context.device_type = 'tablet';
682 } else {
683 context.device_type = 'desktop';
684 }
685
686 context.user_agent = navigator.userAgent;
687 }
688
689 // Connection type detection (basic)
690 if (navigator.connection && (navigator.connection as any).effectiveType) {
691 const connection = navigator.connection as any;
692 if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
693 context.connection_type = 'proxy'; // Might indicate proxy/VPN
694 } else {
695 context.connection_type = 'direct';
696 }
697 }
698
699 return context;
700 }
701
702 /**
703 * Check permission with current environmental context
704 */
705 async checkPermissionWithContext(
706 action: string,
707 resource: string,
708 additionalContext?: Record<string, string>
709 ): Promise<ApiResponse<PermissionCheckResponse>> {
710 const context = this.buildContext();
711 const contextMap: Record<string, string> = {};
712
713 // Convert context to string map
714 if (context.time_of_day) contextMap.time_of_day = context.time_of_day;
715 if (context.day_type) contextMap.day_type = context.day_type;
716 if (context.device_type) contextMap.device_type = context.device_type;
717 if (context.connection_type) contextMap.connection_type = context.connection_type;
718 if (context.user_agent) contextMap.user_agent = context.user_agent;
719
720 // Add any additional context
721 if (additionalContext) {
722 Object.assign(contextMap, additionalContext);
723 }
724
725 return this.rbacManager.checkPermission({
726 action,
727 resource,
728 context: contextMap
729 });
730 }
731
732 /**
733 * Check if current time is within business hours
734 */
735 isBusinessHours(): boolean {
736 const now = new Date();
737 const hour = now.getHours();
738 const dayOfWeek = now.getDay();
739
740 return hour >= 9 && hour < 17 && dayOfWeek >= 1 && dayOfWeek <= 5;
741 }
742
743 /**
744 * Check if current day is weekend
745 */
746 isWeekend(): boolean {
747 const dayOfWeek = new Date().getDay();
748 return dayOfWeek === 0 || dayOfWeek === 6;
749 }
750
751 /**
752 * Get device type from user agent
753 */
754 getDeviceType(): 'desktop' | 'mobile' | 'tablet' | 'unknown' {
755 if (!navigator.userAgent) return 'unknown';
756
757 const ua = navigator.userAgent.toLowerCase();
758 if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) {
759 return 'mobile';
760 } else if (ua.includes('tablet') || ua.includes('ipad')) {
761 return 'tablet';
762 } else if (ua.includes('mozilla') || ua.includes('chrome') || ua.includes('firefox')) {
763 return 'desktop';
764 } else {
765 return 'unknown';
766 }
767 }
768
769 /**
770 * Calculate basic risk score based on context
771 */
772 calculateRiskScore(context?: ConditionalContext): number {
773 let risk = 0;
774 const ctx = context || this.buildContext();
775
776 // Time-based risk
777 if (ctx.time_of_day === 'after_hours') risk += 10;
778 if (ctx.day_type === 'weekend') risk += 5;
779
780 // Device-based risk
781 if (ctx.device_type === 'mobile') risk += 5;
782 if (ctx.device_type === 'unknown') risk += 15;
783
784 // Connection-based risk
785 if (ctx.connection_type === 'proxy') risk += 20;
786 if (ctx.connection_type === 'tor') risk += 50;
787
788 return Math.min(risk, 100); // Cap at 100
789 }
790
791 /**
792 * Create context for high-security operations
793 */
794 createHighSecurityContext(): Record<string, string> {
795 const context = this.buildContext();
796 const contextMap: Record<string, string> = {};
797
798 // Convert to string map
799 if (context.time_of_day) contextMap.time_of_day = context.time_of_day;
800 if (context.day_type) contextMap.day_type = context.day_type;
801 if (context.device_type) contextMap.device_type = context.device_type;
802 if (context.connection_type) contextMap.connection_type = context.connection_type;
803
804 // Add security requirements
805 contextMap.security_level = 'high';
806 contextMap.require_business_hours = 'true';
807 contextMap.block_vpn = 'true';
808 contextMap.max_risk_score = '20';
809
810 return contextMap;
811 }
812}
813"#;
814
815 Ok(conditional_code.to_string())
816 }
817
818 fn generate_audit_module(&self) -> Result<String, Box<dyn std::error::Error>> {
820 let audit_code = format!(
821 r#"
822/**
823 * Audit Logging Module
824 * Provides comprehensive audit trail functionality
825 */
826
827import {{ {} }} from './client';
828import {{ AuditLogResponse, AuditQuery, AuditEntry, ApiResponse }} from './types';
829
830export class AuditManager {{
831 constructor(private client: {}) {{}}
832
833 /**
834 * Get audit logs with filtering and pagination
835 */
836 async getAuditLogs(query?: AuditQuery): Promise<ApiResponse<AuditLogResponse>> {{
837 const params = new URLSearchParams();
838
839 if (query?.user_id) params.append('user_id', query.user_id);
840 if (query?.action) params.append('action', query.action);
841 if (query?.resource) params.append('resource', query.resource);
842 if (query?.start_time) params.append('start_time', query.start_time);
843 if (query?.end_time) params.append('end_time', query.end_time);
844 if (query?.page) params.append('page', query.page.toString());
845 if (query?.per_page) params.append('per_page', query.per_page.toString());
846
847 const queryString = params.toString();
848 const path = queryString ? `/{}/rbac/audit?${{queryString}}` : '/{}/rbac/audit';
849
850 return this.client.get<AuditLogResponse>(path);
851 }}
852
853 /**
854 * Get audit logs for specific user
855 */
856 async getUserAuditLogs(
857 userId: string,
858 options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
859 ): Promise<ApiResponse<AuditLogResponse>> {{
860 return this.getAuditLogs({{
861 user_id: userId,
862 start_time: options?.startTime,
863 end_time: options?.endTime,
864 page: options?.page,
865 per_page: options?.perPage
866 }});
867 }}
868
869 /**
870 * Get audit logs for specific action
871 */
872 async getActionAuditLogs(
873 action: string,
874 options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
875 ): Promise<ApiResponse<AuditLogResponse>> {{
876 return this.getAuditLogs({{
877 action,
878 start_time: options?.startTime,
879 end_time: options?.endTime,
880 page: options?.page,
881 per_page: options?.perPage
882 }});
883 }}
884
885 /**
886 * Get audit logs for specific resource
887 */
888 async getResourceAuditLogs(
889 resource: string,
890 options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
891 ): Promise<ApiResponse<AuditLogResponse>> {{
892 return this.getAuditLogs({{
893 resource,
894 start_time: options?.startTime,
895 end_time: options?.endTime,
896 page: options?.page,
897 per_page: options?.perPage
898 }});
899 }}
900
901 /**
902 * Get audit logs for the last N hours
903 */
904 async getRecentAuditLogs(hours: number = 24): Promise<ApiResponse<AuditLogResponse>> {{
905 const endTime = new Date();
906 const startTime = new Date(endTime.getTime() - (hours * 60 * 60 * 1000));
907
908 return this.getAuditLogs({{
909 start_time: startTime.toISOString(),
910 end_time: endTime.toISOString()
911 }});
912 }}
913
914 /**
915 * Export audit logs as CSV
916 */
917 async exportAuditLogs(query?: AuditQuery): Promise<string> {{
918 try {{
919 const response = await this.getAuditLogs(query);
920 if (!response.data?.entries) {{
921 return '';
922 }}
923
924 const headers = ['Timestamp', 'User ID', 'Action', 'Resource', 'Result', 'Context'];
925 const rows = [headers.join(',')];
926
927 for (const entry of response.data.entries) {{
928 const row = [
929 entry.timestamp,
930 entry.user_id || '',
931 entry.action,
932 entry.resource || '',
933 entry.result,
934 JSON.stringify(entry.context).replace(/"/g, '""') // Escape quotes for CSV
935 ];
936 rows.push(row.map(field => `"${{field}}"`).join(','));
937 }}
938
939 return rows.join('\\n');
940 }} catch (error) {{
941 console.error('Failed to export audit logs:', error);
942 throw error;
943 }}
944 }}
945
946 /**
947 * Get audit statistics
948 */
949 async getAuditStatistics(
950 startTime?: string,
951 endTime?: string
952 ): Promise<{{
953 totalEntries: number;
954 actionCounts: Record<string, number>;
955 userCounts: Record<string, number>;
956 successRate: number;
957 }}> {{
958 try {{
959 const response = await this.getAuditLogs({{
960 start_time: startTime,
961 end_time: endTime,
962 per_page: 1000 // Get a large sample
963 }});
964
965 if (!response.data?.entries) {{
966 return {{
967 totalEntries: 0,
968 actionCounts: {{}},
969 userCounts: {{}},
970 successRate: 0
971 }};
972 }}
973
974 const entries = response.data.entries;
975 const actionCounts: Record<string, number> = {{}};
976 const userCounts: Record<string, number> = {{}};
977 let successCount = 0;
978
979 for (const entry of entries) {{
980 // Count actions
981 actionCounts[entry.action] = (actionCounts[entry.action] || 0) + 1;
982
983 // Count users
984 if (entry.user_id) {{
985 userCounts[entry.user_id] = (userCounts[entry.user_id] || 0) + 1;
986 }}
987
988 // Count successes
989 if (entry.result === 'success' || entry.result === 'granted') {{
990 successCount++;
991 }}
992 }}
993
994 return {{
995 totalEntries: response.data.total_count,
996 actionCounts,
997 userCounts,
998 successRate: entries.length > 0 ? (successCount / entries.length) * 100 : 0
999 }};
1000 }} catch (error) {{
1001 console.error('Failed to get audit statistics:', error);
1002 throw error;
1003 }}
1004 }}
1005
1006 /**
1007 * Monitor real-time audit events (if supported by backend)
1008 */
1009 monitorAuditEvents(
1010 callback: (entry: AuditEntry) => void,
1011 filters?: {{ userId?: string; action?: string; resource?: string }}
1012 ): () => void {{
1013 // This would typically use WebSockets or Server-Sent Events
1014 // For now, we'll implement polling as a fallback
1015
1016 let isMonitoring = true;
1017 let lastTimestamp = new Date().toISOString();
1018
1019 const poll = async () => {{
1020 if (!isMonitoring) return;
1021
1022 try {{
1023 const response = await this.getAuditLogs({{
1024 user_id: filters?.userId,
1025 action: filters?.action,
1026 resource: filters?.resource,
1027 start_time: lastTimestamp,
1028 per_page: 50
1029 }});
1030
1031 if (response.data?.entries) {{
1032 for (const entry of response.data.entries) {{
1033 callback(entry);
1034 if (entry.timestamp > lastTimestamp) {{
1035 lastTimestamp = entry.timestamp;
1036 }}
1037 }}
1038 }}
1039 }} catch (error) {{
1040 console.error('Error polling audit events:', error);
1041 }}
1042
1043 // Poll every 5 seconds
1044 setTimeout(poll, 5000);
1045 }};
1046
1047 // Start polling
1048 poll();
1049
1050 // Return cleanup function
1051 return () => {{
1052 isMonitoring = false;
1053 }};
1054 }}
1055}}
1056"#,
1057 self.config.client_name,
1058 self.config.client_name,
1059 self.config.version,
1060 self.config.version
1061 );
1062
1063 Ok(audit_code)
1064 }
1065
1066 fn generate_utils(&self) -> Result<String, Box<dyn std::error::Error>> {
1068 let utils_code = r#"
1069/**
1070 * Utility functions for AuthFramework SDK
1071 */
1072
1073/**
1074 * Sleep for specified milliseconds
1075 */
1076export function sleep(ms: number): Promise<void> {
1077 return new Promise(resolve => setTimeout(resolve, ms));
1078}
1079
1080/**
1081 * Retry a function with exponential backoff
1082 */
1083export async function retryWithBackoff<T>(
1084 fn: () => Promise<T>,
1085 maxAttempts: number = 3,
1086 baseDelay: number = 1000
1087): Promise<T> {
1088 let lastError: Error | null = null;
1089
1090 for (let attempt = 0; attempt < maxAttempts; attempt++) {
1091 try {
1092 return await fn();
1093 } catch (error) {
1094 lastError = error as Error;
1095
1096 if (attempt < maxAttempts - 1) {
1097 const delay = baseDelay * Math.pow(2, attempt);
1098 await sleep(delay);
1099 }
1100 }
1101 }
1102
1103 throw lastError || new Error('All retry attempts failed');
1104}
1105
1106/**
1107 * Debounce function calls
1108 */
1109export function debounce<T extends (...args: any[]) => any>(
1110 func: T,
1111 wait: number
1112): (...args: Parameters<T>) => void {
1113 let timeout: NodeJS.Timeout | null = null;
1114
1115 return function(this: any, ...args: Parameters<T>) {
1116 const later = () => {
1117 timeout = null;
1118 func.apply(this, args);
1119 };
1120
1121 if (timeout) {
1122 clearTimeout(timeout);
1123 }
1124 timeout = setTimeout(later, wait);
1125 };
1126}
1127
1128/**
1129 * Throttle function calls
1130 */
1131export function throttle<T extends (...args: any[]) => any>(
1132 func: T,
1133 limit: number
1134): (...args: Parameters<T>) => void {
1135 let inThrottle: boolean;
1136
1137 return function(this: any, ...args: Parameters<T>) {
1138 if (!inThrottle) {
1139 func.apply(this, args);
1140 inThrottle = true;
1141 setTimeout(() => inThrottle = false, limit);
1142 }
1143 };
1144}
1145
1146/**
1147 * Format date for API requests
1148 */
1149export function formatDateForApi(date: Date): string {
1150 return date.toISOString();
1151}
1152
1153/**
1154 * Parse API date response
1155 */
1156export function parseApiDate(dateString: string): Date {
1157 return new Date(dateString);
1158}
1159
1160/**
1161 * Check if error is network-related
1162 */
1163export function isNetworkError(error: any): boolean {
1164 return error instanceof TypeError && error.message.includes('fetch');
1165}
1166
1167/**
1168 * Check if error is authentication-related
1169 */
1170export function isAuthError(error: any): boolean {
1171 return error.status === 401 || error.status === 403;
1172}
1173
1174/**
1175 * Check if error is rate limit-related
1176 */
1177export function isRateLimitError(error: any): boolean {
1178 return error.status === 429;
1179}
1180
1181/**
1182 * Extract error message from API response
1183 */
1184export function extractErrorMessage(error: any): string {
1185 if (error.body?.message) return error.body.message;
1186 if (error.body?.error) return error.body.error;
1187 if (error.message) return error.message;
1188 return 'Unknown error occurred';
1189}
1190
1191/**
1192 * Validate email format
1193 */
1194export function isValidEmail(email: string): boolean {
1195 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1196 return emailRegex.test(email);
1197}
1198
1199/**
1200 * Generate UUID v4
1201 */
1202export function generateUuid(): string {
1203 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
1204 const r = Math.random() * 16 | 0;
1205 const v = c === 'x' ? r : (r & 0x3 | 0x8);
1206 return v.toString(16);
1207 });
1208}
1209
1210/**
1211 * Deep merge objects
1212 */
1213export function deepMerge<T>(target: T, source: Partial<T>): T {
1214 const output = { ...target };
1215
1216 if (isObject(target) && isObject(source)) {
1217 Object.keys(source).forEach(key => {
1218 if (isObject(source[key as keyof T])) {
1219 if (!(key in target)) {
1220 Object.assign(output, { [key]: source[key as keyof T] });
1221 } else {
1222 (output as any)[key] = deepMerge(target[key as keyof T], source[key as keyof T]);
1223 }
1224 } else {
1225 Object.assign(output, { [key]: source[key as keyof T] });
1226 }
1227 });
1228 }
1229
1230 return output;
1231}
1232
1233function isObject(item: any): boolean {
1234 return item && typeof item === 'object' && !Array.isArray(item);
1235}
1236
1237/**
1238 * Local storage wrapper with error handling
1239 */
1240export class SafeStorage {
1241 static get(key: string): string | null {
1242 try {
1243 return localStorage.getItem(key);
1244 } catch {
1245 return null;
1246 }
1247 }
1248
1249 static set(key: string, value: string): boolean {
1250 try {
1251 localStorage.setItem(key, value);
1252 return true;
1253 } catch {
1254 return false;
1255 }
1256 }
1257
1258 static remove(key: string): boolean {
1259 try {
1260 localStorage.removeItem(key);
1261 return true;
1262 } catch {
1263 return false;
1264 }
1265 }
1266
1267 static clear(): boolean {
1268 try {
1269 localStorage.clear();
1270 return true;
1271 } catch {
1272 return false;
1273 }
1274 }
1275}
1276"#;
1277
1278 Ok(utils_code.to_string())
1279 }
1280
1281 fn generate_index(&self) -> Result<String, Box<dyn std::error::Error>> {
1283 let index_code = format!(
1284 r#"
1285/**
1286 * AuthFramework Enhanced SDK with RBAC Support
1287 *
1288 * @version {version}
1289 * @description Comprehensive authentication and authorization client library
1290 */
1291
1292// Core client
1293export {{ {client_name}, HttpError }} from './client';
1294export type {{ ClientConfig, ApiResponse }} from './client';
1295
1296// Type definitions
1297export * from './types';
1298
1299// RBAC module
1300{rbac_export}
1301
1302// Conditional permissions
1303{conditional_export}
1304
1305// Audit logging
1306{audit_export}
1307
1308// Utilities
1309export * from './utils';
1310
1311// Main SDK class that combines all functionality
1312export class AuthFrameworkSdk {{
1313 public readonly client: {client_name};
1314 {rbac_property}
1315 {conditional_property}
1316 {audit_property}
1317
1318 constructor(config: ClientConfig) {{
1319 this.client = new {client_name}(config);
1320 {rbac_init}
1321 {conditional_init}
1322 {audit_init}
1323 }}
1324
1325 /**
1326 * Set authentication token for all requests
1327 */
1328 setAccessToken(token: string): void {{
1329 this.client.setAccessToken(token);
1330 }}
1331
1332 /**
1333 * Clear authentication token
1334 */
1335 clearAccessToken(): void {{
1336 this.client.clearAccessToken();
1337 }}
1338
1339 /**
1340 * Set API key for authentication
1341 */
1342 setApiKey(apiKey: string): void {{
1343 this.client.setApiKey(apiKey);
1344 }}
1345
1346 /**
1347 * Check if user is authenticated
1348 */
1349 isAuthenticated(): boolean {{
1350 // This would check if we have a valid token
1351 // Implementation depends on your token storage strategy
1352 return false; // Placeholder
1353 }}
1354
1355 /**
1356 * Refresh authentication token
1357 */
1358 async refreshToken(refreshToken: string): Promise<{{ accessToken: string; refreshToken: string }}> {{
1359 // Implementation would depend on your refresh token flow
1360 throw new Error('Not implemented');
1361 }}
1362}}
1363
1364// Default export
1365export default AuthFrameworkSdk;
1366
1367// Create convenience function
1368export function createAuthFrameworkClient(config: ClientConfig): AuthFrameworkSdk {{
1369 return new AuthFrameworkSdk(config);
1370}}
1371"#,
1372 version = self.config.version,
1373 client_name = self.config.client_name,
1374 rbac_export = if self.config.include_rbac {
1375 "export { RbacManager } from './rbac';"
1376 } else {
1377 ""
1378 },
1379 conditional_export = if self.config.include_conditional_permissions {
1380 "export { ConditionalPermissions } from './conditional';"
1381 } else {
1382 ""
1383 },
1384 audit_export = if self.config.include_audit {
1385 "export { AuditManager } from './audit';"
1386 } else {
1387 ""
1388 },
1389 rbac_property = if self.config.include_rbac {
1390 "public readonly rbac: RbacManager;"
1391 } else {
1392 ""
1393 },
1394 conditional_property = if self.config.include_conditional_permissions {
1395 "public readonly conditional: ConditionalPermissions;"
1396 } else {
1397 ""
1398 },
1399 audit_property = if self.config.include_audit {
1400 "public readonly audit: AuditManager;"
1401 } else {
1402 ""
1403 },
1404 rbac_init = if self.config.include_rbac {
1405 "this.rbac = new RbacManager(this.client);"
1406 } else {
1407 ""
1408 },
1409 conditional_init = if self.config.include_conditional_permissions {
1410 "this.conditional = new ConditionalPermissions(this.rbac);"
1411 } else {
1412 ""
1413 },
1414 audit_init = if self.config.include_audit {
1415 "this.audit = new AuditManager(this.client);"
1416 } else {
1417 ""
1418 },
1419 );
1420
1421 Ok(index_code)
1422 }
1423
1424 fn generate_package_json(&self) -> Result<String, Box<dyn std::error::Error>> {
1426 let package_json = r#"{{
1427 "name": "@authframework/client",
1428 "version": "1.0.0",
1429 "description": "Enhanced AuthFramework client library with RBAC support",
1430 "main": "dist/index.js",
1431 "types": "dist/index.d.ts",
1432 "scripts": {{
1433 "build": "tsc",
1434 "build:watch": "tsc --watch",
1435 "test": "jest",
1436 "test:watch": "jest --watch",
1437 "lint": "eslint src/**/*.ts",
1438 "lint:fix": "eslint src/**/*.ts --fix",
1439 "clean": "rimraf dist",
1440 "prepublishOnly": "npm run clean && npm run build"
1441 }},
1442 "keywords": [
1443 "auth",
1444 "authentication",
1445 "authorization",
1446 "rbac",
1447 "oauth",
1448 "jwt",
1449 "typescript"
1450 ],
1451 "author": "AuthFramework",
1452 "license": "MIT",
1453 "dependencies": {{
1454 "uuid": "^9.0.0"
1455 }},
1456 "devDependencies": {{
1457 "@types/jest": "^29.0.0",
1458 "@types/node": "^18.0.0",
1459 "@types/uuid": "^9.0.0",
1460 "@typescript-eslint/eslint-plugin": "^5.0.0",
1461 "@typescript-eslint/parser": "^5.0.0",
1462 "eslint": "^8.0.0",
1463 "jest": "^29.0.0",
1464 "rimraf": "^3.0.0",
1465 "ts-jest": "^29.0.0",
1466 "typescript": "^4.9.0"
1467 }},
1468 "files": [
1469 "dist/**/*"
1470 ],
1471 "repository": {{
1472 "type": "git",
1473 "url": "https://github.com/authframework/client-js.git"
1474 }},
1475 "bugs": {{
1476 "url": "https://github.com/authframework/client-js/issues"
1477 }},
1478 "homepage": "https://github.com/authframework/client-js#readme"
1479}}
1480"#
1481 .to_string();
1482
1483 Ok(package_json)
1484 }
1485
1486 fn generate_readme(&self) -> Result<String, Box<dyn std::error::Error>> {
1488 let readme = r#"# AuthFramework JavaScript/TypeScript SDK
1489
1490Enhanced client library for AuthFramework with comprehensive RBAC support powered by role-system v1.0.
1491
1492## Features
1493
1494- 🔐 **Complete Authentication** - JWT, OAuth, API keys
1495- 👥 **Enterprise RBAC** - Hierarchical roles and permissions
1496- ⏰ **Conditional Permissions** - Time, location, and context-aware access control
1497- 📊 **Audit Logging** - Comprehensive activity tracking
1498- 🚀 **TypeScript Support** - Full type safety and IntelliSense
1499- 🔄 **Automatic Retries** - Built-in error handling and retry logic
1500- 📱 **Cross-Platform** - Works in browsers and Node.js
1501
1502## Installation
1503
1504```bash
1505npm install @authframework/client
1506# or
1507yarn add @authframework/client
1508```
1509
1510## Quick Start
1511
1512```typescript
1513import {{ AuthFrameworkSdk }} from '@authframework/client';
1514
1515// Initialize the client
1516const auth = new AuthFrameworkSdk({{
1517 baseUrl: 'https://your-api.example.com',
1518 accessToken: 'your-jwt-token'
1519}});
1520
1521// Check permissions
1522const canEdit = await auth.rbac.hasPermission('edit', 'documents');
1523
1524// Get user roles
1525const userRoles = await auth.rbac.getUserRoles('user123');
1526
1527// Conditional permission check
1528const canAccessAfterHours = await auth.conditional.checkPermissionWithContext(
1529 'access',
1530 'admin-panel'
1531);
1532```
1533
1534## RBAC (Role-Based Access Control)
1535
1536### Role Management
1537
1538```typescript
1539// Create a new role
1540await auth.rbac.createRole({{
1541 name: 'Editor',
1542 description: 'Can edit content',
1543 permissions: ['edit:documents', 'read:documents']
1544}});
1545
1546// Assign role to user
1547await auth.rbac.assignUserRole('user123', {{
1548 role_id: 'editor-role-id',
1549 expires_at: '2024-12-31T23:59:59Z'
1550}});
1551
1552// Check user permissions
1553const permissions = await auth.rbac.getUserRoles('user123');
1554console.log(permissions.effective_permissions);
1555```
1556
1557### Permission Checking
1558
1559```typescript
1560// Simple permission check
1561const hasPermission = await auth.rbac.hasPermission('delete', 'documents');
1562
1563// Detailed permission check with context
1564const result = await auth.rbac.checkPermission({{
1565 action: 'access',
1566 resource: 'admin-panel',
1567 context: {{
1568 ip_address: '192.168.1.100',
1569 time_of_day: 'business_hours'
1570 }}
1571}});
1572
1573console.log(result.data?.granted); // boolean
1574console.log(result.data?.reason); // explanation
1575```
1576
1577## Conditional Permissions
1578
1579Context-aware permissions based on time, location, device, and custom attributes.
1580
1581```typescript
1582// Check permission with environmental context
1583const canAccess = await auth.conditional.checkPermissionWithContext(
1584 'access',
1585 'sensitive-data'
1586);
1587
1588// Build custom context
1589const context = auth.conditional.buildContext();
1590console.log(context.time_of_day); // 'business_hours' | 'after_hours' | 'weekend'
1591console.log(context.device_type); // 'desktop' | 'mobile' | 'tablet'
1592console.log(context.connection_type); // 'direct' | 'vpn' | 'proxy'
1593
1594// High-security context for sensitive operations
1595const highSecContext = auth.conditional.createHighSecurityContext();
1596const result = await auth.rbac.checkPermission({{
1597 action: 'delete',
1598 resource: 'user-accounts',
1599 context: highSecContext
1600}});
1601```
1602
1603## Audit Logging
1604
1605Comprehensive activity tracking and analysis.
1606
1607```typescript
1608// Get audit logs
1609const logs = await auth.audit.getAuditLogs({{
1610 user_id: 'user123',
1611 start_time: '2024-01-01T00:00:00Z',
1612 end_time: '2024-01-31T23:59:59Z',
1613 page: 1,
1614 per_page: 50
1615}});
1616
1617// Get recent activity
1618const recent = await auth.audit.getRecentAuditLogs(24); // Last 24 hours
1619
1620// Export audit data
1621const csvData = await auth.audit.exportAuditLogs({{
1622 action: 'login',
1623 start_time: '2024-01-01T00:00:00Z'
1624}});
1625
1626// Real-time monitoring
1627const stopMonitoring = auth.audit.monitorAuditEvents(
1628 (entry) => console.log('New audit entry:', entry),
1629 {{ action: 'permission_check' }}
1630);
1631
1632// Stop monitoring when done
1633stopMonitoring();
1634```
1635
1636## Role Elevation
1637
1638Temporary privilege escalation for administrative tasks.
1639
1640```typescript
1641// Request elevated permissions
1642await auth.rbac.elevateRole({{
1643 target_role: 'admin',
1644 duration_minutes: 30,
1645 justification: 'Emergency system maintenance'
1646}});
1647
1648// Check if user has elevated permissions
1649const hasElevated = await auth.rbac.hasPermission('elevated', 'admin');
1650```
1651
1652## Advanced Usage
1653
1654### Bulk Operations
1655
1656```typescript
1657// Bulk role assignment
1658await auth.rbac.bulkAssignRoles({{
1659 assignments: [
1660 {{ user_id: 'user1', role_id: 'editor' }},
1661 {{ user_id: 'user2', role_id: 'viewer' }},
1662 {{ user_id: 'user3', role_id: 'admin', expires_at: '2024-12-31T23:59:59Z' }}
1663 ]
1664}});
1665```
1666
1667### Error Handling
1668
1669```typescript
1670import {{ HttpError, isAuthError, isRateLimitError }} from '@authframework/client';
1671
1672try {{
1673 await auth.rbac.createRole(roleData);
1674}} catch (error) {{
1675 if (error instanceof HttpError) {{
1676 if (isAuthError(error)) {{
1677 // Handle authentication error
1678 console.log('Authentication required');
1679 }} else if (isRateLimitError(error)) {{
1680 // Handle rate limit
1681 console.log('Rate limit exceeded');
1682 }} else {{
1683 console.log('HTTP error:', error.status, error.statusText);
1684 }}
1685 }}
1686}}
1687```
1688
1689### Custom Configuration
1690
1691```typescript
1692const auth = new AuthFrameworkSdk({{
1693 baseUrl: 'https://api.example.com',
1694 accessToken: 'your-token',
1695 timeout: 30000, // 30 second timeout
1696 retryAttempts: 3, // Retry failed requests 3 times
1697}});
1698
1699// Dynamic token updates
1700auth.setAccessToken('new-token');
1701
1702// API key authentication
1703auth.setApiKey('your-api-key');
1704```
1705
1706## Type Safety
1707
1708Full TypeScript support with comprehensive type definitions:
1709
1710```typescript
1711import type {{
1712 Role,
1713 Permission,
1714 UserRolesResponse,
1715 PermissionCheckResponse,
1716 ConditionalContext
1717}} from '@authframework/client';
1718
1719const role: Role = {{
1720 id: 'role-123',
1721 name: 'Editor',
1722 description: 'Content editor role',
1723 parent_id: 'base-user',
1724 permissions: ['edit:content', 'read:content'],
1725 created_at: '2024-01-01T00:00:00Z',
1726 updated_at: '2024-01-01T00:00:00Z'
1727}};
1728```
1729
1730## Browser Support
1731
1732- Chrome 60+
1733- Firefox 60+
1734- Safari 12+
1735- Edge 79+
1736
1737## Node.js Support
1738
1739- Node.js 14+
1740
1741## License
1742
1743MIT License - see LICENSE file for details.
1744
1745## Support
1746
1747- 📖 [Documentation](https://docs.authframework.com)
1748- 🐛 [Issue Tracker](https://github.com/authframework/client-js/issues)
1749- 💬 [Discord Community](https://discord.gg/authframework)
1750"#.to_string();
1751
1752 Ok(readme)
1753 }
1754}
1755
1756#[cfg(test)]
1757mod tests {
1758 use super::*;
1759
1760 #[test]
1761 fn test_sdk_generation() {
1762 let config = EnhancedSdkConfig::default();
1763 let generator = JsSdkGenerator::new(config);
1764
1765 let result = generator.generate_sdk();
1766 assert!(result.is_ok());
1767
1768 let files = result.unwrap();
1769 assert!(files.contains_key("client.ts"));
1770 assert!(files.contains_key("types.ts"));
1771 assert!(files.contains_key("rbac.ts"));
1772 assert!(files.contains_key("index.ts"));
1773 assert!(files.contains_key("package.json"));
1774 }
1775
1776 #[test]
1777 fn test_typescript_generation() {
1778 let config = EnhancedSdkConfig {
1779 typescript: true,
1780 include_rbac: true,
1781 include_conditional_permissions: true,
1782 include_audit: true,
1783 ..Default::default()
1784 };
1785
1786 let generator = JsSdkGenerator::new(config);
1787 let files = generator.generate_sdk().unwrap();
1788
1789 assert!(files.contains_key("types.ts"));
1791 assert!(files.contains_key("rbac.ts"));
1792 assert!(files.contains_key("conditional.ts"));
1793 assert!(files.contains_key("audit.ts"));
1794 }
1795}
1796
1797