wiki-server 0.25.9 → 0.26.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS.txt +1 -0
- package/index.js +3 -7
- package/lib/defaultargs.js +83 -0
- package/lib/forward.js +10 -10
- package/lib/page.js +448 -0
- package/lib/plugins.js +54 -0
- package/lib/random_id.js +15 -0
- package/lib/search.js +390 -0
- package/lib/security.js +94 -0
- package/lib/server.js +942 -0
- package/lib/sitemap.js +288 -0
- package/package.json +32 -23
- package/.github/workflows/node.js.yml +0 -31
- package/.mailmap +0 -1
- package/Gruntfile.js +0 -55
- package/lib/defaultargs.coffee +0 -77
- package/lib/page.coffee +0 -371
- package/lib/plugins.coffee +0 -48
- package/lib/random_id.coffee +0 -18
- package/lib/search.coffee +0 -304
- package/lib/security.coffee +0 -86
- package/lib/server.coffee +0 -782
- package/lib/sitemap.coffee +0 -246
- package/test/asdf-test-page +0 -8
- package/test/defaultargs.coffee +0 -10
- package/test/mocha.opts +0 -2
- package/test/page.coffee +0 -61
- package/test/random.coffee +0 -8
- package/test/server.coffee +0 -228
- package/test/sitemap.coffee +0 -110
package/lib/search.js
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Federated Wiki : Node Server
|
|
3
|
+
*
|
|
4
|
+
* Copyright Ward Cunningham and other contributors
|
|
5
|
+
* Licensed under the MIT license.
|
|
6
|
+
* https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// **search.js**
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs')
|
|
12
|
+
const path = require('node:path')
|
|
13
|
+
const events = require('node:events')
|
|
14
|
+
const url = require('node:url')
|
|
15
|
+
const writeFileAtomic = require('write-file-atomic')
|
|
16
|
+
|
|
17
|
+
const miniSearch = require('minisearch')
|
|
18
|
+
|
|
19
|
+
module.exports = exports = argv => {
|
|
20
|
+
const wikiName = new URL(argv.url).hostname
|
|
21
|
+
let siteIndex = []
|
|
22
|
+
const queue = []
|
|
23
|
+
|
|
24
|
+
let searchPageHandler = null
|
|
25
|
+
|
|
26
|
+
// ms since last update we will remove index from memory
|
|
27
|
+
// orig - searchTimeoutMs = 1200000
|
|
28
|
+
const searchTimeoutMs = 120000 // temp reduce to 2 minutes
|
|
29
|
+
let searchTimeoutHandler = null
|
|
30
|
+
|
|
31
|
+
const siteIndexLoc = path.join(argv.status, 'site-index.json')
|
|
32
|
+
const indexUpdateFlag = path.join(argv.status, 'index-updated')
|
|
33
|
+
|
|
34
|
+
let working = false
|
|
35
|
+
|
|
36
|
+
const touch = (file, cb) => {
|
|
37
|
+
fs.stat(file, (err, stats) => {
|
|
38
|
+
if (err === null) return cb()
|
|
39
|
+
fs.open(file, 'w', (err, fd) => {
|
|
40
|
+
if (err) cb(err)
|
|
41
|
+
fs.close(fd, err => {
|
|
42
|
+
cb(err)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const searchPageUpdate = (slug, page, cb) => {
|
|
49
|
+
// to update we have to remove the page first, and then readd it
|
|
50
|
+
let pageText
|
|
51
|
+
try {
|
|
52
|
+
pageText = page.story.reduce(extractPageText, '')
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.log(`SITE INDEX *** ${wikiName} reduce to extract the text on ${slug} failed`, err.message)
|
|
55
|
+
pageText = ''
|
|
56
|
+
}
|
|
57
|
+
if (siteIndex.has(slug)) {
|
|
58
|
+
siteIndex.replace({
|
|
59
|
+
id: slug,
|
|
60
|
+
title: page.title,
|
|
61
|
+
content: pageText,
|
|
62
|
+
})
|
|
63
|
+
} else {
|
|
64
|
+
siteIndex.add({
|
|
65
|
+
id: slug,
|
|
66
|
+
title: page.title,
|
|
67
|
+
content: pageText,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
cb()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const searchPageRemove = (slug, cb) => {
|
|
74
|
+
// remove page from index
|
|
75
|
+
try {
|
|
76
|
+
siteIndex.discard(slug)
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// swallow error, if the page was not in index
|
|
79
|
+
if (!err.message.includes('not in the index')) {
|
|
80
|
+
console.log(`removing ${slug} from index ${wikiName} failed`, err)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
cb()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const searchSave = (siteIndex, cb) => {
|
|
87
|
+
// save index to file
|
|
88
|
+
fs.exists(argv.status, exists => {
|
|
89
|
+
if (exists) {
|
|
90
|
+
writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => {
|
|
91
|
+
if (e) return cb(e)
|
|
92
|
+
touch(indexUpdateFlag, () => {
|
|
93
|
+
cb()
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
} else {
|
|
97
|
+
fs.mkdir(argv.status, { recursive: true }, () => {
|
|
98
|
+
writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => {
|
|
99
|
+
if (e) return cb(e)
|
|
100
|
+
touch(indexUpdateFlag, () => {
|
|
101
|
+
cb()
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const searchRestore = cb => {
|
|
110
|
+
// restore index, or create if it doesn't already exist
|
|
111
|
+
fs.exists(siteIndexLoc, exists => {
|
|
112
|
+
if (exists) {
|
|
113
|
+
fs.readFile(siteIndexLoc, (err, data) => {
|
|
114
|
+
if (err) return cb(err)
|
|
115
|
+
try {
|
|
116
|
+
siteIndex = miniSearch.loadJSON(data, {
|
|
117
|
+
fields: ['title', 'content'],
|
|
118
|
+
})
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return cb(e)
|
|
121
|
+
}
|
|
122
|
+
process.nextTick(() => {
|
|
123
|
+
serial(queue.shift())
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const serial = item => {
|
|
131
|
+
if (item) {
|
|
132
|
+
switch (item.action) {
|
|
133
|
+
case 'update':
|
|
134
|
+
itself.start()
|
|
135
|
+
searchPageUpdate(item.slug, item.page, () => {
|
|
136
|
+
process.nextTick(() => {
|
|
137
|
+
serial(queue.shift())
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
break
|
|
141
|
+
case 'remove':
|
|
142
|
+
itself.start()
|
|
143
|
+
searchPageRemove(item.slug, () => {
|
|
144
|
+
process.nextTick(() => {
|
|
145
|
+
serial(queue.shift())
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
break
|
|
149
|
+
default:
|
|
150
|
+
console.log(`SITE INDEX *** unexpected action ${item.action} for ${item.page}`)
|
|
151
|
+
process.nextTick(() => {
|
|
152
|
+
serial(queue.shift)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
searchSave(siteIndex, e => {
|
|
157
|
+
if (e) console.log('SITE INDEX *** save failed: ' + e)
|
|
158
|
+
itself.stop()
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const extractItemText = text => {
|
|
164
|
+
return text
|
|
165
|
+
.replace(/\[([^\]]*?)\][[(].*?[\])]/g, ' $1 ')
|
|
166
|
+
.replace(/\[{2}|\[(?:[\S]+)|\]{1,2}/g, ' ')
|
|
167
|
+
.replace(/\n/g, ' ')
|
|
168
|
+
.replace(/<style.*?<\/style>/g, ' ')
|
|
169
|
+
.replace(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g, ' ')
|
|
170
|
+
.replace(/<(?:[^>])+>/g, ' ')
|
|
171
|
+
.replace(/(https?:.*?)(?=\p{White_Space}|\p{Quotation_Mark}|$)/gu, match => {
|
|
172
|
+
const myUrl = url.parse(match)
|
|
173
|
+
return myUrl.hostname + ' ' + myUrl.pathname
|
|
174
|
+
})
|
|
175
|
+
.replace(/[\p{P}\p{Emoji}\p{Symbol}}]+/gu, ' ')
|
|
176
|
+
.replace(/[\p{White_Space}\n\t]+/gu, ' ')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const extractPageText = (pageText, currentItem, currentIndex, array) => {
|
|
180
|
+
// console.log('extractPageText', pageText, currentItem, currentIndex, array)
|
|
181
|
+
try {
|
|
182
|
+
if (currentItem.text) {
|
|
183
|
+
switch (currentItem.type) {
|
|
184
|
+
case 'paragraph':
|
|
185
|
+
case 'markdown':
|
|
186
|
+
case 'html':
|
|
187
|
+
case 'reference':
|
|
188
|
+
case 'image':
|
|
189
|
+
case 'pagefold':
|
|
190
|
+
case 'math':
|
|
191
|
+
case 'mathjax':
|
|
192
|
+
case 'code':
|
|
193
|
+
pageText += ' ' + extractItemText(currentItem.text)
|
|
194
|
+
break
|
|
195
|
+
case 'audio':
|
|
196
|
+
case 'video':
|
|
197
|
+
case 'frame':
|
|
198
|
+
pageText +=
|
|
199
|
+
' ' +
|
|
200
|
+
extractItemText(
|
|
201
|
+
currentItem.text
|
|
202
|
+
.split(/\r\n?|\n/)
|
|
203
|
+
.map(line => {
|
|
204
|
+
const firstWord = line.split(/\p{White_Space}/u)[0]
|
|
205
|
+
if (
|
|
206
|
+
firstWord.startsWith('http') ||
|
|
207
|
+
firstWord.toUpperCase() === firstWord ||
|
|
208
|
+
firstWord.startsWith('//')
|
|
209
|
+
) {
|
|
210
|
+
// line is markup
|
|
211
|
+
return ''
|
|
212
|
+
} else {
|
|
213
|
+
return line
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
.join(' '),
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
throw new Error(`Error extracting text from ${currentIndex}, ${JSON.stringify(currentItem)} ${err}, ${err.stack}`)
|
|
222
|
+
}
|
|
223
|
+
return pageText
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// #### Public stuff ####
|
|
227
|
+
|
|
228
|
+
var itself = new events.EventEmitter()
|
|
229
|
+
itself.start = () => {
|
|
230
|
+
clearTimeout(searchTimeoutHandler)
|
|
231
|
+
working = true
|
|
232
|
+
return itself.emit('indexing')
|
|
233
|
+
}
|
|
234
|
+
itself.stop = () => {
|
|
235
|
+
const clearsearch = () => {
|
|
236
|
+
console.log(`SITE INDEX ${wikiName} : removed from memory`)
|
|
237
|
+
siteIndex = []
|
|
238
|
+
clearTimeout(searchTimeoutHandler)
|
|
239
|
+
}
|
|
240
|
+
searchTimeoutHandler = setTimeout(clearsearch, searchTimeoutMs)
|
|
241
|
+
working = false
|
|
242
|
+
return itself.emit('indexed')
|
|
243
|
+
}
|
|
244
|
+
itself.isWorking = () => {
|
|
245
|
+
return working
|
|
246
|
+
}
|
|
247
|
+
itself.createIndex = pagehandler => {
|
|
248
|
+
itself.start()
|
|
249
|
+
|
|
250
|
+
// we save the pagehandler, so we can recreate the site index if it is removed
|
|
251
|
+
searchPageHandler = searchPageHandler ?? pagehandler
|
|
252
|
+
|
|
253
|
+
//timeLabel = "SITE INDEX #{wikiName} : Created"
|
|
254
|
+
//console.time timeLabel
|
|
255
|
+
|
|
256
|
+
pagehandler.slugs((e, slugs) => {
|
|
257
|
+
if (e) {
|
|
258
|
+
console.log(`SITE INDEX *** createIndex ${wikiName} error:`, e)
|
|
259
|
+
itself.stop()
|
|
260
|
+
return e
|
|
261
|
+
}
|
|
262
|
+
siteIndex = new miniSearch({
|
|
263
|
+
fields: ['title', 'content'],
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const indexPromises = slugs.map(slug => {
|
|
267
|
+
return new Promise(resolve => {
|
|
268
|
+
pagehandler.get(slug, (err, page) => {
|
|
269
|
+
if (err) {
|
|
270
|
+
console.log(`SITE INDEX *** ${wikiName}: error reading page`, slug)
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
// page
|
|
274
|
+
let pageText
|
|
275
|
+
try {
|
|
276
|
+
pageText = page.story.reduce(extractPageText, '')
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.log(`SITE INDEX *** ${wikiName} reduce to extract text on ${slug} failed`, err.message)
|
|
279
|
+
// console.log "page", page
|
|
280
|
+
pageText = ''
|
|
281
|
+
}
|
|
282
|
+
siteIndex.add({
|
|
283
|
+
id: slug,
|
|
284
|
+
title: page.title,
|
|
285
|
+
content: pageText,
|
|
286
|
+
})
|
|
287
|
+
resolve()
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
Promise.all(indexPromises).then(() => {
|
|
292
|
+
// console.timeEnd timeLabel
|
|
293
|
+
process.nextTick(() => {
|
|
294
|
+
serial(queue.shift())
|
|
295
|
+
})
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
itself.removePage = slug => {
|
|
301
|
+
const action = 'remove'
|
|
302
|
+
queue.push({ action, slug })
|
|
303
|
+
if (Array.isArray(siteIndex) && !working) {
|
|
304
|
+
itself.start()
|
|
305
|
+
searchRestore(e => {
|
|
306
|
+
if (e) console.log(`SITE INDEX *** Problems restoring search index ${wikiName}:` + e)
|
|
307
|
+
itself.createIndex(searchPageHandler)
|
|
308
|
+
})
|
|
309
|
+
} else {
|
|
310
|
+
if (!working) serial(queue.shift())
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
itself.update = (slug, page) => {
|
|
315
|
+
const action = 'update'
|
|
316
|
+
queue.push({ action, slug, page })
|
|
317
|
+
if (Array.isArray(siteIndex) && !working) {
|
|
318
|
+
itself.start()
|
|
319
|
+
searchRestore(e => {
|
|
320
|
+
if (e) console.log(`SITE INDEX *** Problems restoring search index ${wikiName}:` + e)
|
|
321
|
+
itself.createIndex(searchPageHandler)
|
|
322
|
+
})
|
|
323
|
+
} else {
|
|
324
|
+
if (!working) serial(queue.shift())
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
itself.startUp = pagehandler => {
|
|
328
|
+
// called on server startup, here we check if wiki already is index
|
|
329
|
+
// we only create an index if there is either no index or there have been updates since last startup
|
|
330
|
+
console.log(`SITE INDEX ${wikiName} : StartUp`)
|
|
331
|
+
fs.stat(siteIndexLoc, (err, stats) => {
|
|
332
|
+
if (err === null) {
|
|
333
|
+
// site index exists, but has it been updated?
|
|
334
|
+
fs.stat(indexUpdateFlag, (err, stats) => {
|
|
335
|
+
if (!err) {
|
|
336
|
+
// index has been updated, so recreate it.
|
|
337
|
+
itself.createIndex(pagehandler)
|
|
338
|
+
// remove the update flag once the index has been created
|
|
339
|
+
itself.once('indexed', () => {
|
|
340
|
+
fs.unlink(indexUpdateFlag, err => {
|
|
341
|
+
if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`)
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
} else {
|
|
345
|
+
// not been updated, but is it the correct version?
|
|
346
|
+
fs.readFile(siteIndexLoc, (err, data) => {
|
|
347
|
+
if (!err) {
|
|
348
|
+
let testIndex
|
|
349
|
+
try {
|
|
350
|
+
testIndex = JSON.parse(data)
|
|
351
|
+
} catch (err) {
|
|
352
|
+
testIndex = {}
|
|
353
|
+
}
|
|
354
|
+
if (testIndex.serializationVersion != 2)
|
|
355
|
+
console.log(`+++ SITE INDEX ${wikiName} : updating to latest version.`)
|
|
356
|
+
itself.createIndex(pagehandler)
|
|
357
|
+
// remove the update flag once the index has been created
|
|
358
|
+
itself.once('indexed', () => {
|
|
359
|
+
fs.unlink(indexUpdateFlag, err => {
|
|
360
|
+
if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`)
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
} else {
|
|
364
|
+
console.log(`+++ SITE INDEX ${wikiName} : error reading index - attempting creating`)
|
|
365
|
+
itself.createIndex(pagehandler)
|
|
366
|
+
// remove the update flag once the index has been created
|
|
367
|
+
itself.once('indexed', () => {
|
|
368
|
+
fs.unlink(indexUpdateFlag, err => {
|
|
369
|
+
if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`)
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
} else {
|
|
377
|
+
// index does not exist, so create it
|
|
378
|
+
itself.createIndex(pagehandler)
|
|
379
|
+
// remove the update flag once the index has been created
|
|
380
|
+
itself.once('indexed', () => {
|
|
381
|
+
fs.unlink(indexUpdateFlag, err => {
|
|
382
|
+
if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`)
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return itself
|
|
390
|
+
}
|
package/lib/security.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Federated Wiki : Node Server
|
|
3
|
+
*
|
|
4
|
+
* Copyright Ward Cunningham and other contributors
|
|
5
|
+
* Licensed under the MIT license.
|
|
6
|
+
* https://github.com/fedwiki/wiki-node-server/blob/master/LICENSE.txt
|
|
7
|
+
*/
|
|
8
|
+
// **security.js**
|
|
9
|
+
// Module for default site security.
|
|
10
|
+
//
|
|
11
|
+
// This module is not intented for use, but is here to catch a problem with
|
|
12
|
+
// configuration of security. It does not provide any authentication, but will
|
|
13
|
+
// allow the server to run read-only.
|
|
14
|
+
|
|
15
|
+
// #### Requires ####
|
|
16
|
+
const fs = require('node:fs')
|
|
17
|
+
|
|
18
|
+
// Export a function that generates security handler
|
|
19
|
+
// when called with options object.
|
|
20
|
+
module.exports = exports = (log, loga, argv) => {
|
|
21
|
+
const security = {}
|
|
22
|
+
|
|
23
|
+
// #### Private utility methods. ####
|
|
24
|
+
|
|
25
|
+
const user = ''
|
|
26
|
+
|
|
27
|
+
let owner = ''
|
|
28
|
+
|
|
29
|
+
// save the admin user, and location of the identity file
|
|
30
|
+
const { admin, id: idFile } = argv
|
|
31
|
+
|
|
32
|
+
// #### Public stuff ####
|
|
33
|
+
|
|
34
|
+
security.authenticate_session = () => {
|
|
35
|
+
;(req, res, next) => {
|
|
36
|
+
// not possible to login, so always false
|
|
37
|
+
req.isAuthenticated = () => false
|
|
38
|
+
return next()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Retrieve owner infomation from identity file in status directory
|
|
43
|
+
security.retrieveOwner = cb => {
|
|
44
|
+
fs.exists(idFile, exists => {
|
|
45
|
+
if (exists) {
|
|
46
|
+
fs.readFile(idFile, (err, data) => {
|
|
47
|
+
if (err) return cb(err)
|
|
48
|
+
owner += data
|
|
49
|
+
cb()
|
|
50
|
+
})
|
|
51
|
+
} else {
|
|
52
|
+
owner = ''
|
|
53
|
+
cb()
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Return the owners name
|
|
59
|
+
security.getOwner = () => {
|
|
60
|
+
let ownerName
|
|
61
|
+
if (!owner.name) {
|
|
62
|
+
ownerName = ''
|
|
63
|
+
} else {
|
|
64
|
+
ownerName = owner.name
|
|
65
|
+
}
|
|
66
|
+
return ownerName
|
|
67
|
+
}
|
|
68
|
+
security.getUser = req => {
|
|
69
|
+
return ''
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
security.isAuthorized = req => {
|
|
73
|
+
// nobody is authorized - everything is read-only
|
|
74
|
+
// unless legacy support, when unclaimed sites can be editted.
|
|
75
|
+
if (owner == '') {
|
|
76
|
+
if (argv.security_legacy) {
|
|
77
|
+
return true
|
|
78
|
+
} else {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Wiki server admin
|
|
86
|
+
security.isAdmin = () => {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
security.defineRoutes = (app, cors, updateOwner) => {
|
|
90
|
+
// default security does not have any routes
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return security
|
|
94
|
+
}
|