Toegang krijgen tot de OMNI API

joost
Expert
Expert
Netherlands
Berichten: 102
Lid geworden op: 23 dec 2017, 21:53
Locatie: Dordrecht
Merk SP: Stromer ST3

[Discussie over het verkrijgen van toegang tot de OMNI API afgesplitst naar een eigen topic. Stromeur.]

Dat stromer geen API vrijgeeft betekent opzich niet dat er niks mee te doen is. Met behulp van een stukje software (mitmproxy) kan je het verkeer tussen de app en stromer service afluisteren en dus de functionaliteiten die de app bevat zelf nuttig gebruiken. Als ik begin April mijn Stromer krijg heb ik in ieder geval een leuk projectje.
🇳🇱 Stromer ST3 2020 Deep Black 🚀
Bodyfloat
Gebruikersavatar
Stromeur
Admin | Forum Supporter
Admin | Forum Supporter
Netherlands
Berichten: 22831
Lid geworden op: 15 mei 2017, 20:18
Locatie: Friesland
Merk SP: Stromer ST3 PINION SE

joost schreef: 05 mar 2018, 10:31 een leuk projectje.
Zeker! Open daar tegen die tijd gerust een topic over als je zin hebt om je ervaringen hiermee te delen.
Stromer ST1 X 2017 - Stromer ST2 S 2018 - Klever X ALPHA 45 2021 - Stromer ST3 PINON 2023
joost
Expert
Expert
Netherlands
Berichten: 102
Lid geworden op: 23 dec 2017, 21:53
Locatie: Dordrecht
Merk SP: Stromer ST3

Vandaag eens in de api van Stromer gedoken. Via deze url loopt de verbinding https://api3.stromer-portal.ch/rapi/mobile/v2/bike/ krijg alleen de authenticatie nog niet goed buiten de app.

Waar ik al wel uit ben is dat er geen actuele gps data via de api te vinden is. De omni stuurt dus niet zo vaak een gps update naar de cloud.

ik kwam wel data tegen die niet in de app terug te vinden is zoals:

"atmospheric_pressure": 10370,
"average_energy_consumption": 109,
"power_on_cycles": 34,

Nummers van onderdelen. Naast de display, controller en motor ook een serie nr van de fork.

Al met al niet heel interessant om verder naar te kijken. De beveiliging ziet er wel goed uit dus wat dat betreft ben ik tevreden :ay
🇳🇱 Stromer ST3 2020 Deep Black 🚀
Bodyfloat
Gebruikersavatar
Stromeur
Admin | Forum Supporter
Admin | Forum Supporter
Netherlands
Berichten: 22831
Lid geworden op: 15 mei 2017, 20:18
Locatie: Friesland
Merk SP: Stromer ST3 PINION SE

Ik kan echt niet wat jij kunt, dus ik ben diep onder de indruk. Ik zou die URL nog niet eens gevonden hebben. Hoe kom ik bijvoorbeeld in de API van mijn eigen Stromer?

Mocht je nog meer van dit soort boeiende informatie tegen komen, shoot!
Stromer ST1 X 2017 - Stromer ST2 S 2018 - Klever X ALPHA 45 2021 - Stromer ST3 PINON 2023
Gebruikersavatar
FreddyH
Veteraan
Veteraan
Netherlands
Berichten: 9406
Lid geworden op: 24 jan 2018, 17:46
Locatie: regio Eindhoven
Merk SP: Klever X-Speed α

joost schreef: 08 jul 2018, 22:26 Vandaag eens in de api van Stromer gedoken. Via deze url loopt de verbinding https://api3.stromer-portal.ch/rapi/mobile/v2/bike/ krijg alleen de authenticatie nog niet goed buiten de app.
Kan je met een netwerk-sniffer een session id of zo achterhalen?
🇺🇦
There was never a good war, or a bad peace :bn:
[Benjamin Franklin]
joost
Expert
Expert
Netherlands
Berichten: 102
Lid geworden op: 23 dec 2017, 21:53
Locatie: Dordrecht
Merk SP: Stromer ST3

@FreddyH ik heb mitmproxy gebruikt. Mijn iPhone verbind direct met deze proxy en dan zie ik al het verkeer dat over de lijn gaat.

Op deze pagina kom je in de app https://api3.stromer-portal.ch/users/login/

Naast je credentials heb je ook nog een client_id nodig (die heb ik) alleen na de submit wordt je naar de app geredirect.

https://api3.stromer-portal.ch/users/lo ... nt_id=JEID

