feat: add proxy for RPs and integration test utils

Closes #1269
This commit is contained in:
Ben Bangert 2019-06-09 15:22:46 -07:00
Родитель e7771ad875
Коммит c1cf036334
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 340D6D716D25CCA6
28 изменённых файлов: 1547 добавлений и 402 удалений

Просмотреть файл

@ -16,6 +16,7 @@ trap on_sigint INT
# Create pushbox db on start (because pushbox doesn't create it)
docker run --rm --name=pubsub \
--network=host \
-p 8005:8085 \
knarz/pubsub-emulator &

Просмотреть файл

@ -0,0 +1,2 @@
.git
.tscache

Просмотреть файл

@ -0,0 +1,70 @@
# Architecture
## Recurring Services
FxA Event Broker runs two services (`selfUpdatingService`) which refresh a cached copy of data at a given
interval (default is 5 minutes).
```mermaid
sequenceDiagram
participant Auth as FxA Auth Server
participant Ev as FxA Event Broker
participant FS as Google Firestore
Ev-->>Auth: GET /oauth/subscriptions/clients
Auth-->>Ev: Client Capability Response
Note over Ev: Cache Client <br />Capabilities
Ev-->>FS: Fetch Client Webhooks
FS-->>Ev: Webhook Response
Note over Ev: Cache Client <br />Webhook URLs
```
<br /><br /><br /><br /><br />
## Login Events
```mermaid
sequenceDiagram
participant Auth as FxA Auth Server
participant SQS as AWS SQS
participant Ev as FxA Event Broker
participant FS as Google Firestore
Auth->>SQS: LoginEvent
SQS->>Ev: LoginEvent
Ev-->>FS: Store Login
```
<br /><br /><br /><br /><br />
## Subscription State Change Events
Note: SQS participant not shown here to save space.
```mermaid
sequenceDiagram
participant Auth as FxA Auth Server
participant Ev as FxA Event Broker
participant PS as Google PubSub
participant RP as Relying Party
participant FS as Google Firestore
Auth->>Ev: SubscriptionEvent via SQS
Ev-->>+FS: Get RPs the User has logged into (FetchClientIds)
FS-->>-Ev: List of Client Ids
loop on clientIds
Ev->>PS: StateChangeEvent
PS-->>+Ev: POST /proxy/{clientId}
Ev-->>+RP: POST /client/webhook
RP-->>-Ev: {Status: 200}
Ev-->>-PS: {Status: 200}
end
```

Просмотреть файл

