use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnhancedSdkConfig {
pub base_url: String,
pub version: String,
pub typescript: bool,
pub include_rbac: bool,
pub include_conditional_permissions: bool,
pub include_audit: bool,
pub client_name: String,
}
impl Default for EnhancedSdkConfig {
fn default() -> Self {
Self {
base_url: "https://api.example.com".to_string(),
version: "v1".to_string(),
typescript: true,
include_rbac: true,
include_conditional_permissions: true,
include_audit: true,
client_name: "AuthFrameworkClient".to_string(),
}
}
}
pub struct JsSdkGenerator {
config: EnhancedSdkConfig,
}
impl JsSdkGenerator {
pub fn new(config: EnhancedSdkConfig) -> Self {
Self { config }
}
pub fn generate_sdk(&self) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let mut files = HashMap::new();
files.insert("client.ts".to_string(), self.generate_base_client()?);
if self.config.typescript {
files.insert("types.ts".to_string(), self.generate_types()?);
}
if self.config.include_rbac {
files.insert("rbac.ts".to_string(), self.generate_rbac_module()?);
}
if self.config.include_conditional_permissions {
files.insert(
"conditional.ts".to_string(),
self.generate_conditional_module()?,
);
}
if self.config.include_audit {
files.insert("audit.ts".to_string(), self.generate_audit_module()?);
}
files.insert("utils.ts".to_string(), self.generate_utils()?);
files.insert("index.ts".to_string(), self.generate_index()?);
files.insert("package.json".to_string(), self.generate_package_json()?);
files.insert("README.md".to_string(), self.generate_readme()?);
Ok(files)
}
fn generate_base_client(&self) -> Result<String, Box<dyn std::error::Error>> {
let client_code = format!(
r#"
/**
* Enhanced {} with RBAC Support
* Generated by AuthFramework SDK Generator
*/
export interface ClientConfig {{
baseUrl: string;
apiKey?: string;
accessToken?: string;
timeout?: number;
retryAttempts?: number;
}}
export interface ApiResponse<T> {{
success: boolean;
data?: T;
error?: string;
message?: string;
}}
export class HttpError extends Error {{
constructor(
public status: number,
public statusText: string,
public body?: any
) {{
super(`HTTP ${{status}}: ${{statusText}}`);
this.name = 'HttpError';
}}
}}
export class {} {{
private baseUrl: string;
private headers: Record<string, string> = {{}};
private timeout: number;
private retryAttempts: number;
constructor(config: ClientConfig) {{
this.baseUrl = config.baseUrl.replace(/\/$/, '');
this.timeout = config.timeout || 30000;
this.retryAttempts = config.retryAttempts || 3;
if (config.apiKey) {{
this.headers['X-API-Key'] = config.apiKey;
}}
if (config.accessToken) {{
this.headers['Authorization'] = `Bearer ${{config.accessToken}}`;
}}
this.headers['Content-Type'] = 'application/json';
}}
/**
* Set authentication token
*/
setAccessToken(token: string): void {{
this.headers['Authorization'] = `Bearer ${{token}}`;
}}
/**
* Clear authentication token
*/
clearAccessToken(): void {{
delete this.headers['Authorization'];
}}
/**
* Set API key
*/
setApiKey(apiKey: string): void {{
this.headers['X-API-Key'] = apiKey;
}}
/**
* Make HTTP request with retry logic
*/
private async makeRequest<T>(
method: string,
path: string,
body?: any,
headers?: Record<string, string>
): Promise<ApiResponse<T>> {{
const url = `${{this.baseUrl}}${{path}}`;
const requestHeaders = {{ ...this.headers, ...headers }};
let lastError: Error | null = null;
for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {{
try {{
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(url, {{
method,
headers: requestHeaders,
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
}});
clearTimeout(timeoutId);
const responseData = await response.json();
if (!response.ok) {{
throw new HttpError(response.status, response.statusText, responseData);
}}
return responseData as ApiResponse<T>;
}} catch (error) {{
lastError = error as Error;
// Don't retry on client errors (4xx)
if (error instanceof HttpError && error.status >= 400 && error.status < 500) {{
throw error;
}}
// Wait before retry (exponential backoff)
if (attempt < this.retryAttempts) {{
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}}
}}
}}
throw lastError || new Error('Request failed after all retries');
}}
/**
* GET request
*/
async get<T>(path: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
return this.makeRequest<T>('GET', path, undefined, headers);
}}
/**
* POST request
*/
async post<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
return this.makeRequest<T>('POST', path, body, headers);
}}
/**
* PUT request
*/
async put<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
return this.makeRequest<T>('PUT', path, body, headers);
}}
/**
* DELETE request
*/
async delete<T>(path: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
return this.makeRequest<T>('DELETE', path, undefined, headers);
}}
/**
* PATCH request
*/
async patch<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
return this.makeRequest<T>('PATCH', path, body, headers);
}}
}}
"#,
self.config.client_name, self.config.client_name
);
Ok(client_code)
}
fn generate_types(&self) -> Result<String, Box<dyn std::error::Error>> {
let types_code = r#"
/**
* TypeScript type definitions for AuthFramework RBAC
*/
export interface Role {
id: string;
name: string;
description?: string;
parent_id?: string;
permissions: string[];
created_at: string;
updated_at: string;
}
export interface Permission {
id: string;
action: string;
resource: string;
conditions?: Record<string, string>;
created_at: string;
}
export interface UserRole {
role_id: string;
role_name: string;
assigned_at: string;
assigned_by?: string;
expires_at?: string;
}
export interface UserRolesResponse {
user_id: string;
roles: UserRole[];
effective_permissions: string[];
}
export interface CreateRoleRequest {
name: string;
description?: string;
parent_id?: string;
permissions?: string[];
}
export interface UpdateRoleRequest {
name?: string;
description?: string;
parent_id?: string;
}
export interface AssignRoleRequest {
role_id: string;
expires_at?: string;
reason?: string;
}
export interface BulkAssignRequest {
assignments: BulkAssignment[];
}
export interface BulkAssignment {
user_id: string;
role_id: string;
expires_at?: string;
}
export interface ElevateRoleRequest {
target_role: string;
duration_minutes?: number;
justification: string;
}
export interface PermissionCheckRequest {
action: string;
resource: string;
context?: Record<string, string>;
}
export interface PermissionCheckResponse {
granted: boolean;
reason: string;
required_roles: string[];
missing_permissions: string[];
}
export interface AuditEntry {
id: string;
user_id?: string;
action: string;
resource?: string;
result: string;
context: Record<string, string>;
timestamp: string;
}
export interface AuditLogResponse {
entries: AuditEntry[];
total_count: number;
page: number;
per_page: number;
}
export interface AuditQuery {
user_id?: string;
action?: string;
resource?: string;
start_time?: string;
end_time?: string;
page?: number;
per_page?: number;
}
export interface ConditionalContext {
time_of_day?: 'business_hours' | 'after_hours' | 'weekend' | 'holiday';
day_type?: 'weekday' | 'weekend' | 'holiday';
device_type?: 'desktop' | 'mobile' | 'tablet' | 'unknown';
connection_type?: 'direct' | 'vpn' | 'proxy' | 'tor' | 'corporate' | 'unknown';
security_level?: 'low' | 'medium' | 'high' | 'critical';
risk_score?: number;
ip_address?: string;
user_agent?: string;
custom_attributes?: Record<string, string>;
}
export interface RoleListQuery {
page?: number;
per_page?: number;
parent_id?: string;
include_permissions?: boolean;
}
export type TimeOfDay = 'business_hours' | 'after_hours' | 'weekend' | 'holiday';
export type DayType = 'weekday' | 'weekend' | 'holiday';
export type DeviceType = 'desktop' | 'mobile' | 'tablet' | 'unknown';
export type ConnectionType = 'direct' | 'vpn' | 'proxy' | 'tor' | 'corporate' | 'unknown';
export type SecurityLevel = 'low' | 'medium' | 'high' | 'critical';
"#;
Ok(types_code.to_string())
}
fn generate_rbac_module(&self) -> Result<String, Box<dyn std::error::Error>> {
let rbac_code = format!(
r#"
/**
* RBAC (Role-Based Access Control) Module
* Provides comprehensive role and permission management
*/
import {{ {} }} from './client';
import {{
Role, CreateRoleRequest, UpdateRoleRequest, UserRolesResponse,
AssignRoleRequest, BulkAssignRequest, ElevateRoleRequest,
PermissionCheckRequest, PermissionCheckResponse, RoleListQuery,
ApiResponse
}} from './types';
export class RbacManager {{
constructor(private client: {}) {{}}
// ============================================================================
// ROLE MANAGEMENT
// ============================================================================
/**
* Create a new role
*/
async createRole(request: CreateRoleRequest): Promise<ApiResponse<Role>> {{
return this.client.post<Role>('/{}/rbac/roles', request);
}}
/**
* Get role by ID
*/
async getRole(roleId: string): Promise<ApiResponse<Role>> {{
return this.client.get<Role>(`/{}/rbac/roles/${{roleId}}`);
}}
/**
* List all roles with pagination
*/
async listRoles(query?: RoleListQuery): Promise<ApiResponse<Role[]>> {{
const params = new URLSearchParams();
if (query?.page) params.append('page', query.page.toString());
if (query?.per_page) params.append('per_page', query.per_page.toString());
if (query?.parent_id) params.append('parent_id', query.parent_id);
if (query?.include_permissions) params.append('include_permissions', query.include_permissions.toString());
const queryString = params.toString();
const path = queryString ? `/{}/rbac/roles?${{queryString}}` : '/{}/rbac/roles';
return this.client.get<Role[]>(path);
}}
/**
* Update an existing role
*/
async updateRole(roleId: string, request: UpdateRoleRequest): Promise<ApiResponse<Role>> {{
return this.client.put<Role>(`/{}/rbac/roles/${{roleId}}`, request);
}}
/**
* Delete a role
*/
async deleteRole(roleId: string): Promise<ApiResponse<void>> {{
return this.client.delete<void>(`/{}/rbac/roles/${{roleId}}`);
}}
// ============================================================================
// USER ROLE ASSIGNMENTS
// ============================================================================
/**
* Assign role to user
*/
async assignUserRole(userId: string, request: AssignRoleRequest): Promise<ApiResponse<void>> {{
return this.client.post<void>(`/{}/rbac/users/${{userId}}/roles`, request);
}}
/**
* Revoke role from user
*/
async revokeUserRole(userId: string, roleId: string): Promise<ApiResponse<void>> {{
return this.client.delete<void>(`/{}/rbac/users/${{userId}}/roles/${{roleId}}`);
}}
/**
* Get user's roles and effective permissions
*/
async getUserRoles(userId: string): Promise<ApiResponse<UserRolesResponse>> {{
return this.client.get<UserRolesResponse>(`/{}/rbac/users/${{userId}}/roles`);
}}
/**
* Bulk assign roles to multiple users
*/
async bulkAssignRoles(request: BulkAssignRequest): Promise<ApiResponse<void>> {{
return this.client.post<void>('/{}/rbac/bulk/assign', request);
}}
// ============================================================================
// PERMISSION CHECKING
// ============================================================================
/**
* Check if current user has permission
*/
async checkPermission(request: PermissionCheckRequest): Promise<ApiResponse<PermissionCheckResponse>> {{
return this.client.post<PermissionCheckResponse>('/{}/rbac/check-permission', request);
}}
/**
* Quick permission check (returns boolean)
*/
async hasPermission(action: string, resource: string, context?: Record<string, string>): Promise<boolean> {{
try {{
const response = await this.checkPermission({{ action, resource, context }});
return response.data?.granted || false;
}} catch (error) {{
console.error('Permission check failed:', error);
return false;
}}
}}
/**
* Request role elevation
*/
async elevateRole(request: ElevateRoleRequest): Promise<ApiResponse<void>> {{
return this.client.post<void>('/{}/rbac/elevate', request);
}}
// ============================================================================
// CONVENIENCE METHODS
// ============================================================================
/**
* Check if user has any of the specified roles
*/
async userHasAnyRole(userId: string, roleNames: string[]): Promise<boolean> {{
try {{
const response = await this.getUserRoles(userId);
if (!response.data) return false;
const userRoleNames = response.data.roles.map(r => r.role_name);
return roleNames.some(role => userRoleNames.includes(role));
}} catch (error) {{
console.error('Role check failed:', error);
return false;
}}
}}
/**
* Check if user has all of the specified roles
*/
async userHasAllRoles(userId: string, roleNames: string[]): Promise<boolean> {{
try {{
const response = await this.getUserRoles(userId);
if (!response.data) return false;
const userRoleNames = response.data.roles.map(r => r.role_name);
return roleNames.every(role => userRoleNames.includes(role));
}} catch (error) {{
console.error('Role check failed:', error);
return false;
}}
}}
/**
* Get role hierarchy (parent-child relationships)
*/
async getRoleHierarchy(): Promise<Record<string, string[]>> {{
try {{
const response = await this.listRoles({{ include_permissions: false }});
if (!response.data) return {{}};
const hierarchy: Record<string, string[]> = {{}};
for (const role of response.data) {{
if (role.parent_id) {{
if (!hierarchy[role.parent_id]) {{
hierarchy[role.parent_id] = [];
}}
hierarchy[role.parent_id].push(role.id);
}}
}}
return hierarchy;
}} catch (error) {{
console.error('Failed to build role hierarchy:', error);
return {{}};
}}
}}
/**
* Get all child roles for a given parent role
*/
async getChildRoles(parentRoleId: string): Promise<Role[]> {{
try {{
const response = await this.listRoles({{ parent_id: parentRoleId }});
return response.data || [];
}} catch (error) {{
console.error('Failed to get child roles:', error);
return [];
}}
}}
}}
"#,
self.config.client_name,
self.config.client_name,
self.config.version,
self.config.version,
self.config.version,
self.config.version,
self.config.version,
self.config.version,
self.config.version,
self.config.version,
self.config.version,
self.config.version,
self.config.version,
self.config.version
);
Ok(rbac_code)
}
fn generate_conditional_module(&self) -> Result<String, Box<dyn std::error::Error>> {
let conditional_code = r#"
/**
* Conditional Permissions Module
* Provides context-aware permission checking based on time, location, device, etc.
*/
import { ConditionalContext, PermissionCheckRequest, PermissionCheckResponse, ApiResponse } from './types';
export class ConditionalPermissions {
constructor(private rbacManager: any) {}
/**
* Build context from current browser/environment
*/
buildContext(): ConditionalContext {
const context: ConditionalContext = {};
// Time-based context
const now = new Date();
const hour = now.getHours();
const dayOfWeek = now.getDay(); // 0 = Sunday, 6 = Saturday
if (hour >= 9 && hour < 17 && dayOfWeek >= 1 && dayOfWeek <= 5) {
context.time_of_day = 'business_hours';
context.day_type = 'weekday';
} else if (dayOfWeek === 0 || dayOfWeek === 6) {
context.time_of_day = 'weekend';
context.day_type = 'weekend';
} else {
context.time_of_day = 'after_hours';
context.day_type = 'weekday';
}
// Device detection
if (navigator.userAgent) {
const ua = navigator.userAgent.toLowerCase();
if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) {
context.device_type = 'mobile';
} else if (ua.includes('tablet') || ua.includes('ipad')) {
context.device_type = 'tablet';
} else {
context.device_type = 'desktop';
}
context.user_agent = navigator.userAgent;
}
// Connection type detection (basic)
if (navigator.connection && (navigator.connection as any).effectiveType) {
const connection = navigator.connection as any;
if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
context.connection_type = 'proxy'; // Might indicate proxy/VPN
} else {
context.connection_type = 'direct';
}
}
return context;
}
/**
* Check permission with current environmental context
*/
async checkPermissionWithContext(
action: string,
resource: string,
additionalContext?: Record<string, string>
): Promise<ApiResponse<PermissionCheckResponse>> {
const context = this.buildContext();
const contextMap: Record<string, string> = {};
// Convert context to string map
if (context.time_of_day) contextMap.time_of_day = context.time_of_day;
if (context.day_type) contextMap.day_type = context.day_type;
if (context.device_type) contextMap.device_type = context.device_type;
if (context.connection_type) contextMap.connection_type = context.connection_type;
if (context.user_agent) contextMap.user_agent = context.user_agent;
// Add any additional context
if (additionalContext) {
Object.assign(contextMap, additionalContext);
}
return this.rbacManager.checkPermission({
action,
resource,
context: contextMap
});
}
/**
* Check if current time is within business hours
*/
isBusinessHours(): boolean {
const now = new Date();
const hour = now.getHours();
const dayOfWeek = now.getDay();
return hour >= 9 && hour < 17 && dayOfWeek >= 1 && dayOfWeek <= 5;
}
/**
* Check if current day is weekend
*/
isWeekend(): boolean {
const dayOfWeek = new Date().getDay();
return dayOfWeek === 0 || dayOfWeek === 6;
}
/**
* Get device type from user agent
*/
getDeviceType(): 'desktop' | 'mobile' | 'tablet' | 'unknown' {
if (!navigator.userAgent) return 'unknown';
const ua = navigator.userAgent.toLowerCase();
if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) {
return 'mobile';
} else if (ua.includes('tablet') || ua.includes('ipad')) {
return 'tablet';
} else if (ua.includes('mozilla') || ua.includes('chrome') || ua.includes('firefox')) {
return 'desktop';
} else {
return 'unknown';
}
}
/**
* Calculate basic risk score based on context
*/
calculateRiskScore(context?: ConditionalContext): number {
let risk = 0;
const ctx = context || this.buildContext();
// Time-based risk
if (ctx.time_of_day === 'after_hours') risk += 10;
if (ctx.day_type === 'weekend') risk += 5;
// Device-based risk
if (ctx.device_type === 'mobile') risk += 5;
if (ctx.device_type === 'unknown') risk += 15;
// Connection-based risk
if (ctx.connection_type === 'proxy') risk += 20;
if (ctx.connection_type === 'tor') risk += 50;
return Math.min(risk, 100); // Cap at 100
}
/**
* Create context for high-security operations
*/
createHighSecurityContext(): Record<string, string> {
const context = this.buildContext();
const contextMap: Record<string, string> = {};
// Convert to string map
if (context.time_of_day) contextMap.time_of_day = context.time_of_day;
if (context.day_type) contextMap.day_type = context.day_type;
if (context.device_type) contextMap.device_type = context.device_type;
if (context.connection_type) contextMap.connection_type = context.connection_type;
// Add security requirements
contextMap.security_level = 'high';
contextMap.require_business_hours = 'true';
contextMap.block_vpn = 'true';
contextMap.max_risk_score = '20';
return contextMap;
}
}
"#;
Ok(conditional_code.to_string())
}
fn generate_audit_module(&self) -> Result<String, Box<dyn std::error::Error>> {
let audit_code = format!(
r#"
/**
* Audit Logging Module
* Provides comprehensive audit trail functionality
*/
import {{ {} }} from './client';
import {{ AuditLogResponse, AuditQuery, AuditEntry, ApiResponse }} from './types';
export class AuditManager {{
constructor(private client: {}) {{}}
/**
* Get audit logs with filtering and pagination
*/
async getAuditLogs(query?: AuditQuery): Promise<ApiResponse<AuditLogResponse>> {{
const params = new URLSearchParams();
if (query?.user_id) params.append('user_id', query.user_id);
if (query?.action) params.append('action', query.action);
if (query?.resource) params.append('resource', query.resource);
if (query?.start_time) params.append('start_time', query.start_time);
if (query?.end_time) params.append('end_time', query.end_time);
if (query?.page) params.append('page', query.page.toString());
if (query?.per_page) params.append('per_page', query.per_page.toString());
const queryString = params.toString();
const path = queryString ? `/{}/rbac/audit?${{queryString}}` : '/{}/rbac/audit';
return this.client.get<AuditLogResponse>(path);
}}
/**
* Get audit logs for specific user
*/
async getUserAuditLogs(
userId: string,
options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
): Promise<ApiResponse<AuditLogResponse>> {{
return this.getAuditLogs({{
user_id: userId,
start_time: options?.startTime,
end_time: options?.endTime,
page: options?.page,
per_page: options?.perPage
}});
}}
/**
* Get audit logs for specific action
*/
async getActionAuditLogs(
action: string,
options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
): Promise<ApiResponse<AuditLogResponse>> {{
return this.getAuditLogs({{
action,
start_time: options?.startTime,
end_time: options?.endTime,
page: options?.page,
per_page: options?.perPage
}});
}}
/**
* Get audit logs for specific resource
*/
async getResourceAuditLogs(
resource: string,
options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
): Promise<ApiResponse<AuditLogResponse>> {{
return this.getAuditLogs({{
resource,
start_time: options?.startTime,
end_time: options?.endTime,
page: options?.page,
per_page: options?.perPage
}});
}}
/**
* Get audit logs for the last N hours
*/
async getRecentAuditLogs(hours: number = 24): Promise<ApiResponse<AuditLogResponse>> {{
const endTime = new Date();
const startTime = new Date(endTime.getTime() - (hours * 60 * 60 * 1000));
return this.getAuditLogs({{
start_time: startTime.toISOString(),
end_time: endTime.toISOString()
}});
}}
/**
* Export audit logs as CSV
*/
async exportAuditLogs(query?: AuditQuery): Promise<string> {{
try {{
const response = await this.getAuditLogs(query);
if (!response.data?.entries) {{
return '';
}}
const headers = ['Timestamp', 'User ID', 'Action', 'Resource', 'Result', 'Context'];
const rows = [headers.join(',')];
for (const entry of response.data.entries) {{
const row = [
entry.timestamp,
entry.user_id || '',
entry.action,
entry.resource || '',
entry.result,
JSON.stringify(entry.context).replace(/"/g, '""') // Escape quotes for CSV
];
rows.push(row.map(field => `"${{field}}"`).join(','));
}}
return rows.join('\\n');
}} catch (error) {{
console.error('Failed to export audit logs:', error);
throw error;
}}
}}
/**
* Get audit statistics
*/
async getAuditStatistics(
startTime?: string,
endTime?: string
): Promise<{{
totalEntries: number;
actionCounts: Record<string, number>;
userCounts: Record<string, number>;
successRate: number;
}}> {{
try {{
const response = await this.getAuditLogs({{
start_time: startTime,
end_time: endTime,
per_page: 1000 // Get a large sample
}});
if (!response.data?.entries) {{
return {{
totalEntries: 0,
actionCounts: {{}},
userCounts: {{}},
successRate: 0
}};
}}
const entries = response.data.entries;
const actionCounts: Record<string, number> = {{}};
const userCounts: Record<string, number> = {{}};
let successCount = 0;
for (const entry of entries) {{
// Count actions
actionCounts[entry.action] = (actionCounts[entry.action] || 0) + 1;
// Count users
if (entry.user_id) {{
userCounts[entry.user_id] = (userCounts[entry.user_id] || 0) + 1;
}}
// Count successes
if (entry.result === 'success' || entry.result === 'granted') {{
successCount++;
}}
}}
return {{
totalEntries: response.data.total_count,
actionCounts,
userCounts,
successRate: entries.length > 0 ? (successCount / entries.length) * 100 : 0
}};
}} catch (error) {{
console.error('Failed to get audit statistics:', error);
throw error;
}}
}}
/**
* Monitor real-time audit events (if supported by backend)
*/
monitorAuditEvents(
callback: (entry: AuditEntry) => void,
filters?: {{ userId?: string; action?: string; resource?: string }}
): () => void {{
// This would typically use WebSockets or Server-Sent Events
// For now, we'll implement polling as a fallback
let isMonitoring = true;
let lastTimestamp = new Date().toISOString();
const poll = async () => {{
if (!isMonitoring) return;
try {{
const response = await this.getAuditLogs({{
user_id: filters?.userId,
action: filters?.action,
resource: filters?.resource,
start_time: lastTimestamp,
per_page: 50
}});
if (response.data?.entries) {{
for (const entry of response.data.entries) {{
callback(entry);
if (entry.timestamp > lastTimestamp) {{
lastTimestamp = entry.timestamp;
}}
}}
}}
}} catch (error) {{
console.error('Error polling audit events:', error);
}}
// Poll every 5 seconds
setTimeout(poll, 5000);
}};
// Start polling
poll();
// Return cleanup function
return () => {{
isMonitoring = false;
}};
}}
}}
"#,
self.config.client_name,
self.config.client_name,
self.config.version,
self.config.version
);
Ok(audit_code)
}
fn generate_utils(&self) -> Result<String, Box<dyn std::error::Error>> {
let utils_code = r#"
/**
* Utility functions for AuthFramework SDK
*/
/**
* Sleep for specified milliseconds
*/
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Retry a function with exponential backoff
*/
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxAttempts: number = 3,
baseDelay: number = 1000
): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (attempt < maxAttempts - 1) {
const delay = baseDelay * Math.pow(2, attempt);
await sleep(delay);
}
}
}
throw lastError || new Error('All retry attempts failed');
}
/**
* Debounce function calls
*/
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
return function(this: any, ...args: Parameters<T>) {
const later = () => {
timeout = null;
func.apply(this, args);
};
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(later, wait);
};
}
/**
* Throttle function calls
*/
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean;
return function(this: any, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
/**
* Format date for API requests
*/
export function formatDateForApi(date: Date): string {
return date.toISOString();
}
/**
* Parse API date response
*/
export function parseApiDate(dateString: string): Date {
return new Date(dateString);
}
/**
* Check if error is network-related
*/
export function isNetworkError(error: any): boolean {
return error instanceof TypeError && error.message.includes('fetch');
}
/**
* Check if error is authentication-related
*/
export function isAuthError(error: any): boolean {
return error.status === 401 || error.status === 403;
}
/**
* Check if error is rate limit-related
*/
export function isRateLimitError(error: any): boolean {
return error.status === 429;
}
/**
* Extract error message from API response
*/
export function extractErrorMessage(error: any): string {
if (error.body?.message) return error.body.message;
if (error.body?.error) return error.body.error;
if (error.message) return error.message;
return 'Unknown error occurred';
}
/**
* Validate email format
*/
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Generate UUID v4
*/
export function generateUuid(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* Deep merge objects
*/
export function deepMerge<T>(target: T, source: Partial<T>): T {
const output = { ...target };
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key as keyof T])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key as keyof T] });
} else {
(output as any)[key] = deepMerge(target[key as keyof T], source[key as keyof T]);
}
} else {
Object.assign(output, { [key]: source[key as keyof T] });
}
});
}
return output;
}
function isObject(item: any): boolean {
return item && typeof item === 'object' && !Array.isArray(item);
}
/**
* Local storage wrapper with error handling
*/
export class SafeStorage {
static get(key: string): string | null {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
static set(key: string, value: string): boolean {
try {
localStorage.setItem(key, value);
return true;
} catch {
return false;
}
}
static remove(key: string): boolean {
try {
localStorage.removeItem(key);
return true;
} catch {
return false;
}
}
static clear(): boolean {
try {
localStorage.clear();
return true;
} catch {
return false;
}
}
}
"#;
Ok(utils_code.to_string())
}
fn generate_index(&self) -> Result<String, Box<dyn std::error::Error>> {
let index_code = format!(
r#"
/**
* AuthFramework Enhanced SDK with RBAC Support
*
* @version {version}
* @description Comprehensive authentication and authorization client library
*/
// Core client
export {{ {client_name}, HttpError }} from './client';
export type {{ ClientConfig, ApiResponse }} from './client';
// Type definitions
export * from './types';
// RBAC module
{rbac_export}
// Conditional permissions
{conditional_export}
// Audit logging
{audit_export}
// Utilities
export * from './utils';
// Main SDK class that combines all functionality
export class AuthFrameworkSdk {{
public readonly client: {client_name};
{rbac_property}
{conditional_property}
{audit_property}
constructor(config: ClientConfig) {{
this.client = new {client_name}(config);
{rbac_init}
{conditional_init}
{audit_init}
}}
/**
* Set authentication token for all requests
*/
setAccessToken(token: string): void {{
this.client.setAccessToken(token);
}}
/**
* Clear authentication token
*/
clearAccessToken(): void {{
this.client.clearAccessToken();
}}
/**
* Set API key for authentication
*/
setApiKey(apiKey: string): void {{
this.client.setApiKey(apiKey);
}}
/**
* Check if user is authenticated
*/
isAuthenticated(): boolean {{
const token = this.client.getAccessToken?.() ?? null;
if (!token) {{
return false;
}}
// Decode the JWT payload to check expiry without a full verify round-trip.
try {{
const [, payloadB64] = token.split('.');
const json = atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/'));
const payload: {{ exp?: number }} = JSON.parse(json);
if (payload.exp != null && Date.now() / 1000 > payload.exp) {{
return false;
}}
}} catch {{
return false;
}}
return true;
}}
/**
* Refresh authentication token
*/
async refreshToken(refreshToken: string): Promise<{{ accessToken: string; refreshToken: string }}> {{
const response = await this.client.post<{{ access_token: string; refresh_token: string }}>(
'/auth/oauth/token/refresh',
{{ refresh_token: refreshToken }},
);
const accessToken = response.access_token;
this.client.setAccessToken(accessToken);
return {{ accessToken, refreshToken: response.refresh_token }};
}}
}}
// Default export
export default AuthFrameworkSdk;
// Create convenience function
export function createAuthFrameworkClient(config: ClientConfig): AuthFrameworkSdk {{
return new AuthFrameworkSdk(config);
}}
"#,
version = self.config.version,
client_name = self.config.client_name,
rbac_export = if self.config.include_rbac {
"export { RbacManager } from './rbac';"
} else {
""
},
conditional_export = if self.config.include_conditional_permissions {
"export { ConditionalPermissions } from './conditional';"
} else {
""
},
audit_export = if self.config.include_audit {
"export { AuditManager } from './audit';"
} else {
""
},
rbac_property = if self.config.include_rbac {
"public readonly rbac: RbacManager;"
} else {
""
},
conditional_property = if self.config.include_conditional_permissions {
"public readonly conditional: ConditionalPermissions;"
} else {
""
},
audit_property = if self.config.include_audit {
"public readonly audit: AuditManager;"
} else {
""
},
rbac_init = if self.config.include_rbac {
"this.rbac = new RbacManager(this.client);"
} else {
""
},
conditional_init = if self.config.include_conditional_permissions {
"this.conditional = new ConditionalPermissions(this.rbac);"
} else {
""
},
audit_init = if self.config.include_audit {
"this.audit = new AuditManager(this.client);"
} else {
""
},
);
Ok(index_code)
}
fn generate_package_json(&self) -> Result<String, Box<dyn std::error::Error>> {
let package_json = r#"{{
"name": "@authframework/client",
"version": "1.0.0",
"description": "Enhanced AuthFramework client library with RBAC support",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {{
"build": "tsc",
"build:watch": "tsc --watch",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"clean": "rimraf dist",
"prepublishOnly": "npm run clean && npm run build"
}},
"keywords": [
"auth",
"authentication",
"authorization",
"rbac",
"oauth",
"jwt",
"typescript"
],
"author": "AuthFramework",
"license": "MIT",
"dependencies": {{
"uuid": "^9.0.0"
}},
"devDependencies": {{
"@types/jest": "^29.0.0",
"@types/node": "^18.0.0",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.0",
"jest": "^29.0.0",
"rimraf": "^3.0.0",
"ts-jest": "^29.0.0",
"typescript": "^4.9.0"
}},
"files": [
"dist/**/*"
],
"repository": {{
"type": "git",
"url": "https://github.com/authframework/client-js.git"
}},
"bugs": {{
"url": "https://github.com/authframework/client-js/issues"
}},
"homepage": "https://github.com/authframework/client-js#readme"
}}
"#
.to_string();
Ok(package_json)
}
fn generate_readme(&self) -> Result<String, Box<dyn std::error::Error>> {
let readme = r#"# AuthFramework JavaScript/TypeScript SDK
Enhanced client library for AuthFramework with comprehensive RBAC support powered by role-system v1.0.
## Features
- 🔐 **Complete Authentication** - JWT, OAuth, API keys
- 👥 **Enterprise RBAC** - Hierarchical roles and permissions
- ⏰ **Conditional Permissions** - Time, location, and context-aware access control
- 📊 **Audit Logging** - Comprehensive activity tracking
- 🚀 **TypeScript Support** - Full type safety and IntelliSense
- 🔄 **Automatic Retries** - Built-in error handling and retry logic
- 📱 **Cross-Platform** - Works in browsers and Node.js
## Installation
```bash
npm install @authframework/client
# or
yarn add @authframework/client
```
## Quick Start
```typescript
import {{ AuthFrameworkSdk }} from '@authframework/client';
// Initialize the client
const auth = new AuthFrameworkSdk({{
baseUrl: 'https://your-api.example.com',
accessToken: 'your-jwt-token'
}});
// Check permissions
const canEdit = await auth.rbac.hasPermission('edit', 'documents');
// Get user roles
const userRoles = await auth.rbac.getUserRoles('user123');
// Conditional permission check
const canAccessAfterHours = await auth.conditional.checkPermissionWithContext(
'access',
'admin-panel'
);
```
## RBAC (Role-Based Access Control)
### Role Management
```typescript
// Create a new role
await auth.rbac.createRole({{
name: 'Editor',
description: 'Can edit content',
permissions: ['edit:documents', 'read:documents']
}});
// Assign role to user
await auth.rbac.assignUserRole('user123', {{
role_id: 'editor-role-id',
expires_at: '2024-12-31T23:59:59Z'
}});
// Check user permissions
const permissions = await auth.rbac.getUserRoles('user123');
console.log(permissions.effective_permissions);
```
### Permission Checking
```typescript
// Simple permission check
const hasPermission = await auth.rbac.hasPermission('delete', 'documents');
// Detailed permission check with context
const result = await auth.rbac.checkPermission({{
action: 'access',
resource: 'admin-panel',
context: {{
ip_address: '192.168.1.100',
time_of_day: 'business_hours'
}}
}});
console.log(result.data?.granted); // boolean
console.log(result.data?.reason); // explanation
```
## Conditional Permissions
Context-aware permissions based on time, location, device, and custom attributes.
```typescript
// Check permission with environmental context
const canAccess = await auth.conditional.checkPermissionWithContext(
'access',
'sensitive-data'
);
// Build custom context
const context = auth.conditional.buildContext();
console.log(context.time_of_day); // 'business_hours' | 'after_hours' | 'weekend'
console.log(context.device_type); // 'desktop' | 'mobile' | 'tablet'
console.log(context.connection_type); // 'direct' | 'vpn' | 'proxy'
// High-security context for sensitive operations
const highSecContext = auth.conditional.createHighSecurityContext();
const result = await auth.rbac.checkPermission({{
action: 'delete',
resource: 'user-accounts',
context: highSecContext
}});
```
## Audit Logging
Comprehensive activity tracking and analysis.
```typescript
// Get audit logs
const logs = await auth.audit.getAuditLogs({{
user_id: 'user123',
start_time: '2024-01-01T00:00:00Z',
end_time: '2024-01-31T23:59:59Z',
page: 1,
per_page: 50
}});
// Get recent activity
const recent = await auth.audit.getRecentAuditLogs(24); // Last 24 hours
// Export audit data
const csvData = await auth.audit.exportAuditLogs({{
action: 'login',
start_time: '2024-01-01T00:00:00Z'
}});
// Real-time monitoring
const stopMonitoring = auth.audit.monitorAuditEvents(
(entry) => console.log('New audit entry:', entry),
{{ action: 'permission_check' }}
);
// Stop monitoring when done
stopMonitoring();
```
## Role Elevation
Temporary privilege escalation for administrative tasks.
```typescript
// Request elevated permissions
await auth.rbac.elevateRole({{
target_role: 'admin',
duration_minutes: 30,
justification: 'Emergency system maintenance'
}});
// Check if user has elevated permissions
const hasElevated = await auth.rbac.hasPermission('elevated', 'admin');
```
## Advanced Usage
### Bulk Operations
```typescript
// Bulk role assignment
await auth.rbac.bulkAssignRoles({{
assignments: [
{{ user_id: 'user1', role_id: 'editor' }},
{{ user_id: 'user2', role_id: 'viewer' }},
{{ user_id: 'user3', role_id: 'admin', expires_at: '2024-12-31T23:59:59Z' }}
]
}});
```
### Error Handling
```typescript
import {{ HttpError, isAuthError, isRateLimitError }} from '@authframework/client';
try {{
await auth.rbac.createRole(roleData);
}} catch (error) {{
if (error instanceof HttpError) {{
if (isAuthError(error)) {{
// Handle authentication error
console.log('Authentication required');
}} else if (isRateLimitError(error)) {{
// Handle rate limit
console.log('Rate limit exceeded');
}} else {{
console.log('HTTP error:', error.status, error.statusText);
}}
}}
}}
```
### Custom Configuration
```typescript
const auth = new AuthFrameworkSdk({{
baseUrl: 'https://api.example.com',
accessToken: 'your-token',
timeout: 30000, // 30 second timeout
retryAttempts: 3, // Retry failed requests 3 times
}});
// Dynamic token updates
auth.setAccessToken('new-token');
// API key authentication
auth.setApiKey('your-api-key');
```
## Type Safety
Full TypeScript support with comprehensive type definitions:
```typescript
import type {{
Role,
Permission,
UserRolesResponse,
PermissionCheckResponse,
ConditionalContext
}} from '@authframework/client';
const role: Role = {{
id: 'role-123',
name: 'Editor',
description: 'Content editor role',
parent_id: 'base-user',
permissions: ['edit:content', 'read:content'],
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z'
}};
```
## Browser Support
- Chrome 60+
- Firefox 60+
- Safari 12+
- Edge 79+
## Node.js Support
- Node.js 14+
## License
MIT License - see LICENSE file for details.
## Support
- 📖 [Documentation](https://docs.authframework.com)
- 🐛 [Issue Tracker](https://github.com/authframework/client-js/issues)
- 💬 [Discord Community](https://discord.gg/authframework)
"#.to_string();
Ok(readme)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sdk_generation() {
let config = EnhancedSdkConfig::default();
let generator = JsSdkGenerator::new(config);
let result = generator.generate_sdk();
assert!(result.is_ok());
let files = result.unwrap();
assert!(files.contains_key("client.ts"));
assert!(files.contains_key("types.ts"));
assert!(files.contains_key("rbac.ts"));
assert!(files.contains_key("index.ts"));
assert!(files.contains_key("package.json"));
}
#[test]
fn test_typescript_generation() {
let config = EnhancedSdkConfig {
typescript: true,
include_rbac: true,
include_conditional_permissions: true,
include_audit: true,
..Default::default()
};
let generator = JsSdkGenerator::new(config);
let files = generator.generate_sdk().unwrap();
assert!(files.contains_key("types.ts"));
assert!(files.contains_key("rbac.ts"));
assert!(files.contains_key("conditional.ts"));
assert!(files.contains_key("audit.ts"));
}
}