├── CODEOWNERS ├── .gitignore ├── .github └── workflows │ ├── node-tests.yaml │ └── release.yaml ├── tests ├── fixtures │ ├── api.me.json │ ├── api.bad.json │ ├── api.deprecated.json │ └── api.auth.json ├── integration │ ├── 04_REST_multiple_backends.js │ ├── 03_REST_unicode.js │ ├── 01_REST_noAuth.js │ └── 02_REST_domain.js ├── 05_REST_unicode.js ├── 01_REST_construct.js ├── 03_REST_auth.js ├── 06_REST_oauth2.js ├── 04_REST_me.js └── 02_REST_check.js ├── LICENSE ├── package.json ├── lib ├── endpoints.js ├── ovh.js └── ovh.es5.js ├── CONTRIBUTING.md ├── CHANGELOG.md ├── .jshintrc └── README.md /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @ovh/su-developer-platform-api-exposition -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | /auth* 4 | *~ 5 | coverage/ 6 | -------------------------------------------------------------------------------- /.github/workflows/node-tests.yaml: -------------------------------------------------------------------------------- 1 | name: NodeJS Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [18, 20, 22] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Setup NodeJS 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - name: Restore/create node_modules cache 19 | uses: actions/cache@v4 20 | with: 21 | path: '**/node_modules' 22 | key: ${{ runner.os }}-${{ hashFiles('package-lock.json') }} 23 | - name: Install 24 | run: npm install 25 | - name: Test 26 | run: npm run test -------------------------------------------------------------------------------- /tests/fixtures/api.me.json: -------------------------------------------------------------------------------- 1 | { 2 | "models" : {}, 3 | "resourcePath" : "/me", 4 | "apiVersion" : "1.0", 5 | "apis" : [ 6 | { 7 | "operations" : [ 8 | { 9 | "parameters" : [ 10 | ], 11 | "httpMethod" : "GET", 12 | "apiStatus" : { 13 | "value" : "PRODUCTION", 14 | "description" : "Stable production version" 15 | }, 16 | "noAuthentication" : 0, 17 | "description" : "Stub", 18 | "responseType" : "string" 19 | } 20 | ], 21 | "path" : "/me", 22 | "description" : "me" 23 | } 24 | ], 25 | "basePath" : "https://api.ovh.com/1.0" 26 | } 27 | -------------------------------------------------------------------------------- /tests/fixtures/api.bad.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourcePath" : "/auth", 3 | "apiVersion" : "1.0", 4 | "apis" : [ 5 | { 6 | "_operations" : [ 7 | { 8 | "parameters" : [], 9 | "httpMethod" : "GET", 10 | "apiStatus" : { 11 | "value" : "PRODUCTION", 12 | "description" : "Stable production version" 13 | }, 14 | "noAuthentication" : 1, 15 | "description" : "Get the current time of the OVH servers, since UNIX epoch", 16 | "responseType" : "long" 17 | } 18 | ], 19 | "path" : "/auth/time", 20 | "description" : "Get the time of OVH servers" 21 | } 22 | ], 23 | "basePath" : "https://api.ovh.com/1.0" 24 | } 25 | -------------------------------------------------------------------------------- /tests/fixtures/api.deprecated.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourcePath" : "/deprecated", 3 | "apiVersion" : "1.0", 4 | "apis" : [ 5 | { 6 | "operations" : [ 7 | { 8 | "parameters" : [], 9 | "httpMethod" : "GET", 10 | "apiStatus" : { 11 | "value" : "DEPRECATED", 12 | "deprecatedDate" : "2013-07-03T10:00:00+01:00", 13 | "description" : "Deprecated, will be removed", 14 | "deletionDate" : "2013-08-01T10:00:00+01:00", 15 | "replacement" : "/new/{serviceName}/route" 16 | }, 17 | "noAuthentication" : 0, 18 | "description" : "A deprecated method", 19 | "responseType" : "string" 20 | } 21 | ], 22 | "path" : "/deprecated/{serviceName}/route", 23 | "description" : "A route" 24 | } 25 | ], 26 | "models": {}, 27 | "basePath" : "https://api.ovh.com/1.0" 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Setup NodeJS 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | - name: Restore/create node_modules cache 18 | uses: actions/cache@v4 19 | with: 20 | path: '**/node_modules' 21 | key: ${{ runner.os }}-${{ hashFiles('package-lock.json') }} 22 | - name: Install 23 | run: npm install 24 | - name: Tag 25 | run: | 26 | git config user.name "GitHub action" 27 | git config user.email "actions@users.noreply.github.com" 28 | npm run release 29 | git push --follow-tags origin master 30 | - name: version 31 | run: echo "::set-output name=version::$(node -e "console.log(require('./package.json').version);")" 32 | id: version 33 | - name: release 34 | uses: actions/create-release@v1 35 | with: 36 | draft: false 37 | prerelease: false 38 | release_name: ${{ steps.version.outputs.version }} 39 | tag_name: ${{ steps.version.outputs.version }} 40 | body_path: CHANGELOG.md 41 | env: 42 | GITHUB_TOKEN: ${{ github.token }} 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 - 2025 OVH SAS 2 | Copyright (c) 2012 - 2013 Vincent Giersch 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | Except as contained in this notice, the name of OVH and or its trademarks 23 | shall not be used in advertising or otherwise to promote the sale, use or other 24 | dealings in this Software without prior written authorization from OVH. 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ovhcloud/node-ovh", 3 | "version": "3.0.6", 4 | "description": "Official Node.js wrapper for the OVH APIs", 5 | "homepage": "http://ovh.github.io/node-ovh", 6 | "author": "OVH SAS", 7 | "license": "MIT", 8 | "keywords": [ 9 | "OVH", 10 | "API", 11 | "REST", 12 | "api.ovh.com" 13 | ], 14 | "files": [ 15 | "lib" 16 | ], 17 | "main": "./lib/ovh.es5.js", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/ovh/node-ovh.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/ovh/node-ovh/issues" 24 | }, 25 | "dependencies": { 26 | "async": "0.9.x", 27 | "bluebird": "^3.4.0", 28 | "simple-oauth2": "^5.1.0" 29 | }, 30 | "devDependencies": { 31 | "@babel/cli": "^7.24.7", 32 | "@babel/core": "^7.24.7", 33 | "@babel/preset-env": "^7.24.7", 34 | "commit-and-tag-version": "^12.4.1", 35 | "istanbul": "latest", 36 | "jshint": "latest", 37 | "mocha": "latest", 38 | "nock": "latest" 39 | }, 40 | "scripts": { 41 | "compile": "npx babel --presets @babel/preset-env lib/ovh.js --out-file lib/ovh.es5.js", 42 | "compile:watch": "npx babel -w --presets @babel/preset-env lib/ovh.js --out-file lib/ovh.es5.js", 43 | "release": "commit-and-tag-version", 44 | "test": "node_modules/.bin/jshint lib/endpoints.js lib/ovh.js tests && npm run-script test-cov && npm run-script test-without-proxies", 45 | "test-without-proxies": "node node_modules/.bin/mocha -g Proxy -i --reporter spec --ui exports tests/*.js", 46 | "test-cov": "node ./node_modules/.bin/istanbul cover node_modules/mocha/bin/_mocha -- --reporter spec --ui exports --check-leaks tests/*.js", 47 | "test-integration": "node node_modules/mocha/bin/_mocha tests/integration/*.js && node node_modules/.bin/mocha -g Proxy -i --reporter spec --ui exports tests/integration/*.js" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/integration/04_REST_multiple_backends.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * Except as contained in this notice, the name of OVH and or its trademarks 23 | * shall not be used in advertising or otherwise to promote the sale, use or 24 | * other dealings in this Software without prior written authorization from OVH. 25 | */ 26 | 27 | var assert = require('assert'), 28 | async = require('async'), 29 | ovh = require('../..'), 30 | endpoints = require('../../lib/endpoints'); 31 | 32 | exports.REST_call = { 33 | 'Preconfigured API endpoints: backends time': function (done) { 34 | "use strict"; 35 | 36 | async.mapLimit( 37 | Object.keys(endpoints), Object.keys(endpoints).length, 38 | function (endpoint, callback) { 39 | endpoint = ovh({ appKey: 'X', appSecret: 'X', endpoint: endpoint }); 40 | endpoint.request('GET', '/auth/time', callback); 41 | }, 42 | function (err, times) { 43 | if (err) { 44 | return done(err); 45 | } 46 | 47 | for (var i = 0; i < times.length; i++) { 48 | assert(times[i] > Date.now() / 1000 - 3600); 49 | } 50 | 51 | done(); 52 | } 53 | ); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /lib/endpoints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * Except as contained in this notice, the name of OVH and or its trademarks 23 | * shall not be used in advertising or otherwise to promote the sale, use or 24 | * other dealings in this Software without prior written authorization from OVH. 25 | */ 26 | 27 | /** 28 | * Preconfigured API endpoints 29 | * 30 | * ovh-eu: OVH Europe 31 | * ovh-us: OVH US 32 | * ovh-ca: OVH North America 33 | * sys-eu: SoYouStart Europe 34 | * sys-ca: SoYouStart North America 35 | * ks-eu: Kimsufi Europe 36 | * ks-ca: Kimsufi North America 37 | */ 38 | module.exports = { 39 | 'ovh-eu': { 40 | 'host': 'eu.api.ovh.com', 41 | 'tokenURL': 'https://www.ovh.com/auth/' 42 | }, 43 | 'ovh-us': { 44 | 'host': 'api.us.ovhcloud.com', 45 | 'tokenURL': 'https://us.ovhcloud.com/auth/' 46 | }, 47 | 'ovh-ca': { 48 | 'host': 'ca.api.ovh.com', 49 | 'tokenURL': 'https://ca.ovh.com/auth/' 50 | }, 51 | 'sys-eu': { 52 | 'host': 'eu.api.soyoustart.com' 53 | }, 54 | 'sys-ca': { 55 | 'host': 'ca.api.soyoustart.com' 56 | }, 57 | 'soyoustart-eu': { 58 | 'host': 'eu.api.soyoustart.com' 59 | }, 60 | 'soyoustart-ca': { 61 | 'host': 'ca.api.soyoustart.com' 62 | }, 63 | 'ks-eu': { 64 | 'host': 'eu.api.kimsufi.com' 65 | }, 66 | 'ks-ca': { 67 | 'host': 'ca.api.kimsufi.com' 68 | }, 69 | 'kimsufi-eu': { 70 | 'host': 'eu.api.kimsufi.com' 71 | }, 72 | 'kimsufi-ca': { 73 | 'host': 'ca.api.kimsufi.com' 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /tests/integration/03_REST_unicode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var fs = require('fs'), 29 | ovh = require('../..'), 30 | assert = require('assert'), 31 | async = require('async'); 32 | 33 | // To create an application: https://www.ovh.com/fr/cgi-bin/api/createApplication.cgi 34 | if (!process.env.APP_KEY || !process.env.APP_SECRET) { 35 | console.warn('The tests require APP_KEY, APP_SECRET environment variables.'); 36 | console.warn('Some tests (with AUTH mention) will be ignored.'); 37 | } 38 | 39 | var apiKeys = { 40 | appKey: process.env.APP_KEY, 41 | appSecret: process.env.APP_SECRET, 42 | }; 43 | 44 | exports.REST_sms = { 45 | 'POST /sms/{serviceName}/job': function (done) { 46 | 'use strict'; 47 | 48 | var rest = ovh(apiKeys); 49 | rest.request('POST', '/sms/foo/jobs', {'message': 'tèsté'}, function (err, msg) { 50 | assert.equal(err, 401); 51 | done(); 52 | }); 53 | }, 54 | 'POST /sms/{serviceName}/job [promised]': function (done) { 55 | 'use strict'; 56 | 57 | var rest = ovh(apiKeys); 58 | rest.request('POST', '/sms/foo/jobs', {'message': 'tèsté'}) 59 | .then(function (msg) { 60 | assert.ok(!msg); 61 | }) 62 | .catch(function (err) { 63 | assert.equal(err.error, 401); 64 | }) 65 | .finally(done); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /tests/integration/01_REST_noAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var fs = require('fs'), 29 | ovh = require('../..'), 30 | assert = require('assert'); 31 | 32 | // To create an application: https://www.ovh.com/fr/cgi-bin/api/createApplication.cgi 33 | if (!process.env.APP_KEY || !process.env.APP_SECRET) { 34 | console.warn('The tests require APP_KEY, APP_SECRET environment variables.'); 35 | } 36 | 37 | exports.REST_call = { 38 | '[AUTH] POST /newAccount - test noAuthentication method': function (done) { 39 | "use strict"; 40 | 41 | var rest = ovh({ 42 | appKey: process.env.APP_KEY, 43 | appSecret: process.env.APP_SECRET 44 | }); 45 | 46 | rest.request('POST', '/newAccount', { 47 | email: 'h@ovh.fr', 48 | }, function (err, result) { 49 | assert.ok(result.indexOf('Missing') > -1); 50 | done(); 51 | }); 52 | }, 53 | '[AUTH] POST /newAccount - test noAuthentication method [promised]': function (done) { 54 | "use strict"; 55 | 56 | var rest = ovh({ 57 | appKey: process.env.APP_KEY, 58 | appSecret: process.env.APP_SECRET 59 | }); 60 | 61 | rest.requestPromised('POST', '/newAccount', { 62 | email: 'h@ovh.fr', 63 | }) 64 | .then(function (result) { 65 | assert.ok(result.indexOf('Missing') > -1); 66 | }) 67 | .finally(done); 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /tests/integration/02_REST_domain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var fs = require('fs'), 29 | ovh = require('../..'), 30 | assert = require('assert'), 31 | async = require('async'); 32 | 33 | // To create a test token 34 | // https://eu.api.ovh.com/createToken/ 35 | if (!process.env.APP_KEY || !process.env.APP_SECRET || 36 | !process.env.CONSUMER_KEY || !process.env.DOMAIN_ZONE_NAME) { 37 | console.warn('These tests require APP_KEY, APP_SECRET, CONSUMER_KEY and '); 38 | console.warn('DOMAIN_ZONE_NAME environment variables.'); 39 | } 40 | 41 | var apiKeys = { 42 | appKey: process.env.APP_KEY, 43 | appSecret: process.env.APP_SECRET, 44 | consumerKey: process.env.CONSUMER_KEY 45 | }; 46 | 47 | exports.REST_domain = { 48 | '[AUTH_TEST_DOMAIN] GET /domain/zone - .request()': function (done) { 49 | "use strict"; 50 | 51 | var rest = ovh(apiKeys); 52 | rest.request('GET', '/domain/zone', function (err, zones) { 53 | assert.ok(!err && zones.length >= 1); 54 | done(); 55 | }); 56 | }, 57 | '[AUTH_TEST_DOMAIN] GET /domain/zone - .requestPromised() [promised]': function (done) { 58 | "use strict"; 59 | 60 | var rest = ovh(apiKeys); 61 | rest.requestPromised('GET', '/domain/zone') 62 | .then(function (zones) { 63 | assert.ok(zones.length >= 1); 64 | }) 65 | .catch(function (err) { 66 | assert.ok(!err); 67 | }) 68 | .finally(done); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /tests/05_REST_unicode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var fs = require('fs'), 29 | ovh = require('..'), 30 | assert = require('assert'), 31 | async = require('async'), 32 | nock = require('nock'); 33 | 34 | var apiKeys = { 35 | appKey: '42', 36 | appSecret: '43', 37 | consumerKey: '44' 38 | }; 39 | 40 | exports.REST_sms = { 41 | 'POST /sms/{serviceName}/job': function (done) { 42 | 'use strict'; 43 | 44 | nock('https://eu.api.ovh.com') 45 | .intercept('/1.0/auth/time', 'GET') 46 | .reply(200, Math.round(Date.now() / 1000)) 47 | .intercept('/1.0/sms/foo/jobs', 'POST') 48 | .reply(200, function (uri, requestBody) { 49 | assert.equal(requestBody.message, "tèsté"); 50 | return {}; 51 | }); 52 | 53 | var rest = ovh(apiKeys); 54 | rest.request('POST', '/sms/foo/jobs', {'message': 'tèsté'}, function (err, msg) { 55 | assert.ok(!err); 56 | done(); 57 | }); 58 | }, 59 | 'POST /sms/{serviceName}/job [promised]': function (done) { 60 | 'use strict'; 61 | 62 | nock('https://eu.api.ovh.com') 63 | .intercept('/1.0/auth/time', 'GET') 64 | .reply(200, Math.round(Date.now() / 1000)) 65 | .intercept('/1.0/sms/foo/jobs', 'POST') 66 | .reply(200, function (uri, requestBody) { 67 | assert.equal(requestBody.message, "tèsté"); 68 | return {}; 69 | }); 70 | 71 | var rest = ovh(apiKeys); 72 | rest.requestPromised('POST', '/sms/foo/jobs', {'message': 'tèsté'}) 73 | .catch(function (err) { 74 | assert.ok(!err); 75 | }) 76 | .finally(done); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Node-OVH 2 | 3 | This project accepts contributions. In order to contribute, you should 4 | pay attention to a few things: 5 | 6 | 1. your code must follow the coding style rules 7 | 2. your code must be fully (100% coverage) unit-tested 8 | 3. your code must be fully documented 9 | 4. your work must be signed 10 | 5. the format of the submission must be email patches or GitHub Pull Requests 11 | 12 | 13 | ## Submitting Modifications 14 | 15 | The contributions should be email patches. The guidelines are the same 16 | as the patch submission for the Linux kernel except for the DCO which 17 | is defined below. The guidelines are defined in the 18 | 'SubmittingPatches' file, available in the directory 'Documentation' 19 | of the Linux kernel source tree. 20 | 21 | It can be accessed online too: 22 | 23 | https://www.kernel.org/doc/Documentation/SubmittingPatches 24 | 25 | You can submit your patches via GitHub 26 | 27 | ## Licensing for new files 28 | 29 | Node-OVH is licensed under a MIT license. Anything contributed to Node-OVH must 30 | be released under this license. 31 | 32 | When introducing a new file into the project, please make sure it has a 33 | copyright header making clear under which license it's being released. 34 | 35 | ## Developer Certificate of Origin 36 | 37 | To improve tracking of contributions to this project we will use a 38 | process modeled on the modified DCO 1.1 and use a "sign-off" procedure 39 | on patches that are being emailed around or contributed in any other 40 | way. 41 | 42 | The sign-off is a simple line at the end of the explanation for the 43 | patch, which certifies that you wrote it or otherwise have the right 44 | to pass it on as an open-source patch. The rules are pretty simple: 45 | if you can certify the below: 46 | 47 | By making a contribution to this project, I certify that: 48 | 49 | (a) The contribution was created in whole or in part by me and I have 50 | the right to submit it under the open source license indicated in 51 | the file; or 52 | 53 | (b) The contribution is based upon previous work that, to the best of 54 | my knowledge, is covered under an appropriate open source License 55 | and I have the right under that license to submit that work with 56 | modifications, whether created in whole or in part by me, under 57 | the same open source license (unless I am permitted to submit 58 | under a different license), as indicated in the file; or 59 | 60 | (c) The contribution was provided directly to me by some other person 61 | who certified (a), (b) or (c) and I have not modified it. 62 | 63 | (d) The contribution is made free of any other party's intellectual 64 | property claims or rights. 65 | 66 | (e) I understand and agree that this project and the contribution are 67 | public and that a record of the contribution (including all 68 | personal information I submit with it, including my sign-off) is 69 | maintained indefinitely and may be redistributed consistent with 70 | this project or the open source license(s) involved. 71 | 72 | 73 | then you just add a line saying 74 | 75 | Signed-off-by: Random J Developer 76 | 77 | using your real name (sorry, no pseudonyms or anonymous contributions.) 78 | 79 | -------------------------------------------------------------------------------- /tests/fixtures/api.auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "models" : { 3 | "auth.AccessRule" : { 4 | "namespace" : "auth", 5 | "id" : "AccessRule", 6 | "description" : "Access rule required for the application", 7 | "properties" : { 8 | "path" : { 9 | "canBeNull" : 0, 10 | "type" : "string", 11 | "description" : null 12 | }, 13 | "method" : { 14 | "canBeNull" : 0, 15 | "type" : "auth.MethodEnum", 16 | "description" : null 17 | } 18 | } 19 | }, 20 | "auth.MethodEnum" : { 21 | "namespace" : "auth", 22 | "enum" : [ 23 | "DELETE", 24 | "GET", 25 | "POST", 26 | "PUT" 27 | ], 28 | "id" : "MethodEnum", 29 | "description" : "All HTTP methods available", 30 | "enumType" : "string" 31 | }, 32 | "auth.CredentialStateEnum" : { 33 | "namespace" : "auth", 34 | "enum" : [ 35 | "expired", 36 | "pendingValidation", 37 | "refused", 38 | "validated" 39 | ], 40 | "id" : "CredentialStateEnum", 41 | "description" : "All states a Credential can be in", 42 | "enumType" : "string" 43 | }, 44 | "auth.Credential" : { 45 | "namespace" : "auth", 46 | "id" : "Credential", 47 | "description" : "Credential request to get access to the API", 48 | "properties" : { 49 | "validationUrl" : { 50 | "canBeNull" : 1, 51 | "type" : "string", 52 | "description" : null 53 | }, 54 | "consumerKey" : { 55 | "canBeNull" : 0, 56 | "type" : "string", 57 | "description" : null 58 | }, 59 | "state" : { 60 | "canBeNull" : 0, 61 | "type" : "auth.CredentialStateEnum", 62 | "description" : null 63 | } 64 | } 65 | } 66 | }, 67 | "resourcePath" : "/auth", 68 | "apiVersion" : "1.0", 69 | "apis" : [ 70 | { 71 | "operations" : [ 72 | { 73 | "parameters" : [], 74 | "httpMethod" : "GET", 75 | "apiStatus" : { 76 | "value" : "PRODUCTION", 77 | "description" : "Stable production version" 78 | }, 79 | "noAuthentication" : 1, 80 | "description" : "Get the current time of the OVH servers, since UNIX epoch", 81 | "responseType" : "long" 82 | } 83 | ], 84 | "path" : "/auth/time", 85 | "description" : "Get the time of OVH servers" 86 | }, 87 | { 88 | "operations" : [ 89 | { 90 | "parameters" : [], 91 | "httpMethod" : "POST", 92 | "apiStatus" : { 93 | "value" : "PRODUCTION", 94 | "description" : "Stable production version" 95 | }, 96 | "noAuthentication" : 0, 97 | "description" : "Expire current credential", 98 | "responseType" : "void" 99 | } 100 | ], 101 | "path" : "/auth/logout", 102 | "description" : "Expire current credential" 103 | }, 104 | { 105 | "operations" : [ 106 | { 107 | "parameters" : [ 108 | { 109 | "required" : 1, 110 | "dataType" : "auth.AccessRule[]", 111 | "name" : "accessRules", 112 | "paramType" : "body", 113 | "description" : "Access required for your application" 114 | }, 115 | { 116 | "required" : 0, 117 | "dataType" : "string", 118 | "name" : "redirection", 119 | "paramType" : "body", 120 | "description" : "Where you want to redirect the user after sucessfull authentication" 121 | } 122 | ], 123 | "httpMethod" : "POST", 124 | "apiStatus" : { 125 | "value" : "PRODUCTION", 126 | "description" : "Stable production version" 127 | }, 128 | "noAuthentication" : 1, 129 | "description" : "Request a new credential for your application", 130 | "responseType" : "auth.Credential" 131 | } 132 | ], 133 | "path" : "/auth/credential", 134 | "description" : "Operations with credentials" 135 | } 136 | ], 137 | "basePath" : "https://api.ovh.com/1.0" 138 | } 139 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. 4 | 5 | ## 3.0.6 (2025-07-23) 6 | 7 | ## 3.0.5 (2025-04-16) 8 | 9 | ## 3.0.4 (2025-03-27) 10 | 11 | ## 3.0.3 (2025-01-02) 12 | 13 | ## 3.0.2 (2024-09-20) 14 | 15 | ## 3.0.1 (2024-07-29) 16 | 17 | ## [3.0.0](https://github.com/ovh/node-ovh/compare/v2.0.3...v3.0.0) (2024-07-18) 18 | 19 | 20 | ### ⚠ BREAKING CHANGES 21 | 22 | * package name is now @ovhcloud/node-ovh 23 | 24 | Signed-off-by: Marie JONES <14836007+marie-j@users.noreply.github.com> 25 | 26 | ### build 27 | 28 | * update release process ([efd8fcd](https://github.com/ovh/node-ovh/commit/efd8fcdd1607b92400bb1098996a413a35d7cdaa)) 29 | 30 | 31 | ### Features 32 | 33 | * add oauth2 authentication method ([880852b](https://github.com/ovh/node-ovh/commit/880852b48f402ef6f8ca9ebf575a92fc079beb67)) 34 | * allow /v1 or /v2 prefixes in path ([380d1ac](https://github.com/ovh/node-ovh/commit/380d1ac1a68654cbfb3450a01497c59bc4c0885d)) 35 | 36 | ## 2.0.3 37 | 38 | * [#19](https://github.com/ovh/node-ovh/pull/19) - docs(README): Update Mocha link 39 | * [#34](https://github.com/ovh/node-ovh/pull/34) - docs(README): fix typo 40 | * [#36](https://github.com/ovh/node-ovh/pull/36) - ci(travis): require Node.js 8 41 | * [#35](https://github.com/ovh/node-ovh/pull/35) - docs(https): Fix Mixed Content Warnings 42 | * [#37](https://github.com/ovh/node-ovh/pull/37) - fix(endpoint): remove non-reachable runabove endpoint 43 | 44 | ## 2.0.2 45 | 46 | * [#15](https://github.com/ovh/node-ovh/pull/15) - fix(ovh): disable deletion of 0 or empty string params 47 | * [#24](https://github.com/ovh/node-ovh/pull/24) - Add US API 48 | * [#24](https://github.com/ovh/node-ovh/pull/24) - Remove comma to match every other api endpoint 49 | * [#27](https://github.com/ovh/node-ovh/pull/27) - chore(travis): Drop support for node < 4 (Not maintained anymore) 50 | * [#29](https://github.com/ovh/node-ovh/pull/29) - feat(api): change OVH US API endpoint 51 | * chore(travis): drop support < 6 ([1656444](https://github.com/ovh/node-ovh/tree/1656444d0ff3d7485d11aa617b50bc5bb5bc279b)) 52 | * chore(es5): recompile ([1c68edb](https://github.com/ovh/node-ovh/tree/1c68edb7682d85719fd118ebff2ff4ce50e2c0f3)) 53 | 54 | ## 2.0.1 55 | 56 | * [#10](https://github.com/ovh/node-ovh/pull/10) - Upgrade node version, delete proxy support (deprecated), add promise support 57 | * [#15](https://github.com/ovh/node-ovh/pull/15) - fix(ovh): disable deletion of 0 or empty string params 58 | 59 | ## 2.0.0 60 | 61 | * Support of node v5 and v6 62 | * Remove the proxy mode with harmonies 63 | * Add requestPromised for promise support natively 64 | 65 | ## 1.1.3 66 | 67 | * Support zero-byte JSON HTTP bodies (#2) 68 | 69 | ## 1.1.2 70 | 71 | * Add aliases soyoustart-eu, soyoustart-ca, kimsufi-eu, kimsufi-ca to sys-eu, sys-ca, ks-eu and ks-ca. 72 | 73 | ## 1.1.1 74 | 75 | * Add Kimsufi and SoYouStart APIs 76 | 77 | ## 1.1 78 | * Now official Node.js wrapper 79 | * Rewrite part of the tests, README & documentation 80 | * Add CONTRIBUTING guidelines 81 | * Add `endpoint` parameter with preconfigured API endpoints. 82 | * Discontinue node.js <= 0.9 support 83 | 84 | ## 1.0.2 85 | 86 | * Fix noAuthenticated calls 87 | * Optionnal consumer key, now checked only on debug 88 | * Fix unicode (thanks to @naholyr #4) 89 | 90 | ## 1.0.1 91 | 92 | * Fix initial value for apiTimeDiff (gierschv/node-ovh#1) 93 | * Include auth API for /auth/time if `usedApi` parameter is defined 94 | * Fix duplicate call of warning function 95 | 96 | ## 1.0.0 97 | 98 | * WS are not supported anymore 99 | * The usage of the Harmony proxies usage is optional 100 | * Callbacks are designed "errors first" 101 | * Optional check of the existence of a method in the APIs schemas and its status (PRODUCTION, DEPRECATED, etc.) 102 | * Debug mode 103 | * New documentation 104 | 105 | ## 0.3.8 106 | 107 | * Fixes a potential EventEmitter memory leak when client uses a custom timeout value. 108 | 109 | ## 0.3.7 110 | 111 | * Check time drift 112 | 113 | ## 0.3.6 114 | 115 | * Fixes query string parameters. 116 | * Fixes Proxy getter issue [096bff8]. 117 | * Remove exception in callREST. 118 | 119 | ## 0.3.5 120 | 121 | * Ignore undefined parameters in REST API. 122 | * Move VPS tests, add /domains tests. 123 | 124 | ## 0.3.4 125 | 126 | * Add timeout option for REST API. 127 | 128 | ## 0.3.3 129 | 130 | * Fixes requests signature. 131 | 132 | ## 0.3.2 133 | 134 | * Tested with node v0.10. 135 | 136 | ## 0.3.1 137 | 138 | * Fix for node v0.9.8. 139 | 140 | ## 0.3.0 141 | 142 | * Major update, first version supporting OVH REST API (https://api.ovh.com). 143 | 144 | ## 0.2.0 145 | 146 | * Using native harmony-proxies Node.js implementation with --harmony-proxies flag 147 | * Moving from nodeunit to mocha 148 | * Node.js v0.4 and v0.6 are not supported anymore 149 | 150 | ## 0.1.1 151 | 152 | * Fix bad exception catch 153 | 154 | ## 0.1.0 155 | 156 | * Initial release 157 | -------------------------------------------------------------------------------- /tests/01_REST_construct.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var ovh = require('..'), 29 | assert = require('assert'), 30 | nock = require('nock'); 31 | 32 | exports.REST_construct = { 33 | 'Instantiate without or with too many authentication method': function () { 34 | "use strict"; 35 | 36 | assert.throws( 37 | function () { ovh({ endpoint: 'ovh-eu' }); }, 38 | /\[OVH\] Missing authentication. You must provide applicationKey\/applicationSecret or OAuth2 clientID\/clientSecret/ 39 | ); 40 | 41 | assert.throws( 42 | function () { ovh({ appKey: 'XXX', appSecret: 'XXX', clientID: 'XXX', clientSecret: 'XXX', endpoint: 'ovh-eu'}); }, 43 | /\[OVH\] Cannot use both applicationKey\/applicationSecret and OAuth2/ 44 | ); 45 | }, 46 | 'Instantiate with OAuth2': function () { 47 | "use strict"; 48 | 49 | assert.throws( 50 | function () { ovh({ clientID: 'XXX', endpoint: 'ovh-eu'}); }, 51 | /\[OVH\] Both clientID and clientSecret must be given/ 52 | ); 53 | 54 | assert.throws( 55 | function () { ovh({ clientSecret: 'XXX', endpoint: 'ovh-eu'}); }, 56 | /\[OVH\] Both clientID and clientSecret must be given/ 57 | ); 58 | 59 | assert.throws( 60 | function () { ovh({ clientID: 'XXX', clientSecret: 'XXX', endpoint: 'sys-eu'}); }, 61 | /\[OVH\] Endpoint does not support OAuth2 authentication/ 62 | ); 63 | }, 64 | 'Instantiate with appKey': function () { 65 | "use strict"; 66 | 67 | assert.throws( 68 | function () { ovh({ appKey: 'XXX' }); }, 69 | /\[OVH\] Both application key and application secret must be given/ 70 | ); 71 | 72 | assert.throws( 73 | function () { ovh({ appSecret: 'XXX' }); }, 74 | /\[OVH\] Both application key and application secret must be given/ 75 | ); 76 | }, 77 | 'Constructor with specified hosts or basepaths': function () { 78 | "use strict"; 79 | 80 | var rest = ovh({ 81 | appKey: 'XXX', appSecret: 'XXX', 82 | host: 'ca.ovh.com', port: 344, basePath: '/0.42' 83 | }); 84 | 85 | assert.equal(rest.host, 'ca.ovh.com'); 86 | assert.equal(rest.port, 344); 87 | assert.equal(rest.basePath, '/0.42'); 88 | }, 89 | 'Call method without CK': function (done) { 90 | "use strict"; 91 | 92 | nock('https://eu.api.ovh.com') 93 | .intercept('/1.0/auth/time', 'GET') 94 | .reply(200, Math.round(Date.now() / 1000)) 95 | .intercept('/1.0/me', 'GET') 96 | .reply(401, {'message': 'You must login first'}); 97 | 98 | var rest = ovh({ appKey: 'XXX', appSecret: 'XXX', apis: [] }); 99 | rest.request('GET', '/me', function (err, message) { 100 | assert.equal(err, 401); 101 | assert.equal(message, 'You must login first'); 102 | done(); 103 | }); 104 | }, 105 | 'Preconfigured API endpoints': function () { 106 | "use strict"; 107 | 108 | var rest = ovh({ 109 | appKey: 'XXX', appSecret: 'XXX', 110 | endpoint: 'sys-ca' 111 | }); 112 | 113 | assert.equal(rest.host, 'ca.api.soyoustart.com'); 114 | assert.equal(rest.port, 443); 115 | assert.equal(rest.basePath, '/1.0'); 116 | 117 | rest = ovh({ 118 | appKey: 'XXX', appSecret: 'XXX', 119 | endpoint: 'soyoustart-ca' 120 | }); 121 | 122 | assert.equal(rest.host, 'ca.api.soyoustart.com'); 123 | assert.equal(rest.port, 443); 124 | assert.equal(rest.basePath, '/1.0'); 125 | 126 | assert.throws( 127 | function () { ovh({ appKey: 'XXX', appSecret: 'XXX', endpoint: 'eu-ovh' }); }, 128 | /\[OVH\] Unknown API eu-ovh/ 129 | ); 130 | } 131 | }; 132 | -------------------------------------------------------------------------------- /tests/03_REST_auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var ovh = require('..'), 29 | assert = require('assert'), 30 | nock = require('nock'); 31 | 32 | // To create an application: 33 | // https://www.ovh.com/fr/cgi-bin/api/createApplication.cgi 34 | var APP_KEY = '42', 35 | APP_SECRET = '43'; 36 | 37 | exports.REST_call = { 38 | 'GET /auth/time - ovh.request()': function (done) { 39 | "use strict"; 40 | 41 | nock('https://eu.api.ovh.com') 42 | .intercept('/1.0/auth/time', 'GET') 43 | .reply(200, Math.round(Date.now() / 1000)); 44 | 45 | var rest = ovh({ 46 | appKey: APP_KEY, 47 | appSecret: APP_SECRET 48 | }); 49 | 50 | rest.request('GET', '/auth/time', {}, function (err, result) { 51 | assert.ok(!err && typeof(result) === 'number'); 52 | done(); 53 | }); 54 | }, 55 | 'GET /auth/time [promised] - ovh.request()': function (done) { 56 | "use strict"; 57 | 58 | nock('https://eu.api.ovh.com') 59 | .intercept('/1.0/auth/time', 'GET') 60 | .reply(200, Math.round(Date.now() / 1000)); 61 | 62 | var rest = ovh({ 63 | appKey: APP_KEY, 64 | appSecret: APP_SECRET 65 | }); 66 | 67 | rest.requestPromised('GET', '/auth/time', {}) 68 | .then(function (result) { 69 | assert.ok(typeof(result) === 'number'); 70 | }) 71 | .catch(function (err) { 72 | assert.ok(!err); 73 | }) 74 | .finally(done); 75 | }, 76 | 'GET /auth/credential - ovh.request()': function (done) { 77 | "use strict"; 78 | 79 | nock('https://eu.api.ovh.com') 80 | .intercept('/1.0/auth/time', 'GET') 81 | .reply(200, Math.round(Date.now() / 1000)) 82 | .intercept('/1.0/auth/credential', 'POST') 83 | .reply(200, { 84 | 'validationUrl': 'http://eu.api.ovh.com', 85 | 'consumerKey': '84', 86 | 'state': 'pendingValidation' 87 | }); 88 | 89 | var rest = ovh({ 90 | appKey: APP_KEY, 91 | appSecret: APP_SECRET 92 | }); 93 | 94 | rest.request('POST', '/auth/credential', { 95 | 'accessRules': [ 96 | { 'method': 'GET', 'path': '/*'}, 97 | { 'method': 'POST', 'path': '/*'}, 98 | { 'method': 'PUT', 'path': '/*'}, 99 | { 'method': 'DELETE', 'path': '/*'} 100 | ], 101 | 'redirection': 'https://npmjs.org/package/ovh' 102 | }, function (err, credential) { 103 | assert.ok(!err && credential.state === 'pendingValidation'); 104 | done(); 105 | }); 106 | }, 107 | 'GET /auth/credential [promised] - ovh.request()': function (done) { 108 | "use strict"; 109 | 110 | nock('https://eu.api.ovh.com') 111 | .intercept('/1.0/auth/time', 'GET') 112 | .reply(200, Math.round(Date.now() / 1000)) 113 | .intercept('/1.0/auth/credential', 'POST') 114 | .reply(200, { 115 | 'validationUrl': 'http://eu.api.ovh.com', 116 | 'consumerKey': '84', 117 | 'state': 'pendingValidation' 118 | }); 119 | 120 | var rest = ovh({ 121 | appKey: APP_KEY, 122 | appSecret: APP_SECRET 123 | }); 124 | 125 | rest.requestPromised('POST', '/auth/credential', { 126 | 'accessRules': [ 127 | { 'method': 'GET', 'path': '/*'}, 128 | { 'method': 'POST', 'path': '/*'}, 129 | { 'method': 'PUT', 'path': '/*'}, 130 | { 'method': 'DELETE', 'path': '/*'} 131 | ], 132 | 'redirection': 'https://npmjs.org/package/ovh' 133 | }) 134 | .then(function (credential) { 135 | assert.ok(credential && credential.state === 'pendingValidation'); 136 | }) 137 | .catch(function (err) { 138 | assert.ok(!err); 139 | }) 140 | .finally(done); 141 | } 142 | }; 143 | 144 | -------------------------------------------------------------------------------- /tests/06_REST_oauth2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var ovh = require('../lib/ovh.es5'), 29 | assert = require('assert'), 30 | nock = require('nock'); 31 | 32 | var oauthConfig = { 33 | clientID: '1234', 34 | clientSecret: 'abcdef', 35 | endpoint: 'ovh-eu' 36 | }; 37 | 38 | exports.REST_oauth2 = { 39 | 'GET /me with a valid token': function (done) { 40 | 'use strict'; 41 | 42 | nock('https://www.ovh.com/auth') 43 | .intercept('/oauth2/token', 'POST') 44 | .reply(200, { 45 | 'access_token': 'ok', 46 | 'token_type': 'Bearer', 47 | 'expires_in': 3599, 48 | 'scope': 'all' 49 | }); 50 | nock('https://eu.api.ovh.com', { 51 | reqheaders: { 52 | authorization: 'Bearer ok', 53 | }, 54 | }) 55 | .intercept('/1.0/me', 'GET') 56 | .reply(200, {"status": "ok"}); 57 | 58 | var rest = ovh(oauthConfig); 59 | rest.request('GET', '/me', function (err, msg) { 60 | assert.ok(!err); 61 | assert.equal(msg.status, 'ok'); 62 | done(); 63 | }); 64 | }, 65 | 'GET /me with a valid token [promised]': function (done) { 66 | 'use strict'; 67 | 68 | nock('https://www.ovh.com/auth') 69 | .intercept('/oauth2/token', 'POST') 70 | .reply(200, { 71 | 'access_token': 'ok', 72 | 'token_type': 'Bearer', 73 | 'expires_in': 3599, 74 | 'scope': 'all' 75 | }); 76 | nock('https://eu.api.ovh.com', { 77 | reqheaders: { 78 | authorization: 'Bearer ok', 79 | }, 80 | }) 81 | .intercept('/1.0/me', 'GET') 82 | .reply(200, {"status": "ok"}); 83 | 84 | var rest = ovh(oauthConfig); 85 | rest.requestPromised('GET', '/me') 86 | .then(function (msg) { 87 | assert.equal(msg.status, 'ok'); 88 | }) 89 | .catch(function (err) { 90 | assert.ok(!err); 91 | }) 92 | .finally(done); 93 | }, 94 | 'GET /me with invalid credentials': function (done) { 95 | 'use strict'; 96 | 97 | nock('https://www.ovh.com/auth') 98 | .intercept('/oauth2/token', 'POST') 99 | .reply(401, { 100 | 'error': 'Invalid client' 101 | }); 102 | 103 | var rest = ovh(oauthConfig); 104 | rest.request('GET', '/me', function (err, msg) { 105 | assert.deepEqual(err, { 106 | statusCode: 401, 107 | error: 'Unauthorized', 108 | message: { 'error': 'Invalid client' } 109 | }); 110 | done(); 111 | }); 112 | }, 113 | 'GET /me with expired token': function (done) { 114 | 'use strict'; 115 | 116 | this.timeout(5000); 117 | 118 | nock('https://www.ovh.com/auth') 119 | .intercept('/oauth2/token', 'POST') 120 | .reply(200, { 121 | 'access_token': 'token1', 122 | 'token_type': 'Bearer', 123 | 'expires_in': 10, 124 | 'scope': 'all' 125 | }); 126 | nock('https://eu.api.ovh.com', { 127 | reqheaders: { 128 | authorization: 'Bearer token1', 129 | }, 130 | }).intercept('/1.0/me', 'GET') 131 | .reply(200, {"status": "ok"}); 132 | 133 | nock('https://www.ovh.com/auth') 134 | .intercept('/oauth2/token', 'POST') 135 | .reply(200, { 136 | 'access_token': 'token2', 137 | 'token_type': 'Bearer', 138 | 'expires_in': 10, 139 | 'scope': 'all' 140 | }); 141 | nock('https://eu.api.ovh.com', { 142 | reqheaders: { 143 | authorization: 'Bearer token2', 144 | }, 145 | }) 146 | .intercept('/1.0/me', 'GET') 147 | .reply(200, {"status": "ok"}); 148 | 149 | var rest = ovh(oauthConfig); 150 | rest.request('GET', '/me', function (err, msg) { 151 | assert.ok(!err); 152 | assert.equal(msg.status, 'ok'); 153 | 154 | // Wait for first token expiration before resending a request 155 | setTimeout(function() { 156 | rest.request('GET', '/me', function (err, msg) { 157 | assert.ok(!err); 158 | assert.equal(msg.status, 'ok'); 159 | done(); 160 | }); 161 | }, 2000); 162 | }); 163 | }, 164 | }; 165 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // -------------------------------------------------------------------- 3 | // JSHint Configuration, Strict Edition 4 | // -------------------------------------------------------------------- 5 | // 6 | // This is a options template for [JSHint][1], using [JSHint example][2] 7 | // and [Ory Band's example][3] as basis and setting config values to 8 | // be most strict: 9 | // 10 | // * set all enforcing options to true 11 | // * set all relaxing options to false 12 | // * set all environment options to false, except the browser value 13 | // * set all JSLint legacy options to false 14 | // 15 | // [1]: http://www.jshint.com/ 16 | // [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json 17 | // [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc 18 | // 19 | // @author http://michael.haschke.biz/ 20 | // @license http://unlicense.org/ 21 | 22 | // == Enforcing Options =============================================== 23 | // 24 | // These options tell JSHint to be more strict towards your code. Use 25 | // them if you want to allow only a safe subset of JavaScript, very 26 | // useful when your codebase is shared with a big number of developers 27 | // with different skill levels. 28 | 29 | "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). 30 | "curly" : true, // Require {} for every new block or scope. 31 | "eqeqeq" : true, // Require triple equals i.e. `===`. 32 | "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`. 33 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 34 | "latedef" : true, // Prohibit variable use before definition. 35 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 36 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 37 | "noempty" : true, // Prohibit use of empty blocks. 38 | "nonew" : true, // Prohibit use of constructors for side-effects. 39 | "plusplus" : false, // Prohibit use of `++` & `--`. 40 | "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions. 41 | "undef" : true, // Require all non-global variables be declared before they are used. 42 | "strict" : true, // Require `use strict` pragma in every file. 43 | "trailing" : true, // Prohibit trailing whitespaces. 44 | 45 | // == Relaxing Options ================================================ 46 | // 47 | // These options allow you to suppress certain types of warnings. Use 48 | // them only if you are absolutely positive that you know what you are 49 | // doing. 50 | 51 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 52 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 53 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 54 | "eqnull" : true, // Tolerate use of `== null`. 55 | "es5" : false, // Allow EcmaScript 5 syntax. 56 | "esversion" : 8, // ES version to use. 57 | "evil" : false, // Tolerate use of `eval`. 58 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 59 | "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside. 60 | "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 61 | "iterator" : false, // Allow usage of __iterator__ property. 62 | "lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block. 63 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 64 | "laxcomma" : false, // Suppress warnings about comma-first coding style. 65 | "loopfunc" : false, // Allow functions to be defined within loops. 66 | "multistr" : false, // Tolerate multi-line strings. 67 | "onecase" : false, // Tolerate switches with just one case. 68 | "proto" : false, // Tolerate __proto__ property. This property is deprecated. 69 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 70 | "scripturl" : false, // Tolerate script-targeted URLs. 71 | "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only. 72 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 73 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 74 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 75 | "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function." 76 | 77 | // == Environments ==================================================== 78 | // 79 | // These options pre-define global variables that are exposed by 80 | // popular JavaScript libraries and runtime environments—such as 81 | // browser or node.js. 82 | 83 | "browser" : false, // Standard browser globals e.g. `window`, `document`. 84 | "couch" : false, // Enable globals exposed by CouchDB. 85 | "devel" : false, // Allow development statements e.g. `console.log();`. 86 | "dojo" : false, // Enable globals exposed by Dojo Toolkit. 87 | "jquery" : false, // Enable globals exposed by jQuery JavaScript library. 88 | "mootools" : false, // Enable globals exposed by MooTools JavaScript framework. 89 | "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment. 90 | "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape. 91 | "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework. 92 | "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment. 93 | "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host. 94 | 95 | // == JSLint Legacy =================================================== 96 | // 97 | // These options are legacy from JSLint. Aside from bug fixes they will 98 | // not be improved in any way and might be removed at any point. 99 | 100 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 101 | "onevar" : false, // Allow only one `var` statement per function. 102 | "passfail" : false, // Stop on first error. 103 | "white" : false, // Check against strict whitespace and indentation rules. 104 | 105 | // == Undocumented Options ============================================ 106 | // 107 | // While I've found these options in [example1][2] and [example2][3] 108 | // they are not described in the [JSHint Options documentation][4]. 109 | // 110 | // [4]: http://www.jshint.com/options/ 111 | 112 | "maxerr" : 100, // Maximum errors before stopping. 113 | "predef" : [ // Extra globals. 114 | "Proxy" 115 | ], 116 | "indent" : 2 // Specify indentation spacing 117 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Node.js Wrapper for OVHcloud APIs](https://ovh.github.io/node-ovh/img/logo.png)](https://ovh.github.io/node-ovh) 2 | 3 | The easiest way to use the [OVHcloud.com](https://www.ovhcloud.com) APIs in your [node.js](https://nodejs.org/) applications. 4 | 5 | [![NPM Version](https://img.shields.io/npm/v/ovh.svg?style=flat)](https://www.npmjs.org/package/ovh) 6 | [![Build Status](https://img.shields.io/travis/ovh/node-ovh.svg?style=flat)](https://travis-ci.org/ovh/node-ovh) 7 | [![Coverage Status](https://img.shields.io/coveralls/ovh/node-ovh.svg?style=flat)](https://coveralls.io/r/ovh/node-ovh?branch=master) 8 | 9 | ```js 10 | // Create your first application tokens here: https://api.ovh.com/createToken/?GET=/me 11 | var ovh = require('@ovhcloud/node-ovh')({ 12 | appKey: process.env.APP_KEY, 13 | appSecret: process.env.APP_SECRET, 14 | consumerKey: process.env.CONSUMER_KEY 15 | }); 16 | 17 | ovh.request('GET', '/me', function (err, me) { 18 | console.log(err || 'Welcome ' + me.firstname); 19 | }); 20 | ``` 21 | 22 | You can also use the promised version like this: 23 | ```js 24 | ovh.requestPromised('GET', '/me') 25 | .then(function (response) { 26 | //Do what you want 27 | }) 28 | .catch(function (err) { 29 | //Return an error object like this {error: statusCode, message: message} 30 | }); 31 | ``` 32 | 33 | ## Installation 34 | 35 | The easiest way to get the latest stable release is to grab it from the 36 | [npm registry](https://npmjs.org/package/@ovhcloud/node-ovh). 37 | 38 | ```bash 39 | $ npm install @ovhcloud/node-ovh 40 | ``` 41 | 42 | Alternatively, you may get latest development version directly from Git. 43 | 44 | ```bash 45 | $ npm install git://github.com/ovh/node-ovh.git 46 | ``` 47 | 48 | ## Example Usage 49 | 50 | ### OAuth2 51 | 52 | #### 1. Generate credentials 53 | 54 | Generate a valid pair of clientID/clientSecret following this [documentation](https://help.ovhcloud.com/csm/en-manage-service-account?id=kb_article_view&sysparm_article=KB0059343) 55 | 56 | #### 2. Create an OVHcloud API client 57 | 58 | ```js 59 | var ovh = require('@ovhcloud/node-ovh')({ 60 | clientID: 'YOUR_CLIENT_ID' 61 | clientSecret: 'YOUR_CLIENT_SECRET' 62 | endpoint: 'ovh-eu', 63 | }); 64 | ``` 65 | 66 | Depending on the API you want to use, you may set the endpoint to: 67 | * `ovh-eu` for OVHcloud Europe API 68 | * `ovh-us` for OVHcloud US API 69 | * `ovh-ca` for OVHcloud Canada API 70 | 71 | 72 | ### Application Key/ApplicationSecret 73 | 74 | #### 1. Create an application 75 | 76 | Depending the API you plan to use, you need to create an application on the below 77 | websites: 78 | 79 | * [OVHcloud Europe](https://eu.api.ovh.com/createApp/) 80 | * [OVHcloud US](https://api.us.ovhcloud.com/createApp/) 81 | * [OVHcloud North-America](https://ca.api.ovh.com/createApp/) 82 | * [SoYouStart Europe](https://eu.api.soyoustart.com/createApp/) 83 | * [SoYouStart North-America](https://ca.api.soyoustart.com/createApp/) 84 | * [Kimsufi Europe](https://eu.api.kimsufi.com/createApp/) 85 | * [Kimsufi North-America](https://ca.api.kimsufi.com/createApp/) 86 | 87 | Once created, you will obtain an **application key (AK)** and an **application 88 | secret (AS)**. 89 | 90 | #### 2. Authorize your application to access to a customer account 91 | 92 | To allow your application to access to a customer account using an OVHcloud API, 93 | you need a **consumer key (CK)**. 94 | 95 | Here is a sample code you can use to allow your application to access to a 96 | complete account. 97 | 98 | Depending the API you want to use, you need to specify the below API endpoint: 99 | 100 | * OVHcloud Europe: ```ovh-eu``` (default) 101 | * OVHcloud US: ```ovh-us``` 102 | * OVHcloud North-America: ```ovh-ca``` 103 | * SoYouStart Europe: ```soyoustart-eu``` 104 | * SoYouStart North-America: ```soyoustart-ca``` 105 | * Kimsufi Europe: ```kimsufi-eu``` 106 | * Kimsufi North-America: ```kimsufi-ca``` 107 | 108 | ```js 109 | var ovh = require('@ovhcloud/node-ovh')({ 110 | endpoint: 'ovh-eu', 111 | appKey: 'YOUR_APP_KEY', 112 | appSecret: 'YOUR_APP_SECRET' 113 | }); 114 | 115 | ovh.request('POST', '/auth/credential', { 116 | 'accessRules': [ 117 | { 'method': 'GET', 'path': '/*'}, 118 | { 'method': 'POST', 'path': '/*'}, 119 | { 'method': 'PUT', 'path': '/*'}, 120 | { 'method': 'DELETE', 'path': '/*'} 121 | ] 122 | }, function (error, credential) { 123 | console.log(error || credential); 124 | }); 125 | ``` 126 | 127 | ```bash 128 | $ node credentials.js 129 | { validationUrl: 'https://api.ovh.com/auth/?credentialToken=XXX', 130 | consumerKey: 'CK', 131 | state: 'pendingValidation' } 132 | ``` 133 | 134 | This consumer key can be scoped with a **specific authorization**. 135 | For example if your application will only send SMS: 136 | 137 | ```javascript 138 | ovh.request('POST', '/auth/credential', { 139 | 'accessRules': [ 140 | { 'method': 'POST', 'path': '/sms/*/jobs'}, 141 | ] 142 | }, function (error, credential) { 143 | console.log(error || credential); 144 | }); 145 | ``` 146 | 147 | Once the consumer key will be authorized on the specified URL, 148 | you'll be able to play with the API calls allowed by this key. 149 | 150 | #### 3. Let's play! 151 | 152 | You are now be able to play with the API. Look at the 153 | [examples available online](https://ovh.github.io/node-ovh#examples). 154 | 155 | You can browse the API schemas using the web consoles of the APIs: 156 | 157 | * [OVHcloud Europe](https://eu.api.ovh.com/console/) 158 | * [OVHcloud US](https://api.us.ovhcloud.com/console/) 159 | * [OVHcloud North-America](https://ca.api.ovh.com/console/) 160 | * [SoYouStart Europe](https://eu.api.soyoustart.com/console/) 161 | * [SoYouStart North-America](https://ca.api.soyoustart.com/console/) 162 | * [Kimsufi Europe](https://eu.api.kimsufi.com/console/) 163 | * [Kimsufi North-America](https://ca.api.kimsufi.com/console/) 164 | 165 | ## Migration from 1.x.x to 2.x.x without Proxy support 166 | 167 | For example if you use the OVHcloud Europe API, you'll have to check on https://eu.api.ovh.com/console/ the endpoints available for your feature. 168 | 169 | In order to have the informations about the bill with id "0123". 170 | + Before in 1.x.x with Proxy: 171 | 172 | ```javascript 173 | ovh.me.bill["0123"].$get(function (err, billInformation) { 174 | 175 | }); 176 | ``` 177 | 178 | + Now in 2.x.x with promise: 179 | 180 | ```javascript 181 | ovh.requestPromised('GET', '/me/bill/0123') //This route has been found at https://eu.api.ovh.com/console/ 182 | .then(function (billInformation) { 183 | 184 | }) 185 | .catch(function (err) { 186 | 187 | }); 188 | ``` 189 | 190 | ## Full documentation and examples 191 | 192 | The full documentation is available online: https://ovh.github.io/node-ovh. 193 | 194 | ## Hacking 195 | 196 | ### Get the sources 197 | 198 | ```bash 199 | git clone https://github.com/ovh/node-ovh.git 200 | cd node-ovh 201 | ``` 202 | 203 | You've developed a new cool feature? Fixed an annoying bug? We'd be happy 204 | to hear from you! 205 | 206 | ### Run the tests 207 | 208 | Tests are based on [mocha](https://mochajs.org/). 209 | This package includes unit and integration tests. 210 | 211 | ``` 212 | git clone https://github.com/ovh/node-ovh.git 213 | cd node-ovh 214 | npm install -d 215 | npm test 216 | ``` 217 | 218 | Integration tests use the OVHcloud /domain/zone API, the tokens can be created 219 | [here](https://api.ovh.com/createToken/). 220 | 221 | ``` 222 | export APP_KEY=xxxxx 223 | export APP_SECRET=yyyyy 224 | export CONSUMER_KEY=zzzzz 225 | export DOMAIN_ZONE_NAME=example.com 226 | npm run-script test-integration 227 | ``` 228 | 229 | ### Documentation 230 | 231 | The documentation is based on [Github Pages](https://pages.github.com/) and is 232 | available in the *gh-pages* branch. 233 | 234 | 235 | ## Supported APIs 236 | 237 | ### OVHcloud Europe 238 | 239 | - **Documentation**: https://eu.api.ovh.com/ 240 | - **Community support**: api-subscribe@ml.ovh.net 241 | - **Console**: https://eu.api.ovh.com/console 242 | - **Create application credentials**: https://eu.api.ovh.com/createApp/ 243 | - **Create script credentials** (all keys at once): https://eu.api.ovh.com/createToken/ 244 | 245 | ### OVHcloud US 246 | 247 | - **Documentation**: https://api.us.ovhcloud.com/ 248 | - **Console**: https://api.us.ovhcloud.com/console/ 249 | - **Create application credentials**: https://api.us.ovhcloud.com/createApp/ 250 | - **Create script credentials** (all keys at once): https://api.us.ovhcloud.com/createToken/ 251 | 252 | ### OVHcloud North America 253 | 254 | - **Documentation**: https://ca.api.ovh.com/ 255 | - **Community support**: api-subscribe@ml.ovh.net 256 | - **Console**: https://ca.api.ovh.com/console 257 | - **Create application credentials**: https://ca.api.ovh.com/createApp/ 258 | - **Create script credentials** (all keys at once): https://ca.api.ovh.com/createToken/ 259 | 260 | ### SoYouStart Europe 261 | 262 | - **Documentation**: https://eu.api.soyoustart.com/ 263 | - **Community support**: api-subscribe@ml.ovh.net 264 | - **Console**: https://eu.api.soyoustart.com/console/ 265 | - **Create application credentials**: https://eu.api.soyoustart.com/createApp/ 266 | - **Create script credentials** (all keys at once): https://eu.api.soyoustart.com/createToken/ 267 | 268 | ### SoYouStart North America 269 | 270 | - **Documentation**: https://ca.api.soyoustart.com/ 271 | - **Community support**: api-subscribe@ml.ovh.net 272 | - **Console**: https://ca.api.soyoustart.com/console/ 273 | - **Create application credentials**: https://ca.api.soyoustart.com/createApp/ 274 | - **Create script credentials** (all keys at once): https://ca.api.soyoustart.com/createToken/ 275 | 276 | ### Kimsufi Europe 277 | 278 | - **Documentation**: https://eu.api.kimsufi.com/ 279 | - **Community support**: api-subscribe@ml.ovh.net 280 | - **Console**: https://eu.api.kimsufi.com/console/ 281 | - **Create application credentials**: https://eu.api.kimsufi.com/createApp/ 282 | - **Create script credentials** (all keys at once): https://eu.api.kimsufi.com/createToken/ 283 | 284 | ### Kimsufi North America 285 | 286 | - **Documentation**: https://ca.api.kimsufi.com/ 287 | - **Community support**: api-subscribe@ml.ovh.net 288 | - **Console**: https://ca.api.kimsufi.com/console/ 289 | - **Create application credentials**: https://ca.api.kimsufi.com/createApp/ 290 | - **Create script credentials** (all keys at once): https://ca.api.kimsufi.com/createToken/ 291 | 292 | ## Related links 293 | 294 | - **Contribute**: https://github.com/ovh/node-ovh 295 | - **Report bugs**: https://github.com/ovh/node-ovh/issues 296 | - **Download**: https://npmjs.org/package/ovh 297 | -------------------------------------------------------------------------------- /tests/04_REST_me.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var ovh = require('..'), 29 | async = require('async'), 30 | assert = require('assert'), 31 | nock = require('nock'); 32 | 33 | // To create an application: 34 | // https://www.ovh.com/fr/cgi-bin/api/createApplication.cgi 35 | var APP_KEY = '42', 36 | APP_SECRET = '43', 37 | CONSUMER_KEY = '44'; 38 | 39 | exports.REST_me = { 40 | 'PUT /me - ovh.request()': function (done) { 41 | 'use strict'; 42 | 43 | nock('https://eu.api.ovh.com') 44 | .intercept('/1.0/auth/time', 'GET') 45 | .reply(200, Math.round(Date.now() / 1000)) 46 | .intercept('/1.0/me', 'PUT') 47 | .reply(200, { 48 | 'city': 'Roubaix Valley' 49 | }); 50 | 51 | var rest = ovh({ 52 | appKey: APP_KEY, 53 | appSecret: APP_SECRET, 54 | consumerKey: CONSUMER_KEY 55 | }); 56 | 57 | rest.request('PUT', '/me', { 58 | 'city': 'Roubaix Valley' 59 | }, function (err) { 60 | assert.ok(!err); 61 | done(); 62 | }); 63 | }, 64 | 'PUT /me - ovh.request() [promised]': function (done) { 65 | 'use strict'; 66 | 67 | nock('https://eu.api.ovh.com') 68 | .intercept('/1.0/auth/time', 'GET') 69 | .reply(200, Math.round(Date.now() / 1000)) 70 | .intercept('/1.0/me', 'PUT') 71 | .reply(200, { 72 | 'city': 'Roubaix Valley' 73 | }); 74 | 75 | var rest = ovh({ 76 | appKey: APP_KEY, 77 | appSecret: APP_SECRET, 78 | consumerKey: CONSUMER_KEY 79 | }); 80 | 81 | rest.requestPromised('PUT', '/me', { 82 | 'city': 'Roubaix Valley' 83 | }) 84 | .catch(function (err) { 85 | assert.ok(!err); 86 | }) 87 | .finally(done); 88 | }, 89 | 'PUT /me - ovh.request() - 403 [promised]': function (done) { 90 | 'use strict'; 91 | 92 | nock('https://eu.api.ovh.com') 93 | .intercept('/1.0/auth/time', 'GET') 94 | .reply(200, Math.round(Date.now() / 1000)) 95 | .intercept('/1.0/me', 'PUT') 96 | .reply(403, { 97 | 'errorCode': 'INVALID_CREDENTIAL', 98 | 'httpCode': '403 Forbidden', 99 | 'message': 'This credential is not valid' 100 | }); 101 | 102 | var rest = ovh({ 103 | appKey: APP_KEY, 104 | appSecret: APP_SECRET, 105 | consumerKey: CONSUMER_KEY 106 | }); 107 | 108 | rest.requestPromised('PUT', '/me', { 109 | 'city': 'Roubaix Valley' 110 | }) 111 | .then(function (resp) { 112 | assert.ok(!resp); 113 | }) 114 | .catch(function (err) { 115 | assert.equal(err.error, 403); 116 | assert.equal(err.message, 'This credential is not valid'); 117 | }) 118 | .finally(done); 119 | }, 120 | 'GET /me/aggreements/{id} - Variable replacement': function (done) { 121 | 'use strict'; 122 | 123 | nock('https://eu.api.ovh.com') 124 | .intercept('/1.0/auth/time', 'GET') 125 | .reply(200, Math.round(Date.now() / 1000)) 126 | .intercept('/1.0/me/agreements/42', 'GET') 127 | .reply(200, {}); 128 | 129 | var rest = ovh({ 130 | appKey: APP_KEY, 131 | appSecret: APP_SECRET, 132 | consumerKey: CONSUMER_KEY 133 | }); 134 | 135 | rest.request('GET', '/me/agreements/{id}', { 136 | 'id': 42 137 | }, function (err) { 138 | assert.ok(!err); 139 | done(); 140 | }); 141 | }, 142 | 'GET /me/aggreements/{id} - Variable replacement [promised]': function (done) { 143 | 'use strict'; 144 | 145 | nock('https://eu.api.ovh.com') 146 | .intercept('/1.0/auth/time', 'GET') 147 | .reply(200, Math.round(Date.now() / 1000)) 148 | .intercept('/1.0/me/agreements/42', 'GET') 149 | .reply(200, {}); 150 | 151 | var rest = ovh({ 152 | appKey: APP_KEY, 153 | appSecret: APP_SECRET, 154 | consumerKey: CONSUMER_KEY 155 | }); 156 | 157 | rest.requestPromised('GET', '/me/agreements/{id}', { 158 | 'id': 42 159 | }) 160 | .catch(function (err) { 161 | assert.ok(!err); 162 | }) 163 | .finally(done); 164 | }, 165 | 'GET /me/agreements - Filtering': function (done) { 166 | 'use strict'; 167 | 168 | nock('https://eu.api.ovh.com') 169 | .intercept('/1.0/auth/time', 'GET') 170 | .reply(200, Math.round(Date.now() / 1000)) 171 | .intercept('/1.0/me/agreement?agreed=ok', 'GET') 172 | .reply(200, []) 173 | .intercept('/1.0/me/agreement', 'GET') 174 | .reply(200, [42]); 175 | 176 | var rest = ovh({ 177 | appKey: APP_KEY, 178 | appSecret: APP_SECRET, 179 | consumerKey: CONSUMER_KEY, 180 | debug: function (message) { 181 | assert.ok(message); 182 | } 183 | }); 184 | 185 | rest.request('GET', '/me/agreement', { 186 | 'agreed': 'ok' 187 | }, function (err, agreements) { 188 | assert.ok(!err); 189 | assert.equal(agreements.length, 0); 190 | done(); 191 | }); 192 | }, 193 | 'GET /me/agreements - Filtering [promised]': function (done) { 194 | 'use strict'; 195 | 196 | nock('https://eu.api.ovh.com') 197 | .intercept('/1.0/auth/time', 'GET') 198 | .reply(200, Math.round(Date.now() / 1000)) 199 | .intercept('/1.0/me/agreement?agreed=ok', 'GET') 200 | .reply(200, []) 201 | .intercept('/1.0/me/agreement', 'GET') 202 | .reply(200, [42]); 203 | 204 | var rest = ovh({ 205 | appKey: APP_KEY, 206 | appSecret: APP_SECRET, 207 | consumerKey: CONSUMER_KEY, 208 | debug: function (message) { 209 | assert.ok(message); 210 | } 211 | }); 212 | 213 | rest.requestPromised('GET', '/me/agreement', { 214 | 'agreed': 'ok' 215 | }) 216 | .then(function (agreements) { 217 | assert.equal(agreements.length, 0); 218 | }) 219 | .catch(function (err) { 220 | assert.ok(!err); 221 | }) 222 | .finally(done); 223 | }, 224 | 'PUT /me - Remove undefined': function (done) { 225 | 'use strict'; 226 | 227 | nock('https://eu.api.ovh.com') 228 | .intercept('/1.0/auth/time', 'GET') 229 | .reply(200, Math.round(Date.now() / 1000)) 230 | .intercept('/1.0/me', 'PUT') 231 | .reply(403, { 232 | 'errorCode': 'INVALID_CREDENTIAL', 233 | 'httpCode': '403 Forbidden', 234 | 'message': 'This credential is not valid' 235 | }); 236 | 237 | var rest = ovh({ 238 | appKey: APP_KEY, 239 | appSecret: APP_SECRET, 240 | consumerKey: CONSUMER_KEY 241 | }); 242 | 243 | rest.request('PUT', '/me', { 244 | 'city': 'Roubaix Valley', 245 | 'firstname': undefined 246 | }, function (statusCode, message) { 247 | assert.equal(statusCode, 403); 248 | assert.equal(message, 'This credential is not valid'); 249 | done(); 250 | }); 251 | }, 252 | 'PUT /me - Remove undefined [promised]': function (done) { 253 | 'use strict'; 254 | 255 | nock('https://eu.api.ovh.com') 256 | .intercept('/1.0/auth/time', 'GET') 257 | .reply(200, Math.round(Date.now() / 1000)) 258 | .intercept('/1.0/me', 'PUT') 259 | .reply(403, { 260 | 'errorCode': 'INVALID_CREDENTIAL', 261 | 'httpCode': '403 Forbidden', 262 | 'message': 'This credential is not valid' 263 | }); 264 | 265 | var rest = ovh({ 266 | appKey: APP_KEY, 267 | appSecret: APP_SECRET, 268 | consumerKey: CONSUMER_KEY 269 | }); 270 | 271 | rest.requestPromised('PUT', '/me', { 272 | 'city': 'Roubaix Valley', 273 | 'firstname': undefined 274 | }) 275 | .then(function (resp) { 276 | assert.ok(!resp); 277 | }) 278 | .catch(function (err) { 279 | assert.equal(err.error, 403); 280 | assert.equal(err.message, 'This credential is not valid'); 281 | }) 282 | .finally(done); 283 | }, 284 | 'DELETE /todelete - 0 bytes JSON body': function (done) { 285 | 'use strict'; 286 | 287 | nock('https://eu.api.ovh.com') 288 | .intercept('/1.0/auth/time', 'GET') 289 | .reply(200, Math.round(Date.now() / 1000)) 290 | .intercept('/1.0/todelete', 'DELETE') 291 | .reply(200, ''); 292 | 293 | var rest = ovh({ 294 | appKey: APP_KEY, 295 | appSecret: APP_SECRET, 296 | consumerKey: CONSUMER_KEY 297 | }); 298 | 299 | rest.request('DELETE', '/todelete', function (err, message) { 300 | assert.ok(!err); 301 | assert.equal(message, null); 302 | done(); 303 | }); 304 | }, 305 | 'DELETE /todelete - 0 bytes JSON body [promised]': function (done) { 306 | 'use strict'; 307 | 308 | nock('https://eu.api.ovh.com') 309 | .intercept('/1.0/auth/time', 'GET') 310 | .reply(200, Math.round(Date.now() / 1000)) 311 | .intercept('/1.0/todelete', 'DELETE') 312 | .reply(200, ''); 313 | 314 | var rest = ovh({ 315 | appKey: APP_KEY, 316 | appSecret: APP_SECRET, 317 | consumerKey: CONSUMER_KEY 318 | }); 319 | 320 | rest.requestPromised('DELETE', '/todelete') 321 | .then(function (resp) { 322 | assert.equal(resp, null); 323 | }) 324 | .catch(function (err) { 325 | assert.ok(!err); 326 | }) 327 | .finally(done); 328 | } 329 | }; 330 | 331 | -------------------------------------------------------------------------------- /tests/02_REST_check.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 28 | var ovh = require('..'), 29 | fs = require('fs'), 30 | path = require('path'), 31 | assert = require('assert'), 32 | nock = require('nock'); 33 | 34 | // To create an application: 35 | // https://www.ovh.com/fr/cgi-bin/api/createApplication.cgi 36 | var APP_KEY = '42', 37 | APP_SECRET = '43'; 38 | 39 | // Fixtures 40 | var fixtures_path = path.join(__dirname, 'fixtures'); 41 | var auth_path = path.join(fixtures_path, 'api.auth.json'); 42 | var auth_api = fs.readFileSync(auth_path).toString(); 43 | var bad_path = path.join(fixtures_path, 'api.bad.json'); 44 | var bad_api = fs.readFileSync(bad_path).toString(); 45 | var me_path = path.join(fixtures_path, 'api.me.json'); 46 | var me_api = fs.readFileSync(me_path).toString(); 47 | var deprecated_path = path.join(fixtures_path, 'api.deprecated.json'); 48 | var deprecated_api = fs.readFileSync(deprecated_path).toString(); 49 | 50 | 51 | exports.REST_check = { 52 | 'Check Deprecated warning': function (done) { 53 | 'use strict'; 54 | 55 | nock('https://eu.api.ovh.com') 56 | .intercept('/1.0/auth/time', 'GET') 57 | .reply(200, Math.round(Date.now() / 1000)) 58 | .intercept('/1.0/deprecated/my-service/route', 'GET') 59 | .reply(200, 'Deprecated') 60 | .intercept('/1.0/deprecated.json', 'GET') 61 | .reply(200, deprecated_api) 62 | .intercept('/1.0/auth.json', 'GET') 63 | .reply(200, auth_api); 64 | 65 | var rest = ovh({ 66 | appKey: APP_KEY, 67 | appSecret: APP_SECRET, 68 | apis: ['deprecated'], 69 | warn: function (err) { 70 | assert.ok(err.indexOf('is tagged DEPRECATED') > 0); 71 | done(); 72 | } 73 | }); 74 | 75 | rest.request('GET', '/deprecated/my-service/route', function (err) { 76 | assert.ok(err); 77 | }); 78 | }, 79 | 'Check Deprecated warning [promised]': function (done) { 80 | 'use strict'; 81 | 82 | nock('https://eu.api.ovh.com') 83 | .intercept('/1.0/auth/time', 'GET') 84 | .reply(200, Math.round(Date.now() / 1000)) 85 | .intercept('/1.0/deprecated/my-service/route', 'GET') 86 | .reply(200, 'Deprecated') 87 | .intercept('/1.0/deprecated.json', 'GET') 88 | .reply(200, deprecated_api) 89 | .intercept('/1.0/auth.json', 'GET') 90 | .reply(200, auth_api); 91 | 92 | var rest = ovh({ 93 | appKey: APP_KEY, 94 | appSecret: APP_SECRET, 95 | apis: ['deprecated'], 96 | warn: function (err) { 97 | assert.ok(err.indexOf('is tagged DEPRECATED') > 0); 98 | done(); 99 | } 100 | }); 101 | 102 | rest.requestPromised('GET', '/deprecated/my-service/route') 103 | .then(function (resp) { 104 | assert.ok(!resp); 105 | }) 106 | .catch(function (err) { 107 | assert.ok(err); 108 | }); 109 | }, 110 | 'Check call not found warning': function (done) { 111 | 'use strict'; 112 | 113 | nock('https://eu.api.ovh.com') 114 | .intercept('/1.0/auth/time', 'GET') 115 | .reply(200, Math.round(Date.now() / 1000)) 116 | .intercept('/1.0/auth/not-found', 'GET') 117 | .reply(404) 118 | .intercept('/1.0/auth.json', 'GET') 119 | .reply(200, auth_api); 120 | 121 | var rest = ovh({ 122 | appKey: APP_KEY, 123 | appSecret: APP_SECRET, 124 | apis: ['auth'], 125 | warn: function (err) { 126 | assert.equal(err, '[OVH] Your call /auth/not-found was not found in the API schemas.'); 127 | done(); 128 | } 129 | }); 130 | 131 | rest.request('GET', '/auth/not-found', function (err) { 132 | assert.ok(err); 133 | }); 134 | }, 135 | 'Check call not found warning [promised]': function (done) { 136 | 'use strict'; 137 | 138 | nock('https://eu.api.ovh.com') 139 | .intercept('/1.0/auth/time', 'GET') 140 | .reply(200, Math.round(Date.now() / 1000)) 141 | .intercept('/1.0/auth/not-found', 'GET') 142 | .reply(404) 143 | .intercept('/1.0/auth.json', 'GET') 144 | .reply(200, auth_api); 145 | 146 | var rest = ovh({ 147 | appKey: APP_KEY, 148 | appSecret: APP_SECRET, 149 | apis: ['auth'], 150 | warn: function (err) { 151 | assert.equal(err, '[OVH] Your call /auth/not-found was not found in the API schemas.'); 152 | done(); 153 | } 154 | }); 155 | 156 | rest.requestPromised('GET', '/auth/not-found') 157 | .then(function (resp) { 158 | assert.ok(!resp); 159 | }) 160 | .catch(function (err) { 161 | assert.ok(err); 162 | }); 163 | }, 164 | 'Check api not found warning': function (done) { 165 | 'use strict'; 166 | 167 | nock('https://eu.api.ovh.com') 168 | .intercept('/1.0/auth/time', 'GET') 169 | .reply(200, Math.round(Date.now() / 1000)) 170 | .intercept('/1.0/auth', 'GET') 171 | .reply(404) 172 | .intercept('/1.0/auth.json', 'GET') 173 | .reply(200, bad_api); 174 | 175 | var warn = 0; 176 | var rest = ovh({ 177 | appKey: APP_KEY, 178 | appSecret: APP_SECRET, 179 | apis: ['auth'], 180 | warn: function (err) { 181 | assert.ok(err.indexOf('was not found in the API schemas.') > 0); 182 | if (++warn === 2) { 183 | done(); 184 | } 185 | } 186 | }); 187 | 188 | rest.request('GET', '/auth', function (err) { 189 | assert.ok(err); 190 | }); 191 | }, 192 | 'Check api not found warning [promised]': function (done) { 193 | 'use strict'; 194 | 195 | nock('https://eu.api.ovh.com') 196 | .intercept('/1.0/auth/time', 'GET') 197 | .reply(200, Math.round(Date.now() / 1000)) 198 | .intercept('/1.0/auth', 'GET') 199 | .reply(404) 200 | .intercept('/1.0/auth.json', 'GET') 201 | .reply(200, bad_api); 202 | 203 | var warn = 0; 204 | var rest = ovh({ 205 | appKey: APP_KEY, 206 | appSecret: APP_SECRET, 207 | apis: ['auth'], 208 | warn: function (err) { 209 | assert.ok(err.indexOf('was not found in the API schemas.') > 0); 210 | if (++warn === 2) { 211 | done(); 212 | } 213 | } 214 | }); 215 | 216 | rest.requestPromised('GET', '/auth') 217 | .then(function (resp) { 218 | assert.ok(!resp); 219 | }) 220 | .catch(function (err) { 221 | assert.ok(err); 222 | }); 223 | }, 224 | 'Check HTTP method not found warning': function (done) { 225 | 'use strict'; 226 | 227 | nock('https://eu.api.ovh.com') 228 | .intercept('/1.0/auth/time', 'GET') 229 | .reply(200, Math.round(Date.now() / 1000)) 230 | .intercept('/1.0/auth.json', 'GET') 231 | .reply(200, auth_api) 232 | .intercept('/1.0/me.json', 'GET') 233 | .reply(200, me_api) 234 | .intercept('/1.0/me', 'OVH') 235 | .reply(405); 236 | 237 | var rest = ovh({ 238 | appKey: APP_KEY, 239 | appSecret: APP_SECRET, 240 | apis: ['me'], 241 | warn: function (err) { 242 | assert.equal(err, '[OVH] The method OVH for the API call /me was not found in the API schemas.'); 243 | done(); 244 | } 245 | }); 246 | 247 | rest.request('OVH', '/me', function (err) { 248 | assert.ok(err); 249 | }); 250 | }, 251 | 'Check HTTP method not found warning [promised]': function (done) { 252 | 'use strict'; 253 | 254 | nock('https://eu.api.ovh.com') 255 | .intercept('/1.0/auth/time', 'GET') 256 | .reply(200, Math.round(Date.now() / 1000)) 257 | .intercept('/1.0/auth.json', 'GET') 258 | .reply(200, auth_api) 259 | .intercept('/1.0/me.json', 'GET') 260 | .reply(200, me_api) 261 | .intercept('/1.0/me', 'OVH') 262 | .reply(405); 263 | 264 | var rest = ovh({ 265 | appKey: APP_KEY, 266 | appSecret: APP_SECRET, 267 | apis: ['me'], 268 | warn: function (err) { 269 | assert.equal(err, '[OVH] The method OVH for the API call /me was not found in the API schemas.'); 270 | done(); 271 | } 272 | }); 273 | 274 | rest.requestPromised('OVH', '/me') 275 | .then(function (resp) { 276 | assert.ok(!resp); 277 | }) 278 | .catch(function (err) { 279 | assert.ok(err); 280 | }); 281 | }, 282 | 'Call method without CK': function (done) { 283 | 'use strict'; 284 | 285 | nock('https://eu.api.ovh.com') 286 | .intercept('/1.0/auth/time', 'GET') 287 | .reply(200, Math.round(Date.now() / 1000)) 288 | .intercept('/1.0/auth.json', 'GET') 289 | .reply(200, auth_api) 290 | .intercept('/1.0/me.json', 'GET') 291 | .reply(200, me_api) 292 | .intercept('/1.0/me', 'GET') 293 | .reply(403); 294 | 295 | var rest = ovh({ 296 | appKey: APP_KEY, 297 | appSecret: APP_SECRET, 298 | apis: ['me'], 299 | warn: function (err) { 300 | assert.equal(err, '[OVH] The API call /me requires an authentication with a consumer key.'); 301 | done(); 302 | } 303 | }); 304 | 305 | rest.request('GET', '/me', function (err) { 306 | assert.ok(err); 307 | }); 308 | }, 309 | 'Call method without CK [promised]': function (done) { 310 | 'use strict'; 311 | 312 | nock('https://eu.api.ovh.com') 313 | .intercept('/1.0/auth/time', 'GET') 314 | .reply(200, Math.round(Date.now() / 1000)) 315 | .intercept('/1.0/auth.json', 'GET') 316 | .reply(200, auth_api) 317 | .intercept('/1.0/me.json', 'GET') 318 | .reply(200, me_api) 319 | .intercept('/1.0/me', 'GET') 320 | .reply(403); 321 | 322 | var rest = ovh({ 323 | appKey: APP_KEY, 324 | appSecret: APP_SECRET, 325 | apis: ['me'], 326 | warn: function (err) { 327 | assert.equal(err, '[OVH] The API call /me requires an authentication with a consumer key.'); 328 | done(); 329 | } 330 | }); 331 | 332 | rest.requestPromised('GET', '/me') 333 | .then(function (resp) { 334 | assert.ok(!resp); 335 | }) 336 | .catch(function (err) { 337 | assert.ok(err); 338 | }); 339 | }, 340 | 'Unable to load schema': function (done) { 341 | 'use strict'; 342 | 343 | nock('https://eu.api.ovh.com') 344 | .intercept('/1.0/auth/time', 'GET') 345 | .reply(200, Math.round(Date.now() / 1000)) 346 | .intercept('/1.0/auth.json', 'GET') 347 | .reply(200, auth_api) 348 | .intercept('/1.0/meh.json', 'GET') 349 | .reply(404); 350 | 351 | var rest = ovh({ 352 | appKey: APP_KEY, 353 | appSecret: APP_SECRET, 354 | apis: ['meh'], 355 | }); 356 | 357 | rest.request('GET', '/me', function (err) { 358 | assert.equal(err, '[OVH] Unable to load schema /1.0/meh.json, HTTP response code: 404'); 359 | done(); 360 | }); 361 | }, 362 | 'Unable to load schema [promised]': function (done) { 363 | 'use strict'; 364 | 365 | nock('https://eu.api.ovh.com') 366 | .intercept('/1.0/auth/time', 'GET') 367 | .reply(200, Math.round(Date.now() / 1000)) 368 | .intercept('/1.0/auth.json', 'GET') 369 | .reply(200, auth_api) 370 | .intercept('/1.0/meh.json', 'GET') 371 | .reply(404); 372 | 373 | var rest = ovh({ 374 | appKey: APP_KEY, 375 | appSecret: APP_SECRET, 376 | apis: ['meh'], 377 | }); 378 | 379 | rest.requestPromised('GET', '/me') 380 | .then(function (resp) { 381 | assert.ok(!resp); 382 | }) 383 | .catch(function (err) { 384 | assert.equal(err.error, '[OVH] Unable to load schema /1.0/meh.json, HTTP response code: 404'); 385 | }) 386 | .finally(done); 387 | }, 388 | 'Unable to parse schema': function (done) { 389 | 'use strict'; 390 | 391 | nock('https://eu.api.ovh.com') 392 | .intercept('/1.0/auth/time', 'GET') 393 | .reply(200, Math.round(Date.now() / 1000)) 394 | .intercept('/1.0/auth.json', 'GET') 395 | .reply(200, auth_api) 396 | .intercept('/1.0/me.json', 'GET') 397 | .reply(200, '{"'); 398 | 399 | var rest = ovh({ 400 | appKey: APP_KEY, 401 | appSecret: APP_SECRET, 402 | apis: ['me'], 403 | }); 404 | 405 | rest.request('GET', '/me', function (err) { 406 | assert.equal(err, '[OVH] Unable to parse the schema: /1.0/me.json'); 407 | done(); 408 | }); 409 | }, 410 | 'Unable to parse schema [promised]': function (done) { 411 | 'use strict'; 412 | 413 | nock('https://eu.api.ovh.com') 414 | .intercept('/1.0/auth/time', 'GET') 415 | .reply(200, Math.round(Date.now() / 1000)) 416 | .intercept('/1.0/auth.json', 'GET') 417 | .reply(200, auth_api) 418 | .intercept('/1.0/me.json', 'GET') 419 | .reply(200, '{"'); 420 | 421 | var rest = ovh({ 422 | appKey: APP_KEY, 423 | appSecret: APP_SECRET, 424 | apis: ['me'], 425 | }); 426 | 427 | rest.requestPromised('GET', '/me') 428 | .then(function (resp) { 429 | assert.ok(!resp); 430 | }) 431 | .catch(function (err) { 432 | assert.equal(err.error, '[OVH] Unable to parse the schema: /1.0/me.json'); 433 | }) 434 | .finally(done); 435 | }, 436 | 'Unable to fetch time': function (done) { 437 | 'use strict'; 438 | nock('https://ca.api.ovh.com') 439 | .intercept('/1.0/auth/time', 'GET') 440 | .reply(500) 441 | .intercept('/1.0/auth.json', 'GET') 442 | .reply(200, auth_api); 443 | 444 | var rest = ovh({ 445 | appKey: APP_KEY, 446 | appSecret: APP_SECRET, 447 | endpoint: 'ovh-ca' 448 | }); 449 | 450 | rest.request('GET', '/me', function (err) { 451 | assert.equal(err, '[OVH] Unable to fetch OVH API time'); 452 | done(); 453 | }); 454 | }, 455 | 'Unable to fetch time [promised]': function (done) { 456 | 'use strict'; 457 | nock('https://ca.api.ovh.com') 458 | .intercept('/1.0/auth/time', 'GET') 459 | .reply(500) 460 | .intercept('/1.0/auth.json', 'GET') 461 | .reply(200, auth_api); 462 | 463 | var rest = ovh({ 464 | appKey: APP_KEY, 465 | appSecret: APP_SECRET, 466 | endpoint: 'ovh-ca' 467 | }); 468 | 469 | rest.requestPromised('GET', '/me') 470 | .then(function (resp) { 471 | assert.ok(!resp); 472 | }) 473 | .catch(function (err) { 474 | assert.equal(err.error, '[OVH] Unable to fetch OVH API time'); 475 | }) 476 | .finally(done); 477 | }, 478 | 'Fetch v1 endpoint': function (done) { 479 | 'use strict'; 480 | nock('https://ca.api.ovh.com') 481 | .intercept('/v1/call', 'GET') 482 | .reply(200, {}); 483 | 484 | var rest = ovh({ 485 | appKey: APP_KEY, 486 | appSecret: APP_SECRET, 487 | endpoint: 'ovh-ca' 488 | }); 489 | 490 | assert.equal(rest.basePath, '/1.0'); 491 | rest.requestPromised('GET', '/v1/call') 492 | .then(function (resp) { 493 | assert.ok(!resp); 494 | }) 495 | .finally(done); 496 | }, 497 | 'Fetch v2 endpoint': function (done) { 498 | 'use strict'; 499 | nock('https://ca.api.ovh.com') 500 | .intercept('/v2/call', 'GET') 501 | .reply(200, {}); 502 | 503 | var rest = ovh({ 504 | appKey: APP_KEY, 505 | appSecret: APP_SECRET, 506 | endpoint: 'ovh-ca' 507 | }); 508 | 509 | assert.equal(rest.basePath, '/1.0'); 510 | rest.requestPromised('GET', '/v2/call') 511 | .then(function (resp) { 512 | assert.ok(!resp); 513 | }) 514 | .finally(done); 515 | }, 516 | }; 517 | -------------------------------------------------------------------------------- /lib/ovh.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 'use strict'; 28 | 29 | let https = require('https'), 30 | Bluebird = require('bluebird'), 31 | querystring = require('querystring'), 32 | crypto = require('crypto'), 33 | async = require('async'), 34 | endpoints = require('./endpoints'); 35 | 36 | const { ClientCredentials} = require('simple-oauth2'); 37 | 38 | class Ovh { 39 | constructor(params) { 40 | this.endpoint = params.endpoint || null; 41 | // Preconfigured API endpoints 42 | if (this.endpoint) { 43 | if (!endpoints[this.endpoint]) { 44 | throw new Error('[OVH] Unknown API ' + this.endpoint); 45 | } 46 | 47 | for (let key in endpoints[this.endpoint]) { 48 | if (endpoints[this.endpoint].hasOwnProperty(key)) { 49 | params[key] = endpoints[this.endpoint][key]; 50 | } 51 | } 52 | } 53 | 54 | //OAuth2 55 | if (params.clientID || params.clientSecret) { 56 | if (typeof(params.clientID) !== 'string' || typeof(params.clientSecret) !== 'string') { 57 | throw new Error('[OVH] Both clientID and clientSecret must be given'); 58 | } 59 | if (!params.tokenURL) { 60 | throw new Error('[OVH] Endpoint does not support OAuth2 authentication'); 61 | } 62 | this.oauthConfig = { 63 | client: { 64 | id: params.clientID, 65 | secret: params.clientSecret 66 | }, 67 | auth: { 68 | tokenHost: params.tokenURL, 69 | tokenPath: "oauth2/token" 70 | } 71 | }; 72 | this.oauthClient = new ClientCredentials(this.oauthConfig); 73 | } 74 | 75 | // Legacy Application Key authorization 76 | if (params.appKey || params.appSecret) { 77 | this.appKey = params.appKey; 78 | this.appSecret = params.appSecret; 79 | this.consumerKey = params.consumerKey || null; 80 | 81 | if (typeof(this.appKey) !== 'string' || 82 | typeof(this.appSecret) !== 'string') { 83 | throw new Error('[OVH] Both application key and application secret must be given'); 84 | } 85 | } 86 | 87 | if (this.appKey && this.oauthConfig) { 88 | throw new Error('[OVH] Cannot use both applicationKey/applicationSecret and OAuth2'); 89 | } 90 | 91 | if (!this.appKey && !this.oauthConfig) { 92 | throw new Error('[OVH] Missing authentication. You must provide applicationKey/applicationSecret or OAuth2 clientID/clientSecret'); 93 | } 94 | 95 | this.timeout = params.timeout; 96 | this.apiTimeDiff = params.apiTimeDiff || null; 97 | 98 | // Custom configuration of the API endpoint 99 | this.host = params.host || 'eu.api.ovh.com'; 100 | this.port = params.port || 443; 101 | this.basePath = params.basePath || '/1.0'; 102 | 103 | // Declared used API, will be used to check the associated schema 104 | this.usedApi = params.apis || []; 105 | if (Array.isArray(this.usedApi) && this.usedApi.length > 0 && this.usedApi.indexOf('auth') < 0) { 106 | this.usedApi.push('auth'); 107 | } 108 | 109 | // Get warnings from the modules (e.g. deprecated API method) 110 | this.warn = params.warn || console.log; 111 | this.debug = params.debug || false; 112 | 113 | // istanbul ignore next 114 | if (this.debug && typeof(this.debug) !== 'function') { 115 | this.debug = console.log; 116 | } 117 | 118 | this.apis = { _path: '' }; 119 | this.apisLoaded = !this.usedApi.length; 120 | } 121 | 122 | /** 123 | * Returns the endpoint's full path, if the endpoint's path starts 124 | * with /v1 or /v2, remove the trailing '/1.0' from the basePath 125 | */ 126 | getFullPath(path) { 127 | if ((this.basePath || '').endsWith('/1.0') && /^\/v(1|2)/.test(path)) { 128 | return `${this.basePath.slice(0, -4)}${path}`; 129 | } 130 | return `${this.basePath}${path}`; 131 | } 132 | 133 | /** 134 | * Recursively loads the schemas of the specified used APIs. 135 | * 136 | * @param {String} path 137 | * @param {Function} callback 138 | */ 139 | loadSchemas(path, callback) { 140 | let request = { 141 | host: this.host, 142 | port: this.port, 143 | path: this.getFullPath(path) 144 | }; 145 | 146 | // Fetch only selected APIs 147 | if (path === '/') { 148 | return async.each(this.usedApi, (apiName, callback) => { 149 | this.loadSchemas('/' + apiName + '.json', callback); 150 | }, callback); 151 | } 152 | 153 | // Fetch all APIs 154 | this.loadSchemasRequest(request, (err, schema) => { 155 | if (err) { 156 | return callback(err, path); 157 | } 158 | 159 | async.each( 160 | schema.apis, (api, callback) => { 161 | let apiPath = api.path.split('/'); 162 | 163 | this.addApi(apiPath, api, this.apis); 164 | callback(null); 165 | }, callback); 166 | }); 167 | } 168 | 169 | 170 | /** 171 | * Add a fetched schema to the loaded API list 172 | * 173 | * @param {Array} apiPath: Splited API path using '/' 174 | * @param {String} api: API Name 175 | * @param {Function} callback 176 | */ 177 | addApi(apiPath, api, apis) { 178 | let path = apiPath.shift(); 179 | if (path === '') { 180 | return this.addApi(apiPath, api, apis); 181 | } 182 | 183 | if (apis[path] == null) { 184 | apis[path] = { _path: apis._path + '/' + path }; 185 | } 186 | 187 | if (apiPath.length > 0) { 188 | return this.addApi(apiPath, api, apis[path]); 189 | } 190 | 191 | apis[path]._api = api; 192 | } 193 | 194 | /** 195 | * Fetch an API schema 196 | * 197 | * @param {Object} options: HTTP request options 198 | * @param {Function} callback 199 | */ 200 | loadSchemasRequest(options, callback) { 201 | https.get(options, (res) => { 202 | let body = ''; 203 | 204 | res.on('data', (chunk) => body += chunk) 205 | .on('end', function () { 206 | try { 207 | body = JSON.parse(body); 208 | } catch (e) { 209 | if (res.statusCode !== 200) { 210 | return callback( 211 | '[OVH] Unable to load schema ' + options.path + 212 | ', HTTP response code: ' + res.statusCode, res.statusCode); 213 | } 214 | else { 215 | return callback('[OVH] Unable to parse the schema: ' + options.path); 216 | } 217 | } 218 | 219 | return callback(null, body); 220 | }); 221 | }) 222 | .on('error', /* istanbul ignore next */ (err) => callback('[OVH] Unable to fetch the schemas: ' + err)); 223 | } 224 | 225 | /** 226 | * Generates warns from the loaded API schema when processing a request 227 | * 228 | * A warn is generated when the API schema is loaded and: 229 | * - The API method does not exists 230 | * - The API method is not available with the provided httpMethod 231 | * - The API method is tagged as deprecated in the schema 232 | * 233 | * The function called can be customzied by providing a function using the 234 | * 'warn' parameter when instancing the module. Default function used is 235 | * 'console.warn'. 236 | * 237 | * @param {String} httpMethod 238 | * @param {String} pathStr 239 | */ 240 | warnsRequest(httpMethod, pathStr) { 241 | let path = pathStr.split('/'), 242 | api = this.apis; 243 | 244 | while (path.length > 0) { 245 | let pElem = path.shift(); 246 | if (pElem === '') { 247 | continue; 248 | } 249 | 250 | if (api[pElem] != null) { 251 | api = api[pElem]; 252 | continue; 253 | } 254 | 255 | let keys = Object.keys(api); 256 | for (let i = 0 ; i < keys.length ; ++i) { 257 | if (keys[i].charAt(0) === '{') { 258 | api = api[keys[i]]; 259 | keys = null; 260 | break; 261 | } 262 | } 263 | 264 | if (keys) { 265 | return this.warn( 266 | '[OVH] Your call ' + pathStr + ' was not found in the API schemas.' 267 | ); 268 | } 269 | } 270 | 271 | if (!api._api || !api._api.operations) { 272 | return this.warn( 273 | '[OVH] Your call ' + pathStr + ' was not found in the API schemas.' 274 | ); 275 | } 276 | 277 | for (let i = 0 ; i < api._api.operations.length ; ++i) { 278 | if (api._api.operations[i].httpMethod === httpMethod) { 279 | if (api._api.operations[i].apiStatus.value === 'DEPRECATED') { 280 | let status = api._api.operations[i].apiStatus; 281 | return this.warn( 282 | '[OVH] Your API call ' + pathStr + ' is tagged DEPRECATED since ' + 283 | status.deprecatedDate + 284 | ' and will deleted on ' + status.deletionDate, 285 | '. You can replace it with ' + status.replacement 286 | ); 287 | } 288 | 289 | if (typeof(this.consumerKey) !== 'string' && 290 | !api._api.operations[i].noAuthentication) { 291 | return this.warn( 292 | '[OVH] The API call ' + pathStr + ' requires an authentication' + 293 | ' with a consumer key.' 294 | ); 295 | } 296 | 297 | return true; 298 | } 299 | } 300 | 301 | return this.warn( 302 | '[OVH] The method ' + httpMethod + ' for the API call ' + 303 | pathStr + ' was not found in the API schemas.' 304 | ); 305 | } 306 | 307 | /** 308 | * Execute a request on the API 309 | * 310 | * @param {String} httpMethod: The HTTP method 311 | * @param {String} path: The request path 312 | * @param {Object} params: The request parameters (passed as query string or 313 | * body params) 314 | * @param {Function} callback 315 | * @param {Object} refer: The parent proxied object 316 | */ 317 | async request(httpMethod, path, params, callback, refer) { 318 | if (callback == null) { 319 | callback = params; 320 | } 321 | 322 | // Schemas 323 | if (!this.apisLoaded) { 324 | return this.loadSchemas('/', (err) => { 325 | if (err) { 326 | return callback(err); 327 | } 328 | 329 | this.apisLoaded = true; 330 | return this.request(httpMethod, path, params, callback, refer); 331 | }); 332 | } 333 | 334 | // Time drift 335 | if (!this.oauthConfig && this.apiTimeDiff === null && path !== '/auth/time') { 336 | return this.request('GET', '/auth/time', {}, (err, time) => { 337 | if (err) { 338 | return callback('[OVH] Unable to fetch OVH API time'); 339 | } 340 | 341 | this.apiTimeDiff = time - Math.round(Date.now() / 1000); 342 | return this.request(httpMethod, path, params, callback, refer); 343 | }, refer); 344 | } 345 | 346 | // Potential warnings 347 | if (Object.keys(this.apis).length > 1) { 348 | this.warnsRequest(httpMethod, path); 349 | } 350 | 351 | // Replace "{str}", used for $call() 352 | if (path.indexOf('{') >= 0) { 353 | let newPath = path; 354 | 355 | for (let paramKey in params) { 356 | if (params.hasOwnProperty(paramKey)) { 357 | newPath = path.replace('{' + paramKey + '}', params[paramKey]); 358 | 359 | // Remove from body parameters 360 | if (newPath !== path) { 361 | delete params[paramKey]; 362 | } 363 | 364 | path = newPath; 365 | } 366 | } 367 | } 368 | 369 | let options = { 370 | host: this.host, 371 | port: this.port, 372 | method: httpMethod, 373 | path: this.getFullPath(path), 374 | headers: {} 375 | }; 376 | 377 | if (!this.oauthConfig) { 378 | options.headers = { 379 | 'Content-Type': 'application/json', 380 | 'X-Ovh-Application': this.appKey, 381 | }; 382 | } 383 | 384 | 385 | // Remove undefined values 386 | for (let k in params) { 387 | if (params.hasOwnProperty(k) && params[k] == null) { 388 | delete params[k]; 389 | } 390 | } 391 | 392 | let reqBody = null; 393 | if (typeof(params) === 'object' && Object.keys(params).length > 0) { 394 | if (httpMethod === 'PUT' || httpMethod === 'POST') { 395 | // Escape unicode 396 | reqBody = JSON.stringify(params).replace(/[\u0080-\uFFFF]/g, (m) => { 397 | return '\\u' + ('0000' + m.charCodeAt(0).toString(16)).slice(-4); 398 | }); 399 | options.headers['Content-Length'] = reqBody.length; 400 | } 401 | else { 402 | options.path += '?' + querystring.stringify(params); 403 | } 404 | } 405 | 406 | if (this.oauthConfig && (!this.accessToken || this.accessToken.expired(10))) { 407 | try { 408 | this.accessToken = await this.oauthClient.getToken({scope: "all"}); 409 | } catch (e) { 410 | return callback({ 411 | statusCode: e.output && e.output.statusCode, 412 | error: e.output && e.output.payload.error, 413 | message: e.data.payload 414 | }); 415 | } 416 | } 417 | 418 | if (this.accessToken) { 419 | options.headers.Authorization = `Bearer ${this.accessToken.token.access_token}`; 420 | } 421 | 422 | if (path.indexOf('/auth') < 0 && !this.accessToken) { 423 | options.headers['X-Ovh-Timestamp'] = Math.round(Date.now() / 1000) + this.apiTimeDiff; 424 | 425 | // Sign request 426 | if (typeof(this.consumerKey) === 'string') { 427 | options.headers['X-Ovh-Consumer'] = this.consumerKey; 428 | options.headers['X-Ovh-Signature'] = this.signRequest( 429 | httpMethod, 'https://' + options.host + options.path, 430 | reqBody, options.headers['X-Ovh-Timestamp'] 431 | ); 432 | } 433 | } 434 | 435 | if (this.debug) { 436 | this.debug( 437 | '[OVH] API call:', 438 | options.method, options.path, 439 | reqBody || '' 440 | ); 441 | } 442 | 443 | let req = https.request(options, (res) => { 444 | let body = ''; 445 | 446 | res.on('data', (chunk) => body += chunk); 447 | 448 | res.on('end', () => { 449 | let response; 450 | 451 | if (body.length > 0) { 452 | try { 453 | response = JSON.parse(body); 454 | } 455 | catch (e) { 456 | return callback('[OVH] Unable to parse JSON reponse'); 457 | } 458 | } 459 | else { 460 | response = null; 461 | } 462 | 463 | if (this.debug) { 464 | this.debug( 465 | '[OVH] API response to', 466 | options.method, options.path, ':', 467 | body 468 | ); 469 | } 470 | 471 | if (res.statusCode > 299 || res.statusCode < 200) { 472 | callback(res.statusCode, response ? response.message : response); 473 | } 474 | else { 475 | // Return a proxy (for potential next request) 476 | if (refer != null) { 477 | this.proxyResponseREST(response, refer, callback); 478 | } 479 | else { 480 | callback(null, response); 481 | } 482 | } 483 | }); 484 | }); 485 | 486 | // istanbul ignore next 487 | req.on('error', (e) => callback(e.errno || e)); 488 | 489 | // istanbul ignore next 490 | // mocked socket has no setTimeout 491 | if (typeof(this.timeout) === 'number') { 492 | req.on('socket', (socket) => { 493 | socket.setTimeout(this.timeout); 494 | if (socket._events.timeout != null) { 495 | socket.on('timeout', () => req.abort()); 496 | } 497 | }); 498 | } 499 | 500 | if (reqBody != null) { 501 | req.write(reqBody); 502 | } 503 | 504 | req.end(); 505 | } 506 | 507 | /** 508 | * Execute a request on the API with promise 509 | * 510 | * @param {String} httpMethod: The HTTP method 511 | * @param {String} path: The request path 512 | * @param {Object} params: The request parameters (passed as query string or 513 | * body params) 514 | */ 515 | requestPromised(httpMethod, path, params) { 516 | return new Bluebird((resolve, reject) => { 517 | return this.request(httpMethod, path, params, (error, resp) => { 518 | if (error) { 519 | return reject({error, message: resp}); 520 | } 521 | 522 | return resolve(resp); 523 | }); 524 | }); 525 | } 526 | 527 | /** 528 | * Signs an API request 529 | * 530 | * @param {String} httpMethod 531 | * @param {String} url 532 | * @param {String} body 533 | * @param {Number|String} timestamp 534 | * @return {String} The signature 535 | */ 536 | signRequest(httpMethod, url, body, timestamp) { 537 | let s = [ 538 | this.appSecret, 539 | this.consumerKey, 540 | httpMethod, 541 | url, 542 | body || '', 543 | timestamp 544 | ]; 545 | 546 | return '$1$' + crypto.createHash('sha1').update(s.join('+')).digest('hex'); 547 | } 548 | 549 | } 550 | 551 | module.exports = function (params) { 552 | return new Ovh(params || {}); 553 | }; 554 | -------------------------------------------------------------------------------- /lib/ovh.es5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 - 2025 OVH SAS 3 | * Copyright (c) 2012 - 2013 Vincent Giersch 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the name of OVH and or its trademarks 24 | * shall not be used in advertising or otherwise to promote the sale, use or 25 | * other dealings in this Software without prior written authorization from OVH. 26 | */ 27 | 'use strict'; 28 | 29 | function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; } 30 | function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 31 | function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } 32 | function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } 33 | function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 34 | function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 35 | function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 36 | function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 37 | function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 38 | var https = require('https'), 39 | Bluebird = require('bluebird'), 40 | querystring = require('querystring'), 41 | crypto = require('crypto'), 42 | async = require('async'), 43 | endpoints = require('./endpoints'); 44 | var _require = require('simple-oauth2'), 45 | ClientCredentials = _require.ClientCredentials; 46 | var Ovh = /*#__PURE__*/function () { 47 | function Ovh(params) { 48 | _classCallCheck(this, Ovh); 49 | this.endpoint = params.endpoint || null; 50 | // Preconfigured API endpoints 51 | if (this.endpoint) { 52 | if (!endpoints[this.endpoint]) { 53 | throw new Error('[OVH] Unknown API ' + this.endpoint); 54 | } 55 | for (var key in endpoints[this.endpoint]) { 56 | if (endpoints[this.endpoint].hasOwnProperty(key)) { 57 | params[key] = endpoints[this.endpoint][key]; 58 | } 59 | } 60 | } 61 | 62 | //OAuth2 63 | if (params.clientID || params.clientSecret) { 64 | if (typeof params.clientID !== 'string' || typeof params.clientSecret !== 'string') { 65 | throw new Error('[OVH] Both clientID and clientSecret must be given'); 66 | } 67 | if (!params.tokenURL) { 68 | throw new Error('[OVH] Endpoint does not support OAuth2 authentication'); 69 | } 70 | this.oauthConfig = { 71 | client: { 72 | id: params.clientID, 73 | secret: params.clientSecret 74 | }, 75 | auth: { 76 | tokenHost: params.tokenURL, 77 | tokenPath: "oauth2/token" 78 | } 79 | }; 80 | this.oauthClient = new ClientCredentials(this.oauthConfig); 81 | } 82 | 83 | // Legacy Application Key authorization 84 | if (params.appKey || params.appSecret) { 85 | this.appKey = params.appKey; 86 | this.appSecret = params.appSecret; 87 | this.consumerKey = params.consumerKey || null; 88 | if (typeof this.appKey !== 'string' || typeof this.appSecret !== 'string') { 89 | throw new Error('[OVH] Both application key and application secret must be given'); 90 | } 91 | } 92 | if (this.appKey && this.oauthConfig) { 93 | throw new Error('[OVH] Cannot use both applicationKey/applicationSecret and OAuth2'); 94 | } 95 | if (!this.appKey && !this.oauthConfig) { 96 | throw new Error('[OVH] Missing authentication. You must provide applicationKey/applicationSecret or OAuth2 clientID/clientSecret'); 97 | } 98 | this.timeout = params.timeout; 99 | this.apiTimeDiff = params.apiTimeDiff || null; 100 | 101 | // Custom configuration of the API endpoint 102 | this.host = params.host || 'eu.api.ovh.com'; 103 | this.port = params.port || 443; 104 | this.basePath = params.basePath || '/1.0'; 105 | 106 | // Declared used API, will be used to check the associated schema 107 | this.usedApi = params.apis || []; 108 | if (Array.isArray(this.usedApi) && this.usedApi.length > 0 && this.usedApi.indexOf('auth') < 0) { 109 | this.usedApi.push('auth'); 110 | } 111 | 112 | // Get warnings from the modules (e.g. deprecated API method) 113 | this.warn = params.warn || console.log; 114 | this.debug = params.debug || false; 115 | 116 | // istanbul ignore next 117 | if (this.debug && typeof this.debug !== 'function') { 118 | this.debug = console.log; 119 | } 120 | this.apis = { 121 | _path: '' 122 | }; 123 | this.apisLoaded = !this.usedApi.length; 124 | } 125 | 126 | /** 127 | * Returns the endpoint's full path, if the endpoint's path starts 128 | * with /v1 or /v2, remove the trailing '/1.0' from the basePath 129 | */ 130 | return _createClass(Ovh, [{ 131 | key: "getFullPath", 132 | value: function getFullPath(path) { 133 | if ((this.basePath || '').endsWith('/1.0') && /^\/v(1|2)/.test(path)) { 134 | return "".concat(this.basePath.slice(0, -4)).concat(path); 135 | } 136 | return "".concat(this.basePath).concat(path); 137 | } 138 | 139 | /** 140 | * Recursively loads the schemas of the specified used APIs. 141 | * 142 | * @param {String} path 143 | * @param {Function} callback 144 | */ 145 | }, { 146 | key: "loadSchemas", 147 | value: function loadSchemas(path, callback) { 148 | var _this = this; 149 | var request = { 150 | host: this.host, 151 | port: this.port, 152 | path: this.getFullPath(path) 153 | }; 154 | 155 | // Fetch only selected APIs 156 | if (path === '/') { 157 | return async.each(this.usedApi, function (apiName, callback) { 158 | _this.loadSchemas('/' + apiName + '.json', callback); 159 | }, callback); 160 | } 161 | 162 | // Fetch all APIs 163 | this.loadSchemasRequest(request, function (err, schema) { 164 | if (err) { 165 | return callback(err, path); 166 | } 167 | async.each(schema.apis, function (api, callback) { 168 | var apiPath = api.path.split('/'); 169 | _this.addApi(apiPath, api, _this.apis); 170 | callback(null); 171 | }, callback); 172 | }); 173 | } 174 | 175 | /** 176 | * Add a fetched schema to the loaded API list 177 | * 178 | * @param {Array} apiPath: Splited API path using '/' 179 | * @param {String} api: API Name 180 | * @param {Function} callback 181 | */ 182 | }, { 183 | key: "addApi", 184 | value: function addApi(apiPath, api, apis) { 185 | var path = apiPath.shift(); 186 | if (path === '') { 187 | return this.addApi(apiPath, api, apis); 188 | } 189 | if (apis[path] == null) { 190 | apis[path] = { 191 | _path: apis._path + '/' + path 192 | }; 193 | } 194 | if (apiPath.length > 0) { 195 | return this.addApi(apiPath, api, apis[path]); 196 | } 197 | apis[path]._api = api; 198 | } 199 | 200 | /** 201 | * Fetch an API schema 202 | * 203 | * @param {Object} options: HTTP request options 204 | * @param {Function} callback 205 | */ 206 | }, { 207 | key: "loadSchemasRequest", 208 | value: function loadSchemasRequest(options, callback) { 209 | https.get(options, function (res) { 210 | var body = ''; 211 | res.on('data', function (chunk) { 212 | return body += chunk; 213 | }).on('end', function () { 214 | try { 215 | body = JSON.parse(body); 216 | } catch (e) { 217 | if (res.statusCode !== 200) { 218 | return callback('[OVH] Unable to load schema ' + options.path + ', HTTP response code: ' + res.statusCode, res.statusCode); 219 | } else { 220 | return callback('[OVH] Unable to parse the schema: ' + options.path); 221 | } 222 | } 223 | return callback(null, body); 224 | }); 225 | }).on('error', /* istanbul ignore next */function (err) { 226 | return callback('[OVH] Unable to fetch the schemas: ' + err); 227 | }); 228 | } 229 | 230 | /** 231 | * Generates warns from the loaded API schema when processing a request 232 | * 233 | * A warn is generated when the API schema is loaded and: 234 | * - The API method does not exists 235 | * - The API method is not available with the provided httpMethod 236 | * - The API method is tagged as deprecated in the schema 237 | * 238 | * The function called can be customzied by providing a function using the 239 | * 'warn' parameter when instancing the module. Default function used is 240 | * 'console.warn'. 241 | * 242 | * @param {String} httpMethod 243 | * @param {String} pathStr 244 | */ 245 | }, { 246 | key: "warnsRequest", 247 | value: function warnsRequest(httpMethod, pathStr) { 248 | var path = pathStr.split('/'), 249 | api = this.apis; 250 | while (path.length > 0) { 251 | var pElem = path.shift(); 252 | if (pElem === '') { 253 | continue; 254 | } 255 | if (api[pElem] != null) { 256 | api = api[pElem]; 257 | continue; 258 | } 259 | var keys = Object.keys(api); 260 | for (var i = 0; i < keys.length; ++i) { 261 | if (keys[i].charAt(0) === '{') { 262 | api = api[keys[i]]; 263 | keys = null; 264 | break; 265 | } 266 | } 267 | if (keys) { 268 | return this.warn('[OVH] Your call ' + pathStr + ' was not found in the API schemas.'); 269 | } 270 | } 271 | if (!api._api || !api._api.operations) { 272 | return this.warn('[OVH] Your call ' + pathStr + ' was not found in the API schemas.'); 273 | } 274 | for (var _i = 0; _i < api._api.operations.length; ++_i) { 275 | if (api._api.operations[_i].httpMethod === httpMethod) { 276 | if (api._api.operations[_i].apiStatus.value === 'DEPRECATED') { 277 | var status = api._api.operations[_i].apiStatus; 278 | return this.warn('[OVH] Your API call ' + pathStr + ' is tagged DEPRECATED since ' + status.deprecatedDate + ' and will deleted on ' + status.deletionDate, '. You can replace it with ' + status.replacement); 279 | } 280 | if (typeof this.consumerKey !== 'string' && !api._api.operations[_i].noAuthentication) { 281 | return this.warn('[OVH] The API call ' + pathStr + ' requires an authentication' + ' with a consumer key.'); 282 | } 283 | return true; 284 | } 285 | } 286 | return this.warn('[OVH] The method ' + httpMethod + ' for the API call ' + pathStr + ' was not found in the API schemas.'); 287 | } 288 | 289 | /** 290 | * Execute a request on the API 291 | * 292 | * @param {String} httpMethod: The HTTP method 293 | * @param {String} path: The request path 294 | * @param {Object} params: The request parameters (passed as query string or 295 | * body params) 296 | * @param {Function} callback 297 | * @param {Object} refer: The parent proxied object 298 | */ 299 | }, { 300 | key: "request", 301 | value: (function () { 302 | var _request = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(httpMethod, path, params, callback, refer) { 303 | var _this2 = this; 304 | var newPath, paramKey, options, k, reqBody, req; 305 | return _regeneratorRuntime().wrap(function _callee$(_context) { 306 | while (1) switch (_context.prev = _context.next) { 307 | case 0: 308 | if (callback == null) { 309 | callback = params; 310 | } 311 | 312 | // Schemas 313 | if (this.apisLoaded) { 314 | _context.next = 3; 315 | break; 316 | } 317 | return _context.abrupt("return", this.loadSchemas('/', function (err) { 318 | if (err) { 319 | return callback(err); 320 | } 321 | _this2.apisLoaded = true; 322 | return _this2.request(httpMethod, path, params, callback, refer); 323 | })); 324 | case 3: 325 | if (!(!this.oauthConfig && this.apiTimeDiff === null && path !== '/auth/time')) { 326 | _context.next = 5; 327 | break; 328 | } 329 | return _context.abrupt("return", this.request('GET', '/auth/time', {}, function (err, time) { 330 | if (err) { 331 | return callback('[OVH] Unable to fetch OVH API time'); 332 | } 333 | _this2.apiTimeDiff = time - Math.round(Date.now() / 1000); 334 | return _this2.request(httpMethod, path, params, callback, refer); 335 | }, refer)); 336 | case 5: 337 | // Potential warnings 338 | if (Object.keys(this.apis).length > 1) { 339 | this.warnsRequest(httpMethod, path); 340 | } 341 | 342 | // Replace "{str}", used for $call() 343 | if (path.indexOf('{') >= 0) { 344 | newPath = path; 345 | for (paramKey in params) { 346 | if (params.hasOwnProperty(paramKey)) { 347 | newPath = path.replace('{' + paramKey + '}', params[paramKey]); 348 | 349 | // Remove from body parameters 350 | if (newPath !== path) { 351 | delete params[paramKey]; 352 | } 353 | path = newPath; 354 | } 355 | } 356 | } 357 | options = { 358 | host: this.host, 359 | port: this.port, 360 | method: httpMethod, 361 | path: this.getFullPath(path), 362 | headers: {} 363 | }; 364 | if (!this.oauthConfig) { 365 | options.headers = { 366 | 'Content-Type': 'application/json', 367 | 'X-Ovh-Application': this.appKey 368 | }; 369 | } 370 | 371 | // Remove undefined values 372 | for (k in params) { 373 | if (params.hasOwnProperty(k) && params[k] == null) { 374 | delete params[k]; 375 | } 376 | } 377 | reqBody = null; 378 | if (_typeof(params) === 'object' && Object.keys(params).length > 0) { 379 | if (httpMethod === 'PUT' || httpMethod === 'POST') { 380 | // Escape unicode 381 | reqBody = JSON.stringify(params).replace(/[\u0080-\uFFFF]/g, function (m) { 382 | return "\\u" + ('0000' + m.charCodeAt(0).toString(16)).slice(-4); 383 | }); 384 | options.headers['Content-Length'] = reqBody.length; 385 | } else { 386 | options.path += '?' + querystring.stringify(params); 387 | } 388 | } 389 | if (!(this.oauthConfig && (!this.accessToken || this.accessToken.expired(10)))) { 390 | _context.next = 22; 391 | break; 392 | } 393 | _context.prev = 13; 394 | _context.next = 16; 395 | return this.oauthClient.getToken({ 396 | scope: "all" 397 | }); 398 | case 16: 399 | this.accessToken = _context.sent; 400 | _context.next = 22; 401 | break; 402 | case 19: 403 | _context.prev = 19; 404 | _context.t0 = _context["catch"](13); 405 | return _context.abrupt("return", callback({ 406 | statusCode: _context.t0.output && _context.t0.output.statusCode, 407 | error: _context.t0.output && _context.t0.output.payload.error, 408 | message: _context.t0.data.payload 409 | })); 410 | case 22: 411 | if (this.accessToken) { 412 | options.headers.Authorization = "Bearer ".concat(this.accessToken.token.access_token); 413 | } 414 | if (path.indexOf('/auth') < 0 && !this.accessToken) { 415 | options.headers['X-Ovh-Timestamp'] = Math.round(Date.now() / 1000) + this.apiTimeDiff; 416 | 417 | // Sign request 418 | if (typeof this.consumerKey === 'string') { 419 | options.headers['X-Ovh-Consumer'] = this.consumerKey; 420 | options.headers['X-Ovh-Signature'] = this.signRequest(httpMethod, 'https://' + options.host + options.path, reqBody, options.headers['X-Ovh-Timestamp']); 421 | } 422 | } 423 | if (this.debug) { 424 | this.debug('[OVH] API call:', options.method, options.path, reqBody || ''); 425 | } 426 | req = https.request(options, function (res) { 427 | var body = ''; 428 | res.on('data', function (chunk) { 429 | return body += chunk; 430 | }); 431 | res.on('end', function () { 432 | var response; 433 | if (body.length > 0) { 434 | try { 435 | response = JSON.parse(body); 436 | } catch (e) { 437 | return callback('[OVH] Unable to parse JSON reponse'); 438 | } 439 | } else { 440 | response = null; 441 | } 442 | if (_this2.debug) { 443 | _this2.debug('[OVH] API response to', options.method, options.path, ':', body); 444 | } 445 | if (res.statusCode > 299 || res.statusCode < 200) { 446 | callback(res.statusCode, response ? response.message : response); 447 | } else { 448 | // Return a proxy (for potential next request) 449 | if (refer != null) { 450 | _this2.proxyResponseREST(response, refer, callback); 451 | } else { 452 | callback(null, response); 453 | } 454 | } 455 | }); 456 | }); // istanbul ignore next 457 | req.on('error', function (e) { 458 | return callback(e.errno || e); 459 | }); 460 | 461 | // istanbul ignore next 462 | // mocked socket has no setTimeout 463 | if (typeof this.timeout === 'number') { 464 | req.on('socket', function (socket) { 465 | socket.setTimeout(_this2.timeout); 466 | if (socket._events.timeout != null) { 467 | socket.on('timeout', function () { 468 | return req.abort(); 469 | }); 470 | } 471 | }); 472 | } 473 | if (reqBody != null) { 474 | req.write(reqBody); 475 | } 476 | req.end(); 477 | case 30: 478 | case "end": 479 | return _context.stop(); 480 | } 481 | }, _callee, this, [[13, 19]]); 482 | })); 483 | function request(_x, _x2, _x3, _x4, _x5) { 484 | return _request.apply(this, arguments); 485 | } 486 | return request; 487 | }() 488 | /** 489 | * Execute a request on the API with promise 490 | * 491 | * @param {String} httpMethod: The HTTP method 492 | * @param {String} path: The request path 493 | * @param {Object} params: The request parameters (passed as query string or 494 | * body params) 495 | */ 496 | ) 497 | }, { 498 | key: "requestPromised", 499 | value: function requestPromised(httpMethod, path, params) { 500 | var _this3 = this; 501 | return new Bluebird(function (resolve, reject) { 502 | return _this3.request(httpMethod, path, params, function (error, resp) { 503 | if (error) { 504 | return reject({ 505 | error: error, 506 | message: resp 507 | }); 508 | } 509 | return resolve(resp); 510 | }); 511 | }); 512 | } 513 | 514 | /** 515 | * Signs an API request 516 | * 517 | * @param {String} httpMethod 518 | * @param {String} url 519 | * @param {String} body 520 | * @param {Number|String} timestamp 521 | * @return {String} The signature 522 | */ 523 | }, { 524 | key: "signRequest", 525 | value: function signRequest(httpMethod, url, body, timestamp) { 526 | var s = [this.appSecret, this.consumerKey, httpMethod, url, body || '', timestamp]; 527 | return '$1$' + crypto.createHash('sha1').update(s.join('+')).digest('hex'); 528 | } 529 | }]); 530 | }(); 531 | module.exports = function (params) { 532 | return new Ovh(params || {}); 533 | }; 534 | --------------------------------------------------------------------------------