@openneuro/server 4.20.4 → 4.20.6-alpha.2

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 (185) hide show
  1. package/package.json +4 -6
  2. package/src/__mocks__/{config.js → config.ts} +5 -5
  3. package/src/app.ts +32 -31
  4. package/src/cache/item.ts +6 -7
  5. package/src/cache/types.ts +8 -8
  6. package/src/{config.js → config.ts} +6 -6
  7. package/src/datalad/__tests__/changelog.spec.ts +83 -0
  8. package/src/datalad/__tests__/dataset.spec.ts +109 -0
  9. package/src/datalad/__tests__/description.spec.ts +141 -0
  10. package/src/datalad/__tests__/files.spec.ts +77 -0
  11. package/src/datalad/__tests__/pagination.spec.ts +136 -0
  12. package/src/datalad/__tests__/{snapshots.spec.js → snapshots.spec.ts} +17 -17
  13. package/src/datalad/{analytics.js → analytics.ts} +4 -4
  14. package/src/datalad/{changelog.js → changelog.ts} +17 -14
  15. package/src/datalad/{dataset.js → dataset.ts} +95 -93
  16. package/src/datalad/{description.js → description.ts} +37 -37
  17. package/src/datalad/draft.ts +38 -0
  18. package/src/datalad/files.ts +26 -20
  19. package/src/datalad/{pagination.js → pagination.ts} +47 -47
  20. package/src/datalad/{readme.js → readme.ts} +13 -11
  21. package/src/datalad/{reexporter.js → reexporter.ts} +4 -4
  22. package/src/datalad/{snapshots.js → snapshots.ts} +56 -62
  23. package/src/datalad/{upload.js → upload.ts} +7 -5
  24. package/src/elasticsearch/elastic-client.ts +11 -0
  25. package/src/elasticsearch/reindex-dataset.ts +7 -7
  26. package/src/graphql/__tests__/__snapshots__/permissions.spec.ts.snap +5 -0
  27. package/src/graphql/__tests__/{comment.spec.js → comment.spec.ts} +17 -17
  28. package/src/graphql/__tests__/permissions.spec.ts +113 -0
  29. package/src/graphql/{permissions.js → permissions.ts} +14 -14
  30. package/src/graphql/resolvers/__tests__/brainlife.spec.ts +11 -11
  31. package/src/graphql/resolvers/__tests__/{dataset-search.spec.js → dataset-search.spec.ts} +25 -23
  32. package/src/graphql/resolvers/__tests__/dataset.spec.ts +175 -0
  33. package/src/graphql/resolvers/__tests__/derivatives.spec.ts +19 -19
  34. package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +20 -20
  35. package/src/graphql/resolvers/__tests__/permssions.spec.ts +35 -0
  36. package/src/graphql/resolvers/__tests__/snapshots.spec.ts +59 -0
  37. package/src/graphql/resolvers/__tests__/user.spec.ts +18 -0
  38. package/src/graphql/resolvers/brainlife.ts +4 -4
  39. package/src/graphql/resolvers/cache.ts +4 -4
  40. package/src/graphql/resolvers/{comment.js → comment.ts} +16 -16
  41. package/src/graphql/resolvers/{dataset-search.js → dataset-search.ts} +45 -43
  42. package/src/graphql/resolvers/{dataset.js → dataset.ts} +38 -52
  43. package/src/graphql/resolvers/datasetType.ts +3 -3
  44. package/src/graphql/resolvers/derivatives.ts +11 -11
  45. package/src/graphql/resolvers/description.ts +18 -0
  46. package/src/graphql/resolvers/{draft.js → draft.ts} +13 -13
  47. package/src/graphql/resolvers/{flaggedFiles.js → flaggedFiles.ts} +4 -4
  48. package/src/graphql/resolvers/{follow.js → follow.ts} +1 -1
  49. package/src/graphql/resolvers/git.ts +3 -3
  50. package/src/graphql/resolvers/history.ts +13 -0
  51. package/src/graphql/resolvers/importRemoteDataset.ts +12 -11
  52. package/src/graphql/resolvers/index.ts +25 -0
  53. package/src/graphql/resolvers/{issues.js → issues.ts} +9 -9
  54. package/src/graphql/resolvers/metadata.ts +8 -8
  55. package/src/graphql/resolvers/{mutation.js → mutation.ts} +26 -26
  56. package/src/graphql/resolvers/{newsletter.js → newsletter.ts} +2 -2
  57. package/src/graphql/resolvers/permissions.ts +15 -21
  58. package/src/graphql/resolvers/publish.ts +17 -0
  59. package/src/graphql/resolvers/query.ts +21 -0
  60. package/src/graphql/resolvers/{readme.js → readme.ts} +3 -3
  61. package/src/graphql/resolvers/{reexporter.js → reexporter.ts} +2 -2
  62. package/src/graphql/resolvers/relation.ts +5 -5
  63. package/src/graphql/resolvers/{reset.js → reset.ts} +2 -2
  64. package/src/graphql/resolvers/reviewer.ts +4 -4
  65. package/src/graphql/resolvers/{snapshots.js → snapshots.ts} +49 -49
  66. package/src/graphql/resolvers/{stars.js → stars.ts} +1 -1
  67. package/src/graphql/resolvers/summary.ts +3 -3
  68. package/src/graphql/resolvers/{upload.js → upload.ts} +5 -5
  69. package/src/graphql/resolvers/{user.js → user.ts} +16 -18
  70. package/src/graphql/resolvers/{validation.js → validation.ts} +12 -14
  71. package/src/graphql/{schema.js → schema.ts} +4 -6
  72. package/src/graphql/utils/{file.js → file.ts} +2 -2
  73. package/src/handlers/{comments.js → comments.ts} +11 -11
  74. package/src/handlers/{config.js → config.ts} +1 -1
  75. package/src/handlers/{datalad.js → datalad.ts} +22 -22
  76. package/src/handlers/{doi.js → doi.ts} +6 -6
  77. package/src/handlers/reviewer.ts +6 -6
  78. package/src/handlers/{sitemap.js → sitemap.ts} +19 -19
  79. package/src/handlers/stars.ts +11 -10
  80. package/src/handlers/{subscriptions.js → subscriptions.ts} +17 -16
  81. package/src/handlers/{users.js → users.ts} +3 -3
  82. package/src/libs/__tests__/apikey.spec.ts +25 -0
  83. package/src/libs/__tests__/datalad-service.spec.ts +27 -0
  84. package/src/libs/__tests__/{dataset.spec.js → dataset.spec.ts} +9 -9
  85. package/src/libs/{apikey.js → apikey.ts} +5 -5
  86. package/src/libs/authentication/__tests__/jwt.spec.ts +59 -0
  87. package/src/libs/authentication/{crypto.js → crypto.ts} +16 -16
  88. package/src/libs/authentication/google.ts +18 -0
  89. package/src/libs/authentication/jwt.ts +40 -33
  90. package/src/libs/authentication/{orcid.js → orcid.ts} +11 -11
  91. package/src/libs/authentication/{passport.js → passport.ts} +45 -30
  92. package/src/libs/authentication/{states.js → states.ts} +17 -20
  93. package/src/libs/{counter.js → counter.ts} +1 -1
  94. package/src/libs/{datalad-service.js → datalad-service.ts} +4 -4
  95. package/src/libs/dataset.ts +9 -0
  96. package/src/libs/doi/__tests__/__snapshots__/doi.spec.ts.snap +17 -0
  97. package/src/libs/doi/__tests__/doi.spec.ts +25 -0
  98. package/src/libs/doi/__tests__/normalize.spec.ts +19 -19
  99. package/src/libs/doi/{index.js → index.ts} +27 -21
  100. package/src/libs/doi/normalize.ts +2 -2
  101. package/src/libs/email/__tests__/index.spec.ts +14 -14
  102. package/src/libs/email/index.ts +4 -4
  103. package/src/libs/email/templates/__tests__/comment-created.spec.ts +12 -12
  104. package/src/libs/email/templates/__tests__/dataset-deleted.spec.ts +6 -6
  105. package/src/libs/email/templates/__tests__/owner-unsubscribed.spec.ts +6 -6
  106. package/src/libs/email/templates/__tests__/snapshot-created.spec.ts +9 -9
  107. package/src/libs/email/templates/__tests__/snapshot-reminder.spec.ts +7 -7
  108. package/src/libs/email/templates/comment-created.ts +2 -1
  109. package/src/libs/email/templates/dataset-deleted.ts +2 -1
  110. package/src/libs/email/templates/dataset-import-failed.ts +2 -1
  111. package/src/libs/email/templates/dataset-imported.ts +2 -1
  112. package/src/libs/email/templates/owner-unsubscribed.ts +2 -1
  113. package/src/libs/email/templates/snapshot-created.ts +2 -1
  114. package/src/libs/email/templates/snapshot-reminder.ts +2 -1
  115. package/src/libs/{notifications.js → notifications.ts} +100 -113
  116. package/src/libs/{orcid.js → orcid.ts} +20 -20
  117. package/src/libs/{redis.js → redis.ts} +6 -6
  118. package/src/models/__tests__/ingestDataset.spec.ts +15 -15
  119. package/src/models/analytics.ts +2 -2
  120. package/src/models/badAnnexObject.ts +6 -6
  121. package/src/models/comment.ts +10 -10
  122. package/src/models/counter.ts +2 -2
  123. package/src/models/dataset.ts +16 -16
  124. package/src/models/deletion.ts +3 -3
  125. package/src/models/deprecatedSnapshot.ts +2 -2
  126. package/src/models/doi.ts +2 -2
  127. package/src/models/file.ts +2 -2
  128. package/src/models/ingestDataset.ts +4 -4
  129. package/src/models/issue.ts +2 -2
  130. package/src/models/key.ts +2 -2
  131. package/src/models/mailgunIdentifier.ts +2 -2
  132. package/src/models/metadata.ts +3 -3
  133. package/src/models/newsletter.ts +3 -3
  134. package/src/models/notification.ts +2 -2
  135. package/src/models/permission.ts +4 -4
  136. package/src/models/reviewer.ts +7 -7
  137. package/src/models/snapshot.ts +2 -2
  138. package/src/models/stars.ts +6 -6
  139. package/src/models/subscription.ts +2 -2
  140. package/src/models/summary.ts +2 -2
  141. package/src/models/upload.ts +3 -3
  142. package/src/models/user.ts +4 -4
  143. package/src/{routes.js → routes.ts} +62 -62
  144. package/src/server.ts +9 -9
  145. package/src/utils/__tests__/datasetOrSnapshot.spec.ts +25 -25
  146. package/src/utils/__tests__/validateUrl.spec.ts +10 -10
  147. package/src/utils/datasetOrSnapshot.ts +2 -2
  148. package/src/utils/validateUrl.ts +1 -1
  149. package/src/datalad/__tests__/changelog.spec.js +0 -82
  150. package/src/datalad/__tests__/dataset.spec.js +0 -109
  151. package/src/datalad/__tests__/description.spec.js +0 -137
  152. package/src/datalad/__tests__/files.spec.js +0 -75
  153. package/src/datalad/__tests__/pagination.spec.js +0 -136
  154. package/src/datalad/draft.js +0 -37
  155. package/src/elasticsearch/elastic-client.js +0 -11
  156. package/src/graphql/__tests__/permissions.spec.js +0 -107
  157. package/src/graphql/pubsub.js +0 -5
  158. package/src/graphql/resolvers/__tests__/dataset.spec.js +0 -175
  159. package/src/graphql/resolvers/__tests__/permssions.spec.js +0 -34
  160. package/src/graphql/resolvers/__tests__/snapshots.spec.js +0 -58
  161. package/src/graphql/resolvers/__tests__/user.spec.js +0 -17
  162. package/src/graphql/resolvers/description.js +0 -29
  163. package/src/graphql/resolvers/history.js +0 -11
  164. package/src/graphql/resolvers/index.js +0 -25
  165. package/src/graphql/resolvers/publish.js +0 -17
  166. package/src/graphql/resolvers/query.js +0 -21
  167. package/src/graphql/resolvers/subscriptions.js +0 -81
  168. package/src/graphql/utils/publish-draft-update.js +0 -13
  169. package/src/libs/__tests__/apikey.spec.js +0 -24
  170. package/src/libs/__tests__/datalad-service.spec.js +0 -26
  171. package/src/libs/authentication/__tests__/jwt.spec.js +0 -23
  172. package/src/libs/authentication/globus.js +0 -11
  173. package/src/libs/authentication/google.js +0 -19
  174. package/src/libs/bidsId.js +0 -68
  175. package/src/libs/dataset.js +0 -9
  176. package/src/libs/doi/__tests__/doi.spec.js +0 -24
  177. package/src/libs/redis-pubsub.js +0 -5
  178. package/src/libs/request.js +0 -155
  179. package/src/libs/scitran.js +0 -25
  180. package/src/libs/subscription-server.js +0 -20
  181. package/src/libs/testing-utils.js +0 -17
  182. package/src/persistent/datasets/.gitignore +0 -3
  183. package/src/persistent/temp/.gitignore +0 -3
  184. /package/src/libs/__mocks__/{notifications.js → notifications.ts} +0 -0
  185. /package/src/libs/authentication/{verifyUser.js → verifyUser.ts} +0 -0
