This repository has been archived by the owner on Dec 2, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
/
request-with-2fa.ts
101 lines (87 loc) · 2.76 KB
/
request-with-2fa.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
import { RequestError } from "@octokit/request-error";
import { isSmsTriggeringRoute } from "./is-sms-triggering-route";
import { State, EndpointOptions, RequestInterface, AnyResponse } from "./types";
export async function requestWith2Fa(
state: State,
options: EndpointOptions,
customRequest?: RequestInterface
): Promise<AnyResponse> {
const request = customRequest || state.request;
try {
if (state.totp) {
options = Object.assign({}, options, {
headers: Object.assign({}, options.headers, {
"x-github-otp": state.totp,
}),
});
}
const response = await request(options);
return response;
} catch (error) {
if (!error.headers) throw error;
const totpRequired = /required/.test(error.headers["x-github-otp"] || "");
const hasSmsDelivery = /sms/.test(error.headers["x-github-otp"] || "");
// handle "2FA required" error only
if (error.status !== 401 || !totpRequired) {
throw error;
}
if (
error.status === 401 &&
totpRequired &&
error.request.headers["x-github-otp"]
) {
if (state.totp) {
// TOTP is no longer valid, request again
delete state.totp;
} else {
throw new RequestError(
"Invalid TOTP (time-based one-time password) for two-factor authentication",
401,
{
headers: error.headers,
request: error.request,
}
);
}
}
// If user has 2Fa with SMS configured, send a bogus "PATCH /authorizations"
// request to trigger the TOTP delivery via SMS, unless the current request
// already triggered a delivery
if (hasSmsDelivery && !isSmsTriggeringRoute(options)) {
try {
await request("PATCH /authorizations", {
headers: options.headers,
});
} catch (error) {
// we expect a 401
if (error.status !== 401) throw error;
}
}
// we set state.totp after the request to make sure that it's valid
const totp = await state.strategyOptions.on2Fa();
try {
const response = await requestWith2Fa(
state,
Object.assign({}, options, {
headers: Object.assign({}, options.headers, {
"x-github-otp": totp,
}),
}),
customRequest
);
state.totp = totp;
return response;
} catch (error) {
// error without a headers property is an unexpected error
// which we don’t cover with tests
/* istanbul ignore next */
if (!error.headers) throw error;
const totpRequired = /required/.test(error.headers["x-github-otp"] || "");
// unless the error is an invalid TOTP, we can cache it
if (!totpRequired) {
state.totp = totp;
}
throw error;
}
}
}