How I cancelled seat selection in Gol airlines damn website
Table of Contents
Discuss in HN: https://news.ycombinator.com/item?id=47567142
I got stuck analyzing the Gol brazilian airlines check-in page1 during the whole last hour because I selected a seat. And only after selection I realized: there's no UI button to remove the selection.
Even selecting a economic seat in the 48h period before the flight has a cost2, and yes, this kind of seat is what I bought previously.
Anyway, if I extend my personal complaining about websites of airline companies, this content would be enough to fill a book LOL
I could just give up of this online check-in and do it from the airport, but the NoScript temporary trust was already conceded, I had to make it until the end.
Analyzing
The first step was analyze the network requests the website does. The seat selection was extremely slow in my qube without GPU acceleration, but this friction didn't feared me to keep devtools open.
With the intention to aggregate useful identifiers about my order/user, I copied the curl representation of some of the main requests:
- Check-in data retrieval
- Personal data validation. A POST that confirms phone, CPF (government ID) and email (
POST https://g2s-checkin-api.voegol.com.br/api/passenger/update) - Seat selection
Then, I tried to infer the design of the API to bet the deletion method/parameters.
It didn't worked, so what I did was to analyze the JavaScript minified bundle
files of the webpage. With Firefox Librewolf devtools global search in
debugger tab (C-F) I managed to find 2 key files:
- https://b2c.voegol.com.br/check-in/chunk-WC72Z7VP.js
Found with the keyword included in the selection request body, this file exposes a class with these key functions:
select(body: Record<string, any>)cancel(body: Record<string, any>)
Both call a method from
this.seatsClient.(select|cancel). Interesting.I configured a network override for this file in devtools and duplicated the implementation of
cancel(body)intoselect(body), with the hope to get a cancellation when selecting a seat. Didn't worked, but I realized the request wasn'tPOSTanymore, butDELETE.I had to analyze further. Reading it again, I saw the both functions defines the body this way:
// ... select(e) { // `e` is the `body` let r = { seatSelectRequest: l(S({}, e), { returnSession: !0 }) }, // ...
I assumed the function
Sis doing a deep copy ofeproperties into a new object, a kind of aObject.assign(). So it means the meaningful part of the body is built by the caller.- https://b2c.voegol.com.br/check-in/chunk-7A2SV6CP.js
The caller. Found this by searching which files calls the
cancel(body)method.It exposes a class named
owith the methodonSubmitRemoveSeatMap(), which calls thecancel(body)function:onSubmitRemoveSeatMap() { // ... this.seatsClientFacade .cancel({ passengerFlightIds: [this.getFlightIdFromPassenger()] }) // ... }Bingo. This function is strangely not used across the bundles, but luckly it was left there.
The
select(body)payload is known because devtools network tab logged it. Comparing both, it differ. Here's for setting: payload for setting the seat:{ "seatSelectRequest": { "passengerFlightId": "<flight-id>", "seatNumber": "<seat-id>", "returnSession": true } }And the DTO for deleting which this file reveals:
{ "seatsCancelRequest": { "passengerFlightIds": ["<flight-id>"], "returnSession": true } }
Now I have the:
- Method, URL and headers to call the
DELETEroute. - Body built by a unused caller.
The final query
Finally I was able to call the DELETE to unset my selection. Because it
receives a (damn) Google ReCaptcha token and a rotating bearer token, what I
did was patch the select(body) method to build the body from itself, so I can
call DELETE when selecting a seat. The following diff is all I did in
chunk-WC72Z7VP.js bundle:
diff /tmp/chunk-WC72Z7VP.js /tmp/chunk-WC72Z7VP.new.js
95c95,100 < let r = { seatSelectRequest: l(S({}, e), { returnSession: !0 }) }, --- > let r = { > seatsCancelRequest: l( > S({}, { passengerFlightIds: ["<hardcoded-flight-id>"] }), > { returnSession: !0 }, > ), > }, 99c104 < .select( --- > .cancel( 111c116 < reservation: i.result.response.seatSelectResponse.reservation, --- > reservation: i.result.response.seatCancelResponse.reservation,
Once it worked, I reproduced it in restclient.el for clearance. I ran with
my current bearer and ReCaptcha and worked as well:
DELETE https://g2s-checkin-api.voegol.com.br/api/seats/cancel
flow=Default
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0
Accept: application/json
sessionId: :session-id
Content-Type: application/json
Authorization: Bearer :bearer
Origin: https://b2c.voegol.com.br
{
"seatsCancelRequest": {
"passengerFlightIds": [
":flight-id"
],
"returnSession": true
}
}
You can see it in my public restclient repo: https://codeberg.org/thisago/restclient/src/commit/ca487637250bb719acd412eb37ae804809ee072c/voegol.com.br/b2c/seatSelection.http#L13-L29
Outro
In the next time I'll take a increased care before clicking in any selection, these websites offer no transparency and it's pretty frustrating the user experience, forcing you to either finish the check-in paying the seat selection, or having to arrive sooner in the airport to do the check-in from there, hoping it won't take much time.
Keep aware, modern internet is dangerous :P