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