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/local.yml
|
||||
|
||||
# browserid-verifier
|
||||
packages/browserid-verifier/loadtest/venv
|
||||
packages/browserid-verifier/loadtest/*.pyc
|
||||
|
||||
# fxa-admin-server
|
||||
packages/fxa-admin-server/src/config/local.json
|
||||
|
||||
|
||||
@@ -10,16 +10,6 @@ services:
|
||||
# init: true
|
||||
# ports:
|
||||
# - "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:
|
||||
image: fxa-auth-server:build
|
||||
entrypoint: /bin/bash -c
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
9292 # Fortress
|
||||
8080 # 123done
|
||||
10139 # 321done
|
||||
5050 # browserid-verifier
|
||||
3031 # payments server
|
||||
7100 # support admin panel
|
||||
8002 # pushbox
|
||||
|
||||
@@ -11,7 +11,6 @@ spec:
|
||||
- ./backstage/mysql.resources.yaml
|
||||
- ./backstage/redis.resources.yaml
|
||||
- ./packages/123done/backstage.yaml
|
||||
- ./packages/browserid-verifier/backstage.yaml
|
||||
- ./packages/fxa-admin-panel/backstage.yaml
|
||||
- ./packages/fxa-admin-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 \
|
||||
-p \
|
||||
123done \
|
||||
browserid-verifier \
|
||||
fxa-auth-server \
|
||||
fxa-content-server \
|
||||
fxa-graphql-api \
|
||||
|
||||
@@ -20,7 +20,6 @@ spec:
|
||||
providesApis:
|
||||
- api:fxa-auth
|
||||
dependsOn:
|
||||
- component:fxa-browserid-verifier
|
||||
- component:fxa-customs-server
|
||||
- resource:fxa-auth-database
|
||||
- resource:fxa-oauth-database
|
||||
|
||||
@@ -20,7 +20,6 @@ CI=false NODE_ENV=test npx nx run-many \
|
||||
--parallel=3 \
|
||||
-p \
|
||||
123done \
|
||||
browserid-verifier \
|
||||
fxa-auth-server \
|
||||
fxa-content-server \
|
||||
fxa-graphql-api \
|
||||
|
||||
Reference in New Issue
Block a user