Skip to content

Commit

Permalink
Merge branch 'dev' into echarts-json-rerender-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
FalkWolsky authored Oct 24, 2023
2 parents 6ce3ec4 + 1d3375a commit a9122be
Show file tree
Hide file tree
Showing 17 changed files with 368 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.lowcoder.domain.user.model;

import lombok.Getter;
import lombok.Setter;

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

@Getter
@Setter
public class APIKey {

private String id;
private String name;
private String description;
private String token;

public APIKey(@Nullable String id, String name, String description, String token) {
this.id = id;
this.name = name;
this.description = description;
this.token = token;
}

public void doEncrypt(Function<String, String> encryptFunc) {
this.token = encryptFunc.apply(token);
}

public void doDecrypt(Function<String, String> decryptFunc) {
this.token = decryptFunc.apply(token);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
import static com.google.common.base.Suppliers.memoize;
import static org.lowcoder.infra.util.AssetUtils.toAssetPath;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.function.Supplier;

import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.StringUtils;
import org.lowcoder.domain.mongodb.AfterMongodbRead;
import org.lowcoder.domain.mongodb.BeforeMongodbWrite;
import org.lowcoder.domain.mongodb.MongodbInterceptorContext;
import org.lowcoder.sdk.config.SerializeConfig;
import org.lowcoder.sdk.models.HasIdAndAuditing;
import org.lowcoder.sdk.util.JsonUtils;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;

Expand All @@ -29,7 +32,7 @@
@ToString
@Document
@JsonIgnoreProperties(ignoreUnknown = true)
public class User extends HasIdAndAuditing {
public class User extends HasIdAndAuditing implements BeforeMongodbWrite, AfterMongodbRead {

private static final OrgTransformedUserInfo EMPTY_TRANSFORMED_USER_INFO = new OrgTransformedUserInfo();

Expand All @@ -52,6 +55,16 @@ public class User extends HasIdAndAuditing {

private Set<Connection> connections;

@Setter
@Getter
@Transient
private List<APIKey> apiKeysList = new ArrayList<>();

/**
* Only used for mongodb (de)serialization
*/
private List<Object> apiKeys = new ArrayList<>();

@Transient
@JsonIgnore
private Supplier<String> avatarUrl = memoize(() -> StringUtils.isNotBlank(avatar) ? toAssetPath(avatar) : tpAvatarLink);
Expand Down Expand Up @@ -109,4 +122,18 @@ public void markAsDeleted() {
.forEach(connection -> connection.setSource(
connection.getSource() + "(User deleted at " + System.currentTimeMillis() / 1000 + ")"));
}

@Override
public void beforeMongodbWrite(MongodbInterceptorContext context) {
this.apiKeysList.forEach(apiKey -> apiKey.doEncrypt(s -> context.encryptionService().encryptString(s)));
apiKeys = JsonUtils.fromJsonSafely(JsonUtils.toJsonSafely(apiKeysList, SerializeConfig.JsonViews.Internal.class), new TypeReference<>() {
}, new ArrayList<>());
}

@Override
public void afterMongodbRead(MongodbInterceptorContext context) {
this.apiKeysList = JsonUtils.fromJsonSafely(JsonUtils.toJson(apiKeys), new TypeReference<>() {
}, new ArrayList<>());
this.apiKeysList.forEach(authConfig -> authConfig.doDecrypt(s -> context.encryptionService().decryptString(s)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ public Set<String> validateConfig(T connectionConfig) {
invalids.add("HOST_WITH_COLON");
}

if (StringUtils.equalsIgnoreCase(host, "localhost") || StringUtils.equals(host, "127.0.0.1")) {
invalids.add("INVALID_HOST");
}
// if (StringUtils.equalsIgnoreCase(host, "localhost") || StringUtils.equals(host, "127.0.0.1")) {
// invalids.add("INVALID_HOST");
// }

if (StringUtils.isBlank(connectionConfig.getDatabase())) {
invalids.add("DATABASE_NAME_EMPTY");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class AuthProperties {
private Email email = new Email();
private Oauth2Simple google = new Oauth2Simple();
private Oauth2Simple github = new Oauth2Simple();
private ApiKey apiKey = new ApiKey();

@Getter
@Setter
Expand All @@ -53,6 +54,12 @@ public static class Oauth2Simple extends AuthWay {
private String clientSecret;
}

@Setter
@Getter
public static class ApiKey {
private String secret;
}

/**
* For saas mode, such as app.lowcoder.cloud
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ public String getCookieToken(ServerWebExchange exchange) {
return getCookieValue(exchange, getCookieName(), "");
}

@Nullable
public String getJWT(ServerWebExchange exchange) {
return getCookieValue(exchange, "JWT", null);
}

public String getCookieValue(ServerWebExchange exchange, String cookieName, String defaultValue) {
MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
return ofNullable(cookies.getFirst(cookieName))
Expand Down
18 changes: 18 additions & 0 deletions server/api-service/lowcoder-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,24 @@
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

import java.util.List;

import org.lowcoder.api.authentication.dto.APIKeyRequest;
import org.lowcoder.api.authentication.dto.AuthConfigRequest;
import org.lowcoder.api.authentication.service.AuthenticationApiService;
import org.lowcoder.api.framework.view.ResponseView;
import org.lowcoder.api.home.SessionUserService;
import org.lowcoder.api.usermanagement.UserController;
import org.lowcoder.api.usermanagement.UserController.UpdatePasswordRequest;
import org.lowcoder.api.usermanagement.view.APIKeyVO;
import org.lowcoder.api.util.BusinessEventPublisher;
import org.lowcoder.domain.authentication.FindAuthConfig;
import org.lowcoder.domain.user.model.APIKey;
import org.lowcoder.infra.constant.NewUrl;
import org.lowcoder.sdk.auth.AbstractAuthConfig;
import org.lowcoder.sdk.config.SerializeConfig.JsonViews;
Expand Down Expand Up @@ -104,6 +107,26 @@ public Mono<ResponseView<List<AbstractAuthConfig>>> getAllConfigs() {
.map(ResponseView::success);
}

// ----------- API Key Management ----------------
@PostMapping("/api-key")
public Mono<ResponseView<APIKeyVO>> createAPIKey(@RequestBody APIKeyRequest apiKeyRequest) {
return authenticationApiService.createAPIKey(apiKeyRequest)
.map(ResponseView::success);
}

@DeleteMapping("/api-key/{id}")
public Mono<ResponseView<Void>> deleteAPIKey(@PathVariable("id") String id) {
return authenticationApiService.deleteAPIKey(id)
.thenReturn(ResponseView.success(null));
}

@GetMapping("/api-keys")
public Mono<ResponseView<List<APIKey>>> getAllAPIKeys() {
return authenticationApiService.findAPIKeys()
.collectList()
.map(ResponseView::success);
}

/**
* @param loginId phone number or email for now.
* @param register register or login
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.lowcoder.api.authentication.dto;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;

import java.util.HashMap;

import static org.lowcoder.sdk.util.IDUtils.generate;

public class APIKeyRequest extends HashMap<String, Object> {

public String getId() {
return ObjectUtils.firstNonNull(getString("id"), generate());
}

public String getName() {
return getString("name");
}

public String getDescription() {
return getString("description");
}

public String getString(String key) {
return MapUtils.getString(this, key);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.lowcoder.api.authentication.service;

import org.lowcoder.api.authentication.dto.APIKeyRequest;
import org.lowcoder.api.authentication.dto.AuthConfigRequest;
import org.lowcoder.api.usermanagement.view.APIKeyVO;
import org.lowcoder.domain.authentication.FindAuthConfig;
import org.lowcoder.domain.user.model.APIKey;
import org.lowcoder.domain.user.model.AuthUser;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
Expand All @@ -20,4 +23,10 @@ public interface AuthenticationApiService {
Mono<Boolean> disableAuthConfig(String authId, boolean delete);

Flux<FindAuthConfig> findAuthConfigs(boolean enableOnly);

Mono<APIKeyVO> createAPIKey(APIKeyRequest apiKeyRequest);

Mono<Void> deleteAPIKey(String authId);

Flux<APIKey> findAPIKeys();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.lowcoder.api.authentication.dto.APIKeyRequest;
import org.lowcoder.api.authentication.dto.AuthConfigRequest;
import org.lowcoder.api.authentication.request.AuthRequestFactory;
import org.lowcoder.api.authentication.request.oauth2.OAuth2RequestContext;
import org.lowcoder.api.authentication.service.factory.AuthConfigFactory;
import org.lowcoder.api.authentication.util.AuthenticationUtils;
import org.lowcoder.api.authentication.util.JWTUtils;
import org.lowcoder.api.home.SessionUserService;
import org.lowcoder.api.usermanagement.InvitationApiService;
import org.lowcoder.api.usermanagement.OrgApiService;
import org.lowcoder.api.usermanagement.UserApiService;
import org.lowcoder.api.usermanagement.view.APIKeyVO;
import org.lowcoder.api.util.BusinessEventPublisher;
import org.lowcoder.domain.authentication.AuthenticationService;
import org.lowcoder.domain.authentication.FindAuthConfig;
Expand All @@ -22,10 +26,7 @@
import org.lowcoder.domain.organization.model.OrganizationDomain;
import org.lowcoder.domain.organization.service.OrgMemberService;
import org.lowcoder.domain.organization.service.OrganizationService;
import org.lowcoder.domain.user.model.AuthUser;
import org.lowcoder.domain.user.model.Connection;
import org.lowcoder.domain.user.model.ConnectionAuthToken;
import org.lowcoder.domain.user.model.User;
import org.lowcoder.domain.user.model.*;
import org.lowcoder.domain.user.service.UserService;
import org.lowcoder.sdk.auth.AbstractAuthConfig;
import org.lowcoder.sdk.exception.BizError;
Expand Down Expand Up @@ -81,6 +82,9 @@ public class AuthenticationApiServiceImpl implements AuthenticationApiService {
@Autowired
private OrgMemberService orgMemberService;

@Autowired
private JWTUtils jwtUtils;

@Override
public Mono<AuthUser> authenticateByForm(String loginId, String password, String source, boolean register, String authId) {
return authenticate(authId, source, new FormAuthRequestContext(loginId, password, register));
Expand Down Expand Up @@ -262,6 +266,51 @@ public Flux<FindAuthConfig> findAuthConfigs(boolean enableOnly) {
.flatMapMany(orgMember -> authenticationService.findAllAuthConfigs(orgMember.getOrgId(),false));
}

@Override
public Mono<APIKeyVO> createAPIKey(APIKeyRequest apiKeyRequest) {
return sessionUserService.getVisitor()
.map(user -> {
String token = jwtUtils.createToken(user);
APIKey apiKey = new APIKey(apiKeyRequest.getId(), apiKeyRequest.getName(), apiKeyRequest.getDescription(), token);
addAPIKey(user, apiKey);
return Pair.of(token, user);
})
.flatMap(pair -> userService.update(pair.getRight().getId(), pair.getRight()).thenReturn(pair.getKey()))
.map(APIKeyVO::from);
}

private void addAPIKey(User user, APIKey newApiKey) {
Map<String, APIKey> apiKeyMap = user.getApiKeysList()
.stream()
.collect(Collectors.toMap(APIKey::getId, Function.identity()));
apiKeyMap.put(newApiKey.getId(), newApiKey);
user.setApiKeysList(new ArrayList<>(apiKeyMap.values()));
}

@Override
public Mono<Void> deleteAPIKey(String apiKeyId) {
return sessionUserService.getVisitor()
.doOnNext(user -> deleteAPIKey(user, apiKeyId))
.flatMap(user -> userService.update(user.getId(), user))
.then();
}

private void deleteAPIKey(User user, String apiKeyId) {
List<APIKey> apiKeys = Optional.of(user)
.map(User::getApiKeysList)
.orElse(Collections.emptyList());
apiKeys.removeIf(apiKey -> Objects.equals(apiKey.getId(), apiKeyId));
user.setApiKeysList(apiKeys);
}

@Override
public Flux<APIKey> findAPIKeys() {
return sessionUserService.getVisitor()
.flatMapIterable(user ->
new ArrayList<>(user.getApiKeysList())
);
}


private Mono<Void> removeTokensByAuthId(String authId) {
return sessionUserService.getVisitorOrgMemberCache()
Expand Down
Loading

0 comments on commit a9122be

Please sign in to comment.