Родитель
6debcdd0be
Коммит
5b2d155265
20
docs/api.md
20
docs/api.md
|
@ -5,9 +5,15 @@
|
|||
### URL Structure
|
||||
|
||||
```
|
||||
https://<server-url>/oauth/<api-endpoint>
|
||||
https://<server-url>/v1/<api-endpoint>
|
||||
```
|
||||
|
||||
Note that:
|
||||
|
||||
- All API access must be over HTTPS
|
||||
- The URL embeds a version identifier "v1"; future revisions of this API may introduce new version numbers.
|
||||
- The base URL of the server may be configured on a per-client basis
|
||||
|
||||
### Errors
|
||||
|
||||
Invalid requests will return 4XX responses. Internal failures will return 5XX. Both will include JSON responses describing the error.
|
||||
|
@ -38,10 +44,10 @@ The currently-defined error responses are:
|
|||
|
||||
## API Endpoints
|
||||
|
||||
- [POST /oauth/authorization][authorization]
|
||||
- [POST /oauth/token][token]
|
||||
- [POST /v1/authorization][authorization]
|
||||
- [POST /v1/token][token]
|
||||
|
||||
### POST /oauth/authorization
|
||||
### POST /v1/authorization
|
||||
|
||||
This endpoint should be used by the fxa-content-server, requesting that
|
||||
we supply a short-lived code (currently 15 minutes) that will be sent
|
||||
|
@ -69,7 +75,7 @@ Example:
|
|||
https://example.domain/path?foo=bar&code=asdfqwerty&state=zxcvasdf
|
||||
```
|
||||
|
||||
### POST /oauth/token
|
||||
### POST /v1/token
|
||||
|
||||
After having received a [code][], the client sends that code (most
|
||||
likely a server-side request) to this endpoint, to receive a
|
||||
|
@ -100,5 +106,5 @@ Example:
|
|||
}
|
||||
```
|
||||
|
||||
[authorization]: #post-oauthauthorization
|
||||
[token]: #post-oauthtoken
|
||||
[authorization]: #post-v1authorization
|
||||
[token]: #post-v1token
|
||||
|
|
|
@ -8,6 +8,12 @@ const path = require('path');
|
|||
const convict = require('convict');
|
||||
|
||||
const conf = convict({
|
||||
api: {
|
||||
version: {
|
||||
doc: 'Number part of versioned endpoints - ex: /v1/token',
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
browserid: {
|
||||
issuer: {
|
||||
doc: 'We only accept assertions from this issuer',
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const version = require('./config').get('api.version');
|
||||
|
||||
function v(url) {
|
||||
return '/v' + version + url;
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
method: 'GET',
|
||||
|
@ -15,12 +21,12 @@ module.exports = [
|
|||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/oauth/authorization',
|
||||
path: v('/authorization'),
|
||||
config: require('./routes/authorization')
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/oauth/token',
|
||||
path: v('/token'),
|
||||
config: require('./routes/token')
|
||||
}
|
||||
];
|
||||
|
|
92
test/api.js
92
test/api.js
|
@ -101,7 +101,7 @@ function assertRequestParam(result, param) {
|
|||
}
|
||||
|
||||
|
||||
describe('/oauth', function() {
|
||||
describe('/v1', function() {
|
||||
before(function(done) {
|
||||
|
||||
Promise.all([
|
||||
|
@ -130,8 +130,8 @@ describe('/oauth', function() {
|
|||
|
||||
it('is required', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
client_id: undefined
|
||||
})
|
||||
|
@ -142,8 +142,8 @@ describe('/oauth', function() {
|
|||
|
||||
it('succeeds if passed', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams()
|
||||
}).then(function(res) {
|
||||
assert.equal(res.statusCode, 302);
|
||||
|
@ -155,8 +155,8 @@ describe('/oauth', function() {
|
|||
describe('?assertion', function() {
|
||||
|
||||
it('is required', function(done) {
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
assertion: undefined
|
||||
})
|
||||
|
@ -167,8 +167,8 @@ describe('/oauth', function() {
|
|||
|
||||
it('succeeds if passed', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams()
|
||||
}).then(function(res) {
|
||||
assert.equal(res.statusCode, 302);
|
||||
|
@ -177,8 +177,8 @@ describe('/oauth', function() {
|
|||
|
||||
it('errors correctly if invalid', function(done) {
|
||||
mockAssertion().reply(400, '{"status":"failure"}');
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams()
|
||||
}).then(function(res) {
|
||||
assert.equal(res.result.code, 400);
|
||||
|
@ -191,8 +191,8 @@ describe('/oauth', function() {
|
|||
describe('?redirect_uri', function() {
|
||||
it('is optional', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
redirect_uri: client.redirectUri
|
||||
})
|
||||
|
@ -203,8 +203,8 @@ describe('/oauth', function() {
|
|||
|
||||
it('must be same as registered redirect', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
redirect_uri: 'http://herp.derp'
|
||||
})
|
||||
|
@ -218,8 +218,8 @@ describe('/oauth', function() {
|
|||
describe('?state', function() {
|
||||
it('is required', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
state: undefined
|
||||
})
|
||||
|
@ -229,8 +229,8 @@ describe('/oauth', function() {
|
|||
});
|
||||
it('is returned', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
state: 'aa'
|
||||
})
|
||||
|
@ -244,8 +244,8 @@ describe('/oauth', function() {
|
|||
describe('?scope', function() {
|
||||
it('is optional', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
scope: undefined
|
||||
})
|
||||
|
@ -259,8 +259,8 @@ describe('/oauth', function() {
|
|||
describe('with a whitelisted client', function() {
|
||||
it('should redirect to the redirect_uri', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams()
|
||||
}).then(function(res) {
|
||||
assert.equal(res.statusCode, 302);
|
||||
|
@ -281,15 +281,15 @@ describe('/oauth', function() {
|
|||
describe('/token', function() {
|
||||
|
||||
it('disallows GET', function(done) {
|
||||
Server.get('/oauth/token').then(function(res) {
|
||||
Server.api.get('/token').then(function(res) {
|
||||
assert.equal(res.statusCode, 404);
|
||||
}).done(done, done);
|
||||
});
|
||||
|
||||
describe('?client_id', function() {
|
||||
it('is required', function(done) {
|
||||
Server.post({
|
||||
url: '/oauth/token',
|
||||
Server.api.post({
|
||||
url: '/token',
|
||||
payload: {
|
||||
client_secret: secret,
|
||||
code: unique.code().toString('hex')
|
||||
|
@ -302,8 +302,8 @@ describe('/oauth', function() {
|
|||
|
||||
describe('?client_secret', function() {
|
||||
it('is required', function(done) {
|
||||
Server.post({
|
||||
url: '/oauth/token',
|
||||
Server.api.post({
|
||||
url: '/token',
|
||||
payload: {
|
||||
client_id: clientId,
|
||||
code: unique.code().toString('hex')
|
||||
|
@ -314,8 +314,8 @@ describe('/oauth', function() {
|
|||
});
|
||||
|
||||
it('must match server-stored secret', function(done) {
|
||||
Server.post({
|
||||
url: '/oauth/token',
|
||||
Server.api.post({
|
||||
url: '/token',
|
||||
payload: {
|
||||
client_id: clientId,
|
||||
client_secret: unique.secret().toString('hex'),
|
||||
|
@ -330,8 +330,8 @@ describe('/oauth', function() {
|
|||
|
||||
describe('?code', function() {
|
||||
it('is required', function(done) {
|
||||
Server.post({
|
||||
url: '/oauth/token',
|
||||
Server.api.post({
|
||||
url: '/token',
|
||||
payload: {
|
||||
client_id: clientId,
|
||||
client_secret: secret
|
||||
|
@ -342,8 +342,8 @@ describe('/oauth', function() {
|
|||
});
|
||||
|
||||
it('must match an existing code', function(done) {
|
||||
Server.post({
|
||||
url: '/oauth/token',
|
||||
Server.api.post({
|
||||
url: '/token',
|
||||
payload: {
|
||||
client_id: clientId,
|
||||
client_secret: secret,
|
||||
|
@ -365,8 +365,8 @@ describe('/oauth', function() {
|
|||
};
|
||||
db.registerClient(client2).then(function() {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
return Server.post({
|
||||
url: '/oauth/authorization',
|
||||
return Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
client_id: client2.id.toString('hex')
|
||||
})
|
||||
|
@ -374,8 +374,8 @@ describe('/oauth', function() {
|
|||
return url.parse(res.headers.location, true).query.code;
|
||||
});
|
||||
}).then(function(code) {
|
||||
return Server.post({
|
||||
url: '/oauth/token',
|
||||
return Server.api.post({
|
||||
url: '/token',
|
||||
payload: {
|
||||
// client is trying to use client2's code
|
||||
client_id: clientId,
|
||||
|
@ -399,14 +399,14 @@ describe('/oauth', function() {
|
|||
_done.apply(this, arguments);
|
||||
}
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams()
|
||||
}).then(function(res) {
|
||||
return url.parse(res.headers.location, true).query.code;
|
||||
}).delay(60).then(function(code) {
|
||||
return Server.post({
|
||||
url: '/oauth/token',
|
||||
return Server.api.post({
|
||||
url: '/token',
|
||||
payload: {
|
||||
client_id: clientId,
|
||||
client_secret: secret,
|
||||
|
@ -423,15 +423,15 @@ describe('/oauth', function() {
|
|||
describe('response', function() {
|
||||
it('should return a correct response', function(done) {
|
||||
mockAssertion().reply(200, VERIFY_GOOD);
|
||||
Server.post({
|
||||
url: '/oauth/authorization',
|
||||
Server.api.post({
|
||||
url: '/authorization',
|
||||
payload: authParams({
|
||||
scope: 'foo bar bar'
|
||||
})
|
||||
}).then(function(res) {
|
||||
assert.equal(res.statusCode, 302);
|
||||
return Server.post({
|
||||
url: '/oauth/token',
|
||||
return Server.api.post({
|
||||
url: '/token',
|
||||
payload: {
|
||||
client_id: clientId,
|
||||
client_secret: secret,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
const Promise = require('../../lib/promise');
|
||||
const Server = require('../../lib/server');
|
||||
const version = require('../../lib/config').get('api.version');
|
||||
|
||||
function request(options) {
|
||||
var server = Server.create();
|
||||
|
@ -30,3 +31,14 @@ exports.get = function get(options) {
|
|||
options.method = 'GET';
|
||||
return request(options);
|
||||
};
|
||||
|
||||
var api = {};
|
||||
Object.keys(exports).forEach(function(key) {
|
||||
api[key] = function api(options) {
|
||||
options = opts(options);
|
||||
options.url = '/v' + version + options.url;
|
||||
return exports[key](options);
|
||||
};
|
||||
});
|
||||
|
||||
exports.api = api;
|
||||
|
|
Загрузка…
Ссылка в новой задаче