rustauth_plugins/
schema_plugins.rs1use rustauth_core::error::RustAuthError;
7use rustauth_core::plugin::AuthPlugin;
8
9use crate::{
10 admin::{admin, AdminOptions},
11 anonymous::{anonymous, AnonymousOptions},
12 api_key::{api_key, ApiKeyOptions},
13 device_authorization::{device_authorization, DeviceAuthorizationOptions},
14 jwt::{jwt, JwtOptions},
15 last_login_method::{last_login_method, LastLoginMethodOptions},
16 organization::{organization, DynamicAccessControlOptions, OrganizationOptions, TeamOptions},
17 phone_number::{phone_number, PhoneNumberOptions},
18 siwe::siwe_dev,
19 two_factor::{two_factor, TwoFactorOptions},
20 username::{username, UsernameOptions},
21 PLUGIN_IDS,
22};
23
24pub const NO_FIXED_SCHEMA_PLUGIN_IDS: &[&str] = &[
29 "bearer",
30 "captcha",
31 "custom-session",
32 "email-otp",
33 "generic-oauth",
34 "have-i-been-pwned",
35 "magic-link",
36 "multi-session",
37 "oauth-proxy",
38 "one-tap",
39 "one-time-token",
40 "open-api",
41];
42
43pub const APP_CONFIGURED_SCHEMA_PLUGIN_IDS: &[&str] = &["additional-fields"];
48
49fn schema_planning_organization_options() -> OrganizationOptions {
50 OrganizationOptions::builder()
51 .teams(TeamOptions {
52 enabled: true,
53 ..TeamOptions::default()
54 })
55 .dynamic_access_control(DynamicAccessControlOptions {
56 enabled: true,
57 ..DynamicAccessControlOptions::default()
58 })
59 .build()
60}
61
62pub fn is_official_schema_plugin(plugin_id: &str) -> bool {
64 official_schema_plugin(plugin_id).is_some()
65}
66
67pub fn official_schema_plugin(plugin_id: &str) -> Option<Result<AuthPlugin, RustAuthError>> {
69 match plugin_id {
70 "admin" => Some(admin(AdminOptions::default())),
71 "anonymous" => Some(Ok(anonymous(AnonymousOptions::default()))),
72 "api-key" => Some(api_key(ApiKeyOptions::default())),
73 "device-authorization" => Some(device_authorization(DeviceAuthorizationOptions::default())),
74 "jwt" => Some(jwt(JwtOptions::default())),
75 "last-login-method" => Some(Ok(last_login_method(
76 LastLoginMethodOptions::default().store_in_database(true),
77 ))),
78 "organization" => Some(Ok(organization(schema_planning_organization_options()))),
79 "phone-number" => Some(phone_number(PhoneNumberOptions::new(|_phone, _otp| Ok(())))),
80 "siwe" => Some(siwe_dev()),
81 "two-factor" => Some(Ok(two_factor(TwoFactorOptions::default()))),
82 "username" => Some(Ok(username(UsernameOptions::default()))),
83 _ => None,
84 }
85}
86
87pub fn configured_official_schema_plugins(
89 plugin_ids: &[String],
90) -> Result<Vec<AuthPlugin>, RustAuthError> {
91 let mut plugins = Vec::new();
92 for plugin_id in plugin_ids {
93 let Some(plugin) = official_schema_plugin(plugin_id) else {
94 continue;
95 };
96 plugins.push(plugin?);
97 }
98 Ok(plugins)
99}
100
101pub fn official_schema_plugin_ids() -> Vec<&'static str> {
103 PLUGIN_IDS
104 .iter()
105 .copied()
106 .filter(|id| official_schema_plugin(id).is_some())
107 .collect()
108}
109
110#[cfg(test)]
111mod tests {
112 #![allow(clippy::expect_used)]
113
114 use rustauth_core::context::create_auth_context_with_adapter;
115 use rustauth_core::db::MemoryAdapter;
116 use rustauth_core::options::RustAuthOptions;
117 use std::sync::Arc;
118
119 use super::*;
120
121 fn is_catalog_plugin_id(plugin_id: &str) -> bool {
122 PLUGIN_IDS.contains(&plugin_id) || plugin_id == "have-i-been-pwned"
123 }
124
125 #[test]
126 fn schema_exemption_lists_are_official_plugin_ids() {
127 for id in NO_FIXED_SCHEMA_PLUGIN_IDS {
128 assert!(
129 is_catalog_plugin_id(id),
130 "{id} is not an official plugin id"
131 );
132 assert!(
133 official_schema_plugin(id).is_none(),
134 "{id} should not have a fixed schema factory"
135 );
136 }
137 for id in APP_CONFIGURED_SCHEMA_PLUGIN_IDS {
138 assert!(
139 is_catalog_plugin_id(id),
140 "{id} is not an official plugin id"
141 );
142 assert!(
143 official_schema_plugin(id).is_none(),
144 "{id} schema depends on app configuration"
145 );
146 }
147 }
148
149 #[test]
150 fn organization_schema_planning_includes_optional_tables() {
151 let plugin = official_schema_plugin("organization")
152 .expect("organization contributes schema")
153 .expect("organization defaults");
154 let context = create_auth_context_with_adapter(
155 RustAuthOptions::new().plugins(vec![plugin]),
156 Arc::new(MemoryAdapter::new()),
157 )
158 .expect("auth context");
159 assert!(context.db_schema.table("team").is_some());
160 assert!(context.db_schema.table("team_member").is_some());
161 assert!(context.db_schema.table("organization_role").is_some());
162 }
163}