@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,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/main/java/com/bitspur/keycloak/siwe/siu/SiuUtil.java
|
|
3
|
+
* Project: siwe-keycloak
|
|
4
|
+
* File Created: 24-01-2026
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* BitSpur (c) Copyright 2022 - 2026
|
|
8
|
+
*
|
|
9
|
+
* Utility functions for Sign-In With Sui (SIU) authentication.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
package com.bitspur.keycloak.siwe.siu;
|
|
13
|
+
|
|
14
|
+
import com.bitspur.keycloak.siwe.Errors.SiweKeycloakException;
|
|
15
|
+
import com.bitspur.keycloak.siwe.SiweSession;
|
|
16
|
+
import com.bitspur.keycloak.siwe.jpa.nonce.SiweNonce;
|
|
17
|
+
import com.bitspur.keycloak.siwe.jpa.wallet.SiweWalletAddress;
|
|
18
|
+
import com.bitspur.keycloak.siwe.siu.authenticators.browser.SiuBrowserAuthenticatorFactory;
|
|
19
|
+
import com.bitspur.keycloak.siwe.siu.authenticators.directgrant.SiuDirectGrantAuthenticatorFactory;
|
|
20
|
+
import com.iwebpp.crypto.TweetNaclFast;
|
|
21
|
+
|
|
22
|
+
import java.nio.charset.StandardCharsets;
|
|
23
|
+
import java.security.SecureRandom;
|
|
24
|
+
import java.util.ArrayList;
|
|
25
|
+
import java.util.Base64;
|
|
26
|
+
import java.util.List;
|
|
27
|
+
|
|
28
|
+
import jakarta.persistence.EntityManager;
|
|
29
|
+
import jakarta.persistence.NoResultException;
|
|
30
|
+
import jakarta.persistence.TypedQuery;
|
|
31
|
+
import jakarta.ws.rs.core.Cookie;
|
|
32
|
+
import jakarta.ws.rs.core.MultivaluedHashMap;
|
|
33
|
+
import jakarta.ws.rs.core.MultivaluedMap;
|
|
34
|
+
|
|
35
|
+
import org.jboss.logging.Logger;
|
|
36
|
+
import org.keycloak.authentication.AuthenticationFlowContext;
|
|
37
|
+
import org.keycloak.authentication.AuthenticationFlowError;
|
|
38
|
+
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
|
39
|
+
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
|
40
|
+
import org.keycloak.events.Details;
|
|
41
|
+
import org.keycloak.events.Errors;
|
|
42
|
+
import org.keycloak.forms.login.LoginFormsProvider;
|
|
43
|
+
import org.keycloak.http.HttpRequest;
|
|
44
|
+
import org.keycloak.models.AuthenticationExecutionModel;
|
|
45
|
+
import org.keycloak.models.AuthenticationFlowModel;
|
|
46
|
+
import org.keycloak.models.AuthenticatorConfigModel;
|
|
47
|
+
import org.keycloak.models.KeycloakSession;
|
|
48
|
+
import org.keycloak.models.ModelDuplicateException;
|
|
49
|
+
import org.keycloak.models.RealmModel;
|
|
50
|
+
import org.keycloak.models.UserModel;
|
|
51
|
+
import org.keycloak.services.ServicesLogger;
|
|
52
|
+
import org.keycloak.services.messages.Messages;
|
|
53
|
+
import org.keycloak.userprofile.UserProfile;
|
|
54
|
+
import org.keycloak.userprofile.UserProfileContext;
|
|
55
|
+
import org.keycloak.userprofile.UserProfileProvider;
|
|
56
|
+
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
57
|
+
|
|
58
|
+
public class SiuUtil {
|
|
59
|
+
private static final Logger logger = Logger.getLogger(SiuUtil.class);
|
|
60
|
+
private static final SecureRandom random = new SecureRandom();
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Address type for Sui wallets.
|
|
64
|
+
*/
|
|
65
|
+
public static final String SUI_ADDRESS_TYPE = "sui";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Cookie name for SIU session.
|
|
69
|
+
*/
|
|
70
|
+
public static final String KEYCLOAK_SESSION_SIU = "KEYCLOAK_SESSION_SIU";
|
|
71
|
+
|
|
72
|
+
// Hex characters for decoding
|
|
73
|
+
private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Decodes a hex encoded string to bytes.
|
|
77
|
+
* Handles both lowercase and uppercase hex, with or without 0x prefix.
|
|
78
|
+
*
|
|
79
|
+
* @param hex Hex encoded string
|
|
80
|
+
* @return Decoded bytes
|
|
81
|
+
* @throws SiweKeycloakException if decoding fails
|
|
82
|
+
*/
|
|
83
|
+
public static byte[] decodeHex(String hex) throws SiweKeycloakException {
|
|
84
|
+
if (hex == null || hex.isEmpty()) {
|
|
85
|
+
throw new SiweKeycloakException("Cannot decode null or empty hex string");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Remove 0x prefix if present
|
|
89
|
+
if (hex.startsWith("0x") || hex.startsWith("0X")) {
|
|
90
|
+
hex = hex.substring(2);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (hex.length() % 2 != 0) {
|
|
94
|
+
throw new SiweKeycloakException("Invalid hex string length: must be even");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
byte[] result = new byte[hex.length() / 2];
|
|
98
|
+
for (int i = 0; i < hex.length(); i += 2) {
|
|
99
|
+
int high = Character.digit(hex.charAt(i), 16);
|
|
100
|
+
int low = Character.digit(hex.charAt(i + 1), 16);
|
|
101
|
+
if (high == -1 || low == -1) {
|
|
102
|
+
throw new SiweKeycloakException("Invalid hex character at position " + i);
|
|
103
|
+
}
|
|
104
|
+
result[i / 2] = (byte) ((high << 4) + low);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Encodes bytes to hex string with 0x prefix.
|
|
111
|
+
*
|
|
112
|
+
* @param bytes Bytes to encode
|
|
113
|
+
* @return Hex encoded string with 0x prefix
|
|
114
|
+
*/
|
|
115
|
+
public static String encodeHex(byte[] bytes) {
|
|
116
|
+
StringBuilder sb = new StringBuilder("0x");
|
|
117
|
+
for (byte b : bytes) {
|
|
118
|
+
sb.append(HEX_CHARS[(b >> 4) & 0xF]);
|
|
119
|
+
sb.append(HEX_CHARS[b & 0xF]);
|
|
120
|
+
}
|
|
121
|
+
return sb.toString();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Verifies an ed25519 signature for a SIU message.
|
|
126
|
+
*
|
|
127
|
+
* @param message The message that was signed
|
|
128
|
+
* @param signature The Base64-encoded signature
|
|
129
|
+
* @param publicKey The hex-encoded public key (Sui address, with 0x prefix)
|
|
130
|
+
* @return true if the signature is valid
|
|
131
|
+
* @throws SiweKeycloakException if verification fails
|
|
132
|
+
*/
|
|
133
|
+
public static boolean verifySignature(String message, String signature, String publicKey)
|
|
134
|
+
throws SiweKeycloakException {
|
|
135
|
+
try {
|
|
136
|
+
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
|
|
137
|
+
byte[] signatureBytes = Base64.getDecoder().decode(signature);
|
|
138
|
+
byte[] publicKeyBytes = decodeHex(publicKey);
|
|
139
|
+
|
|
140
|
+
// Verify signature length (64 bytes for ed25519)
|
|
141
|
+
if (signatureBytes.length != 64) {
|
|
142
|
+
throw new SiweKeycloakException("Invalid signature length: expected 64 bytes, got " + signatureBytes.length);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Verify public key length (32 bytes for ed25519)
|
|
146
|
+
if (publicKeyBytes.length != 32) {
|
|
147
|
+
throw new SiweKeycloakException("Invalid public key length: expected 32 bytes, got " + publicKeyBytes.length);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Use TweetNaCl for ed25519 signature verification
|
|
151
|
+
// Create a Signature instance with the public key for verification
|
|
152
|
+
TweetNaclFast.Signature sig = new TweetNaclFast.Signature(publicKeyBytes, null);
|
|
153
|
+
return sig.detached_verify(messageBytes, signatureBytes);
|
|
154
|
+
} catch (SiweKeycloakException e) {
|
|
155
|
+
throw e;
|
|
156
|
+
} catch (IllegalArgumentException e) {
|
|
157
|
+
throw new SiweKeycloakException("Invalid Base64 encoding for signature");
|
|
158
|
+
} catch (Exception e) {
|
|
159
|
+
throw new SiweKeycloakException("Signature verification failed: " + e.getMessage());
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Verifies SIU credentials including signature, nonce, and domain.
|
|
165
|
+
*
|
|
166
|
+
* @param session Keycloak session
|
|
167
|
+
* @param configModel Authenticator config
|
|
168
|
+
* @param siuCredentials SIU credentials to verify
|
|
169
|
+
* @return true if credentials are valid
|
|
170
|
+
* @throws SiweKeycloakException if verification fails
|
|
171
|
+
*/
|
|
172
|
+
public static Boolean verifySiuCredentials(KeycloakSession session,
|
|
173
|
+
AuthenticatorConfigModel configModel, SiuCredentials siuCredentials)
|
|
174
|
+
throws SiweKeycloakException {
|
|
175
|
+
HttpRequest httpRequest = session.getContext().getHttpRequest();
|
|
176
|
+
|
|
177
|
+
// Get and validate nonce
|
|
178
|
+
String nonce = getNonce(session);
|
|
179
|
+
if (nonce == null) {
|
|
180
|
+
throw new SiweKeycloakException("Missing nonce in session");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Verify nonce matches
|
|
184
|
+
if (!nonce.equals(siuCredentials.message.getNonce())) {
|
|
185
|
+
throw new SiweKeycloakException("Nonce mismatch");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Reconstruct the message and verify signature
|
|
189
|
+
String reconstructedMessage = siuCredentials.message.toMessage();
|
|
190
|
+
String address = siuCredentials.message.getAddress();
|
|
191
|
+
|
|
192
|
+
return verifySignature(reconstructedMessage, siuCredentials.signature, address);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Finds an existing user by wallet address or creates a new user if none exists.
|
|
197
|
+
*
|
|
198
|
+
* @param context Authentication flow context
|
|
199
|
+
* @param siuCredentials SIU credentials
|
|
200
|
+
* @return UserModel or null if creation/lookup fails
|
|
201
|
+
* @throws SiweKeycloakException if an error occurs
|
|
202
|
+
*/
|
|
203
|
+
public static UserModel findOrCreateUser(AuthenticationFlowContext context,
|
|
204
|
+
SiuCredentials siuCredentials) throws SiweKeycloakException {
|
|
205
|
+
return findOrCreateUser(context, siuCredentials, true);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Finds an existing user by wallet address or creates a new user if none exists.
|
|
210
|
+
*
|
|
211
|
+
* @param context Authentication flow context
|
|
212
|
+
* @param siuCredentials SIU credentials
|
|
213
|
+
* @param failureChallenge Whether to issue a failure challenge on error
|
|
214
|
+
* @return UserModel or null if creation/lookup fails
|
|
215
|
+
* @throws SiweKeycloakException if an error occurs
|
|
216
|
+
*/
|
|
217
|
+
public static UserModel findOrCreateUser(AuthenticationFlowContext context,
|
|
218
|
+
SiuCredentials siuCredentials, Boolean failureChallenge) throws SiweKeycloakException {
|
|
219
|
+
String address = siuCredentials.message.getAddress();
|
|
220
|
+
if (address == null) {
|
|
221
|
+
if (failureChallenge) {
|
|
222
|
+
context.getEvent().error(Errors.USER_NOT_FOUND);
|
|
223
|
+
LoginFormsProvider form = context.form();
|
|
224
|
+
form.setExecution(context.getExecution().getId());
|
|
225
|
+
form.setError(Messages.INVALID_USER);
|
|
226
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_USER, form.createLoginUsernamePassword());
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
UserModel user = null;
|
|
232
|
+
EntityManager entityManager = context.getSession().getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
// Look up existing user by Sui wallet address
|
|
236
|
+
List<SiweWalletAddress> wallets = entityManager
|
|
237
|
+
.createNamedQuery("findByRealmAddressType", SiweWalletAddress.class)
|
|
238
|
+
.setParameter("realmId", context.getRealm().getId())
|
|
239
|
+
.setParameter("address", address.trim().toLowerCase())
|
|
240
|
+
.setParameter("addressType", SUI_ADDRESS_TYPE)
|
|
241
|
+
.getResultList();
|
|
242
|
+
|
|
243
|
+
if (!wallets.isEmpty()) {
|
|
244
|
+
user = context.getSession().users().getUserById(context.getRealm(), wallets.get(0).getUserId());
|
|
245
|
+
}
|
|
246
|
+
} catch (ModelDuplicateException mde) {
|
|
247
|
+
if (failureChallenge) {
|
|
248
|
+
ServicesLogger.LOGGER.modelDuplicateException(mde);
|
|
249
|
+
context.getEvent().error(Errors.USERNAME_IN_USE);
|
|
250
|
+
LoginFormsProvider form = context.form();
|
|
251
|
+
form.setError(Messages.USERNAME_EXISTS);
|
|
252
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_USER, form.createLoginUsernamePassword());
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// If no existing user found, create a new one
|
|
258
|
+
if (user == null) {
|
|
259
|
+
if (verifySiuCredentials(context.getSession(), context.getAuthenticatorConfig(), siuCredentials)) {
|
|
260
|
+
KeycloakSession session = context.getSession();
|
|
261
|
+
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
|
|
262
|
+
MultivaluedMap<String, String> formData = new MultivaluedHashMap<>();
|
|
263
|
+
String username = generateRandomUsername(session, context.getRealm());
|
|
264
|
+
context.getEvent().detail(Details.USERNAME, username);
|
|
265
|
+
context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username);
|
|
266
|
+
formData.add("username", username);
|
|
267
|
+
UserProfile profile = profileProvider.create(UserProfileContext.REGISTRATION, formData);
|
|
268
|
+
user = profile.create();
|
|
269
|
+
user.setEnabled(true);
|
|
270
|
+
|
|
271
|
+
// Add Sui wallet address (normalized to lowercase)
|
|
272
|
+
SiweWalletAddress wallet = new SiweWalletAddress();
|
|
273
|
+
wallet.setAddress(address.trim().toLowerCase());
|
|
274
|
+
wallet.setAddressType(SUI_ADDRESS_TYPE);
|
|
275
|
+
wallet.setUserId(user.getId());
|
|
276
|
+
wallet.setRealmId(context.getRealm().getId());
|
|
277
|
+
entityManager.persist(wallet);
|
|
278
|
+
|
|
279
|
+
return user;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (failureChallenge) {
|
|
283
|
+
context.getEvent().error(Errors.USER_NOT_FOUND);
|
|
284
|
+
LoginFormsProvider form = context.form();
|
|
285
|
+
form.setExecution(context.getExecution().getId());
|
|
286
|
+
form.setError(Messages.INVALID_USER);
|
|
287
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_USER, form.createLoginUsernamePassword());
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
String username = user.getUsername();
|
|
293
|
+
context.getEvent().detail(Details.USERNAME, username);
|
|
294
|
+
context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username);
|
|
295
|
+
return user;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Verifies that an existing user exists for the given wallet address.
|
|
300
|
+
*
|
|
301
|
+
* @param session Keycloak session
|
|
302
|
+
* @param configModel Authenticator config
|
|
303
|
+
* @param siuCredentials SIU credentials
|
|
304
|
+
* @return true if user exists
|
|
305
|
+
*/
|
|
306
|
+
public static Boolean verifyExistingUser(KeycloakSession session,
|
|
307
|
+
AuthenticatorConfigModel configModel, SiuCredentials siuCredentials) {
|
|
308
|
+
String address = siuCredentials.message.getAddress().trim().toLowerCase();
|
|
309
|
+
RealmModel realm = session.getContext().getRealm();
|
|
310
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
311
|
+
List<SiweWalletAddress> wallets = entityManager
|
|
312
|
+
.createNamedQuery("findByRealmAddressType", SiweWalletAddress.class)
|
|
313
|
+
.setParameter("realmId", realm.getId())
|
|
314
|
+
.setParameter("address", address)
|
|
315
|
+
.setParameter("addressType", SUI_ADDRESS_TYPE)
|
|
316
|
+
.getResultList();
|
|
317
|
+
return !wallets.isEmpty();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Performs full verification including existing user, credentials, etc.
|
|
322
|
+
*/
|
|
323
|
+
public static Boolean verify(KeycloakSession session, AuthenticatorConfigModel configModel,
|
|
324
|
+
SiuCredentials siuCredentials) throws SiweKeycloakException {
|
|
325
|
+
return verifyExistingUser(session, configModel, siuCredentials)
|
|
326
|
+
&& verifySiuCredentials(session, configModel, siuCredentials);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Generates a SIU session with a new nonce.
|
|
331
|
+
*/
|
|
332
|
+
public static SiweSession generateSiuSession(KeycloakSession session) {
|
|
333
|
+
String nonce = generateNonce();
|
|
334
|
+
String sessionKey = generateSessionKey(session.getContext().getRealm().getName());
|
|
335
|
+
setNonce(session, sessionKey, nonce);
|
|
336
|
+
return new SiweSession(sessionKey, nonce);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Gets the authenticator config for SIU.
|
|
341
|
+
*/
|
|
342
|
+
public static AuthenticatorConfigModel getAuthenticatorConfig(KeycloakSession session, Boolean isDirectGrantFlow) {
|
|
343
|
+
RealmModel realm = session.getContext().getRealm();
|
|
344
|
+
AuthenticationFlowModel flow = realm.getBrowserFlow();
|
|
345
|
+
if (isDirectGrantFlow) {
|
|
346
|
+
flow = realm.getDirectGrantFlow();
|
|
347
|
+
}
|
|
348
|
+
AuthenticationExecutionModel execution = realm.getAuthenticationExecutionsStream(flow.getId())
|
|
349
|
+
.filter(e -> {
|
|
350
|
+
String authenticator = e.getAuthenticator();
|
|
351
|
+
return authenticator != null
|
|
352
|
+
&& authenticator.equals(isDirectGrantFlow ? SiuDirectGrantAuthenticatorFactory.ID
|
|
353
|
+
: SiuBrowserAuthenticatorFactory.ID);
|
|
354
|
+
})
|
|
355
|
+
.findFirst().orElse(null);
|
|
356
|
+
if (execution == null) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
return realm.getAuthenticatorConfigById(execution.getAuthenticatorConfig());
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Gets the session key from the SIU cookie.
|
|
364
|
+
*/
|
|
365
|
+
public static String getSessionKey(KeycloakSession session) throws SiweKeycloakException {
|
|
366
|
+
HttpRequest request = session.getContext().getHttpRequest();
|
|
367
|
+
Cookie cookie = request.getHttpHeaders().getCookies().get(KEYCLOAK_SESSION_SIU);
|
|
368
|
+
String sessionKey = null;
|
|
369
|
+
if (cookie != null) {
|
|
370
|
+
sessionKey = cookie.getValue();
|
|
371
|
+
}
|
|
372
|
+
if (sessionKey == null) {
|
|
373
|
+
throw new SiweKeycloakException("missing siu session");
|
|
374
|
+
}
|
|
375
|
+
return sessionKey;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Gets the nonce from the session.
|
|
380
|
+
*/
|
|
381
|
+
public static String getNonce(KeycloakSession session) throws SiweKeycloakException {
|
|
382
|
+
String sessionKey = getSessionKey(session);
|
|
383
|
+
return getNonce(session, sessionKey);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Gets the nonce for a specific session key.
|
|
388
|
+
*/
|
|
389
|
+
public static String getNonce(KeycloakSession session, String key) {
|
|
390
|
+
SiweNonce siweNonce = getSiweNonce(session, key);
|
|
391
|
+
if (siweNonce == null) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
return siweNonce.getNonce();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Sets the nonce for a session key.
|
|
399
|
+
*/
|
|
400
|
+
public static String setNonce(KeycloakSession session, String key, String nonce) {
|
|
401
|
+
RealmModel realm = session.getContext().getRealm();
|
|
402
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
403
|
+
SiweNonce siweNonce = getSiweNonce(session, key);
|
|
404
|
+
if (siweNonce == null) {
|
|
405
|
+
siweNonce = new SiweNonce();
|
|
406
|
+
siweNonce.setKey(key);
|
|
407
|
+
siweNonce.setRealmId(realm.getId());
|
|
408
|
+
}
|
|
409
|
+
siweNonce.setNonce(nonce);
|
|
410
|
+
entityManager.merge(siweNonce);
|
|
411
|
+
return nonce;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Removes a nonce from the session.
|
|
416
|
+
*/
|
|
417
|
+
public static Void removeNonce(KeycloakSession session, String key) {
|
|
418
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
419
|
+
SiweNonce siweNonce = getSiweNonce(session, key);
|
|
420
|
+
if (siweNonce == null) {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
entityManager.remove(siweNonce);
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Finds a user by Sui wallet address.
|
|
429
|
+
*/
|
|
430
|
+
public static UserModel findUserByWalletAddress(KeycloakSession session, String address) {
|
|
431
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
432
|
+
try {
|
|
433
|
+
TypedQuery<SiweWalletAddress> query = entityManager.createNamedQuery("findByRealmAddressType",
|
|
434
|
+
SiweWalletAddress.class);
|
|
435
|
+
query.setParameter("address", address.trim().toLowerCase());
|
|
436
|
+
query.setParameter("realmId", session.getContext().getRealm().getId());
|
|
437
|
+
query.setParameter("addressType", SUI_ADDRESS_TYPE);
|
|
438
|
+
List<SiweWalletAddress> wallets = query.getResultList();
|
|
439
|
+
if (!wallets.isEmpty()) {
|
|
440
|
+
return session.users().getUserById(session.getContext().getRealm(), wallets.get(0).getUserId());
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
443
|
+
} catch (NoResultException e) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Adds a Sui wallet address to a user.
|
|
450
|
+
*/
|
|
451
|
+
public static boolean addWalletAddress(KeycloakSession session, UserModel user, String address) {
|
|
452
|
+
String normalizedAddress = address.trim().toLowerCase();
|
|
453
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
454
|
+
TypedQuery<SiweWalletAddress> query = entityManager.createNamedQuery("findByRealmAddressType",
|
|
455
|
+
SiweWalletAddress.class);
|
|
456
|
+
query.setParameter("address", normalizedAddress);
|
|
457
|
+
query.setParameter("realmId", session.getContext().getRealm().getId());
|
|
458
|
+
query.setParameter("addressType", SUI_ADDRESS_TYPE);
|
|
459
|
+
List<SiweWalletAddress> existingWallets = query.getResultList();
|
|
460
|
+
for (SiweWalletAddress wallet : existingWallets) {
|
|
461
|
+
if (wallet.getUserId().equals(user.getId())) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
SiweWalletAddress wallet = new SiweWalletAddress();
|
|
466
|
+
wallet.setAddress(normalizedAddress);
|
|
467
|
+
wallet.setAddressType(SUI_ADDRESS_TYPE);
|
|
468
|
+
wallet.setUserId(user.getId());
|
|
469
|
+
wallet.setRealmId(session.getContext().getRealm().getId());
|
|
470
|
+
entityManager.persist(wallet);
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Removes a Sui wallet address.
|
|
476
|
+
*/
|
|
477
|
+
public static boolean removeWalletAddress(KeycloakSession session, String address) {
|
|
478
|
+
String normalizedAddress = address.trim().toLowerCase();
|
|
479
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
480
|
+
TypedQuery<SiweWalletAddress> query = entityManager.createNamedQuery("findByRealmAddressType",
|
|
481
|
+
SiweWalletAddress.class);
|
|
482
|
+
query.setParameter("address", normalizedAddress);
|
|
483
|
+
query.setParameter("realmId", session.getContext().getRealm().getId());
|
|
484
|
+
query.setParameter("addressType", SUI_ADDRESS_TYPE);
|
|
485
|
+
try {
|
|
486
|
+
List<SiweWalletAddress> wallets = query.getResultList();
|
|
487
|
+
if (!wallets.isEmpty()) {
|
|
488
|
+
entityManager.remove(wallets.get(0));
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
} catch (NoResultException e) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Gets all Sui wallet addresses for a user.
|
|
499
|
+
*/
|
|
500
|
+
public static List<SiweWalletAddress> getWalletAddresses(KeycloakSession session, UserModel user) {
|
|
501
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
502
|
+
TypedQuery<SiweWalletAddress> query = entityManager.createNamedQuery("findByUserId", SiweWalletAddress.class);
|
|
503
|
+
query.setParameter("userId", user.getId());
|
|
504
|
+
List<SiweWalletAddress> allWallets = query.getResultList();
|
|
505
|
+
// Filter to only Sui wallets
|
|
506
|
+
List<SiweWalletAddress> suiWallets = new ArrayList<>();
|
|
507
|
+
for (SiweWalletAddress wallet : allWallets) {
|
|
508
|
+
if (SUI_ADDRESS_TYPE.equals(wallet.getAddressType())) {
|
|
509
|
+
suiWallets.add(wallet);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return suiWallets;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Private helper methods
|
|
516
|
+
|
|
517
|
+
private static SiweNonce getSiweNonce(KeycloakSession session, String key) {
|
|
518
|
+
RealmModel realm = session.getContext().getRealm();
|
|
519
|
+
EntityManager entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
|
520
|
+
SiweNonce siweNonce = entityManager.createNamedQuery("findByRealmKey", SiweNonce.class)
|
|
521
|
+
.setParameter("key", key)
|
|
522
|
+
.setParameter("realmId", realm.getId())
|
|
523
|
+
.getResultList().stream().findFirst().orElse(null);
|
|
524
|
+
return siweNonce;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private static String generateNonce() {
|
|
528
|
+
return Base64.getUrlEncoder().withoutPadding().encodeToString(random.generateSeed(8))
|
|
529
|
+
.replaceAll("[^A-Za-z0-9]", "0")
|
|
530
|
+
.substring(0, 8);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
private static String generateSessionKey(String realmName) {
|
|
534
|
+
byte[] keyBytes = new byte[32];
|
|
535
|
+
random.nextBytes(keyBytes);
|
|
536
|
+
return realmName + "/" + (Base64.getEncoder().encodeToString(keyBytes));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private static String generateRandomUsername(KeycloakSession session, RealmModel realm) {
|
|
540
|
+
while (true) {
|
|
541
|
+
String username = String.format("sui_%010d", Math.abs(random.nextLong() % 10000000000L));
|
|
542
|
+
if (KeycloakModelUtils.findUserByNameOrEmail(session, realm, username) == null) {
|
|
543
|
+
return username;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/browser/SiuBrowserAuthenticator.java
|
|
3
|
+
* Project: siwe-keycloak
|
|
4
|
+
* File Created: 24-01-2026
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* BitSpur (c) Copyright 2022 - 2026
|
|
8
|
+
*
|
|
9
|
+
* Browser authenticator for Sign-In With Sui (SIU).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
package com.bitspur.keycloak.siwe.siu.authenticators.browser;
|
|
13
|
+
|
|
14
|
+
import com.bitspur.keycloak.siwe.Errors.SiweKeycloakException;
|
|
15
|
+
import com.bitspur.keycloak.siwe.siu.SiuCredentials;
|
|
16
|
+
import com.bitspur.keycloak.siwe.siu.SiuUtil;
|
|
17
|
+
|
|
18
|
+
import jakarta.ws.rs.core.MultivaluedMap;
|
|
19
|
+
import jakarta.ws.rs.core.Response;
|
|
20
|
+
|
|
21
|
+
import org.jboss.logging.Logger;
|
|
22
|
+
import org.keycloak.authentication.AuthenticationFlowContext;
|
|
23
|
+
import org.keycloak.authentication.AuthenticationFlowError;
|
|
24
|
+
import org.keycloak.authentication.Authenticator;
|
|
25
|
+
import org.keycloak.forms.login.LoginFormsProvider;
|
|
26
|
+
import org.keycloak.models.KeycloakSession;
|
|
27
|
+
import org.keycloak.models.RealmModel;
|
|
28
|
+
import org.keycloak.models.UserModel;
|
|
29
|
+
import org.keycloak.services.messages.Messages;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Browser-based SIU authenticator.
|
|
33
|
+
* Handles the login form submission with Sui wallet signature.
|
|
34
|
+
*/
|
|
35
|
+
public class SiuBrowserAuthenticator implements Authenticator {
|
|
36
|
+
private static final Logger logger = Logger.getLogger(SiuBrowserAuthenticator.class);
|
|
37
|
+
|
|
38
|
+
@Override
|
|
39
|
+
public void authenticate(AuthenticationFlowContext context) {
|
|
40
|
+
// Display the SIU login form
|
|
41
|
+
LoginFormsProvider form = context.form();
|
|
42
|
+
form.setExecution(context.getExecution().getId());
|
|
43
|
+
Response response = form.createLoginUsernamePassword();
|
|
44
|
+
context.challenge(response);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Override
|
|
48
|
+
public void action(AuthenticationFlowContext context) {
|
|
49
|
+
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
|
50
|
+
String signature = formData.getFirst("username"); // Signature is passed as username
|
|
51
|
+
String message = formData.getFirst("password"); // Base64 SIU message is passed as password
|
|
52
|
+
|
|
53
|
+
if (signature == null || signature.isEmpty() || message == null || message.isEmpty()) {
|
|
54
|
+
context.getEvent().error("missing_credentials");
|
|
55
|
+
LoginFormsProvider form = context.form();
|
|
56
|
+
form.setExecution(context.getExecution().getId());
|
|
57
|
+
form.setError(Messages.INVALID_USER);
|
|
58
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, form.createLoginUsernamePassword());
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
SiuCredentials siuCredentials = new SiuCredentials(signature, message);
|
|
64
|
+
UserModel user = SiuUtil.findOrCreateUser(context, siuCredentials, true);
|
|
65
|
+
|
|
66
|
+
if (user == null) {
|
|
67
|
+
// findOrCreateUser already issued failure challenge
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Verify the credentials
|
|
72
|
+
if (!SiuUtil.verifySiuCredentials(context.getSession(), context.getAuthenticatorConfig(), siuCredentials)) {
|
|
73
|
+
context.getEvent().error("invalid_signature");
|
|
74
|
+
LoginFormsProvider form = context.form();
|
|
75
|
+
form.setExecution(context.getExecution().getId());
|
|
76
|
+
form.setError(Messages.INVALID_USER);
|
|
77
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, form.createLoginUsernamePassword());
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
context.setUser(user);
|
|
82
|
+
context.success();
|
|
83
|
+
} catch (SiweKeycloakException e) {
|
|
84
|
+
logger.error("SIU authentication error", e);
|
|
85
|
+
context.getEvent().error("siu_error");
|
|
86
|
+
LoginFormsProvider form = context.form();
|
|
87
|
+
form.setExecution(context.getExecution().getId());
|
|
88
|
+
form.setError(e.getMessage());
|
|
89
|
+
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, form.createLoginUsernamePassword());
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Override
|
|
94
|
+
public boolean requiresUser() {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Override
|
|
99
|
+
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Override
|
|
104
|
+
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
|
105
|
+
// No required actions
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@Override
|
|
109
|
+
public void close() {
|
|
110
|
+
// Nothing to close
|
|
111
|
+
}
|
|
112
|
+
}
|