@@ -1,8 +1,8 @@
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'
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 || 'HEAD'
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('https://s3.amazonaws.com/')) {
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('Content-Type', mime.lookup(filename) || 'application/octet-stream')
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('Content-Length', Number(r.headers.get('content-length')))
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('Internal error transferring requested file')
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('Content-Type', 'application/octet-stream')
69
+ res.set("Content-Type", "application/octet-stream")
70
70
  return request.get(uri).pipe(res)
71
- } else if (key.startsWith('SHA256E-') || key.startsWith('MD5E-')) {
71
+ } else if (key.startsWith("SHA256E-") || key.startsWith("MD5E-")) {
72
72
  const uri = `${worker}/datasets/${datasetId}/annex/${key}`
73
- res.set('Content-Type', 'application/octet-stream')
73
+ res.set("Content-Type", "application/octet-stream")
74
74
  return request.get(uri).pipe(res)
75
75
  } else {
76
- res.set('Content-Type', 'application/json')
76
+ res.set("Content-Type", "application/json")
77
77
  res.status(400).send({
78
- error: 'Key must be a git object hash or git-annex key',
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 '../config'
2
- import doi from '../libs/doi'
3
- import Doi from '../models/doi'
4
- import Snapshot from '../models/snapshot'
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
- 'doi',
54
+ "doi",
55
55
  ).exec()
56
56
  return res.send(doi)
57
57
  }
@@ -1,24 +1,24 @@
1
- import Reviewer from '../models/reviewer'
2
- import { decodeJWT } from '../libs/authentication/jwt'
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('dataset:reviewer')) {
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('accessToken', token)
15
+ .cookie("accessToken", token)
16
16
  .redirect(`/datasets/${decodedToken.dataset}`)
17
17
  } else {
18
- throw Error('Review token not valid')
18
+ throw Error("Review token not valid")
19
19
  }
20
20
  } else {
21
- throw Error('Invalid token scope for reviewer')
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 'sitemap'
2
- import config from '../config.js'
3
- import Dataset from '../models/dataset'
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: '/', 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 },
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: 'snapshots',
22
- localField: 'id',
23
- foreignField: 'datasetId',
24
- as: 'snapshots',
21
+ from: "snapshots",
22
+ localField: "id",
23
+ foreignField: "datasetId",
24
+ as: "snapshots",
25
25
  },
26
26
  },
27
27
  {
28
- $unwind: '$snapshots',
28
+ $unwind: "$snapshots",
29
29
  },
30
30
  {
31
31
  $project: {
32
32
  url: {
33
- $concat: ['/datasets/', '$id', '/versions/', '$snapshots.tag'],
33
+ $concat: ["/datasets/", "$id", "/versions/", "$snapshots.tag"],
34
34
  },
35
35
  lastmodISO: {
36
- $dateToString: { date: '$snapshots.created' },
36
+ $dateToString: { date: "$snapshots.created" },
37
37
  },
38
38
  priority: 0.8,
39
- changefreq: 'daily', // To make sure new comments are indexed
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('Content-Type', 'application/xml')
60
+ res.header("Content-Type", "application/xml")
61
61
  res.send(xml)
62
62
  })
