Skip to content

Commit f7d375f

Browse files
authored
fix: fetch firebase tokens (#65)
* fix: fetch firebase tokens * fix: encrypt function? * getcookiie * chore: remove junk? * fix: encode key * fix: fetch firebase token and reset app * fix: send post req * fix: icons ios * fix: move call to the right block * fix: correctly save jwt token * fix: fetch token and save on startup
1 parent 3e2d88e commit f7d375f

5 files changed

Lines changed: 136 additions & 49 deletions

File tree

manifest.json

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,107 +40,107 @@
4040
"purpose": "maskable"
4141
},
4242
{
43-
"src": "assets/ios/16.png",
43+
"src": "assets/icons/ios/16.png",
4444
"sizes": "16x16"
4545
},
4646
{
47-
"src": "assets/ios/20.png",
47+
"src": "assets/icons/ios/20.png",
4848
"sizes": "20x20"
4949
},
5050
{
51-
"src": "assets/ios/29.png",
51+
"src": "assets/icons/ios/29.png",
5252
"sizes": "29x29"
5353
},
5454
{
55-
"src": "assets/ios/32.png",
55+
"src": "assets/icons/ios/32.png",
5656
"sizes": "32x32"
5757
},
5858
{
59-
"src": "assets/ios/40.png",
59+
"src": "assets/icons/ios/40.png",
6060
"sizes": "40x40"
6161
},
6262
{
63-
"src": "assets/ios/50.png",
63+
"src": "assets/icons/ios/50.png",
6464
"sizes": "50x50"
6565
},
6666
{
67-
"src": "assets/ios/57.png",
67+
"src": "assets/icons/ios/57.png",
6868
"sizes": "57x57"
6969
},
7070
{
71-
"src": "assets/ios/58.png",
71+
"src": "assets/icons/ios/58.png",
7272
"sizes": "58x58"
7373
},
7474
{
75-
"src": "assets/ios/60.png",
75+
"src": "assets/icons/ios/60.png",
7676
"sizes": "60x60"
7777
},
7878
{
79-
"src": "assets/ios/64.png",
79+
"src": "assets/icons/ios/64.png",
8080
"sizes": "64x64"
8181
},
8282
{
83-
"src": "assets/ios/72.png",
83+
"src": "assets/icons/ios/72.png",
8484
"sizes": "72x72"
8585
},
8686
{
87-
"src": "assets/ios/76.png",
87+
"src": "assets/icons/ios/76.png",
8888
"sizes": "76x76"
8989
},
9090
{
91-
"src": "assets/ios/80.png",
91+
"src": "assets/icons/ios/80.png",
9292
"sizes": "80x80"
9393
},
9494
{
95-
"src": "assets/ios/87.png",
95+
"src": "assets/icons/ios/87.png",
9696
"sizes": "87x87"
9797
},
9898
{
99-
"src": "assets/ios/100.png",
99+
"src": "assets/icons/ios/100.png",
100100
"sizes": "100x100"
101101
},
102102
{
103-
"src": "assets/ios/114.png",
103+
"src": "assets/icons/ios/114.png",
104104
"sizes": "114x114"
105105
},
106106
{
107-
"src": "assets/ios/120.png",
107+
"src": "assets/icons/ios/120.png",
108108
"sizes": "120x120"
109109
},
110110
{
111-
"src": "assets/ios/128.png",
111+
"src": "assets/icons/ios/128.png",
112112
"sizes": "128x128"
113113
},
114114
{
115-
"src": "assets/ios/144.png",
115+
"src": "assets/icons/ios/144.png",
116116
"sizes": "144x144"
117117
},
118118
{
119-
"src": "assets/ios/152.png",
119+
"src": "assets/icons/ios/152.png",
120120
"sizes": "152x152"
121121
},
122122
{
123-
"src": "assets/ios/167.png",
123+
"src": "assets/icons/ios/167.png",
124124
"sizes": "167x167"
125125
},
126126
{
127-
"src": "assets/ios/180.png",
127+
"src": "assets/icons/ios/180.png",
128128
"sizes": "180x180"
129129
},
130130
{
131-
"src": "assets/ios/192.png",
131+
"src": "assets/icons/ios/192.png",
132132
"sizes": "192x192"
133133
},
134134
{
135-
"src": "assets/ios/256.png",
135+
"src": "assets/icons/ios/256.png",
136136
"sizes": "256x256"
137137
},
138138
{
139-
"src": "assets/ios/512.png",
139+
"src": "assets/icons/ios/512.png",
140140
"sizes": "512x512"
141141
},
142142
{
143-
"src": "assets/ios/1024.png",
143+
"src": "assets/icons/ios/1024.png",
144144
"sizes": "1024x1024"
145145
}
146146
],

