Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow linking ORCID to existing non-ORCID oauth accounts #2782

Merged
merged 6 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/openneuro-components/src/user/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ export const UserMenu = ({ profile, signOutAndRedirect }: UserMenuProps) => {
<li className="user-menu-link">
<Link to="/keygen"> Obtain an API Key </Link>
</li>
{profile.provider !== 'orcid' && (
<li className="user-menu-link">
<a href="/crn/auth/orcid?link=true">
{' '}
Link ORCID to my account{' '}
</a>
</li>
)}
{profile.admin && (
<li className="user-menu-link">
<Link to="/admin">Admin</Link>
Expand Down
2 changes: 1 addition & 1 deletion packages/openneuro-server/src/libs/authentication/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const decodeJWT = token => {
return jwt.decode(token)
}

const parsedJwtFromRequest = req => {
export const parsedJwtFromRequest = req => {
const jwt = jwtFromRequest(req)
if (jwt) return decodeJWT(jwt)
else return null
Expand Down
24 changes: 21 additions & 3 deletions packages/openneuro-server/src/libs/authentication/orcid.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import passport from 'passport'
import User from '../../models/user'
import { parsedJwtFromRequest } from './jwt.js'

export const requestAuth = passport.authenticate('orcid', {
session: false,
Expand All @@ -16,7 +18,23 @@ export const authCallback = (req, res, next) =>
if (!user) {
return res.redirect('/')
}
req.logIn(user, { session: false }, err => {
return next(err)
})
const existingAuth = parsedJwtFromRequest(req)
if (existingAuth) {
// Save ORCID to primary account
User.findOne({ id: existingAuth.sub }, (err, userModel) => {
if (err) {
return next(err)
} else {
userModel.orcid = user.providerId
return userModel.save().then(() => {
res.redirect('/')
})
}
})
} else {
// Complete login with ORCID as primary account
req.logIn(user, { session: false }, err => {
return next(err)
})
}
})(req, res, next)
6 changes: 3 additions & 3 deletions packages/openneuro-server/src/libs/authentication/passport.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const verifyGoogleUser = (accessToken, refreshToken, profile, done) => {
profileUpdate,
{ upsert: true, new: true, setDefaultsOnInsert: true },
)
.then(user => done(null, addJWT(config)(user)))
.then(user => done(null, addJWT(config)(user.toObject())))
.catch(err => done(err, null))
} else {
done(profileUpdate, null)
Expand All @@ -80,7 +80,7 @@ export const verifyORCIDUser = (
{ providerId: profile.orcid, provider: profile.provider },
profileUpdate,
{ upsert: true, new: true, setDefaultsOnInsert: true },
).then(user => done(null, addJWT(config)(user)))
).then(user => done(null, addJWT(config)(user.toObject())))
})
.catch(err => done(err, null))
}
Expand Down Expand Up @@ -110,7 +110,7 @@ export const setupPassportAuth = () => {
// A user must already exist to use a JWT to auth a request
User.findOne({ id: jwt.sub, provider: jwt.provider })
.then(user => {
if (user) done(null, user)
if (user) done(null, user.toObject())
else done(null, false)
})
.catch(done)
Expand Down
20 changes: 15 additions & 5 deletions packages/openneuro-server/src/libs/orcid.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ export default {
},
(err, res) => {
if (err) {
reject({
return reject({
message:
'An unexpected ORCID login failure occurred, please try again later.',
})
}
const doc = new xmldoc.XmlDocument(res.body)
let doc
// Catch issues with parsing this response
try {
doc = new xmldoc.XmlDocument(res.body)
} catch (err) {
return reject({
type: 'config',
message:
'ORCID auth response invalid, most likely this is a misconfigured ORCID_API_ENDPOINT value',
})
}
let name = doc.valueWithPath(
'person:person.person:name.personal-details:credit-name',
)
Expand All @@ -41,13 +51,13 @@ export default {

if (!name) {
if (!firstname) {
reject({
return reject({
type: 'given',
message:
'Your ORCID account does not have a given name, or it is not public. Please fix your account before continuing.',
})
} else if (!lastname) {
reject({
return reject({
type: 'family',
message:
'Your ORCID account does not have a family name, or it is not public. Please fix your account before continuing.',
Expand All @@ -58,7 +68,7 @@ export default {
}

if (!email) {
reject({
return reject({
type: 'email',
message:
'Your ORCID account does not have an e-mail, or your e-mail is not public. Please fix your account before continuing.',
Expand Down