1414import org .json .JSONObject ;
1515
1616import java .io .IOException ;
17+ import java .net .URLDecoder ;
18+ import java .net .URLEncoder ;
19+ import java .nio .charset .StandardCharsets ;
1720
1821public class AuthenticationEndpoint extends AbstractServlet
1922{
23+ private static final String RETURN_TO_COOKIE = "plex_return_to" ;
24+
2025 @ GetMapping (endpoint = "/oauth2/login" )
2126 public String login (HttpServletRequest request , HttpServletResponse response ) throws IOException
2227 {
@@ -26,6 +31,20 @@ public String login(HttpServletRequest request, HttpServletResponse response) th
2631 response .sendError (HttpServletResponse .SC_NOT_FOUND , "Authentication is not enabled." );
2732 return null ;
2833 }
34+
35+ String returnTo = sanitizeReturnTo (request .getParameter ("return_to" ));
36+ String cookieValue = returnTo == null ? "" : URLEncoder .encode (returnTo , StandardCharsets .UTF_8 );
37+ Cookie returnCookie = new Cookie (RETURN_TO_COOKIE , cookieValue );
38+ returnCookie .setHttpOnly (true );
39+ returnCookie .setPath ("/" );
40+ returnCookie .setMaxAge (returnTo == null ? 0 : 600 );
41+ returnCookie .setAttribute ("SameSite" , "Lax" );
42+ if (request .isSecure () || "https" .equalsIgnoreCase (request .getHeader ("X-Forwarded-Proto" )))
43+ {
44+ returnCookie .setSecure (true );
45+ }
46+ response .addCookie (returnCookie );
47+
2948 response .sendRedirect (provider .buildAuthorizeUrl (request ));
3049 return null ;
3150 }
@@ -54,7 +73,12 @@ public String callback(HttpServletRequest request, HttpServletResponse response)
5473 + "<p>" + escape (e .getMessage ()) + "</p>"
5574 + "<p><a href=\" /oauth2/login\" >Try again</a></p>" ;
5675 }
57- response .sendRedirect ("/" );
76+
77+ String raw = readCookie (request , RETURN_TO_COOKIE );
78+ String decoded = raw == null || raw .isEmpty () ? null : URLDecoder .decode (raw , StandardCharsets .UTF_8 );
79+ String target = sanitizeReturnTo (decoded );
80+ clearReturnToCookie (request , response );
81+ response .sendRedirect (target == null ? "/" : target );
5882 return null ;
5983 }
6084
@@ -103,19 +127,51 @@ public String me(HttpServletRequest request, HttpServletResponse response)
103127 }
104128
105129 private static String readSessionCookie (HttpServletRequest request )
130+ {
131+ return readCookie (request , OAuth2Provider .SESSION_COOKIE );
132+ }
133+
134+ private static String readCookie (HttpServletRequest request , String name )
106135 {
107136 Cookie [] cookies = request .getCookies ();
108137 if (cookies == null ) return null ;
109138 for (Cookie cookie : cookies )
110139 {
111- if (OAuth2Provider . SESSION_COOKIE .equals (cookie .getName ()))
140+ if (name .equals (cookie .getName ()))
112141 {
113142 return cookie .getValue ();
114143 }
115144 }
116145 return null ;
117146 }
118147
148+ private void clearReturnToCookie (HttpServletRequest request , HttpServletResponse response )
149+ {
150+ Cookie clear = new Cookie (RETURN_TO_COOKIE , "" );
151+ clear .setHttpOnly (true );
152+ clear .setPath ("/" );
153+ clear .setMaxAge (0 );
154+ clear .setAttribute ("SameSite" , "Lax" );
155+ if (request .isSecure () || "https" .equalsIgnoreCase (request .getHeader ("X-Forwarded-Proto" )))
156+ {
157+ clear .setSecure (true );
158+ }
159+ response .addCookie (clear );
160+ }
161+
162+ private static String sanitizeReturnTo (String value )
163+ {
164+ if (value == null || value .isEmpty ()) return null ;
165+ if (!value .startsWith ("/" )) return null ;
166+ if (value .startsWith ("//" ) || value .startsWith ("/\\ " )) return null ;
167+ for (int i = 0 ; i < value .length (); i ++)
168+ {
169+ char c = value .charAt (i );
170+ if (c == '\n' || c == '\r' || c == '\\' ) return null ;
171+ }
172+ return value ;
173+ }
174+
119175 private static String escape (String s )
120176 {
121177 if (s == null ) return "" ;
0 commit comments