Родитель
158eb63a0e
Коммит
9b3366652e
21
docs/API.md
21
docs/API.md
|
@ -84,7 +84,8 @@ curl -v \
|
|||
{
|
||||
"uid": "6d940dd41e636cc156074109b8092f96",
|
||||
"email": "user@example.domain",
|
||||
"avatar": "https://secure.gravatar.com/avatar/6d940dd41e636cc156074109b8092f96"
|
||||
"avatar": "https://firefoxusercontent.com/a9bff302615cd015692a099f691205cc",
|
||||
"avatarDefault": false
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -166,15 +167,29 @@ curl -v \
|
|||
"https://profile.accounts.firefox.com/v1/avatar"
|
||||
```
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "81625c14128d46c2b600e74a017fa4a8",
|
||||
"url": "https://secure.gravatar.com/avatar/6d940dd41e636cc156074109b8092f96"
|
||||
"id": "a9bff302615cd015692a099f691205cc",
|
||||
"avatarDefault": false,
|
||||
"avatar": "https://firefoxusercontent.com/a9bff302615cd015692a099f691205cc"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Response (no avatar set)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "00000000000000000000000000000000",
|
||||
"avatarDefault": true,
|
||||
"avatar": "https://firefoxusercontent.com/00000000000000000000000000000000"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### POST /v1/avatar/upload
|
||||
|
||||
- scope: `profile:avatar:write`
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 2.5 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 8.0 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -133,6 +133,12 @@ const conf = convict({
|
|||
doc: 'Pattern to generate FxA avatar URLs. {id} will be replaced.',
|
||||
default: 'http://127.0.0.1:1112/a/{id}',
|
||||
env: 'IMG_URL'
|
||||
},
|
||||
defaultAvatarId: {
|
||||
default: '00000000000000000000000000000000',
|
||||
doc: 'Default avatar id',
|
||||
env: 'DEFAULT_AVATAR_ID',
|
||||
format: String
|
||||
}
|
||||
},
|
||||
logging: {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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 config = require('../../config');
|
||||
|
||||
const FXA_URL_TEMPLATE = config.get('img.url');
|
||||
|
||||
function fxaUrl(id) {
|
||||
return FXA_URL_TEMPLATE.replace('{id}', id);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fxaUrl: fxaUrl
|
||||
};
|
|
@ -7,12 +7,14 @@ const Joi = require('joi');
|
|||
const P = require('../../promise');
|
||||
|
||||
const AppError = require('../../error');
|
||||
const config = require('../../config');
|
||||
const db = require('../../db');
|
||||
const logger = require('../../logging')('routes.avatar.delete');
|
||||
const notifyProfileUpdated = require('../../updates-queue');
|
||||
const validate = require('../../validate');
|
||||
const workers = require('../../img-workers');
|
||||
|
||||
const DEFAULT_AVATAR_ID = config.get('img.defaultAvatarId');
|
||||
const EMPTY = Object.create(null);
|
||||
const FXA_PROVIDER = 'fxa';
|
||||
|
||||
|
@ -30,6 +32,11 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
handler: function deleteAvatar(req, reply) {
|
||||
if (req.params.id === DEFAULT_AVATAR_ID) {
|
||||
// if we are clearing the default avatar then do nothing
|
||||
return reply({});
|
||||
}
|
||||
|
||||
req.server.methods.batch.cache.drop(req, function() {
|
||||
const uid = req.auth.credentials.user;
|
||||
var avatar, lookup;
|
||||
|
|
|
@ -5,19 +5,27 @@
|
|||
const Joi = require('joi');
|
||||
|
||||
const db = require('../../db');
|
||||
const config = require('../../config');
|
||||
const hex = require('buf').to.hex;
|
||||
const validate = require('../../validate');
|
||||
const logger = require('../../logging')('routes.avatar.get');
|
||||
const avatarShared = require('./_shared');
|
||||
|
||||
const EMPTY = Object.create(null);
|
||||
function avatarOrEmpty(avatar) {
|
||||
const DEFAULT_AVATAR = {
|
||||
avatar: avatarShared.fxaUrl(config.get('img.defaultAvatarId')),
|
||||
avatarDefault: true,
|
||||
id: config.get('img.defaultAvatarId')
|
||||
};
|
||||
|
||||
function avatarOrDefault(avatar) {
|
||||
if (avatar) {
|
||||
return {
|
||||
avatar: avatar.url,
|
||||
avatarDefault: false,
|
||||
id: hex(avatar.id),
|
||||
avatar: avatar.url
|
||||
};
|
||||
}
|
||||
return EMPTY;
|
||||
return DEFAULT_AVATAR;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -30,13 +38,14 @@ module.exports = {
|
|||
id: Joi.string()
|
||||
.regex(validate.hex)
|
||||
.length(32),
|
||||
avatarDefault: Joi.boolean(),
|
||||
avatar: Joi.string().max(256)
|
||||
}
|
||||
},
|
||||
handler: function avatar(req, reply) {
|
||||
var uid = req.auth.credentials.user;
|
||||
db.getSelectedAvatar(uid)
|
||||
.then(avatarOrEmpty)
|
||||
.then(avatarOrDefault)
|
||||
.done(function (result) {
|
||||
var rep = reply(result);
|
||||
if (result.id) {
|
||||
|
|
|
@ -13,15 +13,14 @@ const img = require('../../img');
|
|||
const notifyProfileUpdated = require('../../updates-queue');
|
||||
const validate = require('../../validate');
|
||||
const workers = require('../../img-workers');
|
||||
const avatarShared = require('./_shared');
|
||||
|
||||
const FXA_PROVIDER = 'fxa';
|
||||
const FXA_URL_TEMPLATE = config.get('img.url');
|
||||
assert(FXA_URL_TEMPLATE.indexOf('{id}') !== -1,
|
||||
'img.url must contain the string "{id}"');
|
||||
|
||||
function fxaUrl(id) {
|
||||
return FXA_URL_TEMPLATE.replace('{id}', id);
|
||||
}
|
||||
const DEFAULT_AVATAR_ID = config.get('img.defaultAvatarId');
|
||||
assert(DEFAULT_AVATAR_ID.length === 32, 'img.default');
|
||||
|
||||
module.exports = {
|
||||
auth: {
|
||||
|
@ -50,7 +49,9 @@ module.exports = {
|
|||
handler: function upload(req, reply) {
|
||||
req.server.methods.batch.cache.drop(req, function() {
|
||||
var id = img.id();
|
||||
var url = fxaUrl(id);
|
||||
// precaution to avoid the default id from being overwritten
|
||||
assert(id !== DEFAULT_AVATAR_ID);
|
||||
var url = avatarShared.fxaUrl(id);
|
||||
var uid = req.auth.credentials.user;
|
||||
workers.upload(id, req.payload, req.headers)
|
||||
.then(function save() {
|
||||
|
|
|
@ -6,8 +6,12 @@ const Boom = require('boom');
|
|||
const Joi = require('joi');
|
||||
const checksum = require('checksum');
|
||||
|
||||
const avatarShared = require('./avatar/_shared');
|
||||
const config = require('../config');
|
||||
const logger = require('../logging')('routes.profile');
|
||||
|
||||
const DEFAULT_AVATAR_URL = avatarShared.fxaUrl(config.get('img.defaultAvatarId'));
|
||||
|
||||
function hasAllowedScope(scopes) {
|
||||
for (var i = 0, len = scopes.length; i < len; i++) {
|
||||
var scope = scopes[i];
|
||||
|
@ -36,6 +40,7 @@ module.exports = {
|
|||
email: Joi.string().allow(null),
|
||||
uid: Joi.string().allow(null),
|
||||
avatar: Joi.string().allow(null),
|
||||
avatarDefault: Joi.boolean().allow(null),
|
||||
displayName: Joi.string().allow(null),
|
||||
|
||||
//openid-connect
|
||||
|
@ -65,6 +70,14 @@ module.exports = {
|
|||
if (creds.scope.indexOf('openid') !== -1) {
|
||||
result.sub = creds.user;
|
||||
}
|
||||
|
||||
if (result.avatar) {
|
||||
// currently the batch requests extract a single property.
|
||||
// to avoid refactoring the batch requests to support multiple properties,
|
||||
// set the default flag here
|
||||
result.avatarDefault = result.avatar === DEFAULT_AVATAR_URL;
|
||||
}
|
||||
|
||||
var rep = reply(result);
|
||||
var etag = computeEtag(result);
|
||||
if (etag) {
|
||||
|
|
|
@ -3,11 +3,19 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const Hapi = require('hapi');
|
||||
const Boom = require('boom');
|
||||
const path = require('path');
|
||||
const Inert = require('inert');
|
||||
|
||||
const config = require('../config').getProperties();
|
||||
const logger = require('../logging')('server.static');
|
||||
|
||||
const DEFAULT_AVATAR_DIR = path.resolve(__dirname, '..', 'assets');
|
||||
const DEFAULT_AVATAR_ID = config.img.defaultAvatarId;
|
||||
const DEFAULT_AVATAR = path.resolve(DEFAULT_AVATAR_DIR, 'default-profile.png');
|
||||
const DEFAULT_AVATAR_LARGE = path.resolve(DEFAULT_AVATAR_DIR, 'default-profile_large.png');
|
||||
const DEFAULT_AVATAR_SMALL = path.resolve(DEFAULT_AVATAR_DIR, 'default-profile_small.png');
|
||||
|
||||
exports.create = function() {
|
||||
var server = new Hapi.Server({
|
||||
debug: false
|
||||
|
@ -20,6 +28,26 @@ exports.create = function() {
|
|||
port: config.server.port + 1
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/a/' + DEFAULT_AVATAR_ID + '{type?}',
|
||||
handler: function (request, reply) {
|
||||
switch (request.params.type) {
|
||||
case '':
|
||||
reply.file(DEFAULT_AVATAR);
|
||||
break;
|
||||
case '_small':
|
||||
reply.file(DEFAULT_AVATAR_SMALL);
|
||||
break;
|
||||
case '_large':
|
||||
reply.file(DEFAULT_AVATAR_LARGE);
|
||||
break;
|
||||
default:
|
||||
reply(Boom.notFound());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/a/{id}',
|
||||
|
|
10
test/api.js
10
test/api.js
|
@ -13,8 +13,12 @@ const checksum = require('checksum');
|
|||
|
||||
const assert = require('insist');
|
||||
const P = require('../lib/promise');
|
||||
const config = require('../lib/config');
|
||||
const avatarShared = require('../lib/routes/avatar/_shared');
|
||||
const assertSecurityHeaders = require('./lib/util').assertSecurityHeaders;
|
||||
|
||||
const DEFAULT_AVATAR_ID = config.get('img.defaultAvatarId');
|
||||
|
||||
function randomHex(bytes) {
|
||||
return crypto.randomBytes(bytes).toString('hex');
|
||||
}
|
||||
|
@ -71,9 +75,11 @@ describe('/profile', function() {
|
|||
}
|
||||
}).then(function(res) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(Object.keys(res.result).length, 4);
|
||||
assert.equal(res.result.uid, USERID);
|
||||
assert.equal(res.result.email, 'user@example.domain');
|
||||
assert.equal(res.result.avatar, null);
|
||||
assert.equal(res.result.avatar, avatarShared.fxaUrl(DEFAULT_AVATAR_ID), 'return default avatar');
|
||||
assert.equal(res.result.avatarDefault, true, 'has the default avatar flag');
|
||||
assertSecurityHeaders(res);
|
||||
});
|
||||
});
|
||||
|
@ -452,8 +458,10 @@ describe('/avatar', function() {
|
|||
}
|
||||
}).then(function(res) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(Object.keys(res.result).length, 3);
|
||||
assert.equal(res.result.avatar, GRAVATAR);
|
||||
assert.equal(res.result.id, id2);
|
||||
assert.equal(res.result.avatarDefault, false);
|
||||
assertSecurityHeaders(res);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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 assert = require('insist');
|
||||
const avatarShared = require('../../../lib/routes/avatar/_shared');
|
||||
const config = require('../../../lib/config').getProperties();
|
||||
|
||||
/*global describe,it,beforeEach*/
|
||||
describe('routes/avatar/_shared', function () {
|
||||
|
||||
describe('fxaUrl', function () {
|
||||
|
||||
it('creates a proper avatarUrl', function () {
|
||||
const id = 'foo';
|
||||
assert.equal(avatarShared.fxaUrl(id), config.img.url.replace('{id}', id));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
Загрузка…
Ссылка в новой задаче