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