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.
Files changed (112) hide show
  1. package/.dockerignore +20 -0
  2. package/.editorconfig +9 -0
  3. package/.github/workflows/docker.yml +59 -0
  4. package/.github/workflows/gh-pages.yml +23 -0
  5. package/.github/workflows/gh-release.yml +19 -0
  6. package/.github/workflows/test.yml +39 -0
  7. package/.husky/pre-commit +1 -0
  8. package/CHANGELOG.md +18 -0
  9. package/LICENSE +21 -0
  10. package/README.md +2710 -0
  11. package/bin/extra.js +81 -0
  12. package/bin/import.js +438 -0
  13. package/bin/mongodb.js +21 -0
  14. package/bin/reset.js +257 -0
  15. package/bin/upgrade.js +34 -0
  16. package/config/config.default.json +88 -0
  17. package/config/config.schema.json +877 -0
  18. package/config/config.test.json +107 -0
  19. package/config/index.js +77 -0
  20. package/config/setup.js +212 -0
  21. package/depdendencies.png +0 -0
  22. package/docker/.env +1 -0
  23. package/docker/Dockerfile +20 -0
  24. package/docker/README.md +175 -0
  25. package/docker/docker-compose.yml +29 -0
  26. package/docker/docker-entrypoint.sh +8 -0
  27. package/docker/mongo-initdb.d/mongo_setup.sh +22 -0
  28. package/ecosystem.example.json +7 -0
  29. package/errors/index.js +94 -0
  30. package/eslint.config.js +17 -0
  31. package/index.js +10 -0
  32. package/models/annotations.js +13 -0
  33. package/models/concepts.js +12 -0
  34. package/models/concordances.js +12 -0
  35. package/models/index.js +33 -0
  36. package/models/mappings.js +20 -0
  37. package/models/meta.js +21 -0
  38. package/models/registries.js +12 -0
  39. package/models/schemes.js +12 -0
  40. package/package.json +91 -0
  41. package/routes/annotations.js +83 -0
  42. package/routes/common.js +86 -0
  43. package/routes/concepts.js +64 -0
  44. package/routes/concordances.js +86 -0
  45. package/routes/data.js +19 -0
  46. package/routes/mappings.js +108 -0
  47. package/routes/registries.js +24 -0
  48. package/routes/schemes.js +72 -0
  49. package/routes/validate.js +37 -0
  50. package/server.js +190 -0
  51. package/services/abstract.js +328 -0
  52. package/services/annotations.js +237 -0
  53. package/services/concepts.js +459 -0
  54. package/services/concordances.js +264 -0
  55. package/services/data.js +30 -0
  56. package/services/index.js +34 -0
  57. package/services/mappings.js +978 -0
  58. package/services/registries.js +319 -0
  59. package/services/schemes.js +318 -0
  60. package/services/validate.js +39 -0
  61. package/status.schema.json +145 -0
  62. package/test/abstract-service.js +36 -0
  63. package/test/annotations/annotation.json +13 -0
  64. package/test/api.js +2481 -0
  65. package/test/chai.js +14 -0
  66. package/test/changes.js +179 -0
  67. package/test/concepts/conceptNoFileEnding +4 -0
  68. package/test/concepts/concepts-ddc-6-60-61-62.json +123 -0
  69. package/test/concordances/concordances.ndjson +2 -0
  70. package/test/config.js +26 -0
  71. package/test/configs/complex-config.json +90 -0
  72. package/test/configs/empty-object.json +1 -0
  73. package/test/configs/fail-array.json +1 -0
  74. package/test/configs/fail-empty.json +0 -0
  75. package/test/configs/fail-mapping-only-props1.json +5 -0
  76. package/test/configs/fail-mapping-only-props2.json +5 -0
  77. package/test/configs/fail-mapping-only-props3.json +5 -0
  78. package/test/configs/fail-mapping-only-props4.json +5 -0
  79. package/test/configs/fail-nonexisting-prop.json +3 -0
  80. package/test/configs/fail-port-string.json +3 -0
  81. package/test/configs/fail-registry-types.json +16 -0
  82. package/test/configs/registry-types.json +16 -0
  83. package/test/data-write.js +784 -0
  84. package/test/eslint.js +22 -0
  85. package/test/import-reset.js +287 -0
  86. package/test/infer-mappings.js +340 -0
  87. package/test/ipcheck.js +287 -0
  88. package/test/mappings/README.md +1 -0
  89. package/test/mappings/ddc-gnd-1.mapping.json +33 -0
  90. package/test/mappings/ddc-gnd-2.mapping.json +67 -0
  91. package/test/mappings/mapping-ddc-gnd-noScheme.json +145 -0
  92. package/test/mappings/mapping-ddc-gnd.json +175 -0
  93. package/test/mappings/mappings-ddc.json +214 -0
  94. package/test/registries/registries.ndjson +2 -0
  95. package/test/services.js +557 -0
  96. package/test/terminologies/terminologies.json +94 -0
  97. package/test/test-utils.js +182 -0
  98. package/test/utils.js +425 -0
  99. package/test/validate.js +226 -0
  100. package/utils/adjust.js +206 -0
  101. package/utils/auth.js +154 -0
  102. package/utils/changes.js +88 -0
  103. package/utils/db.js +106 -0
  104. package/utils/ipcheck.js +76 -0
  105. package/utils/middleware.js +636 -0
  106. package/utils/searchHelper.js +153 -0
  107. package/utils/status.js +77 -0
  108. package/utils/users.js +7 -0
  109. package/utils/utils.js +114 -0
  110. package/utils/uuid.js +6 -0
  111. package/utils/version.js +324 -0
  112. 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
+
@@ -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
+ }