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