-
Notifications
You must be signed in to change notification settings - Fork 50
/
hook.ts
152 lines (130 loc) · 4.59 KB
/
hook.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
import { requiresBasicAuth } from "@octokit/auth-oauth-user";
import { RequestError } from "@octokit/request-error";
import { getAppAuthentication } from "./get-app-authentication";
import { getInstallationAuthentication } from "./get-installation-authentication";
import { requiresAppAuth } from "./requires-app-auth";
import type {
AnyResponse,
EndpointOptions,
RequestParameters,
RequestInterface,
Route,
State,
} from "./types";
const FIVE_SECONDS_IN_MS = 5 * 1000;
function isNotTimeSkewError(error: RequestError) {
return !(
error.message.match(
/'Expiration time' claim \('exp'\) must be a numeric value representing the future time at which the assertion expires/,
) ||
error.message.match(
/'Issued at' claim \('iat'\) must be an Integer representing the time that the assertion was issued/,
)
);
}
export async function hook(
state: State,
request: RequestInterface,
route: Route | EndpointOptions,
parameters?: RequestParameters,
): Promise<AnyResponse> {
const endpoint = request.endpoint.merge(route as string, parameters);
const url = endpoint.url as string;
// Do not intercept request to retrieve a new token
if (/\/login\/oauth\/access_token$/.test(url)) {
return request(endpoint as EndpointOptions);
}
if (requiresAppAuth(url.replace(request.endpoint.DEFAULTS.baseUrl, ""))) {
const { token } = await getAppAuthentication(state);
endpoint.headers.authorization = `bearer ${token}`;
let response;
try {
response = await request(endpoint as EndpointOptions);
} catch (error: any) {
// If there's an issue with the expiration, regenerate the token and try again.
// Otherwise rethrow the error for upstream handling.
if (isNotTimeSkewError(error)) {
throw error;
}
// If the date header is missing, we can't correct the system time skew.
// Throw the error to be handled upstream.
if (typeof error.response.headers.date === "undefined") {
throw error;
}
const diff = Math.floor(
(Date.parse(error.response.headers.date) -
Date.parse(new Date().toString())) /
1000,
);
state.log.warn(error.message);
state.log.warn(
`[@octokit/auth-app] GitHub API time and system time are different by ${diff} seconds. Retrying request with the difference accounted for.`,
);
const { token } = await getAppAuthentication({
...state,
timeDifference: diff,
});
endpoint.headers.authorization = `bearer ${token}`;
return request(endpoint as EndpointOptions);
}
return response;
}
if (requiresBasicAuth(url)) {
const authentication = await state.oauthApp({ type: "oauth-app" });
endpoint.headers.authorization = authentication.headers.authorization;
return request(endpoint as EndpointOptions);
}
const { token, createdAt } = await getInstallationAuthentication(
state,
// @ts-expect-error TBD
{},
request,
);
endpoint.headers.authorization = `token ${token}`;
return sendRequestWithRetries(
state,
request,
endpoint as EndpointOptions,
createdAt,
);
}
/**
* Newly created tokens might not be accessible immediately after creation.
* In case of a 401 response, we retry with an exponential delay until more
* than five seconds pass since the creation of the token.
*
* @see https://github.com/octokit/auth-app.js/issues/65
*/
async function sendRequestWithRetries(
state: State,
request: RequestInterface,
options: EndpointOptions,
createdAt: string,
retries: number = 0,
): Promise<AnyResponse> {
const timeSinceTokenCreationInMs = +new Date() - +new Date(createdAt);
try {
return await request(options);
} catch (error: any) {
if (error.status !== 401) {
throw error;
}
if (timeSinceTokenCreationInMs >= FIVE_SECONDS_IN_MS) {
if (retries > 0) {
error.message = `After ${retries} retries within ${
timeSinceTokenCreationInMs / 1000
}s of creating the installation access token, the response remains 401. At this point, the cause may be an authentication problem or a system outage. Please check https://www.githubstatus.com for status information`;
}
throw error;
}
++retries;
const awaitTime = retries * 1000;
state.log.warn(
`[@octokit/auth-app] Retrying after 401 response to account for token replication delay (retry: ${retries}, wait: ${
awaitTime / 1000
}s)`,
);
await new Promise((resolve) => setTimeout(resolve, awaitTime));
return sendRequestWithRetries(state, request, options, createdAt, retries);
}
}