1+ use crate :: AppState ;
2+ use axum:: extract:: { Request , State } ;
3+ use axum:: middleware:: Next ;
4+ use axum:: response:: Response ;
5+ use axum:: routing:: post;
6+ use axum:: { middleware, Json } ;
7+ use ed25519_dalek:: { Verifier , VerifyingKey , PUBLIC_KEY_LENGTH } ;
8+ use hex:: FromHex ;
9+ use http:: StatusCode ;
10+ use http_body_util:: BodyExt ;
11+ use once_cell:: sync:: Lazy ;
12+ use serde_json:: { json, Value } ;
13+ use twilight_model:: application:: interaction:: { Interaction , InteractionType } ;
14+ use twilight_model:: http:: interaction:: { InteractionResponse , InteractionResponseType } ;
15+
16+ static PUB_KEY : Lazy < VerifyingKey > = Lazy :: new ( || {
17+ VerifyingKey :: from_bytes ( & <[ u8 ; PUBLIC_KEY_LENGTH ] as FromHex >:: from_hex ( "DISCORD_PUBLIC_KEY" ) . unwrap ( ) )
18+ . unwrap ( )
19+ } ) ;
20+
21+ pub fn router ( ) -> axum:: Router < AppState > {
22+ axum:: Router :: new ( )
23+ . route ( "/" , post ( post_interactions) )
24+ . layer ( middleware:: from_fn ( pubkey_middleware) )
25+ }
26+
27+ pub async fn pubkey_middleware (
28+ request : Request ,
29+ next : Next ,
30+ ) -> Result < Response , StatusCode > {
31+ let timestamp = if let Some ( ts) = request. headers ( ) . get ( "x-signature-timestamp" ) {
32+ ts. to_owned ( )
33+ } else {
34+ return Err ( StatusCode :: BAD_REQUEST ) ;
35+ } ;
36+ // Extract the signature to check against.
37+ let signature = if let Some ( hex_sig) = request
38+ . headers ( )
39+ . get ( "x-signature-ed25519" )
40+ . and_then ( |v| v. to_str ( ) . ok ( ) )
41+ {
42+ hex_sig. parse ( ) . unwrap ( )
43+ } else {
44+ return Err ( StatusCode :: BAD_REQUEST ) ;
45+ } ;
46+
47+ let ( parts, body) = request. into_parts ( ) ;
48+ let body = body
49+ . collect ( )
50+ . await
51+ . map_err ( |_| StatusCode :: INTERNAL_SERVER_ERROR ) ?
52+ . to_bytes ( ) ;
53+
54+ if PUB_KEY
55+ . verify (
56+ [ timestamp. as_bytes ( ) , & body] . concat ( ) . as_ref ( ) ,
57+ & signature,
58+ )
59+ . is_err ( )
60+ {
61+ return Err ( StatusCode :: UNAUTHORIZED ) ;
62+ }
63+ let new_request = Request :: from_parts ( parts, axum:: body:: Body :: from ( body) ) ;
64+ Ok ( next. run ( new_request) . await )
65+ }
66+
67+ async fn post_interactions (
68+ State ( _app_state) : State < AppState > ,
69+ Json ( interaction) : Json < Interaction > ,
70+ ) -> Result < Json < Value > , StatusCode > {
71+ match interaction. kind {
72+ InteractionType :: Ping => {
73+ Ok ( Json ( json ! ( InteractionResponse { kind: InteractionResponseType :: Pong , data: None } ) ) )
74+ }
75+ InteractionType :: ApplicationCommand => {
76+ Err ( StatusCode :: NOT_IMPLEMENTED )
77+ }
78+ InteractionType :: MessageComponent => {
79+ Err ( StatusCode :: NOT_IMPLEMENTED )
80+ }
81+ InteractionType :: ApplicationCommandAutocomplete => {
82+ Err ( StatusCode :: NOT_IMPLEMENTED )
83+ }
84+ InteractionType :: ModalSubmit => {
85+ Err ( StatusCode :: NOT_IMPLEMENTED )
86+ }
87+ _ => Err ( StatusCode :: BAD_REQUEST )
88+ }
89+ }
0 commit comments