-
Notifications
You must be signed in to change notification settings - Fork 325
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #619 from ipfs-shipyard/feat/window.ipfs-2.0
Add window.ipfs.enable(opts) (Bulk Permission Prompt)
- Loading branch information
Showing
25 changed files
with
3,185 additions
and
1,342 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,52 @@ | ||
'use strict' | ||
|
||
const { createProxyClient } = require('ipfs-postmsg-proxy') | ||
const _Buffer = Buffer | ||
const { assign, freeze } = Object | ||
|
||
// TODO: (wip) this should not be injected by default into every page, | ||
// instead should be lazy-loaded when .enable() method is called for the first time | ||
const { createProxyClient } = require('ipfs-postmsg-proxy') | ||
|
||
function createEnableCommand (proxyClient) { | ||
return { | ||
enable: async (opts) => { | ||
// Send message to proxy server for additional validation | ||
// eg. trigger user prompt if a list of requested capabilities is not empty | ||
// or fail fast and throw if IPFS Proxy is disabled globally | ||
await require('postmsg-rpc').call('proxy.enable', opts) | ||
// Create client | ||
const proxyClient = createProxyClient() | ||
// Additional client-side features | ||
if (opts && opts.experiments) { | ||
if (opts.experiments.ipfsx) { | ||
// Experiment: wrap API with https://github.com/alanshaw/ipfsx | ||
return freeze(require('ipfsx')(proxyClient)) | ||
} | ||
} | ||
return freeze(proxyClient) | ||
} | ||
} | ||
} | ||
|
||
function createWindowIpfs () { | ||
const proxyClient = createProxyClient() | ||
|
||
// Add deprecation warning to window.ipfs.<cmd> | ||
for (let cmd in proxyClient) { | ||
let fn = proxyClient[cmd] | ||
proxyClient[cmd] = function () { | ||
console.warn('Calling commands directly on window.ipfs is deprecated and will be removed on 2019-04-01. Use API instance returned by window.ipfs.enable() instead. More: https://github.com/ipfs-shipyard/ipfs-companion/blob/master/docs/window.ipfs.md') | ||
return fn.apply(this, arguments) | ||
} | ||
} | ||
|
||
// TODO: return thin object with lazy-init inside of window.ipfs.enable | ||
assign(proxyClient, createEnableCommand()) | ||
|
||
return freeze(proxyClient) | ||
} | ||
|
||
// TODO: we should remove Buffer and add support for Uint8Array/ArrayBuffer natively | ||
// See: https://github.com/ipfs/interface-ipfs-core/issues/404 | ||
window.Buffer = window.Buffer || _Buffer | ||
window.ipfs = window.ipfs || createProxyClient() | ||
window.ipfs = window.ipfs || createWindowIpfs() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
const { inCommandWhitelist, createCommandWhitelistError } = require('./pre-command') | ||
const { createProxyAclError } = require('./pre-acl') | ||
|
||
// Artificial API command responsible for backend orchestration | ||
// during window.ipfs.enable() | ||
function createEnableCommand (getIpfs, getState, getScope, accessControl, requestAccess) { | ||
return async (opts) => { | ||
const scope = await getScope() | ||
console.log(`[ipfs-companion] received window.ipfs.enable request from ${scope}`, opts) | ||
|
||
// Check if all access to the IPFS node is disabled | ||
if (!getState().ipfsProxy) throw new Error('User disabled access to API proxy in IPFS Companion') | ||
|
||
// NOOP if .enable() was called without any arguments | ||
if (!opts) return | ||
|
||
// Validate and prompt for any missing permissions in bulk | ||
// if a list of needed commands is announced up front | ||
if (opts.commands) { | ||
let missingAcls = [] | ||
let deniedAcls = [] | ||
for (let command of opts.commands) { | ||
// Fail fast if command is not allowed to be proxied at all | ||
if (!inCommandWhitelist(command)) { | ||
throw createCommandWhitelistError(command) | ||
} | ||
// Get the current access flag to decide if it should be added | ||
// to the list of permissions to be prompted about in the next step | ||
let access = await accessControl.getAccess(scope, command) | ||
if (!access) { | ||
missingAcls.push(command) | ||
} else if (access.allow !== true) { | ||
deniedAcls.push(command) | ||
} | ||
} | ||
// Fail fast if user already denied any of requested permissions | ||
if (deniedAcls.length) { | ||
throw createProxyAclError(scope, deniedAcls) | ||
} | ||
// Display a single prompt with all missing permissions | ||
if (missingAcls.length) { | ||
const { allow, wildcard } = await requestAccess(scope, missingAcls) | ||
let access = await accessControl.setAccess(scope, wildcard ? '*' : missingAcls, allow) | ||
if (!access.allow) { | ||
throw createProxyAclError(scope, missingAcls) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
module.exports = createEnableCommand |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,58 @@ | ||
// This are the functions that DO NOT require an allow/deny decision by the user. | ||
// All other IPFS functions require authorization. | ||
const ACL_WHITELIST = Object.freeze(require('./acl-whitelist.json')) | ||
|
||
// Creates a "pre" function that is called prior to calling a real function | ||
// on the IPFS instance. It will throw if access is denied, and ask the user if | ||
// no access decision has been made yet. | ||
function createPreAcl (permission, getState, getScope, accessControl, requestAccess) { | ||
return async (...args) => { | ||
// Check if all access to the IPFS node is disabled | ||
if (!getState().ipfsProxy) throw new Error('User disabled access to IPFS') | ||
|
||
// No need to verify access if permission is on the whitelist | ||
if (ACL_WHITELIST.includes(permission)) return args | ||
if (!getState().ipfsProxy) throw new Error('User disabled access to API proxy in IPFS Companion') | ||
|
||
const scope = await getScope() | ||
let access = await accessControl.getAccess(scope, permission) | ||
|
||
if (!access) { | ||
const { allow, wildcard } = await requestAccess(scope, permission) | ||
access = await accessControl.setAccess(scope, wildcard ? '*' : permission, allow) | ||
} | ||
const access = await getAccessWithPrompt(accessControl, requestAccess, scope, permission) | ||
|
||
if (!access.allow) { | ||
const err = new Error(`User denied access to ${permission}`) | ||
err.output = { payload: { isIpfsProxyAclError: true, permission, scope } } | ||
throw err | ||
throw createProxyAclError(scope, permission) | ||
} | ||
|
||
return args | ||
} | ||
} | ||
|
||
module.exports = createPreAcl | ||
async function getAccessWithPrompt (accessControl, requestAccess, scope, permission) { | ||
let access = await accessControl.getAccess(scope, permission) | ||
if (!access) { | ||
const { allow, wildcard } = await requestAccess(scope, permission) | ||
access = await accessControl.setAccess(scope, wildcard ? '*' : permission, allow) | ||
} | ||
return access | ||
} | ||
|
||
// Standardized error thrown when a command access is denied | ||
// TODO: return errors following conventions from https://github.com/ipfs/js-ipfs/pull/1746 | ||
function createProxyAclError (scope, permission) { | ||
const err = new Error(`User denied access to selected commands over IPFS proxy: ${permission}`) | ||
const permissions = Array.isArray(permission) ? permission : [permission] | ||
err.output = { | ||
payload: { | ||
// Error follows convention from https://github.com/ipfs/js-ipfs/pull/1746/files | ||
code: 'ERR_IPFS_PROXY_ACCESS_DENIED', | ||
permissions, | ||
scope, | ||
// TODO: remove below after deprecation period ends with Q1 | ||
get isIpfsProxyAclError () { | ||
console.warn("[ipfs-companion] reading .isIpfsProxyAclError from Ipfs Proxy errors is deprecated, use '.code' instead") | ||
return true | ||
}, | ||
get permission () { | ||
if (!this.permissions || !this.permissions.length) return undefined | ||
console.warn("[ipfs-companion] reading .permission from Ipfs Proxy errors is deprecated, use '.permissions' instead") | ||
return this.permissions[0] | ||
} | ||
} | ||
} | ||
return err | ||
} | ||
|
||
module.exports = { | ||
createPreAcl, | ||
createProxyAclError | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.