0
Fork 0
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:
Fabien 'egg' O'Carroll 2020-09-18 17:32:18 +01:00 committed by GitHub
parent 3c0f2d1595
commit 7c5a3bb537
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 18 deletions

View 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;

View file

@ -6,6 +6,8 @@ const models = require('../../models');
const signinEmail = require('./emails/signin');
const signupEmail = require('./emails/signup');
const subscribeEmail = require('./emails/subscribe');
const SingleUseTokenProvider = require('./SingleUseTokenProvider');
const MAGIC_LINK_TOKEN_VALIDITY = 4 * 60 * 60 * 1000;
const ghostMailer = new mail.GhostMailer();
@ -17,7 +19,7 @@ function createApiInstance(config) {
auth: {
getSigninURL: config.getSigninURL.bind(config),
allowSelfSignup: config.getAllowSelfSignup(),
secret: config.getAuthSecret()
tokenProvider: new SingleUseTokenProvider(models.SingleUseToken, MAGIC_LINK_TOKEN_VALIDITY)
},
mail: {
transporter: {

View file

@ -55,7 +55,7 @@
"@tryghost/kg-markdown-html-renderer": "2.0.2",
"@tryghost/kg-mobiledoc-html-renderer": "3.0.1",
"@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-ssr": "0.8.5",
"@tryghost/mw-session-from-token": "0.1.7",

View file

@ -481,7 +481,7 @@
dependencies:
"@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"
resolved "https://registry.yarnpkg.com/@tryghost/magic-link/-/magic-link-0.6.0.tgz#a656e30385b60f44e5678819d6184c65f0757794"
integrity sha512-KeE1dpCCAhZy34pjXirn1w1Oq4+dNsX9XqyZMvdruiYEB+88WkL/x//aNGCo84RoerTr1W8TGQF/1lh4ulrsZg==
@ -491,22 +491,12 @@
jsonwebtoken "^8.5.1"
lodash "^4.17.15"
"@tryghost/magic-link@^0.4.13":
version "0.4.13"
resolved "https://registry.yarnpkg.com/@tryghost/magic-link/-/magic-link-0.4.13.tgz#266940cec6f0fe837071621b43dfaa08f552d23c"
integrity sha512-m5rz9rDEeRwrDePRiw/5t3D/yfDkqtvhjWNRUqL0NXhvD/m8f8soP1m1fY46JVaUd35xGDa4BQRnFhXerNId3w==
"@tryghost/members-api@0.30.0":
version "0.30.0"
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-0.30.0.tgz#459a82369786110e80e9d0287e1a1acf80edc688"
integrity sha512-meu0ipF0AGpY8f80yRbllW1XSpjQWeFwTR0Coq0Mpzsw9u6Ryodppq2uUSxFCUKyb6BtJLW6q+pJAvgB+eV2Gg==
dependencies:
bluebird "^3.5.5"
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"
"@tryghost/magic-link" "^0.6.0"
bluebird "^3.5.4"
body-parser "^1.19.0"
cookies "^0.8.0"