( req: IncomingMessage, res: ServerResponse, expectedState: string, redirectUri: string, resolveResult: (value: LoopbackResult) => void, rejectResult: (err: Error) => void, )
| 110 | |
| 111 | // fallow-ignore-next-line complexity |
| 112 | function handleRequest( |
| 113 | req: IncomingMessage, |
| 114 | res: ServerResponse, |
| 115 | expectedState: string, |
| 116 | redirectUri: string, |
| 117 | resolveResult: (value: LoopbackResult) => void, |
| 118 | rejectResult: (err: Error) => void, |
| 119 | ): void { |
| 120 | // Only GET is part of the OAuth redirect contract. Anything else is |
| 121 | // probe-traffic on the ephemeral port; reject without leaking that a |
| 122 | // CLI is listening there. |
| 123 | if (req.method !== "GET") { |
| 124 | res.writeHead(405, { "content-type": "text/plain" }).end("Method Not Allowed"); |
| 125 | return; |
| 126 | } |
| 127 | |
| 128 | const url = new URL(req.url ?? "/", "http://127.0.0.1"); |
| 129 | if (url.pathname !== CALLBACK_PATH) { |
| 130 | res.writeHead(404, { "content-type": "text/plain" }).end("Not Found"); |
| 131 | return; |
| 132 | } |
| 133 | |
| 134 | const params = url.searchParams; |
| 135 | const error = params.get("error"); |
| 136 | if (error) { |
| 137 | const desc = params.get("error_description") ?? ""; |
| 138 | respond(res, 400, errorPage(error, desc)); |
| 139 | rejectResult(new Error(`OAuth authorize returned error: ${error}${desc ? ` — ${desc}` : ""}`)); |
| 140 | return; |
| 141 | } |
| 142 | |
| 143 | const state = params.get("state"); |
| 144 | if (!state || !stateMatches(state, expectedState)) { |
| 145 | respond(res, 400, errorPage("invalid_state", "State parameter did not match.")); |
| 146 | rejectResult(new Error("OAuth state mismatch — possible CSRF, aborting.")); |
| 147 | return; |
| 148 | } |
| 149 | |
| 150 | const code = params.get("code"); |
| 151 | if (!code) { |
| 152 | respond( |
| 153 | res, |
| 154 | 400, |
| 155 | errorPage("missing_code", "Authorization code is missing from the redirect."), |
| 156 | ); |
| 157 | rejectResult(new Error("OAuth redirect did not include `code`.")); |
| 158 | return; |
| 159 | } |
| 160 | |
| 161 | respond(res, 200, successPage()); |
| 162 | resolveResult({ code, redirectUri }); |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Constant-time comparison for the OAuth `state` parameter. Real |
no test coverage detected