ghost 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/tryghost-i18n-6.4.0.tgz +0 -0
- package/core/built/admin/assets/activitypub/activitypub.js +6 -0
- package/core/built/admin/assets/{admin-x-activitypub/index-C8tyOPu-.mjs → activitypub/index-2Xkl6sDz.mjs} +2 -2
- package/core/built/admin/assets/{admin-x-activitypub/index-QqbAPyqT.mjs → activitypub/index-CKBuhv6F.mjs} +13906 -13199
- package/core/built/admin/assets/{chunk.524.aac61953956de04feb53.js → chunk.524.5fd100e92f0b07a59e66.js} +6 -6
- package/core/built/admin/assets/{chunk.582.0a1461429ddbaef85ea9.js → chunk.582.411877f4da4644562f10.js} +8 -8
- package/core/built/admin/assets/{ghost-1bfab97cb7f550726e894fae6650a808.js → ghost-e7b2c10bfc27fe1ef28d2697e9e7551b.js} +20 -38
- package/core/built/admin/assets/posts/posts.js +5529 -5511
- package/core/built/admin/assets/stats/stats.js +20 -20
- package/core/built/admin/index.html +3 -3
- package/core/frontend/helpers/reading_time.js +4 -4
- package/core/server/api/endpoints/utils/serializers/output/utils/extra-attrs.js +8 -3
- package/core/server/data/migrations/versions/6.4/2025-10-13-10-18-38-add-tokens-otc-used-count-column.js +8 -0
- package/core/server/data/schema/schema.js +3 -2
- package/core/server/models/single-use-token.js +1 -0
- package/core/server/services/email-service/email-templates/template.hbs +0 -2
- package/core/server/services/lib/MailgunClient.js +2 -1
- package/core/server/services/lib/magic-link/MagicLink.js +0 -1
- package/core/server/services/mail/GhostMailer.js +21 -0
- package/core/server/services/members/SingleUseTokenProvider.js +218 -62
- package/core/server/services/stats/ReferrersStatsService.js +237 -111
- package/core/server/services/stats/StatsService.js +4 -4
- package/package.json +6 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +381 -243
- package/components/tryghost-i18n-6.3.0.tgz +0 -0
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +0 -6
- package/core/server/services/lib/magic-link/JWTTokenProvider.js +0 -52
- package/core/server/services/lib/magic-link/README.md +0 -60
- /package/core/built/admin/assets/{admin-x-activitypub → activitypub}/styles/reader.css +0 -0
|
@@ -8,12 +8,18 @@ const readingMinutes = require('@tryghost/helpers').utils.readingMinutes;
|
|
|
8
8
|
* @returns {void} - modifies attrs
|
|
9
9
|
*/
|
|
10
10
|
module.exports.forPost = (options, model, attrs) => {
|
|
11
|
+
// requested via `columns`
|
|
11
12
|
const columnsIncludesCustomExcerpt = options.columns?.includes('custom_excerpt');
|
|
12
13
|
const columnsIncludesExcerpt = options.columns?.includes('excerpt');
|
|
13
14
|
const columnsIncludesPlaintext = options.columns?.includes('plaintext');
|
|
14
15
|
const columnsIncludesReadingTime = options.columns?.includes('reading_time');
|
|
16
|
+
|
|
17
|
+
// requested via `formats`
|
|
15
18
|
const formatsIncludesPlaintext = options.formats?.includes('plaintext');
|
|
16
19
|
|
|
20
|
+
// no columns requested
|
|
21
|
+
const noColumnsRequested = !Object.prototype.hasOwnProperty.call(options, 'columns');
|
|
22
|
+
|
|
17
23
|
// 1. Gets excerpt from post's plaintext. If custom_excerpt exists, it overrides the excerpt but the key remains excerpt.
|
|
18
24
|
if (columnsIncludesExcerpt) {
|
|
19
25
|
if (!attrs.custom_excerpt) {
|
|
@@ -32,7 +38,6 @@ module.exports.forPost = (options, model, attrs) => {
|
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
// 2. Displays plaintext if requested via `columns` or `formats`
|
|
36
41
|
if (columnsIncludesPlaintext || formatsIncludesPlaintext) {
|
|
37
42
|
let plaintext = model.get('plaintext');
|
|
38
43
|
if (plaintext) {
|
|
@@ -43,7 +48,7 @@ module.exports.forPost = (options, model, attrs) => {
|
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
// 3. Displays excerpt if no columns was requested - specifically needed for the Admin Posts API
|
|
46
|
-
if (
|
|
51
|
+
if (noColumnsRequested) {
|
|
47
52
|
let customExcerpt = model.get('custom_excerpt');
|
|
48
53
|
|
|
49
54
|
if (customExcerpt !== null) {
|
|
@@ -59,7 +64,7 @@ module.exports.forPost = (options, model, attrs) => {
|
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
// 4. Add `reading_time` if no columns were requested, or if `reading_time` was requested via `columns`
|
|
62
|
-
if (
|
|
67
|
+
if (noColumnsRequested || columnsIncludesReadingTime) {
|
|
63
68
|
if (attrs.html) {
|
|
64
69
|
let additionalImages = 0;
|
|
65
70
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* String Column Sizes Information
|
|
2
2
|
* (From: https://github.com/TryGhost/Ghost/pull/7932)
|
|
3
3
|
* New/Updated column maxlengths should meet these guidlines
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Small strings = length 50
|
|
6
6
|
* Medium strings = length 191
|
|
7
7
|
* Large strings = length 2000 (use soft limits via validation for 191-2000)
|
|
@@ -925,7 +925,8 @@ module.exports = {
|
|
|
925
925
|
created_at: {type: 'dateTime', nullable: false},
|
|
926
926
|
updated_at: {type: 'dateTime', nullable: true},
|
|
927
927
|
first_used_at: {type: 'dateTime', nullable: true},
|
|
928
|
-
used_count: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
|
|
928
|
+
used_count: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0},
|
|
929
|
+
otc_used_count: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
|
|
929
930
|
},
|
|
930
931
|
snippets: {
|
|
931
932
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
@@ -9,12 +9,10 @@
|
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<span class="preheader">{{preheader}}</span>
|
|
12
|
-
{{#hasFeature 'emailCustomization'}}
|
|
13
12
|
<!-- SPACING TO AVOID BODY TEXT BEING DUPLICATED IN PREVIEW TEXT -->
|
|
14
13
|
<div style="display:none; max-height:0; overflow:hidden; mso-hide: all;" aria-hidden="true" role="presentation">
|
|
15
14
|
{{{preheaderSpacing}}}
|
|
16
15
|
</div>
|
|
17
|
-
{{/hasFeature}}
|
|
18
16
|
|
|
19
17
|
<!-- HEADER WITH FULL-WIDTH BACKGROUND -->
|
|
20
18
|
{{#if hasHeaderContent}}
|
|
@@ -68,7 +68,8 @@ module.exports = class MailgunClient {
|
|
|
68
68
|
html: messageContent.html,
|
|
69
69
|
text: messageContent.plaintext,
|
|
70
70
|
'recipient-variables': JSON.stringify(recipientData),
|
|
71
|
-
'h:Sender': message.from
|
|
71
|
+
'h:Sender': message.from,
|
|
72
|
+
'h:Auto-Submitted': 'auto-generated'
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
// Do we have a custom List-Unsubscribe header set?
|
|
@@ -17,6 +17,7 @@ const messages = {
|
|
|
17
17
|
messageSent: 'Message sent. Double check inbox and spam folder!'
|
|
18
18
|
};
|
|
19
19
|
const EmailAddressParser = require('../email-address/EmailAddressParser');
|
|
20
|
+
const DEFAULT_TAGS = ['ghost-email', 'transactional-email'];
|
|
20
21
|
|
|
21
22
|
function getDomain() {
|
|
22
23
|
const domain = urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
|
|
@@ -129,6 +130,15 @@ module.exports = class GhostMailer {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
const messageToSend = createMessage(message);
|
|
133
|
+
if (this.state.usingMailgun) {
|
|
134
|
+
const tags = this.getTags();
|
|
135
|
+
if (tags.length > 0) {
|
|
136
|
+
messageToSend['o:tag'] = tags;
|
|
137
|
+
}
|
|
138
|
+
if (settingsCache.get('emailTrackOpens')) {
|
|
139
|
+
messageToSend['o:tracking-opens'] = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
132
142
|
|
|
133
143
|
const response = await this.sendMail(messageToSend);
|
|
134
144
|
|
|
@@ -184,4 +194,15 @@ module.exports = class GhostMailer {
|
|
|
184
194
|
|
|
185
195
|
return tpl(messages.messageSent);
|
|
186
196
|
}
|
|
197
|
+
|
|
198
|
+
getTags() {
|
|
199
|
+
const tagList = [...DEFAULT_TAGS];
|
|
200
|
+
|
|
201
|
+
const siteId = config.get('hostSettings:siteId');
|
|
202
|
+
if (siteId) {
|
|
203
|
+
tagList.push(`blog-${siteId}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return tagList;
|
|
207
|
+
}
|
|
187
208
|
};
|
|
@@ -9,6 +9,7 @@ const messages = {
|
|
|
9
9
|
INVALID_OTC_VERIFICATION_HASH: 'Invalid OTC verification hash',
|
|
10
10
|
INVALID_TOKEN: 'Invalid token provided',
|
|
11
11
|
TOKEN_EXPIRED: 'Token expired',
|
|
12
|
+
OTC_EXPIRED: 'One-time code expired',
|
|
12
13
|
DERIVE_OTC_MISSING_INPUT: 'tokenId and tokenValue are required'
|
|
13
14
|
};
|
|
14
15
|
|
|
@@ -56,7 +57,7 @@ class SingleUseTokenProvider {
|
|
|
56
57
|
* @param {Object} [options.transacting] - Database transaction object
|
|
57
58
|
* @param {string} [options.otcVerification] - OTC verification hash for additional validation
|
|
58
59
|
*
|
|
59
|
-
* @returns {Promise<Object<string,
|
|
60
|
+
* @returns {Promise<Object<string, unknown>>}
|
|
60
61
|
*/
|
|
61
62
|
async validate(token, options = {}) {
|
|
62
63
|
if (!options.transacting) {
|
|
@@ -68,66 +69,13 @@ class SingleUseTokenProvider {
|
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
const isValidOTCVerification = await this._validateOTCVerificationHash(options.otcVerification, token);
|
|
73
|
-
if (!isValidOTCVerification) {
|
|
74
|
-
throw new ValidationError({
|
|
75
|
-
message: tpl(messages.INVALID_OTC_VERIFICATION_HASH),
|
|
76
|
-
code: 'INVALID_OTC_VERIFICATION_HASH'
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const model = await this.model.findOne({token}, {transacting: options.transacting, forUpdate: true});
|
|
82
|
-
|
|
83
|
-
if (!model) {
|
|
84
|
-
throw new ValidationError({
|
|
85
|
-
message: tpl(messages.INVALID_TOKEN),
|
|
86
|
-
code: 'INVALID_TOKEN'
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (model.get('used_count') >= this.maxUsageCount) {
|
|
91
|
-
throw new ValidationError({
|
|
92
|
-
message: tpl(messages.TOKEN_EXPIRED),
|
|
93
|
-
code: 'TOKEN_EXPIRED'
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const createdAtEpoch = model.get('created_at').getTime();
|
|
98
|
-
const firstUsedAtEpoch = model.get('first_used_at')?.getTime() ?? createdAtEpoch;
|
|
99
|
-
|
|
100
|
-
// Is this token already used?
|
|
101
|
-
if (model.get('used_count') > 0) {
|
|
102
|
-
const timeSinceFirstUsage = Date.now() - firstUsedAtEpoch;
|
|
103
|
-
|
|
104
|
-
if (timeSinceFirstUsage > this.validityPeriodAfterUsage) {
|
|
105
|
-
throw new ValidationError({
|
|
106
|
-
message: tpl(messages.TOKEN_EXPIRED),
|
|
107
|
-
code: 'TOKEN_EXPIRED'
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const tokenLifetimeMilliseconds = Date.now() - createdAtEpoch;
|
|
112
|
-
|
|
113
|
-
if (tokenLifetimeMilliseconds > this.validityPeriod) {
|
|
114
|
-
throw new ValidationError({
|
|
115
|
-
message: tpl(messages.TOKEN_EXPIRED),
|
|
116
|
-
code: 'TOKEN_EXPIRED'
|
|
117
|
-
});
|
|
118
|
-
}
|
|
72
|
+
const model = await this._findAndLockTokenModel(token, options.transacting);
|
|
119
73
|
|
|
120
|
-
if (
|
|
121
|
-
await model.
|
|
122
|
-
|
|
123
|
-
updated_at: new Date(),
|
|
124
|
-
used_count: model.get('used_count') + 1
|
|
125
|
-
}, {autoRefresh: false, patch: true, transacting: options.transacting});
|
|
74
|
+
if (options.otcVerification) {
|
|
75
|
+
await this._validateOTCVerificationHash(options.otcVerification, model.get('token'));
|
|
76
|
+
await this._validateOTCUsageLimit(model, options.transacting);
|
|
126
77
|
} else {
|
|
127
|
-
await model.
|
|
128
|
-
used_count: model.get('used_count') + 1,
|
|
129
|
-
updated_at: new Date()
|
|
130
|
-
}, {autoRefresh: false, patch: true, transacting: options.transacting});
|
|
78
|
+
await this._validateUsageLimit(model, options.transacting);
|
|
131
79
|
}
|
|
132
80
|
|
|
133
81
|
try {
|
|
@@ -137,6 +85,40 @@ class SingleUseTokenProvider {
|
|
|
137
85
|
}
|
|
138
86
|
}
|
|
139
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @private
|
|
90
|
+
* @method _validateOTCUsageLimit
|
|
91
|
+
* Validates a token model is within it's usage limits after additional OTC verification..
|
|
92
|
+
* OTC bypasses the non-OTC usage count and time-since-first-usage validation but is true single-use.
|
|
93
|
+
*
|
|
94
|
+
* @param {Object} model - Token model instance
|
|
95
|
+
* @param {Object} transaction - Database transaction object
|
|
96
|
+
*
|
|
97
|
+
* @returns {Promise<void>}
|
|
98
|
+
*/
|
|
99
|
+
async _validateOTCUsageLimit(model, transaction) {
|
|
100
|
+
this._validateOTCUsageCount(model);
|
|
101
|
+
this._validateTotalTokenLifetime(model);
|
|
102
|
+
await this._incrementOTCUsageCount(model, transaction);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @private
|
|
107
|
+
* @method _validateUsageLimit
|
|
108
|
+
* Validates a token model is within it's usage limits
|
|
109
|
+
*
|
|
110
|
+
* @param {Object} model - Token model instance
|
|
111
|
+
* @param {Object} transaction - Database transaction object
|
|
112
|
+
*
|
|
113
|
+
* @returns {Promise<void>}
|
|
114
|
+
*/
|
|
115
|
+
async _validateUsageLimit(model, transaction) {
|
|
116
|
+
this._validateUsageCount(model);
|
|
117
|
+
this._validateTimeSinceFirstUsage(model);
|
|
118
|
+
this._validateTotalTokenLifetime(model);
|
|
119
|
+
await this._incrementUsageCount(model, transaction);
|
|
120
|
+
}
|
|
121
|
+
|
|
140
122
|
/**
|
|
141
123
|
* @private
|
|
142
124
|
* @method deriveCounter
|
|
@@ -268,17 +250,59 @@ class SingleUseTokenProvider {
|
|
|
268
250
|
return hmac.digest('hex');
|
|
269
251
|
}
|
|
270
252
|
|
|
253
|
+
/**
|
|
254
|
+
* @private
|
|
255
|
+
* @method _findAndLockTokenModel
|
|
256
|
+
* Finds token in database and locks it for update
|
|
257
|
+
*
|
|
258
|
+
* @param {string} token
|
|
259
|
+
* @param {Object} transacting
|
|
260
|
+
* @returns {Promise<Object>}
|
|
261
|
+
*/
|
|
262
|
+
async _findAndLockTokenModel(token, transacting) {
|
|
263
|
+
const model = await this.model.findOne({token}, {transacting, forUpdate: true});
|
|
264
|
+
|
|
265
|
+
if (!model) {
|
|
266
|
+
throw new ValidationError({
|
|
267
|
+
message: tpl(messages.INVALID_TOKEN),
|
|
268
|
+
code: 'INVALID_TOKEN'
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return model;
|
|
273
|
+
}
|
|
274
|
+
|
|
271
275
|
/**
|
|
272
276
|
* @private
|
|
273
277
|
* @method _validateOTCVerificationHash
|
|
274
|
-
* Validates OTC verification hash
|
|
275
|
-
* Private because it's only used internally by the public validate method.
|
|
278
|
+
* Validates OTC verification hash, throwing on invalid hash.
|
|
276
279
|
*
|
|
277
280
|
* @param {string} otcVerificationHash - The hash to validate (timestamp:hash format)
|
|
278
281
|
* @param {string} token - The token value
|
|
279
|
-
* @returns {Promise<
|
|
282
|
+
* @returns {Promise<void>}
|
|
280
283
|
*/
|
|
281
284
|
async _validateOTCVerificationHash(otcVerificationHash, token) {
|
|
285
|
+
const isValid = await this._isValidOTCVerificationHash(otcVerificationHash, token);
|
|
286
|
+
|
|
287
|
+
if (!isValid) {
|
|
288
|
+
throw new ValidationError({
|
|
289
|
+
message: tpl(messages.INVALID_OTC_VERIFICATION_HASH),
|
|
290
|
+
code: 'INVALID_OTC_VERIFICATION_HASH'
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* @private
|
|
297
|
+
* @method _isValidOTCVerificationHash
|
|
298
|
+
* Validates OTC verification hash by recreating and comparing the hash.
|
|
299
|
+
* Returns true if the hash is valid, false otherwise.
|
|
300
|
+
*
|
|
301
|
+
* @param {string} otcVerificationHash - The hash to validate (timestamp:hash format)
|
|
302
|
+
* @param {string} token - The token value
|
|
303
|
+
* @returns {Promise<boolean>}
|
|
304
|
+
*/
|
|
305
|
+
async _isValidOTCVerificationHash(otcVerificationHash, token) {
|
|
282
306
|
try {
|
|
283
307
|
if (!this.secret || !otcVerificationHash || !token) {
|
|
284
308
|
return false;
|
|
@@ -319,6 +343,138 @@ class SingleUseTokenProvider {
|
|
|
319
343
|
return false;
|
|
320
344
|
}
|
|
321
345
|
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @private
|
|
349
|
+
* @method _validateUsageCount
|
|
350
|
+
* Validates that token has not exceeded usage limits, throws on over-used token
|
|
351
|
+
*
|
|
352
|
+
* @param {Object} model - The token model
|
|
353
|
+
* @returns {void}
|
|
354
|
+
*/
|
|
355
|
+
_validateUsageCount(model) {
|
|
356
|
+
// Magic links are invalid if OTC has been used
|
|
357
|
+
const otcUsedCount = model.get('otc_used_count') || 0;
|
|
358
|
+
if (otcUsedCount > 0) {
|
|
359
|
+
throw new ValidationError({
|
|
360
|
+
message: tpl(messages.TOKEN_EXPIRED),
|
|
361
|
+
code: 'TOKEN_EXPIRED'
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (model.get('used_count') >= this.maxUsageCount) {
|
|
366
|
+
throw new ValidationError({
|
|
367
|
+
message: tpl(messages.TOKEN_EXPIRED),
|
|
368
|
+
code: 'TOKEN_EXPIRED'
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @private
|
|
375
|
+
* @method _validateOTCUsageCount
|
|
376
|
+
* Validates that OTC has not exceeded usage limits, throws on over-used token
|
|
377
|
+
*
|
|
378
|
+
* @param {Object} model - The token model
|
|
379
|
+
* @returns {void}
|
|
380
|
+
*/
|
|
381
|
+
_validateOTCUsageCount(model) {
|
|
382
|
+
const otcUsedCount = model.get('otc_used_count') || 0;
|
|
383
|
+
if (otcUsedCount >= 1) {
|
|
384
|
+
throw new ValidationError({
|
|
385
|
+
message: tpl(messages.OTC_EXPIRED),
|
|
386
|
+
code: 'OTC_EXPIRED'
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* @private
|
|
393
|
+
* @method _validateTimeSinceFirstUsage
|
|
394
|
+
* Validates token has not exceeded its time since first usage, throws on expired token
|
|
395
|
+
*
|
|
396
|
+
* @param {Object} model - The token model
|
|
397
|
+
* @returns {void}
|
|
398
|
+
*/
|
|
399
|
+
_validateTimeSinceFirstUsage(model) {
|
|
400
|
+
const createdAtEpoch = model.get('created_at').getTime();
|
|
401
|
+
const firstUsedAtEpoch = model.get('first_used_at')?.getTime() ?? createdAtEpoch;
|
|
402
|
+
const timeSinceFirstUsage = Date.now() - firstUsedAtEpoch;
|
|
403
|
+
|
|
404
|
+
if (timeSinceFirstUsage > this.validityPeriodAfterUsage) {
|
|
405
|
+
throw new ValidationError({
|
|
406
|
+
message: tpl(messages.TOKEN_EXPIRED),
|
|
407
|
+
code: 'TOKEN_EXPIRED'
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @private
|
|
414
|
+
* @method _validateTotalTokenLifetime
|
|
415
|
+
* Validates token has not exceeded its total lifetime, throws on expired token
|
|
416
|
+
*
|
|
417
|
+
* @param {Object} model - The token model
|
|
418
|
+
* @returns {void}
|
|
419
|
+
*/
|
|
420
|
+
_validateTotalTokenLifetime(model) {
|
|
421
|
+
const createdAtEpoch = model.get('created_at').getTime();
|
|
422
|
+
const tokenLifetimeMilliseconds = Date.now() - createdAtEpoch;
|
|
423
|
+
if (tokenLifetimeMilliseconds > this.validityPeriod) {
|
|
424
|
+
throw new ValidationError({
|
|
425
|
+
message: tpl(messages.TOKEN_EXPIRED),
|
|
426
|
+
code: 'TOKEN_EXPIRED'
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @private
|
|
433
|
+
* @method _incrementUsageCount
|
|
434
|
+
* Increments the usage count for a token
|
|
435
|
+
*
|
|
436
|
+
* @param {Object} model - The token model
|
|
437
|
+
* @param {Object} transacting - Database transaction object
|
|
438
|
+
* @returns {Promise<void>}
|
|
439
|
+
*/
|
|
440
|
+
async _incrementUsageCount(model, transacting) {
|
|
441
|
+
const updateData = {used_count: model.get('used_count') + 1};
|
|
442
|
+
await this._saveUsageData(updateData, model, transacting);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @private
|
|
447
|
+
* @method _incrementOTCUsageCount
|
|
448
|
+
* Increments the OTC usage count for a token
|
|
449
|
+
*
|
|
450
|
+
* @param {Object} model - The token model
|
|
451
|
+
* @param transacting - Database transaction object
|
|
452
|
+
* @returns {Promise<void>}
|
|
453
|
+
*/
|
|
454
|
+
async _incrementOTCUsageCount(model, transacting) {
|
|
455
|
+
const updateData = {otc_used_count: model.get('otc_used_count') + 1};
|
|
456
|
+
await this._saveUsageData(updateData, model, transacting);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* @private
|
|
461
|
+
* @method _saveUsageData
|
|
462
|
+
* Saves the usage data for a token
|
|
463
|
+
*
|
|
464
|
+
* @param {Object} updateData - The data to save
|
|
465
|
+
* @param {Object} model - The token model
|
|
466
|
+
* @param {Object} transacting - Database transaction object
|
|
467
|
+
* @returns {Promise<void>}
|
|
468
|
+
*/
|
|
469
|
+
async _saveUsageData(updateData, model, transacting) {
|
|
470
|
+
updateData.updated_at = new Date();
|
|
471
|
+
|
|
472
|
+
if (!model.get('first_used_at')) {
|
|
473
|
+
updateData.first_used_at = new Date();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
await model.save(updateData, {autoRefresh: false, patch: true, transacting});
|
|
477
|
+
}
|
|
322
478
|
}
|
|
323
479
|
|
|
324
480
|
module.exports = SingleUseTokenProvider;
|