63
63
  })
@@ -1,5 +1,5 @@
1
1
  // dependencies ------------------------------------------------------------
2
- import Star from '../models/stars'
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
- req.params.datasetId === 'undefined' ? null : req.params.datasetId
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 '../libs/notifications'
2
- import Subscription from '../models/subscription'
3
- import mongoose from 'mongoose'
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
- req.params.datasetId === 'undefined' ? null : req.params.datasetId
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 '../libs/apikey'
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 'vitest'
2
- import { connect } from 'mongoose'
3
- import { getAccessionNumber } from '../dataset.js'
1
+ import { vi } from "vitest"
2
+ import { connect } from "mongoose"
3
+ import { getAccessionNumber } from "../dataset"
4
4
 
5
- vi.mock('ioredis')
5
+ vi.mock("ioredis")
6
6
 
7
- describe('libs/dataset.js', () => {
8
- describe('getAccessionNumber', () => {
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('ds')
14
+ expect(ds.slice(0, 2)).toEqual("ds")
15
15
  })
16
- it('generates sequential numbers', async () => {
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('returns 6 digits for ds ids', async () => {
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 '../models/key'
2
- import { addJWT } from '../libs/authentication/jwt'
3
- import config from '../config'
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 'crypto'
2
- import config from '../../config'
1
+ import crypto from "crypto"
2
+ import config from "../../config"
3
3
 
4
4
  const secret = config.auth.jwt.secret
5
- const algorithm = 'aes256'
5
+ const algorithm = "aes256"
6
6
  const key = crypto
7
- .createHash('sha256')
7
+ .createHash("sha256")
8
8
  .update(secret)
9
- .digest('base64')
9
+ .digest("base64")
10
10
  .substr(0, 32)
11
11
 
12
- const delimiter = '.'
13
- const encoding = 'base64'
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.update(plainText, 'utf8', 'hex') + cipher.final('hex')
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.update(encryptedText, 'hex', 'utf8') + decipher.final('utf8')
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('sha1').update(JSON.stringify(object)).digest('hex')
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
+ })