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