Skip to content

Dev > Main - 2.1.2 hot fixes #438

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 21 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1d3375a
API Key Management Support (#430)
aq-ikhwa-tech Oct 22, 2023
763c30e
Fixed UI schema link
Nangelov7 Oct 23, 2023
a862305
Fixed tooltip content to break on full words
Nangelov7 Oct 23, 2023
12970d0
fix: echarts json rerendering on data change
raheeliftikhar5 Oct 10, 2023
db250dd
feat: github action to publish lowcoder-comps
raheeliftikhar5 Oct 10, 2023
720bc64
fix: publish-comps action fix
raheeliftikhar5 Oct 10, 2023
3034390
fix: publish-comps action fix
raheeliftikhar5 Oct 10, 2023
24eab6e
fix: unit tests fix
raheeliftikhar5 Oct 11, 2023
4daa462
fix: publish lowcoder-comps fix
raheeliftikhar5 Oct 11, 2023
5e853ef
fix: publish lowcoder-comps fix
raheeliftikhar5 Oct 11, 2023
001343b
fix: publish lowcoder-comps fix
raheeliftikhar5 Oct 11, 2023
6ce3ec4
fix: api key issue + exposing map instance
raheeliftikhar5 Oct 24, 2023
a9122be
Merge branch 'dev' into echarts-json-rerender-fix
FalkWolsky Oct 24, 2023
70cbaf0
Merge pull request #436 from raheeliftikhar5/echarts-json-rerender-fix
FalkWolsky Oct 24, 2023
54fb391
Merge branch 'dev' into fix-ui-schema-link
FalkWolsky Oct 24, 2023
0ba573c
Merge pull request #434 from Nangelov7/fix-ui-schema-link
FalkWolsky Oct 24, 2023
f3548bc
Merge branch 'dev' into fix-tooltip-word-break
FalkWolsky Oct 24, 2023
332a044
Merge pull request #435 from Nangelov7/fix-tooltip-word-break
FalkWolsky Oct 24, 2023
40f5561
fix: update comps version for publish
raheeliftikhar5 Oct 24, 2023
8c16636
Merge branch 'dev' into comps-version-upgrade
FalkWolsky Oct 24, 2023
1c82bb9
Merge pull request #437 from raheeliftikhar5/comps-version-upgrade
FalkWolsky Oct 24, 2023
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,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