зеркало из https://github.com/mozilla/fxa.git
Родитель
e7771ad875
Коммит
c1cf036334
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче