|
/* |
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
|
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
DEALINGS IN THE SOFTWARE. |
|
*/ |
|
|
|
const axios = require('axios'); |
|
|
|
const getCert = (async (email, password) => { |
|
const ed = await import("@noble/ed25519"); |
|
|
|
const privKey = ed.utils.randomPrivateKey(); // Secure random private key |
|
const pubKey = await ed.getPublicKeyAsync(privKey); |
|
|
|
console.log("Privkey = " + Buffer.from(privKey).toString('base64')); |
|
console.log("Pubkey = " + Buffer.from(pubKey).toString('base64')); |
|
|
|
console.log(""); |
|
|
|
try { |
|
const authResponse = await axios.post('https://my.vanmoof.com/api/v8/authenticate', null, { |
|
headers: { |
|
'Authorization': "Basic "+Buffer.from(email+':'+password).toString('base64'), |
|
'Api-Key': "fcb38d47-f14b-30cf-843b-26283f6a5819", |
|
'User-Agent': "VanMoof/20 CFNetwork/1404.0.5 Darwin/22.3.0", |
|
} |
|
}); |
|
|
|
const authToken = authResponse.data.token; |
|
|
|
const appTokenResponse = await axios.get('https://api.vanmoof-api.com/v8/getApplicationToken', { |
|
headers: { |
|
'Authorization': "Bearer "+authToken, |
|
'Api-Key': "fcb38d47-f14b-30cf-843b-26283f6a5819", |
|
'User-Agent': "VanMoof/20 CFNetwork/1404.0.5 Darwin/22.3.0", |
|
} |
|
}); |
|
|
|
const appToken = appTokenResponse.data.token; |
|
|
|
const bikeDataResponse = await axios.get('https://my.vanmoof.com/api/v8/getCustomerData?includeBikeDetails', { |
|
headers: { |
|
'Authorization': "Bearer "+authToken, |
|
'Api-Key': "fcb38d47-f14b-30cf-843b-26283f6a5819", |
|
'User-Agent': "VanMoof/20 CFNetwork/1404.0.5 Darwin/22.3.0", |
|
} |
|
}); |
|
|
|
for (const bikeIdx in bikeDataResponse.data.data.bikes) { |
|
const bike = bikeDataResponse.data.data.bikes[bikeIdx]; |
|
|
|
console.log("Bike "+bike.name); |
|
console.log("Bike ID: "+ bike.bikeId); |
|
console.log("Frame number: "+ bike.frameNumber); |
|
console.log("Frame serial: "+ bike.frameSerial); |
|
|
|
if (bike.bleProfile === 'ELECTRIFIED_2022') { |
|
console.log("Bike is an SA5"); |
|
console.log("ECU Serial: "+ bike.mainEcuSerial); |
|
|
|
const certificateResponse = await axios.post('https://bikeapi.production.vanmoof.cloud/bikes/'+bike.bikeId+'/create_certificate', { |
|
'public_key': Buffer.from(pubKey).toString('base64'), |
|
}, { |
|
headers: { |
|
'Authorization': "Bearer "+appToken, |
|
'User-Agent': "VanMoof/20 CFNetwork/1404.0.5 Darwin/22.3.0", |
|
} |
|
}); |
|
|
|
console.log("Certificate below:") |
|
console.log("-----------"); |
|
console.log(certificateResponse.data); |
|
console.log("-----------"); |
|
} else { |
|
console.log("Not an SA5.") |
|
} |
|
} |
|
|
|
console.log(""); |
|
console.log(""); |
|
} catch (error) { |
|
console.log(error); |
|
} |
|
}); |
|
|
|
getCert("YOUR_VANMOOF_EMAIL", 'YOUR_VANMOOF_PASSWORD'); |
@magnusnordlander Thanks for your work!
Finally I got my certificate today.
Expiration date 10 years in the future ✔️.
Now that VanMoof is officially declared bankrupt today, it felt extra reassuring to secure my S5 certificate.
Being a nodejs-noob, I failed to get the script to work the past week (working on Ubuntu):
crypto.getRandomValues()
.package.json
.This morning, a series of magical actions eventually helped:
npm install @noble/ed25519
npm install axios
Perhaps using
npm
for installing axios turned out to be the thing that made it work?Other attempts using
curl
/bash
Your script was also very helpful in using raw
curl
calls.Basically the same as @MaksimKravchuk's version, using curl which should be generally available.
Then again, I resort to jq for parsing the json.
If not available, you could copy the required info from the raw json for example.
I could get as far as step 3, but repeatedly failed at step 4.
I suspect my ed25519 public key didn't work, possibly some failed assumption at the URL data syntax or something else.
For others who may want to try:
The final step should then probably be something like:
Note: this requires
jq
, for parsing json.Alternatively, you could parse the json using python for example (or just copy/paste):
Obviously, adresssing @NeilBetham's concerns, plaintext credentials are even less safe than their base64-encoded versions.
You should find other ways yourself to secure your data.
Off-topic: finally created a github account specifically for this :-)