Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
57b0e73768 | |||
45161953db | |||
8326e3b92c | |||
ea4648ac92 | |||
1fe351e209 | |||
49ec70ab68 | |||
b8f10703b0 | |||
858ead987c | |||
1591ef77d6 | |||
cce6197e7a | |||
01a356a09d | |||
98b67aba4f | |||
2685bed581 | |||
7276615298 | |||
baa5d39dd4 | |||
4cd9caeacb | |||
feaf965859 | |||
dcb5e222e7 | |||
b60d965dec | |||
c1e1a9e462 | |||
63bad8b0ce | |||
7b7e8e5d8f | |||
2035d168ea | |||
bc7128f75a | |||
23d73f87d7 | |||
e6cc1f6975 | |||
68fcd99f63 | |||
562d5e8eba | |||
c8f8b6efd6 | |||
f16557f64c | |||
be419ef5b9 | |||
7cb8137130 | |||
dc46c44535 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,6 +14,7 @@ replay_pid*
|
||||
.idea
|
||||
cmake-build-*/
|
||||
*.iws
|
||||
target/
|
||||
work/
|
||||
out/
|
||||
build/
|
||||
|
28
Dockerfile
28
Dockerfile
@ -1,11 +1,29 @@
|
||||
# Stage 1: Building
|
||||
FROM maven:3.8.5-openjdk-17-slim AS builder
|
||||
WORKDIR /app
|
||||
COPY pom.xml .
|
||||
COPY src ./src
|
||||
RUN mvn package
|
||||
|
||||
FROM openjdk:11-ea-17-jre-slim
|
||||
# Set the work dir inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the POM file to the work dir
|
||||
COPY pom.xml .
|
||||
|
||||
# Copy src to the work dir
|
||||
COPY src ./src
|
||||
|
||||
# Run Maven to clean and package the app
|
||||
RUN mvn clean package -T12
|
||||
|
||||
# Stage 2: Running
|
||||
FROM openjdk:17.0.1-jdk-slim
|
||||
|
||||
# Set the work dir inside the container
|
||||
WORKDIR /usr/local/app
|
||||
|
||||
# Copy the compiled JAR file from the builder stage to the work dir
|
||||
COPY --from=builder /app/target/LicenseServer.jar .
|
||||
|
||||
# Expose the port
|
||||
EXPOSE 7500
|
||||
|
||||
# Set the command to run the app when the container starts
|
||||
CMD ["java", "-jar", "LicenseServer.jar"]
|
79
Example/pom.xml
Normal file
79
Example/pom.xml
Normal file
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>me.braydon</groupId>
|
||||
<artifactId>Example</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.28</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Gson -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Oshi -->
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.4.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- OkHttp -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.11.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
210
Example/src/main/java/me/braydon/example/LicenseExample.java
Normal file
210
Example/src/main/java/me/braydon/example/LicenseExample.java
Normal file
@ -0,0 +1,210 @@
|
||||
package me.braydon.example;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import okhttp3.*;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.ComputerSystem;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An example of how to interact
|
||||
* with the license server. This
|
||||
* can be conveniently used in
|
||||
* any project by simply copying
|
||||
* the class into your project.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://git.rainnny.club/Rainnny/LicenseServer">License Server</a>
|
||||
*/
|
||||
public final class LicenseExample {
|
||||
/**
|
||||
* The endpoint to check licenses at.
|
||||
*/
|
||||
private static final String CHECK_ENDPOINT = "http://localhost:7500/check";
|
||||
|
||||
/**
|
||||
* The {@link Gson} instance to use.
|
||||
*/
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.serializeNulls()
|
||||
.create();
|
||||
|
||||
/**
|
||||
* Check the license with the given
|
||||
* key for the given product.
|
||||
*
|
||||
* @param key the key to check
|
||||
* @param product the product the key belongs to
|
||||
* @return the license response
|
||||
* @see LicenseResponse for response
|
||||
*/
|
||||
@NonNull
|
||||
public static LicenseResponse check(@NonNull String key, @NonNull String product) {
|
||||
String hardwareId = getHardwareId(); // Get the hardware id of the machine
|
||||
|
||||
// Build the json body
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("key", key);
|
||||
body.put("product", product);
|
||||
body.put("hwid", hardwareId);
|
||||
String bodyJson = GSON.toJson(body); // The json body
|
||||
|
||||
OkHttpClient client = new OkHttpClient(); // Create a new http client
|
||||
MediaType mediaType = MediaType.parse("application/json"); // Ensure the media type is json
|
||||
RequestBody requestBody = RequestBody.create(bodyJson, mediaType); // Build the request body
|
||||
Request request = new Request.Builder()
|
||||
.url(CHECK_ENDPOINT)
|
||||
.post(requestBody)
|
||||
.build(); // Build the POST request
|
||||
|
||||
Response response = null; // The response of the request
|
||||
int responseCode = -1; // The response code of the request
|
||||
try { // Attempt to execute the request
|
||||
response = client.newCall(request).execute();
|
||||
responseCode = response.code();
|
||||
|
||||
// If the response is successful, we can parse the response
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody responseBody = response.body();
|
||||
assert responseBody != null; // We don't want the response body being null
|
||||
|
||||
JsonObject json = GSON.fromJson(responseBody.string(), JsonObject.class); // Parse the json
|
||||
JsonElement description = json.get("description");
|
||||
JsonElement ownerSnowflake = json.get("ownerSnowflake");
|
||||
JsonElement ownerName = json.get("ownerName");
|
||||
JsonElement duration = json.get("duration");
|
||||
|
||||
// Return the license response
|
||||
return new LicenseResponse(200, null,
|
||||
description.isJsonNull() ? null : description.getAsString(),
|
||||
ownerSnowflake.isJsonNull() ? -1 : ownerSnowflake.getAsLong(),
|
||||
ownerName.isJsonNull() ? null : ownerName.getAsString(),
|
||||
duration.isJsonNull() ? -1 : duration.getAsLong()
|
||||
);
|
||||
} else {
|
||||
ResponseBody errorBody = response.body(); // Get the error body
|
||||
if (errorBody != null) { // If we have an error body, we can parse it
|
||||
String errorResponse = errorBody.string();
|
||||
JsonObject jsonError = GSON.fromJson(errorResponse, JsonObject.class);
|
||||
JsonElement errorMessage = jsonError.get("error");
|
||||
if (!errorMessage.isJsonNull()) { // We have an error message, return it
|
||||
return new LicenseResponse(responseCode, errorMessage.getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
// Close the response if it's open
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
// Return an unknown error
|
||||
return new LicenseResponse(responseCode, "An unknown error occurred");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique hardware
|
||||
* identifier of this machine.
|
||||
*
|
||||
* @return the hardware id
|
||||
*/
|
||||
@NonNull
|
||||
private static String getHardwareId() {
|
||||
SystemInfo systemInfo = new SystemInfo();
|
||||
OperatingSystem operatingSystem = systemInfo.getOperatingSystem();
|
||||
HardwareAbstractionLayer hardwareAbstractionLayer = systemInfo.getHardware();
|
||||
CentralProcessor centralProcessor = hardwareAbstractionLayer.getProcessor();
|
||||
ComputerSystem computerSystem = hardwareAbstractionLayer.getComputerSystem();
|
||||
|
||||
// Retrieve necessary hardware information
|
||||
String vendor = operatingSystem.getManufacturer();
|
||||
String processorSerialNumber = computerSystem.getSerialNumber();
|
||||
String uuid = computerSystem.getHardwareUUID();
|
||||
String processorIdentifier = centralProcessor.getProcessorIdentifier().getIdentifier();
|
||||
int processors = centralProcessor.getLogicalProcessorCount();
|
||||
|
||||
// Generate a unique hardware id using the retrieved information
|
||||
return String.format("%08x", vendor.hashCode()) + "-"
|
||||
+ String.format("%08x", processorSerialNumber.hashCode()) + "-"
|
||||
+ String.format("%08x", uuid.hashCode()) + "-"
|
||||
+ String.format("%08x", processorIdentifier.hashCode()) + "-" + processors;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public static class LicenseResponse {
|
||||
/**
|
||||
* The status code of the response.
|
||||
*/
|
||||
private final long status;
|
||||
|
||||
/**
|
||||
* The error in the response, null if none.
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* The description of the license, present if valid.
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* The Discord snowflake of the license owner, present
|
||||
* if valid and there is an owner.
|
||||
*/
|
||||
private long ownerSnowflake;
|
||||
|
||||
/**
|
||||
* The Discord name of the license owner, present
|
||||
* if valid and there is an owner.
|
||||
*/
|
||||
private String ownerName;
|
||||
|
||||
/**
|
||||
* The duration of the license, present if valid.
|
||||
* <p>
|
||||
* If -1, the license will be permanent.
|
||||
* </p>
|
||||
*/
|
||||
private long duration;
|
||||
|
||||
public LicenseResponse(long status, @NonNull String error) {
|
||||
this.status = status;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the license is valid.
|
||||
*
|
||||
* @return true if valid, otherwise false
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return status == 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the license is permanent.
|
||||
*
|
||||
* @return true if permanent, otherwise false
|
||||
*/
|
||||
public boolean isPermanent() {
|
||||
return duration == -1;
|
||||
}
|
||||
}
|
||||
}
|
30
Example/src/main/java/me/braydon/example/Main.java
Normal file
30
Example/src/main/java/me/braydon/example/Main.java
Normal file
@ -0,0 +1,30 @@
|
||||
package me.braydon.example;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
public final class Main {
|
||||
public static void main(String[] args) {
|
||||
LicenseExample.LicenseResponse response = LicenseExample.check("XXXX-XXXX-XXXX-XXXX", "Example");
|
||||
if (!response.isValid()) { // License isn't valid
|
||||
System.err.println("Invalid license: " + response.getError());
|
||||
return;
|
||||
}
|
||||
// License is valid
|
||||
System.out.println("License is valid!");
|
||||
if (response.getOwnerName() != null) {
|
||||
System.out.println("Welcome " + response.getOwnerName() + "!");
|
||||
}
|
||||
if (response.getDescription() != null) {
|
||||
System.out.println("Description: " + response.getDescription()); // License description
|
||||
}
|
||||
if (response.isPermanent()) { // License is permanent
|
||||
System.out.println("Your license is permanent");
|
||||
} else { // License has a duration
|
||||
long durationSeconds = TimeUnit.SECONDS.toMillis(response.getDuration()); // The duration in seconds
|
||||
System.out.println("Your license will expire in " + durationSeconds + " seconds");
|
||||
}
|
||||
}
|
||||
}
|
8
docker-compose.yml
Normal file
8
docker-compose.yml
Normal file
@ -0,0 +1,8 @@
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
image: git.rainnny.club/rainnny/licenseserver:latest
|
||||
volumes:
|
||||
- ./data:/usr/local/app
|
||||
ports:
|
||||
- "7500:7500"
|
24
pom.xml
24
pom.xml
@ -12,7 +12,7 @@
|
||||
|
||||
<groupId>me.braydon</groupId>
|
||||
<artifactId>LicenseServer</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.0.1</version>
|
||||
<description>A simple open-source licensing server for your products.</description>
|
||||
|
||||
<properties>
|
||||
@ -36,6 +36,13 @@
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>build-info</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
@ -45,10 +52,23 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.26</version>
|
||||
<version>1.18.28</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Discord JDA -->
|
||||
<dependency>
|
||||
<groupId>net.dv8tion</groupId>
|
||||
<artifactId>JDA</artifactId>
|
||||
<version>5.0.0-beta.9</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>club.minnced</groupId>
|
||||
<artifactId>opus-java</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- BCrypt -->
|
||||
<dependency>
|
||||
<groupId>org.mindrot</groupId>
|
||||
|
@ -2,9 +2,11 @@ package me.braydon.license;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@ -17,7 +19,7 @@ import java.util.Objects;
|
||||
* @author Braydon
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@Slf4j
|
||||
@Slf4j(topic = "License Server")
|
||||
public class LicenseServer {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.serializeNulls()
|
||||
@ -36,4 +38,10 @@ public class LicenseServer {
|
||||
log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config
|
||||
SpringApplication.run(LicenseServer.class, args); // Load the application
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void onInitialize() {
|
||||
// Log a randomly generated salt
|
||||
log.info("Generated a random salt: {} (This is only for you to copy and paste for config)", BCrypt.gensalt());
|
||||
}
|
||||
}
|
||||
|
86
src/main/java/me/braydon/license/common/IPUtils.java
Normal file
86
src/main/java/me/braydon/license/common/IPUtils.java
Normal file
@ -0,0 +1,86 @@
|
||||
package me.braydon.license.common;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class IPUtils {
|
||||
/**
|
||||
* The regex expression for validating IPv4 addresses.
|
||||
*/
|
||||
public static final String IPV4_REGEX = "^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$";
|
||||
|
||||
/**
|
||||
* The regex expression for validating IPv6 addresses.
|
||||
*/
|
||||
public static final String IPV6_REGEX = "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?$";
|
||||
|
||||
private static final String[] IP_HEADERS = new String[] {
|
||||
"CF-Connecting-IP",
|
||||
"X-Forwarded-For"
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the real IP from the given request.
|
||||
*
|
||||
* @param request the request
|
||||
* @return the real IP
|
||||
*/
|
||||
@NonNull
|
||||
public static String getRealIp(@NonNull HttpServletRequest request) {
|
||||
String ip = request.getRemoteAddr();
|
||||
for (String headerName : IP_HEADERS) {
|
||||
String header = request.getHeader(headerName);
|
||||
if (header == null) {
|
||||
continue;
|
||||
}
|
||||
if (!header.contains(",")) { // Handle single IP
|
||||
ip = header;
|
||||
break;
|
||||
}
|
||||
// Handle multiple IPs
|
||||
String[] ips = header.split(",");
|
||||
for (String ipHeader : ips) {
|
||||
ip = ipHeader;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IP type of the given input.
|
||||
*
|
||||
* @param input the input
|
||||
* @return the IP type, -1 if invalid
|
||||
*/
|
||||
public static int getIpType(@NonNull String input) {
|
||||
return isIpV4(input) ? 4 : isIpV6(input) ? 6 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given input is
|
||||
* a valid IPv4 address.
|
||||
*
|
||||
* @param input the input
|
||||
* @return true if IPv4, otherwise false
|
||||
*/
|
||||
public static boolean isIpV4(@NonNull String input) {
|
||||
return input.matches(IPV4_REGEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given input is
|
||||
* a valid IPv6 address.
|
||||
*
|
||||
* @param input the input
|
||||
* @return true if IPv6, otherwise false
|
||||
*/
|
||||
public static boolean isIpV6(@NonNull String input) {
|
||||
return input.matches(IPV6_REGEX);
|
||||
}
|
||||
}
|
23
src/main/java/me/braydon/license/common/MiscUtils.java
Normal file
23
src/main/java/me/braydon/license/common/MiscUtils.java
Normal file
@ -0,0 +1,23 @@
|
||||
package me.braydon.license.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class MiscUtils {
|
||||
/**
|
||||
* Obfuscate the given key.
|
||||
*
|
||||
* @param rawKey the key to obfuscate
|
||||
* @return the obfuscated key
|
||||
*/
|
||||
@NonNull
|
||||
public static String obfuscateKey(@NonNull String rawKey) {
|
||||
int length = 9; // The amount of chars to show
|
||||
String key = rawKey.substring(0, length);
|
||||
return key + "*".repeat(rawKey.length() - length);
|
||||
}
|
||||
}
|
25
src/main/java/me/braydon/license/common/TimeUtils.java
Normal file
25
src/main/java/me/braydon/license/common/TimeUtils.java
Normal file
@ -0,0 +1,25 @@
|
||||
package me.braydon.license.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class TimeUtils {
|
||||
private static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
|
||||
|
||||
/**
|
||||
* Get the current date time.
|
||||
*
|
||||
* @return the current date time
|
||||
*/
|
||||
@NonNull
|
||||
public static String dateTime() {
|
||||
return DATE_TIME_FORMAT.format(new Date());
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ import com.google.gson.JsonObject;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.license.LicenseServer;
|
||||
import me.braydon.license.common.IPUtils;
|
||||
import me.braydon.license.dto.LicenseDTO;
|
||||
import me.braydon.license.exception.APIException;
|
||||
import me.braydon.license.model.License;
|
||||
import me.braydon.license.service.LicenseService;
|
||||
@ -40,11 +42,11 @@ public final class LicenseController {
|
||||
* @see License for license
|
||||
* @see ResponseEntity for response entity
|
||||
*/
|
||||
@GetMapping("/check")
|
||||
@PostMapping("/check")
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> check(@NonNull HttpServletRequest request, @RequestBody @NonNull String body) {
|
||||
try { // Attempt to check the license
|
||||
String ip = request.getRemoteAddr(); // The IP of the requester
|
||||
String ip = IPUtils.getRealIp(request); // The IP of the requester
|
||||
|
||||
JsonObject jsonObject = LicenseServer.GSON.fromJson(body, JsonObject.class);
|
||||
JsonElement key = jsonObject.get("key"); // Get the key
|
||||
@ -55,14 +57,25 @@ public final class LicenseController {
|
||||
if (key.isJsonNull() || product.isJsonNull() || hwid.isJsonNull()) {
|
||||
throw new APIException(HttpStatus.BAD_REQUEST, "Invalid request body");
|
||||
}
|
||||
// Ensure the IP is valid
|
||||
if (IPUtils.getIpType(ip) == -1) {
|
||||
throw new APIException(HttpStatus.BAD_REQUEST, "Invalid IP address");
|
||||
}
|
||||
|
||||
// Check the license
|
||||
service.check(
|
||||
License license = service.check(
|
||||
key.getAsString(),
|
||||
product.getAsString(),
|
||||
ip,
|
||||
hwid.getAsString()
|
||||
);
|
||||
return ResponseEntity.ok().build(); // Return OK
|
||||
// Return OK with the license DTO
|
||||
return ResponseEntity.ok(new LicenseDTO(
|
||||
license.getDescription(),
|
||||
license.getOwnerSnowflake(),
|
||||
license.getOwnerName(),
|
||||
license.getDuration()
|
||||
));
|
||||
} catch (APIException ex) { // Handle the exception
|
||||
return ResponseEntity.status(ex.getStatus())
|
||||
.body(Map.of("error", ex.getLocalizedMessage()));
|
||||
|
43
src/main/java/me/braydon/license/dto/LicenseDTO.java
Normal file
43
src/main/java/me/braydon/license/dto/LicenseDTO.java
Normal file
@ -0,0 +1,43 @@
|
||||
package me.braydon.license.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import me.braydon.license.model.License;
|
||||
|
||||
/**
|
||||
* A data transfer object for a {@link License}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public class LicenseDTO {
|
||||
/**
|
||||
* The optional description of this license.
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* The Discord snowflake of the owner of this license.
|
||||
* <p>
|
||||
* If this is -1, the license is not owned by anyone.
|
||||
* </p>
|
||||
*/
|
||||
private long ownerSnowflake;
|
||||
|
||||
/**
|
||||
* The Discord name of the owner of this license.
|
||||
* <p>
|
||||
* If this is null, the license is not owned by anyone.
|
||||
* </p>
|
||||
*/
|
||||
private String ownerName;
|
||||
|
||||
/**
|
||||
* The duration that this licensee is valid for.
|
||||
* <p>
|
||||
* If -1, the license will be permanent.
|
||||
* </p>
|
||||
*/
|
||||
private long duration;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package me.braydon.license.exception;
|
||||
|
||||
import me.braydon.license.model.License;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* This exception is raised when
|
||||
* a {@link License} has been used
|
||||
* but is expired.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public class LicenseExpiredException extends APIException {
|
||||
public LicenseExpiredException() {
|
||||
super(HttpStatus.BAD_REQUEST, "License has expired");
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ import lombok.ToString;
|
||||
import me.braydon.license.exception.APIException;
|
||||
import me.braydon.license.exception.LicenseHwidLimitExceededException;
|
||||
import me.braydon.license.exception.LicenseIpLimitExceededException;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@ -39,6 +38,22 @@ public class License {
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* The Discord snowflake of the owner of this license.
|
||||
* <p>
|
||||
* If this is -1, the license is not owned by anyone.
|
||||
* </p>
|
||||
*/
|
||||
private long ownerSnowflake;
|
||||
|
||||
/**
|
||||
* The Discord name of the owner of this license.
|
||||
* <p>
|
||||
* If this is null, the license is not owned by anyone.
|
||||
* </p>
|
||||
*/
|
||||
private String ownerName;
|
||||
|
||||
/**
|
||||
* The amount of uses this license has.
|
||||
*/
|
||||
@ -64,6 +79,14 @@ public class License {
|
||||
*/
|
||||
private int hwidLimit;
|
||||
|
||||
/**
|
||||
* The duration that this licensee is valid for.
|
||||
* <p>
|
||||
* If -1, the license will be permanent.
|
||||
* </p>
|
||||
*/
|
||||
private long duration;
|
||||
|
||||
/**
|
||||
* The {@link Date} this license was last used.
|
||||
*/
|
||||
@ -74,16 +97,41 @@ public class License {
|
||||
*/
|
||||
@NonNull private Date created;
|
||||
|
||||
/**
|
||||
* Check if this license has expired.
|
||||
* <p>
|
||||
* If this license has no
|
||||
* expiration, this will
|
||||
* always return false.
|
||||
* </p>
|
||||
*
|
||||
* @return true if expired, otherwise false
|
||||
*/
|
||||
public boolean hasExpired() {
|
||||
// License is permanent, not expired
|
||||
if (isPermanent()) {
|
||||
return false;
|
||||
}
|
||||
// Check if the license has expired
|
||||
return System.currentTimeMillis() - created.getTime() >= duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this license has no expiration.
|
||||
*
|
||||
* @return true if permanent, otherwise false
|
||||
*/
|
||||
public boolean isPermanent() {
|
||||
return duration == -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when this license is used.
|
||||
*
|
||||
* @param ip the ip used
|
||||
* @param ipSalt the IP salt to use
|
||||
* @param hwid the hardware id used
|
||||
* @param hashedIp the hashed ip used
|
||||
* @param hwid the hardware id used
|
||||
*/
|
||||
public void use(@NonNull String ip, @NonNull String ipSalt, @NonNull String hwid) throws APIException {
|
||||
String hashedIp = BCrypt.hashpw(ip, ipSalt); // Hash the IP
|
||||
|
||||
public void use(@NonNull String hashedIp, @NonNull String hwid) throws APIException {
|
||||
// IP limit has been exceeded
|
||||
if (!ips.contains(hashedIp) && ips.size() >= ipLimit) {
|
||||
throw new LicenseIpLimitExceededException();
|
||||
|
141
src/main/java/me/braydon/license/service/DiscordService.java
Normal file
141
src/main/java/me/braydon/license/service/DiscordService.java
Normal file
@ -0,0 +1,141 @@
|
||||
package me.braydon.license.service;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.braydon.license.common.TimeUtils;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.info.BuildProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Service
|
||||
@Slf4j(topic = "Discord")
|
||||
public final class DiscordService {
|
||||
/**
|
||||
* The version of this Springboot application.
|
||||
*/
|
||||
@NonNull private final String applicationVersion;
|
||||
/**
|
||||
* The name of this Springboot application.
|
||||
*/
|
||||
@Value("${spring.application.name}")
|
||||
@NonNull private String applicationName;
|
||||
/**
|
||||
* The token to the Discord bot.
|
||||
*/
|
||||
@Value("${discord.token}")
|
||||
@NonNull private String token;
|
||||
|
||||
/**
|
||||
* The channel ID to log to.
|
||||
*/
|
||||
@Value("${discord.logs.channel}")
|
||||
private long logsChannel;
|
||||
|
||||
/**
|
||||
* Should used licenses be logged?
|
||||
*/
|
||||
@Value("${discord.logs.uses}") @Getter
|
||||
private boolean logUses;
|
||||
|
||||
/**
|
||||
* Should we log if an expired license was used?
|
||||
*/
|
||||
@Value("${discord.logs.expired}") @Getter
|
||||
private boolean logExpired;
|
||||
|
||||
/**
|
||||
* Should IP limited licenses be logged when used?
|
||||
*/
|
||||
@Value("${discord.logs.expired}") @Getter
|
||||
private boolean logIpLimitExceeded;
|
||||
|
||||
/**
|
||||
* Should HWID limited licenses be logged when used?
|
||||
*/
|
||||
@Value("${discord.logs.expired}") @Getter
|
||||
private boolean logHwidLimitExceeded;
|
||||
|
||||
/**
|
||||
* The {@link JDA} instance of the bot.
|
||||
*/
|
||||
private JDA jda;
|
||||
|
||||
@Autowired
|
||||
public DiscordService(@NonNull BuildProperties buildProperties) {
|
||||
this.applicationVersion = buildProperties.getVersion();
|
||||
}
|
||||
|
||||
@PostConstruct @SneakyThrows
|
||||
public void onInitialize() {
|
||||
// No token was provided
|
||||
if (token.trim().isEmpty()) {
|
||||
log.info("Not using Discord, no token provided");
|
||||
return;
|
||||
}
|
||||
// Initialize the bot
|
||||
long before = System.currentTimeMillis();
|
||||
log.info("Logging in..."); // Log that we're logging in
|
||||
jda = JDABuilder.createDefault(token)
|
||||
.enableIntents(
|
||||
GatewayIntent.GUILD_MEMBERS
|
||||
).setStatus(OnlineStatus.DO_NOT_DISTURB)
|
||||
.setActivity(Activity.watching("your licenses"))
|
||||
.build();
|
||||
jda.awaitReady(); // Await JDA to be ready
|
||||
|
||||
// Log that we're logged in
|
||||
log.info("Logged into {} in {}ms",
|
||||
jda.getSelfUser().getAsTag(), System.currentTimeMillis() - before
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log to the logs channel
|
||||
* with the given embed.
|
||||
*
|
||||
* @param embed the embed to send
|
||||
* @see TextChannel for channel
|
||||
* @see EmbedBuilder for embed
|
||||
*/
|
||||
public void sendLog(@NonNull EmbedBuilder embed) {
|
||||
// JDA must be ready to send logs
|
||||
if (!isReady()) {
|
||||
return;
|
||||
}
|
||||
// Not enabled
|
||||
if (logsChannel <= 0L) {
|
||||
return;
|
||||
}
|
||||
TextChannel textChannel = jda.getTextChannelById(logsChannel); // Get the logs channel
|
||||
if (textChannel == null) { // We must have a logs channel
|
||||
throw new IllegalArgumentException("Log channel %s wasn't found".formatted(logsChannel));
|
||||
}
|
||||
// Send the log
|
||||
textChannel.sendMessageEmbeds(embed.setFooter("%s v%s - %s".formatted(
|
||||
applicationName, applicationVersion, TimeUtils.dateTime()
|
||||
)).build()).queue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the bot is ready.
|
||||
*
|
||||
* @return true if ready, otherwise false
|
||||
*/
|
||||
public boolean isReady() {
|
||||
return jda != null && (jda.getStatus() == JDA.Status.CONNECTED);
|
||||
}
|
||||
}
|
@ -1,17 +1,18 @@
|
||||
package me.braydon.license.service;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.braydon.license.exception.APIException;
|
||||
import me.braydon.license.exception.LicenseNotFoundException;
|
||||
import me.braydon.license.common.MiscUtils;
|
||||
import me.braydon.license.exception.*;
|
||||
import me.braydon.license.model.License;
|
||||
import me.braydon.license.repository.LicenseRepository;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
@ -22,13 +23,18 @@ import java.util.Optional;
|
||||
* @author Braydon
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@Slf4j(topic = "Licenses")
|
||||
public final class LicenseService {
|
||||
/**
|
||||
* The {@link LicenseRepository} to use.
|
||||
*/
|
||||
@NonNull private final LicenseRepository repository;
|
||||
|
||||
/**
|
||||
* The {@link DiscordService} to use for logging.
|
||||
*/
|
||||
@NonNull private final DiscordService discordService;
|
||||
|
||||
/**
|
||||
* The salt to use for hashing license keys.
|
||||
*/
|
||||
@ -42,44 +48,39 @@ public final class LicenseService {
|
||||
@NonNull private String ipsSalt;
|
||||
|
||||
@Autowired
|
||||
public LicenseService(@NonNull LicenseRepository repository) {
|
||||
public LicenseService(@NonNull LicenseRepository repository, @NonNull DiscordService discordService) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void onInitialize() {
|
||||
// TODO: remove this and make it either
|
||||
// a test, or a route to gen a license
|
||||
System.out.println("SALT - " + BCrypt.gensalt());
|
||||
// String key = RandomUtils.generateLicenseKey();
|
||||
// log.info(create(key,
|
||||
// "CloudSpigot",
|
||||
// "Testing " + Math.random(), Integer.MAX_VALUE, Integer.MAX_VALUE).toString());
|
||||
// System.out.println("key = " + key);
|
||||
this.discordService = discordService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new license key.
|
||||
*
|
||||
* @param key the key of the license
|
||||
* @param product the product the license is for
|
||||
* @param description the optional description of the license
|
||||
* @param ipLimit the IP limit of the license
|
||||
* @param hwidLimit the HWID limit of the license
|
||||
* @param key the key of the license
|
||||
* @param product the product the license is for
|
||||
* @param description the optional description of the license
|
||||
* @param ownerSnowflake the optional owner snowflake of the license
|
||||
* @param ownerName the optional owner name of the license
|
||||
* @param ipLimit the IP limit of the license
|
||||
* @param hwidLimit the HWID limit of the license
|
||||
* @param duration the duration of the license, -1 for permanent
|
||||
* @return the created license
|
||||
* @see License for license
|
||||
*/
|
||||
public License create(@NonNull String key, @NonNull String product,
|
||||
String description, int ipLimit, int hwidLimit) {
|
||||
public License create(@NonNull String key, @NonNull String product, String description, long ownerSnowflake,
|
||||
String ownerName, int ipLimit, int hwidLimit, long duration) {
|
||||
// Create the new license
|
||||
License license = new License();
|
||||
license.setKey(BCrypt.hashpw(key, licensesSalt)); // Hash the key
|
||||
license.setProduct(product); // Use the given product
|
||||
license.setDescription(description); // Use the given description, if any
|
||||
license.setOwnerSnowflake(ownerSnowflake);
|
||||
license.setOwnerName(ownerName);
|
||||
license.setIps(new HashSet<>());
|
||||
license.setHwids(new HashSet<>());
|
||||
license.setIpLimit(ipLimit); // Use the given IP limit
|
||||
license.setHwidLimit(hwidLimit); // Use the given HWID limit
|
||||
license.setDuration(duration);
|
||||
license.setCreated(new Date());
|
||||
repository.insert(license); // Insert the newly created license
|
||||
return license;
|
||||
@ -92,19 +93,123 @@ public final class LicenseService {
|
||||
* @param product the product of the license
|
||||
* @param ip the ip using the license
|
||||
* @param hwid the hwid using the license
|
||||
* @return the checked license
|
||||
* @throws APIException if there was an error checking the license
|
||||
* @see License for license
|
||||
*/
|
||||
public void check(@NonNull String key, @NonNull String product,
|
||||
@NonNull String ip, @NonNull String hwid) throws APIException {
|
||||
@NonNull
|
||||
public License check(@NonNull String key, @NonNull String product, @NonNull String ip,
|
||||
@NonNull String hwid) throws APIException {
|
||||
Optional<License> optionalLicense = repository.getLicense(BCrypt.hashpw(key, licensesSalt), product); // Get the license
|
||||
if (optionalLicense.isEmpty()) { // License key not found
|
||||
log.error("License key {} for product {} not found", key, product); // Log the error
|
||||
throw new LicenseNotFoundException();
|
||||
}
|
||||
License license = optionalLicense.get(); // The license found
|
||||
license.use(ip, ipsSalt, hwid); // Use the license
|
||||
repository.save(license); // Save the used license
|
||||
log.info("License key {} for product {} was used by {} ({})", key, product, ip, hwid);
|
||||
String hashedIp = BCrypt.hashpw(ip, ipsSalt); // Hash the IP
|
||||
|
||||
// Log the license being used, if enabled
|
||||
if (discordService.isLogUses()) {
|
||||
// god i hate sending discord embeds, it's so big and ugly :(
|
||||
boolean newIp = !license.getIps().contains(hashedIp); // Is the IP new?
|
||||
boolean newHwid = !license.getHwids().contains(hwid); // Is the HWID new?
|
||||
|
||||
// Constructing tags
|
||||
StringBuilder tags = new StringBuilder();
|
||||
if (newIp) { // New IP
|
||||
tags.append("New IP");
|
||||
}
|
||||
if (newHwid) { // New HWID
|
||||
if (tags.length() > 0) {
|
||||
tags.append(" & ");
|
||||
}
|
||||
tags.append("HWID");
|
||||
}
|
||||
long expirationDate = (license.getCreated().getTime() + license.getDuration()) / 1000L;
|
||||
discordService.sendLog(new EmbedBuilder()
|
||||
.setColor(Color.BLUE)
|
||||
.setTitle("License Used" + (!tags.isEmpty() ? " (" + tags + ")" : ""))
|
||||
.addField("License",
|
||||
"`" + MiscUtils.obfuscateKey(key) + "`",
|
||||
true
|
||||
)
|
||||
.addField("Product",
|
||||
license.getProduct(),
|
||||
true
|
||||
)
|
||||
.addField("Description",
|
||||
license.getDescription(),
|
||||
true
|
||||
)
|
||||
.addField("Owner ID",
|
||||
license.getOwnerSnowflake() <= 0L ? "N/A" : String.valueOf(license.getOwnerSnowflake()),
|
||||
true
|
||||
)
|
||||
.addField("Owner Name",
|
||||
license.getOwnerName() == null ? "N/A" : license.getOwnerName(),
|
||||
true
|
||||
)
|
||||
.addField("Expires",
|
||||
license.isPermanent() ? "Never" : "<t:" + expirationDate + ":R>",
|
||||
true
|
||||
)
|
||||
.addField("IP",
|
||||
ip,
|
||||
true
|
||||
)
|
||||
.addField("HWID",
|
||||
"```" + hwid + "```",
|
||||
false
|
||||
)
|
||||
.addField("IPs",
|
||||
license.getIps().size() + "/" + license.getIpLimit(),
|
||||
true
|
||||
)
|
||||
.addField("HWIDs",
|
||||
license.getHwids().size() + "/" + license.getHwidLimit(),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
// The license has expired
|
||||
if (license.hasExpired()) {
|
||||
// Log the expired license
|
||||
if (discordService.isLogExpired()) {
|
||||
discordService.sendLog(new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License Expired")
|
||||
.setDescription("License `%s` is expired".formatted(MiscUtils.obfuscateKey(key)))
|
||||
);
|
||||
}
|
||||
throw new LicenseExpiredException();
|
||||
}
|
||||
try {
|
||||
license.use(hashedIp, hwid); // Use the license
|
||||
repository.save(license); // Save the used license
|
||||
log.info("License key '{}' for product '{}' was used by {} (HWID: {})", key, product, ip, hwid);
|
||||
return license;
|
||||
} catch (APIException ex) {
|
||||
// Log that the license has reached it's IP limit
|
||||
if (ex instanceof LicenseIpLimitExceededException && discordService.isLogIpLimitExceeded()) {
|
||||
discordService.sendLog(new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License IP Limit Reached")
|
||||
.setDescription("License `%s` has reached it's IP limit: **%s**".formatted(
|
||||
MiscUtils.obfuscateKey(key),
|
||||
license.getIpLimit()
|
||||
))
|
||||
);
|
||||
} else if (ex instanceof LicenseHwidLimitExceededException && discordService.isLogHwidLimitExceeded()) {
|
||||
discordService.sendLog(new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License HWID Limit Reached")
|
||||
.setDescription("License `%s` has reached it's HWID limit: **%s**".formatted(
|
||||
MiscUtils.obfuscateKey(key),
|
||||
license.getHwidLimit()
|
||||
))
|
||||
);
|
||||
}
|
||||
throw ex; // Rethrow to handle where this method was invoked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,16 @@ salts:
|
||||
licenses: "$2a$10$/nQyzQDMkCf97ZlJLLWa3O"
|
||||
ips: "$2a$10$Xus.AHTCas97Ofx0tFs85O"
|
||||
|
||||
# Discord Bot Configuration
|
||||
discord:
|
||||
token: ""
|
||||
logs:
|
||||
channel: 0 # The channel ID to log to, leave as 0 to disable
|
||||
uses: true # Should used licenses be logged?
|
||||
expired: true # Should we log if an expired license was used?
|
||||
ipLimitExceeded: true # Should IP limited licenses be logged when used?
|
||||
hwidLimitExceeded: true # Should HWID limited licenses be logged when used?
|
||||
|
||||
# Log Configuration
|
||||
logging:
|
||||
file:
|
||||
@ -16,14 +26,16 @@ logging:
|
||||
|
||||
# Spring Configuration
|
||||
spring:
|
||||
application:
|
||||
name: "License Server"
|
||||
|
||||
# Database Configuration
|
||||
data:
|
||||
# MongoDB - This is used to store persistent data
|
||||
mongodb:
|
||||
uri: "mongodb://licenseServer:p4$$w0rd@127.0.0.1:27017/licenseServer?authSource=admin"
|
||||
auto-index-creation: true # Automatically create collection indexes
|
||||
|
||||
# Ignore
|
||||
application:
|
||||
name: "License Server"
|
||||
# Banner
|
||||
banner:
|
||||
location: "classpath:banner.txt"
|
@ -3,6 +3,6 @@
|
||||
| |__| / _/ -_) ' \(_-</ -_) \__ \/ -_) '_\ V / -_) '_|
|
||||
|____|_\__\___|_||_/__/\___| |___/\___|_| \_/\___|_|
|
||||
|
||||
| API Version - v${application.version}
|
||||
| Application Version - v${application.version}
|
||||
| Spring Version - ${spring-boot.formatted-version}
|
||||
___________________________________________________________
|
Reference in New Issue
Block a user