-
Notifications
You must be signed in to change notification settings - Fork 325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add window.ipfs.enable(opts) (Bulk Permission Prompt) #619
Merged
Merged
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
4e6f65d
feat: scaffolding for window.ipfs.enable
lidel b0110a0
feat: window.ipfs.enable()
lidel fb27d28
fix(build): regenerate yarn.lock
lidel a8db570
chore: pre-api-whitelist → pre-command
lidel 19a9577
refactor(window.ipfs): backward-compatible errors
lidel b633eb4
feat(window.ipfs): opt-in ipfsx experiment
lidel 690ad80
feat: add deprecation warning to window.ipfs.<cmd>
lidel 756b177
fix(privacy): remove ACL whitelist for window.ipfs
lidel f3e9c6c
docs(window.ipfs): updates before beta with v2
lidel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is to enable opt-in experiments such as ipfsx which is used as an example: