@openneuro/server 4.20.5 → 4.20.6-alpha.3
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/package.json +4 -6
- package/src/__mocks__/{config.js → config.ts} +5 -5
- package/src/app.ts +32 -31
- package/src/cache/item.ts +6 -7
- package/src/cache/types.ts +8 -8
- package/src/{config.js → config.ts} +6 -6
- package/src/datalad/__tests__/changelog.spec.ts +83 -0
- package/src/datalad/__tests__/dataset.spec.ts +109 -0
- package/src/datalad/__tests__/description.spec.ts +141 -0
- package/src/datalad/__tests__/files.spec.ts +77 -0
- package/src/datalad/__tests__/pagination.spec.ts +136 -0
- package/src/datalad/__tests__/{snapshots.spec.js → snapshots.spec.ts} +17 -17
- package/src/datalad/{analytics.js → analytics.ts} +4 -4
- package/src/datalad/{changelog.js → changelog.ts} +17 -14
- package/src/datalad/{dataset.js → dataset.ts} +95 -93
- package/src/datalad/{description.js → description.ts} +37 -37
- package/src/datalad/draft.ts +38 -0
- package/src/datalad/files.ts +26 -20
- package/src/datalad/{pagination.js → pagination.ts} +47 -47
- package/src/datalad/{readme.js → readme.ts} +13 -11
- package/src/datalad/{reexporter.js → reexporter.ts} +4 -4
- package/src/datalad/{snapshots.js → snapshots.ts} +56 -62
- package/src/datalad/{upload.js → upload.ts} +7 -5
- package/src/elasticsearch/elastic-client.ts +11 -0
- package/src/elasticsearch/reindex-dataset.ts +7 -7
- package/src/graphql/__tests__/__snapshots__/permissions.spec.ts.snap +5 -0
- package/src/graphql/__tests__/{comment.spec.js → comment.spec.ts} +17 -17
- package/src/graphql/__tests__/permissions.spec.ts +113 -0
- package/src/graphql/{permissions.js → permissions.ts} +14 -14
- package/src/graphql/resolvers/__tests__/brainlife.spec.ts +11 -11
- package/src/graphql/resolvers/__tests__/{dataset-search.spec.js → dataset-search.spec.ts} +25 -23
- package/src/graphql/resolvers/__tests__/dataset.spec.ts +175 -0
- package/src/graphql/resolvers/__tests__/derivatives.spec.ts +19 -19
- package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +20 -20
- package/src/graphql/resolvers/__tests__/permssions.spec.ts +35 -0
- package/src/graphql/resolvers/__tests__/snapshots.spec.ts +59 -0
- package/src/graphql/resolvers/__tests__/user.spec.ts +18 -0
- package/src/graphql/resolvers/brainlife.ts +4 -4
- package/src/graphql/resolvers/cache.ts +4 -4
- package/src/graphql/resolvers/{comment.js → comment.ts} +16 -16
- package/src/graphql/resolvers/{dataset-search.js → dataset-search.ts} +45 -43
- package/src/graphql/resolvers/{dataset.js → dataset.ts} +38 -52
- package/src/graphql/resolvers/datasetType.ts +3 -3
- package/src/graphql/resolvers/derivatives.ts +11 -11
- package/src/graphql/resolvers/description.ts +18 -0
- package/src/graphql/resolvers/{draft.js → draft.ts} +13 -13
- package/src/graphql/resolvers/{flaggedFiles.js → flaggedFiles.ts} +4 -4
- package/src/graphql/resolvers/{follow.js → follow.ts} +1 -1
- package/src/graphql/resolvers/git.ts +3 -3
- package/src/graphql/resolvers/history.ts +13 -0
- package/src/graphql/resolvers/importRemoteDataset.ts +12 -11
- package/src/graphql/resolvers/index.ts +25 -0
- package/src/graphql/resolvers/{issues.js → issues.ts} +9 -9
- package/src/graphql/resolvers/metadata.ts +8 -8
- package/src/graphql/resolvers/{mutation.js → mutation.ts} +26 -26
- package/src/graphql/resolvers/{newsletter.js → newsletter.ts} +2 -2
- package/src/graphql/resolvers/permissions.ts +15 -21
- package/src/graphql/resolvers/publish.ts +17 -0
- package/src/graphql/resolvers/query.ts +21 -0
- package/src/graphql/resolvers/{readme.js → readme.ts} +3 -3
- package/src/graphql/resolvers/{reexporter.js → reexporter.ts} +2 -2
- package/src/graphql/resolvers/relation.ts +5 -5
- package/src/graphql/resolvers/{reset.js → reset.ts} +2 -2
- package/src/graphql/resolvers/reviewer.ts +4 -4
- package/src/graphql/resolvers/{snapshots.js → snapshots.ts} +49 -49
- package/src/graphql/resolvers/{stars.js → stars.ts} +1 -1
- package/src/graphql/resolvers/summary.ts +3 -3
- package/src/graphql/resolvers/{upload.js → upload.ts} +5 -5
- package/src/graphql/resolvers/{user.js → user.ts} +16 -18
- package/src/graphql/resolvers/{validation.js → validation.ts} +12 -14
- package/src/graphql/{schema.js → schema.ts} +4 -6
- package/src/graphql/utils/{file.js → file.ts} +2 -2
- package/src/handlers/{comments.js → comments.ts} +11 -11
- package/src/handlers/{config.js → config.ts} +1 -1
- package/src/handlers/{datalad.js → datalad.ts} +22 -22
- package/src/handlers/{doi.js → doi.ts} +6 -6
- package/src/handlers/reviewer.ts +6 -6
- package/src/handlers/{sitemap.js → sitemap.ts} +19 -19
- package/src/handlers/stars.ts +11 -10
- package/src/handlers/{subscriptions.js → subscriptions.ts} +17 -16
- package/src/handlers/{users.js → users.ts} +3 -3
- package/src/libs/__tests__/apikey.spec.ts +25 -0
- package/src/libs/__tests__/datalad-service.spec.ts +27 -0
- package/src/libs/__tests__/{dataset.spec.js → dataset.spec.ts} +9 -9
- package/src/libs/{apikey.js → apikey.ts} +5 -5
- package/src/libs/authentication/__tests__/jwt.spec.ts +59 -0
- package/src/libs/authentication/{crypto.js → crypto.ts} +16 -16
- package/src/libs/authentication/google.ts +18 -0
- package/src/libs/authentication/jwt.ts +40 -33
- package/src/libs/authentication/{orcid.js → orcid.ts} +11 -11
- package/src/libs/authentication/{passport.js → passport.ts} +45 -30
- package/src/libs/authentication/{states.js → states.ts} +17 -20
- package/src/libs/{counter.js → counter.ts} +1 -1
- package/src/libs/{datalad-service.js → datalad-service.ts} +4 -4
- package/src/libs/dataset.ts +9 -0
- package/src/libs/doi/__tests__/__snapshots__/doi.spec.ts.snap +17 -0
- package/src/libs/doi/__tests__/doi.spec.ts +25 -0
- package/src/libs/doi/__tests__/normalize.spec.ts +19 -19
- package/src/libs/doi/{index.js → index.ts} +27 -21
- package/src/libs/doi/normalize.ts +2 -2
- package/src/libs/email/__tests__/index.spec.ts +14 -14
- package/src/libs/email/index.ts +4 -4
- package/src/libs/email/templates/__tests__/comment-created.spec.ts +12 -12
- package/src/libs/email/templates/__tests__/dataset-deleted.spec.ts +6 -6
- package/src/libs/email/templates/__tests__/owner-unsubscribed.spec.ts +6 -6
- package/src/libs/email/templates/__tests__/snapshot-created.spec.ts +9 -9
- package/src/libs/email/templates/__tests__/snapshot-reminder.spec.ts +7 -7
- package/src/libs/email/templates/comment-created.ts +2 -1
- package/src/libs/email/templates/dataset-deleted.ts +2 -1
- package/src/libs/email/templates/dataset-import-failed.ts +2 -1
- package/src/libs/email/templates/dataset-imported.ts +2 -1
- package/src/libs/email/templates/owner-unsubscribed.ts +2 -1
- package/src/libs/email/templates/snapshot-created.ts +2 -1
- package/src/libs/email/templates/snapshot-reminder.ts +2 -1
- package/src/libs/{notifications.js → notifications.ts} +100 -113
- package/src/libs/{orcid.js → orcid.ts} +20 -20
- package/src/libs/{redis.js → redis.ts} +6 -6
- package/src/models/__tests__/ingestDataset.spec.ts +15 -15
- package/src/models/analytics.ts +2 -2
- package/src/models/badAnnexObject.ts +6 -6
- package/src/models/comment.ts +10 -10
- package/src/models/counter.ts +2 -2
- package/src/models/dataset.ts +16 -16
- package/src/models/deletion.ts +3 -3
- package/src/models/deprecatedSnapshot.ts +2 -2
- package/src/models/doi.ts +2 -2
- package/src/models/file.ts +2 -2
- package/src/models/ingestDataset.ts +4 -4
- package/src/models/issue.ts +2 -2
- package/src/models/key.ts +2 -2
- package/src/models/mailgunIdentifier.ts +2 -2
- package/src/models/metadata.ts +3 -3
- package/src/models/newsletter.ts +3 -3
- package/src/models/notification.ts +2 -2
- package/src/models/permission.ts +4 -4
- package/src/models/reviewer.ts +7 -7
- package/src/models/snapshot.ts +2 -2
- package/src/models/stars.ts +6 -6
- package/src/models/subscription.ts +2 -2
- package/src/models/summary.ts +2 -2
- package/src/models/upload.ts +3 -3
- package/src/models/user.ts +4 -4
- package/src/{routes.js → routes.ts} +62 -62
- package/src/server.ts +9 -9
- package/src/utils/__tests__/datasetOrSnapshot.spec.ts +25 -25
- package/src/utils/__tests__/validateUrl.spec.ts +10 -10
- package/src/utils/datasetOrSnapshot.ts +2 -2
- package/src/utils/validateUrl.ts +1 -1
- package/src/datalad/__tests__/changelog.spec.js +0 -82
- package/src/datalad/__tests__/dataset.spec.js +0 -109
- package/src/datalad/__tests__/description.spec.js +0 -137
- package/src/datalad/__tests__/files.spec.js +0 -75
- package/src/datalad/__tests__/pagination.spec.js +0 -136
- package/src/datalad/draft.js +0 -37
- package/src/elasticsearch/elastic-client.js +0 -11
- package/src/graphql/__tests__/permissions.spec.js +0 -107
- package/src/graphql/pubsub.js +0 -5
- package/src/graphql/resolvers/__tests__/dataset.spec.js +0 -175
- package/src/graphql/resolvers/__tests__/permssions.spec.js +0 -34
- package/src/graphql/resolvers/__tests__/snapshots.spec.js +0 -58
- package/src/graphql/resolvers/__tests__/user.spec.js +0 -17
- package/src/graphql/resolvers/description.js +0 -29
- package/src/graphql/resolvers/history.js +0 -11
- package/src/graphql/resolvers/index.js +0 -25
- package/src/graphql/resolvers/publish.js +0 -17
- package/src/graphql/resolvers/query.js +0 -21
- package/src/graphql/resolvers/subscriptions.js +0 -81
- package/src/graphql/utils/publish-draft-update.js +0 -13
- package/src/libs/__tests__/apikey.spec.js +0 -24
- package/src/libs/__tests__/datalad-service.spec.js +0 -26
- package/src/libs/authentication/__tests__/jwt.spec.js +0 -23
- package/src/libs/authentication/globus.js +0 -11
- package/src/libs/authentication/google.js +0 -19
- package/src/libs/bidsId.js +0 -68
- package/src/libs/dataset.js +0 -9
- package/src/libs/doi/__tests__/doi.spec.js +0 -24
- package/src/libs/redis-pubsub.js +0 -5
- package/src/libs/request.js +0 -155
- package/src/libs/scitran.js +0 -25
- package/src/libs/subscription-server.js +0 -20
- package/src/libs/testing-utils.js +0 -17
- package/src/persistent/datasets/.gitignore +0 -3
- package/src/persistent/temp/.gitignore +0 -3
- /package/src/libs/__mocks__/{notifications.js → notifications.ts} +0 -0
- /package/src/libs/authentication/{verifyUser.js → verifyUser.ts} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import request from
|
|
2
|
-
import { Readable } from
|
|
3
|
-
import mime from
|
|
4
|
-
import { getFiles } from
|
|
5
|
-
import { getDatasetWorker } from
|
|
1
|
+
import request from "superagent"
|
|
2
|
+
import { Readable } from "node:stream"
|
|
3
|
+
import mime from "mime-types"
|
|
4
|
+
import { getFiles } from "../datalad/files"
|
|
5
|
+
import { getDatasetWorker } from "../libs/datalad-service"
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Handlers for datalad dataset manipulation
|
|
@@ -20,39 +20,39 @@ export const getFile = async (req, res) => {
|
|
|
20
20
|
const { datasetId, snapshotId, filename } = req.params
|
|
21
21
|
const worker = getDatasetWorker(datasetId)
|
|
22
22
|
// Find the right tree
|
|
23
|
-
const pathComponents = filename.split(
|
|
24
|
-
let tree = snapshotId ||
|
|
23
|
+
const pathComponents = filename.split(":")
|
|
24
|
+
let tree = snapshotId || "HEAD"
|
|
25
25
|
let file
|
|
26
26
|
for (const level of pathComponents) {
|
|
27
27
|
const files = await getFiles(datasetId, tree)
|
|
28
28
|
if (level == pathComponents.slice(-1)) {
|
|
29
|
-
file = files.find(f => !f.directory && f.filename === level)
|
|
29
|
+
file = files.find((f) => !f.directory && f.filename === level)
|
|
30
30
|
} else {
|
|
31
|
-
tree = files.find(f => f.directory && f.filename === level).id
|
|
31
|
+
tree = files.find((f) => f.directory && f.filename === level).id
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
// Get the file URL and redirect if external or serve if local
|
|
35
|
-
if (file && file.urls[0].startsWith(
|
|
35
|
+
if (file && file.urls[0].startsWith("https://s3.amazonaws.com/")) {
|
|
36
36
|
res.redirect(file.urls[0])
|
|
37
37
|
} else {
|
|
38
38
|
// Serve the file directly
|
|
39
|
-
res.set(
|
|
39
|
+
res.set("Content-Type", mime.lookup(filename) || "application/octet-stream")
|
|
40
40
|
const uri = snapshotId
|
|
41
41
|
? `http://${worker}/datasets/${datasetId}/snapshots/${snapshotId}/files/${filename}`
|
|
42
42
|
: `http://${worker}/datasets/${datasetId}/files/${filename}`
|
|
43
43
|
return fetch(uri)
|
|
44
|
-
.then(r => {
|
|
44
|
+
.then((r) => {
|
|
45
45
|
// Set the content length (allow clients to catch HTTP issues better)
|
|
46
|
-
res.setHeader(
|
|
46
|
+
res.setHeader("Content-Length", Number(r.headers.get("content-length")))
|
|
47
47
|
return r.body
|
|
48
48
|
})
|
|
49
|
-
.then(stream =>
|
|
49
|
+
.then((stream) =>
|
|
50
50
|
// @ts-expect-error
|
|
51
|
-
Readable.fromWeb(stream, { highWaterMark: 4194304 }).pipe(res)
|
|
51
|
+
Readable.fromWeb(stream, { highWaterMark: 4194304 }).pipe(res)
|
|
52
52
|
)
|
|
53
|
-
.catch(err => {
|
|
53
|
+
.catch((err) => {
|
|
54
54
|
console.error(err)
|
|
55
|
-
res.status(500).send(
|
|
55
|
+
res.status(500).send("Internal error transferring requested file")
|
|
56
56
|
})
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -66,16 +66,16 @@ export const getObject = (req, res) => {
|
|
|
66
66
|
// Backend depends on git object or git-annex key
|
|
67
67
|
if (key.length === 40) {
|
|
68
68
|
const uri = `${worker}/datasets/${datasetId}/objects/${key}`
|
|
69
|
-
res.set(
|
|
69
|
+
res.set("Content-Type", "application/octet-stream")
|
|
70
70
|
return request.get(uri).pipe(res)
|
|
71
|
-
} else if (key.startsWith(
|
|
71
|
+
} else if (key.startsWith("SHA256E-") || key.startsWith("MD5E-")) {
|
|
72
72
|
const uri = `${worker}/datasets/${datasetId}/annex/${key}`
|
|
73
|
-
res.set(
|
|
73
|
+
res.set("Content-Type", "application/octet-stream")
|
|
74
74
|
return request.get(uri).pipe(res)
|
|
75
75
|
} else {
|
|
76
|
-
res.set(
|
|
76
|
+
res.set("Content-Type", "application/json")
|
|
77
77
|
res.status(400).send({
|
|
78
|
-
error:
|
|
78
|
+
error: "Key must be a git object hash or git-annex key",
|
|
79
79
|
})
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import config from
|
|
2
|
-
import doi from
|
|
3
|
-
import Doi from
|
|
4
|
-
import Snapshot from
|
|
1
|
+
import config from "../config"
|
|
2
|
+
import doi from "../libs/doi"
|
|
3
|
+
import Doi from "../models/doi"
|
|
4
|
+
import Snapshot from "../models/snapshot"
|
|
5
5
|
|
|
6
6
|
export async function createSnapshotDoi(req, res) {
|
|
7
7
|
let doiRes = null
|
|
@@ -28,7 +28,7 @@ export async function createSnapshotDoi(req, res) {
|
|
|
28
28
|
}
|
|
29
29
|
await doi
|
|
30
30
|
.registerSnapshotDoi(datasetId, snapshotId, oldDesc)
|
|
31
|
-
.then(doiRes => {
|
|
31
|
+
.then((doiRes) => {
|
|
32
32
|
if (doiRes) {
|
|
33
33
|
Doi.updateOne(
|
|
34
34
|
{ datasetId: datasetId, snapshotId: snapshotId },
|
|
@@ -51,7 +51,7 @@ export async function getDoi(req, res) {
|
|
|
51
51
|
datasetId: datasetId,
|
|
52
52
|
snapshotId: snapshotId,
|
|
53
53
|
},
|
|
54
|
-
|
|
54
|
+
"doi",
|
|
55
55
|
).exec()
|
|
56
56
|
return res.send(doi)
|
|
57
57
|
}
|
package/src/handlers/reviewer.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import Reviewer from
|
|
2
|
-
import { decodeJWT } from
|
|
1
|
+
import Reviewer from "../models/reviewer"
|
|
2
|
+
import { decodeJWT } from "../libs/authentication/jwt"
|
|
3
3
|
|
|
4
4
|
export const reviewerHandler = async (req, res, next) => {
|
|
5
5
|
try {
|
|
6
6
|
const token = req.params.token
|
|
7
7
|
const decodedToken = decodeJWT(token)
|
|
8
|
-
if (decodedToken?.scopes.includes(
|
|
8
|
+
if (decodedToken?.scopes.includes("dataset:reviewer")) {
|
|
9
9
|
const reviewer = await Reviewer.exists({
|
|
10
10
|
id: decodedToken.sub,
|
|
11
11
|
datasetId: decodedToken.dataset,
|
|
12
12
|
})
|
|
13
13
|
if (reviewer) {
|
|
14
14
|
res
|
|
15
|
-
.cookie(
|
|
15
|
+
.cookie("accessToken", token)
|
|
16
16
|
.redirect(`/datasets/${decodedToken.dataset}`)
|
|
17
17
|
} else {
|
|
18
|
-
throw Error(
|
|
18
|
+
throw Error("Review token not valid")
|
|
19
19
|
}
|
|
20
20
|
} else {
|
|
21
|
-
throw Error(
|
|
21
|
+
throw Error("Invalid token scope for reviewer")
|
|
22
22
|
}
|
|
23
23
|
} catch (err) {
|
|
24
24
|
res.status(401)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import sitemap from
|
|
2
|
-
import config from
|
|
3
|
-
import Dataset from
|
|
1
|
+
import sitemap from "sitemap"
|
|
2
|
+
import config from "../config"
|
|
3
|
+
import Dataset from "../models/dataset"
|
|
4
4
|
|
|
5
5
|
// Static URLs - manual for now, could be generated from routes
|
|
6
6
|
export const sitemapStaticUrls = () => [
|
|
7
|
-
{ url:
|
|
8
|
-
{ url:
|
|
9
|
-
{ url:
|
|
10
|
-
{ url:
|
|
11
|
-
{ url:
|
|
12
|
-
{ url:
|
|
7
|
+
{ url: "/", priority: 0.9, changefreq: "weekly" },
|
|
8
|
+
{ url: "/public/datasets", priority: 1.0, changefreq: "daily" },
|
|
9
|
+
{ url: "/faq", priority: 0.6, changefreq: "monthly" },
|
|
10
|
+
{ url: "/pet", priority: 0.5, changefreq: "monthly" },
|
|
11
|
+
{ url: "/public/jobs", priority: 0.5, changefreq: "monthly" },
|
|
12
|
+
{ url: "/crn/graphql", priority: 0.3 },
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
// URLs generated from site data
|
|
@@ -18,25 +18,25 @@ export const sitemapDynamicUrls = () =>
|
|
|
18
18
|
{ $match: { public: true } },
|
|
19
19
|
{
|
|
20
20
|
$lookup: {
|
|
21
|
-
from:
|
|
22
|
-
localField:
|
|
23
|
-
foreignField:
|
|
24
|
-
as:
|
|
21
|
+
from: "snapshots",
|
|
22
|
+
localField: "id",
|
|
23
|
+
foreignField: "datasetId",
|
|
24
|
+
as: "snapshots",
|
|
25
25
|
},
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
$unwind:
|
|
28
|
+
$unwind: "$snapshots",
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
$project: {
|
|
32
32
|
url: {
|
|
33
|
-
$concat: [
|
|
33
|
+
$concat: ["/datasets/", "$id", "/versions/", "$snapshots.tag"],
|
|
34
34
|
},
|
|
35
35
|
lastmodISO: {
|
|
36
|
-
$dateToString: { date:
|
|
36
|
+
$dateToString: { date: "$snapshots.created" },
|
|
37
37
|
},
|
|
38
38
|
priority: 0.8,
|
|
39
|
-
changefreq:
|
|
39
|
+
changefreq: "daily", // To make sure new comments are indexed
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
42
|
]).exec()
|
|
@@ -50,14 +50,14 @@ export const sitemapFactory = async () => {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export const sitemapHandler = (req, res) => {
|
|
53
|
-
sitemapFactory().then(sm => {
|
|
53
|
+
sitemapFactory().then((sm) => {
|
|
54
54
|
sm.toXML((err, xml) => {
|
|
55
55
|
if (err) {
|
|
56
56
|
// eslint-disable-next-line no-console
|
|
57
57
|
console.error(err)
|
|
58
58
|
return res.status(500).end()
|
|
59
59
|
}
|
|
60
|
-
res.header(
|
|
60
|
+
res.header("Content-Type", "application/xml")
|
|
61
61
|
res.send(xml)
|
|
62
62
|
})
|
|
63
63
|
})
|
package/src/handlers/stars.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// dependencies ------------------------------------------------------------
|
|
2
|
-
import Star from
|
|
2
|
+
import Star from "../models/stars"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Stars
|
|
@@ -22,10 +22,10 @@ export default {
|
|
|
22
22
|
datasetId: datasetId,
|
|
23
23
|
userId: userId,
|
|
24
24
|
})
|
|
25
|
-
.then(response => {
|
|
25
|
+
.then((response) => {
|
|
26
26
|
return res.send(response.$op)
|
|
27
27
|
})
|
|
28
|
-
.catch(err => {
|
|
28
|
+
.catch((err) => {
|
|
29
29
|
next(err)
|
|
30
30
|
})
|
|
31
31
|
},
|
|
@@ -46,7 +46,7 @@ export default {
|
|
|
46
46
|
Star.deleteOne({
|
|
47
47
|
datasetId: datasetId,
|
|
48
48
|
userId: userId,
|
|
49
|
-
}).catch(err => {
|
|
49
|
+
}).catch((err) => {
|
|
50
50
|
if (err) {
|
|
51
51
|
return next(err)
|
|
52
52
|
} else {
|
|
@@ -64,26 +64,27 @@ export default {
|
|
|
64
64
|
*/
|
|
65
65
|
|
|
66
66
|
getStars(req, res, next) {
|
|
67
|
-
const datasetId =
|
|
68
|
-
|
|
67
|
+
const datasetId = req.params.datasetId === "undefined"
|
|
68
|
+
? null
|
|
69
|
+
: req.params.datasetId
|
|
69
70
|
if (datasetId) {
|
|
70
71
|
Star.find({
|
|
71
72
|
datasetId: datasetId,
|
|
72
73
|
})
|
|
73
74
|
.exec()
|
|
74
|
-
.then(stars => {
|
|
75
|
+
.then((stars) => {
|
|
75
76
|
res.send(stars)
|
|
76
77
|
})
|
|
77
|
-
.catch(err => {
|
|
78
|
+
.catch((err) => {
|
|
78
79
|
return next(err)
|
|
79
80
|
})
|
|
80
81
|
} else {
|
|
81
82
|
Star.find()
|
|
82
83
|
.exec()
|
|
83
|
-
.then(stars => {
|
|
84
|
+
.then((stars) => {
|
|
84
85
|
res.send(stars)
|
|
85
86
|
})
|
|
86
|
-
.catch(err => {
|
|
87
|
+
.catch((err) => {
|
|
87
88
|
return next(err)
|
|
88
89
|
})
|
|
89
90
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import notifications from
|
|
2
|
-
import Subscription from
|
|
3
|
-
import mongoose from
|
|
1
|
+
import notifications from "../libs/notifications"
|
|
2
|
+
import Subscription from "../models/subscription"
|
|
3
|
+
import mongoose from "mongoose"
|
|
4
4
|
const ObjectID = mongoose.Schema.Types.ObjectId
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -24,8 +24,8 @@ export const create = (req, res, next) => {
|
|
|
24
24
|
const userId = data.userId
|
|
25
25
|
|
|
26
26
|
subscribe(datasetId, userId)
|
|
27
|
-
.then(response => res.send(response.$op))
|
|
28
|
-
.catch(err => next(err))
|
|
27
|
+
.then((response) => res.send(response.$op))
|
|
28
|
+
.catch((err) => next(err))
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -43,7 +43,7 @@ export const deleteSubscription = (req, res, next) => {
|
|
|
43
43
|
Subscription.deleteOne({
|
|
44
44
|
datasetId: datasetId,
|
|
45
45
|
userId: userId,
|
|
46
|
-
}).catch(err => {
|
|
46
|
+
}).catch((err) => {
|
|
47
47
|
if (err) {
|
|
48
48
|
return next(err)
|
|
49
49
|
} else {
|
|
@@ -61,15 +61,15 @@ export const deleteAll = (req, res, next) => {
|
|
|
61
61
|
datasetId: datasetId,
|
|
62
62
|
})
|
|
63
63
|
.exec()
|
|
64
|
-
.then(subscriptions => {
|
|
65
|
-
subscriptions.forEach(subscription => {
|
|
64
|
+
.then((subscriptions) => {
|
|
65
|
+
subscriptions.forEach((subscription) => {
|
|
66
66
|
Subscription.deleteOne({
|
|
67
67
|
_id: new ObjectID(subscription._id),
|
|
68
68
|
})
|
|
69
69
|
})
|
|
70
70
|
return res.send()
|
|
71
71
|
})
|
|
72
|
-
.catch(err => {
|
|
72
|
+
.catch((err) => {
|
|
73
73
|
if (err) {
|
|
74
74
|
return next(err)
|
|
75
75
|
}
|
|
@@ -84,26 +84,27 @@ export const deleteAll = (req, res, next) => {
|
|
|
84
84
|
* Returns a list of subscriptions that are associated with a dataset
|
|
85
85
|
*/
|
|
86
86
|
export const getSubscriptions = (req, res, next) => {
|
|
87
|
-
const datasetId =
|
|
88
|
-
|
|
87
|
+
const datasetId = req.params.datasetId === "undefined"
|
|
88
|
+
? null
|
|
89
|
+
: req.params.datasetId
|
|
89
90
|
if (datasetId) {
|
|
90
91
|
Subscription.find({
|
|
91
92
|
datasetId: datasetId,
|
|
92
93
|
})
|
|
93
94
|
.exec()
|
|
94
|
-
.then(subscriptions => {
|
|
95
|
+
.then((subscriptions) => {
|
|
95
96
|
res.send(subscriptions)
|
|
96
97
|
})
|
|
97
|
-
.catch(err => {
|
|
98
|
+
.catch((err) => {
|
|
98
99
|
return next(err)
|
|
99
100
|
})
|
|
100
101
|
} else {
|
|
101
102
|
Subscription.find()
|
|
102
103
|
.exec()
|
|
103
|
-
.then(subscriptions => {
|
|
104
|
+
.then((subscriptions) => {
|
|
104
105
|
res.send(subscriptions)
|
|
105
106
|
})
|
|
106
|
-
.catch(err => {
|
|
107
|
+
.catch((err) => {
|
|
107
108
|
return next(err)
|
|
108
109
|
})
|
|
109
110
|
}
|
|
@@ -123,7 +124,7 @@ export const checkUserSubscription = (req, res) => {
|
|
|
123
124
|
userId: userId,
|
|
124
125
|
})
|
|
125
126
|
.exec()
|
|
126
|
-
.then(resp => {
|
|
127
|
+
.then((resp) => {
|
|
127
128
|
if (resp) {
|
|
128
129
|
return res.send({ subscribed: true })
|
|
129
130
|
} else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// dependencies ------------------------------------------------------------
|
|
2
|
-
import { generateApiKey } from
|
|
2
|
+
import { generateApiKey } from "../libs/apikey"
|
|
3
3
|
|
|
4
4
|
// handlers ----------------------------------------------------------------
|
|
5
5
|
|
|
@@ -10,6 +10,6 @@ import { generateApiKey } from '../libs/apikey'
|
|
|
10
10
|
*/
|
|
11
11
|
export function createAPIKey(req, res, next) {
|
|
12
12
|
generateApiKey(req.user)
|
|
13
|
-
.then(key => res.send(key))
|
|
14
|
-
.catch(err => next(err))
|
|
13
|
+
.then((key) => res.send(key))
|
|
14
|
+
.catch((err) => next(err))
|
|
15
15
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { vi } from "vitest"
|
|
2
|
+
import jwt from "jsonwebtoken"
|
|
3
|
+
import { apiKeyFactory } from "../apikey.js"
|
|
4
|
+
import config from "../../config"
|
|
5
|
+
|
|
6
|
+
vi.mock("ioredis")
|
|
7
|
+
vi.mock("../../config.ts")
|
|
8
|
+
|
|
9
|
+
const userMock = {
|
|
10
|
+
id: "1337",
|
|
11
|
+
name: "Total Poser",
|
|
12
|
+
email: "porkchopsandwich@gijoe.com",
|
|
13
|
+
admin: true,
|
|
14
|
+
provider: "google",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("util/apikey.js", () => {
|
|
18
|
+
describe("apiKeyFactory", () => {
|
|
19
|
+
it("produces a valid JWT", () => {
|
|
20
|
+
const token = jwt.verify(apiKeyFactory(userMock), config.auth.jwt.secret)
|
|
21
|
+
expect(token.sub).toEqual(userMock.id)
|
|
22
|
+
expect(token.email).toEqual(userMock.email)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { vi } from "vitest"
|
|
2
|
+
import { hashDatasetToRange } from "../datalad-service"
|
|
3
|
+
|
|
4
|
+
vi.mock("ioredis")
|
|
5
|
+
|
|
6
|
+
describe("datalad-service utils", () => {
|
|
7
|
+
describe("hashDatasetToRange()", () => {
|
|
8
|
+
it("is stable across a range of datasets", () => {
|
|
9
|
+
const range = 8
|
|
10
|
+
expect(hashDatasetToRange("ds000001", range)).toBe(4)
|
|
11
|
+
expect(hashDatasetToRange("ds000002", range)).toBe(5)
|
|
12
|
+
expect(hashDatasetToRange("ds000003", range)).toBe(4)
|
|
13
|
+
expect(hashDatasetToRange("ds000004", range)).toBe(6)
|
|
14
|
+
expect(hashDatasetToRange("ds001734", range)).toBe(1)
|
|
15
|
+
expect(hashDatasetToRange("ds001919", range)).toBe(6)
|
|
16
|
+
})
|
|
17
|
+
it("is comparable with a small range (4)", () => {
|
|
18
|
+
const range = 4
|
|
19
|
+
expect(hashDatasetToRange("ds000001", range)).toBe(0)
|
|
20
|
+
expect(hashDatasetToRange("ds000002", range)).toBe(1)
|
|
21
|
+
expect(hashDatasetToRange("ds000003", range)).toBe(0)
|
|
22
|
+
expect(hashDatasetToRange("ds000004", range)).toBe(2)
|
|
23
|
+
expect(hashDatasetToRange("ds001734", range)).toBe(1)
|
|
24
|
+
expect(hashDatasetToRange("ds001919", range)).toBe(2)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { vi } from
|
|
2
|
-
import { connect } from
|
|
3
|
-
import { getAccessionNumber } from
|
|
1
|
+
import { vi } from "vitest"
|
|
2
|
+
import { connect } from "mongoose"
|
|
3
|
+
import { getAccessionNumber } from "../dataset"
|
|
4
4
|
|
|
5
|
-
vi.mock(
|
|
5
|
+
vi.mock("ioredis")
|
|
6
6
|
|
|
7
|
-
describe(
|
|
8
|
-
describe(
|
|
7
|
+
describe("libs/dataset", () => {
|
|
8
|
+
describe("getAccessionNumber", () => {
|
|
9
9
|
beforeAll(() => {
|
|
10
10
|
connect(globalThis.__MONGO_URI__)
|
|
11
11
|
})
|
|
12
12
|
it('returns strings starting with "ds"', async () => {
|
|
13
13
|
const ds = await getAccessionNumber()
|
|
14
|
-
expect(ds.slice(0, 2)).toEqual(
|
|
14
|
+
expect(ds.slice(0, 2)).toEqual("ds")
|
|
15
15
|
})
|
|
16
|
-
it(
|
|
16
|
+
it("generates sequential numbers", async () => {
|
|
17
17
|
const first = await getAccessionNumber()
|
|
18
18
|
const second = await getAccessionNumber()
|
|
19
19
|
const fNum = parseInt(first.slice(2))
|
|
20
20
|
const sNum = parseInt(second.slice(2))
|
|
21
21
|
expect(fNum).toBeLessThan(sNum)
|
|
22
22
|
})
|
|
23
|
-
it(
|
|
23
|
+
it("returns 6 digits for ds ids", async () => {
|
|
24
24
|
const ds = await getAccessionNumber()
|
|
25
25
|
const num = ds.slice(2)
|
|
26
26
|
expect(num).toHaveLength(6)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import Key from
|
|
2
|
-
import { addJWT } from
|
|
3
|
-
import config from
|
|
1
|
+
import Key from "../models/key"
|
|
2
|
+
import { addJWT } from "./authentication/jwt"
|
|
3
|
+
import config from "../config"
|
|
4
4
|
|
|
5
|
-
export const apiKeyFactory = user => {
|
|
5
|
+
export const apiKeyFactory = (user) => {
|
|
6
6
|
const apiKeyExpiration = 31536000
|
|
7
7
|
return addJWT(config)(user, apiKeyExpiration).token
|
|
8
8
|
}
|
|
@@ -11,7 +11,7 @@ export const apiKeyFactory = user => {
|
|
|
11
11
|
* Replace or create an API key for a given user
|
|
12
12
|
* @param {object} user
|
|
13
13
|
*/
|
|
14
|
-
export const generateApiKey = user => {
|
|
14
|
+
export const generateApiKey = (user) => {
|
|
15
15
|
const userId = user.id
|
|
16
16
|
const token = apiKeyFactory(user)
|
|
17
17
|
return Key.updateOne(
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { vi } from "vitest"
|
|
2
|
+
import User from "../../../models/user"
|
|
3
|
+
import { addJWT, jwtFromRequest } from "../jwt"
|
|
4
|
+
|
|
5
|
+
vi.mock("ioredis")
|
|
6
|
+
vi.mock("../../../config.ts")
|
|
7
|
+
vi.unmock("mongoose")
|
|
8
|
+
|
|
9
|
+
describe("jwt auth", () => {
|
|
10
|
+
describe("addJWT()", () => {
|
|
11
|
+
it("Extends a User model with a valid token", () => {
|
|
12
|
+
const config = {
|
|
13
|
+
auth: {
|
|
14
|
+
jwt: {
|
|
15
|
+
secret: "1234",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
const user = new User({ email: "test@example.com" })
|
|
20
|
+
const obj = addJWT(config)(user)
|
|
21
|
+
expect(obj).toHaveProperty("token")
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
describe("jwtFromRequest()", () => {
|
|
25
|
+
it("handles both cookie and authorization headers", () => {
|
|
26
|
+
const cookieToken = "1234"
|
|
27
|
+
const headersToken = "Bearer 5678"
|
|
28
|
+
const cookieRequest = {
|
|
29
|
+
cookies: {
|
|
30
|
+
accessToken: cookieToken,
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
const headersRequest = {
|
|
34
|
+
headers: {
|
|
35
|
+
authorization: headersToken,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
expect(jwtFromRequest(cookieRequest)).toEqual(cookieToken)
|
|
39
|
+
expect(jwtFromRequest(headersRequest)).toEqual("5678")
|
|
40
|
+
})
|
|
41
|
+
it("prefers authorization header when cookies are present", () => {
|
|
42
|
+
const req = {
|
|
43
|
+
cookies: {
|
|
44
|
+
accessToken: "1234",
|
|
45
|
+
},
|
|
46
|
+
headers: {
|
|
47
|
+
authorization: "Bearer 5678",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
expect(jwtFromRequest(req)).toEqual("5678")
|
|
51
|
+
})
|
|
52
|
+
it("returns null when authorization header is missing", () => {
|
|
53
|
+
const req = {
|
|
54
|
+
headers: {},
|
|
55
|
+
}
|
|
56
|
+
expect(jwtFromRequest(req)).toEqual(null)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import crypto from
|
|
2
|
-
import config from
|
|
1
|
+
import crypto from "crypto"
|
|
2
|
+
import config from "../../config"
|
|
3
3
|
|
|
4
4
|
const secret = config.auth.jwt.secret
|
|
5
|
-
const algorithm =
|
|
5
|
+
const algorithm = "aes256"
|
|
6
6
|
const key = crypto
|
|
7
|
-
.createHash(
|
|
7
|
+
.createHash("sha256")
|
|
8
8
|
.update(secret)
|
|
9
|
-
.digest(
|
|
9
|
+
.digest("base64")
|
|
10
10
|
.substr(0, 32)
|
|
11
11
|
|
|
12
|
-
const delimiter =
|
|
13
|
-
const encoding =
|
|
12
|
+
const delimiter = "."
|
|
13
|
+
const encoding = "base64"
|
|
14
14
|
|
|
15
15
|
const pack = (iv, encrypted) => iv.toString(encoding) + delimiter + encrypted
|
|
16
16
|
|
|
17
|
-
const unpack = encryptedPackage => {
|
|
17
|
+
const unpack = (encryptedPackage) => {
|
|
18
18
|
const [strIv, encrypted] = encryptedPackage.split(delimiter)
|
|
19
19
|
const iv = Buffer.from(strIv, encoding)
|
|
20
20
|
return [iv, encrypted]
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export const encrypt = plainText => {
|
|
23
|
+
export const encrypt = (plainText) => {
|
|
24
24
|
const iv = crypto.randomBytes(16)
|
|
25
25
|
const cipher = crypto.createCipheriv(algorithm, key, iv)
|
|
26
|
-
const encryptedText =
|
|
27
|
-
cipher.
|
|
26
|
+
const encryptedText = cipher.update(plainText, "utf8", "hex") +
|
|
27
|
+
cipher.final("hex")
|
|
28
28
|
const encryptedPackage = pack(iv, encryptedText)
|
|
29
29
|
return encryptedPackage
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export const decrypt = encryptedPackage => {
|
|
32
|
+
export const decrypt = (encryptedPackage) => {
|
|
33
33
|
const [iv, encryptedText] = unpack(encryptedPackage)
|
|
34
34
|
const decipher = crypto.createDecipheriv(algorithm, key, iv)
|
|
35
|
-
const decryptedText =
|
|
36
|
-
decipher.
|
|
35
|
+
const decryptedText = decipher.update(encryptedText, "hex", "utf8") +
|
|
36
|
+
decipher.final("utf8")
|
|
37
37
|
return decryptedText
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export const hashObject = object =>
|
|
41
|
-
crypto.createHash(
|
|
40
|
+
export const hashObject = (object) =>
|
|
41
|
+
crypto.createHash("sha1").update(JSON.stringify(object)).digest("hex")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import passport from "passport"
|
|
2
|
+
|
|
3
|
+
export const requestAuth = (req, res, next) =>
|
|
4
|
+
passport.authenticate("google", {
|
|
5
|
+
scope: [
|
|
6
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
7
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
8
|
+
],
|
|
9
|
+
session: false,
|
|
10
|
+
accessType: "offline",
|
|
11
|
+
prompt: "consent",
|
|
12
|
+
state: req.query.redirectPath || null,
|
|
13
|
+
})(req, res, next)
|
|
14
|
+
|
|
15
|
+
export const authCallback = passport.authenticate("google", {
|
|
16
|
+
failureRedirect: "/",
|
|
17
|
+
session: false,
|
|
18
|
+
})
|