@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.
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,9 +1,9 @@
1
- import passport from 'passport'
2
- import refresh from 'passport-oauth2-refresh'
3
- import jwt from 'jsonwebtoken'
4
- import { decrypt } from './crypto'
5
- import User from '../../models/user'
6
- import config from '../../config.js'
1
+ import passport from "passport"
2
+ import refresh from "passport-oauth2-refresh"
3
+ import jwt from "jsonwebtoken"
4
+ import { decrypt } from "./crypto"
5
+ import User from "../../models/user"
6
+ import config from "../../config"
7
7
 
8
8
  interface OpenNeuroTokenProfile {
9
9
  sub: string
@@ -24,7 +24,7 @@ export const buildToken = (
24
24
  expiresIn,
25
25
  options?: { scopes?: string[]; dataset?: string },
26
26
  ): string => {
27
- const fields: Omit<OpenNeuroTokenProfile, 'iat' | 'exp'> = {
27
+ const fields: Omit<OpenNeuroTokenProfile, "iat" | "exp"> = {
28
28
  sub: user.id,
29
29
  email: user.email,
30
30
  provider: user.provider,
@@ -33,10 +33,10 @@ export const buildToken = (
33
33
  }
34
34
  // Allow extensions of the base token format
35
35
  if (options) {
36
- if (options && 'scopes' in options) {
36
+ if (options && "scopes" in options) {
37
37
  fields.scopes = options.scopes
38
38
  }
39
- if ('dataset' in options) {
39
+ if ("dataset" in options) {
40
40
  fields.dataset = options.dataset
41
41
  }
42
42
  }
@@ -46,12 +46,10 @@ export const buildToken = (
46
46
  }
47
47
 
48
48
  // Helper to generate a JWT containing user info
49
- export const addJWT =
50
- config =>
51
- (user, expiration = 60 * 60 * 24 * 7) => {
52
- const token = buildToken(config, user, expiration)
53
- return Object.assign({}, user, { token })
54
- }
49
+ export const addJWT = (config) => (user, expiration = 60 * 60 * 24 * 7) => {
50
+ const token = buildToken(config, user, expiration)
51
+ return Object.assign({}, user, { token })
52
+ }
55
53
 
56
54
  /**
57
55
  * Generate an upload specific token
@@ -64,7 +62,7 @@ export function generateUploadToken(
64
62
  expiresIn = 60 * 60 * 24 * 7,
65
63
  ) {
66
64
  const options = {
67
- scopes: ['dataset:upload'],
65
+ scopes: ["dataset:upload"],
68
66
  dataset: datasetId,
69
67
  }
70
68
  return buildToken(config, user, expiresIn, options)
@@ -79,14 +77,14 @@ export function generateReviewerToken(
79
77
  expiresIn = 60 * 60 * 24 * 365,
80
78
  ) {
81
79
  const options = {
82
- scopes: ['dataset:reviewer'],
80
+ scopes: ["dataset:reviewer"],
83
81
  dataset: datasetId,
84
82
  }
85
83
  const reviewer = {
86
84
  id,
87
- email: 'reviewer@openneuro.org',
88
- provider: 'OpenNeuro',
89
- name: 'Anonymous Reviewer',
85
+ email: "reviewer@openneuro.org",
86
+ provider: "OpenNeuro",
87
+ name: "Anonymous Reviewer",
90
88
  admin: false,
91
89
  }
92
90
  return buildToken(config, reviewer, expiresIn, options)
@@ -99,7 +97,7 @@ export function generateReviewerToken(
99
97
  */
100
98
  export function generateRepoToken(user, datasetId, expiresIn = 60 * 60 * 24) {
101
99
  const options = {
102
- scopes: ['dataset:git'],
100
+ scopes: ["dataset:git"],
103
101
  dataset: datasetId,
104
102
  }
105
103
  return buildToken(config, user, expiresIn, options)
@@ -121,8 +119,17 @@ const requestNewAccessToken = (jwtProvider, refreshToken) =>
121
119
  * Extract the JWT from a cookie
122
120
  * @param {Object} req
123
121
  */
124
- export const jwtFromRequest = req => {
125
- if (req.cookies && req.cookies.accessToken) {
122
+ export const jwtFromRequest = (req) => {
123
+ if (req.headers?.authorization) {
124
+ try {
125
+ return req.headers.authorization.substring(
126
+ 7,
127
+ req.headers.authorization.length,
128
+ )
129
+ } catch (_err) {
130
+ return null
131
+ }
132
+ } else if (req.cookies && req.cookies.accessToken) {
126
133
  return req.cookies.accessToken
127
134
  } else {
128
135
  return null
@@ -133,13 +140,13 @@ export const decodeJWT = (token: string): OpenNeuroTokenProfile => {
133
140
  return jwt.decode(token) as OpenNeuroTokenProfile
134
141
  }
135
142
 
136
- export const parsedJwtFromRequest = req => {
143
+ export const parsedJwtFromRequest = (req) => {
137
144
  const jwt = jwtFromRequest(req)
138
145
  if (jwt) return decodeJWT(jwt)
139
146
  else return null
140
147
  }
141
148
 
142
- const refreshToken = async jwt => {
149
+ const refreshToken = async (jwt) => {
143
150
  const user = await User.findOne({ id: jwt.sub, provider: jwt.provider })
144
151
  if (user && user.refresh) {
145
152
  const refreshToken = decrypt(user.refresh)
@@ -152,7 +159,7 @@ const refreshToken = async jwt => {
152
159
  }
153
160
 
154
161
  // Shared options for Express response.cookie()
155
- const cookieOptions = { sameSite: 'Lax' }
162
+ const cookieOptions = { sameSite: "Lax" }
156
163
 
157
164
  // attach user obj to request based on jwt
158
165
  // if user does not exist, continue
@@ -163,10 +170,10 @@ export const authenticate = (req, res, next) => {
163
170
  const token = await refreshToken(jwt)
164
171
  if (token) {
165
172
  req.cookies.accessToken = token
166
- res.cookie('accessToken', token, cookieOptions)
173
+ res.cookie("accessToken", token, cookieOptions)
167
174
  }
168
175
  }
169
- passport.authenticate('jwt', { session: false }, (err, user) => {
176
+ passport.authenticate("jwt", { session: false }, (err, user) => {
170
177
  req.login(user, { session: false }, () => next())
171
178
  })(req, res, next)
172
179
  }
@@ -175,11 +182,11 @@ export const authenticate = (req, res, next) => {
175
182
 
176
183
  export const authSuccessHandler = (req, res, next) => {
177
184
  const redirectPath = req.query.state
178
- ? Buffer.from(req.query.state, 'base64').toString()
179
- : '/'
185
+ ? Buffer.from(req.query.state, "base64").toString()
186
+ : "/"
180
187
  if (req.user) {
181
188
  // Set the JWT associated with this login on a cookie
182
- res.cookie('accessToken', req.user.token, cookieOptions)
189
+ res.cookie("accessToken", req.user.token, cookieOptions)
183
190
  res.redirect(redirectPath)
184
191
  } else {
185
192
  res.status(401)
@@ -187,6 +194,6 @@ export const authSuccessHandler = (req, res, next) => {
187
194
  return next()
188
195
  }
189
196
 
190
- export const generateDataladCookie = config => user => {
191
- return user ? `accessToken=${addJWT(config)(user).token}` : ''
197
+ export const generateDataladCookie = (config) => (user) => {
198
+ return user ? `accessToken=${addJWT(config)(user).token}` : ""
192
199
  }
@@ -1,39 +1,39 @@
1
- import passport from 'passport'
2
- import User from '../../models/user'
3
- import { parsedJwtFromRequest } from './jwt'
1
+ import passport from "passport"
2
+ import User from "../../models/user"
3
+ import { parsedJwtFromRequest } from "./jwt"
4
4
 
5
- export const requestAuth = passport.authenticate('orcid', {
5
+ export const requestAuth = passport.authenticate("orcid", {
6
6
  session: false,
7
7
  })
8
8
 
9
9
  export const authCallback = (req, res, next) =>
10
- passport.authenticate('orcid', (err, user) => {
10
+ passport.authenticate("orcid", (err, user) => {
11
11
  if (err) {
12
12
  if (err.type) {
13
13
  return res.redirect(`/error/orcid/${err.type}`)
14
14
  } else {
15
- return res.redirect('/error/orcid/unknown')
15
+ return res.redirect("/error/orcid/unknown")
16
16
  }
17
17
  }
18
18
  if (!user) {
19
- return res.redirect('/')
19
+ return res.redirect("/")
20
20
  }
21
21
  const existingAuth = parsedJwtFromRequest(req)
22
22
  if (existingAuth) {
23
23
  // Save ORCID to primary account
24
24
  User.findOne({ id: existingAuth.sub })
25
- .then(userModel => {
25
+ .then((userModel) => {
26
26
  userModel.orcid = user.providerId
27
27
  return userModel.save().then(() => {
28
- res.redirect('/')
28
+ res.redirect("/")
29
29
  })
30
30
  })
31
- .catch(err => {
31
+ .catch((err) => {
32
32
  return next(err)
33
33
  })
34
34
  } else {
35
35
  // Complete login with ORCID as primary account
36
- req.logIn(user, { session: false }, err => {
36
+ req.logIn(user, { session: false }, (err) => {
37
37
  return next(err)
38
38
  })
39
39
  }
@@ -1,30 +1,40 @@
1
- import passport from 'passport'
2
- import refresh from 'passport-oauth2-refresh'
3
- import { Strategy as JwtStrategy } from 'passport-jwt'
4
- import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
5
- import { Strategy as ORCIDStrategy } from 'passport-orcid'
6
- import config from '../../config.js'
7
- import User from '../../models/user'
8
- import { encrypt } from './crypto'
9
- import { addJWT, jwtFromRequest } from './jwt'
10
- import orcid from '../orcid.js'
1
+ import passport from "passport"
2
+ import refresh from "passport-oauth2-refresh"
3
+ import { Strategy as JwtStrategy } from "passport-jwt"
4
+ import { Strategy as GoogleStrategy } from "passport-google-oauth20"
5
+ import { Strategy as ORCIDStrategy } from "passport-orcid"
6
+ import config from "../../config"
7
+ import User from "../../models/user"
8
+ import { encrypt } from "./crypto"
9
+ import { addJWT, jwtFromRequest } from "./jwt"
10
+ import orcid from "../orcid"
11
11
 
12
12
  const PROVIDERS = {
13
- GOOGLE: 'google',
14
- ORCID: 'orcid',
13
+ GOOGLE: "google",
14
+ ORCID: "orcid",
15
15
  }
16
16
 
17
- const loadProfile = profile => {
17
+ interface OauthProfile {
18
+ email: string
19
+ name: string
20
+ provider: string
21
+ providerId: string
22
+ orcid?: string
23
+ refresh?: string
24
+ }
25
+
26
+ const loadProfile = (profile): OauthProfile | Error => {
18
27
  if (profile.provider === PROVIDERS.GOOGLE) {
19
28
  // Get the account email from Google profile
20
29
  const primaryEmail = profile.emails
21
- .filter(email => email.verified === true)
30
+ .filter((email) => email.verified === true)
22
31
  .shift()
23
32
  return {
24
33
  email: primaryEmail.value,
25
34
  name: profile.displayName,
26
35
  provider: profile.provider,
27
36
  providerId: profile.id,
37
+ refresh: undefined,
28
38
  }
29
39
  } else if (profile.provider === PROVIDERS.ORCID) {
30
40
  return {
@@ -33,19 +43,21 @@ const loadProfile = profile => {
33
43
  provider: profile.provider,
34
44
  providerId: profile.orcid,
35
45
  orcid: profile.orcid,
46
+ refresh: undefined,
36
47
  }
37
48
  } else {
38
49
  // Some unknown profile type
39
- return new Error('Unhandled profile type.')
50
+ return new Error("Unhandled profile type.")
40
51
  }
41
52
  }
42
53
 
43
54
  export const verifyGoogleUser = (accessToken, refreshToken, profile, done) => {
44
55
  const profileUpdate = loadProfile(profile)
45
- if (refreshToken && !(profileUpdate instanceof Error))
56
+ if (refreshToken && !(profileUpdate instanceof Error)) {
46
57
  profileUpdate.refresh = encrypt(refreshToken)
58
+ }
47
59
 
48
- if ('email' in profileUpdate) {
60
+ if ("email" in profileUpdate) {
49
61
  // Look for an existing user
50
62
  User.findOneAndUpdate(
51
63
  {
@@ -55,8 +67,12 @@ export const verifyGoogleUser = (accessToken, refreshToken, profile, done) => {
55
67
  profileUpdate,
56
68
  { upsert: true, new: true, setDefaultsOnInsert: true },
57
69
  )
58
- .then(user => done(null, addJWT(config)(user.toObject())))
59
- .catch(err => done(err, null))
70
+ .then((user) => {
71
+ done(null, addJWT(config)(user.toObject()))
72
+ })
73
+ .catch((err) => {
74
+ done(err, null)
75
+ })
60
76
  } else {
61
77
  done(profileUpdate, null)
62
78
  }
@@ -72,7 +88,7 @@ export const verifyORCIDUser = (
72
88
  const token = `${profile.orcid}:${profile.access_token}`
73
89
  orcid
74
90
  .getProfile(token)
75
- .then(info => {
91
+ .then((info) => {
76
92
  profile.info = info
77
93
  profile.provider = PROVIDERS.ORCID
78
94
  const profileUpdate = loadProfile(profile)
@@ -80,9 +96,9 @@ export const verifyORCIDUser = (
80
96
  { providerId: profile.orcid, provider: profile.provider },
81
97
  profileUpdate,
82
98
  { upsert: true, new: true, setDefaultsOnInsert: true },
83
- ).then(user => done(null, addJWT(config)(user.toObject())))
99
+ ).then((user) => done(null, addJWT(config)(user.toObject())))
84
100
  })
85
- .catch(err => done(err, null))
101
+ .catch((err) => done(err, null))
86
102
  }
87
103
 
88
104
  export const setupPassportAuth = () => {
@@ -93,13 +109,13 @@ export const setupPassportAuth = () => {
93
109
  const jwtStrategy = new JwtStrategy(
94
110
  { secretOrKey: config.auth.jwt.secret, jwtFromRequest },
95
111
  (jwt, done) => {
96
- if (jwt.scopes?.includes('dataset:indexing')) {
112
+ if (jwt.scopes?.includes("dataset:indexing")) {
97
113
  done(null, {
98
114
  admin: false,
99
115
  blocked: false,
100
116
  indexer: true,
101
117
  })
102
- } else if (jwt.scopes?.includes('dataset:reviewer')) {
118
+ } else if (jwt.scopes?.includes("dataset:reviewer")) {
103
119
  done(null, {
104
120
  admin: false,
105
121
  blocked: false,
@@ -109,7 +125,7 @@ export const setupPassportAuth = () => {
109
125
  } else {
110
126
  // A user must already exist to use a JWT to auth a request
111
127
  User.findOne({ id: jwt.sub, provider: jwt.provider })
112
- .then(user => {
128
+ .then((user) => {
113
129
  if (user) done(null, user.toObject())
114
130
  else done(null, false)
115
131
  })
@@ -119,7 +135,7 @@ export const setupPassportAuth = () => {
119
135
  )
120
136
  passport.use(jwtStrategy)
121
137
  } else {
122
- throw new Error('JWT_SECRET must be configured to allow authentication.')
138
+ throw new Error("JWT_SECRET must be configured to allow authentication.")
123
139
  }
124
140
 
125
141
  // Google first
@@ -129,7 +145,7 @@ export const setupPassportAuth = () => {
129
145
  clientID: config.auth.google.clientID,
130
146
  clientSecret: config.auth.google.clientSecret,
131
147
  callbackURL: `${config.url + config.apiPrefix}auth/google/callback`,
132
- userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo',
148
+ userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo",
133
149
  },
134
150
  verifyGoogleUser,
135
151
  )
@@ -141,9 +157,8 @@ export const setupPassportAuth = () => {
141
157
  if (config.auth.orcid.clientID && config.auth.orcid.clientSecret) {
142
158
  const orcidStrategy = new ORCIDStrategy(
143
159
  {
144
- sandbox:
145
- config.auth.orcid.apiURI &&
146
- config.auth.orcid.apiURI.includes('sandbox'),
160
+ sandbox: config.auth.orcid.apiURI &&
161
+ config.auth.orcid.apiURI.includes("sandbox"),
147
162
  clientID: config.auth.orcid.clientID,
148
163
  clientSecret: config.auth.orcid.clientSecret,
149
164
  callbackURL: `${config.url + config.apiPrefix}auth/orcid/callback`,
@@ -1,10 +1,9 @@
1
1
  /** Middleware to check for authorization states on top of authentication */
2
2
 
3
- import Dataset from '../../models/dataset'
4
- import Permission from '../../models/permission'
5
- import Comment from '../../models/comment'
6
- import bidsId from '../bidsId'
7
- import mongoose from 'mongoose'
3
+ import Dataset from "../../models/dataset"
4
+ import Permission from "../../models/permission"
5
+ import Comment from "../../models/comment"
6
+ import mongoose from "mongoose"
8
7
  const ObjectID = mongoose.Schema.Types.ObjectId
9
8
 
10
9
  /**
@@ -17,7 +16,7 @@ export const authenticated = (req, res, next) => {
17
16
  if (req.isAuthenticated()) {
18
17
  return next()
19
18
  } else {
20
- return res.status(401).send({ error: 'Not logged in.' })
19
+ return res.status(401).send({ error: "Not logged in." })
21
20
  }
22
21
  }
23
22
 
@@ -46,10 +45,10 @@ export const superuser = (req, res, next) => {
46
45
  if (req.user.admin) {
47
46
  return next()
48
47
  } else {
49
- return res.status(401).send({ error: 'You do not have admin access.' })
48
+ return res.status(401).send({ error: "You do not have admin access." })
50
49
  }
51
50
  } else {
52
- return res.status(401).send({ error: 'Not logged in.' })
51
+ return res.status(401).send({ error: "Not logged in." })
53
52
  }
54
53
  }
55
54
 
@@ -61,21 +60,19 @@ export const superuser = (req, res, next) => {
61
60
  * the request object.
62
61
  */
63
62
  export const datasetAccess = (req, res, next) => {
64
- let datasetId = req.params.datasetId
63
+ const datasetId = req.params.datasetId
65
64
  ? req.params.datasetId
66
65
  : req.query.datasetId
67
66
 
68
- datasetId = bidsId.decodeId(datasetId) // handle old dataset request methods that encode ids
69
-
70
67
  // check to make sure that the dataset exists
71
68
  return Dataset.findOne({ id: datasetId })
72
69
  .exec()
73
- .then(dataset => {
70
+ .then((dataset) => {
74
71
  // if dataset does not exist, return 404 error
75
72
  if (!dataset) {
76
73
  return res
77
74
  .status(404)
78
- .send({ error: 'The dataset you have requested does not exist.' })
75
+ .send({ error: "The dataset you have requested does not exist." })
79
76
  }
80
77
 
81
78
  // if there is no user option on the request,
@@ -92,19 +89,19 @@ export const datasetAccess = (req, res, next) => {
92
89
  // find permissions information for this user & dataset
93
90
  Permission.findOne({ datasetId: datasetId, userId: req.user.id })
94
91
  .exec()
95
- .then(permission => {
92
+ .then((permission) => {
96
93
  if (permission) {
97
94
  req.hasAccess = true
98
95
  return next()
99
96
  } else {
100
97
  return res
101
98
  .status(401)
102
- .send({ error: 'You do not have access to this dataset.' })
99
+ .send({ error: "You do not have access to this dataset." })
103
100
  }
104
101
  })
105
- .catch(err => res.status(404).send(err))
102
+ .catch((err) => res.status(404).send(err))
106
103
  })
107
- .catch(err => res.status(404).send(err))
104
+ .catch((err) => res.status(404).send(err))
108
105
  }
109
106
 
110
107
  /**
@@ -122,14 +119,14 @@ export const commentAccess = (req, res, next) => {
122
119
  _id: new ObjectID(commentId),
123
120
  })
124
121
  .exec()
125
- .then(comment => {
122
+ .then((comment) => {
126
123
  if (comment.user._id === req.user.id) {
127
124
  return next()
128
125
  } else {
129
126
  return res
130
127
  .status(401)
131
- .send({ error: 'You do not have access to this comment.' })
128
+ .send({ error: "You do not have access to this comment." })
132
129
  }
133
130
  })
134
- .catch(err => res.status(404).send(err))
131
+ .catch((err) => res.status(404).send(err))
135
132
  }
@@ -1,4 +1,4 @@
1
- import Counter from '../models/counter'
1
+ import Counter from "../models/counter"
2
2
 
3
3
  /**
4
4
  * Counter
@@ -1,5 +1,5 @@
1
- import crypto from 'crypto'
2
- import config from '../config'
1
+ import crypto from "crypto"
2
+ import config from "../config"
3
3
 
4
4
  /**
5
5
  * Map dataset IDs to a normal distribution of backend workers
@@ -7,8 +7,8 @@ import config from '../config'
7
7
  * @param {number} range Integer bound for offset from hash
8
8
  */
9
9
  export function hashDatasetToRange(dataset, range) {
10
- const hash = crypto.createHash('sha1').update(dataset, 'utf8')
11
- const hexstring = hash.digest().toString('hex')
10
+ const hash = crypto.createHash("sha1").update(dataset, "utf8")
11
+ const hexstring = hash.digest().toString("hex")
12
12
  return parseInt(hexstring.substring(32, 40), 16) % range
13
13
  }
14
14
 
@@ -0,0 +1,9 @@
1
+ import { getNext } from "./counter"
2
+
3
+ /**
4
+ * Returns the next accession number string
5
+ */
6
+ export async function getAccessionNumber() {
7
+ const datasetNumber = (await getNext("datasets")) + 1000
8
+ return `ds${("000000" + datasetNumber).substr(-6, 6)}`
9
+ }
@@ -0,0 +1,17 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`DOI minting utils > template() > accepts expected arguments 1`] = `
4
+ "<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>
5
+ <resource xmlns:xsi=\\"http://www.w3.org/2001/XMLSchema-instance\\" xmlns=\\"http://datacite.org/schema/kernel-4\\" xsi:schemaLocation=\\"http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4/metadata.xsd\\">
6
+ <identifier identifierType=\\"DOI\\">12345</identifier>
7
+ <creators>
8
+ <creator><creatorName>A. User</creatorName></creator><creator><creatorName>B. User</creatorName></creator>
9
+ </creators>
10
+ <titles>
11
+ <title xml:lang=\\"en-us\\">Test Dataset</title>
12
+ </titles>
13
+ <publisher>Openneuro</publisher>
14
+ <publicationYear>1999</publicationYear>
15
+ <resourceType resourceTypeGeneral=\\"Dataset\\">fMRI</resourceType>
16
+ </resource>"
17
+ `;
@@ -0,0 +1,25 @@
1
+ import { vi } from "vitest"
2
+ import { formatBasicAuth, template } from "../index.js"
3
+
4
+ vi.mock("ioredis")
5
+
6
+ describe("DOI minting utils", () => {
7
+ describe("auth()", () => {
8
+ it("returns a base64 basic auth string", () => {
9
+ const doiConfig = { username: "test", password: "12345" }
10
+ expect(formatBasicAuth(doiConfig)).toBe("Basic dGVzdDoxMjM0NQ==")
11
+ })
12
+ })
13
+ describe("template()", () => {
14
+ it("accepts expected arguments", () => {
15
+ const context = {
16
+ doi: "12345",
17
+ creators: ["A. User", "B. User"],
18
+ title: "Test Dataset",
19
+ year: "1999",
20
+ resourceType: "fMRI",
21
+ }
22
+ expect(template(context)).toMatchSnapshot()
23
+ })
24
+ })
25
+ })
@@ -1,31 +1,31 @@
1
- import { vi } from 'vitest'
2
- import { normalizeDOI } from '../normalize'
1
+ import { vi } from "vitest"
2
+ import { normalizeDOI } from "../normalize"
3
3
 
4
- vi.mock('ioredis')
4
+ vi.mock("ioredis")
5
5
 
6
- describe('DOI normalize utility', () => {
7
- it('returns null for non-DOI input', () => {
8
- expect(normalizeDOI('Sphinx of black quartz, judge my vow.')).toBe(null)
6
+ describe("DOI normalize utility", () => {
7
+ it("returns null for non-DOI input", () => {
8
+ expect(normalizeDOI("Sphinx of black quartz, judge my vow.")).toBe(null)
9
9
  })
10
- it('returns the raw DOI value from a raw input', () => {
11
- expect(normalizeDOI('10.18112/openneuro.ds000001.v1.0.0')).toBe(
12
- '10.18112/openneuro.ds000001.v1.0.0',
10
+ it("returns the raw DOI value from a raw input", () => {
11
+ expect(normalizeDOI("10.18112/openneuro.ds000001.v1.0.0")).toBe(
12
+ "10.18112/openneuro.ds000001.v1.0.0",
13
13
  )
14
14
  })
15
- it('returns the raw DOI value from a URI input', () => {
16
- expect(normalizeDOI('doi:10.18112/openneuro.ds000001.v1.0.0')).toBe(
17
- '10.18112/openneuro.ds000001.v1.0.0',
15
+ it("returns the raw DOI value from a URI input", () => {
16
+ expect(normalizeDOI("doi:10.18112/openneuro.ds000001.v1.0.0")).toBe(
17
+ "10.18112/openneuro.ds000001.v1.0.0",
18
18
  )
19
- expect(normalizeDOI('DOI:10.18112/openneuro.ds000001.v1.0.0')).toBe(
20
- '10.18112/openneuro.ds000001.v1.0.0',
19
+ expect(normalizeDOI("DOI:10.18112/openneuro.ds000001.v1.0.0")).toBe(
20
+ "10.18112/openneuro.ds000001.v1.0.0",
21
21
  )
22
22
  })
23
- it('returns the raw DOI value from a URI input', () => {
23
+ it("returns the raw DOI value from a URI input", () => {
24
24
  expect(
25
- normalizeDOI('https://doi.org/10.18112/openneuro.ds000001.v1.0.0'),
26
- ).toBe('10.18112/openneuro.ds000001.v1.0.0')
25
+ normalizeDOI("https://doi.org/10.18112/openneuro.ds000001.v1.0.0"),
26
+ ).toBe("10.18112/openneuro.ds000001.v1.0.0")
27
27
  expect(
28
- normalizeDOI('HTTPS://DOI.ORG/10.18112/openneuro.ds000001.v1.0.0'),
29
- ).toBe('10.18112/openneuro.ds000001.v1.0.0')
28
+ normalizeDOI("HTTPS://DOI.ORG/10.18112/openneuro.ds000001.v1.0.0"),
29
+ ).toBe("10.18112/openneuro.ds000001.v1.0.0")
30
30
  })
31
31
  })