Skip to content

Commit

Permalink
*BREAKING*: Replace application.generateApiKey with application.gener…
Browse files Browse the repository at this point in the history
…ateProvisioningKey

We now no longer allow generating app wide keys. Generate a provisioning
key to register new devices, and look at JWTs or user API keys (coming
soon) for other uses.

Change-Type: major
  • Loading branch information
pimterry committed Oct 2, 2017
1 parent 0f8c57c commit c67d8d0
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 48 deletions.
26 changes: 13 additions & 13 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ If you feel something is missing, not clear or could be improved, please don't h
* [.create(name, deviceType, [parentNameOrId])](#resin.models.application.create) ⇒ <code>Promise</code>
* [.remove(nameOrId)](#resin.models.application.remove) ⇒ <code>Promise</code>
* [.restart(nameOrId)](#resin.models.application.restart) ⇒ <code>Promise</code>
* [.generateApiKey(nameOrId)](#resin.models.application.generateApiKey) ⇒ <code>Promise</code>
* [.generateProvisioningKey(nameOrId)](#resin.models.application.generateProvisioningKey) ⇒ <code>Promise</code>
* [.purge(appId)](#resin.models.application.purge) ⇒ <code>Promise</code>
* [.shutdown(appId, [options])](#resin.models.application.shutdown) ⇒ <code>Promise</code>
* [.reboot(appId, [options])](#resin.models.application.reboot) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -274,7 +274,7 @@ resin.models.device.get(123).catch(function (error) {
* [.create(name, deviceType, [parentNameOrId])](#resin.models.application.create) ⇒ <code>Promise</code>
* [.remove(nameOrId)](#resin.models.application.remove) ⇒ <code>Promise</code>
* [.restart(nameOrId)](#resin.models.application.restart) ⇒ <code>Promise</code>
* [.generateApiKey(nameOrId)](#resin.models.application.generateApiKey) ⇒ <code>Promise</code>
* [.generateProvisioningKey(nameOrId)](#resin.models.application.generateProvisioningKey) ⇒ <code>Promise</code>
* [.purge(appId)](#resin.models.application.purge) ⇒ <code>Promise</code>
* [.shutdown(appId, [options])](#resin.models.application.shutdown) ⇒ <code>Promise</code>
* [.reboot(appId, [options])](#resin.models.application.reboot) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -382,7 +382,7 @@ resin.models.device.get(123).catch(function (error) {
* [.create(name, deviceType, [parentNameOrId])](#resin.models.application.create) ⇒ <code>Promise</code>
* [.remove(nameOrId)](#resin.models.application.remove) ⇒ <code>Promise</code>
* [.restart(nameOrId)](#resin.models.application.restart) ⇒ <code>Promise</code>
* [.generateApiKey(nameOrId)](#resin.models.application.generateApiKey) ⇒ <code>Promise</code>
* [.generateProvisioningKey(nameOrId)](#resin.models.application.generateProvisioningKey) ⇒ <code>Promise</code>
* [.purge(appId)](#resin.models.application.purge) ⇒ <code>Promise</code>
* [.shutdown(appId, [options])](#resin.models.application.shutdown) ⇒ <code>Promise</code>
* [.reboot(appId, [options])](#resin.models.application.reboot) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -629,35 +629,35 @@ resin.models.application.restart('MyApp', function(error) {
if (error) throw error;
});
```
<a name="resin.models.application.generateApiKey"></a>
<a name="resin.models.application.generateProvisioningKey"></a>

##### application.generateApiKey(nameOrId) ⇒ <code>Promise</code>
##### application.generateProvisioningKey(nameOrId) ⇒ <code>Promise</code>
**Kind**: static method of <code>[application](#resin.models.application)</code>
**Summary**: Generate an API key for a specific application
**Summary**: Generate a device provisioning key for a specific application
**Access**: public
**Fulfil**: <code>String</code> - api key
**Fulfil**: <code>String</code> - device provisioning key

| Param | Type | Description |
| --- | --- | --- |
| nameOrId | <code>String</code> \| <code>Number</code> | application name (string) or id (number) |

**Example**
```js
resin.models.application.generateApiKey('MyApp').then(function(apiKey) {
console.log(apiKey);
resin.models.application.generateProvisioningKey('MyApp').then(function(key) {
console.log(key);
});
```
**Example**
```js
resin.models.application.generateApiKey(123).then(function(apiKey) {
console.log(apiKey);
resin.models.application.generateProvisioningKey(123).then(function(key) {
console.log(key);
});
```
**Example**
```js
resin.models.application.generateApiKey('MyApp', function(error, apiKey) {
resin.models.application.generateProvisioningKey('MyApp', function(error, key) {
if (error) throw error;
console.log(apiKey);
console.log(key);
});
```
<a name="resin.models.application.purge"></a>
Expand Down
37 changes: 22 additions & 15 deletions lib/models/application.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ filter = require('lodash/filter')
size = require('lodash/size')
errors = require('resin-errors')

{ isId, findCallback, mergePineOptions, notFoundResponse, treatAsMissingApplication, LOCKED_STATUS_CODE } = require('../util')
{
isId,
findCallback,
mergePineOptions,
notFoundResponse,
noApplicationForKeyResponse,
treatAsMissingApplication,
LOCKED_STATUS_CODE
} = require('../util')
{ normalizeDeviceOsVersion } = require('../util/device-os-version')

getApplicationModel = (deps, opts) ->
Expand Down Expand Up @@ -419,40 +427,39 @@ getApplicationModel = (deps, opts) ->
.asCallback(callback)

###*
# @summary Generate an API key for a specific application
# @name generateApiKey
# @summary Generate a device provisioning key for a specific application
# @name generateProvisioningKey
# @public
# @function
# @memberof resin.models.application
#
# @param {String|Number} nameOrId - application name (string) or id (number)
# @fulfil {String} - api key
# @fulfil {String} - device provisioning key
# @returns {Promise}
#
# @example
# resin.models.application.generateApiKey('MyApp').then(function(apiKey) {
# console.log(apiKey);
# resin.models.application.generateProvisioningKey('MyApp').then(function(key) {
# console.log(key);
# });
#
# @example
# resin.models.application.generateApiKey(123).then(function(apiKey) {
# console.log(apiKey);
# resin.models.application.generateProvisioningKey(123).then(function(key) {
# console.log(key);
# });
#
# @example
# resin.models.application.generateApiKey('MyApp', function(error, apiKey) {
# resin.models.application.generateProvisioningKey('MyApp', function(error, key) {
# if (error) throw error;
# console.log(apiKey);
# console.log(key);
# });
###
exports.generateApiKey = (nameOrId, callback) ->
# Do a full get, not just getId, because the actual api endpoint doesn't fail if the id
# doesn't exist. TODO: Can use getId once https://github.com/resin-io/resin-api/issues/110 is resolved
exports.get(nameOrId, select: 'id').then ({ id }) ->
exports.generateProvisioningKey = (nameOrId, callback) ->
getId(nameOrId).then (applicationId) ->
return request.send
method: 'POST'
url: "/application/#{id}/generate-api-key"
url: "/api-key/application/#{applicationId}/provisioning"
baseUrl: apiUrl
.catch(noApplicationForKeyResponse, treatAsMissingApplication(nameOrId))
.get('body')
.asCallback(callback)

Expand Down
23 changes: 13 additions & 10 deletions lib/models/device.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ semver = require('semver')
errors = require('resin-errors')
deviceStatus = require('resin-device-status')

{ onlyIf, isId, findCallback, mergePineOptions, notFoundResponse, treatAsMissingDevice, LOCKED_STATUS_CODE, timeSince } = require('../util')
{
onlyIf,
isId,
findCallback,
mergePineOptions,
notFoundResponse,
noDeviceForKeyResponse,
treatAsMissingDevice,
LOCKED_STATUS_CODE,
timeSince
} = require('../util')
{ normalizeDeviceOsVersion } = require('../util/device-os-version')

# The min version where /apps API endpoints are implemented is 1.8.0 but we'll
Expand Down Expand Up @@ -1297,7 +1307,7 @@ getDeviceModel = (deps, opts) ->
exports.register = (applicationNameOrId, uuid, callback) ->
Promise.props
userId: auth.getUserId()
apiKey: applicationModel().getApiKey(applicationNameOrId)
apiKey: applicationModel().generateProvisioningKey(applicationNameOrId)
application: applicationModel().get(applicationNameOrId, select: ['id', 'device_type'])
.then ({ userId, apiKey, application }) ->

Expand Down Expand Up @@ -1344,14 +1354,7 @@ getDeviceModel = (deps, opts) ->
url: "/api-key/device/#{deviceId}/device-key"
baseUrl: apiUrl
.get('body')
.catch(
{
code: 'ResinRequestError'
statusCode: 500
body: 'No device found to associate with the api key'
}
treatAsMissingDevice(uuidOrId)
)
.catch(noDeviceForKeyResponse, treatAsMissingDevice(uuidOrId))
.asCallback(callback)

###*
Expand Down
10 changes: 10 additions & 0 deletions lib/util/index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ exports.notFoundResponse =
code: 'ResinRequestError'
statusCode: 404

exports.noDeviceForKeyResponse =
code: 'ResinRequestError'
statusCode: 500
body: 'No device found to associate with the api key'

exports.noApplicationForKeyResponse =
code: 'ResinRequestError'
statusCode: 500
body: 'No application found to associate with the api key'

exports.treatAsMissingApplication = (nameOrId) ->
return (err) ->
replacementErr = new errors.ResinApplicationNotFound(nameOrId)
Expand Down
20 changes: 10 additions & 10 deletions tests/integration/models/application.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -186,24 +186,24 @@ describe 'Application Model', ->
promise = resin.models.application.remove(999999)
m.chai.expect(promise).to.be.rejectedWith('Application not found: 999999')

describe 'resin.models.application.getApiKey()', ->
describe 'resin.models.application.generateProvisioningKey()', ->

it 'should be able to generate an API key by name', ->
resin.models.application.getApiKey(@application.app_name).then (apiKey) ->
m.chai.expect(_.isString(apiKey)).to.be.true
m.chai.expect(apiKey).to.have.length(32)
it 'should be able to generate an provisioning key by name', ->
resin.models.application.generateProvisioningKey(@application.app_name).then (key) ->
m.chai.expect(_.isString(key)).to.be.true
m.chai.expect(key).to.have.length(32)

it 'should be able to generate an API key by id', ->
resin.models.application.getApiKey(@application.id).then (apiKey) ->
m.chai.expect(_.isString(apiKey)).to.be.true
m.chai.expect(apiKey).to.have.length(32)
resin.models.application.generateProvisioningKey(@application.id).then (key) ->
m.chai.expect(_.isString(key)).to.be.true
m.chai.expect(key).to.have.length(32)

it 'should be rejected if the application name does not exist', ->
promise = resin.models.application.getApiKey('HelloWorldApp')
promise = resin.models.application.generateProvisioningKey('HelloWorldApp')
m.chai.expect(promise).to.be.rejectedWith('Application not found: HelloWorldApp')

it 'should be rejected if the application id does not exist', ->
promise = resin.models.application.getApiKey(999999)
promise = resin.models.application.generateProvisioningKey(999999)
m.chai.expect(promise).to.be.rejectedWith('Application not found: 999999')

describe 'when toggling device URLs', ->
Expand Down

0 comments on commit c67d8d0

Please sign in to comment.