scripts/extras.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ function createCookie(name, value, expiryDate) {
146146
"=" +
147147
value +
148148
"; expires=" +
149-
expiryDate.toGMTString() +
149+
expiryDate.toUTCString() +
150150
"; SameSite=strict" +
151151
"; Domain=" +
152152
getTLD() +

scripts/map.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ async function initMap() {
112112
user.accessToken = accessTokenCookie;
113113
}
114114

115+
// Check if the user has a stored firebase token
116+
const firebaseTokenCookie = getCookie("firebaseToken");
117+
if (firebaseTokenCookie) {
118+
user.firebaseToken = firebaseTokenCookie;
119+
} else await fetchFirebaseToken(user.accessToken);
120+
115121
/* Run the startup functions */
116122
await runStartupFunctions();
117123
}

scripts/requests.js

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,32 @@ const GIRA_GRAPHQL_WS_ENDPOINT = "wss://c2g091p01.emel.pt/ws/graphql";
88
const GIRA_AUTH_ENDPOINT = "https://api-auth.emel.pt/auth";
99
const GIRA_TOKEN_REFRESH_ENDPOINT = "https://api-auth.emel.pt/token/refresh";
1010
const GIRA_USER_ENDPOINT = "https://api-auth.emel.pt/user";
11+
const FIREBASE_TOKEN_URL = "https://luk.moe/girabot_tokens/exchange";
1112

1213
const NUMBER_OF_RETRIES = 3;
1314
const DEFAULT_PROXY = "https://corsproxy.afonsosousah.workers.dev/";
1415

