Initial commit

This commit is contained in:
2025-09-24 04:08:55 +00:00
commit 85456ebf3a
99 changed files with 13332 additions and 0 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

37
.gitignore vendored Normal file
View File

@@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

70
build.gradle Normal file
View File

@@ -0,0 +1,70 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.3'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.psg.ldsysinfo'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// Spring Boot
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-logging'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// Database
implementation 'mysql:mysql-connector-java:8.0.29'
// Jackson + Date/Time
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
// Validation & JAXB (Jakarta & Legacy)
implementation 'jakarta.xml.bind:jakarta.xml.bind-api:3.0.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'jakarta.validation:jakarta.validation-api:3.0.0'
// Version comparison
implementation 'org.apache.maven:maven-artifact:3.9.6'
// Twilio
implementation 'com.twilio.sdk:twilio:9.14.1'
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// Testing
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
// ✅ Ensure resources (like springboot.p12) are available when using `bootRun`
bootRun {
sourceResources sourceSets.main
}

23
cert3.pem Normal file
View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDxTCCA0ugAwIBAgISBhz4/YQD6EMezB5EopCan11yMAoGCCqGSM49BAMDMDIx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
NjAeFw0yNTA1MDUwNTUzMzNaFw0yNTA4MDMwNTUzMzJaMBcxFTATBgNVBAMMDCou
cHNnLm5ldC5hdTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJpLCOwSblm5ACILhTab
CcJ33gbdrFPQuFhvyinKWQ9DH+YCD630JX23hpKjsz69H1TrpMgIhQHkEgZEj5Ci
qmqySQmqgKIVoKQKWlGA/UAyDkTwyQP1RruTZnvVAD6/HaOCAj0wggI5MA4GA1Ud
DwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
AQH/BAIwADAdBgNVHQ4EFgQUYL0bYOh8hJwrdPX1U2xU2Q/2938wHwYDVR0jBBgw
FoAUkydGmAOpUWiOmNbEQkjbI79YlNIwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUF
BzABhhVodHRwOi8vZTYuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9l
Ni5pLmxlbmNyLm9yZy8wFwYDVR0RBBAwDoIMKi5wc2cubmV0LmF1MBMGA1UdIAQM
MAowCAYGZ4EMAQIBMC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9lNi5jLmxlbmNy
Lm9yZy8xMTUuY3JsMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHYADeHyMCvTDcFA
YhIJ6lUu/Ed0fLHX6TDvDkIetH5OqjQAAAGWnzdNfwAABAMARzBFAiEAwLijBIRC
CJOfwuxVFhgUfXd7uCK30uOTYP+RfC9nhr0CIGfHOYsvZHe08QbHafkOzPN6tyRY
L4RKXMAv6JtS+XsAAHUAzPsPaoVxCWX+lZtTzumyfCLphVwNl422qX5UwP5MDbAA
AAGWnzdNlAAABAMARjBEAiArgRGaKdgItURxSHkcHCrx/DZ5pnyUMxLB5Q41BhCk
awIgG407b550tzhr+mvkOI+99cLXAdxabP7Fi2KMhq9Fsv0wCgYIKoZIzj0EAwMD
aAAwZQIxALHo2SGs6E34Q19nu7BFXQq0ccqE2S7Kb8EkxOeTn4Y4xa3M+owUJubH
wxi1cNOeYgIwdx43f6qVT+1BzT3sAkgFMVyDcc8iAaJIDmLv3hxR8WwIwG4h9K9F
QbLJbCSxXSo5
-----END CERTIFICATE-----

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDmDCCAx2gAwIBAgISA3cx6pdudinoyNPQQ6tDERv0MAoGCCqGSM49BAMDMDIx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
NjAeFw0yNTAzMDYxNDE2NTJaFw0yNTA2MDQxNDE2NTFaMBcxFTATBgNVBAMMDCou
cHNnLm5ldC5hdTB2MBAGByqGSM49AgEGBSuBBAAiA2IABOG0jKcfM4uf8gGG4/k4
bCVyivf6p6euAk3gSu3evvIs3YjJK8zlMYZM/YogO+GQxBipRHJ75/j35umpfGMM
TdeIHX1b8BkuxaJM1+OPbEhMcEweRIVihfqCQMi8ogofrqOCAg8wggILMA4GA1Ud
DwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
AQH/BAIwADAdBgNVHQ4EFgQUNG8CwKH0p/+/ityNnLV5qe1XUMowHwYDVR0jBBgw
FoAUkydGmAOpUWiOmNbEQkjbI79YlNIwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUF
BzABhhVodHRwOi8vZTYuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9l
Ni5pLmxlbmNyLm9yZy8wFwYDVR0RBBAwDoIMKi5wc2cubmV0LmF1MBMGA1UdIAQM
MAowCAYGZ4EMAQIBMIIBBQYKKwYBBAHWeQIEAgSB9gSB8wDxAHcAouMK5EXvva2b
fjjtR2d3U9eCW4SU1yteGyzEuVCkR+cAAAGVbAaKcAAABAMASDBGAiEAo1M9qabG
y9ULFqX2f0aSFUEbq4/mxgYMGI8CyRHIMysCIQDKiqjf02C7djRbUXFgY8i525D4
Dk//piv8x3BVnwFOKAB2AMz7D2qFcQll/pWbU87psnwi6YVcDZeNtql+VMD+TA2w
AAABlWwGktIAAAQDAEcwRQIhAMfWVBUyDceYf5CIOYCWb4jBHw0cDsmpzV2rnLlW
+lYGAiBbBBf1oAiL74wvRoYYHITbUBkJzeiKsdd4QL+g9ITgXDAKBggqhkjOPQQD
AwNpADBmAjEA+2N7kXIuSeR+q48M3ZAddqQ1yKk9epGr/vjn8kAN6kHB18CYZ1IZ
fSS1qzj+GdJ4AjEAmaUjf/N+08nte4SlEo9bl0hszoPZCEEDaorzzZUZZg0rlwtU
TmnpNMRKPWKDDuNZ
-----END CERTIFICATE-----

View File

@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEVzCCAj+gAwIBAgIRALBXPpFzlydw27SHyzpFKzgwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCRTYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZ8Z5G
h/ghcWCoJuuj+rnq2h25EqfUJtlRFLFhfHWWvyILOR/VvtEKRqotPEoJhC6+QJVV
6RlAN2Z17TJOdwRJ+HB7wxjnzvdxEP6sdNgA1O1tHHMWMxCcOrLqbGL0vbijgfgw
gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD
ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSTJ0aYA6lRaI6Y1sRCSNsj
v1iU0jAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB
AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g
BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu
Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAfYt7SiA1sgWGCIpunk46r4AExIRc
MxkKgUhNlrrv1B21hOaXN/5miE+LOTbrcmU/M9yvC6MVY730GNFoL8IhJ8j8vrOL
pMY22OP6baS1k9YMrtDTlwJHoGby04ThTUeBDksS9RiuHvicZqBedQdIF65pZuhp
eDcGBcLiYasQr/EO5gxxtLyTmgsHSOVSBcFOn9lgv7LECPq9i7mfH3mpxgrRKSxH
pOoZ0KXMcB+hHuvlklHntvcI0mMMQ0mhYj6qtMFStkF1RpCG3IPdIwpVCQqu8GV7
s8ubknRzs+3C/Bm19RFOoiPpDkwvyNfvmQ14XkyqqKK5oZ8zhD32kFRQkxa8uZSu
h4aTImFxknu39waBxIRXE4jKxlAmQc4QjFZoq1KmQqQg0J/1JF8RlFvJas1VcjLv
YlvUB2t6npO6oQjB3l+PNf0DpQH7iUx3Wz5AjQCi6L25FjyE06q6BZ/QlmtYdl/8
ZYao4SRqPEs/6cAiF+Qf5zg2UkaWtDphl1LKMuTNLotvsX99HP69V2faNyegodQ0
LyTApr/vT01YPE46vNsDLgK+4cL6TrzC/a4WcmF5SRJ938zrv/duJHLXQIku5v0+
EwOy59Hdm0PT/Er/84dDV0CSjdR/2XuZM3kpysSKLgD1cKiDA+IRguODCxfO9cyY
Ig46v9mFmBvyH04=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,48 @@
-----BEGIN CERTIFICATE-----
MIIDmDCCAx2gAwIBAgISA3cx6pdudinoyNPQQ6tDERv0MAoGCCqGSM49BAMDMDIx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
NjAeFw0yNTAzMDYxNDE2NTJaFw0yNTA2MDQxNDE2NTFaMBcxFTATBgNVBAMMDCou
cHNnLm5ldC5hdTB2MBAGByqGSM49AgEGBSuBBAAiA2IABOG0jKcfM4uf8gGG4/k4
bCVyivf6p6euAk3gSu3evvIs3YjJK8zlMYZM/YogO+GQxBipRHJ75/j35umpfGMM
TdeIHX1b8BkuxaJM1+OPbEhMcEweRIVihfqCQMi8ogofrqOCAg8wggILMA4GA1Ud
DwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
AQH/BAIwADAdBgNVHQ4EFgQUNG8CwKH0p/+/ityNnLV5qe1XUMowHwYDVR0jBBgw
FoAUkydGmAOpUWiOmNbEQkjbI79YlNIwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUF
BzABhhVodHRwOi8vZTYuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9l
Ni5pLmxlbmNyLm9yZy8wFwYDVR0RBBAwDoIMKi5wc2cubmV0LmF1MBMGA1UdIAQM
MAowCAYGZ4EMAQIBMIIBBQYKKwYBBAHWeQIEAgSB9gSB8wDxAHcAouMK5EXvva2b
fjjtR2d3U9eCW4SU1yteGyzEuVCkR+cAAAGVbAaKcAAABAMASDBGAiEAo1M9qabG
y9ULFqX2f0aSFUEbq4/mxgYMGI8CyRHIMysCIQDKiqjf02C7djRbUXFgY8i525D4
Dk//piv8x3BVnwFOKAB2AMz7D2qFcQll/pWbU87psnwi6YVcDZeNtql+VMD+TA2w
AAABlWwGktIAAAQDAEcwRQIhAMfWVBUyDceYf5CIOYCWb4jBHw0cDsmpzV2rnLlW
+lYGAiBbBBf1oAiL74wvRoYYHITbUBkJzeiKsdd4QL+g9ITgXDAKBggqhkjOPQQD
AwNpADBmAjEA+2N7kXIuSeR+q48M3ZAddqQ1yKk9epGr/vjn8kAN6kHB18CYZ1IZ
fSS1qzj+GdJ4AjEAmaUjf/N+08nte4SlEo9bl0hszoPZCEEDaorzzZUZZg0rlwtU
TmnpNMRKPWKDDuNZ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEVzCCAj+gAwIBAgIRALBXPpFzlydw27SHyzpFKzgwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCRTYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZ8Z5G
h/ghcWCoJuuj+rnq2h25EqfUJtlRFLFhfHWWvyILOR/VvtEKRqotPEoJhC6+QJVV
6RlAN2Z17TJOdwRJ+HB7wxjnzvdxEP6sdNgA1O1tHHMWMxCcOrLqbGL0vbijgfgw
gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD
ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSTJ0aYA6lRaI6Y1sRCSNsj
v1iU0jAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB
AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g
BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu
Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAfYt7SiA1sgWGCIpunk46r4AExIRc
MxkKgUhNlrrv1B21hOaXN/5miE+LOTbrcmU/M9yvC6MVY730GNFoL8IhJ8j8vrOL
pMY22OP6baS1k9YMrtDTlwJHoGby04ThTUeBDksS9RiuHvicZqBedQdIF65pZuhp
eDcGBcLiYasQr/EO5gxxtLyTmgsHSOVSBcFOn9lgv7LECPq9i7mfH3mpxgrRKSxH
pOoZ0KXMcB+hHuvlklHntvcI0mMMQ0mhYj6qtMFStkF1RpCG3IPdIwpVCQqu8GV7
s8ubknRzs+3C/Bm19RFOoiPpDkwvyNfvmQ14XkyqqKK5oZ8zhD32kFRQkxa8uZSu
h4aTImFxknu39waBxIRXE4jKxlAmQc4QjFZoq1KmQqQg0J/1JF8RlFvJas1VcjLv
YlvUB2t6npO6oQjB3l+PNf0DpQH7iUx3Wz5AjQCi6L25FjyE06q6BZ/QlmtYdl/8
ZYao4SRqPEs/6cAiF+Qf5zg2UkaWtDphl1LKMuTNLotvsX99HP69V2faNyegodQ0
LyTApr/vT01YPE46vNsDLgK+4cL6TrzC/a4WcmF5SRJ938zrv/duJHLXQIku5v0+
EwOy59Hdm0PT/Er/84dDV0CSjdR/2XuZM3kpysSKLgD1cKiDA+IRguODCxfO9cyY
Ig46v9mFmBvyH04=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDNruDvCgZHOnvf2iE7
tALMNMgDbRaW4VZTLIOuIAddLOG9V5UzpAHbXuy9ldvsFlKhZANiAAThtIynHzOL
n/IBhuP5OGwlcor3+qenrgJN4Ert3r7yLN2IySvM5TGGTP2KIDvhkMQYqURye+f4
9+bpqXxjDE3XiB19W/AZLsWiTNfjj2xITHBMHkSFYoX6gkDIvKIKH64=
-----END PRIVATE KEY-----

26
chain3.pem Normal file
View File

@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEVzCCAj+gAwIBAgIRALBXPpFzlydw27SHyzpFKzgwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCRTYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZ8Z5G
h/ghcWCoJuuj+rnq2h25EqfUJtlRFLFhfHWWvyILOR/VvtEKRqotPEoJhC6+QJVV
6RlAN2Z17TJOdwRJ+HB7wxjnzvdxEP6sdNgA1O1tHHMWMxCcOrLqbGL0vbijgfgw
gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD
ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSTJ0aYA6lRaI6Y1sRCSNsj
v1iU0jAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB
AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g
BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu
Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAfYt7SiA1sgWGCIpunk46r4AExIRc
MxkKgUhNlrrv1B21hOaXN/5miE+LOTbrcmU/M9yvC6MVY730GNFoL8IhJ8j8vrOL
pMY22OP6baS1k9YMrtDTlwJHoGby04ThTUeBDksS9RiuHvicZqBedQdIF65pZuhp
eDcGBcLiYasQr/EO5gxxtLyTmgsHSOVSBcFOn9lgv7LECPq9i7mfH3mpxgrRKSxH
pOoZ0KXMcB+hHuvlklHntvcI0mMMQ0mhYj6qtMFStkF1RpCG3IPdIwpVCQqu8GV7
s8ubknRzs+3C/Bm19RFOoiPpDkwvyNfvmQ14XkyqqKK5oZ8zhD32kFRQkxa8uZSu
h4aTImFxknu39waBxIRXE4jKxlAmQc4QjFZoq1KmQqQg0J/1JF8RlFvJas1VcjLv
YlvUB2t6npO6oQjB3l+PNf0DpQH7iUx3Wz5AjQCi6L25FjyE06q6BZ/QlmtYdl/8
ZYao4SRqPEs/6cAiF+Qf5zg2UkaWtDphl1LKMuTNLotvsX99HP69V2faNyegodQ0
LyTApr/vT01YPE46vNsDLgK+4cL6TrzC/a4WcmF5SRJ938zrv/duJHLXQIku5v0+
EwOy59Hdm0PT/Er/84dDV0CSjdR/2XuZM3kpysSKLgD1cKiDA+IRguODCxfO9cyY
Ig46v9mFmBvyH04=
-----END CERTIFICATE-----

112
cve-sync.log Normal file
View File

@@ -0,0 +1,112 @@
2025-04-22T08:01:13.987630700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-22T16:00:00.026814600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-23T00:00:00.004035100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-23T08:00:00.007768800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-23T15:59:57.763430 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-24T00:00:00.016624800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-24T08:01:08.636887200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-24T15:59:57.765749700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-25T00:01:08.092828800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-25T08:01:06.857710500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-25T15:59:57.731443100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-26T00:00:00.009948400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-26T08:00:00.006638600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-26T16:01:08.623954 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-27T00:00:00.011085300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-27T08:01:11.972091100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-27T16:01:28.937311200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-28T00:00:00.008333500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-28T07:59:56.208168300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-28T16:01:32.912372 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-29T00:01:16.831892100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-29T08:00:00.009434600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-29T16:00:00.015109300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-30T00:00:00.010769200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-30T08:00:00.013797700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-04-30T16:00:00.030601100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-01T00:00:00.009068200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-01T07:59:58.572069400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-01T16:01:19.091394300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-02T00:00:00.014060300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-02T08:01:13.207239700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-02T16:00:00.013796700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-03T00:00:00.011198800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-03T07:59:58.264769400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-03T16:01:22.373358400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-04T00:00:00.004206300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-04T08:01:13.972592100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-04T16:00:00.007804200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-05T00:01:19.060217500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-05T08:01:17.844706800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-05T16:00:00.022030200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-06T00:01:17.022330500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-06T08:01:16.912824 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-06T16:01:18.366174500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-07T00:01:19.032636900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-07T08:00:00.032290600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-07T15:59:58.216182700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-08T00:00:00.015498300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-08T08:01:16.749082900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-08T16:00:00.008310600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-09T00:01:17.028476100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-09T08:01:17.895912100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-09T16:00:00.004853500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-10T00:01:20.875962100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-10T08:01:16.991245100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-10T16:01:18.649239800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-11T00:01:17.603202100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-11T08:01:16.431230500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-11T16:01:17.459599700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-12T00:01:17.619409600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-12T08:01:16.031570900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-12T16:01:18.305099800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-13T00:00:00.011872100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-13T08:01:17.321038700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-13T16:00:00.016488600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-14T00:00:00.013132800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-14T07:59:58.286809100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-14T16:00:00.017691100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-15T00:01:17.549725700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-15T07:59:58.224836 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-15T16:01:17.142352500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-16T00:01:15.618005100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-16T08:01:15.076920900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-16T16:00:00.005493400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-17T00:01:15.777671300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-17T08:00:00.015485600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-17T15:59:58.217869200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-18T00:00:00.004178500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-18T08:01:16.545760700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-18T16:01:15.940931100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-19T00:00:00.007971400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-19T08:00:00.007816900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-19T15:59:58.215865600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-20T00:00:00.016141700 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-20T07:59:58.232237500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-20T16:00:00.012581200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-21T00:01:15.109241800 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-21T08:01:17.164403100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-21T15:59:58.179601100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-22T00:01:15.630576100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-22T07:59:58.194641300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-22T16:01:16.704448200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-23T00:01:15.748653200 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-23T08:01:16.353470600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-23T15:59:58.209189500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-24T16:01:21.527591900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-25T00:01:18.771297300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-25T08:00:00.013248900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-25T15:59:58.178269400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-26T00:00:00.009849900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-26T08:00:00.015168300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-26T15:59:58.190632400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-27T00:00:00.014399300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-05-27T08:01:23.904986900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-06-10T16:00:00.065091900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-06-11T00:00:00.232468600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-06-11T08:00:00.038478600 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-06-11T16:00:00.169154300 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-06-12T00:00:00.218915900 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-06-12T08:00:00.234811500 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-06-12T16:00:00.234497100 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null
2025-06-13T00:00:00.235901400 — ? Exception during CVE sync: Cannot invoke "String.indexOf(int)" because "value" is null

