Skip to content

Commit

Permalink
First shot at new login API
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanrdoherty committed Nov 25, 2024
1 parent 22dd5ea commit 4b21a6b
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 29 deletions.
1 change: 1 addition & 0 deletions Model/src/main/java/org/gusdb/wdk/core/api/JsonKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ public class JsonKeys {
public static final String PREFERENCES = "preferences";
public static final String GLOBAL = "global";
public static final String PROJECT = "project";
public static final String BEARER_TOKEN = "bearerToken";

// date and date range keys
public static final String MIN_DATE = "minDate";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import java.util.Set;
import java.util.UUID;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
Expand All @@ -23,6 +25,7 @@
import org.gusdb.fgputil.EncryptionUtil;
import org.gusdb.fgputil.FormatUtil;
import org.gusdb.fgputil.Tuples.TwoTuple;
import org.gusdb.fgputil.functional.Either;
import org.gusdb.fgputil.web.CookieBuilder;
import org.gusdb.fgputil.web.LoginCookieFactory;
import org.gusdb.oauth2.client.ValidatedToken;
Expand All @@ -41,13 +44,20 @@
import org.json.JSONException;
import org.json.JSONObject;

import com.google.common.net.HttpHeaders;

@Path("/")
public class SessionService extends AbstractWdkService {

private static final Logger LOG = Logger.getLogger(SessionService.class);

private enum ResponseType {
REDIRECT, JSON;
}

private static class UserTupleEither extends Either<TwoTuple<ValidatedToken, User>, String> {
public UserTupleEither(TwoTuple<ValidatedToken, User> userTuple) { super(userTuple, null); }
public UserTupleEither(String errorMessage) { super(null, errorMessage); }
}

public static final int EXPIRATION_3_YEARS_SECS = 3 * 365 * 24 * 60 * 60;

private static final String REFERRER_HEADER_KEY = "Referer";
Expand Down Expand Up @@ -106,6 +116,14 @@ private static String generateStateToken(WdkModel wdkModel) throws WdkModelExcep
return EncryptionUtil.encrypt(saltedString);
}

@GET
@Path("create-guest")
@Produces(MediaType.APPLICATION_JSON)
public Response createGuest() throws WdkModelException {
TwoTuple<ValidatedToken, User> guest = getWdkModel().getUserFactory().createUnregisteredUser();
return getSuccessResponse(guest, null, ResponseType.JSON);
}

@GET
@Path("login")
public Response processOauthLogin(
Expand All @@ -128,7 +146,8 @@ public Response processOauthLogin(
// Was existing bearer token submitted with this request?
User oldUser = getRequestingUser();
if (!oldUser.isGuest()) {
return createRedirectResponse(redirectUrl).build();
// TODO: should this be 409? Work it out with UI
throw new BadRequestException("Only guests can log in");
}

try {
Expand Down Expand Up @@ -157,12 +176,19 @@ public Response processOauthLogin(
// transfer ownership from guest to logged-in user
transferOwnership(oldUser, newUser, wdkModel);

// determine response type
ResponseType responseType = getHeaders()
.get(HttpHeaders.ACCEPT)
.stream().findFirst()
.map(val -> val.equals(MediaType.APPLICATION_JSON) ? ResponseType.JSON : ResponseType.REDIRECT)
.orElse(ResponseType.REDIRECT);

// login successful; create redirect response
return getSuccessResponse(bearerToken, newUser, oldUser, redirectUrl, true);
return getSuccessResponse(new TwoTuple<>(bearerToken, newUser), redirectUrl, responseType);
}
catch (InvalidPropertiesException ex) {
LOG.error("Could not authenticate user's identity. Exception thrown: ", ex);
return createJsonResponse(false, "Invalid auth token or redirect URI", null).build();
return createJsonResponse(new UserTupleEither("Invalid auth token or redirect URI"), null);
}
catch (Exception ex) {
LOG.error("Unsuccessful login attempt: " + ex.getMessage(), ex);
Expand Down Expand Up @@ -206,15 +232,16 @@ public Response processDbLogin(@HeaderParam(REFERRER_HEADER_KEY) String referrer
// transfer ownership from guest to logged-in user
transferOwnership(oldUser, newUser, wdkModel);

return getSuccessResponse(bearerToken, newUser, oldUser, redirectUrl, false);
// return success JSON response
return getSuccessResponse(new TwoTuple<>(bearerToken, newUser), redirectUrl, ResponseType.JSON);

}
catch (JSONException e) {
throw new RequestMisformatException(e.getMessage());
}
catch (InvalidPropertiesException ex) {
LOG.error("Could not authenticate user's identity. Exception thrown: ", ex);
return createJsonResponse(false, "Invalid username or password", null).build();
return createJsonResponse(new UserTupleEither("Invalid username or password"), null);
}
}

Expand All @@ -231,33 +258,33 @@ protected void transferOwnership(User oldUser, User newUser, WdkModel wdkModel)
*
* @param bearerToken bearer token for the new user
* @param newUser newly logged in user
* @param oldUser user previously on session, if any
* @param redirectUrl incoming original page
* @param isRedirectResponse whether to return redirect or JSON response
* @return success response
* @throws WdkModelException
*/
private Response getSuccessResponse(ValidatedToken bearerToken, User newUser, User oldUser,
String redirectUrl, boolean isRedirectResponse) throws WdkModelException {
private Response getSuccessResponse(TwoTuple<ValidatedToken, User> newUser,
String redirectUrl, ResponseType responseType) throws WdkModelException {

// only using this to synchronize on the user
TemporaryUserData tmpData = getTemporaryUserData();

synchronized(tmpData) {

// 3-year expiration (should change secret key before then)
// FIXME: cookie sending should be removed/deleted once client has support for header/response body transmission
CookieBuilder bearerTokenCookie = new CookieBuilder(
HttpHeaders.AUTHORIZATION,
bearerToken.getTokenValue());
"Bearer " + newUser.getFirst().getTokenValue());
bearerTokenCookie.setMaxAge(EXPIRATION_3_YEARS_SECS);

redirectUrl = getSuccessRedirectUrl(redirectUrl, newUser, bearerTokenCookie);
redirectUrl = getSuccessRedirectUrl(redirectUrl, newUser.getSecond(), bearerTokenCookie);

return (isRedirectResponse ?
createRedirectResponse(redirectUrl) :
createJsonResponse(true, null, redirectUrl)
)
.cookie(bearerTokenCookie.toJaxRsCookie())
.build();
switch(responseType) {
case REDIRECT: return createRedirectResponse(redirectUrl).cookie(bearerTokenCookie.toJaxRsCookie()).build();
case JSON: return createJsonResponse(new UserTupleEither(newUser), redirectUrl);
default: throw new IllegalStateException(); // should never happen
}
}
}

Expand Down Expand Up @@ -316,19 +343,31 @@ public static NewCookie getAuthCookie(String tokenValue) {
}

/**
* Convenience method to set up a response builder that returns JSON containing request result
* Creates a JSON response for requests served by this class
*
* @param success whether the login was successful
* @param message a failure message if not successful
* @param redirectUrl url to which to redirect the user if successful
* @return partially constructed response
* @param userTupleOrErrorMessage Either object containing a token/user (success case) or error message
* @param redirectUrl URL to which use should eventually be redirected
* @return JSON response
*/
private static ResponseBuilder createJsonResponse(boolean success, String message, String redirectUrl) {
return Response.ok(new JSONObject()
.put(JsonKeys.SUCCESS, success)
.put(JsonKeys.MESSAGE, message)
.put(JsonKeys.REDIRECT_URL, redirectUrl)
.toString());
private static Response createJsonResponse(UserTupleEither userTupleOrErrorMessage, String redirectUrl) {
JSONObject json = new JSONObject()
.put(JsonKeys.SUCCESS, userTupleOrErrorMessage.isLeft())
.put(JsonKeys.REDIRECT_URL, redirectUrl);
userTupleOrErrorMessage
.ifLeft(userTuple ->
json
.put(JsonKeys.BEARER_TOKEN, userTuple.getFirst().getTokenValue())
.put(JsonKeys.USER_ID, userTuple.getSecond().getUserId())
.put(JsonKeys.IS_GUEST, userTuple.getSecond().isGuest())
)
.ifRight(errorMessage ->
json
.put(JsonKeys.MESSAGE, errorMessage)
);
return Response
.ok(json.toString())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.build();
}

/**
Expand Down

0 comments on commit 4b21a6b

Please sign in to comment.