Skip to content

Add Feature To Include ORY As Possible Auth Provider #348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.lowcoder.sdk.auth;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.lowcoder.sdk.auth.constants.AuthTypeConstants;
import org.lowcoder.sdk.auth.constants.Oauth2Constants;
import org.lowcoder.sdk.config.SerializeConfig.JsonViews;

import javax.annotation.Nullable;
import java.util.function.Function;

import static org.lowcoder.sdk.auth.constants.Oauth2Constants.CLIENT_ID_PLACEHOLDER;
import static org.lowcoder.sdk.auth.constants.Oauth2Constants.INSTANCE_ID_PLACEHOLDER;

/**
* OAuth2 ORY auth config.
*/
@Getter
public class Oauth2OryAuthConfig extends Oauth2SimpleAuthConfig {

protected String instanceId;

@JsonCreator
public Oauth2OryAuthConfig(
@Nullable String id,
Boolean enable,
Boolean enableRegister,
String source,
String sourceName,
String clientId,
String clientSecret,
String instanceId,
String authType) {
super(id, enable, enableRegister, source, sourceName, clientId, clientSecret, authType);
this.instanceId = instanceId;
}

@Override
public String replaceAuthUrlClientIdPlaceholder(String url) {
return super.replaceAuthUrlClientIdPlaceholder(url).replace(INSTANCE_ID_PLACEHOLDER, instanceId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public String getAuthorizeUrl() {
return switch (authType) {
case AuthTypeConstants.GOOGLE -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.GOOGLE_AUTHORIZE_URL);
case AuthTypeConstants.GITHUB -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.GITHUB_AUTHORIZE_URL);
case AuthTypeConstants.ORY -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.ORY_AUTHORIZE_URL);
default -> null;
};
}
Expand All @@ -73,7 +74,7 @@ public void merge(AbstractAuthConfig oldConfig) {
}
}

