11use async_trait:: async_trait;
22use chrono:: { DateTime , Utc } ;
33use nillion_chain_client:: { client:: NillionChainClient , transactions:: TokenAmount } ;
4- use nillion_nucs:: k256:: {
5- ecdsa:: { signature:: Signer , Signature , SigningKey } ,
6- sha2:: { Digest , Sha256 } ,
7- PublicKey , SecretKey ,
4+ use nillion_nucs:: {
5+ builder:: { ExtendTokenError , NucTokenBuildError , NucTokenBuilder } ,
6+ envelope:: { InvalidSignature , NucEnvelopeParseError , NucTokenEnvelope } ,
7+ k256:: {
8+ ecdsa:: { signature:: Signer , Signature , SigningKey } ,
9+ sha2:: { Digest , Sha256 } ,
10+ PublicKey , SecretKey ,
11+ } ,
12+ token:: { Did , ProofHash , TokenBody } ,
813} ;
914use serde:: { Deserialize , Serialize } ;
10- use std:: time:: Duration ;
15+ use serde_json:: json;
16+ use std:: { iter, time:: Duration } ;
1117
1218const TOKEN_REQUEST_EXPIRATION : Duration = Duration :: from_secs ( 60 ) ;
1319const REQUEST_TIMEOUT : Duration = Duration :: from_secs ( 30 ) ;
@@ -30,6 +36,15 @@ pub trait NilauthClient {
3036
3137 /// Get the cost of a subscription.
3238 async fn subscription_cost ( & self ) -> Result < TokenAmount , SubscriptionCostError > ;
39+
40+ /// Revoke a token.
41+ async fn revoke_token ( & self , token : & NucTokenEnvelope , key : & SecretKey ) -> Result < ( ) , RevokeTokenError > ;
42+
43+ /// Lookup whether a token is revoked.
44+ async fn lookup_revoked_tokens (
45+ & self ,
46+ envelope : & NucTokenEnvelope ,
47+ ) -> Result < Vec < RevokedToken > , LookupRevokedTokensError > ;
3348}
3449
3550/// An error when requesting a token.
@@ -77,13 +92,45 @@ pub enum SubscriptionCostError {
7792 Request ( #[ from] reqwest:: Error ) ,
7893}
7994
95+ /// An error when revoking a token.
96+ #[ derive( Debug , thiserror:: Error ) ]
97+ pub enum RevokeTokenError {
98+ #[ error( "fetching server's about: {0}" ) ]
99+ About ( #[ from] AboutError ) ,
100+
101+ #[ error( "requesting token: {0}" ) ]
102+ RequestToken ( #[ from] RequestTokenError ) ,
103+
104+ #[ error( "malformed token returned from nilauth: {0}" ) ]
105+ MalformedAuthToken ( #[ from] NucEnvelopeParseError ) ,
106+
107+ #[ error( "invalid signatures in token returned from nilauth: {0}" ) ]
108+ InvalidAuthTokenSignatures ( #[ from] InvalidSignature ) ,
109+
110+ #[ error( "cannot extend token returned from nilauth: {0}" ) ]
111+ AuthTokenNotDelegation ( #[ from] ExtendTokenError ) ,
112+
113+ #[ error( "building invocation: {0}" ) ]
114+ BuildInvocation ( #[ from] NucTokenBuildError ) ,
115+
116+ #[ error( "request: {0}" ) ]
117+ Request ( #[ from] reqwest:: Error ) ,
118+ }
119+
80120/// An error when requesting the information about a nilauth instance.
81121#[ derive( Debug , thiserror:: Error ) ]
82122pub enum AboutError {
83123 #[ error( "request: {0}" ) ]
84124 Request ( #[ from] reqwest:: Error ) ,
85125}
86126
127+ /// An error when looking up revoked tokens.
128+ #[ derive( Debug , thiserror:: Error ) ]
129+ pub enum LookupRevokedTokensError {
130+ #[ error( "request: {0}" ) ]
131+ Request ( #[ from] reqwest:: Error ) ,
132+ }
133+
87134/// The default nilauth client that hits the actual service.
88135pub struct DefaultNilauthClient {
89136 client : reqwest:: Client ,
@@ -154,9 +201,39 @@ impl NilauthClient for DefaultNilauthClient {
154201
155202 async fn subscription_cost ( & self ) -> Result < TokenAmount , SubscriptionCostError > {
156203 let url = self . make_url ( "/api/v1/payments/cost" ) ;
157- let response: GetCostResponse = self . client . get ( url) . send ( ) . await ?. json ( ) . await ?;
204+ let response: GetCostResponse = self . client . get ( url) . send ( ) . await ?. error_for_status ( ) ? . json ( ) . await ?;
158205 Ok ( TokenAmount :: Unil ( response. cost_unils ) )
159206 }
207+
208+ async fn revoke_token ( & self , token : & NucTokenEnvelope , key : & SecretKey ) -> Result < ( ) , RevokeTokenError > {
209+ let about = self . about ( ) . await ?;
210+ let token = token. encode ( ) ;
211+ let auth_token = self . request_token ( key) . await ?;
212+ let auth_token = NucTokenEnvelope :: decode ( & auth_token) ?. validate_signatures ( ) ?;
213+ // SAFETY: this can't not be an object
214+ let args = json ! ( { "token" : token} ) . as_object ( ) . cloned ( ) . expect ( "not an object" ) ;
215+ let invocation = NucTokenBuilder :: extending ( auth_token) ?
216+ . audience ( Did :: new ( about. public_key ) )
217+ . body ( TokenBody :: Invocation ( args) )
218+ . command ( [ "nuc" , "revoke" ] )
219+ . build ( & key. into ( ) ) ?;
220+ let header_value = format ! ( "Bearer {invocation}" ) ;
221+ let url = self . make_url ( "/api/v1/revocations/revoke" ) ;
222+ self . client . post ( url) . header ( "Authorization" , header_value) . send ( ) . await ?. error_for_status ( ) ?;
223+ Ok ( ( ) )
224+ }
225+
226+ async fn lookup_revoked_tokens (
227+ & self ,
228+ envelope : & NucTokenEnvelope ,
229+ ) -> Result < Vec < RevokedToken > , LookupRevokedTokensError > {
230+ let hashes = iter:: once ( envelope. token ( ) ) . chain ( envelope. proofs ( ) ) . map ( |t| t. compute_hash ( ) ) . collect ( ) ;
231+ let request = LookupRevokedTokensRequest { hashes } ;
232+ let url = self . make_url ( "/api/v1/revocations/lookup" ) ;
233+ let response: LookupRevokedTokensResponse =
234+ self . client . post ( url) . json ( & request) . send ( ) . await ?. error_for_status ( ) ?. json ( ) . await ?;
235+ Ok ( response. revoked )
236+ }
160237}
161238
162239/// A transaction hash.
@@ -230,3 +307,23 @@ struct GetCostResponse {
230307 // The cost in unils.
231308 cost_unils : u64 ,
232309}
310+
311+ #[ derive( Serialize ) ]
312+ struct LookupRevokedTokensRequest {
313+ hashes : Vec < ProofHash > ,
314+ }
315+
316+ #[ derive( Deserialize ) ]
317+ struct LookupRevokedTokensResponse {
318+ revoked : Vec < RevokedToken > ,
319+ }
320+
321+ /// A revoked token.
322+ #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
323+ pub struct RevokedToken {
324+ /// The token hash.
325+ pub token_hash : ProofHash ,
326+
327+ /// The timestamp at which the token was revoked.
328+ pub revoked_at : DateTime < Utc > ,
329+ }
0 commit comments