11package me .kavin .piped .server .handlers .auth ;
22
33import com .fasterxml .jackson .core .JsonProcessingException ;
4- import com .nimbusds .jwt .JWTClaimsSet ;
4+ import com .nimbusds .jose .JOSEException ;
5+ import com .nimbusds .jose .proc .BadJOSEException ;
6+ import com .nimbusds .jwt .JWT ;
7+ import com .nimbusds .jwt .JWTParser ;
58import com .nimbusds .oauth2 .sdk .*;
69import com .nimbusds .oauth2 .sdk .auth .ClientAuthentication ;
710import com .nimbusds .oauth2 .sdk .auth .ClientSecretBasic ;
811import com .nimbusds .oauth2 .sdk .id .State ;
12+ import com .nimbusds .oauth2 .sdk .pkce .CodeChallengeMethod ;
13+ import com .nimbusds .oauth2 .sdk .pkce .CodeVerifier ;
914import com .nimbusds .openid .connect .sdk .*;
15+ import com .nimbusds .openid .connect .sdk .claims .IDTokenClaimsSet ;
1016import com .nimbusds .openid .connect .sdk .claims .UserInfo ;
1117import io .activej .http .HttpResponse ;
1218import jakarta .persistence .criteria .CriteriaBuilder ;
@@ -131,16 +137,20 @@ public static HttpResponse oidcLoginResponse(OidcProvider provider, String redir
131137 }
132138
133139 URI callback = new URI (Constants .PUBLIC_URL + "/oidc/" + provider .name + "/callback" );
134- OidcData data = new OidcData (redirectUri );
140+ CodeVerifier codeVerifier = new CodeVerifier ();
141+ OidcData data = new OidcData (redirectUri , codeVerifier );
135142 String state = data .getState ();
136143
137144 PENDING_OIDC .put (state , data );
138145
139146 AuthenticationRequest oidcRequest = new AuthenticationRequest .Builder (
140147 new ResponseType ("code" ),
141148 new Scope ("openid" ),
142- provider .clientID , callback ).endpointURI (provider .authUri )
143- .state (new State (state )).nonce (data .nonce ).build ();
149+ provider .clientID , callback )
150+ .endpointURI (provider .authUri )
151+ .codeChallenge (codeVerifier , CodeChallengeMethod .S256 )
152+ .state (new State (state ))
153+ .nonce (data .nonce ).build ();
144154
145155 if (redirectUri .equals (Constants .FRONTEND_URL + "/login" )) {
146156 return HttpResponse .redirect302 (oidcRequest .toURI ().toString ());
@@ -155,24 +165,25 @@ public static HttpResponse oidcLoginResponse(OidcProvider provider, String redir
155165 "\" >here</a></body></html>" );
156166 }
157167 public static HttpResponse oidcCallbackResponse (OidcProvider provider , URI requestUri ) throws Exception {
158- ClientAuthentication clientAuth = new ClientSecretBasic (provider .clientID , provider .clientSecret );
159-
160- AuthenticationSuccessResponse sr = parseOidcUri (requestUri );
168+ AuthenticationSuccessResponse authResponse = parseOidcUri (requestUri );
161169
162- OidcData data = PENDING_OIDC .get (sr .getState ().toString ());
170+ OidcData data = PENDING_OIDC .get (authResponse .getState ().toString ());
163171 if (data == null ) {
164172 return HttpResponse .ofCode (400 ).withHtml (
165173 "Your oidc provider sent invalid state data. Try again or contact your oidc admin"
166174 );
167175 }
168176
169177 URI callback = new URI (Constants .PUBLIC_URL + "/oidc/" + provider .name + "/callback" );
170- AuthorizationCode code = sr .getAuthorizationCode ();
171- AuthorizationGrant codeGrant = new AuthorizationCodeGrant (code , callback );
178+ AuthorizationCode code = authResponse .getAuthorizationCode ();
172179
180+ AuthorizationGrant codeGrant = new AuthorizationCodeGrant (code , callback , data .pkceVerifier );
173181
182+ ClientAuthentication clientAuth = new ClientSecretBasic (provider .clientID , provider .clientSecret );
174183 TokenRequest tokenReq = new TokenRequest (provider .tokenUri , clientAuth , codeGrant );
175- OIDCTokenResponse tokenResponse = (OIDCTokenResponse ) OIDCTokenResponseParser .parse (tokenReq .toHTTPRequest ().send ());
184+
185+ com .nimbusds .oauth2 .sdk .http .HTTPResponse tokenResponseText = tokenReq .toHTTPRequest ().send ();
186+ OIDCTokenResponse tokenResponse = (OIDCTokenResponse ) OIDCTokenResponseParser .parse (tokenResponseText );
176187
177188 if (!tokenResponse .indicatesSuccess ()) {
178189 TokenErrorResponse errorResponse = tokenResponse .toErrorResponse ();
@@ -181,11 +192,17 @@ public static HttpResponse oidcCallbackResponse(OidcProvider provider, URI reque
181192
182193 OIDCTokenResponse successResponse = tokenResponse .toSuccessResponse ();
183194
184- if (data .isInvalidNonce ((String ) successResponse .getOIDCTokens ().getIDToken ().getJWTClaimsSet ().getClaim ("nonce" ))) {
185- return HttpResponse .ofCode (400 ).withHtml (
186- "Your oidc provider sent an invalid nonce. Try again or contact your oidc admin"
187- );
188- }
195+ JWT idToken = JWTParser .parse (successResponse .getOIDCTokens ().getIDTokenString ());
196+
197+ try {
198+ provider .validator .validate (idToken , data .nonce );
199+ } catch (BadJOSEException e ) {
200+ System .out .println ("Invalid token received: " + e .toString ());
201+ return HttpResponse .ofCode (400 ).withHtml ("Received a bad token. Please try again" );
202+ } catch (JOSEException e ) {
203+ System .out .println ("Token processing error" + e .toString ());
204+ return HttpResponse .ofCode (500 ).withHtml ("Internal processing error. Please try again" );
205+ }
189206
190207 UserInfoRequest ur = new UserInfoRequest (provider .userinfoUri , successResponse .getOIDCTokens ().getBearerAccessToken ());
191208 UserInfoResponse userInfoResponse = UserInfoResponse .parse (ur .toHTTPRequest ().send ());
@@ -200,38 +217,86 @@ public static HttpResponse oidcCallbackResponse(OidcProvider provider, URI reque
200217
201218 UserInfo userInfo = userInfoResponse .toSuccessResponse ().getUserInfo ();
202219
203-
204- String uid = userInfo .getSubject ().toString ();
220+ String sub = userInfo .getSubject ().toString ();
205221 String sessionId ;
206222 try (Session s = DatabaseSessionFactory .createSession ()) {
207- // TODO: Add oidc provider to database
208- String dbName = provider + "-" + uid ;
209223 CriteriaBuilder cb = s .getCriteriaBuilder ();
210- CriteriaQuery <User > cr = cb .createQuery (User .class );
211- Root <User > root = cr .from (User .class );
212- cr .select (root ).where (root .get ("username" ).in (
213- dbName
214- ));
224+ CriteriaQuery <OidcUserData > cr = cb .createQuery (OidcUserData .class );
225+ Root <OidcUserData > root = cr .from (OidcUserData .class );
215226
216- User dbuser = s . createQuery ( cr ). uniqueResult ( );
227+ cr . select ( root ). where ( root . get ( "sub" ). in ( sub ) );
217228
218- if (dbuser == null ) {
219- User newuser = new User (dbName , "" , Set .of ());
229+ OidcUserData dbuser = s .createQuery (cr ).uniqueResult ();
230+
231+ if (dbuser != null ) {
232+ sessionId = dbuser .getUser ().getSessionId ();
233+ } else {
234+ String username = userInfo .getPreferredUsername ();
235+ OidcUserData newUser = new OidcUserData (sub , username , provider .name );
220236
221237 var tr = s .beginTransaction ();
222- s .persist (newuser );
238+ s .persist (newUser );
223239 tr .commit ();
224240
225-
226- sessionId = newuser .getSessionId ();
227- } else sessionId = dbuser .getSessionId ();
241+ sessionId = newUser .getUser ().getSessionId ();
242+ }
228243 }
229244 return HttpResponse .redirect302 (data .data + "?session=" + sessionId );
230-
231245 }
232246
233- public static HttpResponse oidcDeleteResponse (OidcProvider provider , URI requestUri ) throws Exception {
234- ClientAuthentication clientAuth = new ClientSecretBasic (provider .clientID , provider .clientSecret );
247+ public static HttpResponse oidcDeleteRequest (String session ) throws Exception {
248+
249+ if (StringUtils .isBlank (session )) {
250+ return HttpResponse .ofCode (400 ).withHtml ("session is a required parameter" );
251+ }
252+
253+ OidcProvider provider = null ;
254+ try (Session s = DatabaseSessionFactory .createSession ()) {
255+
256+ User user = DatabaseHelper .getUserFromSession (session );
257+
258+ if (user == null ) {
259+ return HttpResponse .ofCode (400 ).withHtml ("User not found" );
260+ }
261+
262+ CriteriaBuilder cb = s .getCriteriaBuilder ();
263+ CriteriaQuery <OidcUserData > cr = cb .createQuery (OidcUserData .class );
264+ Root <OidcUserData > root = cr .from (OidcUserData .class );
265+ cr .select (root ).where (cb .equal (root .get ("user" ), user ));
266+
267+ OidcUserData oidcUserData = s .createQuery (cr ).uniqueResult ();
268+
269+ for (OidcProvider test : Constants .OIDC_PROVIDERS ) {
270+ if (test .name .equals (oidcUserData .getProvider ())) {
271+ provider = test ;
272+ }
273+ }
274+ }
275+
276+ if (provider == null ) {
277+ return HttpResponse .ofCode (400 ).withHtml ("Invalid user" );
278+ }
279+ CodeVerifier pkceVerifier = new CodeVerifier ();
280+
281+ URI callback = URI .create (String .format ("%s/oidc/%s/delete" , Constants .PUBLIC_URL , provider .name ));
282+ OidcData data = new OidcData (session + "|" + Instant .now ().getEpochSecond (), pkceVerifier );
283+ String state = data .getState ();
284+ PENDING_OIDC .put (state , data );
285+
286+ AuthenticationRequest oidcRequest = new AuthenticationRequest .Builder (
287+ new ResponseType ("code" ),
288+ new Scope ("openid" ), provider .clientID , callback )
289+ .endpointURI (provider .authUri )
290+ .codeChallenge (pkceVerifier , CodeChallengeMethod .S256 )
291+ .state (new State (state ))
292+ .nonce (data .nonce )
293+ // This parameter is optional and the idp does't have to honor it.
294+ .maxAge (0 )
295+ .build ();
296+
297+ return HttpResponse .redirect302 (oidcRequest .toURI ().toString ());
298+ }
299+ public static HttpResponse oidcDeleteCallback (OidcProvider provider , URI requestUri ) throws Exception {
235300
236301 AuthenticationSuccessResponse sr = parseOidcUri (requestUri );
237302
@@ -247,8 +312,9 @@ public static HttpResponse oidcDeleteResponse(OidcProvider provider, URI request
247312
248313 URI callback = new URI (Constants .PUBLIC_URL + "/oidc/" + provider .name + "/delete" );
249314 AuthorizationCode code = sr .getAuthorizationCode ();
250- AuthorizationGrant codeGrant = new AuthorizationCodeGrant (code , callback );
315+ AuthorizationGrant codeGrant = new AuthorizationCodeGrant (code , callback , data . pkceVerifier );
251316
317+ ClientAuthentication clientAuth = new ClientSecretBasic (provider .clientID , provider .clientSecret );
252318
253319 TokenRequest tokenRequest = new TokenRequest (provider .tokenUri , clientAuth , codeGrant );
254320 TokenResponse tokenResponse = OIDCTokenResponseParser .parse (tokenRequest .toHTTPRequest ().send ());
@@ -260,15 +326,24 @@ public static HttpResponse oidcDeleteResponse(OidcProvider provider, URI request
260326
261327 OIDCTokenResponse successResponse = (OIDCTokenResponse ) tokenResponse .toSuccessResponse ();
262328
263- JWTClaimsSet claims = successResponse .getOIDCTokens ().getIDToken ().getJWTClaimsSet ();
329+ JWT idToken = JWTParser .parse (successResponse .getOIDCTokens ().getIDTokenString ());
330+
331+ IDTokenClaimsSet claims ;
332+ try {
333+ claims = provider .validator .validate (idToken , data .nonce );
334+ } catch (BadJOSEException e ) {
335+ System .out .println ("Invalid token received: " + e .toString ());
336+ return HttpResponse .ofCode (400 ).withHtml ("Received a bad token. Please try again" );
337+ } catch (JOSEException e ) {
338+ System .out .println ("Token processing error" + e .toString ());
339+ return HttpResponse .ofCode (500 ).withHtml ("Internal processing error. Please try again" );
340+ }
264341
265- if (data .isInvalidNonce ((String ) claims .getClaim ("nonce" ))) {
266- return HttpResponse .ofCode (400 ).withHtml (
267- "Your oidc provider sent an invalid nonce. Please try again or contact your oidc admin."
268- );
269- }
342+ Long authTime = (Long ) claims .getNumberClaim ("auth_time" );
270343
271- long authTime = (long ) claims .getClaim ("auth_time" );
344+ if (authTime == null ) {
345+ return HttpResponse .ofCode (400 ).withHtml ("Couldn't get the `auth_time` claim from the provided id token" );
346+ }
272347
273348 if (authTime < start ) {
274349 return HttpResponse .ofCode (500 ).withHtml (
@@ -277,7 +352,6 @@ public static HttpResponse oidcDeleteResponse(OidcProvider provider, URI request
277352 }
278353
279354 try (Session s = DatabaseSessionFactory .createSession ()) {
280-
281355 var tr = s .beginTransaction ();
282356 s .remove (DatabaseHelper .getUserFromSession (session ));
283357 tr .commit ();
@@ -297,31 +371,6 @@ public static byte[] deleteUserResponse(String session, String pass) throws IOEx
297371
298372 String hash = user .getPassword ();
299373
300- if (hash .isEmpty ()) {
301-
302- CriteriaBuilder cb = s .getCriteriaBuilder ();
303- CriteriaQuery <OidcUserData > cr = cb .createQuery (OidcUserData .class );
304- Root <OidcUserData > root = cr .from (OidcUserData .class );
305- cr .select (root ).where (cb .equal (root .get ("user" ), user .getId ()));
306-
307- OidcUserData oidcUserData = s .createQuery (cr ).uniqueResult ();
308-
309- //TODO: Get user from oidc table and lookup provider
310- OidcProvider provider = Constants .OIDC_PROVIDERS .get (0 );
311- URI callback = URI .create (String .format ("%s/oidc/%s/delete" , Constants .PUBLIC_URL , provider .name ));
312- OidcData data = new OidcData (session + "|" + Instant .now ().getEpochSecond ());
313- String state = data .getState ();
314- PENDING_OIDC .put (state , data );
315-
316- AuthenticationRequest oidcRequest = new AuthenticationRequest .Builder (
317- new ResponseType ("code" ),
318- new Scope ("openid" ), provider .clientID , callback ).endpointURI (provider .authUri )
319- .state (new State (state )).nonce (data .nonce ).maxAge (0 ).build ();
320-
321-
322- return mapper .writeValueAsBytes (mapper .createObjectNode ()
323- .put ("redirect" , oidcRequest .toURI ().toString ()));
324- }
325374 if (!hashMatch (hash , pass ))
326375 ExceptionHandler .throwErrorResponse (new IncorrectCredentialsResponse ());
327376
@@ -333,7 +382,6 @@ public static byte[] deleteUserResponse(String session, String pass) throws IOEx
333382 }
334383 }
335384
336-
337385 public static byte [] logoutResponse (String session ) throws JsonProcessingException {
338386
339387 if (StringUtils .isBlank (session ))
0 commit comments