49
fullchain3.pem Normal file
View File

@@ -0,0 +1,49 @@
-----BEGIN CERTIFICATE-----
MIIDxTCCA0ugAwIBAgISBhz4/YQD6EMezB5EopCan11yMAoGCCqGSM49BAMDMDIx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
NjAeFw0yNTA1MDUwNTUzMzNaFw0yNTA4MDMwNTUzMzJaMBcxFTATBgNVBAMMDCou
cHNnLm5ldC5hdTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJpLCOwSblm5ACILhTab
CcJ33gbdrFPQuFhvyinKWQ9DH+YCD630JX23hpKjsz69H1TrpMgIhQHkEgZEj5Ci
qmqySQmqgKIVoKQKWlGA/UAyDkTwyQP1RruTZnvVAD6/HaOCAj0wggI5MA4GA1Ud
DwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
AQH/BAIwADAdBgNVHQ4EFgQUYL0bYOh8hJwrdPX1U2xU2Q/2938wHwYDVR0jBBgw
FoAUkydGmAOpUWiOmNbEQkjbI79YlNIwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUF
BzABhhVodHRwOi8vZTYuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9l
Ni5pLmxlbmNyLm9yZy8wFwYDVR0RBBAwDoIMKi5wc2cubmV0LmF1MBMGA1UdIAQM
MAowCAYGZ4EMAQIBMC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9lNi5jLmxlbmNy
Lm9yZy8xMTUuY3JsMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHYADeHyMCvTDcFA
YhIJ6lUu/Ed0fLHX6TDvDkIetH5OqjQAAAGWnzdNfwAABAMARzBFAiEAwLijBIRC
CJOfwuxVFhgUfXd7uCK30uOTYP+RfC9nhr0CIGfHOYsvZHe08QbHafkOzPN6tyRY
L4RKXMAv6JtS+XsAAHUAzPsPaoVxCWX+lZtTzumyfCLphVwNl422qX5UwP5MDbAA
AAGWnzdNlAAABAMARjBEAiArgRGaKdgItURxSHkcHCrx/DZ5pnyUMxLB5Q41BhCk
awIgG407b550tzhr+mvkOI+99cLXAdxabP7Fi2KMhq9Fsv0wCgYIKoZIzj0EAwMD
aAAwZQIxALHo2SGs6E34Q19nu7BFXQq0ccqE2S7Kb8EkxOeTn4Y4xa3M+owUJubH
wxi1cNOeYgIwdx43f6qVT+1BzT3sAkgFMVyDcc8iAaJIDmLv3hxR8WwIwG4h9K9F
QbLJbCSxXSo5
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEVzCCAj+gAwIBAgIRALBXPpFzlydw27SHyzpFKzgwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCRTYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZ8Z5G
h/ghcWCoJuuj+rnq2h25EqfUJtlRFLFhfHWWvyILOR/VvtEKRqotPEoJhC6+QJVV
6RlAN2Z17TJOdwRJ+HB7wxjnzvdxEP6sdNgA1O1tHHMWMxCcOrLqbGL0vbijgfgw
gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD
ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSTJ0aYA6lRaI6Y1sRCSNsj
v1iU0jAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB
AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g
BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu
Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAfYt7SiA1sgWGCIpunk46r4AExIRc
MxkKgUhNlrrv1B21hOaXN/5miE+LOTbrcmU/M9yvC6MVY730GNFoL8IhJ8j8vrOL
pMY22OP6baS1k9YMrtDTlwJHoGby04ThTUeBDksS9RiuHvicZqBedQdIF65pZuhp
eDcGBcLiYasQr/EO5gxxtLyTmgsHSOVSBcFOn9lgv7LECPq9i7mfH3mpxgrRKSxH
pOoZ0KXMcB+hHuvlklHntvcI0mMMQ0mhYj6qtMFStkF1RpCG3IPdIwpVCQqu8GV7
s8ubknRzs+3C/Bm19RFOoiPpDkwvyNfvmQ14XkyqqKK5oZ8zhD32kFRQkxa8uZSu
h4aTImFxknu39waBxIRXE4jKxlAmQc4QjFZoq1KmQqQg0J/1JF8RlFvJas1VcjLv
YlvUB2t6npO6oQjB3l+PNf0DpQH7iUx3Wz5AjQCi6L25FjyE06q6BZ/QlmtYdl/8
ZYao4SRqPEs/6cAiF+Qf5zg2UkaWtDphl1LKMuTNLotvsX99HP69V2faNyegodQ0
LyTApr/vT01YPE46vNsDLgK+4cL6TrzC/a4WcmF5SRJ938zrv/duJHLXQIku5v0+
EwOy59Hdm0PT/Er/84dDV0CSjdR/2XuZM3kpysSKLgD1cKiDA+IRguODCxfO9cyY
Ig46v9mFmBvyH04=
-----END CERTIFICATE-----

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
gradlew vendored Normal file
View File

@@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

702
package-lock.json generated Normal file
View File

@@ -0,0 +1,702 @@
{
"name": "ld-sysinfo-server",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ld-sysinfo-server",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"axios": "^1.9.0",
"cheerio": "^1.0.0",
"dotenv": "^16.5.0",
"mysql2": "^3.14.1"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/cheerio": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
"integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
"license": "MIT",
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.1.0",
"encoding-sniffer": "^0.2.0",
"htmlparser2": "^9.1.0",
"parse5": "^7.1.2",
"parse5-htmlparser2-tree-adapter": "^7.0.0",
"parse5-parser-stream": "^7.1.2",
"undici": "^6.19.5",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
"node": ">=18.17"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": {
"version": "16.5.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/encoding-sniffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
"integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
"license": "MIT",
"dependencies": {
"iconv-lite": "^0.6.3",
"whatwg-encoding": "^3.1.1"
},
"funding": {
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"license": "MIT",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/htmlparser2": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
"integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.1.0",
"entities": "^4.5.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz",
"integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==",
"license": "Apache-2.0"
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/lru.min": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mysql2": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz",
"integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"long": "^5.2.1",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/named-placeholders": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
"license": "MIT",
"dependencies": {
"lru-cache": "^7.14.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-parser-stream": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
"license": "MIT",
"dependencies": {
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
},
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/undici": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
}
}
}

19
package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "ld-sysinfo-server",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"description": "",
"dependencies": {
"axios": "^1.9.0",
"cheerio": "^1.0.0",
"dotenv": "^16.5.0",
"mysql2": "^3.14.1"
}
}

6
privkey3.pem Normal file
View File

@@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCxBRzMYQJh/ETrQptv
cBEuQ/9PlGZTVbCYt6AEj5ritog9aew0Uxu+rOh46yn9YV+hZANiAASaSwjsEm5Z
uQAiC4U2mwnCd94G3axT0LhYb8opylkPQx/mAg+t9CV9t4aSo7M+vR9U66TICIUB
5BIGRI+QoqpqskkJqoCiFaCkClpRgP1AMg5E8MkD9Ua7k2Z71QA+vx0=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1 @@
CVE-1999-0057

7
scripts/.env.local Normal file
View File

@@ -0,0 +1,7 @@
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=6DRR4xWvHBhSqLGtIOEKa7gHjKnX33Hf
DB_NAME=db_ld-spring-backend
NVD_API_KEY=42b4f093-e8c4-4110-a7d1-6ab2ba6234aa
NVD_MAX_RANGE_DAYS=30

View File

@@ -0,0 +1 @@
2001-08-20T00:00:00.000Z

48
scripts/cve-sync.log Normal file
View File

@@ -0,0 +1,48 @@
[27 May 2025, 12:53:07 pm] 🚀 📡 CVE sync launched in background.
[27 May 2025, 12:53:08 pm] 🧪 Getting CVEs from the last 7 of days
[27 May 2025, 12:53:08 pm] 🧪 Getting CVEs from the last 7 of days
[27 May 2025, 12:53:08 pm] 🚀 CVE sync started
[27 May 2025, 12:53:08 pm] 🔄 Initializing script...
[27 May 2025, 12:53:08 pm] 📍 Launching script
[27 May 2025, 12:53:08 pm] 📅 Starting CVE sync from 2025-05-20T04:53:08.063Z to 2025-05-27T04:53:08.063Z
[27 May 2025, 12:53:08 pm] 📡 Fetching modified CVEs from 20 May 2025 to 27 May 2025...
[27 May 2025, 12:53:08 pm] 🚀 CVE sync started
[27 May 2025, 12:53:08 pm] 🔄 Initializing script...
[27 May 2025, 12:53:08 pm] 📍 Launching script
[27 May 2025, 12:53:08 pm] 📅 Starting CVE sync from 2025-05-20T04:53:08.063Z to 2025-05-27T04:53:08.063Z
[27 May 2025, 12:53:08 pm] 📡 Fetching modified CVEs from 20 May 2025 to 27 May 2025...
[27 May 2025, 12:53:10 pm] 📄 Page 1 — Processing 2000 CVEs from index 0 of ~2212
[27 May 2025, 12:53:10 pm] 📄 Page 1 — Processing 2000 CVEs from index 0 of ~2212
[27 May 2025, 12:53:27 pm] 📄 Page 2 — Processing 212 CVEs from index 2000 of ~2212
[27 May 2025, 12:53:27 pm] 📄 Page 2 — Processing 212 CVEs from index 2000 of ~2212
[27 May 2025, 12:53:33 pm] ✅ CVE import complete!
[27 May 2025, 12:53:33 pm] ✅ CVE import complete!
[27 May 2025, 12:53:33 pm] ✅ fetchCVE.js finished with exit code: 0
[27 May 2025, 12:54:46 pm] 🚀 📡 CVE sync launched in background.
[27 May 2025, 12:54:46 pm] 🧪 Getting CVEs from the last 30 of days
[27 May 2025, 12:54:46 pm] 🧪 Getting CVEs from the last 30 of days
[27 May 2025, 12:54:46 pm] 🚀 CVE sync started
[27 May 2025, 12:54:46 pm] 🔄 Initializing script...
[27 May 2025, 12:54:46 pm] 📍 Launching script
[27 May 2025, 12:54:46 pm] 📅 Starting CVE sync from 2025-04-27T04:54:46.513Z to 2025-05-27T04:54:46.513Z
[27 May 2025, 12:54:46 pm] 📡 Fetching modified CVEs from 27 Apr 2025 to 27 May 2025...
[27 May 2025, 12:54:46 pm] 🚀 CVE sync started
[27 May 2025, 12:54:46 pm] 🔄 Initializing script...
[27 May 2025, 12:54:46 pm] 📍 Launching script
[27 May 2025, 12:54:46 pm] 📅 Starting CVE sync from 2025-04-27T04:54:46.513Z to 2025-05-27T04:54:46.513Z
[27 May 2025, 12:54:46 pm] 📡 Fetching modified CVEs from 27 Apr 2025 to 27 May 2025...
[27 May 2025, 12:54:49 pm] 📄 Page 1 — Processing 2000 CVEs from index 0 of ~10257
[27 May 2025, 12:54:49 pm] 📄 Page 1 — Processing 2000 CVEs from index 0 of ~10257
[27 May 2025, 12:55:55 pm] 📄 Page 2 — Processing 2000 CVEs from index 2000 of ~10257
[27 May 2025, 12:55:55 pm] 📄 Page 2 — Processing 2000 CVEs from index 2000 of ~10257
[27 May 2025, 12:56:18 pm] 📄 Page 3 — Processing 2000 CVEs from index 4000 of ~10257
[27 May 2025, 12:56:18 pm] 📄 Page 3 — Processing 2000 CVEs from index 4000 of ~10257
[27 May 2025, 12:56:35 pm] 📄 Page 4 — Processing 2000 CVEs from index 6000 of ~10257
[27 May 2025, 12:56:35 pm] 📄 Page 4 — Processing 2000 CVEs from index 6000 of ~10257
[27 May 2025, 12:57:03 pm] 📄 Page 5 — Processing 2000 CVEs from index 8000 of ~10257
[27 May 2025, 12:57:03 pm] 📄 Page 5 — Processing 2000 CVEs from index 8000 of ~10257
[27 May 2025, 12:57:15 pm] 📄 Page 6 — Processing 257 CVEs from index 10000 of ~10257
[27 May 2025, 12:57:15 pm] 📄 Page 6 — Processing 257 CVEs from index 10000 of ~10257
[27 May 2025, 12:57:21 pm] ✅ CVE import complete!
[27 May 2025, 12:57:21 pm] ✅ CVE import complete!
[27 May 2025, 12:57:21 pm] ✅ fetchCVE.js finished with exit code: 0

177
scripts/enrichCVE_MSRC.js Normal file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env node
import axios from 'axios';
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
const DB = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
function formatDate(isoString) {
if (!isoString) return null;
const date = new Date(isoString);
return date.toISOString().slice(0, 19).replace('T', ' ');
}
function getLocalizedText(entry, fallback = '') {
if (!entry || typeof entry !== 'object') return fallback;
// MSRC: sometimes the object directly has "Value"
if ('Value' in entry && typeof entry.Value === 'string') {
return entry.Value;
}
// If it's localized (e.g., { "en-US": { Value: ... } })
if (entry['en-US']?.Value) return entry['en-US'].Value;
// Fallback: first valid value
for (const key in entry) {
if (entry[key]?.Value) return entry[key].Value;
}
return fallback;
}
function extractDescription(notes = [], fallback = '') {
const note = notes.find(n =>
n.Title === 'Description' &&
n.Type === 2 &&
typeof n.Value === 'string' &&
n.Value.trim().length > 0
);
return note?.Value || fallback;
}
function extractAffectedProducts(vuln) {
const fixed = vuln.ProductStatuses?.Fixed ?? [];
const known = vuln.ProductStatuses?.KnownAffected ?? [];
const underInvestigation = vuln.ProductStatuses?.UnderInvestigation ?? [];
return [...new Set([...fixed, ...known, ...underInvestigation])].join(', ');
}
async function fetchCVRFDoc(cvrfId) {
const url = `https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/${cvrfId}`;
try {
const res = await axios.get(url, {
headers: { Accept: 'application/json' },
});
return res.data;
} catch (err) {
console.error(`❌ Failed to fetch CVRF for ${cvrfId}:`, err.message);
return null;
}
}
async function enrichCVE(cve, data) {
const [rows] = await DB.execute(
`SELECT title, severity, description, cvss_score, cvss_vector FROM cves WHERE id = ?`,
[cve]
);
if (rows.length === 0) return;
const existing = rows[0];
const updateFields = {
title: existing.title || data.title,
severity: existing.severity || data.severity,
description: (existing.description?.length ?? 0) < 20 ? data.description : existing.description,
cvss_score: existing.cvss_score ?? data.cvssScore,
cvss_vector: existing.cvss_vector || data.cvssVector,
};
await DB.execute(
`UPDATE cves
SET title = ?, severity = ?, description = ?, cvss_score = ?, cvss_vector = ?
WHERE id = ?`,
[updateFields.title, updateFields.severity, updateFields.description, updateFields.cvss_score, updateFields.cvss_vector, cve]
);
}
async function storeVulnerability(v, published, modified) {
const cve = v.CVE;
console.log(`⏳ Processing ${cve}...`);
const title = getLocalizedText(v.Title, '');
console.debug(`🧪 Raw Title for ${cve}:`, JSON.stringify(v.Title, null, 2));
console.debug(`➡️ Parsed Title: "${title}"`);
const description = extractDescription(v.Notes, title);
const affectedProducts = extractAffectedProducts(v);
const severity = v.Threats?.find(t => t.Type === 'Severity')?.Description?.Value || '';
const exploitability = v.Threats?.find(t => t.Type === 'Exploitability')?.Description?.Value || '';
const cvssSet = v.CVSSScoreSets?.[0];
const cvssScore = cvssSet?.BaseScore || null;
const cvssVector = cvssSet?.Vector || '';
const cweList = Array.isArray(v.CWE) ? v.CWE : [];
await DB.execute(
`INSERT INTO microsoft_cves (cve_id, title, severity, published_date, last_modified_date,
affected_products, description, exploitability, cvss_score, cvss_vector)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
last_modified_date = VALUES(last_modified_date),
affected_products = VALUES(affected_products),
severity = VALUES(severity),
description = VALUES(description),
exploitability = VALUES(exploitability),
cvss_score = VALUES(cvss_score),
cvss_vector = VALUES(cvss_vector)`,
[cve, title, severity, published, modified, affectedProducts, description, exploitability, cvssScore, cvssVector]
);
for (const { ID: cweId, Value: cweName } of cweList) {
if (!cweId) continue;
await DB.execute(
`INSERT IGNORE INTO cwe_entries (cwe_id, cwe_name) VALUES (?, ?)`,
[cweId, cweName || null]
);
await DB.execute(
`INSERT IGNORE INTO cve_cwe (cve_id, cwe_id) VALUES (?, ?)`,
[cve, cweId]
);
}
await enrichCVE(cve, { title, severity, description, cvssScore, cvssVector });
console.log(`✅ Updated ${cve} in master table`);
}
function getPreviousMonthID() {
const now = new Date();
now.setMonth(now.getMonth() - 1);
const year = now.getFullYear();
const month = now.toLocaleString('en-US', { month: 'short' }); // e.g., "Mar"
return `${year}-${month}`;
}
async function enrichPreviousMonth() {
const cvrfId = getPreviousMonthID();
console.log(`📡 Fetching CVEs for ${cvrfId}...`);
const data = await fetchCVRFDoc(cvrfId);
if (!data) return;
const published = formatDate(data.DocumentTracking?.InitialReleaseDate);
const modified = formatDate(data.DocumentTracking?.CurrentReleaseDate);
const vulns = data.Vulnerability || [];
console.log(`📋 Found ${vulns.length} vulnerabilities.`);
for (const v of vulns) {
await storeVulnerability(v, published, modified);
}
console.log(`🎉 Enrichment complete for ${cvrfId}`);
await DB.end();
}
console.log("🚀 Starting MSRC enrichment of CVEs...");
await enrichPreviousMonth();

