refactor(basket): remove fxa-basket-proxy from the mono repo

This commit is contained in:
Vlad Filippov 2019-09-06 14:22:26 -04:00
Родитель b7d2c756ff
Коммит 8ca039616c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CC6973602B80B17D
56 изменённых файлов: 0 добавлений и 9892 удалений

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

@ -325,11 +325,6 @@ workflows:
test: test-ci
requires:
- install
- build-module:
name: fxa-basket-proxy
module: fxa-basket-proxy
requires:
- install
- build-module:
name: fxa-customs-server
module: fxa-customs-server
@ -440,16 +435,6 @@ workflows:
module: fxa-auth-server
requires:
- install
- deploy-module:
filters:
tags:
only: /.*/
branches:
ignore: /.*/
name: fxa-basket-proxy
module: fxa-basket-proxy
requires:
- install
- deploy-module:
filters:
tags:

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

@ -28,7 +28,6 @@ PATH=$PATH:$HOME/.cargo/bin
"cd fxa-event-broker; npm ci" \
"cd fxa-payments-server; npm ci" \
"cd fxa-profile-server; npm ci; mkdir -p var/public/" \
"cd fxa-basket-proxy; npm ci" \
"cd 123done; npm i" \
"cd fortress; npm i" \
"cd fxa-geodb; npm i" \

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

@ -1,7 +0,0 @@
plugins:
- fxa
extends:
- plugin:fxa/server
- prettier
parserOptions:
ecmaVersion: 2018

33
packages/fxa-basket-proxy/.gitignore поставляемый
Просмотреть файл

@ -1,33 +0,0 @@
### Node ###
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
.nyc_output
npm-debug.log*
node_modules
### Linux ###
.*
!.gitignore
!.git*
!.eslint*
!.jscsrc
!.git*
!.npmignore
!.travis.yml
!.prettier*
*~
### Local configuration ###
config/local.json

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

@ -1,4 +0,0 @@
{
"exceptions": [
]
}

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

@ -1,6 +0,0 @@
AUTHORS
LICENSE
.*
Dockerfile*
*.sh
*.json-dist

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

@ -1,4 +0,0 @@
{
"singleQuote": true,
"trailingComma": "es5"
}

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

@ -1,6 +0,0 @@
John Morrison <jrgmorrison@gmail.com>
Peter deHaan <pdehaan@mozilla.com>
Ryan Kelly <rkelly@mozilla.com>
Shane Tomlinson <stomlinson@mozilla.com>
Vlad Filippov <vlad.filippov@gmail.com>
Zach Carter <zcarter@mozilla.com>

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

