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 oauth2;
73pub mod oauth_popup;
74pub mod oauth_provider;
75pub mod oauth_proxy;
76pub mod one_tap;
77pub mod one_time_token;
78pub mod open_api;
79pub mod organization;
80#[cfg(feature = "passkey")]
81pub mod passkey;
82pub mod password_management;
83mod passwordless_numeric;
84pub mod phone_number;
85pub mod scim;
86pub mod session_management;
87pub mod siwe;
88pub mod sso;
89pub(crate) mod token_crypto;
90pub mod two_factor;
91pub mod user_management;
92
93// LCOV_EXCL_START
94#[cfg(test)]
95pub(crate) mod test_helpers {
96    use alibi_core::config::AuthConfig;
97    use alibi_core::wire::{SessionView, UserView};
98    use alibi_core::{AuthContext, AuthRequest, CreateSession, CreateUser, HttpMethod};
99    use alibi_seaorm::store::__private_test_support::bundled_schema::BundledSchema;
100    use alibi_seaorm::{Database, SeaOrmStore};
101    use chrono::{Duration, Utc};
102    use std::collections::HashMap;
103    use std::sync::Arc;
104
105    pub type TestDatabase = dyn alibi_core::store::AuthStore<BundledSchema>;
106
107    pub fn create_test_config() -> AuthConfig {
108        AuthConfig::new("test-secret-key-at-least-32-chars-long")
109    }
110
111    pub async fn create_test_database() -> Arc<TestDatabase> {
112        let database = Database::connect("sqlite::memory:")
113            .await
114            .expect("sqlite test database should connect");
115        alibi_seaorm::store::__private_test_support::migrator::run_migrations(&database)
116            .await
117            .expect("sqlite test migrations should run");
118        Arc::new(SeaOrmStore::<BundledSchema>::new(
119            Arc::new(create_test_config()),
120            database,
121        ))
122    }
123
124    pub async fn create_test_context() -> AuthContext<BundledSchema> {
125        create_test_context_with_config(create_test_config()).await
126    }
127
128    pub fn create_test_context_blocking() -> AuthContext<BundledSchema> {
129        tokio::runtime::Builder::new_current_thread()
130            .enable_all()
131            .build()
132            .expect("test runtime should build")
133            .block_on(create_test_context())
134    }
135
136    pub async fn create_test_context_with_config(config: AuthConfig) -> AuthContext<BundledSchema> {
137        let config = Arc::new(config);
138        let database = create_test_database().await;
139        AuthContext::new(config, database)
140    }
141
142    pub async fn create_user(
143        ctx: &AuthContext<impl alibi_core::AuthSchema>,
144        create_user: CreateUser,
145    ) -> UserView {
146        let user = ctx.database.create_user(create_user).await.unwrap();
147        UserView::from(&user)
148    }
149
150    pub async fn create_session(
151        ctx: &AuthContext<impl alibi_core::AuthSchema>,
152        user_id: String,
153        expires_in: Duration,
154    ) -> SessionView {
155        let create_session = CreateSession {
156            additional_fields: alibi_core::field_policy::FieldValues::default(),
157            token: None,
158            active_team_id: None,
159            user_id,
160            expires_at: Utc::now() + expires_in,
161            ip_address: Some("127.0.0.1".to_owned()),
162            user_agent: Some("test-agent".to_owned()),
163            impersonated_by: None,
164            active_organization_id: None,
165        };
166        let session = ctx.database.create_session(create_session).await.unwrap();
167        SessionView::from(&session)
168    }
169
170    pub async fn create_user_and_session(
171        ctx: &AuthContext<impl alibi_core::AuthSchema>,
172        user_data: CreateUser,
173        session_expires_in: Duration,
174    ) -> (UserView, SessionView) {
175        let user = create_user(ctx, user_data).await;
176        let session = create_session(ctx, user.id.clone(), session_expires_in).await;
177        (user, session)
178    }
179
180    pub async fn create_test_context_with_user(
181        create_user: CreateUser,
182        session_expires_in: Duration,
183    ) -> (AuthContext<BundledSchema>, UserView, SessionView) {
184        let ctx = create_test_context().await;
185        let (user, session) = create_user_and_session(&ctx, create_user, session_expires_in).await;
186        (ctx, user, session)
187    }
188
189    pub fn create_auth_request(
190        method: HttpMethod,
191        path: &str,
192        token: Option<&str>,
193        body: Option<Vec<u8>>,
194        query: HashMap<String, String>,
195    ) -> AuthRequest {
196        let mut headers = HashMap::new();
197        if let Some(token) = token {
198            let config = create_test_config();
199            headers.insert(
200                "cookie".to_owned(),
201                format!(
202                    "{}={}",
203                    config.session.cookie_name,
204                    alibi_core::utils::cookie_utils::sign_cookie_value(
205                        token,
206                        config.current_secret()
207                    )
208                ),
209            );
210        }
211
212        AuthRequest::from_parts(method, path.to_owned(), headers, body, query)
213    }
214
215    pub fn create_auth_request_no_query(
216        method: HttpMethod,
217        path: &str,
218        token: Option<&str>,
219        body: Option<Vec<u8>>,
220    ) -> AuthRequest {
221        create_auth_request(method, path, token, body, HashMap::new())
222    }
223
224    pub fn create_auth_json_request_no_query(
225        method: HttpMethod,
226        path: &str,
227        token: Option<&str>,
228        body: Option<serde_json::Value>,
229    ) -> AuthRequest {
230        create_auth_json_request(method, path, token, body, HashMap::new())
231    }
232
233    pub fn create_auth_json_request(
234        method: HttpMethod,
235        path: &str,
236        token: Option<&str>,
237        body: Option<serde_json::Value>,
238        query: HashMap<String, String>,
239    ) -> AuthRequest {
240        let mut req = create_auth_request(
241            method,
242            path,
243            token,
244            body.map(|b| serde_json::to_vec(&b).unwrap()),
245            query,
246        );
247        req.headers
248            .insert("content-type".to_owned(), "application/json".to_owned());
249        req
250    }
251}
252// LCOV_EXCL_STOP
253
254pub use account_management::AccountManagementPlugin;
255pub use admin::{
256    AdminBanUserRequest, AdminBannedUserMessage, AdminBannedUserMessageHandler, AdminConfig,
257    AdminCreateUserRequest, AdminImpersonationResponse, AdminListUsersQuery,
258    AdminListUsersResponse, AdminPermissionRequest, AdminPermissionResponse, AdminPlugin,
259    AdminRoleInput, AdminSetRoleRequest, AdminSetUserPasswordRequest, AdminSuccessResponse,
260    AdminUpdateUserRequest, AdminUserOutput, AdminUserResponse, AdminUserSessionsResponse,
261    RolePermissions,
262};
263pub use alibi_core::PasswordHasher;
264pub use anonymous::{
265    AnonymousConfig, AnonymousIdentity, AnonymousLink, AnonymousPlugin, LinkAnonymousAccount,
266};
267pub use api_key::{
268    ApiKeyCallbackContext, ApiKeyConfig, ApiKeyDefaultPermissions, ApiKeyErrorMessage,
269    ApiKeyGenerationOptions, ApiKeyGenerator, ApiKeyGetter, ApiKeyPermissions, ApiKeyPlugin,
270    ApiKeyValidator, DeleteExpiredApiKeysResponse,
271};
272pub use bearer::{BearerConfig, BearerPlugin};
273pub use captcha::{CaptchaConfig, CaptchaPlugin, CaptchaProvider};
274pub use custom_session::{CustomSessionPlugin, SessionTransform, SessionTransformContext};
275pub use device_authorization::DeviceAuthorizationPlugin;
276pub use email_otp::{EmailOtpConfig, EmailOtpPlugin, SendEmailOtp};
277pub use email_password::{EmailPasswordConfig, EmailPasswordPlugin};
278pub use email_verification::{
279    EmailVerificationConfig, EmailVerificationHook, EmailVerificationPlugin, SendVerificationEmail,
280};
281pub use haveibeenpwned::{HaveIBeenPwnedConfig, HaveIBeenPwnedPlugin, PwnedPasswordClient};
282pub use last_login_method::{
283    BeforeStoreLastLoginMethodCookie, LastLoginMethodConfig, LastLoginMethodContext,
284    LastLoginMethodEndpointResponse, LastLoginMethodPlugin, ResolveLastLoginMethod,
285};
286pub use magic_link::{MagicLinkConfig, MagicLinkPlugin, SendMagicLink};
287pub use multi_session::{MultiSessionConfig, MultiSessionPlugin};
288pub use oauth::OAuthPlugin;
289pub use oauth_popup::OAuthPopupPlugin;
290pub use oauth_provider::{
291    OAuthAuthenticatedClient, OAuthClientAuthenticationOptions, OAuthClientDiscovery,
292    OAuthClientIdGenerator, OAuthClientMetadataAbortSignal, OAuthClientMetadataFetchOptions,
293    OAuthClientMetadataFetchResponse, OAuthClientMetadataResourceFetch,
294    OAuthClientPrivilegeContext, OAuthClientPrivileges, OAuthClientSecretCipher,
295    OAuthClientSecretGenerator, OAuthClientSecretHasher, OAuthProviderConfig, OAuthProviderPlugin,
296    OAuthTokenHasher, OAuthTokenIssueParams, consume_client_assertion,
297};
298pub use oauth_proxy::{OAuthProxyConfig, OAuthProxyPlugin};
299pub use one_tap::{OAuthJwksSource, OneTapClientId, OneTapConfig, OneTapPlugin};
300pub use open_api::{OpenApiConfig, OpenApiPlugin};
301pub use organization::{
302    OrganizationConfig, OrganizationCreatePatch, OrganizationCreatedContext,
303    OrganizationCreationHooks, OrganizationDraftContext, OrganizationMemberCreatePatch,
304    OrganizationMemberDraftContext, OrganizationPlugin,
305};
306#[cfg(feature = "passkey")]
307pub use passkey::{
308    AuthenticationResult, PasskeyAuthenticationAfterVerification, PasskeyAuthenticationConfig,
309    PasskeyAuthenticationContext, PasskeyAuthenticatorSelection, PasskeyConfig, PasskeyExtensions,
310    PasskeyExtensionsResolver, PasskeyOptionsContext, PasskeyPlugin,
311    PasskeyRegistrationAfterVerification, PasskeyRegistrationConfig, PasskeyRegistrationContext,
312    PasskeyRegistrationOverride, PasskeyRegistrationUser, PasskeyUserResolver,
313    VerifiedPasskeyAuthentication, VerifiedPasskeyRegistration,
314};
315pub use password_management::{
316    PasswordManagementConfig, PasswordManagementPlugin, SendResetPassword,
317};
318pub use phone_number::{PhoneNumberConfig, PhoneNumberPlugin, SendPhoneOtp};
319pub use session_management::{GetSessionQuery, GetSessionResponse, SessionManagementPlugin};
320pub use siwe::{SiweConfig, SiwePlugin};
321pub use two_factor::{
322    SendTwoFactorOtp, TwoFactorBackupCipher, TwoFactorBackupStorage, TwoFactorConfig,
323    TwoFactorOtpCipher, TwoFactorOtpHasher, TwoFactorOtpStorage, TwoFactorPlugin,
324};
325pub use user_management::{
326    ChangeEmailConfig, DeleteUserConfig, SendChangeEmailConfirmation, UserManagementConfig,
327    UserManagementPlugin,
328};
329
330#[derive(Debug, serde::Serialize, serde::Deserialize)]
331pub(crate) struct StatusResponse {
332    status: bool,
333}
334
335pub use scim::ScimPlugin;
336pub use sso::{
337    SsoAccountKey, SsoConfig, SsoDefaultProvider, SsoDeprecatedAlgorithmBehavior, SsoKeyResolver,
338    SsoMutationAction, SsoMutationGuard, SsoMutationProvider, SsoOrganizationProvisioning,
339    SsoOrganizationRoleResolver, SsoPlugin, SsoPrivateKey, SsoPrivateKeyContext,
340    SsoPrivateKeyResolver, SsoProtocol, SsoProviderLimit, SsoProviderLimitResolver,
341    SsoProviderMutation, SsoProviderMutationGuard, SsoProvisioner, SsoProvisioningData,
342    SsoResolvedProfile, SsoResolver, SsoRoleResolver, SsoSamlEncryptionPolicy,
343    SsoSamlSigningPolicy, SsoUserProvisioner, SsoUserResolution, SsoUserResolutionInput,
344    SsoUserResolver,
345};