@@ -12,8 +12,7 @@ use actix_web::{
12
12
} ;
13
13
use actix_web_lab:: extract:: { Json , RequestSignature , RequestSignatureScheme } ;
14
14
use async_trait:: async_trait;
15
- use bytes:: { BufMut as _, BytesMut } ;
16
- use ed25519_dalek:: { PublicKey , Signature , Verifier as _} ;
15
+ use ed25519_dalek:: { DigestVerifier , PublicKey , Signature } ;
17
16
use hex_literal:: hex;
18
17
use once_cell:: sync:: Lazy ;
19
18
use rustls:: { Certificate , PrivateKey , ServerConfig } ;
@@ -30,13 +29,16 @@ static SIG_HDR_NAME: Lazy<HeaderName> =
30
29
static TS_HDR_NAME : Lazy < HeaderName > =
31
30
Lazy :: new ( || HeaderName :: from_static ( "x-signature-timestamp" ) ) ;
32
31
32
+ /// Signature scheme for Discord interactions/webhooks.
33
+ ///
34
+ /// Verification is done in `finalize` so this does not support optional verification.
33
35
#[ derive( Debug ) ]
34
36
struct DiscordWebhook {
35
37
/// Signature taken from webhook request header.
36
38
candidate_signature : Signature ,
37
39
38
- /// Cloned payload state .
39
- chunks : Vec < Bytes > ,
40
+ /// Signature verifier .
41
+ verifier : DigestVerifier ,
40
42
}
41
43
42
44
impl DiscordWebhook {
@@ -65,50 +67,44 @@ impl DiscordWebhook {
65
67
66
68
#[ async_trait( ?Send ) ]
67
69
impl RequestSignatureScheme for DiscordWebhook {
68
- type Signature = ( BytesMut , Signature ) ;
70
+ type Signature = Signature ;
69
71
70
72
type Error = Error ;
71
73
72
74
async fn init ( req : & HttpRequest ) -> Result < Self , Self :: Error > {
73
75
let ts = Self :: get_timestamp ( req) ?. to_owned ( ) ;
74
76
let candidate_signature = Self :: get_signature ( req) ?;
75
77
78
+ let mut verifier = APP_PUBLIC_KEY
79
+ . verify_digest ( & candidate_signature)
80
+ . map_err ( error:: ErrorBadRequest ) ?;
81
+
82
+ verifier. update ( ts) ;
83
+
76
84
Ok ( Self {
77
85
candidate_signature,
78
- chunks : vec ! [ Bytes :: from ( ts ) ] ,
86
+ verifier ,
79
87
} )
80
88
}
81
89
82
90
async fn consume_chunk ( & mut self , _req : & HttpRequest , chunk : Bytes ) -> Result < ( ) , Self :: Error > {
83
- self . chunks . push ( chunk) ;
91
+ self . verifier . update ( chunk) ;
84
92
Ok ( ( ) )
85
93
}
86
94
87
95
async fn finalize ( self , _req : & HttpRequest ) -> Result < Self :: Signature , Self :: Error > {
88
- let buf_len = self . chunks . iter ( ) . map ( |chunk| chunk. len ( ) ) . sum ( ) ;
89
- let mut buf = BytesMut :: with_capacity ( buf_len) ;
90
-
91
- for chunk in self . chunks {
92
- buf. put ( chunk) ;
93
- }
96
+ self . verifier . finalize_and_verify ( ) . map_err ( |_| {
97
+ error:: ErrorUnauthorized ( "given signature does not match calculated signature" )
98
+ } ) ?;
94
99
95
- Ok ( ( buf , self . candidate_signature ) )
100
+ Ok ( self . candidate_signature )
96
101
}
97
102
98
103
fn verify (
99
- ( payload , candidate_signature ) : Self :: Signature ,
104
+ signature : Self :: Signature ,
100
105
_req : & HttpRequest ,
101
106
) -> Result < Self :: Signature , Self :: Error > {
102
- if APP_PUBLIC_KEY
103
- . verify ( & payload, & candidate_signature)
104
- . is_ok ( )
105
- {
106
- Ok ( ( payload, candidate_signature) )
107
- } else {
108
- Err ( error:: ErrorUnauthorized (
109
- "given signature does not match calculated signature" ,
110
- ) )
111
- }
107
+ Ok ( signature)
112
108
}
113
109
}
114
110
@@ -126,6 +122,7 @@ async fn main() -> io::Result<()> {
126
122
let ( Json ( form) , _) = body. into_parts ( ) ;
127
123
println ! ( "{}" , serde_json:: to_string_pretty( & form) . unwrap( ) ) ;
128
124
125
+ // reply with PONG code
129
126
web:: Json ( serde_json:: json!( {
130
127
"type" : 1
131
128
} ) )
0 commit comments