@@ -2,6 +2,7 @@ use rand::distr::{Alphanumeric, SampleString};
22use secrecy:: { ExposeSecret , SecretString } ;
33use sha2:: digest:: Output ;
44use sha2:: { Digest , Sha256 } ;
5+ use std:: str:: FromStr ;
56
67/// A temporary access token used to publish crates to crates.io using
78/// the "Trusted Publishing" feature.
@@ -29,19 +30,37 @@ impl AccessToken {
2930 Self ( raw. into ( ) )
3031 }
3132
32- /// Parse a byte string into an access token.
33+ /// Wrap the raw access token with the token prefix and a checksum .
3334 ///
34- /// This can be used to convert an HTTP header value into an access token.
35- pub fn from_byte_str ( byte_str : & [ u8 ] ) -> Result < Self , AccessTokenError > {
36- let suffix = byte_str
37- . strip_prefix ( Self :: PREFIX . as_bytes ( ) )
35+ /// This turns e.g. `ABC` into `cio_tp_ABC{checksum}`.
36+ pub fn finalize ( & self ) -> SecretString {
37+ let raw = self . 0 . expose_secret ( ) ;
38+ let checksum = checksum ( raw. as_bytes ( ) ) ;
39+ format ! ( "{}{raw}{checksum}" , Self :: PREFIX ) . into ( )
40+ }
41+
42+ /// Generate a SHA256 hash of the access token.
43+ ///
44+ /// This is used to create a hashed version of the token for storage in
45+ /// the database to avoid storing the plaintext token.
46+ pub fn sha256 ( & self ) -> Output < Sha256 > {
47+ Sha256 :: digest ( self . 0 . expose_secret ( ) )
48+ }
49+ }
50+
51+ impl FromStr for AccessToken {
52+ type Err = AccessTokenError ;
53+
54+ /// Parse a string into an access token.
55+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
56+ let suffix = s
57+ . strip_prefix ( Self :: PREFIX )
3858 . ok_or ( AccessTokenError :: MissingPrefix ) ?;
3959
4060 if suffix. len ( ) != Self :: RAW_LENGTH + 1 {
4161 return Err ( AccessTokenError :: InvalidLength ) ;
4262 }
4363
44- let suffix = std:: str:: from_utf8 ( suffix) . map_err ( |_| AccessTokenError :: InvalidCharacter ) ?;
4564 if !suffix. chars ( ) . all ( |c| char:: is_ascii_alphanumeric ( & c) ) {
4665 return Err ( AccessTokenError :: InvalidCharacter ) ;
4766 }
@@ -58,23 +77,6 @@ impl AccessToken {
5877
5978 Ok ( Self ( raw. into ( ) ) )
6079 }
61-
62- /// Wrap the raw access token with the token prefix and a checksum.
63- ///
64- /// This turns e.g. `ABC` into `cio_tp_ABC{checksum}`.
65- pub fn finalize ( & self ) -> SecretString {
66- let raw = self . 0 . expose_secret ( ) ;
67- let checksum = checksum ( raw. as_bytes ( ) ) ;
68- format ! ( "{}{raw}{checksum}" , Self :: PREFIX ) . into ( )
69- }
70-
71- /// Generate a SHA256 hash of the access token.
72- ///
73- /// This is used to create a hashed version of the token for storage in
74- /// the database to avoid storing the plaintext token.
75- pub fn sha256 ( & self ) -> Output < Sha256 > {
76- Sha256 :: digest ( self . 0 . expose_secret ( ) )
77- }
7880}
7981
8082/// The error type for parsing access tokens.
@@ -129,42 +131,33 @@ mod tests {
129131 }
130132
131133 #[ test]
132- fn test_from_byte_str ( ) {
134+ fn test_from_str ( ) {
133135 let token = AccessToken :: generate ( ) . finalize ( ) ;
134136 let token = token. expose_secret ( ) ;
135- let token2 = assert_ok ! ( AccessToken :: from_byte_str ( token . as_bytes ( ) ) ) ;
137+ let token2 = assert_ok ! ( token . parse :: < AccessToken > ( ) ) ;
136138 assert_eq ! ( token2. finalize( ) . expose_secret( ) , token) ;
137139
138- let bytes = b "cio_tp_0000000000000000000000000000000w";
139- assert_ok ! ( AccessToken :: from_byte_str ( bytes ) ) ;
140+ let str = "cio_tp_0000000000000000000000000000000w" ;
141+ assert_ok ! ( str . parse :: < AccessToken > ( ) ) ;
140142
141- let bytes = b"invalid_token" ;
142- assert_err_eq ! (
143- AccessToken :: from_byte_str( bytes) ,
144- AccessTokenError :: MissingPrefix
145- ) ;
143+ let str = "invalid_token" ;
144+ assert_err_eq ! ( str . parse:: <AccessToken >( ) , AccessTokenError :: MissingPrefix ) ;
146145
147- let bytes = b"cio_tp_invalid_token" ;
148- assert_err_eq ! (
149- AccessToken :: from_byte_str( bytes) ,
150- AccessTokenError :: InvalidLength
151- ) ;
146+ let str = "cio_tp_invalid_token" ;
147+ assert_err_eq ! ( str . parse:: <AccessToken >( ) , AccessTokenError :: InvalidLength ) ;
152148
153- let bytes = b"cio_tp_00000000000000000000000000" ;
154- assert_err_eq ! (
155- AccessToken :: from_byte_str( bytes) ,
156- AccessTokenError :: InvalidLength
157- ) ;
149+ let str = "cio_tp_00000000000000000000000000" ;
150+ assert_err_eq ! ( str . parse:: <AccessToken >( ) , AccessTokenError :: InvalidLength ) ;
158151
159- let bytes = b "cio_tp_000000@0000000000000000000000000";
152+ let str = "cio_tp_000000@0000000000000000000000000" ;
160153 assert_err_eq ! (
161- AccessToken :: from_byte_str ( bytes ) ,
154+ str . parse :: < AccessToken > ( ) ,
162155 AccessTokenError :: InvalidCharacter
163156 ) ;
164157
165- let bytes = b "cio_tp_00000000000000000000000000000000";
158+ let str = "cio_tp_00000000000000000000000000000000" ;
166159 assert_err_eq ! (
167- AccessToken :: from_byte_str ( bytes ) ,
160+ str . parse :: < AccessToken > ( ) ,
168161 AccessTokenError :: InvalidChecksum {
169162 claimed: '0' ,
170163 actual: 'w' ,
0 commit comments