/
webhook-event-check.ts
173 lines (159 loc) 路 5.34 KB
/
webhook-event-check.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import { Application } from './application'
import { GitHubAPI } from './github'
let appMeta: ReturnType<GitHubAPI['apps']['getAuthenticated']> | null = null
let didFailRetrievingAppMeta = false
/**
* Check if an application is subscribed to an event.
*
* @returns Returns `false` if the app is not subscribed to an event. Otherwise,
* returns `true`. Returns `undefined` if the webhook-event-check feature is
* disabled or if Probot failed to retrieve the GitHub App's metadata.
*/
async function webhookEventCheck (app: Application, eventName: string) {
if (isWebhookEventCheckEnabled() === false) {
return
}
const baseEventName = eventName.split('.')[0]
if (await isSubscribedToEvent(app, baseEventName)) {
return true
} else if (didFailRetrievingAppMeta === false) {
const userFriendlyBaseEventName = baseEventName.split('_').join(' ')
app.log.error(`Your app is attempting to listen to "${eventName}", but your GitHub App is not subscribed to the "${userFriendlyBaseEventName}" event.`)
}
return didFailRetrievingAppMeta ? undefined : false
}
/**
* @param {string} baseEventName The base event name refers to the part before
* the first period mark (e.g. the `issues` part in `issues.opened`).
* @returns Returns `false` when the application is not subscribed to a webhook
* event. Otherwise, returns `true`. Returns `undefined` if Probot failed to
* retrieve GitHub App metadata.
*
* **Note**: Probot will only check against a list of events known to be in the
* `GET /app` response. Therefore, only the `false` value should be considered
* truthy.
*/
async function isSubscribedToEvent (app: Application, baseEventName: string) {
// A list of events known to be in the response of `GET /app`. This list can
// be retrieved by calling `GET /app` from an authenticated app that has
// maximum permissions and is subscribed to all available webhook events.
const knownBaseEvents = [
'check_run',
'check_suite',
'commit_comment',
'content_reference',
'create',
'delete',
'deployment',
'deployment_status',
'deploy_key',
'fork',
'gollum',
'issues',
'issue_comment',
'label',
'member',
'membership',
'milestone',
'organization',
'org_block',
'page_build',
'project',
'project_card',
'project_column',
'public',
'pull_request',
'pull_request_review',
'pull_request_review_comment',
'push',
'release',
'repository',
'repository_dispatch',
'star',
'status',
'team',
'team_add',
'watch'
]
// Because `GET /app` does not include all events - such as default events
// that all GitHub Apps are subscribed to (e.g.`installation`, `meta`, or
// `marketplace_purchase`) - we can only check `baseEventName` if it is known
// to be in the `GET /app` response.
const eventMayExistInAppResponse = knownBaseEvents.includes(baseEventName)
if (!eventMayExistInAppResponse) {
return true
}
let events
try {
events = (await retrieveAppMeta(app)).data.events
} catch (e) {
if (!didFailRetrievingAppMeta) {
app.log.warn(e)
}
didFailRetrievingAppMeta = true
return
}
return events.includes(baseEventName)
}
async function retrieveAppMeta (app: Application) {
if (appMeta) return appMeta
appMeta = new Promise(async (resolve, reject) => {
const api = await app.auth()
try {
const meta = await api.apps.getAuthenticated()
return resolve(meta)
} catch (e) {
app.log.trace(e)
/**
* There are a few reasons why Probot might be unable to retrieve
* application metadata.
*
* - Probot may not be connected to the Internet.
* - The GitHub API is not responding to requests (see
* https://www.githubstatus.com/).
* - The user has incorrectly configured environment variables (e.g.
* APP_ID, PRIVATE_KEY, etc.) used for authentication between the Probot
* app and the GitHub API.
*/
return reject([
'Probot is unable to retrieve app information from GitHub for event subscription verification.',
'',
'If this error persists, feel free to raise an issue at:',
' - https://github.com/probot/probot/issues'
].join('\n'))
}
})
return appMeta
}
function isWebhookEventCheckEnabled () {
if (process.env.DISABLE_WEBHOOK_EVENT_CHECK?.toLowerCase() === 'true') {
return false
} else if (process.env.NODE_ENV?.toLowerCase() === 'production') {
return false
} else if (inTestEnvironment()) {
// We disable the feature in test environments to avoid requiring developers
// to add a stub mocking the `GET /app` route this feature calls.
return false
}
return true
}
/**
* Detects if Probot is likely running in a test environment.
*
* **Note**: This method only detects Jest environments or when NODE_ENV starts
* with `test`.
* @returns Returns `true` if Probot is in a test environment.
*/
function inTestEnvironment (): boolean {
const nodeEnvContainsTest = process.env.NODE_ENV?.substr(0, 4).toLowerCase() === 'test'
const isRunningJest = process.env.JEST_WORKER_ID !== undefined
return nodeEnvContainsTest || isRunningJest
}
export default webhookEventCheck
/**
* A helper function used in testing that resets the cached result of /app.
*/
export function clearCache () {
appMeta = null
didFailRetrievingAppMeta = false
}