jskos-server 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +20 -0
- package/.editorconfig +9 -0
- package/.github/workflows/docker.yml +59 -0
- package/.github/workflows/gh-pages.yml +23 -0
- package/.github/workflows/gh-release.yml +19 -0
- package/.github/workflows/test.yml +39 -0
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +2710 -0
- package/bin/extra.js +81 -0
- package/bin/import.js +438 -0
- package/bin/mongodb.js +21 -0
- package/bin/reset.js +257 -0
- package/bin/upgrade.js +34 -0
- package/config/config.default.json +88 -0
- package/config/config.schema.json +877 -0
- package/config/config.test.json +107 -0
- package/config/index.js +77 -0
- package/config/setup.js +212 -0
- package/depdendencies.png +0 -0
- package/docker/.env +1 -0
- package/docker/Dockerfile +20 -0
- package/docker/README.md +175 -0
- package/docker/docker-compose.yml +29 -0
- package/docker/docker-entrypoint.sh +8 -0
- package/docker/mongo-initdb.d/mongo_setup.sh +22 -0
- package/ecosystem.example.json +7 -0
- package/errors/index.js +94 -0
- package/eslint.config.js +17 -0
- package/index.js +10 -0
- package/models/annotations.js +13 -0
- package/models/concepts.js +12 -0
- package/models/concordances.js +12 -0
- package/models/index.js +33 -0
- package/models/mappings.js +20 -0
- package/models/meta.js +21 -0
- package/models/registries.js +12 -0
- package/models/schemes.js +12 -0
- package/package.json +91 -0
- package/routes/annotations.js +83 -0
- package/routes/common.js +86 -0
- package/routes/concepts.js +64 -0
- package/routes/concordances.js +86 -0
- package/routes/data.js +19 -0
- package/routes/mappings.js +108 -0
- package/routes/registries.js +24 -0
- package/routes/schemes.js +72 -0
- package/routes/validate.js +37 -0
- package/server.js +190 -0
- package/services/abstract.js +328 -0
- package/services/annotations.js +237 -0
- package/services/concepts.js +459 -0
- package/services/concordances.js +264 -0
- package/services/data.js +30 -0
- package/services/index.js +34 -0
- package/services/mappings.js +978 -0
- package/services/registries.js +319 -0
- package/services/schemes.js +318 -0
- package/services/validate.js +39 -0
- package/status.schema.json +145 -0
- package/test/abstract-service.js +36 -0
- package/test/annotations/annotation.json +13 -0
- package/test/api.js +2481 -0
- package/test/chai.js +14 -0
- package/test/changes.js +179 -0
- package/test/concepts/conceptNoFileEnding +4 -0
- package/test/concepts/concepts-ddc-6-60-61-62.json +123 -0
- package/test/concordances/concordances.ndjson +2 -0
- package/test/config.js +26 -0
- package/test/configs/complex-config.json +90 -0
- package/test/configs/empty-object.json +1 -0
- package/test/configs/fail-array.json +1 -0
- package/test/configs/fail-empty.json +0 -0
- package/test/configs/fail-mapping-only-props1.json +5 -0
- package/test/configs/fail-mapping-only-props2.json +5 -0
- package/test/configs/fail-mapping-only-props3.json +5 -0
- package/test/configs/fail-mapping-only-props4.json +5 -0
- package/test/configs/fail-nonexisting-prop.json +3 -0
- package/test/configs/fail-port-string.json +3 -0
- package/test/configs/fail-registry-types.json +16 -0
- package/test/configs/registry-types.json +16 -0
- package/test/data-write.js +784 -0
- package/test/eslint.js +22 -0
- package/test/import-reset.js +287 -0
- package/test/infer-mappings.js +340 -0
- package/test/ipcheck.js +287 -0
- package/test/mappings/README.md +1 -0
- package/test/mappings/ddc-gnd-1.mapping.json +33 -0
- package/test/mappings/ddc-gnd-2.mapping.json +67 -0
- package/test/mappings/mapping-ddc-gnd-noScheme.json +145 -0
- package/test/mappings/mapping-ddc-gnd.json +175 -0
- package/test/mappings/mappings-ddc.json +214 -0
- package/test/registries/registries.ndjson +2 -0
- package/test/services.js +557 -0
- package/test/terminologies/terminologies.json +94 -0
- package/test/test-utils.js +182 -0
- package/test/utils.js +425 -0
- package/test/validate.js +226 -0
- package/utils/adjust.js +206 -0
- package/utils/auth.js +154 -0
- package/utils/changes.js +88 -0
- package/utils/db.js +106 -0
- package/utils/ipcheck.js +76 -0
- package/utils/middleware.js +636 -0
- package/utils/searchHelper.js +153 -0
- package/utils/status.js +77 -0
- package/utils/users.js +7 -0
- package/utils/utils.js +114 -0
- package/utils/uuid.js +6 -0
- package/utils/version.js +324 -0
- package/views/base.ejs +172 -0
package/utils/db.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import config from "../config/index.js"
|
|
2
|
+
import { Upgrader } from "../utils/version.js"
|
|
3
|
+
|
|
4
|
+
const upgrader = new Upgrader(config)
|
|
5
|
+
|
|
6
|
+
import mongoose from "mongoose"
|
|
7
|
+
const connection = mongoose.connection
|
|
8
|
+
import { Meta } from "../models/meta.js"
|
|
9
|
+
|
|
10
|
+
// Set mongoose buffering options
|
|
11
|
+
mongoose.set("bufferCommands", true)
|
|
12
|
+
mongoose.set("bufferTimeoutMS", 30000)
|
|
13
|
+
mongoose.set("strictQuery", false)
|
|
14
|
+
|
|
15
|
+
connection.on("connected", () => {
|
|
16
|
+
config.warn("Connected to database")
|
|
17
|
+
})
|
|
18
|
+
const onDisconnected = () => {
|
|
19
|
+
config.warn("Disconnected from database, waiting for automatic reconnect...")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
mongoose,
|
|
24
|
+
connection,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function connect(retry = false) {
|
|
28
|
+
connection.on("disconnected", onDisconnected)
|
|
29
|
+
function addErrorHandler() {
|
|
30
|
+
connection.on("error", (error) => {
|
|
31
|
+
config.error("Database error", error)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
// If retry === false, add error handler before connecting
|
|
35
|
+
!retry && addErrorHandler()
|
|
36
|
+
async function _connect() {
|
|
37
|
+
const mongoUri = process.env.MONGO_URI || `${config.mongo.url}/${config.mongo.db}`
|
|
38
|
+
return await mongoose.connect(mongoUri, config.mongo.options)
|
|
39
|
+
}
|
|
40
|
+
let result
|
|
41
|
+
while (!result) {
|
|
42
|
+
try {
|
|
43
|
+
result = await _connect()
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (!retry) {
|
|
46
|
+
throw error
|
|
47
|
+
}
|
|
48
|
+
config.error(error)
|
|
49
|
+
}
|
|
50
|
+
if (!result) {
|
|
51
|
+
config.error("Error connecting to database, trying again in 10 seconds...")
|
|
52
|
+
await new Promise(resolve => setTimeout(resolve, 10000))
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// If retry === true, add error handler after connecting
|
|
56
|
+
retry && addErrorHandler()
|
|
57
|
+
let collections, meta
|
|
58
|
+
try {
|
|
59
|
+
// Check meta collection whether upgrade script is necessary.
|
|
60
|
+
collections = (await connection.db.listCollections().toArray()).map(c => c.name)
|
|
61
|
+
if (!collections.length) {
|
|
62
|
+
// no collections = first launch of jskos-server
|
|
63
|
+
meta = new Meta({ version: config.serverVersion })
|
|
64
|
+
await meta.save()
|
|
65
|
+
} else if (!collections.includes("meta")) {
|
|
66
|
+
// meta does not exist = upgrade from <= 1.1.9
|
|
67
|
+
meta = new Meta({ version: "1.1.9" })
|
|
68
|
+
await meta.save()
|
|
69
|
+
} else {
|
|
70
|
+
// get version from meta
|
|
71
|
+
meta = await Meta.findOne()
|
|
72
|
+
}
|
|
73
|
+
if (meta && upgrader.getUpgrades(meta.version).length) {
|
|
74
|
+
console.warn("Info: jskos-server was updated. Please run \"npm run upgrade\" to perform necessary upgrades to ensure full functionalities of all features.")
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// do nothing
|
|
78
|
+
}
|
|
79
|
+
return result
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function disconnect() {
|
|
83
|
+
connection.removeListener("disconnected", onDisconnected)
|
|
84
|
+
config.log("Disconnected from database (on purpose)")
|
|
85
|
+
return mongoose.disconnect()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Waits for the MongoDB replica set to become available
|
|
90
|
+
* by retrying replSetGetStatus until success or timeout.
|
|
91
|
+
*/
|
|
92
|
+
export async function waitForReplicaSet({ retries = 10, interval = 3000 } = {}) {
|
|
93
|
+
for (let i = 0; i < retries; i++) {
|
|
94
|
+
try {
|
|
95
|
+
await connection.db.admin().command({ replSetGetStatus: 1 })
|
|
96
|
+
return true
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.log(
|
|
99
|
+
`Replica set not yet ready (attempt ${i + 1}/${retries}), retrying in ${interval}ms...`,
|
|
100
|
+
)
|
|
101
|
+
await new Promise(resolve => setTimeout(resolve, interval))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
package/utils/ipcheck.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import ipaddr from "ipaddr.js"
|
|
2
|
+
import { ForbiddenAccessError, ConfigurationError } from "../errors/index.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Middleware to check IP whitelists if configured.
|
|
6
|
+
*/
|
|
7
|
+
export const ipcheck = config => {
|
|
8
|
+
return (req, res, next) => {
|
|
9
|
+
|
|
10
|
+
// Determine IP whitelist from config
|
|
11
|
+
let ips, action
|
|
12
|
+
if (req.method == "GET") {
|
|
13
|
+
action = "read"
|
|
14
|
+
}
|
|
15
|
+
if (req.method == "POST") {
|
|
16
|
+
action = "create"
|
|
17
|
+
}
|
|
18
|
+
if (req.method == "PUT" || req.method == "PATCH") {
|
|
19
|
+
action = "update"
|
|
20
|
+
}
|
|
21
|
+
if (req.method == "DELETE") {
|
|
22
|
+
action = "delete"
|
|
23
|
+
}
|
|
24
|
+
if (action && config[req.type] && config[req.type][action]) {
|
|
25
|
+
ips = config[req.type][action].ips
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (ips && ips.length) {
|
|
29
|
+
// Determine client's IP address
|
|
30
|
+
let ip
|
|
31
|
+
try {
|
|
32
|
+
ip = ipaddr.parse(req.ip)
|
|
33
|
+
if (ip.kind() == "ipv6") {
|
|
34
|
+
if (ip.range() == "loopback") {
|
|
35
|
+
// Set IPv6 loopback addresses to 127.0.0.1
|
|
36
|
+
ip = ipaddr.parse("127.0.0.1")
|
|
37
|
+
} else {
|
|
38
|
+
// Convert to IPv4 address
|
|
39
|
+
ip = ip.toIPv4Address()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch(error) {
|
|
43
|
+
config.warn(`Could not determine client's address for IP ${ip && ip.toString && ip.toString()}.`)
|
|
44
|
+
next(new ForbiddenAccessError("Access forbidden. An IP filter is in place, but the client's address could not be determined."))
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
// Convert IP whitelist to ranges
|
|
48
|
+
try {
|
|
49
|
+
ips = ips.map(ip => {
|
|
50
|
+
if (ip.includes("/")) {
|
|
51
|
+
// Parse CIDR ranges
|
|
52
|
+
return ipaddr.parseCIDR(ip)
|
|
53
|
+
} else {
|
|
54
|
+
// Parse normal IP address
|
|
55
|
+
return [ipaddr.parse(ip), 32]
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
} catch(error) {
|
|
59
|
+
config.error("Error: Invalid IP address in config:", ips, `=> Currently denying all requests to ${action} ${req.type}.`)
|
|
60
|
+
next(new ConfigurationError())
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
for (let range of ips) {
|
|
64
|
+
if (ip.match(range)) {
|
|
65
|
+
// IP is on the list
|
|
66
|
+
next()
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
next(new ForbiddenAccessError("Access forbidden. An IP filter is in place, but the client is not on that list."))
|
|
71
|
+
} else {
|
|
72
|
+
next()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
}
|