[feat] access control lists (with access tokens) (#2864)
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
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -482,6 +482,12 @@ body.body--dark .q-field--error .q-field__messages {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.lnbits__table-bordered td,
|
||||
.lnbits__table-bordered th {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.q-table--dense th:first-child,
|
||||
.q-table--dense td:first-child,
|
||||
.q-table--dense .q-table__bottom {
|
||||
|
||||
@@ -85,9 +85,11 @@ window.localisation.en = {
|
||||
cancel: 'Cancel',
|
||||
scan: 'Scan',
|
||||
read: 'Read',
|
||||
write: 'Write',
|
||||
pay: 'Pay',
|
||||
memo: 'Memo',
|
||||
date: 'Date',
|
||||
path: 'Path',
|
||||
processing_payment: 'Processing payment...',
|
||||
not_enough_funds: 'Not enough funds!',
|
||||
search_by_tag_memo_amount: 'Search by tag, memo, amount',
|
||||
@@ -256,6 +258,13 @@ window.localisation.en = {
|
||||
back: 'Back',
|
||||
logout: 'Logout',
|
||||
look_and_feel: 'Look and Feel',
|
||||
api_token: 'API Token',
|
||||
api_tokens: 'API Tokens',
|
||||
access_control_list: 'Access Control List',
|
||||
access_control_list_admin_warning:
|
||||
'This is an admin account. The generated tokens will have admin privileges.',
|
||||
new_api_acl: 'New Access Control List',
|
||||
api_token_id: 'Token Id',
|
||||
toggle_gradient: 'Toggle Gradient',
|
||||
gradient_background: 'Gradient Background',
|
||||
language: 'Language',
|
||||
|
||||
@@ -21,6 +21,60 @@ window.AccountPageLogic = {
|
||||
newPasswordRepeat: null,
|
||||
username: null,
|
||||
pubkey: null
|
||||
},
|
||||
apiAcl: {
|
||||
showNewAclDialog: false,
|
||||
showPasswordDialog: false,
|
||||
showNewTokenDialog: false,
|
||||
data: [],
|
||||
passwordGuardedFunction: null,
|
||||
newAclName: '',
|
||||
newTokenName: '',
|
||||
password: '',
|
||||
apiToken: null,
|
||||
selectedTokenId: null,
|
||||
columns: [
|
||||
{
|
||||
name: 'Name',
|
||||
align: 'left',
|
||||
label: this.$t('Name'),
|
||||
field: 'Name',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
align: 'left',
|
||||
label: this.$t('path'),
|
||||
field: 'path',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
name: 'read',
|
||||
align: 'left',
|
||||
label: this.$t('read'),
|
||||
field: 'read',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
name: 'write',
|
||||
align: 'left',
|
||||
label: this.$t('write'),
|
||||
field: 'write',
|
||||
sortable: false
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 100,
|
||||
page: 1
|
||||
}
|
||||
},
|
||||
selectedApiAcl: {
|
||||
id: null,
|
||||
name: null,
|
||||
endpoints: [],
|
||||
token_id_list: [],
|
||||
allRead: false,
|
||||
allWrite: false
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -151,6 +205,215 @@ window.AccountPageLogic = {
|
||||
newPassword: null,
|
||||
newPasswordRepeat: null
|
||||
}
|
||||
},
|
||||
newApiAclDialog() {
|
||||
this.apiAcl.newAclName = null
|
||||
this.apiAcl.showNewAclDialog = true
|
||||
},
|
||||
newTokenAclDialog() {
|
||||
this.apiAcl.newTokenName = null
|
||||
this.apiAcl.newTokenExpiry = null
|
||||
this.apiAcl.showNewTokenDialog = true
|
||||
},
|
||||
handleApiACLSelected(aclId) {
|
||||
this.selectedApiAcl = {
|
||||
id: null,
|
||||
name: null,
|
||||
endpoints: [],
|
||||
token_id_list: []
|
||||
}
|
||||
this.apiAcl.selectedTokenId = null
|
||||
if (!aclId) {
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
const selectedApiAcl = this.apiAcl.data.find(t => t.id === aclId)
|
||||
if (!this.selectedApiAcl) {
|
||||
return
|
||||
}
|
||||
this.selectedApiAcl = {...selectedApiAcl}
|
||||
this.selectedApiAcl.allRead = this.selectedApiAcl.endpoints.every(
|
||||
e => e.read
|
||||
)
|
||||
this.selectedApiAcl.allWrite = this.selectedApiAcl.endpoints.every(
|
||||
e => e.write
|
||||
)
|
||||
})
|
||||
},
|
||||
handleAllEndpointsReadAccess() {
|
||||
this.selectedApiAcl.endpoints.forEach(
|
||||
e => (e.read = this.selectedApiAcl.allRead)
|
||||
)
|
||||
},
|
||||
handleAllEndpointsWriteAccess() {
|
||||
this.selectedApiAcl.endpoints.forEach(
|
||||
e => (e.write = this.selectedApiAcl.allWrite)
|
||||
)
|
||||
},
|
||||
async getApiACLs() {
|
||||
try {
|
||||
const {data} = await LNbits.api.request('GET', '/api/v1/auth/acl', null)
|
||||
this.apiAcl.data = data.access_control_list
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
}
|
||||
},
|
||||
askPasswordAndRunFunction(func) {
|
||||
this.apiAcl.passwordGuardedFunction = func
|
||||
this.apiAcl.showPasswordDialog = true
|
||||
},
|
||||
runPasswordGuardedFunction() {
|
||||
this.apiAcl.showPasswordDialog = false
|
||||
const func = this.apiAcl.passwordGuardedFunction
|
||||
if (func) {
|
||||
this[func]()
|
||||
}
|
||||
},
|
||||
async addApiACL() {
|
||||
if (!this.apiAcl.newAclName) {
|
||||
this.$q.notify({
|
||||
type: 'warning',
|
||||
message: 'Name is required.'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'PUT',
|
||||
'/api/v1/auth/acl',
|
||||
null,
|
||||
{
|
||||
id: this.apiAcl.newAclName,
|
||||
name: this.apiAcl.newAclName,
|
||||
password: this.apiAcl.password
|
||||
}
|
||||
)
|
||||
this.apiAcl.data = data.access_control_list
|
||||
const acl = this.apiAcl.data.find(
|
||||
t => t.name === this.apiAcl.newAclName
|
||||
)
|
||||
|
||||
this.handleApiACLSelected(acl.id)
|
||||
this.apiAcl.showNewAclDialog = false
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Access Control List created.'
|
||||
})
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
} finally {
|
||||
this.apiAcl.name = ''
|
||||
this.apiAcl.password = ''
|
||||
}
|
||||
|
||||
this.apiAcl.showNewAclDialog = false
|
||||
},
|
||||
|
||||
async updateApiACLs() {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'PUT',
|
||||
'/api/v1/auth/acl',
|
||||
null,
|
||||
{
|
||||
id: this.user.id,
|
||||
password: this.apiAcl.password,
|
||||
...this.selectedApiAcl
|
||||
}
|
||||
)
|
||||
this.apiAcl.data = data.access_control_list
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
} finally {
|
||||
this.apiAcl.password = ''
|
||||
}
|
||||
},
|
||||
async deleteApiACL() {
|
||||
if (!this.selectedApiAcl.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await LNbits.api.request('DELETE', '/api/v1/auth/acl', null, {
|
||||
id: this.selectedApiAcl.id,
|
||||
password: this.apiAcl.password
|
||||
})
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Access Control List deleted.'
|
||||
})
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
} finally {
|
||||
this.apiAcl.password = ''
|
||||
}
|
||||
this.apiAcl.data = this.apiAcl.data.filter(
|
||||
t => t.id !== this.selectedApiAcl.id
|
||||
)
|
||||
this.handleApiACLSelected(this.apiAcl.data[0]?.id)
|
||||
},
|
||||
async generateApiToken() {
|
||||
if (!this.selectedApiAcl.id) {
|
||||
return
|
||||
}
|
||||
const expirationTimeMilliseconds =
|
||||
new Date(this.apiAcl.newTokenExpiry) - new Date()
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'POST',
|
||||
'/api/v1/auth/acl/token',
|
||||
null,
|
||||
{
|
||||
acl_id: this.selectedApiAcl.id,
|
||||
token_name: this.apiAcl.newTokenName,
|
||||
password: this.apiAcl.password,
|
||||
expiration_time_minutes: Math.trunc(
|
||||
expirationTimeMilliseconds / 60000
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
this.apiAcl.apiToken = data.api_token
|
||||
this.apiAcl.selectedTokenId = data.id
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: 'Token Generated.'
|
||||
})
|
||||
|
||||
await this.getApiACLs()
|
||||
this.handleApiACLSelected(this.selectedApiAcl.id)
|
||||
this.apiAcl.showNewTokenDialog = false
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
} finally {
|
||||
this.apiAcl.password = ''
|
||||
}
|
||||
},
|
||||
async deleteToken() {
|
||||
if (!this.apiAcl.selectedTokenId) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await LNbits.api.request('DELETE', '/api/v1/auth/acl/token', null, {
|
||||
id: this.apiAcl.selectedTokenId,
|
||||
acl_id: this.selectedApiAcl.id,
|
||||
password: this.apiAcl.password
|
||||
})
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Token deleted.'
|
||||
})
|
||||
|
||||
this.selectedApiAcl.token_id_list =
|
||||
this.selectedApiAcl.token_id_list.filter(
|
||||
t => t.id !== this.apiAcl.selectedTokenId
|
||||
)
|
||||
this.apiAcl.selectedTokenId = null
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
} finally {
|
||||
this.apiAcl.password = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
@@ -166,5 +429,6 @@ window.AccountPageLogic = {
|
||||
if (hash) {
|
||||
this.tab = hash
|
||||
}
|
||||
await this.getApiACLs()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,14 +308,9 @@ window.app.component('lnbits-notifications-btn', {
|
||||
.subscribe(options)
|
||||
.then(subscription => {
|
||||
LNbits.api
|
||||
.request(
|
||||
'POST',
|
||||
'/api/v1/webpush',
|
||||
this.g.user.wallets[0].adminkey,
|
||||
{
|
||||
subscription: JSON.stringify(subscription)
|
||||
}
|
||||
)
|
||||
.request('POST', '/api/v1/webpush', null, {
|
||||
subscription: JSON.stringify(subscription)
|
||||
})
|
||||
.then(response => {
|
||||
this.saveUserSubscribed(response.data.user)
|
||||
this.isSubscribed = true
|
||||
@@ -337,7 +332,7 @@ window.app.component('lnbits-notifications-btn', {
|
||||
.request(
|
||||
'DELETE',
|
||||
'/api/v1/webpush?endpoint=' + btoa(subscription.endpoint),
|
||||
this.g.user.wallets[0].adminkey
|
||||
null
|
||||
)
|
||||
.then(() => {
|
||||
this.removeUserSubscribed(this.g.user.id)
|
||||
|
||||
@@ -152,6 +152,12 @@ body.body--dark .q-field--error {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.lnbits__table-bordered td,
|
||||
.lnbits__table-bordered th {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.q-table--dense {
|
||||
th:first-child,
|
||||
td:first-child,
|
||||
|
||||
Reference in New Issue
Block a user