Skip to main content

alibi_plugins/
lib.rs

1//! Built-in authentication plugins for Alibi.
2
3#![cfg_attr(
4    test,
5    allow(
6        unused_results,
7        unreachable_pub,
8        reason = "test code intentionally discards setup return values and exposes helpers broadly"
9    )
10)]
11
12#[cfg(all(feature = "native-tls", feature = "rustls"))]
13compile_error!(
14    "features `native-tls` and `rustls` are mutually exclusive. \
15     Enable exactly one of them: \
16     for `native-tls` (default), remove the `rustls` feature; \
17     for `rustls`, set `default-features = false, features = [\"rustls\"]`."
18);
19
20#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
21compile_error!(
22    "one of the TLS backends must be enabled: \
23     enable either the `native-tls` (default) or `rustls` feature."
24);
25
26/// Expand to the `OpenAPI` metadata methods of a plugin whose documentation
27/// comes entirely from its registered routes.
28macro_rules! route_openapi_metadata {
29    ($schema:ident) => {
30        fn static_openapi_metadata(&self) -> alibi_core::PluginOpenApiMetadata {
31            crate::metadata::plugin_metadata(
32                <Self as alibi_core::AuthPlugin<$schema>>::name(self),
33                &<Self as alibi_core::AuthPlugin<$schema>>::routes(self),
34            )
35        }
36
37        fn openapi_metadata(
38            &self,
39            ctx: &alibi_core::AuthInitContext<$schema>,
40        ) -> alibi_core::PluginOpenApiMetadata {
41            crate::metadata::instance_plugin_metadata(
42                <Self as alibi_core::AuthPlugin<$schema>>::name(self),
43                &<Self as alibi_core::AuthPlugin<$schema>>::routes(self),
44                ctx,
45            )
46        }
47    };
48}
49
50pub mod access;
51pub mod account_management;
52pub mod admin;
53pub mod anonymous;
54pub mod api_key;
55pub(crate) mod authentication_helpers;
56pub mod bearer;
57pub mod captcha;
58pub mod custom_session;
59pub mod device_authorization;
60pub mod email_otp;
61pub mod email_password;
62pub mod email_verification;
63mod endpoint;
64pub mod haveibeenpwned;
65pub mod helpers;
66pub mod jwt;
67pub mod last_login_method;
68pub mod magic_link;
69pub mod metadata;
70pub mod multi_session;
71pub mod oauth;
72pub mod oauth_popup;
73pub mod oauth_provider;
74pub mod oauth_proxy;
75pub mod one_tap;
76pub mod one_time_token;
77pub mod open_api;
78pub mod organization;
79#[cfg(feature = "passkey")]
80pub mod passkey;
81pub mod password_management;
82mod passwordless_numeric;
83pub mod phone_number;
84pub mod session_management;
85pub mod siwe;
86pub(crate) mod token_crypto;
87pub mod two_factor;
88pub mod user_management;
89
90// LCOV_EXCL_START
91#[cfg(test)]
92pub(crate) mod test_helpers {
93    use alibi_core::config::AuthConfig;
94    use alibi_core::wire::{SessionView, UserView};
95    use alibi_core::{AuthContext, AuthRequest, CreateSession, CreateUser, HttpMethod};
96    use alibi_seaorm::store::__private_test_support::bundled_schema::BundledSchema;
97    use alibi_seaorm::{Database, SeaOrmStore};
98    use chrono::{Duration, Utc};
99    use std::collections::HashMap;
100    use std::sync::Arc;
101
102    pub type TestDatabase = dyn alibi_core::store::AuthStore<BundledSchema>;
103
104    pub fn create_test_config() -> AuthConfig {
105        AuthConfig::new("test-secret-key-at-least-32-chars-long")
106    }
107
108    pub async fn create_test_database() -> Arc<TestDatabase> {
109        let database = Database::connect("sqlite::memory:")
110            .await
111            .expect("sqlite test database should connect");
112        alibi_seaorm::store::__private_test_support::migrator::run_migrations(&database)
113            .await
114            .expect("sqlite test migrations should run");
115        Arc::new(SeaOrmStore::<BundledSchema>::new(
116            Arc::new(create_test_config()),
117            database,
118        ))
119    }
120
121    pub async fn create_test_context() -> AuthContext<BundledSchema> {
122        create_test_context_with_config(create_test_config()).await
123    }
124
125    pub fn create_test_context_blocking() -> AuthContext<BundledSchema> {
126        tokio::runtime::Builder::new_current_thread()
127            .enable_all()
128            .build()
129            .expect("test runtime should build")
130            .block_on(create_test_context())
131    }
132
133    pub async fn create_test_context_with_config(config: AuthConfig) -> AuthContext<BundledSchema> {
134        let config = Arc::new(config);
135        let database = create_test_database().await;
136        AuthContext::new(config, database)
137    }
138
139    pub async fn create_user(
140        ctx: &AuthContext<impl alibi_core::AuthSchema>,
141        create_user: CreateUser,
142    ) -> UserView {
143        let user = ctx.database.create_user(create_user).await.unwrap();
144        UserView::from(&user)
145    }
146
147    pub async fn create_session(
148        ctx: &AuthContext<impl alibi_core::AuthSchema>,
149        user_id: String,
150        expires_in: Duration,
151    ) -> SessionView {
152        let create_session = CreateSession {
153            additional_fields: alibi_core::field_policy::FieldValues::default(),
154            token: None,
155            active_team_id: None,
156            user_id,
157            expires_at: Utc::now() + expires_in,
158            ip_address: Some("127.0.0.1".to_owned()),
159            user_agent: Some("test-agent".to_owned()),
160            impersonated_by: None,
161            active_organization_id: None,
162        };
163        let session = ctx.database.create_session(create_session).await.unwrap();
164        SessionView::from(&session)
165    }
166
167    pub async fn create_user_and_session(
168        ctx: &AuthContext<impl alibi_core::AuthSchema>,
169        user_data: CreateUser,
170        session_expires_in: Duration,
171    ) -> (UserView, SessionView) {
172        let user = create_user(ctx, user_data).await;
173        let session = create_session(ctx, user.id.clone(), session_expires_in).await;
174        (user, session)
175    }
176
177    pub async fn create_test_context_with_user(
178        create_user: CreateUser,
179        session_expires_in: Duration,
180    ) -> (AuthContext<BundledSchema>, UserView, SessionView) {
181        let ctx = create_test_context().await;
182        let (user, session) = create_user_and_session(&ctx, create_user, session_expires_in).await;
183        (ctx, user, session)
184    }
185
186    pub fn create_auth_request(
187        method: HttpMethod,
188        path: &str,
189        token: Option<&str>,
190        body: Option<Vec<u8>>,
191        query: HashMap<String, String>,
192    ) -> AuthRequest {
193        let mut headers = HashMap::new();
194        if let Some(token) = token {
195            let config = create_test_config();
196            headers.insert(
197                "cookie".to_owned(),
198                format!(
199                    "{}={}",
200                    config.session.cookie_name,
201                    alibi_core::utils::cookie_utils::sign_cookie_value(
202                        token,
203                        config.current_secret()
204                    )
205                ),
206            );
207        }
208
209        AuthRequest::from_parts(method, path.to_owned(), headers, body, query)
210    }
211
212    pub fn create_auth_request_no_query(
213        method: HttpMethod,
214        path: &str,
215        token: Option<&str>,
216        body: Option<Vec<u8>>,
217    ) -> AuthRequest {
218        create_auth_request(method, path, token, body, HashMap::new())
219    }
220
221    pub fn create_auth_json_request_no_query(
222        method: HttpMethod,
223        path: &str,
224        token: Option<&str>,
225        body: Option<serde_json::Value>,
226    ) -> AuthRequest {
227        create_auth_json_request(method, path, token, body, HashMap::new())
228    }
229
230    pub fn create_auth_json_request(
231        method: HttpMethod,
232        path: &str,
233        token: Option<&str>,
234        body: Option<serde_json::Value>,
235        query: HashMap<String, String>,
236    ) -> AuthRequest {
237        let mut req = create_auth_request(
238            method,
239            path,
240            token,
241            body.map(|b| serde_json::to_vec(&b).unwrap()),
242            query,
243        );
244        req.headers
245            .insert("content-type".to_owned(), "application/json".to_owned());
246        req
247    }
248}
249// LCOV_EXCL_STOP
250
251pub use account_management::AccountManagementPlugin;
252pub use admin::{
253    AdminBanUserRequest, AdminBannedUserMessage, AdminBannedUserMessageHandler, AdminConfig,
254    AdminCreateUserRequest, AdminImpersonationResponse, AdminListUsersQuery,
255    AdminListUsersResponse, AdminPermissionRequest, AdminPermissionResponse, AdminPlugin,
256    AdminRoleInput, AdminSetRoleRequest, AdminSetUserPasswordRequest, AdminSuccessResponse,
257    AdminUpdateUserRequest, AdminUserOutput, AdminUserResponse, AdminUserSessionsResponse,
258    RolePermissions,
259};
260pub use alibi_core::PasswordHasher;
261pub use anonymous::{
262    AnonymousConfig, AnonymousIdentity, AnonymousLink, AnonymousPlugin, LinkAnonymousAccount,
263};
264pub use api_key::{
265    ApiKeyCallbackContext, ApiKeyConfig, ApiKeyDefaultPermissions, ApiKeyErrorMessage,
266    ApiKeyGenerationOptions, ApiKeyGenerator, ApiKeyGetter, ApiKeyPermissions, ApiKeyPlugin,
267    ApiKeyValidator, DeleteExpiredApiKeysResponse,
268};
269pub use bearer::{BearerConfig, BearerPlugin};
270pub use captcha::{CaptchaConfig, CaptchaPlugin, CaptchaProvider};
271pub use custom_session::{CustomSessionPlugin, SessionTransform};
272pub use device_authorization::DeviceAuthorizationPlugin;
273pub use email_otp::{EmailOtpConfig, EmailOtpPlugin, SendEmailOtp};
274pub use email_password::{EmailPasswordConfig, EmailPasswordPlugin};
275pub use email_verification::{
276    EmailVerificationConfig, EmailVerificationHook, EmailVerificationPlugin, SendVerificationEmail,
277};
278pub use haveibeenpwned::{HaveIBeenPwnedConfig, HaveIBeenPwnedPlugin, PwnedPasswordClient};
279pub use last_login_method::{
280    BeforeStoreLastLoginMethodCookie, LastLoginMethodConfig, LastLoginMethodContext,
281    LastLoginMethodPlugin, ResolveLastLoginMethod,
282};
283pub use magic_link::{MagicLinkConfig, MagicLinkPlugin, SendMagicLink};
284pub use multi_session::{MultiSessionConfig, MultiSessionPlugin};
285pub use oauth::OAuthPlugin;
286pub use oauth_popup::OAuthPopupPlugin;
287pub use oauth_provider::{OAuthProviderConfig, OAuthProviderPlugin};
288pub use oauth_proxy::{OAuthProxyConfig, OAuthProxyPlugin};
289pub use one_tap::{OAuthJwksSource, OneTapClientId, OneTapConfig, OneTapPlugin};
290pub use open_api::{OpenApiConfig, OpenApiPlugin};
291pub use organization::{
292    OrganizationConfig, OrganizationCreatePatch, OrganizationCreatedContext,
293    OrganizationCreationHooks, OrganizationDraftContext, OrganizationMemberCreatePatch,
294    OrganizationMemberDraftContext, OrganizationPlugin,
295};
296#[cfg(feature = "passkey")]
297pub use passkey::{
298    AuthenticationResult, PasskeyAuthenticationAfterVerification, PasskeyAuthenticationConfig,
299    PasskeyAuthenticationContext, PasskeyAuthenticatorSelection, PasskeyConfig, PasskeyExtensions,
300    PasskeyExtensionsResolver, PasskeyOptionsContext, PasskeyPlugin,
301    PasskeyRegistrationAfterVerification, PasskeyRegistrationConfig, PasskeyRegistrationContext,
302    PasskeyRegistrationOverride, PasskeyRegistrationUser, PasskeyUserResolver,
303    VerifiedPasskeyAuthentication, VerifiedPasskeyRegistration,
304};
305pub use password_management::{
306    PasswordManagementConfig, PasswordManagementPlugin, SendResetPassword,
307};
308pub use phone_number::{PhoneNumberConfig, PhoneNumberPlugin, SendPhoneOtp};
309pub use session_management::{GetSessionQuery, GetSessionResponse, SessionManagementPlugin};
310pub use siwe::{SiweConfig, SiwePlugin};
311pub use two_factor::{
312    SendTwoFactorOtp, TwoFactorBackupCipher, TwoFactorBackupStorage, TwoFactorConfig,
313    TwoFactorOtpCipher, TwoFactorOtpHasher, TwoFactorOtpStorage, TwoFactorPlugin,
314};
315pub use user_management::{
316    ChangeEmailConfig, DeleteUserConfig, SendChangeEmailConfirmation, UserManagementConfig,
317    UserManagementPlugin,
318};
319
320#[derive(Debug, serde::Serialize, serde::Deserialize)]
321pub(crate) struct StatusResponse {
322    status: bool,
323}