зеркало из https://github.com/mozilla/fxa.git
Merge pull request #14547 from mozilla/FXA-298-shared-js-to-ts
chore(typescript): convert more of fxa-shared to TS
This commit is contained in:
Коммит
d81e6cb196
|
@ -52,7 +52,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.js": [
|
"packages/!(fxa-payments-server)/**/*.js": [
|
||||||
"prettier --config _dev/.prettierrc --write",
|
"prettier --config _dev/.prettierrc --write",
|
||||||
"eslint"
|
"eslint"
|
||||||
],
|
],
|
||||||
|
|
|
@ -8,7 +8,7 @@ import path from 'path';
|
||||||
import mysql from 'mysql';
|
import mysql from 'mysql';
|
||||||
import patcher from 'mysql-patcher';
|
import patcher from 'mysql-patcher';
|
||||||
import convict from 'convict';
|
import convict from 'convict';
|
||||||
import { makeMySQLConfig } from 'fxa-shared/db/config.js';
|
import { makeMySQLConfig } from 'fxa-shared/db/config';
|
||||||
const patch = promisify(patcher.patch);
|
const patch = promisify(patcher.patch);
|
||||||
|
|
||||||
const conf = convict({
|
const conf = convict({
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
const { Container } = require('typedi');
|
const { Container } = require('typedi');
|
||||||
const { StatsD } = require('hot-shots');
|
const { StatsD } = require('hot-shots');
|
||||||
|
|
||||||
const { GROUPS, initialize } = require('fxa-shared/metrics/amplitude');
|
const { GROUPS, initialize } =
|
||||||
|
require('fxa-shared/metrics/amplitude').amplitude;
|
||||||
const { version: VERSION } = require('../../package.json');
|
const { version: VERSION } = require('../../package.json');
|
||||||
|
|
||||||
// Maps template name to email type
|
// Maps template name to email type
|
||||||
|
|
|
@ -1175,7 +1175,7 @@ export class AccountHandler {
|
||||||
scope = { contains: () => true };
|
scope = { contains: () => true };
|
||||||
} else {
|
} else {
|
||||||
uid = auth.credentials.user;
|
uid = auth.credentials.user;
|
||||||
scope = ScopeSet.fromArray(auth.credentials.scope);
|
scope = ScopeSet.fromArray(auth.credentials.scope!);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res: Record<string, any> = {};
|
const res: Record<string, any> = {};
|
||||||
|
|
|
@ -13,7 +13,7 @@ const { OAUTH_SCOPE_OLD_SYNC } = require('fxa-shared/oauth/constants');
|
||||||
const encrypt = require('fxa-shared/auth/encrypt');
|
const encrypt = require('fxa-shared/auth/encrypt');
|
||||||
const oauthDB = require('../../oauth/db');
|
const oauthDB = require('../../oauth/db');
|
||||||
const client = require('../../oauth/client');
|
const client = require('../../oauth/client');
|
||||||
const ScopeSet = require('fxa-shared/oauth/scopes');
|
const ScopeSet = require('fxa-shared/oauth/scopes').scopeSetHelpers;
|
||||||
|
|
||||||
// the refresh token scheme is currently used by things connected to sync,
|
// the refresh token scheme is currently used by things connected to sync,
|
||||||
// and we're at a transitionary stage of its evolution into something more generic,
|
// and we're at a transitionary stage of its evolution into something more generic,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
const MISC_DOCS = require('../../docs/swagger/misc-api').default;
|
const MISC_DOCS = require('../../docs/swagger/misc-api').default;
|
||||||
const validators = require('./validators');
|
const validators = require('./validators');
|
||||||
const ScopeSet = require('fxa-shared/oauth/scopes');
|
const ScopeSet = require('fxa-shared/oauth/scopes').scopeSetHelpers;
|
||||||
const AppError = require('../../lib/error');
|
const AppError = require('../../lib/error');
|
||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ export class AppleIapHandler {
|
||||||
// once VPN migration is complete (FXA-5848).
|
// once VPN migration is complete (FXA-5848).
|
||||||
'profile:subscriptions',
|
'profile:subscriptions',
|
||||||
];
|
];
|
||||||
const scope = ScopeSet.fromArray(auth.credentials.scope);
|
const scope = ScopeSet.fromArray(auth.credentials.scope!);
|
||||||
if (!scopes.some((requiredScope) => scope.contains(requiredScope))) {
|
if (!scopes.some((requiredScope) => scope.contains(requiredScope))) {
|
||||||
throw error.invalidScopes();
|
throw error.invalidScopes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export async function handleAuth(
|
||||||
auth: AuthRequest['auth'],
|
auth: AuthRequest['auth'],
|
||||||
fetchEmail = false
|
fetchEmail = false
|
||||||
) {
|
) {
|
||||||
const scope = ScopeSet.fromArray(auth.credentials.scope);
|
const scope = ScopeSet.fromArray(auth.credentials.scope!);
|
||||||
if (!scope.contains(OAUTH_SCOPE_SUBSCRIPTIONS)) {
|
if (!scope.contains(OAUTH_SCOPE_SUBSCRIPTIONS)) {
|
||||||
throw error.invalidScopes();
|
throw error.invalidScopes();
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ export async function handleAuth(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleUidAuth(auth: AuthRequest['auth']): string {
|
export function handleUidAuth(auth: AuthRequest['auth']): string {
|
||||||
const scope = ScopeSet.fromArray(auth.credentials.scope);
|
const scope = ScopeSet.fromArray(auth.credentials.scope!);
|
||||||
if (!scope.contains(OAUTH_SCOPE_SUBSCRIPTIONS)) {
|
if (!scope.contains(OAUTH_SCOPE_SUBSCRIPTIONS)) {
|
||||||
throw error.invalidScopes();
|
throw error.invalidScopes();
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ export function handleUidAuth(auth: AuthRequest['auth']): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleAuthScoped(auth: AuthRequest['auth'], scopes: string[]) {
|
export function handleAuthScoped(auth: AuthRequest['auth'], scopes: string[]) {
|
||||||
const scope = ScopeSet.fromArray(auth.credentials.scope);
|
const scope = ScopeSet.fromArray(auth.credentials.scope!);
|
||||||
for (const requiredScope of scopes) {
|
for (const requiredScope of scopes) {
|
||||||
if (!scope.contains(requiredScope)) {
|
if (!scope.contains(requiredScope)) {
|
||||||
throw error.invalidScopes();
|
throw error.invalidScopes();
|
||||||
|
|
|
@ -8,7 +8,7 @@ const sinon = require('sinon');
|
||||||
const assert = { ...sinon.assert, ...require('chai').assert };
|
const assert = { ...sinon.assert, ...require('chai').assert };
|
||||||
const getRoute = require('../../routes_helpers').getRoute;
|
const getRoute = require('../../routes_helpers').getRoute;
|
||||||
const mocks = require('../../mocks');
|
const mocks = require('../../mocks');
|
||||||
const ScopeSet = require('fxa-shared/oauth/scopes');
|
const ScopeSet = require('fxa-shared/oauth/scopes').scopeSetHelpers;
|
||||||
const error = require('../../../lib/error');
|
const error = require('../../../lib/error');
|
||||||
|
|
||||||
const { INVALID_PARAMETER, MISSING_PARAMETER } = error.ERRNO;
|
const { INVALID_PARAMETER, MISSING_PARAMETER } = error.ERRNO;
|
||||||
|
|
|
@ -34,13 +34,13 @@ const serveStatic = require('serve-static');
|
||||||
|
|
||||||
const sentry = require('../lib/sentry');
|
const sentry = require('../lib/sentry');
|
||||||
const statsd = require('../lib/statsd');
|
const statsd = require('../lib/statsd');
|
||||||
const { cors, routing } = require('fxa-shared/express')();
|
const { cors, routing } = require('fxa-shared/express').express();
|
||||||
const {
|
const {
|
||||||
useSettingsProxy,
|
useSettingsProxy,
|
||||||
modifySettingsStatic,
|
modifySettingsStatic,
|
||||||
} = require('../lib/beta-settings');
|
} = require('../lib/beta-settings');
|
||||||
|
|
||||||
const userAgent = require('fxa-shared/metrics/user-agent');
|
const userAgent = require('fxa-shared/metrics/user-agent').default;
|
||||||
if (!userAgent.isToVersionStringSupported()) {
|
if (!userAgent.isToVersionStringSupported()) {
|
||||||
// npm@3 installs the incorrect version of node-uap, one without `toVersionString`.
|
// npm@3 installs the incorrect version of node-uap, one without `toVersionString`.
|
||||||
// To ensure the correct version is installed, check toVersionString is available.
|
// To ensure the correct version is installed, check toVersionString is available.
|
||||||
|
|
|
@ -23,9 +23,9 @@ const {
|
||||||
mapLocation,
|
mapLocation,
|
||||||
mapOs,
|
mapOs,
|
||||||
validate,
|
validate,
|
||||||
} = require('fxa-shared/metrics/amplitude');
|
} = require('fxa-shared/metrics/amplitude').amplitude;
|
||||||
const logger = require('./logging/log')();
|
const logger = require('./logging/log')();
|
||||||
const ua = require('fxa-shared/metrics/user-agent');
|
const ua = require('fxa-shared/metrics/user-agent').default;
|
||||||
const config = require('./configuration');
|
const config = require('./configuration');
|
||||||
const { version: VERSION } = require('../../package.json');
|
const { version: VERSION } = require('../../package.json');
|
||||||
|
|
||||||
|
|
|
@ -10,20 +10,17 @@ const flowMetrics = require('./flow-metrics');
|
||||||
const log = require('./logging/log')('server.flow-event');
|
const log = require('./logging/log')('server.flow-event');
|
||||||
const geodbConfig = config.get('geodb');
|
const geodbConfig = config.get('geodb');
|
||||||
const geodb = require('fxa-geodb')(geodbConfig);
|
const geodb = require('fxa-geodb')(geodbConfig);
|
||||||
const remoteAddress = require('fxa-shared/express/remote-address')(
|
const remoteAddress =
|
||||||
config.get('clientAddressDepth')
|
require('fxa-shared/express/remote-address').remoteAddress(
|
||||||
);
|
config.get('clientAddressDepth')
|
||||||
const geolocate = require('fxa-shared/express/geo-locate')(geodb)(
|
);
|
||||||
|
const geolocate = require('fxa-shared/express/geo-locate').geolocate(geodb)(
|
||||||
remoteAddress
|
remoteAddress
|
||||||
)(log);
|
)(log);
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const statsd = require('./statsd');
|
const statsd = require('./statsd');
|
||||||
const {
|
const { VERSION, PERFORMANCE_TIMINGS, limitLength, isValidTime } =
|
||||||
VERSION,
|
require('fxa-shared').metrics.flowPerformance;
|
||||||
PERFORMANCE_TIMINGS,
|
|
||||||
limitLength,
|
|
||||||
isValidTime,
|
|
||||||
} = require('fxa-shared').metrics.flowPerformance;
|
|
||||||
|
|
||||||
const VALIDATION_PATTERNS = require('./validation').PATTERNS;
|
const VALIDATION_PATTERNS = require('./validation').PATTERNS;
|
||||||
const DNT_ALLOWED_DATA = ['context', 'entrypoint', 'service'];
|
const DNT_ALLOWED_DATA = ['context', 'entrypoint', 'service'];
|
||||||
|
|
|
@ -10,9 +10,10 @@
|
||||||
const logger = require('./log')('server.requests');
|
const logger = require('./log')('server.requests');
|
||||||
const morgan = require('morgan');
|
const morgan = require('morgan');
|
||||||
const config = require('../configuration').getProperties();
|
const config = require('../configuration').getProperties();
|
||||||
const remoteAddress = require('fxa-shared/express/remote-address')(
|
const remoteAddress =
|
||||||
config.clientAddressDepth
|
require('fxa-shared/express/remote-address').remoteAddress(
|
||||||
);
|
config.clientAddressDepth
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enhances connect logger middleware - custom formats.
|
* Enhances connect logger middleware - custom formats.
|
||||||
|
|
|
@ -12,10 +12,11 @@ const logFlowEvent = require('../flow-event').logFlowEvent;
|
||||||
const logger = require('../logging/log')('server.get-metrics-flow');
|
const logger = require('../logging/log')('server.get-metrics-flow');
|
||||||
const geodbConfig = config.get('geodb');
|
const geodbConfig = config.get('geodb');
|
||||||
const geodb = require('fxa-geodb')(geodbConfig);
|
const geodb = require('fxa-geodb')(geodbConfig);
|
||||||
const remoteAddress = require('fxa-shared/express/remote-address')(
|
const remoteAddress =
|
||||||
config.get('clientAddressDepth')
|
require('fxa-shared/express/remote-address').remoteAddress(
|
||||||
);
|
config.get('clientAddressDepth')
|
||||||
const geolocate = require('fxa-shared/express/geo-locate')(geodb)(
|
);
|
||||||
|
const geolocate = require('fxa-shared/express/geo-locate').geolocate(geodb)(
|
||||||
remoteAddress
|
remoteAddress
|
||||||
)(logger);
|
)(logger);
|
||||||
const uuid = require('node-uuid');
|
const uuid = require('node-uuid');
|
||||||
|
|
|
@ -38,7 +38,12 @@ const amplitude = proxyquire(path.resolve('server/lib/amplitude'), {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'./logging/log': () => logger,
|
'./logging/log': () => logger,
|
||||||
'fxa-shared/metrics/amplitude': { validate: schemaValidatorStub },
|
'fxa-shared/metrics/amplitude': {
|
||||||
|
amplitude: {
|
||||||
|
...require('fxa-shared/metrics/amplitude').amplitude,
|
||||||
|
validate: schemaValidatorStub,
|
||||||
|
},
|
||||||
|
},
|
||||||
'@sentry/node': mockSentry,
|
'@sentry/node': mockSentry,
|
||||||
});
|
});
|
||||||
const Sentry = require('@sentry/node');
|
const Sentry = require('@sentry/node');
|
||||||
|
|
|
@ -50,7 +50,7 @@ registerSuite('flow-event', {
|
||||||
'./amplitude': mocks.amplitude,
|
'./amplitude': mocks.amplitude,
|
||||||
'./configuration': mocks.config,
|
'./configuration': mocks.config,
|
||||||
'./flow-metrics': mocks.flowMetrics,
|
'./flow-metrics': mocks.flowMetrics,
|
||||||
'fxa-shared/express/geo-locate': mocks.geolocate,
|
'fxa-shared/express/geo-locate': { geolocate: mocks.geolocate },
|
||||||
'./statsd': mocks.statsd,
|
'./statsd': mocks.statsd,
|
||||||
}).metricsRequest;
|
}).metricsRequest;
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,7 +53,7 @@ registerSuite('routes/get-metrics-flow', {
|
||||||
route = proxyquire('../../../server/lib/routes/get-metrics-flow', {
|
route = proxyquire('../../../server/lib/routes/get-metrics-flow', {
|
||||||
'../amplitude': mocks.amplitude,
|
'../amplitude': mocks.amplitude,
|
||||||
'../flow-event': mocks.flowEvent,
|
'../flow-event': mocks.flowEvent,
|
||||||
'fxa-shared/express/geo-locate': mocks.geolocate,
|
'fxa-shared/express/geo-locate': { geolocate: mocks.geolocate },
|
||||||
'../logging/log': () => mocks.log,
|
'../logging/log': () => mocks.log,
|
||||||
});
|
});
|
||||||
instance = route(mocks.config);
|
instance = route(mocks.config);
|
||||||
|
|
|
@ -11,7 +11,7 @@ const {
|
||||||
mapOs,
|
mapOs,
|
||||||
toSnakeCase,
|
toSnakeCase,
|
||||||
validate,
|
validate,
|
||||||
} = require('fxa-shared/metrics/amplitude');
|
} = require('fxa-shared/metrics/amplitude').amplitude;
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const amplitude = config.get('amplitude');
|
const amplitude = config.get('amplitude');
|
||||||
const log = require('./logging/log')();
|
const log = require('./logging/log')();
|
||||||
|
|
|
@ -9,9 +9,11 @@ const mockAmplitudeConfig = {
|
||||||
schemaValidation: true,
|
schemaValidation: true,
|
||||||
rawEvents: false,
|
rawEvents: false,
|
||||||
};
|
};
|
||||||
jest.mock('fxa-shared/metrics/amplitude.js', () => ({
|
jest.mock('fxa-shared/metrics/amplitude', () => ({
|
||||||
...jest.requireActual('fxa-shared/metrics/amplitude.js'),
|
amplitude: {
|
||||||
validate: mockSchemaValidatorFn,
|
...jest.requireActual('fxa-shared/metrics/amplitude').amplitude,
|
||||||
|
validate: mockSchemaValidatorFn,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
let scope;
|
let scope;
|
||||||
const mockSentry = {
|
const mockSentry = {
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
const logger = require('./log')('server.requests');
|
const logger = require('./log')('server.requests');
|
||||||
const morgan = require('morgan');
|
const morgan = require('morgan');
|
||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
const remoteAddress = require('fxa-shared/express/remote-address')(
|
const remoteAddress =
|
||||||
config.get('clientAddressDepth')
|
require('fxa-shared/express/remote-address').remoteAddress(
|
||||||
);
|
config.get('clientAddressDepth')
|
||||||
|
);
|
||||||
|
|
||||||
const { enabled, format } = config.get('logging.routes');
|
const { enabled, format } = config.get('logging.routes');
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ module.exports = () => {
|
||||||
const csp = require('../lib/csp');
|
const csp = require('../lib/csp');
|
||||||
const cspRulesBlocking = require('../lib/csp/blocking')(config);
|
const cspRulesBlocking = require('../lib/csp/blocking')(config);
|
||||||
const cspRulesReportOnly = require('../lib/csp/report-only')(config);
|
const cspRulesReportOnly = require('../lib/csp/report-only')(config);
|
||||||
const { cors, routing } = require('fxa-shared/express')();
|
const { cors, routing } = require('fxa-shared/express').express();
|
||||||
const { v4: uuid } = require('uuid');
|
const { v4: uuid } = require('uuid');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
import * as ScopeSet from '../oauth/scopes';
|
import ScopeSet from '../oauth/scopes';
|
||||||
import { IClientFormatter } from './formatters';
|
import { IClientFormatter } from './formatters';
|
||||||
import {
|
import {
|
||||||
AttachedClient,
|
AttachedClient,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import mysql from 'mysql';
|
||||||
|
|
||||||
import { AccessToken as AccessToken } from '../db/models/auth/access-token';
|
import { AccessToken as AccessToken } from '../db/models/auth/access-token';
|
||||||
import { ILogger } from '../log';
|
import { ILogger } from '../log';
|
||||||
import * as ScopeSet from '../oauth/scopes';
|
import ScopeSet from '../oauth/scopes';
|
||||||
|
|
||||||
// TODO: Improve types. Ported form javascript...
|
// TODO: Improve types. Ported form javascript...
|
||||||
const buf = require('buf').hex;
|
const buf = require('buf').hex;
|
||||||
|
|
|
@ -2,4 +2,6 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
module.exports = require('cors');
|
import cors from 'cors';
|
||||||
|
|
||||||
|
export default cors;
|
|
@ -1,16 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
// IP address geolocation
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = (geodb) => (remoteAddress) => (log) => (request) => {
|
|
||||||
try {
|
|
||||||
return geodb(remoteAddress(request).clientAddress);
|
|
||||||
} catch (err) {
|
|
||||||
log.error('geodb.error', err);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
// IP address geolocation
|
||||||
|
|
||||||
|
import express from 'express';
|
||||||
|
import { Logger } from 'mozlog';
|
||||||
|
|
||||||
|
export const geolocate =
|
||||||
|
(geodb: Function) =>
|
||||||
|
(remoteAddress: Function) =>
|
||||||
|
(log: Logger) =>
|
||||||
|
(request: express.Request) => {
|
||||||
|
try {
|
||||||
|
return geodb(remoteAddress(request).clientAddress);
|
||||||
|
} catch (err) {
|
||||||
|
log.error('geodb.error', err);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default geolocate;
|
|
@ -2,12 +2,12 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
module.exports = () => {
|
import cors from './cors';
|
||||||
const cors = require('./cors');
|
import routing from './routing';
|
||||||
const routing = require('./routing');
|
|
||||||
|
|
||||||
return {
|
export const express = () => ({
|
||||||
cors,
|
cors,
|
||||||
routing,
|
routing,
|
||||||
};
|
});
|
||||||
};
|
|
||||||
|
export default express;
|
|
@ -1,31 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
// Utility function to parse the client IP address from request headers.
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const joi = require('joi');
|
|
||||||
|
|
||||||
const IP_ADDRESS = joi.string().ip().required();
|
|
||||||
|
|
||||||
module.exports = (clientIpAddressDepth) => (request) => {
|
|
||||||
let ipAddresses = (request.headers['x-forwarded-for'] || '')
|
|
||||||
.split(',')
|
|
||||||
.map((address) => address.trim());
|
|
||||||
ipAddresses.push(request.ip || request.connection.remoteAddress);
|
|
||||||
ipAddresses = ipAddresses.filter(
|
|
||||||
(ipAddress) => !IP_ADDRESS.validate(ipAddress).error
|
|
||||||
);
|
|
||||||
|
|
||||||
let clientAddressIndex = ipAddresses.length - clientIpAddressDepth;
|
|
||||||
if (clientAddressIndex < 0) {
|
|
||||||
clientAddressIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
addresses: ipAddresses,
|
|
||||||
clientAddress: ipAddresses[clientAddressIndex],
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
// Utility function to parse the client IP address from request headers.
|
||||||
|
|
||||||
|
import express from 'express';
|
||||||
|
import joi from 'joi';
|
||||||
|
|
||||||
|
const IP_ADDRESS = joi.string().ip().required();
|
||||||
|
|
||||||
|
export const remoteAddress =
|
||||||
|
(clientIpAddressDepth: number) => (request: express.Request) => {
|
||||||
|
let ipAddresses = ((request.headers['x-forwarded-for'] as string) || '')
|
||||||
|
.split(',')
|
||||||
|
.map((address) => address.trim());
|
||||||
|
ipAddresses.push(
|
||||||
|
request.ip || (request.connection.remoteAddress as string)
|
||||||
|
);
|
||||||
|
ipAddresses = ipAddresses.filter(
|
||||||
|
(ipAddress) => !IP_ADDRESS.validate(ipAddress).error
|
||||||
|
);
|
||||||
|
|
||||||
|
let clientAddressIndex = ipAddresses.length - clientIpAddressDepth;
|
||||||
|
if (clientAddressIndex < 0) {
|
||||||
|
clientAddressIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
addresses: ipAddresses,
|
||||||
|
clientAddress: ipAddresses[clientAddressIndex],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default remoteAddress;
|
|
@ -2,8 +2,29 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
module.exports = (app, logger) => {
|
import express from 'express';
|
||||||
const cors = require('./cors');
|
import Logger from '../lib/logger';
|
||||||
|
import cors from './cors';
|
||||||
|
|
||||||
|
type RouteMethod =
|
||||||
|
| 'all'
|
||||||
|
| 'get'
|
||||||
|
| 'post'
|
||||||
|
| 'put'
|
||||||
|
| 'delete'
|
||||||
|
| 'patch'
|
||||||
|
| 'options'
|
||||||
|
| 'head';
|
||||||
|
type RouteDefinition = {
|
||||||
|
method: RouteMethod;
|
||||||
|
path: string | RegExp;
|
||||||
|
process: Function;
|
||||||
|
cors?: any;
|
||||||
|
preProcess?: Function;
|
||||||
|
validate?: Object;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const routing = (app: express.Express, logger: Logger) => {
|
||||||
const {
|
const {
|
||||||
celebrate,
|
celebrate,
|
||||||
isCelebrateError: isValidationError,
|
isCelebrateError: isValidationError,
|
||||||
|
@ -26,7 +47,7 @@ module.exports = (app, logger) => {
|
||||||
* @param {Object} [routeDefinition.validate] declare JOI validation.
|
* @param {Object} [routeDefinition.validate] declare JOI validation.
|
||||||
* Follows [celebrate](https://www.npmjs.com/package/celebrate) conventions.
|
* Follows [celebrate](https://www.npmjs.com/package/celebrate) conventions.
|
||||||
*/
|
*/
|
||||||
addRoute(routeDefinition) {
|
addRoute(routeDefinition: RouteDefinition) {
|
||||||
if (!isValidRouteDefinition(routeDefinition)) {
|
if (!isValidRouteDefinition(routeDefinition)) {
|
||||||
logger.error('route definition invalid: ', routeDefinition);
|
logger.error('route definition invalid: ', routeDefinition);
|
||||||
throw new Error('Invalid route definition');
|
throw new Error('Invalid route definition');
|
||||||
|
@ -45,7 +66,10 @@ module.exports = (app, logger) => {
|
||||||
: undefined;
|
: undefined;
|
||||||
// Enable the pre-flight OPTIONS request
|
// Enable the pre-flight OPTIONS request
|
||||||
const corsHandler = cors(corsConfig);
|
const corsHandler = cors(corsConfig);
|
||||||
app.options(routeDefinition.path, corsHandler);
|
app.options(
|
||||||
|
routeDefinition.path as string,
|
||||||
|
corsHandler as express.RequestHandler
|
||||||
|
);
|
||||||
routeHandlers.push(corsHandler);
|
routeHandlers.push(corsHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,10 +87,18 @@ module.exports = (app, logger) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
routeHandlers.push(routeDefinition.process);
|
routeHandlers.push(routeDefinition.process);
|
||||||
app[routeDefinition.method](routeDefinition.path, ...routeHandlers);
|
app[routeDefinition.method as RouteMethod](
|
||||||
|
routeDefinition.path as string,
|
||||||
|
...routeHandlers
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
validationErrorHandler(err, req, res, next) {
|
validationErrorHandler(
|
||||||
|
err: Error,
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
next: express.NextFunction
|
||||||
|
) {
|
||||||
if (err && isValidationError(err)) {
|
if (err && isValidationError(err)) {
|
||||||
logger.error('validation.failed', {
|
logger.error('validation.failed', {
|
||||||
err,
|
err,
|
||||||
|
@ -81,7 +113,9 @@ module.exports = (app, logger) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function isValidRouteDefinition(route) {
|
function isValidRouteDefinition(route: { [key: string]: unknown }) {
|
||||||
return !!route.method && route.path && route.process;
|
return !!route.method && route.path && route.process;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default routing;
|
|
@ -6,6 +6,7 @@ import * as invoice from './dto/auth/payments/invoice';
|
||||||
import * as emailHelpers from './email/helpers';
|
import * as emailHelpers from './email/helpers';
|
||||||
import popularDomains from './email/popularDomains.json';
|
import popularDomains from './email/popularDomains.json';
|
||||||
import BaseGroupingRule from './experiments/base';
|
import BaseGroupingRule from './experiments/base';
|
||||||
|
import express from './express';
|
||||||
import featureFlags from './feature-flags';
|
import featureFlags from './feature-flags';
|
||||||
import { localizeTimestamp } from './l10n/localizeTimestamp';
|
import { localizeTimestamp } from './l10n/localizeTimestamp';
|
||||||
import supportedLanguages from './l10n/supportedLanguages.json';
|
import supportedLanguages from './l10n/supportedLanguages.json';
|
||||||
|
@ -29,6 +30,7 @@ module.exports = {
|
||||||
experiments: {
|
experiments: {
|
||||||
BaseGroupingRule,
|
BaseGroupingRule,
|
||||||
},
|
},
|
||||||
|
express,
|
||||||
featureFlags,
|
featureFlags,
|
||||||
l10n: {
|
l10n: {
|
||||||
localizeTimestamp,
|
localizeTimestamp,
|
||||||
|
|
|
@ -2,9 +2,19 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
'use strict';
|
import Ajv from 'ajv';
|
||||||
|
import { ParsedUserAgentProperties, ParsedUa, ParsedOs } from './user-agent';
|
||||||
|
import { Location } from '../connected-services/models/Location';
|
||||||
|
|
||||||
|
type AmplitudeEventGroup = typeof GROUPS;
|
||||||
|
type AmplitudeEventGroupKey = keyof AmplitudeEventGroup;
|
||||||
|
type AmplitudeEventFuzzyEventGroupMapFn = (category: string) => string;
|
||||||
|
type AmplitudeEventFuzzyEventNameMapFn = (
|
||||||
|
category: string,
|
||||||
|
target: string
|
||||||
|
) => string;
|
||||||
|
type EventData = { [key: string]: any };
|
||||||
|
|
||||||
const Ajv = require('ajv');
|
|
||||||
const ajv = new Ajv();
|
const ajv = new Ajv();
|
||||||
const amplitudeSchema = require('./amplitude-event.1.schema.json');
|
const amplitudeSchema = require('./amplitude-event.1.schema.json');
|
||||||
const validateAmplitudeEvent = ajv.compile(amplitudeSchema);
|
const validateAmplitudeEvent = ajv.compile(amplitudeSchema);
|
||||||
|
@ -72,11 +82,17 @@ const EVENT_PROPERTIES = {
|
||||||
|
|
||||||
function NOP() {}
|
function NOP() {}
|
||||||
|
|
||||||
function mapConnectDeviceFlow(eventType, eventCategory, eventTarget) {
|
function mapConnectDeviceFlow(
|
||||||
|
eventType: string,
|
||||||
|
eventCategory: string,
|
||||||
|
eventTarget: string
|
||||||
|
) {
|
||||||
|
// @ts-ignore
|
||||||
const connect_device_flow = CONNECT_DEVICE_FLOWS[eventCategory];
|
const connect_device_flow = CONNECT_DEVICE_FLOWS[eventCategory];
|
||||||
|
|
||||||
if (connect_device_flow) {
|
if (connect_device_flow) {
|
||||||
const result = { connect_device_flow };
|
const result: { connect_device_flow: string; connect_device_os?: string } =
|
||||||
|
{ connect_device_flow };
|
||||||
|
|
||||||
if (eventTarget) {
|
if (eventTarget) {
|
||||||
result.connect_device_os = eventTarget;
|
result.connect_device_os = eventTarget;
|
||||||
|
@ -84,13 +100,20 @@ function mapConnectDeviceFlow(eventType, eventCategory, eventTarget) {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapEmailType(eventType, eventCategory, eventTarget, data) {
|
function mapEmailType(
|
||||||
|
eventType: string,
|
||||||
|
eventCategory: string,
|
||||||
|
eventTarget: string,
|
||||||
|
data: EventData
|
||||||
|
) {
|
||||||
const email_type = data.emailTypes[eventCategory];
|
const email_type = data.emailTypes[eventCategory];
|
||||||
|
|
||||||
if (email_type) {
|
if (email_type) {
|
||||||
const result = {
|
const result: { [key: string]: string } = {
|
||||||
email_type,
|
email_type,
|
||||||
email_provider: data.emailDomain,
|
email_provider: data.emailDomain,
|
||||||
};
|
};
|
||||||
|
@ -103,41 +126,47 @@ function mapEmailType(eventType, eventCategory, eventTarget, data) {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapSettingsEventProperties(...args) {
|
function mapSettingsEventProperties(...args: [string, string]) {
|
||||||
return {
|
return {
|
||||||
...mapDisconnectReason(...args),
|
...mapDisconnectReason(...args),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDisconnectReason(eventType, eventCategory) {
|
function mapDisconnectReason(eventType: string, eventCategory: string) {
|
||||||
if (eventType === 'disconnect_device' && eventCategory) {
|
if (eventType === 'disconnect_device' && eventCategory) {
|
||||||
return { reason: eventCategory };
|
return { reason: eventCategory };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDomainValidationResult(
|
function mapDomainValidationResult(
|
||||||
eventType,
|
eventType: string,
|
||||||
eventCategory,
|
eventCategory: string,
|
||||||
eventTarget,
|
eventTarget: string,
|
||||||
data
|
data: EventData
|
||||||
) {
|
) {
|
||||||
// This function is called for all fxa_reg event types, only add the event
|
// This function is called for all fxa_reg event types, only add the event
|
||||||
// properties for the results pertaining to domain_validation_result.
|
// properties for the results pertaining to domain_validation_result.
|
||||||
if (eventType === 'domain_validation_result' && eventCategory) {
|
if (eventType === 'domain_validation_result' && eventCategory) {
|
||||||
return { validation_result: eventCategory };
|
return { validation_result: eventCategory };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapSubscriptionUpgradeEventProperties(
|
function mapSubscriptionUpgradeEventProperties(
|
||||||
eventType,
|
eventType: string,
|
||||||
eventCategory,
|
eventCategory: string,
|
||||||
eventTarget,
|
eventTarget: string,
|
||||||
data
|
data: EventData
|
||||||
) {
|
) {
|
||||||
if (data) {
|
if (data) {
|
||||||
const properties = {};
|
const properties: { [key: string]: string } = {};
|
||||||
|
|
||||||
if (data.previousPlanId) {
|
if (data.previousPlanId) {
|
||||||
properties['previous_plan_id'] = data.previousPlanId;
|
properties['previous_plan_id'] = data.previousPlanId;
|
||||||
|
@ -149,16 +178,18 @@ function mapSubscriptionUpgradeEventProperties(
|
||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapSubscriptionPaymentEventProperties(
|
function mapSubscriptionPaymentEventProperties(
|
||||||
eventType,
|
eventType: string,
|
||||||
eventCategory,
|
eventCategory: string,
|
||||||
eventTarget,
|
eventTarget: string,
|
||||||
data
|
data: EventData
|
||||||
) {
|
) {
|
||||||
if (data) {
|
if (data) {
|
||||||
const properties = {};
|
const properties: { [key: string]: string } = {};
|
||||||
|
|
||||||
if (data.sourceCountry) {
|
if (data.sourceCountry) {
|
||||||
properties['source_country'] = data.sourceCountry;
|
properties['source_country'] = data.sourceCountry;
|
||||||
|
@ -174,9 +205,11 @@ function mapSubscriptionPaymentEventProperties(
|
||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate(event) {
|
function validate(event: { [key: string]: any }) {
|
||||||
if (!validateAmplitudeEvent(event)) {
|
if (!validateAmplitudeEvent(event)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid data: ${ajv.errorsText(validateAmplitudeEvent.errors, {
|
`Invalid data: ${ajv.errorsText(validateAmplitudeEvent.errors, {
|
||||||
|
@ -187,7 +220,7 @@ function validate(event) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export const amplitude = {
|
||||||
EVENT_PROPERTIES,
|
EVENT_PROPERTIES,
|
||||||
GROUPS,
|
GROUPS,
|
||||||
mapBrowser,
|
mapBrowser,
|
||||||
|
@ -227,7 +260,23 @@ module.exports = {
|
||||||
*
|
*
|
||||||
* @returns {Function} The mapper function.
|
* @returns {Function} The mapper function.
|
||||||
*/
|
*/
|
||||||
initialize(services, events, fuzzyEvents) {
|
initialize(
|
||||||
|
services: { [key: string]: string },
|
||||||
|
events: {
|
||||||
|
[key: string]: {
|
||||||
|
group: AmplitudeEventGroupKey | Function;
|
||||||
|
event: string | AmplitudeEventFuzzyEventNameMapFn;
|
||||||
|
minimal?: boolean;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
fuzzyEvents: Map<
|
||||||
|
RegExp,
|
||||||
|
{
|
||||||
|
group: AmplitudeEventGroupKey | AmplitudeEventFuzzyEventGroupMapFn;
|
||||||
|
event: string | AmplitudeEventFuzzyEventNameMapFn;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* Map from a source event and it's associated data to an amplitude event.
|
* Map from a source event and it's associated data to an amplitude event.
|
||||||
*
|
*
|
||||||
|
@ -242,7 +291,7 @@ module.exports = {
|
||||||
* numerous to list here, but may be discerned with
|
* numerous to list here, but may be discerned with
|
||||||
* ease by perusing the code.
|
* ease by perusing the code.
|
||||||
*/
|
*/
|
||||||
return (event, data) => {
|
return (event: { [key: string]: any }, data: EventData) => {
|
||||||
if (!event || !data) {
|
if (!event || !data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -288,6 +337,7 @@ module.exports = {
|
||||||
|
|
||||||
let version;
|
let version;
|
||||||
try {
|
try {
|
||||||
|
// @ts-ignore
|
||||||
version = /([0-9]+)\.([0-9]+)$/.exec(data.version)[0];
|
version = /([0-9]+)\.([0-9]+)$/.exec(data.version)[0];
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
|
@ -317,22 +367,28 @@ module.exports = {
|
||||||
device_model: data.formFactor,
|
device_model: data.formFactor,
|
||||||
event_properties: mapEventProperties(
|
event_properties: mapEventProperties(
|
||||||
eventType,
|
eventType,
|
||||||
eventGroup,
|
eventGroup as string,
|
||||||
eventCategory,
|
eventCategory as string,
|
||||||
eventTarget,
|
eventTarget as string,
|
||||||
|
data
|
||||||
|
),
|
||||||
|
user_properties: mapUserProperties(
|
||||||
|
eventGroup as string,
|
||||||
|
eventCategory as string,
|
||||||
data
|
data
|
||||||
),
|
),
|
||||||
user_properties: mapUserProperties(eventGroup, eventCategory, data),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapEventProperties(
|
function mapEventProperties(
|
||||||
eventType,
|
eventType: string,
|
||||||
eventGroup,
|
eventGroup: string,
|
||||||
eventCategory,
|
eventCategory: string,
|
||||||
eventTarget,
|
eventTarget: string,
|
||||||
data
|
data: EventData
|
||||||
) {
|
) {
|
||||||
const { serviceName, clientId } = getServiceNameAndClientId(data);
|
const { serviceName, clientId } = getServiceNameAndClientId(data);
|
||||||
|
|
||||||
|
@ -356,7 +412,7 @@ module.exports = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getServiceNameAndClientId(data) {
|
function getServiceNameAndClientId(data: EventData) {
|
||||||
let serviceName, clientId;
|
let serviceName, clientId;
|
||||||
|
|
||||||
const { service } = data;
|
const { service } = data;
|
||||||
|
@ -372,7 +428,11 @@ module.exports = {
|
||||||
return { serviceName, clientId };
|
return { serviceName, clientId };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapUserProperties(eventGroup, eventCategory, data) {
|
function mapUserProperties(
|
||||||
|
eventGroup: string,
|
||||||
|
eventCategory: string,
|
||||||
|
data: EventData
|
||||||
|
) {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
pruneUnsetValues({
|
pruneUnsetValues({
|
||||||
entrypoint: data.entrypoint,
|
entrypoint: data.entrypoint,
|
||||||
|
@ -395,7 +455,7 @@ module.exports = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapAppendProperties(data) {
|
function mapAppendProperties(data: EventData) {
|
||||||
const servicesUsed = mapServicesUsed(data);
|
const servicesUsed = mapServicesUsed(data);
|
||||||
const experiments = mapExperiments(data);
|
const experiments = mapExperiments(data);
|
||||||
const userPreferences = mapUserPreferences(data);
|
const userPreferences = mapUserPreferences(data);
|
||||||
|
@ -410,9 +470,11 @@ module.exports = {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapServicesUsed(data) {
|
function mapServicesUsed(data: EventData) {
|
||||||
const { serviceName } = getServiceNameAndClientId(data);
|
const { serviceName } = getServiceNameAndClientId(data);
|
||||||
|
|
||||||
if (serviceName) {
|
if (serviceName) {
|
||||||
|
@ -420,12 +482,14 @@ module.exports = {
|
||||||
fxa_services_used: serviceName,
|
fxa_services_used: serviceName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function pruneUnsetValues(data) {
|
function pruneUnsetValues(data: EventData) {
|
||||||
const result = {};
|
const result: Partial<EventData> = {};
|
||||||
|
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
const value = data[key];
|
const value = data[key];
|
||||||
|
@ -438,7 +502,7 @@ function pruneUnsetValues(data) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapExperiments(data) {
|
function mapExperiments(data: EventData) {
|
||||||
const { experiments } = data;
|
const { experiments } = data;
|
||||||
|
|
||||||
if (Array.isArray(experiments) && experiments.length > 0) {
|
if (Array.isArray(experiments) && experiments.length > 0) {
|
||||||
|
@ -448,9 +512,11 @@ function mapExperiments(data) {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapUserPreferences(data) {
|
function mapUserPreferences(data: EventData) {
|
||||||
const { userPreferences } = data;
|
const { userPreferences } = data;
|
||||||
|
|
||||||
// Don't send user preferences metric if there are none!
|
// Don't send user preferences metric if there are none!
|
||||||
|
@ -458,7 +524,7 @@ function mapUserPreferences(data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedUserPreferences = {};
|
const formattedUserPreferences: { [key: string]: any } = {};
|
||||||
for (const pref in userPreferences) {
|
for (const pref in userPreferences) {
|
||||||
formattedUserPreferences[toSnakeCase(pref)] = userPreferences[pref];
|
formattedUserPreferences[toSnakeCase(pref)] = userPreferences[pref];
|
||||||
}
|
}
|
||||||
|
@ -466,7 +532,7 @@ function mapUserPreferences(data) {
|
||||||
return formattedUserPreferences;
|
return formattedUserPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toSnakeCase(string) {
|
function toSnakeCase(string: string) {
|
||||||
return string
|
return string
|
||||||
.replace(/([a-z])([A-Z])/g, (s, c1, c2) => `${c1}_${c2.toLowerCase()}`)
|
.replace(/([a-z])([A-Z])/g, (s, c1, c2) => `${c1}_${c2.toLowerCase()}`)
|
||||||
.replace(/([A-Z])/g, (c) => c.toLowerCase())
|
.replace(/([A-Z])/g, (c) => c.toLowerCase())
|
||||||
|
@ -474,79 +540,102 @@ function toSnakeCase(string) {
|
||||||
.replace(/-/g, '_');
|
.replace(/-/g, '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapSyncDevices(data) {
|
function mapSyncDevices(data: EventData) {
|
||||||
const { devices } = data;
|
const { devices } = data;
|
||||||
|
|
||||||
if (Array.isArray(devices)) {
|
if (Array.isArray(devices)) {
|
||||||
return {
|
return {
|
||||||
sync_device_count: devices.length,
|
sync_device_count: devices.length,
|
||||||
sync_active_devices_day: countDevices(devices, DAY),
|
sync_active_devices_day: countDevices(
|
||||||
sync_active_devices_week: countDevices(devices, WEEK),
|
devices as [{ [key: string]: any }],
|
||||||
sync_active_devices_month: countDevices(devices, FOUR_WEEKS),
|
DAY
|
||||||
|
),
|
||||||
|
sync_active_devices_week: countDevices(
|
||||||
|
devices as [{ [key: string]: any }],
|
||||||
|
WEEK
|
||||||
|
),
|
||||||
|
sync_active_devices_month: countDevices(
|
||||||
|
devices as [{ [key: string]: any }],
|
||||||
|
FOUR_WEEKS
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function countDevices(devices, period) {
|
function countDevices(devices: [{ [key: string]: any }], period: number) {
|
||||||
return devices.filter(
|
return devices.filter(
|
||||||
(device) => device.lastAccessTime >= Date.now() - period
|
(device) => device.lastAccessTime >= Date.now() - period
|
||||||
).length;
|
).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapSyncEngines(data) {
|
function mapSyncEngines(data: EventData) {
|
||||||
const { syncEngines: sync_engines } = data;
|
const { syncEngines: sync_engines } = data;
|
||||||
|
|
||||||
if (Array.isArray(sync_engines) && sync_engines.length > 0) {
|
if (Array.isArray(sync_engines) && sync_engines.length > 0) {
|
||||||
return { sync_engines };
|
return { sync_engines };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapNewsletters(data) {
|
function mapNewsletters(data: EventData) {
|
||||||
let { newsletters } = data;
|
let { newsletters } = data;
|
||||||
if (newsletters) {
|
if (newsletters) {
|
||||||
newsletters = newsletters.map((newsletter) => {
|
newsletters = newsletters.map((newsletter: string) => {
|
||||||
return toSnakeCase(newsletter);
|
return toSnakeCase(newsletter);
|
||||||
});
|
});
|
||||||
return { newsletters, newsletter_state: 'subscribed' };
|
return { newsletters, newsletter_state: 'subscribed' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapBrowser(userAgent) {
|
function mapBrowser(userAgent: ParsedUserAgentProperties) {
|
||||||
return mapUserAgentProperties(userAgent, 'ua', 'browser', 'browserVersion');
|
return mapUserAgentProperties(userAgent, 'ua', 'browser', 'browserVersion');
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapOs(userAgent) {
|
function mapOs(userAgent: ParsedUserAgentProperties) {
|
||||||
return mapUserAgentProperties(userAgent, 'os', 'os', 'osVersion');
|
return mapUserAgentProperties(userAgent, 'os', 'os', 'osVersion');
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapUserAgentProperties(
|
function mapUserAgentProperties(
|
||||||
userAgent,
|
userAgent: ParsedUserAgentProperties,
|
||||||
key,
|
key: keyof ParsedUserAgentProperties,
|
||||||
familyProperty,
|
familyProperty: string,
|
||||||
versionProperty
|
versionProperty: string
|
||||||
) {
|
) {
|
||||||
const group = userAgent[key];
|
const group = userAgent[key];
|
||||||
const { family } = group;
|
const { family } = group;
|
||||||
if (family && family !== 'Other') {
|
if (family && family !== 'Other') {
|
||||||
return {
|
return {
|
||||||
[familyProperty]: family,
|
[familyProperty]: family,
|
||||||
[versionProperty]: group.toVersionString(),
|
[versionProperty]: (group as ParsedUa | ParsedOs).toVersionString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapFormFactor(userAgent) {
|
function mapFormFactor(userAgent: ParsedUserAgentProperties) {
|
||||||
const { brand, family: formFactor } = userAgent.device;
|
const { brand, family: formFactor } = userAgent.device;
|
||||||
if (brand && formFactor && brand !== 'Generic') {
|
if (brand && formFactor && brand !== 'Generic') {
|
||||||
return { formFactor };
|
return { formFactor };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapLocation(location) {
|
function mapLocation(location: Location) {
|
||||||
if (location && (location.country || location.state)) {
|
if (location && (location.country || location.state)) {
|
||||||
return {
|
return {
|
||||||
country: location.country,
|
country: location.country,
|
||||||
region: location.state,
|
region: location.state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default amplitude;
|
|
@ -2,11 +2,9 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const MAX_DATA_LENGTH = 100;
|
const MAX_DATA_LENGTH = 100;
|
||||||
const VERSION = 1;
|
export const VERSION = 1;
|
||||||
const PERFORMANCE_TIMINGS = [
|
export const PERFORMANCE_TIMINGS = [
|
||||||
// These timings are only an approximation, to be used as extra signals
|
// These timings are only an approximation, to be used as extra signals
|
||||||
// when looking for correlations in the flow data. They're not perfect
|
// when looking for correlations in the flow data. They're not perfect
|
||||||
// representations, for instance:
|
// representations, for instance:
|
||||||
|
@ -37,7 +35,7 @@ const PERFORMANCE_TIMINGS = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function limitLength(data) {
|
export function limitLength(data: string) {
|
||||||
if (data && data.length > MAX_DATA_LENGTH) {
|
if (data && data.length > MAX_DATA_LENGTH) {
|
||||||
return data.substr(0, MAX_DATA_LENGTH);
|
return data.substr(0, MAX_DATA_LENGTH);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +43,11 @@ function limitLength(data) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidTime(time, requestReceivedTime, expiry) {
|
export function isValidTime(
|
||||||
|
time: any,
|
||||||
|
requestReceivedTime: number,
|
||||||
|
expiry: number
|
||||||
|
) {
|
||||||
if (typeof time !== 'number') {
|
if (typeof time !== 'number') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -58,9 +60,4 @@ function isValidTime(time, requestReceivedTime, expiry) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export default { VERSION, PERFORMANCE_TIMINGS, limitLength, isValidTime };
|
||||||
VERSION,
|
|
||||||
PERFORMANCE_TIMINGS,
|
|
||||||
limitLength,
|
|
||||||
isValidTime,
|
|
||||||
};
|
|
|
@ -1,9 +1,13 @@
|
||||||
const joi = require('joi');
|
/* 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 joi from 'joi';
|
||||||
|
|
||||||
const TIMESTAMP_MS = joi.number().required();
|
const TIMESTAMP_MS = joi.number().required();
|
||||||
// Validate the subset of PerformanceNavgationTiming properties used here.
|
// Validate the subset of PerformanceNavgationTiming properties used here.
|
||||||
// (Ref: https://www.w3.org/TR/navigation-timing-2/#dom-performancenavigationtiming)
|
// (Ref: https://www.w3.org/TR/navigation-timing-2/#dom-performancenavigationtiming)
|
||||||
module.exports.navigationTimingSchema = joi.object().keys({
|
export const navigationTimingSchema = joi.object().keys({
|
||||||
domainLookupStart: TIMESTAMP_MS.min(0),
|
domainLookupStart: TIMESTAMP_MS.min(0),
|
||||||
domComplete: TIMESTAMP_MS.min(joi.ref('domInteractive')),
|
domComplete: TIMESTAMP_MS.min(joi.ref('domInteractive')),
|
||||||
domInteractive: TIMESTAMP_MS.min(joi.ref('responseEnd')),
|
domInteractive: TIMESTAMP_MS.min(joi.ref('responseEnd')),
|
||||||
|
@ -15,3 +19,5 @@ module.exports.navigationTimingSchema = joi.object().keys({
|
||||||
responseEnd: TIMESTAMP_MS.min(joi.ref('responseStart')),
|
responseEnd: TIMESTAMP_MS.min(joi.ref('responseStart')),
|
||||||
responseStart: TIMESTAMP_MS.min(joi.ref('requestStart')),
|
responseStart: TIMESTAMP_MS.min(joi.ref('requestStart')),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default { navigationTimingSchema };
|
|
@ -1,25 +0,0 @@
|
||||||
export type ParsedUa = {
|
|
||||||
family: string | null;
|
|
||||||
major: string | null;
|
|
||||||
minor: string | null;
|
|
||||||
patch: string | null;
|
|
||||||
toVersionString: () => string;
|
|
||||||
};
|
|
||||||
export type ParsedOs = ParsedUa & {
|
|
||||||
patchMinor: string | null;
|
|
||||||
};
|
|
||||||
export type ParsedDevice = {
|
|
||||||
family: string | null;
|
|
||||||
brand: string | null;
|
|
||||||
model: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ParsedUserAgentProperties = {
|
|
||||||
ua: ParsedUa;
|
|
||||||
os: ParsedOs;
|
|
||||||
device: ParsedDevice;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function parse(uaString: string | undefined): ParsedUserAgentProperties;
|
|
||||||
|
|
||||||
export function isToVersionStringSupported(ua: ParsedUa): boolean;
|
|
|
@ -2,12 +2,32 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
export type ParsedUa = {
|
||||||
|
family: string | null;
|
||||||
|
major: string | null;
|
||||||
|
minor: string | null;
|
||||||
|
patch: string | null;
|
||||||
|
toVersionString: () => string;
|
||||||
|
};
|
||||||
|
export type ParsedOs = ParsedUa & {
|
||||||
|
patchMinor: string | null;
|
||||||
|
};
|
||||||
|
export type ParsedDevice = {
|
||||||
|
family: string | null;
|
||||||
|
brand: string | null;
|
||||||
|
model: string | null;
|
||||||
|
};
|
||||||
|
export type ParsedUserAgentProperties = {
|
||||||
|
ua: ParsedUa;
|
||||||
|
os: ParsedOs;
|
||||||
|
device: ParsedDevice;
|
||||||
|
};
|
||||||
|
|
||||||
// Safe wrapper around node-uap, which prevents unsafe input from
|
// Safe wrapper around node-uap, which prevents unsafe input from
|
||||||
// leaking back to the result data.
|
// leaking back to the result data.
|
||||||
|
|
||||||
'use strict';
|
// @ts-ignore
|
||||||
|
import * as ua from 'node-uap';
|
||||||
const ua = require('node-uap');
|
|
||||||
|
|
||||||
// We know this won't match "Symbian^3", "UI/WKWebView" or "Mail.ru" but
|
// We know this won't match "Symbian^3", "UI/WKWebView" or "Mail.ru" but
|
||||||
// it's simpler and safer to limit to alphanumerics, underscore and space.
|
// it's simpler and safer to limit to alphanumerics, underscore and space.
|
||||||
|
@ -15,7 +35,9 @@ const VALID_FAMILY = /^[\w ]{1,32}$/;
|
||||||
|
|
||||||
const VALID_VERSION = /^[\w.]{1,16}$/;
|
const VALID_VERSION = /^[\w.]{1,16}$/;
|
||||||
|
|
||||||
exports.parse = (userAgentString) => {
|
export const parse = (
|
||||||
|
userAgentString: string | undefined
|
||||||
|
): ParsedUserAgentProperties => {
|
||||||
const result = ua.parse(userAgentString);
|
const result = ua.parse(userAgentString);
|
||||||
|
|
||||||
safeFamily(result.ua);
|
safeFamily(result.ua);
|
||||||
|
@ -27,7 +49,9 @@ exports.parse = (userAgentString) => {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.isToVersionStringSupported = (result) => {
|
export const isToVersionStringSupported = (
|
||||||
|
result: ParsedUserAgentProperties
|
||||||
|
): boolean => {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = exports.parse(
|
result = exports.parse(
|
||||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:65.0) Gecko/20100101 Firefox/65.0'
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:65.0) Gecko/20100101 Firefox/65.0'
|
||||||
|
@ -45,13 +69,13 @@ exports.isToVersionStringSupported = (result) => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
function safeFamily(parent) {
|
function safeFamily(parent: ParsedUa) {
|
||||||
if (!VALID_FAMILY.test(parent.family)) {
|
if (!VALID_FAMILY.test(parent.family as string)) {
|
||||||
parent.family = null;
|
parent.family = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeVersion(parent) {
|
function safeVersion(parent: ParsedOs) {
|
||||||
if (
|
if (
|
||||||
parent &&
|
parent &&
|
||||||
parent.toVersionString &&
|
parent.toVersionString &&
|
||||||
|
@ -60,3 +84,5 @@ function safeVersion(parent) {
|
||||||
parent.major = parent.minor = parent.patch = parent.patchMinor = null;
|
parent.major = parent.minor = parent.patch = parent.patchMinor = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default { parse, isToVersionStringSupported };
|
|
@ -1,18 +0,0 @@
|
||||||
/* 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 OAUTH_SCOPE_SUBSCRIPTIONS =
|
|
||||||
'https://identity.mozilla.com/account/subscriptions';
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
OAUTH_SCOPE_OLD_SYNC: 'https://identity.mozilla.com/apps/oldsync',
|
|
||||||
OAUTH_SCOPE_SESSION_TOKEN: 'https://identity.mozilla.com/tokens/session',
|
|
||||||
OAUTH_SCOPE_NEWSLETTERS: 'https://identity.mozilla.com/account/newsletters',
|
|
||||||
OAUTH_SCOPE_SUBSCRIPTIONS,
|
|
||||||
OAUTH_SCOPE_SUBSCRIPTIONS_IAP: `${OAUTH_SCOPE_SUBSCRIPTIONS}/iap`,
|
|
||||||
SHORT_ACCESS_TOKEN_TTL_IN_MS: 1000 * 60 * 60 * 6,
|
|
||||||
// Maximum age an account is considered "new", useful when sending
|
|
||||||
// notification emails
|
|
||||||
MAX_NEW_ACCOUNT_AGE: 1000 * 60 * 60 * 24,
|
|
||||||
};
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
export const OAUTH_SCOPE_SUBSCRIPTIONS =
|
||||||
|
'https://identity.mozilla.com/account/subscriptions';
|
||||||
|
|
||||||
|
export const OAUTH_SCOPE_OLD_SYNC = 'https://identity.mozilla.com/apps/oldsync';
|
||||||
|
export const OAUTH_SCOPE_SESSION_TOKEN =
|
||||||
|
'https://identity.mozilla.com/tokens/session';
|
||||||
|
export const OAUTH_SCOPE_NEWSLETTERS =
|
||||||
|
'https://identity.mozilla.com/account/newsletters';
|
||||||
|
export const OAUTH_SCOPE_SUBSCRIPTIONS_IAP = `${OAUTH_SCOPE_SUBSCRIPTIONS}/iap`;
|
||||||
|
export const SHORT_ACCESS_TOKEN_TTL_IN_MS = 1000 * 60 * 60 * 6;
|
||||||
|
// Maximum age an account is considered "new"; useful when sending
|
||||||
|
// notification emails
|
||||||
|
export const MAX_NEW_ACCOUNT_AGE = 1000 * 60 * 60 * 24;
|
||||||
|
|
||||||
|
export const OauthConsts = {
|
||||||
|
OAUTH_SCOPE_OLD_SYNC,
|
||||||
|
OAUTH_SCOPE_SESSION_TOKEN,
|
||||||
|
OAUTH_SCOPE_NEWSLETTERS,
|
||||||
|
OAUTH_SCOPE_SUBSCRIPTIONS,
|
||||||
|
OAUTH_SCOPE_SUBSCRIPTIONS_IAP,
|
||||||
|
SHORT_ACCESS_TOKEN_TTL_IN_MS,
|
||||||
|
MAX_NEW_ACCOUNT_AGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OauthConsts;
|
|
@ -2,9 +2,9 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
'use strict';
|
import { URL } from 'url';
|
||||||
|
|
||||||
const { URL } = require('url');
|
type Coerceable = ScopeSet | string[] | string;
|
||||||
|
|
||||||
// These character ranges are from the OAuth RFC,
|
// These character ranges are from the OAuth RFC,
|
||||||
// https://tools.ietf.org/html/rfc6749#section-3.3
|
// https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
|
@ -75,7 +75,10 @@ const VALID_FRAGMENT_VALUE = /^#[a-zA-Z0-9_]+$/;
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class ScopeSet {
|
class ScopeSet {
|
||||||
constructor(scopes = []) {
|
private _scopesToImplicants: { [key: string]: Set<string> };
|
||||||
|
private _implicantsToScopes: { [key: string]: Set<string> };
|
||||||
|
|
||||||
|
constructor(scopes: string[] = []) {
|
||||||
// To support efficient lookups, we store the set of scopes and
|
// To support efficient lookups, we store the set of scopes and
|
||||||
// their fully-expanded set of implicants in a bi-directional mapping.
|
// their fully-expanded set of implicants in a bi-directional mapping.
|
||||||
//
|
//
|
||||||
|
@ -103,7 +106,7 @@ class ScopeSet {
|
||||||
* Check whether this `ScopeSet` contains the given scope.
|
* Check whether this `ScopeSet` contains the given scope.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_hasScope(scope) {
|
_hasScope(scope: string) {
|
||||||
return scope in this._scopesToImplicants;
|
return scope in this._scopesToImplicants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +114,7 @@ class ScopeSet {
|
||||||
* Check whether this `ScopeSet` contains one of the given scopes.
|
* Check whether this `ScopeSet` contains one of the given scopes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_hasSomeScope(scopes) {
|
_hasSomeScope(scopes: Set<string>) {
|
||||||
for (const scope of scopes) {
|
for (const scope of scopes) {
|
||||||
if (this._hasScope(scope)) {
|
if (this._hasScope(scope)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -125,7 +128,7 @@ class ScopeSet {
|
||||||
* the given scope.
|
* the given scope.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_hasImplicant(scope) {
|
_hasImplicant(scope: string) {
|
||||||
return scope in this._implicantsToScopes;
|
return scope in this._implicantsToScopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +137,7 @@ class ScopeSet {
|
||||||
* one of the given scopes.
|
* one of the given scopes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_hasSomeImplicant(scopes) {
|
_hasSomeImplicant(scopes: string[]) {
|
||||||
for (const scope of scopes) {
|
for (const scope of scopes) {
|
||||||
if (scope in this._implicantsToScopes) {
|
if (scope in this._implicantsToScopes) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -148,7 +151,7 @@ class ScopeSet {
|
||||||
* corresponding sets of implicants.
|
* corresponding sets of implicants.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_iterScopes(cb) {
|
_iterScopes(cb: (scope: string, implicants: Set<string>) => void) {
|
||||||
for (const scope in this._scopesToImplicants) {
|
for (const scope in this._scopesToImplicants) {
|
||||||
cb(scope, this._scopesToImplicants[scope]);
|
cb(scope, this._scopesToImplicants[scope]);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +162,7 @@ class ScopeSet {
|
||||||
* corresponding sets of implied scopes.
|
* corresponding sets of implied scopes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_iterImplicants(cb) {
|
_iterImplicants(cb: (implicant: string, scopes: Set<string>) => void) {
|
||||||
for (const implicant in this._implicantsToScopes) {
|
for (const implicant in this._implicantsToScopes) {
|
||||||
cb(implicant, this._implicantsToScopes[implicant]);
|
cb(implicant, this._implicantsToScopes[implicant]);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +173,7 @@ class ScopeSet {
|
||||||
* the given scope.
|
* the given scope.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_iterImpliedScopes(implicant, cb) {
|
_iterImpliedScopes(implicant: string, cb: (s: string) => void) {
|
||||||
const impliedScopes = this._implicantsToScopes[implicant];
|
const impliedScopes = this._implicantsToScopes[implicant];
|
||||||
if (impliedScopes) {
|
if (impliedScopes) {
|
||||||
for (const impliedScope of impliedScopes) {
|
for (const impliedScope of impliedScopes) {
|
||||||
|
@ -185,7 +188,7 @@ class ScopeSet {
|
||||||
* at the first successful match.
|
* at the first successful match.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_searchScopes(cb) {
|
_searchScopes(cb: (scope: string, implicants: Set<string>) => boolean) {
|
||||||
for (const scope in this._scopesToImplicants) {
|
for (const scope in this._scopesToImplicants) {
|
||||||
if (cb(scope, this._scopesToImplicants[scope])) {
|
if (cb(scope, this._scopesToImplicants[scope])) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -200,7 +203,7 @@ class ScopeSet {
|
||||||
* at the first successful match.
|
* at the first successful match.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_searchImplicants(cb) {
|
_searchImplicants(cb: (implicant: string, scopes: Set<string>) => boolean) {
|
||||||
for (const implicant in this._implicantsToScopes) {
|
for (const implicant in this._implicantsToScopes) {
|
||||||
if (cb(implicant, this._implicantsToScopes[implicant])) {
|
if (cb(implicant, this._implicantsToScopes[implicant])) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -216,7 +219,7 @@ class ScopeSet {
|
||||||
* order to keep memory usage down and simplify further handling.
|
* order to keep memory usage down and simplify further handling.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_addScope(scope, implicants) {
|
_addScope(scope: string, implicants: Set<string>) {
|
||||||
// If the scope is already implied by something in this `ScopeSet`,
|
// If the scope is already implied by something in this `ScopeSet`,
|
||||||
// then we can safely ignore it.
|
// then we can safely ignore it.
|
||||||
if (this._hasSomeScope(implicants)) {
|
if (this._hasSomeScope(implicants)) {
|
||||||
|
@ -244,7 +247,7 @@ class ScopeSet {
|
||||||
* scopes and their implicants.
|
* scopes and their implicants.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_removeScope(scope) {
|
_removeScope(scope: string) {
|
||||||
const implicants = this._scopesToImplicants[scope];
|
const implicants = this._scopesToImplicants[scope];
|
||||||
for (const implicant of implicants) {
|
for (const implicant of implicants) {
|
||||||
const impliedScopes = this._implicantsToScopes[implicant];
|
const impliedScopes = this._implicantsToScopes[implicant];
|
||||||
|
@ -307,8 +310,8 @@ class ScopeSet {
|
||||||
* quadratic" performance traps.
|
* quadratic" performance traps.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
add(other) {
|
add(other: Coerceable) {
|
||||||
other = coerce(other)._iterScopes((scope, implicants) => {
|
coerce(other)._iterScopes((scope, implicants) => {
|
||||||
this._addScope(scope, implicants);
|
this._addScope(scope, implicants);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -322,7 +325,7 @@ class ScopeSet {
|
||||||
* than `B`.
|
* than `B`.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
contains(other) {
|
contains(other: Coerceable) {
|
||||||
return !coerce(other)._searchScopes((scope, implicants) => {
|
return !coerce(other)._searchScopes((scope, implicants) => {
|
||||||
return !this._hasSomeScope(implicants);
|
return !this._hasSomeScope(implicants);
|
||||||
});
|
});
|
||||||
|
@ -336,14 +339,14 @@ class ScopeSet {
|
||||||
* some scope value in `A` that is implied by a scope value in `B`.
|
* some scope value in `A` that is implied by a scope value in `B`.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
intersects(other) {
|
intersects(other: Parameters<typeof coerce>[number]) {
|
||||||
other = coerce(other);
|
other = coerce(other);
|
||||||
return (
|
return (
|
||||||
other._searchImplicants((implicant) => {
|
other._searchImplicants((implicant) => {
|
||||||
return this._hasScope(implicant);
|
return this._hasScope(implicant);
|
||||||
}) ||
|
}) ||
|
||||||
this._searchImplicants((implicant) => {
|
this._searchImplicants((implicant) => {
|
||||||
return other._hasScope(implicant);
|
return (other as ScopeSet)._hasScope(implicant);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -375,11 +378,11 @@ class ScopeSet {
|
||||||
* directly a member of `A`, it will not appear in the result.
|
* directly a member of `A`, it will not appear in the result.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
filtered(other) {
|
filtered(other: Coerceable) {
|
||||||
other = coerce(other);
|
other = coerce(other);
|
||||||
const result = new ScopeSet();
|
const result = new ScopeSet();
|
||||||
this._iterScopes((scope, implicants) => {
|
this._iterScopes((scope, implicants) => {
|
||||||
if (other._hasSomeScope(implicants)) {
|
if ((other as ScopeSet)._hasSomeScope(implicants)) {
|
||||||
result._addScope(scope, implicants);
|
result._addScope(scope, implicants);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -396,11 +399,11 @@ class ScopeSet {
|
||||||
* `A.filtered(B)`. It returns a new `ScopeSet` object.
|
* `A.filtered(B)`. It returns a new `ScopeSet` object.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
difference(other) {
|
difference(other: Coerceable) {
|
||||||
other = coerce(other);
|
other = coerce(other);
|
||||||
const result = new ScopeSet();
|
const result = new ScopeSet();
|
||||||
this._iterScopes((scope, implicants) => {
|
this._iterScopes((scope, implicants) => {
|
||||||
if (!other._hasSomeScope(implicants)) {
|
if (!(other as ScopeSet)._hasSomeScope(implicants)) {
|
||||||
result._addScope(scope, implicants);
|
result._addScope(scope, implicants);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -415,7 +418,7 @@ class ScopeSet {
|
||||||
* either `A` or `B` (or both). It returns a new `ScopeSet` object.
|
* either `A` or `B` (or both). It returns a new `ScopeSet` object.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
union(other) {
|
union(other: Coerceable) {
|
||||||
other = coerce(other);
|
other = coerce(other);
|
||||||
const result = new ScopeSet();
|
const result = new ScopeSet();
|
||||||
this._iterScopes((scope, implicants) => {
|
this._iterScopes((scope, implicants) => {
|
||||||
|
@ -433,7 +436,7 @@ class ScopeSet {
|
||||||
* kinds of input data into a `ScopeSet` instance.
|
* kinds of input data into a `ScopeSet` instance.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function coerce(scopes) {
|
function coerce(scopes: Coerceable) {
|
||||||
if (scopes instanceof ScopeSet) {
|
if (scopes instanceof ScopeSet) {
|
||||||
return scopes;
|
return scopes;
|
||||||
}
|
}
|
||||||
|
@ -452,7 +455,7 @@ function coerce(scopes) {
|
||||||
* An iterator yielding all implicants of the given scope value.
|
* An iterator yielding all implicants of the given scope value.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function getImplicantValues(value) {
|
function getImplicantValues(value: string) {
|
||||||
if (value.startsWith('https:')) {
|
if (value.startsWith('https:')) {
|
||||||
return getImplicantValuesForURLScope(value);
|
return getImplicantValuesForURLScope(value);
|
||||||
} else {
|
} else {
|
||||||
|
@ -478,7 +481,7 @@ function getImplicantValues(value) {
|
||||||
* only "write" scopes can imply other "write" scopes.
|
* only "write" scopes can imply other "write" scopes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function* getImplicantValuesForShortScope(value) {
|
function* getImplicantValuesForShortScope(value: string) {
|
||||||
if (!VALID_SCOPE_VALUE.test(value)) {
|
if (!VALID_SCOPE_VALUE.test(value)) {
|
||||||
throw new Error('Invalid scope value: ' + value);
|
throw new Error('Invalid scope value: ' + value);
|
||||||
}
|
}
|
||||||
|
@ -522,7 +525,7 @@ function* getImplicantValuesForShortScope(value) {
|
||||||
* URL is a child of another.
|
* URL is a child of another.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function* getImplicantValuesForURLScope(value) {
|
function* getImplicantValuesForURLScope(value: string) {
|
||||||
if (!VALID_SCOPE_VALUE.test(value)) {
|
if (!VALID_SCOPE_VALUE.test(value)) {
|
||||||
throw new Error('Invalid scope value: ' + value);
|
throw new Error('Invalid scope value: ' + value);
|
||||||
}
|
}
|
||||||
|
@ -532,7 +535,7 @@ function* getImplicantValuesForURLScope(value) {
|
||||||
throw new Error('Invalid scope value: ' + value);
|
throw new Error('Invalid scope value: ' + value);
|
||||||
}
|
}
|
||||||
// No credentials or query params are allowed.
|
// No credentials or query params are allowed.
|
||||||
if (url.username || url.password || url.query) {
|
if (url.username || url.password || url.search) {
|
||||||
throw new Error('Invalid scope value: ' + value);
|
throw new Error('Invalid scope value: ' + value);
|
||||||
}
|
}
|
||||||
// The pathname must be non-empty and not end in a slash.
|
// The pathname must be non-empty and not end in a slash.
|
||||||
|
@ -561,12 +564,12 @@ function* getImplicantValuesForURLScope(value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export const scopeSetHelpers = {
|
||||||
/**
|
/**
|
||||||
* Parse a list of strings into a Scope object.
|
* Parse a list of strings into a Scope object.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fromArray(scopesArray) {
|
fromArray(scopesArray: string[]) {
|
||||||
return new ScopeSet(scopesArray);
|
return new ScopeSet(scopesArray);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -578,7 +581,7 @@ module.exports = {
|
||||||
* case-sensitive strings identifying individual scope values.
|
* case-sensitive strings identifying individual scope values.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fromString(scopesString) {
|
fromString(scopesString: string) {
|
||||||
// Split the string by one or more space characters.
|
// Split the string by one or more space characters.
|
||||||
return new ScopeSet(
|
return new ScopeSet(
|
||||||
scopesString.split(/ +/).filter((scopeString) => {
|
scopesString.split(/ +/).filter((scopeString) => {
|
||||||
|
@ -597,7 +600,9 @@ module.exports = {
|
||||||
* characters in the scopes will be expanded.
|
* characters in the scopes will be expanded.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fromURLEncodedString(encodedScopesString) {
|
fromURLEncodedString(
|
||||||
|
encodedScopesString: ReturnType<typeof encodeURIComponent>
|
||||||
|
) {
|
||||||
// Split the string by a literal plus character.
|
// Split the string by a literal plus character.
|
||||||
return new ScopeSet(
|
return new ScopeSet(
|
||||||
encodedScopesString
|
encodedScopesString
|
||||||
|
@ -611,3 +616,5 @@ module.exports = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default scopeSetHelpers;
|
|
@ -5,7 +5,52 @@
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
"./": "./dist/"
|
"./*": "./dist/*",
|
||||||
|
"./auth": "./dist/auth/index.js",
|
||||||
|
"./auth/*": "./dist/auth/*.js",
|
||||||
|
"./cache/*": "./dist/cache/*.js",
|
||||||
|
"./configuration/*": "./dist/configuration/*.js",
|
||||||
|
"./connected-services": "./dist/connected-services/index.js",
|
||||||
|
"./connected-services/*": "./dist/connected-services/*.js",
|
||||||
|
"./coverage/*": "./dist/coverage/*.js",
|
||||||
|
"./db": "./dist/db/index.js",
|
||||||
|
"./db/*": "./dist/db/*.js",
|
||||||
|
"./db/config": "./dist/db/config.js",
|
||||||
|
"./db/models": "./dist/db/models/index.js",
|
||||||
|
"./db/models/auth": "./dist/db/models/auth/index.js",
|
||||||
|
"./db/models/auth/*": "./dist/db/models/auth/*.js",
|
||||||
|
"./db/models/profile": "./dist/db/models/profile/index.js",
|
||||||
|
"./db/mysql": "./dist/db/mysql.js",
|
||||||
|
"./db/redis": "./dist/db/redis.js",
|
||||||
|
"./dto/*": "./dist/dto/*.js",
|
||||||
|
"./email/*": "./dist/email/*.js",
|
||||||
|
"./email/popularDomains.json": "./dist/email/popularDomains.json",
|
||||||
|
"./experiments/*": "./dist/experiments/*.js",
|
||||||
|
"./express": "./dist/express/index.js",
|
||||||
|
"./express/*": "./dist/express/*.js",
|
||||||
|
"./feature-flags": "./dist/feature-flags/index.js",
|
||||||
|
"./feature-flags/*": "./dist/feature-flags/*.js",
|
||||||
|
"./guards": "./dist/guards/index.js",
|
||||||
|
"./guards/*": "./dist/guards/*.js",
|
||||||
|
"./l10n/*": "./dist/l10n/*.js",
|
||||||
|
"./lib/*": "./dist/lib/*.js",
|
||||||
|
"./log": "./dist/log/index.js",
|
||||||
|
"./log/*": "./dist/log/*.js",
|
||||||
|
"./metrics/*": "./dist/metrics/*.js",
|
||||||
|
"./nestjs/*": "./dist/nestjs/*.js",
|
||||||
|
"./oauth/*": "./dist/oauth/*.js",
|
||||||
|
"./payments/*": "./dist/payments/*.js",
|
||||||
|
"./payments/configuration/*": "./dist/payments/configuration/*.js",
|
||||||
|
"./payments/iap/*": "./dist/payments/iap/*.js",
|
||||||
|
"./payments/iap/apple-app-store/types": "./dist/payments/iap/apple-app-store/types/index.js",
|
||||||
|
"./payments/iap/google-play/types": "./dist/payments/iap/google-play/types/index.js",
|
||||||
|
"./payments/stripe": "./dist/payments/stripe.js",
|
||||||
|
"./payments/stripe-firestore": "./dist/payments/stripe-firestore.js",
|
||||||
|
"./scripts/*": "./dist/scripts/*.js",
|
||||||
|
"./sentry": "./dist/sentry/index.js",
|
||||||
|
"./sentry/*": "./dist/sentry/*.js",
|
||||||
|
"./subscriptions/*": "./dist/subscriptions/*.js",
|
||||||
|
"./tracing/*": "./dist/tracing/*.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "yarn build || true",
|
"postinstall": "yarn build || true",
|
||||||
|
@ -43,6 +88,7 @@
|
||||||
"@types/accept-language-parser": "^1.5.3",
|
"@types/accept-language-parser": "^1.5.3",
|
||||||
"@types/chai": "^4.2.18",
|
"@types/chai": "^4.2.18",
|
||||||
"@types/chance": "^1.1.2",
|
"@types/chance": "^1.1.2",
|
||||||
|
"@types/express": "^4.17.12",
|
||||||
"@types/generic-pool": "^3.1.9",
|
"@types/generic-pool": "^3.1.9",
|
||||||
"@types/i18n-abide": "^0",
|
"@types/i18n-abide": "^0",
|
||||||
"@types/ioredis": "^4.26.4",
|
"@types/ioredis": "^4.26.4",
|
||||||
|
@ -106,8 +152,6 @@
|
||||||
"@sentry/browser": "^6.19.7",
|
"@sentry/browser": "^6.19.7",
|
||||||
"@sentry/integrations": "^6.19.1",
|
"@sentry/integrations": "^6.19.1",
|
||||||
"@sentry/node": "^6.19.1",
|
"@sentry/node": "^6.19.1",
|
||||||
"@types/js-md5": "^0.4.2",
|
|
||||||
"@types/uuid": "^8.3.1",
|
|
||||||
"accept-language-parser": "^1.5.0",
|
"accept-language-parser": "^1.5.0",
|
||||||
"ajv": "^8.11.0",
|
"ajv": "^8.11.0",
|
||||||
"apollo-server": "^2.26.0",
|
"apollo-server": "^2.26.0",
|
||||||
|
@ -120,6 +164,7 @@
|
||||||
"cldr-localenames-full": "42.0.0",
|
"cldr-localenames-full": "42.0.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"db-migrations": "workspace:*",
|
"db-migrations": "workspace:*",
|
||||||
|
"express": "^4.17.2",
|
||||||
"find-up": "^5.0.0",
|
"find-up": "^5.0.0",
|
||||||
"generic-pool": "^3.8.2",
|
"generic-pool": "^3.8.2",
|
||||||
"graphql": "^15.6.1",
|
"graphql": "^15.6.1",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { assert } = require('chai');
|
const { assert } = require('chai');
|
||||||
const remoteAddress = require('../../express/remote-address')(3);
|
const remoteAddress = require('../../express/remote-address').remoteAddress(3);
|
||||||
|
|
||||||
describe('remote-address', () => {
|
describe('remote-address', () => {
|
||||||
it('has the correct interface', () => {
|
it('has the correct interface', () => {
|
||||||
|
|
|
@ -34,7 +34,7 @@ describe('express/routing:', () => {
|
||||||
|
|
||||||
routingFactory = proxyquire('../../express/routing', {
|
routingFactory = proxyquire('../../express/routing', {
|
||||||
celebrate: celebrateMock,
|
celebrate: celebrateMock,
|
||||||
'./cors': () => corsHandler,
|
'./cors': { default: () => corsHandler },
|
||||||
});
|
});
|
||||||
|
|
||||||
appMock = {
|
appMock = {
|
||||||
|
@ -49,7 +49,7 @@ describe('express/routing:', () => {
|
||||||
error: sinon.spy(),
|
error: sinon.spy(),
|
||||||
};
|
};
|
||||||
|
|
||||||
routing = routingFactory(appMock, loggerMock);
|
routing = routingFactory.default(appMock, loggerMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exposes the correct interface', () => {
|
it('exposes the correct interface', () => {
|
||||||
|
|
|
@ -15,7 +15,7 @@ describe('metrics/amplitude:', () => {
|
||||||
let amplitude;
|
let amplitude;
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
amplitude = require('../../metrics/amplitude');
|
amplitude = require('../../metrics/amplitude').amplitude;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exports the event groups', () => {
|
it('exports the event groups', () => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe('oauth/scopes:', () => {
|
||||||
let scopes;
|
let scopes;
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
scopes = require('../../oauth/scopes');
|
scopes = require('../../oauth/scopes').default;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('valid implications', () => {
|
describe('valid implications', () => {
|
||||||
|
|
|
@ -25723,6 +25723,7 @@ fsevents@~2.1.1:
|
||||||
"@types/accept-language-parser": ^1.5.3
|
"@types/accept-language-parser": ^1.5.3
|
||||||
"@types/chai": ^4.2.18
|
"@types/chai": ^4.2.18
|
||||||
"@types/chance": ^1.1.2
|
"@types/chance": ^1.1.2
|
||||||
|
"@types/express": ^4.17.12
|
||||||
"@types/generic-pool": ^3.1.9
|
"@types/generic-pool": ^3.1.9
|
||||||
"@types/i18n-abide": ^0
|
"@types/i18n-abide": ^0
|
||||||
"@types/ioredis": ^4.26.4
|
"@types/ioredis": ^4.26.4
|
||||||
|
@ -25757,6 +25758,7 @@ fsevents@~2.1.1:
|
||||||
esbuild-register: ^3.2.0
|
esbuild-register: ^3.2.0
|
||||||
eslint: ^7.32.0
|
eslint: ^7.32.0
|
||||||
eslint-plugin-fxa: ^2.0.2
|
eslint-plugin-fxa: ^2.0.2
|
||||||
|
express: ^4.17.2
|
||||||
find-up: ^5.0.0
|
find-up: ^5.0.0
|
||||||
generic-pool: ^3.8.2
|
generic-pool: ^3.8.2
|
||||||
graphql: ^15.6.1
|
graphql: ^15.6.1
|
||||||
|
|
Загрузка…
Ссылка в новой задаче