-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrated authentication controller to v2 (#10950)
refs #10060 - Migrated authentication.resetPassword method to v2 - Migrated authentication.acceptInvitation method to v2 - Migrated authentication.setup method to v2 - Added missing test coverage for "setupUpdate" method - Migrated authentication.updateSetup method to v2 - Migrated authentication.isInvitation method to v2 - Migrated authentication.isSetup method to v2 - Removed unused 'setup.completed' event as it wasn's used anywhere in the system and has been complicating the logic unnecessarily - Without the event, it's possible to simplify sendNotification method to just use email address of the user - Added email sending check to v0.1 test suite - Refactored sendNotification method to just use email address as parameter - Renamed sendNotification to sendWelcomeMail - The only thing the method does now is sending welcome mail, so new naming seems natural :)
- Loading branch information
Showing
15 changed files
with
763 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
const api = require('./index'); | ||
const config = require('../../config'); | ||
const common = require('../../lib/common'); | ||
const web = require('../../web'); | ||
const models = require('../../models'); | ||
const auth = require('../../services/auth'); | ||
const invitations = require('../../services/invitations'); | ||
|
||
module.exports = { | ||
docName: 'authentication', | ||
|
||
setup: { | ||
statusCode: 201, | ||
permissions: false, | ||
validation: { | ||
docName: 'setup' | ||
}, | ||
query(frame) { | ||
return Promise.resolve() | ||
.then(() => { | ||
return auth.setup.assertSetupCompleted(false)(); | ||
}) | ||
.then(() => { | ||
const setupDetails = { | ||
name: frame.data.setup[0].name, | ||
email: frame.data.setup[0].email, | ||
password: frame.data.setup[0].password, | ||
blogTitle: frame.data.setup[0].blogTitle, | ||
status: 'active' | ||
}; | ||
|
||
return auth.setup.setupUser(setupDetails); | ||
}) | ||
.then((data) => { | ||
return auth.setup.doSettings(data, api.settings); | ||
}) | ||
.then((user) => { | ||
return auth.setup.sendWelcomeEmail(user.get('email'), api.mail) | ||
.then(() => user); | ||
}); | ||
} | ||
}, | ||
|
||
updateSetup: { | ||
permissions: (frame) => { | ||
return models.User.findOne({role: 'Owner', status: 'all'}) | ||
.then((owner) => { | ||
if (owner.id !== frame.options.context.user) { | ||
throw new common.errors.NoPermissionError({message: common.i18n.t('errors.api.authentication.notTheBlogOwner')}); | ||
} | ||
}); | ||
}, | ||
validation: { | ||
docName: 'setup' | ||
}, | ||
query(frame) { | ||
return Promise.resolve() | ||
.then(() => { | ||
return auth.setup.assertSetupCompleted(true)(); | ||
}) | ||
.then(() => { | ||
const setupDetails = { | ||
name: frame.data.setup[0].name, | ||
email: frame.data.setup[0].email, | ||
password: frame.data.setup[0].password, | ||
blogTitle: frame.data.setup[0].blogTitle, | ||
status: 'active' | ||
}; | ||
|
||
return auth.setup.setupUser(setupDetails); | ||
}) | ||
.then((data) => { | ||
return auth.setup.doSettings(data, api.settings); | ||
}); | ||
} | ||
}, | ||
|
||
isSetup: { | ||
permissions: false, | ||
query() { | ||
return auth.setup.checkIsSetup() | ||
.then((isSetup) => { | ||
return { | ||
status: isSetup, | ||
// Pre-populate from config if, and only if the values exist in config. | ||
title: config.title || undefined, | ||
name: config.user_name || undefined, | ||
email: config.user_email || undefined | ||
}; | ||
}); | ||
} | ||
}, | ||
|
||
generateResetToken: { | ||
validation: { | ||
docName: 'passwordreset' | ||
}, | ||
permissions: true, | ||
options: [ | ||
'email' | ||
], | ||
query(frame) { | ||
return Promise.resolve() | ||
.then(() => { | ||
return auth.setup.assertSetupCompleted(true)(); | ||
}) | ||
.then(() => { | ||
return auth.passwordreset.generateToken(frame.data.passwordreset[0].email, api.settings); | ||
}) | ||
.then((token) => { | ||
return auth.passwordreset.sendResetNotification(token, api.mail); | ||
}); | ||
} | ||
}, | ||
|
||
resetPassword: { | ||
validation: { | ||
docName: 'passwordreset', | ||
data: { | ||
newPassword: {required: true}, | ||
ne2Password: {required: true} | ||
} | ||
}, | ||
permissions: false, | ||
options: [ | ||
'ip' | ||
], | ||
query(frame) { | ||
return Promise.resolve() | ||
.then(() => { | ||
return auth.setup.assertSetupCompleted(true)(); | ||
}) | ||
.then(() => { | ||
return auth.passwordreset.extractTokenParts(frame); | ||
}) | ||
.then((params) => { | ||
return auth.passwordreset.protectBruteForce(params); | ||
}) | ||
.then(({options, tokenParts}) => { | ||
options = Object.assign(options, {context: {internal: true}}); | ||
return auth.passwordreset.doReset(options, tokenParts, api.settings) | ||
.then((params) => { | ||
web.shared.middlewares.api.spamPrevention.userLogin().reset(frame.options.ip, `${tokenParts.email}login`); | ||
return params; | ||
}); | ||
}); | ||
} | ||
}, | ||
|
||
acceptInvitation: { | ||
validation: { | ||
docName: 'invitations' | ||
}, | ||
permissions: false, | ||
query(frame) { | ||
return Promise.resolve() | ||
.then(() => { | ||
return auth.setup.assertSetupCompleted(true)(); | ||
}) | ||
.then(() => { | ||
return invitations.accept(frame.data); | ||
}); | ||
} | ||
}, | ||
|
||
isInvitation: { | ||
data: [ | ||
'email' | ||
], | ||
validation: { | ||
docName: 'invitations' | ||
}, | ||
permissions: false, | ||
query(frame) { | ||
return Promise.resolve() | ||
.then(() => { | ||
return auth.setup.assertSetupCompleted(true)(); | ||
}) | ||
.then(() => { | ||
const email = frame.data.email; | ||
|
||
return models.Invite.findOne({email: email, status: 'sent'}, frame.options); | ||
}); | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
core/server/api/v2/utils/serializers/output/authentication.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
const common = require('../../../../../lib/common'); | ||
const mapper = require('./utils/mapper'); | ||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:authentication'); | ||
|
||
module.exports = { | ||
setup(user, apiConfig, frame) { | ||
frame.response = { | ||
users: [ | ||
mapper.mapUser(user, {options: {context: {internal: true}}}) | ||
] | ||
}; | ||
}, | ||
|
||
updateSetup(user, apiConfig, frame) { | ||
frame.response = { | ||
users: [ | ||
mapper.mapUser(user, {options: {context: {internal: true}}}) | ||
] | ||
}; | ||
}, | ||
|
||
isSetup(data, apiConfig, frame) { | ||
frame.response = { | ||
setup: [data] | ||
}; | ||
}, | ||
|
||
generateResetToken(data, apiConfig, frame) { | ||
frame.response = { | ||
passwordreset: [{ | ||
message: common.i18n.t('common.api.authentication.mail.checkEmailForInstructions') | ||
}] | ||
}; | ||
}, | ||
|
||
resetPassword(data, apiConfig, frame) { | ||
frame.response = { | ||
passwordreset: [{ | ||
message: common.i18n.t('common.api.authentication.mail.passwordChanged') | ||
}] | ||
}; | ||
}, | ||
|
||
acceptInvitation(data, apiConfig, frame) { | ||
debug('acceptInvitation'); | ||
|
||
frame.response = { | ||
invitation: [ | ||
{message: common.i18n.t('common.api.authentication.mail.invitationAccepted')} | ||
] | ||
}; | ||
}, | ||
|
||
isInvitation(data, apiConfig, frame) { | ||
debug('acceptInvitation'); | ||
|
||
frame.response = { | ||
invitation: [{ | ||
valid: !!data | ||
}] | ||
}; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const Promise = require('bluebird'); | ||
const validator = require('validator'); | ||
const debug = require('ghost-ignition').debug('api:v2:utils:validators:input:invitation'); | ||
const common = require('../../../../../lib/common'); | ||
|
||
module.exports = { | ||
acceptInvitation(apiConfig, frame) { | ||
debug('acceptInvitation'); | ||
|
||
const data = frame.data.invitation[0]; | ||
|
||
if (!data.token) { | ||
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.authentication.noTokenProvided')})); | ||
} | ||
|
||
if (!data.email) { | ||
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.authentication.noEmailProvided')})); | ||
} | ||
|
||
if (!data.password) { | ||
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.authentication.noPasswordProvided')})); | ||
} | ||
|
||
if (!data.name) { | ||
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.authentication.noNameProvided')})); | ||
} | ||
}, | ||
|
||
isInvitation(apiConfig, frame) { | ||
debug('isInvitation'); | ||
|
||
const email = frame.data.email; | ||
|
||
if (typeof email !== 'string' || !validator.isEmail(email)) { | ||
throw new common.errors.BadRequestError({ | ||
message: common.i18n.t('errors.api.authentication.invalidEmailReceived') | ||
}); | ||
} | ||
} | ||
}; |
30 changes: 30 additions & 0 deletions
30
core/server/api/v2/utils/validators/input/passwordreset.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const Promise = require('bluebird'); | ||
const validator = require('validator'); | ||
const debug = require('ghost-ignition').debug('api:v2:utils:validators:input:passwordreset'); | ||
const common = require('../../../../../lib/common'); | ||
|
||
module.exports = { | ||
resetPassword(apiConfig, frame) { | ||
debug('resetPassword'); | ||
|
||
const data = frame.data.passwordreset[0]; | ||
|
||
if (data.newPassword !== data.ne2Password) { | ||
return Promise.reject(new common.errors.ValidationError({ | ||
message: common.i18n.t('errors.models.user.newPasswordsDoNotMatch') | ||
})); | ||
} | ||
}, | ||
|
||
generateResetToken(apiConfig, frame) { | ||
debug('generateResetToken'); | ||
|
||
const email = frame.data.passwordreset[0].email; | ||
|
||
if (typeof email !== 'string' || !validator.isEmail(email)) { | ||
throw new common.errors.BadRequestError({ | ||
message: common.i18n.t('errors.api.authentication.invalidEmailReceived') | ||
}); | ||
} | ||
} | ||
}; |
Oops, something went wrong.