alibi_plugins/siwe/
endpoint.rs1use super::{SiwePlugin, SiweVerificationInput, validation};
3use crate::authentication_helpers::json_type;
4use crate::endpoint::{definition, error_response};
5use alibi_core::endpoint::{
6 EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
7};
8use alibi_core::utils::json::JsValue;
9use alibi_core::{AuthContext, AuthResult, AuthSchema, HttpMethod};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Serialize, Deserialize)]
14pub struct SiweNonceResponse {
15 pub nonce: String,
16}
17
18#[derive(Debug, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct SiweVerifiedUser {
22 pub id: String,
23 pub wallet_address: String,
24 pub chain_id: f64,
25}
26#[derive(Debug, Serialize, Deserialize)]
28pub struct SiweVerificationResponse {
29 pub success: bool,
30 pub token: String,
31 pub user: SiweVerifiedUser,
32}
33
34pub(super) fn definitions() -> Vec<EndpointDefinition> {
35 vec![
36 definition(
37 "verifySiweMessage",
38 "verifySiweMessage",
39 Some("/siwe/verify"),
40 HttpMethod::Post,
41 ),
42 definition(
43 "getSiweNonce",
44 "getSiweNonce",
45 Some("/siwe/nonce"),
46 HttpMethod::Post,
47 ),
48 definition(
49 "getNonce",
50 "getNonce",
51 Some("/siwe/get-nonce"),
52 HttpMethod::Post,
53 ),
54 ]
55}
56
57pub(super) fn validate(plugin: &SiwePlugin, call: &EndpointCall) -> AuthResult<EndpointInput> {
58 if call.operation_id() == "verifySiweMessage" {
59 _ = validation::verify_value(call.body(), plugin.config.anonymous)
60 .map_err(error_response)?;
61 if call.request().is_none() {
62 return Err(crate::endpoint::validation("Request is required"));
63 }
64 return Ok(EndpointInput {
65 body: call.body().cloned(),
66 query: call.query().cloned(),
67 });
68 }
69 if let Some(value) = call.body() {
70 let object = value.as_object().ok_or_else(|| {
71 crate::endpoint::validation(format!(
72 "[body] Invalid input: expected object, received {}",
73 json_type(Some(value)),
74 ))
75 })?;
76 if let Some(message) =
77 validation::unknown_field_keys(object.keys().map(String::as_str), &[])
78 {
79 return Err(crate::endpoint::validation(message));
80 }
81 }
82 Ok(EndpointInput {
83 body: call.body().cloned(),
84 query: call.query().cloned(),
85 })
86}
87
88impl SiwePlugin {
89 pub fn verify_message_endpoint(
93 input: &SiweVerificationInput,
94 ) -> AuthResult<ServerEndpoint<SiweVerificationResponse>> {
95 ServerEndpoint::new("siwe", "verifySiweMessage").with_body(input)
96 }
97 #[must_use]
99 pub fn get_siwe_nonce_endpoint() -> ServerEndpoint<SiweNonceResponse> {
100 ServerEndpoint::new("siwe", "getSiweNonce")
101 }
102 #[must_use]
104 pub fn get_nonce_endpoint() -> ServerEndpoint<SiweNonceResponse> {
105 ServerEndpoint::new("siwe", "getNonce")
106 }
107}
108
109pub(super) async fn execute<S: AuthSchema>(
110 plugin: &SiwePlugin,
111 call: &EndpointCall,
112 ctx: &AuthContext<S>,
113) -> AuthResult<EndpointResponse> {
114 let output = if call.operation_id() == "verifySiweMessage" {
115 let request = call
116 .request()
117 .ok_or_else(|| crate::endpoint::validation("Request is required"))?;
118 plugin
119 .verify_response(request, ctx, call.body_as()?)
120 .await?
121 } else {
122 plugin.issue_nonce(ctx).await?
123 };
124 let body = alibi_core::utils::json::from_slice::<JsValue>(&output.body)?;
125 let mut response = if output.status >= 400 {
126 EndpointResponse::error(error_response(output.clone())).with_error_body(body)
127 } else {
128 EndpointResponse::value(body)
129 };
130 response.set_status(Some(output.status));
131 response.merge_headers(output.headers);
132 Ok(response)
133}