Ghost/core/server/services/members/SingleUseTokenProvider.js
Fabien 'egg' O'Carroll 7c5a3bb537
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
2020-09-18 17:32:18 +01:00

68 lines
1.8 KiB
JavaScript

// @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;