-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathutils.ts
More file actions
196 lines (178 loc) · 5.58 KB
/
utils.ts
File metadata and controls
196 lines (178 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import { fetchRdfDocument, LdhopEngine, LdhopQuery, run } from '@ldhop/core'
import { v6, v7 } from 'css-authn'
import { NamedNode } from 'n3'
import { rdf, rdfs, solid, space } from 'rdf-namespaces'
import * as config from './config/index.js'
/**
* To find verified email of a person
*
* - Go to person's webId
* - Find public type index `(webId) - solid:publicTypeIndex -> (publicTypeIndex)``
* - Find instances of specific class defined in config (EMAIL_DISCOVERY_TYPE defaults to hospex:PersonalHospexDocument)
* - Find settings in the relevant instance (webId) - space:preferencesFile -> (settings)
* - In the settings, find (webId) - example:emailVerificationToken -> (JWT)
*/
const findEmailQuery: LdhopQuery<
| '?person'
| '?extendedDocument'
| '?publicTypeIndex'
| '?typeRegistration'
| '?typeRegistrationForClass'
| '?classDocument'
| '?settings'
> = [
// Go to person's webId and fetch extended proimage documents, too
{
type: 'match',
subject: '?person',
predicate: rdfs.seeAlso,
pick: 'object',
target: '?extendedDocument',
},
{ type: 'add resources', variable: '?extendedDocument' },
// Find public type index
{
type: 'match',
subject: '?person',
predicate: solid.publicTypeIndex,
pick: 'object',
target: '?publicTypeIndex',
},
// Find instances of specific class defined in config (EMAIL_DISCOVERY_TYPE)
{
type: 'match',
predicate: rdf.type,
object: solid.TypeRegistration,
graph: '?publicTypeIndex',
pick: 'subject',
target: '?typeRegistration',
},
{
type: 'match',
subject: '?typeRegistration',
predicate: solid.forClass,
object: config.emailDiscoveryType,
pick: 'subject',
target: '?typeRegistrationForClass',
},
{
type: 'match',
subject: '?typeRegistrationForClass',
predicate: solid.instance,
pick: 'object',
target: `?classDocument`,
},
{ type: 'add resources', variable: '?classDocument' },
// Find settings
{
type: 'match',
subject: '?person',
predicate: space.preferencesFile,
pick: 'object',
target: '?settings',
},
{ type: 'add resources', variable: '?settings' },
]
/**
* Search through person's storage and find email verification token in settings
*/
export const findEmailVerificationTokens = async (webId: string) => {
// initialize knowledge graph and follow your nose through it
// according to the query
const qas = new LdhopEngine(findEmailQuery, { '?person': new Set([webId]) })
const botFetch = await getBotFetch()
await run(qas, botFetch)
// Find email verification tokens
const objects = qas.store.getObjects(
new NamedNode(webId),
new NamedNode(config.verificationTokenPredicate),
null,
)
return objects.map(o => o.value)
}
/**
* Given a webId of person, find settings that the bot identity can write to
*/
export const findWritableSettings = async (webId: string) => {
// initialize knowledge graph and follow your nose through it
// according to the query
const qas = new LdhopEngine(findEmailQuery, { '?person': new Set([webId]) })
const botFetch = await getBotFetch()
await run(qas, botFetch)
// get uris of settings
const settings = Array.from(qas.getVariable('?settings'))
.filter(t => t.termType === 'NamedNode')
.map(t => t.value)
// find out which settings the bot can edit
const authBotFetch = await getBotFetch()
const results = await Promise.allSettled(
settings.map(s => getAllowedAccess(s, authBotFetch)),
)
const writableSettings = settings.filter((setting, index) => {
const result = results[index]
return (
result.status === 'fulfilled' &&
(result.value.user.includes('write') ||
result.value.user.includes('append'))
)
})
return writableSettings
}
type Permission = 'read' | 'write' | 'append' | 'control'
type PermissionDict = {
user: Permission[]
public: Permission[]
}
/**
* Parse WAC-Allow header
* https://solid.github.io/web-access-control-spec/#wac-allow
* user="append read write",public="read"
*/
const parseWACAllowHeader = (header: string): PermissionDict => {
const result: PermissionDict = { user: [], public: [] }
const entries = header.split(/,\s*/)
for (const entry of entries) {
const [key, value] = entry.split(/\s*=\s*/) as ['user' | 'public', string]
result[key] = value.trim().replace(/"\s*/g, '').split(/\s+/) as Permission[]
}
return result
}
/**
* Given url, find what kind of accesses current user has
* and what kind of accesses public has
*
* This is done via WAC-Allow header
* https://solid.github.io/web-access-control-spec/#wac-allow
*/
const getAllowedAccess = async (
url: string,
authenticatedFetch: typeof globalThis.fetch,
) => {
const response = await authenticatedFetch(url, { method: 'head' })
const header = response.headers.get('wac-allow')
if (header === null)
throw new Error('WAC-Allow header not found for resource ' + url)
const permissions = parseWACAllowHeader(header)
return permissions
}
/**
* Get authenticated fetch for the notification bot identity defined in config
*/
export const getBotFetch = async () => {
const getAuthenticatedFetch =
config.mailerCredentials.cssVersion === 6
? v6.getAuthenticatedFetch
: v7.getAuthenticatedFetch
return await getAuthenticatedFetch(config.mailerCredentials)
}
/**
* Fetch RDF document with notification bot identity, and return quads
*/
export const fetchRdf = async (
uri: string,
authFetch?: typeof globalThis.fetch,
) => {
const authBotFetch = authFetch ?? (await getBotFetch())
const { data: quads } = await fetchRdfDocument(uri, authBotFetch)
return quads
}