refactor: extract permissions component
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -520,7 +520,6 @@ window.localisation.en = {
|
|||||||
extension_cost: 'This release requires a payment of minimum {cost} sats.',
|
extension_cost: 'This release requires a payment of minimum {cost} sats.',
|
||||||
extension_paid_sats: 'You have already paid {paid_sats} sats.',
|
extension_paid_sats: 'You have already paid {paid_sats} sats.',
|
||||||
extension_permissions_title: 'Grant extension permissions',
|
extension_permissions_title: 'Grant extension permissions',
|
||||||
extension_permissions_request: 'This extension requests these permissions:',
|
|
||||||
extension_permissions_grant_install: 'Grant and install',
|
extension_permissions_grant_install: 'Grant and install',
|
||||||
extension_permissions_high_risk_warning:
|
extension_permissions_high_risk_warning:
|
||||||
'This extension requests permissions that can move funds.',
|
'This extension requests permissions that can move funds.',
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
;(function () {
|
||||||
|
function translate(translateFn, key) {
|
||||||
|
return translateFn ? translateFn(key) : key
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionI18nKey(permission) {
|
||||||
|
return `extension_permission_${permission.id.replace(/[^A-Za-z0-9]/g, '_')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionLabel(permission, translateFn) {
|
||||||
|
const key = permissionI18nKey(permission)
|
||||||
|
const label = translate(translateFn, key)
|
||||||
|
return label === key ? permission.id : label
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionManifestDescription(permission) {
|
||||||
|
return typeof permission.description === 'string'
|
||||||
|
? permission.description
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function lowRisk(translateFn) {
|
||||||
|
return {
|
||||||
|
level: 'low',
|
||||||
|
color: 'grey-6',
|
||||||
|
label: translate(translateFn, 'extension_permission_risk_low'),
|
||||||
|
warning: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediumRisk(translateFn) {
|
||||||
|
return {
|
||||||
|
level: 'medium',
|
||||||
|
color: 'warning',
|
||||||
|
label: translate(translateFn, 'extension_permission_risk_medium'),
|
||||||
|
warning: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function highRisk(translateFn, warningKey) {
|
||||||
|
return {
|
||||||
|
level: 'high',
|
||||||
|
color: 'negative',
|
||||||
|
label: translate(translateFn, 'extension_permission_risk_high'),
|
||||||
|
warning: translate(translateFn, warningKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extensionDisplayName(extensions, extensionId) {
|
||||||
|
const extension = (extensions || []).find(
|
||||||
|
extension => extension.id === extensionId
|
||||||
|
)
|
||||||
|
return extension?.name || extensionId
|
||||||
|
}
|
||||||
|
|
||||||
|
function extensionApiPermissionTargets(permission, extensions) {
|
||||||
|
const extensionPolicies = permission.policies
|
||||||
|
if (!Array.isArray(extensionPolicies)) return []
|
||||||
|
return extensionPolicies
|
||||||
|
.map(extension => {
|
||||||
|
const extensionId =
|
||||||
|
typeof extension === 'string' ? extension : extension?.id
|
||||||
|
if (!extensionId) return null
|
||||||
|
const access =
|
||||||
|
typeof extension === 'string'
|
||||||
|
? ['read']
|
||||||
|
: Array.isArray(extension.access) && extension.access.length
|
||||||
|
? extension.access
|
||||||
|
: ['read']
|
||||||
|
return {
|
||||||
|
id: extensionId,
|
||||||
|
name: extensionDisplayName(extensions, extensionId),
|
||||||
|
access
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionRiskForPermission(permission, extensions, translateFn) {
|
||||||
|
if (permission.id === 'wallet.pay_invoice') {
|
||||||
|
return highRisk(
|
||||||
|
translateFn,
|
||||||
|
'extension_permission_warning_wallet_pay_invoice'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (permission.id === 'extension.api.request') {
|
||||||
|
const hasWriteAccess = extensionApiPermissionTargets(
|
||||||
|
permission,
|
||||||
|
extensions
|
||||||
|
).some(target => target.access.includes('write'))
|
||||||
|
return hasWriteAccess
|
||||||
|
? highRisk(
|
||||||
|
translateFn,
|
||||||
|
'extension_permission_warning_extension_api_request_write'
|
||||||
|
)
|
||||||
|
: mediumRisk(translateFn)
|
||||||
|
}
|
||||||
|
if (permission.id === 'http.request') {
|
||||||
|
return mediumRisk(translateFn)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
'wallet.list',
|
||||||
|
'wallet.balance.read',
|
||||||
|
'wallet.create_invoice_public',
|
||||||
|
'ext.storage.read_public',
|
||||||
|
'payments.watch'
|
||||||
|
].includes(permission.id)
|
||||||
|
) {
|
||||||
|
return mediumRisk(translateFn)
|
||||||
|
}
|
||||||
|
return lowRisk(translateFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionRisk(permissions, extensions, translateFn) {
|
||||||
|
const risks = permissions.map(permission =>
|
||||||
|
permissionRiskForPermission(permission, extensions, translateFn)
|
||||||
|
)
|
||||||
|
const highestRisk = risks.find(risk => risk.level === 'high')
|
||||||
|
if (highestRisk) return highestRisk
|
||||||
|
return risks.find(risk => risk.level === 'medium') || lowRisk(translateFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionOrderIndex(permissionId) {
|
||||||
|
const order = [
|
||||||
|
'wallet.pay_invoice',
|
||||||
|
'wallet.list',
|
||||||
|
'wallet.balance.read',
|
||||||
|
'extension.api.request',
|
||||||
|
'http.request',
|
||||||
|
'ui.camera.scan_qr',
|
||||||
|
'ext.storage.read',
|
||||||
|
'ext.storage.write',
|
||||||
|
'ext.storage.read_public',
|
||||||
|
'wallet.create_invoice_public',
|
||||||
|
'wallet.create_invoice',
|
||||||
|
'utils.basic'
|
||||||
|
]
|
||||||
|
const index = order.indexOf(permissionId)
|
||||||
|
return index === -1 ? order.length : index
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicStorageFieldGroups(permission) {
|
||||||
|
const tables = permission.policies
|
||||||
|
if (!Array.isArray(tables)) return []
|
||||||
|
return tables
|
||||||
|
.map(table => {
|
||||||
|
const tableName =
|
||||||
|
typeof table === 'string' ? table : table?.table_name || ''
|
||||||
|
const fields =
|
||||||
|
typeof table === 'string' || !Array.isArray(table?.public_fields)
|
||||||
|
? []
|
||||||
|
: table.public_fields.filter(
|
||||||
|
field => typeof field === 'string' && field
|
||||||
|
)
|
||||||
|
return tableName ? {table: tableName, fields} : null
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
function httpRequestPermissionHosts(permission) {
|
||||||
|
const hosts = permission.policies
|
||||||
|
if (!Array.isArray(hosts)) return []
|
||||||
|
return hosts
|
||||||
|
.map(host => (typeof host === 'string' ? host : host?.host || ''))
|
||||||
|
.filter(host => typeof host === 'string' && host)
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicInvoicePolicies(permission) {
|
||||||
|
const policies = permission.policies
|
||||||
|
if (!Array.isArray(policies)) return []
|
||||||
|
return policies
|
||||||
|
.map(policy => {
|
||||||
|
if (!policy || typeof policy !== 'object') return null
|
||||||
|
const table = policy.table
|
||||||
|
const walletField = policy.wallet_field
|
||||||
|
if (typeof table !== 'string' || !table) return null
|
||||||
|
if (typeof walletField !== 'string' || !walletField) return null
|
||||||
|
return {table, walletField}
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionDisplayItem(permissions, extensions, translateFn) {
|
||||||
|
const permission = permissions[0]
|
||||||
|
const isReadWriteStorage =
|
||||||
|
permissions.length === 2 &&
|
||||||
|
permissions.some(permission => permission.id === 'ext.storage.read') &&
|
||||||
|
permissions.some(permission => permission.id === 'ext.storage.write')
|
||||||
|
const descriptions = permissions
|
||||||
|
.map(permission => permissionManifestDescription(permission))
|
||||||
|
.filter(Boolean)
|
||||||
|
const item = {
|
||||||
|
id: isReadWriteStorage ? 'ext.storage.read_write' : permission.id,
|
||||||
|
label: isReadWriteStorage
|
||||||
|
? translate(translateFn, 'extension_permission_ext_storage_read_write')
|
||||||
|
: permissionLabel(permission, translateFn),
|
||||||
|
risk: permissionRisk(permissions, extensions, translateFn),
|
||||||
|
badges: [],
|
||||||
|
descriptions,
|
||||||
|
fieldGroups: [],
|
||||||
|
invoicePolicies: [],
|
||||||
|
extensionAccess: [],
|
||||||
|
httpHosts: []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.id === 'ext.storage.read_public') {
|
||||||
|
item.fieldGroups = publicStorageFieldGroups(permission)
|
||||||
|
item.badges = item.fieldGroups.map(group => ({
|
||||||
|
key: group.table,
|
||||||
|
label: group.table
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.id === 'extension.api.request') {
|
||||||
|
item.extensionAccess = extensionApiPermissionTargets(
|
||||||
|
permission,
|
||||||
|
extensions
|
||||||
|
)
|
||||||
|
item.badges = item.extensionAccess.map(target => ({
|
||||||
|
key: target.id,
|
||||||
|
label: target.name
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.id === 'http.request') {
|
||||||
|
item.httpHosts = httpRequestPermissionHosts(permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.id === 'wallet.create_invoice_public') {
|
||||||
|
item.invoicePolicies = publicInvoicePolicies(permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayItems({permissions, extensions, translate}) {
|
||||||
|
const permissionList = permissions || []
|
||||||
|
const permissionsById = new Map(
|
||||||
|
permissionList.map(permission => [permission.id, permission])
|
||||||
|
)
|
||||||
|
const hasReadWriteStorage =
|
||||||
|
permissionsById.has('ext.storage.read') &&
|
||||||
|
permissionsById.has('ext.storage.write')
|
||||||
|
let addedReadWriteStorage = false
|
||||||
|
|
||||||
|
return permissionList
|
||||||
|
.map((permission, index) => {
|
||||||
|
if (
|
||||||
|
hasReadWriteStorage &&
|
||||||
|
['ext.storage.read', 'ext.storage.write'].includes(permission.id)
|
||||||
|
) {
|
||||||
|
if (addedReadWriteStorage) return null
|
||||||
|
addedReadWriteStorage = true
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
orderId: 'ext.storage.read',
|
||||||
|
permissions: [
|
||||||
|
permissionsById.get('ext.storage.read'),
|
||||||
|
permissionsById.get('ext.storage.write')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
orderId: permission.id,
|
||||||
|
permissions: [permission]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort((left, right) => {
|
||||||
|
const leftOrder = permissionOrderIndex(left.orderId)
|
||||||
|
const rightOrder = permissionOrderIndex(right.orderId)
|
||||||
|
return leftOrder === rightOrder
|
||||||
|
? left.index - right.index
|
||||||
|
: leftOrder - rightOrder
|
||||||
|
})
|
||||||
|
.map(group =>
|
||||||
|
permissionDisplayItem(group.permissions, extensions || [], translate)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.LNbitsExtensionPermissions = {
|
||||||
|
displayItems,
|
||||||
|
hasHighRisk({permissions, extensions, translate}) {
|
||||||
|
return displayItems({permissions, extensions, translate}).some(
|
||||||
|
permission => permission.risk.level === 'high'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.app.component('lnbits-extension-permissions', {
|
||||||
|
template: '#lnbits-extension-permissions',
|
||||||
|
props: {
|
||||||
|
permissions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
extensions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayItems() {
|
||||||
|
return window.LNbitsExtensionPermissions.displayItems({
|
||||||
|
permissions: this.permissions,
|
||||||
|
extensions: this.extensions,
|
||||||
|
translate: key => this.$t(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
publicInvoicePolicySentence(policy) {
|
||||||
|
return `Invoices will be created using ${policy.walletField} from ${policy.table}.`
|
||||||
|
},
|
||||||
|
permissionAccessLabel(access) {
|
||||||
|
const key = `extension_permission_access_${access}`
|
||||||
|
const label = this.$t(key)
|
||||||
|
return label === key ? access : label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
@@ -725,268 +725,11 @@ window.PageExtensions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
permissionGrantHasHighRisk() {
|
permissionGrantHasHighRisk() {
|
||||||
return this.permissionGrantDisplayItems().some(
|
return window.LNbitsExtensionPermissions.hasHighRisk({
|
||||||
permission => permission.risk.level === 'high'
|
permissions: this.permissionGrant.permissions,
|
||||||
)
|
extensions: this.extensions,
|
||||||
},
|
translate: key => this.$t(key)
|
||||||
permissionGrantDisplayItems() {
|
})
|
||||||
const permissions = this.permissionGrant.permissions || []
|
|
||||||
const permissionsById = new Map(
|
|
||||||
permissions.map(permission => [permission.id, permission])
|
|
||||||
)
|
|
||||||
const hasReadWriteStorage =
|
|
||||||
permissionsById.has('ext.storage.read') &&
|
|
||||||
permissionsById.has('ext.storage.write')
|
|
||||||
let addedReadWriteStorage = false
|
|
||||||
|
|
||||||
return permissions
|
|
||||||
.map((permission, index) => {
|
|
||||||
if (
|
|
||||||
hasReadWriteStorage &&
|
|
||||||
['ext.storage.read', 'ext.storage.write'].includes(permission.id)
|
|
||||||
) {
|
|
||||||
if (addedReadWriteStorage) return null
|
|
||||||
addedReadWriteStorage = true
|
|
||||||
return {
|
|
||||||
index,
|
|
||||||
orderId: 'ext.storage.read',
|
|
||||||
permissions: [
|
|
||||||
permissionsById.get('ext.storage.read'),
|
|
||||||
permissionsById.get('ext.storage.write')
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
index,
|
|
||||||
orderId: permission.id,
|
|
||||||
permissions: [permission]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
.sort((left, right) => {
|
|
||||||
const leftOrder = this.permissionOrderIndex(left.orderId)
|
|
||||||
const rightOrder = this.permissionOrderIndex(right.orderId)
|
|
||||||
return leftOrder === rightOrder
|
|
||||||
? left.index - right.index
|
|
||||||
: leftOrder - rightOrder
|
|
||||||
})
|
|
||||||
.map(group => this.permissionDisplayItem(group.permissions))
|
|
||||||
},
|
|
||||||
permissionDisplayItem(permissions) {
|
|
||||||
const permission = permissions[0]
|
|
||||||
const isReadWriteStorage =
|
|
||||||
permissions.length === 2 &&
|
|
||||||
permissions.some(permission => permission.id === 'ext.storage.read') &&
|
|
||||||
permissions.some(permission => permission.id === 'ext.storage.write')
|
|
||||||
const descriptions = permissions
|
|
||||||
.map(permission => this.permissionManifestDescription(permission))
|
|
||||||
.filter(Boolean)
|
|
||||||
const item = {
|
|
||||||
id: isReadWriteStorage ? 'ext.storage.read_write' : permission.id,
|
|
||||||
label: isReadWriteStorage
|
|
||||||
? this.$t('extension_permission_ext_storage_read_write')
|
|
||||||
: this.permissionLabel(permission),
|
|
||||||
risk: this.permissionRisk(permissions),
|
|
||||||
badges: [],
|
|
||||||
descriptions,
|
|
||||||
fieldGroups: [],
|
|
||||||
invoicePolicies: [],
|
|
||||||
extensionAccess: [],
|
|
||||||
httpHosts: []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission.id === 'ext.storage.read_public') {
|
|
||||||
item.fieldGroups = this.publicStorageFieldGroups(permission)
|
|
||||||
item.badges = item.fieldGroups.map(group => ({
|
|
||||||
key: group.table,
|
|
||||||
label: group.table
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission.id === 'extension.api.request') {
|
|
||||||
item.extensionAccess = this.extensionApiPermissionTargets(permission)
|
|
||||||
item.badges = item.extensionAccess.map(target => ({
|
|
||||||
key: target.id,
|
|
||||||
label: target.name
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission.id === 'http.request') {
|
|
||||||
item.httpHosts = this.httpRequestPermissionHosts(permission)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission.id === 'wallet.create_invoice_public') {
|
|
||||||
item.invoicePolicies = this.publicInvoicePolicies(permission)
|
|
||||||
}
|
|
||||||
|
|
||||||
return item
|
|
||||||
},
|
|
||||||
permissionRisk(permissions) {
|
|
||||||
const risks = permissions.map(permission =>
|
|
||||||
this.permissionRiskForPermission(permission)
|
|
||||||
)
|
|
||||||
const highestRisk = risks.find(risk => risk.level === 'high')
|
|
||||||
if (highestRisk) return highestRisk
|
|
||||||
return risks.find(risk => risk.level === 'medium') || this.lowRisk()
|
|
||||||
},
|
|
||||||
permissionRiskForPermission(permission) {
|
|
||||||
if (permission.id === 'wallet.pay_invoice') {
|
|
||||||
return this.highRisk('extension_permission_warning_wallet_pay_invoice')
|
|
||||||
}
|
|
||||||
if (permission.id === 'extension.api.request') {
|
|
||||||
const hasWriteAccess = this.extensionApiPermissionTargets(
|
|
||||||
permission
|
|
||||||
).some(target => target.access.includes('write'))
|
|
||||||
return hasWriteAccess
|
|
||||||
? this.highRisk(
|
|
||||||
'extension_permission_warning_extension_api_request_write'
|
|
||||||
)
|
|
||||||
: this.mediumRisk()
|
|
||||||
}
|
|
||||||
if (permission.id === 'http.request') {
|
|
||||||
return this.mediumRisk()
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
'wallet.list',
|
|
||||||
'wallet.balance.read',
|
|
||||||
'wallet.create_invoice_public',
|
|
||||||
'ext.storage.read_public',
|
|
||||||
'payments.watch'
|
|
||||||
].includes(permission.id)
|
|
||||||
) {
|
|
||||||
return this.mediumRisk()
|
|
||||||
}
|
|
||||||
return this.lowRisk()
|
|
||||||
},
|
|
||||||
lowRisk() {
|
|
||||||
return {
|
|
||||||
level: 'low',
|
|
||||||
color: 'grey-6',
|
|
||||||
label: this.$t('extension_permission_risk_low'),
|
|
||||||
warning: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mediumRisk() {
|
|
||||||
return {
|
|
||||||
level: 'medium',
|
|
||||||
color: 'warning',
|
|
||||||
label: this.$t('extension_permission_risk_medium'),
|
|
||||||
warning: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
highRisk(warningKey) {
|
|
||||||
return {
|
|
||||||
level: 'high',
|
|
||||||
color: 'negative',
|
|
||||||
label: this.$t('extension_permission_risk_high'),
|
|
||||||
warning: this.$t(warningKey)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
permissionOrderIndex(permissionId) {
|
|
||||||
const order = [
|
|
||||||
'wallet.pay_invoice',
|
|
||||||
'wallet.list',
|
|
||||||
'wallet.balance.read',
|
|
||||||
'extension.api.request',
|
|
||||||
'http.request',
|
|
||||||
'ui.camera.scan_qr',
|
|
||||||
'ext.storage.read',
|
|
||||||
'ext.storage.write',
|
|
||||||
'ext.storage.read_public',
|
|
||||||
'wallet.create_invoice_public',
|
|
||||||
'wallet.create_invoice',
|
|
||||||
'utils.basic'
|
|
||||||
]
|
|
||||||
const index = order.indexOf(permissionId)
|
|
||||||
return index === -1 ? order.length : index
|
|
||||||
},
|
|
||||||
publicStorageFieldGroups(permission) {
|
|
||||||
const tables = permission.policies
|
|
||||||
if (!Array.isArray(tables)) return []
|
|
||||||
return tables
|
|
||||||
.map(table => {
|
|
||||||
const tableName =
|
|
||||||
typeof table === 'string' ? table : table?.table_name || ''
|
|
||||||
const fields =
|
|
||||||
typeof table === 'string' || !Array.isArray(table?.public_fields)
|
|
||||||
? []
|
|
||||||
: table.public_fields.filter(
|
|
||||||
field => typeof field === 'string' && field
|
|
||||||
)
|
|
||||||
return tableName ? {table: tableName, fields} : null
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
},
|
|
||||||
httpRequestPermissionHosts(permission) {
|
|
||||||
const hosts = permission.policies
|
|
||||||
if (!Array.isArray(hosts)) return []
|
|
||||||
return hosts
|
|
||||||
.map(host => (typeof host === 'string' ? host : host?.host || ''))
|
|
||||||
.filter(host => typeof host === 'string' && host)
|
|
||||||
},
|
|
||||||
publicInvoicePolicies(permission) {
|
|
||||||
const policies = permission.policies
|
|
||||||
if (!Array.isArray(policies)) return []
|
|
||||||
return policies
|
|
||||||
.map(policy => {
|
|
||||||
if (!policy || typeof policy !== 'object') return null
|
|
||||||
const table = policy.table
|
|
||||||
const walletField = policy.wallet_field
|
|
||||||
if (typeof table !== 'string' || !table) return null
|
|
||||||
if (typeof walletField !== 'string' || !walletField) return null
|
|
||||||
return {table, walletField}
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
},
|
|
||||||
publicInvoicePolicySentence(policy) {
|
|
||||||
return `Invoices will be created using ${policy.walletField} from ${policy.table}.`
|
|
||||||
},
|
|
||||||
extensionApiPermissionTargets(permission) {
|
|
||||||
const extensions = permission.policies
|
|
||||||
if (!Array.isArray(extensions)) return []
|
|
||||||
return extensions
|
|
||||||
.map(extension => {
|
|
||||||
const extensionId =
|
|
||||||
typeof extension === 'string' ? extension : extension?.id
|
|
||||||
if (!extensionId) return null
|
|
||||||
const access =
|
|
||||||
typeof extension === 'string'
|
|
||||||
? ['read']
|
|
||||||
: Array.isArray(extension.access) && extension.access.length
|
|
||||||
? extension.access
|
|
||||||
: ['read']
|
|
||||||
return {
|
|
||||||
id: extensionId,
|
|
||||||
name: this.extensionDisplayName(extensionId),
|
|
||||||
access
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
},
|
|
||||||
extensionDisplayName(extensionId) {
|
|
||||||
const extension = (this.extensions || []).find(
|
|
||||||
extension => extension.id === extensionId
|
|
||||||
)
|
|
||||||
return extension?.name || extensionId
|
|
||||||
},
|
|
||||||
permissionAccessLabel(access) {
|
|
||||||
const key = `extension_permission_access_${access}`
|
|
||||||
const label = this.$t(key)
|
|
||||||
return label === key ? access : label
|
|
||||||
},
|
|
||||||
permissionManifestDescription(permission) {
|
|
||||||
return typeof permission.description === 'string'
|
|
||||||
? permission.description
|
|
||||||
: ''
|
|
||||||
},
|
|
||||||
permissionI18nKey(permission) {
|
|
||||||
return `extension_permission_${permission.id.replace(/[^A-Za-z0-9]/g, '_')}`
|
|
||||||
},
|
|
||||||
permissionLabel(permission) {
|
|
||||||
const key = this.permissionI18nKey(permission)
|
|
||||||
const label = this.$t(key)
|
|
||||||
return label === key ? permission.id : label
|
|
||||||
},
|
},
|
||||||
async selectAllUpdatableExtensionss() {
|
async selectAllUpdatableExtensionss() {
|
||||||
this.updatableExtensions.forEach(e => (e.selectedForUpdate = true))
|
this.updatableExtensions.forEach(e => (e.selectedForUpdate = true))
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
"js/components/lnbits-theme.js",
|
"js/components/lnbits-theme.js",
|
||||||
"js/components/lnbits-qrcode-scanner.js",
|
"js/components/lnbits-qrcode-scanner.js",
|
||||||
"js/components/lnbits-manage-extension-list.js",
|
"js/components/lnbits-manage-extension-list.js",
|
||||||
|
"js/components/lnbits-extension-permissions.js",
|
||||||
"js/components/lnbits-manage-wallet-list.js",
|
"js/components/lnbits-manage-wallet-list.js",
|
||||||
"js/components/lnbits-language-dropdown.js",
|
"js/components/lnbits-language-dropdown.js",
|
||||||
"js/components/lnbits-payment-list.js",
|
"js/components/lnbits-payment-list.js",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ include('components/lnbits-header-wallets.vue') %} {%
|
|||||||
include('components/lnbits-drawer.vue') %} {%
|
include('components/lnbits-drawer.vue') %} {%
|
||||||
include('components/lnbits-home-logos.vue') %} {%
|
include('components/lnbits-home-logos.vue') %} {%
|
||||||
include('components/lnbits-manage-extension-list.vue') %} {%
|
include('components/lnbits-manage-extension-list.vue') %} {%
|
||||||
|
include('components/lnbits-extension-permissions.vue') %} {%
|
||||||
include('components/lnbits-manage-wallet-list.vue') %} {%
|
include('components/lnbits-manage-wallet-list.vue') %} {%
|
||||||
include('components/lnbits-language-dropdown.vue') %} {%
|
include('components/lnbits-language-dropdown.vue') %} {%
|
||||||
include('components/lnbits-payment-list.vue') %} {%
|
include('components/lnbits-payment-list.vue') %} {%
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<template id="lnbits-extension-permissions">
|
||||||
|
<q-list bordered separator>
|
||||||
|
<q-expansion-item
|
||||||
|
v-for="permission of displayItems"
|
||||||
|
:key="permission.id"
|
||||||
|
dense
|
||||||
|
expand-separator
|
||||||
|
class="q-pt-xs"
|
||||||
|
>
|
||||||
|
<template v-slot:header>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label class="text-weight-medium">
|
||||||
|
<span v-text="permission.label"></span>
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section
|
||||||
|
v-if="permission.risk.level !== 'low' || permission.badges.length"
|
||||||
|
side
|
||||||
|
top
|
||||||
|
>
|
||||||
|
<div class="row items-center justify-end q-gutter-xs">
|
||||||
|
<q-badge
|
||||||
|
v-for="badge of permission.badges"
|
||||||
|
:key="badge.key"
|
||||||
|
outline
|
||||||
|
color="primary"
|
||||||
|
v-text="badge.label"
|
||||||
|
></q-badge>
|
||||||
|
<q-badge
|
||||||
|
v-if="permission.risk.level !== 'low'"
|
||||||
|
:color="permission.risk.color"
|
||||||
|
v-text="permission.risk.label"
|
||||||
|
></q-badge>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="q-px-md q-pb-sm">
|
||||||
|
<div
|
||||||
|
v-if="permission.risk.warning"
|
||||||
|
class="row items-center text-negative text-caption q-mb-xs"
|
||||||
|
>
|
||||||
|
<q-icon name="warning" size="16px" class="q-mr-xs"></q-icon>
|
||||||
|
<span v-text="permission.risk.warning"></span>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-for="description of permission.descriptions"
|
||||||
|
:key="description"
|
||||||
|
class="text-caption q-mb-xs"
|
||||||
|
v-text="description"
|
||||||
|
></p>
|
||||||
|
<p
|
||||||
|
v-for="policy of permission.invoicePolicies"
|
||||||
|
:key="policy.table + ':' + policy.walletField"
|
||||||
|
class="text-caption q-mb-xs"
|
||||||
|
v-text="publicInvoicePolicySentence(policy)"
|
||||||
|
></p>
|
||||||
|
<ul v-if="permission.fieldGroups.length" class="q-my-sm q-pl-md">
|
||||||
|
<li v-for="group of permission.fieldGroups" :key="group.table">
|
||||||
|
<span v-text="group.table"></span>
|
||||||
|
<ul v-if="group.fields.length" class="q-pl-md">
|
||||||
|
<li
|
||||||
|
v-for="field of group.fields"
|
||||||
|
:key="group.table + ':' + field"
|
||||||
|
v-text="field"
|
||||||
|
></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div v-if="permission.extensionAccess.length" class="q-mt-sm">
|
||||||
|
<div
|
||||||
|
class="text-caption text-grey"
|
||||||
|
v-text="$t('extension_permission_extension_api_request_extensions')"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-for="target of permission.extensionAccess"
|
||||||
|
:key="target.id"
|
||||||
|
class="row items-center q-gutter-xs q-mt-xs"
|
||||||
|
>
|
||||||
|
<span class="text-caption" v-text="target.name"></span>
|
||||||
|
<q-badge
|
||||||
|
v-for="access of target.access"
|
||||||
|
:key="target.id + access"
|
||||||
|
color="grey-7"
|
||||||
|
v-text="permissionAccessLabel(access)"
|
||||||
|
></q-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="permission.httpHosts.length" class="q-mt-sm">
|
||||||
|
<div
|
||||||
|
class="text-caption text-grey"
|
||||||
|
v-text="$t('extension_permission_http_request_hosts')"
|
||||||
|
></div>
|
||||||
|
<ul class="q-my-sm q-pl-md">
|
||||||
|
<li
|
||||||
|
v-for="host of permission.httpHosts"
|
||||||
|
:key="host"
|
||||||
|
v-text="host"
|
||||||
|
></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-expansion-item>
|
||||||
|
</q-list>
|
||||||
|
</template>
|
||||||
@@ -463,10 +463,6 @@
|
|||||||
<q-card v-if="permissionGrant.show" class="q-pa-md lnbits__dialog-card">
|
<q-card v-if="permissionGrant.show" class="q-pa-md lnbits__dialog-card">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="text-h6" v-text="$t('extension_permissions_title')"></div>
|
<div class="text-h6" v-text="$t('extension_permissions_title')"></div>
|
||||||
<div
|
|
||||||
class="text-body2 q-mt-sm"
|
|
||||||
v-text="$t('extension_permissions_request')"
|
|
||||||
></div>
|
|
||||||
<q-banner
|
<q-banner
|
||||||
v-if="permissionGrantHasHighRisk()"
|
v-if="permissionGrantHasHighRisk()"
|
||||||
dense
|
dense
|
||||||
@@ -479,111 +475,11 @@
|
|||||||
</q-banner>
|
</q-banner>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-list bordered separator class="q-mt-md">
|
<lnbits-extension-permissions
|
||||||
<q-expansion-item
|
class="q-mt-md"
|
||||||
v-for="permission of permissionGrantDisplayItems()"
|
:permissions="permissionGrant.permissions"
|
||||||
:key="permission.id"
|
:extensions="extensions"
|
||||||
dense
|
></lnbits-extension-permissions>
|
||||||
expand-separator
|
|
||||||
class="q-pt-xs"
|
|
||||||
>
|
|
||||||
<template v-slot:header>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-weight-medium">
|
|
||||||
<span v-text="permission.label"></span>
|
|
||||||
</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section
|
|
||||||
v-if="permission.risk.level !== 'low' || permission.badges.length"
|
|
||||||
side
|
|
||||||
top
|
|
||||||
>
|
|
||||||
<div class="row items-center justify-end q-gutter-xs">
|
|
||||||
<q-badge
|
|
||||||
v-for="badge of permission.badges"
|
|
||||||
:key="badge.key"
|
|
||||||
outline
|
|
||||||
color="primary"
|
|
||||||
v-text="badge.label"
|
|
||||||
></q-badge>
|
|
||||||
<q-badge
|
|
||||||
v-if="permission.risk.level !== 'low'"
|
|
||||||
:color="permission.risk.color"
|
|
||||||
v-text="permission.risk.label"
|
|
||||||
></q-badge>
|
|
||||||
</div>
|
|
||||||
</q-item-section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="q-px-md q-pb-sm">
|
|
||||||
<div
|
|
||||||
v-if="permission.risk.warning"
|
|
||||||
class="row items-center text-negative text-caption q-mb-xs"
|
|
||||||
>
|
|
||||||
<q-icon name="warning" size="16px" class="q-mr-xs"></q-icon>
|
|
||||||
<span v-text="permission.risk.warning"></span>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
v-for="description of permission.descriptions"
|
|
||||||
:key="description"
|
|
||||||
class="text-caption q-mb-xs"
|
|
||||||
v-text="description"
|
|
||||||
></p>
|
|
||||||
<p
|
|
||||||
v-for="policy of permission.invoicePolicies"
|
|
||||||
:key="policy.table + ':' + policy.walletField"
|
|
||||||
class="text-caption q-mb-xs"
|
|
||||||
v-text="publicInvoicePolicySentence(policy)"
|
|
||||||
></p>
|
|
||||||
<ul v-if="permission.fieldGroups.length" class="q-my-sm q-pl-md">
|
|
||||||
<li v-for="group of permission.fieldGroups" :key="group.table">
|
|
||||||
<span v-text="group.table"></span>
|
|
||||||
<ul v-if="group.fields.length" class="q-pl-md">
|
|
||||||
<li
|
|
||||||
v-for="field of group.fields"
|
|
||||||
:key="group.table + ':' + field"
|
|
||||||
v-text="field"
|
|
||||||
></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div v-if="permission.extensionAccess.length" class="q-mt-sm">
|
|
||||||
<div
|
|
||||||
class="text-caption text-grey"
|
|
||||||
v-text="
|
|
||||||
$t('extension_permission_extension_api_request_extensions')
|
|
||||||
"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
v-for="target of permission.extensionAccess"
|
|
||||||
:key="target.id"
|
|
||||||
class="row items-center q-gutter-xs q-mt-xs"
|
|
||||||
>
|
|
||||||
<span class="text-caption" v-text="target.name"></span>
|
|
||||||
<q-badge
|
|
||||||
v-for="access of target.access"
|
|
||||||
:key="target.id + access"
|
|
||||||
color="grey-7"
|
|
||||||
v-text="permissionAccessLabel(access)"
|
|
||||||
></q-badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="permission.httpHosts.length" class="q-mt-sm">
|
|
||||||
<div
|
|
||||||
class="text-caption text-grey"
|
|
||||||
v-text="$t('extension_permission_http_request_hosts')"
|
|
||||||
></div>
|
|
||||||
<ul class="q-my-sm q-pl-md">
|
|
||||||
<li
|
|
||||||
v-for="host of permission.httpHosts"
|
|
||||||
:key="host"
|
|
||||||
v-text="host"
|
|
||||||
></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-expansion-item>
|
|
||||||
</q-list>
|
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|||||||
@@ -143,6 +143,7 @@
|
|||||||
"js/components/lnbits-theme.js",
|
"js/components/lnbits-theme.js",
|
||||||
"js/components/lnbits-qrcode-scanner.js",
|
"js/components/lnbits-qrcode-scanner.js",
|
||||||
"js/components/lnbits-manage-extension-list.js",
|
"js/components/lnbits-manage-extension-list.js",
|
||||||
|
"js/components/lnbits-extension-permissions.js",
|
||||||
"js/components/lnbits-manage-wallet-list.js",
|
"js/components/lnbits-manage-wallet-list.js",
|
||||||
"js/components/lnbits-language-dropdown.js",
|
"js/components/lnbits-language-dropdown.js",
|
||||||
"js/components/lnbits-payment-list.js",
|
"js/components/lnbits-payment-list.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user