@multiplatform.one/wallet 7.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/cjs/SiweKeycloakProvider.cjs +273 -0
- package/dist/cjs/SiweKeycloakProvider.native.js +309 -0
- package/dist/cjs/SiweKeycloakProvider.native.js.map +1 -0
- package/dist/cjs/base64.cjs +51 -0
- package/dist/cjs/base64.native.js +55 -0
- package/dist/cjs/base64.native.js.map +1 -0
- package/dist/cjs/base64.spec.cjs +57 -0
- package/dist/cjs/base64.spec.native.js +79 -0
- package/dist/cjs/base64.spec.native.js.map +1 -0
- package/dist/cjs/context.cjs +29 -0
- package/dist/cjs/context.native.js +32 -0
- package/dist/cjs/context.native.js.map +1 -0
- package/dist/cjs/hooks/context.cjs +29 -0
- package/dist/cjs/hooks/context.native.js +32 -0
- package/dist/cjs/hooks/context.native.js.map +1 -0
- package/dist/cjs/hooks/index.cjs +20 -0
- package/dist/cjs/hooks/index.native.js +23 -0
- package/dist/cjs/hooks/index.native.js.map +1 -0
- package/dist/cjs/hooks/useSiweKeycloak.cjs +61 -0
- package/dist/cjs/hooks/useSiweKeycloak.native.js +64 -0
- package/dist/cjs/hooks/useSiweKeycloak.native.js.map +1 -0
- package/dist/cjs/index.cjs +133 -0
- package/dist/cjs/index.native.js +189 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/render.cjs +78 -0
- package/dist/cjs/render.native.js +82 -0
- package/dist/cjs/render.native.js.map +1 -0
- package/dist/cjs/siwe.cjs +76 -0
- package/dist/cjs/siwe.native.js +94 -0
- package/dist/cjs/siwe.native.js.map +1 -0
- package/dist/cjs/types.cjs +18 -0
- package/dist/cjs/types.native.js +21 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/cjs/util.cjs +53 -0
- package/dist/cjs/util.native.js +56 -0
- package/dist/cjs/util.native.js.map +1 -0
- package/dist/cjs/util.spec.cjs +53 -0
- package/dist/cjs/util.spec.native.js +62 -0
- package/dist/cjs/util.spec.native.js.map +1 -0
- package/dist/esm/SiweKeycloakProvider.mjs +237 -0
- package/dist/esm/SiweKeycloakProvider.mjs.map +1 -0
- package/dist/esm/SiweKeycloakProvider.native.js +270 -0
- package/dist/esm/SiweKeycloakProvider.native.js.map +1 -0
- package/dist/esm/base64.mjs +26 -0
- package/dist/esm/base64.mjs.map +1 -0
- package/dist/esm/base64.native.js +27 -0
- package/dist/esm/base64.native.js.map +1 -0
- package/dist/esm/base64.spec.mjs +58 -0
- package/dist/esm/base64.spec.mjs.map +1 -0
- package/dist/esm/base64.spec.native.js +77 -0
- package/dist/esm/base64.spec.native.js.map +1 -0
- package/dist/esm/context.mjs +4 -0
- package/dist/esm/context.mjs.map +1 -0
- package/dist/esm/context.native.js +4 -0
- package/dist/esm/context.native.js.map +1 -0
- package/dist/esm/hooks/context.mjs +4 -0
- package/dist/esm/hooks/context.mjs.map +1 -0
- package/dist/esm/hooks/context.native.js +4 -0
- package/dist/esm/hooks/context.native.js.map +1 -0
- package/dist/esm/hooks/index.mjs +2 -0
- package/dist/esm/hooks/index.mjs.map +1 -0
- package/dist/esm/hooks/index.native.js +2 -0
- package/dist/esm/hooks/index.native.js.map +1 -0
- package/dist/esm/hooks/useSiweKeycloak.mjs +36 -0
- package/dist/esm/hooks/useSiweKeycloak.mjs.map +1 -0
- package/dist/esm/hooks/useSiweKeycloak.native.js +36 -0
- package/dist/esm/hooks/useSiweKeycloak.native.js.map +1 -0
- package/dist/esm/index.mjs +107 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +160 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/render.mjs +42 -0
- package/dist/esm/render.mjs.map +1 -0
- package/dist/esm/render.native.js +43 -0
- package/dist/esm/render.native.js.map +1 -0
- package/dist/esm/siwe.mjs +51 -0
- package/dist/esm/siwe.mjs.map +1 -0
- package/dist/esm/siwe.native.js +66 -0
- package/dist/esm/siwe.native.js.map +1 -0
- package/dist/esm/types.mjs +2 -0
- package/dist/esm/types.mjs.map +1 -0
- package/dist/esm/types.native.js +2 -0
- package/dist/esm/types.native.js.map +1 -0
- package/dist/esm/util.mjs +27 -0
- package/dist/esm/util.mjs.map +1 -0
- package/dist/esm/util.native.js +27 -0
- package/dist/esm/util.native.js.map +1 -0
- package/dist/esm/util.spec.mjs +54 -0
- package/dist/esm/util.spec.mjs.map +1 -0
- package/dist/esm/util.spec.native.js +60 -0
- package/dist/esm/util.spec.native.js.map +1 -0
- package/package.json +81 -0
- package/src/SiweKeycloakProvider.tsx +297 -0
- package/src/base64.spec.ts +65 -0
- package/src/base64.ts +26 -0
- package/src/context.ts +10 -0
- package/src/hooks/context.ts +10 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useSiweKeycloak.ts +23 -0
- package/src/index.ts +127 -0
- package/src/main/java/com/bitspur/keycloak/siwe/Errors.java +21 -0
- package/src/main/java/com/bitspur/keycloak/siwe/LiteAuthenticationService.java +167 -0
- package/src/main/java/com/bitspur/keycloak/siwe/SiweCredentials.java +36 -0
- package/src/main/java/com/bitspur/keycloak/siwe/SiweSession.java +36 -0
- package/src/main/java/com/bitspur/keycloak/siwe/SiweUtil.java +599 -0
- package/src/main/java/com/bitspur/keycloak/siwe/Token.java +85 -0
- package/src/main/java/com/bitspur/keycloak/siwe/authenticators/browser/SiweBrowserAuthenticator.java +109 -0
- package/src/main/java/com/bitspur/keycloak/siwe/authenticators/browser/SiweBrowserAuthenticatorFactory.java +93 -0
- package/src/main/java/com/bitspur/keycloak/siwe/authenticators/directgrant/SiweDirectGrantAuthenticator.java +153 -0
- package/src/main/java/com/bitspur/keycloak/siwe/authenticators/directgrant/SiweDirectGrantAuthenticatorFactory.java +93 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/nonce/SiweNonce.java +95 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/nonce/SiweNonceJpaEntityProvider.java +82 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/nonce/SiweNonceJpaEntityProviderFactory.java +46 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/wallet/SiweWalletAddress.java +103 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/wallet/SiweWalletJpaEntityProvider.java +43 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/wallet/SiweWalletJpaEntityProviderFactory.java +46 -0
- package/src/main/java/com/bitspur/keycloak/siwe/protocol/SiweWalletProtocolMapper.java +85 -0
- package/src/main/java/com/bitspur/keycloak/siwe/resource/SiweResource.java +210 -0
- package/src/main/java/com/bitspur/keycloak/siwe/resource/SiweResourceProvider.java +34 -0
- package/src/main/java/com/bitspur/keycloak/siwe/resource/SiweResourceProviderFactory.java +46 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/SiuCredentials.java +52 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/SiuMessage.java +362 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/SiuParseException.java +23 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/SiuUtil.java +547 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/browser/SiuBrowserAuthenticator.java +112 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/browser/SiuBrowserAuthenticatorFactory.java +96 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/directgrant/SiuDirectGrantAuthenticator.java +169 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/directgrant/SiuDirectGrantAuthenticatorFactory.java +96 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/resource/SiuResource.java +244 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/resource/SiuResourceProvider.java +34 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/resource/SiuResourceProviderFactory.java +48 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/SiwsCredentials.java +52 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/SiwsMessage.java +362 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/SiwsParseException.java +23 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/SiwsUtil.java +564 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/authenticators/browser/SiwsBrowserAuthenticator.java +112 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/authenticators/browser/SiwsBrowserAuthenticatorFactory.java +96 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/authenticators/directgrant/SiwsDirectGrantAuthenticator.java +169 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/authenticators/directgrant/SiwsDirectGrantAuthenticatorFactory.java +96 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/resource/SiwsResource.java +244 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/resource/SiwsResourceProvider.java +34 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/resource/SiwsResourceProviderFactory.java +48 -0
- package/src/main/resources/META-INF/keycloak-themes.json +8 -0
- package/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory +6 -0
- package/src/main/resources/META-INF/services/org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory +2 -0
- package/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +1 -0
- package/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory +3 -0
- package/src/main/resources/META-INF/siwe-nonce-changelog.xml +29 -0
- package/src/main/resources/META-INF/siwe-wallet-changelog.xml +46 -0
- package/src/render.tsx +47 -0
- package/src/siwe.tsx +57 -0
- package/src/types.ts +6 -0
- package/src/util.spec.ts +72 -0
- package/src/util.ts +28 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/main/java/com/bitspur/keycloak/siwe/SiweUtil.java
|
|
3
|
+
* Project: siwe-keycloak
|
|
4
|
+
* File Created: 20-01-2025 07:51:00
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: 20-01-2025 18:57:32
|
|
8
|
+
* Modified By: Clay Risser
|
|
9
|
+
* -----
|
|
10
|
+
* BitSpur (c) Copyright 2022 - 2025
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
package com.bitspur.keycloak.siwe;
|
|
14
|
+
|
|
15
|
+
import com.bitspur.keycloak.siwe.Errors.SiweKeycloakException;
|
|
16
|
+
import com.bitspur.keycloak.siwe.authenticators.browser.SiweBrowserAuthenticatorFactory;
|
|
17
|
+
import com.bitspur.keycloak.siwe.authenticators.directgrant.SiweDirectGrantAuthenticatorFactory;
|
|
18
|
+
import com.bitspur.keycloak.siwe.jpa.nonce.SiweNonce;
|
|
19
|
+
import com.bitspur.keycloak.siwe.jpa.wallet.SiweWalletAddress;
|
|
20
|
+
import com.moonstoneid.siwe.error.SiweException;
|
|
21
|
+
|
|
22
|
+
import java.io.IOException;
|
|
23
|
+
import java.lang.reflect.Field;
|
|
24
|
+
import java.lang.reflect.Method;
|
|
25
|
+
import java.math.BigInteger;
|
|
26
|
+
import java.net.URI;
|
|
27
|
+
import java.security.SecureRandom;
|
|
28
|
+
import java.util.ArrayList;
|
|
29
|
+
import java.util.Base64;
|
|
30
|
+
import java.util.HashMap;
|
|
31
|
+
import java.util.List;
|
|
32
|
+
import java.util.Locale;
|
|
33
|
+
import java.util.Map;
|
|
34
|
+
import java.util.Properties;
|
|
35
|
+
|
|
36
|
+
import jakarta.persistence.EntityManager;
|
|
37
|
+
import jakarta.persistence.NoResultException;
|
|
38
|
+
import jakarta.persistence.TypedQuery;
|
|
39
|
+
import jakarta.ws.rs.core.Cookie;
|
|
40
|
+
import jakarta.ws.rs.core.MultivaluedHashMap;
|
|
41
|
+
import jakarta.ws.rs.core.MultivaluedMap;
|
|
42
|
+
import jakarta.ws.rs.core.Response;
|
|
43
|
+
import jakarta.ws.rs.core.UriBuilder;
|
|
44
|
+
|
|
45
|
+
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
|
|
46
|
+
import static org.keycloak.provider.ProviderConfigProperty.TEXT_TYPE;
|
|
47
|
+
|
|
48
|
+
import org.jboss.logging.Logger;
|
|
49
|
+
import org.keycloak.authentication.AuthenticationFlowContext;
|
|
50
|
+
import org.keycloak.authentication.AuthenticationFlowError;
|
|
51
|
+
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
|
52
|
+
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
|
53
|
+
import org.keycloak.events.Details;
|
|
54
|
+
import org.keycloak.events.Errors;
|
|
55
|
+
import org.keycloak.forms.login.LoginFormsPages;
|
|
56
|
+
import org.keycloak.forms.login.LoginFormsProvider;
|
|
57
|
+
import org.keycloak.forms.login.freemarker.model.LoginBean;
|
|
58
|
+
import org.keycloak.http.HttpRequest;
|
|
59
|
+
import org.keycloak.models.AuthenticationExecutionModel;
|
|
60
|
+
import org.keycloak.models.AuthenticationFlowModel;
|
|
61
|
+
import org.keycloak.models.AuthenticatorConfigModel;
|
|
62
|
+
import org.keycloak.models.KeycloakSession;
|
|
63
|
+
import org.keycloak.models.ModelDuplicateException;
|
|
64
|
+
import org.keycloak.models.RealmModel;
|
|
65
|
+
import org.keycloak.models.UserModel;
|
|
66
|
+
import org.keycloak.provider.ProviderConfigProperty;
|
|
67
|
+
import org.keycloak.services.ServicesLogger;
|
|
68
|
+
import org.keycloak.services.messages.Messages;
|
|
69
|
+
import org.keycloak.theme.Theme;
|
|
70
|
+
import org.keycloak.userprofile.UserProfile;
|
|
71
|
+
import org.keycloak.userprofile.UserProfileContext;
|
|
72
|
+
import org.keycloak.userprofile.UserProfileProvider;
|
|
73
|
+
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
74
|
+
|
|
75
|
+
public class SiweUtil {
|
|
76
|
+
private static final String LOGIN_SIWE_PAGE = "login-siwe.ftl";
|
|
77
|
+
|
|
78
|
+
private static final Logger logger = Logger.getLogger(SiweUtil.class);
|
|
79
|
+
|
|
80
|
+
private static final SecureRandom random = new SecureRandom();
|
|
81
|
+
|
|
82
|
+
public static final String JSON_RPC_URL_CONFIG = "json_rpc_url";
|
|
83
|
+
|
|
84
|
+
public static final String REQUIRED_TOKENS_CONFIG = "required_tokens";
|
|
85
|
+
|
|
86
|
+
public static final String SIWE_ADDRESSES_ATTRIBUTE = "SIWE_ADDRESSES";
|
|
87
|
+
|
|
88
|
+
public static final String KEYCLOAK_SESSION_SIWE = "KEYCLOAK_SESSION_SIWE";
|
|
89
|
+
|
|
90
|
+
public static final String DEFAULT_ADDRESS_TYPE = "ethereum";
|
|
91
|
+
|
|
92
|
+
public static final String DEFAULT_JSON_RPC_URL = (System.getenv("DEFAULT_JSON_RPC_URL") != null
|
|
93
|
+
&& !System.getenv("DEFAULT_JSON_RPC_URL").isEmpty())
|
|
94
|
+
? System.getenv("DEFAULT_JSON_RPC_URL")
|
|
95
|
+
: "https://cloudflare-eth.com";
|
|
96
|
+
|
|
97
|
+
public static UserModel findOrCreateUser(AuthenticationFlowContext context,
|
|
98
|
+
SiweCredentials siweCredentials) throws SiweKeycloakException {
|
|
99
|
+
return SiweUtil.findOrCreateUser(context, siweCredentials, true);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static UserModel findOrCreateUser(AuthenticationFlowContext context,
|
|
103
|
+
SiweCredentials siweCredentials,
|
|
104
|
+
Boolean failureChallenge) throws SiweKeycloakException {
|
|
105
|
+
String address = siweCredentials.message.getAddress();
|
|
106
|
+
if (address == null) {
|
|
107
|
+
if (failureChallenge) {
|
|
108
|
+
context.getEvent().error(Errors.USER_NOT_FOUND);
|
|
109
|
+
LoginFormsProvider form = context.form();
|
|
110
|
+
form.setExecution(context.getExecution().getId());
|
|
111
|
+
form.setError(Messages.INVALID_USER);
|
|
112
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_USER, form.createLoginUsernamePassword());
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
UserModel user = null;
|
|
117
|
+
EntityManager entityManager = context.getSession().getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
118
|
+
try {
|
|
119
|
+
List<SiweWalletAddress> wallets = entityManager
|
|
120
|
+
.createNamedQuery("findByRealmAddressType", SiweWalletAddress.class)
|
|
121
|
+
.setParameter("realmId", context.getRealm().getId())
|
|
122
|
+
.setParameter("address", address.toLowerCase().trim())
|
|
123
|
+
.setParameter("addressType", SiweUtil.DEFAULT_ADDRESS_TYPE)
|
|
124
|
+
.getResultList();
|
|
125
|
+
if (!wallets.isEmpty()) {
|
|
126
|
+
user = context.getSession().users().getUserById(context.getRealm(), wallets.get(0).getUserId());
|
|
127
|
+
}
|
|
128
|
+
} catch (ModelDuplicateException mde) {
|
|
129
|
+
if (failureChallenge) {
|
|
130
|
+
ServicesLogger.LOGGER.modelDuplicateException(mde);
|
|
131
|
+
context.getEvent().error(Errors.USERNAME_IN_USE);
|
|
132
|
+
LoginFormsProvider form = context.form();
|
|
133
|
+
form.setError(Messages.USERNAME_EXISTS);
|
|
134
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_USER, SiweUtil.createLoginSiwePage(form));
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (user == null) {
|
|
139
|
+
if (SiweUtil.verifySiweCredentials(context.getSession(), context.getAuthenticatorConfig(), siweCredentials)
|
|
140
|
+
&& SiweUtil.verifyHasRequiredTokens(context.getSession(), context.getAuthenticatorConfig(),
|
|
141
|
+
siweCredentials)) {
|
|
142
|
+
KeycloakSession session = context.getSession();
|
|
143
|
+
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
|
|
144
|
+
MultivaluedMap<String, String> formData = new MultivaluedHashMap<>();
|
|
145
|
+
String username = generateRandomUsername(session, context.getRealm());
|
|
146
|
+
context.getEvent().detail(Details.USERNAME, username);
|
|
147
|
+
context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username);
|
|
148
|
+
formData.add("username", username);
|
|
149
|
+
UserProfile profile = profileProvider.create(UserProfileContext.REGISTRATION, formData);
|
|
150
|
+
user = profile.create();
|
|
151
|
+
user.setEnabled(true);
|
|
152
|
+
SiweWalletAddress wallet = new SiweWalletAddress();
|
|
153
|
+
wallet.setAddress(address.toLowerCase().trim());
|
|
154
|
+
wallet.setAddressType(SiweUtil.DEFAULT_ADDRESS_TYPE);
|
|
155
|
+
wallet.setUserId(user.getId());
|
|
156
|
+
wallet.setRealmId(context.getRealm().getId());
|
|
157
|
+
entityManager.persist(wallet);
|
|
158
|
+
return user;
|
|
159
|
+
}
|
|
160
|
+
if (failureChallenge) {
|
|
161
|
+
context.getEvent().error(Errors.USER_NOT_FOUND);
|
|
162
|
+
LoginFormsProvider form = context.form();
|
|
163
|
+
form.setExecution(context.getExecution().getId());
|
|
164
|
+
form.setError(Messages.INVALID_USER);
|
|
165
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_USER, SiweUtil.createLoginSiwePage(form));
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
String username = user.getUsername();
|
|
170
|
+
context.getEvent().detail(Details.USERNAME, username);
|
|
171
|
+
context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username);
|
|
172
|
+
return user;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public static ProviderConfigProperty getRequiredTokensConfigProperty() {
|
|
176
|
+
ProviderConfigProperty property = new ProviderConfigProperty();
|
|
177
|
+
property.setType(TEXT_TYPE);
|
|
178
|
+
property.setName(SiweUtil.REQUIRED_TOKENS_CONFIG);
|
|
179
|
+
property.setLabel("required tokens");
|
|
180
|
+
property.setHelpText("Expects a list of smart contract addresses. "
|
|
181
|
+
+ "The minium number of tokens required can be specified by appending a colon and "
|
|
182
|
+
+ "the number of tokens required at the end of the address. For example: "
|
|
183
|
+
+ "0x1234567890123456789012345678901234567890:10");
|
|
184
|
+
return property;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public static ProviderConfigProperty getJsonRpcUrlConfigProperty() {
|
|
188
|
+
ProviderConfigProperty property = new ProviderConfigProperty();
|
|
189
|
+
property.setType(STRING_TYPE);
|
|
190
|
+
property.setName(SiweUtil.JSON_RPC_URL_CONFIG);
|
|
191
|
+
property.setLabel("json rpc url");
|
|
192
|
+
property.setDefaultValue(SiweUtil.DEFAULT_JSON_RPC_URL);
|
|
193
|
+
return property;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public static Response createResponse(LoginFormsProvider form, String template) {
|
|
197
|
+
return SiweUtil.createResponse(form, template, null);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public static Response createLoginSiwePage(LoginFormsProvider form) {
|
|
201
|
+
return SiweUtil.createResponse(form, SiweUtil.LOGIN_SIWE_PAGE, LoginFormsPages.LOGIN);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public static String getJsonRpcUrl(AuthenticatorConfigModel configModel) {
|
|
205
|
+
try {
|
|
206
|
+
Map<String, String> config = configModel.getConfig();
|
|
207
|
+
String jsonRpcUrl = config.get(SiweUtil.JSON_RPC_URL_CONFIG);
|
|
208
|
+
if (jsonRpcUrl == null || jsonRpcUrl.length() <= 0) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return jsonRpcUrl;
|
|
212
|
+
} catch (NullPointerException e) {
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
public static List<Token> getRequiredTokens(AuthenticatorConfigModel configModel) {
|
|
218
|
+
String jsonRpcUrl = SiweUtil.getJsonRpcUrl(configModel);
|
|
219
|
+
if (jsonRpcUrl == null || jsonRpcUrl.length() <= 0) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
List<Token> requiredTokens = new ArrayList<Token>();
|
|
224
|
+
Map<String, String> config = configModel.getConfig();
|
|
225
|
+
String requiredTokensStr = config.get(SiweUtil.REQUIRED_TOKENS_CONFIG);
|
|
226
|
+
if (requiredTokensStr == null || requiredTokensStr.length() <= 0) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
String[] requiredTokensArr = requiredTokensStr.split("[,\n]+");
|
|
230
|
+
for (Integer i = 0; i < requiredTokensArr.length; i++) {
|
|
231
|
+
String[] requiredTokenArr = requiredTokensArr[0].split(":");
|
|
232
|
+
BigInteger requiredBalanceOf = BigInteger.valueOf(1);
|
|
233
|
+
String contractAddress = requiredTokenArr[0];
|
|
234
|
+
if (contractAddress.length() <= 0) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (requiredTokenArr.length > 1) {
|
|
238
|
+
try {
|
|
239
|
+
requiredBalanceOf = new BigInteger(requiredTokenArr[1]);
|
|
240
|
+
} catch (NumberFormatException e) {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
requiredTokens.add(new Token(contractAddress, jsonRpcUrl, requiredBalanceOf));
|
|
244
|
+
}
|
|
245
|
+
if (requiredTokens.size() <= 0) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
return requiredTokens;
|
|
249
|
+
} catch (NullPointerException e) {
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
public static SiweSession generateSiweSession(KeycloakSession session) {
|
|
255
|
+
String nonce = SiweUtil.generateNonce();
|
|
256
|
+
String sessionKey = SiweUtil.generateSessionKey(session.getContext().getRealm().getName());
|
|
257
|
+
SiweUtil.setNonce(session, sessionKey, nonce);
|
|
258
|
+
return new SiweSession(sessionKey, nonce);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public static AuthenticatorConfigModel getAuthenticatorConfig(KeycloakSession session, Boolean isDirectGrantFlow) {
|
|
262
|
+
RealmModel realm = session.getContext().getRealm();
|
|
263
|
+
AuthenticationFlowModel flow = realm.getBrowserFlow();
|
|
264
|
+
if (isDirectGrantFlow) {
|
|
265
|
+
flow = realm.getDirectGrantFlow();
|
|
266
|
+
}
|
|
267
|
+
AuthenticationExecutionModel execution = realm.getAuthenticationExecutionsStream(flow.getId())
|
|
268
|
+
.filter(e -> {
|
|
269
|
+
String authenticator = e.getAuthenticator();
|
|
270
|
+
return authenticator != null
|
|
271
|
+
&& authenticator.equals(isDirectGrantFlow ? SiweDirectGrantAuthenticatorFactory.ID
|
|
272
|
+
: SiweBrowserAuthenticatorFactory.ID);
|
|
273
|
+
})
|
|
274
|
+
.findFirst().orElse(null);
|
|
275
|
+
if (execution == null) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return realm.getAuthenticatorConfigById(execution.getAuthenticatorConfig());
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public static String getSessionKey(KeycloakSession session) throws SiweKeycloakException {
|
|
282
|
+
HttpRequest request = session.getContext().getHttpRequest();
|
|
283
|
+
Cookie cookie = request.getHttpHeaders().getCookies().get(SiweUtil.KEYCLOAK_SESSION_SIWE);
|
|
284
|
+
String sessionKey = null;
|
|
285
|
+
if (cookie != null) {
|
|
286
|
+
sessionKey = cookie.getValue();
|
|
287
|
+
}
|
|
288
|
+
if (sessionKey == null) {
|
|
289
|
+
throw new SiweKeycloakException("missing siwe session");
|
|
290
|
+
}
|
|
291
|
+
return sessionKey;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public static String getNonce(KeycloakSession session) throws SiweKeycloakException {
|
|
295
|
+
String sessionKey = SiweUtil.getSessionKey(session);
|
|
296
|
+
return SiweUtil.getNonce(session, sessionKey);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
public static String getNonce(KeycloakSession session, String key) {
|
|
300
|
+
SiweNonce siweNonce = SiweUtil.getSiweNonce(session, key);
|
|
301
|
+
if (siweNonce == null) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
return siweNonce.getNonce();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public static String setNonce(KeycloakSession session, String key, String nonce) {
|
|
308
|
+
RealmModel realm = session.getContext().getRealm();
|
|
309
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
310
|
+
SiweNonce siweNonce = SiweUtil.getSiweNonce(session, key);
|
|
311
|
+
if (siweNonce == null) {
|
|
312
|
+
siweNonce = new SiweNonce();
|
|
313
|
+
siweNonce.setKey(key);
|
|
314
|
+
siweNonce.setRealmId(realm.getId());
|
|
315
|
+
}
|
|
316
|
+
siweNonce.setNonce(nonce);
|
|
317
|
+
entityManager.merge(siweNonce);
|
|
318
|
+
return nonce;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
public static Void removeNonce(KeycloakSession session, String key) {
|
|
322
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
323
|
+
SiweNonce siweNonce = SiweUtil.getSiweNonce(session, key);
|
|
324
|
+
if (siweNonce == null) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
entityManager.remove(siweNonce);
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
public static Map<String, String> getNonces(KeycloakSession session) {
|
|
332
|
+
RealmModel realm = session.getContext().getRealm();
|
|
333
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
334
|
+
Map<String, String> nonces = new HashMap<>();
|
|
335
|
+
entityManager.createNamedQuery("findByRealm", SiweNonce.class)
|
|
336
|
+
.setParameter("realmId", realm.getId())
|
|
337
|
+
.getResultList().stream().forEach((nonce) -> {
|
|
338
|
+
nonces.put(nonce.getKey(), nonce.getNonce());
|
|
339
|
+
});
|
|
340
|
+
return nonces;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
public static Boolean verify(
|
|
344
|
+
KeycloakSession session, AuthenticatorConfigModel configModel,
|
|
345
|
+
SiweCredentials siweCredentials)
|
|
346
|
+
throws SiweKeycloakException {
|
|
347
|
+
return SiweUtil.verifyExistingUser(session, configModel, siweCredentials)
|
|
348
|
+
&& SiweUtil.verifySiweCredentials(session, configModel, siweCredentials) &&
|
|
349
|
+
SiweUtil.verifyHasRequiredTokens(session, configModel, siweCredentials);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
public static Boolean verifyExistingUser(
|
|
353
|
+
KeycloakSession session,
|
|
354
|
+
AuthenticatorConfigModel configModel, SiweCredentials siweCredentials)
|
|
355
|
+
throws SiweKeycloakException {
|
|
356
|
+
String address = siweCredentials.message.getAddress().toLowerCase().trim();
|
|
357
|
+
RealmModel realm = session.getContext().getRealm();
|
|
358
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
359
|
+
List<SiweWalletAddress> wallets = entityManager
|
|
360
|
+
.createNamedQuery("findByRealmAddressType", SiweWalletAddress.class)
|
|
361
|
+
.setParameter("realmId", realm.getId())
|
|
362
|
+
.setParameter("address", address)
|
|
363
|
+
.setParameter("addressType", SiweUtil.DEFAULT_ADDRESS_TYPE)
|
|
364
|
+
.getResultList();
|
|
365
|
+
return !wallets.isEmpty();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
public static Boolean verifySiweCredentials(KeycloakSession session,
|
|
369
|
+
AuthenticatorConfigModel configModel, SiweCredentials siweCredentials)
|
|
370
|
+
throws SiweKeycloakException {
|
|
371
|
+
HttpRequest httpRequest = session.getContext().getHttpRequest();
|
|
372
|
+
URI requestUri = httpRequest.getUri().getRequestUri();
|
|
373
|
+
String domain = requestUri.getHost();
|
|
374
|
+
Integer port = requestUri.getPort();
|
|
375
|
+
String scheme = requestUri.getScheme();
|
|
376
|
+
if (port != -1 && !(scheme.equals("http") && port == 80) && !(scheme.equals("https") && port == 443)) {
|
|
377
|
+
domain += ":" + port;
|
|
378
|
+
}
|
|
379
|
+
// TODO: temporary fix for cross domains
|
|
380
|
+
domain = siweCredentials.message.getDomain();
|
|
381
|
+
String nonce = SiweUtil.getNonce(session);
|
|
382
|
+
if (nonce == null) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
siweCredentials.message.verify(domain, nonce, siweCredentials.signature);
|
|
387
|
+
return true;
|
|
388
|
+
} catch (SiweException e) {
|
|
389
|
+
throw new SiweKeycloakException(e.getMessage());
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
public static Boolean verifyHasRequiredTokens(KeycloakSession session,
|
|
394
|
+
AuthenticatorConfigModel configModel, SiweCredentials siweCredentials) {
|
|
395
|
+
List<Token> tokens = SiweUtil.getRequiredTokens(configModel);
|
|
396
|
+
if (tokens == null || tokens.size() <= 0) {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
for (Integer i = 0; i < tokens.size(); i++) {
|
|
400
|
+
Token token = tokens.get(i);
|
|
401
|
+
if (!token.hasRequiredBalanceOf(siweCredentials.message.getAddress())) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
protected static SiweNonce getSiweNonce(KeycloakSession session, String key) {
|
|
409
|
+
RealmModel realm = session.getContext().getRealm();
|
|
410
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
411
|
+
SiweNonce siweNonce = entityManager.createNamedQuery("findByRealmKey", SiweNonce.class)
|
|
412
|
+
.setParameter("key", key)
|
|
413
|
+
.setParameter("realmId", realm.getId())
|
|
414
|
+
.getResultList().stream().findFirst().orElse(null);
|
|
415
|
+
return siweNonce;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// https://github.com/keycloak/keycloak/blob/8b7a0b357ac1c6e7b74460312e231fc785b885b1/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java#L220
|
|
419
|
+
protected static Response createResponse(LoginFormsProvider form, String template, LoginFormsPages page) {
|
|
420
|
+
KeycloakSession session = null;
|
|
421
|
+
try {
|
|
422
|
+
Field sessionField = form.getClass().getDeclaredField("session");
|
|
423
|
+
sessionField.setAccessible(true);
|
|
424
|
+
session = (KeycloakSession) sessionField.get(form);
|
|
425
|
+
Method getTheme = form.getClass().getDeclaredMethod("getTheme");
|
|
426
|
+
getTheme.setAccessible(true);
|
|
427
|
+
Theme theme;
|
|
428
|
+
try {
|
|
429
|
+
theme = session.theme().getTheme(Theme.Type.LOGIN);
|
|
430
|
+
} catch (IOException e) {
|
|
431
|
+
logger.error("Failed to create theme", e);
|
|
432
|
+
return Response.serverError().build();
|
|
433
|
+
}
|
|
434
|
+
Field userField = form.getClass().getDeclaredField("user");
|
|
435
|
+
userField.setAccessible(true);
|
|
436
|
+
UserModel user = (UserModel) userField.get(form);
|
|
437
|
+
Method handleThemeResourcesMethod = form.getClass().getDeclaredMethod("handleThemeResources", Theme.class,
|
|
438
|
+
Locale.class);
|
|
439
|
+
handleThemeResourcesMethod.setAccessible(true);
|
|
440
|
+
Method handleMessagesMethod = form.getClass().getDeclaredMethod("handleMessages", Locale.class, Properties.class);
|
|
441
|
+
handleMessagesMethod.setAccessible(true);
|
|
442
|
+
Locale locale = session.getContext().resolveLocale(user);
|
|
443
|
+
Properties messagesBundle = (Properties) handleThemeResourcesMethod.invoke(form, theme, locale);
|
|
444
|
+
handleMessagesMethod.invoke(form, locale, messagesBundle);
|
|
445
|
+
Method prepareBaseUriBuilderMethod = form.getClass().getDeclaredMethod("prepareBaseUriBuilder", boolean.class);
|
|
446
|
+
prepareBaseUriBuilderMethod.setAccessible(true);
|
|
447
|
+
Method createCommonAttributesMethod = form.getClass().getDeclaredMethod("createCommonAttributes", Theme.class,
|
|
448
|
+
Locale.class, Properties.class, UriBuilder.class, LoginFormsPages.class);
|
|
449
|
+
createCommonAttributesMethod.setAccessible(true);
|
|
450
|
+
UriBuilder uriBuilder = (UriBuilder) prepareBaseUriBuilderMethod.invoke(form, false);
|
|
451
|
+
createCommonAttributesMethod.invoke(form, theme, locale, messagesBundle, uriBuilder, page);
|
|
452
|
+
Field attributesField = form.getClass().getDeclaredField("attributes");
|
|
453
|
+
attributesField.setAccessible(true);
|
|
454
|
+
@SuppressWarnings("unchecked")
|
|
455
|
+
Map<String, Object> attributes = (Map<String, Object>) attributesField.get(form);
|
|
456
|
+
Field formDataField = form.getClass().getDeclaredField("formData");
|
|
457
|
+
formDataField.setAccessible(true);
|
|
458
|
+
@SuppressWarnings("unchecked")
|
|
459
|
+
MultivaluedMap<String, String> formData = (MultivaluedMap<String, String>) formDataField.get(form);
|
|
460
|
+
attributes.put("login", new LoginBean(formData));
|
|
461
|
+
Field statusField = form.getClass().getDeclaredField("status");
|
|
462
|
+
statusField.setAccessible(true);
|
|
463
|
+
Response.Status status = (Response.Status) statusField.get(form);
|
|
464
|
+
if (status != null) {
|
|
465
|
+
attributes.put("statusCode", status.getStatusCode());
|
|
466
|
+
}
|
|
467
|
+
Method processTemplateMethod = form.getClass().getDeclaredMethod("processTemplate", Theme.class, String.class,
|
|
468
|
+
Locale.class);
|
|
469
|
+
processTemplateMethod.setAccessible(true);
|
|
470
|
+
Response response = (Response) processTemplateMethod.invoke(form, theme, template, locale);
|
|
471
|
+
return response;
|
|
472
|
+
} catch (Exception e) {
|
|
473
|
+
logger.error(e);
|
|
474
|
+
return null;
|
|
475
|
+
} finally {
|
|
476
|
+
if (session != null) {
|
|
477
|
+
session.close();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private static String generateNonce() {
|
|
483
|
+
return Base64.getUrlEncoder().withoutPadding().encodeToString(random.generateSeed(8))
|
|
484
|
+
.replaceAll("[^A-Za-z0-9]", "0")
|
|
485
|
+
.substring(0, 8);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private static String generateSessionKey(String realmName) {
|
|
489
|
+
byte[] keyBytes = new byte[32];
|
|
490
|
+
random.nextBytes(keyBytes);
|
|
491
|
+
return realmName + "/" + (Base64.getEncoder().encodeToString(keyBytes));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private static String generateRandomUsername(KeycloakSession session, RealmModel realm) {
|
|
495
|
+
while (true) {
|
|
496
|
+
String username = String.format("%010d", Math.abs(random.nextLong() % 10000000000L));
|
|
497
|
+
if (KeycloakModelUtils.findUserByNameOrEmail(session, realm, username) == null) {
|
|
498
|
+
return username;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
public static Map<String, List<String>> getWalletsByType(KeycloakSession session, UserModel user) {
|
|
504
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
505
|
+
List<SiweWalletAddress> wallets = entityManager
|
|
506
|
+
.createNamedQuery("findByUserId", SiweWalletAddress.class)
|
|
507
|
+
.setParameter("userId", user.getId())
|
|
508
|
+
.getResultList();
|
|
509
|
+
Map<String, List<String>> walletsByType = new HashMap<>();
|
|
510
|
+
for (SiweWalletAddress wallet : wallets) {
|
|
511
|
+
String type = wallet.getAddressType();
|
|
512
|
+
if (!walletsByType.containsKey(type)) {
|
|
513
|
+
walletsByType.put(type, new ArrayList<>());
|
|
514
|
+
}
|
|
515
|
+
walletsByType.get(type).add(wallet.getAddress());
|
|
516
|
+
}
|
|
517
|
+
return walletsByType;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
public static void addWalletsToToken(KeycloakSession session, UserModel user, Map<String, Object> token) {
|
|
521
|
+
Map<String, List<String>> walletsByType = getWalletsByType(session, user);
|
|
522
|
+
Map<String, Object> crypto = new HashMap<>();
|
|
523
|
+
Map<String, Object> wallets = new HashMap<>();
|
|
524
|
+
for (Map.Entry<String, List<String>> entry : walletsByType.entrySet()) {
|
|
525
|
+
Map<String, Object> typeInfo = new HashMap<>();
|
|
526
|
+
typeInfo.put("addresses", entry.getValue());
|
|
527
|
+
wallets.put(entry.getKey(), typeInfo);
|
|
528
|
+
}
|
|
529
|
+
crypto.put("wallets", wallets);
|
|
530
|
+
token.put("crypto", crypto);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
public static UserModel findUserByWalletAddress(KeycloakSession session, String address) {
|
|
534
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
535
|
+
try {
|
|
536
|
+
TypedQuery<SiweWalletAddress> query = entityManager.createNamedQuery("findByRealmAddressType",
|
|
537
|
+
SiweWalletAddress.class);
|
|
538
|
+
query.setParameter("address", address.toLowerCase().trim());
|
|
539
|
+
query.setParameter("realmId", session.getContext().getRealm().getId());
|
|
540
|
+
query.setParameter("addressType", SiweUtil.DEFAULT_ADDRESS_TYPE);
|
|
541
|
+
List<SiweWalletAddress> wallets = query.getResultList();
|
|
542
|
+
if (!wallets.isEmpty()) {
|
|
543
|
+
return session.users().getUserById(session.getContext().getRealm(), wallets.get(0).getUserId());
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
} catch (NoResultException e) {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
public static boolean addWalletAddress(KeycloakSession session, UserModel user, String address) {
|
|
552
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
553
|
+
TypedQuery<SiweWalletAddress> query = entityManager.createNamedQuery("findByRealmAddressType",
|
|
554
|
+
SiweWalletAddress.class);
|
|
555
|
+
query.setParameter("address", address.toLowerCase().trim());
|
|
556
|
+
query.setParameter("realmId", session.getContext().getRealm().getId());
|
|
557
|
+
query.setParameter("addressType", SiweUtil.DEFAULT_ADDRESS_TYPE);
|
|
558
|
+
List<SiweWalletAddress> existingWallets = query.getResultList();
|
|
559
|
+
for (SiweWalletAddress wallet : existingWallets) {
|
|
560
|
+
if (wallet.getUserId().equals(user.getId())) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
SiweWalletAddress wallet = new SiweWalletAddress();
|
|
565
|
+
wallet.setAddress(address.toLowerCase().trim());
|
|
566
|
+
wallet.setAddressType(SiweUtil.DEFAULT_ADDRESS_TYPE);
|
|
567
|
+
wallet.setUserId(user.getId());
|
|
568
|
+
wallet.setRealmId(session.getContext().getRealm().getId());
|
|
569
|
+
entityManager.persist(wallet);
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
public static List<SiweWalletAddress> getWalletAddresses(KeycloakSession session, UserModel user) {
|
|
574
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
575
|
+
TypedQuery<SiweWalletAddress> query = entityManager.createNamedQuery("findByUserId", SiweWalletAddress.class);
|
|
576
|
+
query.setParameter("userId", user.getId());
|
|
577
|
+
query.setParameter("realmId", session.getContext().getRealm().getId());
|
|
578
|
+
return query.getResultList();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
public static boolean removeWalletAddress(KeycloakSession session, String address) {
|
|
582
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
583
|
+
TypedQuery<SiweWalletAddress> query = entityManager.createNamedQuery("findByRealmAddressType",
|
|
584
|
+
SiweWalletAddress.class);
|
|
585
|
+
query.setParameter("address", address.toLowerCase().trim());
|
|
586
|
+
query.setParameter("realmId", session.getContext().getRealm().getId());
|
|
587
|
+
query.setParameter("addressType", SiweUtil.DEFAULT_ADDRESS_TYPE);
|
|
588
|
+
try {
|
|
589
|
+
List<SiweWalletAddress> wallets = query.getResultList();
|
|
590
|
+
if (!wallets.isEmpty()) {
|
|
591
|
+
entityManager.remove(wallets.get(0));
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
return false;
|
|
595
|
+
} catch (NoResultException e) {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/main/java/com/bitspur/keycloak/siwe/Token.java
|
|
3
|
+
* Project: authenticators
|
|
4
|
+
* File Created: 29-03-2022 00:41:18
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: 10-08-2023 16:34:52
|
|
8
|
+
* Modified By: Clay Risser
|
|
9
|
+
* -----
|
|
10
|
+
* BitSpur (c) Copyright 2022
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
package com.bitspur.keycloak.siwe;
|
|
14
|
+
|
|
15
|
+
import java.math.BigInteger;
|
|
16
|
+
import java.util.Arrays;
|
|
17
|
+
import java.util.List;
|
|
18
|
+
import java.util.concurrent.ExecutionException;
|
|
19
|
+
|
|
20
|
+
import org.jboss.logging.Logger;
|
|
21
|
+
|
|
22
|
+
import org.web3j.abi.FunctionEncoder;
|
|
23
|
+
import org.web3j.abi.FunctionReturnDecoder;
|
|
24
|
+
import org.web3j.abi.TypeReference;
|
|
25
|
+
import org.web3j.abi.datatypes.Function;
|
|
26
|
+
import org.web3j.abi.datatypes.Uint;
|
|
27
|
+
import org.web3j.abi.datatypes.Address;
|
|
28
|
+
import org.web3j.protocol.Web3j;
|
|
29
|
+
import org.web3j.protocol.core.DefaultBlockParameterName;
|
|
30
|
+
import org.web3j.protocol.core.methods.request.Transaction;
|
|
31
|
+
import org.web3j.protocol.http.HttpService;
|
|
32
|
+
|
|
33
|
+
public class Token {
|
|
34
|
+
private static final Logger logger = Logger.getLogger(Token.class);
|
|
35
|
+
|
|
36
|
+
private String contractAddress;
|
|
37
|
+
|
|
38
|
+
private Web3j web3j;
|
|
39
|
+
|
|
40
|
+
private String jsonRpcUrl;
|
|
41
|
+
|
|
42
|
+
private BigInteger requiredBalanceOf;
|
|
43
|
+
|
|
44
|
+
Token(String contractAddress, String jsonRpcUrl, BigInteger requiredBalanceOf) {
|
|
45
|
+
this.contractAddress = contractAddress;
|
|
46
|
+
this.jsonRpcUrl = jsonRpcUrl;
|
|
47
|
+
this.web3j = Web3j.build(new HttpService(jsonRpcUrl));
|
|
48
|
+
this.requiredBalanceOf = requiredBalanceOf;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Token(String contractAddress, String jsonRpcUrl) {
|
|
52
|
+
this.contractAddress = contractAddress;
|
|
53
|
+
this.jsonRpcUrl = jsonRpcUrl;
|
|
54
|
+
this.web3j = Web3j.build(new HttpService(jsonRpcUrl));
|
|
55
|
+
this.requiredBalanceOf = BigInteger.valueOf(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public Boolean hasRequiredBalanceOf(String address) {
|
|
59
|
+
return this.balanceOf(address).compareTo(this.requiredBalanceOf) >= 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public BigInteger balanceOf(String address) {
|
|
63
|
+
if (this.jsonRpcUrl == null || this.jsonRpcUrl.length() <= 0) {
|
|
64
|
+
return BigInteger.ZERO;
|
|
65
|
+
}
|
|
66
|
+
Function function = new Function("balanceOf", Arrays.asList(new Address(address)),
|
|
67
|
+
Arrays.asList(new TypeReference<Uint>() {
|
|
68
|
+
}));
|
|
69
|
+
String encodedFunction = FunctionEncoder.encode(function);
|
|
70
|
+
try {
|
|
71
|
+
List<?> result = FunctionReturnDecoder.decode(this.web3j.ethCall(
|
|
72
|
+
Transaction.createEthCallTransaction(address, this.contractAddress, encodedFunction),
|
|
73
|
+
DefaultBlockParameterName.LATEST).sendAsync().get().getValue(), function.getOutputParameters());
|
|
74
|
+
if (result.size() <= 0) {
|
|
75
|
+
return BigInteger.ZERO;
|
|
76
|
+
}
|
|
77
|
+
return ((Uint) result.get(0)).getValue();
|
|
78
|
+
} catch (InterruptedException e) {
|
|
79
|
+
logger.error(e);
|
|
80
|
+
} catch (ExecutionException e) {
|
|
81
|
+
logger.error(e);
|
|
82
|
+
}
|
|
83
|
+
return BigInteger.ZERO;
|
|
84
|
+
}
|
|
85
|
+
}
|