redirect_uri al wel aangepast naar api3.stromer-portal.ch maar dat gaat niet goed. Wellicht dat iemand de android apk eens kan uitpakken en zien wat daar gebeurd.
🇳🇱 Stromer ST3 2020 Deep Black 🚀
Bodyfloat
Gebruikersavatar
FreddyH
Veteraan
Veteraan
Netherlands
Berichten: 9406
Lid geworden op: 24 jan 2018, 17:46
Locatie: regio Eindhoven
Merk SP: Klever X-Speed α

Je bent alweer een stapje verder. Ik zou graag ook een keer gaan hacken, maar heb zelf geen Stromer dus dat gaat hem niet worden... Wellicht weer een keer een paar dagen een 'proefrit' maken :dwarf:

Die Android APK uitpakken kun je zelf natuurlijk ook. Moet ik deze downloaden en naar je toe sturen?
🇺🇦
There was never a good war, or a bad peace :bn:
[Benjamin Franklin]
merstro
Runner-up
Runner-up
Berichten: 12
Lid geworden op: 22 feb 2018, 19:54

Ik heb het inmiddels even uitgezocht met een beetje python code:

Code: Selecteer alles

import requests
from IPython.display import HTML
from urllib.parse import urlencode, parse_qs, splitquery

password = 'xxx'
username = 'xxx'
client_id = 'xxxxxxx'
client_secret = 'xxxxxxxxxxxxx'

def get_code(client_id, username, password):
    url = "https://api3.stromer-portal.ch/users/login/"
    s = requests.session()
    res = s.get(url)
    HTML(res.text)
    s.cookies

    qs = urlencode(
        {
            "client_id": client_id,
            "response_type": "code",
            "redirect_url": "stromerauth://auth",
            "scope": "bikeposition bikestatus bikeconfiguration bikelock biketheft bikedata bikepin bikeblink userprofile",
        }
    )

    data = {
        "password": password,
        "username": username,
        "csrfmiddlewaretoken": s.cookies.get("csrftoken"),
        "next": "/o/authorize/?" + qs,
    }

    res = s.post(url, data=data, headers=dict(Referer=url), allow_redirects=False)
    res = s.send(res.next, allow_redirects=False)
    _, qs = splitquery(res.headers["Location"])
    code = parse_qs(qs)["code"][0]
    return code

def get_access_token(client_id, client_secret, code):
    url = "https://api3.stromer-portal.ch//o/token/"
    params = {
        "grant_type": "authorization_code",
        "client_id": client_id,
        "client_secret": client_secret,
        "code": code,
        "redirect_uri": "stromerauth://auth",
    }

    res = requests.post(url, params=params)
    return res.json()["access_token"]
    
    
def call_api(access_token, endpoint, params={}):
    url = f"https://api3.stromer-portal.ch/rapi/mobile/v2/{endpoint}"
    headers = {"Authorization": f"Bearer {access_token}"}
    res = requests.get(url, headers=headers, params={})
    return res.json()["data"][0]

Code: Selecteer alles

import json
endpoint="bike/"
bike = call_api(access_token, endpoint)
print('bike:', json.dumps(bike, indent=True))


bike = call_api(access_token, endpoint="bike/")
endpoint = f'bike/{bike["bikeid"]}/state/'
params = {'cached':'false'}
state = call_api(access_token, endpoint, params)
print('state:', json.dumps(state, indent=True))

bike: {
 "bikeid": ***,
 "biketype": "ST2",
 "color": "silver",
 "hardware": "omniinterface",
 "bikemodel": "ST2",
 "nickname": "***",
 "size": "sport 20"
}
state: {
 "battery_SOC": 40,
 "suiversion": "3.4.2.1",
 "bike_speed": 0.0,
 "tntversion": "3.5",
 "trip_time": 81401,
 "light_on": 255,
 "average_speed_trip": 34.9,
 "trip_distance": 788.5,
 "average_speed_total": 34.1,
 "motor_temp": 16.0,
 "average_energy_consumption": 141,
 "power_on_cycles": 670,
 "total_time": 318409,
 "atmospheric_pressure": 0,
 "battery_temp": 24.0,
 "battery_health": 95,
 "total_distance": 3013.6,
 "assistance_level": 0,
 "rcvts": 1538643543,
 "theft_flag": false,
 "lock_flag": true,
 "total_energy_consumption": 42594
}

