@@ -6,7 +6,7 @@ use crate::error::{AppErrorKind, Result};
66use crate :: user:: User ;
77use crate :: { CONFIG , PROXY_ORIGIN_HEADER } ;
88
9- use super :: primitive:: { InternalUserSecret , UserSecret , UserSecretKind } ;
9+ use super :: primitive:: { UserSecret , UserSecretKind } ;
1010use super :: { MetadataKind , SecretString } ;
1111use crate :: database:: { Database , UserSecretRow } ;
1212
@@ -64,14 +64,20 @@ impl ApiKeySecret {
6464 . checked_add_signed ( final_duration)
6565 . ok_or ( AppErrorKind :: InvalidDuration ) ?
6666 } ;
67- let internal = InternalUserSecret {
68- code : SecretString :: new ( ApiKeySecretKind :: PREFIX ) ,
69- user,
67+ let code = SecretString :: new ( ApiKeySecretKind :: PREFIX ) ;
68+ let metadata = ApiKeyMetadata { service } ;
69+ let user_str = serde_json:: to_string ( & user) ?;
70+ let metadata_str = serde_json:: to_string ( & metadata) ?;
71+ let row = UserSecretRow {
72+ id : code. to_str_that_i_wont_print ( ) . to_string ( ) ,
73+ secret_type : ApiKeySecretKind :: PREFIX . to_string ( ) ,
74+ user_data : user_str,
7075 expires_at,
71- metadata : ApiKeyMetadata { service } ,
76+ metadata : metadata_str,
77+ created_at : None ,
7278 } ;
73- internal . save ( db) . await ?;
74- Ok ( Self ( internal ) )
79+ row . save ( db) . await ?;
80+ ApiKeySecret :: try_from_string ( row . id , db ) . await
7581 }
7682
7783 /// List API keys for a user and service.
@@ -91,14 +97,20 @@ impl ApiKeySecret {
9197 if meta. service != service {
9298 continue ;
9399 }
94- let display = format ! ( "{}…" , & row. id[ ..row. id. len( ) . min( 8 ) ] ) ;
100+ let prefix = crate :: secret:: get_prefix ( ApiKeySecretKind :: PREFIX ) ;
101+ let bare = row. id . strip_prefix ( & prefix) . unwrap_or ( & row. id ) ;
102+ let display = if bare. len ( ) <= 8 {
103+ bare. to_string ( )
104+ } else {
105+ format ! ( "{}…{}" , & bare[ ..4 ] , & bare[ bare. len( ) . saturating_sub( 4 ) ..] , )
106+ } ;
95107 let expires_at = if row. expires_at == NaiveDateTime :: MAX {
96108 None
97109 } else {
98110 Some ( row. expires_at )
99111 } ;
100112 keys. push ( ApiKeyInfo {
101- id : row. id ,
113+ key : row. id ,
102114 display,
103115 expires_at,
104116 } ) ;
@@ -108,14 +120,41 @@ impl ApiKeySecret {
108120
109121 /// Get the associated service name.
110122 pub fn service ( & self ) -> & str {
111- & self . 0 . metadata . service
123+ & self . metadata ( ) . service
124+ }
125+
126+ /// Rotate an API key if it belongs to `user`.
127+ pub async fn rotate_with_user ( code : String , user : & User , db : & Database ) -> Result < Self > {
128+ let old = Self :: try_from_string ( code, db) . await ?;
129+ if old. user ( ) != user {
130+ return Err ( AppErrorKind :: Unauthorized . into ( ) ) ;
131+ }
132+ let remaining = if old. expires_at ( ) == NaiveDateTime :: MAX {
133+ Duration :: seconds ( 0 )
134+ } else {
135+ old. expires_at ( ) - Utc :: now ( ) . naive_utc ( )
136+ } ;
137+ let new_key =
138+ Self :: new_with_expiration ( user. clone ( ) , old. service ( ) . to_string ( ) , remaining, db)
139+ . await ?;
140+ old. delete ( db) . await ?;
141+ Ok ( new_key)
142+ }
143+
144+ /// Delete an API key if it belongs to `user`.
145+ pub async fn delete_with_user ( code : String , user : & User , db : & Database ) -> Result < ( ) > {
146+ let key = Self :: try_from_string ( code, db) . await ?;
147+ if key. user ( ) != user {
148+ return Err ( AppErrorKind :: Unauthorized . into ( ) ) ;
149+ }
150+ key. delete ( db) . await
112151 }
113152}
114153
115154/// Public representation of an API key for listing purposes.
116155#[ derive( Debug , Clone , Serialize ) ]
117156pub struct ApiKeyInfo {
118- pub id : String ,
157+ pub key : String ,
119158 pub display : String ,
120159 pub expires_at : Option < NaiveDateTime > ,
121160}
@@ -174,25 +213,16 @@ mod tests {
174213 let id = key. code ( ) . to_str_that_i_wont_print ( ) . to_string ( ) ;
175214 let list = ApiKeySecret :: list ( & user, "example" , & db) . await . unwrap ( ) ;
176215 assert_eq ! ( list. len( ) , 1 ) ;
177- let fetched = ApiKeySecret :: try_from_string ( id. clone ( ) , & db)
216+ let rotated = ApiKeySecret :: rotate_with_user ( id. clone ( ) , & user , & db)
178217 . await
179218 . unwrap ( ) ;
180- let new_key = ApiKeySecret :: new_with_expiration (
181- user. clone ( ) ,
182- "example" . to_string ( ) ,
183- Duration :: try_seconds ( 60 ) . unwrap ( ) ,
219+ ApiKeySecret :: delete_with_user (
220+ rotated. code ( ) . to_str_that_i_wont_print ( ) . to_string ( ) ,
221+ & user,
184222 & db,
185223 )
186224 . await
187225 . unwrap ( ) ;
188- fetched. delete ( & db) . await . unwrap ( ) ;
189- let new_id = new_key. code ( ) . to_str_that_i_wont_print ( ) . to_string ( ) ;
190- let list = ApiKeySecret :: list ( & user, "example" , & db) . await . unwrap ( ) ;
191- assert_eq ! ( list. len( ) , 1 ) ;
192- let fetched_new = ApiKeySecret :: try_from_string ( new_id. clone ( ) , & db)
193- . await
194- . unwrap ( ) ;
195- fetched_new. delete ( & db) . await . unwrap ( ) ;
196226 let list = ApiKeySecret :: list ( & user, "example" , & db) . await . unwrap ( ) ;
197227 assert ! ( list. is_empty( ) ) ;
198228 }
0 commit comments