alibi_plugins/email_password/
endpoint.rs1use super::types::SignInCoreResult;
3use super::{EmailPasswordPlugin, SignInEmailRequest, SignInEmailResponse, sign_in_core};
4use crate::authentication_helpers::RequestBody;
5use alibi_core::endpoint::{
6 EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
7};
8use alibi_core::utils::json::JsValue;
9use alibi_core::{AuthContext, AuthError, AuthResult, AuthSchema, HttpMethod, RequestMeta};
10
11pub(super) fn definitions(username_enabled: bool) -> Vec<EndpointDefinition> {
12 let mut definitions = vec![
13 crate::endpoint::definition(
14 "signInEmail",
15 "signInEmail",
16 Some("/sign-in/email"),
17 HttpMethod::Post,
18 ),
19 crate::endpoint::definition(
20 "signUpEmail",
21 "signUpWithEmailAndPassword",
22 Some("/sign-up/email"),
23 HttpMethod::Post,
24 ),
25 ];
26 if username_enabled {
27 definitions.push(crate::endpoint::definition(
28 "signInUsername",
29 "signInUsername",
30 Some("/sign-in/username"),
31 HttpMethod::Post,
32 ));
33 definitions.push(crate::endpoint::definition(
34 "isUsernameAvailable",
35 "isUsernameAvailable",
36 Some("/is-username-available"),
37 HttpMethod::Post,
38 ));
39 }
40 definitions
41}
42pub(super) fn validate(call: &EndpointCall) -> AuthResult<EndpointInput> {
43 if call.operation_id() == "signInUsername" {
44 return Ok(EndpointInput {
45 body: Some(crate::endpoint::validate_fields(
46 call.body(),
47 "body",
48 &[
49 crate::authentication_helpers::JsonField::string("username", true),
50 crate::authentication_helpers::JsonField::string("password", true),
51 crate::authentication_helpers::JsonField {
52 name: "rememberMe",
53 kind: crate::authentication_helpers::JsonFieldKind::Boolean,
54 required: false,
55 },
56 crate::authentication_helpers::JsonField::string("callbackURL", false),
57 ],
58 )?),
59 query: call.query().cloned(),
60 });
61 }
62 if call.operation_id() == "isUsernameAvailable" {
63 return Ok(EndpointInput {
64 body: Some(crate::endpoint::validate_fields(
65 call.body(),
66 "body",
67 &[crate::authentication_helpers::JsonField::string(
68 "username", true,
69 )],
70 )?),
71 query: call.query().cloned(),
72 });
73 }
74 if call.operation_id() == "signUpWithEmailAndPassword" {
75 _ = crate::endpoint::validate_fields(
76 call.body(),
77 "body",
78 super::SignUpEmailRequest::FIELDS,
79 )?;
80 return Ok(EndpointInput {
81 body: call.body().cloned(),
82 query: call.query().cloned(),
83 });
84 }
85 let mut body =
86 crate::endpoint::validate_fields(call.body(), "body", SignInEmailRequest::FIELDS)?;
87 if let JsValue::Object(body) = &mut body {
88 _ = body
89 .entry("rememberMe".into())
90 .or_insert(JsValue::Bool(true));
91 }
92 Ok(EndpointInput {
93 body: Some(body),
94 query: call.query().cloned(),
95 })
96}
97impl EmailPasswordPlugin {
98 pub fn sign_in_username_endpoint(
103 input: &super::SignInUsernameRequest,
104 ) -> AuthResult<ServerEndpoint<SignInEmailResponse>> {
105 ServerEndpoint::new("email-password", "signInUsername").with_body(input)
106 }
107
108 #[must_use]
110 pub fn is_username_available_endpoint(
111 username: impl Into<String>,
112 ) -> ServerEndpoint<super::IsUsernameAvailableResponse> {
113 ServerEndpoint::new("email-password", "isUsernameAvailable").with_body_value(
114 JsValue::Object(
115 [("username".into(), JsValue::String(username.into()))]
116 .into_iter()
117 .collect(),
118 ),
119 )
120 }
121
122 pub fn sign_in_email_endpoint(
124 body: &SignInEmailRequest,
125 ) -> AuthResult<ServerEndpoint<SignInEmailResponse>> {
126 ServerEndpoint::new("email-password", "signInEmail").with_body(body)
127 }
128 pub fn sign_up_email_endpoint(
130 body: &super::SignUpEmailRequest,
131 ) -> AuthResult<ServerEndpoint<super::SignUpEmailResponse<alibi_core::field_policy::FieldOutput>>>
132 {
133 ServerEndpoint::new("email-password", "signUpEmail").with_body(body)
134 }
135 pub(super) async fn call_sign_in_endpoint(
136 &self,
137 call: &EndpointCall,
138 ctx: &AuthContext<impl AuthSchema>,
139 ) -> AuthResult<EndpointResponse> {
140 if call.operation_id() == "signInUsername" {
141 let body: super::SignInUsernameRequest = call.body_as()?;
142 alibi_core::session::cookie_cache::runtime::set_issuance_preference(
143 call,
144 body.remember_me == Some(false),
145 );
146 let headers = call.headers().cloned().unwrap_or_default();
147 let meta = RequestMeta {
148 ip_address: Some(
149 ctx.config
150 .advanced
151 .ip_address
152 .resolve_ip(&headers)
153 .unwrap_or_default(),
154 ),
155 user_agent: Some(headers.get("user-agent").cloned().unwrap_or_default()),
156 };
157 let output = self.username_sign_in_core(call, ctx, body, &meta).await?;
158 let mut response = if output.status >= 400 {
159 EndpointResponse::error(crate::endpoint::error_response(output.clone()))
160 } else {
161 EndpointResponse::value(alibi_core::utils::json::from_slice(&output.body)?)
162 };
163 response.set_status(Some(output.status));
164 response.merge_headers(output.headers);
165 return Ok(response);
166 }
167 if call.operation_id() == "isUsernameAvailable" {
168 let output = self
169 .username_availability_core(ctx, call.body_as()?)
170 .await?;
171 let mut response = if output.status >= 400 {
172 EndpointResponse::error(crate::endpoint::error_response(output.clone()))
173 } else {
174 EndpointResponse::value(alibi_core::utils::json::from_slice(&output.body)?)
175 };
176 response.set_status(Some(output.status));
177 response.merge_headers(output.headers);
178 return Ok(response);
179 }
180 if call.operation_id() == "signUpWithEmailAndPassword" {
181 return self.call_signup_endpoint(call, ctx).await;
182 }
183 let body: SignInEmailRequest = call.body_as()?;
184 if let Some(request) = call.request() {
185 alibi_core::middleware::CsrfMiddleware::new(
186 alibi_core::middleware::CsrfConfig::new(),
187 ctx.config.clone(),
188 )
189 .check_login_origin(request)?;
190 if let Some(url) = body.callback_url.as_deref().filter(|url| !url.is_empty())
191 && !ctx.config.current_origin_check_disabled()
192 && !ctx.config.is_redirect_target_trusted(url)
193 {
194 return Err(AuthError::forbidden("Invalid callbackURL"));
195 }
196 }
197 alibi_core::session::cookie_cache::runtime::set_issuance_preference(
198 call,
199 body.remember_me == Some(false),
200 );
201 let headers = call.headers().cloned().unwrap_or_default();
202 let meta = RequestMeta {
203 ip_address: Some(
204 ctx.config
205 .advanced
206 .ip_address
207 .resolve_ip(&headers)
208 .unwrap_or_default(),
209 ),
210 user_agent: Some(headers.get("user-agent").cloned().unwrap_or_default()),
211 };
212 match Box::pin(sign_in_core(
213 call,
214 &body,
215 &self.config,
216 self.email_verification.as_deref(),
217 &meta,
218 ctx,
219 ))
220 .await?
221 {
222 SignInCoreResult::Success {
223 response,
224 token,
225 set_cookie_headers,
226 } => {
227 let mut output = EndpointResponse::json(&response)?;
228 output.merge_headers(crate::authentication_helpers::session_cookie_headers(
229 &ctx.config,
230 &token,
231 body.remember_me == Some(false),
232 )?);
233 if let Some(url) = body.callback_url.as_deref().filter(|url| !url.is_empty()) {
234 output = output.with_header("location", url);
235 }
236 for cookie in set_cookie_headers {
237 output = output.with_header("set-cookie", cookie);
238 }
239 Ok(output)
240 }
241 SignInCoreResult::TwoFactorRedirect {
242 response,
243 set_cookie_headers,
244 } => {
245 let mut output = EndpointResponse::json(&response)?;
246 for cookie in set_cookie_headers {
247 output = output.with_header("set-cookie", cookie);
248 }
249 Ok(output)
250 }
251 }
252 }
253 async fn call_signup_endpoint(
254 &self,
255 call: &EndpointCall,
256 ctx: &AuthContext<impl AuthSchema>,
257 ) -> AuthResult<EndpointResponse> {
258 let raw = call.body().cloned().unwrap_or(JsValue::Null);
259 let mut parsed = raw.clone();
260 if !self.config.enable_username
261 && let JsValue::Object(body) = &mut parsed
262 {
263 _ = body.shift_remove("username");
264 _ = body.shift_remove("displayUsername");
265 }
266 let mut body: super::SignUpEmailRequest = alibi_core::utils::json::from_value(parsed)?;
267 if let Some(request) = call.request() {
268 alibi_core::middleware::CsrfMiddleware::new(
269 alibi_core::middleware::CsrfConfig::new(),
270 ctx.config.clone(),
271 )
272 .check_login_origin(request)?;
273 if let Some(url) = body.callback_url.as_deref().filter(|url| !url.is_empty())
274 && !ctx.config.current_origin_check_disabled()
275 && !ctx.config.is_redirect_target_trusted(url)
276 {
277 return Err(AuthError::forbidden("Invalid callbackURL"));
278 }
279 }
280 if let Some(response) = self.prepare_signup_input(&mut body, raw, call, ctx).await? {
281 return Err(crate::endpoint::error_response(response));
282 }
283 alibi_core::session::cookie_cache::runtime::set_issuance_preference(
284 call,
285 body.remember_me == Some(false),
286 );
287 let headers = call.headers().cloned().unwrap_or_default();
288 let meta = RequestMeta {
289 ip_address: Some(
290 ctx.config
291 .advanced
292 .ip_address
293 .resolve_ip(&headers)
294 .unwrap_or_default(),
295 ),
296 user_agent: Some(headers.get("user-agent").cloned().unwrap_or_default()),
297 };
298 let (value, cookies) = Box::pin(super::sign_up_core(
299 call.request(),
300 &body,
301 &self.config,
302 &meta,
303 ctx,
304 ))
305 .await?;
306 let mut response = EndpointResponse::json(&value)?;
307 for cookie in cookies.into_iter().flatten() {
308 response = response.with_header("set-cookie", cookie);
309 }
310 Ok(response)
311 }
312}