208
scripts/fetchCVE.js Normal file
View File

@@ -0,0 +1,208 @@
#!/usr/bin/env node
import fs from 'fs';
import axios from 'axios';
import mysql from 'mysql2/promise';
const logFile = fs.createWriteStream('cve-sync.log', {
flags: 'a',
encoding: 'utf8',
});
function log(msg) {
const now = new Date();
// Generate the locale string with hour12 enabled (e.g. "14 Apr 2025, 08:14:42 AM")
const raw = now.toLocaleString('en-AU', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true,
});
// Regex to convert only the AM/PM to lowercase
const formatted = raw.replace(/\b(AM|PM)\b/, (match) => match.toLowerCase());
const timestamp = `[${formatted}]`;
const line = `${timestamp} ${msg}`;
console.log(line);
logFile.write(`${line}\n`);
}
function formatDate(isoString) {
if (!isoString) return null;
const date = new Date(isoString);
return date.toISOString().slice(0, 19).replace('T', ' ');
}
function formatShortDate(isoString) {
return new Date(isoString).toLocaleDateString('en-AU', {
day: '2-digit',
month: 'short',
year: 'numeric',
}).replace(/\b([A-Z])([a-z]+)\b/, (_, a, b) => a + b); // Capitalize only first letter of month
}
function extractCpeParts(cpe) {
const parts = cpe.split(':');
return {
vendor: parts[3] || null,
product: parts[4] || null,
version: parts[5] || null
};
}
function addDaysToISO(dateISO, days) {
const date = new Date(dateISO);
date.setDate(date.getDate() + days);
return date.toISOString();
}
const DB = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
const BASE_URL = 'https://services.nvd.nist.gov/rest/json/cves/2.0';
const API_KEY = process.env.NVD_API_KEY;
const RESULTS_PER_PAGE = 2000;
let MAX_RANGE_DAYS = Number(process.env.NVD_MAX_RANGE_DAYS || 7);
log(`🧪 Getting CVEs from the last ${MAX_RANGE_DAYS} of days`);
if (MAX_RANGE_DAYS > 120) {
log("⚠️ MAX_RANGE_DAYS exceeds NVD API limit. Defaulting to 120.");
MAX_RANGE_DAYS = 120;
}
if (isNaN(MAX_RANGE_DAYS) || MAX_RANGE_DAYS < 1) {
log("⚠️ Invalid MAX_RANGE_DAYS. Using default of 7.");
MAX_RANGE_DAYS = 7;
}
async function fetchCVEPage(startIndex, startDate, endDate) {
try {
const res = await axios.get(BASE_URL, {
params: {
startIndex,
resultsPerPage: RESULTS_PER_PAGE,
lastModStartDate: startDate,
lastModEndDate: endDate,
},
headers: API_KEY ? { apiKey: API_KEY } : {}
});
return res.data;
} catch (err) {
log(`❌ API error: ${err.response?.status} - ${err.response?.data?.message || err.message}`);
throw err;
}
}
async function processCVE(cveWrapper) {
const cve = cveWrapper.cve;
const cveId = cve.id;
const desc = cve.descriptions.find(d => d.lang === 'en')?.value ?? '';
const published = formatDate(cve.published);
const modified = formatDate(cve.lastModified);
const severity = cve.metrics?.cvssMetricV31?.[0]?.cvssData?.baseSeverity ?? null;
const score = cve.metrics?.cvssMetricV31?.[0]?.cvssData?.baseScore ?? null;
try {
await DB.execute(
`INSERT INTO cves (id, description, published_date, last_modified_date, severity, cvss_score)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE last_modified_date = VALUES(last_modified_date)`,
[cveId, desc, published, modified, severity, score]
);
} catch (err) {
log(`❌ Error inserting CVE ${cveId}: ${err.message}`);
}
const configurations = cve.configurations ?? [];
for (const node of configurations) {
for (const match of node.nodes?.flatMap(n => n.cpeMatch ?? []) ?? []) {
const cpe = match.criteria;
const vulnerable = match.vulnerable ? 1 : 0;
const start = match.versionStartIncluding || match.versionStartExcluding || null;
const end = match.versionEndIncluding || match.versionEndExcluding || null;
const { vendor, product, version } = extractCpeParts(cpe);
try {
await DB.execute(
`INSERT INTO cpe_matches (cve_id, cpe_uri, version_start, version_end, vulnerable, vendor, product, version)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[cveId, cpe, start, end, vulnerable, vendor, product, version]
);
} catch (err) {
log(`⚠️ Error inserting CPE for CVE ${cveId}: ${err.message}`);
}
}
}
}
async function getMostRecentModifiedDateFromDB() {
const [rows] = await DB.query(`SELECT MAX(last_modified_date) AS lastMod FROM cves`);
const lastMod = rows[0]?.lastMod;
return lastMod ? new Date(lastMod).toISOString() : '2020-01-01T00:00:00.000Z';
}
async function importCVEFeed() {
const now = new Date();
const endDate = now.toISOString();
const startDateObj = new Date(now);
startDateObj.setDate(startDateObj.getDate() - MAX_RANGE_DAYS);
const startDate = startDateObj.toISOString();
log(`🚀 CVE sync started`);
log(`🔄 Initializing script...`);
log(`📍 Launching script`);
log(`📅 Starting CVE sync from ${startDate} to ${endDate}`);
const humanStart = formatShortDate(startDate);
const humanEnd = formatShortDate(endDate);
log(`📡 Fetching modified CVEs from ${humanStart} to ${humanEnd}...`);
let startIndex = 0;
let totalResults = Infinity;
let pageCount = 0;
do {
const data = await fetchCVEPage(startIndex, startDate, endDate);
const vulnerabilities = data.vulnerabilities || [];
totalResults = data.totalResults ?? vulnerabilities.length;
if (vulnerabilities.length === 0) {
log(`⚠️ No CVEs returned at index ${startIndex}`);
break;
}
log(`📄 Page ${++pageCount} — Processing ${vulnerabilities.length} CVEs from index ${startIndex} of ~${totalResults}`);
for (const vuln of vulnerabilities) {
await processCVE(vuln);
}
startIndex += RESULTS_PER_PAGE;
await new Promise((r) => setTimeout(r, 6000));
} while (startIndex < totalResults);
log('✅ CVE import complete!');
await DB.end();
logFile.end();
}
importCVEFeed().catch((err) => {
log(`❌ Fatal error during import: ${err.message}`);
logFile.end();
});

457
scripts/fetchCVE_v2.js Normal file
View File

@@ -0,0 +1,457 @@
#!/usr/bin/env node
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
import fs from 'fs';
import axios from 'axios';
import mysql from 'mysql2/promise';
const logFile = fs.createWriteStream('cve-sync.log', {
flags: 'a',
encoding: 'utf8',
});
const RESUME_FILE = '.enrichment_resume';
const DB = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
const BASE_URL = 'https://services.nvd.nist.gov/rest/json/cves/2.0';
const API_KEY = process.env.NVD_API_KEY;
const RESULTS_PER_PAGE = 2000;
let MAX_RANGE_DAYS = Number(process.env.NVD_MAX_RANGE_DAYS || 120);
log(`🧪 Getting CVEs from the last ${MAX_RANGE_DAYS} of days`);
if (MAX_RANGE_DAYS > 120) {
log("⚠️ MAX_RANGE_DAYS exceeds NVD API limit. Defaulting to 120.");
MAX_RANGE_DAYS = 120;
}
if (isNaN(MAX_RANGE_DAYS) || MAX_RANGE_DAYS < 1) {
log("⚠️ Invalid MAX_RANGE_DAYS. Using default of 7.");
MAX_RANGE_DAYS = 7;
}
function saveLastProcessedCVE(cveId) {
fs.writeFileSync(RESUME_FILE, cveId, 'utf8');
}
function loadLastProcessedCVE() {
if (!fs.existsSync(RESUME_FILE)) return null;
return fs.readFileSync(RESUME_FILE, 'utf8');
}
function log(msg) {
const now = new Date();
// Generate the locale string with hour12 enabled (e.g. "14 Apr 2025, 08:14:42 AM")
const raw = now.toLocaleString('en-AU', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true,
});
// Regex to convert only the AM/PM to lowercase
const formatted = raw.replace(/\b(AM|PM)\b/, (match) => match.toLowerCase());
const timestamp = `[${formatted}]`;
const line = `${timestamp} ${msg}`;
console.log(line);
logFile.write(`${line}\n`);
}
function formatDate(isoString) {
if (!isoString) return null;
const date = new Date(isoString);
return date.toISOString().slice(0, 19).replace('T', ' ');
}
function formatShortDate(isoString) {
return new Date(isoString).toLocaleDateString('en-AU', {
day: '2-digit',
month: 'short',
year: 'numeric',
}).replace(/\b([A-Z])([a-z]+)\b/, (_, a, b) => a + b); // Capitalize only first letter of month
}
function extractCpeParts(cpe) {
const parts = cpe.split(':');
return {
vendor: parts[3] || null,
product: parts[4] || null,
version: parts[5] || null
};
}
function addDaysToISO(dateISO, days) {
const date = new Date(dateISO);
date.setDate(date.getDate() + days);
return date.toISOString();
}
async function fetchCVEPage(startIndex, startDate, endDate, extraOptions = {}) {
try {
const res = await axios.get(BASE_URL, {
params: {
pubStartDate: startDate,
pubEndDate: endDate,
startIndex,
resultsPerPage: RESULTS_PER_PAGE,
...extraOptions,
},
headers: API_KEY ? { apiKey: API_KEY } : {}
});
return res.data;
} catch (err) {
log(`❌ API error: ${err.response?.status} - ${err.response?.data?.message || err.message}`);
throw err;
}
}
async function processCVE(cveWrapper) {
const cve = cveWrapper.cve;
const cveId = cve.id;
const title = cve.titles?.find(t => t.lang === 'en')?.title || '';
const desc = cve.descriptions?.find(d => d.lang === 'en')?.value || '';
const published = formatDate(cve.published);
const modified = formatDate(cve.lastModified);
// CVSSv2
const metricV2 = cve.metrics?.cvssMetricV2?.[0];
const severityV2 = metricV2?.cvssData?.baseSeverity || null;
const scoreV2 = metricV2?.cvssData?.baseScore || null;
const vectorV2 = metricV2?.cvssData?.vectorString || '';
// CVSSv3
const metricV3 = cve.metrics?.cvssMetricV31?.[0];
const severityV3 = metricV3?.cvssData?.baseSeverity || null;
const scoreV3 = metricV3?.cvssData?.baseScore || null;
const vectorV3 = metricV3?.cvssData?.vectorString || '';
// CVSSv4
const metricV4 = cve.metrics?.cvssMetricV40?.[0] || cve.metrics?.cvssMetricV4?.[0];
const severityV4 = metricV4?.cvssData?.baseSeverity || null;
const scoreV4 = metricV4?.cvssData?.baseScore || null;
const vectorV4 = metricV4?.cvssData?.vectorString || '';
// CWE IDs
const cweIds = (cve.weaknesses || [])
.flatMap(w => w.description || [])
.filter(desc => desc.lang === 'en')
.map(desc => desc.value)
.join(',');
// References
const references = (cve.references || [])
.map(ref => ref.url)
.join(',');
// Tags
const cveTags = cve.cveMetadata?.cveTags || [];
const hasKev = cveTags.includes('Known_Exploited_Vulnerability');
const hasCertNotes = cveTags.includes('CERT-VN');
const hasCertAlerts = cveTags.includes('US-CERT-TA');
try {
await DB.execute(
`INSERT INTO cves (
id, title, description, published_date, last_modified_date,
severity_v2, cvss_score_v2, cvss_vector_v2,
severity_v3, cvss_score_v3, cvss_vector_v3,
severity_v4, cvss_score_v4, cvss_vector_v4,
cwe_ids, \`references\`, hasKev, hasCertNotes, hasCertAlerts, source
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
last_modified_date = VALUES(last_modified_date),
severity_v2 = IFNULL(severity_v2, VALUES(severity_v2)),
cvss_score_v2 = IFNULL(cvss_score_v2, VALUES(cvss_score_v2)),
cvss_vector_v2 = IFNULL(cvss_vector_v2, VALUES(cvss_vector_v2)),
severity_v3 = IFNULL(severity_v3, VALUES(severity_v3)),
cvss_score_v3 = IFNULL(cvss_score_v3, VALUES(cvss_score_v3)),
cvss_vector_v3 = IFNULL(cvss_vector_v3, VALUES(cvss_vector_v3)),
severity_v4 = IFNULL(severity_v4, VALUES(severity_v4)),
cvss_score_v4 = IFNULL(cvss_score_v4, VALUES(cvss_score_v4)),
cvss_vector_v4 = IFNULL(cvss_vector_v4, VALUES(cvss_vector_v4)),
cwe_ids = IFNULL(cwe_ids, VALUES(cwe_ids)),
\`references\` = IFNULL(\`references\`, VALUES(\`references\`)),
hasKev = VALUES(hasKev),
hasCertNotes = VALUES(hasCertNotes),
hasCertAlerts = VALUES(hasCertAlerts),
source = VALUES(source)
`,
[
cveId, title, desc, published, modified,
severityV2, scoreV2, vectorV2,
severityV3, scoreV3, vectorV3,
severityV4, scoreV4, vectorV4,
cweIds, references, hasKev ? 1 : 0, hasCertNotes ? 1 : 0, hasCertAlerts ? 1 : 0, 'NVD'
]
);
} catch (err) {
log(`❌ Error inserting CVE ${cveId}: ${err.message}`);
}
}
async function getMostRecentModifiedDateFromDB() {
const [rows] = await DB.query(`SELECT MAX(last_modified_date) AS lastMod FROM cves`);
const lastMod = rows[0]?.lastMod;
return lastMod ? new Date(lastMod).toISOString() : '2020-01-01T00:00:00.000Z';
}
async function importCVEFeed() {
const now = new Date();
const endDate = now.toISOString();
const startDateObj = new Date(now);
startDateObj.setDate(startDateObj.getDate() - MAX_RANGE_DAYS);
const startDate = startDateObj.toISOString();
log(`🚀 CVE sync started`);
log(`🔄 Initializing script...`);
log(`📍 Launching script`);
log(`📅 Starting CVE sync from ${startDate} to ${endDate}`);
const humanStart = formatShortDate(startDate);
const humanEnd = formatShortDate(endDate);
log(`📡 Fetching modified CVEs from ${humanStart} to ${humanEnd}...`);
let startIndex = 0;
let totalResults = Infinity;
let pageCount = 0;
do {
const data = await fetchCVEPage(startIndex, startDate, endDate);
const vulnerabilities = data.vulnerabilities || [];
totalResults = data.totalResults ?? vulnerabilities.length;
if (vulnerabilities.length === 0) {
log(`⚠️ No CVEs returned at index ${startIndex}`);
break;
}
log(`📄 Page ${++pageCount} — Processing ${vulnerabilities.length} CVEs from index ${startIndex} of ~${totalResults}`);
for (const vuln of vulnerabilities) {
await processCVE(vuln);
}
startIndex += RESULTS_PER_PAGE;
await new Promise((r) => setTimeout(r, 6000));
} while (startIndex < totalResults);
log('✅ CVE import complete!');
await DB.end();
logFile.end();
}
async function importCVEFeedBackfill() {
const now = new Date();
const resumeFrom = loadLastSyncedDate();
let startFrom = resumeFrom ? new Date(resumeFrom) : now;
const MAX_RANGE_DAYS = 120;
log(resumeFrom
? `🔁 Resuming CVE backfill from ${formatShortDate(startFrom.toISOString())}`
: `⏮️ Starting CVE backfill from today (${formatShortDate(startFrom.toISOString())})`
);
while (true) {
const end = new Date(startFrom);
const start = new Date(startFrom);
start.setDate(start.getDate() - MAX_RANGE_DAYS + 1); // 120-day window
const startISO = start.toISOString();
const endISO = end.toISOString();
const humanRange = `${formatShortDate(startISO)} to ${formatShortDate(endISO)}`;
log(`📡 Fetching published CVEs from ${humanRange}...`);
let startIndex = 0;
let totalResults = Infinity;
let pageCount = 0;
try {
do {
const data = await fetchCVEPage(startIndex, startISO, endISO);
const vulnerabilities = data.vulnerabilities || [];
totalResults = data.totalResults ?? vulnerabilities.length;
if (vulnerabilities.length === 0) {
log(`⚠️ No CVEs returned for ${humanRange} at index ${startIndex}`);
break;
}
log(`📄 Page ${++pageCount}${vulnerabilities.length} CVEs from index ${startIndex}`);
for (const vuln of vulnerabilities) {
await processCVE(vuln);
}
startIndex += RESULTS_PER_PAGE;
await new Promise((r) => setTimeout(r, 6000));
} while (startIndex < totalResults);
// Move the window backward
saveLastSyncedDate(start.toISOString());
startFrom = start;
} catch (err) {
log(`❌ Error during ${humanRange}: ${err.message}`);
break;
}
if (start < new Date('2002-01-01')) {
log(`🛑 Reached earliest supported CVE publication date — halting backfill.`);
break;
}
}
log('✅ CVE backfill complete!');
await DB.end();
logFile.end();
}
async function importCVEEnrichmentFromDB() {
log(`🔍 Starting CVE enrichment from existing database records...`);
const lastProcessed = loadLastProcessedCVE();
if (lastProcessed) {
log(`🔁 Resuming enrichment from after ${lastProcessed}`);
}
const [rows] = await DB.query(`
SELECT id
FROM cves
WHERE severity_v3 IS NULL OR cvss_score_v3 IS NULL OR cvss_vector_v3 IS NULL
ORDER BY id ASC
`);
if (rows.length === 0) {
log(`✅ All CVEs seem enriched! Nothing to do.`);
await DB.end();
logFile.end();
return;
}
let startIndex = 0;
if (lastProcessed) {
const resumeIndex = rows.findIndex(r => r.id === lastProcessed);
if (resumeIndex !== -1 && resumeIndex + 1 < rows.length) {
startIndex = resumeIndex + 1; // Start *after* last processed
}
}
log(`📄 Found ${rows.length} CVEs needing enrichment, starting at index ${startIndex}.`);
for (let i = startIndex; i < rows.length; i++) {
const row = rows[i];
const cveId = row.id;
try {
const res = await axios.get(BASE_URL, {
params: { cveId },
headers: API_KEY ? { apiKey: API_KEY } : {}
});
const vulnerabilities = res.data.vulnerabilities || [];
if (vulnerabilities.length > 0) {
await processCVE(vulnerabilities[0]);
log(`✅ Enriched ${cveId}`);
} else {
log(`⚠️ No data found for ${cveId}`);
}
saveLastProcessedCVE(cveId); // ✨ Save after each successful CVE
await new Promise(r => setTimeout(r, 6000)); // Sleep to respect rate limit
} catch (err) {
log(`❌ Failed to fetch/enrich ${cveId}: ${err.message}`);
}
}
log('✅ CVE enrichment batch complete!');
fs.unlinkSync(RESUME_FILE); // ✨ Cleanup resume file if done
await DB.end();
logFile.end();
}
async function importCVEEnrichmentFast() {
log(`🚀 Starting FAST CVE enrichment by modified date...`);
const now = new Date();
const earliestDate = new Date('2002-01-01T00:00:00.000Z');
let endDateObj = new Date(); // most recent
const STEP_DAYS = 120;
while (endDateObj > earliestDate) {
const startDateObj = new Date(endDateObj);
startDateObj.setDate(startDateObj.getDate() - STEP_DAYS);
const startISO = startDateObj.toISOString();
const endISO = endDateObj.toISOString();
const humanRange = `${formatShortDate(startISO)} to ${formatShortDate(endISO)}`;
log(`📅 Fetching modified CVEs from ${humanRange}`);
let startIndex = 0;
let totalResults = 0;
let processedCount = 0;
let pageCount = 0;
try {
const initial = await fetchCVEPage(0, startISO, endISO);
totalResults = initial.totalResults || 0;
if (totalResults === 0) {
log(`⚠️ No CVEs to process in this window.`);
endDateObj = startDateObj;
continue;
}
log(`📦 Found ${totalResults} CVEs to enrich from ${humanRange}`);
while (startIndex < totalResults) {
const data = startIndex === 0 ? initial : await fetchCVEPage(startIndex, startISO, endISO);
const vulnerabilities = data.vulnerabilities || [];
log(`📄 Page ${++pageCount}${vulnerabilities.length} CVEs (Index ${startIndex})`);
for (const vuln of vulnerabilities) {
await processCVE(vuln);
processedCount++;
if (processedCount % 100 === 0 || processedCount === totalResults) {
const pct = ((processedCount / totalResults) * 100).toFixed(1);
log(`📊 Progress: ${processedCount}/${totalResults} CVEs (${pct}%)`);
}
}
startIndex += RESULTS_PER_PAGE;
await new Promise(r => setTimeout(r, 6000)); // API rate limit
}
} catch (err) {
log(`❌ Error during enrichment for ${humanRange}: ${err.message}`);
}
endDateObj = startDateObj; // 🕒 step backward
}
log('✅ Full enrichment pass complete!');
await DB.end();
logFile.end();
}
importCVEEnrichmentFast().catch((err) => {
log(`❌ Fatal error during enrichment: ${err.message}`);
logFile.end();
});

View File

@@ -0,0 +1,298 @@
#!/usr/bin/env node
import fs from 'fs';
import axios from 'axios';
import mysql from 'mysql2/promise';
const logFile = fs.createWriteStream('cve-sync.log', {
flags: 'a',
encoding: 'utf8',
});
const RESUME_FILE = '.last_synced_date';
function saveLastSyncedDate(dateStr) {
fs.writeFileSync(RESUME_FILE, dateStr);
}
function loadLastSyncedDate() {
if (!fs.existsSync(RESUME_FILE)) return null;
return fs.readFileSync(RESUME_FILE, 'utf8');
}
function log(msg) {
const now = new Date();
// Generate the locale string with hour12 enabled (e.g. "14 Apr 2025, 08:14:42 AM")
const raw = now.toLocaleString('en-AU', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true,
});
// Regex to convert only the AM/PM to lowercase
const formatted = raw.replace(/\b(AM|PM)\b/, (match) => match.toLowerCase());
const timestamp = `[${formatted}]`;
const line = `${timestamp} ${msg}`;
console.log(line);
logFile.write(`${line}\n`);
}
function formatDate(isoString) {
if (!isoString) return null;
const date = new Date(isoString);
return date.toISOString().slice(0, 19).replace('T', ' ');
}
function formatShortDate(isoString) {
return new Date(isoString).toLocaleDateString('en-AU', {
day: '2-digit',
month: 'short',
year: 'numeric',
}).replace(/\b([A-Z])([a-z]+)\b/, (_, a, b) => a + b); // Capitalize only first letter of month
}
function extractCpeParts(cpe) {
const parts = cpe.split(':');
return {
vendor: parts[3] || null,
product: parts[4] || null,
version: parts[5] || null
};
}
function addDaysToISO(dateISO, days) {
const date = new Date(dateISO);
date.setDate(date.getDate() + days);
return date.toISOString();
}
const DB = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
const BASE_URL = 'https://services.nvd.nist.gov/rest/json/cves/2.0';
const API_KEY = process.env.NVD_API_KEY;
const RESULTS_PER_PAGE = 2000;
let MAX_RANGE_DAYS = Number(process.env.NVD_MAX_RANGE_DAYS || 120);
log(`🧪 Getting CVEs from the last ${MAX_RANGE_DAYS} of days`);
if (MAX_RANGE_DAYS > 120) {
log("⚠️ MAX_RANGE_DAYS exceeds NVD API limit. Defaulting to 120.");
MAX_RANGE_DAYS = 120;
}
if (isNaN(MAX_RANGE_DAYS) || MAX_RANGE_DAYS < 1) {
log("⚠️ Invalid MAX_RANGE_DAYS. Using default of 7.");
MAX_RANGE_DAYS = 7;
}
async function fetchCVEPage(startIndex, startDate, endDate) {
try {
const res = await axios.get(BASE_URL, {
params: {
pubStartDate: startDate,
pubEndDate: endDate,
startIndex,
resultsPerPage: RESULTS_PER_PAGE,
},
headers: API_KEY ? { apiKey: API_KEY } : {}
});
return res.data;
} catch (err) {
log(`❌ API error: ${err.response?.status} - ${err.response?.data?.message || err.message}`);
throw err;
}
}
async function processCVE(cveWrapper) {
const cve = cveWrapper.cve;
const cveId = cve.id;
const title = cve.titles?.find(t => t.lang === 'en')?.title || '';
const desc = cve.descriptions.find(d => d.lang === 'en')?.value ?? '';
const published = formatDate(cve.published);
const modified = formatDate(cve.lastModified);
const metric = cve.metrics?.cvssMetricV31?.[0];
const severity = metric?.cvssData?.baseSeverity ?? null;
const score = metric?.cvssData?.baseScore ?? null;
const vector = metric?.cvssData?.vectorString ?? '';
try {
await DB.execute(
`INSERT INTO cves (id, title, description, published_date, last_modified_date, severity, cvss_score, cvss_vector, source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
last_modified_date = VALUES(last_modified_date),
severity = IFNULL(severity, VALUES(severity)),
cvss_score = IFNULL(cvss_score, VALUES(cvss_score)),
cvss_vector = IFNULL(cvss_vector, VALUES(cvss_vector)),
title = IFNULL(title, VALUES(title)),
source = IFNULL(source, VALUES(source))`,
[cveId, title, desc, published, modified, severity, score, vector, 'NVD']
);
} catch (err) {
log(`❌ Error inserting CVE ${cveId}: ${err.message}`);
}
const configurations = cve.configurations ?? [];
for (const node of configurations) {
for (const match of node.nodes?.flatMap(n => n.cpeMatch ?? []) ?? []) {
const cpe = match.criteria;
const vulnerable = match.vulnerable ? 1 : 0;
const start = match.versionStartIncluding || match.versionStartExcluding || null;
const end = match.versionEndIncluding || match.versionEndExcluding || null;
const { vendor, product, version } = extractCpeParts(cpe);
try {
await DB.execute(
`INSERT INTO cpe_matches (cve_id, cpe_uri, version_start, version_end, vulnerable, vendor, product, version)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[cveId, cpe, start, end, vulnerable, vendor, product, version]
);
} catch (err) {
log(`⚠️ Error inserting CPE for CVE ${cveId}: ${err.message}`);
}
}
}
}
async function getMostRecentModifiedDateFromDB() {
const [rows] = await DB.query(`SELECT MAX(last_modified_date) AS lastMod FROM cves`);
const lastMod = rows[0]?.lastMod;
return lastMod ? new Date(lastMod).toISOString() : '2020-01-01T00:00:00.000Z';
}
async function importCVEFeed() {
const now = new Date();
const endDate = now.toISOString();
const startDateObj = new Date(now);
startDateObj.setDate(startDateObj.getDate() - MAX_RANGE_DAYS);
const startDate = startDateObj.toISOString();
log(`🚀 CVE sync started`);
log(`🔄 Initializing script...`);
log(`📍 Launching script`);
log(`📅 Starting CVE sync from ${startDate} to ${endDate}`);
const humanStart = formatShortDate(startDate);
const humanEnd = formatShortDate(endDate);
log(`📡 Fetching modified CVEs from ${humanStart} to ${humanEnd}...`);
let startIndex = 0;
let totalResults = Infinity;
let pageCount = 0;
do {
const data = await fetchCVEPage(startIndex, startDate, endDate);
const vulnerabilities = data.vulnerabilities || [];
totalResults = data.totalResults ?? vulnerabilities.length;
if (vulnerabilities.length === 0) {
log(`⚠️ No CVEs returned at index ${startIndex}`);
break;
}
log(`📄 Page ${++pageCount} — Processing ${vulnerabilities.length} CVEs from index ${startIndex} of ~${totalResults}`);
for (const vuln of vulnerabilities) {
await processCVE(vuln);
}
startIndex += RESULTS_PER_PAGE;
await new Promise((r) => setTimeout(r, 6000));
} while (startIndex < totalResults);
log('✅ CVE import complete!');
await DB.end();
logFile.end();
}
async function importCVEFeedBackfill() {
const now = new Date();
const resumeFrom = loadLastSyncedDate();
let startFrom = resumeFrom ? new Date(resumeFrom) : now;
const MAX_RANGE_DAYS = 120;
log(resumeFrom
? `🔁 Resuming CVE backfill from ${formatShortDate(startFrom.toISOString())}`
: `⏮️ Starting CVE backfill from today (${formatShortDate(startFrom.toISOString())})`
);
while (true) {
const end = new Date(startFrom);
const start = new Date(startFrom);
start.setDate(start.getDate() - MAX_RANGE_DAYS + 1); // 120-day window
const startISO = start.toISOString();
const endISO = end.toISOString();
const humanRange = `${formatShortDate(startISO)} to ${formatShortDate(endISO)}`;
log(`📡 Fetching published CVEs from ${humanRange}...`);
let startIndex = 0;
let totalResults = Infinity;
let pageCount = 0;
try {
do {
const data = await fetchCVEPage(startIndex, startISO, endISO);
const vulnerabilities = data.vulnerabilities || [];
totalResults = data.totalResults ?? vulnerabilities.length;
if (vulnerabilities.length === 0) {
log(`⚠️ No CVEs returned for ${humanRange} at index ${startIndex}`);
break;
}
log(`📄 Page ${++pageCount}${vulnerabilities.length} CVEs from index ${startIndex}`);
for (const vuln of vulnerabilities) {
await processCVE(vuln);
}
startIndex += RESULTS_PER_PAGE;
await new Promise((r) => setTimeout(r, 6000));
} while (startIndex < totalResults);
// Move the window backward
saveLastSyncedDate(start.toISOString());
startFrom = start;
} catch (err) {
log(`❌ Error during ${humanRange}: ${err.message}`);
break;
}
if (start < new Date('2002-01-01')) {
log(`🛑 Reached earliest supported CVE publication date — halting backfill.`);
break;
}
}
log('✅ CVE backfill complete!');
await DB.end();
logFile.end();
}
//importCVEFeed().catch((err) => {
importCVEFeedBackfill(9000) // ~25 years (goes back to 2000)
.catch((err) => {
log(`❌ Fatal error during import: ${err.message}`);
logFile.end();
});

79
scripts/fetchKEV.js Normal file
View File

@@ -0,0 +1,79 @@
// fetchKEV.js
import fs from 'fs';
import axios from 'axios';
import mysql from 'mysql2/promise';
const KEV_URL = 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json';
const DB = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
fs.writeFileSync('kev-sync.log', '', { flag: 'w' }); // 👈 Reset the log file cleanly
function log(msg) {
const now = new Date().toLocaleString('en-AU', {
day: '2-digit', month: 'short', year: 'numeric',
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true
}).replace(/\b(AM|PM)\b/, m => m.toLowerCase());
const line = `[${now}] ${msg}`;
console.log(line);
fs.appendFileSync('kev-sync.log', `${line}\n`);
}
function safeDate(input) {
return input ? new Date(input).toISOString().slice(0, 10) : null;
}
async function fetchAndInsertKEVs() {
log('🚀 Starting KEV fetch from CISA...');
const { data } = await axios.get(KEV_URL);
const vulns = data.vulnerabilities;
let inserted = 0;
for (const v of vulns) {
try {
await DB.execute(`
INSERT INTO kev_catalog (
cve_id, vendor_project, product, vulnerability_name,
date_added, short_description, required_action, due_date,
known_ransomware_campaign_use, notes
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
date_added = VALUES(date_added),
short_description = VALUES(short_description),
required_action = VALUES(required_action),
due_date = VALUES(due_date),
known_ransomware_campaign_use = VALUES(known_ransomware_campaign_use),
notes = VALUES(notes)
`, [
v.cveID,
v.vendorProject,
v.product,
v.vulnerabilityName,
safeDate(v.dateAdded),
v.shortDescription,
v.requiredAction,
safeDate(v.dueDate),
v.knownRansomwareCampaignUse ?? null,
v.notes ?? null
]);
inserted++;
} catch (err) {
log(`❌ Failed to insert ${v.cveID}: ${err.message}`);
}
}
log(`✅ Finished KEV sync. Inserted/updated: ${inserted}`);
await DB.end();
}
fetchAndInsertKEVs().catch(err => {
log(`❌ Uncaught error: ${err.message}`);
DB.end();
});

193
scripts/fetchMSRC.js Normal file
View File

@@ -0,0 +1,193 @@
#!/usr/bin/env node
import axios from 'axios';
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
import readline from 'readline';
dotenv.config({ path: '.env.local' });
const DB = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
function formatDate(isoString) {
if (!isoString) return null;
const date = new Date(isoString);
return date.toISOString().slice(0, 19).replace('T', ' ');
}
function getPreviousMonthID() {
const now = new Date();
now.setMonth(now.getMonth() - 1);
const year = now.getFullYear();
const month = now.toLocaleString('en-US', { month: 'short' }); // e.g. "Mar"
return `${year}-${month}`;
}
function getLocalizedText(entry, fallback = '') {
if (!entry || typeof entry !== 'object') return fallback;
// Try to get English (US) first
if (entry['en-US']?.Value) return entry['en-US'].Value;
// Fallback: get first entry with a Value field
for (const key in entry) {
if (entry[key]?.Value) {
return entry[key].Value;
}
}
return fallback;
}
function extractDescription(notes = [], fallback = '') {
const note = notes.find(n =>
n.Title === 'Description' &&
n.Type === 2 &&
typeof n.Value === 'string' &&
n.Value.trim().length > 0
);
return note?.Value || fallback;
}
function extractAffectedProducts(vuln) {
const fixed = vuln.ProductStatuses?.Fixed ?? [];
const known = vuln.ProductStatuses?.KnownAffected ?? [];
const underInvestigation = vuln.ProductStatuses?.UnderInvestigation ?? [];
return [...new Set([...fixed, ...known, ...underInvestigation])].join(', ');
}
async function fetchCVRFDoc(cvrfId) {
const url = `https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/${cvrfId}`;
try {
const res = await axios.get(url, {
headers: { 'Accept': 'application/json' }
});
return res.data;
} catch (err) {
console.error(`❌ Failed to fetch CVRF for ${cvrfId}:`, err.message);
return null;
}
}
async function storeVulnerability(v, published, modified) {
const cve = v.CVE;
console.log(`⏳ Saving ${cve}...`);
const title = getLocalizedText(v.Title, '');
const description = extractDescription(v.Notes, title);
const affectedProducts = extractAffectedProducts(v);
const severity = v.Threats?.find(t => t.Type === 'Severity')?.Description?.Value || '';
const impact = v.Threats?.find(t => t.Type === 'Impact')?.Description?.Value || '';
const exploitability = v.Threats?.find(t => t.Type === 'Exploitability')?.Description?.Value || '';
const cvssSet = v.CVSSScoreSets?.[0];
const cvssScore = cvssSet?.BaseScore || null;
const cvssVector = cvssSet?.Vector || '';
const cweList = Array.isArray(v.CWE) ? v.CWE : [];
if (!title) {
console.warn(`⚠️ No title found for ${cve}`);
console.debug('Title raw:', JSON.stringify(v.Title, null, 2));
}
if (!description) {
console.warn(`⚠️ No description for ${cve}`);
console.debug('Notes raw:', JSON.stringify(v.Notes, null, 2));
}
try {
// Insert or update the main CVE entry (no cwe column now)
await DB.execute(
`INSERT INTO microsoft_cves (cve_id, title, severity, published_date, last_modified_date,
affected_products, description, exploitability, cvss_score, cvss_vector)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE last_modified_date = VALUES(last_modified_date),
affected_products = VALUES(affected_products),
severity = VALUES(severity),
description = VALUES(description),
exploitability = VALUES(exploitability),
cvss_score = VALUES(cvss_score),
cvss_vector = VALUES(cvss_vector)`,
[cve, title, severity, published, modified, affectedProducts, description, exploitability, cvssScore, cvssVector]
);
// Insert CWE entries and links
for (const { ID: cweId, Value: cweName } of cweList) {
if (!cweId) continue;
// Insert into cwe_entries if not already present
await DB.execute(
`INSERT IGNORE INTO cwe_entries (cwe_id, cwe_name) VALUES (?, ?)`,
[cweId, cweName || null]
);
// Link CVE to CWE
await DB.execute(
`INSERT IGNORE INTO cve_cwe (cve_id, cwe_id) VALUES (?, ?)`,
[cve, cweId]
);
}
console.log(`✅ Saved ${cve}${severity}, ${cvssScore ?? '?'}`);
} catch (err) {
console.error(`❌ DB error for ${cve}:`, err.message);
}
}
function waitForKeypress() {
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('🔄 Press any key to continue to the next 10 CVEs...', () => {
rl.close();
resolve();
});
});
}
async function importPreviousMonth() {
const cvrfId = getPreviousMonthID();
console.log(`📡 Fetching CVEs for ${cvrfId}...`);
const data = await fetchCVRFDoc(cvrfId);
if (!data) {
console.log("❌ No data returned from MSRC.");
return;
}
const published = formatDate(data.DocumentTracking?.InitialReleaseDate);
const modified = formatDate(data.DocumentTracking?.CurrentReleaseDate);
const vulns = data.Vulnerability || [];
console.log(`📋 Found ${vulns.length} vulnerabilities to import.`);
const batchSize = 10;
for (let i = 0; i < vulns.length; i += batchSize) {
const batch = vulns.slice(i, i + batchSize);
console.log(`🔄 Processing batch ${i / batchSize + 1} (${batch.length} items)...`);
for (const v of batch) {
await storeVulnerability(v, published, modified);
}
}
console.log(`✅ Finished importing ${vulns.length} CVEs for ${cvrfId}`);
await DB.end();
}
console.log("🚀 Starting MSRC sync now...");
await importPreviousMonth();

5
scripts/kev-sync.log Normal file
View File

@@ -0,0 +1,5 @@
[29 Apr 2025, 11:56:08 am] 🚀 Starting KEV fetch from CISA...
[29 Apr 2025, 11:56:08 am] 🚀 Starting KEV fetch from CISA...
[29 Apr 2025, 11:56:08 am] ✅ Finished KEV sync. Inserted/updated: 1326
[29 Apr 2025, 11:56:08 am] ✅ Finished KEV sync. Inserted/updated: 1326
[29 Apr 2025, 11:56:08 am] ✅ fetchKEV.js finished with exit code: 0

6978
scripts/msrc-sync.log Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
// runSpringSync_Manual.js
import axios from 'axios';
async function triggerSpringSync() {
try {
const res = await axios.post('https://sys.psg.net.au:8443/api/admin/scripts/run-scheduled-sync');
console.log(res.data);
} catch (err) {
console.error("❌ Failed to trigger:", err.response?.data || err.message);
}
}
triggerSpringSync();

1
settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'ld-sysinfo-server'

View File

@@ -0,0 +1,15 @@
package com.psg.dlsysinfo.dl_sysinfo_server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // ✅ Enables cron-style scheduled tasks across the app
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,12 @@
package com.psg.dlsysinfo.dl_sysinfo_server.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Authenticated {
// You can define an attribute here if needed, but not a method
}

View File

@@ -0,0 +1,22 @@
package com.psg.dlsysinfo.dl_sysinfo_server.auth;
import org.springframework.security.core.Authentication;
public class AuthenticatedData {
private final Long groupId;
private final Authentication authentication;
// Constructor
public AuthenticatedData(Long groupId, Authentication authentication) {
this.groupId = groupId;
this.authentication = authentication;
}
public Long getGroupId() {
return groupId;
}
public Authentication getAuthentication() {
return authentication;
}
}

View File

@@ -0,0 +1,34 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hcaptcha")
public class CaptchaConfig {
private boolean enabled;
private String sitekey;
private String secret; // This will not be serialized
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getSitekey() {
return sitekey;
}
public void setSitekey(String sitekey) {
this.sitekey = sitekey;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}

View File

@@ -0,0 +1,36 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class Config {
private MySQLConfig mysql;
private LoggerConfig logger = new LoggerConfig();
private CaptchaConfig hcaptcha = new CaptchaConfig(); // Default values
public MySQLConfig getMysql() {
return mysql;
}
public void setMysql(MySQLConfig mysql) {
this.mysql = mysql;
}
public LoggerConfig getLogger() {
return logger;
}
public void setLogger(LoggerConfig logger) {
this.logger = logger;
}
public CaptchaConfig getHcaptcha() {
return hcaptcha;
}
public void setHcaptcha(CaptchaConfig hcaptcha) {
this.hcaptcha = hcaptcha;
}
}

View File

@@ -0,0 +1,20 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}

View File

@@ -0,0 +1,20 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
public enum LogLevel {
INFO,
WARN,
ERROR;
public String toString() {
switch (this) {
case INFO:
return "info";
case WARN:
return "warn";
case ERROR:
return "error";
default:
return "info"; // Default case
}
}
}

View File

@@ -0,0 +1,16 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "logger")
public class LoggerConfig {
private LogLevel level;
public LogLevel getLevel() {
return level;
}
public void setLevel(LogLevel level) {
this.level = level;
}
}

View File

@@ -0,0 +1,52 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "mysql")
public class MySQLConfig {
private String user;
private String password;
private String database;
private String host;
private int port;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}

View File

@@ -0,0 +1,16 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.stereotype.Component;
@Component
public class PingToggle {
private boolean acceptPings = true;
public boolean isAcceptPings() {
return acceptPings;
}
public void setAcceptPings(boolean acceptPings) {
this.acceptPings = acceptPings;
}
}

View File

@@ -0,0 +1,6 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
public enum ActionType {
ADD,
DELETE
}

View File

@@ -0,0 +1,120 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.GuestSiteDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.SiteCodeword;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.service.*;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.SiteCodewordRepository;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/admin")
@PreAuthorize("hasRole('ADMIN')")
public class AdminController {
@Autowired
private UserService userService;
@Autowired
private DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private GuestSiteService guestSiteService;
@Autowired
private SiteCodewordRepository siteCodewordRepository;
@GetMapping("/users")
public ResponseEntity<List<UserDTO>> getAllUsersDecrypted(@AuthenticationPrincipal CurrentUser user) {
return ResponseEntity.ok(userService.getAllDecryptedUsers());
}
@PutMapping("/users/{userId}/enabled")
public ResponseEntity<Void> setUserEnabled(
@PathVariable Long userId,
@RequestParam boolean enabled,
@AuthenticationPrincipal CurrentUser user
) {
userService.setUserEnabledState(userId, enabled);
return ResponseEntity.ok().build();
}
@PostMapping("/sites")
public ResponseEntity<GuestSiteDTO> createGuestSite(
@RequestBody GuestSiteDTO dto,
@AuthenticationPrincipal CurrentUser user
) {
GuestSiteDTO created = guestSiteService.createSite(dto);
return ResponseEntity.ok(created);
}
@GetMapping("/sites")
public ResponseEntity<List<GuestSiteDTO>> getGuestSites(@AuthenticationPrincipal CurrentUser user) {
return ResponseEntity.ok(guestSiteService.getAllSites());
}
@PutMapping("/sites/{siteId}/enabled")
public ResponseEntity<Void> setGuestSiteEnabled(
@PathVariable Long siteId,
@RequestParam boolean enabled,
@AuthenticationPrincipal CurrentUser user
) {
guestSiteService.setGuestSiteEnabledState(siteId, enabled);
return ResponseEntity.ok().build();
}
@PutMapping("/sites/{siteId}")
public ResponseEntity<GuestSiteDTO> updateGuestSite(
@PathVariable Long siteId,
@RequestBody GuestSiteDTO dto,
@AuthenticationPrincipal CurrentUser user
) {
dto.setId(siteId); // Ensure consistency
GuestSiteDTO updated = guestSiteService.updateSite(dto);
return ResponseEntity.ok(updated);
}
@GetMapping("/codewords")
public ResponseEntity<List<String>> getDistinctCodewords() {
List<String> codewords = siteCodewordRepository.findAll().stream()
.map(SiteCodeword::getCodeword)
.sorted()
.toList();
return ResponseEntity.ok(codewords);
}
}

View File

@@ -0,0 +1,180 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.AuthRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ChangePasswordRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.LoginResponse;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.security.JwtUtil;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil;
private final UserService userAuthService;
private final EncryptionService encryptionService;
@Autowired
private PasswordEncoder passwordEncoder;
public AuthController(AuthenticationManager authenticationManager,
JwtUtil jwtUtil,
UserService userAuthService,
EncryptionService encryptionService,
Environment environment) {
this.authenticationManager = authenticationManager;
this.jwtUtil = jwtUtil;
this.userAuthService = userAuthService;
this.encryptionService = encryptionService;
}
private static String stripBOM(String s) {
if (s.startsWith("\uFEFF")) {
return s.substring(1);
}
return s;
}
private String resolveCookieDomain(HttpServletRequest request) {
String host = request.getHeader("Host");
System.out.println("🔍 Host: " + host);
if (host != null && host.contains("localhost")) {
return "localhost";
}
return ".psg.net.au";
}
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@RequestBody AuthRequest encryptedRequest,
HttpServletRequest request,
HttpServletResponse response) {
try {
String username = encryptionService.decryptData(encryptedRequest.getUsername()).trim();
String password = encryptionService.decryptData(encryptedRequest.getPassword()).trim();
password = stripBOM(password).replaceAll("[^\\x20-\\x7E]", "").trim();
// 🐞 Debug logs
System.out.println("🔓 Decrypted Username: " + username);
System.out.println("🔓 Decrypted Password: " + password);
UserAuth user = userAuthService.findByUsername(username);
if (user == null) {
System.out.println("❌ User lookup failed for: " + username);
return ResponseEntity.status(401).body(Map.of("error", "User not found"));
}
System.out.println("👤 Retrieved User from DB: " + user.getUsername());
System.out.println("🧠 Stored Hash: " + user.getPasswordHash());
System.out.println("🔍 Comparing to decrypted password: " + password);
boolean passwordMatches = passwordEncoder.matches(password, user.getPasswordHash());
if (!passwordMatches) {
System.out.println("❌ Password did not match DB hash");
return ResponseEntity.status(401).body(Map.of("error", "Invalid password"));
}
if (user.getClient() == null) {
return ResponseEntity.status(401).body(Map.of("error", "User is not linked to any client"));
}
String decryptedDisplayName = user.getDisplayNameHash() != null
? encryptionService.decryptData(user.getDisplayNameHash())
: "";
List<String> roles = List.of(user.getRole());
System.out.printf("✅ Assigning roles %s to user %s%n", roles, username);
String token = jwtUtil.generateToken(
username,
decryptedDisplayName,
user.getClient().getClientIdentifier(),
user.getId(),
roles
);
String cookieDomain = resolveCookieDomain(request);
ResponseCookie.ResponseCookieBuilder cookieBuilder = ResponseCookie.from("authToken", token)
.httpOnly(true)
.secure(true)
.path("/")
.sameSite("None")
.maxAge(60 * 60);
if (!"localhost".equals(cookieDomain)) {
cookieBuilder.domain(cookieDomain);
}
ResponseCookie cookie = cookieBuilder.build();
System.out.println("🧁 Set-Cookie: " + cookie.toString());
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
return ResponseEntity.ok(new LoginResponse(token, user.getUsername(), user.getId()));
} catch (Exception e) {
System.out.println("🔥 Exception in authenticateUser:");
e.printStackTrace();
return ResponseEntity.status(401).body(Map.of("error", "Invalid credentials or decryption error"));
}
}
@PostMapping("/logout")
public ResponseEntity<Map<String, String>> logout(HttpServletRequest request,
HttpServletResponse response) {
SecurityContextHolder.clearContext();
ResponseCookie expiredCookie = ResponseCookie.from("authToken", "")
.httpOnly(true)
.secure(true)
.path("/")
.sameSite("None")
.maxAge(0)
.domain(resolveCookieDomain(request))
.build();
response.addHeader(HttpHeaders.SET_COOKIE, expiredCookie.toString());
return ResponseEntity.ok(Map.of("message", "Logged out successfully"));
}
@PutMapping("/change-password")
public ResponseEntity<?> changePassword(@RequestBody ChangePasswordRequest request,
@AuthenticationPrincipal CurrentUser user) {
boolean success = userAuthService.changePassword(user.getUsername(), request);
if (success) {
return ResponseEntity.ok("Password changed successfully");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Incorrect current password");
}
}
}

View File

@@ -0,0 +1,42 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.config.PingToggle;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UniFiService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class HealthCheckController {
private final PingToggle pingToggle;
private final UniFiService uniFiService;
public HealthCheckController(PingToggle pingToggle, UniFiService uniFiService) {
this.pingToggle = pingToggle;
this.uniFiService = uniFiService;
}
@GetMapping("/system/ping-status")
public ResponseEntity<Map<String, Boolean>> pingStatus() {
return ResponseEntity.ok(Map.of("acceptPings", pingToggle.isAcceptPings()));
}
@GetMapping("/system/unifi-health")
public ResponseEntity<Map<String, Boolean>> unifiHealth() {
boolean status = uniFiService.isUnifiAvailable();
return ResponseEntity.ok(Map.of("unifiAvailable", status));
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/admin/toggle-ping")
public ResponseEntity<String> togglePing(@RequestParam boolean enabled, @AuthenticationPrincipal Object user) {
pingToggle.setAcceptPings(enabled);
return ResponseEntity.ok("Ping is now " + (enabled ? "ENABLED" : "DISABLED"));
}
}

View File

@@ -0,0 +1,17 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/admin/manage")
@PreAuthorize("hasRole('ADMIN')")
public class ManagementController {
// Other create/delete endpoints can go here
}

View File

@@ -0,0 +1,33 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.GuestSiteDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.PublicGuestSiteDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.service.GuestSiteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/public/sites")
public class PublicGuestSiteController {
@Autowired
private GuestSiteService guestSiteService;
@GetMapping("/{siteCode}")
public ResponseEntity<PublicGuestSiteDTO> getSiteByCode(@PathVariable String siteCode) {
GuestSiteDTO dto = guestSiteService.getSiteByCode(siteCode);
PublicGuestSiteDTO publicDto = new PublicGuestSiteDTO(
dto.getSiteCode(),
dto.getLocationName(),
dto.getCodeword(),
dto.getQrUrl()
);
return ResponseEntity.ok(publicDto);
}
}

View File

@@ -0,0 +1,73 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ClientDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ClientRegisterRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserRegisterRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.service.ClientService;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class RegisterController {
@Autowired
private ClientService clientService;
@Autowired
private UserService userService;
@Autowired
private ClientRepository clientRepository;
@Autowired
private EncryptionService encryptionService;
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/clients")
public ResponseEntity<List<ClientDTO>> getAllClients() {
List<ClientDTO> clients = clientRepository.findAll().stream()
.map(client -> {
String decryptedName;
try {
decryptedName = encryptionService.decryptData(client.getClientNameEncrypted());
} catch (Exception e) {
decryptedName = "[Decryption failed]";
}
return new ClientDTO(client.getClientId(), client.getClientIdentifier(), decryptedName);
})
.toList();
return ResponseEntity.ok(clients);
}
@PostMapping("/register/client")
public ResponseEntity<?> registerClient(@RequestBody ClientRegisterRequest request) {
Client client = clientService.registerClient(request.getClientName());
return ResponseEntity.ok(Map.of(
"clientId", client.getClientId(),
"clientIdentifier", client.getClientIdentifier()
));
}
@PostMapping("/register/user")
public ResponseEntity<?> registerUser(@RequestBody UserRegisterRequest request) {
UserAuth user = userService.registerUser(
request.getUsername(),
request.getPassword(),
request.getRole(),
request.getClientId()
);
return ResponseEntity.ok(Map.of("userId", user.getId()));
}
}

View File

@@ -0,0 +1,287 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.MediaType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Map;
@RestController
@RequestMapping("/api/admin/scripts")
public class ScriptController {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String dbUser;
@Value("${spring.datasource.password}")
private String dbPass;
@Value("${nvd.api.key}")
private String apiKey;
@Value("${nvd.max-range-days:7}")
private String nvdMaxRangeDays;
private final File cveLogFile = new File("scripts/cve-sync.log");
private final File kevLogFile = new File("scripts/kev-sync.log");
private final File msrcLogFile = new File("scripts/msrc-sync.log");
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-cve")
public ResponseEntity<String> runCveScript(@AuthenticationPrincipal Object user) {
return triggerScript("fetchCVE.js", "📡 CVE sync launched in background.", cveLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-kev")
public ResponseEntity<String> runKevScript(@AuthenticationPrincipal Object user) {
return triggerScript("fetchKEV.js", "📡 KEV sync launched in background.", kevLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-msrc")
public ResponseEntity<String> runMsrcScript(@AuthenticationPrincipal Object user) {
return triggerScript("enrichCVE_MSRC.js", "📡 MSRC sync launched in background.", msrcLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/fetch-cve/logs")
public ResponseEntity<String> fetchLogs(@AuthenticationPrincipal Object user) {
return readLogs(cveLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-cve/clear-logs")
public ResponseEntity<String> clearLogs(@AuthenticationPrincipal Object user) {
return clearLogs(cveLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-kev/clear-logs")
public ResponseEntity<String> clearKevLogs(@AuthenticationPrincipal Object user) {
return clearLogs(kevLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-msrc/clear-logs")
public ResponseEntity<String> clearMsrcLogs(@AuthenticationPrincipal Object user) {
return clearLogs(msrcLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping(value = "/fetch-cve/logs/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamLogs(@AuthenticationPrincipal Object user) {
Path logFile = Paths.get("scripts/cve-sync.log");
return Flux.interval(Duration.ofSeconds(1))
.map(tick -> {
try {
byte[] rawBytes = Files.readAllBytes(logFile);
String content = new String(rawBytes, StandardCharsets.UTF_8)
.replace("\r\n", "\n")
.replace("\r", "\n")
.replace("\uFEFF", "");
return content.isEmpty() ? "📭 No log output yet." : content;
} catch (IOException e) {
return "❌ Failed to read logs: " + e.getMessage();
}
});
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping(value = "/fetch-kev/logs/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamKevLogs(@AuthenticationPrincipal Object user) {
Path logFile = Paths.get("scripts/kev-sync.log");
return Flux.<String>create(emitter -> {
final long[] lastKnownPosition = {0};
emitter.onRequest(n -> {
try {
if (!Files.exists(logFile)) {
emitter.next("📭 Log file not found yet.");
return;
}
long fileSize = Files.size(logFile);
if (fileSize > lastKnownPosition[0]) {
try (RandomAccessFile raf = new RandomAccessFile(logFile.toFile(), "r")) {
raf.seek(lastKnownPosition[0]);
byte[] newBytes = new byte[(int) (fileSize - lastKnownPosition[0])];
raf.readFully(newBytes);
String newContent = new String(newBytes, StandardCharsets.UTF_8)
.replace("\r\n", "\n")
.replace("\r", "\n")
.replace("\uFEFF", "");
emitter.next(newContent); // 👈 perfectly legal now
lastKnownPosition[0] = fileSize;
}
}
} catch (Exception e) {
emitter.error(e);
}
});
emitter.onDispose(() -> {
System.out.println("Client disconnected from KEV stream.");
});
}).delayElements(Duration.ofSeconds(1));
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping(value = "/fetch-msrc/logs/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamMsrcLogs(@AuthenticationPrincipal Object user) {
Path logFile = Paths.get("scripts/msrc-sync.log");
return Flux.interval(Duration.ofSeconds(1))
.map(tick -> {
try {
byte[] rawBytes = Files.readAllBytes(logFile);
String content = new String(rawBytes, StandardCharsets.UTF_8)
.replace("\r\n", "\n")
.replace("\r", "\n")
.replace("\uFEFF", "");
return content.isEmpty() ? "📭 No log output yet." : content;
} catch (IOException e) {
return "❌ Failed to read logs: " + e.getMessage();
}
});
}
private ResponseEntity<String> triggerScript(String scriptName, String message, File targetLogFile) {
File scriptFile = new File("scripts", scriptName);
if (!scriptFile.exists()) {
return ResponseEntity.status(404).body("" + scriptName + " not found at: " + scriptFile.getAbsolutePath());
}
runNodeScript(scriptName, message, targetLogFile);
return ResponseEntity.ok("🚀 " + message);
}
private void runNodeScript(String scriptName, String startMessage, File logTarget) {
File scriptDir = new File("scripts");
File scriptFile = new File(scriptDir, scriptName);
if (!scriptFile.exists()) {
appendLog("" + scriptName + " not found at: " + scriptFile.getAbsolutePath(), logTarget);
return;
}
new Thread(() -> {
try (PrintWriter logWriter = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(logTarget, true), StandardCharsets.UTF_8), true)) {
logWriter.println(formatNow() + " 🚀 " + startMessage);
ProcessBuilder builder = new ProcessBuilder("node", scriptName);
builder.directory(scriptDir);
builder.redirectErrorStream(true);
Map<String, String> env = builder.environment();
env.put("DB_HOST", extractHost(dbUrl));
env.put("DB_NAME", extractDbName(dbUrl));
env.put("DB_USER", dbUser);
env.put("DB_PASSWORD", dbPass);
env.put("NVD_API_KEY", apiKey);
env.put("NVD_MAX_RANGE_DAYS", nvdMaxRangeDays);
env.put("NODE_OPTIONS", "--no-warnings --enable-source-maps");
env.put("LC_ALL", "en_US.UTF-8");
env.put("LANG", "en_US.UTF-8");
env.put("LANGUAGE", "en_US:en");
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
logWriter.println(line);
}
}
int exitCode = process.waitFor();
logWriter.println(formatNow() + "" + scriptName + " finished with exit code: " + exitCode);
} catch (Exception e) {
appendLog("" + scriptName + " error: " + e.getMessage(), logTarget);
}
}).start();
}
private String extractHost(String url) {
return url.replace("jdbc:mysql://", "").split(":")[0].split("/")[0];
}
private String extractDbName(String url) {
return url.substring(url.lastIndexOf("/") + 1).split("\\?")[0];
}
private void appendLog(String message, File logTarget) {
try (PrintWriter logWriter = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(logTarget, true), StandardCharsets.UTF_8), true)) {
logWriter.println(formatNow() + " " + message);
} catch (IOException e) {
e.printStackTrace();
}
}
private ResponseEntity<String> readLogs(File logTarget) {
if (!logTarget.exists()) {
return ResponseEntity.ok("📭 No log file found yet.");
}
try {
byte[] rawBytes = Files.readAllBytes(logTarget.toPath());
String content = new String(rawBytes, StandardCharsets.UTF_8)
.replace("\r\n", "\n")
.replace("\r", "\n")
.replace("\uFEFF", "");
return ResponseEntity.ok(content.isEmpty() ? "📭 Log is empty." : content);
} catch (IOException e) {
return ResponseEntity.status(500).body("❌ Error reading logs: " + e.getMessage());
}
}
private ResponseEntity<String> clearLogs(File logTarget) {
try {
if (logTarget.exists()) {
Files.delete(logTarget.toPath());
}
return ResponseEntity.ok("🧹 Logs cleared.");
} catch (IOException e) {
return ResponseEntity.status(500).body("❌ Failed to clear logs: " + e.getMessage());
}
}
private String formatNow() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd LLL yyyy, hh:mm:ss a", Locale.ENGLISH)
.withZone(ZoneId.systemDefault());
return "[" + formatter.format(java.time.ZonedDateTime.now()).replace("AM", "am").replace("PM", "pm") + "]";
}
}

View File

@@ -0,0 +1,37 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.TwoFactorRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.VerifyRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.service.TwoFactorAuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/2fa")
public class TwoFactorController {
@Autowired
private TwoFactorAuthService twoFactorAuthService;
@PostMapping("/send-code")
public ResponseEntity<String> sendCode(@RequestBody TwoFactorRequest req) {
try {
twoFactorAuthService.sendVerificationCode(req);
return ResponseEntity.ok("Verification code sent");
} catch (Exception e) {
e.printStackTrace(); // log this
return ResponseEntity.status(500).body("Unable to send verification code: " + e.getMessage());
}
}
@PostMapping("/verify-code")
public ResponseEntity<?> verifyCode(@RequestBody VerifyRequest req) {
boolean valid = twoFactorAuthService.verifyCodeAndAuthorize(req);
return valid ? ResponseEntity.ok("Guest authorized") : ResponseEntity.status(401).body("Invalid code");
}
}

View File

@@ -0,0 +1,84 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserProfileDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.security.JwtUtil;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserProfileController {
private final UserService userAuthService;
private final EncryptionService encryptionService;
private final JwtUtil jwtUtil;
@PreAuthorize("isAuthenticated()")
@GetMapping("/profile")
public ResponseEntity<?> getUserProfile(@AuthenticationPrincipal CurrentUser user) {
try {
UserAuth userAuth = userAuthService.findByUsername(user.getUsername());
UserProfileDTO profileDto = new UserProfileDTO(
userAuth.getUsername(),
encryptionService.decryptData(userAuth.getDisplayNameHash()),
encryptionService.decryptData(userAuth.getFirstNameHash()),
encryptionService.decryptData(userAuth.getLastNameHash()),
encryptionService.decryptData(userAuth.getEmailHash())
);
return ResponseEntity.ok(profileDto);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to decrypt user data");
}
}
@PreAuthorize("isAuthenticated()")
@PostMapping("/profile")
public ResponseEntity<?> updateUserProfile(@AuthenticationPrincipal CurrentUser user,
@RequestBody UserProfileDTO profileDto) {
try {
UserAuth userAuth = userAuthService.findByUsername(user.getUsername());
userAuth.setDisplayNameHash(encryptionService.encryptData(profileDto.getDisplayName()));
userAuth.setFirstNameHash(encryptionService.encryptData(profileDto.getFirstName()));
userAuth.setLastNameHash(encryptionService.encryptData(profileDto.getLastName()));
userAuth.setEmailHash(encryptionService.encryptData(profileDto.getEmail()));
userAuthService.save(userAuth);
// ✅ Rebuild token including roles
String decryptedDisplayName = encryptionService.decryptData(userAuth.getDisplayNameHash());
List<String> roles = List.of(userAuth.getRole());
String newToken = jwtUtil.generateToken(
userAuth.getUsername(),
decryptedDisplayName,
userAuth.getClient().getClientIdentifier(),
userAuth.getId(),
roles
);
return ResponseEntity.ok(Map.of(
"message", "Profile updated successfully",
"token", newToken
));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to encrypt or update user data");
}
}
}

View File

@@ -0,0 +1,41 @@
package com.psg.dlsysinfo.dl_sysinfo_server.db;
import com.psg.dlsysinfo.dl_sysinfo_server.error.ApiError;
import org.springframework.jdbc.core.JdbcTemplate;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class DatabaseService {
private final JdbcTemplate jdbcTemplate;
private static final int CURRENT_GROUP_VERSION = 2;
private static final String SHARED_MEMBER = "SharedMember"; // Define your shared member constant
public DatabaseService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private String hashToken(String token, String groupName) throws ApiError {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
String input = token + groupName; // Combine token and group name for hashing
byte[] hashBytes = digest.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString(); // Return the hashed token as a hex string
} catch (NoSuchAlgorithmException e) {
throw new ApiError("Hashing algorithm not found", e);
}
}
// ... (rest of your methods remain unchanged)
}

View File

@@ -0,0 +1,23 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class AuthRequest {
private String username;
private String password;
// Getters and Setters
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,25 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class ChangePasswordRequest {
private String currentPassword;
private String newPassword;
public String getCurrentPassword() {
return currentPassword;
}
public void setCurrentPassword(String currentPassword) {
this.currentPassword = currentPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}

View File

@@ -0,0 +1,17 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class ClientDTO {
private Long clientId;
private String clientIdentifier;
private String clientName; // decrypted name
public ClientDTO(Long clientId, String clientIdentifier, String clientName) {
this.clientId = clientId;
this.clientIdentifier = clientIdentifier;
this.clientName = clientName;
}
public Long getClientId() { return clientId; }
public String getClientIdentifier() { return clientIdentifier; }
public String getClientName() { return clientName; }
}

View File

@@ -0,0 +1,11 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ClientRegisterRequest {
private String clientName;
}

View File

@@ -0,0 +1,81 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class GuestSiteDTO {
private Long id;
private String siteCode;
private String locationName;
private String codeword;
private String qrUrl;
private boolean active;
private Long clientId;
public GuestSiteDTO() {
// default constructor
}
public GuestSiteDTO(Long id, String siteCode, String locationName, String codeword, String qrUrl, boolean active, Long clientId) {
this.id = id;
this.siteCode = siteCode;
this.locationName = locationName;
this.codeword = codeword;
this.qrUrl = qrUrl;
this.active = active;
this.clientId = clientId;
}
public Long getId() {
return id;
}
public String getSiteCode() {
return siteCode;
}
public String getLocationName() {
return locationName;
}
public String getCodeword() {
return codeword;
}
public String getQrUrl() {
return qrUrl;
}
public boolean isActive() {
return active;
}
public Long getClientId() {
return clientId;
}
public void setId(Long id) {
this.id = id;
}
public void setSiteCode(String siteCode) {
this.siteCode = siteCode;
}
public void setLocationName(String locationName) {
this.locationName = locationName;
}
public void setCodeword(String codeword) {
this.codeword = codeword;
}
public void setQrUrl(String qrUrl) {
this.qrUrl = qrUrl;
}
public void setActive(boolean active) {
this.active = active;
}
public void setClientId(Long clientId) {
this.clientId = clientId;
}
}

View File

@@ -0,0 +1,26 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class LoginResponse {
private String token;
private String username;
private Long userId;
// Constructor, Getters, and Setters
public LoginResponse(String token, String username, Long userId) {
this.token = token;
this.username = username;
this.userId = userId;
}
public String getToken() {
return token;
}
public String getUsername() {
return username;
}
public Long getUserId() {
return userId;
}
}

View File

@@ -0,0 +1,29 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class PublicGuestSiteDTO {
private String siteCode;
private String locationName;
private String codeword;
private String qrUrl;
public PublicGuestSiteDTO() {}
public PublicGuestSiteDTO(String siteCode, String locationName, String codeword, String qrUrl) {
this.siteCode = siteCode;
this.locationName = locationName;
this.codeword = codeword;
this.qrUrl = qrUrl;
}
public String getSiteCode() { return siteCode; }
public void setSiteCode(String siteCode) { this.siteCode = siteCode; }
public String getLocationName() { return locationName; }
public void setLocationName(String locationName) { this.locationName = locationName; }
public String getCodeword() { return codeword; }
public void setCodeword(String codeword) { this.codeword = codeword; }
public String getQrUrl() { return qrUrl; }
public void setQrUrl(String qrUrl) { this.qrUrl = qrUrl; }
}

View File

@@ -0,0 +1,31 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class TwoFactorRequest {
private String to;
private String siteCode;
private String codeword;
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getSiteCode() {
return siteCode;
}
public void setSiteCode(String siteCode) {
this.siteCode = siteCode;
}
public String getCodeword() {
return codeword;
}
public void setCodeword(String codeword) {
this.codeword = codeword;
}
}

View File

@@ -0,0 +1,54 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class UserDTO {
private Long id;
private String username;
private String displayName;
private String firstName;
private String lastName;
private String email;
private String role;
private String clientIdentifier;
private String clientName;
private boolean enabled;
public UserDTO() {}
public UserDTO(Long id, String username, String displayName, String firstName, String lastName,
String email, String role, String clientIdentifier,String clientName, boolean enabled) {
this.id = id;
this.username = username;
this.displayName = displayName;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.role = role;
this.clientIdentifier = clientIdentifier;
this.clientName = clientName;
this.enabled = enabled;
}
// Getters
public Long getId() { return id; }
public String getUsername() { return username; }
public String getDisplayName() { return displayName; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String getEmail() { return email; }
public String getRole() { return role; }
public String getClientIdentifier() { return clientIdentifier; }
public boolean isEnabled() { return enabled; }
public String getClientName() { return clientName; }
// Setters
public void setId(Long id) { this.id = id; }
public void setUsername(String username) { this.username = username; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public void setEmail(String email) { this.email = email; }
public void setRole(String role) { this.role = role; }
public void setClientIdentifier(String clientIdentifier) { this.clientIdentifier = clientIdentifier; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public void setClientName(String clientName) { this.clientName = clientName; }
}

View File

@@ -0,0 +1,61 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class UserProfileDTO {
private String username;
private String displayName;
private String firstName;
private String lastName;
private String email;
public UserProfileDTO() {}
public UserProfileDTO(String username, String displayName, String firstName, String lastName, String email) {
this.username = username;
this.displayName = displayName;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
// Getters
public String getUsername() {
return username;
}
public String getDisplayName() {
return displayName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getEmail() {
return email;
}
// Setters
public void setUsername(String username) {
this.username = username;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@@ -0,0 +1,15 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserRegisterRequest {
private String username;
private String password;
private String role;
private Long clientId;
}

View File

@@ -0,0 +1,13 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.Data;
@Data
public class VerifyRequest {
private String to;
private String code;
private String clientMac;
private String apMac;
private String siteCode; // optional but useful
private String codeword; // 💡 THIS is what was missing
}

View File

@@ -0,0 +1,58 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import java.util.List;
@Entity
@Table(name = "clients")
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "clientId")
private Long clientId;
@Column(nullable = false, unique = true)
private String clientIdentifier; // likely a UUID or slug for public use
@Column(name = "client_name_encrypted", nullable = false)
private String clientNameEncrypted;
@OneToMany(mappedBy = "client", cascade = CascadeType.ALL)
private List<GuestSite> guestSites;
// --- Getters & Setters ---
public Long getClientId() {
return clientId;
}
public void setClientId(Long clientId) {
this.clientId = clientId;
}
public String getClientIdentifier() {
return clientIdentifier;
}
public void setClientIdentifier(String clientIdentifier) {
this.clientIdentifier = clientIdentifier;
}
public String getClientNameEncrypted() {
return clientNameEncrypted;
}
public void setClientNameEncrypted(String clientNameEncrypted) {
this.clientNameEncrypted = clientNameEncrypted;
}
public List<GuestSite> getGuestSites() {
return guestSites;
}
public void setGuestSites(List<GuestSite> guestSites) {
this.guestSites = guestSites;
}
}

View File

@@ -0,0 +1,102 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import lombok.Data;
@Entity
@Table(name = "guest_sites")
public class GuestSite {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "site_code", unique = true, nullable = false)
private String siteCode;
@Column(name = "location_name", nullable = false)
private String locationName;
@ManyToOne
@JoinColumn(name = "codeword_id", referencedColumnName = "id")
private SiteCodeword codeword;
@Column(name = "qr_url")
private String qrUrl;
@Column(name = "is_active")
private boolean active;
@ManyToOne
@JoinColumn(name = "clientId", nullable = false)
private Client client;
@Column(name = "unifi_site_id")
private String unifiSiteId;
// ✅ Explicit getters
public Long getId() {
return id;
}
public String getSiteCode() {
return siteCode;
}
public String getLocationName() {
return locationName;
}
public SiteCodeword getCodeword() {
return codeword;
}
public String getQrUrl() {
return qrUrl;
}
public boolean isActive() {
return active;
}
public Client getClient() {
return client;
}
// ✅ Explicit setters
public void setId(Long id) {
this.id = id;
}
public void setSiteCode(String siteCode) {
this.siteCode = siteCode;
}
public void setLocationName(String locationName) {
this.locationName = locationName;
}
public void setCodeword(SiteCodeword codeword) {
this.codeword = codeword;
}
public void setQrUrl(String qrUrl) {
this.qrUrl = qrUrl;
}
public void setActive(boolean active) {
this.active = active;
}
public void setClient(Client client) {
this.client = client;
}
public String getUnifiSiteId() {
return unifiSiteId;
}
public void setUnifiSiteId(String unifiSiteId) {
this.unifiSiteId = unifiSiteId;
}
}

View File

@@ -0,0 +1,31 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "site_codewords")
public class SiteCodeword {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "codeword", nullable = false, unique = true)
private String codeword;
public Long getId() {
return id;
}
public String getCodeword() {
return codeword;
}
public void setId(Long id) {
this.id = id;
}
public void setCodeword(String codeword) {
this.codeword = codeword;
}
}

View File

@@ -0,0 +1,120 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class UserAuth {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "username", nullable = false, length = 50)
private String username;
@Column(name = "password_hash", nullable = false, length = 60)
private String passwordHash;
@ManyToOne
@JoinColumn(name = "clientId", nullable = false)
private Client client;
@Column(nullable = false)
private String role;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "password_changed_at")
private LocalDateTime passwordChangedAt;
@Column(name = "display_name_hash", length = 100)
private String displayNameHash;
@Column(name = "first_name_hash")
private String firstNameHash;
@Column(name = "last_name_hash")
private String lastNameHash;
@Column(name = "email_hash", length = 256)
private String emailHash;
@Column(nullable = false)
private boolean enabled = true;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
public Client getClient() { return client; }
public void setClient(Client client) { this.client = client; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getPasswordChangedAt() {
return passwordChangedAt;
}
public void setPasswordChangedAt(LocalDateTime passwordChangedAt) {
this.passwordChangedAt = passwordChangedAt;
}
public String getDisplayNameHash() {return displayNameHash; }
public void setDisplayNameHash(String displayNameHash) {this.displayNameHash = displayNameHash; }
public String getEmailHash() { return emailHash; }
public void setEmailHash(String emailHash) { this.emailHash = emailHash; }
public String getFirstNameHash() {
return firstNameHash;
}
public void setFirstNameHash(String firstNameHash) {
this.firstNameHash = firstNameHash;
}
public String getLastNameHash() {
return lastNameHash;
}
public void setLastNameHash(String lastNameHash) {
this.lastNameHash = lastNameHash;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getClientNameEncrypted() {
return client != null ? client.getClientNameEncrypted() : null;
}
}

View File

@@ -0,0 +1,19 @@
package com.psg.dlsysinfo.dl_sysinfo_server.error;
public class ApiError extends Throwable {
private String message;
private Throwable cause; // Optional
// Existing constructor
public ApiError(String message) {
this.message = message;
}
// New constructor
public ApiError(String message, Throwable cause) {
this.message = message;
this.cause = cause;
}
// Getters and setters...
}

View File

@@ -0,0 +1,14 @@
package com.psg.dlsysinfo.dl_sysinfo_server.model;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class LoginRequest {
// Getters and setters
private String username;
private String password;
}

View File

@@ -0,0 +1,11 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface ClientRepository extends JpaRepository<Client, Long> {
Optional<Client> findByClientIdentifier(String clientIdentifier);
}

View File

@@ -0,0 +1,11 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.GuestSite;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface GuestSiteRepository extends JpaRepository<GuestSite, Long> {
boolean existsBySiteCode(String siteCode);
Optional<GuestSite> findBySiteCode(String siteCode);
}

View File

@@ -0,0 +1,10 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.SiteCodeword;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface SiteCodewordRepository extends JpaRepository<SiteCodeword, Long> {
Optional<SiteCodeword> findByCodewordIgnoreCase(String codeword);
}

View File

@@ -0,0 +1,16 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface UserAuthRepository extends JpaRepository<UserAuth, Long> {
@Query("SELECT u FROM UserAuth u JOIN FETCH u.client WHERE u.username = :username")
Optional<UserAuth> findByUsernameWithClient(@Param("username") String username);
Optional<UserAuth> findByUsername(String username); // still used by Spring Security
}

View File

@@ -0,0 +1,29 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
public class AuthenticationInfo {
private Long groupId; // Keep this as Long
private String version; // This should be String
// Constructor
public AuthenticationInfo(Long groupId, String version) {
this.groupId = groupId; // Should match Long type
this.version = version; // Should match String type
}
// Getters and setters
public Long getGroupId() {
return groupId;
}
public void setGroupId(Long groupId) {
this.groupId = groupId;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}

View File

@@ -0,0 +1,173 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.converter.Converter;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.util.Base64;
import java.util.List;
@Configuration
@EnableWebSecurity
public class BasicConfiguration implements WebMvcConfigurer {
private final UserService userAuthService;
private final JwtToCurrentUserConverter jwtToCurrentUserConverter;
@Autowired
public BasicConfiguration(@Lazy UserService userAuthService, JwtToCurrentUserConverter jwtToCurrentUserConverter) {
this.userAuthService = userAuthService;
this.jwtToCurrentUserConverter = jwtToCurrentUserConverter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public JwtUtil jwtUtil() {
return new JwtUtil(); // Ensure you have a JwtUtil bean
}
@Bean
public JwtDecoder jwtDecoder(@Value("${jwt.secret}") String base64Secret) {
byte[] secret = Base64.getDecoder().decode(base64Secret);
SecretKey secretKey = new SecretKeySpec(secret, 0, secret.length, "HmacSHA256");
return NimbusJwtDecoder.withSecretKey(secretKey).build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(JwtUtil jwtUtil) {
return new JwtAuthenticationFilter(userAuthService, jwtUtil); // Pass GroupService
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
// Public endpoints
.requestMatchers("/register", "/login", "/error").permitAll()
.requestMatchers("/api/auth/login", "/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/scripts/run-scheduled-sync").permitAll()
.requestMatchers("/admin/vulnerabilities").permitAll()
.requestMatchers("/api/2fa/**").permitAll()
// Internal system health checks, available without auth
.requestMatchers("/api/system/**").permitAll()
// Pre-fetched cached information to prevent API abuse.
.requestMatchers("/api/cached/**").authenticated()
// Authenticated API endpoints
.requestMatchers("/api/ping", "/api/system-info").authenticated()
.requestMatchers("/api/devices", "/api/devices/**").authenticated()
.requestMatchers("/api/user/**").authenticated()
.requestMatchers("/api/vuln/**").authenticated()
// Special: Allow stream endpoints for authenticated users (not strict role)
.requestMatchers("/api/admin/scripts/*/logs/stream").authenticated()
// Admin-only API and frontend views
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/admin/**").hasRole("ADMIN")
// All other endpoints
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter(jwtUtil()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CustomCacheControlFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userAuthService).passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:3000","https://localhost:3000", "https://sys.psg.net.au", "http://172.16.10.180:3000", "https://wireless.psg.net.au"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
configuration.setExposedHeaders(List.of("Set-Cookie", "Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Component
public class CorsDebugFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("Origin: " + request.getHeader("Origin"));
System.out.println("Cookie: " + request.getHeader("Cookie"));
filterChain.doFilter(request, response);
}
}
@Autowired
private CurrentUserArgumentResolver currentUserArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(currentUserArgumentResolver);
}
}

View File

@@ -0,0 +1,20 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class CurrentUser {
private final String username;
private final String displayName;
private final String clientIdentifier;
private final Long userId;
private final List<String> roles;
public boolean hasRole(String role) {
return roles != null && roles.contains(role);
}
}

View File

@@ -0,0 +1,30 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import org.springframework.core.MethodParameter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.stereotype.Component;
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof CurrentUser currentUser) {
return currentUser;
}
throw new IllegalArgumentException("Authenticated user context is missing");
}
}

View File

@@ -0,0 +1,32 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class CustomCacheControlFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (response != null) {
// Prevent caching of sensitive data
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
// Harden browser security
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-XSS-Protection", "1; mode=block");
response.setHeader("Referrer-Policy", "no-referrer");
}
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,80 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
@Service // ✅ Mark this as a Spring Service so it can be injected
public class EncryptionService {
@Value("${encryption.aes.key}") // Load AES Key from properties
private String aesKey;
@Value("${encryption.aes.iv}") // Load AES IV from properties
private String aesIv;
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
public String decryptData(String encryptedData) throws Exception {
if (encryptedData == null || encryptedData.trim().isEmpty()) {
return "";
}
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(), "AES");
byte[] ivBytes = aesIv.getBytes();
if (ivBytes.length != 16) {
throw new IllegalArgumentException("Invalid IV length: " + ivBytes.length + " bytes (must be 16 bytes)");
}
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
if (encryptedBytes.length % 16 != 0) {
throw new IllegalArgumentException("Invalid encrypted data length: must be multiple of 16 bytes.");
}
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public String encryptData(String plainText) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(StandardCharsets.UTF_8), "AES");
byte[] ivBytes = aesIv.getBytes(StandardCharsets.UTF_8);
if (ivBytes.length != 16) {
throw new IllegalArgumentException("Invalid IV length: " + ivBytes.length + " bytes (must be 16 bytes)");
}
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String hashString(String input) {
try {
// 🔑 Using SHA-256 hashing algorithm
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
// 🔑 Encode the hash as Base64 for storage
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error generating hash: " + e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,138 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.io.IOException;
import java.util.List;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserService userService;
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(UserService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String clientIp = extractClientIp(request);
String uri = request.getRequestURI();
System.out.println("🌐 Incoming request to " + uri + " from IP: " + clientIp);
String jwt = null;
// Try to get token from Authorization header
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
jwt = authHeader.substring(7);
System.out.println("🔐 JWT found in Authorization header");
}
// If not in header, try cookie
if (jwt == null && request.getCookies() != null) {
for (jakarta.servlet.http.Cookie cookie : request.getCookies()) {
if ("authToken".equals(cookie.getName())) {
jwt = cookie.getValue();
System.out.println("🍪 JWT extracted from authToken cookie");
break;
}
}
}
if (jwt == null) {
System.out.println("🔓 No JWT found in header or cookie");
chain.doFilter(request, response);
return;
}
try {
String username = jwtUtil.extractUsername(jwt);
System.out.println("👤 Extracted username: " + username + " from IP: " + clientIp);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, username)) {
String displayName = jwtUtil.extractDisplayName(jwt);
String clientIdentifier = jwtUtil.extractClientIdentifier(jwt);
Long userId = jwtUtil.extractUserId(jwt);
List<String> roles = jwtUtil.extractRoles(jwt); // youll define this next
CurrentUser currentUser = new CurrentUser(
username,
displayName,
clientIdentifier,
userId,
roles
);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
currentUser, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authToken);
System.out.println("✅ Authenticated user: " + username + " from IP: " + clientIp);
}
}
} catch (ExpiredJwtException e) {
System.out.println("⏰ JWT expired from IP " + clientIp + ": " + e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"Token expired\"}");
return;
} catch (JwtException | IllegalArgumentException e) {
System.out.println("❌ Invalid JWT from IP " + clientIp + ": " + e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"Invalid token\"}");
return;
} catch (UsernameNotFoundException e) {
System.out.println("❌ User not found from IP " + clientIp + ": " + e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"User not found\"}");
return;
}
chain.doFilter(request, response);
}
private String extractClientIp(HttpServletRequest request) {
String cfIp = request.getHeader("CF-Connecting-IP");
if (cfIp != null && !cfIp.isEmpty()) return cfIp;
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader != null && !xfHeader.isEmpty()) {
return xfHeader.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}

View File

@@ -0,0 +1,26 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class JwtToCurrentUserConverter implements Converter<Jwt, CurrentUser> {
@Override
public CurrentUser convert(Jwt jwt) {
// 👇 Try to get the "roles" claim as a list of strings
var roles = jwt.getClaimAsStringList("roles");
return new CurrentUser(
jwt.getSubject(), // username
jwt.getClaim("displayname"), // display name
jwt.getClaim("idauth"), // clientIdentifier
jwt.getClaim("userId"), // userId
roles != null ? roles : List.of() // roles fallback to empty list
);
}
}

View File

@@ -0,0 +1,146 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secretKeyString;
@Value("${jwt.expiration}")
private long jwtExpirationMs;
/**
* Converts Base64-encoded key from properties to SecretKey.
*/
private SecretKey getSigningKey() {
byte[] keyBytes = Base64.getDecoder().decode(secretKeyString);
return Keys.hmacShaKeyFor(keyBytes);
}
/**
* Generates a JWT token with a username and idauth.
*/
public String generateToken(String username, String displayName, String idauth, Long userId, List<String> roles) {
return Jwts.builder()
.setSubject(username)
.claim("displayname", displayName)
.claim("idauth", idauth)
.claim("userId", userId)
.claim("roles", roles)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS256, getSigningKey())
.compact();
}
/**
* Extracts claims from a token.
*/
private Claims extractAllClaims(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
System.err.println("⏰ JWT expired: " + e.getMessage());
throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "JWT expired", e); // re-throw
} catch (JwtException e) {
System.err.println("❌ Invalid JWT: " + e.getMessage());
throw new IllegalArgumentException("Invalid JWT Token", e);
}
}
public String extractDisplayName(String token) {
return extractClaim(token, claims -> claims.get("displayname", String.class));
}
public Long extractUserId(String token) {
return extractClaim(token, claims -> claims.get("userId", Long.class));
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public String extractIdAuth(String token) {
return extractClaim(token, claims -> claims.get("idauth", String.class));
}
public String extractClientIdentifier(String token) {
return extractIdAuth(token);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
return claimsResolver.apply(extractAllClaims(token));
}
public boolean isTokenExpired(String token) {
return extractClaim(token, Claims::getExpiration).before(new Date());
}
/**
* Validates the token and checks if it matches the provided username.
*/
public boolean validateToken(String token, String username) {
try {
return extractUsername(token).equals(username) && !isTokenExpired(token);
} catch (JwtException | IllegalArgumentException e) {
System.err.println("Token validation failed: " + e.getMessage());
return false;
}
}
/**
* Overloaded method for validating token without username check.
*/
public boolean validateToken(String token) {
try {
return !isTokenExpired(token);
} catch (JwtException | IllegalArgumentException e) {
System.err.println("Token validation failed: " + e.getMessage());
return false;
}
}
/**
* Extracts token from Authorization header.
*/
public String extractToken(String authHeader) {
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7); // Remove "Bearer " prefix
}
return authHeader; // If it's a direct token, return as is
}
public List<String> extractRoles(String token) {
Claims claims = extractAllClaims(token);
Object rolesClaim = claims.get("roles");
if (rolesClaim instanceof List<?>) {
return ((List<?>) rolesClaim).stream()
.filter(String.class::isInstance)
.map(String.class::cast)
.toList();
}
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,28 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
@Component
public class TokenResolver {
public String resolveToken(HttpServletRequest request) {
// Check Authorization header first
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
// Fallback to Cookie
if (request.getCookies() != null) {
for (var cookie : request.getCookies()) {
if ("authToken".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,31 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
public class ClientService {
@Autowired
private ClientRepository clientRepository;
@Autowired
private EncryptionService encryptionService;
public Client registerClient(String clientName) {
try {
Client client = new Client();
client.setClientIdentifier(UUID.randomUUID().toString());
client.setClientNameEncrypted(encryptionService.encryptData(clientName));
return clientRepository.save(client);
} catch (Exception e) {
throw new RuntimeException("Failed to register client", e);
}
}
}

View File

@@ -0,0 +1,47 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
@Value("${app.mail.from}")
private String from;
@Value("${app.mail.to}")
private String to;
private final JavaMailSender mailSender;
private final Environment env;
@Autowired
public EmailService(JavaMailSender mailSender, Environment env) {
this.mailSender = mailSender;
this.env = env;
}
public void sendHtmlEmail(String subject, String htmlBody) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlBody, true); // `true` enables HTML
mailSender.send(message);
} catch (MessagingException e) {
System.err.println("❌ Error sending HTML email: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,125 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.GuestSiteDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.GuestSite;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.SiteCodeword;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.GuestSiteRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.SiteCodewordRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class GuestSiteService {
@Autowired
private GuestSiteRepository guestSiteRepository;
@Autowired
private SiteCodewordRepository siteCodewordRepository;
@Autowired
private ClientRepository clientRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
public List<GuestSiteDTO> getAllSites() {
return guestSiteRepository.findAll().stream().map(site -> {
GuestSiteDTO dto = new GuestSiteDTO();
dto.setId(site.getId());
dto.setSiteCode(site.getSiteCode());
dto.setLocationName(site.getLocationName());
dto.setCodeword(site.getCodeword().getCodeword());
dto.setQrUrl(site.getQrUrl());
dto.setActive(site.isActive());
System.out.println("🔎 Site " + site.getSiteCode() + " active? " + site.isActive());
if (site.getClient() != null) {
dto.setClientId(site.getClient().getClientId());
}
return dto;
}).collect(Collectors.toList());
}
public GuestSiteDTO createSite(GuestSiteDTO dto) {
GuestSite site = new GuestSite();
site.setSiteCode(dto.getSiteCode());
site.setLocationName(dto.getLocationName());
SiteCodeword codeword = siteCodewordRepository.findByCodewordIgnoreCase(dto.getCodeword())
.orElseThrow(() -> new RuntimeException("Codeword not found"));
site.setCodeword(codeword);
site.setQrUrl(dto.getQrUrl());
site.setActive(dto.isActive());
if (dto.getClientId() != null) {
Client client = clientRepository.findById(dto.getClientId())
.orElseThrow(() -> new RuntimeException("Client not found"));
site.setClient(client);
}
GuestSite saved = guestSiteRepository.save(site);
dto.setId(saved.getId());
return dto;
}
public void setGuestSiteEnabledState(Long siteId, boolean enabled) {
GuestSite site = guestSiteRepository.findById(siteId)
.orElseThrow(() -> new RuntimeException("Guest site not found"));
site.setActive(enabled);
guestSiteRepository.save(site);
}
public GuestSiteDTO getSiteByCode(String siteCode) {
GuestSite site = guestSiteRepository.findBySiteCode(siteCode)
.orElseThrow(() -> new RuntimeException("Site not found"));
GuestSiteDTO dto = new GuestSiteDTO();
dto.setId(site.getId());
dto.setSiteCode(site.getSiteCode());
dto.setLocationName(site.getLocationName());
dto.setCodeword(site.getCodeword().getCodeword());
dto.setQrUrl(site.getQrUrl());
dto.setActive(site.isActive());
if (site.getClient() != null) {
dto.setClientId(site.getClient().getClientId());
}
return dto;
}
public GuestSiteDTO updateSite(GuestSiteDTO dto) {
GuestSite site = guestSiteRepository.findById(dto.getId())
.orElseThrow(() -> new RuntimeException("Guest site not found"));
site.setSiteCode(dto.getSiteCode());
site.setLocationName(dto.getLocationName());
site.setQrUrl(dto.getQrUrl());
// Fetch the related SiteCodeword entity
SiteCodeword codeword = siteCodewordRepository.findByCodewordIgnoreCase(dto.getCodeword())
.orElseThrow(() -> new RuntimeException("Codeword not found"));
site.setCodeword(codeword);
// Save updated site
GuestSite updated = guestSiteRepository.save(site);
// Update and return the DTO (optional — or map from updated)
dto.setCodeword(codeword.getCodeword());
return dto;
}
}

View File

@@ -0,0 +1,112 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.TwoFactorRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.VerifyRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.GuestSite;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.GuestSiteRepository;
import com.twilio.Twilio;
import com.twilio.rest.verify.v2.service.Verification;
import com.twilio.rest.verify.v2.service.VerificationCheck;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
import java.util.Optional;
@Service
public class TwoFactorAuthService {
@Value("${twilio.account-sid}")
private String accountSid;
@Value("${twilio.auth-token}")
private String authToken;
@Value("${twilio.verify-service-sid}")
private String verifyServiceSid;
@Value("${unifi.api-key}")
private String unifiApiKey;
@Value("${unifi.gateway-url}")
private String unifiGatewayUrl;
private final GuestSiteRepository guestSiteRepository;
public TwoFactorAuthService(GuestSiteRepository guestSiteRepository) {
this.guestSiteRepository = guestSiteRepository;
}
public void sendVerificationCode(TwoFactorRequest request) {
Twilio.init(accountSid, authToken);
String to = request.getTo();
String channel = to.contains("@") ? "email" : "sms";
Verification verification = Verification.creator(verifyServiceSid, to, channel).create();
if (!"pending".equalsIgnoreCase(verification.getStatus())) {
throw new RuntimeException("Failed to send verification code");
}
}
public boolean verifyCodeAndAuthorize(VerifyRequest request) {
Twilio.init(accountSid, authToken);
VerificationCheck check = VerificationCheck.creator(verifyServiceSid)
.setTo(request.getTo())
.setCode(request.getCode())
.create();
if (!"approved".equalsIgnoreCase(check.getStatus())) {
return false;
}
Optional<GuestSite> siteOpt = guestSiteRepository.findBySiteCode(request.getSiteCode());
if (siteOpt.isEmpty()) {
throw new RuntimeException("Invalid site");
}
GuestSite site = siteOpt.get();
if (!site.isActive() || !site.getCodeword().getCodeword().equalsIgnoreCase(request.getCodeword())) {
return false;
}
// Use MAC address as clientId (Unifi API expects this)
String clientMac = request.getClientMac().toUpperCase();
String siteId = site.getUnifiSiteId(); // ← make sure this is stored in DB
authorizeWithUnifi(clientMac, siteId);
return true;
}
private void authorizeWithUnifi(String clientId, String siteId) {
RestTemplate restTemplate = new RestTemplate();
String url = String.format("%s/v1/sites/%s/clients/%s/actions", unifiGatewayUrl, siteId, clientId);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-API-KEY", unifiApiKey);
Map<String, Object> payload = Map.of(
"action", "AUTHORIZE_GUEST_ACCESS",
"timeLimitMinutes", 1440,
"dataUsageLimitMBytes", 2048,
"rxRateLimitKbps", 10000,
"txRateLimitKbps", 10000
);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(payload, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
System.out.println("UniFi authorization response: " + response.getBody());
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("Failed to authorize guest via new UniFi API");
}
}
}

View File

@@ -0,0 +1,56 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
@Service
public class UniFiService {
@Value("${unifi.api-key}")
private String apiKey;
@Value("${unifi.gateway-url}")
private String gatewayUrl;
private final RestTemplate restTemplate = new RestTemplate();
private static final Logger logger = LoggerFactory.getLogger(UniFiService.class);
public boolean isUnifiAvailable() {
String url = gatewayUrl + "/proxy/network/integration/v1/sites";
logger.info("Checking UniFi availability at URL: {}", url);
HttpHeaders headers = new HttpHeaders();
headers.set("X-API-KEY", apiKey);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
HttpEntity<Void> request = new HttpEntity<>(headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.GET,
request,
String.class
);
logger.info("UniFi response status: {}", response.getStatusCode());
logger.debug("UniFi response body: {}", response.getBody());
return response.getStatusCode().is2xxSuccessful();
} catch (Exception e) {
logger.error("Error while checking UniFi availability", e);
return false;
}
}
}

View File

@@ -0,0 +1,173 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ChangePasswordRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserProfileDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.UserAuthRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Primary
@Transactional
public class UserService implements UserDetailsService {
private final UserAuthRepository userAuthRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
private EncryptionService encryptionService;
@Autowired
public UserService(UserAuthRepository userAuthRepository, PasswordEncoder passwordEncoder) {
this.userAuthRepository = userAuthRepository;
this.passwordEncoder = passwordEncoder;
}
@Autowired
private ClientRepository clientRepository;
public UserAuth registerUser(String username, String password, String role, Long clientId) {
if (password == null || password.isEmpty()) {
throw new IllegalArgumentException("Password cannot be null or empty");
}
String hashedPassword = passwordEncoder.encode(password);
UserAuth userAuth = new UserAuth();
userAuth.setUsername(username);
userAuth.setPasswordHash(hashedPassword);
userAuth.setRole(role);
userAuth.setCreatedAt(java.time.LocalDateTime.now());
Client client = clientRepository.findById(clientId)
.orElseThrow(() -> new IllegalArgumentException("Invalid client ID"));
userAuth.setClient(client);
return userAuthRepository.save(userAuth);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserAuth> userOpt = userAuthRepository.findByUsername(username);
UserAuth userAuth = userOpt.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return User.builder()
.username(userAuth.getUsername())
.password(userAuth.getPasswordHash())
.roles(userAuth.getRole())
.build();
}
public UserAuth findByUsername(String username) {
return userAuthRepository.findByUsernameWithClient(username)
.orElse(null);
}
public boolean changePassword(String username, ChangePasswordRequest request) {
Optional<UserAuth> userOpt = userAuthRepository.findByUsername(username);
if (userOpt.isPresent()) {
UserAuth user = userOpt.get();
try {
String decryptedCurrentPassword = encryptionService.decryptData(request.getCurrentPassword());
String decryptedNewPassword = encryptionService.decryptData(request.getNewPassword());
if (passwordEncoder.matches(decryptedCurrentPassword, user.getPasswordHash())) {
user.setPasswordHash(passwordEncoder.encode(decryptedNewPassword));
user.setPasswordChangedAt(LocalDateTime.now());
userAuthRepository.save(user);
return true;
}
} catch (Exception e) {
System.out.println("❌ Decryption failed: " + e.getMessage());
}
}
return false;
}
public UserProfileDTO getUserProfile(String username) {
UserAuth user = userAuthRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
try {
return new UserProfileDTO(
user.getUsername(),
user.getDisplayNameHash() != null ? encryptionService.decryptData(user.getDisplayNameHash()) : "",
user.getFirstNameHash() != null ? encryptionService.decryptData(user.getFirstNameHash()) : "",
user.getLastNameHash() != null ? encryptionService.decryptData(user.getLastNameHash()) : "",
user.getEmailHash() != null ? encryptionService.decryptData(user.getEmailHash()) : ""
);
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt user profile data", e);
}
}
public void updateUserProfile(String username, UserProfileDTO profileDto) {
UserAuth user = userAuthRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
try {
user.setDisplayNameHash(encryptionService.encryptData(profileDto.getDisplayName()));
user.setFirstNameHash(encryptionService.encryptData(profileDto.getFirstName()));
user.setLastNameHash(encryptionService.encryptData(profileDto.getLastName()));
user.setEmailHash(encryptionService.encryptData(profileDto.getEmail()));
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt user profile fields", e);
}
userAuthRepository.save(user);
}
public List<UserDTO> getAllDecryptedUsers() {
return userAuthRepository.findAll().stream()
.map(user -> {
try {
return new UserDTO(
user.getId(),
user.getUsername(),
encryptionService.decryptData(user.getDisplayNameHash()),
encryptionService.decryptData(user.getFirstNameHash()),
encryptionService.decryptData(user.getLastNameHash()),
encryptionService.decryptData(user.getEmailHash()),
user.getRole(),
user.getClient().getClientIdentifier(),
encryptionService.decryptData(user.getClient().getClientNameEncrypted()),
user.isEnabled()
);
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt user data for user: " + user.getUsername(), e);
}
})
.collect(Collectors.toList());
}
public UserAuth save(UserAuth user) {
return userAuthRepository.save(user);
}
public void setUserEnabledState(Long userId, boolean enabled) {
UserAuth user = userAuthRepository.findById(userId)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
user.setEnabled(enabled);
userAuthRepository.save(user);
}
}

View File

@@ -0,0 +1,19 @@
package com.psg.dlsysinfo.dl_sysinfo_server.util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class HashUtil {
public static String sha256(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 hashing failed", e);
}
}
}

View File

@@ -0,0 +1,50 @@
package com.psg.dlsysinfo.dl_sysinfo_server.util;
public class NormalizationUtils {
public static String normalize(String input) {
if (input == null) return "";
return input
.toLowerCase()
.replaceAll("[^a-z0-9]", "_")
.replaceAll("_+", "_")
.replaceAll("^_|_$", "");
}
public static String simplifyPublisher(String publisher) {
String norm = normalize(publisher);
return switch (norm) {
case "microsoft_corporation", "microsoft" -> "microsoft";
case "adobe_systems_incorporated", "adobe_inc" -> "adobe";
case "oracle_corporation" -> "oracle";
case "videolan" -> "videolan"; // ensure no unexpected variation
default -> norm;
};
}
public static String normalizeProduct(String appName) {
if (appName == null) return "";
String cleaned = stripJunkFromName(appName);
String norm = normalize(cleaned);
return switch (norm) {
case "vlc_media_player" -> "vlc";
case "adobe_reader", "acrobat_reader", "adobe_acrobat_reader" -> "acrobat_reader";
case "java_platform_se", "java", "java_se", "java_platform" -> "jdk"; // adapt based on your CPE data
case "google_chrome" -> "chrome";
case "windows_powershell" -> "powershell";
case "microsoft_teams" -> "teams";
case "visual_studio_code" -> "vscode";
default -> norm;
};
}
public static String stripJunkFromName(String input) {
if (input == null) return "";
return input
.replaceAll("(?i)(\\(tm\\)|\\(r\\)|\\(c\\)|™|®|©)", "")
.replaceAll("\\s+", " ")
.trim();
}
}

View File

@@ -0,0 +1,89 @@
spring.application.name=ld-sysinfo-server
# Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/db_psg-spring-backend
spring.datasource.username=root
spring.datasource.password=6DRR4xWvHBhSqLGtIOEKa7gHjKnX33Hf
# JPA configuration
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
# Spring Security stuff
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
logging.level.org.springframework.security=DEBUG
logging.level.com.dashboard=DEBUG
# Turn off SQL logs temporarily
logging.level.org.hibernate.SQL=OFF
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=OFF
# JWT stuff
jwt.secret=JBYLzeID2fj7DJ1f+HIk3KeKwRAjo/jxZoJ2PZWe7YQ=
jwt.expiration=36000000
# AES from Client App
encryption.aes.key=HWJGbwmF2pWdXySDExMNEbJSrXn0YCBF
encryption.aes.iv=VWYRtYCfch0sKs6k
# Twilio Stuff
twilio.account-sid=AC9194c43c94a7d38bd37356f829edb2c3
twilio.auth-token=4a76601f13b493d8fdd0745123fa692c
twilio.verify-service-sid=VAb6f6ba723cb5bb076f695958fefae370
# New Unifi Stuff
unifi.api-key=T9QvofmtB3vSALbZyl9EusZw79TGkg_q
unifi.gateway-url=https://unifi.psg.net.au
#Unifi Stuff
unifi.auth.url=https://unifi.psg.net.au/api/s/default/cmd/stamgr
UNIFI_PORT=9443
UNIFI_SITE=29jn47fk
UNIFI_USERNAME=svc_api_access
UNIFI_PASSWORD="\>`(w,YoIK_Y+a=oQ^3e"
UNIFI_CSRF_TOKEN=6t7fwydRvwPoJSEPujigmLmwHwQj1it8
UNIFI_SESSION_ID=aDptge8Bx54V51BxSS9pQ97qmoWyLkZF
# Server stuff
server.address=0.0.0.0
server.port=8443
server.ssl.enabled=true
server.ssl.key-alias=springboot
server.ssl.key-store=file:/C:/Users/sonder/Git/GuestWirelessDEVELOPMENT/backend/src/main/resources/springboot.p12
server.ssl.key-store-password=Howaboutno123!
server.ssl.key-store-type=PKCS12
logging.level.org.apache.tomcat.util.net.SSLUtilBase=DEBUG
logging.level.org.apache.tomcat.util.net.SSLHostConfig=DEBUG
# Script Controller (NVD) related
nvd.api.key=42b4f093-e8c4-4110-a7d1-6ab2ba6234aa
nvd.max-range-days=30
# SMTP/Mail related
spring.mail.host=psg-net-au.mail.protection.outlook.com
app.mail.from=oversight@psg.net.au
app.mail.to=bailey@psg.net.au
spring.mail.port=25
spring.mail.protocol=smtp
spring.mail.properties.mail.smtp.auth=false
spring.mail.properties.mail.smtp.starttls.enable=false
spring.mail.properties.mail.smtp.connectiontimeout=30000
spring.mail.properties.mail.smtp.timeout=60000
spring.mail.properties.mail.smtp.writetimeout=60000
# Legacy/Logger related
app.logger.level=Info
app.hcaptcha.enabled=false
app.hcaptcha.sitekey=
app.hcaptcha.secret=

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,13 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BcryptTest {
public static void main(String[] args) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12); // 🔹 Make sure this matches DB cost
String plainTextPassword = "testuser";
String storedHash = "$2a$12$8hUCqm9xSHelrm2KO3LXvundybefBGl8GeiOmnqdmyst.V1NFcUQ2"; // Use latest hash
boolean matches = passwordEncoder.matches(plainTextPassword, storedHash);
System.out.println("✅ Password Matches: " + matches);
}
}

View File

@@ -0,0 +1,11 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class GenerateBcryptHash {
public static void main(String[] args) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12);
String plainTextPassword = "testuser";
String bcryptHash = passwordEncoder.encode(plainTextPassword);
System.out.println("🔹 New BCrypt Hash: " + bcryptHash);
}
}