private String replaceAuthUrlClientIdPlaceholder(String url) {
public String replaceAuthUrlClientIdPlaceholder(String url) {
return url.replace(CLIENT_ID_PLACEHOLDER, clientId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ public class AuthTypeConstants {
public static final String FORM = "FORM";
public static final String GOOGLE = "GOOGLE";
public static final String GITHUB = "GITHUB";
public static final String ORY = "ORY";
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public class Oauth2Constants {
public static final String REDIRECT_URL_PLACEHOLDER = "$REDIRECT_URL";
public static final String STATE_PLACEHOLDER = "$STATE";

public static final String INSTANCE_ID_PLACEHOLDER = "INSTANCE_ID";

// authorize url
public static final String GITHUB_AUTHORIZE_URL = "https://github.com/login/oauth/authorize"
+ "?response_type=code"
Expand All @@ -23,4 +25,11 @@ public class Oauth2Constants {
+ "&access_type=offline"
+ "&scope=openid email profile"
+ "&prompt=select_account";

public static final String ORY_AUTHORIZE_URL = "https://" + INSTANCE_ID_PLACEHOLDER + "/oauth2/auth"
+ "?response_type=code"
+ "&client_id=" + CLIENT_ID_PLACEHOLDER
+ "&redirect_uri=" + REDIRECT_URL_PLACEHOLDER
+ "&state=" + STATE_PLACEHOLDER
+ "&scope=openid email profile offline_access";
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ public class AuthSourceConstants {
public static final String PHONE = "PHONE";
public static final String GOOGLE = "GOOGLE";
public static final String GITHUB = "GITHUB";
public static final String ORY = "ORY";

// source name
public static final String GOOGLE_NAME = "Google";
public static final String GITHUB_NAME = "Github";
public static final String ORY_NAME = "Ory";

// default source and source name for common protocol
// oauth 2.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package org.lowcoder.sdk.util;

import static org.lowcoder.sdk.auth.constants.AuthTypeConstants.FORM;
import static org.lowcoder.sdk.auth.constants.AuthTypeConstants.GITHUB;
import static org.lowcoder.sdk.auth.constants.AuthTypeConstants.GOOGLE;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
Expand All @@ -12,6 +8,7 @@
import javax.annotation.Nullable;

import org.lowcoder.sdk.auth.EmailAuthConfig;
import org.lowcoder.sdk.auth.Oauth2OryAuthConfig;
import org.lowcoder.sdk.auth.Oauth2SimpleAuthConfig;

import com.fasterxml.jackson.annotation.JsonCreator;
Expand All @@ -32,6 +29,8 @@

import lombok.extern.slf4j.Slf4j;

import static org.lowcoder.sdk.auth.constants.AuthTypeConstants.*;

@Slf4j
public final class JsonUtils {

Expand All @@ -46,6 +45,7 @@ public final class JsonUtils {
OBJECT_MAPPER.registerSubtypes(new NamedType(EmailAuthConfig.class, FORM));
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2SimpleAuthConfig.class, GITHUB));
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2SimpleAuthConfig.class, GOOGLE));
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2OryAuthConfig.class, ORY));
}

public static final JsonNode EMPTY_JSON_NODE = createObjectNode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public boolean isEnableRegister() {
return MapUtils.getBoolean(this, "enableRegister", true);
}

@Nullable
public String getInstanceId() {
return getString("instanceId");
}

@Nullable
public String getClientId() {
return getString("clientId");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package org.lowcoder.api.authentication.request.oauth2;

import static org.lowcoder.sdk.auth.constants.AuthTypeConstants.GITHUB;
import static org.lowcoder.sdk.auth.constants.AuthTypeConstants.GOOGLE;

import java.util.Set;

import org.lowcoder.api.authentication.request.AuthRequest;
import org.lowcoder.api.authentication.request.AuthRequestFactory;
import org.lowcoder.api.authentication.request.oauth2.request.AbstractOauth2Request;
import org.lowcoder.api.authentication.request.oauth2.request.GithubRequest;
import org.lowcoder.api.authentication.request.oauth2.request.GoogleRequest;
import org.lowcoder.api.authentication.request.oauth2.request.OryRequest;
import org.lowcoder.sdk.auth.Oauth2OryAuthConfig;
import org.lowcoder.sdk.auth.Oauth2SimpleAuthConfig;
import org.springframework.stereotype.Component;

import reactor.core.publisher.Mono;

import static org.lowcoder.sdk.auth.constants.AuthTypeConstants.*;

@Component
public class Oauth2AuthRequestFactory implements AuthRequestFactory<OAuth2RequestContext> {

Expand All @@ -27,6 +28,7 @@ private AbstractOauth2Request<? extends Oauth2SimpleAuthConfig> buildRequest(OAu
return switch (context.getAuthConfig().getAuthType()) {
case GITHUB -> new GithubRequest((Oauth2SimpleAuthConfig) context.getAuthConfig());
case GOOGLE -> new GoogleRequest((Oauth2SimpleAuthConfig) context.getAuthConfig());
case ORY -> new OryRequest((Oauth2OryAuthConfig) context.getAuthConfig());
default -> throw new UnsupportedOperationException(context.getAuthConfig().getAuthType());
};
}
Expand All @@ -35,6 +37,7 @@ private AbstractOauth2Request<? extends Oauth2SimpleAuthConfig> buildRequest(OAu
public Set<String> supportedAuthTypes() {
return Set.of(
GITHUB,
GOOGLE);
GOOGLE,
ORY);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.lowcoder.api.authentication.request.oauth2;

import org.lowcoder.sdk.auth.constants.Oauth2Constants;

public enum Oauth2DefaultSource implements Oauth2Source {

GITHUB {
Expand Down Expand Up @@ -35,5 +37,23 @@ public String refresh() {
return "https://www.googleapis.com/oauth2/v4/token";
}

},

ORY {
@Override
public String accessToken() {
return "https://" + Oauth2Constants.INSTANCE_ID_PLACEHOLDER + "/oauth2/token";
}

@Override
public String userInfo() {
return "https://" + Oauth2Constants.INSTANCE_ID_PLACEHOLDER + "/userinfo";
}

@Override
public String refresh() {
return "https://" + Oauth2Constants.INSTANCE_ID_PLACEHOLDER + "/oauth2/token";
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package org.lowcoder.api.authentication.request.oauth2.request;

import org.apache.commons.collections4.MapUtils;
import org.apache.http.client.utils.URIBuilder;
import org.lowcoder.api.authentication.request.AuthException;
import org.lowcoder.api.authentication.request.oauth2.OAuth2RequestContext;
import org.lowcoder.api.authentication.request.oauth2.Oauth2DefaultSource;
import org.lowcoder.domain.user.model.AuthToken;
import org.lowcoder.domain.user.model.AuthUser;
import org.lowcoder.sdk.auth.Oauth2OryAuthConfig;
import org.lowcoder.sdk.util.JsonUtils;
import org.lowcoder.sdk.webclient.WebClientBuildHelper;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;

import static org.springframework.web.reactive.function.BodyInserters.fromFormData;

public class OryRequest extends AbstractOauth2Request<Oauth2OryAuthConfig> {

public OryRequest(Oauth2OryAuthConfig config) {
super(config, Oauth2DefaultSource.ORY);
}

@Override
protected Mono<AuthToken> getAuthToken(OAuth2RequestContext context) {
URI uri;
try {
uri = new URIBuilder(config.replaceAuthUrlClientIdPlaceholder(source.accessToken())).build();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}

return WebClientBuildHelper.builder()
.systemProxy()
.build()
.post()
.uri(uri)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(fromFormData("code", context.getCode())
.with("client_id", config.getClientId())
.with("client_secret", config.getClientSecret())
.with("grant_type", "authorization_code")
.with("redirect_uri", context.getRedirectUrl()))
.exchangeToMono(response -> response.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}))
.flatMap(map -> {
if (map.containsKey("error") || map.containsKey("error_description")) {
throw new AuthException(JsonUtils.toJson(map));
}
AuthToken authToken = AuthToken.builder()
.accessToken(MapUtils.getString(map, "access_token"))
.expireIn(MapUtils.getIntValue(map, "expires_in"))
.refreshToken(MapUtils.getString(map, "refresh_token"))
.build();
return Mono.just(authToken);
});
}

@Override
protected Mono<AuthToken> refreshAuthToken(String refreshToken) {

URI uri;
try {
uri = new URIBuilder(config.replaceAuthUrlClientIdPlaceholder(source.refresh())).build();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}

return WebClientBuildHelper.builder()
.systemProxy()
.build()
.post()
.uri(uri)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(fromFormData("refresh_token", refreshToken)
.with("client_id", config.getClientId())
.with("client_secret", config.getClientSecret())
.with("grant_type", "refresh_token"))
.exchangeToMono(response -> response.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}))
.flatMap(map -> {
if (map.containsKey("error") || map.containsKey("error_description")) {
throw new AuthException(JsonUtils.toJson(map));
}
AuthToken authToken = AuthToken.builder()
.accessToken(MapUtils.getString(map, "access_token"))
.expireIn(MapUtils.getIntValue(map, "expires_in"))
.refreshToken(MapUtils.getString(map, "refresh_token"))
.build();
return Mono.just(authToken);
});

}

@Override
protected Mono<AuthUser> getAuthUser(AuthToken authToken) {
return WebClientBuildHelper.builder()
.systemProxy()
.build()
.post()
.uri(config.replaceAuthUrlClientIdPlaceholder(source.userInfo()))
.header("Authorization", "Bearer " + authToken.getAccessToken())
.exchangeToMono(response -> response.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}))
.flatMap(map -> {
if (map.containsKey("error") || map.containsKey("error_description")) {
throw new AuthException(JsonUtils.toJson(map));
}
AuthUser authUser = AuthUser.builder()
.uid(MapUtils.getString(map, "sub"))
.username(MapUtils.getString(map, "name"))
.avatar(MapUtils.getString(map, "picture"))
.rawUserInfo(map)
.build();
return Mono.just(authUser);
});
}
}
Loading