diff --git a/backend/explorer-core/autoconfigure/pom.xml b/backend/explorer-core/autoconfigure/pom.xml
index 9a8b0b94e..e68720ea6 100644
--- a/backend/explorer-core/autoconfigure/pom.xml
+++ b/backend/explorer-core/autoconfigure/pom.xml
@@ -59,6 +59,28 @@
${jdbc.driver.postgresql.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ com.github.theborakompanioni.bitcoin-spring-boot-starter
+ spring-lnurl-auth-starter
+ devel-SNAPSHOT
+
+
+ io.github.theborakompanioni
+ lnurl-simple
+ ${bitcoin-spring-boot-starter.version}
+
+
io.minio
@@ -153,6 +175,14 @@
+
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/configuration/CachingConfiguration.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/configuration/CachingConfiguration.java
index a9566d7a2..dd6342da3 100644
--- a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/configuration/CachingConfiguration.java
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/configuration/CachingConfiguration.java
@@ -1,5 +1,6 @@
package org.royllo.explorer.core.configuration;
+import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@@ -8,6 +9,7 @@
*/
@Configuration
@EnableCaching
+@RequiredArgsConstructor
public class CachingConfiguration {
}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/User.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/User.java
index ea20e6038..16d8f8873 100644
--- a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/User.java
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/User.java
@@ -6,7 +6,6 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
-import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@@ -18,6 +17,7 @@
import static jakarta.persistence.EnumType.STRING;
import static jakarta.persistence.GenerationType.IDENTITY;
+import static lombok.AccessLevel.PACKAGE;
/**
* Application user.
@@ -26,7 +26,7 @@
@Setter
@ToString
@RequiredArgsConstructor
-@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@AllArgsConstructor(access = PACKAGE)
@Builder
@Entity
@Table(name = "APPLICATION_USER")
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/UserLnurlAuthKey.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/UserLnurlAuthKey.java
new file mode 100644
index 000000000..15296ef63
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/UserLnurlAuthKey.java
@@ -0,0 +1,54 @@
+package org.royllo.explorer.core.domain.user;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+import static jakarta.persistence.FetchType.EAGER;
+import static jakarta.persistence.GenerationType.IDENTITY;
+import static lombok.AccessLevel.PACKAGE;
+
+/**
+ * Public key generated by the user's Lightning wallet.
+ * This key is unique to each user and service combination, ensuring that the user's identity is consistent with each service but not across different services.
+ */
+@Getter
+@Setter
+@ToString
+@RequiredArgsConstructor
+@AllArgsConstructor(access = PACKAGE)
+@Builder
+@Entity
+@Table(name = "APPLICATION_USER_LNURL_AUTH_LINKING_KEY")
+public class UserLnurlAuthKey {
+
+ /** Unique identifier. */
+ @Id
+ @Column(name = "ID")
+ @GeneratedValue(strategy = IDENTITY)
+ private Long id;
+
+ /** User. */
+ @ManyToOne(fetch = EAGER)
+ @JoinColumn(name = "FK_USER_OWNER", nullable = false)
+ private User owner;
+
+ /** Linking key. */
+ @Column(name = "LINKING_KEY", nullable = false)
+ private String linkingKey;
+
+ /** K1: Randomly generated token that served as a challenge. */
+ @Column(name = "K1", nullable = false)
+ private String k1;
+
+}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/util/K1Value.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/util/K1Value.java
new file mode 100644
index 000000000..a3347e831
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/util/K1Value.java
@@ -0,0 +1,35 @@
+package org.royllo.explorer.core.domain.util;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.royllo.explorer.core.util.base.BaseDomain;
+
+import static lombok.AccessLevel.PACKAGE;
+
+/**
+ * K1 Value created by the system.
+ */
+@Getter
+@Setter
+@ToString
+@RequiredArgsConstructor
+@AllArgsConstructor(access = PACKAGE)
+@Builder
+@Entity
+@Table(name = "UTIL_K1_CACHE")
+public class K1Value extends BaseDomain {
+
+ /** K1 (Unique identifier). */
+ @Id
+ @Column(name = "K1")
+ private String k1;
+
+}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/util/package-info.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/util/package-info.java
new file mode 100644
index 000000000..fa4a8152e
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/util/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Utility domain objects.
+ */
+package org.royllo.explorer.core.domain.util;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/ContentService.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/ContentService.java
index e507aa223..2b34af7d5 100644
--- a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/ContentService.java
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/ContentService.java
@@ -13,4 +13,19 @@ public interface ContentService {
*/
void storeFile(byte[] fileContent, String fileName);
+ /**
+ * Check if a file exists.
+ *
+ * @param fileName file name
+ * @return true if file exists, false otherwise
+ */
+ boolean fileExists(String fileName);
+
+ /**
+ * Delete a file.
+ *
+ * @param fileName file name
+ */
+ void deleteFile(String fileName);
+
}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/LocalFileServiceImplementation.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/LocalFileServiceImplementation.java
index 477601931..32b672b73 100644
--- a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/LocalFileServiceImplementation.java
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/LocalFileServiceImplementation.java
@@ -6,12 +6,14 @@
import io.undertow.server.handlers.resource.PathResourceManager;
import io.undertow.server.handlers.resource.ResourceHandler;
import jakarta.annotation.PreDestroy;
+import lombok.NonNull;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import javax.xml.bind.DatatypeConverter;
+import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
@@ -100,7 +102,7 @@ public void onDestroy() throws Exception {
@Override
@SuppressWarnings("checkstyle:DesignForExtension")
public void storeFile(final byte[] fileContent,
- final String fileName) {
+ @NonNull final String fileName) {
try {
Files.write(fileSystem.getPath(".").resolve(fileName), fileContent);
} catch (Exception e) {
@@ -108,6 +110,22 @@ public void storeFile(final byte[] fileContent,
}
}
+ @Override
+ @SuppressWarnings("checkstyle:DesignForExtension")
+ public boolean fileExists(@NonNull final String fileName) {
+ return Files.exists(fileSystem.getPath(".").resolve(fileName));
+ }
+
+ @Override
+ @SuppressWarnings("checkstyle:DesignForExtension")
+ public void deleteFile(@NonNull final String fileName) {
+ try {
+ Files.delete(fileSystem.getPath(".").resolve(fileName));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Returns the sha256 value calculated with the parameter.
*
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/S3ServiceImplementation.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/S3ServiceImplementation.java
index 5bfb498cb..fd075afdf 100644
--- a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/S3ServiceImplementation.java
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/provider/storage/S3ServiceImplementation.java
@@ -2,6 +2,9 @@
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
+import io.minio.RemoveObjectArgs;
+import io.minio.StatObjectArgs;
+import io.minio.errors.ErrorResponseException;
import lombok.NonNull;
import org.apache.tika.Tika;
import org.royllo.explorer.core.util.base.BaseService;
@@ -41,17 +44,22 @@ public void updateS3Parameters(final S3Parameters newS3Parameters) {
this.s3Parameters = newS3Parameters;
}
- @Override
- public void storeFile(final byte[] fileContent, @NonNull final String fileName) {
- // Creating a client.
- MinioClient minioClient = MinioClient.builder()
+ /**
+ * Get Minio client.
+ *
+ * @return Minio client.
+ */
+ private MinioClient getMinioClient() {
+ return MinioClient.builder()
.endpoint(s3Parameters.getEndpointURL())
.credentials(s3Parameters.getAccessKey(), s3Parameters.getSecretKey())
.build();
+ }
- // Adding the file to the bucket.
+ @Override
+ public void storeFile(final byte[] fileContent, @NonNull final String fileName) {
try {
- minioClient.putObject(PutObjectArgs.builder()
+ getMinioClient().putObject(PutObjectArgs.builder()
.bucket(s3Parameters.getBucketName())
.object(fileName).stream(new ByteArrayInputStream(fileContent), fileContent.length, -1)
.contentType(new Tika().detect(fileContent))
@@ -61,4 +69,34 @@ public void storeFile(final byte[] fileContent, @NonNull final String fileName)
}
}
+ @Override
+ public boolean fileExists(@NonNull final String fileName) {
+ try {
+ getMinioClient().statObject(StatObjectArgs.builder()
+ .bucket(s3Parameters.getBucketName())
+ .object(fileName)
+ .build());
+ return true;
+ } catch (ErrorResponseException e) {
+ return false;
+ } catch (Exception e) {
+ logger.error("Error checking if file exists {} in S3: {}", fileName, e.getMessage());
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void deleteFile(@NonNull final String fileName) {
+ try {
+ getMinioClient().removeObject(
+ RemoveObjectArgs.builder()
+ .bucket(s3Parameters.getBucketName())
+ .object(fileName)
+ .build());
+ } catch (Exception e) {
+ logger.error("Error deleting file {} in S3: {}", fileName, e.getMessage());
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/user/UserLnurlAuthKeyRepository.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/user/UserLnurlAuthKeyRepository.java
new file mode 100644
index 000000000..ef19e0f0e
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/user/UserLnurlAuthKeyRepository.java
@@ -0,0 +1,31 @@
+package org.royllo.explorer.core.repository.user;
+
+import org.royllo.explorer.core.domain.user.UserLnurlAuthKey;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+/**
+ * {@link UserLnurlAuthKey} repository.
+ */
+@Repository
+public interface UserLnurlAuthKeyRepository extends JpaRepository {
+
+ /**
+ * Find a user lnurl-auth by the linking key.
+ *
+ * @param linkingKey linking key
+ * @return user lnurl-auth key
+ */
+ Optional findByLinkingKey(String linkingKey);
+
+ /**
+ * Find a user lnurl-auth by the k1.
+ *
+ * @param k1 k1
+ * @return user lnurl-auth key
+ */
+ Optional findByK1(String k1);
+
+}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/util/K1ValueRepository.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/util/K1ValueRepository.java
new file mode 100644
index 000000000..5c4d37715
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/util/K1ValueRepository.java
@@ -0,0 +1,24 @@
+package org.royllo.explorer.core.repository.util;
+
+import org.royllo.explorer.core.domain.util.K1Value;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+/**
+ * {@link K1Value} repository.
+ */
+@Repository
+public interface K1ValueRepository extends JpaRepository {
+
+ /**
+ * Find all K1 values created before a given date.
+ *
+ * @param createdOn the date
+ * @return the list of K1 values
+ */
+ List findByCreatedOnBefore(ZonedDateTime createdOn);
+
+}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/util/package-info.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/util/package-info.java
new file mode 100644
index 000000000..9552b0b2d
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/repository/util/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * util repository package.
+ */
+package org.royllo.explorer.core.repository.util;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/asset/AssetServiceImplementation.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/asset/AssetServiceImplementation.java
index fe9932ecf..c01fe78d0 100644
--- a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/asset/AssetServiceImplementation.java
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/asset/AssetServiceImplementation.java
@@ -1,5 +1,6 @@
package org.royllo.explorer.core.service.asset;
+import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.common.util.StringUtils;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@@ -20,6 +21,7 @@
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
+import java.io.IOException;
import java.math.BigInteger;
import java.time.ZonedDateTime;
import java.util.Optional;
@@ -102,21 +104,29 @@ public void updateAsset(final String assetId,
if (metadata != null) {
try {
- // Decoding (same as using xxd -r -p)
+ // Decoding (same as using xxd -r -p).
byte[] decodedBytes = Hex.decodeHex(metadata);
// Detecting the file type.
final String mimeType = new Tika().detect(decodedBytes);
- final String extension = MimeTypes.getDefaultMimeTypes().forName(mimeType).getExtension();
+ String extension = MimeTypes.getDefaultMimeTypes().forName(mimeType).getExtension();
+
+ // If we have a file extension ".txt", we check if it's a JSON.
+ if (".txt".equalsIgnoreCase(extension)) {
+ if (isJSONValid(new String(decodedBytes))) {
+ extension = ".json";
+ }
+ }
// Saving the file.
final String fileName = assetId + extension;
contentService.storeFile(decodedBytes, fileName);
+ logger.info("Asset id update for {}: Metadata saved as {}", assetId, fileName);
// Setting the name of the file.
assetToUpdate.get().setMetaDataFileName(fileName);
} catch (DecoderException | MimeTypeException e) {
- logger.error("Error decoding and saving metadata {}", e.getMessage());
+ logger.error("Asset id update for {}: Error decoding and saving metadata {}", assetId, e.getMessage());
}
}
@@ -124,12 +134,14 @@ public void updateAsset(final String assetId,
// If we have the new amount.
if (amount != null) {
assetToUpdate.get().setAmount(amount);
+ logger.info("Asset id update for {}: Amount updated to {}", assetId, amount);
}
// =============================================================================================================
// If we have the issuance date.
if (issuanceDate != null) {
assetToUpdate.get().setIssuanceDate(issuanceDate);
+ logger.info("Asset id update for {}: Issuance date updated to {}", assetId, issuanceDate);
}
// We save the asset with the new information.
@@ -198,4 +210,20 @@ public Page getAssetsByAssetGroupId(final String assetGroupId, final i
.map(ASSET_MAPPER::mapToAssetDTO);
}
+
+ /**
+ * Returns true if the string is a valid JSON.
+ *
+ * @param content string to check
+ * @return true if content is a valid JSON
+ */
+ private boolean isJSONValid(final String content) {
+ try {
+ new ObjectMapper().readTree(content);
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserService.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserService.java
index 2227c2c87..479f0fc80 100644
--- a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserService.java
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserService.java
@@ -23,6 +23,14 @@ public interface UserService {
*/
UserDTO getAnonymousUser();
+ /**
+ * Create user.
+ *
+ * @param username user name
+ * @return user created
+ */
+ UserDTO createUser(String username);
+
/**
* Get user by its user id.
*
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java
index 7ec0a9d74..3e5051682 100644
--- a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java
@@ -1,31 +1,49 @@
package org.royllo.explorer.core.service.user;
+import io.micrometer.common.util.StringUtils;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.royllo.explorer.core.domain.user.User;
+import org.royllo.explorer.core.domain.user.UserLnurlAuthKey;
import org.royllo.explorer.core.dto.user.UserDTO;
+import org.royllo.explorer.core.repository.user.UserLnurlAuthKeyRepository;
import org.royllo.explorer.core.repository.user.UserRepository;
import org.royllo.explorer.core.util.base.BaseService;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
+import org.tbk.lnurl.auth.K1;
+import org.tbk.lnurl.auth.LinkingKey;
+import org.tbk.lnurl.auth.LnurlAuthPairingService;
+import org.tbk.lnurl.simple.auth.SimpleLinkingKey;
+import java.util.Collections;
import java.util.Optional;
+import java.util.UUID;
import static org.royllo.explorer.core.util.constants.AdministratorUserConstants.ADMINISTRATOR_ID;
import static org.royllo.explorer.core.util.constants.AnonymousUserConstants.ANONYMOUS_ID;
+import static org.royllo.explorer.core.util.enums.UserRole.USER;
/**
* {@link UserService} implementation.
+ * Also implements {@link LnurlAuthPairingService} that brings the wallet and the web session together.
*/
-@SuppressWarnings("unused")
@Service
@RequiredArgsConstructor
-public class UserServiceImplementation extends BaseService implements UserService {
+@SuppressWarnings({"checkstyle:DesignForExtension", "unused"})
+public class UserServiceImplementation extends BaseService implements UserService, LnurlAuthPairingService, UserDetailsService {
/** User repository. */
private final UserRepository userRepository;
+ /** User lnurl-auth key repository. */
+ private final UserLnurlAuthKeyRepository userLnurlAuthKeyRepository;
+
@Override
- public final UserDTO getAdministratorUser() {
+ public UserDTO getAdministratorUser() {
logger.info("Getting administrator user");
final Optional administratorUser = userRepository.findById(ADMINISTRATOR_ID);
@@ -39,7 +57,7 @@ public final UserDTO getAdministratorUser() {
}
@Override
- public final UserDTO getAnonymousUser() {
+ public UserDTO getAnonymousUser() {
logger.info("Getting anonymous user");
final Optional anonymousUser = userRepository.findById(ANONYMOUS_ID);
@@ -53,7 +71,26 @@ public final UserDTO getAnonymousUser() {
}
@Override
- public final Optional getUserByUserId(@NonNull final String userId) {
+ public UserDTO createUser(final String username) {
+ logger.info("Creating a user with username: {}", username);
+
+ // Verification.
+ assert StringUtils.isNotEmpty(username) : "Username is required";
+ assert userRepository.findByUsernameIgnoreCase(username.trim()).isEmpty() : "Username '" + username + "' already registered";
+
+ // Creation.
+ final User userCreated = userRepository.save(User.builder()
+ .userId(UUID.randomUUID().toString())
+ .username(username.trim().toLowerCase())
+ .role(USER)
+ .build());
+
+ logger.info("User created: {}", userCreated);
+ return USER_MAPPER.mapToUserDTO(userCreated);
+ }
+
+ @Override
+ public Optional getUserByUserId(@NonNull final String userId) {
logger.info("Getting user with userId: {}", userId);
final Optional user = userRepository.findByUserId(userId);
@@ -67,7 +104,7 @@ public final Optional getUserByUserId(@NonNull final String userId) {
}
@Override
- public final Optional getUserByUsername(@NonNull final String username) {
+ public Optional getUserByUsername(@NonNull final String username) {
logger.info("Getting user with username: {}", username);
final Optional user = userRepository.findByUsernameIgnoreCase(username);
@@ -80,4 +117,54 @@ public final Optional getUserByUsername(@NonNull final String username)
}
}
+ @Override
+ public boolean pairK1WithLinkingKey(@NonNull final K1 k1, @NonNull final LinkingKey linkingKey) {
+ // This method is called by the spring boot starter when a user has provided a k1 signed with a linking key and everything is valid.
+ // Usually, this is where you search if the linking key already exists in the database.
+ // If not, you create a new user and store the linking key.
+ // If yes, you just return the user, and you update the k1 used.
+ final Optional linkingKeyInDatabase = userLnurlAuthKeyRepository.findByLinkingKey(linkingKey.toHex());
+ if (linkingKeyInDatabase.isEmpty()) {
+ logger.info("Creating the user for the linking key: {}", linkingKey.toHex());
+ // We create the user.
+ final UserDTO user = createUser(linkingKey.toHex());
+ // We create the user lnurl-auth key.
+ userLnurlAuthKeyRepository.save(UserLnurlAuthKey.builder()
+ .owner(USER_MAPPER.mapToUser(user))
+ .linkingKey(linkingKey.toHex())
+ .k1(k1.toHex())
+ .build());
+ } else {
+ logger.info("User with the linking key {} exists", linkingKey.toHex());
+ linkingKeyInDatabase.get().setK1(k1.toHex());
+ userLnurlAuthKeyRepository.save(linkingKeyInDatabase.get());
+ }
+ return true;
+ }
+
+ @Override
+ public Optional findPairedLinkingKeyByK1(@NonNull final K1 k1) {
+ // This method returns the linking key associated with the k1 passed as parameter.
+ logger.info("Finding the paired linking key for k1 {}", k1.toHex());
+ final Optional linkingKey = userLnurlAuthKeyRepository.findByK1(k1.toHex());
+ if (linkingKey.isPresent()) {
+ logger.info("Linking key found: {}", linkingKey.get().getLinkingKey());
+ return Optional.of(SimpleLinkingKey.fromHex(linkingKey.get().getLinkingKey()));
+ } else {
+ logger.info("Linking key NOT found: {}", k1.toHex());
+ return Optional.empty();
+ }
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
+ // Search for the user with it's linking key.
+ final UserLnurlAuthKey userLinkingKey = userLnurlAuthKeyRepository.findByLinkingKey(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
+
+ return new org.springframework.security.core.userdetails.User(userLinkingKey.getOwner().getUsername(),
+ UUID.randomUUID().toString(),
+ Collections.singletonList((new SimpleGrantedAuthority(userLinkingKey.getOwner().getRole().toString()))));
+ }
+
}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/util/DatabaseK1Manager.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/util/DatabaseK1Manager.java
new file mode 100644
index 000000000..f9599b12d
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/util/DatabaseK1Manager.java
@@ -0,0 +1,50 @@
+package org.royllo.explorer.core.service.util;
+
+import lombok.RequiredArgsConstructor;
+import org.royllo.explorer.core.domain.util.K1Value;
+import org.royllo.explorer.core.repository.util.K1ValueRepository;
+import org.tbk.lnurl.auth.K1;
+import org.tbk.lnurl.auth.K1Factory;
+import org.tbk.lnurl.auth.K1Manager;
+import org.tbk.lnurl.auth.SimpleK1Factory;
+import org.tbk.lnurl.simple.auth.SimpleK1;
+
+import java.time.ZonedDateTime;
+
+/**
+ * Database K1 manager.
+ */
+@RequiredArgsConstructor
+@SuppressWarnings({"checkstyle:DesignForExtension"})
+public class DatabaseK1Manager implements K1Manager {
+
+ /** Simple K1 factory (Generated with random). */
+ private final K1Factory factory = new SimpleK1Factory();
+
+ /** K1 value repository. */
+ private final K1ValueRepository repository;
+
+ @Override
+ public boolean isValid(final K1 k1) {
+ // Purge old k1 before searching for it.
+ repository.findByCreatedOnBefore(ZonedDateTime.now().minusHours(1))
+ .stream()
+ .map(k1Value -> SimpleK1.fromHex(k1Value.getK1()))
+ .forEach(this::invalidate);
+
+ return repository.existsById(k1.toHex());
+ }
+
+ @Override
+ public void invalidate(final K1 k1) {
+ repository.delete(K1Value.builder().k1(k1.toHex()).build());
+ }
+
+ @Override
+ public K1 create() {
+ K1 k1 = factory.create();
+ repository.save(K1Value.builder().k1(k1.toHex()).build());
+ return k1;
+ }
+
+}
diff --git a/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/util/package-info.java b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/util/package-info.java
new file mode 100644
index 000000000..8f0714fc5
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/util/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Utilities.
+ */
+package org.royllo.explorer.core.service.util;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/META-INF/spring.factories b/backend/explorer-core/autoconfigure/src/main/resources/META-INF/spring.factories
index 2e2d55626..eb2b80faa 100644
--- a/backend/explorer-core/autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/backend/explorer-core/autoconfigure/src/main/resources/META-INF/spring.factories
@@ -1,4 +1,4 @@
-#org.springframework.boot.diagnostics.FailureAnalyzer=tech.cassandre.trading.bot.util.exception.ConfigurationFailureAnalyzer
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.royllo.explorer.core.configuration.DatabaseConfiguration,\
-org.royllo.explorer.core.configuration.ParametersConfiguration
\ No newline at end of file
+org.royllo.explorer.core.configuration.ParametersConfiguration,\
+org.royllo.explorer.core.configuration.CachingConfiguration
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/application.properties b/backend/explorer-core/autoconfigure/src/main/resources/application.properties
index 87c4b323d..b792135d1 100644
--- a/backend/explorer-core/autoconfigure/src/main/resources/application.properties
+++ b/backend/explorer-core/autoconfigure/src/main/resources/application.properties
@@ -11,7 +11,7 @@ royllo.explorer.content.base-url=https://content.royllo.org
# ======================================================================================================================
# Database access configuration.
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
-spring.datasource.url=jdbc:hsqldb:mem:cassandre-database;DB_CLOSE_DELAY=-1
+spring.datasource.url=jdbc:hsqldb:mem:explorer-royllo-database
spring.datasource.username=sa
spring.datasource.password=
#
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/specific/postgresql.xml b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/specific/postgresql.xml
index 64c827338..5bdf59339 100644
--- a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/specific/postgresql.xml
+++ b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/specific/postgresql.xml
@@ -3,15 +3,12 @@
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.4.xsd">
- 1:any
create extension if not exists pg_trgm;
-
- CREATE INDEX INDEX_ASSET_GIN_NAME ON ASSET USING GIN (NAME gin_trgm_ops);
-
+ CREATE INDEX INDEX_ASSET_GIN_NAME ON ASSET USING GIN (NAME gin_trgm_ops);
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table-constraints/table-constraints-application_user.xml b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table-constraints/table-constraints-application_user.xml
index afc7c8f49..83783c024 100644
--- a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table-constraints/table-constraints-application_user.xml
+++ b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table-constraints/table-constraints-application_user.xml
@@ -2,7 +2,14 @@
-
+
+
+
+
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table-constraints/table-constraints-application_user_lnurl_auth_linking_key.xml b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table-constraints/table-constraints-application_user_lnurl_auth_linking_key.xml
new file mode 100644
index 000000000..afc7c8f49
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table-constraints/table-constraints-application_user_lnurl_auth_linking_key.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-application_user_lnurl_auth_linking_key.xml b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-application_user_lnurl_auth_linking_key.xml
new file mode 100644
index 000000000..80b47706e
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-application_user_lnurl_auth_linking_key.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-asset_state.xml b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-asset_state.xml
index 89ff53ec8..92c32b868 100644
--- a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-asset_state.xml
+++ b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-asset_state.xml
@@ -3,7 +3,6 @@
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.4.xsd">
- 1:any
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-util_k1_cache.xml b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-util_k1_cache.xml
new file mode 100644
index 000000000..682ea8117
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-util_k1_cache.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/db.changelog-master.yaml b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/db.changelog-master.yaml
index 2cbb16b38..51df75c7f 100644
--- a/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/db.changelog-master.yaml
+++ b/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -5,7 +5,11 @@ databaseChangeLog:
# Users.
- include:
- file: /db/changelog/1.0.0/table/table-application_user.xml
+ file:
+ /db/changelog/1.0.0/table/table-application_user.xml
+ - include:
+ file:
+ /db/changelog/1.0.0/table/table-application_user_lnurl_auth_linking_key.xml
# Assets.
- include:
@@ -31,6 +35,11 @@ databaseChangeLog:
- include:
file: /db/changelog/1.0.0/table/table-proof.xml
+ # Users.
+ - include:
+ file:
+ /db/changelog/1.0.0/table/table-util_k1_cache.xml
+
# ====================================================================================================================
# 1.0.0 Table constraints definition
@@ -45,6 +54,8 @@ databaseChangeLog:
file: /db/changelog/1.0.0/table-constraints/table-constraints-bitcoin_transaction_output.xml
- include:
file: /db/changelog/1.0.0/table-constraints/table-constraints-application_user.xml
+ - include:
+ file: /db/changelog/1.0.0/table-constraints/table-constraints-application_user_lnurl_auth_linking_key.xml
- include:
file: /db/changelog/1.0.0/table-constraints/table-constraints-universe_server.xml
- include:
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetGroupServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetGroupServiceTest.java
similarity index 98%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetGroupServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetGroupServiceTest.java
index 410d79503..871421caf 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetGroupServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetGroupServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.asset;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetServiceTest.java
similarity index 78%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetServiceTest.java
index e198695b3..c1ec1ac60 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.asset;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -360,6 +360,18 @@ public void updateAsset() {
assertEquals(asset1.getAssetId() + ".png", assetUpdated.get().getMetaDataFileName());
assertEquals(0, new BigInteger("100").compareTo(assetUpdated.get().getAmount()));
assertTrue(testDate.isEqual(assetUpdated.get().getIssuanceDate()));
+
+ // =============================================================================================================
+ // We test with a JSON File.
+ final String adamCoinMetadata = "227b226465736372697074696f6e223a20224120636f696e2064656469636174656420746f204164616d2066726f6d2047656e657369732c20746865206669727374206d616e206f6e2065617274682e20416c736f2061206669727374206173736574206d696e74656420696e20546972616d6973752077616c6c6574206f6e206d61696e6e65742e222c20226e616d65223a20224164616d436f696e222c20226163726f6e796d223a20224143222c202275736572223a20226661756365745f757365725f31222c2022656d61696c223a2022222c20226d696e7465645f7573696e67223a202268747470733a2f2f746573746e65742e7461726f77616c6c65742e6e65742f222c2022696d6167655f64617461223a2022646174613a696d6167652f6a70673b6261736536342c2f396a2f34414151536b5a4a5267414241514141415141424141442f32774244414d694b6c712b57666369766f362f6831636a752f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f327742444164586834662f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f7741415243414367414b41444153494141684542417845422f38514148774141415155424151454241514541414141414141414141414543417751464267634943516f4c2f3851417452414141674544417749454177554642415141414146394151494441415152425249684d5545474531466842794a7846444b426b61454949304b78775256533066416b4d324a7967676b4b4668635947526f6c4a69636f4b536f304e5459334f446b3651305246526b644953557054564656575631685a576d4e6b5a575a6e61476c7163335231646e643465587144684957476834694a69704b546c4a57576c35695a6d714b6a704b576d7036697071724b7a744c57327437693575734c44784d584778386a4a79744c54314e585731396a5a32754869342b546c3575666f3665727838765030396662332b506e362f38514148774541417745424151454241514542415141414141414141414543417751464267634943516f4c2f385141745245414167454342415144424163464241514141514a3341414543417845454253457842684a425551646863524d694d6f454946454b526f62484243534d7a55764156596e4c524368596b4e4f456c3852635947526f6d4a7967704b6a55324e7a67354f6b4e4552555a4853456c4b55315256566c64595756706a5a47566d5a326870616e4e3064585a3365486c36676f4f456859614869496d4b6b704f556c5a61586d4a6d616f714f6b7061616e714b6d7173724f3074626133754c6d367773504578636248794d6e4b3074505531646258324e6e613475506b3565626e364f6e7138765030396662332b506e362f396f4144414d4241414952417845415077434d6a42704b6d70724c6e703170697552305575302b6c49526a725347464646464142525252514155555555414646464641425252525141555555554146464646414252525251424e52535a48725453343755795239495642706d382b6c42633044734e49776355555539597965547750317044475534527365325072556f3272393066352b74495750722b564b3444664b50714b504a392f3070324365782f45306667507a6f4161596a36696d6c4748616e382b2f35307566582f43693479476970574150616d4663644b5968744646464142525252514155555555414646464641425252556b532f784838503861414652416f334e312f6c2f77445870784f66384b43632f7742422f582f5051556f475063306745436b396550616e594136554667426b314730685033525141386e74546161417a594f65445432544a42427869697777413570574978696d5959444f546e7350386154635239345557415870307065473664615145487052534161792b6c4d71587231707369344f5254454d6f6f6f7067464646464142525252514171727559437032394f77362f30464d68484262384b643149392b542f414a2f4b67425648633936474f31636d6e564337626a6a4f425373416e332b57622f363153717058766b5647713835794d65347151664b4f76483871594471544e423745476a48507451415647344a4a39425478366b2b76383652766d484641454979447855696e497068427a78316f35567552696b41382b7448576c7050616b4d6a497763556c4f666e42707455494b4b4b4b4143696969674364526949652f7744576c48336a2b5648384b2f682f4b68652f314e4a674448436d6f534d6438314d78414850725565437a4768414b754d444834342f6e2b46504f4d41446b31477041552b7450586b6261594337654b43636744756154357830352b7638416a536b5935787a514162514254547a2f414a37555964757449526a6967426a636367307056334737673030395451474936476743526334356f50576b5535424a3961576b4d61333354544b6c49345030714b6843436969696d41555555554154352b5666772f6c5372332b744d5535516533394b6550764833352f7a2b6c5377456b2b35544e35505370534d67696f56516e706a696d67486244672b76387853786e6e3844516f4b484a4f6330787547794b5945394a6b35786a38615253536f497853382b3141436e70554a4f4455684f4279616a54356d79653141434d75464761622b465062356a545370484e41446b2b37532f77434e47414647442b4648656b4d582b467638397159553944542f414f48366d696d684d6a3247676f52556c464d5679476969696b4d66476531536476702f4c2f3841562f4b6f4163484e5441385a2f77412f35464a6753557a6f782f4f6c48484835663455704761414774794b615275464b66656b397159434258586c66307054493354627a533434366b5530357a316f41546c75744f4879703961546236304830394b41416355756565656c4a547541754f357044457a514f6e316f417a394b584f4f6151444a4479414f314d6f4a796330565168636e3170435365706f6f6f414b4b4b4b41436e6f324f4430706c4641453252305054742f6e2b564f42787733352f3537314372646a55675048504970414b787a785463656c4c6a2b36632f352f774139615436385544444a704f63394b576c41396141454753616474413638306f474254533265425341516e4a6f41794f507850394253376637332b663841507451547836436741506f4f6c5275326542306f5a73384470546159676f6f6f706746464646414252525251415555555541464b435230707972334e506f734b34774d506f6165435436476b363030726a6c614c447550494864615441715065337253377a53416b7750536c336363635644755072535a7a525943517550716159574a704b4b5942525252514155555555414646464641425467704e4b4550656e3078584762423730465051302b696756776f6f6f6f414b4b4b4b414954316f71586150536a6150536764794b696e4d6d4f52546151776f6f6f6f414b4b4b4b414369696967416f705655734f4b556f5251422f2f396b3d227d22";
+ assetService.updateAsset(asset1.getAssetId(), adamCoinMetadata, null, null);
+
+ // We test the data.
+ assetUpdated = assetService.getAssetByAssetId(asset1.getAssetId());
+ assertTrue(assetUpdated.isPresent());
+ assertEquals(asset1.getAssetId() + ".json", assetUpdated.get().getMetaDataFileName());
+ assertEquals(0, new BigInteger("100").compareTo(assetUpdated.get().getAmount()));
+ assertTrue(testDate.isEqual(assetUpdated.get().getIssuanceDate()));
}
@Test
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetStateServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetStateServiceTest.java
similarity index 99%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetStateServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetStateServiceTest.java
index f7c87bdcb..f0777ba8f 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/AssetStateServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/AssetStateServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.asset;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/package-info.java
new file mode 100644
index 000000000..c7c8e0b3c
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/asset/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Asset services tests.
+ */
+package org.royllo.explorer.core.test.core.service.asset;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/BitcoinServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/bitcoin/BitcoinServiceTest.java
similarity index 98%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/BitcoinServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/bitcoin/BitcoinServiceTest.java
index 4a3b09029..9053391c2 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/BitcoinServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/bitcoin/BitcoinServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.bitcoin;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/ProofServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/proof/ProofServiceTest.java
similarity index 99%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/ProofServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/proof/ProofServiceTest.java
index 575e378eb..f34dd74bc 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/ProofServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/proof/ProofServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.proof;
import okhttp3.OkHttpClient;
import okhttp3.Request;
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/proof/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/proof/package-info.java
new file mode 100644
index 000000000..df41d0e45
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/proof/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Proof service tests.
+ */
+package org.royllo.explorer.core.test.core.service.proof;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/RequestServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/request/RequestServiceTest.java
similarity index 99%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/RequestServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/request/RequestServiceTest.java
index 3666b5687..b5d1acaa9 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/RequestServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/request/RequestServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.request;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/request/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/request/package-info.java
new file mode 100644
index 000000000..0aba417ba
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/request/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Request service tests.
+ */
+package org.royllo.explorer.core.test.core.service.request;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/SearchServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/search/SearchServiceTest.java
similarity index 98%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/SearchServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/search/SearchServiceTest.java
index 2e2e784b0..267d7bb7d 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/SearchServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/search/SearchServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.search;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/search/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/search/package-info.java
new file mode 100644
index 000000000..4dd6e5abe
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/search/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Search services tests.
+ */
+package org.royllo.explorer.core.test.core.service.search;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/StatisticServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/statistics/StatisticServiceTest.java
similarity index 97%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/StatisticServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/statistics/StatisticServiceTest.java
index 33ea61818..c9eb6ae5a 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/StatisticServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/statistics/StatisticServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.statistics;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/statistics/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/statistics/package-info.java
new file mode 100644
index 000000000..28babe6b5
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/statistics/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Statistics service tests.
+ */
+package org.royllo.explorer.core.test.core.service.statistics;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/LocalFileServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/storage/LocalFileServiceTest.java
similarity index 74%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/LocalFileServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/storage/LocalFileServiceTest.java
index e025ebb9d..239b85453 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/LocalFileServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/storage/LocalFileServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.storage;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -14,6 +14,7 @@
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.royllo.explorer.core.provider.storage.LocalFileServiceImplementation.WEB_SERVER_HOST;
@@ -82,4 +83,37 @@ public void storeAndGetFile() {
}
}
+
+ @Test
+ @DisplayName("File exists")
+ public void fileExists() {
+ // The file should not exist.
+ assertFalse(localFileServiceImplementation.fileExists("fileExistsTest.txt"));
+
+ // We create the file.
+ localFileServiceImplementation.storeFile("Hello World!".getBytes(), "fileExistsTest.txt");
+
+ // The file should now exist.
+ assertTrue(localFileServiceImplementation.fileExists("fileExistsTest.txt"));
+ }
+
+ @Test
+ @DisplayName("Delete file")
+ public void deleteFile() {
+ // The file should not exist.
+ assertFalse(localFileServiceImplementation.fileExists("fileDeleteTest.txt"));
+
+ // We create the file.
+ localFileServiceImplementation.storeFile("Hello World!".getBytes(), "fileDeleteTest.txt");
+
+ // The file should now exist.
+ assertTrue(localFileServiceImplementation.fileExists("fileDeleteTest.txt"));
+
+ // We delete the file.
+ localFileServiceImplementation.deleteFile("fileDeleteTest.txt");
+
+ // The file should not exist anymore.
+ assertFalse(localFileServiceImplementation.fileExists("fileDeleteTest.txt"));
+ }
+
}
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/storage/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/storage/package-info.java
new file mode 100644
index 000000000..2ead87c32
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/storage/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Storage services test.
+ */
+package org.royllo.explorer.core.test.core.service.storage;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/UniverseServerServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/universe/UniverseServerServiceTest.java
similarity index 98%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/UniverseServerServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/universe/UniverseServerServiceTest.java
index 203d4421c..be16bff17 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/UniverseServerServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/universe/UniverseServerServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.universe;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/universe/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/universe/package-info.java
new file mode 100644
index 000000000..384756698
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/universe/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Universe service tests.
+ */
+package org.royllo.explorer.core.test.core.service.universe;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/LnurlAuthPairingServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/LnurlAuthPairingServiceTest.java
new file mode 100644
index 000000000..2babe031d
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/LnurlAuthPairingServiceTest.java
@@ -0,0 +1,108 @@
+package org.royllo.explorer.core.test.core.service.user;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.royllo.explorer.core.domain.user.UserLnurlAuthKey;
+import org.royllo.explorer.core.dto.user.UserDTO;
+import org.royllo.explorer.core.repository.user.UserLnurlAuthKeyRepository;
+import org.royllo.explorer.core.repository.user.UserRepository;
+import org.royllo.explorer.core.service.user.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.tbk.lnurl.auth.K1;
+import org.tbk.lnurl.auth.LinkingKey;
+import org.tbk.lnurl.auth.LnurlAuthPairingService;
+import org.tbk.lnurl.simple.auth.SimpleK1;
+import org.tbk.lnurl.simple.auth.SimpleLinkingKey;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.royllo.explorer.core.util.enums.UserRole.USER;
+
+@SpringBootTest
+@DirtiesContext
+@DisplayName("LnurlAuthPairingService tests")
+@SuppressWarnings({"checkstyle:DesignForExtension", "unused"})
+public class LnurlAuthPairingServiceTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private UserLnurlAuthKeyRepository userLnurlAuthKeyRepository;
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private LnurlAuthPairingService lnurlAuthPairingService;
+
+ @Test
+ @DisplayName("pairK1WithLinkingKey() and findPairedLinkingKeyByK1()")
+ void lnurlAuthPairingService() {
+ // Unknown user login information.
+ String simpleK1Value = "e2af6254a8df433264fa23f67eb8188635d15ce883e8fc020989d5f82ae6f11e";
+ String anotherK1Value = "d4f067cf72b94baddac1b3856b231669c3239d59cce95ef260da58363fb01822";
+ String linkingKey1Value = "02c3b844b8104f0c1b15c507774c9ba7fc609f58f343b9b149122e944dd20c9362";
+ K1 k1Test1 = SimpleK1.fromHex(simpleK1Value);
+ K1 k1Test2 = SimpleK1.fromHex(anotherK1Value);
+ LinkingKey linkingKeyTest1 = SimpleLinkingKey.fromHex(linkingKey1Value);
+
+ // We clean the data.
+ userLnurlAuthKeyRepository.findByLinkingKey(linkingKey1Value).ifPresent(userLnurlAuthKey -> userLnurlAuthKeyRepository.delete(userLnurlAuthKey));
+ userRepository.findByUsernameIgnoreCase(linkingKey1Value).ifPresent(user -> userRepository.delete(user));
+
+ // We check the data we have.
+ final long userCount = userRepository.count();
+ final long userLnurlAuthKeyCount = userLnurlAuthKeyRepository.count();
+
+
+ // =============================================================================================================
+ // Test 1 : a new user logs in (it doesn't exist in database).
+ assertFalse(userService.getUserByUsername(linkingKey1Value).isPresent());
+ assertFalse(userLnurlAuthKeyRepository.findByK1(simpleK1Value).isPresent());
+
+ // We pair & check if the data is here.
+ lnurlAuthPairingService.pairK1WithLinkingKey(k1Test1, linkingKeyTest1);
+
+ // We check the number of data created.
+ assertEquals(userCount + 1, userRepository.count());
+ assertEquals(userLnurlAuthKeyCount + 1, userLnurlAuthKeyRepository.count());
+
+ // We check the user created.
+ Optional newUserCreated = userService.getUserByUsername(linkingKey1Value);
+ assertTrue(newUserCreated.isPresent());
+ assertNotNull(newUserCreated.get().getId());
+ assertEquals(linkingKey1Value, newUserCreated.get().getUsername());
+ assertEquals(USER, newUserCreated.get().getRole());
+
+ // We check the user lnurl-auth key created.
+ Optional newUserLinkingKeyCreated = userLnurlAuthKeyRepository.findByLinkingKey(linkingKey1Value);
+ assertTrue(newUserLinkingKeyCreated.isPresent());
+ assertNotNull(newUserLinkingKeyCreated.get().getId());
+ assertEquals(newUserCreated.get().getId(), newUserLinkingKeyCreated.get().getOwner().getId());
+ assertEquals(simpleK1Value, newUserLinkingKeyCreated.get().getK1());
+ assertEquals(linkingKey1Value, newUserLinkingKeyCreated.get().getLinkingKey());
+ assertTrue(userLnurlAuthKeyRepository.findByK1(simpleK1Value).isPresent());
+
+ // =============================================================================================================
+ // Test 2 : the same user logs again with another k1 - No new data but k1 updated.
+ lnurlAuthPairingService.pairK1WithLinkingKey(k1Test2, linkingKeyTest1);
+ assertEquals(userCount + 1, userRepository.count());
+ assertEquals(userLnurlAuthKeyCount + 1, userLnurlAuthKeyRepository.count());
+
+ // We check the user lnurl-auth key updated.
+ newUserLinkingKeyCreated = userLnurlAuthKeyRepository.findByLinkingKey(linkingKey1Value);
+ assertTrue(newUserLinkingKeyCreated.isPresent());
+ assertNotNull(newUserLinkingKeyCreated.get().getId());
+ assertEquals(newUserCreated.get().getId(), newUserLinkingKeyCreated.get().getOwner().getId());
+ assertEquals(anotherK1Value, newUserLinkingKeyCreated.get().getK1());
+ assertEquals(linkingKey1Value, newUserLinkingKeyCreated.get().getLinkingKey());
+ }
+
+}
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/UserDetailsServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/UserDetailsServiceTest.java
new file mode 100644
index 000000000..b619e2964
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/UserDetailsServiceTest.java
@@ -0,0 +1,54 @@
+package org.royllo.explorer.core.test.core.service.user;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.test.annotation.DirtiesContext;
+import org.tbk.lnurl.auth.K1;
+import org.tbk.lnurl.auth.LinkingKey;
+import org.tbk.lnurl.auth.LnurlAuthPairingService;
+import org.tbk.lnurl.simple.auth.SimpleK1;
+import org.tbk.lnurl.simple.auth.SimpleLinkingKey;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@SpringBootTest
+@DirtiesContext
+@DisplayName("UserDetailsService tests")
+public class UserDetailsServiceTest {
+
+ @Autowired
+ private LnurlAuthPairingService lnurlAuthPairingService;
+
+ @Autowired
+ private UserDetailsService userDetailsService;
+
+ @Test
+ @DisplayName("loadUserByUsername()")
+ void loadUserByUsername() {
+ // New user.
+ String user1K1Value = "e2af6254a8df433264fa23f67eb8188635d15ce883e8fc020989d5f82ae6f11e";
+ String user1linkingKey1Value = "02c3b844b8104f0c1b15c507774c9ba7fc609f58f343b9b149122e944dd20c9362";
+ K1 user1K1Test1 = SimpleK1.fromHex(user1K1Value);
+ LinkingKey user1LinkingKeyTest1 = SimpleLinkingKey.fromHex(user1linkingKey1Value);
+
+ // We create a new user.
+ lnurlAuthPairingService.pairK1WithLinkingKey(user1K1Test1, user1LinkingKeyTest1);
+
+ // We try the load the user created.
+ final UserDetails userDetails = userDetailsService.loadUserByUsername(user1linkingKey1Value);
+ assertNotNull(userDetails);
+ assertEquals(user1linkingKey1Value, userDetails.getUsername());
+
+ // We try to load a non-existing user.
+ UsernameNotFoundException e = assertThrows(UsernameNotFoundException.class, () -> userDetailsService.loadUserByUsername("NON_EXISTING_USERNAME"));
+ assertEquals("User not found with username: NON_EXISTING_USERNAME", e.getMessage());
+ }
+
+}
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/UserServiceTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/UserServiceTest.java
similarity index 71%
rename from backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/UserServiceTest.java
rename to backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/UserServiceTest.java
index ec7655fa0..048c67dd1 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/UserServiceTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/UserServiceTest.java
@@ -1,4 +1,4 @@
-package org.royllo.explorer.core.test.core.service;
+package org.royllo.explorer.core.test.core.service.user;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -13,6 +13,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.royllo.explorer.core.util.constants.AdministratorUserConstants.ADMINISTRATOR_ID;
import static org.royllo.explorer.core.util.constants.AdministratorUserConstants.ADMINISTRATOR_USER;
@@ -23,6 +24,7 @@
import static org.royllo.explorer.core.util.constants.AnonymousUserConstants.ANONYMOUS_USER_ID;
import static org.royllo.explorer.core.util.constants.AnonymousUserConstants.ANONYMOUS_USER_USERNAME;
import static org.royllo.explorer.core.util.enums.UserRole.ADMINISTRATOR;
+import static org.royllo.explorer.core.util.enums.UserRole.USER;
@SpringBootTest
@DirtiesContext
@@ -54,6 +56,31 @@ public void getAnonymousUserTest() {
assertEquals(ANONYMOUS_USER.getRole(), anonymousUser.getRole());
}
+ @Test
+ @DisplayName("createUser()")
+ public void createUserTest() {
+ // Creating a user with empty name or null.
+ AssertionError e = assertThrows(AssertionError.class, () -> userService.createUser(null));
+ assertEquals("Username is required", e.getMessage());
+ e = assertThrows(AssertionError.class, () -> userService.createUser(""));
+ assertEquals("Username is required", e.getMessage());
+
+ // Creating an existing user.
+ e = assertThrows(AssertionError.class, () -> userService.createUser(ANONYMOUS_USER_USERNAME));
+ assertEquals("Username 'anonymous' already registered", e.getMessage());
+ e = assertThrows(AssertionError.class, () -> userService.createUser(ADMINISTRATOR_USER_USERNAME));
+ assertEquals("Username 'administrator' already registered", e.getMessage());
+ e = assertThrows(AssertionError.class, () -> userService.createUser("straumat"));
+ assertEquals("Username 'straumat' already registered", e.getMessage());
+
+ // Creating a new user.
+ final UserDTO newUser = userService.createUser("newUser");
+ assertNotNull(newUser);
+ assertNotNull(newUser.getId());
+ assertEquals("newuser", newUser.getUsername());
+ assertEquals(USER, newUser.getRole());
+ }
+
@Test
@DisplayName("getUserByUsername()")
public void getUserByUsernameTest() {
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/package-info.java
new file mode 100644
index 000000000..2ef1bbb64
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/user/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * User service tests.
+ */
+package org.royllo.explorer.core.test.core.service.user;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/util/DatabaseK1ManagerTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/util/DatabaseK1ManagerTest.java
new file mode 100644
index 000000000..111c7f0dd
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/util/DatabaseK1ManagerTest.java
@@ -0,0 +1,91 @@
+package org.royllo.explorer.core.test.core.service.util;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.royllo.explorer.core.domain.util.K1Value;
+import org.royllo.explorer.core.repository.util.K1ValueRepository;
+import org.royllo.explorer.core.service.util.DatabaseK1Manager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.tbk.lnurl.auth.K1;
+import org.tbk.lnurl.simple.auth.SimpleK1;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SpringBootTest
+@DirtiesContext
+@DisplayName("DatabaseK1Manager tests")
+public class DatabaseK1ManagerTest {
+
+ @Autowired
+ private DataSource dataSource;
+
+ @Autowired
+ private K1ValueRepository k1ValueRepository;
+
+ @Test
+ @DisplayName("Create, retrieve and delete a K1 value")
+ public void databaseK1Manager() {
+ DatabaseK1Manager databaseK1Manager = new DatabaseK1Manager(k1ValueRepository);
+
+ k1ValueRepository.deleteAll();
+
+ // K1 value for test.
+ String nonExistingK1Value = "e2af6254a8df433264fa23f67eb8188635d15ce883e8fc020989d5f82ae6f11e";
+ K1 nonExistingK1 = SimpleK1.fromHex(nonExistingK1Value);
+
+ // Checking k1 value creation.
+ assertEquals(0, k1ValueRepository.count());
+ final K1 k1Created = databaseK1Manager.create();
+ assertEquals(1, k1ValueRepository.count());
+
+ // Checking isValid() method.
+ assertTrue(databaseK1Manager.isValid(k1Created));
+ assertFalse(databaseK1Manager.isValid(nonExistingK1));
+
+ // Checking invalidate() method.
+ databaseK1Manager.invalidate(k1Created);
+ databaseK1Manager.invalidate(nonExistingK1);
+ assertEquals(0, k1ValueRepository.count());
+ assertFalse(databaseK1Manager.isValid(k1Created));
+ assertFalse(databaseK1Manager.isValid(nonExistingK1));
+ }
+
+ @Test
+ @DisplayName("Purge old K1 values")
+ public void oldK1Purge() throws SQLException {
+ DatabaseK1Manager databaseK1Manager = new DatabaseK1Manager(k1ValueRepository);
+ k1ValueRepository.deleteAll();
+
+ // Test values.
+ String firstK1Value = "e2af6254a8df433264fa23f67eb8188635d15ce883e8fc020989d5f82ae6f11e";
+ String secondK1Value = "d4f067cf72b94baddac1b3856b231669c3239d59cce95ef260da58363fb01822";
+
+ // Creating two values.
+ k1ValueRepository.save(K1Value.builder().k1(firstK1Value).build());
+ k1ValueRepository.save(K1Value.builder().k1(secondK1Value).build());
+ assertTrue(databaseK1Manager.isValid(SimpleK1.fromHex(firstK1Value)));
+ assertTrue(databaseK1Manager.isValid(SimpleK1.fromHex(secondK1Value)));
+
+ // Updating the first value to change its creation date.
+ try (Connection connection = dataSource.getConnection()) {
+ connection.createStatement().executeQuery("""
+ UPDATE UTIL_K1_CACHE
+ SET CREATED_ON = '2021-01-01 00:00:00'
+ WHERE K1 = '""" + firstK1Value + "'");
+ }
+
+ // Checking isValid() method (will trigger purge so k1 won't exist anymore.
+ assertFalse(databaseK1Manager.isValid(SimpleK1.fromHex(firstK1Value)));
+ assertTrue(databaseK1Manager.isValid(SimpleK1.fromHex(secondK1Value)));
+
+ }
+
+}
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/util/package-info.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/util/package-info.java
new file mode 100644
index 000000000..528a422b0
--- /dev/null
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/core/service/util/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Util service tests.
+ */
+package org.royllo.explorer.core.test.core.service.util;
\ No newline at end of file
diff --git a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/integration/storage/S3ServiceImplementationTest.java b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/integration/storage/S3ServiceImplementationTest.java
index 2607ebb3c..aaee602cd 100644
--- a/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/integration/storage/S3ServiceImplementationTest.java
+++ b/backend/explorer-core/autoconfigure/src/test/java/org/royllo/explorer/core/test/integration/storage/S3ServiceImplementationTest.java
@@ -34,6 +34,7 @@
import java.security.NoSuchAlgorithmException;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -99,6 +100,7 @@ public void s3ImplementationTest() throws DecoderException {
}
// Adding the file.
+ assertFalse(contentService.fileExists("test.txt"));
contentService.storeFile("test".getBytes(), "test.txt");
// Adding the file (again) - Checking there is no exception.
@@ -107,6 +109,7 @@ public void s3ImplementationTest() throws DecoderException {
// Checking that the file now exists.
try {
// File exists ?
+ assertTrue(contentService.fileExists("test.txt"));
minioClient.statObject(StatObjectArgs.builder().bucket(S3_BUCKET_NAME).object("test.txt").build());
// Retrieving the file.
@@ -121,6 +124,10 @@ public void s3ImplementationTest() throws DecoderException {
fail("The file should now exist");
}
+ // Testing file deletion.
+ assertTrue(contentService.fileExists("test.txt"));
+ contentService.deleteFile("test.txt");
+ assertFalse(contentService.fileExists("test.txt"));
}
@BeforeAll
diff --git a/backend/explorer-core/autoconfigure/src/test/resources/application.properties b/backend/explorer-core/autoconfigure/src/test/resources/application.properties
index 6a010af5d..3b1d2f4a5 100644
--- a/backend/explorer-core/autoconfigure/src/test/resources/application.properties
+++ b/backend/explorer-core/autoconfigure/src/test/resources/application.properties
@@ -5,7 +5,7 @@
# ======================================================================================================================
# Database access configuration.
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
-spring.datasource.url=jdbc:hsqldb:mem:cassandre-database;DB_CLOSE_DELAY=-1
+spring.datasource.url=jdbc:hsqldb:mem:explorer-royllo-database
spring.datasource.username=sa
spring.datasource.password=
#
diff --git a/backend/servers/explorer-api/src/main/java/org/royllo/explorer/api/configuration/SecurityConfiguration.java b/backend/servers/explorer-api/src/main/java/org/royllo/explorer/api/configuration/SecurityConfiguration.java
new file mode 100644
index 000000000..4dd212f1f
--- /dev/null
+++ b/backend/servers/explorer-api/src/main/java/org/royllo/explorer/api/configuration/SecurityConfiguration.java
@@ -0,0 +1,35 @@
+package org.royllo.explorer.api.configuration;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.stereotype.Controller;
+
+import static org.springframework.security.config.http.SessionCreationPolicy.NEVER;
+import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
+
+/**
+ * Security configuration.
+ */
+@Controller
+@EnableWebSecurity
+@RequiredArgsConstructor
+@SuppressWarnings({"checkstyle:DesignForExtension"})
+public class SecurityConfiguration {
+
+ @Bean
+ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
+ // Each request that comes in passes through this chain of filters before reaching your application.
+ return http.csrf(AbstractHttpConfigurer::disable)
+ .cors(AbstractHttpConfigurer::disable)
+ .sessionManagement(session -> session.sessionCreationPolicy(NEVER))
+ // Page authorisations.
+ .authorizeHttpRequests((authorize) -> authorize
+ .requestMatchers(antMatcher("/**")).permitAll()
+ ).build();
+ }
+
+}
diff --git a/backend/servers/explorer-api/src/main/resources/application.properties b/backend/servers/explorer-api/src/main/resources/application.properties
index bb824a0a7..6708717c1 100644
--- a/backend/servers/explorer-api/src/main/resources/application.properties
+++ b/backend/servers/explorer-api/src/main/resources/application.properties
@@ -5,7 +5,7 @@
# ======================================================================================================================
# Database access configuration.
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
-spring.datasource.url=jdbc:hsqldb:mem:cassandre-database;DB_CLOSE_DELAY=-1
+spring.datasource.url=jdbc:hsqldb:mem:explorer-royllo-database
spring.datasource.username=sa
spring.datasource.password=
#
diff --git a/backend/servers/explorer-batch/src/main/resources/application.properties b/backend/servers/explorer-batch/src/main/resources/application.properties
index e1ee0aff9..59f6718ff 100644
--- a/backend/servers/explorer-batch/src/main/resources/application.properties
+++ b/backend/servers/explorer-batch/src/main/resources/application.properties
@@ -5,7 +5,7 @@
# ======================================================================================================================
# Database access configuration.
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
-spring.datasource.url=jdbc:hsqldb:mem:cassandre-database;DB_CLOSE_DELAY=-1
+spring.datasource.url=jdbc:hsqldb:mem:explorer-royllo-database
spring.datasource.username=sa
spring.datasource.password=
#
diff --git a/backend/servers/explorer-web/README.md b/backend/servers/explorer-web/README.md
index ca117bda3..6d0204dea 100644
--- a/backend/servers/explorer-web/README.md
+++ b/backend/servers/explorer-web/README.md
@@ -8,10 +8,30 @@ Server side:
Client side:
`npm run build && npm run watch`
-## Design choices
+## Lnurl Auth
-- [HTML first for the philosophy](https://html-first.com/).
-- [Tailwindcss as CSS framework](https://tailwindcss.com/).
-- [Tailwind components library](https://daisyui.com/).
-- [Tailwind dynamic components with Aline](https://devdojo.com/pines)
-- [Example design](https://tailwindui.com/components/ecommerce/page-examples/shopping-cart-pages).
+In order for lnurl-auth to work you must serve your app over `https` (no self-signed cert allowed).
+
+Serving your app with `https` during development can be done with [ngrok](https://ngrok.com/):
+
+```bash
+./ngrok http 8080
+```
+
+This will return a public url that you can use to access your app over `https`:
+
+```bash
+Session Status online
+Account stephane.traumat@gmail.com (Plan: Free)
+Version 3.5.0
+Region Europe (eu)
+Latency 22ms
+Web Interface http://127.0.0.1:4040
+Forwarding https://348a-2001-861-5300-9e20-bf7e-7941-39d8-1e6d.ngrok-free.app -> http://localhost:8080
+```
+
+You can then use the `https` url to access your app by updating `application-dev.properties`, change
+to `royllo.explorer.web.base-url=https://348a-2001-861-5300-9e20-bf7e-7941-39d8-1e6d.ngrok-free.app` (without the
+trailing `/`).
+
+Then run `mvn spring-boot:run -Dspring-boot.run.profiles=dev`.s
\ No newline at end of file
diff --git a/backend/servers/explorer-web/pom.xml b/backend/servers/explorer-web/pom.xml
index b97e899a2..264d2313d 100644
--- a/backend/servers/explorer-web/pom.xml
+++ b/backend/servers/explorer-web/pom.xml
@@ -34,10 +34,19 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.session
+ spring-session-jdbc
+
+
org.springframework.boot
spring-boot-starter-thymeleaf
+
+ org.thymeleaf.extras
+ thymeleaf-extras-springsecurity6
+
nz.net.ultraq.thymeleaf
thymeleaf-layout-dialect
diff --git a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/ErrorConfiguration.java b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/ErrorConfiguration.java
index 79862a782..ea157ac72 100644
--- a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/ErrorConfiguration.java
+++ b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/ErrorConfiguration.java
@@ -2,6 +2,7 @@
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
+import org.royllo.explorer.core.util.base.Base;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -18,7 +19,7 @@
*/
@Controller
@RequiredArgsConstructor
-public class ErrorConfiguration implements ErrorController {
+public class ErrorConfiguration extends Base implements ErrorController {
/**
* Handle error.
@@ -32,6 +33,7 @@ public String handleError(final HttpServletRequest request) {
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
if (statusCode == NOT_FOUND.value()) {
+ logger.error("Error 404: Page not found: {}", request.getRequestURI());
return ERROR_404_PAGE;
}
if (statusCode == INTERNAL_SERVER_ERROR.value()) {
diff --git a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/LnurlAuthConfiguration.java b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/LnurlAuthConfiguration.java
new file mode 100644
index 000000000..ed48c38aa
--- /dev/null
+++ b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/LnurlAuthConfiguration.java
@@ -0,0 +1,60 @@
+package org.royllo.explorer.web.configuration;
+
+import jakarta.servlet.ServletContext;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.royllo.explorer.core.repository.util.K1ValueRepository;
+import org.royllo.explorer.core.service.util.DatabaseK1Manager;
+import org.royllo.explorer.core.util.parameters.RoylloExplorerParameters;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.tbk.lnurl.auth.K1Manager;
+import org.tbk.lnurl.auth.LnurlAuthFactory;
+import org.tbk.lnurl.auth.SimpleLnurlAuthFactory;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import static org.royllo.explorer.web.util.constants.AuthenticationPageConstants.LNURL_AUTH_WALLET_LOGIN_PATH;
+
+/**
+ * Configuration for LNURL-auth.
+ */
+@Configuration
+@RequiredArgsConstructor
+@SuppressWarnings({"checkstyle:DesignForExtension"})
+public class LnurlAuthConfiguration {
+
+ /** Royllo explorer parameters. */
+ private final RoylloExplorerParameters roylloExplorerParameters;
+
+ /** K1 value repository. */
+ private final K1ValueRepository k1ValueRepository;
+
+ /**
+ * K1 manager (managed thanks to a database table).
+ * "k1" refers to a one-time, randomly generated key or token.
+ *
+ * @return k1 manager
+ */
+ @Bean
+ K1Manager k1Manager() {
+ return new DatabaseK1Manager(k1ValueRepository);
+ }
+
+ /**
+ * This factory creates a LNURL-auth callback url for the given k1.
+ *
+ * @param k1Manager k1 manager
+ * @param servletContext servlet context
+ * @return lnurl auth factory
+ */
+ @Bean
+ @SneakyThrows(URISyntaxException.class)
+ LnurlAuthFactory lnurlAuthFactory(final K1Manager k1Manager,
+ final ServletContext servletContext) {
+ URI callbackUrl = new URI(roylloExplorerParameters.getWeb().getBaseUrl() + servletContext.getContextPath() + LNURL_AUTH_WALLET_LOGIN_PATH);
+ return new SimpleLnurlAuthFactory(callbackUrl, k1Manager);
+ }
+
+}
diff --git a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/SecurityConfiguration.java b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/SecurityConfiguration.java
new file mode 100644
index 000000000..225252139
--- /dev/null
+++ b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/SecurityConfiguration.java
@@ -0,0 +1,121 @@
+package org.royllo.explorer.web.configuration;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.stereotype.Controller;
+import org.tbk.lnurl.auth.K1Manager;
+import org.tbk.lnurl.auth.LnurlAuthFactory;
+import org.tbk.lnurl.auth.LnurlAuthPairingService;
+import org.tbk.spring.lnurl.security.LnurlAuthConfigurer;
+
+import static org.royllo.explorer.web.util.constants.AuthenticationPageConstants.LNURL_AUTH_LOGIN_PAGE_PATH;
+import static org.royllo.explorer.web.util.constants.AuthenticationPageConstants.LNURL_AUTH_SESSION_K1_KEY;
+import static org.royllo.explorer.web.util.constants.AuthenticationPageConstants.LNURL_AUTH_SESSION_LOGIN_PATH;
+import static org.royllo.explorer.web.util.constants.AuthenticationPageConstants.LNURL_AUTH_WALLET_LOGIN_PATH;
+import static org.springframework.security.config.http.SessionCreationPolicy.NEVER;
+import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
+
+/**
+ * Security configuration.
+ * TODO remove @Controller
+ */
+@Controller
+@EnableWebSecurity
+@RequiredArgsConstructor
+@SuppressWarnings({"checkstyle:DesignForExtension"})
+public class SecurityConfiguration {
+
+ /** LNURL Auth factory. */
+ private final LnurlAuthFactory lnurlAuthFactory;
+
+ /** K1 manager. "k1" refers to a one-time, randomly generated key or token. */
+ private final K1Manager lnurlAuthk1Manager;
+
+ /** This service acts like a user detail service for LNURL-auth. */
+ private final LnurlAuthPairingService lnurlAuthPairingService;
+
+ /** User details service. */
+ private final UserDetailsService userDetailsService;
+
+ @Bean
+ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
+ // Each request that comes in passes through this chain of filters before reaching your application.
+ return http
+ // TODO Is it necessary to disable CSRF and CORS protection?
+ .csrf(AbstractHttpConfigurer::disable)
+ .cors(AbstractHttpConfigurer::disable)
+ // Specifying that the application should not create new HTTP sessions on its own, but can use existing ones if they are present.
+ // Additionally, it protects against session fixation attacks by migrating the session (i.e., changing the session ID) upon authentication.
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(NEVER)
+ .sessionFixation().migrateSession()
+ )
+ // Page authorisations.
+ .authorizeHttpRequests((authorize) -> authorize
+ // Public website.
+ .requestMatchers(
+ // Pages.
+ antMatcher("/"),
+ antMatcher("/search"),
+ antMatcher("/asset/**"),
+ antMatcher("/request/**"),
+ // Login pages
+ // TODO Try to remove those lines.
+ antMatcher(LNURL_AUTH_LOGIN_PAGE_PATH + "/**"),
+ antMatcher(LNURL_AUTH_WALLET_LOGIN_PATH + "/**"),
+ antMatcher("/api/v1/lnurl-auth/login/**"),
+ // CSS, images and javascript libraries.
+ antMatcher("/css/**"),
+ antMatcher("/images/**"),
+ antMatcher("/svg/**"),
+ antMatcher("/favicon/**"),
+ antMatcher("/webjars/**"),
+ // Util.
+ antMatcher("/sitemap.xml")
+ ).permitAll()
+ // User account pages (Requires authorizations).
+ .requestMatchers(
+ antMatcher("/account/**")
+ ).authenticated()
+ )
+ // If the user is not authenticated when accessing a protected page, redirect to the login page.
+// .exceptionHandling((exceptionHandling) -> exceptionHandling
+// .accessDeniedPage(ERROR_403_PAGE)
+// .authenticationEntryPoint((request, response, authenticationException) -> response.sendRedirect(LNURL_AUTH_LOGIN_PAGE_PATH))
+// )
+ // LNURL Auth configuration.
+ .with(new LnurlAuthConfigurer(), lnurlAuthConfigurer ->
+ lnurlAuthConfigurer
+ // Specify the services we built.
+ .k1Manager(lnurlAuthk1Manager)
+ .pairingService(lnurlAuthPairingService)
+ .lnurlAuthFactory(lnurlAuthFactory)
+ .authenticationUserDetailsService(userDetailsService)
+ // Set the login page endpoint.
+ .loginPageEndpoint(login -> login
+ .enable(true)
+ .baseUri(LNURL_AUTH_LOGIN_PAGE_PATH)
+ )
+ // Configures the LNURL Authorization Server's Session Endpoint.
+ .sessionEndpoint(session -> session
+ .baseUri(LNURL_AUTH_SESSION_LOGIN_PATH)
+ .sessionK1Key(LNURL_AUTH_SESSION_K1_KEY)
+ .successHandlerCustomizer(successHandler -> {
+ successHandler.setDefaultTargetUrl("/");
+ successHandler.setTargetUrlParameter("redirect");
+ successHandler.setAlwaysUseDefaultTargetUrl(false);
+ successHandler.setUseReferer(false);
+ })
+ )
+ // Configures the LNURL Authorization Server's Wallet Endpoint.
+ .walletEndpoint(wallet -> wallet.baseUri(LNURL_AUTH_WALLET_LOGIN_PATH))
+ )
+ .build();
+ }
+
+}
diff --git a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/WebConfiguration.java b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/WebConfiguration.java
index e96975613..ae2bf5f25 100644
--- a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/WebConfiguration.java
+++ b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/WebConfiguration.java
@@ -23,7 +23,6 @@
@RequiredArgsConstructor
public class WebConfiguration implements WebMvcConfigurer {
-
/** Assets search results default page size. */
public static final int ASSET_SEARCH_DEFAULT_PAGE_SIZE = 10;
diff --git a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/advice/ConfigurationControllerAdvice.java b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/advice/ConfigurationControllerAdvice.java
index f6de4f850..199d72171 100644
--- a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/advice/ConfigurationControllerAdvice.java
+++ b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/advice/ConfigurationControllerAdvice.java
@@ -28,13 +28,14 @@ public class ConfigurationControllerAdvice {
@ModelAttribute
public final void handleRequest(final Model model) {
- // Set the base url for web, API and assets.
+ // Set the base url for web, api and contents.
model.addAttribute(API_BASE_URL_ATTRIBUTE, roylloExplorerParameters.getApi().getBaseUrl());
model.addAttribute(WEB_BASE_URL_ATTRIBUTE, roylloExplorerParameters.getWeb().getBaseUrl());
model.addAttribute(CONTENT_BASE_URL_ATTRIBUTE, roylloExplorerParameters.getContent().getBaseUrl());
// Set the analytics parameters.
model.addAttribute(PIWIK_ANALYTICS_TRACKING_ID_ATTRIBUTE, roylloExplorerAnalyticsParameters.getPiwik().getTrackingId());
+
}
}
diff --git a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/constants/AuthenticationPageConstants.java b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/constants/AuthenticationPageConstants.java
new file mode 100644
index 000000000..cb2f7018b
--- /dev/null
+++ b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/constants/AuthenticationPageConstants.java
@@ -0,0 +1,24 @@
+package org.royllo.explorer.web.util.constants;
+
+import lombok.experimental.UtilityClass;
+
+/**
+ * Authentication page constants.
+ */
+@UtilityClass
+@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
+public class AuthenticationPageConstants {
+
+ /** The path to the login page. */
+ public static final String LNURL_AUTH_LOGIN_PAGE_PATH = "/login";
+
+ /** The path to the auth wallet login page. */
+ public static final String LNURL_AUTH_WALLET_LOGIN_PATH = "/api/v1/lnurl-auth/login/wallet";
+
+ /** The path to the auth session login page. */
+ public static final String LNURL_AUTH_SESSION_LOGIN_PATH = "/api/v1/lnurl-auth/login/session?redirect=/";
+
+ /** Authentication session K1 key. */
+ public static final String LNURL_AUTH_SESSION_K1_KEY = "k1";
+
+}
diff --git a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/constants/UtilPagesConstants.java b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/constants/UtilPagesConstants.java
index 2470b3876..33f5760bc 100644
--- a/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/constants/UtilPagesConstants.java
+++ b/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/util/constants/UtilPagesConstants.java
@@ -15,6 +15,9 @@ public class UtilPagesConstants {
/** Generic error page. */
public static final String ERROR_PAGE = "util/errors/error";
+ /** Error 403 page. */
+ public static final String ERROR_403_PAGE = "/error/403";
+
/** Error 404 page. */
public static final String ERROR_404_PAGE = "util/errors/error-404";
diff --git a/backend/servers/explorer-web/src/main/resources/application-dev.properties b/backend/servers/explorer-web/src/main/resources/application-dev.properties
index acbdd4ef3..fc527b7e2 100644
--- a/backend/servers/explorer-web/src/main/resources/application-dev.properties
+++ b/backend/servers/explorer-web/src/main/resources/application-dev.properties
@@ -1,7 +1,7 @@
# ======================================================================================================================
# URL for dev
royllo.explorer.api.base-url=http://localhost:9090/api
-royllo.explorer.web.base-url=http://localhost:3000
+royllo.explorer.web.base-url=https://7daf-2001-861-5300-9e20-c520-117c-aa9a-d9b7.ngrok-free.app
royllo.explorer.content.base-url=http://localhost:9093
#
# ======================================================================================================================
@@ -9,6 +9,11 @@ royllo.explorer.content.base-url=http://localhost:9093
royllo.explorer.analytics.piwik.trackingId=
#
# ======================================================================================================================
+# Session configuration
+spring.session.store-type=jdbc
+spring.session.jdbc.initialize-schema=always
+#
+# ======================================================================================================================
# Using Liquibase to import test data
spring.liquibase.enabled=true
spring.liquibase.change-log=db/dev/db.dev-data.yaml
@@ -16,4 +21,15 @@ spring.liquibase.change-log=db/dev/db.dev-data.yaml
# ======================================================================================================================
# Disable cache during local dev
spring.thymeleaf.cache=false
-spring.web.resources.chain.cache=false
\ No newline at end of file
+spring.web.resources.chain.cache=false
+#
+# Enable SQL logging
+#spring.jpa.show-sql=true
+# Set logging level for Spring Session JDBC
+#logging.level.org.springframework.session.jdbc=DEBUG
+# If you want to see the SQL statements in the console
+#logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
+#logging.level.org.hibernate.SQL=DEBUG
+logging.config:classpath:logback.xml
+logging.level.org.springframework:INFO
+logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping:TRACE
\ No newline at end of file
diff --git a/backend/servers/explorer-web/src/main/resources/application.properties b/backend/servers/explorer-web/src/main/resources/application.properties
index 2fc4b846e..5f840be8f 100644
--- a/backend/servers/explorer-web/src/main/resources/application.properties
+++ b/backend/servers/explorer-web/src/main/resources/application.properties
@@ -15,7 +15,7 @@ royllo.explorer.analytics.piwik.trackingId=00000000-0000-0000-0000-000000000000
# ======================================================================================================================
# Database access configuration.
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
-spring.datasource.url=jdbc:hsqldb:mem:cassandre-database;DB_CLOSE_DELAY=-1
+spring.datasource.url=jdbc:hsqldb:mem:explorer-royllo-database
spring.datasource.username=sa
spring.datasource.password=
#
@@ -27,6 +27,11 @@ s3.bucket-name=royllo-explorer-s3-asset-bucket
s3.endpoint-url=https://s3.amazonaws.com
#
# ======================================================================================================================
+# Session configuration
+spring.session.store-type=jdbc
+spring.session.jdbc.initialize-schema=always
+#
+# ======================================================================================================================
# Cache configuration.
spring.cache.type=caffeine
#
diff --git a/backend/servers/explorer-web/src/main/resources/logback.xml b/backend/servers/explorer-web/src/main/resources/logback.xml
new file mode 100644
index 000000000..4bbffe165
--- /dev/null
+++ b/backend/servers/explorer-web/src/main/resources/logback.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/servers/explorer-web/src/main/resources/templates/util/fragments/layouts/default/header.html b/backend/servers/explorer-web/src/main/resources/templates/util/fragments/layouts/default/header.html
index 32b3a6e89..6ab7397b1 100644
--- a/backend/servers/explorer-web/src/main/resources/templates/util/fragments/layouts/default/header.html
+++ b/backend/servers/explorer-web/src/main/resources/templates/util/fragments/layouts/default/header.html
@@ -4,10 +4,7 @@
-
+
+
+ User authenticated
+
+
+
+
+
+
diff --git a/backend/servers/explorer-web/src/main/resources/templates/util/fragments/layouts/default/layout.html b/backend/servers/explorer-web/src/main/resources/templates/util/fragments/layouts/default/layout.html
index 85bce2a32..43d8c06d2 100644
--- a/backend/servers/explorer-web/src/main/resources/templates/util/fragments/layouts/default/layout.html
+++ b/backend/servers/explorer-web/src/main/resources/templates/util/fragments/layouts/default/layout.html
@@ -1,7 +1,8 @@
+ xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+>
diff --git a/backend/servers/explorer-web/src/test/resources/application.properties b/backend/servers/explorer-web/src/test/resources/application.properties
index 69abd4f62..4c519a5dc 100644
--- a/backend/servers/explorer-web/src/test/resources/application.properties
+++ b/backend/servers/explorer-web/src/test/resources/application.properties
@@ -16,7 +16,7 @@ royllo.explorer.analytics.piwik.trackingId=
# ======================================================================================================================
# Database access configuration.
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
-spring.datasource.url=jdbc:hsqldb:mem:cassandre-database;DB_CLOSE_DELAY=-1
+spring.datasource.url=jdbc:hsqldb:mem:explorer-royllo-database
spring.datasource.username=sa
spring.datasource.password=
#
diff --git a/backend/servers/explorer-web/src/test/resources/db/test/web-test-data-user.xml b/backend/servers/explorer-web/src/test/resources/db/test/web-test-data-user.xml
new file mode 100644
index 000000000..683831708
--- /dev/null
+++ b/backend/servers/explorer-web/src/test/resources/db/test/web-test-data-user.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 49481767e..856e34b2a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -20,8 +20,7 @@ services:
# ====================================================================================================================
# Database used to store data ( https://hub.docker.com/_/postgres ).
royllo-explorer-database-server:
- image: library/postgres:16-alpine
- restart: always
+ image: library/postgres:15-alpine
networks:
- royllo-explorer-network
ports:
@@ -29,8 +28,6 @@ services:
volumes:
- royllo-explorer-database-volume:/var/lib/postgresql/data
environment:
- - TZ=Europe/Paris
- - PGTZ=Europe/Paris
- POSTGRES_DB=royllo_explorer_database
- POSTGRES_USER=royllo_explorer_username
- POSTGRES_PASSWORD=Q5QnE5S%Y]qt7
@@ -39,7 +36,6 @@ services:
# Storage service used to store content ( https://hub.docker.com/r/minio/minio ).
royllo-explorer-storage-server:
image: minio/minio:RELEASE.2024-01-01T16-36-33Z.fips
- restart: always
networks:
- royllo-explorer-network
ports:
@@ -81,21 +77,24 @@ services:
networks:
- royllo-explorer-network
environment:
- - TZ=Europe/Paris
+ # Database configuration.
- ROYLLO_EXPLORER_CONTENT_BASE_URL=http://localhost:9000/royllo-explorer
- SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver
- SPRING_DATASOURCE_URL=jdbc:postgresql://royllo-explorer-database-server/royllo_explorer_database
- SPRING_DATASOURCE_USERNAME=royllo_explorer_username
- SPRING_DATASOURCE_PASSWORD=Q5QnE5S%Y]qt7
- - MEMPOOL_API_BASE-URL=https://mempool.space/testnet/api
- - TAPD_API_BASE-URL=https://testnet.universe.royllo.org:8089
- - TAPD_API_MACAROON=0201047461706402A301030A1004650A26E6928A4C60E007BDA107389E1201301A180A09616464726573736573120472656164120577726974651A150A06617373657473120472656164120577726974651A150A066461656D6F6E120472656164120577726974651A130A046D696E74120472656164120577726974651A150A0670726F6F6673120472656164120577726974651A170A08756E6976657273651204726561641205777269746500000620A28CB923A36B8B86E03C47E5275CA31104A6065401227FFD068BC545DFAB383F
- SPRING_LIQUIBASE_ENABLED=true
+ # Storage configuration.
- SPRING_PROFILES_ACTIVE=s3-storage
- S3_ACCESS_KEY=royllo
- S3_SECRET_KEY=tTX4LD7gYaQbhZ
- S3_BUCKET_NAME=royllo-explorer
- S3_ENDPOINTURL=http://royllo-explorer-storage-server:9000
+ # Mempool configuration.
+ - MEMPOOL_API_BASE-URL=https://mempool.space/testnet/api
+ # Taproot configuration.
+ - TAPD_API_BASE-URL=https://testnet.universe.royllo.org:8089
+ - TAPD_API_MACAROON=0201047461706402A301030A1004650A26E6928A4C60E007BDA107389E1201301A180A09616464726573736573120472656164120577726974651A150A06617373657473120472656164120577726974651A150A066461656D6F6E120472656164120577726974651A130A046D696E74120472656164120577726974651A150A0670726F6F6673120472656164120577726974651A170A08756E6976657273651204726561641205777269746500000620A28CB923A36B8B86E03C47E5275CA31104A6065401227FFD068BC545DFAB383F
# ====================================================================================================================
# Royllo explorer API server ( https://hub.docker.com/r/royllo/explorer-api ).
@@ -108,18 +107,24 @@ services:
ports:
- "9090:8080"
environment:
- - TZ=Europe/Paris
+ # Database configuration.
- ROYLLO_EXPLORER_CONTENT_BASE_URL=http://localhost:9000/royllo-explorer
- SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver
- SPRING_DATASOURCE_URL=jdbc:postgresql://royllo-explorer-database-server/royllo_explorer_database
- SPRING_DATASOURCE_USERNAME=royllo_explorer_username
- SPRING_DATASOURCE_PASSWORD=Q5QnE5S%Y]qt7
- SPRING_LIQUIBASE_ENABLED=false
+ # Storage configuration.
- SPRING_PROFILES_ACTIVE=s3-storage
- S3_ACCESS_KEY=royllo
- S3_SECRET_KEY=tTX4LD7gYaQbhZ
- S3_BUCKET_NAME=royllo-explorer
- S3_ENDPOINTURL=http://royllo-explorer-storage-server:9000
+ # Mempool configuration.
+ - MEMPOOL_API_BASE-URL=https://mempool.space/testnet/api
+ # Taproot configuration.
+ - TAPD_API_BASE-URL=https://testnet.universe.royllo.org:8089
+ - TAPD_API_MACAROON=0201047461706402A301030A1004650A26E6928A4C60E007BDA107389E1201301A180A09616464726573736573120472656164120577726974651A150A06617373657473120472656164120577726974651A150A066461656D6F6E120472656164120577726974651A130A046D696E74120472656164120577726974651A150A0670726F6F6673120472656164120577726974651A170A08756E6976657273651204726561641205777269746500000620A28CB923A36B8B86E03C47E5275CA31104A6065401227FFD068BC545DFAB383F
# ====================================================================================================================
# Royllo explorer web server ( https://hub.docker.com/r/royllo/explorer-web ).
@@ -132,15 +137,21 @@ services:
ports:
- "8080:8080"
environment:
- - TZ=Europe/Paris
+ # Database configuration.
- ROYLLO_EXPLORER_CONTENT_BASE_URL=http://localhost:9000/royllo-explorer
- SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver
- SPRING_DATASOURCE_URL=jdbc:postgresql://royllo-explorer-database-server/royllo_explorer_database
- SPRING_DATASOURCE_USERNAME=royllo_explorer_username
- SPRING_DATASOURCE_PASSWORD=Q5QnE5S%Y]qt7
- SPRING_LIQUIBASE_ENABLED=false
+ # Storage configuration.
- SPRING_PROFILES_ACTIVE=s3-storage
- S3_ACCESS_KEY=royllo
- S3_SECRET_KEY=tTX4LD7gYaQbhZ
- S3_BUCKET_NAME=royllo-explorer
- S3_ENDPOINTURL=http://royllo-explorer-storage-server:9000
+ # Mempool configuration.
+ - MEMPOOL_API_BASE-URL=https://mempool.space/testnet/api
+ # Taproot configuration.
+ - TAPD_API_BASE-URL=https://testnet.universe.royllo.org:8089
+ - TAPD_API_MACAROON=0201047461706402A301030A1004650A26E6928A4C60E007BDA107389E1201301A180A09616464726573736573120472656164120577726974651A150A06617373657473120472656164120577726974651A150A066461656D6F6E120472656164120577726974651A130A046D696E74120472656164120577726974651A150A0670726F6F6673120472656164120577726974651A170A08756E6976657273651204726561641205777269746500000620A28CB923A36B8B86E03C47E5275CA31104A6065401227FFD068BC545DFAB383F
\ No newline at end of file
diff --git a/docs/design/lnurl-auth/README.md b/docs/design/lnurl-auth/README.md
new file mode 100644
index 000000000..ebdf39568
--- /dev/null
+++ b/docs/design/lnurl-auth/README.md
@@ -0,0 +1,205 @@
+# Bitcoin & Java: a guide to LNURL-auth with Spring Boot.
+
+[LNURL-auth](https://lightninglogin.live/) is an authentication protocol that authenticates a user using a digital
+signature only. The protocol can be used as a replacement for email/username, password, social media, 2FA, forgotten
+password... A big win for security and privacy. All the user need to have is a lightning wallet that supports LNURL.
+
+This is the solution I choose for my
+project [Royllo: a search engine / explorer for Taproot Assets](https://explorer.royllo.org/) living on the
+Bitcoin blockchain. To implement it, I used a great spring boot
+starter: [https://github.com/theborakompanioni/bitcoin-spring-boot-starter](https://github.com/theborakompanioni/bitcoin-spring-boot-starter).
+
+-----
+
+## A secure and fast solution.
+
+The first thing is, of course, to have a compatible lightning wallet like [phoenix](https://phoenix.acinq.co/) (you
+don't need to own bitcoins).
+
+When you arrive at a website using LNURL-auth like [lightninglogin.live](https://lightninglogin.live/) and you click on
+the login button, you will see a qr code appears:
+
+
+
+Start your lighting wallet, click on send button, scan the qr code and you are logged, voilà! no email, password, phone
+number… have been exchanged…
+
+-----
+
+## How it works?
+
+The specification can be found [here](https://github.com/lnurl/luds/blob/luds/04.md).
+
+### Step 1: The server generates a login url (in the qr code).
+
+Each time a new login request is made, a new lnurl-auth url is created, and it looks like this: It will look like
+this: `https://site.com?tag=login&k1=RANDOM`
+
+The `tag` parameter with value set to login which means no GET should be made yet.
+
+The `k1` parameter is a randomly generated token that serves as a challenge. It is unique for each authentication
+request. As we will see later, the lightning wallet is expected to sign this `k1` token.
+
+### Step 2: The user scans the qr code with its lighting wallet.
+
+Once the lighting wallet retrieves the qr code, it decodes the two parameters (`tag` & `k1`) and retrieve the domain
+name used (in our example `site.com`).
+
+The domain name is then used by the wallets as material for key derivation. This means the master key of your wallet
+plus the domain will generate what we call a linking key that will only be used for site.com (just like all the keys
+generated from your master key). It's called the `linking private key`.
+
+### Step 3: The lighting wallet calls the server login page.
+
+The lightning wallet will now call the login page with the following parameters:
+
+```
+sig=&key=
+```
+
+The `sig` parameter contains the signature of the `k1` using the `linking private key`.
+The `key` parameter contains the `linking public key`.
+
+### Step 4: the server makes the verification.
+
+The server receives the `sig` parameter that should contain the signature of the `k1` value made by the wallet with the
+`linking private key`.
+
+The first thing to do for the server is to verify the `k1` value's signature created by the lighting wallet and
+transmitted with the `sig` parameter. This is done quite simply by using the `linking public key` transmitted with
+the `key` parameter.
+
+After this verification, we can create or retrieve the user !
+
+-----
+
+## Using bitcoin-spring-boot-starter.
+
+Integrating LNURL-auth in Java/Spring boot is quite simple
+with https://github.com/theborakompanioni/bitcoin-spring-boot-starter.
+
+In order to use LNURL-auth, you must serve your application over https (no self-signed cert allowed). To make it easy, I
+advise you to use [ngrok](https://ngrok.com). It will create a public address with a valid SSL for your local web
+server.
+
+It's quite simple, create a ngrok account, install the ngrok application, configure the token. Then, after starting your
+application (on port 8080 for example), just run: `ngrok http 8080` and you will get:
+
+```
+Session Status online
+Forwarding https://6a8b-92.ngrok-free.app -> http://localhost:8080
+```
+
+When you will type `https://6a8b-92.ngrok-free.app` in your browser, you will access your local server with a valid SSL
+certificate.
+
+### Adding bitcoin-spring-boot-starter.
+
+Adding bitcoin-spring-boot-starter is quite easy, add this dependency to your java spring boot project:
+
+```xml
+
+
+ ...
+
+ io.github.theborakompanioni
+ spring-lnurl-auth-starter
+
+
+ io.github.theborakompanioni
+ lnurl-simple
+
+ ...
+
+```
+
+### Database & domain objects modification.
+
+To represent a user, we already have
+a [user](https://github.com/royllo/explorer/blob/development/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/User.java)
+domain object and
+a [user database script](https://github.com/royllo/explorer/blob/development/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-application_user.xml)
+to create the table. Nothing special here.
+
+We now add
+a [UserLnurlAuthKey](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/UserLnurlAuthKey.java)
+object, its repository, and
+its [sql creation script]https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core/autoconfigure/src/main/resources/db/changelog/1.0.0/table/table-application_user_lnurl_auth_linking_key.xml).
+
+This object is used to store all the linking keys and corresponding k1 of users.
+
+### Adding a k1 manager.
+
+Your application will generate k1 values for each login request. Maybe a wallet will call your url with a k1 value
+or maybe not but for sure, if your server receives an authentication attempt from a wallet, it will need to verify
+the k1 value's have been generated.
+
+To keep it simple, you can use a simple k1 manager like the one provided by the spring boot
+starter: [SimpleK1Manager](https://github.com/theborakompanioni/bitcoin-spring-boot-starter/blob/master/incubator/spring-lnurl/spring-lnurl-auth-simple/src/main/java/org/tbk/lnurl/auth/SimpleK1Manager.java)
+
+In my case, I have several front servers and I need to share the k1 values between them. So I build a K1 store to
+manage using a database:
+
+- I have a very simple table `UTIL_K1_CACHE` to store k1 values [Database script]().
+- A [K1Value]() object to represent a k1 value in database.
+- A simple [K1ValueRepository]() to manage the k1 values in database.
+- A [DatabaseK1Manager]() to create, valid and invalid k1 in the database.
+
+### Implements LnurlAuthPairingService.
+
+LnurlAuthPairingService is an interface provided by bitcoin-spring-boot-starter that you must implement. It declares two
+methods:
+
+**findPairedLinkingKeyByK1**: This method is called by the spring boot starter when a user has provided a k1 signed
+with a linking key and everything is valid.
+Usually, this is where you search if the linking key already exists in the database.
+If not, you create a new user and store the linking key.
+If yes, you just return the user, and you update the k1 used.
+
+We decided to implement direct it in our
+existing [UserService](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java).
+
+Please, not that when you create the user, we don't know anything about the user except the linking key. So we use
+this key as the username.
+
+### Implements UserDetailsService.
+
+`UserDetailsService` is used throughout the Spring framework as a user DAO. The method `UserDetails loadUserByUsername(
+String username)` should be implemented to return a `UserDetails` object that matches the provided username.
+
+Again, We decided to implement direct it in our
+existing [UserService](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core
+/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java).
+
+The implementation is quite simple, the `username` parameter will be filled by the framework with the linking key,
+so we just search the corresponding user by its linking key thanks to
+our [UserLnurlAuthKey](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core
+/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/UserLnurlAuthKey.java) object.
+
+### SecurityConfiguration
+
+To secure our application, we use spring security and, to configure it, we declare a SecurityConfiguration class with
+the `@EnableWebSecurity` annotation.
+
+The method to implement is `public SecurityFilterChain filterChain(final HttpSecurity http)`.
+
+Two things are important:
+
+- The `.authorizeHttpRequests` method that declares the url that should be secured. In our case, we want to secure all
+ the urls behind `account` and allow anything else;
+- The `.with(new LnurlAuthConfigurer(), lnurlAuthConfigurer -> ...)` method that declares the lnurl-auth
+ configuration. First, we declare all the services we build before and the URL configuration.
+
+### lnurlAuthFactory
+
+The `LnurlAuthFactory` is the class that will generate the lnurl-auth url. It is used by the controller to generate the
+qr code. It takes the full login url (this url will be used by the wallet to login) and a k1 manager to generate the
+associated k1 in the qr code.
+
+## Conclusion
+
+LNURL-auth is a great protocol to authenticate users without exchanging any personal information. It is quite easy
+to implement in Java/Spring boot with the bitcoin-spring-boot-starter and @theborakompanioni provides an amazing
+support !
+
+So let's go and let's build a future where we don't need to give our email, phone number, password, social media... !
\ No newline at end of file
diff --git a/docs/design/lnurl-auth/lightninglogin.login.png b/docs/design/lnurl-auth/lightninglogin.login.png
new file mode 100644
index 000000000..b2fd99258
Binary files /dev/null and b/docs/design/lnurl-auth/lightninglogin.login.png differ
diff --git a/docs/design/lnurl-auth/questions.md b/docs/design/lnurl-auth/questions.md
new file mode 100644
index 000000000..62cde82a9
--- /dev/null
+++ b/docs/design/lnurl-auth/questions.md
@@ -0,0 +1,84 @@
+# Lnurl-Auth
+
+Login successful but user not authenticated
+
+I think I'm close to have something working! and the blog post is close to be done too.
+
+But I'm stuck at this step: login page is displayed, I scan the QR code, my wallet says "authentication success",
+but I'm stuck on the login page, no redirect and if I manually go back to home page where there is this thymeleaf code:
+
+```html
+
+```
+
+It says anonymous user
+
+When I check the logs, I can the user is crated and the SecurityContextHolder is set:
+
+```
+s.s.LnurlAuthSessionAuthenticationFilter : got lnurl-auth session migration request for k1 '0f0dbde709f33c8879093987ba53e22eb5eaa2aa0f7acdfd227f598bd01957e5'
+o.r.e.c.s.u.UserServiceImplementation : Finding the paired linking key for k1 0f0dbde709f33c8879093987ba53e22eb5eaa2aa0f7acdfd227f598bd01957e5
+o.r.e.c.s.u.UserServiceImplementation : Linking key not found
+
+.s.w.LnurlAuthWalletAuthenticationFilter : got lnurl-auth wallet authentication request for k1 '55c5460f801a6241d42ac21f86f15d4bf7d176e4acd660e58bff988632e51719'
+o.r.e.c.s.u.UserServiceImplementation : Creating the user for the linking key: 02ed9953f5e3a7a79b943ffa333f08bdfe42373199b21b91a77511d384344bd1c2
+o.r.e.c.s.u.UserServiceImplementation : Creating a user with username: 02ed9953f5e3a7a79b943ffa333f08bdfe42373199b21b91a77511d384344bd1c2
+o.r.e.c.s.u.UserServiceImplementation : User created: User(id=3, userId=b486f731-1335-4d0c-ad5c-1ca509a166aa, username=02ed9953f5e3a7a79b943ffa333f08bdfe42373199b21b91a77511d384344bd1c2, role=USER)
+.s.w.LnurlAuthWalletAuthenticationFilter : Set SecurityContextHolder to LnurlAuthWalletToken [Principal=02ed9953f5e3a7a79b943ffa333f08bdfe42373199b21b91a77511d384344bd1c2, Credentials=[PROTECTED], Authenticated=true, Details=org.springframework.security.core.userdetails.User [Username=02ed9953f5e3a7a79b943ffa333f08bdfe42373199b21b91a77511d384344bd1c2, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[USER]], Granted Authorities=[USER]]
+rlAuthWalletAuthenticationSuccessHandler : Received successful lnurl-auth wallet request of user '02ed9953f5e3a7a79b943ffa333f08bdfe42373199b21b91a77511d384344bd1c2'
+
+s.s.LnurlAuthSessionAuthenticationFilter : got lnurl-auth session migration request for k1 '94b1f3fa2cfcea4ece82b193375f0d5e51b7c83c0d2817657da2f479f7a5e3b5'
+o.r.e.c.s.u.UserServiceImplementation : Finding the paired linking key for k1 94b1f3fa2cfcea4ece82b193375f0d5e51b7c83c0d2817657da2f479f7a5e3b5
+o.r.e.c.s.u.UserServiceImplementation : Linking key not found
+
+s.s.LnurlAuthSessionAuthenticationFilter : got lnurl-auth session migration request for k1 '93a030bff5a07fd26930b1ec2d8535e3a6f15531627969e967b2fb25406657ed'
+o.r.e.c.s.u.UserServiceImplementation : Finding the paired linking key for k1 93a030bff5a07fd26930b1ec2d8535e3a6f15531627969e967b2fb25406657ed
+o.r.e.c.s.u.UserServiceImplementation : Linking key not found
+```
+
+I'm using `0.12.0` and this is what I did:
+
+## DatabaseK1Manager
+
+I
+created
+a [DatabaseK1Manager](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/util/DatabaseK1Manager.java)
+that implements your K1Manager but stores the k1 in database.
+
+## UserLnurlAuthKey
+
+I added a domain object
+named [UserLnurlAuthKey](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/domain/user/UserLnurlAuthKey.java)
+linked to the user. It has a `linkingKey` and a `k1` fields.
+
+## Implements LnurlAuthPairingService
+
+I re-used my user service to
+implement [LnurlAuthPairingService](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java)
+
+[pairK1WithLinkingKey](https://github.com/royllo/explorer/blob/0b4446ef1335f26d4bbc5d0e75460d1e998f51af/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java#L121C20-L121C40)
+If the linking key is not already in our database, I create a new user, and it's UserLnurlAuthKey object else I only
+update the k1.
+
+[findPairedLinkingKeyByK1](https://github.com/royllo/explorer/blob/0b4446ef1335f26d4bbc5d0e75460d1e998f51af/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java#L146C33-L146C57)
+Looking at UserLnurlAuthKey, I search by k1 and returns the linking key if found.
+
+## Implements UserDetailsService
+
+I re-used my user service to
+implement [UserDetailsService](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java#L146C33-L146C57)
+
+[loadUserByUsername](https://github.com/royllo/explorer/blob/0b4446ef1335f26d4bbc5d0e75460d1e998f51af/backend/explorer-core/autoconfigure/src/main/java/org/royllo/explorer/core/service/user/UserServiceImplementation.java#L160C24-L160C42)
+I search a user by its linking key (I saw you search by username in your example but i may let the user change its
+username).
+
+## lnurlAuthFactory
+
+i copied on what you and change the url everytime to nrgok https url:
+[lnurlAuthFactory](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/servers/explorer
+-web/src/main/java/org/royllo/explorer/web/configuration/OtherLnurlAuthConfiguration.java)
+
+## securityConfiguration
+
+I did, I think, the same thing as
+you: [SecurityConfiguration](https://github.com/royllo/explorer/blob/461-user-asset-data-management/backend/servers/explorer-web/src/main/java/org/royllo/explorer/web/configuration/SecurityConfiguration.java)
\ No newline at end of file
diff --git a/justfile b/justfile
index 5dbbde664..475879195 100644
--- a/justfile
+++ b/justfile
@@ -26,6 +26,9 @@ run_web_backend:
run_web_frontend:
npm run --prefix backend/servers/explorer-web build && npm run --prefix backend/servers/explorer-web watch
+run_ngrok:
+ ngrok http 8080
+
# ======================================================================================================================
# Release
start_release:
diff --git a/pom.xml b/pom.xml
index b9ff234b3..89d515693 100644
--- a/pom.xml
+++ b/pom.xml
@@ -93,6 +93,8 @@
3.3.0
3.2.0
+ 0.12.0
+
0.50
1.9.10
3.13.3