1pub mod core;
29pub mod handlers;
30
31pub use better_auth_core as types_mod;
33pub use better_auth_core::{
34 Account, Argon2Config, AuthConfig, AuthContext, AuthError, AuthPlugin, AuthRequest,
35 AuthResponse, AuthResult, AuthRoute, BodyLimitConfig, BodyLimitMiddleware, CacheAdapter,
36 ConsoleEmailProvider, CorsConfig, CorsMiddleware, CreateAccount, CreateSession, CreateUser,
37 CreateVerification, CsrfConfig, CsrfMiddleware, DatabaseAdapter, DatabaseError, DatabaseHooks,
38 DeleteUserResponse, EmailProvider, EndpointRateLimit, HookedDatabaseAdapter, HttpMethod,
39 JwtConfig, MemoryCacheAdapter, MemoryDatabaseAdapter, Middleware, OpenApiBuilder, OpenApiSpec,
40 Passkey, PasswordConfig, RateLimitConfig, RateLimitMiddleware, SameSite, Session,
41 SessionConfig, SessionManager, TwoFactor, UpdateUser, UpdateUserRequest, UpdateUserResponse,
42 User, Verification,
43};
44
45pub use better_auth_core::entity::{
47 AuthAccount, AuthInvitation, AuthMember, AuthOrganization, AuthPasskey, AuthSession,
48 AuthTwoFactor, AuthUser, AuthVerification, MemberUserView,
49};
50
51pub mod types {
53 pub use better_auth_core::{
54 Account, AuthRequest, AuthResponse, CreateAccount, CreateSession, CreateUser,
55 CreateVerification, DeleteUserResponse, HttpMethod, Passkey, Session, TwoFactor,
56 UpdateUser, UpdateUserRequest, UpdateUserResponse, User, Verification,
57 };
58}
59
60pub mod adapters {
62 pub use better_auth_core::{
63 CacheAdapter, DatabaseAdapter, MemoryCacheAdapter, MemoryDatabaseAdapter,
64 };
65
66 #[cfg(feature = "sqlx-postgres")]
67 pub use better_auth_core::SqlxAdapter;
68}
69
70pub mod plugins {
72 pub use better_auth_api::plugins::*;
73 pub use better_auth_api::*;
74}
75
76pub use core::{AuthBuilder, BetterAuth, TypedAuthBuilder};
78
79#[cfg(feature = "axum")]
80pub use handlers::axum::AxumIntegration;
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use serde_json::json;
86
87 fn test_config() -> AuthConfig {
88 AuthConfig::new("test-secret-key-that-is-at-least-32-characters-long")
89 .base_url("http://localhost:3000")
90 .password_min_length(8)
91 }
92
93 async fn create_test_auth() -> BetterAuth<MemoryDatabaseAdapter> {
94 AuthBuilder::new(test_config())
95 .database(MemoryDatabaseAdapter::new())
96 .plugin(plugins::EmailPasswordPlugin::new().enable_signup(true))
97 .build()
98 .await
99 .expect("Failed to create test auth instance")
100 }
101
102 #[tokio::test]
103 async fn test_auth_builder() {
104 let auth = create_test_auth().await;
105 assert_eq!(auth.plugin_names(), vec!["email-password"]);
106 assert_eq!(
107 auth.config().secret,
108 "test-secret-key-that-is-at-least-32-characters-long"
109 );
110 }
111
112 #[tokio::test]
113 async fn test_signup_flow() {
114 let auth = create_test_auth().await;
115
116 let signup_data = json!({
117 "email": "test@example.com",
118 "password": "password123",
119 "name": "Test User"
120 });
121
122 let mut request = AuthRequest::new(HttpMethod::Post, "/sign-up/email");
123 request.body = Some(signup_data.to_string().into_bytes());
124 request
125 .headers
126 .insert("content-type".to_string(), "application/json".to_string());
127
128 let response = auth
129 .handle_request(request)
130 .await
131 .expect("Signup request failed");
132
133 assert_eq!(response.status, 200);
134
135 let response_json: serde_json::Value =
136 serde_json::from_slice(&response.body).expect("Failed to parse response JSON");
137
138 assert!(response_json["user"]["id"].is_string());
139 assert_eq!(response_json["user"]["email"], "test@example.com");
140 assert_eq!(response_json["user"]["name"], "Test User");
141 assert!(response_json["token"].is_string());
142 }
143
144 #[tokio::test]
145 async fn test_signin_flow() {
146 let auth = create_test_auth().await;
147
148 let signup_data = json!({
149 "email": "signin@example.com",
150 "password": "password123",
151 "name": "Signin User"
152 });
153
154 let mut signup_request = AuthRequest::new(HttpMethod::Post, "/sign-up/email");
155 signup_request.body = Some(signup_data.to_string().into_bytes());
156 signup_request
157 .headers
158 .insert("content-type".to_string(), "application/json".to_string());
159
160 let signup_response = auth
161 .handle_request(signup_request)
162 .await
163 .expect("Signup failed");
164 assert_eq!(signup_response.status, 200);
165
166 let signin_data = json!({
167 "email": "signin@example.com",
168 "password": "password123"
169 });
170
171 let mut signin_request = AuthRequest::new(HttpMethod::Post, "/sign-in/email");
172 signin_request.body = Some(signin_data.to_string().into_bytes());
173 signin_request
174 .headers
175 .insert("content-type".to_string(), "application/json".to_string());
176
177 let signin_response = auth
178 .handle_request(signin_request)
179 .await
180 .expect("Signin failed");
181 assert_eq!(signin_response.status, 200);
182
183 let response_json: serde_json::Value =
184 serde_json::from_slice(&signin_response.body).expect("Failed to parse signin response");
185
186 assert_eq!(response_json["user"]["email"], "signin@example.com");
187 assert!(response_json["token"].is_string());
188 }
189
190 #[tokio::test]
191 async fn test_duplicate_email_signup() {
192 let auth = create_test_auth().await;
193
194 let signup_data = json!({
195 "name": "Duplicate User",
196 "email": "duplicate@example.com",
197 "password": "password123"
198 });
199
200 let mut request = AuthRequest::new(HttpMethod::Post, "/sign-up/email");
201 request.body = Some(signup_data.to_string().into_bytes());
202 request
203 .headers
204 .insert("content-type".to_string(), "application/json".to_string());
205
206 let response1 = auth
207 .handle_request(request.clone())
208 .await
209 .expect("First signup failed");
210 assert_eq!(response1.status, 200);
211
212 let response2 = auth
213 .handle_request(request)
214 .await
215 .expect("Second signup request failed");
216 assert_eq!(response2.status, 409);
217 }
218
219 #[tokio::test]
220 async fn test_invalid_credentials_signin() {
221 let auth = create_test_auth().await;
222
223 let signin_data = json!({
224 "email": "nonexistent@example.com",
225 "password": "password123"
226 });
227
228 let mut request = AuthRequest::new(HttpMethod::Post, "/sign-in/email");
229 request.body = Some(signin_data.to_string().into_bytes());
230 request
231 .headers
232 .insert("content-type".to_string(), "application/json".to_string());
233
234 let response = auth
235 .handle_request(request)
236 .await
237 .expect("Request should not panic");
238 assert_eq!(response.status, 401);
239 }
240
241 #[tokio::test]
242 async fn test_weak_password_validation() {
243 let auth = create_test_auth().await;
244
245 let signup_data = json!({
246 "email": "weak@example.com",
247 "password": "123",
248 "name": "Weak Password User"
249 });
250
251 let mut request = AuthRequest::new(HttpMethod::Post, "/sign-up/email");
252 request.body = Some(signup_data.to_string().into_bytes());
253 request
254 .headers
255 .insert("content-type".to_string(), "application/json".to_string());
256
257 let response = auth
258 .handle_request(request)
259 .await
260 .expect("Request should not panic");
261 assert_eq!(response.status, 400);
262
263 let response_json: serde_json::Value =
264 serde_json::from_slice(&response.body).expect("Failed to parse response");
265 assert!(
266 response_json["message"]
267 .as_str()
268 .unwrap_or("")
269 .contains("Password must be at least")
270 );
271 }
272
273 #[tokio::test]
274 async fn test_session_management() {
275 let auth = create_test_auth().await;
276 let session_manager = auth.session_manager();
277
278 let database = auth.database();
279 let create_user = CreateUser::new()
280 .with_email("session@example.com")
281 .with_name("Session User");
282
283 let user = database
284 .create_user(create_user)
285 .await
286 .expect("Failed to create user");
287
288 let session = session_manager
289 .create_session(&user, None, None)
290 .await
291 .expect("Failed to create session");
292
293 assert!(session.token.starts_with("session_"));
294 assert_eq!(session.user_id, user.id);
295 assert!(session.active);
296
297 let retrieved_session = session_manager
298 .get_session(&session.token)
299 .await
300 .expect("Failed to get session")
301 .expect("Session not found");
302
303 assert_eq!(retrieved_session.id, session.id);
304 assert_eq!(retrieved_session.user_id, user.id);
305
306 session_manager
307 .delete_session(&session.token)
308 .await
309 .expect("Failed to delete session");
310
311 let deleted_session = session_manager
312 .get_session(&session.token)
313 .await
314 .expect("Failed to check deleted session");
315 assert!(deleted_session.is_none());
316 }
317
318 #[tokio::test]
319 async fn test_token_format_validation() {
320 let auth = create_test_auth().await;
321 let session_manager = auth.session_manager();
322
323 assert!(
324 session_manager
325 .validate_token_format("session_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN")
326 );
327 assert!(!session_manager.validate_token_format("invalid_token"));
328 assert!(!session_manager.validate_token_format("session_short"));
329 assert!(!session_manager.validate_token_format(""));
330 }
331
332 #[tokio::test]
333 async fn test_health_check_route() {
334 let auth = create_test_auth().await;
335
336 let request = AuthRequest::new(HttpMethod::Get, "/health");
337 let response = auth
338 .handle_request(request)
339 .await
340 .expect("Health check failed");
341
342 assert_eq!(response.status, 404);
343 }
344
345 #[tokio::test]
346 async fn test_config_validation() {
347 let config = AuthConfig::new("");
348 assert!(config.validate().is_err());
349
350 let config = AuthConfig::new("short");
351 assert!(config.validate().is_err());
352
353 let config = AuthConfig::new("this-is-a-valid-32-character-secret-key");
354 assert!(config.validate().is_ok());
355 }
356}