mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Updated magic links to use shorter, single us, longer lived tokens (#12218)
no-issue * Added SingleUseTokenProvider to members service This implements the TokenProvider interface required by members-api to generate magic links. It handles checking if the token is expired and pulls out any associated data. Future improvments may include the email in the error for expired tokens, which would make resending a token simpler. * Passed SingleUseTokenProvider to members-api This sets up the members-api module to use the new single use tokens * Installed @tryghost/members-api@0.30.0 This includes the change to allow us to pass a token provider to the members-api
This commit is contained in:
parent
3c0f2d1595
commit
7c5a3bb537
4 changed files with 77 additions and 18 deletions
67
core/server/services/members/SingleUseTokenProvider.js
Normal file
67
core/server/services/members/SingleUseTokenProvider.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// @ts-check
|
||||||
|
const {UnauthorizedError} = require('@tryghost/errors');
|
||||||
|
|
||||||
|
class SingleUseTokenProvider {
|
||||||
|
/**
|
||||||
|
* @param {import('../../models/base')} SingleUseTokenModel - A model for creating and retrieving tokens.
|
||||||
|
* @param {number} validity - How long a token is valid for from it's creation in milliseconds.
|
||||||
|
*/
|
||||||
|
constructor(SingleUseTokenModel, validity) {
|
||||||
|
this.model = SingleUseTokenModel;
|
||||||
|
this.validity = validity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method create
|
||||||
|
* Creates and stores a token, with the passed data associated with it.
|
||||||
|
* Returns the created token value.
|
||||||
|
*
|
||||||
|
* @param {Object<string, any>} data
|
||||||
|
*
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
async create(data) {
|
||||||
|
const model = await this.model.add({
|
||||||
|
data: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
return model.get('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method validate
|
||||||
|
* Validates a token, returning any parsable data associated.
|
||||||
|
* If the token is invalid the returned Promise will reject.
|
||||||
|
*
|
||||||
|
* @param {string} token
|
||||||
|
*
|
||||||
|
* @returns {Promise<Object<string, any>>}
|
||||||
|
*/
|
||||||
|
async validate(token) {
|
||||||
|
const model = await this.model.findOne({token});
|
||||||
|
|
||||||
|
if (!model) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: 'Invalid token provided'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdAtEpoch = model.get('created_at').getTime();
|
||||||
|
|
||||||
|
const tokenLifetimeMilliseconds = Date.now() - createdAtEpoch;
|
||||||
|
|
||||||
|
if (tokenLifetimeMilliseconds > this.validity) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: 'Token expired'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(model.get('data'));
|
||||||
|
} catch (err) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SingleUseTokenProvider;
|
|
@ -6,6 +6,8 @@ const models = require('../../models');
|
||||||
const signinEmail = require('./emails/signin');
|
const signinEmail = require('./emails/signin');
|
||||||
const signupEmail = require('./emails/signup');
|
const signupEmail = require('./emails/signup');
|
||||||
const subscribeEmail = require('./emails/subscribe');
|
const subscribeEmail = require('./emails/subscribe');
|
||||||
|
const SingleUseTokenProvider = require('./SingleUseTokenProvider');
|
||||||
|
const MAGIC_LINK_TOKEN_VALIDITY = 4 * 60 * 60 * 1000;
|
||||||
|
|
||||||
const ghostMailer = new mail.GhostMailer();
|
const ghostMailer = new mail.GhostMailer();
|
||||||
|
|
||||||
|
@ -17,7 +19,7 @@ function createApiInstance(config) {
|
||||||
auth: {
|
auth: {
|
||||||
getSigninURL: config.getSigninURL.bind(config),
|
getSigninURL: config.getSigninURL.bind(config),
|
||||||
allowSelfSignup: config.getAllowSelfSignup(),
|
allowSelfSignup: config.getAllowSelfSignup(),
|
||||||
secret: config.getAuthSecret()
|
tokenProvider: new SingleUseTokenProvider(models.SingleUseToken, MAGIC_LINK_TOKEN_VALIDITY)
|
||||||
},
|
},
|
||||||
mail: {
|
mail: {
|
||||||
transporter: {
|
transporter: {
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
"@tryghost/kg-markdown-html-renderer": "2.0.2",
|
"@tryghost/kg-markdown-html-renderer": "2.0.2",
|
||||||
"@tryghost/kg-mobiledoc-html-renderer": "3.0.1",
|
"@tryghost/kg-mobiledoc-html-renderer": "3.0.1",
|
||||||
"@tryghost/magic-link": "0.6.0",
|
"@tryghost/magic-link": "0.6.0",
|
||||||
"@tryghost/members-api": "0.28.2",
|
"@tryghost/members-api": "0.30.0",
|
||||||
"@tryghost/members-csv": "0.3.0",
|
"@tryghost/members-csv": "0.3.0",
|
||||||
"@tryghost/members-ssr": "0.8.5",
|
"@tryghost/members-ssr": "0.8.5",
|
||||||
"@tryghost/mw-session-from-token": "0.1.7",
|
"@tryghost/mw-session-from-token": "0.1.7",
|
||||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -481,7 +481,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@tryghost/kg-clean-basic-html" "^1.0.6"
|
"@tryghost/kg-clean-basic-html" "^1.0.6"
|
||||||
|
|
||||||
"@tryghost/magic-link@0.6.0":
|
"@tryghost/magic-link@0.6.0", "@tryghost/magic-link@^0.6.0":
|
||||||
version "0.6.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/magic-link/-/magic-link-0.6.0.tgz#a656e30385b60f44e5678819d6184c65f0757794"
|
resolved "https://registry.yarnpkg.com/@tryghost/magic-link/-/magic-link-0.6.0.tgz#a656e30385b60f44e5678819d6184c65f0757794"
|
||||||
integrity sha512-KeE1dpCCAhZy34pjXirn1w1Oq4+dNsX9XqyZMvdruiYEB+88WkL/x//aNGCo84RoerTr1W8TGQF/1lh4ulrsZg==
|
integrity sha512-KeE1dpCCAhZy34pjXirn1w1Oq4+dNsX9XqyZMvdruiYEB+88WkL/x//aNGCo84RoerTr1W8TGQF/1lh4ulrsZg==
|
||||||
|
@ -491,22 +491,12 @@
|
||||||
jsonwebtoken "^8.5.1"
|
jsonwebtoken "^8.5.1"
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
|
|
||||||
"@tryghost/magic-link@^0.4.13":
|
"@tryghost/members-api@0.30.0":
|
||||||
version "0.4.13"
|
version "0.30.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/magic-link/-/magic-link-0.4.13.tgz#266940cec6f0fe837071621b43dfaa08f552d23c"
|
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-0.30.0.tgz#459a82369786110e80e9d0287e1a1acf80edc688"
|
||||||
integrity sha512-m5rz9rDEeRwrDePRiw/5t3D/yfDkqtvhjWNRUqL0NXhvD/m8f8soP1m1fY46JVaUd35xGDa4BQRnFhXerNId3w==
|
integrity sha512-meu0ipF0AGpY8f80yRbllW1XSpjQWeFwTR0Coq0Mpzsw9u6Ryodppq2uUSxFCUKyb6BtJLW6q+pJAvgB+eV2Gg==
|
||||||
dependencies:
|
dependencies:
|
||||||
bluebird "^3.5.5"
|
"@tryghost/magic-link" "^0.6.0"
|
||||||
ghost-ignition "4.2.2"
|
|
||||||
jsonwebtoken "^8.5.1"
|
|
||||||
lodash "^4.17.15"
|
|
||||||
|
|
||||||
"@tryghost/members-api@0.28.2":
|
|
||||||
version "0.28.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-0.28.2.tgz#9ed4464754f9b8e70d1d4e62e8e50ca139346b6d"
|
|
||||||
integrity sha512-LgfhjLpSUusyjrDIDreXEH8PqdIsQ97q77opl3MRITMhTUS9qz0Q6GfCpuZfq0nVQ1qGx79pPC4K3CQqNRK/4w==
|
|
||||||
dependencies:
|
|
||||||
"@tryghost/magic-link" "^0.4.13"
|
|
||||||
bluebird "^3.5.4"
|
bluebird "^3.5.4"
|
||||||
body-parser "^1.19.0"
|
body-parser "^1.19.0"
|
||||||
cookies "^0.8.0"
|
cookies "^0.8.0"
|
||||||
|
|
Loading…
Reference in a new issue