Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
feat(developers): adds support for oauth developers
Browse files Browse the repository at this point in the history
  • Loading branch information
vladikoff committed Apr 27, 2015
1 parent d5bb046 commit abe0e52
Show file tree
Hide file tree
Showing 17 changed files with 897 additions and 91 deletions.
20 changes: 20 additions & 0 deletions docs/api.md
Expand Up @@ -61,6 +61,8 @@ The currently-defined error responses are:
- [POST /v1/client][register]
- [POST /v1/client/:id][client-update]
- [DELETE /v1/client/:id][client-delete]
- Developers
- [POST /v1/developer/activate][developer-activate]
- [POST /v1/verify][verify]

### GET /v1/client/:id
Expand Down Expand Up @@ -249,6 +251,23 @@ curl -v \

A valid response will have a 204 response code and an empty body.

### POST /v1/developer/activate

Register an oauth developer.

**Required scope:** `oauth`

#### Request Parameters

- None

#### Response

A valid response will have a 200 status code and a developer object:
```
{"developerId":"f5b176ab5be5928d01d4bb0a6c182994","email":"d91c30a8@mozilla.com","createdAt":"2015-03-23T01:22:59.000Z"}
```

### GET /v1/authorization

This endpoint starts the OAuth flow. A client redirects the user agent
Expand Down Expand Up @@ -451,3 +470,4 @@ A valid request will return JSON with these properties:
[token]: #post-v1token
[delete]: #post-v1destroy
[verify]: #post-v1verify
[developer-activate]: #post-v1developeractivate
4 changes: 4 additions & 0 deletions lib/config.js
Expand Up @@ -175,6 +175,10 @@ const conf = convict({
token: {
doc: 'Bytes of generated tokens',
default: 32
},
developerId: {
doc: 'Bytes of generated developer ids',
default: 16
}
}
});
Expand Down
182 changes: 170 additions & 12 deletions lib/db/memory.js
Expand Up @@ -34,6 +34,20 @@ const unique = require('../unique');
* createdAt: <timestamp>
* }
* },
* developers: {
* <developerId>: {
* developerId: <developer_id>,
* email: <string>,
* createdAt: <timestamp>
* }
* },
* clientDevelopers: {
* <id>: {
* developerId: <developer_id>,
* clientId: <client_id>,
* createdAt: <timestamp>
* }
* },
* tokens: {
* <token>: {
* token: <string>,
Expand All @@ -53,6 +67,8 @@ function MemoryStore() {
this.clients = {};
this.codes = {};
this.tokens = {};
this.developers = {};
this.clientDevelopers = {};
}

MemoryStore.connect = function memoryConnect() {
Expand Down Expand Up @@ -119,18 +135,37 @@ MemoryStore.prototype = {
getClient: function getClient(id) {
return P.resolve(this.clients[unbuf(id)]);
},
getClients: function getClients() {
return P.resolve(Object.keys(this.clients).map(function(id) {
var client = this.clients[id];
return {
id: client.id,
name: client.name,
imageUri: client.imageUri,
redirectUri: client.redirectUri,
canGrant: client.canGrant,
whitelisted: client.whitelisted
};
}, this));
getClients: function getClients(email) {
var self = this;

return this.getDeveloper(email)
.then(function (developer) {
if (! developer) {
return [];
}

var clients = [];

Object.keys(self.clientDevelopers).forEach(function(key) {
var entry = self.clientDevelopers[key];
if (entry.developerId === developer.developerId) {
clients.push(unbuf(entry.clientId));
}
});

return clients.map(function(id) {
var client = self.clients[id];

return {
id: client.id,
name: client.name,
imageUri: client.imageUri,
redirectUri: client.redirectUri,
canGrant: client.canGrant,
whitelisted: client.whitelisted
};
}, this);
});
},
removeClient: function removeClient(id) {
delete this.clients[unbuf(id)];
Expand Down Expand Up @@ -187,7 +222,130 @@ MemoryStore.prototype = {
deleteByUserId(this.tokens, userId);
deleteByUserId(this.codes, userId);
return P.resolve();
},
activateDeveloper: function activateDeveloper(email) {
var self = this;

if (! email) {
return P.reject(new Error('Email is required'));
}

return this.getDeveloper(email)
.then(function(result) {
if (result) {
return P.reject(new Error('ER_DUP_ENTRY'));
}

var newId = unique.developerId();
var developer = {
developerId: newId,
email: email,
createdAt: new Date()
};

self.developers[unbuf(newId)] = developer;
return developer;

});
},
getDeveloper: function getDeveloper(email) {
var self = this;
var developer = null;

if (! email) {
return P.reject(new Error('Email is required'));
}

Object.keys(self.developers).forEach(function(developerId) {
var devEntry = self.developers[developerId];

if (devEntry.email === email) {
developer = devEntry;
}
});

return P.resolve(developer);
},
removeDeveloper: function removeDeveloper(email) {
var self = this;

if (! email) {
return P.reject(new Error('Email is required'));
}

Object.keys(self.developers).forEach(function(developerId) {
var devEntry = self.developers[developerId];

if (devEntry.email === email) {
delete self.developers[developerId];
}
});

return P.resolve();
},
developerOwnsClient: function devOwnsClient(developerEmail, clientId) {
var self = this;
var developerId;

logger.debug('developerOwnsClient');
return self.getDeveloper(developerEmail)
.then(function (developer) {
if (! developer) {
return P.reject();
}
developerId = developer.developerId;

return self.getClientDevelopers(clientId);
})
.then(function (developers) {
var result;

function hasDeveloper(developer) {
result = developer;
return unbuf(developer.developerId) === unbuf(developerId);
}

if (developers.some(hasDeveloper)) {
return P.resolve(true);
} else {
return P.reject(false);
}

});
},
registerClientDeveloper: function regClientDeveloper(developerId, clientId) {
var entry = {
developerId: buf(developerId),
clientId: buf(clientId),
createdAt: new Date()
};
var uniqueHexId = unbuf(unique.id());

logger.debug('registerClientDeveloper', entry);
this.clientDevelopers[uniqueHexId] = entry;
return P.resolve(entry);
},
getClientDevelopers: function getClientDevelopers(clientId) {
var self = this;
var developers = [];

if (! clientId) {
return P.reject(new Error('Client id is required'));
}

clientId = unbuf(clientId);

Object.keys(self.clientDevelopers).forEach(function(key) {
var entry = self.clientDevelopers[key];

if (unbuf(entry.clientId) === clientId) {
developers.push(self.developers[unbuf(entry.developerId)]);
}
});

return P.resolve(developers);
}

};

module.exports = MemoryStore;
99 changes: 96 additions & 3 deletions lib/db/mysql/index.js
Expand Up @@ -104,9 +104,30 @@ const QUERY_CLIENT_REGISTER =
'INSERT INTO clients ' +
'(id, name, imageUri, secret, redirectUri, whitelisted, canGrant) ' +
'VALUES (?, ?, ?, ?, ?, ?, ?);';
const QUERY_CLIENT_DEVELOPER_INSERT =
'INSERT INTO clientDevelopers ' +
'(rowId, developerId, clientId) ' +
'VALUES (?, ?, ?);';
const QUERY_CLIENT_DEVELOPER_LIST_BY_CLIENT_ID =
'SELECT developers.email, developers.createdAt ' +
'FROM clientDevelopers, developers ' +
'WHERE clientDevelopers.developerId = developers.developerId ' +
'AND clientDevelopers.clientId=?;';
const QUERY_DEVELOPER_OWNS_CLIENT =
'SELECT clientDevelopers.rowId ' +
'FROM clientDevelopers, developers ' +
'WHERE developers.developerId = clientDevelopers.developerId ' +
'AND developers.email =? AND clientDevelopers.clientId =?;';
const QUERY_DEVELOPER_INSERT =
'INSERT INTO developers ' +
'(developerId, email) ' +
'VALUES (?, ?);';
const QUERY_CLIENT_GET = 'SELECT * FROM clients WHERE id=?';
const QUERY_CLIENT_LIST = 'SELECT id, name, redirectUri, imageUri, canGrant, ' +
'whitelisted FROM clients';
'whitelisted FROM fxa_oauth.clients, clientDevelopers, developers ' +
'WHERE clients.id = clientDevelopers.clientId AND ' +
'developers.developerId = clientDevelopers.developerId AND ' +
'developers.email =?;';
const QUERY_CLIENT_UPDATE = 'UPDATE clients SET ' +
'name=COALESCE(?, name), imageUri=COALESCE(?, imageUri), ' +
'secret=COALESCE(?, secret), redirectUri=COALESCE(?, redirectUri), ' +
Expand All @@ -125,6 +146,8 @@ const QUERY_CODE_DELETE = 'DELETE FROM codes WHERE code=?';
const QUERY_TOKEN_DELETE = 'DELETE FROM tokens WHERE token=?';
const QUERY_TOKEN_DELETE_USER = 'DELETE FROM tokens WHERE userId=?';
const QUERY_CODE_DELETE_USER = 'DELETE FROM codes WHERE userId=?';
const QUERY_DEVELOPER = 'SELECT * FROM developers WHERE email=?';
const QUERY_DEVELOPER_DELETE = 'DELETE FROM developers WHERE email=?';

function firstRow(rows) {
return rows[0];
Expand Down Expand Up @@ -177,7 +200,77 @@ MysqlStore.prototype = {
return client;
});
},
registerClientDeveloper: function regClientDeveloper(developerId, clientId) {
if (!developerId || !clientId) {
var err = new Error('Owner registration requires user and developer id');
return P.reject(err);
}

var rowId = unique.id();

logger.debug('registerClientDeveloper', {
rowId: rowId,
developerId: developerId,
clientId: clientId
});

return this._write(QUERY_CLIENT_DEVELOPER_INSERT, [
buf(rowId),
buf(developerId),
buf(clientId)
]);
},
getClientDevelopers: function getClientDevelopers (clientId) {
if (! clientId) {
return P.reject(new Error('Client id is required'));
}

return this._read(QUERY_CLIENT_DEVELOPER_LIST_BY_CLIENT_ID, [
buf(clientId)
]);
},
activateDeveloper: function activateDeveloper(email) {
if (! email) {
return P.reject(new Error('Email is required'));
}

var developerId = unique.developerId();
logger.debug('activateDeveloper', { developerId: developerId });
return this._write(QUERY_DEVELOPER_INSERT, [
developerId, email
]).then(function () {
return this.getDeveloper(email);
}.bind(this));
},
getDeveloper: function(email) {
if (! email) {
return P.reject(new Error('Email is required'));
}

return this._readOne(QUERY_DEVELOPER, [
email
]);
},
removeDeveloper: function(email) {
if (! email) {
return P.reject(new Error('Email is required'));
}

return this._write(QUERY_DEVELOPER_DELETE, [
email
]);
},
developerOwnsClient: function devOwnsClient(developerEmail, clientId) {
return this._readOne(QUERY_DEVELOPER_OWNS_CLIENT, [
developerEmail, buf(clientId)
]).then(function(result) {
if (result) {
return P.resolve(true);
} else {
return P.reject(false);
}
});
},
updateClient: function updateClient(client) {
if (!client.id) {
return P.reject(new Error('Update client needs an id'));
Expand All @@ -203,8 +296,8 @@ MysqlStore.prototype = {
getClient: function getClient(id) {
return this._readOne(QUERY_CLIENT_GET, [buf(id)]);
},
getClients: function getClients() {
return this._read(QUERY_CLIENT_LIST);
getClients: function getClients(email) {
return this._read(QUERY_CLIENT_LIST, [ email ]);
},
removeClient: function removeClient(id) {
return this._write(QUERY_CLIENT_DELETE, [buf(id)]);
Expand Down
2 changes: 1 addition & 1 deletion lib/db/mysql/patch.js
Expand Up @@ -6,5 +6,5 @@
// Update this if you add a new patch, and don't forget to update
// the documentation for the current schema in ../schema.sql.

module.exports.level = 3;
module.exports.level = 4;

0 comments on commit abe0e52

Please sign in to comment.