Skip to main content

ri/c/
auth.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Auth Module C API
19//!
20//! This module provides C language bindings for Ri's authentication and authorization
21//! subsystem. The authentication module is responsible for handling user authentication,
22//! session management, permission verification, and OAuth authentication flows. This C API
23//! enables C/C++ applications to integrate with Ri's security features without requiring
24//! Rust runtime dependencies.
25//!
26//! ## Module Architecture
27//!
28//! The authentication module consists of five primary components:
29//!
30//! - **RiAuthConfig**: Centralized configuration container for authentication parameters.
31//!   Manages JWT secret keys, session timeouts, token expiration settings, and OAuth
32//!   provider configurations. This configuration object is required for initializing
33//!   authentication managers and controls security policy enforcement across the system.
34//!
35//! - **RiJWTManager**: JSON Web Token (JWT) generation and validation handler.
36//!   Provides token creation with custom claims, signature verification using HMAC-SHA256,
37//!   expiration checking, and audience validation. The JWT manager supports both access
38//!   tokens and refresh tokens with configurable expiration periods. It implements RFC 7519
39//!   specification for secure stateless authentication in distributed systems.
40//!
41//! - **RiSessionManager**: Server-side session state management for stateful authentication.
42//!   Maintains active user sessions in memory with configurable timeout policies. Supports
43//!   session creation, validation, renewal, and invalidation. The session manager uses
44//!   DashMap for thread-safe concurrent access in multi-threaded server environments.
45//!
46//! - **RiPermissionManager**: Role-based access control (RBAC) permission evaluator.
47//!   Manages user roles, permissions, and resource access policies. Supports hierarchical
48//!   role definitions with permission inheritance. The permission manager provides efficient
49//!   permission checking for high-throughput authorization decisions.
50//!
51//! - **RiOAuthManager**: OAuth 2.0 authentication flow handler for third-party integrations.
52//!   Implements authorization code flow for web applications, implicit flow for single-page
53//!   applications, and client credentials flow for machine-to-machine communication. Supports
54//!   multiple OAuth providers with configurable redirect URIs and scope requirements.
55//!
56//! ## Memory Management
57//!
58//! All C API objects use opaque pointers with manual memory management. The caller is
59//! responsible for freeing allocated objects using the provided destructor functions.
60//! Objects must not be used after being freed to prevent use-after-free vulnerabilities.
61//! Null pointer checks must be performed before accessing any object methods or fields.
62//!
63//! ## Thread Safety
64//!
65//! All underlying Rust implementations use synchronization primitives appropriate for
66//! concurrent access. The C API itself is not thread-safe; callers must implement their
67//! own synchronization when accessing objects from multiple threads simultaneously.
68//!
69//! ## Error Handling
70//!
71//! Functions return null pointers or error codes (-1) to indicate failure conditions.
72//! Callers should check return values and handle errors appropriately. Memory allocation
73//! failures and invalid arguments are the primary error conditions.
74//!
75//! ## Usage Example
76//!
77//! ```c
78//! // Create authentication configuration
79//! CRiAuthConfig* config = ri_auth_config_new();
80//!
81//! // Create JWT manager with secret and expiration
82//! CRiJWTManager* jwt = ri_jwt_manager_new("your-secret-key", 3600);
83//!
84//! // Generate token for authenticated user
85//! const char* token = ri_jwt_manager_generate(jwt, "user-id", "admin");
86//!
87//! // Validate token on subsequent requests
88//! bool valid = ri_jwt_manager_validate(jwt, token);
89//!
90//! // Cleanup resources
91//! ri_jwt_manager_free(jwt);
92//! ri_auth_config_free(config);
93//! ```
94//!
95//! ## Dependencies
96//!
97//! This module depends on the following core Ri modules:
98//!
99//! - `crate::auth`: Rust implementation of authentication logic
100//! - `crate::prelude`: Common types and traits
101//!
102//! ## Feature Flags
103//!
104//! The authentication module is enabled by default with the "auth" feature flag.
105//! Disable this feature to reduce binary size in deployments that do not require
106//! authentication capabilities.
107
108use crate::auth::{
109    RiAuthConfig, RiJWTManager, RiSessionManager, RiPermissionManager, RiOAuthManager,
110    RiRole,
111};
112use std::ffi::{c_char, c_int};
113use std::collections::HashSet;
114
115c_wrapper!(CRiAuthConfig, RiAuthConfig);
116c_wrapper!(CRiJWTManager, RiJWTManager);
117c_wrapper!(CRiSessionManager, RiSessionManager);
118c_wrapper!(CRiPermissionManager, RiPermissionManager);
119c_wrapper!(CRiOAuthManager, RiOAuthManager);
120
121c_constructor!(ri_auth_config_new, CRiAuthConfig, RiAuthConfig, RiAuthConfig::default());
122c_destructor!(ri_auth_config_free, CRiAuthConfig);
123
124#[no_mangle]
125pub extern "C" fn ri_jwt_manager_new(secret: *const c_char, expiry_secs: u64) -> *mut CRiJWTManager {
126    if secret.is_null() {
127        return std::ptr::null_mut();
128    }
129    unsafe {
130        let secret_str = match std::ffi::CStr::from_ptr(secret).to_str() {
131            Ok(s) => s,
132            Err(_) => return std::ptr::null_mut(),
133        };
134        let manager = RiJWTManager::create(secret_str.to_string(), expiry_secs);
135        Box::into_raw(Box::new(CRiJWTManager::new(manager)))
136    }
137}
138
139c_destructor!(ri_jwt_manager_free, CRiJWTManager);
140
141#[no_mangle]
142pub extern "C" fn ri_jwt_manager_generate(
143    manager: *mut CRiJWTManager,
144    user_id: *const c_char,
145    roles: *const *const c_char,
146    roles_count: usize,
147    permissions: *const *const c_char,
148    permissions_count: usize,
149    out_token: *mut *mut c_char,
150) -> c_int {
151    if manager.is_null() || user_id.is_null() || out_token.is_null() {
152        return -1;
153    }
154
155    unsafe {
156        let user_id_str = match std::ffi::CStr::from_ptr(user_id).to_str() {
157            Ok(s) => s,
158            Err(_) => return -2,
159        };
160
161        let roles_vec: Vec<String> = if roles.is_null() || roles_count == 0 {
162            Vec::new()
163        } else {
164            let roles_slice = std::slice::from_raw_parts(roles, roles_count);
165            roles_slice
166                .iter()
167                .filter_map(|&r| {
168                    if r.is_null() {
169                        None
170                    } else {
171                        std::ffi::CStr::from_ptr(r).to_str().ok().map(|s| s.to_string())
172                    }
173                })
174                .collect()
175        };
176
177        let permissions_vec: Vec<String> = if permissions.is_null() || permissions_count == 0 {
178            Vec::new()
179        } else {
180            let perms_slice = std::slice::from_raw_parts(permissions, permissions_count);
181            perms_slice
182                .iter()
183                .filter_map(|&p| {
184                    if p.is_null() {
185                        None
186                    } else {
187                        std::ffi::CStr::from_ptr(p).to_str().ok().map(|s| s.to_string())
188                    }
189                })
190                .collect()
191        };
192
193        match (*manager).inner.generate_token(user_id_str, roles_vec, permissions_vec) {
194            Ok(token) => {
195                match std::ffi::CString::new(token) {
196                    Ok(c_token) => {
197                        *out_token = c_token.into_raw();
198                        0
199                    }
200                    Err(_) => -4,
201                }
202            }
203            Err(_) => -3,
204        }
205    }
206}
207
208#[repr(C)]
209pub struct CRiJWTClaims {
210    pub sub: *mut c_char,
211    pub exp: u64,
212    pub iat: u64,
213}
214
215#[no_mangle]
216pub extern "C" fn ri_jwt_manager_validate(
217    manager: *mut CRiJWTManager,
218    token: *const c_char,
219    out_claims: *mut CRiJWTClaims,
220) -> c_int {
221    if manager.is_null() || token.is_null() || out_claims.is_null() {
222        return -1;
223    }
224
225    unsafe {
226        let token_str = match std::ffi::CStr::from_ptr(token).to_str() {
227            Ok(s) => s,
228            Err(_) => return -2,
229        };
230
231        match (*manager).inner.validate_token(token_str) {
232            Ok(claims) => {
233                let sub = match std::ffi::CString::new(claims.sub) {
234                    Ok(s) => s.into_raw(),
235                    Err(_) => return -4,
236                };
237
238                *out_claims = CRiJWTClaims {
239                    sub,
240                    exp: claims.exp,
241                    iat: claims.iat,
242                };
243                0
244            }
245            Err(_) => -3,
246        }
247    }
248}
249
250#[no_mangle]
251pub extern "C" fn ri_jwt_claims_free(claims: *mut CRiJWTClaims) {
252    if claims.is_null() {
253        return;
254    }
255
256    unsafe {
257        let claims = Box::from_raw(claims);
258        if !claims.sub.is_null() {
259            let _ = std::ffi::CString::from_raw(claims.sub);
260        }
261    }
262}
263
264#[no_mangle]
265pub extern "C" fn ri_session_manager_new(timeout_secs: u64) -> *mut CRiSessionManager {
266    let manager = RiSessionManager::new(timeout_secs);
267    Box::into_raw(Box::new(CRiSessionManager::new(manager)))
268}
269
270c_destructor!(ri_session_manager_free, CRiSessionManager);
271
272#[no_mangle]
273pub extern "C" fn ri_session_manager_create(
274    manager: *mut CRiSessionManager,
275    user_id: *const c_char,
276    ip_address: *const c_char,
277    user_agent: *const c_char,
278    out_session_id: *mut *mut c_char,
279) -> c_int {
280    if manager.is_null() || user_id.is_null() || out_session_id.is_null() {
281        return -1;
282    }
283
284    unsafe {
285        let user_id_str = match std::ffi::CStr::from_ptr(user_id).to_str() {
286            Ok(s) => s,
287            Err(_) => return -2,
288        };
289
290        let ip_str = if ip_address.is_null() {
291            None
292        } else {
293            std::ffi::CStr::from_ptr(ip_address).to_str().ok().map(|s| s.to_string())
294        };
295
296        let ua_str = if user_agent.is_null() {
297            None
298        } else {
299            std::ffi::CStr::from_ptr(user_agent).to_str().ok().map(|s| s.to_string())
300        };
301
302        let rt = match tokio::runtime::Runtime::new() {
303            Ok(r) => r,
304            Err(_) => return -3,
305        };
306
307        let result = rt.block_on(async {
308            (*manager).inner.create_session(user_id_str.to_string(), ip_str, ua_str).await
309        });
310
311        match result {
312            Ok(session_id) => {
313                match std::ffi::CString::new(session_id) {
314                    Ok(c_id) => {
315                        *out_session_id = c_id.into_raw();
316                        0
317                    }
318                    Err(_) => -5,
319                }
320            }
321            Err(_) => -4,
322        }
323    }
324}
325
326#[repr(C)]
327pub struct CRiSession {
328    pub id: *mut c_char,
329    pub user_id: *mut c_char,
330    pub created_at: u64,
331    pub expires_at: u64,
332}
333
334#[no_mangle]
335pub extern "C" fn ri_session_manager_get(
336    manager: *mut CRiSessionManager,
337    session_id: *const c_char,
338    out_session: *mut CRiSession,
339) -> c_int {
340    if manager.is_null() || session_id.is_null() || out_session.is_null() {
341        return -1;
342    }
343
344    unsafe {
345        let session_id_str = match std::ffi::CStr::from_ptr(session_id).to_str() {
346            Ok(s) => s,
347            Err(_) => return -2,
348        };
349
350        let rt = match tokio::runtime::Runtime::new() {
351            Ok(r) => r,
352            Err(_) => return -3,
353        };
354
355        let result = rt.block_on(async {
356            (*manager).inner.get_session(session_id_str).await
357        });
358
359        match result {
360            Ok(Some(session)) => {
361                let id = match std::ffi::CString::new(session.id) {
362                    Ok(s) => s.into_raw(),
363                    Err(_) => return -5,
364                };
365
366                let user_id = match std::ffi::CString::new(session.user_id) {
367                    Ok(s) => s.into_raw(),
368                    Err(_) => {
369                        let _ = std::ffi::CString::from_raw(id);
370                        return -6;
371                    }
372                };
373
374                *out_session = CRiSession {
375                    id,
376                    user_id,
377                    created_at: session.created_at,
378                    expires_at: session.expires_at,
379                };
380                0
381            }
382            Ok(None) => 1,
383            Err(_) => -4,
384        }
385    }
386}
387
388#[no_mangle]
389pub extern "C" fn ri_session_free(session: *mut CRiSession) {
390    if session.is_null() {
391        return;
392    }
393
394    unsafe {
395        let session = Box::from_raw(session);
396        if !session.id.is_null() {
397            let _ = std::ffi::CString::from_raw(session.id);
398        }
399        if !session.user_id.is_null() {
400            let _ = std::ffi::CString::from_raw(session.user_id);
401        }
402    }
403}
404
405#[no_mangle]
406pub extern "C" fn ri_session_manager_destroy(
407    manager: *mut CRiSessionManager,
408    session_id: *const c_char,
409) -> c_int {
410    if manager.is_null() || session_id.is_null() {
411        return -1;
412    }
413
414    unsafe {
415        let session_id_str = match std::ffi::CStr::from_ptr(session_id).to_str() {
416            Ok(s) => s,
417            Err(_) => return -2,
418        };
419
420        let rt = match tokio::runtime::Runtime::new() {
421            Ok(r) => r,
422            Err(_) => return -3,
423        };
424
425        let result = rt.block_on(async {
426            (*manager).inner.destroy_session(session_id_str).await
427        });
428
429        match result {
430            Ok(destroyed) => if destroyed { 0 } else { 1 },
431            Err(_) => -4,
432        }
433    }
434}
435
436#[no_mangle]
437pub extern "C" fn ri_permission_manager_new() -> *mut CRiPermissionManager {
438    let manager = RiPermissionManager::new();
439    Box::into_raw(Box::new(CRiPermissionManager::new(manager)))
440}
441
442c_destructor!(ri_permission_manager_free, CRiPermissionManager);
443
444#[no_mangle]
445pub extern "C" fn ri_permission_manager_create_role(
446    manager: *mut CRiPermissionManager,
447    role_id: *const c_char,
448    role_name: *const c_char,
449    description: *const c_char,
450    permissions: *const *const c_char,
451    permissions_count: usize,
452) -> c_int {
453    if manager.is_null() || role_id.is_null() || role_name.is_null() {
454        return -1;
455    }
456
457    unsafe {
458        let role_id_str = match std::ffi::CStr::from_ptr(role_id).to_str() {
459            Ok(s) => s,
460            Err(_) => return -2,
461        };
462
463        let role_name_str = match std::ffi::CStr::from_ptr(role_name).to_str() {
464            Ok(s) => s,
465            Err(_) => return -3,
466        };
467
468        let desc_str = if description.is_null() {
469            ""
470        } else {
471            match std::ffi::CStr::from_ptr(description).to_str() {
472                Ok(s) => s,
473                Err(_) => "",
474            }
475        };
476
477        let perms_set: HashSet<String> = if permissions.is_null() || permissions_count == 0 {
478            HashSet::new()
479        } else {
480            let perms_slice = std::slice::from_raw_parts(permissions, permissions_count);
481            perms_slice
482                .iter()
483                .filter_map(|&p| {
484                    if p.is_null() {
485                        None
486                    } else {
487                        std::ffi::CStr::from_ptr(p).to_str().ok().map(|s| s.to_string())
488                    }
489                })
490                .collect()
491        };
492
493        let role = RiRole::new(
494            role_id_str.to_string(),
495            role_name_str.to_string(),
496            desc_str.to_string(),
497            perms_set,
498        );
499
500        let rt = match tokio::runtime::Runtime::new() {
501            Ok(r) => r,
502            Err(_) => return -4,
503        };
504
505        let result = rt.block_on(async {
506            (*manager).inner.create_role(role).await
507        });
508
509        match result {
510            Ok(()) => 0,
511            Err(_) => -5,
512        }
513    }
514}
515
516#[no_mangle]
517pub extern "C" fn ri_permission_manager_assign_role(
518    manager: *mut CRiPermissionManager,
519    user_id: *const c_char,
520    role_id: *const c_char,
521) -> c_int {
522    if manager.is_null() || user_id.is_null() || role_id.is_null() {
523        return -1;
524    }
525
526    unsafe {
527        let user_id_str = match std::ffi::CStr::from_ptr(user_id).to_str() {
528            Ok(s) => s,
529            Err(_) => return -2,
530        };
531
532        let role_id_str = match std::ffi::CStr::from_ptr(role_id).to_str() {
533            Ok(s) => s,
534            Err(_) => return -3,
535        };
536
537        let rt = match tokio::runtime::Runtime::new() {
538            Ok(r) => r,
539            Err(_) => return -4,
540        };
541
542        let result = rt.block_on(async {
543            (*manager).inner.assign_role_to_user(user_id_str.to_string(), role_id_str.to_string()).await
544        });
545
546        match result {
547            Ok(assigned) => if assigned { 0 } else { 1 },
548            Err(_) => -5,
549        }
550    }
551}
552
553#[no_mangle]
554pub extern "C" fn ri_permission_manager_has_permission(
555    manager: *mut CRiPermissionManager,
556    user_id: *const c_char,
557    permission_id: *const c_char,
558) -> c_int {
559    if manager.is_null() || user_id.is_null() || permission_id.is_null() {
560        return -1;
561    }
562
563    unsafe {
564        let user_id_str = match std::ffi::CStr::from_ptr(user_id).to_str() {
565            Ok(s) => s,
566            Err(_) => return -2,
567        };
568
569        let perm_id_str = match std::ffi::CStr::from_ptr(permission_id).to_str() {
570            Ok(s) => s,
571            Err(_) => return -3,
572        };
573
574        let rt = match tokio::runtime::Runtime::new() {
575            Ok(r) => r,
576            Err(_) => return -4,
577        };
578
579        let result = rt.block_on(async {
580            (*manager).inner.has_permission(user_id_str, perm_id_str).await
581        });
582
583        match result {
584            Ok(has_perm) => if has_perm { 1 } else { 0 },
585            Err(_) => -5,
586        }
587    }
588}
589
590#[no_mangle]
591pub extern "C" fn ri_permission_manager_remove_role(
592    manager: *mut CRiPermissionManager,
593    user_id: *const c_char,
594    role_id: *const c_char,
595) -> c_int {
596    if manager.is_null() || user_id.is_null() || role_id.is_null() {
597        return -1;
598    }
599
600    unsafe {
601        let user_id_str = match std::ffi::CStr::from_ptr(user_id).to_str() {
602            Ok(s) => s,
603            Err(_) => return -2,
604        };
605
606        let role_id_str = match std::ffi::CStr::from_ptr(role_id).to_str() {
607            Ok(s) => s,
608            Err(_) => return -3,
609        };
610
611        let rt = match tokio::runtime::Runtime::new() {
612            Ok(r) => r,
613            Err(_) => return -4,
614        };
615
616        let result = rt.block_on(async {
617            (*manager).inner.remove_role_from_user(user_id_str, role_id_str).await
618        });
619
620        match result {
621            Ok(removed) => if removed { 0 } else { 1 },
622            Err(_) => -5,
623        }
624    }
625}
626
627#[no_mangle]
628pub extern "C" fn ri_permission_manager_delete_role(
629    manager: *mut CRiPermissionManager,
630    role_id: *const c_char,
631) -> c_int {
632    if manager.is_null() || role_id.is_null() {
633        return -1;
634    }
635
636    unsafe {
637        let role_id_str = match std::ffi::CStr::from_ptr(role_id).to_str() {
638            Ok(s) => s,
639            Err(_) => return -2,
640        };
641
642        let rt = match tokio::runtime::Runtime::new() {
643            Ok(r) => r,
644            Err(_) => return -3,
645        };
646
647        let result = rt.block_on(async {
648            (*manager).inner.delete_role(role_id_str).await
649        });
650
651        match result {
652            Ok(deleted) => if deleted { 0 } else { 1 },
653            Err(_) => -4,
654        }
655    }
656}