16+
async function makeProxyRequest(url, init) {
17+
return fetch(proxyURL ?? DEFAULT_PROXY, {
18+
...init,
19+
headers: {
20+
...init.headers,
21+
"X-Proxy-URL": url,
22+
},
23+
});
24+
}
25+
1526
async function makePostRequest(url, body, accessToken = null) {
1627
// Increment current request try
1728
currentRequestTry += 1;
1829

19-
const response = await fetch(proxyURL ?? DEFAULT_PROXY, {
30+
const response = await makeProxyRequest(url, {
2031
method: "POST",
2132
headers: {
22-
"User-Agent": "Gira/3.4.0 (Android 34)",
23-
"X-Proxy-URL": url,
33+
"User-Agent": "Gira/3.4.3 (Android 34)",
2434
"Content-Type": "application/json",
2535
"X-Authorization": `Bearer ${accessToken}`,
36+
"X-Firebase-Token": await encryptFirebaseToken(user.firebaseToken, user.accessToken),
2637
},
2738
body: body,
2839
});
@@ -31,9 +42,14 @@ async function makePostRequest(url, body, accessToken = null) {
3142

3243
// refresh token
3344
accessToken = await tokenRefresh();
45+
// se o token tiver expirado
46+
if (!getCookie("firebaseToken")) {
47+
const firebaseToken = await fetchFirebaseToken(user.accessToken);
48+
if (!firebaseToken) delete user.firebaseToken;
49+
}
3450

35-
// check if token refresh was successful
36-
if (typeof accessToken !== "undefined") {
51+
// check if token refresh was successful and there's a firebase token
52+
if (typeof accessToken !== "undefined" && user.firebaseToken) {
3753
// try to make request again
3854
return await retryPostRequest(url, body, accessToken, "Erro da API (401)"); // be sure to use latest available token
3955
}
@@ -156,6 +172,41 @@ async function makePostRequest(url, body, accessToken = null) {
156172
// return;
157173
// }
158174
}
175+
returnToDefaultState();
176+
}
177+
178+
// source: trust me bro
179+
async function encryptFirebaseToken(firebaseToken, authToken) {
180+
const { sub, jti } = getJWTPayload(authToken);
181+
182+
// 1. Create key from sub (hex string)
183+
const key = new TextEncoder().encode(sub.replaceAll("-", ""));
184+
185+
// 2. IV from jti.slice(0, 16) using UTF-8 encoding
186+
const iv = new TextEncoder().encode(jti.slice(0, 16));
187+
188+
// 3. Import key
189+
const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-CBC" }, false, ["encrypt"]);
190+
191+
// 4. Encrypt
192+
const tokenEncoded = new TextEncoder().encode(firebaseToken);
193+
const encryptedBuffer = await crypto.subtle.encrypt({ name: "AES-CBC", iv }, cryptoKey, tokenEncoded);
194+
195+
// 5. Convert to base64
196+
return bufferToBase64(encryptedBuffer);
197+
}
198+
199+
function hexStringToBytes(hex) {
200+
const bytes = new Uint8Array(hex.length / 2);
201+
for (let i = 0; i < bytes.length; i++) {
202+
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
203+
}
204+
return bytes;
205+
}
206+
207+
function bufferToBase64(buffer) {
208+
const binary = String.fromCharCode(...new Uint8Array(buffer));
209+
return btoa(binary);
159210
}
160211

161212
async function makeGetRequest(url, accessToken = null) {
@@ -339,25 +390,27 @@ async function retryPostRequest(url, body, accessToken, errorMessage) {
339390
// Warn user about the API error
340391
alert(errorMessage);
341392

342-
// Return to app starting state
343-
hideUserSettings();
344-
hidePlaceSearchMenu();
345-
let bikeListMenu = document.getElementById("bikeMenu");
346-
if (bikeListMenu) {
347-
bikeListMenu.classList.add("smooth-slide-to-bottom");
348-
setTimeout(() => bikeListMenu.remove(), 500); // remove element after animation
349-
return; // prevent station card from being hidden if there was a bike list menu
350-
}
351-
let menu = document.getElementById("stationMenu");
352-
if (menu) {
353-
menu.classList.add("smooth-slide-to-left");
354-
setTimeout(() => menu.remove(), 500); // remove element after animation
355-
document.getElementById("zoomControls").classList.add("smooth-slide-down-zoom-controls"); // move zoom controls back down
356-
}
393+
returnToDefaultState();
357394

358395
// Reset currentRequestTry
359396
currentRequestTry = 0;
397+
}
398+
}
360399

361-
return;
400+
function returnToDefaultState() {
401+
// Return to app starting state
402+
hideUserSettings();
403+
hidePlaceSearchMenu();
404+
let bikeListMenu = document.getElementById("bikeMenu");
405+
if (bikeListMenu) {
406+
bikeListMenu.classList.add("smooth-slide-to-bottom");
407+
setTimeout(() => bikeListMenu.remove(), 500); // remove element after animation
408+
return; // prevent station card from being hidden if there was a bike list menu
409+
}
410+
let menu = document.getElementById("stationMenu");
411+
if (menu) {
412+
menu.classList.add("smooth-slide-to-left");
413+
setTimeout(() => menu.remove(), 500); // remove element after animation
414+
document.getElementById("zoomControls").classList.add("smooth-slide-down-zoom-controls"); // move zoom controls back down
362415
}
363416
}

scripts/user.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ async function login(event) {
6363
user.accessToken = response.data.accessToken;
6464
user.refreshToken = response.data.refreshToken;
6565
user.expiration = response.data.expiration;
66+
await fetchFirebaseToken(user.accessToken);
6667

6768
/* Run the startup functions */
6869
await runStartupFunctions();
@@ -88,6 +89,33 @@ async function login(event) {
8889
}
8990
}
9091

92+
async function fetchFirebaseToken(accessToken) {
93+
const res = await makeProxyRequest(FIREBASE_TOKEN_URL, {
94+
headers: {
95+
"User-Agent": `mGira ${currentVersion}`,
96+
"X-Gira-Token": accessToken,
97+
},
98+
method: "POST",
99+
}),
100+
token = await res.text();
101+
if (!res.ok) {
102+
console.error("Error fetching encrypted token: ", token);
103+
alert("Erro ao obter o token de verificação do dispositivo. A app não vai funcionar corretamente.");
104+
return null;
105+
}
106+
const { exp } = getJWTPayload(token);
107+
createCookie("firebaseToken", token, new Date(exp * 1000)); // 30 days
108+
user.firebaseToken = token;
109+
return token;
110+
}
111+
112+
function getJWTPayload(token) {
113+
// Decode the JWT token and get the payload
114+
const payload = token.split(".")[1];
115+
const decodedPayload = atob(payload);
116+
return JSON.parse(decodedPayload);
117+
}
118+
91119
async function runStartupFunctions() {
92120
// Get all user details
93121
getUserInformation();
@@ -115,7 +143,7 @@ async function validateLogin() {
115143
query: `mutation {
116144
validateLogin(in: {
117145
language: "pt",
118-
userAgent: "Gira/3.4.0 (Android 34)",
146+
userAgent: "Gira/3.4.3 (Android 34)",
119147
firebaseToken: "cwEUfibvTHCRZ6z3R1l3B8"
120148
}) { messages { code text } }
121149
}`,

0 commit comments

Comments
 (0)