Skip to content

Commit 05ff386

Browse files
authored
Merge pull request #59 from solid/feature/trusted-app
An attempt at a new pane - trustedApplications
2 parents 16f2c85 + dd9943a commit 05ff386

2 files changed

Lines changed: 263 additions & 0 deletions

File tree

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ register(require('./internalPane.js'))
130130
// The home pane is a 2016 experiment. Always there.
131131

132132
register(require('./profile/profilePane.js')) // edit your public profile
133+
register(require('./trustedApplications/trustedApplicationsPane.js')) // manage your trusted applications
133134
register(require('./home/homePane.js'))
134135

135136
// ENDS
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/* Profile Editing Pane
2+
**
3+
** Unlike most panes, this is available any place whatever the real subject,
4+
** and allows the user to edit their own profile.
5+
**
6+
** Usage: paneRegistry.register('profile/profilePane')
7+
** or standalone script adding onto existing mashlib.
8+
*/
9+
10+
const nodeMode = (typeof module !== 'undefined')
11+
var panes, UI
12+
13+
if (nodeMode) {
14+
UI = require('solid-ui')
15+
panes = require('pane-registry')
16+
} else { // Add to existing mashlib
17+
panes = window.panes
18+
UI = panes.UI
19+
}
20+
21+
const kb = UI.store
22+
const ns = UI.ns
23+
24+
const thisColor = '#418d99'
25+
26+
const thisPane = {
27+
icon: UI.icons.iconBase + 'noun_15177.svg', // Looks like an A - could say it's for Applications?
28+
29+
name: 'trustedApplications',
30+
31+
label: function (subject) {
32+
var types = kb.findTypeURIs(subject)
33+
if (types[UI.ns.foaf('Person').uri] || types[UI.ns.vcard('Individual').uri]) {
34+
return 'Manage your trusted applications'
35+
}
36+
return null
37+
},
38+
39+
render: function (subject, dom) {
40+
var div = dom.createElement('div')
41+
div.classList.add('trusted-applications-pane')
42+
div.setAttribute('style', 'border: 0.3em solid ' + thisColor + '; border-radius: 0.5em; padding: 0.7em; margin-top:0.7em;')
43+
var table = div.appendChild(dom.createElement('table'))
44+
var main = table.appendChild(dom.createElement('tr'))
45+
var bottom = table.appendChild(dom.createElement('tr'))
46+
var statusArea = bottom.appendChild(dom.createElement('div'))
47+
statusArea.setAttribute('style', 'padding: 0.7em;')
48+
49+
var context = { dom: dom, div: main, statusArea: statusArea, me: null }
50+
UI.authn.logInLoadProfile(context).then(context => {
51+
subject = context.me
52+
53+
var profile = subject.doc()
54+
var editable = UI.store.updater.editable(profile.uri, kb)
55+
56+
main.appendChild(createText('h3', 'Manage your trusted applications'))
57+
58+
if (!editable) {
59+
main.appendChild(UI.widgets.errorMessageBlock(dom, `Your profile ${subject.doc().uri} is not editable, so we cannot do much here.`))
60+
return
61+
}
62+
63+
main.appendChild(createText('p', 'Here you can manage the applications you trust.'))
64+
65+
const applicationsTable = createApplicationTable(subject)
66+
main.appendChild(applicationsTable)
67+
68+
main.appendChild(createText('h4', 'Notes'))
69+
main.appendChild(createContainer('ol', [
70+
main.appendChild(createText('li', 'Trusted applications will get access to all resources that you have access to.')),
71+
main.appendChild(createText('li', 'You can limit which modes they have by default.')),
72+
main.appendChild(createText('li', 'They will not gain more access than you have.'))
73+
]))
74+
main.appendChild(createText('p', 'Application URLs must be valid URL. Examples are http://localhost:3000, https://trusted.app, and https://sub.trusted.app.'))
75+
}, err => {
76+
statusArea.appendChild(UI.widgets.errorMessageBlock(dom, err))
77+
})
78+
return div
79+
} // render()
80+
} //
81+
82+
function createApplicationTable(subject) {
83+
var applicationsTable = createElement('table', {
84+
'class': 'results'
85+
})
86+
87+
// creating headers
88+
var header = createContainer('tr', [
89+
createText('th', 'Application URL'),
90+
createText('th', 'Access modes'),
91+
createText('th', 'Actions')
92+
])
93+
applicationsTable.appendChild(header)
94+
95+
// creating rows
96+
kb.each(subject, ns.acl('trustedApp'))
97+
.flatMap(app => kb.each(app, ns.acl('origin')).map(origin => ({appModes: kb.each(app, ns.acl('mode')), origin})))
98+
.sort(({origin: a}, {origin: b}) => a.value < b.value ? -1 : 1)
99+
.forEach(({appModes, origin}) => applicationsTable.appendChild(createApplicationEntry(subject, origin, appModes, updateTable)))
100+
101+
// adding a row for new applications
102+
applicationsTable.appendChild(createApplicationEntry(subject, null, [ns.acl('Read')], updateTable))
103+
104+
return applicationsTable
105+
106+
function updateTable() {
107+
applicationsTable.parentElement.replaceChild(createApplicationTable(subject), applicationsTable)
108+
}
109+
}
110+
111+
function createApplicationEntry(subject, origin, appModes, updateTable) {
112+
var trustedApplicationState = { origin, appModes, formElements: { modes: [] } }
113+
return createContainer('tr', [
114+
createContainer('td', [
115+
createElement('input', {
116+
'class': 'textinput',
117+
placeholder: 'Write new URL here',
118+
value: origin ? origin.value : ''
119+
}, {}, (element) => trustedApplicationState.formElements.origin = element)
120+
]),
121+
createContainer('td', createModesInput(trustedApplicationState)),
122+
createContainer('td', origin
123+
? [
124+
createText('button', 'Update', {
125+
'class': 'controlButton',
126+
style: 'background: LightGreen;'
127+
}, {
128+
click: () => addOrEditApplication()
129+
}),
130+
createText('button', 'Delete', {
131+
'class': 'controlButton',
132+
style: 'background: LightCoral;'
133+
}, {
134+
click: () => removeApplication()
135+
})
136+
]
137+
: [
138+
createText('button', 'Add', {
139+
'class': 'controlButton',
140+
style: 'background: LightGreen;'
141+
}, {
142+
click: () => addOrEditApplication()
143+
})
144+
])
145+
])
146+
147+
function addOrEditApplication() {
148+
let origin
149+
try {
150+
origin = $rdf.sym(trustedApplicationState.formElements.origin.value)
151+
} catch (err) {
152+
return alert('Please provide an application URL you want to trust')
153+
}
154+
155+
// remove existing statements on same origin - if it exists
156+
kb.statementsMatching(null, ns.acl('origin'), origin).forEach(st => {
157+
kb.removeStatements([...kb.statementsMatching(null, ns.acl('trustedApp'), st.subject)])
158+
kb.removeStatements([...kb.statementsMatching(st.subject)])
159+
})
160+
161+
// add new triples
162+
const application = new $rdf.BlankNode()
163+
kb.add(subject, ns.acl('trustedApp'), application, subject)
164+
kb.add(application, ns.acl('origin'), origin, subject)
165+
trustedApplicationState.formElements.modes
166+
.filter(checkbox => checkbox.checked)
167+
.map(checkbox => $rdf.sym(checkbox.value))
168+
.forEach(mode => {
169+
kb.add(application, ns.acl('mode'), mode)
170+
})
171+
172+
save()
173+
}
174+
175+
function removeApplication() {
176+
let origin
177+
try {
178+
origin = $rdf.sym(trustedApplicationState.formElements.origin.value)
179+
} catch (err) {
180+
return alert('Please provide an application URL you want to trust')
181+
}
182+
183+
// remove existing statements on same origin - if it exists
184+
kb.statementsMatching(null, ns.acl('origin'), origin).forEach(st => {
185+
kb.removeStatements([...kb.statementsMatching(null, ns.acl('trustedApp'), st.subject)])
186+
kb.removeStatements([...kb.statementsMatching(st.subject)])
187+
})
188+
189+
save()
190+
}
191+
192+
function save() {
193+
// remove response triples - this should not be necessary, but do not know how to turn it off
194+
kb.statementsMatching(null, ns.link('response')).forEach(st => {
195+
kb.removeStatements([...kb.statementsMatching(st.subject)])
196+
kb.removeStatements([...kb.statementsMatching(st.object)])
197+
})
198+
199+
// serialize data
200+
$rdf.serialize(null, kb, subject.uri, 'text/turtle', (err, data) => {
201+
if (err) {
202+
alert('Something went wrong when preparing data for the server. Try again.')
203+
return
204+
}
205+
// save data to POD
206+
kb.fetcher.webOperation('PUT', subject.uri, {
207+
data,
208+
saveMetadata: false,
209+
contentType: 'text/turtle'
210+
})
211+
.then(() => updateTable())
212+
})
213+
}
214+
}
215+
216+
function createElement(elementName, attributes = {}, eventListeners = {}, onCreated = null) {
217+
var element = document.createElement(elementName)
218+
if (onCreated) {
219+
onCreated(element)
220+
}
221+
Object.keys(attributes).forEach(attName => {
222+
element.setAttribute(attName, attributes[attName])
223+
})
224+
Object.keys(eventListeners).forEach(eventName => {
225+
element.addEventListener(eventName, eventListeners[eventName])
226+
})
227+
return element
228+
}
229+
230+
function createContainer(elementName, children, attributes = {}, eventListeners = {}, onCreated = null) {
231+
var element = createElement(elementName, attributes, eventListeners, onCreated)
232+
children.forEach(child => element.appendChild(child))
233+
return element
234+
}
235+
236+
function createText(elementName, textContent, attributes = {}, eventListeners = {}, onCreated = null) {
237+
var element = createElement(elementName, attributes, eventListeners, onCreated)
238+
element.textContent = textContent
239+
return element
240+
}
241+
242+
function createModesInput({ appModes, formElements }) {
243+
return ['Read', 'Write', 'Append', 'Control'].map(mode => {
244+
var isChecked = appModes.some(appMode => appMode.value === ns.acl(mode).value)
245+
return createContainer('label', [
246+
createElement('input', {
247+
type: 'checkbox',
248+
...(isChecked ? { checked: '' } : {}),
249+
value: ns.acl(mode).uri
250+
}, {}, (element) => formElements.modes.push(element)),
251+
createText('span', mode)
252+
])
253+
})
254+
}
255+
256+
if (nodeMode) {
257+
module.exports = thisPane
258+
} else {
259+
console.log('*** patching in live pane: ' + thisPane.name)
260+
panes.register(thisPane)
261+
}
262+
// ENDS

0 commit comments

Comments
 (0)