viz-js-lib 0.18.2 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/api-frontend/app.js +5 -1
- package/dist/statistics.html +1 -1
- package/dist/viz-tests.min.js.gz +0 -0
- package/dist/viz.min.js.gz +0 -0
- package/lib/agent.js +237 -0
- package/lib/index.js +2 -0
- package/package.json +1 -1
- package/test/agent.test.js +199 -0
- package/test/methods_by_version.js +2 -0
package/dist/viz-tests.min.js.gz
CHANGED
|
Binary file
|
package/dist/viz.min.js.gz
CHANGED
|
Binary file
|
package/lib/agent.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var _bluebird = require('bluebird');
|
|
4
|
+
|
|
5
|
+
var _bluebird2 = _interopRequireDefault(_bluebird);
|
|
6
|
+
|
|
7
|
+
var _key_public = require('./auth/ecc/src/key_public');
|
|
8
|
+
|
|
9
|
+
var _key_public2 = _interopRequireDefault(_key_public);
|
|
10
|
+
|
|
11
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
|
+
|
|
13
|
+
/** Mirrors the node's never_delegable_operation_names(): a row holding one of these grants nothing. */
|
|
14
|
+
/**
|
|
15
|
+
* VIZ agent access (HF15, op-id 105 `set_agent_permission`) — read-side helpers.
|
|
16
|
+
*
|
|
17
|
+
* A VIZ key is no longer only an authority key. A principal may register up to
|
|
18
|
+
* CHAIN_AGENT_MAX_PER_ACCOUNT live agents: a label plus a public key that may sign an explicit,
|
|
19
|
+
* frozen list of that principal's operations, optionally scoped to off-chain addons, optionally
|
|
20
|
+
* expiring (epoch `1970-01-01T00:00:00` = perpetual).
|
|
21
|
+
*
|
|
22
|
+
* The old check — "getAccount and look at active_authority.key_auths" — silently rejects agent
|
|
23
|
+
* keys, because an agent is not an account and never appears in any authority. The recipe now is
|
|
24
|
+
* authority OR agents, and `check()` does both in one call.
|
|
25
|
+
*
|
|
26
|
+
* Mirrors `VIZ\Agent` of viz-php-lib; the node (`agent_evaluator.cpp::usable_agent_row`) is the
|
|
27
|
+
* source of truth:
|
|
28
|
+
* - an agent answers direct active/regular requirements only, and only for operations on its list;
|
|
29
|
+
* the master authority is never satisfied by an agent, nor is a nested/other authority;
|
|
30
|
+
* - a row is unusable when expired or when its list holds a never-delegable name;
|
|
31
|
+
* - one key belongs to one agent of a principal; a key change of master/active, a recovery or a
|
|
32
|
+
* sale wipes every agent of the principal.
|
|
33
|
+
*
|
|
34
|
+
* @module viz/agent
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
var NEVER_DELEGABLE = ['set_agent_permission', 'proposal_update', 'account_update', 'recover_account', 'change_recovery_account', 'set_account_price', 'set_subaccount_price', 'target_account_sale'];
|
|
38
|
+
|
|
39
|
+
/** Authority roles a key can belong to, most privileged first. Agents answer for none of them. */
|
|
40
|
+
var ROLES = ['master', 'active', 'regular'];
|
|
41
|
+
|
|
42
|
+
module.exports = function (VIZ_API) {
|
|
43
|
+
/** Agent rows of a principal as the node returns them (expired rows included; it sweeps them lazily). */
|
|
44
|
+
function get(account) {
|
|
45
|
+
return VIZ_API.getAgentPermissionsAsync(account);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** First live agent row signed by `key`, or false. A failed request reads as "no agents". */
|
|
49
|
+
function find(account, key) {
|
|
50
|
+
return _bluebird2.default.resolve(get(account)).then(function (rows) {
|
|
51
|
+
if (!Array.isArray(rows)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
for (var i = 0; i < rows.length; i++) {
|
|
55
|
+
var row = rows[i];
|
|
56
|
+
if (row && row.agent_key && !isExpired(row) && keyEquals(row.agent_key, key)) {
|
|
57
|
+
return row;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}).catch(function () {
|
|
62
|
+
return false;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Resolves true when `key` is a live agent of `account` — and, when `operation` is given, may sign it. */
|
|
67
|
+
function has(account, key, operation) {
|
|
68
|
+
return find(account, key).then(function (row) {
|
|
69
|
+
if (false === row) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (null === operation || undefined === operation || '' === operation) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return grants(row, operation);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Unified check: may `key` act for `account`? Answers for the authority roles first, then for the
|
|
81
|
+
* agents. Resolves to an object:
|
|
82
|
+
* allowed — bool
|
|
83
|
+
* source — 'authority' | 'agent' | 'none'
|
|
84
|
+
* role — authority role the key matched, when source is 'authority', else false
|
|
85
|
+
* agent — the matched agent row, when source is 'agent', else false
|
|
86
|
+
* operation — the operation the caller asked about (null = any)
|
|
87
|
+
*
|
|
88
|
+
* With `operation` omitted an authority key is answered by membership alone, while an agent is
|
|
89
|
+
* only answered as "this is a live agent key": its grants are per-operation, so pass the wire
|
|
90
|
+
* name (`transfer`, `pm_place_bet`, ...) to check a specific operation. A key that is only in a
|
|
91
|
+
* nested authority (account_auths) is NOT resolved here — use `verify_authority` with a signed
|
|
92
|
+
* transaction for the full recursive check. Note that a node without HF15 answers every agent
|
|
93
|
+
* question with "none": the check then degrades to authorities only.
|
|
94
|
+
*/
|
|
95
|
+
function check(account, key, operation) {
|
|
96
|
+
var result = {
|
|
97
|
+
allowed: false,
|
|
98
|
+
source: 'none',
|
|
99
|
+
role: false,
|
|
100
|
+
agent: false,
|
|
101
|
+
operation: null === operation ? null : operation
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return _bluebird2.default.resolve(VIZ_API.getAccountAsync(account, '')).catch(function () {
|
|
105
|
+
return false;
|
|
106
|
+
}).then(function (accountObject) {
|
|
107
|
+
if (accountObject && 'string' === typeof accountObject.name && accountObject.name === account) {
|
|
108
|
+
for (var i = 0; i < ROLES.length; i++) {
|
|
109
|
+
var role = ROLES[i];
|
|
110
|
+
if (inAuthority(accountObject[role + '_authority'], key)) {
|
|
111
|
+
result.allowed = true;
|
|
112
|
+
result.source = 'authority';
|
|
113
|
+
result.role = role;
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return find(account, key).then(function (row) {
|
|
119
|
+
if (false !== row && (null === operation || undefined === operation || '' === operation || grants(row, operation))) {
|
|
120
|
+
result.allowed = true;
|
|
121
|
+
result.source = 'agent';
|
|
122
|
+
result.agent = row;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** True when the agent row is alive at head block time. The node's own flag wins; the timestamp is the fallback. */
|
|
130
|
+
function isExpired(row) {
|
|
131
|
+
if (undefined !== row.expired) {
|
|
132
|
+
return !!row.expired;
|
|
133
|
+
}
|
|
134
|
+
if (!row.expiration) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// The chain reports UTC without a trailing 'Z', so parse it as UTC explicitly.
|
|
138
|
+
var time = Date.parse(row.expiration + 'Z');
|
|
139
|
+
if (isNaN(time) || time <= 0) {
|
|
140
|
+
return false; // epoch = perpetual; an unparsable value is not a reason to deny the key
|
|
141
|
+
}
|
|
142
|
+
return time <= Date.now();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** True when the row's list covers `operation` and the row is not poisoned by a never-delegable name. */
|
|
146
|
+
function grants(row, operation) {
|
|
147
|
+
if (!Array.isArray(row.operations) || -1 === row.operations.indexOf(operation)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return !poisoned(row);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* True when the row's list holds a never-delegable name. The node refuses such a row WHOLE
|
|
155
|
+
* (`usable_agent_row` bails on the first forbidden name), it does not merely drop that name — so
|
|
156
|
+
* the row grants nothing at all, including its off-chain addons.
|
|
157
|
+
*/
|
|
158
|
+
function poisoned(row) {
|
|
159
|
+
if (!Array.isArray(row.operations)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
for (var i = 0; i < row.operations.length; i++) {
|
|
163
|
+
if (-1 !== NEVER_DELEGABLE.indexOf(row.operations[i])) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* True when the row is scoped to `scope`. Addons are opaque off-chain scopes: the node stores
|
|
172
|
+
* them and never interprets them, so a service matches its OWN scope here, by exact string
|
|
173
|
+
* (case-sensitive — the chain does not normalise them for us).
|
|
174
|
+
*/
|
|
175
|
+
function addonMatches(row, scope) {
|
|
176
|
+
if (!Array.isArray(row.addons)) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return -1 !== row.addons.indexOf(scope);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** True when `key` is a member of the authority with enough weight to meet its threshold. */
|
|
183
|
+
function inAuthority(authority, key) {
|
|
184
|
+
if (!authority || !Array.isArray(authority.key_auths)) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
var threshold = parseInt(authority.weight_threshold, 10) || 0;
|
|
188
|
+
var weight = 0;
|
|
189
|
+
for (var i = 0; i < authority.key_auths.length; i++) {
|
|
190
|
+
var member = authority.key_auths[i];
|
|
191
|
+
if (member && member.length > 1 && keyEquals(member[0], key)) {
|
|
192
|
+
weight += parseInt(member[1], 10) || 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return weight >= threshold && weight > 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Compare two encoded keys, tolerating whitespace, a missing 'VIZ' prefix and base58 spelling differences. */
|
|
199
|
+
function keyEquals(a, b) {
|
|
200
|
+
if ('string' !== typeof a || 'string' !== typeof b) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (a === b) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
return normalizeKey(a) === normalizeKey(b);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Canonical `VIZ...` form of an encoded public key; the trimmed input when it cannot be parsed. */
|
|
210
|
+
function normalizeKey(key) {
|
|
211
|
+
var value = String(key === null || undefined === key ? '' : key).trim();
|
|
212
|
+
if ('' === value) {
|
|
213
|
+
return '';
|
|
214
|
+
}
|
|
215
|
+
if (0 !== value.indexOf('VIZ')) {
|
|
216
|
+
value = 'VIZ' + value;
|
|
217
|
+
}
|
|
218
|
+
var parsed = _key_public2.default.fromString(value);
|
|
219
|
+
return parsed ? parsed.toString() : String(key).trim();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
NEVER_DELEGABLE: NEVER_DELEGABLE,
|
|
224
|
+
ROLES: ROLES,
|
|
225
|
+
get: get,
|
|
226
|
+
find: find,
|
|
227
|
+
has: has,
|
|
228
|
+
check: check,
|
|
229
|
+
isExpired: isExpired,
|
|
230
|
+
grants: grants,
|
|
231
|
+
poisoned: poisoned,
|
|
232
|
+
addonMatches: addonMatches,
|
|
233
|
+
inAuthority: inAuthority,
|
|
234
|
+
keyEquals: keyEquals,
|
|
235
|
+
normalizeKey: normalizeKey
|
|
236
|
+
};
|
|
237
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var api = require('./api');
|
|
|
4
4
|
var auth = require('./auth');
|
|
5
5
|
var broadcast = require('./broadcast');
|
|
6
6
|
var formatter = require('./formatter')(api);
|
|
7
|
+
var agent = require('./agent')(api);
|
|
7
8
|
var memo = require('./auth/memo');
|
|
8
9
|
var aes = require('./auth/ecc/src/aes');
|
|
9
10
|
var config = require('./config');
|
|
@@ -15,6 +16,7 @@ module.exports = {
|
|
|
15
16
|
auth: auth,
|
|
16
17
|
broadcast: broadcast,
|
|
17
18
|
formatter: formatter,
|
|
19
|
+
agent: agent,
|
|
18
20
|
memo: memo,
|
|
19
21
|
aes: aes,
|
|
20
22
|
config: config,
|
package/package.json
CHANGED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline tests for the HF15 agent helpers. The decision rules are pure functions on purpose:
|
|
3
|
+
* they mirror the node's agent_evaluator.cpp::usable_agent_row and must be testable without a node.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
require('babel-polyfill');
|
|
7
|
+
const assert = require('assert');
|
|
8
|
+
const agentFactory = require('../src/agent');
|
|
9
|
+
|
|
10
|
+
const KEY = 'VIZ7iiYUk6hFuieveBJcTVTV33aNuC2vZauXuYc9Hki3moxsm9WXe';
|
|
11
|
+
const OTHER_KEY = 'VIZ7FzBqZJmTdve9iuHXWvB3Ncu7VeJWKwpA1FpbLi4X4ZdsVAYjC';
|
|
12
|
+
|
|
13
|
+
function row(overrides) {
|
|
14
|
+
return Object.assign({
|
|
15
|
+
account: 'alice',
|
|
16
|
+
agent_name: 'shop',
|
|
17
|
+
agent_key: KEY,
|
|
18
|
+
operations: ['transfer'],
|
|
19
|
+
expiration: '1970-01-01T00:00:00',
|
|
20
|
+
addons: ['wallet'],
|
|
21
|
+
expired: false,
|
|
22
|
+
}, overrides || {});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('viz.agent', function () {
|
|
26
|
+
const agent = agentFactory({});
|
|
27
|
+
|
|
28
|
+
describe('isExpired', function () {
|
|
29
|
+
it('treats the epoch as perpetual, not as expired', function () {
|
|
30
|
+
assert.strictEqual(agent.isExpired(row()), false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('flags a past expiration when the node sent no flag', function () {
|
|
34
|
+
//the timestamp path is the fallback for a node that does not compute `expired`
|
|
35
|
+
assert.strictEqual(
|
|
36
|
+
agent.isExpired(row({ expiration: '2020-01-01T00:00:00', expired: undefined })),
|
|
37
|
+
true
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('keeps a future expiration live', function () {
|
|
42
|
+
assert.strictEqual(agent.isExpired(row({ expiration: '2099-01-01T00:00:00' })), false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('lets the node flag outrank the client clock', function () {
|
|
46
|
+
assert.strictEqual(
|
|
47
|
+
agent.isExpired(row({ expiration: '2099-01-01T00:00:00', expired: true })),
|
|
48
|
+
true
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('grants', function () {
|
|
54
|
+
it('covers only listed operations', function () {
|
|
55
|
+
assert.strictEqual(agent.grants(row(), 'transfer'), true);
|
|
56
|
+
assert.strictEqual(agent.grants(row(), 'pm_place_bet'), false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('refuses a row holding a never-delegable name whole', function () {
|
|
60
|
+
const poisoned = row({ operations: ['transfer', 'account_update'] });
|
|
61
|
+
assert.strictEqual(agent.grants(poisoned, 'transfer'), false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('refuses a malformed row', function () {
|
|
65
|
+
assert.strictEqual(agent.grants(row({ operations: undefined }), 'transfer'), false);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('poisoned', function () {
|
|
70
|
+
it('flags a row holding a never-delegable name', function () {
|
|
71
|
+
assert.strictEqual(agent.poisoned(row()), false);
|
|
72
|
+
assert.strictEqual(agent.poisoned(row({ operations: ['transfer', 'account_update'] })), true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('does not call a row without an operation list poisoned', function () {
|
|
76
|
+
assert.strictEqual(agent.poisoned(row({ operations: undefined })), false);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('addonMatches', function () {
|
|
81
|
+
it('matches the service scope exactly and case-sensitively', function () {
|
|
82
|
+
assert.strictEqual(agent.addonMatches(row(), 'wallet'), true);
|
|
83
|
+
assert.strictEqual(agent.addonMatches(row(), 'Wallet'), false);
|
|
84
|
+
assert.strictEqual(agent.addonMatches(row(), 'exchange'), false);
|
|
85
|
+
assert.strictEqual(agent.addonMatches(row(), ''), false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('treats a row without addons as no match, not as any scope', function () {
|
|
89
|
+
assert.strictEqual(agent.addonMatches(row({ addons: [] }), 'wallet'), false);
|
|
90
|
+
assert.strictEqual(agent.addonMatches(row({ addons: undefined }), 'wallet'), false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('inAuthority', function () {
|
|
95
|
+
it('accepts a member that alone meets the threshold', function () {
|
|
96
|
+
const authority = {
|
|
97
|
+
weight_threshold: 2,
|
|
98
|
+
key_auths: [[KEY, 2], [OTHER_KEY, 1]],
|
|
99
|
+
};
|
|
100
|
+
assert.strictEqual(agent.inAuthority(authority, KEY), true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('refuses a member below the threshold', function () {
|
|
104
|
+
const authority = { weight_threshold: 2, key_auths: [[KEY, 1]] };
|
|
105
|
+
assert.strictEqual(agent.inAuthority(authority, KEY), false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('refuses a missing authority', function () {
|
|
109
|
+
assert.strictEqual(agent.inAuthority(false, KEY), false);
|
|
110
|
+
assert.strictEqual(agent.inAuthority(undefined, KEY), false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('keyEquals / normalizeKey', function () {
|
|
115
|
+
it('tolerates whitespace and a missing prefix', function () {
|
|
116
|
+
assert.strictEqual(agent.keyEquals(KEY, ` ${KEY} `), true);
|
|
117
|
+
assert.strictEqual(agent.keyEquals(KEY, KEY.slice(3)), true);
|
|
118
|
+
assert.strictEqual(agent.keyEquals(KEY, OTHER_KEY), false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('returns the canonical form', function () {
|
|
122
|
+
assert.strictEqual(agent.normalizeKey(KEY.slice(3)), KEY);
|
|
123
|
+
assert.strictEqual(agent.normalizeKey(KEY), KEY);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('hands back an unparsable value trimmed', function () {
|
|
127
|
+
assert.strictEqual(agent.normalizeKey(' not-a-key '), 'not-a-key');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('find / has / check against a stubbed node', function () {
|
|
132
|
+
function stub(rows, accountObject) {
|
|
133
|
+
return agentFactory({
|
|
134
|
+
getAgentPermissionsAsync: () => Promise.resolve(rows),
|
|
135
|
+
getAccountAsync: () => Promise.resolve(accountObject),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
it('finds the key and applies the operation list', function () {
|
|
140
|
+
const stubbed = stub([row()], false);
|
|
141
|
+
return stubbed.has('alice', KEY, 'transfer').then((allowed) => {
|
|
142
|
+
assert.strictEqual(allowed, true);
|
|
143
|
+
return stubbed.has('alice', KEY, 'pm_place_bet');
|
|
144
|
+
}).then((allowed) => {
|
|
145
|
+
assert.strictEqual(allowed, false);
|
|
146
|
+
return stubbed.has('alice', OTHER_KEY, 'transfer');
|
|
147
|
+
}).then((allowed) => {
|
|
148
|
+
assert.strictEqual(allowed, false);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('skips expired rows', function () {
|
|
153
|
+
const stubbed = stub([row({ expired: true })], false);
|
|
154
|
+
return stubbed.has('alice', KEY, 'transfer').then((allowed) => {
|
|
155
|
+
assert.strictEqual(allowed, false);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('answers an authority key from the account object', function () {
|
|
160
|
+
const stubbed = stub([], {
|
|
161
|
+
name: 'alice',
|
|
162
|
+
active_authority: { weight_threshold: 1, key_auths: [[KEY, 1]] },
|
|
163
|
+
});
|
|
164
|
+
return stubbed.check('alice', KEY, 'transfer').then((verdict) => {
|
|
165
|
+
assert.strictEqual(verdict.allowed, true);
|
|
166
|
+
assert.strictEqual(verdict.source, 'authority');
|
|
167
|
+
assert.strictEqual(verdict.role, 'active');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('answers an agent key from the rows', function () {
|
|
172
|
+
const stubbed = stub([row()], false);
|
|
173
|
+
return stubbed.check('alice', KEY, 'transfer').then((verdict) => {
|
|
174
|
+
assert.strictEqual(verdict.allowed, true);
|
|
175
|
+
assert.strictEqual(verdict.source, 'agent');
|
|
176
|
+
assert.strictEqual(verdict.agent.agent_name, 'shop');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('denies a key matching neither', function () {
|
|
181
|
+
const stubbed = stub([], { name: 'alice' });
|
|
182
|
+
return stubbed.check('alice', OTHER_KEY, 'transfer').then((verdict) => {
|
|
183
|
+
assert.strictEqual(verdict.allowed, false);
|
|
184
|
+
assert.strictEqual(verdict.source, 'none');
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('degrades to authorities when the node refuses the agent call', function () {
|
|
189
|
+
const stubbed = agentFactory({
|
|
190
|
+
getAgentPermissionsAsync: () => Promise.reject(new Error('unknown method')),
|
|
191
|
+
getAccountAsync: () => Promise.resolve({ name: 'alice' }),
|
|
192
|
+
});
|
|
193
|
+
return stubbed.check('alice', KEY, 'transfer').then((verdict) => {
|
|
194
|
+
assert.strictEqual(verdict.allowed, false);
|
|
195
|
+
assert.strictEqual(verdict.source, 'none');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|