Ghost/ghost/version-notifications-data-service/lib/VersionNotificationsDataService.js
Daniel Lockyer 6c7b230efe Fixed handling requests with mismatching version and missing key
fix https://linear.app/tryghost/issue/SLO-88/typeerror-cannot-read-properties-of-null-reading-relations

- in the event that we make it through the version mismatch code, but
  without a key, which is possible if you send a request like POST
  /ghost/api/v2/content/posts/`, then the version mismatch code will try
  and look up the API key attached to a null key, which won't work
- we should handle this case and soft return, to avoid trying to read
  `.relations` from `null`
- I'm not entirely convinced by how this code works in general, it seems
  quite confusing to reason about, but this commit should solve the HTTP
  500 we've been seeing from this
- perhaps in the future we can return earlier in the flow if we receive
  a `null` key
2024-05-02 13:03:26 +02:00

81 lines
2.6 KiB
JavaScript

const internalContext = {
internal: true
};
class VersionNotificationsDataService {
/**
* @param {Object} options
* @param {Object} options.UserModel - ghost user model
* @param {Object} options.ApiKeyModel - ghost api key model
* @param {Object} options.settingsService - ghost settings service
*/
constructor({UserModel, ApiKeyModel, settingsService}) {
this.UserModel = UserModel;
this.ApiKeyModel = ApiKeyModel;
this.settingsService = settingsService;
}
async fetchNotification(acceptVersion) {
const setting = await this.settingsService.read('version_notifications', internalContext);
const versionNotifications = JSON.parse(setting.version_notifications.value);
return versionNotifications.find(version => version === acceptVersion);
}
async saveNotification(acceptVersion) {
const setting = await this.settingsService.read('version_notifications', internalContext);
const versionNotifications = JSON.parse(setting.version_notifications.value);
if (!versionNotifications.find(version => version === acceptVersion)) {
versionNotifications.push(acceptVersion);
return this.settingsService.edit([{
key: 'version_notifications',
value: JSON.stringify(versionNotifications)
}], {
context: internalContext
});
}
}
async getNotificationEmails() {
const data = await this.UserModel.findAll(Object.assign({
withRelated: ['roles'],
filter: 'status:active'
}, internalContext));
const adminEmails = data
.toJSON()
.filter(user => ['Owner', 'Administrator'].includes(user.roles[0].name))
.map(user => user.email);
return adminEmails;
}
/**
* This method is for internal use only.
*
* @param {String} key - api key identification value, it's "secret" in case of Content API key and "id" for Admin API
* @param {String} type - one of "content" or "admin" values
* @returns {Promise<Object | null>} Integration JSON object
*/
async getIntegration(key, type) {
let queryOptions = null;
if (type === 'content') {
queryOptions = {secret: key};
} else if (type === 'admin') {
queryOptions = {id: key};
}
const apiKey = await this.ApiKeyModel.findOne(queryOptions, {withRelated: ['integration']});
if (!apiKey) {
return null;
}
return apiKey.relations.integration.toJSON();
}
}
module.exports = VersionNotificationsDataService;