66import com .nimbusds .oauth2 .sdk .http .JakartaServletUtils ;
77import jakarta .servlet .http .HttpServletRequest ;
88import jakarta .servlet .http .HttpSession ;
9+ import oidc .crypto .SimpleEncryptionHandler ;
910import oidc .exceptions .InvalidGrantException ;
1011import oidc .exceptions .UnknownClientException ;
1112import oidc .model .DeviceAuthorization ;
3536
3637import java .io .IOException ;
3738import java .net .URLEncoder ;
39+ import java .nio .charset .Charset ;
3840import java .security .SecureRandom ;
3941import java .time .Instant ;
4042import java .time .temporal .ChronoUnit ;
4143import java .util .*;
4244import java .util .concurrent .atomic .AtomicBoolean ;
45+ import java .util .concurrent .atomic .AtomicReference ;
4346
4447import static com .nimbusds .oauth2 .sdk .GrantType .DEVICE_CODE ;
4548import static java .nio .charset .Charset .defaultCharset ;
@@ -106,12 +109,15 @@ public ResponseEntity<Map<String, Object>> deviceAuthorization(HttpServletReques
106109 );
107110 deviceAuthorizationRepository .save (deviceAuthorization );
108111
112+ String hint = URLEncoder .encode (SimpleEncryptionHandler .encrypt (userCode ), Charset .defaultCharset ());
113+ String verificationUrlWithHint = String .format ("%s?hint=%s" , verificationUrl , hint );
114+
109115 Map <String , Object > results = Map .of (
110116 "device_code" , deviceCode ,
111117 "user_code" , userCode ,
112- "verification_uri" , verificationUrl ,
118+ "verification_uri" , verificationUrlWithHint ,
113119 "verification_uri_complete" , String .format ("%s?user_code=%s" , verificationUrl , userCode ),
114- "qr_code" , QRGenerator .qrCode (verificationUrl ).getImage (),
120+ "qr_code" , QRGenerator .qrCode (verificationUrlWithHint ).getImage (),
115121 //The lifetime in seconds of the "device_code" and "user_code"
116122 "expires_in" , 60 * 15 ,
117123 //The minimum amount of time in seconds that the client SHOULD wait between polling requests to the token endpoint
@@ -122,18 +128,23 @@ public ResponseEntity<Map<String, Object>> deviceAuthorization(HttpServletReques
122128
123129 @ GetMapping (value = "oidc/verify" )
124130 public ModelAndView verification (@ RequestParam (value = "user_code" , required = false ) String userCode ,
131+ @ RequestParam (value = "hint" , required = false ) String hint ,
125132 @ RequestParam (value = "error" , required = false , defaultValue = "false" ) String error ,
126133 HttpServletRequest request ) {
134+ AtomicReference <String > userCodeRef = new AtomicReference <>(userCode );
127135 Map <String , Object > model = new HashMap <>();
128- if (StringUtils .hasText (userCode )) {
136+ if (StringUtils .hasText (hint )) {
137+ userCodeRef .set (SimpleEncryptionHandler .decrypt (hint ));
138+ }
139+ if (StringUtils .hasText (userCodeRef .get ())) {
129140 //When the code checks out, then retrieve the client for displaying purposes
130- findByUserCode (userCode )
141+ findByUserCode (userCodeRef . get () )
131142 .flatMap (deviceAuthorization -> openIDClientRepository .findOptionalByClientId (deviceAuthorization .getClientId ()))
132143 //Check the very strange use-case for the client not existing anymore
133144 .ifPresent (openIDClient -> {
134145 model .put ("client" , openIDClient );
135- model .put ("userCode" , userCode );
136- model .put ("completeURI" , true );
146+ model .put ("userCode" , StringUtils . hasText ( userCode ) ? userCodeRef . get () : null );
147+ model .put ("completeURI" , StringUtils . hasText ( userCode ) );
137148 });
138149 }
139150 model .putIfAbsent ("completeURI" , false );
@@ -169,7 +180,7 @@ public ModelAndView postVerify(@RequestParam Map<String, String> body, HttpServl
169180 logout (request );
170181 return new ModelAndView (new RedirectView (deviceAuthorizeURL (deviceAuthorization ), true ));
171182 })
172- .orElseGet (() -> this .verification (null , "true" , request ));
183+ .orElseGet (() -> this .verification (null , null , "true" , request ));
173184 return modelAndView ;
174185 }
175186
@@ -178,7 +189,7 @@ public ModelAndView deviceAuthorize(@RequestParam(value = "state") String state,
178189 @ RequestParam (value = "user_code" ) String userCode ,
179190 Authentication authentication ) {
180191 LOG .debug (String .format ("/oidc/device_authorize %s %s" , authentication .getDetails (), userCode ));
181- //If the state (e.g. userCode) corresponds with a DeviceAuthentication then mark this as success and inform the user
192+ //If the state (e.g. userCode) corresponds with a DeviceAuthentication then mark this as succes and inform the user
182193 Map <String , Object > model = new HashMap <>();
183194 Optional <DeviceAuthorization > optionalDeviceAuthorization = findByUserCode (userCode );
184195 AtomicBoolean stateMatches = new AtomicBoolean (false );
0 commit comments