@@ -105,6 +105,25 @@ pub struct PairingVerifyResponse {
105105 pub token : String ,
106106}
107107
108+ /// Request to verify PIN directly (without session)
109+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
110+ pub struct DirectVerifyRequest {
111+ /// The PIN entered by user
112+ pub pin : String ,
113+ /// Device name provided by client
114+ pub device_name : String ,
115+ /// Device type hint
116+ #[ serde( default ) ]
117+ pub device_type : Option < String > ,
118+ }
119+
120+ /// Response containing the persistent PIN
121+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
122+ pub struct PersistentPinResponse {
123+ /// The 6-digit PIN
124+ pub pin : String ,
125+ }
126+
108127/// QR code data structure
109128#[ derive( Debug , Clone , Serialize , Deserialize ) ]
110129pub struct QrCodeData {
@@ -132,6 +151,8 @@ pub struct PairingManager {
132151 server_url : String ,
133152 /// Certificate fingerprint for QR codes
134153 cert_fingerprint : Option < String > ,
154+ /// Persistent PIN for direct entry (valid for server lifetime)
155+ persistent_pin : Arc < RwLock < String > > ,
135156}
136157
137158impl PairingManager {
@@ -142,6 +163,7 @@ impl PairingManager {
142163 storage,
143164 server_url,
144165 cert_fingerprint : None ,
166+ persistent_pin : Arc :: new ( RwLock :: new ( generate_pin ( ) ) ) ,
145167 }
146168 }
147169
@@ -156,6 +178,7 @@ impl PairingManager {
156178 storage,
157179 server_url,
158180 cert_fingerprint : fingerprint,
181+ persistent_pin : Arc :: new ( RwLock :: new ( generate_pin ( ) ) ) ,
159182 }
160183 }
161184
@@ -164,6 +187,57 @@ impl PairingManager {
164187 self . cert_fingerprint = fingerprint;
165188 }
166189
190+ /// Get the persistent PIN (valid for entire server lifetime)
191+ pub async fn get_persistent_pin ( & self ) -> String {
192+ self . persistent_pin . read ( ) . await . clone ( )
193+ }
194+
195+ /// Regenerate the persistent PIN
196+ pub async fn refresh_persistent_pin ( & self ) -> String {
197+ let mut pin = self . persistent_pin . write ( ) . await ;
198+ * pin = generate_pin ( ) ;
199+ info ! ( "Persistent PIN refreshed" ) ;
200+ pin. clone ( )
201+ }
202+
203+ /// Verify PIN directly without requiring a session
204+ /// This is for direct PIN entry when user navigates to the URL
205+ pub async fn verify_persistent_pin (
206+ & self ,
207+ request : DirectVerifyRequest ,
208+ ) -> PairingResult < PairingVerifyResponse > {
209+ let persistent = self . persistent_pin . read ( ) . await ;
210+
211+ if * persistent != request. pin {
212+ warn ! ( "Invalid persistent PIN attempt" ) ;
213+ return Err ( PairingError :: InvalidPin ) ;
214+ }
215+
216+ // Generate auth token
217+ let token = generate_token ( ) ;
218+ let token_hash = hash_token ( & token) ;
219+
220+ // Create device
221+ let device_type = request
222+ . device_type
223+ . as_deref ( )
224+ . and_then ( |s| s. parse ( ) . ok ( ) )
225+ . unwrap_or ( DeviceType :: Unknown ) ;
226+
227+ let device = Device :: new ( request. device_name , device_type, token_hash) ;
228+ let device_id = device. id . to_string ( ) ;
229+
230+ // Save device
231+ self . storage . save_device ( device) . await ?;
232+
233+ info ! (
234+ "Device {} paired successfully via persistent PIN" ,
235+ device_id
236+ ) ;
237+
238+ Ok ( PairingVerifyResponse { device_id, token } )
239+ }
240+
167241 /// Start a new pairing session
168242 pub async fn start_pairing ( & self ) -> PairingStartResponse {
169243 let session = PairingSession :: new ( ) ;
@@ -295,6 +369,13 @@ impl PairingManager {
295369 }
296370}
297371
372+ /// Generate a 6-digit PIN
373+ fn generate_pin ( ) -> String {
374+ let mut rng = rand:: thread_rng ( ) ;
375+ let pin: u32 = rng. gen_range ( 0 ..1_000_000 ) ;
376+ format ! ( "{:06}" , pin)
377+ }
378+
298379/// Generate a secure random token
299380fn generate_token ( ) -> String {
300381 let mut rng = rand:: thread_rng ( ) ;
0 commit comments