Ik weet niet of ik die client_id en client_secret zo mag posten, maar zijn zijn heel gemakkelijk uit de APK te halen (of door een MITM op de app). Ik ga dit gebruiken om een tooltje te maken dat verifieert dat de fiets goed wordt opgeladen. Ik heb heel soms dat ie na een uurtje ofzo stopt met opladen en dan kom ik bijna niet meer thuis. Straks checkt ie dat gewoon ieder kwartier: staat de fiets bij mijn werk en is de batterij niet aan het opladen ==> stuur een mailtje
ST2 2016 - 983wh
Gebruikersavatar
JeroenDG
Expert
Expert
Belgium
Berichten: 256
Lid geworden op: 22 aug 2018, 09:42
Locatie: Aalst, BE

Knap gedaan :ay
Met die informatie ontstaan er best wat leuke mogelijkheden.
ST3 Deep Black XL 983 Wh | Stuur 20mm rise & 20° backsweep | Thudbuster ST

Woon-werk & Stroomverbruikoverzicht
TimB
Runner-up
Runner-up
Berichten: 13
Lid geworden op: 19 apr 2018, 08:31

merstro schreef: 04 okt 2018, 11:05 Ik heb het inmiddels even uitgezocht met een beetje python code:
Met dank aan @merstro heb ik een portje naar NodeJS gemaakt:

Code: Selecteer alles

const request = require("request-promise");
const qs = require("qs");
const cheerio = require("cheerio");

class Stromer {
  constructor(options) {
    this.client_id = options.client_id;
    this.client_secret = options.client_secret;
    this.username = options.username;
    this.password = options.password;
    this.jar = request.jar();

    this.LOGIN_URL = "https://api3.stromer-portal.ch/users/login/";
    this.TOKEN_URL = "https://api3.stromer-portal.ch/o/token/";
    this.API_URL = "https://api3.stromer-portal.ch/rapi/mobile/v2/";
  }

  async getCode() {
    const loginPage = await request({
      url: this.LOGIN_URL,
      jar: this.jar
    });
    const cookies = this.jar.getCookieString(this.LOGIN_URL);
    const [_, csrf] = cookies.split("=");

    const queryString = qs.stringify({
      client_id: this.client_id,
      response_type: "code",
      redirect_url: "stromerauth://auth",
      scope:
        "bikeposition bikestatus bikeconfiguration bikelock biketheft bikedata bikepin bikeblink userprofile"
    });

    const $ = cheerio.load(loginPage);

    const formCsrf = $("input[name=csrfmiddlewaretoken]").val();

    const form = {
      username: this.username,
      password: this.password,
      csrfmiddlewaretoken: formCsrf,
      next: `/o/authorize/?${queryString}`
    };

    let code = "";
    try {
      const redirect = await request({
        url: this.LOGIN_URL,
        method: "post",
        form,
        jar: this.jar,
        followRedirect: false,
        headers: {
          Referer: this.LOGIN_URL
        }
      });
    } catch (e) {
      try {
        const authorize = await request({
          url: "https://api3.stromer-portal.ch" + e.response.headers.location,
          followRedirect: false,
          jar: this.jar
        });
      } catch (e) {
        const location = e.response.headers.location;
        const [_, code] = location.split("=");
        return code;
      }
    }
  }

  async getAccessToken(code) {
    const data = {
      grant_type: "authorization_code",
      client_id: this.client_id,
      client_secret: this.client_secret,
      code,
      redirect_uri: "stromerauth://auth"
    };
    const json = await request({
      url: this.TOKEN_URL,
      method: "post",
      formData: data,
      jar: this.jar
    });
    return JSON.parse(json).access_token;
  }

  async call(endpoint) {
    if (this.accessToken) {
      return await this._callApi(this.accessToken, endpoint);
    } else {
      const code = await this.getCode();
      this.accessToken = await this.getAccessToken(code);
      if (this.accessToken) {
        return await this._callApi(this.accessToken, endpoint);
      } else {
        throw new Error("No access token could be retrieved");
      }
    }
  }

  async _callApi(access_token, endpoint) {
    const response = await request({
      url: `${this.API_URL}${endpoint}`,
      headers: { Authorization: `Bearer ${access_token}` }
    });

    return JSON.parse(response).data;
  }
}
Gebruik:

Code: Selecteer alles

const test = new Stromer({
  username: "xxxxx",
  password: "xxxxxx",
  client_id: "xxxxxxx",
  client_secret: "xxxxxxx"
});

test.call("bike").then(c => console.log(c));
Plaats reactie