diff --git a/auth-service/build.gradle b/auth-service/build.gradle index 110d6b4..7c48103 100644 --- a/auth-service/build.gradle +++ b/auth-service/build.gradle @@ -10,6 +10,8 @@ liberty { dropins = [war] bootstrapProperties = ['httpPort': httpPort, 'httpsPort': httpsPort] configDirectory = file('src/main/liberty/config') + jvmOptions = ['-Dorg.libertybikes.auth.service.github.GitHubOAuthAPI/mp-rest/url=https://github.com', + '-Dorg.libertybikes.auth.service.github.GitHubUserAPI/mp-rest/url=https://api.github.com'] } } dependencies { diff --git a/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubAuth.java b/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubAuth.java new file mode 100644 index 0000000..5047ab5 --- /dev/null +++ b/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubAuth.java @@ -0,0 +1,49 @@ +package org.libertybikes.auth.service.github; + +import java.net.URI; +import java.util.UUID; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.libertybikes.auth.service.AuthApp; + +@Path("/GitHubAuth") +@ApplicationScoped +public class GitHubAuth { + + private final static String GITHUB_AUTH_URL = "https://github.com/login/oauth/authorize"; + + @Inject + @ConfigProperty(name = "github_key") + String key; + + @Inject + @ConfigProperty(name = "auth_url", defaultValue = AuthApp.HTTPS_AUTH_SERVICE) + String authUrl; + + @GET + public Response getAuthURL(@Context HttpServletRequest request) { + try { + String randomCode = UUID.randomUUID().toString(); + request.getSession().setAttribute("github", randomCode); + + // GitHub will tell the users browser to go to this address once they are done authing. + String callbackURL = authUrl + "/GitHubCallback"; + + // send the user to GitHub to be authenticated + String redirectURL = GITHUB_AUTH_URL + "?client_id=" + key + "&redirect_url=" + callbackURL + "&scope=user:email&state=" + randomCode; + return Response.temporaryRedirect(new URI(redirectURL)).build(); + } catch (Exception e) { + e.printStackTrace(); + return Response.status(500).build(); + } + } + +} diff --git a/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubCallback.java b/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubCallback.java new file mode 100644 index 0000000..cdf6be3 --- /dev/null +++ b/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubCallback.java @@ -0,0 +1,131 @@ +package org.libertybikes.auth.service.github; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Valid; +import javax.validation.Validator; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.libertybikes.auth.service.AuthApp; +import org.libertybikes.auth.service.JwtAuth; +import org.libertybikes.auth.service.github.GitHubOAuthAPI.GithubTokenResponse; +import org.libertybikes.auth.service.github.GitHubUserAPI.EmailData; + +@Path("/GitHubCallback") +@ApplicationScoped +public class GitHubCallback extends JwtAuth { + + @Inject + GitHubOAuthAPI githubOAuthAPI; + + @Inject + GitHubUserAPI githubUserAPI; + + @Inject + @ConfigProperty(name = "github_key") + String key; + + @Inject + @ConfigProperty(name = "github_secret") + String secret; + + @Inject + @ConfigProperty(name = "frontend_url", defaultValue = AuthApp.FRONTEND_URL) + String frontendUrl; + + @Inject + @ConfigProperty(name = "auth_url", defaultValue = AuthApp.HTTPS_AUTH_SERVICE) + String authUrl; + + @Inject + Validator validator; + + @GET + @Path("/test") + @Valid + public GithubTokenResponse testToken() { + for (Method m : getClass().getMethods()) { + System.out.println("@AGG found method: " + m.getName()); + for (Annotation a : m.getAnnotations()) + System.out.println(" anno=" + a); + } + GithubTokenResponse badToken = new GithubTokenResponse(); + badToken.access_token = "bogus"; + return badToken; + } + + // TODO: need to manually validate return types because the @Valid annotation on our MP Rest Client interface + // is getting lost when the instance is injected into this class. + private void validate(Object obj) { + Objects.requireNonNull(obj); + Set> issues = validator.validate(obj); + if (!issues.isEmpty()) { + throw new ConstraintViolationException(issues); + } + } + + @GET + public Response getAuthURL(@Context HttpServletRequest request) throws IOException, URISyntaxException { + try { + String githubCode = request.getParameter("code"); + String randomCode = (String) request.getSession().getAttribute("github"); + + String thisURL = authUrl + "/GitHubCallback"; + + // First send the user through GitHub OAuth to get permission to read their email address + GithubTokenResponse response = githubOAuthAPI.accessToken(key, secret, githubCode, thisURL, randomCode); +// for (Method m : githubOAuthAPI.getClass().getMethods()) { +// System.out.println("@AGG found method: " + m.getName()); +// for (Annotation a : m.getAnnotations()) +// System.out.println(" anno=" + a); +// } + validate(response); + System.out.println("GitHub access token: " + response.access_token); + + // Once we have the access token, use it to read their email + EmailData[] emails = githubUserAPI.getEmail(response.access_token); + for (EmailData email : emails) + validate(email); + String primaryEmail = null; + for (EmailData data : emails) + if (data.primary) { + primaryEmail = data.email; + break; + } else { + primaryEmail = data.email; + } + System.out.println("Got primary email of: " + primaryEmail); + + Map claims = new HashMap(); + claims.put("valid", "true"); + claims.put("id", "GITHUB:" + primaryEmail); + claims.put("upn", primaryEmail); + claims.put("email", primaryEmail); + return Response.temporaryRedirect(new URI(frontendUrl + "/" + createJwt(claims))).build(); + } catch (Exception e) { + e.printStackTrace(); + return fail(); + } + } + + private Response fail() throws URISyntaxException { + return Response.temporaryRedirect(new URI(frontendUrl)).build(); + } +} \ No newline at end of file diff --git a/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubOAuthAPI.java b/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubOAuthAPI.java new file mode 100644 index 0000000..a941c2e --- /dev/null +++ b/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubOAuthAPI.java @@ -0,0 +1,32 @@ +package org.libertybikes.auth.service.github; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient +@Path("/login/oauth") +public interface GitHubOAuthAPI { + + public static class GithubTokenResponse { + @NotNull + public String access_token; + } + + @GET + @Path("/access_token") + @Produces(MediaType.APPLICATION_JSON) + @Valid // TODO: beanval isn't being honored here when used with MP Rest client + public GithubTokenResponse accessToken(@QueryParam("client_id") String key, + @QueryParam("client_secret") String secret, + @QueryParam("code") String code, + @QueryParam("redirect_uri") String redirect_uri, + @QueryParam("state") String state); + +} diff --git a/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubUserAPI.java b/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubUserAPI.java new file mode 100644 index 0000000..96fae34 --- /dev/null +++ b/auth-service/src/main/java/org/libertybikes/auth/service/github/GitHubUserAPI.java @@ -0,0 +1,29 @@ +package org.libertybikes.auth.service.github; + +import javax.validation.Valid; +import javax.validation.constraints.Email; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient +@Path("/user") +public interface GitHubUserAPI { + + public static class EmailData { + @Email + public String email; + public boolean primary; + } + + @GET + @Path("/emails") + @Produces(MediaType.APPLICATION_JSON) + @Valid // TODO: beanval isn't being called here when rest client instance is injected + public EmailData[] getEmail(@QueryParam("access_token") String accessToken); + +} diff --git a/auth-service/src/main/java/org/libertybikes/auth/service/GoogleAuth.java b/auth-service/src/main/java/org/libertybikes/auth/service/google/GoogleAuth.java similarity index 95% rename from auth-service/src/main/java/org/libertybikes/auth/service/GoogleAuth.java rename to auth-service/src/main/java/org/libertybikes/auth/service/google/GoogleAuth.java index b848962..27e1f08 100644 --- a/auth-service/src/main/java/org/libertybikes/auth/service/GoogleAuth.java +++ b/auth-service/src/main/java/org/libertybikes/auth/service/google/GoogleAuth.java @@ -1,4 +1,4 @@ -package org.libertybikes.auth.service; +package org.libertybikes.auth.service.google; import java.net.URI; import java.util.Arrays; @@ -12,6 +12,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.libertybikes.auth.service.AuthApp; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; import com.google.api.client.http.HttpTransport; diff --git a/auth-service/src/main/java/org/libertybikes/auth/service/GoogleCallback.java b/auth-service/src/main/java/org/libertybikes/auth/service/google/GoogleCallback.java similarity index 97% rename from auth-service/src/main/java/org/libertybikes/auth/service/GoogleCallback.java rename to auth-service/src/main/java/org/libertybikes/auth/service/google/GoogleCallback.java index a8bfa0e..91d1233 100644 --- a/auth-service/src/main/java/org/libertybikes/auth/service/GoogleCallback.java +++ b/auth-service/src/main/java/org/libertybikes/auth/service/google/GoogleCallback.java @@ -1,4 +1,4 @@ -package org.libertybikes.auth.service; +package org.libertybikes.auth.service.google; import java.io.IOException; import java.net.URI; @@ -20,6 +20,8 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.libertybikes.auth.service.AuthApp; +import org.libertybikes.auth.service.JwtAuth; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; diff --git a/auth-service/src/main/liberty/config/server.xml b/auth-service/src/main/liberty/config/server.xml index 3e884ff..489637e 100644 --- a/auth-service/src/main/liberty/config/server.xml +++ b/auth-service/src/main/liberty/config/server.xml @@ -1,12 +1,14 @@ appSecurity-3.0 + beanValidation-2.0 cdi-2.0 jaxrs-2.1 jndi-1.0 jsonb-1.0 mpConfig-1.2 mpJwt-1.0 + mpRestClient-1.0 diff --git a/frontend/prebuild/src/app/login/login.component.html b/frontend/prebuild/src/app/login/login.component.html index 94f45d6..f4c521f 100644 --- a/frontend/prebuild/src/app/login/login.component.html +++ b/frontend/prebuild/src/app/login/login.component.html @@ -18,6 +18,9 @@
+
+ +

or

diff --git a/frontend/prebuild/src/app/login/login.component.ts b/frontend/prebuild/src/app/login/login.component.ts index 9763140..4460300 100644 --- a/frontend/prebuild/src/app/login/login.component.ts +++ b/frontend/prebuild/src/app/login/login.component.ts @@ -71,6 +71,10 @@ export class LoginComponent implements OnInit, OnDestroy { loginGoogle() { window.location.href = `${environment.API_URL_AUTH}/auth-service/GoogleAuth`; } + + loginGithub() { + window.location.href = `${environment.API_URL_AUTH}/auth-service/GitHubAuth`; + } async quickJoin() { // First get an unstarted round ID diff --git a/player-service/src/main/java/org/libertybikes/player/service/Player.java b/player-service/src/main/java/org/libertybikes/player/service/Player.java index 1064dca..d17d196 100644 --- a/player-service/src/main/java/org/libertybikes/player/service/Player.java +++ b/player-service/src/main/java/org/libertybikes/player/service/Player.java @@ -9,7 +9,8 @@ public class Player { public static enum DOMAIN { BASIC, - GMAIL; + GMAIL, + GITHUB; @Override public String toString() { @@ -59,10 +60,11 @@ public class Player { @JsonbTransient public DOMAIN getDomain() { - if (id.startsWith(DOMAIN.GMAIL.toString())) - return DOMAIN.GMAIL; - else - return DOMAIN.BASIC; + for (DOMAIN d : DOMAIN.values()) { + if (id.startsWith(d.toString())) + return d; + } + return DOMAIN.BASIC; } @Override