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