New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

bedrock-account-http

Package Overview
Dependencies
Maintainers
5
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bedrock-account-http - npm Package Compare versions

Comparing version

to
4.0.0

schemas/bedrock-account-http.js

18

CHANGELOG.md
# bedrock-account-http ChangeLog
## 4.0.0 - 2022-03-07
### Changed
- **BREAKING**: Update peer deps:
- `bedrock@4.4`
- `bedrock-validation@5.5`
- `bedrock-mongodb@8.4`
- `bedrock-passport@8`
- **BREAKING**: Disable cors on get account route to avoid CSRF attacks.
- **BREAKING**: Remove `schemas` directory from configured schemas, load
locally only using the new `bedrock-validation@5` model.
### Removed
- **BREAKING**: Remove all usage of `bedrock-permission` including
roles (e.g., `sysResourceRole`), `actor`, etc. All authz should
be managed via HTTP (or other) APIs and technologies such as
zcaps, meters, and oauth2.
## 3.1.0 - 2021-06-08

@@ -4,0 +22,0 @@

2

lib/config.js
/*!
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
*/

@@ -4,0 +4,0 @@ 'use strict';

/*!
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
*/

@@ -9,3 +9,2 @@ 'use strict';

const {config, util: {uuid, BedrockError}} = bedrock;
const cors = require('cors');
const brAccount = require('bedrock-account');

@@ -17,6 +16,6 @@ const brPassport = require('bedrock-passport');

} = brPassport;
const {validate} = require('bedrock-validation');
// FIXME add an implementation in bedrock-express
const {createValidateMiddleware} = require('bedrock-validation');
const boolParser = require('express-query-boolean');
const intParser = require('express-query-int');
const validators = require('../schemas/bedrock-account-http');

@@ -26,8 +25,2 @@ // load config defaults

