mirror of
https://github.com/mozilla/fxa.git
synced 2025-12-13 20:36:41 +01:00
chore(deps): Remove browserid-verifier packages and references
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -88,10 +88,6 @@ Thumbs.db
|
|||||||
# circleci
|
# circleci
|
||||||
.circleci/local.yml
|
.circleci/local.yml
|
||||||
|
|
||||||
# browserid-verifier
|
|
||||||
packages/browserid-verifier/loadtest/venv
|
|
||||||
packages/browserid-verifier/loadtest/*.pyc
|
|
||||||
|
|
||||||
# fxa-admin-server
|
# fxa-admin-server
|
||||||
packages/fxa-admin-server/src/config/local.json
|
packages/fxa-admin-server/src/config/local.json
|
||||||
|
|
||||||
|
|||||||
@@ -10,16 +10,6 @@ services:
|
|||||||
# init: true
|
# init: true
|
||||||
# ports:
|
# ports:
|
||||||
# - "8080:8080"
|
# - "8080:8080"
|
||||||
browserid-verifier:
|
|
||||||
image: browserid-verifier:build
|
|
||||||
command: node server.js
|
|
||||||
environment:
|
|
||||||
- PORT=5050
|
|
||||||
- IP_ADDRESS=0.0.0.0
|
|
||||||
- FORCE_INSECURE_LOOKUP_OVER_HTTP=true
|
|
||||||
init: true
|
|
||||||
ports:
|
|
||||||
- "5050:5050"
|
|
||||||
auth:
|
auth:
|
||||||
image: fxa-auth-server:build
|
image: fxa-auth-server:build
|
||||||
entrypoint: /bin/bash -c
|
entrypoint: /bin/bash -c
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
9292 # Fortress
|
9292 # Fortress
|
||||||
8080 # 123done
|
8080 # 123done
|
||||||
10139 # 321done
|
10139 # 321done
|
||||||
5050 # browserid-verifier
|
|
||||||
3031 # payments server
|
3031 # payments server
|
||||||
7100 # support admin panel
|
7100 # support admin panel
|
||||||
8002 # pushbox
|
8002 # pushbox
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ spec:
|
|||||||
- ./backstage/mysql.resources.yaml
|
- ./backstage/mysql.resources.yaml
|
||||||
- ./backstage/redis.resources.yaml
|
- ./backstage/redis.resources.yaml
|
||||||
- ./packages/123done/backstage.yaml
|
- ./packages/123done/backstage.yaml
|
||||||
- ./packages/browserid-verifier/backstage.yaml
|
|
||||||
- ./packages/fxa-admin-panel/backstage.yaml
|
- ./packages/fxa-admin-panel/backstage.yaml
|
||||||
- ./packages/fxa-admin-server/backstage.yaml
|
- ./packages/fxa-admin-server/backstage.yaml
|
||||||
- ./packages/fxa-auth-server/backstage.yaml
|
- ./packages/fxa-auth-server/backstage.yaml
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["plugin:fxa/recommended"],
|
|
||||||
"plugins": ["fxa"],
|
|
||||||
"root": true
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"comment": "532, 534, 545 are various ReDoS that don't affect us.",
|
|
||||||
"comment_566": "Hoek merge vuln, which we don't use.",
|
|
||||||
"comment_1179": "1179 is prototype pollution in minimist, used by eslint, optimist, and mocha. Doesn't affect us, as we don't pass untrusted external inputs to any of these.",
|
|
||||||
"comment_1488": "Acorn DoS vuln (dep of browserify), only applies if passed untrusted user input.",
|
|
||||||
"exceptions": [
|
|
||||||
"https://nodesecurity.io/advisories/532",
|
|
||||||
"https://nodesecurity.io/advisories/534",
|
|
||||||
"https://nodesecurity.io/advisories/535",
|
|
||||||
"https://nodesecurity.io/advisories/566",
|
|
||||||
"https://npmjs.com/advisories/1179",
|
|
||||||
"https://npmjs.com/advisories/1488"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
LICENSE
|
|
||||||
.*
|
|
||||||
Dockerfile
|
|
||||||
loadtest/*
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
## A BrowserID verification server
|
|
||||||
|
|
||||||
This repository contains a flexible BrowserID verification server authored in
|
|
||||||
Node.JS which uses the [local verification library](https://github.com/mozilla/browserid-local-verify).
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
To run the verification server locally:
|
|
||||||
|
|
||||||
$ git clone https://github.com/mozilla/browserid-verifier
|
|
||||||
$ cd browserid-verifier
|
|
||||||
$ yarn install
|
|
||||||
$ yarn start
|
|
||||||
|
|
||||||
At this point, your verifier will be running and available to use locally over
|
|
||||||
HTTP.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
There are several configuration variables which can change the behavior of the
|
|
||||||
server. You can inspect available configuration variables in `lib/config.js`.
|
|
||||||
You can specify a set of `json` configuration files using the `CONFIG_FILES`
|
|
||||||
environment variable (separate each path with a comma (`,`)). Finally, you can
|
|
||||||
inspect the current server configuration with:
|
|
||||||
|
|
||||||
$ node ./lib/server.js -c
|
|
||||||
|
|
||||||
## Health Checks
|
|
||||||
|
|
||||||
The server exports an endpoint at `/status` that can be polled for server health.
|
|
||||||
When the server is healthy, a `200` HTTP response is returned with a body of `OK`.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
This package uses [Mocha](https://mochajs.org/) to test its code. By default `npm test` will test all JS files under `tests/`.
|
|
||||||
|
|
||||||
Test specific tests with the following commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test only tests/health-check.js
|
|
||||||
npx mocha tests/health-check.js
|
|
||||||
|
|
||||||
# Grep for "test servers should start"
|
|
||||||
npx mocha -g "test servers should start"
|
|
||||||
```
|
|
||||||
|
|
||||||
Refer to Mocha's [CLI documentation](https://mochajs.org/#command-line-usage) for more advanced test configuration.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
The server exports an HTTP endpoint at `/v2` that can be POSTed to verify BrowserID
|
|
||||||
assertions. Arguments may be provided inside a JSON object. The following are
|
|
||||||
required:
|
|
||||||
|
|
||||||
1. Requests must be an HTTP POST.
|
|
||||||
2. Content-Type must equal `application/json`
|
|
||||||
3. POST body must be valid JSON.
|
|
||||||
|
|
||||||
The following arguments are supported:
|
|
||||||
|
|
||||||
### **required** (string) `assertion`
|
|
||||||
|
|
||||||
A BrowserID assertion
|
|
||||||
|
|
||||||
### **required** (string) `audience`
|
|
||||||
|
|
||||||
The origin of the site to which the assertion is expected to be bound.
|
|
||||||
|
|
||||||
### **optional** (array of strings) `trustedIssuers`
|
|
||||||
|
|
||||||
An array of domain names that are _trusted_ issuers. Assertions
|
|
||||||
signed by any of the domains in this set will be honored regardless of
|
|
||||||
the presence of a subject or principal in a BrowserID assertion.
|
|
||||||
|
|
||||||
### Error Response
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
$ curl -H 'Content-Type: application/json' \
|
|
||||||
-d '{ "audience": "http://example.com", "assertion": "bogus" }' \
|
|
||||||
https://verifier.mozcloud.org/v2
|
|
||||||
{
|
|
||||||
"status": "failure",
|
|
||||||
"reason": "no certificates provided"
|
|
||||||
}
|
|
||||||
|
|
||||||
Upon failure, the verifier returns a non-200 HTTP status code. Additionally, the
|
|
||||||
response body contains a JSON formated response containing a `status` key with the
|
|
||||||
value of `failure`. Additionally, more verbose developer readable information will
|
|
||||||
be available in a string value on the `reason` key.
|
|
||||||
|
|
||||||
### Success Response
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
$ curl -H 'Content-Type: application/json' \
|
|
||||||
-d '{ "audience": "http://123done.org" , "assertion": "eyJhbG...ZEe7A" }'
|
|
||||||
https://verifier.mozcloud.org/v2
|
|
||||||
{
|
|
||||||
"audience": "http://123done.org",
|
|
||||||
"expires": 1389791993675,
|
|
||||||
"issuer": "mockmyid.com",
|
|
||||||
"email": "lloyd@mockmyid.com",
|
|
||||||
"status": "okay"
|
|
||||||
}
|
|
||||||
|
|
||||||
Upon successful assertion verification, a 200 response will be sent with a JSON formatted body.
|
|
||||||
The body will always include `audience`, `issuer`, `status` (of "okay"), and `expires`.
|
|
||||||
|
|
||||||
### Extra IdP claims
|
|
||||||
|
|
||||||
The verifier will extract any number of additional claims from the
|
|
||||||
Identity Certificate generated by the Identity Provider. These claims
|
|
||||||
will be returned under a `idpClaims` top level key in the success response. Hence, an identity
|
|
||||||
certificate which looks like this:
|
|
||||||
|
|
||||||
{
|
|
||||||
"pubkey": { "...": "..." },
|
|
||||||
"sub": "60ae5097-8118-4c58-bb80-7db2742d137e",
|
|
||||||
"iat": 1389964111,
|
|
||||||
"exp": 1421500111,
|
|
||||||
"iss": "example.com",
|
|
||||||
"fxa-version": 1,
|
|
||||||
"fxa-generation": 504,
|
|
||||||
"email": "user@example.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
Upon successful verification (which could only occur via `.trustedIssuers` because authority lookup will fail), will
|
|
||||||
result in a verifier response like this:
|
|
||||||
|
|
||||||
{
|
|
||||||
"audience": "...",
|
|
||||||
"expires": 1421500111,
|
|
||||||
"issuer": "example.com",
|
|
||||||
"idpClaims": {
|
|
||||||
"fxa-version": 1,
|
|
||||||
"fxa-generation": 504,
|
|
||||||
"email": "user@example.com"
|
|
||||||
},
|
|
||||||
"status": "okay"
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: backstage.io/v1alpha1
|
|
||||||
kind: Component
|
|
||||||
metadata:
|
|
||||||
name: fxa-browserid-verifier
|
|
||||||
description: Verifies BrowserID assertions.
|
|
||||||
tags:
|
|
||||||
- typescript
|
|
||||||
- javascript
|
|
||||||
- node
|
|
||||||
- hapi
|
|
||||||
annotations:
|
|
||||||
sentry.io/project-slug: mozilla/fxa-browserid-verify
|
|
||||||
circleci.com/project-slug: github/mozilla/fxa
|
|
||||||
spec:
|
|
||||||
type: service
|
|
||||||
lifecycle: production
|
|
||||||
owner: fxa-devs
|
|
||||||
system: mozilla-accounts
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"logging": {
|
|
||||||
"handlers": {
|
|
||||||
"console": {
|
|
||||||
"class": "intel/handlers/console",
|
|
||||||
"formatter": "json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"bid.summary": {
|
|
||||||
"propagate": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +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/. */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is browserid-local-verify wrapped in node-compute-cluster.
|
|
||||||
*
|
|
||||||
* It provides a "Verifier" class with a "verify" method just like the one
|
|
||||||
* in browserid-local-verify, except that it farms out work to subprocesses
|
|
||||||
* via node-compute-cluster rather than doing it inline.
|
|
||||||
*
|
|
||||||
* It's not a drop-in replacement for browserid-local-verify:
|
|
||||||
*
|
|
||||||
* * There is only verify(), not lookup() or other methods.
|
|
||||||
*
|
|
||||||
* * It doesn't emit async events like "debug" or "metrics" because
|
|
||||||
* there"s no support for that in node-compute-cluster. Yet...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const util = require('util'),
|
|
||||||
events = require('events'),
|
|
||||||
path = require('path'),
|
|
||||||
log = require('../log')('ccverifier'),
|
|
||||||
config = require('../config'),
|
|
||||||
cc = require('compute-cluster'),
|
|
||||||
_ = require('underscore');
|
|
||||||
|
|
||||||
function Verifier(args) {
|
|
||||||
events.EventEmitter.call(this);
|
|
||||||
this.args = args;
|
|
||||||
this.cc = new cc({
|
|
||||||
module: path.join(__dirname, 'worker.js'),
|
|
||||||
max_processes: config.get('computecluster.maxProcesses'),
|
|
||||||
max_backlog: config.get('computecluster.maxBacklog'),
|
|
||||||
})
|
|
||||||
.on('error', function (err) {
|
|
||||||
log.error('computeCluster.error', { err });
|
|
||||||
})
|
|
||||||
.on('info', function (msg) {
|
|
||||||
log.info('computeCluster.info', { message: msg });
|
|
||||||
})
|
|
||||||
.on('debug', function (msg) {
|
|
||||||
log.debug('computeCluster.debug', { message: msg });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(Verifier, events.EventEmitter);
|
|
||||||
|
|
||||||
const testServiceFailure = config.get('testServiceFailure');
|
|
||||||
|
|
||||||
Verifier.prototype.verify = function (args, cb) {
|
|
||||||
if (!cb) {
|
|
||||||
cb = args;
|
|
||||||
args = {};
|
|
||||||
}
|
|
||||||
args = _.extend({}, this.args, args);
|
|
||||||
this.cc.enqueue({ args: args }, function (err, res) {
|
|
||||||
if (err || testServiceFailure) {
|
|
||||||
// An error from the cluster itself.
|
|
||||||
return cb('compute cluster error: ' + err);
|
|
||||||
}
|
|
||||||
if (res.err) {
|
|
||||||
// An error from inside the verifier.
|
|
||||||
return cb(res.err);
|
|
||||||
} else {
|
|
||||||
// A valid result from the verifier.
|
|
||||||
return cb(null, res.res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Verifier.prototype.shutdown = function () {
|
|
||||||
this.cc.exit();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Verifier;
|
|
||||||
@@ -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/. */
|
|
||||||
|
|
||||||
const LocalVerifier = require('browserid-local-verify');
|
|
||||||
|
|
||||||
var verifier = new LocalVerifier();
|
|
||||||
|
|
||||||
process.on('message', function (message) {
|
|
||||||
if (!message.args) {
|
|
||||||
message.args = {};
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
verifier.verify(message.args, function (err, res) {
|
|
||||||
if (err) {
|
|
||||||
return process.send({ err: err });
|
|
||||||
}
|
|
||||||
return process.send({ res: res });
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
return process.send({ err: err });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('uncaughtException', function () {
|
|
||||||
process.exit(8);
|
|
||||||
});
|
|
||||||
@@ -1,167 +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 convict = require('convict');
|
|
||||||
convict.addFormats(require('convict-format-with-moment'));
|
|
||||||
convict.addFormats(require('convict-format-with-validator'));
|
|
||||||
|
|
||||||
function loadConf() {
|
|
||||||
var conf = convict({
|
|
||||||
ip: {
|
|
||||||
doc: 'The IP address to bind.',
|
|
||||||
format: String,
|
|
||||||
default: 'localhost',
|
|
||||||
env: 'IP_ADDRESS',
|
|
||||||
},
|
|
||||||
port: {
|
|
||||||
doc: 'The port to bind.',
|
|
||||||
format: 'port',
|
|
||||||
default: 0,
|
|
||||||
env: 'PORT',
|
|
||||||
},
|
|
||||||
fallback: {
|
|
||||||
doc: 'The domain of the fallback server, authoritative when lookup fails.',
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'FALLBACK_DOMAIN',
|
|
||||||
},
|
|
||||||
httpTimeout: {
|
|
||||||
doc: '(s) how long to spend attempting to fetch support documents',
|
|
||||||
format: Number,
|
|
||||||
default: 8.0,
|
|
||||||
env: 'HTTP_TIMEOUT',
|
|
||||||
},
|
|
||||||
insecureSSL: {
|
|
||||||
doc: '(testing only) Ignore invalid SSL certificates',
|
|
||||||
format: Boolean,
|
|
||||||
default: false,
|
|
||||||
env: 'INSECURE_SSL',
|
|
||||||
},
|
|
||||||
forceInsecureLookupOverHTTP: {
|
|
||||||
doc: '(testing only) Lookup /.well-known/browserid documents over HTTP',
|
|
||||||
format: Boolean,
|
|
||||||
default: false,
|
|
||||||
env: 'FORCE_INSECURE_LOOKUP_OVER_HTTP',
|
|
||||||
},
|
|
||||||
toobusy: {
|
|
||||||
maxLag: {
|
|
||||||
doc: 'Max event-loop lag before toobusy reports failure',
|
|
||||||
format: Number,
|
|
||||||
default: 70,
|
|
||||||
env: 'TOOBUSY_MAX_LAG',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computecluster: {
|
|
||||||
maxProcesses: {
|
|
||||||
doc: 'Max worker processes to spawn for the compute cluster',
|
|
||||||
format: Number,
|
|
||||||
default: undefined,
|
|
||||||
env: 'COMPUTECLUSTER_MAX_PROCESSES',
|
|
||||||
},
|
|
||||||
maxBacklog: {
|
|
||||||
doc: 'Max length of work queue for the compute cluster',
|
|
||||||
format: Number,
|
|
||||||
default: undefined,
|
|
||||||
env: 'COMPUTECLUSTER_MAX_BACKLOG',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logging: {
|
|
||||||
app: {
|
|
||||||
default: 'browserid-verifier',
|
|
||||||
},
|
|
||||||
fmt: {
|
|
||||||
format: ['heka', 'pretty'],
|
|
||||||
default: 'heka',
|
|
||||||
},
|
|
||||||
level: {
|
|
||||||
env: 'LOG_LEVEL',
|
|
||||||
default: 'debug',
|
|
||||||
},
|
|
||||||
debug: {
|
|
||||||
env: 'LOG_DEBUG',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sentry: {
|
|
||||||
dsn: {
|
|
||||||
doc: 'Sentry DSN for error and log reporting',
|
|
||||||
default: '',
|
|
||||||
format: 'String',
|
|
||||||
env: 'SENTRY_DSN',
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
doc: 'Environment name to report to sentry',
|
|
||||||
default: 'local',
|
|
||||||
format: ['local', 'ci', 'dev', 'stage', 'prod'],
|
|
||||||
env: 'SENTRY_ENV',
|
|
||||||
},
|
|
||||||
sampleRate: {
|
|
||||||
doc: 'Rate at which sentry errors are captured.',
|
|
||||||
default: 1.0,
|
|
||||||
format: 'Number',
|
|
||||||
env: 'SENTRY_SAMPLE_RATE',
|
|
||||||
},
|
|
||||||
serverName: {
|
|
||||||
doc: 'Name used by sentry to identify the server.',
|
|
||||||
default: 'browserid-verifier',
|
|
||||||
format: 'String',
|
|
||||||
env: 'SENTRY_SERVER_NAME',
|
|
||||||
},
|
|
||||||
tracesSampleRate: {
|
|
||||||
doc: 'Rate at which sentry traces are captured',
|
|
||||||
default: 0,
|
|
||||||
format: 'Number',
|
|
||||||
env: 'SENTRY_TRACES_SAMPLE_RATE',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testServiceFailure: {
|
|
||||||
doc: '(testing only) trigger a service failure in the verifier',
|
|
||||||
format: Boolean,
|
|
||||||
default: false,
|
|
||||||
env: 'TEST_SERVICE_FAILURE',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// load environment dependent configuration
|
|
||||||
if (process.env.CONFIG_FILES) {
|
|
||||||
var files = process.env.CONFIG_FILES.split(',');
|
|
||||||
files.forEach(function (file) {
|
|
||||||
conf.loadFile(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// validation configuration
|
|
||||||
conf.validate();
|
|
||||||
|
|
||||||
module.exports = conf;
|
|
||||||
|
|
||||||
process.nextTick(function () {
|
|
||||||
require('./log')('config').debug(
|
|
||||||
'current configuration:',
|
|
||||||
JSON.stringify(conf.get(), null, 2)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadConf();
|
|
||||||
|
|
||||||
// command line options
|
|
||||||
|
|
||||||
var args = require('optimist')
|
|
||||||
.alias('h', 'help')
|
|
||||||
.describe('h', 'display this usage message')
|
|
||||||
.alias('c', 'config')
|
|
||||||
.describe('c', 'Display current configuration.');
|
|
||||||
|
|
||||||
var argv = args.argv;
|
|
||||||
|
|
||||||
if (argv.h) {
|
|
||||||
args.showHelp();
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv.c) {
|
|
||||||
console.log(module.exports.get());
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
@@ -1,7 +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 = require('mozlog');
|
|
||||||
|
|
||||||
module.exports.config(require('../config').get('logging'));
|
|
||||||
@@ -1,186 +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 Sentry = require('@sentry/node');
|
|
||||||
|
|
||||||
const express = require('express'),
|
|
||||||
bodyParser = require('body-parser'),
|
|
||||||
morgan = require('morgan'),
|
|
||||||
http = require('http'),
|
|
||||||
toobusy = require('toobusy-js'),
|
|
||||||
log = require('./log')('server'),
|
|
||||||
summary = require('./summary'),
|
|
||||||
config = require('./config'),
|
|
||||||
CCVerifier = require('./ccverifier'),
|
|
||||||
version = require('./version'),
|
|
||||||
v1api = require('./v1'),
|
|
||||||
v2api = require('./v2');
|
|
||||||
|
|
||||||
const {
|
|
||||||
tagCriticalEvent,
|
|
||||||
buildSentryConfig,
|
|
||||||
tagFxaName,
|
|
||||||
} = require('fxa-shared/sentry');
|
|
||||||
|
|
||||||
log.debug('starting');
|
|
||||||
|
|
||||||
var app = express();
|
|
||||||
var server = http.createServer(app);
|
|
||||||
|
|
||||||
// Initialize Sentry
|
|
||||||
const sentryConfig = config.get('sentry');
|
|
||||||
if (sentryConfig.dsn) {
|
|
||||||
const release = require('../package.json').version;
|
|
||||||
const opts = buildSentryConfig(
|
|
||||||
{
|
|
||||||
sentry: sentryConfig,
|
|
||||||
release,
|
|
||||||
},
|
|
||||||
log
|
|
||||||
);
|
|
||||||
Sentry.init({
|
|
||||||
...opts,
|
|
||||||
beforeSend(event, _hint) {
|
|
||||||
event = tagCriticalEvent(event);
|
|
||||||
event = tagFxaName(event, opts.serverName);
|
|
||||||
return event;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Sentry.setupExpressErrorHandler(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
var verifier = new CCVerifier({
|
|
||||||
httpTimeout: config.get('httpTimeout'),
|
|
||||||
insecureSSL: config.get('insecureSSL'),
|
|
||||||
forceInsecureLookupOverHTTP: config.get('forceInsecureLookupOverHTTP'),
|
|
||||||
testServiceFailure: config.get('testServiceFailure'),
|
|
||||||
});
|
|
||||||
|
|
||||||
// handle shutdown
|
|
||||||
function shutdown(signal) {
|
|
||||||
return function () {
|
|
||||||
log.info('shutdown', { signal });
|
|
||||||
toobusy.shutdown();
|
|
||||||
verifier.shutdown();
|
|
||||||
server.close();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach(function (signal) {
|
|
||||||
process.on(signal, shutdown(signal.substr(3)));
|
|
||||||
});
|
|
||||||
|
|
||||||
// header manipulation
|
|
||||||
app.use(function (req, res, next) {
|
|
||||||
// no caching allowed, this is an API server.
|
|
||||||
res.setHeader(
|
|
||||||
'Cache-Control',
|
|
||||||
'private, no-cache, no-store, must-revalidate, max-age=0'
|
|
||||||
);
|
|
||||||
|
|
||||||
// security headers
|
|
||||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
||||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
||||||
res.setHeader('X-Frame-Options', 'DENY');
|
|
||||||
res.setHeader('Strict-Transport-Security', 'max-age=31536000');
|
|
||||||
res.setHeader(
|
|
||||||
'Content-Security-Policy',
|
|
||||||
"default-src 'none'; frame-ancestors 'none'; report-uri /__cspreport__"
|
|
||||||
);
|
|
||||||
|
|
||||||
// shave some needless bytes
|
|
||||||
res.removeHeader('X-Powered-By');
|
|
||||||
res.setHeader('Connection', 'close');
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// health checks - registered before all other middleware.
|
|
||||||
app.use(function (req, res, next) {
|
|
||||||
switch (req.url) {
|
|
||||||
case '/status':
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.send('OK');
|
|
||||||
break;
|
|
||||||
case '/__heartbeat__':
|
|
||||||
case '/__lbheartbeat__':
|
|
||||||
res.send({});
|
|
||||||
break;
|
|
||||||
case '/__version__':
|
|
||||||
version.getVersionInfo(function (info) {
|
|
||||||
res.send(info);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// return 503 when the server is too busy
|
|
||||||
toobusy.maxLag(config.get('toobusy.maxLag'));
|
|
||||||
app.use(function (req, res, next) {
|
|
||||||
if (toobusy()) {
|
|
||||||
log.warn('tooBusy');
|
|
||||||
res.json(503, { status: 'failure', reason: 'too busy' });
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// log HTTP requests
|
|
||||||
app.use(
|
|
||||||
morgan('common', {
|
|
||||||
stream: {
|
|
||||||
write: function (message) {
|
|
||||||
// trim newlines as our logger inserts them for us.
|
|
||||||
if (typeof message === 'string') {
|
|
||||||
message = message.trim();
|
|
||||||
}
|
|
||||||
log.info('message', { message });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// log summary - GH24
|
|
||||||
app.use(summary());
|
|
||||||
|
|
||||||
app.use(bodyParser.json({ limit: '10kb' }));
|
|
||||||
app.use(bodyParser.urlencoded({ limit: '10kb' }));
|
|
||||||
|
|
||||||
app.post('/verify', v1api.bind(v1api, verifier));
|
|
||||||
app.post('/', v1api.bind(v1api, verifier));
|
|
||||||
app.post('/v2', v2api.bind(v2api, verifier));
|
|
||||||
|
|
||||||
function wrongMethod(req, res) {
|
|
||||||
return res.sendStatus(405);
|
|
||||||
}
|
|
||||||
|
|
||||||
['/verify', '/', '/v2'].forEach(function (route) {
|
|
||||||
app.get(route, wrongMethod);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sentryConfig.dsn) {
|
|
||||||
// Send errors to sentry.
|
|
||||||
app.use(Sentry.Handlers.errorHandler());
|
|
||||||
}
|
|
||||||
|
|
||||||
// error handler goes last, to receive any errors from previous middleware
|
|
||||||
app.use(function (err, req, res, next) {
|
|
||||||
if (err) {
|
|
||||||
if (err.status) {
|
|
||||||
res.statusCode = err.status;
|
|
||||||
} else {
|
|
||||||
res.statusCode = 500;
|
|
||||||
log.error(err);
|
|
||||||
}
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(config.get('port'), config.get('ip'), function () {
|
|
||||||
log.info('running', {
|
|
||||||
url: 'http://' + server.address().address + ':' + server.address().port,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,34 +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 logger = require('./log')('summary');
|
|
||||||
|
|
||||||
module.exports = function middlewareFactory() {
|
|
||||||
return function summary(req, res, next) {
|
|
||||||
function log() {
|
|
||||||
res.removeListener('finish', log);
|
|
||||||
res.removeListener('close', log);
|
|
||||||
|
|
||||||
var summary = res._summary;
|
|
||||||
summary.code = res.statusCode;
|
|
||||||
|
|
||||||
logger.info('info', summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
res._summary = {};
|
|
||||||
|
|
||||||
// Add useful request-level info to the summary automatically.
|
|
||||||
res._summary.agent = req.headers['user-agent'] || '';
|
|
||||||
var xff = (req.headers['x-forwarded-for'] || '').split(/\s*,\s*/);
|
|
||||||
xff.push(req.connection.remoteAddress);
|
|
||||||
res._summary.remoteAddressChain = xff.filter(function (x) {
|
|
||||||
return x;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.on('finish', log);
|
|
||||||
res.on('close', log);
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,129 +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 log = require('./log')('v1'),
|
|
||||||
config = require('./config'),
|
|
||||||
_ = require('underscore'),
|
|
||||||
util = require('util');
|
|
||||||
|
|
||||||
function verify(verifier, req, res) {
|
|
||||||
req.query = req.query || {};
|
|
||||||
req.body = req.body || {};
|
|
||||||
|
|
||||||
res._summary.api = 1;
|
|
||||||
|
|
||||||
var assertion = req.query.assertion
|
|
||||||
? req.query.assertion
|
|
||||||
: req.body.assertion;
|
|
||||||
var audience = req.query.audience ? req.query.audience : req.body.audience;
|
|
||||||
var forceIssuer = req.query.experimental_forceIssuer
|
|
||||||
? req.query.experimental_forceIssuer
|
|
||||||
: req.body.experimental_forceIssuer;
|
|
||||||
var allowUnverified = req.query.experimental_allowUnverified
|
|
||||||
? req.query.experimental_allowUnverified
|
|
||||||
: req.body.experimental_allowUnverified;
|
|
||||||
|
|
||||||
res._summary.rp = audience;
|
|
||||||
|
|
||||||
if (!(assertion && audience)) {
|
|
||||||
// why couldn't we extract these guys? Is it because the request parameters weren't encoded as we expect? GH-643
|
|
||||||
const want_ct = ['application/x-www-form-urlencoded', 'application/json'];
|
|
||||||
var reason;
|
|
||||||
var ct = 'none';
|
|
||||||
try {
|
|
||||||
ct = req.headers['content-type'] || ct;
|
|
||||||
if (ct.indexOf(';') !== -1) {
|
|
||||||
ct = ct.substr(0, ct.indexOf(';'));
|
|
||||||
}
|
|
||||||
if (want_ct.indexOf(ct) === -1) {
|
|
||||||
throw new Error('wrong content type');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
reason = util.format(
|
|
||||||
'Unsupported Content-Type: %s (expected ' + want_ct.join(' or ') + ')',
|
|
||||||
ct
|
|
||||||
);
|
|
||||||
log.info('verify', {
|
|
||||||
result: 'failure',
|
|
||||||
reason: reason,
|
|
||||||
rp: audience,
|
|
||||||
});
|
|
||||||
res._summary.err = e;
|
|
||||||
return res.json(415, { status: 'failure', reason: reason });
|
|
||||||
}
|
|
||||||
reason = util.format(
|
|
||||||
'missing %s parameter',
|
|
||||||
assertion ? 'audience' : 'assertion'
|
|
||||||
);
|
|
||||||
log.info('verify', {
|
|
||||||
result: 'failure',
|
|
||||||
reason: reason,
|
|
||||||
rp: audience,
|
|
||||||
});
|
|
||||||
res._summary.err = reason;
|
|
||||||
return res.json(400, { status: 'failure', reason: reason });
|
|
||||||
}
|
|
||||||
|
|
||||||
var trustedIssuers = [];
|
|
||||||
if (forceIssuer) {
|
|
||||||
trustedIssuers.push(forceIssuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var startTime = new Date();
|
|
||||||
verifier.verify(
|
|
||||||
{
|
|
||||||
assertion: assertion,
|
|
||||||
audience: audience,
|
|
||||||
trustedIssuers: trustedIssuers,
|
|
||||||
fallback: config.get('fallback'),
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
var reqTime = new Date() - startTime;
|
|
||||||
log.info('assertion_verification_time', { reqTime });
|
|
||||||
res._summary.assertion_verification_time = reqTime;
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
if (typeof err !== 'string') {
|
|
||||||
err = 'unexpected error';
|
|
||||||
}
|
|
||||||
if (err.indexOf('compute cluster') === 0) {
|
|
||||||
log.info('service_failure');
|
|
||||||
res.json(503, { status: 'failure', reason: 'service unavailable' });
|
|
||||||
} else {
|
|
||||||
log.info('assertion_failure');
|
|
||||||
res.json(200, { status: 'failure', reason: err }); //Could be 500 or 200 OK if invalid cert
|
|
||||||
}
|
|
||||||
res._summary.err = err;
|
|
||||||
log.info('verify', {
|
|
||||||
result: 'failure',
|
|
||||||
reason: err,
|
|
||||||
assertion: assertion,
|
|
||||||
trustedIssuers: trustedIssuers,
|
|
||||||
rp: audience,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (allowUnverified) {
|
|
||||||
if (r.idpClaims && r.idpClaims['unverified-email']) {
|
|
||||||
r['unverified-email'] = r.idpClaims['unverified-email'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(
|
|
||||||
_.extend(r, {
|
|
||||||
status: 'okay',
|
|
||||||
audience: audience, // NOTE: we return the audience formatted as the RP provided it, not normalized in any way.
|
|
||||||
expires: new Date(r.expires).valueOf(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
log.info('verify', {
|
|
||||||
result: 'success',
|
|
||||||
rp: r.audience,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = verify;
|
|
||||||
@@ -1,129 +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 log = require('./log')('v2'),
|
|
||||||
config = require('./config'),
|
|
||||||
_ = require('underscore'),
|
|
||||||
util = require('util');
|
|
||||||
|
|
||||||
function validateTrustedIssuers(obj) {
|
|
||||||
var ti = obj.trustedIssuers;
|
|
||||||
if (!ti) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (!_.isArray(ti)) {
|
|
||||||
throw {
|
|
||||||
reason: 'trusted issuers must be an array',
|
|
||||||
code: 400,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ti.forEach(function (hostname) {
|
|
||||||
if (typeof hostname !== 'string') {
|
|
||||||
throw {
|
|
||||||
reason: 'trusted issuers must be an array of strings',
|
|
||||||
code: 400,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return ti;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verify(verifier, req, res) {
|
|
||||||
res._summary.api = 2;
|
|
||||||
try {
|
|
||||||
// content-type must be application/json
|
|
||||||
var ct = req.headers['content-type'] || 'none';
|
|
||||||
if (ct.indexOf('application/json') !== 0) {
|
|
||||||
throw {
|
|
||||||
reason: util.format(
|
|
||||||
'Unsupported Content-Type: %s (expected application/json)',
|
|
||||||
ct
|
|
||||||
),
|
|
||||||
code: 415,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
req.body = req.body || {};
|
|
||||||
res._summary.rp = req.body.audience;
|
|
||||||
|
|
||||||
// assertion and audience are required
|
|
||||||
['assertion', 'audience'].forEach(function (field) {
|
|
||||||
if (!req.body[field]) {
|
|
||||||
throw {
|
|
||||||
reason: util.format('missing %s parameter', field),
|
|
||||||
code: 400,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// validate and extract trusted issuers
|
|
||||||
var trustedIssuers = validateTrustedIssuers(req.body);
|
|
||||||
|
|
||||||
var startTime = new Date();
|
|
||||||
verifier.verify(
|
|
||||||
{
|
|
||||||
assertion: req.body.assertion,
|
|
||||||
audience: req.body.audience,
|
|
||||||
trustedIssuers: trustedIssuers,
|
|
||||||
fallback: config.get('fallback'),
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
var reqTime = new Date() - startTime;
|
|
||||||
log.info('assertion_verification_time', { reqTime });
|
|
||||||
res._summary.assertion_verification_time = reqTime;
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
if (typeof err !== 'string') {
|
|
||||||
err = 'unexpected error';
|
|
||||||
}
|
|
||||||
if (err.indexOf('compute cluster') === 0) {
|
|
||||||
log.info('service_failure');
|
|
||||||
res.json(503, { status: 'failure', reason: 'service unavailable' });
|
|
||||||
} else {
|
|
||||||
log.info('assertion_failure');
|
|
||||||
res.json(200, { status: 'failure', reason: err }); //Could be 500 or 200 OK if invalid cert
|
|
||||||
}
|
|
||||||
res._summary.err = err;
|
|
||||||
log.info('verify', {
|
|
||||||
result: 'failure',
|
|
||||||
reason: err,
|
|
||||||
assertion: req.body.assertion,
|
|
||||||
trustedIssuers: trustedIssuers,
|
|
||||||
rp: req.body.audience,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.json(
|
|
||||||
_.extend(r, {
|
|
||||||
status: 'okay',
|
|
||||||
audience: req.body.audience, // NOTE: we return the audience formatted as the RP provided it, not normalized in any way.
|
|
||||||
expires: new Date(r.expires).valueOf(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
log.info('verify', {
|
|
||||||
result: 'success',
|
|
||||||
rp: r.audience,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
var reason = err.reason ? err.reason : err.toString();
|
|
||||||
|
|
||||||
res._summary.err = reason;
|
|
||||||
log.info('verify', {
|
|
||||||
result: 'failure',
|
|
||||||
reason: reason,
|
|
||||||
rp: req.body.audience,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json(err.code ? err.code : 500, {
|
|
||||||
status: 'failure',
|
|
||||||
reason: reason,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = verify;
|
|
||||||
@@ -1,71 +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 path = require('path'),
|
|
||||||
cp = require('child_process');
|
|
||||||
|
|
||||||
const UNKNOWN = 'unknown';
|
|
||||||
|
|
||||||
// For production builds, we write version into to a json
|
|
||||||
// file for easy reporting in /__version__ endpoint.
|
|
||||||
var commitHash;
|
|
||||||
var sourceRepo;
|
|
||||||
const version = require('../package.json').version;
|
|
||||||
try {
|
|
||||||
var versionJson = path.join(__dirname, '..', 'version.json');
|
|
||||||
var info = require(versionJson);
|
|
||||||
commitHash = info.version.hash;
|
|
||||||
sourceRepo = info.version.source;
|
|
||||||
} catch (e) {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getVersionInfo: function getVersionInfo(cb) {
|
|
||||||
if (commitHash) {
|
|
||||||
return cb({
|
|
||||||
version: version,
|
|
||||||
commit: commitHash,
|
|
||||||
source: sourceRepo,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// ignore errors and default to 'unknown' if not found
|
|
||||||
var gitDir = path.resolve(__dirname, '..', '..', '..', '.git');
|
|
||||||
cp.exec('git rev-parse HEAD', { cwd: gitDir }, function (err, stdout1) {
|
|
||||||
if (err != null) {
|
|
||||||
console.error('Error getting git commit hash: ' + err.message);
|
|
||||||
return cb({
|
|
||||||
version: version,
|
|
||||||
commit: UNKNOWN,
|
|
||||||
source: UNKNOWN,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var configPath = path.join(gitDir, 'config');
|
|
||||||
var cmd = 'git config --get remote.origin.url';
|
|
||||||
cp.exec(
|
|
||||||
cmd,
|
|
||||||
{ env: { GIT_CONFIG: configPath } },
|
|
||||||
function (err, stdout2) {
|
|
||||||
if (err != null) {
|
|
||||||
console.error('Error getting git config: ' + err.message);
|
|
||||||
return cb({
|
|
||||||
version: version,
|
|
||||||
commit: UNKNOWN,
|
|
||||||
source: UNKNOWN,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
commitHash = (stdout1 && stdout1.trim()) || UNKNOWN;
|
|
||||||
sourceRepo = (stdout2 && stdout2.trim()) || UNKNOWN;
|
|
||||||
return cb({
|
|
||||||
version: version,
|
|
||||||
commit: commitHash,
|
|
||||||
source: sourceRepo,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
SERVER_URL = https://verifier.stage.mozaws.net
|
|
||||||
|
|
||||||
# Hackety-hack around OSX system python bustage.
|
|
||||||
# The need for this should go away with a future osx/xcode update.
|
|
||||||
ARCHFLAGS = -Wno-error=unused-command-line-argument-hard-error-in-future
|
|
||||||
INSTALL = ARCHFLAGS=$(ARCHFLAGS) ./venv/bin/pip install
|
|
||||||
|
|
||||||
.PHONY: build clean test bench megabench
|
|
||||||
|
|
||||||
# Build virtualenv, to ensure we have all the dependencies.
|
|
||||||
build:
|
|
||||||
virtualenv --no-site-packages ./venv
|
|
||||||
$(INSTALL) pexpect
|
|
||||||
$(INSTALL) gevent
|
|
||||||
$(INSTALL) https://github.com/mozilla-services/loads/archive/master.zip
|
|
||||||
$(INSTALL) PyBrowserID
|
|
||||||
|
|
||||||
# Clean all the things installed by `make build`.
|
|
||||||
clean:
|
|
||||||
rm -rf ./venv *.pyc
|
|
||||||
|
|
||||||
# Run a single test from the venv machine, for sanity-checking.
|
|
||||||
test:
|
|
||||||
./venv/bin/loads-runner --config=./config/test.ini --server-url=$(SERVER_URL) loadtest.VerifierLoadTest.test_verifier
|
|
||||||
|
|
||||||
# Run a bench of 20 concurrent users.
|
|
||||||
bench:
|
|
||||||
./venv/bin/loads-runner --config=./config/bench.ini --server-url=$(SERVER_URL) loadtest.VerifierLoadTest.test_verifier
|
|
||||||
|
|
||||||
# Run a much bigger bench, by submitting to broker in AWS.
|
|
||||||
megabench:
|
|
||||||
./venv/bin/loads-runner --config=./config/megabench.ini --user-id=$(USER) --server-url=$(SERVER_URL) loadtest.VerifierLoadTest.test_verifier
|
|
||||||
|
|
||||||
# Purge any currently-running loadtest runs.
|
|
||||||
purge:
|
|
||||||
./venv/bin/loads-runner --config=./config/megabench.ini --purge-broker
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
This directory contains some very simple loadtests, written using
|
|
||||||
the "loads" framework:
|
|
||||||
|
|
||||||
https://github.com/mozilla/loads
|
|
||||||
|
|
||||||
|
|
||||||
To run them, you will need the following dependencies:
|
|
||||||
|
|
||||||
* Python development files (e.g. python-dev or python-devel package)
|
|
||||||
* Virtualenv (e.g. python-virtualenv package)
|
|
||||||
* ZeroMQ development files (e.g. libzmq-dev package)
|
|
||||||
* (for megabench) ssh access to the mozilla loads cluster
|
|
||||||
|
|
||||||
Then do the following:
|
|
||||||
|
|
||||||
$> make build # installs local environment with all dependencies
|
|
||||||
$> make test # runs a single test, to check that everything's working
|
|
||||||
$> make bench # runs a longer, higher-concurrency test.
|
|
||||||
$> make megabench # runs a really-long, really-high-concurrency test
|
|
||||||
# using https://loads.services.mozilla.com
|
|
||||||
|
|
||||||
To hit a specific server you can specify the SERVER_URL make variable, like
|
|
||||||
this:
|
|
||||||
|
|
||||||
$> make test SERVER_URL=https://verifier.stage.mozaws.net
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[loads]
|
|
||||||
users = 20
|
|
||||||
duration = 300
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[loads]
|
|
||||||
users = 20
|
|
||||||
duration = 1800
|
|
||||||
include_file = ./loadtest.py
|
|
||||||
python_dep = PyBrowserID
|
|
||||||
agents = 5
|
|
||||||
detach = true
|
|
||||||
observer = irc
|
|
||||||
ssh = ubuntu@loads.services.mozilla.com
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[loads]
|
|
||||||
hits = 1
|
|
||||||
users = 1
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import random
|
|
||||||
|
|
||||||
import browserid
|
|
||||||
import browserid.jwt
|
|
||||||
from browserid.tests.support import make_assertion
|
|
||||||
|
|
||||||
from loads import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
PERCENT_INVALID_REQUESTS = 5
|
|
||||||
|
|
||||||
ONE_YEAR = 60 * 60 * 24 * 365
|
|
||||||
|
|
||||||
MOCKMYID_DOMAIN = "mockmyid.s3-us-west-2.amazonaws.com"
|
|
||||||
MOCKMYID_PRIVATE_KEY = browserid.jwt.DS128Key({
|
|
||||||
"algorithm": "DS",
|
|
||||||
"x": "385cb3509f086e110c5e24bdd395a84b335a09ae",
|
|
||||||
"y": "738ec929b559b604a232a9b55a5295afc368063bb9c20fac4e53a74970a4db795"
|
|
||||||
"6d48e4c7ed523405f629b4cc83062f13029c4d615bbacb8b97f5e56f0c7ac9bc1"
|
|
||||||
"d4e23809889fa061425c984061fca1826040c399715ce7ed385c4dd0d40225691"
|
|
||||||
"2451e03452d3c961614eb458f188e3e8d2782916c43dbe2e571251ce38262",
|
|
||||||
"p": "ff600483db6abfc5b45eab78594b3533d550d9f1bf2a992a7a8daa6dc34f8045a"
|
|
||||||
"d4e6e0c429d334eeeaaefd7e23d4810be00e4cc1492cba325ba81ff2d5a5b305a"
|
|
||||||
"8d17eb3bf4a06a349d392e00d329744a5179380344e82a18c47933438f891e22a"
|
|
||||||
"eef812d69c8f75e326cb70ea000c3f776dfdbd604638c2ef717fc26d02e17",
|
|
||||||
"q": "e21e04f911d1ed7991008ecaab3bf775984309c3",
|
|
||||||
"g": "c52a4a0ff3b7e61fdf1867ce84138369a6154f4afa92966e3c827e25cfa6cf508b"
|
|
||||||
"90e5de419e1337e07a2e9e2a3cd5dea704d175f8ebf6af397d69e110b96afb17c7"
|
|
||||||
"a03259329e4829b0d03bbc7896b15b4ade53e130858cc34d96269aa89041f40913"
|
|
||||||
"6c7242a38895c9d5bccad4f389af1d7a4bd1398bd072dffa896233397a",
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
INVALID_PRIVATE_KEY = browserid.jwt.DS128Key({
|
|
||||||
"algorithm": "DS",
|
|
||||||
"x": "abcdef0123456789abcdef0123456789abcdef01",
|
|
||||||
"y": "738ec929b559b604a232a9b55a5295afc368063bb9c20fac4e53a74970a4db795"
|
|
||||||
"6d48e4c7ed523405f629b4cc83062f13029c4d615bbacb8b97f5e56f0c7ac9bc1"
|
|
||||||
"d4e23809889fa061425c984061fca1826040c399715ce7ed385c4dd0d40225691"
|
|
||||||
"2451e03452d3c961614eb458f188e3e8d2782916c43dbe2e571251ce38262",
|
|
||||||
"p": "ff600483db6abfc5b45eab78594b3533d550d9f1bf2a992a7a8daa6dc34f8045a"
|
|
||||||
"d4e6e0c429d334eeeaaefd7e23d4810be00e4cc1492cba325ba81ff2d5a5b305a"
|
|
||||||
"8d17eb3bf4a06a349d392e00d329744a5179380344e82a18c47933438f891e22a"
|
|
||||||
"eef812d69c8f75e326cb70ea000c3f776dfdbd604638c2ef717fc26d02e17",
|
|
||||||
"q": "e21e04f911d1ed7991008ecaab3bf775984309c3",
|
|
||||||
"g": "c52a4a0ff3b7e61fdf1867ce84138369a6154f4afa92966e3c827e25cfa6cf508b"
|
|
||||||
"90e5de419e1337e07a2e9e2a3cd5dea704d175f8ebf6af397d69e110b96afb17c7"
|
|
||||||
"a03259329e4829b0d03bbc7896b15b4ade53e130858cc34d96269aa89041f40913"
|
|
||||||
"6c7242a38895c9d5bccad4f389af1d7a4bd1398bd072dffa896233397a",
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class VerifierLoadTest(TestCase):
|
|
||||||
|
|
||||||
server_url = "https://verifier.stage.mozaws.net"
|
|
||||||
|
|
||||||
def _make_assertion(self, email=None, audience=None, **kwds):
|
|
||||||
if email is None:
|
|
||||||
email = "user%d@%s" % (random.randint(0, 1000000), MOCKMYID_DOMAIN)
|
|
||||||
if audience is None:
|
|
||||||
audience = "https://secret.mozilla.com"
|
|
||||||
if "exp" not in kwds:
|
|
||||||
kwds["exp"] = int((time.time() + ONE_YEAR) * 1000)
|
|
||||||
if "issuer" not in kwds:
|
|
||||||
kwds["issuer"] = MOCKMYID_DOMAIN
|
|
||||||
if "issuer_keypair" not in kwds:
|
|
||||||
kwds["issuer_keypair"] = (None, MOCKMYID_PRIVATE_KEY)
|
|
||||||
return make_assertion(email, audience, **kwds)
|
|
||||||
|
|
||||||
def _verify_assertion(self, assertion, audience=None, **kwds):
|
|
||||||
if assertion is None:
|
|
||||||
assertion = self._make_assertion()
|
|
||||||
if audience is None:
|
|
||||||
audience = "https://secret.mozilla.com"
|
|
||||||
body = {
|
|
||||||
"assertion": assertion,
|
|
||||||
"audience": audience,
|
|
||||||
}
|
|
||||||
body.update(kwds)
|
|
||||||
body = json.dumps(body)
|
|
||||||
r = self.session.post(self.server_url + "/v2", body, headers={
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
})
|
|
||||||
self.assertEquals(r.status_code, 200)
|
|
||||||
return json.loads(r.content)
|
|
||||||
|
|
||||||
def test_verifier(self):
|
|
||||||
if random.randint(0, 99) < PERCENT_INVALID_REQUESTS:
|
|
||||||
self.test_invalid_assertion()
|
|
||||||
else:
|
|
||||||
self.test_valid_assertion()
|
|
||||||
|
|
||||||
def test_valid_assertion(self):
|
|
||||||
assertion = self._make_assertion()
|
|
||||||
data = self._verify_assertion(assertion)
|
|
||||||
self.assertEquals(data["status"], "okay")
|
|
||||||
|
|
||||||
def test_invalid_assertion(self):
|
|
||||||
# Randomly pick and run a _test_invalid_assertion_<foo>() method.
|
|
||||||
tests = []
|
|
||||||
for nm in dir(self):
|
|
||||||
if nm.startswith("_test_invalid_assertion"):
|
|
||||||
tests.append(getattr(self, nm))
|
|
||||||
random.choice(tests)()
|
|
||||||
|
|
||||||
def _test_invalid_assertion_nonprimary(self):
|
|
||||||
assertion = self._make_assertion("test@mozilla.com")
|
|
||||||
data = self._verify_assertion(assertion)
|
|
||||||
self.assertEquals(data["status"], "failure")
|
|
||||||
|
|
||||||
def _test_invalid_assertion_expired(self):
|
|
||||||
exp = int(time.time() - ONE_YEAR) * 1000,
|
|
||||||
assertion = self._make_assertion(exp=exp)
|
|
||||||
data = self._verify_assertion(assertion)
|
|
||||||
self.assertEquals(data["status"], "failure")
|
|
||||||
|
|
||||||
def _test_invalid_assertion_wrongissuer(self):
|
|
||||||
assertion = self._make_assertion(issuer="login.mozilla.org")
|
|
||||||
data = self._verify_assertion(assertion)
|
|
||||||
self.assertEquals(data["status"], "failure")
|
|
||||||
|
|
||||||
def _test_invalid_assertion_wrongkey(self):
|
|
||||||
issuer_keypair = (None, INVALID_PRIVATE_KEY)
|
|
||||||
assertion = self._make_assertion(issuer_keypair=issuer_keypair)
|
|
||||||
data = self._verify_assertion(assertion)
|
|
||||||
self.assertEquals(data["status"], "failure")
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"author": "Mozilla (https://mozilla.org/)",
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"name": "browserid-verifier",
|
|
||||||
"description": "A node.js verification server for BrowserID assertions.",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/mozilla/fxa.git"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/mozilla/fxa/tree/main/packages/browserid-verifier/",
|
|
||||||
"bugs": "https://github.com/mozilla/fxa/issues/",
|
|
||||||
"main": "lib/server.js",
|
|
||||||
"dependencies": {
|
|
||||||
"async": "3.2.4",
|
|
||||||
"body-parser": "^1.20.3",
|
|
||||||
"browserid-local-verify": "0.5.2",
|
|
||||||
"compute-cluster": "0.0.9",
|
|
||||||
"convict": "^6.2.4",
|
|
||||||
"convict-format-with-moment": "^6.2.0",
|
|
||||||
"convict-format-with-validator": "^6.2.0",
|
|
||||||
"express": "^4.21.2",
|
|
||||||
"intel": "1.2.0",
|
|
||||||
"morgan": "^1.10.0",
|
|
||||||
"mozlog": "^3.0.2",
|
|
||||||
"optimist": "0.6.1",
|
|
||||||
"toobusy-js": "0.5.1",
|
|
||||||
"underscore": "^1.13.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"audit-filter": "0.5.0",
|
|
||||||
"eslint": "^7.32.0",
|
|
||||||
"fxa-shared": "workspace:*",
|
|
||||||
"mocha": "^10.4.0",
|
|
||||||
"pm2": "^5.4.2",
|
|
||||||
"prettier": "^3.5.3",
|
|
||||||
"request": "^2.88.2",
|
|
||||||
"should": "13.2.3",
|
|
||||||
"temp": "0.9.4",
|
|
||||||
"walk": "^2.3.15"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"audit": "npm audit --json | audit-filter --nsp-config=.nsprc --audit=-",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"test": "mocha --exit -t 5000 -R spec tests/*.js",
|
|
||||||
"format": "prettier --write --config ../../_dev/.prettierrc '**'",
|
|
||||||
"start": "pm2 start pm2.config.js",
|
|
||||||
"stop": "pm2 stop pm2.config.js",
|
|
||||||
"restart": "pm2 restart pm2.config.js",
|
|
||||||
"delete": "pm2 delete pm2.config.js"
|
|
||||||
},
|
|
||||||
"nx": {
|
|
||||||
"tags": [
|
|
||||||
"scope:server"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +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 PATH = process.env.PATH.split(':')
|
|
||||||
.filter((p) => !p.includes(process.env.TMPDIR))
|
|
||||||
.join(':');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
apps: [
|
|
||||||
{
|
|
||||||
name: 'browserid',
|
|
||||||
script: 'node server.js',
|
|
||||||
cwd: __dirname,
|
|
||||||
env: {
|
|
||||||
PATH,
|
|
||||||
PORT: '5050',
|
|
||||||
IP_ADDRESS: '0.0.0.0',
|
|
||||||
FORCE_INSECURE_LOOKUP_OVER_HTTP: 'true',
|
|
||||||
SENTRY_ENV: 'local',
|
|
||||||
SENTRY_DSN: process.env.SENTRY_DSN_BROWSERID,
|
|
||||||
},
|
|
||||||
filter_env: ['npm_'],
|
|
||||||
max_restarts: '1',
|
|
||||||
min_uptime: '2m',
|
|
||||||
time: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,7 +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/. */
|
|
||||||
|
|
||||||
require('./lib/server.js');
|
|
||||||
@@ -1,102 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var IdP = require('browserid-local-verify/testing').IdP,
|
|
||||||
Client = require('browserid-local-verify/testing').Client,
|
|
||||||
Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('audience tests', function () {
|
|
||||||
var verifier = new Verifier();
|
|
||||||
var idp = new IdP();
|
|
||||||
var client;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve) => idp.start(resolve));
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
client = new Client({ idp: idp });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
await new Promise((resolve) => idp.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
var assertion;
|
|
||||||
|
|
||||||
it('test assertion should be created', function (done) {
|
|
||||||
client.assertion({ audience: 'http://example.com' }, function (err, ass) {
|
|
||||||
assertion = ass;
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function submitWithAudience(audience, cb) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: audience,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should verify with complete audience', function (done) {
|
|
||||||
submitWithAudience('http://example.com', function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
'okay'.should.equal(r.body.status);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail to verify with different domain as audience', function (done) {
|
|
||||||
submitWithAudience('incorrect.com', function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.equal('audience mismatch: domain mismatch');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail to verify with different port', function (done) {
|
|
||||||
submitWithAudience('http://example.com:8080', function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.equal('audience mismatch: port mismatch');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail to verify with incorrect scheme', function (done) {
|
|
||||||
submitWithAudience('https://example.com', function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.equal('audience mismatch: scheme mismatch');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail to verify if audience is missing', function (done) {
|
|
||||||
submitWithAudience(undefined, function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.equal('missing audience parameter');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,91 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var IdP = require('browserid-local-verify/testing').IdP,
|
|
||||||
Client = require('browserid-local-verify/testing').Client,
|
|
||||||
Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('basic verifier test', function () {
|
|
||||||
var idp = new IdP();
|
|
||||||
var verifier = new Verifier();
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve) => idp.start(resolve));
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
await new Promise((resolve) => idp.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should verify an assertion', function (done) {
|
|
||||||
var client = new Client({ idp: idp });
|
|
||||||
|
|
||||||
client.assertion(
|
|
||||||
{ audience: 'http://example.com' },
|
|
||||||
function (err, assertion) {
|
|
||||||
should.not.exist(err);
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.body.email.should.equal(client.email());
|
|
||||||
r.body.issuer.should.equal(idp.domain());
|
|
||||||
r.body.status.should.equal('okay');
|
|
||||||
r.body.audience.should.equal('http://example.com');
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 405 for GET requests', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'get',
|
|
||||||
url: verifier.url(),
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(405);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle any errors', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
body: "{ 'a' }",
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(400);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,60 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it */
|
|
||||||
|
|
||||||
const should = require('should');
|
|
||||||
|
|
||||||
var Verifier = require('./lib/verifier.js'),
|
|
||||||
async = require('async'),
|
|
||||||
temp = require('temp'),
|
|
||||||
fs = require('fs');
|
|
||||||
|
|
||||||
describe('cascading configuration files', function () {
|
|
||||||
var verifier;
|
|
||||||
|
|
||||||
it('create a couple configuration files', function (done) {
|
|
||||||
var aPath = temp.path({ suffix: '.json' }),
|
|
||||||
bPath = temp.path({ suffix: '.json' });
|
|
||||||
|
|
||||||
// because "b" is specified later, it should over-ride "a"
|
|
||||||
verifier = new Verifier({
|
|
||||||
files: aPath + ',' + bPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
async.parallel(
|
|
||||||
[
|
|
||||||
function (cb) {
|
|
||||||
fs.writeFile(
|
|
||||||
aPath,
|
|
||||||
JSON.stringify({ fallback: 'a.example.com' }),
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function (cb) {
|
|
||||||
fs.writeFile(
|
|
||||||
bPath,
|
|
||||||
JSON.stringify({ fallback: 'b.example.com' }),
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
done
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('test servers should start and finish cleanly', async () => {
|
|
||||||
verifier.buffer(true);
|
|
||||||
should(verifier.process).equal(undefined);
|
|
||||||
await new Promise((resolve, error) => verifier.start(resolve));
|
|
||||||
should(verifier.process).not.equal(null);
|
|
||||||
await new Promise((resolve, error) => verifier.stop(resolve));
|
|
||||||
should(verifier.process).equal(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('verifier should have determined proper configuration', () => {
|
|
||||||
verifier.buffer().indexOf('a.example.com').should.equal(-1);
|
|
||||||
verifier.buffer().indexOf('b.example.com').should.not.equal(-1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,228 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var IdP = require('browserid-local-verify/testing').IdP,
|
|
||||||
Client = require('browserid-local-verify/testing').Client,
|
|
||||||
Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('content-type tests', function () {
|
|
||||||
var verifier = new Verifier();
|
|
||||||
var idp = new IdP();
|
|
||||||
var client;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve) => idp.start(resolve));
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
client = new Client({ idp: idp });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
await new Promise((resolve) => idp.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
var assertion;
|
|
||||||
|
|
||||||
it('test assertion should be created', function (done) {
|
|
||||||
client.assertion({ audience: 'http://example.com' }, function (err, ass) {
|
|
||||||
assertion = ass;
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v2 api) should fail to verify when content-type is unsupported', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(415);
|
|
||||||
(function () {
|
|
||||||
r.body = JSON.parse(r.body);
|
|
||||||
}.should.not.throw());
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.startWith('Unsupported Content-Type: text/plain');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v2 api) should fail to verify when content-type is not specified', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
headers: {},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(415);
|
|
||||||
(function () {
|
|
||||||
r.body = JSON.parse(r.body);
|
|
||||||
}.should.not.throw());
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.startWith('Unsupported Content-Type: none');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v1 api) should fail to verify when content-type is unsupported', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.v1url(),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(415);
|
|
||||||
(function () {
|
|
||||||
r.body = JSON.parse(r.body);
|
|
||||||
}.should.not.throw());
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.startWith('Unsupported Content-Type: text/plain');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v1 api) should fail to verify when content-type is not specified', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.v1url(),
|
|
||||||
headers: {},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(415);
|
|
||||||
(function () {
|
|
||||||
r.body = JSON.parse(r.body);
|
|
||||||
}.should.not.throw());
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.startWith('Unsupported Content-Type: none');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("(v2 api) shouldn't get confused when extended content-types are used", function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json; charset: utf-8',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
assertion: assertion,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(400);
|
|
||||||
(function () {
|
|
||||||
r.body = JSON.parse(r.body);
|
|
||||||
}.should.not.throw());
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("(v1 api) shouldn't get confused when extended content-types are used", function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.v1url(),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json; charset: utf-8',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
assertion: assertion,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(400);
|
|
||||||
(function () {
|
|
||||||
r.body = JSON.parse(r.body);
|
|
||||||
}.should.not.throw());
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("(v2 api) shouldn't support x-www-form-urlencoded", function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: require('querystring').stringify({
|
|
||||||
assertion: assertion,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(415);
|
|
||||||
(function () {
|
|
||||||
r.body = JSON.parse(r.body);
|
|
||||||
}.should.not.throw());
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.startWith('Unsupported Content-Type');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v1 api) should support x-www-form-urlencoded', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.v1url(),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: require('querystring').stringify({
|
|
||||||
assertion: assertion,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(400);
|
|
||||||
(function () {
|
|
||||||
r.body = JSON.parse(r.body);
|
|
||||||
}.should.not.throw());
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.startWith('missing audience');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,111 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var IdP = require('browserid-local-verify/testing').IdP,
|
|
||||||
Client = require('browserid-local-verify/testing').Client,
|
|
||||||
Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('fallback configuration test', function () {
|
|
||||||
var idp = new IdP();
|
|
||||||
var verifier = new Verifier();
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
this.timeout(10000);
|
|
||||||
await new Promise((resolve) => idp.start(resolve));
|
|
||||||
verifier.setFallback(idp);
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
await new Promise((resolve) => idp.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should verify an assertion vouched by the configured fallback', function (done) {
|
|
||||||
var client = new Client({
|
|
||||||
idp: idp,
|
|
||||||
email: 'lloyd@nonidp.example.com',
|
|
||||||
});
|
|
||||||
|
|
||||||
client.assertion(
|
|
||||||
{
|
|
||||||
audience: 'http://rp.example.com',
|
|
||||||
},
|
|
||||||
function (err, assertion) {
|
|
||||||
should.not.exist(err);
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://rp.example.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.body.status.should.equal('okay');
|
|
||||||
r.body.email.should.equal(client.email());
|
|
||||||
r.body.issuer.should.equal(idp.domain());
|
|
||||||
r.body.audience.should.equal('http://rp.example.com');
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('verifier should restart with cleared fallback', function (done) {
|
|
||||||
verifier.setFallback(null);
|
|
||||||
verifier.stop(function (e) {
|
|
||||||
verifier.start(function (e1) {
|
|
||||||
done(e || e1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail to verify an assertion when fallback is not configured', function (done) {
|
|
||||||
var client = new Client({
|
|
||||||
idp: idp,
|
|
||||||
email: 'lloyd@nonidp.example.com',
|
|
||||||
});
|
|
||||||
|
|
||||||
client.assertion(
|
|
||||||
{
|
|
||||||
audience: 'http://rp.example.com',
|
|
||||||
},
|
|
||||||
function (err, assertion) {
|
|
||||||
should.not.exist(err);
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://rp.example.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
// XXX: better error message
|
|
||||||
r.body.reason.should.startWith('untrusted issuer');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,131 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var IdP = require('browserid-local-verify/testing').IdP,
|
|
||||||
Client = require('browserid-local-verify/testing').Client,
|
|
||||||
Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('force issuer', function () {
|
|
||||||
var idp = new IdP();
|
|
||||||
var fallback = new IdP();
|
|
||||||
var client;
|
|
||||||
var verifier = new Verifier();
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve, error) => idp.start(resolve));
|
|
||||||
await new Promise((resolve, error) => fallback.start(resolve));
|
|
||||||
verifier.setFallback(idp);
|
|
||||||
await new Promise((resolve, error) => verifier.start(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve, error) => verifier.stop(resolve));
|
|
||||||
await new Promise((resolve, error) => fallback.stop(resolve));
|
|
||||||
await new Promise((resolve, error) => idp.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('assertion by fallback when primary support is present should fail', function (done) {
|
|
||||||
// user has an email from idp, but fallback will be used for certificate
|
|
||||||
client = new Client({
|
|
||||||
idp: fallback,
|
|
||||||
email: 'user@' + idp.domain(),
|
|
||||||
});
|
|
||||||
|
|
||||||
client.assertion(
|
|
||||||
{ audience: 'http://example.com' },
|
|
||||||
function (err, assertion) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (_, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.startWith('untrusted issuer');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v1) forceIssuer should over-ride authority discovery', function (done) {
|
|
||||||
// user has an email from idp, but fallback will be used for certificate
|
|
||||||
client = new Client({
|
|
||||||
idp: fallback,
|
|
||||||
email: 'user@' + idp.domain(),
|
|
||||||
});
|
|
||||||
|
|
||||||
client.assertion(
|
|
||||||
{ audience: 'http://example.com' },
|
|
||||||
function (_, assertion) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.v1url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
experimental_forceIssuer: fallback.domain(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.status.should.equal('okay');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v2) trustedIssuers should over-ride authority discovery', function (done) {
|
|
||||||
// user has an email from idp, but fallback will be used for certificate
|
|
||||||
client = new Client({
|
|
||||||
idp: fallback,
|
|
||||||
email: 'user@' + idp.domain(),
|
|
||||||
});
|
|
||||||
|
|
||||||
client.assertion(
|
|
||||||
{ audience: 'http://example.com' },
|
|
||||||
function (_, assertion) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
trustedIssuers: [fallback.domain()],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.status.should.equal('okay');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,99 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
require('should');
|
|
||||||
|
|
||||||
var Verifier = require('./lib/verifier.js'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('health check', function () {
|
|
||||||
var verifier = new Verifier();
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('health check should return OK', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
url: verifier.baseurl() + '/status',
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.should.equal('OK');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('__heartbeat__ should return success', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
url: verifier.baseurl() + '/__heartbeat__',
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.should.equal('{}');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('__lbheartbeat__ should return success', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
url: verifier.baseurl() + '/__lbheartbeat__',
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.should.equal('{}');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('__version__ should return version info', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
url: verifier.baseurl() + '/__version__',
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
var obj = JSON.parse(r.body);
|
|
||||||
obj.version.should.match(/^[0-9.]+$/);
|
|
||||||
obj.commit.should.match(/^[a-z0-9]{40}$/);
|
|
||||||
obj.source.should.be.a.String();
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('__version__ should return version info (cached)', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
url: verifier.baseurl() + '/__version__',
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
var obj = JSON.parse(r.body);
|
|
||||||
obj.version.should.match(/^[0-9.]+$/);
|
|
||||||
obj.commit.should.match(/^[a-z0-9]{40}$/);
|
|
||||||
obj.source.should.be.a.String();
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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/. */
|
|
||||||
|
|
||||||
var should = require('should');
|
|
||||||
|
|
||||||
function shouldReturnSecurityHeaders(res) {
|
|
||||||
var expect = {
|
|
||||||
'strict-transport-security': 'max-age=31536000',
|
|
||||||
'x-content-type-options': 'nosniff',
|
|
||||||
'x-xss-protection': '1; mode=block',
|
|
||||||
'x-frame-options': 'DENY',
|
|
||||||
'cache-control': 'private, no-cache, no-store, must-revalidate, max-age=0',
|
|
||||||
'content-security-policy':
|
|
||||||
"default-src 'none'; frame-ancestors 'none'; report-uri /__cspreport__",
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(expect).forEach(function (header) {
|
|
||||||
should.exist(res.headers[header]);
|
|
||||||
res.headers[header].should.equal(expect[header]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = shouldReturnSecurityHeaders;
|
|
||||||
@@ -1,6 +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/. */
|
|
||||||
require('../../lib/server.js');
|
|
||||||
@@ -1,148 +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/. */
|
|
||||||
|
|
||||||
/* jshint curly:false */
|
|
||||||
|
|
||||||
const cp = require('child_process'),
|
|
||||||
path = require('path');
|
|
||||||
|
|
||||||
function later(cb /* args */) {
|
|
||||||
var args = Array.prototype.slice.call(arguments, 1);
|
|
||||||
process.nextTick(function () {
|
|
||||||
cb.apply(null, args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function Verifier(args) {
|
|
||||||
if (!args) {
|
|
||||||
args = {};
|
|
||||||
}
|
|
||||||
this.config = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
Verifier.prototype.setFallback = function (idp) {
|
|
||||||
if (idp === null) delete this.config.fallback;
|
|
||||||
else this.config.fallback = idp.domain();
|
|
||||||
};
|
|
||||||
|
|
||||||
Verifier.prototype.setHTTPTimeout = function (timeo) {
|
|
||||||
this.config.httpTimeout = timeo;
|
|
||||||
};
|
|
||||||
|
|
||||||
Verifier.prototype.buffer = function (b) {
|
|
||||||
if (b !== undefined) {
|
|
||||||
if (!b) {
|
|
||||||
delete this._outBuf;
|
|
||||||
} else if (!this._outBuf) {
|
|
||||||
this._outBuf = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this._outBuf;
|
|
||||||
};
|
|
||||||
|
|
||||||
Verifier.prototype.url = function () {
|
|
||||||
return this.baseurl() + '/v2';
|
|
||||||
};
|
|
||||||
|
|
||||||
Verifier.prototype.v1url = function () {
|
|
||||||
return this.baseurl();
|
|
||||||
};
|
|
||||||
|
|
||||||
Verifier.prototype.baseurl = function () {
|
|
||||||
if (!this._url) {
|
|
||||||
throw new Error('verifier not running');
|
|
||||||
}
|
|
||||||
return this._url;
|
|
||||||
};
|
|
||||||
|
|
||||||
Verifier.prototype.start = function (cb) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var repoBaseDir = path.join(__dirname, '..', '..');
|
|
||||||
|
|
||||||
if (this.process) {
|
|
||||||
return later(cb, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var e = {
|
|
||||||
INSECURE_SSL: true,
|
|
||||||
HTTP_TIMEOUT: this.config.httpTimeout || 8.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.config.fallback) {
|
|
||||||
e.FALLBACK_DOMAIN = this.config.fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.files) {
|
|
||||||
e.CONFIG_FILES = this.config.files;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.testServiceFailure) {
|
|
||||||
e.TEST_SERVICE_FAILURE = this.config.testServiceFailure;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.process = cp.spawn(
|
|
||||||
process.execPath,
|
|
||||||
[path.join(repoBaseDir, 'tests', 'lib', 'test-server.js')],
|
|
||||||
{
|
|
||||||
cwd: repoBaseDir,
|
|
||||||
stdio: 'pipe',
|
|
||||||
env: e,
|
|
||||||
timeout: 4 * 1000,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.process.stdout.on('data', function (line) {
|
|
||||||
if (typeof self._outBuf === 'string') {
|
|
||||||
self._outBuf += line;
|
|
||||||
}
|
|
||||||
|
|
||||||
// figure out the bound port
|
|
||||||
try {
|
|
||||||
var m = JSON.parse(line);
|
|
||||||
if (m.Type === 'server.running') {
|
|
||||||
self._url = m.Fields.url;
|
|
||||||
if (cb) {
|
|
||||||
cb(null, m.Fields.url);
|
|
||||||
}
|
|
||||||
cb = null;
|
|
||||||
}
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
if (process.env.VERBOSE) {
|
|
||||||
console.log(line.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.errBuf = '';
|
|
||||||
this.process.stderr.on('data', function (d) {
|
|
||||||
self.errBuf += d.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.process.on('exit', function (code) {
|
|
||||||
var msg = 'exited';
|
|
||||||
if (code !== 0) {
|
|
||||||
msg += ' with code ' + code + ' (' + self.errBuf + ')';
|
|
||||||
}
|
|
||||||
if (cb) cb(msg);
|
|
||||||
cb = null;
|
|
||||||
self._url = null;
|
|
||||||
self.process = null;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Verifier.prototype.stop = function (cb) {
|
|
||||||
if (!this.process || !this._url) {
|
|
||||||
throw new Error('verifier not running');
|
|
||||||
}
|
|
||||||
this.process.on('exit', function (code) {
|
|
||||||
cb(!code ? null : 'non-zero exit code: ' + code);
|
|
||||||
});
|
|
||||||
const pid = this.process.pid;
|
|
||||||
// Really really kill it. This shouldn't be necesary, but sometimes when
|
|
||||||
// running tests in a loop, the process doesn't die with just a SIGINT.
|
|
||||||
cp.spawn('kill', ['-9', pid]);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Verifier;
|
|
||||||
@@ -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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('missing assertion test', function () {
|
|
||||||
var verifier = new Verifier();
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail to verify when assertion is missing', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
audience: 'http://example.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(400);
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.equal('missing assertion parameter');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,80 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var IdP = require('browserid-local-verify/testing').IdP,
|
|
||||||
Client = require('browserid-local-verify/testing').Client,
|
|
||||||
Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('audience tests', function () {
|
|
||||||
var verifier = new Verifier({ testServiceFailure: true });
|
|
||||||
var idp = new IdP();
|
|
||||||
var client;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve) => idp.start(resolve));
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
client = new Client({ idp: idp });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
await new Promise((resolve) => idp.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
var assertion;
|
|
||||||
|
|
||||||
it('test assertion should be created', function (done) {
|
|
||||||
client.assertion({ audience: 'http://example.com' }, function (err, ass) {
|
|
||||||
assertion = ass;
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v1 api) should return a 503 on service failure', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.v1url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
(503).should.equal(r.statusCode);
|
|
||||||
'failure'.should.equal(r.body.status);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v2 api) should return a 503 on service failure', function (done) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
(503).should.equal(r.statusCode);
|
|
||||||
'failure'.should.equal(r.body.status);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,102 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var IdP = require('browserid-local-verify/testing').IdP,
|
|
||||||
Client = require('browserid-local-verify/testing').Client,
|
|
||||||
Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('audience tests', function () {
|
|
||||||
var verifier = new Verifier();
|
|
||||||
var idp = new IdP();
|
|
||||||
var client;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve) => idp.start(resolve));
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
client = new Client({
|
|
||||||
idp: idp,
|
|
||||||
// note, using an email not rooted at the idp. trustIssuer is the only
|
|
||||||
// way this can work
|
|
||||||
email: 'user@example.com',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
await new Promise((resolve) => idp.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
var assertion;
|
|
||||||
|
|
||||||
it('test assertion should be created', function (done) {
|
|
||||||
client.assertion({ audience: 'http://example.com' }, function (err, ass) {
|
|
||||||
assertion = ass;
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function submitWithTrustedIssuers(ti, cb) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
trustedIssuers: ti,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should verify when trusted issuers is specified', function (done) {
|
|
||||||
submitWithTrustedIssuers([idp.domain()], function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
'okay'.should.equal(r.body.status);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail when trusted issuers is not specified', function (done) {
|
|
||||||
submitWithTrustedIssuers(undefined, function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
'failure'.should.equal(r.body.status);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail when trusted issuers is not an array', function (done) {
|
|
||||||
submitWithTrustedIssuers(idp.domain(), function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
'failure'.should.equal(r.body.status);
|
|
||||||
'trusted issuers must be an array'.should.equal(r.body.reason);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail when trusted issuers contains non-strings', function (done) {
|
|
||||||
submitWithTrustedIssuers(
|
|
||||||
[idp.domain(), ['example.com']],
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
'failure'.should.equal(r.body.status);
|
|
||||||
'trusted issuers must be an array of strings'.should.equal(
|
|
||||||
r.body.reason
|
|
||||||
);
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,136 +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/. */
|
|
||||||
|
|
||||||
/* global describe,it,before,after */
|
|
||||||
|
|
||||||
var IdP = require('browserid-local-verify/testing').IdP,
|
|
||||||
Client = require('browserid-local-verify/testing').Client,
|
|
||||||
Verifier = require('./lib/verifier.js'),
|
|
||||||
should = require('should'),
|
|
||||||
shouldReturnSecurityHeaders = require('./lib/should-return-security-headers.js'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
describe('unverified email', function () {
|
|
||||||
var fallback = new IdP();
|
|
||||||
var verifier = new Verifier();
|
|
||||||
var client;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await new Promise((resolve) => fallback.start(resolve));
|
|
||||||
verifier.setFallback(fallback);
|
|
||||||
await new Promise((resolve) => verifier.start(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await new Promise((resolve) => verifier.stop(resolve));
|
|
||||||
await new Promise((resolve) => fallback.stop(resolve));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v1) assertion with unverified email address should fail to verify', function (done) {
|
|
||||||
client = new Client({
|
|
||||||
idp: fallback,
|
|
||||||
principal: { 'unverified-email': 'bob@example.com' },
|
|
||||||
});
|
|
||||||
// clear email
|
|
||||||
client.email(null);
|
|
||||||
client.assertion(
|
|
||||||
{ audience: 'http://example.com' },
|
|
||||||
function (_, assertion) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.v1url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.status.should.equal('failure');
|
|
||||||
r.body.reason.should.startWith('untrusted assertion');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v1) assertion with unverified email address and forceIssuer should verify', function (done) {
|
|
||||||
client = new Client({
|
|
||||||
idp: fallback,
|
|
||||||
principal: { 'unverified-email': 'bob@example.com' },
|
|
||||||
});
|
|
||||||
client.assertion(
|
|
||||||
{ audience: 'http://example.com' },
|
|
||||||
function (_, assertion) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
experimental_forceIssuer: fallback.domain(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.status.should.equal('okay');
|
|
||||||
r.body.idpClaims.should.be.type('object');
|
|
||||||
r.body.idpClaims['unverified-email'].should.equal(
|
|
||||||
'bob@example.com'
|
|
||||||
);
|
|
||||||
r.body.should.not.have.property('unverified-email');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('(v1) allowUnverified causes extraction of unverified email addresses', function (done) {
|
|
||||||
client = new Client({
|
|
||||||
idp: fallback,
|
|
||||||
principal: { 'unverified-email': 'bob@example.com' },
|
|
||||||
});
|
|
||||||
|
|
||||||
client.assertion(
|
|
||||||
{ audience: 'http://example.com' },
|
|
||||||
function (_, assertion) {
|
|
||||||
request(
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
url: verifier.v1url(),
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
assertion: assertion,
|
|
||||||
audience: 'http://example.com',
|
|
||||||
experimental_forceIssuer: fallback.domain(),
|
|
||||||
experimental_allowUnverified: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function (err, r) {
|
|
||||||
should.not.exist(err);
|
|
||||||
r.statusCode.should.equal(200);
|
|
||||||
r.body.status.should.equal('okay');
|
|
||||||
r.body.idpClaims.should.be.type('object');
|
|
||||||
r.body.idpClaims['unverified-email'].should.equal(
|
|
||||||
'bob@example.com'
|
|
||||||
);
|
|
||||||
r.body.should.have.property('unverified-email');
|
|
||||||
shouldReturnSecurityHeaders(r);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -18,7 +18,6 @@ CI=false NODE_ENV=test npx nx run-many \
|
|||||||
--verbose \
|
--verbose \
|
||||||
-p \
|
-p \
|
||||||
123done \
|
123done \
|
||||||
browserid-verifier \
|
|
||||||
fxa-auth-server \
|
fxa-auth-server \
|
||||||
fxa-content-server \
|
fxa-content-server \
|
||||||
fxa-graphql-api \
|
fxa-graphql-api \
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ spec:
|
|||||||
providesApis:
|
providesApis:
|
||||||
- api:fxa-auth
|
- api:fxa-auth
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- component:fxa-browserid-verifier
|
|
||||||
- component:fxa-customs-server
|
- component:fxa-customs-server
|
||||||
- resource:fxa-auth-database
|
- resource:fxa-auth-database
|
||||||
- resource:fxa-oauth-database
|
- resource:fxa-oauth-database
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ CI=false NODE_ENV=test npx nx run-many \
|
|||||||
--parallel=3 \
|
--parallel=3 \
|
||||||
-p \
|
-p \
|
||||||
123done \
|
123done \
|
||||||
browserid-verifier \
|
|
||||||
fxa-auth-server \
|
fxa-auth-server \
|
||||||
fxa-content-server \
|
fxa-content-server \
|
||||||
fxa-graphql-api \
|
fxa-graphql-api \
|
||||||
|
|||||||
Reference in New Issue
Block a user