зеркало из https://github.com/mozilla/fxa.git
feat(fxa-auth-server) set display name from 3rd party auth data
This commit is contained in:
Родитель
bc02c80694
Коммит
123a585850
|
@ -13,6 +13,8 @@ const PATH_PREFIX = '/v1';
|
|||
// here other than that it didn't fail in error
|
||||
const DeleteCacheResponse = isA.any();
|
||||
|
||||
const UpdateDisplayNameResponse = isA.any();
|
||||
|
||||
module.exports = function (log, config, statsd) {
|
||||
const ProfileAPI = createBackendServiceAPI(
|
||||
log,
|
||||
|
@ -29,6 +31,19 @@ module.exports = function (log, config, statsd) {
|
|||
response: DeleteCacheResponse,
|
||||
},
|
||||
},
|
||||
updateDisplayName: {
|
||||
path: `${PATH_PREFIX}/_display_name/:uid`,
|
||||
method: 'POST',
|
||||
validate: {
|
||||
params: {
|
||||
uid: isA.string().required(),
|
||||
},
|
||||
payload: {
|
||||
name: isA.string().required(),
|
||||
},
|
||||
response: UpdateDisplayNameResponse,
|
||||
}
|
||||
},
|
||||
},
|
||||
statsd
|
||||
);
|
||||
|
@ -49,5 +64,13 @@ module.exports = function (log, config, statsd) {
|
|||
throw err;
|
||||
}
|
||||
},
|
||||
async updateDisplayName(uid, name) {
|
||||
try {
|
||||
return await api.updateDisplayName(uid, { name: name });
|
||||
} catch (err) {
|
||||
log.error('profile.updateDisplayName.failed', { uid, name, err});
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -172,7 +172,7 @@ module.exports = function (
|
|||
const util = require('./util')(log, config, config.smtp.redirectDomain);
|
||||
|
||||
const { linkedAccountRoutes } = require('./linked-accounts');
|
||||
const linkedAccounts = linkedAccountRoutes(log, db, config, mailer);
|
||||
const linkedAccounts = linkedAccountRoutes(log, db, config, mailer, profile);
|
||||
|
||||
let basePath = url.parse(config.publicUrl).path;
|
||||
if (basePath === '/') {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* 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/. */
|
||||
import { AuthLogger, AuthRequest } from '../types';
|
||||
import { AuthLogger, AuthRequest, ProfileClient } from '../types';
|
||||
import { ConfigType } from '../../config';
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
import axios from 'axios';
|
||||
|
@ -32,7 +32,8 @@ export class LinkedAccountHandler {
|
|||
private log: AuthLogger,
|
||||
private db: any,
|
||||
private config: ConfigType,
|
||||
private mailer: any
|
||||
private mailer: any,
|
||||
private profile: ProfileClient,
|
||||
) {
|
||||
const tokenCodeConfig = config.signinConfirmation.tokenVerificationCode;
|
||||
this.tokenCodeLifetime =
|
||||
|
@ -166,6 +167,7 @@ export class LinkedAccountHandler {
|
|||
|
||||
const userid = idToken.sub;
|
||||
const email = idToken.email;
|
||||
const name = idToken.name;
|
||||
|
||||
let accountRecord;
|
||||
let linkedAccountRecord = await this.db.getLinkedAccount(userid, provider);
|
||||
|
@ -176,6 +178,10 @@ export class LinkedAccountHandler {
|
|||
accountRecord = await this.db.accountRecord(email);
|
||||
await this.db.createLinkedAccount(accountRecord.uid, userid, provider);
|
||||
|
||||
if (name) {
|
||||
await this.profile.updateDisplayName(accountRecord.uid, name);
|
||||
}
|
||||
|
||||
const geoData = request.app.geo;
|
||||
const ip = request.app.clientAddress;
|
||||
const { deviceId, flowId, flowBeginTime } = await request.app
|
||||
|
@ -237,6 +243,10 @@ export class LinkedAccountHandler {
|
|||
locale: request.app.acceptLanguage,
|
||||
});
|
||||
await this.db.createLinkedAccount(accountRecord.uid, userid, provider);
|
||||
|
||||
if (name) {
|
||||
await this.profile.updateDisplayName(accountRecord.uid, name);
|
||||
}
|
||||
// Currently, we treat accounts created from a linked account as a new
|
||||
// registration and emit the correspond event. Note that depending on
|
||||
// where might not be a top of funnel for this completion event.
|
||||
|
@ -300,9 +310,10 @@ export const linkedAccountRoutes = (
|
|||
log: AuthLogger,
|
||||
db: any,
|
||||
config: ConfigType,
|
||||
mailer: any
|
||||
mailer: any,
|
||||
profile: ProfileClient,
|
||||
) => {
|
||||
const handler = new LinkedAccountHandler(log, db, config, mailer);
|
||||
const handler = new LinkedAccountHandler(log, db, config, mailer, profile);
|
||||
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -67,6 +67,7 @@ export interface AuthRequest extends Request {
|
|||
|
||||
export interface ProfileClient {
|
||||
deleteCache(uid: string): Promise<void>;
|
||||
updateDisplayName(uid: string, name: string): Promise<void>;
|
||||
}
|
||||
|
||||
// Container token types
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/* 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 Joi = require('@hapi/joi');
|
||||
const db = require('../../db');
|
||||
const notifyProfileUpdated = require('../../updates-queue');
|
||||
|
||||
const EMPTY = Object.create(null);
|
||||
|
||||
// We're pretty liberal with what's allowed in a display-name,
|
||||
// but we exclude the following classes of characters:
|
||||
//
|
||||
// \u0000-\u001F - C0 (ascii) control characters
|
||||
// \u007F - ascii DEL character
|
||||
// \u0080-\u009F - C1 (ansi escape) control characters
|
||||
// \u2028-\u2029 - unicode line/paragraph separator
|
||||
// \uE000-\uF8FF - BMP private use area
|
||||
// \uFFF9-\uFFFC - unicode specials prior to the replacement character
|
||||
// \uFFFE-\uFFFF - unicode this-is-not-a-character specials
|
||||
//
|
||||
// Note that the unicode replacement character \uFFFD is explicitly allowed,
|
||||
// and clients may use it to replace other disallowed characters.
|
||||
//
|
||||
// We might tweak this list in future.
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const ALLOWED_DISPLAY_NAME_CHARS = /^(?:[^\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uE000-\uF8FF\uFFF9-\uFFFC\uFFFE-\uFFFF])*$/;
|
||||
|
||||
module.exports = {
|
||||
auth: {
|
||||
strategy: 'secretBearerToken',
|
||||
},
|
||||
validate: {
|
||||
payload: {
|
||||
name: Joi.string()
|
||||
.max(256)
|
||||
.required()
|
||||
.allow('')
|
||||
.regex(ALLOWED_DISPLAY_NAME_CHARS),
|
||||
},
|
||||
params: {
|
||||
uid: Joi.string(),
|
||||
}
|
||||
},
|
||||
handler: async function displayNamePost(req) {
|
||||
const uid = req.params.uid;
|
||||
return req.server.methods.profileCache.drop(uid).then(() => {
|
||||
const payload = req.payload;
|
||||
return db.setDisplayName(uid, payload.name).then(() => {
|
||||
notifyProfileUpdated(uid); // Don't wait on promise
|
||||
return EMPTY;
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
|
@ -84,6 +84,12 @@ module.exports = [
|
|||
path: v('/display_name'),
|
||||
config: require('./routes/display_name/post'),
|
||||
},
|
||||
// This is an internal-only route that allows us to set profile name from the auth server
|
||||
{
|
||||
method: 'POST',
|
||||
path: v('/_display_name/{uid}'),
|
||||
config: require('./routes/display_name/post-from-auth-server'),
|
||||
},
|
||||
{
|
||||
method: 'DELETE',
|
||||
path: v('/cache/{uid}'),
|
||||
|
|
|
@ -1330,6 +1330,19 @@ describe('api', function () {
|
|||
describe('/display_name', function () {
|
||||
var tok = token();
|
||||
|
||||
const EXPECTED_TOKEN = 'thisisnotthedefault';
|
||||
|
||||
let origSecretBearerToken = null;
|
||||
|
||||
before(function () {
|
||||
origSecretBearerToken = config.get('secretBearerToken');
|
||||
config.set('secretBearerToken', EXPECTED_TOKEN);
|
||||
});
|
||||
|
||||
after(function () {
|
||||
config.set('secretBearerToken', origSecretBearerToken);
|
||||
});
|
||||
|
||||
describe('GET', function () {
|
||||
it('should return a displayName', function () {
|
||||
mock.token({
|
||||
|
@ -1420,6 +1433,28 @@ describe('api', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should post a new display name via secretBearerToken', function () {
|
||||
var NAME = 'Spock';
|
||||
return Server.api
|
||||
.post({
|
||||
url: '/_display_name/' + USERID,
|
||||
payload: {
|
||||
name: NAME,
|
||||
},
|
||||
headers: {
|
||||
authorization: 'Bearer ' + EXPECTED_TOKEN,
|
||||
},
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
assertSecurityHeaders(res);
|
||||
return db.getDisplayName(USERID);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.displayName, NAME);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail post if display name longer than 256 chars', function () {
|
||||
var NAME = Array.from('x'.repeat('257')).join('');
|
||||
mock.token({
|
||||
|
|
Загрузка…
Ссылка в новой задаче