@ -1,281 +0,0 @@
<a name="1.134.0"></a>
# [1.134.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.133.0...v1.134.0) (2019-04-02)
### Bug Fixes
- **deps:** Fix the audit warnings ([3c751c9](https://github.com/mozilla/fxa-basket-proxy/commit/3c751c9))
<a name="1.133.0"></a>
# [1.133.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.126.0...v1.133.0) (2019-03-19)
### Bug Fixes
- **deps:** Fix the audit warnings. ([352bdc6](https://github.com/mozilla/fxa-basket-proxy/commit/352bdc6))
### Features
- **fake-server:** Have the fake server serve CORS requests, handle OAuth tokens. ([046476c](https://github.com/mozilla/fxa-basket-proxy/commit/046476c)), closes [#75](https://github.com/mozilla/fxa-basket-proxy/issues/75)
<a name="1.126.0"></a>
# [1.126.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.115.0...v1.126.0) (2018-11-27)
### Bug Fixes
- **deps:** add filtered npm audit ([698ac3c](https://github.com/mozilla/fxa-basket-proxy/commit/698ac3c)), closes [mozilla/fxa#303](https://github.com/mozilla/fxa/issues/303)
- **deps:** drop nodemon, update mozlog ([3d78ee6](https://github.com/mozilla/fxa-basket-proxy/commit/3d78ee6))
- **deps:** Update deps to resolve `npm audit` warnings. ([c10d68d](https://github.com/mozilla/fxa-basket-proxy/commit/c10d68d))
### chore
- **deps:** remove grunt-nsp and nsp grunttask ([138fdd7](https://github.com/mozilla/fxa-basket-proxy/commit/138fdd7))
### Features
- **ci:** update to circle 2 (#61); r=rfk ([3bd0f43](https://github.com/mozilla/fxa-basket-proxy/commit/3bd0f43)), closes [#61](https://github.com/mozilla/fxa-basket-proxy/issues/61)
<a name="1.115.0"></a>
# [1.115.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.112.0...v1.115.0) (2018-06-27)
### Bug Fixes
- **docker:** base image node:8-alpine and upgrade to npm6 (#59) r=@vladikoff ([215f960](https://github.com/mozilla/fxa-basket-proxy/commit/215f960))
<a name="1.112.0"></a>
# [1.112.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.109.0...v1.112.0) (2018-05-21)
### Bug Fixes
- **api:** More closely match native basket api behaviour. (#56) r=@vladikoff ([fc19a0d](https://github.com/mozilla/fxa-basket-proxy/commit/fc19a0d))
### chore
- **api:** More tests and cleanups for basket API compatibility. (#57); r=stomlinson ([9982bc6](https://github.com/mozilla/fxa-basket-proxy/commit/9982bc6))
### Features
- **node:** update to node 8 (#58) r=@jrgm ([6f4f5a0](https://github.com/mozilla/fxa-basket-proxy/commit/6f4f5a0))
<a name="1.109.0"></a>
# [1.109.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.106.0...v1.109.0) (2018-04-04)
### Bug Fixes
- **node:** Use Node.js v6.14.0 (#54) r=@vladikoff ([31f545f](https://github.com/mozilla/fxa-basket-proxy/commit/31f545f))
<a name="1.106.0"></a>
# [1.106.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.100.0...v1.106.0) (2018-02-21)
### Bug Fixes
- **node:** use node 6.12.3 (#51) r=@vladikoff ([c78f69d](https://github.com/mozilla/fxa-basket-proxy/commit/c78f69d))
### chore
- **deps:** update deps, fix nsp (#52) r=@philbooth ([4307e25](https://github.com/mozilla/fxa-basket-proxy/commit/4307e25)), closes [(#52](https://github.com/(/issues/52)
<a name="1.100.0"></a>
# [1.100.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.99.0...v1.100.0) (2017-11-15)
### Bug Fixes
- **node:** use node 6.12.0 (#50) r=@vladikoff ([0c005eb](https://github.com/mozilla/fxa-basket-proxy/commit/0c005eb))
- **travis:** run tests with 6 and 8 (#49) ([1579354](https://github.com/mozilla/fxa-basket-proxy/commit/1579354))
<a name="1.99.0"></a>
# [1.99.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.98.0...v1.99.0) (2017-10-31)
### Features
- **events:** Add ability to discard events after a certain timestamp. (#48); r=philbooth ([39d19ee](https://github.com/mozilla/fxa-basket-proxy/commit/39d19ee))
<a name="1.98.0"></a>
# [1.98.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.97.0...v1.98.0) (2017-10-26)
### chore
- **docker:** Update to node v6.11.5 for security fix ([0ec3ab9](https://github.com/mozilla/fxa-basket-proxy/commit/0ec3ab9))
<a name="1.97.0"></a>
# [1.97.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.96.0...v1.97.0) (2017-10-03)
### chore
- **deps:** update dependencies (#46) r=vladikoff ([47c486b](https://github.com/mozilla/fxa-basket-proxy/commit/47c486b))
<a name="1.96.0"></a>
# [1.96.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.95.0...v1.96.0) (2017-09-19)
### chore
- **style:** ES6-ify ./lib/events/index.js ([35c6bd9](https://github.com/mozilla/fxa-basket-proxy/commit/35c6bd9))
### Features
- **events:** Add ability to selectively disable SQS events. ([3f6adfb](https://github.com/mozilla/fxa-basket-proxy/commit/3f6adfb))
<a name="1.95.0"></a>
# [1.95.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.92.0...v1.95.0) (2017-09-06)
### Features
- **campaigns:** Add additional newsletter campaigns ([3bbeda7](https://github.com/mozilla/fxa-basket-proxy/commit/3bbeda7))
<a name="1.92.0"></a>
# [1.92.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.91.0...v1.92.0) (2017-07-26)
### Features
- **sqs:** look for `marketingOptIn` on 'verified' events (#41) r=vladikoff ([df6bddd](https://github.com/mozilla/fxa-basket-proxy/commit/df6bddd)), closes [#40](https://github.com/mozilla/fxa-basket-proxy/issues/40)
<a name="1.91.0"></a>
# [1.91.0](https://github.com/mozilla/fxa-basket-proxy/compare/v1.75.0...v1.91.0) (2017-07-17)
### Bug Fixes
- **tests:** make tests work in node 4 and 6 ([e917f6e](https://github.com/mozilla/fxa-basket-proxy/commit/e917f6e)), closes [#38](https://github.com/mozilla/fxa-basket-proxy/issues/38)
<a name="1.75.0"></a>
# [1.75.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.74.0...v1.75.0) (2017-05-24)
### chore
- **version:** bump from v0.74.0 to v1.74.0 ([cc7dfd4](https://github.com/mozilla/fxa-basket-proxy/commit/cc7dfd4))
<a name="0.74.0"></a>
# [0.74.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.73.0...v0.74.0) (2017-05-24)
### Bug Fixes
- **version:** use cwd and env var to get version (#34) r=vladikoff ([1f7e8b0](https://github.com/mozilla/fxa-basket-proxy/commit/1f7e8b0))
### chore
- **config:** Add environment config options ([231eced](https://github.com/mozilla/fxa-basket-proxy/commit/231eced))
- **docker:** Use official node image & update to Node.js v4.8.2 (#36) r=vladikoff ([1054164](https://github.com/mozilla/fxa-basket-proxy/commit/1054164))
- **docs:** update to node 4 ([c2a4d3b](https://github.com/mozilla/fxa-basket-proxy/commit/c2a4d3b))
### Features
- **docker:** add custom feature branch (#37) r=jrgm ([827c041](https://github.com/mozilla/fxa-basket-proxy/commit/827c041))
- **docker:** Shrink Docker image size ([00f3e70](https://github.com/mozilla/fxa-basket-proxy/commit/00f3e70))
<a name="0.73.0"></a>
# [0.73.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.71.0...v0.73.0) (2016-11-02)
### Bug Fixes
- **basket:** Always send a source_url param to /subscribe ([7e88cdb](https://github.com/mozilla/fxa-basket-proxy/commit/7e88cdb))
<a name="0.71.0"></a>
# [0.71.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.64.0...v0.71.0) (2016-10-05)
### Bug Fixes
- **test:** Allow NODE_ENV=test when running the tests. ([2ee82c7](https://github.com/mozilla/fxa-basket-proxy/commit/2ee82c7))
### chore
- **deps:** Downgrade some deps for node 0.10 compatibility ([5f0878a](https://github.com/mozilla/fxa-basket-proxy/commit/5f0878a))
- **deps:** Update dependencies to latest versions. ([5f1f293](https://github.com/mozilla/fxa-basket-proxy/commit/5f1f293))
### Features
- **docker:** Add CloudOps Dockerfile & CircleCI build instructions ([726494b](https://github.com/mozilla/fxa-basket-proxy/commit/726494b))
<a name="0.64.0"></a>
# [0.64.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.63.0...v0.64.0) (2016-06-22)
### Bug Fixes
- **index:** remove unused callback ([ce858ef](https://github.com/mozilla/fxa-basket-proxy/commit/ce858ef))
### Features
- **events:** Simplify logic for utm_campaign auto-subscription. ([350bfd4](https://github.com/mozilla/fxa-basket-proxy/commit/350bfd4))
<a name="0.63.0"></a>
# [0.63.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.62.0...v0.63.0) (2016-06-02)
### Bug Fixes
- **logging:** run tests at INFO log level, fixes #25 ([1de2c93](https://github.com/mozilla/fxa-basket-proxy/commit/1de2c93)), closes [#25](https://github.com/mozilla/fxa-basket-proxy/issues/25)
### chore
- **events:** Promisify the event-handling machinery. ([654f62b](https://github.com/mozilla/fxa-basket-proxy/commit/654f62b))
### Features
- **events:** Flip subscription flags in response to certain utm\_\* params. ([7aa4ac7](https://github.com/mozilla/fxa-basket-proxy/commit/7aa4ac7))
- **events:** Forward metrics context from events to /fxa-activity ([836725a](https://github.com/mozilla/fxa-basket-proxy/commit/836725a))
- **metrics:** Send a `source_url` with utm params where possible. ([abf8031](https://github.com/mozilla/fxa-basket-proxy/commit/abf8031))
<a name="0.62.0"></a>
# [0.62.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.59.0...v0.62.0) (2016-05-23)
### chore
- **mozlog:** update to mozlog 2.0.4 ([4bb5a24](https://github.com/mozilla/fxa-basket-proxy/commit/4bb5a24))
- **travis:** stop running travis on node@0.12 ([8bada17](https://github.com/mozilla/fxa-basket-proxy/commit/8bada17))
<a name="0.59.0"></a>
# [0.59.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.56.1...v0.59.0) (2016-03-29)
### Bug Fixes
- **errors:** move common error handling into a basket.proxy() method ([21ff37f](https://github.com/mozilla/fxa-basket-proxy/commit/21ff37f))
### Features
- **sms:** add /subscribe_sms route ([575b848](https://github.com/mozilla/fxa-basket-proxy/commit/575b848)), closes [#21](https://github.com/mozilla/fxa-basket-proxy/issues/21)
<a name="0.56.1"></a>
## [0.56.1](https://github.com/mozilla/fxa-basket-proxy/compare/v0.56.0...v0.56.1) (2016-02-22)
### Bug Fixes
- **version:** a script to record git version info during rpm build ([c0e242d](https://github.com/mozilla/fxa-basket-proxy/commit/c0e242d))
<a name="0.56.0"></a>
# [0.56.0](https://github.com/mozilla/fxa-basket-proxy/compare/v0.50.0...v0.56.0) (2016-02-10)
### chore
- **deps:** Update dependencies ([d21fdab](https://github.com/mozilla/fxa-basket-proxy/commit/d21fdab))
<a name="0.50.0"></a>
# 0.50.0 (2015-11-18)
### Features
- **email:** explicitly fetch email address from profile data ([3cb5b31](https://github.com/mozilla/fxa-basket-proxy/commit/3cb5b31))
<a name="0.48.0"></a>
# 0.48.0 (2015-10-26)

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

@ -1,82 +0,0 @@
# Contributing
Anyone is welcome to help with Firefox Accounts. Feel free to get in touch with other community members on IRC, the
mailing list or through issues here on GitHub.
- IRC: `#fxa` on `irc.mozilla.org`
- Mailing list: <https://mail.mozilla.org/listinfo/dev-fxacct>
- and of course, [the issues list](https://github.com/mozilla/fxa-basket-proxy/issues)
## Bug Reports
You can file issues here on GitHub. Please try to include as much information as you can and under what conditions
you saw the issue.
## Sending Pull Requests
Patches should be submitted as pull requests (PR).
Before submitting a PR:
- Your code must run and pass all the automated tests before you submit your PR for review. "Work in progress" pull requests are allowed to be submitted, but should be clearly labeled as such and should not be merged until all tests pass and the code has been reviewed.
- Run `grunt lint` to make sure your code passes linting.
- Run `npm test` to make sure all tests still pass.
- Your patch should include new tests that cover your changes. It is your and your reviewer's responsibility to ensure your patch includes adequate tests.
When submitting a PR:
- You agree to license your code under the project's open source license ([MPL 2.0](/LICENSE)).
- Base your branch off the current `master` (see below for an example workflow).
- Add both your code and new tests if relevant.
- Run `grunt lint` and `npm test` to make sure your code passes linting and tests.
- Please do not include merge commits in pull requests; include only commits with the new relevant code.
See the main [README.md](/README.md) for information on prerequisites, installing, running and testing.
## Code Review
This project is production Mozilla code and subject to our [engineering practices and quality standards](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Committing_Rules_and_Responsibilities). Every patch must be peer reviewed. This project is part of the [Firefox Accounts module](https://wiki.mozilla.org/Modules/Other#Firefox_Accounts), and your patch must be reviewed by one of the listed module owners or peers.
## Example Workflow
This is an example workflow to make it easier to submit Pull Requests. Imagine your username is `user1`:
1. Fork this repository via the GitHub interface
2. The clone the upstream (as origin) and add your own repo as a remote:
```sh
$ git clone https://github.com/mozilla/fxa-basket-proxy.git
$ cd fxa-basket-proxy
$ git remote add user1 git@github.com:user1/fxa-basket-proxy.git
```
3. Create a branch for your fix/feature and make sure it's your currently checked-out branch:
```sh
$ git checkout -b add-new-feature
```
4. Add/fix code, add tests then commit and push this branch to your repo:
```sh
$ git add <files...>
$ git commit
$ git push user1 add-new-feature
```
5. From the GitHub interface for your repo, click the `Review Changes and Pull Request` which appears next to your new branch.
6. Click `Send pull request`.
### Keeping up to Date
The main reason for creating a new branch for each feature or fix is so that you can track master correctly. If you need
to fetch the latest code for a new fix, try the following:
```sh
$ git checkout master
$ git pull
```
Now you're ready to branch again for your new feature (from step 3 above).

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

@ -1,21 +0,0 @@
FROM node:8-alpine
RUN npm install -g npm@6 && rm -rf ~app/.npm /tmp/*
RUN addgroup -g 10001 app && \
adduser -D -G app -h /app -u 10001 app
WORKDIR /app
# S3 bucket in Cloud Services prod IAM
ADD https://s3.amazonaws.com/dumb-init-dist/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init
RUN chmod +x /usr/local/bin/dumb-init
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
USER app
COPY npm-shrinkwrap.json npm-shrinkwrap.json
COPY package.json package.json
RUN npm install --production && rm -rf ~app/.npm /tmp/*
COPY . /app

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

@ -1,2 +0,0 @@
FROM fxa-basket-proxy:build
RUN npm install

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

@ -1,33 +0,0 @@
#!/usr/bin/env node
/* 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/. */
// Generated on 2013-12-04 using generator-backbone-amd 0.0.4
// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'
module.exports = function(grunt) {
// load all grunt tasks based on environment
if (process.env.NODE_ENV && process.env.NODE_ENV === 'production') {
require('load-grunt-tasks')(grunt, { scope: 'dependencies' });
} else {
// show elapsed time at the end
require('time-grunt')(grunt);
require('load-grunt-tasks')(grunt);
}
// setup logging
require('./lib/logging');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
});
grunt.loadTasks('grunttasks');
};

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

@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

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

@ -1,47 +0,0 @@
# Firefox Accounts Basket Proxy
[![Build Status: Travis](https://travis-ci.org/mozilla/fxa-basket-proxy.svg?branch=master)](https://travis-ci.org/mozilla/fxa-basket-proxy)
This server acts as an intermediary between Firefox Accounts and
[Basket](http://basket.readthedocs.org/en/latest/), Mozilla's email newsletter
subscription system. It allows FxA-OAuth-authenticated access to the Basket API
and is responsible for some background data-syncing tasks.
Over time, we expect most of the functionality of this proxy to be absorbed
into Basket itself; running it as a separate system in the meantime gives us
the ability to iterate quickly and minimise coupling between the two systems.
To run the proxy:
node ./bin/basket-proxy-server.js
To process account-related events from SQS:
node ./bin/basket-event-handler.js
For testing and development purposes, there's a minimal 'fake' implementation
of the Basket server API that stores its state in memory. Run it like so,
and the proxy will use it unless configured with the URL of a live Basket
server:
node ./bin/fake-basket-server.js
## Prerequisites
- node 6
- npm
- Grunt
[Grunt](http://gruntjs.com/) is used to run common tasks to build, test, and run local servers.
| TASK | DESCRIPTION |
| --------------------- | ------------------------------------------------------------------------------------- |
| `grunt lint` | run ESLint and JSCS (code style checker) on the code. |
| `grunt server` | run a local server running on port 1114. |
| `grunt test` | run local tests. |
| `grunt version` | stamp a new minor version. Updates the version number and creates a new CHANGELOG.md. |
| `grunt version:patch` | stamp a new patch version. Updates the version number and creates a new CHANGELOG.md. |
## License
MPL 2.0

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

@ -1,17 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Runs an event-handling loop to process account-related events from SQS.
*/
var config = require('../lib/config');
var events = require('../lib/events');
var SQSReceiver = require('../lib/events/sqs');
var basketQueue = new SQSReceiver(config.get('basket.sqs.region'), [
config.get('basket.sqs.queue_url'),
]);
basketQueue.on('data', events.handleEvent);
basketQueue.start();

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

@ -1,24 +0,0 @@
#!/usr/bin/env node
/* 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/. */
/*
* Runs an OAuth-authenticted proxy API to a basket server.
*/
var url = require('url');
var config = require('../lib/config');
var logger = require('../lib/logging')('server');
var app = require('../lib/app.js');
function listen(app) {
var proxyUrl = url.parse(config.get('basket.proxy_url'));
app.listen(proxyUrl.port, proxyUrl.hostname);
logger.info('FxA Basket Proxy listening on port', proxyUrl.port);
return true;
}
listen(app());

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

@ -1,24 +0,0 @@
#!/usr/bin/env node
/* 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/. */
/*
* Runs a fake in-memory basket server, for dev/testing purposes.
*/
var url = require('url');
var config = require('../lib/config');
var logger = require('../lib/logging')('server');
const app = require('../lib/basket/fake');
function listen(app) {
var apiUrl = url.parse(config.get('basket.api_url'));
app.listen(apiUrl.port, apiUrl.hostname);
logger.info(`FxA Fake Basket Server listening on port ${apiUrl.port}`);
return true;
}
listen(app(logger));

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

@ -1,6 +0,0 @@
{
"env": "dev",
"log": {
"level": "debug"
}
}

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

@ -1,9 +0,0 @@
{
"env": "prod",
"basket": {
"apiUrl": "https://basket.mozilla.org/news"
},
"log": {
"format": "heka"
}
}

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

@ -1,28 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// takes care of bumping the version number in package.json
module.exports = function(grunt) {
grunt.config('bump', {
options: {
files: ['package.json', 'npm-shrinkwrap.json'],
bumpVersion: true,
commit: true,
commitMessage: 'Release v%VERSION%',
commitFiles: [
'package.json',
'npm-shrinkwrap.json',
'CHANGELOG.md',
'AUTHORS',
],
createTag: true,
tagName: 'v%VERSION%',
tagMessage: 'Version %VERSION%',
push: false,
pushTo: 'origin',
gitDescribeOptions: '--tags --always --abrev=1 --dirty=-d',
},
});
};

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

@ -1,18 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var fxaChangelog = require('fxa-conventional-changelog')();
module.exports = function(grunt) {
grunt.config('conventionalChangelog', {
options: {
changelogOpts: {},
parserOpts: fxaChangelog.parserOpts,
writerOpts: fxaChangelog.writerOpts,
},
release: {
src: 'CHANGELOG.md',
},
});
};

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

@ -1,14 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module.exports = function(grunt) {
grunt.config('copyright', {
app: {
options: {
pattern: /This Source Code Form is subject to the terms of the Mozilla Public/,
},
src: ['<%= eslint.files %>', '!test/lib/blanket.js'],
},
});
};

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

@ -1,12 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module.exports = function(grunt) {
grunt.config('eslint', {
options: {
eslintrc: '.eslintrc',
},
files: ['{,bin/,config/,grunttasks/,lib/**/,test/**/}*.js'],
});
};

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

@ -1,11 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// meta grunt task to run other linters.
module.exports = function(grunt) {
var SUBTASKS = ['copyright', 'eslint'];
grunt.registerTask('lint', 'lint all the things', SUBTASKS);
};

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

@ -1,46 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module.exports = function(grunt) {
grunt.config('todo', {
options: {
marks: [
{
name: 'FIX',
pattern: /FIXME/,
color: 'red',
},
{
name: 'TODO',
pattern: /TODO/,
color: 'yellow',
},
{
name: 'NOTE',
pattern: /NOTE/,
color: 'blue',
},
{
name: 'XXX',
pattern: /XXX/,
color: 'yellow',
},
{
name: 'HACK',
pattern: /HACK/,
color: 'red',
},
],
},
app: {
files: {
src: [
'<%= eslint.files %>',
// ignore this file, lest we get oodles of false positives.
'!grunttasks/todo.js',
],
},
},
});
};

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

@ -1,24 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// A task to stamp a new version.
//
// * version is updated in package.json
// * CHANGELOG.md updated with changes since last version
// * git tag with version name is created.
// * git commit with updated package.json and CHANGELOG.md created.
module.exports = function(grunt) {
grunt.registerTask('version', [
'bump-only:minor',
'conventionalChangelog',
'bump-commit',
]);
grunt.registerTask('version:patch', [
'bump-only:patch',
'conventionalChangelog',
'bump-commit',
]);
};

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

@ -1,38 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var express = require('express');
var bodyParser = require('body-parser');
var cors = require('cors');
var config = require('./config');
var verifyOAuthToken = require('./verify');
var logSummary = require('./logging/summary');
var routes = require('./routes');
var CORS_ORIGIN = config.get('cors_origin');
module.exports = function initApp() {
var app = express();
app.set('x-powered-by', false);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(logSummary());
app.use(
cors({
origin: CORS_ORIGIN,
})
);
app.use(verifyOAuthToken());
app.get('/', routes.version);
app.get('/__version__', routes.version);
app.get('/lookup-user', routes.lookup);
app.post('/subscribe', routes.subscribe);
app.post('/unsubscribe', routes.unsubscribe);
app.post('/subscribe_sms', routes.sms);
return app;
};

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

@ -1,146 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* A fake basket server API, for testing and development purposes.
*/
var config = require('../config');
var basket = require('./');
const cors = require('cors');
var bodyParser = require('body-parser');
var express = require('express');
const verifyOAuthToken = require('../verify')();
const API_KEY = config.get('basket.api_key');
const CORS_ORIGIN = config.get('cors_origin');
function verifyAuthorization(logger) {
return (req, res, next) => {
const apiKey = req.headers['x-api-key'];
const authHeader = req.headers.authorization;
if (apiKey) {
logger.info('fake.authorization.api_key');
verifyApiKey(req, res, next);
} else if (authHeader) {
logger.info('fake.authorization.oauth');
verifyOAuthToken(req, res, next);
} else {
res
.status(400)
.json(basket.errorResponse('unauthorized', basket.errors.AUTH_ERROR));
}
};
}
function verifyApiKey(req, res, next) {
var key = req.headers['x-api-key'];
if (key && key === API_KEY) {
return next();
}
res
.status(400)
.json(basket.errorResponse('unauthorized', basket.errors.AUTH_ERROR));
}
function extend(target, source) {
for (var key in source) {
target[key] = source[key];
}
return target;
}
module.exports = function initApp(logger) {
var userData = {};
var tokenToUser = {};
var tokens = 0;
function newToken() {
return tokens++;
}
var app = express();
app.use(bodyParser.urlencoded());
app.use(
cors({
origin: CORS_ORIGIN,
})
);
app.use(verifyAuthorization(logger));
app.get('/lookup-user/', function(req, res) {
const email =
(res.locals.creds && res.locals.creds.email) || req.query.email;
if (!userData[email]) {
res
.status(404)
.json(
basket.errorResponse('unknown-email', basket.errors.UNKNOWN_EMAIL)
);
return;
}
var dataToSend = extend({ status: 'ok' }, userData[email]);
res.status(200).json(dataToSend);
});
app.post('/subscribe/', function(req, res) {
var params = req.body;
const email = (res.locals.creds && res.locals.creds.email) || params.email;
var user = userData[email];
// Basket accepts either an explicit language choice,
// or an "accept_lang" preference string from which it
// will choose the best language.
var lang = params.lang;
if (!lang) {
lang = params.accept_lang;
if (lang) {
// We're just a test server, don't bother with any
// elaborate accept-lang parsing, just use first one.
lang = lang.split(/[\s\-;,]/)[0];
} else {
lang = 'en-US';
}
}
var token;
if (!user) {
token = newToken();
userData[email] = {
email: email,
token: token,
lang: lang,
newsletters: params.newsletters.split(','),
};
tokenToUser[token] = userData[email];
} else {
user.newsletters = user.newsletters.concat(params.newsletters.split(','));
}
res.status(200).json({ status: 'ok' });
});
app.post('/unsubscribe/:token/', function(req, res) {
var user = tokenToUser[req.params.token];
var newsletters = req.body.newsletters.split(',');
if (user) {
user.newsletters = user.newsletters.filter(function(id) {
return newsletters.indexOf(id) === -1;
});
res.status(200).json({ status: 'ok' });
} else {
res
.status(400)
.json(
basket.errorResponse('unknown-token', basket.errors.UNKNOWN_TOKEN)
);
return;
}
});
return app;
};

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

@ -1,79 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var request = require('request');
var config = require('../config');
var logger = require('../logging')('basket');
var API_KEY = config.get('basket.api_key');
var API_URL = config.get('basket.api_url');
var API_TIMEOUT = config.get('basket.api_timeout');
// Proxy a request to the Basket backend and pipe onto the response
exports.proxy = function basketProxy(path, options, res) {
return exports
.request(path, options)
.on('error', function(error) {
logger.error(error);
// If the error event fires after a response has already started,
// we'll error out trying to send the 500 Server Error response.
// That's OK, just ignore it.
try {
res
.status(500)
.json(exports.errorResponse(error, exports.errors.UNKNOWN_ERROR));
} catch (ex) {
logger.error(ex);
}
})
.pipe(res);
};
// Send a request to the Basket backend
exports.request = function basketRequest(path, options, done) {
var reqOptions = {
url: API_URL + path,
strictSSL: true,
timeout: API_TIMEOUT,
headers: {
'X-API-Key': API_KEY,
},
};
Object.keys(options).forEach(function(key) {
reqOptions[key] = options[key];
});
return request(reqOptions, done);
};
// Error codes are defined in:
// https://github.com/mozilla/basket-client/blob/master/basket/errors.py
exports.errors = {
NETWORK_FAILURE: 1,
INVALID_EMAIL: 2,
UNKNOWN_EMAIL: 3,
UNKNOWN_TOKEN: 4,
USAGE_ERROR: 5,
EMAIL_PROVIDER_AUTH_FAILURE: 6,
AUTH_ERROR: 7,
SSL_REQUIRED: 8,
INVALID_NEWSLETTER: 9,
INVALID_LANGUAGE: 10,
EMAIL_NOT_CHANGED: 11,
CHANGE_REQUEST_NOT_FOUND: 12,
// If you get this, report it as a bug so we can add a more specific
// error code.
UNKNOWN_ERROR: 99,
};
module.exports.errorResponse = function errorResponse(desc, code) {
// Format from
// https://basket.readthedocs.org/en/latest/newsletter_api.html
return {
status: 'error',
desc: String(desc),
code: code || module.exports.errors.UNKNOWN_ERROR,
};
};

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

@ -1,152 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*eslint-disable camelcase */
var convict = require('convict');
var fs = require('fs');
var path = require('path');
var conf = (module.exports = convict({
env: {
doc:
"What environment are we running in? Note: all hosted environments are 'production'.",
format: ['production', 'development', 'test'],
default: 'production',
env: 'NODE_ENV',
},
log: {
level: {
default: 'info',
env: 'LOG_LEVEL',
},
format: {
default: 'pretty',
format: ['heka', 'pretty'],
env: 'LOG_FORMAT',
},
app: {
default: 'fxa-basket-proxy',
env: 'LOG_APP_NAME',
},
},
basket: {
proxy_url: {
doc: 'Url for the Basket proxy server',
format: String,
default: 'http://127.0.0.1:1114',
env: 'BASKET_PROXY_URL',
},
api_url: {
doc: 'Url for the Basket API server',
format: String,
default: 'http://127.0.0.1:10140',
env: 'BASKET_API_URL',
},
api_key: {
doc: 'Basket API key',
format: String,
default: 'test key please change',
env: 'BASKET_API_KEY',
},
api_timeout: {
doc: 'Timeout for talking to the Basket API server, in ms',
format: 'duration',
default: '5 seconds',
env: 'BASKET_API_TIMEOUT',
},
newsletter_campaigns: {
doc: 'Values of utm_campaign that identify a newsletter campaign',
format: Object,
default: {
'fxa-embedded-form-moz': 'mozilla-welcome',
'fxa-embedded-form-fx': 'firefox-welcome',
'membership-idealo': 'member-idealo',
'membership-comm': 'member-comm',
'membership-tech': 'member-tech',
'membership-tk': 'member-tk',
},
env: 'BASKET_NEWSLETTER_CAMPAIGNS',
},
newsletter_id_register: {
doc: 'Newsletter ID to subscribe new registrations who opted in.',
format: String,
default: 'firefox-accounts-journey',
env: 'BASKET_NEWSLETTER_ID_REGISTER',
},
source_url: {
doc: 'The source_url value to report to basket in subscription requests',
format: String,
default: 'https://accounts.firefox.com',
env: 'BASKET_SOURCE_URL',
},
sqs: {
region: {
doc: 'The region where the queues live, e.g. us-east-1, us-west-2',
format: String,
env: 'BASKET_SQS_REGION',
default: '',
},
queue_url: {
doc: 'The basket event queue URL',
format: String,
env: 'BASKET_SQS_QUEUE_URL',
default: '',
},
disabled_event_types: {
doc: 'List of SQS events types that have been explicitly disabled',
format: Array,
env: 'BASKET_SQS_DISABLED_EVENT_TYPES',
default: [],
},
disabled_after_timestamp: {
doc: 'Events with timestamp greater than this value will be discarded',
format: 'timestamp',
env: 'BASKET_SQS_DISABLED_AFTER_TIMESTAMP',
default: 0,
},
},
},
fxaccount_url: {
default: 'http://127.0.0.1:9000',
doc: 'The url of the Firefox Account auth server',
env: 'FXA_URL',
format: 'url',
},
oauth_url: {
doc: 'The url of the Firefox Account OAuth server',
format: 'url',
default: 'http://127.0.0.1:9010',
env: 'FXA_OAUTH_URL',
},
cors_origin: {
doc: 'Origin to allow in CORS headers',
default: '*',
env: 'CORS_ORIGIN',
},
}));
var DEV_CONFIG_PATH = path.join(__dirname, '..', 'config', 'local.json');
var files;
// Handle configuration files.
// You can specify a CSV list of configuration files to process in the
// CONFIG_FILES environment variable. They will be overlaid on the default
// config in order.
if (process.env.CONFIG_FILES && process.env.CONFIG_FILES.trim() !== '') {
files = process.env.CONFIG_FILES.split(',');
} else if (fs.existsSync(DEV_CONFIG_PATH)) {
files = [DEV_CONFIG_PATH];
}
if (files) {
conf.loadFile(files);
}
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = conf.get('env');
}
conf.validate({
allowed: 'strict',
});

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

@ -1,195 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const url = require('url');
const Promise = require('bluebird');
const basket = require('../basket');
const logger = require('../logging')('events');
const config = require('../config');
// Certain special values of the utm_campaign metrics parameters
// are used to indicate a newsletter campaign, and cause us to
// auto-subscribe the user to a particular newsletter.
const NEWSLETTER_CAMPAIGNS = config.get('basket.newsletter_campaigns');
const NEWSLETTER_ID_REGISTER = config.get('basket.newsletter_id_register');
const SOURCE_URL_BASE = config.get('basket.source_url');
const messageHandlers = (module.exports._messageHandlers = {
verified: onVerified,
login: onLogin,
});
const DISABLED_EVENT_TYPES = config.get('basket.sqs.disabled_event_types');
if (DISABLED_EVENT_TYPES) {
DISABLED_EVENT_TYPES.forEach(typ => {
delete messageHandlers[typ];
});
}
const DISABLED_AFTER_TIMESTAMP = config.get(
'basket.sqs.disabled_after_timestamp'
);
module.exports.handleEvent = function handleEvent(message) {
logger.info('handleEvent', message);
const messageHandler = messageHandlers[message.event];
if (!messageHandler) {
logger.info('handleEvent.ignored', message.event);
return new Promise(function(cb) {
message.del(cb);
});
}
if (DISABLED_AFTER_TIMESTAMP && message.ts) {
// Event timestamps are in seconds, config is in milliseconds.
if (message.ts * 1000 >= DISABLED_AFTER_TIMESTAMP) {
logger.info('handleEvent.disabledAfterTimestamp', message.event);
return new Promise(function(cb) {
message.del(cb);
});
}
}
return messageHandler(message);
};
/* eslint-disable camelcase */
// For each new verified account, register it with basket.
function onVerified(message) {
// Ignore email addresses that are clearly from dev testing.
if (shouldIgnoreEmail(message.email)) {
message.del();
return Promise.resolve();
}
const params = {
fxa_id: message.uid,
email: message.email,
// Basket won't accept empty or null `accept_lang` field,
// so we default to en-US. This should only happen if
// the user has not sent an explicit Accept-Language header.
accept_lang: message.locale || 'en-US',
};
if (message.marketingOptIn) {
return basketPromise(message, '/fxa-register/', params, 'form').then(
function() {
const metrics = message.metricsContext || {};
// We don't look for a newsletter campaign this time around. We
// specifically want to subcribe to the default newletter. If
// there is a utm_campaign parameter, we'll hear about it in the
// 'login' event later.
const source_url = url.parse(SOURCE_URL_BASE, true);
for (const key in metrics) {
if (key.indexOf('utm_') === 0) {
source_url.query[key] = metrics[key];
}
}
const params = {
email: message.email,
newsletters: NEWSLETTER_ID_REGISTER,
source_url: url.format(source_url),
};
return forwardEvent(message, '/subscribe/', params, 'form');
}
);
} else {
return forwardEvent(message, '/fxa-register/', params, 'form');
}
}
// For each new login, inform basket so it can build up a user model.
function onLogin(message) {
// Ignore email addresses that are clearly from dev testing.
if (shouldIgnoreEmail(message.email)) {
message.del();
return Promise.resolve();
}
const metrics = message.metricsContext || {};
return Promise.resolve()
.then(function() {
// If utm_campaign indicates it's a newsletter campaign, flag it by
// subscribing to the corresponding newsletter.
const newsletter = NEWSLETTER_CAMPAIGNS[metrics.utm_campaign];
if (metrics.utm_campaign && newsletter) {
const source_url = url.parse(SOURCE_URL_BASE, true);
for (const key in metrics) {
if (key.indexOf('utm_') === 0) {
source_url.query[key] = metrics[key];
}
}
const params = {
email: message.email,
newsletters: newsletter,
source_url: url.format(source_url),
};
logger.info('campaign-subscribe', params);
return basketPromise(message, '/subscribe/', params, 'form');
}
})
.then(function() {
// Pass on all logins to basket, for metrics and analysis purposes.
return forwardEvent(
message,
'/fxa-activity/',
{
activity: 'account.login',
service: message.service,
fxa_id: message.uid,
first_device: message.deviceCount === 1,
user_agent: message.userAgent,
metrics_context: metrics,
},
'json'
);
});
}
/* eslint-enable camelcase */
function forwardEvent(message, endpoint, data, dataFormat) {
// Forward all others to basket API.
return basketPromise(message, endpoint, data, dataFormat).then(function() {
message.del();
});
}
function basketPromise(message, endpoint, data, dataFormat) {
return new Promise(function(resolve, reject) {
const options = { method: 'POST' };
options[dataFormat] = data;
basket.request(endpoint, options, function(err, res, body) {
message.endpoint = endpoint;
// Log network-level errors, and leave event in queue for retry.
if (err) {
message.err = err;
logger.error('event.request.error.network', message);
return reject(err);
}
message.status = res.statusCode;
message.body = body;
// Log at error level for HTTP-level errors, info level otherwise.
if (res.statusCode < 200 || res.statusCode >= 300) {
logger.error('event.request.error.http', message);
} else {
logger.info('event.request', message);
}
resolve();
});
});
}
function shouldIgnoreEmail(email) {
if (email.match(/@restmail.net$/)) {
return true;
}
if (email.match(/@restmail.lcip.org$/)) {
return true;
}
return false;
}

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

@ -1,79 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var AWS = require('aws-sdk');
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var logger = require('../logging')('events.sqs');
module.exports = SQSReceiver;
function SQSReceiver(region, urls) {
this.sqs = new AWS.SQS({ region: region });
this.queueUrls = urls || [];
EventEmitter.call(this);
}
inherits(SQSReceiver, EventEmitter);
SQSReceiver.prototype.start = function() {
for (var i = 0; i < this.queueUrls.length; i++) {
this.fetch(this.queueUrls[i]);
}
};
SQSReceiver.prototype.fetch = function(url) {
var errRetryTimer = null;
this.sqs.receiveMessage(
{
QueueUrl: url,
AttributeNames: [],
MaxNumberOfMessages: 10,
WaitTimeSeconds: 20,
},
function(err, data) {
if (err) {
logger.error('fetch.receive-error', { url: url, err: err });
if (!errRetryTimer) {
// The aws lib will call the callback more than once with different errors.
// Avoid spawning multiple retries by using a timer.
// ಠ_ಠ
errRetryTimer = setTimeout(this.fetch.bind(this, url), 2000);
}
return;
}
function deleteMessage(message, cb) {
this.sqs.deleteMessage(
{
QueueUrl: url,
ReceiptHandle: message.ReceiptHandle,
},
function checkDeleteError(err) {
if (err) {
logger.error('delete.error', { url: url, err: err });
}
if (cb) {
cb(err);
}
}
);
}
data.Messages = data.Messages || [];
for (var i = 0; i < data.Messages.length; i++) {
var msg = data.Messages[i];
var deleteFromQueue = deleteMessage.bind(this, msg);
try {
var body = JSON.parse(msg.Body);
var message = JSON.parse(body.Message);
message.del = deleteFromQueue;
this.emit('data', message);
} catch (e) {
logger.error('fetch.dispatch-error', { url: url, err: e });
deleteFromQueue();
}
}
this.fetch(url);
}.bind(this)
);
};

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

@ -1,17 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var mozlog = require('mozlog');
var config = require('../config').get('log');
mozlog.config(config);
var root = mozlog('logging');
if (root.isEnabledFor('debug')) {
root.warn(
'\t*** CAREFUL! Louder logs (less than INFO)' + ' may include SECRETS! ***'
);
}
module.exports = mozlog;

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

@ -1,39 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var onFinished = require('on-finished');
var logger = require('./')('summary');
// Adds request-summary logging to an express app.
// https://mana.mozilla.org/wiki/display/CLOUDSERVICES/Logging+Standard
module.exports = function() {
return function summary(req, res, next) {
if (req.method === 'options') {
return;
}
var startTime = Date.now();
onFinished(res, function logSummary(err, res) {
var line = {
agent: req.headers['user-agent'],
auth: res.locals &&
res.locals.creds && {
user: res.locals.creds.user,
},
code: res.statusCode,
method: req.method,
path: req.path,
payload: Object.keys(req.body || {}),
t: Date.now() - startTime,
};
if (line.code >= 500) {
line.stack = res.stack || err ? err.stack : '';
logger.error('summary', line);
} else {
logger.info('summary', line);
}
});
next();
};
};

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

@ -1,11 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module.exports = {
lookup: require('./lookup'),
sms: require('./sms'),
subscribe: require('./subscribe'),
unsubscribe: require('./unsubscribe'),
version: require('./version'),
};

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

@ -1,25 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var logger = require('../logging')('routes.lookup-user');
var basket = require('../basket');
module.exports = function lookup(req, res) {
if (!res.locals.creds) {
logger.error('auth.missing-authorization-header');
res
.status(400)
.json(
basket.errorResponse(
'missing authorization header',
basket.errors.USAGE_ERROR
)
);
return;
}
var email = encodeURIComponent(res.locals.creds.email);
basket.proxy('/lookup-user/?email=' + email, { method: 'get' }, res);
};

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

@ -1,31 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var logger = require('../logging')('routes.sms');
var basket = require('../basket');
module.exports = function sms(req, res) {
if (!res.locals.creds) {
logger.error('auth.missing-authorization-header');
res
.status(400)
.json(
basket.errorResponse(
'missing authorization header',
basket.errors.USAGE_ERROR
)
);
return;
}
var params = req.body;
logger.info('params', params);
logger.info('activityEvent', {
event: 'basket.sms',
uid: res.locals.creds.user,
userAgent: req.headers['user-agent'] || undefined,
});
basket.proxy('/subscribe_sms/', { method: 'post', form: params }, res);
};

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

@ -1,43 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var logger = require('../logging')('routes.subscribe');
var basket = require('../basket');
var config = require('../config');
var DEFAULT_SOURCE_URL = config.get('basket.source_url');
module.exports = function subscribe(req, res) {
if (!res.locals.creds) {
logger.error('auth.missing-authorization-header');
res
.status(400)
.json(
basket.errorResponse(
'missing authorization header',
basket.errors.USAGE_ERROR
)
);
return;
}
var params = req.body;
params.email = res.locals.creds.email;
/* eslint-disable camelcase */
if (req.headers['accept-language']) {
params.accept_lang = req.headers['accept-language'];
}
if (!params.source_url) {
params.source_url = DEFAULT_SOURCE_URL;
}
/* eslint-enable camelcase */
logger.info('params', params);
logger.info('activityEvent', {
event: 'basket.subscribe',
uid: res.locals.creds.user,
userAgent: req.headers['user-agent'] || undefined,
});
basket.proxy('/subscribe/', { method: 'post', form: params }, res);
};

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

@ -1,70 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var logger = require('../logging')('routes.unsubscribe');
var basket = require('../basket');
module.exports = function unsubscribe(req, res) {
if (!res.locals.creds) {
logger.error('auth.missing-authorization-header');
res
.status(400)
.json(
basket.errorResponse(
'missing authorization header',
basket.errors.USAGE_ERROR
)
);
return;
}
var creds = res.locals.creds;
var email = encodeURIComponent(creds.email);
logger.info('activityEvent', {
event: 'basket.unsubscribe',
uid: res.locals.creds.user,
userAgent: req.headers['user-agent'] || undefined,
});
basket.request('/lookup-user/?email=' + email, { method: 'get' }, function(
lookupError,
httpRequest,
body
) {
if (lookupError) {
logger.error('lookup-user.error', lookupError);
res
.status(400)
.json(basket.errorResponse(lookupError, basket.errors.UNKNOWN_ERROR));
return;
}
var responseData;
try {
responseData = JSON.parse(body);
} catch (parseError) {
logger.error('lookup-user.cannot-parse-response', parseError);
res
.status(400)
.json(basket.errorResponse(parseError, basket.errors.UNKNOWN_ERROR));
return;
}
if (responseData.status !== 'ok') {
logger.error('lookup-user.status-not-ok', responseData.status);
res.status(httpRequest.statusCode).json(responseData);
return;
}
var params = req.body;
params.email = creds.email;
logger.info('params', params);
basket.proxy(
'/unsubscribe/' + responseData.token + '/',
{ method: 'post', form: params },
res
);
});
};

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

@ -1,112 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Return version info based on package.json, the git sha, and source repo
*
* Try to statically determine commitHash, sourceRepo at startup.
*
* If commitHash cannot be found from ./config/version.json (i.e., this is not
* production or stage), then an attempt will be made to determine commitHash
* and sourceRepo dynamically from `git`. If it cannot be found with `git`,
* just show UNKNOWN for commitHash and sourceRepo.
*
*/
var cp = require('child_process');
var path = require('path');
var Promise = require('bluebird');
var logger = require('../logging')('routes.version');
var UNKNOWN = 'unknown';
var versionJsonPath = '../../config/version.json';
function getPkgVersion() {
return require('../../package.json').version;
}
function getCommitHash() {
try {
var versionInfo = require(versionJsonPath);
var ver = versionInfo.version;
return ver.hash;
} catch (e) {
/* ignore, shell out to `git` for hash */
}
var deferred = Promise.defer();
var gitDir = path.resolve(__dirname, '..', '..', '.git');
cp.exec('git rev-parse HEAD', { cwd: gitDir }, function(err, stdout) {
if (err) {
// ignore the error
deferred.resolve(UNKNOWN);
return;
}
deferred.resolve((stdout && stdout.trim()) || UNKNOWN);
});
return deferred.promise;
}
function getSourceRepo() {
try {
var versionInfo = require(versionJsonPath);
var ver = versionInfo.version;
return ver.source;
} catch (e) {
/* ignore, shell out to `git` for repo */
}
var deferred = Promise.defer();
var gitDir = path.resolve(__dirname, '..', '..', '.git');
var configPath = path.join(gitDir, 'config');
var cmd = 'git config --get remote.origin.url';
cp.exec(cmd, { env: { GIT_CONFIG: configPath } }, function(err, stdout) {
if (err) {
// ignore the error
deferred.resolve(UNKNOWN);
return;
}
deferred.resolve((stdout && stdout.trim()) || UNKNOWN);
});
return deferred.promise;
}
var versionPromise;
function getVersionInfo() {
if (!versionPromise) {
// only fetch version info if it has not already been fetched.
versionPromise = Promise.all([
getSourceRepo(),
getPkgVersion(),
getCommitHash(),
]).spread(function(sourceRepo, pkgVersion, commitHash) {
logger.info('source set to', sourceRepo);
logger.info('version set to', pkgVersion);
logger.info('commit hash set', commitHash);
return {
version: pkgVersion,
commit: commitHash,
source: sourceRepo,
};
});
}
return versionPromise;
}
getVersionInfo();
module.exports = function(req, res) {
getVersionInfo().then(function(versionInfo) {
// charset must be set on json responses.
res.charset = 'utf-8';
res.type('json').send(JSON.stringify(versionInfo, null, 2) + '\n');
});
};

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

@ -1,152 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const request = require('request');
const config = require('./config');
const logger = require('./logging')('verify');
const basket = require('./basket');
const VERIFY_URL = config.get('oauth_url') + '/v1/verify';
const PROFILE_URL = config.get('fxaccount_url') + '/v1/account/profile';
const REQUIRED_SCOPE_REGEX = /^basket(:write)?$/;
// Adds FxA OAuth token verification to an express app.
module.exports = function verifyOAuthToken() {
return function(req, res, next) {
var authHeader = req.headers && req.headers.authorization;
if (!authHeader) {
next();
return;
}
if (!authHeader.match(/^Bearer /)) {
logger.error('auth.invalid-authorization-header');
res
.status(400)
.json(
basket.errorResponse(
'invalid authorization header',
basket.errors.USAGE_ERROR
)
);
return;
}
var token = authHeader.replace(/^Bearer /, '');
logger.info('auth.valid.starting');
request.post(
{
url: VERIFY_URL,
json: {
token: token,
},
},
function(err, result, body) {
if (err) {
logger.error('auth.error', err);
res
.status(500)
.json(basket.errorResponse(err, basket.errors.UNKNOWN_ERROR));
return;
}
if (result.statusCode >= 400) {
logger.error('auth.unauthorized', body);
res
.status(result.statusCode)
.json(
basket.errorResponse('unauthorized', basket.errors.AUTH_ERROR)
);
return;
}
if (!body.user) {
logger.error('auth.missing-user', body);
res
.status(400)
.json(
basket.errorResponse('missing user', basket.errors.AUTH_ERROR)
);
return;
}
if (!body.scope || !(body.scope instanceof Array)) {
logger.error('auth.missing-scope', body);
res
.status(400)
.json(
basket.errorResponse('missing scope', basket.errors.AUTH_ERROR)
);
return;
}
if (!body.scope.find(s => REQUIRED_SCOPE_REGEX.test(s))) {
logger.error('auth.invalid-scope', body);
res
.status(400)
.json(
basket.errorResponse('invalid scope', basket.errors.AUTH_ERROR)
);
return;
}
logger.info('auth.valid', body);
res.locals.creds = body;
logger.info('auth.profile.starting');
request.get(
{
url: PROFILE_URL,
json: true,
headers: {
Authorization: authHeader,
},
},
function(err, result, body) {
if (err) {
logger.error('auth.profile.error', err);
res
.status(500)
.json(basket.errorResponse(err, basket.errors.UNKNOWN_ERROR));
return;
}
if (result.statusCode >= 400) {
logger.error('auth.profile.unauthorized', body);
res
.status(result.statusCode)
.json(
basket.errorResponse('unauthorized', basket.errors.AUTH_ERROR)
);
return;
}
if (!body.email) {
logger.error('auth.profile.missing-email', body);
res
.status(400)
.json(
basket.errorResponse(
'missing email',
basket.errors.AUTH_ERROR
)
);
return;
}
logger.info('auth.profile');
res.locals.creds.email = body.email;
next();
}
);
}
);
};
};

5599
packages/fxa-basket-proxy/npm-shrinkwrap.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -1,57 +0,0 @@
{
"name": "fxa-basket-proxy",
"version": "1.134.0",
"description": "Firefox Accounts Basket Proxy Server",
"scripts": {
"start": "NODE_ENV=development node bin/basket-proxy-server.js",
"lint:deps": "npm audit --json | audit-filter --nsp-config=.nsprc --audit=-",
"test": "NODE_ENV=test ./scripts/test-local.sh",
"format": "prettier '**' --write"
},
"repository": {
"type": "git",
"url": "https://github.com/mozilla/fxa.git"
},
"homepage": "https://github.com/mozilla/fxa/tree/master/packages/fxa-basket-proxy",
"bugs": "https://github.com/mozilla/fxa/issues",
"author": "Mozilla (https://mozilla.org/)",
"license": "MPL-2.0",
"dependencies": {
"aws-sdk": "2.343.0",
"bluebird": "3.4.6",
"body-parser": "1.18.2",
"convict": "4.0.2",
"cors": "2.8.1",
"express": "4.16.2",
"grunt": "1.0.4",
"grunt-cli": "1.2.0",
"grunt-rev": "0.1.0",
"load-grunt-tasks": "3.5.2",
"mozlog": "2.2.0",
"on-finished": "2.3.0",
"request": "2.88.0",
"time-grunt": "1.4.0"
},
"devDependencies": {
"audit-filter": "0.3.0",
"eslint-config-prettier": "^5.0.0",
"eslint-plugin-fxa": "1.0.0",
"fxa-conventional-changelog": "1.1.0",
"grunt-bump": "0.8.0",
"grunt-conventional-changelog": "6.1.0",
"grunt-copyright": "0.3.0",
"grunt-eslint": "21.1.0",
"grunt-todo": "0.5.0",
"mocha": "5.2.0",
"moment": "2.22.2",
"nock": "8.0.0",
"npmshrink": "1.0.1",
"nyc": "13.1.0",
"prettier": "^1.18.2",
"supertest": "3.3.0"
},
"engines": {
"node": ">=8"
},
"readmeFilename": "README.md"
}

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

@ -1,41 +0,0 @@
#!/usr/bin/env node
/* 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/. */
'use strict';
const path = require('path');
const spawn = require('child_process').spawn;
const MOCHA_BIN = path.join(
path.dirname(__dirname),
'node_modules',
'.bin',
'mocha'
);
const NYC_BIN = path.join(
path.dirname(__dirname),
'node_modules',
'.bin',
'nyc'
);
let bin = NYC_BIN;
let argv = ['--cache', MOCHA_BIN, '--check-coverage', '--lines', '90'];
if (process.env.NO_COVERAGE) {
bin = MOCHA_BIN;
argv = [];
}
const p = spawn(bin, argv.concat(process.argv.slice(2)), {
stdio: 'inherit',
env: process.env,
});
// exit this process with the same exit code as the test process
p.on('close', function(code) {
process.exit(code);
});

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

@ -1,36 +0,0 @@
#!/usr/bin/env node
/* 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/. */
var cp = require('child_process');
var util = require('util');
// Generate legacy-format output that looks something like:
//
// {
// "version": {
// "hash": "88f6f24e53da56faa933c357bffb61cbeaec7ff3",
// "subject": "Merge pull request #2939 from vladikoff/sentry-patch3",
// "committer date": "1439472293",
// "source": "git://github.com/mozilla/fxa-content-server.git"
// }
// }
//
// This content is placed in the stage/prod rpm at `./config/version.json`.
// Ignore errors and always produce a (possibly empty struct) output.
var args = '{"hash":"%H","subject":"%s","committer date":"%ct"}';
var cmd = util.format("git --no-pager log --format=format:'%s' -1", args);
cp.exec(cmd, function(err, stdout) {
var info = {
version: JSON.parse(stdout || '{}'),
};
var cmd = 'git config --get remote.origin.url';
cp.exec(cmd, function(err, stdout) {
info.version.source = (stdout && stdout.trim()) || '';
// eslint-disable-next-line no-console
console.log(JSON.stringify(info, null, 2));
});
});

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

@ -1,19 +0,0 @@
#!/bin/sh
set -eu
glob=$*
if [ -z "$glob" ]; then
glob="--recursive test/*.js"
fi
if [ -z ${NODE_ENV+x} ]; then
NODE_ENV=test
fi
if [ -z ${LOG_LEVEL+x} ]; then
LOG_LEVEL=critical
fi
LOG_LEVEL=$LOG_LEVEL NODE_ENV=$NODE_ENV ./scripts/mocha-coverage.js -R spec $glob --timeout 5000
grunt lint

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

@ -1,9 +0,0 @@
plugins:
- fxa
extends: ../.eslintrc
env:
mocha: true
rules:
fxa/no-new-buffer: 2

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

@ -1,690 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
/* eslint-disable camelcase */
const assert = require('assert');
const moment = require('moment');
const events = require('../lib/events');
const mocks = require('./lib/mocks');
const utils = require('./lib/utils');
const UID = 'foobar';
const EMAIL = 'foobar@example.com';
const LOCALE = 'en-AU';
const USER_AGENT = 'a fake testing browser (like Gecko)';
const SERVICE = 'sync';
const CAMPAIGN_NEWSLETTER_SLUG = 'mozilla-welcome';
const CAMPAIGN_NEWSLETTER_CONTEXT = {
utm_campaign: 'fxa-embedded-form-moz',
utm_source: 'firstrun',
};
const CAMPAIGN_NEWSLETTER_SOURCE_URL =
'https://accounts.firefox.com/?utm_campaign=fxa-embedded-form-moz&utm_source=firstrun';
const NEWSLETTER_ID_REGISTER = 'firefox-accounts-journey';
const SOURCE_URL_REGISTER = 'https://accounts.firefox.com/';
const MAYBE_NEXT_TICK = cb => {
if (cb) {
process.nextTick(cb);
}
};
describe('the handleEvent() function', function() {
it('calls /fxa-register for account verification events', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/x-www-form-urlencoded' },
})
.post('/fxa-register/', function(body) {
assert.deepEqual(body, {
fxa_id: UID,
email: EMAIL,
accept_lang: LOCALE,
});
return true;
})
.reply(200, {
status: 'ok',
});
events.handleEvent({
event: 'verified',
ts: Date.now() / 1000,
uid: UID,
email: EMAIL,
locale: LOCALE,
del: function() {
done();
},
});
});
it('calls /subscribe for verifications events when user opts in', function(done) {
const subscribe = mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTER_ID_REGISTER,
source_url: SOURCE_URL_REGISTER,
});
return true;
})
.reply(200, {
status: 'ok',
});
const register = mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/x-www-form-urlencoded' },
})
.post('/fxa-register/', function(body) {
assert.deepEqual(body, {
fxa_id: UID,
email: EMAIL,
accept_lang: LOCALE,
});
return true;
})
.reply(200, {
status: 'ok',
});
events.handleEvent({
event: 'verified',
ts: Date.now() / 1000,
uid: UID,
email: EMAIL,
locale: LOCALE,
marketingOptIn: true,
del: function() {
subscribe.done();
register.done();
done();
},
});
});
it('calls /fxa-activity for device login events', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/json' },
})
.post('/fxa-activity/', function(body) {
assert.deepEqual(body, {
activity: 'account.login',
service: SERVICE,
fxa_id: UID,
first_device: true,
user_agent: USER_AGENT,
metrics_context: {},
});
return true;
})
.reply(200, {
status: 'ok',
});
events.handleEvent({
event: 'login',
ts: Date.now() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 1,
userAgent: USER_AGENT,
del: function() {
done();
},
});
});
it('calls /fxa-activity with a correct `first_device` value', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/json' },
})
.post('/fxa-activity/', function(body) {
assert.deepEqual(body, {
activity: 'account.login',
service: SERVICE,
fxa_id: UID,
first_device: false,
user_agent: USER_AGENT,
metrics_context: {},
});
return true;
})
.reply(200, {
status: 'ok',
});
events.handleEvent({
event: 'login',
ts: Date.now() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 2,
userAgent: USER_AGENT,
del: function() {
done();
},
});
});
it('calls /fxa-activity with metrics context data', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/json' },
})
.post('/fxa-activity/', function(body) {
assert.deepEqual(body, {
activity: 'account.login',
service: SERVICE,
fxa_id: UID,
first_device: false,
user_agent: USER_AGENT,
metrics_context: {
utm_campaign: 'test-campaign',
utm_source: 'firstrun',
},
});
return true;
})
.reply(200, {
status: 'ok',
});
events.handleEvent({
event: 'login',
ts: Date.now() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 2,
userAgent: USER_AGENT,
metricsContext: {
utm_campaign: 'test-campaign',
utm_source: 'firstrun',
},
del: function() {
done();
},
});
});
it('ignores unrecognized event types', function(done) {
events.handleEvent({
event: 'unknownEvent',
del: function() {
// This is reached without trying to hit the basket server.
done();
},
});
});
it('ignores "verified" events from dev email addresses', function(done) {
events.handleEvent({
event: 'verified',
ts: Date.now() / 1000,
uid: UID,
email: 'foo@restmail.net',
locale: LOCALE,
del: function() {
// This is reached without trying to hit the basket server.
done();
},
});
});
it('ignores "login" events from dev email addresses', function(done) {
events.handleEvent({
event: 'login',
ts: Date.now() / 1000,
service: SERVICE,
uid: UID,
email: 'bar@restmail.lcip.org',
deviceCount: 1,
userAgent: USER_AGENT,
del: function() {
// This is reached without trying to hit the basket server.
done();
},
});
});
it('uses "en-US" as the default locale is none is provided', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/x-www-form-urlencoded' },
})
.post('/fxa-register/', function(body) {
assert.deepEqual(body, {
fxa_id: UID,
email: EMAIL,
accept_lang: 'en-US',
});
return true;
})
.reply(200, {
status: 'ok',
});
events.handleEvent({
event: 'verified',
ts: Date.now() / 1000,
uid: UID,
email: EMAIL,
del: function() {
done();
},
});
});
it('does not delete events if a network-level error occurs', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/x-www-form-urlencoded' },
})
.post('/fxa-register/', function(body) {
assert.deepEqual(body, {
fxa_id: UID,
email: EMAIL,
accept_lang: LOCALE,
});
return true;
})
.replyWithError('ruh-roh!');
events
.handleEvent({
event: 'verified',
ts: Date.now() / 1000,
uid: UID,
email: EMAIL,
locale: LOCALE,
del: function() {
assert.fail('should not delete the message from the queue');
},
})
.catch(function(err) {
assert.equal(err.message, 'ruh-roh!');
done();
});
});
it('does delete events if a HTTP-level error occurs', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/x-www-form-urlencoded' },
})
.post('/fxa-register/', function(body) {
assert.deepEqual(body, {
fxa_id: UID,
email: EMAIL,
accept_lang: LOCALE,
});
return true;
})
.reply(500, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
});
events.handleEvent({
event: 'verified',
ts: Date.now() / 1000,
uid: UID,
email: EMAIL,
locale: LOCALE,
del: function() {
done();
},
});
});
it('subscribes to newsletters when given specific utm params', function(done) {
mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
assert.deepEqual(body, {
email: EMAIL,
newsletters: CAMPAIGN_NEWSLETTER_SLUG,
source_url: CAMPAIGN_NEWSLETTER_SOURCE_URL,
});
return true;
})
.reply(200, {
status: 'ok',
});
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/json' },
})
.post('/fxa-activity/', function(body) {
assert.deepEqual(body, {
activity: 'account.login',
service: SERVICE,
fxa_id: UID,
first_device: false,
user_agent: USER_AGENT,
metrics_context: CAMPAIGN_NEWSLETTER_CONTEXT,
});
return true;
})
.reply(200, {
status: 'ok',
});
events.handleEvent({
event: 'login',
ts: Date.now() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 2,
userAgent: USER_AGENT,
metricsContext: CAMPAIGN_NEWSLETTER_CONTEXT,
del: function() {
done();
},
});
});
it('does not delete events on network-level error in campaign subscription', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/x-www-form-urlencoded' },
})
.post('/subscribe/', function(body) {
assert.deepEqual(body, {
email: EMAIL,
newsletters: CAMPAIGN_NEWSLETTER_SLUG,
source_url: CAMPAIGN_NEWSLETTER_SOURCE_URL,
});
return true;
})
.replyWithError('ruh-roh!');
events
.handleEvent({
event: 'login',
ts: Date.now() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 2,
userAgent: USER_AGENT,
metricsContext: CAMPAIGN_NEWSLETTER_CONTEXT,
del: function() {
assert.fail('should not delete the message from the queue');
},
})
.catch(function(err) {
assert.equal(err.message, 'ruh-roh!');
done();
});
});
it('does delete events on HTTP-level error in campaign subscription', function(done) {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/x-www-form-urlencoded' },
})
.post('/subscribe/', function(body) {
assert.deepEqual(body, {
email: EMAIL,
newsletters: CAMPAIGN_NEWSLETTER_SLUG,
source_url: CAMPAIGN_NEWSLETTER_SOURCE_URL,
});
return true;
})
.reply(500, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
});
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/json' },
})
.post('/fxa-activity/', function(body) {
assert.deepEqual(body, {
activity: 'account.login',
service: SERVICE,
fxa_id: UID,
first_device: false,
user_agent: USER_AGENT,
metrics_context: CAMPAIGN_NEWSLETTER_CONTEXT,
});
return true;
})
.reply(200, {
status: 'ok',
});
events.handleEvent({
event: 'login',
ts: Date.now() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 2,
userAgent: USER_AGENT,
metricsContext: CAMPAIGN_NEWSLETTER_CONTEXT,
del: function() {
done();
},
});
});
it('correctly processes events without a `ts` property when $BASKET_SQS_DISABLED_AFTER_TIMESTAMP is not set', () => {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/json' },
})
.post('/fxa-activity/', function(body) {
assert.deepEqual(body, {
activity: 'account.login',
service: SERVICE,
fxa_id: UID,
first_device: true,
user_agent: USER_AGENT,
metrics_context: {},
});
return true;
})
.reply(200, {
status: 'ok',
});
return events.handleEvent({
event: 'login',
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 1,
userAgent: USER_AGENT,
del: MAYBE_NEXT_TICK,
});
});
it('correctly processes events without a `ts` property when $BASKET_SQS_DISABLED_AFTER_TIMESTAMP is set', () => {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/json' },
})
.post('/fxa-activity/', function(body) {
assert.deepEqual(body, {
activity: 'account.login',
service: SERVICE,
fxa_id: UID,
first_device: true,
user_agent: USER_AGENT,
metrics_context: {},
});
return true;
})
.reply(200, {
status: 'ok',
});
return utils.withEnviron(
{ BASKET_SQS_DISABLED_AFTER_TIMESTAMP: '2016-01-02 03:14:15' },
() => {
return utils.withFreshModules(
require,
['../lib/events', '../lib/config'],
() => {
const events = require('../lib/events');
return events.handleEvent({
event: 'login',
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 1,
userAgent: USER_AGENT,
del: MAYBE_NEXT_TICK,
});
}
);
}
);
});
it('correctly processes events with `ts` less than $BASKET_SQS_DISABLED_AFTER_TIMESTAMP', () => {
mocks
.mockBasketResponse({
reqheaders: { 'content-type': 'application/json' },
})
.post('/fxa-activity/', function(body) {
assert.deepEqual(body, {
activity: 'account.login',
service: SERVICE,
fxa_id: UID,
first_device: true,
user_agent: USER_AGENT,
metrics_context: {},
});
return true;
})
.reply(200, {
status: 'ok',
});
const cutover = '2016-01-02 03:14:15';
return utils.withEnviron(
{ BASKET_SQS_DISABLED_AFTER_TIMESTAMP: cutover },
() => {
return utils.withFreshModules(
require,
['../lib/events', '../lib/config'],
() => {
const events = require('../lib/events');
return events.handleEvent({
event: 'login',
ts:
moment(cutover)
.subtract(1, 'second')
.valueOf() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 1,
userAgent: USER_AGENT,
del: MAYBE_NEXT_TICK,
});
}
);
}
);
});
it('discards events with `ts` greater than $BASKET_SQS_DISABLED_AFTER_TIMESTAMP', () => {
// Since we don't set up any mocks,
// the below will fail if it attempts to process the event.
const cutover = '2016-01-02 03:14:15';
return utils.withEnviron(
{ BASKET_SQS_DISABLED_AFTER_TIMESTAMP: cutover },
() => {
return utils.withFreshModules(
require,
['../lib/events', '../lib/config'],
() => {
const events = require('../lib/events');
return events.handleEvent({
event: 'login',
ts:
moment(cutover)
.add(1, 'second')
.valueOf() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 1,
userAgent: USER_AGENT,
del: MAYBE_NEXT_TICK,
});
}
);
}
);
});
it('discards events with `ts` equal to $BASKET_SQS_DISABLED_AFTER_TIMESTAMP', () => {
// Since we don't set up any mocks,
// the below will fail if it attempts to process the event.
const cutover = '2016-01-02 03:14:15';
return utils.withEnviron(
{ BASKET_SQS_DISABLED_AFTER_TIMESTAMP: cutover },
() => {
return utils.withFreshModules(
require,
['../lib/events', '../lib/config'],
() => {
const events = require('../lib/events');
return events.handleEvent({
event: 'login',
ts: moment(cutover).valueOf() / 1000,
service: SERVICE,
uid: UID,
email: EMAIL,
deviceCount: 1,
userAgent: USER_AGENT,
del: MAYBE_NEXT_TICK,
});
}
);
}
);
});
});
describe('the set of message handler functions', () => {
it('defaults to "verified" and "login"', () => {
const handlers = Object.keys(events._messageHandlers);
assert.deepEqual(handlers.sort(), ['login', 'verified']);
});
it('excludes events listed in $BASKET_SQS_DISABLED_EVENT_TYPES', () => {
return utils.withEnviron(
{ BASKET_SQS_DISABLED_EVENT_TYPES: 'verified,some_other_event' },
() => {
return utils.withFreshModules(
require,
['../lib/events', '../lib/config'],
() => {
const events = require('../lib/events');
const handlers = Object.keys(events._messageHandlers);
assert.deepEqual(handlers.sort(), ['login']);
return events.handleEvent({
event: 'verified',
ts: Date.now() / 1000,
uid: UID,
email: EMAIL,
locale: LOCALE,
// This gets executed without attempting any HTTP requests.
// If HTTP requests are attempted they'll fail, and fail the test.
del: MAYBE_NEXT_TICK,
});
}
);
}
);
});
});

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

@ -1,27 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var nock = require('nock');
var config = require('../../lib/config');
var API_KEY = config.get('basket.api_key');
var API_URL = config.get('basket.api_url');
var VERIFY_URL = config.get('oauth_url') + '/v1/verify';
var PROFILE_URL = config.get('fxaccount_url') + '/v1/account/profile';
module.exports.mockOAuthResponse = function mockOAuthResponse() {
return nock(VERIFY_URL).post('');
};
module.exports.mockProfileResponse = function mockOAuthResponse() {
return nock(PROFILE_URL).get('');
};
module.exports.mockBasketResponse = function mockBasketResponse(opts) {
opts = opts || {};
opts.reqheaders = opts.reqheaders || {};
opts.reqheaders['x-api-key'] = API_KEY;
return nock(API_URL, opts);
};

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

@ -1,57 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const Promise = require('bluebird');
/* Temporarily set environment variables while executing a function.
*/
module.exports.withEnviron = function withEnviron(env, cb) {
const origEnv = {};
Object.keys(env).forEach(key => {
if (process.env.hasOwnProperty(key)) {
origEnv[key] = process.env[key];
}
process.env[key] = env[key];
});
return Promise.resolve()
.then(() => {
return cb();
})
.finally(() => {
Object.keys(env).forEach(key => {
if (origEnv.hasOwnProperty(key)) {
process.env[key] = origEnv[key];
} else {
delete process.env[key];
}
});
});
};
/* Temporarily clear module cache while executing a function.
*/
module.exports.withFreshModules = function withFreshModules(
require,
modules,
cb
) {
const origModules = {};
modules.forEach(module => {
const path = require.resolve(module);
origModules[path] = require.cache[path];
delete require.cache[path];
});
return Promise.resolve()
.then(() => {
return cb();
})
.finally(() => {
modules.forEach(module => {
const path = require.resolve(module);
require.cache[path] = origModules[path];
});
});
};

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

@ -1,301 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var request = require('supertest');
var app = require('../lib/app')();
var mocks = require('./lib/mocks');
var UID = 'abcdef123456';
describe('the route /lookup-user', function() {
it('forwards properly-authenticated requests through to basket', function(done) {
var EMAIL = 'test@example.com';
var TOKEN = 'abcdef123456';
var NEWSLETTERS = 'a,b,c';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.reply(200, {
status: 'ok',
email: EMAIL,
token: TOKEN,
newsletters: NEWSLETTERS,
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect(200, {
status: 'ok',
email: EMAIL,
token: TOKEN,
newsletters: NEWSLETTERS,
})
.end(done);
});
it('accepts `basket:write` scope for backwards-compatibility', function(done) {
var EMAIL = 'test@example.com';
var TOKEN = 'abcdef123456';
var NEWSLETTERS = 'a,b,c';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket:write'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.reply(200, {
status: 'ok',
email: EMAIL,
token: TOKEN,
newsletters: NEWSLETTERS,
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect(200, {
status: 'ok',
email: EMAIL,
token: TOKEN,
newsletters: NEWSLETTERS,
})
.end(done);
});
it('returns an error if the basket server request errors out', function(done) {
var EMAIL = 'test@example.com';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.replyWithError('ruh-roh!');
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(500, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
})
.end(done);
});
it('returns an error if no credentials are provided', function(done) {
request(app)
.get('/lookup-user')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 5,
desc: 'missing authorization header',
})
.end(done);
});
it('returns an error if invalid authn type is specified', function(done) {
request(app)
.get('/lookup-user')
.set('authorization', 'Basic username:password')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 5,
desc: 'invalid authorization header',
})
.end(done);
});
it('returns an error if the oauth token is invalid', function(done) {
mocks.mockOAuthResponse().reply(401, {
message: 'invalid assertion',
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(401, {
status: 'error',
code: 7,
desc: 'unauthorized',
})
.end(done);
});
it('returns an error if the oauth token has incorrect scope', function(done) {
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['profile'],
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 7,
desc: 'invalid scope',
})
.end(done);
});
it('returns an error if the oauth token has several scopes, but none match', function(done) {
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['profile', 'basketto', 'basket:writer'],
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 7,
desc: 'invalid scope',
})
.end(done);
});
it('returns an error if the oauth server request errors out', function(done) {
mocks.mockOAuthResponse().replyWithError('ruh-roh!');
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(500, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
})
.end(done);
});
it('returns an error if the auth server profile request errors out', function(done) {
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().replyWithError('ruh-roh!');
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(500, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
})
.end(done);
});
it('returns an error if the oauth response has no userid', function(done) {
mocks.mockOAuthResponse().reply(200, {
scope: ['basket', 'profile:email'],
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 7,
desc: 'missing user',
})
.end(done);
});
it('returns an error if the oauth response has no scope', function(done) {
mocks.mockOAuthResponse().reply(200, {
user: UID,
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 7,
desc: 'missing scope',
})
.end(done);
});
it('returns an error if the oauth response has non-array scope', function(done) {
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: 'basket profile:email',
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 7,
desc: 'missing scope',
})
.end(done);
});
it('returns an error if the auth server profile has no associated email', function(done) {
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket', 'profile:locale'],
});
mocks.mockProfileResponse().reply(200, {
locale: 'en-AU',
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 7,
desc: 'missing email',
})
.end(done);
});
it('returns an error if the oauth token cant read profile data', function(done) {
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(401, {
message: 'unauthorized',
});
request(app)
.get('/lookup-user')
.set('authorization', 'Bearer TOKEN')
.expect('Content-Type', /json/)
.expect(401, {
status: 'error',
code: 7,
desc: 'unauthorized',
})
.end(done);
});
});

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

@ -1,110 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var assert = require('assert');
var request = require('supertest');
var app = require('../lib/app')();
var mocks = require('./lib/mocks');
var UID = 'abcdef123456';
describe('/sms', function() {
it('forwards properly-authenticated requests through to basket', function(done) {
var PHONE_NUMBER = '15555551234';
var MSG_ID = 'SMS_Android';
var OPTIN = 'N';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: 'dont@ca.re',
});
mocks
.mockBasketResponse()
.post('/subscribe_sms/', function(body) {
assert.deepEqual(body, {
/*eslint-disable camelcase*/
mobile_number: PHONE_NUMBER,
msg_id: MSG_ID,
/*eslint-enable camelcase*/
optin: OPTIN,
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/subscribe_sms')
.set('authorization', 'Bearer TOKEN')
.send({
/*eslint-disable camelcase*/
mobile_number: PHONE_NUMBER,
msg_id: MSG_ID,
/*eslint-enable camelcase*/
optin: OPTIN,
})
.expect(200, {
status: 'ok',
})
.end(done);
});
it('returns an error if no credentials are provided', function(done) {
request(app)
.post('/subscribe_sms')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 5,
desc: 'missing authorization header',
})
.end(done);
});
it('returns an error if the basket server request errors out', function(done) {
var PHONE_NUMBER = '15555551234';
var MSG_ID = 'SMS_Android';
var OPTIN = 'N';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: 'dont@ca.re',
});
mocks
.mockBasketResponse()
.post('/subscribe_sms/', function(body) {
assert.deepEqual(body, {
/*eslint-disable camelcase*/
mobile_number: PHONE_NUMBER,
msg_id: MSG_ID,
/*eslint-enable camelcase*/
optin: OPTIN,
});
return true;
})
.replyWithError('ruh-roh!');
request(app)
.post('/subscribe_sms')
.set('authorization', 'Bearer TOKEN')
.send({
/*eslint-disable camelcase*/
mobile_number: PHONE_NUMBER,
msg_id: MSG_ID,
/*eslint-enable camelcase*/
optin: OPTIN,
})
.expect(500, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
})
.end(done);
});
});

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

@ -1,280 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var assert = require('assert');
var request = require('supertest');
var config = require('../lib/config');
var app = require('../lib/app')();
var mocks = require('./lib/mocks');
var UID = 'abcdef123456';
var DEFAULT_SOURCE_URL = config.get('basket.source_url');
describe('the /subscribe route', function() {
it('forwards properly-authenticated requests through to basket', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
/*eslint-disable camelcase */
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
source_url: DEFAULT_SOURCE_URL,
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/subscribe')
.set('authorization', 'Bearer TOKEN')
.send({ newsletters: NEWSLETTERS })
.expect(200, {
status: 'ok',
})
.end(done);
});
it('accepts form-encoded request bodies', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
/*eslint-disable camelcase */
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
source_url: DEFAULT_SOURCE_URL,
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/subscribe')
.set('authorization', 'Bearer TOKEN')
.type('form')
.send({ newsletters: NEWSLETTERS })
.expect(200, {
status: 'ok',
})
.end(done);
});
it('accepts a trailing slash on the path', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
/*eslint-disable camelcase */
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
source_url: DEFAULT_SOURCE_URL,
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/subscribe/')
.set('authorization', 'Bearer TOKEN')
.send({ newsletters: NEWSLETTERS })
.expect(200, {
status: 'ok',
})
.end(done);
});
it('passes through all params from body, except email', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
/*eslint-disable camelcase */
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
source_url: DEFAULT_SOURCE_URL,
sync: 'Y',
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/subscribe')
.set('authorization', 'Bearer TOKEN')
.send({
email: 'someone-else@example.com',
newsletters: NEWSLETTERS,
sync: 'Y',
})
.expect(200, {
status: 'ok',
})
.end(done);
});
it('returns an error if no credentials are provided', function(done) {
request(app)
.post('/subscribe')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 5,
desc: 'missing authorization header',
})
.end(done);
});
it('returns an error if the basket server request errors out', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
/*eslint-disable camelcase */
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
source_url: DEFAULT_SOURCE_URL,
});
return true;
})
.replyWithError('ruh-roh!');
request(app)
.post('/subscribe')
.set('authorization', 'Bearer TOKEN')
.send({ newsletters: NEWSLETTERS })
.expect(500, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
})
.end(done);
});
it('forwards the accept-language header if present', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
var ACCEPT_LANG = 'Accept-Language: de; q=1.0, en; q=0.5';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
/*eslint-disable camelcase */
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
source_url: DEFAULT_SOURCE_URL,
accept_lang: ACCEPT_LANG,
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/subscribe')
.set('authorization', 'Bearer TOKEN')
.set('accept-language', ACCEPT_LANG)
.send({ newsletters: NEWSLETTERS })
.expect(200, {
status: 'ok',
})
.end(done);
});
it('passes through the source_url param if provided', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
var SOURCE_URL = 'https://secure.example.com';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.post('/subscribe/', function(body) {
/*eslint-disable camelcase */
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
source_url: SOURCE_URL,
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/subscribe')
.set('authorization', 'Bearer TOKEN')
/*eslint-disable camelcase */
.send({
newsletters: NEWSLETTERS,
source_url: SOURCE_URL,
})
.expect(200, {
status: 'ok',
})
.end(done);
});
});

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

@ -1,289 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var assert = require('assert');
var request = require('supertest');
var app = require('../lib/app')();
var mocks = require('./lib/mocks');
var UID = 'abcdef123456';
describe('the /unsubscribe route', function() {
it('looks up token, forwards authenticated requests through to basket', function(done) {
var EMAIL = 'test@example.com';
var TOKEN = 'abcdef123456';
var NEWSLETTERS = 'a';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.reply(200, {
status: 'ok',
token: TOKEN,
});
mocks
.mockBasketResponse()
.post('/unsubscribe/' + TOKEN + '/', function(body) {
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/unsubscribe')
.set('authorization', 'Bearer TOKEN')
.send({ newsletters: NEWSLETTERS })
.expect(200, {
status: 'ok',
})
.end(done);
});
it('passes through all params from body, except email', function(done) {
var EMAIL = 'test@example.com';
var TOKEN = 'abcdef123456';
var NEWSLETTERS = 'b';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.reply(200, {
status: 'ok',
token: TOKEN,
});
mocks
.mockBasketResponse()
.post('/unsubscribe/' + TOKEN + '/', function(body) {
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
optout: 'Y',
});
return true;
})
.reply(200, {
status: 'ok',
});
request(app)
.post('/unsubscribe')
.set('authorization', 'Bearer TOKEN')
.send({
email: 'someone-else@example.com',
newsletters: NEWSLETTERS,
optout: 'Y',
})
.expect(200, {
status: 'ok',
})
.end(done);
});
it('guards aginst errors from looking up the token', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'c,d';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.reply(404, {
status: 'error',
desc: 'not found',
});
request(app)
.post('/unsubscribe')
.set('authorization', 'Bearer TOKEN')
.send({
email: EMAIL,
newsletters: NEWSLETTERS,
})
.expect(404, {
status: 'error',
desc: 'not found',
})
.end(done);
});
it('returns an error if no credentials are provided', function(done) {
request(app)
.post('/unsubscribe')
.expect('Content-Type', /json/)
.expect(400, {
status: 'error',
code: 5,
desc: 'missing authorization header',
})
.end(done);
});
it('returns an error if the basket server request errors out', function(done) {
var EMAIL = 'test@example.com';
var TOKEN = 'abcdef123456';
var NEWSLETTERS = 'b';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.reply(200, {
status: 'ok',
token: TOKEN,
});
mocks
.mockBasketResponse()
.post('/unsubscribe/' + TOKEN + '/', function(body) {
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
});
return true;
})
.replyWithError('ruh-roh!');
request(app)
.post('/unsubscribe')
.set('authorization', 'Bearer TOKEN')
.send({ newsletters: NEWSLETTERS })
.expect(500, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
})
.end(done);
});
it('returns an error if the basket server returns an error', function(done) {
var EMAIL = 'test@example.com';
var TOKEN = 'abcdef123456';
var NEWSLETTERS = 'b';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.reply(200, {
status: 'ok',
token: TOKEN,
});
mocks
.mockBasketResponse()
.post('/unsubscribe/' + TOKEN + '/', function(body) {
assert.deepEqual(body, {
email: EMAIL,
newsletters: NEWSLETTERS,
});
return true;
})
.reply(403, {
status: 'error',
desc: 'some random error',
});
request(app)
.post('/unsubscribe')
.set('authorization', 'Bearer TOKEN')
.send({ newsletters: NEWSLETTERS })
.expect(403, {
status: 'error',
desc: 'some random error',
})
.end(done);
});
it('returns an error if the token lookup request errors out', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.replyWithError('ruh-roh!');
request(app)
.post('/unsubscribe')
.set('authorization', 'Bearer TOKEN')
.send({ newsletters: NEWSLETTERS })
.expect(400, {
status: 'error',
code: 99,
desc: 'Error: ruh-roh!',
})
.end(done);
});
it('returns an error if the token lookup request returns invalid JSON', function(done) {
var EMAIL = 'test@example.com';
var NEWSLETTERS = 'a,b,c';
var desc;
try {
JSON.parse('<');
} catch (ex) {
desc = String(ex);
}
mocks.mockOAuthResponse().reply(200, {
user: UID,
scope: ['basket'],
});
mocks.mockProfileResponse().reply(200, {
email: EMAIL,
});
mocks
.mockBasketResponse()
.get('/lookup-user/')
.query({ email: EMAIL })
.reply(200, '<html>eh?</html>');
request(app)
.post('/unsubscribe')
.set('authorization', 'Bearer TOKEN')
.send({ newsletters: NEWSLETTERS })
.expect(400, {
status: 'error',
code: 99,
desc: desc,
})
.end(done);
});
});

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

@ -1,40 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var assert = require('assert');
var request = require('supertest');
var app = require('../lib/app')();
describe('the route /', function() {
it('should return version information', function(done) {
request(app)
.get('/')
.expect('Content-Type', /json/)
.expect(function(res) {
assert.deepEqual(Object.keys(res.body), [
'version',
'commit',
'source',
]);
})
.end(done);
});
});
describe('the route /__version__', function() {
it('should return version information', function(done) {
request(app)
.get('/')
.expect('Content-Type', /json/)
.expect(function(res) {
assert.deepEqual(Object.keys(res.body), [
'version',
'commit',
'source',
]);
})
.end(done);
});
});