@ -0,0 +1,76 @@
/* 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 * as AWS from 'aws-sdk';
import { SQS } from 'aws-sdk';
import { Chance } from 'chance';
import * as mozlog from 'mozlog';
import Config from '../config';
import { ClientCapabilityService } from '../lib/selfUpdatingService/clientCapabilityService';
import { FactoryBot, LoginEvent, SubscriptionEvent } from '../test/service-notifications';
AWS.config.update({
region: 'us-east-1'
});
const MESSAGE_COUNT = 10_000;
const chance = new Chance();
const sqs = new SQS();
const queueUrl = Config.get('serviceNotificationQueueUrl');
const logger = mozlog(Config.get('log'))('generate-sqs-traffic');
// Promisify the AWS send message, the Node promisify mangled the TS signature
const sqsSendMessage = (params: SQS.SendMessageRequest): Promise<SQS.SendMessageResult> =>
new Promise((resolve, reject) => {
sqs.sendMessage(params, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
async function queueMessage(message: object): Promise<SQS.SendMessageResult> {
const params = {
MessageBody: JSON.stringify(message),
QueueUrl: queueUrl
};
return await sqsSendMessage(params);
}
async function main() {
const capabilityService = new ClientCapabilityService(
logger,
Config.get('clientCapabilityFetch')
);
await capabilityService.start();
await capabilityService.stop();
const clientData = capabilityService.serviceData();
let i = 0;
while (i < MESSAGE_COUNT) {
const clientId = chance.pickone(Object.keys(clientData));
const capabilities = clientData[clientId].map(cap => `${clientId}:${cap}`);
const loginEvent = FactoryBot.build<LoginEvent>('loginEvent', { clientId });
const loginResult = await queueMessage(loginEvent);
logger.info('main', { ...loginEvent, messageId: loginResult.MessageId });
const subEvent = FactoryBot.build<SubscriptionEvent>('subscriptionEvent', {
isActive: true,
productCapabilities: capabilities,
uid: loginEvent.uid
});
const subResult = await queueMessage(subEvent);
logger.info('main', { ...subEvent, messageId: subResult.MessageId });
i += 1;
}
}
main().then(() => {
logger.info('main', { message: 'Shutting down' });
});

Просмотреть файл

@ -9,6 +9,7 @@ import * as mozlog from 'mozlog';
import Config from '../config';
import { createDatastore, FirestoreDatastore, InMemoryDatastore } from '../lib/db';
import { ServiceNotificationProcessor } from '../lib/notificationProcessor';
import * as proxyServer from '../lib/proxy-server';
import { ClientCapabilityService } from '../lib/selfUpdatingService/clientCapabilityService';
import { ClientWebhookService } from '../lib/selfUpdatingService/clientWebhookService';
@ -22,21 +23,35 @@ const db = firestoreEnabled
? createDatastore(FirestoreDatastore, firestoreConfig)
: createDatastore(InMemoryDatastore, {});
const capabilityService = new ClientCapabilityService(logger, Config.get('clientCapabilityFetch'));
const webhookService = new ClientWebhookService(
logger,
Config.get('clientCapabilityFetch.refreshInterval'),
db
);
const pubsub = new PubSub();
const processor = new ServiceNotificationProcessor(
logger,
db,
Config.get('serviceNotificationQueueUrl'),
new SQS(),
capabilityService,
webhookService,
pubsub
);
logger.info('startup', { message: 'Starting event broker...' });
processor.start();
async function main() {
const capabilityService = new ClientCapabilityService(
logger,
Config.get('clientCapabilityFetch')
);
const webhookService = new ClientWebhookService(
logger,
Config.get('clientCapabilityFetch.refreshInterval'),
db
);
const pubsub = new PubSub();
const processor = new ServiceNotificationProcessor(
logger,
db,
Config.get('serviceNotificationQueueUrl'),
new SQS(),
capabilityService,
webhookService,
pubsub
);
logger.info('startup', { message: 'Starting event broker...' });
processor.start();
logger.info('startup', { message: 'Starting proxy server...' });
const server = await proxyServer.init(
{ ...Config.get('proxy'), openid: Config.get('openid'), pubsub: Config.get('pubsub') },
logger,
webhookService
);
await server.start();
}
main();

Просмотреть файл

@ -12,15 +12,17 @@ import * as mozlog from 'mozlog';
import Config from '../config';
import { createDatastore, FirestoreDatastore, InMemoryDatastore } from '../lib/db';
import { ServiceNotificationProcessor } from '../lib/notificationProcessor';
import * as proxyServer from '../lib/proxy-server';
import { ClientCapabilityService } from '../lib/selfUpdatingService/clientCapabilityService';
import { ClientWebhookService } from '../lib/selfUpdatingService/clientWebhookService';
const NODE_ENV = Config.get('env');
const logger = mozlog(Config.get('log'))('notificationProcessor');
// This is a development only server
if (Config.get('env') !== 'development') {
if (NODE_ENV === 'production') {
logger.error('workerDev', {
message: 'NODE_ENV must be set to development to run the dev server'
message: 'NODE_ENV must not be set to production to run the dev server'
});
process.exit(1);
}
@ -33,14 +35,51 @@ let db = firestoreEnabled
? createDatastore(FirestoreDatastore, firestoreConfig)
: createDatastore(InMemoryDatastore, { webhooks: Config.get('clientWebhooks') });
/**
* Verify the topic configuration is setup properly to route subscriptions locally
*
* @param pubsub
* @param proxyPort
* @param webhookService
*/
async function verifyTopicConfig(
pubsub: PubSub,
proxyPort: number,
webhookService: ClientWebhookService
) {
// Start the service to grab the clientIds
await webhookService.start();
const clientCapabilities = webhookService.serviceData();
const clientTopics = Object.keys(clientCapabilities).map(clientId => 'rpQueue-' + clientId);
// Grab the topics we already have made
const [topics] = await pubsub.getTopics();
const existingTopics = topics
.map(topic => topic.name.split('/').slice(-1)[0])
.filter(name => clientTopics.includes(name));
// Create a topic and subscription for each clientId we're missing a topic for
const newTopics = clientTopics.filter(clientTopic => !existingTopics.includes(clientTopic));
for (const clientTopic of newTopics) {
const [topic] = await pubsub.createTopic(clientTopic);
const clientId = clientTopic.slice('rpQueue-'.length);
await topic.createSubscription(clientTopic + '-proxy', {
pushEndpoint: `http://localhost:${proxyPort}/v1/proxy/${clientId}`
});
}
}
async function main() {
AWS.config.update({
accessKeyId: 'fake',
['endpoint' as any]: 'localhost:4100',
region: 'us-east-1',
secretAccessKey: 'fake',
sslEnabled: false
});
AWS.config.update({ region: 'us-east-1' });
if (NODE_ENV === 'development') {
AWS.config.update({
accessKeyId: 'fake',
['endpoint' as any]: 'localhost:4100',
secretAccessKey: 'fake',
sslEnabled: false
});
}
try {
// Verify the queue exists
@ -58,7 +97,7 @@ async function main() {
}
// Utilize the local firestore emulator if we were told to use it
if (firestoreEnabled) {
if (firestoreEnabled && NODE_ENV === 'development') {
const app = firebase.initializeTestApp({
auth: { uid: 'alice' },
databaseName: 'my-database',
@ -80,7 +119,9 @@ async function main() {
Config.get('clientCapabilityFetch.refreshInterval'),
db
);
const pubsub = new PubSub();
const pubsub = new PubSub({ projectId: 'fxa-event-broker' });
await verifyTopicConfig(pubsub, Config.get('proxy').port, webhookService);
const processor = new ServiceNotificationProcessor(
logger,
db,
@ -90,8 +131,17 @@ async function main() {
webhookService,
pubsub
);
logger.info('startup', { message: 'Starting event broker...' });
processor.start();
logger.info('startup', { message: 'Starting proxy server...' });
const server = await proxyServer.init(
{ ...Config.get('proxy'), openid: Config.get('openid'), pubsub: Config.get('pubsub') },
logger,
webhookService
);
await server.start();
}
main();

Просмотреть файл

@ -8,6 +8,11 @@
"fmt": "pretty",
"level": "debug"
},
"openid": {
"issuer": "http://127.0.0.1:3030",
"keyFile": "../config/key.json",
"key": {}
},
"firestore": {
"enabled": true
},

Просмотреть файл

@ -90,11 +90,58 @@ const conf = convict({
env: 'LOG_LEVEL'
}
},
openid: {
issuer: {
default: '',
doc: 'OpenID Issuer',
env: 'OPENID_ISSUER',
format: String
},
key: {
default: {},
doc: 'Private JWK to sign Security Event Tokens',
env: 'OPENID_KEY'
},
keyFile: {
default: '',
doc: 'OpenID Keyfile',
env: 'OPENID_KEYFILE',
format: String
}
},
proxy: {
port: {
default: 8090,
doc: 'Port to run PubSub proxy on',
env: 'PUBSUB_PROXY_PORT',
format: Number
}
},
pubsub: {
audience: {
default: 'example.com',
doc: 'PubSub JWT Audience for incoming Push Notifications',
env: 'PUBSUB_AUDIENCE',
format: String
},
authenticate: {
default: true,
doc: 'Authenticate that incoming Push Notifcation originate from Google',
env: 'PUBSUB_AUTHENTICATE',
format: Boolean
},
verificationToken: {
default: '',
doc: 'PubSub Verification Token for incoming Push Notifications',
env: 'PUBSUB_VERIFICATION_TOKEN',
format: String
}
},
sentryDsn: {
default: '',
doc: 'Sentry DSN for error and log reporting',
env: 'SENTRY_DSN',
format: 'String'
format: String
},
serviceNotificationQueueUrl: {
default: '',
@ -116,6 +163,14 @@ envConfig = `${envConfig},${process.env.CONFIG_FILES || ''}`;
const files = envConfig.split(',').filter(fs.existsSync);
conf.loadFile(files);
conf.validate({ allowed: 'strict' });
// Replace openid key if file specified
if (conf.get('openid.keyFile')) {
// tslint:disable-next-line
let key = require(conf.get('openid.keyFile'));
conf.set('openid.key', key);
}
const Config = conf;
export default Config;

Просмотреть файл

@ -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/. */
import { Server } from '@hapi/hapi';
import { Logger } from 'mozlog';
import { JWT } from '../jwts';
import { ClientWebhookService } from '../selfUpdatingService/clientWebhookService';
import pubSubAuth from './pubsub-auth';
import Routes from './routes';
export type ProxyConfig = {
pubsub: {
audience: string;
authenticate: boolean;
verificationToken: string;
};
openid: {
issuer: string;
key: object;
};
};
export function init(
logger: Logger,
config: ProxyConfig,
server: Server,
webhookService: ClientWebhookService
) {
const jwt = new JWT(config.openid);
server.auth.scheme('googlePubSub', pubSubAuth);
server.auth.strategy('pubsub', 'googlePubSub', config.pubsub);
Routes(logger, server, jwt, webhookService);
}

Просмотреть файл

@ -0,0 +1,85 @@
/* 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 * as hapi from '@hapi/hapi';
import { Logger } from 'mozlog';
import * as requests from 'request-promise-native';
import { JWT } from '../jwts';
import { ClientWebhookService } from '../selfUpdatingService/clientWebhookService';
import { proxyPayload } from './proxy-validator';
export default class ProxyController {
constructor(
private readonly logger: Logger,
private readonly webhookService: ClientWebhookService,
private readonly jwt: JWT
) {}
public async proxyDelivery(request: hapi.Request, h: hapi.ResponseToolkit) {
const webhookData = this.webhookService.serviceData();
const clientId = request.params.clientId;
if (!Object.keys(webhookData).includes(clientId)) {
return h.response().code(404);
}
const webhookEndpoint = webhookData[clientId];
const payload = request.payload as proxyPayload;
let message: any;
try {
// The message is a unicode string encoded in base64.
const rawMessage = Buffer.from(payload.message.data, 'base64').toString('utf-8');
message = JSON.parse(rawMessage);
this.logger.debug('proxyDelivery', message);
} catch (err) {
this.logger.error('proxyDelivery', { message: 'Failure to load message payload', err });
return h.response('Invalid message').code(400);
}
const jwtPayload = await this.jwt.generateSubscriptionSET({
capabilities: message.capabilities,
clientId,
isActive: message.isActive,
uid: message.uid
});
const options: requests.OptionsWithUri = {
headers: { Authorization: 'Bearer ' + jwtPayload },
resolveWithFullResponse: true,
uri: webhookEndpoint
};
let response: requests.FullResponse;
try {
response = await requests.post(options);
} catch (err) {
if (err.response) {
// Proxy normal HTTP responses that aren't 200.
response = err.response;
} else {
throw err;
}
}
let resp = h.response(response.body).code(response.statusCode);
// Copy the headers over to our hapi Response
Object.entries(response.headers).map(([name, value]) => {
if (!value) {
return;
}
if (Array.isArray(value)) {
for (const val of value) {
resp = resp.header(name, val);
}
} else {
resp = resp.header(name, value);
}
});
return resp;
}
}

Просмотреть файл

@ -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/. */
import * as joi from 'typesafe-joi';
export const proxyPayloadValidator = joi
.object({
message: joi
.object({
attributes: joi.object().unknown(true),
data: joi.string().required(),
messageId: joi.string().required()
})
.required(),
subscription: joi.string().required()
})
.required();
export type proxyPayload = joi.Literal<typeof proxyPayloadValidator>;

Просмотреть файл

@ -0,0 +1,63 @@
/* 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 * as hapi from '@hapi/hapi';
import { OAuth2Client } from 'google-auth-library';
const authClient = new OAuth2Client();
type PubSubOptions = { audience?: string; authenticate?: boolean; verificationToken?: string };
export class PubSubScheme {
private audience: string;
private verificationToken: string;
private verifyAuth: boolean;
constructor(server: hapi.Server, options?: hapi.ServerAuthSchemeOptions) {
if (!options) {
throw new Error('Invalid options for pubsub auth scheme');
}
const opts = options as PubSubOptions;
this.audience = opts.audience || '';
this.verificationToken = opts.verificationToken || '';
this.verifyAuth = opts.authenticate || false;
}
public async authenticate(
request: hapi.Request,
h: hapi.ResponseToolkit
): Promise<hapi.Auth | void> {
if (!this.verifyAuth) {
return h.authenticated({ credentials: {} });
}
if (request.query.token !== this.verificationToken) {
throw new Error('Invalid application token: ' + request.query.token);
}
// Verify that the push request originates from Cloud Pub/Sub.
try {
// Get the Cloud Pub/Sub-generated JWT in the "Authorization" header.
const bearer = request.headers.authorization;
const token = (bearer.match(/Bearer (.*)/) as string[])[1];
// Verify and decode the JWT.
const ticket = await authClient.verifyIdToken({
audience: this.audience,
idToken: token
});
const claim = ticket.getPayload();
if (claim) {
return h.authenticated({ credentials: { app: claim } });
}
return h.unauthenticated(new Error('No claims found'));
} catch (e) {
return h.unauthenticated(new Error(`Invalid JWT token: ${e}`));
}
}
}
export default function(server: hapi.Server, options?: hapi.ServerAuthSchemeOptions) {
return new PubSubScheme(server, options);
}

Просмотреть файл

@ -0,0 +1,34 @@
/* 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 * as hapi from '@hapi/hapi';
import * as hapiJoi from '@hapi/joi';
import { Logger } from 'mozlog';
import { JWT } from '../jwts';
import { ClientWebhookService } from '../selfUpdatingService/clientWebhookService';
import ProxyController from './proxy-controller';
import { proxyPayloadValidator } from './proxy-validator';
export default function(
logger: Logger,
server: hapi.Server,
jwt: JWT,
webhookService: ClientWebhookService
) {
const proxyController = new ProxyController(logger, webhookService, jwt);
server.bind(proxyController);
server.route({
method: 'POST',
options: {
auth: 'pubsub',
handler: proxyController.proxyDelivery,
validate: {
payload: proxyPayloadValidator as hapiJoi.ObjectSchema
}
},
path: '/v1/proxy/{clientId}'
});
}

Просмотреть файл

@ -1,7 +1,6 @@
/* 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 { Firestore, Settings } from '@google-cloud/firestore';
import { ClientWebhooks } from '../selfUpdatingService/clientWebhookService';
@ -14,6 +13,10 @@ class FirestoreDatastore implements Datastore {
if (firestore) {
this.db = firestore;
} else {
// keyFilename takes precedence over credentials
if (config.keyFilename) {
delete config.credentials;
}
this.db = new Firestore(config);
}
}
@ -44,8 +47,7 @@ class FirestoreDatastore implements Datastore {
}
public async fetchClientIdWebhooks(): Promise<ClientWebhooks> {
const query = this.db.collection('clients');
const results = await query.select('webhookUrl').get();
const results = await this.db.collection('clients').get();
const clientWebhooks: ClientWebhooks = {};
results.docs.forEach(doc => {
clientWebhooks[doc.id] = doc.get('webhookUrl');

Просмотреть файл

@ -0,0 +1,77 @@
/* 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 * as jwtool from 'fxa-jwtool';
// SET Event identifiers
export const SUBSCRIPTION_STATE_EVENT_ID =
'https://schemas.accounts.firefox.com/event/subscription-state-change';
type securityEvent = {
uid: string;
clientId: string;
events?: {
[key: string]: {
[key: string]: any;
};
};
};
type subscriptionEvent = {
uid: string;
clientId: string;
capabilities: string[];
isActive: boolean;
};
type JWTConfig = {
issuer: string;
key: any;
};
export class JWT {
private issuer: string;
private tokenKey: jwtool.PrivateJWK;
constructor(config: JWTConfig) {
this.issuer = config.issuer;
this.tokenKey = jwtool.JWK.fromObject(config.key, { iss: this.issuer });
}
/**
* Generate a Security Event Token for delivery to Relying Parties.
*
* See: https://tools.ietf.org/html/rfc8417
*
* @param event
*/
public generateSET(event: securityEvent): Promise<string> {
const now = Math.floor(Date.now() / 1000);
const claims = {
aud: event.clientId,
events: event.events,
sub: event.uid
};
return this.tokenKey.sign(claims);
}
/**
* Generate a Subscription Security Event Token.
*
* @param subEvent
*/
public generateSubscriptionSET(subEvent: subscriptionEvent): Promise<string> {
return this.generateSET({
clientId: subEvent.clientId,
events: {
[SUBSCRIPTION_STATE_EVENT_ID]: {
capabilities: subEvent.capabilities,
id: subEvent.uid,
isActive: subEvent.isActive
}
},
uid: subEvent.uid
});
}
}

Просмотреть файл

@ -1,6 +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 { PubSub } from '@google-cloud/pubsub';
import { SQS } from 'aws-sdk';
import { Logger } from 'mozlog';
@ -84,6 +85,7 @@ class ServiceNotificationProcessor {
this.db = db;
this.logger = logger;
this.app = Consumer.create({
batchSize: 10,
handleMessage: async (message: SQS.Message) => {
return await this.handleMessage(message);
},
@ -106,8 +108,14 @@ class ServiceNotificationProcessor {
public start() {
this.app.start();
this.capabilityService.start().catch(_ => process.exit(1));
this.webhookService.start().catch(_ => process.exit(1));
this.capabilityService.start().catch(err => {
this.logger.error('notificationProcessorStartCapability', { err });
process.exit(1);
});
this.webhookService.start().catch(err => {
this.logger.error('notificationProcessorStartWebhook', { err });
process.exit(1);
});
}
public stop() {
@ -118,17 +126,19 @@ class ServiceNotificationProcessor {
private async handleMessage(sqsMessage: SQS.Message) {
const body = JSON.parse(sqsMessage.Body || '{}');
const message = joi.attempt(JSON.parse(body.Message), BASE_MESSAGE_SCHEMA);
const message = joi.attempt(body, BASE_MESSAGE_SCHEMA);
switch (message.event) {
case LOGIN_EVENT: {
const loginMessage = joi.attempt(message, LOGIN_SCHEMA);
await this.db.storeLogin(loginMessage.uid, loginMessage.clientId);
this.logger.debug('sqs.loginEvent', loginMessage);
return;
}
case SUBSCRIPTION_UPDATE_EVENT: {
const subMessage = joi.attempt(message, SUBSCRIPTION_UPDATE_SCHEMA);
const clientIds = await this.db.fetchClientIds(subMessage.uid);
const clientWebhooks = this.webhookService.serviceData();
this.logger.debug('sqs.subEvent', subMessage);
// Split the product capabilities by clientId each capability goes to
const notifyClientIds: { [clientId: string]: string[] } = {};
@ -162,9 +172,7 @@ class ServiceNotificationProcessor {
);
// TODO: Failures to publish due to missing queue should be Sentry reported.
const messageId = await this.pubsub.topic(topicName).publishJSON(rpMessage, {
webhookUrl: clientWebhooks[clientId] || ''
});
const messageId = await this.pubsub.topic(topicName).publishJSON(rpMessage);
this.logger.debug('publishedMessage', { topicName, messageId });
});
return;

Просмотреть файл

@ -0,0 +1,28 @@
/* 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 * as hapi from '@hapi/hapi';
import { Logger } from 'mozlog';
import * as proxy from './api';
import { ClientWebhookService } from './selfUpdatingService/clientWebhookService';
export type ServerConfig = proxy.ProxyConfig & {
port: number;
};
export async function init(
config: ServerConfig,
logger: Logger,
webhookService: ClientWebhookService
): Promise<hapi.Server> {
const server = new hapi.Server({
debug: { request: ['error'] },
port: config.port
});
proxy.init(logger, config, server, webhookService);
return server;
}

Просмотреть файл

@ -37,7 +37,13 @@ abstract class SelfUpdatingService<T> {
throw result;
}
this.data = result;
this.timer = setInterval(async () => await this.updateService(), this.refreshInterval);
this.timer = setInterval(async () => {
try {
await this.updateService();
} catch (err) {
this.logger.error('updateService', { err });
}
}, this.refreshInterval);
}
/**

731
packages/fxa-event-broker/package-lock.json сгенерированный
Просмотреть файл

@ -825,15 +825,15 @@
"dev": true
},
"@google-cloud/firestore": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-2.0.0.tgz",
"integrity": "sha512-KZy9VXXP5zGCnp5y79SMDORGpFJj72V/MhFw7L8ZK1QS4ajEbbuxqTTv6abIca162FDoob8WZVRMvEVSdjoZkw==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-2.1.1.tgz",
"integrity": "sha512-tRQFYlangmPC8YGWLVABXroKeSmALygDK8pU9Jp1mueWtcRWlhUYaoc/KPwRcBhrNeVelL9Usn9D9VQptkGvOw==",
"requires": {
"@grpc/grpc-js": "0.4.0",
"bun": "^0.0.12",
"deep-equal": "^1.0.1",
"functional-red-black-tree": "^1.0.1",
"google-gax": "^1.0.0",
"lodash.merge": "^4.6.1",
"through2": "^3.0.0"
}
},
@ -1479,16 +1479,154 @@
"protobufjs": "^6.8.6"
}
},
"@hapi/accept": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-3.2.2.tgz",
"integrity": "sha512-UtXlTT59srtMr7ZRBzK2CvyWqFwlf78hPt9jEXqkwfbwiwRH1PRv/qkS8lgr5ZyoG6kfpU3xTgt2X91Yfe/6Yg==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/hoek": "6.x.x"
}
},
"@hapi/address": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz",
"integrity": "sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw=="
},
"@hapi/ammo": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-3.1.0.tgz",
"integrity": "sha512-iFQBEfm3WwWy8JdPQ8l6qXVLPtzmjITVfaxwl6dfoP8kKv6i2Uk43Ax+ShkNfOVyfEnNggqL2IyZTY3DaaRGNg==",
"requires": {
"@hapi/hoek": "6.x.x"
}
},
"@hapi/b64": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-4.2.0.tgz",
"integrity": "sha512-hmfPC1aF7cP21489A/IWPC3s1GE+1eAteVwFcOWLwj0Pky8eHgvrXPSSko2IeCpxqOdZhYw71IFN8xKPdv3CtQ==",
"requires": {
"@hapi/hoek": "6.x.x"
}
},
"@hapi/boom": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-7.4.2.tgz",
"integrity": "sha512-T2CYcTI0AqSvC6YC7keu/fh9LVSMzfoMLharBnPbOwmc+Cexj9joIc5yNDKunaxYq9LPuOwMS0f2B3S1tFQUNw==",
"requires": {
"@hapi/hoek": "6.x.x"
}
},
"@hapi/bounce": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-1.3.0.tgz",
"integrity": "sha512-gF5W/9AL10h/06HEf1bi0FP6KxZZ8LC/yHtDuoACw+1HrULvigHfnBIaSPFJXeHI3V3g0EkJpt1UOW0NKB+m+w==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/hoek": "6.x.x"
}
},
"@hapi/bourne": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz",
"integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA=="
},
"@hapi/call": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/call/-/call-5.1.0.tgz",
"integrity": "sha512-CiVEXjD/jiIHBqufBW3pdedshEMjRmHtff7m1puot8j4MUmuKRbLlh0DB8fv6QqH/7/55pH1qgFj300r0WpyMw==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/hoek": "6.x.x"
}
},
"@hapi/catbox": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-10.2.1.tgz",
"integrity": "sha512-u13BXlnmmrNUZssjTriRVTLuk6I/yUy5C1/Pia1+E2cpfd7o2/jmEvYdFgeS0Ft9QTz7WWhpXKlrguARUuohhQ==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/hoek": "6.x.x",
"@hapi/joi": "15.x.x",
"@hapi/podium": "3.x.x"
}
},
"@hapi/catbox-memory": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-4.1.0.tgz",
"integrity": "sha512-libCGyufOZaJu6uE9nVXw/u8tqOt4ifNIrOSAsDjzS+af3vPJyid8faOICqKCAh3E338UAsUe5AeYdezdsmtpg==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/hoek": "6.x.x"
}
},
"@hapi/content": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@hapi/content/-/content-4.1.0.tgz",
"integrity": "sha512-hv2Czsl49hnWDEfRZOFow/BmYbKyfEknmk3k83gOp6moFn5ceHB4xVcna8OwsGfy8dxO81lhpPy+JgQEaU4SWw==",
"requires": {
"@hapi/boom": "7.x.x"
}
},
"@hapi/cryptiles": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-4.2.0.tgz",
"integrity": "sha512-P+ioMP1JGhwDOKPRuQls6sT/ln6Fk+Ks6d90mlBi6HcOu5itvdUiFv5Ynq2DvLadPDWaA43lwNxkfZrjE9s2MA==",
"requires": {
"@hapi/boom": "7.x.x"
}
},
"@hapi/hapi": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-18.3.1.tgz",
"integrity": "sha512-gBiU9isWWezrg0ucX95Ph6AY6fUKZub3FxKapaleoFBJDOUcxTYiQR6Lha2zvHalIFoTl3K04O3Yr/5pD17QkQ==",
"requires": {
"@hapi/accept": "3.x.x",
"@hapi/ammo": "3.x.x",
"@hapi/boom": "7.x.x",
"@hapi/bounce": "1.x.x",
"@hapi/call": "5.x.x",
"@hapi/catbox": "10.x.x",
"@hapi/catbox-memory": "4.x.x",
"@hapi/heavy": "6.x.x",
"@hapi/hoek": "6.x.x",
"@hapi/joi": "15.x.x",
"@hapi/mimos": "4.x.x",
"@hapi/podium": "3.x.x",
"@hapi/shot": "4.x.x",
"@hapi/somever": "2.x.x",
"@hapi/statehood": "6.x.x",
"@hapi/subtext": "6.x.x",
"@hapi/teamwork": "3.x.x",
"@hapi/topo": "3.x.x"
}
},
"@hapi/heavy": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-6.2.0.tgz",
"integrity": "sha512-tzGU9cElY0IxRBudGB7tLFkdpBD8XQPfd6G7DSOnvHRK+q96UHGHn4t59Yd7kDpVucNkErWWYarsGx2KmKPkXA==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/hoek": "6.x.x",
"@hapi/joi": "15.x.x"
}
},
"@hapi/hoek": {
"version": "6.2.4",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-6.2.4.tgz",
"integrity": "sha512-HOJ20Kc93DkDVvjwHyHawPwPkX44sIrbXazAUDiUXaY2R9JwQGo2PhFfnQtdrsIe4igjG2fPgMra7NYw7qhy0A=="
},
"@hapi/iron": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-5.1.0.tgz",
"integrity": "sha512-+MK3tBPkEKd50SrDTRXa2DVvE0UTPFKxGbodlbQpNP9SVlxi+ZwA640VJtMNj84FZh81UUxda8AOLPRKFffnEA==",
"requires": {
"@hapi/b64": "4.x.x",
"@hapi/boom": "7.x.x",
"@hapi/cryptiles": "4.x.x",
"@hapi/hoek": "6.x.x"
}
},
"@hapi/joi": {
"version": "15.0.3",
"resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.0.3.tgz",
@ -1499,6 +1637,95 @@
"@hapi/topo": "3.x.x"
}
},
"@hapi/mimos": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-4.1.0.tgz",
"integrity": "sha512-CkxOB15TFZDMl5tQ5qezKZvvBnkRYVc8YksNfA5TnqQMMsU7vGPyvuuNFqj+15bfEwHyM6qasxyQNdkX9B/cQw==",
"requires": {
"@hapi/hoek": "6.x.x",
"mime-db": "1.x.x"
}
},
"@hapi/nigel": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-3.1.0.tgz",
"integrity": "sha512-IJyau32pz5Bf7pzUU/8AIn/SvPvhLMQcOel6kM7ECpKyPc895AwttSusRKfgTwfxZOEG6W8DnNv25gLtqrVFSg==",
"requires": {
"@hapi/hoek": "6.x.x",
"@hapi/vise": "3.x.x"
}
},
"@hapi/pez": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-4.1.0.tgz",
"integrity": "sha512-c+AxL8/cCj+7FB+tzJ5FhWKYP8zF7/7mA3Ft3a5y7h6YT26qzhj5d2JY27jur30KaZbrZAd4ofXXkqvE/IpJlA==",
"requires": {
"@hapi/b64": "4.x.x",
"@hapi/boom": "7.x.x",
"@hapi/content": "4.x.x",
"@hapi/hoek": "6.x.x",
"@hapi/nigel": "3.x.x"
}
},
"@hapi/podium": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-3.4.0.tgz",
"integrity": "sha512-IwyewAPGlCoq+g5536PKSDqSTfgpwbj+q4cBJpEUNqzwc5C5SM2stuFsULU7x1jKeWevfgWDoYWC75ML4IOYug==",
"requires": {
"@hapi/hoek": "6.x.x",
"@hapi/joi": "15.x.x"
}
},
"@hapi/shot": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-4.1.0.tgz",
"integrity": "sha512-rpUU5cF08fqAZLLnue6Sy0osj1QMPbrYskehxtLFPdk7CwlPcu9N/wRtgu7vDHTQCKTkag6M8sjc8V8p8lSxpg==",
"requires": {
"@hapi/hoek": "6.x.x",
"@hapi/joi": "15.x.x"
}
},
"@hapi/somever": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-2.1.0.tgz",
"integrity": "sha512-kMPewbpgLd0MSlNg0bjvq57Levozbg7c3O0idpWRxRgXfXBALNATLf8GRVbnMehYXAh7YRD2mR/91kginDtJ2Q==",
"requires": {
"@hapi/bounce": "1.x.x",
"@hapi/hoek": "6.x.x"
}
},
"@hapi/statehood": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-6.1.0.tgz",
"integrity": "sha512-qc8Qq3kg0b3XK7siXf6DK0wp+rcOrXv336kIP6YrtD9TbQ45TsBobwKkUXB+4R3GCCQ8a6tOj8FR/9bdtjKJCA==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/bounce": "1.x.x",
"@hapi/bourne": "1.x.x",
"@hapi/cryptiles": "4.x.x",
"@hapi/hoek": "6.x.x",
"@hapi/iron": "5.x.x",
"@hapi/joi": "15.x.x"
}
},
"@hapi/subtext": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-6.1.0.tgz",
"integrity": "sha512-dNL4IspNciKUK9RJuArwyS1MO07ZU64z4JrCzY1+vRKczYqin8M5i34cpOrQNP3pD/A/6IbRcFg0Jl0G6pwjnA==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/bourne": "1.x.x",
"@hapi/content": "4.x.x",
"@hapi/hoek": "6.x.x",
"@hapi/pez": "4.x.x",
"@hapi/wreck": "15.x.x"
}
},
"@hapi/teamwork": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-3.3.1.tgz",
"integrity": "sha512-61tiqWCYvMKP7fCTXy0M4VE6uNIwA0qvgFoiDubgfj7uqJ0fdHJFQNnVPGrxhLWlwz0uBPWrQlBH7r8y9vFITQ=="
},
"@hapi/topo": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.0.tgz",
@ -1507,6 +1734,24 @@
"@hapi/hoek": "6.x.x"
}
},
"@hapi/vise": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-3.1.0.tgz",
"integrity": "sha512-DUDzV0D4iVO5atghsjGZtzaF0HVtRLcxcnH6rAONyH0stnoLiFloGEuP5nkbIPU0B9cgWTzTUsQPuNHBzxy9Yw==",
"requires": {
"@hapi/hoek": "6.x.x"
}
},
"@hapi/wreck": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-15.0.1.tgz",
"integrity": "sha512-ByXQna/W1FZk7dg8NEhL79u4QkhzszRz76VpgyGstSH8bLM01a0C8RsxmUBgi6Tjkag5jA9kaEIhF9dLpMrtBw==",
"requires": {
"@hapi/boom": "7.x.x",
"@hapi/bourne": "1.x.x",
"@hapi/hoek": "6.x.x"
}
},
"@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -1602,30 +1847,24 @@
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
"dev": true
},
"@types/boom": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@types/boom/-/boom-7.2.1.tgz",
"integrity": "sha512-kOiap+kSa4DPoookJXQGQyKy1rjZ55tgfKAh9F0m1NUdukkcwVzpSnXPMH42a5L+U++ugdQlh/xFJu/WAdr1aw==",
"dev": true
},
"@types/caseless": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==",
"dev": true
},
"@types/catbox": {
"version": "10.0.6",
"resolved": "https://registry.npmjs.org/@types/catbox/-/catbox-10.0.6.tgz",
"integrity": "sha512-qS0VHlL6eBUUoUeBnI/ASCffoniS62zdV6IUtLSIjGKmRhZNawotaOMsTYivZOTZVktfe9koAJkD9XFac7tEEg==",
"dev": true
},
"@types/chai": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz",
"integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==",
"dev": true
},
"@types/chance": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@types/chance/-/chance-1.0.4.tgz",
"integrity": "sha512-Fwkv18sHVKsJBVEB6u7uDu5gDKbEYWz3ZB2vV3XfoD/C9eYABRZuHVx+4cuuYE2Th7IGMpaRpyJxDnTSeNleag==",
"dev": true
},
"@types/convict": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@types/convict/-/convict-4.2.1.tgz",
@ -1649,20 +1888,41 @@
"@types/node": "*"
}
},
"@types/hapi": {
"version": "18.0.2",
"resolved": "https://registry.npmjs.org/@types/hapi/-/hapi-18.0.2.tgz",
"integrity": "sha512-I3THPpOY0G0bXNLzyaqUzMIbqPrci21C1Vqlnek348SJq2Gp7TLSnQbwOb+v/xunxCTzY3o2fV3h7uffL3Ln0w==",
"@types/hapi__boom": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@types/hapi__boom/-/hapi__boom-7.4.0.tgz",
"integrity": "sha512-i4nerd/aJd5zW8OPHxgeuDYiuiuHxHczkqIaALhINUqBCavRgbiwjfndpgrarB43jcb/2LxzrohMOH7akyt3og==",
"dev": true
},
"@types/hapi__catbox": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/@types/hapi__catbox/-/hapi__catbox-10.2.2.tgz",
"integrity": "sha512-AWK70LgRsRWL1TNw+aT0IlS56E0pobvFdr/en0K8XazyK4Ey6T/jXhQqv/iQ6FJAAU+somMzgmt9fWq2TaaOkA==",
"dev": true
},
"@types/hapi__hapi": {
"version": "18.2.3",
"resolved": "https://registry.npmjs.org/@types/hapi__hapi/-/hapi__hapi-18.2.3.tgz",
"integrity": "sha512-fDLS+F1ORahczOH+iZfgLDc6gBu1iHEtb3hCey+GzE6T8KkvAmvroOcagE1VdBVckClful0DfsH2S/2Zx1e3mg==",
"dev": true,
"requires": {
"@types/boom": "*",
"@types/catbox": "*",
"@types/iron": "*",
"@types/joi": "*",
"@types/mimos": "*",
"@types/node": "*",
"@types/podium": "*",
"@types/shot": "*"
"@types/hapi__boom": "*",
"@types/hapi__catbox": "*",
"@types/hapi__iron": "*",
"@types/hapi__joi": "*",
"@types/hapi__mimos": "*",
"@types/hapi__podium": "*",
"@types/hapi__shot": "*",
"@types/node": "*"
}
},
"@types/hapi__iron": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@types/hapi__iron/-/hapi__iron-5.1.0.tgz",
"integrity": "sha512-RxYHIc8wFe8M1jMwgovskoHNVjuP1q0tUGCNnbHnhA4SBMyYg+JHIAz8yFibSwgF4gWYh5yHMpbmK5kmnG4HRA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/hapi__joi": {
@ -1673,21 +1933,30 @@
"@types/hapi__joi": "*"
}
},
"@types/iron": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@types/iron/-/iron-5.0.1.tgz",
"integrity": "sha512-Ng5BkVGPt7Tw9k1OJ6qYwuD9+dmnWgActmsnnrdvs4075N8V2go1f6Pz8omG3q5rbHjXN6yzzZDYo3JOgAE/Ug==",
"@types/hapi__mimos": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@types/hapi__mimos/-/hapi__mimos-4.1.0.tgz",
"integrity": "sha512-hcdSoYa32wcP+sEfyf85ieGwElwokcZ/mma8eyqQ4OTHeCAGwfaoiGxjG4z1Dm+RGhIYLHlW54ji5FFwahH12A==",
"dev": true,
"requires": {
"@types/mime-db": "*"
}
},
"@types/hapi__podium": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@types/hapi__podium/-/hapi__podium-3.4.0.tgz",
"integrity": "sha512-LE85jLgqR5HscGQ7SaSz6FMRsKlQ1wHVbYc9u0yq7NKDRvZiQFIrr3Pl1RPzK7QNUdZP8zmJibe8q0JcafTAJQ==",
"dev": true
},
"@types/hapi__shot": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@types/hapi__shot/-/hapi__shot-4.1.0.tgz",
"integrity": "sha512-vIySJYkwIGXMB5eFaZu3U8dS9CAZmteJfmkRn9bYH5uNcSvVgiwDROiwAkD7ej88qA+RZPkUK70KmeDs3LRHvw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/joi": {
"version": "14.3.3",
"resolved": "https://registry.npmjs.org/@types/joi/-/joi-14.3.3.tgz",
"integrity": "sha512-6gAT/UkIzYb7zZulAbcof3lFxpiD5EI6xBeTvkL1wYN12pnFQ+y/+xl9BvnVgxkmaIDN89xWhGZLD9CvuOtZ9g==",
"dev": true
},
"@types/long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz",
@ -1699,15 +1968,6 @@
"integrity": "sha1-m8AUof0f30dknBpUxt15ZrgoR5I=",
"dev": true
},
"@types/mimos": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mimos/-/mimos-3.0.1.tgz",
"integrity": "sha512-MATIRH4VMIJki8lcYUZdNQEHuAG7iQ1FWwoLgxV+4fUOly2xZYdhHtGgvQyWiTeJqq2tZbE0nOOgZD6pR0FpNQ==",
"dev": true,
"requires": {
"@types/mime-db": "*"
}
},
"@types/mocha": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz",
@ -1728,12 +1988,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA=="
},
"@types/podium": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/podium/-/podium-1.0.0.tgz",
"integrity": "sha1-v6ohUb4rHWEJzGn3+qnawsujuyA=",
"dev": true
},
"@types/request": {
"version": "2.48.1",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz",
@ -1755,15 +2009,6 @@
"@types/request": "*"
}
},
"@types/shot": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/shot/-/shot-4.0.0.tgz",
"integrity": "sha512-Xv+n8yfccuicMlwBY58K5PVVNtXRm7uDzcwwmCarBxMP+XxGfnh1BI06YiVAsPbTAzcnYsrzpoS5QHeyV7LS8A==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/sinon": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.11.tgz",
@ -1885,6 +2130,16 @@
"safer-buffer": "~2.1.0"
}
},
"asn1.js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-1.0.3.tgz",
"integrity": "sha1-KBuj7B8kSP52X5Kk7s+IP+E2S1Q=",
"requires": {
"bn.js": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
@ -1955,6 +2210,17 @@
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz",
"integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ=="
},
"bluebird": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.0.5.tgz",
"integrity": "sha1-L/nQfJs+2ynW0oD+B1KDZefs05I="
},
"bn.js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-1.3.0.tgz",
"integrity": "sha1-DbTL+W+PI7dC9by50ap6mZSgXoM=",
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -2055,6 +2321,12 @@
"supports-color": "^5.3.0"
}
},
"chance": {
"version": "1.0.18",
"resolved": "https://registry.npmjs.org/chance/-/chance-1.0.18.tgz",
"integrity": "sha512-g9YLQVHVZS/3F+zIicfB58vjcxopvYQRp7xHzvyDFDhXH1aRZI/JhwSAO0X5qYiQluoGnaNAU6wByD2KTxJN1A==",
"dev": true
},
"check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@ -2305,7 +2577,6 @@
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"dev": true,
"requires": {
"iconv-lite": "~0.4.13"
}
@ -2426,6 +2697,15 @@
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"factory-bot-ts": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/factory-bot-ts/-/factory-bot-ts-0.1.5.tgz",
"integrity": "sha512-2jpeR5Ny6pdsV/RexGWpHxbZhRE8qv/qWfBEC25/a0f83Fx8eiXDnF7RUuINrRHgG+W0zsHsvCJPLFM+XO4+1Q==",
"dev": true,
"requires": {
"lodash": "^4.17.10"
}
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
@ -2456,6 +2736,14 @@
"websocket-driver": ">=0.5.1"
}
},
"fetch": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/fetch/-/fetch-0.3.6.tgz",
"integrity": "sha1-N1Q3GMIqisA8fHscUTe7N/Y9g9g=",
"requires": {
"encoding": "*"
}
},
"firebase": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/firebase/-/firebase-6.1.0.tgz",
@ -2524,6 +2812,16 @@
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
},
"fxa-jwtool": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/fxa-jwtool/-/fxa-jwtool-0.7.2.tgz",
"integrity": "sha1-Mu61wC4CWCjrIPUIFiyJ+vSjS/s=",
"requires": {
"bluebird": "3.0.5",
"fetch": "0.3.6",
"pem-jwk": "1.5.1"
}
},
"gaxios": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.0.1.tgz",
@ -2587,9 +2885,9 @@
}
},
"google-auth-library": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-4.0.0.tgz",
"integrity": "sha512-yyxl74G16GjKLevccXK3/DYEXphtI9Q2Qw3Eh7y8scjBKNL0IbAZF1mi999gC0tkfG6J23sCbd9tMEbNYeWfJQ==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-4.2.0.tgz",
"integrity": "sha512-AtIzv/MZpo8BQvt1J+ObetUVFQBAae2I3u6Wy4XqePYShHnYiRdXqWr2WWBkIllOGbWEwsq4PUfvafgw76XGLQ==",
"requires": {
"arrify": "^2.0.0",
"base64-js": "^1.3.0",
@ -3208,279 +3506,6 @@
"pify": "^4.0.0"
}
},
"hapi": {
"version": "18.1.0",
"resolved": "https://registry.npmjs.org/hapi/-/hapi-18.1.0.tgz",
"integrity": "sha512-nSU1VLyTAgp7P5gy47QzJIP2JAb+wOFvJIV3gnL0lFj/mD+HuTXhyUsDYXjF/dhADMVXVEz31z6SUHBJhtsvGA==",
"requires": {
"accept": "3.x.x",
"ammo": "3.x.x",
"boom": "7.x.x",
"bounce": "1.x.x",
"call": "5.x.x",
"catbox": "10.x.x",
"catbox-memory": "4.x.x",
"heavy": "6.x.x",
"hoek": "6.x.x",
"joi": "14.x.x",
"mimos": "4.x.x",
"podium": "3.x.x",
"shot": "4.x.x",
"somever": "2.x.x",
"statehood": "6.x.x",
"subtext": "6.x.x",
"teamwork": "3.x.x",
"topo": "3.x.x"
},
"dependencies": {
"accept": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/accept/-/accept-3.1.3.tgz",
"integrity": "sha512-OgOEAidVEOKPup+Gv2+2wdH2AgVKI9LxsJ4hicdJ6cY0faUuZdZoi56kkXWlHp9qicN1nWQLmW5ZRGk+SBS5xg==",
"requires": {
"boom": "7.x.x",
"hoek": "6.x.x"
}
},
"ammo": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/ammo/-/ammo-3.0.3.tgz",
"integrity": "sha512-vo76VJ44MkUBZL/BzpGXaKzMfroF4ZR6+haRuw9p+eSWfoNaH2AxVc8xmiEPC08jhzJSeM6w7/iMUGet8b4oBQ==",
"requires": {
"hoek": "6.x.x"
}
},
"b64": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/b64/-/b64-4.1.2.tgz",
"integrity": "sha512-+GUspBxlH3CJaxMUGUE1EBoWM6RKgWiYwUDal0qdf8m3ArnXNN1KzKVo5HOnE/FSq4HHyWf3TlHLsZI8PKQgrQ==",
"requires": {
"hoek": "6.x.x"
}
},
"boom": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-7.3.0.tgz",
"integrity": "sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A==",
"requires": {
"hoek": "6.x.x"
}
},
"bounce": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bounce/-/bounce-1.2.3.tgz",
"integrity": "sha512-3G7B8CyBnip5EahCZJjnvQ1HLyArC6P5e+xcolo13BVI9ogFaDOsNMAE7FIWliHtIkYI8/nTRCvCY9tZa3Mu4g==",
"requires": {
"boom": "7.x.x",
"hoek": "6.x.x"
}
},
"bourne": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/bourne/-/bourne-1.1.1.tgz",
"integrity": "sha512-Ou0l3W8+n1FuTOoIfIrCk9oF9WVWc+9fKoAl67XQr9Ws0z7LgILRZ7qtc9xdT4BveSKtnYXfKPgn8pFAqeQRew=="
},
"call": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/call/-/call-5.0.3.tgz",
"integrity": "sha512-eX16KHiAYXugbFu6VifstSdwH6aMuWWb4s0qvpq1nR1b+Sf+u68jjttg8ixDBEldPqBi30bDU35OJQWKeTLKxg==",
"requires": {
"boom": "7.x.x",
"hoek": "6.x.x"
}
},
"catbox": {
"version": "10.0.6",
"resolved": "https://registry.npmjs.org/catbox/-/catbox-10.0.6.tgz",
"integrity": "sha512-gQWCnF/jbHcfwGbQ4FQxyRiAwLRipqWTTXjpq7rTqqdcsnZosFa0L3LsCZcPTF33QIeMMkS7QmFBHt6QdzGPvg==",
"requires": {
"boom": "7.x.x",
"hoek": "6.x.x",
"joi": "14.x.x"
}
},
"catbox-memory": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/catbox-memory/-/catbox-memory-4.0.1.tgz",
"integrity": "sha512-ZmqNiLsYCIu9qvBJ/MQbznDV2bFH5gFiH67TgIJgSSffJFtTXArT+MM3AvJQlby9NSkLHOX4eH/uuUqnch/Ldw==",
"requires": {
"boom": "7.x.x",
"hoek": "6.x.x"
}
},
"content": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/content/-/content-4.0.6.tgz",
"integrity": "sha512-lR9ND3dXiMdmsE84K6l02rMdgiBVmtYWu1Vr/gfSGHcIcznBj2QxmSdUgDuNFOA+G9yrb1IIWkZ7aKtB6hDGyA==",
"requires": {
"boom": "7.x.x"
}
},
"cryptiles": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-4.1.3.tgz",
"integrity": "sha512-gT9nyTMSUC1JnziQpPbxKGBbUg8VL7Zn2NB4E1cJYvuXdElHrwxrV9bmltZGDzet45zSDGyYceueke1TjynGzw==",
"requires": {
"boom": "7.x.x"
}
},
"heavy": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/heavy/-/heavy-6.1.2.tgz",
"integrity": "sha512-cJp884bqhiebNcEHydW0g6V1MUGYOXRPw9c7MFiHQnuGxtbWuSZpsbojwb2kxb3AA1/Rfs8CNiV9MMOF8pFRDg==",
"requires": {
"boom": "7.x.x",
"hoek": "6.x.x",
"joi": "14.x.x"
}
},
"hoek": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz",
"integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q=="
},
"iron": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/iron/-/iron-5.0.6.tgz",
"integrity": "sha512-zYUMOSkEXGBdwlV/AXF9zJC0aLuTJUKHkGeYS5I2g225M5i6SrxQyGJGhPgOR8BK1omL6N5i6TcwfsXbP8/Exw==",
"requires": {
"b64": "4.x.x",
"boom": "7.x.x",
"cryptiles": "4.x.x",
"hoek": "6.x.x"
}
},
"joi": {
"version": "14.3.1",
"resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz",
"integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==",
"requires": {
"hoek": "6.x.x",
"isemail": "3.x.x",
"topo": "3.x.x"
}
},
"mime-db": {
"version": "1.37.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
},
"mimos": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/mimos/-/mimos-4.0.2.tgz",
"integrity": "sha512-5XBsDqBqzSN88XPPH/TFpOalWOjHJM5Z2d3AMx/30iq+qXvYKd/8MPhqBwZDOLtoaIWInR3nLzMQcxfGK9djXA==",
"requires": {
"hoek": "6.x.x",
"mime-db": "1.x.x"
}
},
"nigel": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/nigel/-/nigel-3.0.4.tgz",
"integrity": "sha512-3SZCCS/duVDGxFpTROHEieC+itDo4UqL9JNUyQJv3rljudQbK6aqus5B4470OxhESPJLN93Qqxg16rH7DUjbfQ==",
"requires": {
"hoek": "6.x.x",
"vise": "3.x.x"
}
},
"pez": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pez/-/pez-4.0.5.tgz",
"integrity": "sha512-HvL8uiFIlkXbx/qw4B8jKDCWzo7Pnnd65Uvanf9OOCtb20MRcb9gtTVBf9NCnhETif1/nzbDHIjAWC/sUp7LIQ==",
"requires": {
"b64": "4.x.x",
"boom": "7.x.x",
"content": "4.x.x",
"hoek": "6.x.x",
"nigel": "3.x.x"
}
},
"podium": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/podium/-/podium-3.2.0.tgz",
"integrity": "sha512-rbwvxwVkI6gRRlxZQ1zUeafrpGxZ7QPHIheinehAvGATvGIPfWRkaTeWedc5P4YjXJXEV8ZbBxPtglNylF9hjw==",
"requires": {
"hoek": "6.x.x",
"joi": "14.x.x"
}
},
"shot": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/shot/-/shot-4.0.7.tgz",
"integrity": "sha512-RKaKAGKxJ11EjJl0cf2fYVSsd4KB5Cncb9J0v7w+0iIaXpxNqFWTYNDNhBX7f0XSyDrjOH9a4OWZ9Gp/ZML+ew==",
"requires": {
"hoek": "6.x.x",
"joi": "14.x.x"
}
},
"somever": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/somever/-/somever-2.0.0.tgz",
"integrity": "sha512-9JaIPP+HxwYGqCDqqK3tRaTqdtQHoK6Qy3IrXhIt2q5x8fs8RcfU7BMWlFTCOgFazK8p88zIv1tHQXvAwtXMyw==",
"requires": {
"bounce": "1.x.x",
"hoek": "6.x.x"
}
},
"statehood": {
"version": "6.0.9",
"resolved": "https://registry.npmjs.org/statehood/-/statehood-6.0.9.tgz",
"integrity": "sha512-jbFg1+MYEqfC7ABAoWZoeF4cQUtp3LUvMDUGExL76cMmleBHG7I6xlZFsE8hRi7nEySIvutHmVlLmBe9+2R5LQ==",
"requires": {
"boom": "7.x.x",
"bounce": "1.x.x",
"bourne": "1.x.x",
"cryptiles": "4.x.x",
"hoek": "6.x.x",
"iron": "5.x.x",
"joi": "14.x.x"
}
},
"subtext": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/subtext/-/subtext-6.0.12.tgz",
"integrity": "sha512-yT1wCDWVgqvL9BIkWzWqgj5spUSYo/Enu09iUV8t2ZvHcr2tKGTGg2kc9tUpVEsdhp1ihsZeTAiDqh0TQciTPQ==",
"requires": {
"boom": "7.x.x",
"bourne": "1.x.x",
"content": "4.x.x",
"hoek": "6.x.x",
"pez": "4.x.x",
"wreck": "14.x.x"
}
},
"teamwork": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/teamwork/-/teamwork-3.0.3.tgz",
"integrity": "sha512-OCB56z+G70iA1A1OFoT+51TPzfcgN0ks75uN3yhxA+EU66WTz2BevNDK4YzMqfaL5tuAvxy4iFUn35/u8pxMaQ=="
},
"topo": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz",
"integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==",
"requires": {
"hoek": "6.x.x"
}
},
"vise": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/vise/-/vise-3.0.2.tgz",
"integrity": "sha512-X52VtdRQbSBXdjcazRiY3eRgV3vTQ0B+7Wh8uC9cVv7lKfML5m9+9NHlbcgCY0R9EAqD1v/v7o9mhGh2A3ANFg==",
"requires": {
"hoek": "6.x.x"
}
},
"wreck": {
"version": "14.1.3",
"resolved": "https://registry.npmjs.org/wreck/-/wreck-14.1.3.tgz",
"integrity": "sha512-hb/BUtjX3ObbwO3slCOLCenQ4EP8e+n8j6FmTne3VhEFp5XV1faSJojiyxVSvw34vgdeTG5baLTl4NmjwokLlw==",
"requires": {
"boom": "7.x.x",
"hoek": "6.x.x"
}
}
}
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@ -3559,7 +3584,6 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
@ -3689,14 +3713,6 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isemail": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz",
"integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==",
"requires": {
"punycode": "2.x.x"
}
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -3987,6 +4003,11 @@
"mime-db": "1.40.0"
}
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@ -4366,6 +4387,14 @@
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
"dev": true
},
"pem-jwk": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/pem-jwk/-/pem-jwk-1.5.1.tgz",
"integrity": "sha1-eoY3/S9nqCflfAxC4cI8P9Us+wE=",
"requires": {
"asn1.js": "1.0.3"
}
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@ -4822,9 +4851,9 @@
},
"dependencies": {
"readable-stream": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz",
"integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",

Просмотреть файл

@ -24,12 +24,14 @@
"homepage": "https://github.com/mozilla/fxa#readme",
"readmeFilename": "README.md",
"dependencies": {
"@google-cloud/firestore": "^2.0.0",
"@google-cloud/firestore": "^2.1.1",
"@google-cloud/pubsub": "^0.29.1",
"@hapi/hapi": "^18.3.1",
"@hapi/joi": "^15.0.3",
"aws-sdk": "^2.459.0",
"convict": "^5.0.0",
"hapi": "^18.1.0",
"fxa-jwtool": "^0.7.2",
"google-auth-library": "^4.2.0",
"mozlog": "^2.2.0",
"request": "^2.88.0",
"request-promise-native": "^1.0.7",
@ -39,8 +41,9 @@
"devDependencies": {
"@firebase/testing": "^0.10.0",
"@types/chai": "^4.1.7",
"@types/chance": "^1.0.4",
"@types/convict": "^4.2.1",
"@types/hapi": "^18.0.2",
"@types/hapi__hapi": "^18.2.3",
"@types/mocha": "^5.2.6",
"@types/nock": "^10.0.3",
"@types/node": "^12.0.2",
@ -48,7 +51,9 @@
"@types/sinon": "^7.0.11",
"@types/uuid": "^3.4.4",
"chai": "^4.2.0",
"chance": "^1.0.18",
"eslint-plugin-fxa": "git+https://github.com/mozilla/eslint-plugin-fxa.git#master",
"factory-bot-ts": "^0.1.5",
"mocha": "^6.1.4",
"nock": "^10.0.6",
"p-event": "^4.1.0",

Просмотреть файл

@ -0,0 +1,169 @@
/* 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 * as hapi from '@hapi/hapi';
import { assert as cassert } from 'chai';
import * as jwtool from 'fxa-jwtool';
import 'mocha';
import { Logger } from 'mozlog';
import * as nock from 'nock';
import { assert, SinonSpy } from 'sinon';
import * as sinon from 'sinon';
import { stubInterface } from 'ts-sinon';
import Config from '../../../config';
import { SUBSCRIPTION_STATE_EVENT_ID } from '../../../lib/jwts';
import * as proxyserver from '../../../lib/proxy-server';
import { ClientWebhookService } from '../../../lib/selfUpdatingService/clientWebhookService';
const TEST_KEY = {
d:
'nvfTzcMqVr8fa-b3IIFBk0J69sZQsyhKc3jYN5pPG7FdJyA-D5aPNv5zsF64JxNJetAS44cAsGAKN3Kh7LfjvLCtV56Ckg2tkBMn3GrbhE1BX6ObYvMuOBz5FJ9GmTOqSCxotAFRbR6AOBd5PCw--Rls4MylX393TFg6jJTGLkuYGuGHf8ILWyb17hbN0iyT9hME-cgLW1uc_u7oZ0vK9IxGPTblQhr82RBPQDTvZTM4s1wYiXzbJNrI_RGTAhdbwXuoXKiBN4XL0YRDKT0ENVqQLMiBwfdT3sW-M0L6kIv-L8qX3RIhbM3WA_a_LjTOM3WwRcNanSGiAeJLHwE5cQ',
dp:
'5U4HJsH2g_XSGw8mrv5LZ2kvnh7cibWfmB2x_h7ZFGLsXSphG9xSo3KDQqlLw4WiUHZ5kTyL9x-MiaUSxo-yEgtoyUy8C6gGTzQGxUyAq8nvQUq0J3J8kdCvdxM370Is7QmUF97LDogFlYlJ4eY1ASaV39SwwMd0Egf-JsPA9bM',
dq:
'k65nnWFsWAnPunppcedFZ6x6It1BZhqUiQQUN0Mok2aPiKjSDbQJ8_CospKDoTOgU0i3Bbnfp--PuUNwKO2VZoZ4clD-5vEJ9lz7AxgHMp4lJ-gy0TLEnITBmrYRdJY4aSGZ8L4IiUTFDUvmx8KdzkLGYZqH3cCVDGZANjgXoDU',
e: 'AQAB',
'fxa-createdAt': 1557356400,
kid: '2019-05-08-cd8b15e7a1d6d51e31de4f6aa79e9f9e',
kty: 'RSA',
n:
'uJIoiOOZsS7XZ5HuyBTV59YMpm73sF1OwlNgLYJ5l3RHskVp6rR7UCDZCU7tAVSx4mHl1qoqbfUSlVeseY3yuSa7Tz_SW_WDO4ihYelXX5lGF7uxn5KmY1--6p9Gx7oiwgO5EdU6vkh2T4xD1BY4GUpqTLCdYDdAsykhVpNyQiO2tSJrxJLIMAYxUIw6lMHtyJDRe6m_OUAjBm_xyS3JbbTXOoeYbFXXvktqxkxNtmYEDCjdj8v2NGy9z9zMao2KwCmu-S6L6BJid3W0rKNR_yxAQPLSSrqUwyO1wPntR5qVJ3C0n-HeqOZK3M3ObHAFK0vShNZsrY4gPpwUl3BZsw',
p:
'72yifmIgqTJwpU06DyKwnhJbmAXRmKZH3QswH1OvXx_o5jjr9oLLN9xdQeIt3vo2OqlLLeFf8nk0q-kQVU0f1yOB5LAaIxm7SgYA6S1qMfDIc2H8TBnG0-dJ_yNcfef2LPKuDhljiwXN5Z-SadsRbuxh1JcGHqngTJiOSc43PO8',
q:
'xVlYc0LRkOvQOpl0WSOPQ-0SVYe-v29RYamYlxTvq3mHkpexvERWVlHR94Igz5Taip1pxfhAHCREInJwMtncHnEcLQt-0T62I_BTmjpGzmRLTXx2Slmn-mlRSW_rwrdxeONPzxmJiSZE0dMOln9NBjr6Vp-5-J8TYE8TChoj930',
qi:
'E5GCQCyG7AGplCUyZPBS4OEW9QTmzJoG42rLZc9HNJPfjE2hrNUJqmjIWy_n3QQZaNJwps_t-PNaLHBwM043yM_neBGPIgGQwOw6YJp_nbUvDaJnHAtDhAaR7jPWQeDqypg0ysrZvWsd2x1BNowFUFNjmHkpejp2ueS6C_hgv_g'
};
const TEST_PUBLIC_KEY = {
e: TEST_KEY.e,
kid: TEST_KEY.kid,
kty: TEST_KEY.kty,
n: TEST_KEY.n
};
const TEST_CLIENT_ID = 'abc1234';
const PUBLIC_JWT = jwtool.JWK.fromObject(TEST_PUBLIC_KEY);
describe('Proxy Controller', () => {
let logger: Logger;
let server: hapi.Server;
let webhookService: ClientWebhookService;
const mockWebhook = () => {
nock('http://accounts.firefox.com')
.post('/webhook')
.reply(200, '', {
'X-AUTH': (req, res, body) => {
return req.headers.authorization;
}
});
};
const createValidMessage = (): string => {
return Buffer.from(
JSON.stringify({
capabilities: ['cap1', 'cap2'],
isActive: true
})
).toString('base64');
};
beforeEach(async () => {
logger = stubInterface<Logger>();
webhookService = stubInterface<ClientWebhookService>({
serviceData: {},
start: Promise.resolve()
});
(webhookService.serviceData as sinon.SinonStub).returns({
[TEST_CLIENT_ID]: 'http://accounts.firefox.com/webhook'
});
server = await proxyserver.init(
{
openid: { issuer: 'testing', key: TEST_KEY },
port: 8099,
pubsub: { ...Config.get('pubsub'), authenticate: false }
},
logger,
webhookService
);
await server.start();
});
afterEach(async () => {
await server.stop();
});
it('notifies successfully on subscription state change', async () => {
mockWebhook();
const message = createValidMessage();
const result = await server.inject({
method: 'POST',
payload: JSON.stringify({
message: { data: message, messageId: 'test-message' },
subscription: 'test-sub'
}),
url: '/v1/proxy/abc1234'
});
const bearer = result.headers['x-auth'] as string;
const token = (bearer.match(/Bearer (.*)/) as string[])[1];
const payload = await PUBLIC_JWT.verify(token);
cassert.equal(payload.aud, 'abc1234');
cassert.equal(payload.iss, 'testing');
cassert.deepEqual(payload.events[SUBSCRIPTION_STATE_EVENT_ID].capabilities, ['cap1', 'cap2']);
cassert.equal(payload.events[SUBSCRIPTION_STATE_EVENT_ID].isActive, true);
});
it('logs an error on invalid message payloads', async () => {
const message = Buffer.from('invalid payload').toString('base64');
const result = await server.inject({
method: 'POST',
payload: JSON.stringify({
message: { data: message, messageId: 'test-message' },
subscription: 'test-sub'
}),
url: '/v1/proxy/abc1234'
});
assert.calledOnce(logger.error as SinonSpy);
cassert.equal(result.statusCode, 400);
});
it('proxies an error code back', async () => {
nock('http://accounts.firefox.com')
.post('/webhook')
.reply(400, 'Error123');
const message = createValidMessage();
const result = await server.inject({
method: 'POST',
payload: JSON.stringify({
message: { data: message, messageId: 'test-message' },
subscription: 'test-sub'
}),
url: '/v1/proxy/abc1234'
});
cassert.equal(result.statusCode, 400);
cassert.equal(result.payload, 'Error123');
});
it('doesnt accept invalid payloads', async () => {
const message = createValidMessage();
const result = await server.inject({
method: 'POST',
payload: JSON.stringify({
message: { data: message }
}),
url: '/v1/proxy/abc1234'
});
cassert.equal(result.statusCode, 400);
cassert.deepEqual(JSON.parse(result.payload), {
error: 'Bad Request',
message: 'Invalid request payload input',
statusCode: 400
});
});
});

Просмотреть файл

@ -65,9 +65,7 @@ describe('ServiceNotificationProcessor', () => {
};
const updateStubMessage = (message: any) => {
response.Messages[0].Body = JSON.stringify({
Message: JSON.stringify(message)
});
response.Messages[0].Body = JSON.stringify(message);
};
const createConsumer = () => {
@ -197,7 +195,7 @@ describe('ServiceNotificationProcessor', () => {
consumer.start();
await pEvent(consumer.app, 'message_processed');
consumer.stop();
assert.calledTwice(logger.debug as SinonSpy);
assert.calledThrice(logger.debug as SinonSpy);
// Note that we aren't resetting the sandbox here, so we have 2 log calls thus far
db = stubInterface<Datastore>({
@ -207,8 +205,7 @@ describe('ServiceNotificationProcessor', () => {
consumer.start();
await pEvent(consumer.app, 'message_processed');
consumer.stop();
assert.calledThrice(logger.debug as SinonSpy);
(logger.debug as SinonSpy).getCalls()[2].calledWith({
(logger.debug as SinonSpy).getCalls()[3].calledWith({
messageId: undefined,
topicName: 'rpQueue-send'
});

Просмотреть файл

@ -0,0 +1,49 @@
/* 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 * as Chance from 'chance';
import { FactoryBot } from 'factory-bot-ts';
import { LoginEvent } from './login-event';
import { SubscriptionEvent } from './subscription-event';
const chance = new Chance.Chance();
export interface MetricsContext {
[key: string]: string;
}
FactoryBot.define(
'subscriptionEvent',
{
event: 'subscription:update',
isActive: () => chance.bool(),
metricsContext: {},
productCapabilities: (): string[] =>
[...new Array(chance.integer({ max: 5, min: 1 }))].map(() => chance.word()),
productName: () => chance.word(),
subscriptionId: () => chance.hash(),
ts: () => new Date().getTime(),
uid: () => chance.hash()
},
SubscriptionEvent
);
FactoryBot.define(
'loginEvent',
{
clientId: () => chance.hash(),
deviceCount: () => chance.integer({ min: 1, max: 3 }),
email: () => chance.email(),
event: 'login',
metricsContext: {},
service: () => chance.word(),
ts: () => new Date().getTime(),
uid: () => chance.hash(),
userAgent: () => chance.word()
},
LoginEvent
);
export { FactoryBot, LoginEvent, SubscriptionEvent };

Просмотреть файл

@ -0,0 +1,19 @@
/* 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 { MetricsContext } from './index';
export class LoginEvent {
constructor(
public clientId: string,
public event: string,
public ts: number,
public metricsContext: MetricsContext,
public service: string,
public uid: string,
public email: string,
public deviceCount: number,
public userAgent: string
) {}
}

Просмотреть файл

@ -0,0 +1,18 @@
/* 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 { MetricsContext } from './index';
export class SubscriptionEvent {
constructor(
public event: string,
public ts: number,
public metricsContext: MetricsContext,
public subscriptionId: string,
public uid: string,
public isActive: boolean,
public productName: string,
public productCapabilities: string[]
) {}
}

Просмотреть файл

@ -8,11 +8,13 @@
"module": "commonjs",
"moduleResolution": "node",
"strict": true,
"typeRoots": ["./types", "node_modules/@types"]
"typeRoots": ["./types", "node_modules/@types"],
"skipLibCheck": true
},
"include": [
"./bin/**/*",
"./config/**/*",
"./lib/**/*",
"./test/**/*",
"./types/**/*"
],

Просмотреть файл

@ -6,6 +6,7 @@
"rulesDirectory": ["tslint-plugin-prettier"],
"rules": {
"interface-name": [true, "never-prefix"],
"interface-over-type-literal": false,
"prettier": [true, ".prettierrc"]
}
}

224
packages/fxa-event-broker/types/fxa-jwtool/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,224 @@
declare module 'fxa-jwtool' {
export = fxa_jwtool;
}
declare class fxa_jwtool {
constructor(trusted: any);
fetch(jku: any, kid: any): any;
verify(str: any, defaults: any): any;
static JWTVerificationError(msg: any): void;
static sign(jwt: any, pem: any): any;
static unverify(str: any): any;
static verify(str: any, pem: any): any;
}
declare namespace fxa_jwtool {
class BN {
constructor(number: any, base: any, endian: any);
abs(): any;
add(num: any): any;
addn(num: any): any;
and(num: any): any;
andln(num: any): any;
bincn(bit: any): any;
bitLength(): any;
byteLength(): any;
clone(): any;
cmp(num: any): any;
cmpn(num: any): any;
copy(dest: any): void;
div(num: any): any;
divRound(num: any): any;
divmod(num: any, mode: any): any;
divn(num: any): any;
forceRed(ctx: any): any;
fromRed(): any;
gcd(num: any): any;
iabs(): any;
iadd(num: any): any;
iaddn(num: any): any;
iand(num: any): any;
idivn(num: any): any;
imaskn(bits: any): any;
imul(num: any): any;
imuln(num: any): any;
inspect(): any;
invm(num: any): any;
ior(num: any): any;
isEven(): any;
isOdd(): any;
ishln(bits: any): any;
ishrn(bits: any, hint: any, extended: any): any;
isqr(): any;
isub(num: any): any;
isubn(num: any): any;
ixor(num: any): any;
maskn(bits: any): any;
mod(num: any): any;
modn(num: any): any;
mul(num: any): any;
mulTo(num: any, out: any): any;
neg(): any;
or(num: any): any;
redAdd(num: any): any;
redIAdd(num: any): any;
redIMul(num: any): any;
redISqr(): any;
redISub(num: any): any;
redInvm(): any;
redMul(num: any): any;
redNeg(): any;
redPow(num: any): any;
redShl(num: any): any;
redSqr(): any;
redSqrt(): any;
redSub(num: any): any;
setn(bit: any, val: any): any;
shln(bits: any): any;
shrn(bits: any): any;
sqr(): any;
strip(): any;
sub(num: any): any;
subn(num: any): any;
testn(bit: any): any;
toArray(): any;
toJSON(): any;
toRed(ctx: any): any;
toString(base: any, padding: any): any;
ucmp(num: any): any;
xor(num: any): any;
static BN: any;
static mont(num: any): any;
static red(num: any): any;
static wordSize: number;
}
type PublicKeyType = {
e: string;
'fxa-createdAt'?: number;
kty: string;
kid?: string;
n: string;
};
type PrivateKeyTye = PublicKeyType & {
d: string;
p: string;
dp: string;
dq: string;
qi: string;
};
class JWK {
constructor(jwk: any, pem: any);
toJSON(): any;
static fromFile<PrivateJWK>(filename: any, extras: any): PrivateJWK;
static fromFile<PublicJWK>(filename: any, extras: any): PublicJWK;
static fromObject(obj: PrivateKeyTye, extras?: any): PrivateJWK;
static fromObject(obj: PublicKeyType, extras?: any): PublicJWK;
static fromPEM<PrivateJWK>(pem: any, extras: any): PrivateJWK;
static fromPEM<PublicJWK>(pem: any, extras: any): PublicJWK;
}
class PrivateJWK {
constructor(jwk: any, pem: any);
sign(data: any): Promise<any>;
signSync(data: any): Promise<any>;
}
class PublicJWK {
constructor(jwk: any, pem: any);
verify(str: any): Promise<any>;
verifySync(str: any): any;
}
}