1mod account_annotations;
3mod email_annotations;
4mod model_annotations;
5mod oauth_annotations;
6mod password_annotations;
7mod session_annotations;
8mod sign_in_annotations;
9mod user_annotations;
10#[rustfmt::skip]
11#[allow(warnings, clippy::all, clippy::pedantic, clippy::nursery, clippy::restriction, reason = "Generated API declarations retain the upstream generator output")]
12mod source_endpoints;
13#[rustfmt::skip]
14#[allow(warnings, clippy::all, clippy::pedantic, clippy::nursery, clippy::restriction, reason = "Generated API declarations retain the upstream generator output")]
15mod source_models;
16use alibi_core::AuthRoute;
17use alibi_core::{OpenApiEndpoint, OpenApiField, OpenApiModel, PluginOpenApiMetadata};
18use alibi_schema_registry::{FieldDef, plugin_schemas};
19use serde_json::{Value, json};
20fn wire_name(name: &str) -> String {
21 let mut words = name.split('_');
22 let mut output = words.next().unwrap_or_default().to_owned();
23 for word in words {
24 let mut letters = word.chars();
25 if let Some(first) = letters.next() {
26 output.extend(first.to_uppercase());
27 output.extend(letters);
28 }
29 }
30 output
31}
32fn field(field: &FieldDef, core: bool) -> Option<OpenApiField> {
33 if field.is_primary_key || (core && field.name == "active") {
35 return None;
36 }
37 let ty = field.ty.trim_start_matches("Option<").trim_end_matches('>');
38 let schema = match ty {
39 "DateTimeUtc" => json!({"type":"string","format":"date-time"}),
40 "bool" => json!({"type":"boolean"}),
41 "i64" | "f64" => json!({"type":"number"}),
42 "Json" => json!({"type":"json"}),
43 _ => json!({"type":"string"}),
44 };
45 let mut field = OpenApiField::new(
46 wire_name(field.name),
47 schema,
48 !field.ty.starts_with("Option<"),
49 );
50 if core && matches!(field.name.as_str(), "name" | "email") {
51 field.required = true;
52 }
53 if core && field.name == "emailVerified" {
54 field.input = false;
55 if let Some(object) = field.schema.as_object_mut() {
56 drop(object.insert("default".into(), json!(false)));
57 }
58 }
59 Some(field)
60}
61#[must_use]
64pub fn plugin_metadata(plugin: &str, routes: &[AuthRoute]) -> PluginOpenApiMetadata {
65 let is_core = |owner: &str| {
66 matches!(
67 owner,
68 "core"
69 | "email-password"
70 | "session-management"
71 | "email-verification"
72 | "password-management"
73 | "user-management"
74 | "account-management"
75 | "oauth"
76 )
77 };
78 let mut metadata = PluginOpenApiMetadata {
79 core: is_core(plugin),
80 ..Default::default()
81 };
82 for route in routes {
83 let owner = if plugin == "email-password"
84 && matches!(
85 route.path.as_str(),
86 "/sign-in/username" | "/is-username-available"
87 ) {
88 "username"
89 } else {
90 plugin
91 };
92 let mut annotation = source_endpoints::endpoint(owner, &route.path)
93 .or_else(|| endpoint(&route.path))
94 .unwrap_or_else(|| OpenApiEndpoint {
95 operation_id: Some(route.operation_id.clone()),
96 ..Default::default()
97 });
98 if owner != plugin {
99 annotation.owner = Some(owner.into());
100 annotation.core = Some(is_core(owner));
101 }
102 metadata
103 .endpoints
104 .push((route.method.clone(), route.path.clone(), annotation));
105 }
106 if let Some(models) = source_models::models(plugin) {
107 metadata.models = models;
108 return metadata;
109 }
110 if let Some(schema) = plugin_schemas().iter().find(|schema| schema.name == plugin) {
111 for (name, definitions) in [
112 ("User", schema.user_fields),
113 ("Session", schema.session_fields),
114 ] {
115 let fields = definitions
116 .iter()
117 .filter_map(|definition| field(definition, false))
118 .collect::<Vec<_>>();
119 if !fields.is_empty() {
120 metadata.models.push(OpenApiModel::new(name, fields));
121 }
122 }
123 for entity in schema.extra_entities {
124 let wire = if plugin == "jwt" {
125 "jwks".into()
126 } else {
127 wire_name(entity.mod_name)
128 };
129 let mut chars = wire.chars();
130 let name = chars
131 .next()
132 .map(|first| {
133 let mut name = first.to_uppercase().collect::<String>();
134 name.push_str(chars.as_str());
135 name
136 })
137 .unwrap_or_default();
138 metadata.models.push(OpenApiModel::new(
139 name,
140 entity
141 .fields
142 .iter()
143 .filter_map(|definition| field(definition, false))
144 .collect(),
145 ));
146 }
147 }
148 metadata
149}
150#[must_use]
151pub fn instance_plugin_metadata<S: alibi_core::AuthSchema>(
152 plugin: &str,
153 routes: &[AuthRoute],
154 ctx: &alibi_core::AuthInitContext<S>,
155) -> PluginOpenApiMetadata {
156 let mut metadata = plugin_metadata(plugin, routes);
157 model_annotations::apply(plugin, ctx, &mut metadata);
158 metadata
159}
160#[must_use]
161pub fn core_routes() -> Vec<AuthRoute> {
162 vec![
163 AuthRoute::get("/ok", "ok"),
164 AuthRoute::get("/error", "error"),
165 AuthRoute::post("/update-user", "update_user"),
166 ]
167}
168fn response(description: &str, schema: &Value) -> Value {
169 json!({"description":description,"content":{"application/json":{"schema":schema}}})
170}
171fn endpoint(path: &str) -> Option<OpenApiEndpoint> {
172 if let Some(metadata) =
173 sign_in_annotations::endpoint(path).or_else(|| oauth_annotations::endpoint(path))
174 {
175 return Some(metadata);
176 }
177 if let Some(metadata) = user_annotations::endpoint(path) {
178 return Some(metadata);
179 }
180 if let Some(metadata) = account_annotations::endpoint(path)
181 .or_else(|| password_annotations::endpoint(path))
182 .or_else(|| email_annotations::endpoint(path))
183 {
184 return Some(metadata);
185 }
186 if let Some(metadata) = session_annotations::endpoint(path) {
187 return Some(metadata);
188 }
189 let mut metadata = OpenApiEndpoint::default();
190 match path {
191 "/callback/{provider}" | "/callback/:id" => {
192 metadata.document_path = Some("/callback/:id".into());
193 let mut properties = serde_json::Map::new();
194 for name in [
195 "code",
196 "error",
197 "device_id",
198 "error_description",
199 "state",
200 "user",
201 "iss",
202 ] {
203 drop(properties.insert(name.into(), json!({"type":"string"})));
204 }
205 metadata.request_body = Some(
206 json!({"required":false,"content":{"application/json":{"schema":{"type":"object","properties":properties}}}}),
207 );
208 }
209 "/ok" => {
210 metadata.description = Some("Check if the API is working".into());
211 drop(metadata.responses.insert("200".into(), response("API is working",&(json!({"type":"object","properties":{"ok":{"type":"boolean","description":"Indicates if the API is working"}},"required":["ok"]})))));
212 }
213 "/error" => {
214 metadata.description = Some("Displays an error page".into());
215 drop(metadata.responses.insert("200".into(),json!({"description":"Success","content":{"text/html":{"schema":{"type":"string","description":"The HTML content of the error page"}}}})));
216 }
217 "/get-session" => {
218 metadata.operation_id = Some("getSession".into());
219 metadata.description = Some("Get the current session".into());
220 drop(metadata.responses.insert("200".into(),response("Success",&(json!({"type":["object","null"],"properties":{"session":{"$ref":"#/components/schemas/Session"},"user":{"$ref":"#/components/schemas/User"}},"required":["session","user"]})))));
223 }
224 _ => return None,
225 }
226 Some(metadata)
227}