Reverse Engineering Lidl Fun Trips
Cette page est une archive : les informations qu’elle contient peuvent ne plus être à jour.
Technos
L'appli est une simple webapp qui fait une serie d'appels a un backend rest.
Url du site: https://lidlfuntrips.be/
Architecture
Config Postman
Je suis une fénéant et j'aime postman. Voici un fichier qui contient toutes les query interessantes a charger dans postman.
J'utilise l'env var { {bearer} }. Il suffit de faire un call a login pour recup le bearer et mettre a jour la var d'env, j'ai la flemme de le faire pour vous. Je le ferai un jour peut être.
Et vu que j'ai pas de manière d'heberger le fichier de manière sure fiable sur la longeur je vous le file ici, copiez collez et et ROTFL sur mon sujet
{
"id": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
"name": "Lidle",
"description": "",
"order": [
"7f95e59e-5c21-9ce7-361e-b12f1c9bd0f4",
"89d86de1-4ef4-5af1-cad4-429e3ca18c67",
"e46ed2b5-7747-bf0d-5cd1-2498397e470e",
"e525b5d4-6ce9-3442-bb37-f02b9c95d50b",
"a7eb7921-622c-491c-b08f-26b6e8f098e4"
],
"folders": [],
"folders_order": [],
"timestamp": 1526791518755,
"owner": 0,
"public": false,
"requests": [
{
"id": "7f95e59e-5c21-9ce7-361e-b12f1c9bd0f4",
"headers": "Content-Type: application/json\nAuthorization: Basic Og==\n",
"headerData": [
{
"key": "Content-Type",
"value": "application/json",
"description": "",
"enabled": true
},
{
"key": "Authorization",
"value": "Basic Og==",
"description": "",
"enabled": true
}
],
"url": "https://wallet-service.qup.eu/v1/users/login",
"queryParams": [],
"pathVariables": {},
"pathVariableData": [],
"preRequestScript": null,
"method": "POST",
"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
"data": [],
"dataMode": "raw",
"name": "Login",
"description": "",
"descriptionFormat": "html",
"time": 1526791551026,
"version": 2,
"responses": [],
"tests": null,
"currentHelper": "normal",
"helperAttributes": {},
"rawModeData": "{\n\t\"device_id\": 1234567890,\n\t\"plateform\": \"android\",\n\t\"grant_type\": \"password\",\n\t\"client_id\": \"lidl-fr_FR-android@ws.apps.queueup.eu\",\n\t\"client_secret\": \"elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs\",\n\t\"username\": \"chamaloriz@gmail.com\",\n\t\"password\": \"Radislav250399\"\n}"
},
{
"id": "89d86de1-4ef4-5af1-cad4-429e3ca18c67",
"headers": "Authorization: Bearer NSx5kamSYNYCBsmgCipO49wqzzQ2ZRwHmZvukKMC\n",
"headerData": [
{
"key": "Authorization",
"value": "Bearer NSx5kamSYNYCBsmgCipO49wqzzQ2ZRwHmZvukKMC",
"description": "",
"enabled": true
}
],
"url": "https://wallet-service.qup.eu/v1/me/messages",
"queryParams": [],
"pathVariables": {},
"pathVariableData": [],
"preRequestScript": null,
"method": "GET",
"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
"data": null,
"dataMode": "params",
"name": "Get Message",
"description": "",
"descriptionFormat": "html",
"time": 1526791538316,
"version": 2,
"responses": [],
"tests": null,
"currentHelper": "normal",
"helperAttributes": {}
},
{
"id": "a7eb7921-622c-491c-b08f-26b6e8f098e4",
"headers": "Authorization: Bearer {{bearer}}\nContent-Type: application/json\n",
"headerData": [
{
"key": "Authorization",
"value": "Bearer {{bearer}}",
"description": "",
"enabled": true
},
{
"key": "Content-Type",
"value": "application/json",
"description": "",
"enabled": true
}
],
"url": "https://wallet-service.qup.eu/v1/me/vouchers/add",
"queryParams": [],
"pathVariables": {},
"pathVariableData": [],
"preRequestScript": null,
"method": "POST",
"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
"data": [],
"dataMode": "raw",
"name": "Post Vaucher",
"description": "",
"descriptionFormat": "html",
"time": 1526792030344,
"version": 2,
"responses": [],
"tests": null,
"currentHelper": "normal",
"helperAttributes": {},
"rawModeData": "{\r\n\"code\":\"LIA25636QZ\"\r\n}"
},
{
"id": "e46ed2b5-7747-bf0d-5cd1-2498397e470e",
"headers": "Authorization: Bearer MqyGajuFyVAUfJgNTfdLK0AOsWqBTCc11CGLP1yH\n",
"headerData": [
{
"key": "Authorization",
"value": "Bearer MqyGajuFyVAUfJgNTfdLK0AOsWqBTCc11CGLP1yH",
"description": "",
"enabled": true
}
],
"url": "https://wallet-service.qup.eu/v1/me",
"queryParams": [],
"pathVariables": {},
"pathVariableData": [],
"preRequestScript": null,
"method": "GET",
"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
"data": null,
"dataMode": "params",
"name": "Get Profile",
"description": "",
"descriptionFormat": "html",
"time": 1526791618943,
"version": 2,
"responses": [],
"tests": null,
"currentHelper": "normal",
"helperAttributes": {}
},
{
"id": "e525b5d4-6ce9-3442-bb37-f02b9c95d50b",
"headers": "Authorization: Bearer {{bearer}}\n",
"headerData": [
{
"key": "Authorization",
"value": "Bearer {{bearer}}",
"description": "",
"enabled": true
}
],
"url": "https://wallet-service.qup.eu/v1/me/vouchers",
"queryParams": [],
"pathVariables": {},
"pathVariableData": [],
"preRequestScript": null,
"method": "GET",
"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
"data": null,
"dataMode": "params",
"name": "Get encoded voucher",
"description": "",
"descriptionFormat": "html",
"time": 1526791836364,
"version": 2,
"responses": [],
"tests": null,
"currentHelper": "normal",
"helperAttributes": {}
}
]
}
Authentification / Recup un token
Authentification des requêtes via le header HTTP Authorization Bearer. Chaque requête doit contenir le token.
Le token peut être obtenu en faisait un POST sur /users/login (UserProvider.Login())
Query
curl -X POST \
https://wallet-service.qup.eu/v1/users/login \
-H 'content-type: application/json' \
-d '{
"device_id": 1234567890,
"plateform": "android",
"grant_type": "password",
"client_id": "lidl-fr_FR-android@ws.apps.queueup.eu",
"client_secret": "elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs",
"username": "toto",
"password": "1234"
}'
Réponse
{
"access_token": "zlsTbxIbDpdUr27vmrJQSgaUPj4NcdcU7d3XwdgI",
"token_type": "Bearer",
"expires_in": 30758400
}A noter que le ContentPageProvider utilise un autre mécanisme d'authentification, cfr doc du provider.
Creation d'un compte
Query
curl -X POST \
https://wallet-service.qup.eu/v1/users/register \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{
"title": "f",
"firs_name": "AZERTYU",
"suffix": "AZERTY",
"last_name": "AZERTYU2",
"name": "AZERTYU AZERTYU",
"postalcode": "4000",
"email": "jeanhkjhkjhkjh@hotmail.com",
"password": "azerty1234$A",
"device_id": 1234567890,
"plateform": "android",
"grant_type": "password",
"client_id": "lidl-fr_FR-android@ws.apps.queueup.eu",
"client_secret": "elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs"
}'
Response
{
"access_token": "kVSU7NPBrYP3meb4XtvQfq3JwbfH7P0htnmGph4J",
"token_type": "Bearer",
"expires_in": 30758400
}
Envoyer un voucher
Query
curl -X POST \
https://wallet-service.qup.eu/v1/me/vouchers/add \
-H 'authorization: Bearer kVSU7NPBrYP3meb4XtvQfq3JwbfH7P0htnmGph4J' \
-H 'content-type: application/json' \
-d '{
"code":"LIA25636QZ"
}'
Response (erreur - inexistant)
{
"internal_code": 105,
"success": false,
"errors": [
""
]
}
Response (erreur - déja encodé)
{
"internal_code": 101,
"success": false,
"errors": [
""
]
}
Responses (succes)
{
"internal_code": 0,
"success": true,
"data": {
"voucher": "XKN5GWWDCE",
"order_id": null,
"used": false
}
}
Récuperer les vouchers déja encodé
Query
curl -X GET \ https://wallet-service.qup.eu/v1/me/vouchers \ -H 'authorization: Bearer Ad1GI7gG39DKJiijewuNUn019RZclLewSTFVolJt'
Response
{
"internal_code": 0,
"success": true,
"data": [
{
"voucher": "D6G279P2KB",
"used": false,
"order_id": null
},
{
"voucher": "3Z9LAXDM12",
"used": false,
"order_id": null
},
// Une liste quoi...
]
}
Récupérer le profile
Query
curl -X GET \ https://wallet-service.qup.eu/v1/me/ \ -H 'authorization: Bearer Ad1GI7gG39DKJiijewuNUn019RZclLewSTFVolJt'
Response
{
"internal_code": 0,
"success": true,
"data": {
"name": "AZERTYU AZERTYU",
"email": "jeanhkjhkjhkjh@hotmail.com",
"postalcode": "4000",
"created_at": "2018-05-18 16:04:56GMT+0000",
"temp_password": null,
"locale": "en_US",
"first_name": "",
"suffix": "AZERTY",
"last_name": "AZERTYU2",
"title": "f",
"telephone": null
}
}
Recuperer les messages
Query
curl -X GET \ https://wallet-service.qup.eu/v1/me/messages \ -H 'authorization: Bearer NSx5kamSYNYCBsmgCipO49wqzzQ2ZRwHmZvukKMC'
Response
{
"internal_code": 0,
"success": true,
"data": [
{
"code": "cVR4ckSGLNc:APA91bFqUUafMNVfIphHlBaisTVBQQCtM8TSck5jOhaS8LZanKaxj-8tKM7B-AH2pZj_RqgqFw2CzlHnxkGKEH3vFoxdhyh9nF4uKMqUWaxAcBI4HTgG_8rdi5FZZT4ZKsA9KnxIX5Tx",
"title": "Namens Lidl: Fijne Pinksteren!",
"message": "De la part de Lidl Funtrips: joyeuse fête de la Pentecôte!",
"read": false,
"created_at": "2018-05-19 10:55:59GMT+0000"
},
{
"code": "cVR4ckSGLNc:APA91bFqUUafMNVfIphHlBaisTVBQQCtM8TSck5jOhaS8LZanKaxj-8tKM7B-AH2pZj_RqgqFw2CzlHnxkGKEH3vFoxdhyh9nF4uKMqUWaxAcBI4HTgG_8rdi5FZZT4ZKsA9KnxIX5Tx",
"title": "€ 10 korting bij Bobbejaanland met Pinksteren!",
"message": "Une réduction de 10 € chez Bobbejaanland à la Pentecôte! ",
"read": false,
"created_at": "2018-05-16 13:09:23GMT+0000"
},
{
"code": "cVR4ckSGLNc:APA91bFqUUafMNVfIphHlBaisTVBQQCtM8TSck5jOhaS8LZanKaxj-8tKM7B-AH2pZj_RqgqFw2CzlHnxkGKEH3vFoxdhyh9nF4uKMqUWaxAcBI4HTgG_8rdi5FZZT4ZKsA9KnxIX5Tx",
"title": "Ga met Hemelvaart de baby-olifantjes bezoeken in Planckendael!",
"message": "Allez visiter les bébés éléphants à Planckendael pendant l'Ascension!",
"read": false,
"created_at": "2018-05-09 14:55:05GMT+0000"
}
]
}
REST API Endpoints
Fichiers décompilé
Tout se trouve dans /assets/www/build/
Quelques remarques:
Main.js
ApiService
ApiGateway
Toutes les requêtes REST passent par ce service.
Expose POST, GET, PUT, etc. Toutes les méthodes appellent derrière request(). Cette méthode construit la request rest et set le header Authorization avec le bon token, c'est a peut près tout ce qu'il y a d’intéressant ici.
LocaleService
Retourne nl_NL ou fr_FR, utilise pour build le client_id
ProjectAppSettings
Have Fun, tout est assez clair.
ProjectAppSettings.ENDPOINT = {
TEST: {
BASE_URL: 'https://wallet-service.test.qup.eu/v1/',
CLIENT_ID: {
'IOS': 'lidl-<locale>-android@ws.apps.queueup.eu',
'ANDROID': 'lidl-<locale>-ios@ws.apps.queueup.eu'
},
CLIENT_SECRET: {
'IOS': 'elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs',
'ANDROID': 'elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs'
},
SHOP_URL: 'https://lidlfuntrips.qup.uat1.aanzee.cc'
},
LIVE: {
BASE_URL: 'https://wallet-service.qup.eu/v1/',
CLIENT_ID: {
'IOS': 'lidl-<locale>-android@ws.apps.queueup.eu',
'ANDROID': 'lidl-<locale>-ios@ws.apps.queueup.eu'
},
CLIENT_SECRET: {
'IOS': 'elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs',
'ANDROID': 'elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs'
},
SHOP_URL: 'https://lidlfuntrips.be'
}
};
ProjectAppSettings.SERVER = ProjectAppSettings.ENDPOINT.LIVE;
ProjectAppSettings.PROJECT = 'lidl';
ProjectAppSettings.VOUCHER_MASK = [/[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/];
ProjectAppSettings.HAS_NAME_SPLIT = true;
ProjectAppSettings.API_ENDPOINT = ProjectAppSettings.SERVER.BASE_URL;
ProjectAppSettings.SHOP_URL = ProjectAppSettings.SERVER.SHOP_URL;
ProjectAppSettings.ANDROID_SENDER_ID = '430671127661';
ProjectAppSettings.VOUCHER_VALUE = 2;
ProjectAppSettings.CLIENT_ID = {
'IOS': ProjectAppSettings.SERVER.CLIENT_ID.IOS,
'ANDROID': ProjectAppSettings.SERVER.CLIENT_ID.ANDROID
};
ProjectAppSettings.CLIENT_SECRET = {
'IOS': ProjectAppSettings.SERVER.CLIENT_SECRET.IOS,
'ANDROID': ProjectAppSettings.SERVER.CLIENT_SECRET.ANDROID
};
ProjectAppSettings.GA_ID = 'UA-2367280-26';
ProjectAppSettings.VALUTA = 'EUR';
ProjectAppSettings.isContentAccessTokenRequired = true;
ProjectAppSettings.scanditLicenceKey = {
ios: 'AVwrDYiBHE1gFCB1uhW4ICsgS+avHh6XEnHDO5hQsMErS7UolXsALqohduCGfldXnU/BAxojgEKDfl8zF2pvsxJXazqWVxJW2GIkv4hhqnStawfmiWDcO9xDh8IObnMk8wagkxkXNeTfB1LQ/x0SWoIvRAWtw1RFUbigMVJsbSLIH7ERDf8nMHIiCor+4Mi8KPJ2pj9GtjYklSjesjSk2d9Td3kw/86f16aFSPvL6qmFTsyK04PSo+zeV9rvYIfu2aigvQC0V30mGjnwrB5Cjg6fCVSyBoqKCpl90G+tXo/s6C3T5kPAUpVkAD7V1OZs43W0AJhzG82qNO1Jdh8G8ZmywQhFMWI/i80no4fU+qGNDzvrJr8X59Q3izDbTdCCZ20P1iXQCNXScJn/vpiuAjQyEPSrHybJ/69pDm1oYYP6KxHg+Hyjn2KtukMqJxpXSlcduwB7YrcSnVJSU/YLULUZANDKxt15FCKUpNwg/bBss45yryRh5eAv6LOSGyBZ6FAxwcxSQ+Em5DGqX8dN72dd9m8wHZfco9qIOSl3SiY3GZWivSqK/Wvca+hJ60CtFGuaf34JpKYIELfInznSTGC90VxSMpqRkbch0eCKh/mA4DcdmzuZKDz1jxbnFuWfCSK3y6T42gck+r/KrAixdtbo1inRD+ILKqmc7paZzBa1UUkSx6PvqB1vLa1smqTWMii1hOASKyVj7Nju9TxjqFuidfQcuhvl9f1d5njr62zW0jGB7PqWfKealcvLBReSqgcnghbRQQuSje/xTe8Ou2Ssc/sQNpKafaGjdqC3ucezwzTukj8=',
android: 'AXBrj7WBIUINN103cBKCgjQPNEdxOLC/3F3tyddJn1zdaZMasEGF6v0vUVobRJq5zmrnrnpbXF1UbIrgPUMKaoZB628XZHMOhHMoNud1xQtNTrdQD3pauIp28UvaTId/PSOuGGAE6gchxM+MvXFTU+4+OzmZM5kgo8Gx+ob2Ok6TZ7VBmk0XTVCtU6ZbHs9Cax9b/njJLjvDHRK5PErlFGjtSq7/gWN6IyjPGqcYEhPeepz+MpCXluOgjHlU1JUh3laLUjLYnMP5i10gMW5nWwkzv9DZfwbeLh3+2Sg90EPWlyBsqekHRXuze7Ams74cmYh3rsraTCucGTKyCgyqKFzLzb5vR7u7tbsq3vTxLQ87xfjRzx3CNUY37TW9NBNHUYWWfc2BgL7b/jOneWxrkFC1MjLahWn44d6mkEM6bxI6UAW5zjk1T48g7D6ALo15sNrDFJ8aohS+qUSXd6Z73uRJ6F9yBbNCBeMZfrgh8eyMyq0Cb1JaXdMDBq+h+maLZr3ySbi+sCZtoaGbrUd5+7IVk7UmkhKiYQntl+gzIoI089hY0qGYLy6hvbzpS1NbiJn6F9QwjMV4w7FxH242jqv5+kbx/5T80ecKxIHdjXEeZYQ+5+aCfYSfmOucDxGVZJVGjh4J+7EJT0Zl0dts++pwXW2vIT/wKQfM+BM7pt8KAeltegl/+33OgIPDsptJ+XKiOA48Wlhb7tly8j78yAsLdzW39Xt/Btw9UTvJdrUqp8YrzeUGCXup4te6YCB+Vn0W302o/Z4GSSvyoIQNHwlxyc2qW4lFS4+wJHhes8ZX4ZjdJGo=',
};
ProjectAppSettings.availableLocales = [{
iso: 'nl',
locale: 'nl_NL',
}, {
iso: 'fr',
locale: 'fr_FR',
}];
ProjectAppSettings.defaultLanguage = 'fr';
ProjectAppSettings.hasIntroImageBackground = false;
ProjectAppSettings.contentPages = {
contact: 'contact',
privacy: 'privacy',
};
AppSettings
Interface sur ProjectAppSettings
Providers
UserProvider
VoucherProvider
MessageProvider
ContentPageProvider
Récupére du contenu depuis le webservice mais utilise un autre système d'authentification si isContentAccessTokenRequired est a true (ce qui est le cas).
getAccessToken() POST sur /client/access_token avec un grant_type 'client_credentials' pour récupérer un token qui sera ensuite utilisé dans getContentPageForKey(key) qui GET sur /content/{key}