// module API
const api = {};
module.exports = api;
// const logger = bedrock.loggers.get('app').child('bedrock-account-http');
bedrock.events.on('bedrock-session-http.session.get', (req, session) => {

@@ -48,4 +41,6 @@ // if user is authenticated, include account ID in session

basePath,
// only check authn to establish whether to auto-login or not; anyone can
// create a new account
optionallyAuthenticated,
validate('account.create'),
createValidateMiddleware({bodySchema: validators.create()}),
asyncHandler(async (req, res, next) => {

@@ -56,21 +51,20 @@ const account = {

};
const meta = {
sysResourceRole: [{
sysRole: 'account.registered',
resource: [account.id]
}]
};
// actor is not required, but passed through if given
let {actor = null} = req.user || {};
await brAccount.insert({actor, account, meta});
const meta = {};
if(actor || !cfg.autoLoginNewAccounts) {
// actor given or auto-login disabled, do not auto-login the new account
res.status(201).location(baseUri + '/' + account.id).json(account);
// anyone may create a new account; must be rate limited via another
// means if necessary
await brAccount.insert({account, meta});
// get location for new account
const location = `${baseUri}/${encodeURIComponent(account.id)}`;
if(req.user || !cfg.autoLoginNewAccounts) {
// if user already logged into another account or auto-login disabled,
// do not auto-login the new account
res.status(201).location(location).json(account);
return;
}
// no actor given, so auto-login
actor = await brAccount.getCapabilities({id: account.id});
const user = {account, actor};
// auto-login
const user = {account};
// TODO: if passport supports promises or this can be safely promisified

@@ -89,4 +83,3 @@ // then do that in the future; (note that passport's `logIn` signature

}).then(
() => res.status(201).location(baseUri + '/' + account.id)
.json(account),
() => res.status(201).location(location).json(account),
next);

@@ -98,7 +91,6 @@ });

basePath,
optionallyAuthenticated,
// FIXME add an implementation in bedrock-express
boolParser(),
intParser(),
validate({query: 'account.get'}),
optionallyAuthenticated,
createValidateMiddleware({querySchema: validators.get()}),
asyncHandler(async (req, res) => {

@@ -115,3 +107,4 @@ /**

* Visitors (not logged in) can confirm an email exists
* Admins (logged in) can list accounts by email
* Authenticated account can get its own account info via email
* ZCAP-authorized can list accounts by email (Not implemented)
*/

@@ -121,7 +114,6 @@ if(req.query.exists === true) {

const {email} = req.query;
const exists = await brAccount.exists({actor: null, email});
const exists = await brAccount.exists({email});
if(exists) {
return res.status(200).end();
}
// TODO: improve error details
throw new BedrockError(

@@ -133,17 +125,31 @@ 'Account does not exist.', 'NotFoundError', {

}
const {email, after = null} = req.query;
const {actor} = req.user || {};
// unauthenticated users must pass `exists` param
if(!(req.user && req.user.account)) {
throw new BedrockError(
'The "exists" query parameter must be passed.',
'NotAllowedError', {
httpStatusCode: 403,
public: true
});
}
const {email/*, after = null*/} = req.query;
const query = {'account.email': email};
if(after) {
// only support searches on authenticated account
query['account.id'] = req.user.account.id;
/*if(after) {
query['account.id'] = {$gt: after};
}
const fields = {
account: 1,
'meta.created': 1,
'meta.sequence': 1,
'meta.status': 1,
'meta.sysResourceRole': 1
}*/
const options = {
sort: {'account.id': -1},
projection: {
_id: 0,
account: 1,
'meta.created': 1,
'meta.sequence': 1,
'meta.status': 1
}
};
const options = {sort: {'account.id': -1}};
const records = await brAccount.getAll({actor, query, fields, options});
const records = await brAccount.getAll({query, options});
res.json(records);

@@ -155,3 +161,3 @@ }));

ensureAuthenticated,
validate('account.setStatus'),
createValidateMiddleware({bodySchema: validators.setStatus()}),
asyncHandler(async (req, res) => {

@@ -165,9 +171,9 @@ /**

* changes that status in the account's meta
* possible statuses are active and deleted
* possible statuses are active, disabled, and deleted
* @route /:account/status
*/
const {account: id} = req.params;
const {actor} = req.user || {};
_checkAccount({req, id});
const {status} = req.body;
await brAccount.setStatus({actor, status, id});
await brAccount.setStatus({status, id});
res.status(204).send();

@@ -181,3 +187,3 @@ })

intParser(),
validate('account.update'),
createValidateMiddleware({bodySchema: validators.update()}),
asyncHandler(async (req, res) => {

@@ -196,5 +202,5 @@ /**

*/
const {actor} = req.user || {};
const {account: id} = req.params;
const updater = {actor, id, ...req.body};
_checkAccount({req, id});
const updater = {id, ...req.body};
await brAccount.update(updater);

@@ -204,40 +210,32 @@ res.status(204).send();

app.options(accountPath, cors());
app.get(
`${accountPath}/roles`,
accountPath,
ensureAuthenticated,
cors(),
asyncHandler(async (req, res) => {
/**
* @func getCapabilitiesForAnAccount
* @param {Request} req
* @param {Response} res
* @description uses the account param to get
* all the capabilities for an account
* @route /:account/roles
*/
const {account: id} = req.params;
const record = await brAccount.getCapabilities({id});
_checkAccount({req, id});
const record = await brAccount.get({id});
res.json(record);
}));
app.get(
app.delete(
accountPath,
optionallyAuthenticated,
cors(),
ensureAuthenticated,
asyncHandler(async (req, res) => {
const {account: id} = req.params;
const {actor} = req.user || {};
const record = await brAccount.get({actor, id});
res.json(record);
_checkAccount({req, id});
await brAccount.setStatus({status: 'deleted', id});
res.status(204).send();
}));
});
app.delete(
accountPath,
ensureAuthenticated,
asyncHandler(async (req, res, next) => {
// TODO: next
next();
}));
});
function _checkAccount({req, id}) {
if(!(req.user && req.user.account && req.user.account.id === id)) {
throw new BedrockError(
'The authenticated account does not match the target account.',
'NotAllowedError', {
httpStatusCode: 403,
public: true
});
}
}
{
"name": "bedrock-account-http",
"version": "3.1.0",
"version": "4.0.0",
"description": "HTTP API for Bedrock User Accounts",

@@ -27,3 +27,2 @@ "license": "SEE LICENSE IN LICENSE.md",

"dependencies": {
"cors": "^2.8.4",
"express-query-boolean": "^2.0.0",

@@ -33,7 +32,7 @@ "express-query-int": "^3.0.0"

"peerDependencies": {
"bedrock": "1.12.1 - 3.x",
"bedrock-account": "^5.0.0",
"bedrock-express": "2.0.8 - 3.x",
"bedrock-passport": "5.x - 6.x",
"bedrock-validation": "^5.0.0"
"bedrock": "^4.4.3",
"bedrock-account": "^6.0.0",
"bedrock-express": "^6.2.2",
"bedrock-passport": "^8.0.2",
"bedrock-validation": "^5.5.0"
},

@@ -40,0 +39,0 @@ "directories": {

@@ -1,3 +0,3 @@

/*
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved.
/*!
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
*/

@@ -8,3 +8,2 @@ 'use strict';

const database = require('bedrock-mongodb');
const {promisify} = require('util');
const {util: {uuid}} = require('bedrock');

@@ -23,12 +22,3 @@

//called in before
api.getActors = async mockData => {
const actors = {};
for(const [key, record] of Object.entries(mockData.accounts)) {
actors[key] = await brAccount.getCapabilities({id: record.account.id});
}
return actors;
};
//called in test before hook
// called in test before hook
api.prepareDatabase = async mockData => {

@@ -41,3 +31,3 @@ await api.removeCollections();

api.removeCollections = async (collectionNames = ['account']) => {
await promisify(database.openCollections)(collectionNames);
await database.openCollections(collectionNames);
for(const collectionName of collectionNames) {

@@ -56,4 +46,5 @@ await database.collections[collectionName].deleteMany({});

try {
await brAccount.insert(
{actor: null, account: record.account, meta: record.meta || {}});
await brAccount.insert({
account: record.account, meta: record.meta || {}
});
} catch(e) {

@@ -60,0 +51,0 @@ if(e.name === 'DuplicateError') {

/*!
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
*/
'use strict';

@@ -14,5 +13,6 @@

const mockData = require('../mock.data');
const {util: {uuid}} = require('bedrock');
const {_deserializeUser} = require('bedrock-passport');
const Emails = {
admin: 'admin@example.com',
const emails = {
alpha: 'alpha@example.com',

@@ -25,3 +25,2 @@ multi: 'multi@example.com',

let api;
let actors;

@@ -32,3 +31,2 @@ const baseURL =

// simple quick func to check validation errors
// TODO extend mocha should with this
function validationError(

@@ -57,7 +55,15 @@ result, errorMethod,

function stubPassportStub(email) {
passportStub.callsFake((req, res, next) => {
req.user = {
actor: actors[email],
account: accounts[email].account
};
passportStub.callsFake(async (req, res, next) => {
if(!email) {
req.user = null;
return next();
}
try {
req.user = await _deserializeUser({
accountId: accounts[email].account.id
});
} catch(e) {
return next(e);
}
next();

@@ -70,4 +76,3 @@ });

await helpers.prepareDatabase(mockData);
actors = await helpers.getActors(mockData);
accounts = mockData.accounts;
accounts = {...mockData.accounts};
api = create({

@@ -78,2 +83,5 @@ baseURL,

});
afterEach(function() {
stubPassportStub(null);
});
after(async function() {

@@ -116,3 +124,2 @@ passportStub.restore();

});
});

@@ -123,3 +130,3 @@

const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.admin);
stubPassportStub(emails.alpha);
const result = await api.get(`/${id}`);

@@ -133,5 +140,5 @@ result.status.should.equal(200);

it('should return 403 if actor does not have permission', async function() {
it('should return 403', async function() {
const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.multi);
stubPassportStub(emails.multi);
const result = await api.get(`/${id}`);

@@ -145,7 +152,7 @@ result.status.should.equal(403);

it('should return 404 if not account for id', async function() {
it('should return 403 if no account exists for id', async function() {
const id = 'does-not-exist';
stubPassportStub(Emails.admin);
stubPassportStub(emails.alpha);
const result = await api.get(`/${id}`);
result.status.should.equal(404);
result.status.should.equal(403);
const {data} = result;

@@ -160,4 +167,7 @@ data.should.be.an('object');

it('should change the status to deleted', async function() {
const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.admin);
const email = `${uuid()}@digitalbazaar.com`;
const {data} = await api.post('/', {email});
accounts[email] = {account: data, meta: {}};
const {id} = data;
stubPassportStub(email);
const status = 'deleted';

@@ -167,10 +177,11 @@ const result = await api.post(`/${id}/status`, {status});

const nextResult = await api.get(`/${id}`);
nextResult.data.should.have.property('meta');
nextResult.data.meta.should.have.property('status');
nextResult.data.meta.status.should.equal(status);
nextResult.status.should.equal(404);
});
it('should change the status to disabled', async function() {
const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.admin);
const email = `${uuid()}@digitalbazaar.com`;
const {data} = await api.post('/', {email});
accounts[email] = {account: data, meta: {}};
const {id} = data;
stubPassportStub(email);
const status = 'disabled';

@@ -180,10 +191,11 @@ const result = await api.post(`/${id}/status`, {status});

const nextResult = await api.get(`/${id}`);
nextResult.data.should.have.property('meta');
nextResult.data.meta.should.have.property('status');
nextResult.data.meta.status.should.equal(status);
nextResult.status.should.equal(403);
});
it('should change the status to active', async function() {
const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.admin);
it('should keep status at active', async function() {
const email = `${uuid()}@digitalbazaar.com`;
const {data} = await api.post('/', {email});
accounts[email] = {account: data, meta: {}};
const {id} = data;
stubPassportStub(email);
const status = 'active';

@@ -198,5 +210,33 @@ const result = await api.post(`/${id}/status`, {status});

it('should fail to reactivate disabled account', async function() {
const email = `${uuid()}@digitalbazaar.com`;
const {data} = await api.post('/', {email});
accounts[email] = {account: data, meta: {}};
const {id} = data;
stubPassportStub(email);
const status = 'disabled';
const result = await api.post(`/${id}/status`, {status});
result.status.should.equal(204);
const nextResult = await api.post(`/${id}/status`, {status: 'active'});
nextResult.status.should.equal(403);
});
it('should fail to reactivate deleted account', async function() {
const email = `${uuid()}@digitalbazaar.com`;
const {data} = await api.post('/', {email});
accounts[email] = {account: data, meta: {}};
const {id} = data;
stubPassportStub(email);
const status = 'deleted';
const result = await api.post(`/${id}/status`, {status});
result.status.should.equal(204);
const nextResult = await api.post(`/${id}/status`, {status: 'active'});
nextResult.status.should.equal(404);
});
it('should return 403', async function() {
const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.multi);
stubPassportStub(emails.multi);
const status = 'deleted';

@@ -209,3 +249,3 @@ const result = await api.post(`/${id}/status`, {status});

const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.multi);
stubPassportStub(emails.multi);
const result = await api.post(`/${id}/status`);

@@ -215,26 +255,7 @@ validationError(result, 'patch', /status/i);

});
describe('get /:account/roles', function() {
it('should return an account', async function() {
const {account: {id}} = accounts['alpha@example.com'];
const result = await api.get(`/${id}/roles`);
result.status.should.equal(200);
const {data} = result;
data.should.be.an('object');
data.should.have.property('id');
data.should.have.property('sysResourceRole');
data.sysResourceRole.should.be.an('array');
data.sysResourceRole.forEach(role => {
role.should.have.property('sysRole');
role.sysRole.should.be.an('string');
role.should.have.property('resource');
role.resource.should.be.an('array');
});
});
});
describe('patch /:account', function() {
it('should update an account', async function() {
const {account: {id}} = accounts[Emails.updated];
stubPassportStub(Emails.admin);
const {account: {id}} = accounts[emails.updated];
stubPassportStub(emails.updated);
const value = 'updated@tester.org';

@@ -253,3 +274,3 @@ const patch = [{op: 'replace', path: '/email', value}];

account.email.should.equal(value);
account.email.should.not.contain(Emails.updated);
account.email.should.not.contain(emails.updated);
});

@@ -259,3 +280,3 @@

const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.admin);
stubPassportStub(emails.alpha);
const result = await api.patch(`/${id}`, {sequence: 10, patch: []});

@@ -267,3 +288,3 @@ validationError(result, 'update', /items/i);

const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.admin);
stubPassportStub(emails.alpha);
const value = 'fail@extras.org';

@@ -278,3 +299,3 @@ const patch = [{op: 'replace', path: '/email', value}];

const {account: {id}} = accounts['alpha@example.com'];
stubPassportStub(Emails.admin);
stubPassportStub(emails.alpha);
const value = 'updated@tester.org';

@@ -294,3 +315,3 @@ const patch = [{op: 'replace', path: '/email', value}];

it('return 200 if the email is found', async function returnAccount() {
const email = 'admin@example.com';
const email = 'alpha@example.com';
const result = await api.get('/', {exists: true, email});

@@ -314,3 +335,3 @@ const {status} = result;

const email = 'multi@example.com';
stubPassportStub(Emails.admin);
stubPassportStub(emails.multi);
const result = await api.get('/', {email});

@@ -331,3 +352,3 @@ result.data.should.be.an('array');

const email = null;
stubPassportStub(Emails.admin);
stubPassportStub(emails.alpha);
const result = await api.get('/', {email});

@@ -339,3 +360,3 @@ validationError(result, 'accounts', /email/i);

const email = 'tomany@params.org';
stubPassportStub(Emails.admin);
stubPassportStub(emails.alpha);
const result = await api.get('/', {email, extra: true});

@@ -345,13 +366,12 @@ validationError(result, 'accounts', /additional/i);

it('should return 403 due to permission', async function() {
const email = 'admin@example.com';
stubPassportStub(Emails.multi);
it('should return no results for non-matching account', async function() {
const email = 'multi@example.com';
stubPassportStub(emails.alpha);
const result = await api.get('/', {email});
result.status.should.equal(403);
result.status.should.equal(200);
const {data} = result;
data.should.be.an('object');
data.should.not.have.property('meta');
data.should.not.have.property('account');
data.should.be.an('array');
data.length.should.equal(0);
});
});
});

@@ -1,3 +0,3 @@

/*
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved.
/*!
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
*/

@@ -18,8 +18,3 @@ 'use strict';

accounts[email].meta = {};
accounts[email].meta.sysResourceRole = [{
sysRole: 'bedrock-account.regular',
generateResource: 'id'
}];
// regular permissions
email = 'alpha@example.com';

@@ -29,16 +24,3 @@ accounts[email] = {};

accounts[email].meta = {};
accounts[email].meta.sysResourceRole = [{
sysRole: 'bedrock-account.regular',
generateResource: 'id'
}];
// admin permissions
email = 'admin@example.com';
accounts[email] = {};
accounts[email].account = helpers.createAccount(email);
accounts[email].meta = {};
accounts[email].meta.sysResourceRole = [{
sysRole: 'bedrock-account.admin'
}];
// multiple accounts one email

@@ -45,0 +27,0 @@ email = 'multi@example.com';

@@ -29,13 +29,11 @@ {

"apisauce": "^2.0.0",
"bedrock": "^3.1.1",
"bedrock-account": "^5.0.0",
"bedrock": "^4.4.3",
"bedrock-account": "^6.0.0",
"bedrock-account-http": "file:..",
"bedrock-express": "^3.2.0",
"bedrock-mongodb": "^7.1.0",
"bedrock-passport": "^6.0.0",
"bedrock-permission": "^3.0.0",
"bedrock-rest": "^3.0.0",
"bedrock-server": "^2.6.0",
"bedrock-test": "^5.3.0",
"bedrock-validation": "^5.0.0",
"bedrock-express": "^6.2.2",
"bedrock-mongodb": "^8.4.0",
"bedrock-passport": "^8.0.2",
"bedrock-server": "^3.1.0",
"bedrock-test": "^6.0.0",
"bedrock-validation": "^5.5.0",
"cross-env": "^7.0.2",

@@ -42,0 +40,0 @@ "grunt": "^1.1.0",

/*!
* Copyright (c) 2012-2021 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2012-2022 Digital Bazaar, Inc. All rights reserved.
*/

@@ -8,5 +8,5 @@ 'use strict';

const path = require('path');
require('bedrock-mongodb');
require('bedrock-express');
const {permissions, roles} = config.permission;
config.mocha.tests.push(path.join(__dirname, 'mocha'));

@@ -16,3 +16,2 @@

config.mongodb.name = 'bedrock_account_http_test';
config.mongodb.local.collection = 'bedrock_account_http_test';
config.mongodb.dropCollections = {};

@@ -22,24 +21,3 @@ config.mongodb.dropCollections.onInit = true;

roles['bedrock-account.regular'] = {
id: 'bedrock-account.regular',
label: 'Account Test Role',
comment: 'Role for Test User',
sysPermission: [
permissions.ACCOUNT_ACCESS.id,
permissions.ACCOUNT_UPDATE.id,
permissions.ACCOUNT_INSERT.id
]
};
roles['bedrock-account.admin'] = {
id: 'bedrock-account.admin',
label: 'Account Test Role',
comment: 'Role for Admin User',
sysPermission: [
permissions.ACCOUNT_ACCESS.id,
permissions.ACCOUNT_UPDATE.id,
permissions.ACCOUNT_INSERT.id,
permissions.ACCOUNT_REMOVE.id,
permissions.ACCOUNT_META_UPDATE.id
]
};
// enable sessions
config.express.useSession = true;

@@ -10,5 +10,7 @@ /*!

const bedrock = require('bedrock');
require('bedrock-account');
require('bedrock-account-http');
require('bedrock-mongodb');
require('bedrock-test');
bedrock.start();