Initial commit
This commit is contained in:
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal 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
70
build.gradle
Normal 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
23
cert3.pem
Normal 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-----
|
||||
22
cert_STAR-PSG-NET-AU/cert2.pem
Normal file
22
cert_STAR-PSG-NET-AU/cert2.pem
Normal 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-----
|
||||
26
cert_STAR-PSG-NET-AU/chain2.pem
Normal file
26
cert_STAR-PSG-NET-AU/chain2.pem
Normal 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-----
|
||||
48
cert_STAR-PSG-NET-AU/fullchain2.pem
Normal file
48
cert_STAR-PSG-NET-AU/fullchain2.pem
Normal 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-----
|
||||
6
cert_STAR-PSG-NET-AU/privkey2.pem
Normal file
6
cert_STAR-PSG-NET-AU/privkey2.pem
Normal 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
26
chain3.pem
Normal 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
112
cve-sync.log
Normal 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
49
fullchain3.pem
Normal 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
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
249
gradlew
vendored
Normal 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
92
gradlew.bat
vendored
Normal 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
702
package-lock.json
generated
Normal 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
19
package.json
Normal 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
6
privkey3.pem
Normal file
@@ -0,0 +1,6 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCxBRzMYQJh/ETrQptv
|
||||
cBEuQ/9PlGZTVbCYt6AEj5ritog9aew0Uxu+rOh46yn9YV+hZANiAASaSwjsEm5Z
|
||||
uQAiC4U2mwnCd94G3axT0LhYb8opylkPQx/mAg+t9CV9t4aSo7M+vR9U66TICIUB
|
||||
5BIGRI+QoqpqskkJqoCiFaCkClpRgP1AMg5E8MkD9Ua7k2Z71QA+vx0=
|
||||
-----END PRIVATE KEY-----
|
||||
1
scripts/.enrichment_resume
Normal file
1
scripts/.enrichment_resume
Normal file
@@ -0,0 +1 @@
|
||||
CVE-1999-0057
|
||||
7
scripts/.env.local
Normal file
7
scripts/.env.local
Normal 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
|
||||
1
scripts/.last_synced_date
Normal file
1
scripts/.last_synced_date
Normal file
@@ -0,0 +1 @@
|
||||
2001-08-20T00:00:00.000Z
|
||||
48
scripts/cve-sync.log
Normal file
48
scripts/cve-sync.log
Normal 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
177
scripts/enrichCVE_MSRC.js
Normal 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
208
scripts/fetchCVE.js
Normal 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
457
scripts/fetchCVE_v2.js
Normal 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();
|
||||
});
|
||||
|
||||
298
scripts/fetchCVE_withMORE.js
Normal file
298
scripts/fetchCVE_withMORE.js
Normal 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
79
scripts/fetchKEV.js
Normal 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
193
scripts/fetchMSRC.js
Normal 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
5
scripts/kev-sync.log
Normal 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
6978
scripts/msrc-sync.log
Normal file
File diff suppressed because it is too large
Load Diff
13
scripts/runSpringSync_Manual.js
Normal file
13
scripts/runSpringSync_Manual.js
Normal 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
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'ld-sysinfo-server'
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
|
||||
|
||||
public enum ActionType {
|
||||
ADD,
|
||||
DELETE
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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") + "]";
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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...
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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); // you’ll 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
89
src/main/resources/application.properties
Normal file
89
src/main/resources/application.properties
Normal 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=
|
||||
|
||||
|
||||
BIN
src/main/resources/keystore.p12
Normal file
BIN
src/main/resources/keystore.p12
Normal file
Binary file not shown.
BIN
src/main/resources/springboot.p12
Normal file
BIN
src/main/resources/springboot.p12
Normal file
Binary file not shown.
BIN
src/main/resources/springboot_OLD.p12
Normal file
BIN
src/main/resources/springboot_OLD.p12
Normal file
Binary file not shown.
13
src/test/java/BcryptTest.java
Normal file
13
src/test/java/BcryptTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
11
src/test/java/GenerateBcryptHash.java
Normal file
11
src/test/java/GenerateBcryptHash.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user