aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-07-13 11:13:19 +0200
committerChocobozzz <me@florianbigard.com>2022-07-13 11:16:29 +0200
commitea8107bff8e544594716a76d30ff372fc80d755c (patch)
treec2383e0e9cd1298ff12106073086721edb5fb441
parentc5cadb2859050449596199090231d6e38bc4a571 (diff)
downloadPeerTube-ea8107bff8e544594716a76d30ff372fc80d755c.tar.gz
PeerTube-ea8107bff8e544594716a76d30ff372fc80d755c.tar.zst
PeerTube-ea8107bff8e544594716a76d30ff372fc80d755c.zip
Split static router
-rw-r--r--server.ts4
-rw-r--r--server/controllers/index.ts2
-rw-r--r--server/controllers/misc.ts206
-rw-r--r--server/controllers/static.ts271
-rw-r--r--server/controllers/well-known.ts76
5 files changed, 291 insertions, 268 deletions
diff --git a/server.ts b/server.ts
index e69ad7fb7..35ccc6758 100644
--- a/server.ts
+++ b/server.ts
@@ -95,9 +95,11 @@ import { VideosPreviewCache, VideosCaptionCache } from './server/lib/files-cache
95import { 95import {
96 activityPubRouter, 96 activityPubRouter,
97 apiRouter, 97 apiRouter,
98 miscRouter,
98 clientsRouter, 99 clientsRouter,
99 feedsRouter, 100 feedsRouter,
100 staticRouter, 101 staticRouter,
102 wellKnownRouter,
101 lazyStaticRouter, 103 lazyStaticRouter,
102 servicesRouter, 104 servicesRouter,
103 liveRouter, 105 liveRouter,
@@ -231,6 +233,8 @@ app.use('/', botsRouter)
231 233
232// Static files 234// Static files
233app.use('/', staticRouter) 235app.use('/', staticRouter)
236app.use('/', wellKnownRouter)
237app.use('/', miscRouter)
234app.use('/', downloadRouter) 238app.use('/', downloadRouter)
235app.use('/', lazyStaticRouter) 239app.use('/', lazyStaticRouter)
236 240
diff --git a/server/controllers/index.ts b/server/controllers/index.ts
index fa27ecec2..e8833d58c 100644
--- a/server/controllers/index.ts
+++ b/server/controllers/index.ts
@@ -7,7 +7,9 @@ export * from './services'
7export * from './static' 7export * from './static'
8export * from './lazy-static' 8export * from './lazy-static'
9export * from './live' 9export * from './live'
10export * from './misc'
10export * from './webfinger' 11export * from './webfinger'
11export * from './tracker' 12export * from './tracker'
12export * from './bots' 13export * from './bots'
13export * from './plugins' 14export * from './plugins'
15export * from './well-known'
diff --git a/server/controllers/misc.ts b/server/controllers/misc.ts
new file mode 100644
index 000000000..4c8af2adc
--- /dev/null
+++ b/server/controllers/misc.ts
@@ -0,0 +1,206 @@
1import cors from 'cors'
2import express from 'express'
3import { CONFIG, isEmailEnabled } from '@server/initializers/config'
4import { serveIndexHTML } from '@server/lib/client-html'
5import { ServerConfigManager } from '@server/lib/server-config-manager'
6import { HttpStatusCode } from '@shared/models'
7import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model'
8import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION, ROUTE_CACHE_LIFETIME } from '../initializers/constants'
9import { getThemeOrDefault } from '../lib/plugins/theme-utils'
10import { asyncMiddleware } from '../middlewares'
11import { cacheRoute } from '../middlewares/cache/cache'
12import { UserModel } from '../models/user/user'
13import { VideoModel } from '../models/video/video'
14import { VideoCommentModel } from '../models/video/video-comment'
15
16const miscRouter = express.Router()
17
18miscRouter.use(cors())
19
20miscRouter.use('/nodeinfo/:version.json',
21 cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
22 asyncMiddleware(generateNodeinfo)
23)
24
25// robots.txt service
26miscRouter.get('/robots.txt',
27 cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS),
28 (_, res: express.Response) => {
29 res.type('text/plain')
30
31 return res.send(CONFIG.INSTANCE.ROBOTS)
32 }
33)
34
35miscRouter.all('/teapot',
36 getCup,
37 asyncMiddleware(serveIndexHTML)
38)
39
40// security.txt service
41miscRouter.get('/security.txt',
42 (_, res: express.Response) => {
43 return res.redirect(HttpStatusCode.MOVED_PERMANENTLY_301, '/.well-known/security.txt')
44 }
45)
46
47// ---------------------------------------------------------------------------
48
49export {
50 miscRouter
51}
52
53// ---------------------------------------------------------------------------
54
55async function generateNodeinfo (req: express.Request, res: express.Response) {
56 const { totalVideos } = await VideoModel.getStats()
57 const { totalLocalVideoComments } = await VideoCommentModel.getStats()
58 const { totalUsers, totalMonthlyActiveUsers, totalHalfYearActiveUsers } = await UserModel.getStats()
59
60 if (!req.params.version || req.params.version !== '2.0') {
61 return res.fail({
62 status: HttpStatusCode.NOT_FOUND_404,
63 message: 'Nodeinfo schema version not handled'
64 })
65 }
66
67 const json = {
68 version: '2.0',
69 software: {
70 name: 'peertube',
71 version: PEERTUBE_VERSION
72 },
73 protocols: [
74 'activitypub'
75 ],
76 services: {
77 inbound: [],
78 outbound: [
79 'atom1.0',
80 'rss2.0'
81 ]
82 },
83 openRegistrations: CONFIG.SIGNUP.ENABLED,
84 usage: {
85 users: {
86 total: totalUsers,
87 activeMonth: totalMonthlyActiveUsers,
88 activeHalfyear: totalHalfYearActiveUsers
89 },
90 localPosts: totalVideos,
91 localComments: totalLocalVideoComments
92 },
93 metadata: {
94 taxonomy: {
95 postsName: 'Videos'
96 },
97 nodeName: CONFIG.INSTANCE.NAME,
98 nodeDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
99 nodeConfig: {
100 search: {
101 remoteUri: {
102 users: CONFIG.SEARCH.REMOTE_URI.USERS,
103 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
104 }
105 },
106 plugin: {
107 registered: ServerConfigManager.Instance.getRegisteredPlugins()
108 },
109 theme: {
110 registered: ServerConfigManager.Instance.getRegisteredThemes(),
111 default: getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
112 },
113 email: {
114 enabled: isEmailEnabled()
115 },
116 contactForm: {
117 enabled: CONFIG.CONTACT_FORM.ENABLED
118 },
119 transcoding: {
120 hls: {
121 enabled: CONFIG.TRANSCODING.HLS.ENABLED
122 },
123 webtorrent: {
124 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
125 },
126 enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('vod')
127 },
128 live: {
129 enabled: CONFIG.LIVE.ENABLED,
130 transcoding: {
131 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
132 enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('live')
133 }
134 },
135 import: {
136 videos: {
137 http: {
138 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
139 },
140 torrent: {
141 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
142 }
143 }
144 },
145 autoBlacklist: {
146 videos: {
147 ofUsers: {
148 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
149 }
150 }
151 },
152 avatar: {
153 file: {
154 size: {
155 max: CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max
156 },
157 extensions: CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME
158 }
159 },
160 video: {
161 image: {
162 extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
163 size: {
164 max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
165 }
166 },
167 file: {
168 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
169 }
170 },
171 videoCaption: {
172 file: {
173 size: {
174 max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
175 },
176 extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
177 }
178 },
179 user: {
180 videoQuota: CONFIG.USER.VIDEO_QUOTA,
181 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
182 },
183 trending: {
184 videos: {
185 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
186 }
187 },
188 tracker: {
189 enabled: CONFIG.TRACKER.ENABLED
190 }
191 }
192 }
193 } as HttpNodeinfoDiasporaSoftwareNsSchema20
194
195 res.contentType('application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"')
196 .send(json)
197 .end()
198}
199
200function getCup (req: express.Request, res: express.Response, next: express.NextFunction) {
201 res.status(HttpStatusCode.I_AM_A_TEAPOT_418)
202 res.setHeader('Accept-Additions', 'Non-Dairy;1,Sugar;1')
203 res.setHeader('Safe', 'if-sepia-awake')
204
205 return next()
206}
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 87bceba7a..7668ceb82 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -1,37 +1,13 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3import { join } from 'path' 3import { CONFIG } from '../initializers/config'
4import { serveIndexHTML } from '@server/lib/client-html' 4import { HLS_STREAMING_PLAYLIST_DIRECTORY, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers/constants'
5import { ServerConfigManager } from '@server/lib/server-config-manager'
6import { HttpStatusCode } from '@shared/models'
7import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model'
8import { root } from '@shared/core-utils'
9import { CONFIG, isEmailEnabled } from '../initializers/config'
10import {
11 CONSTRAINTS_FIELDS,
12 DEFAULT_THEME_NAME,
13 HLS_STREAMING_PLAYLIST_DIRECTORY,
14 PEERTUBE_VERSION,
15 ROUTE_CACHE_LIFETIME,
16 STATIC_MAX_AGE,
17 STATIC_PATHS,
18 WEBSERVER
19} from '../initializers/constants'
20import { getThemeOrDefault } from '../lib/plugins/theme-utils'
21import { asyncMiddleware } from '../middlewares'
22import { cacheRoute } from '../middlewares/cache/cache'
23import { UserModel } from '../models/user/user'
24import { VideoModel } from '../models/video/video'
25import { VideoCommentModel } from '../models/video/video-comment'
26 5
27const staticRouter = express.Router() 6const staticRouter = express.Router()
28 7
8// Cors is very important to let other servers access torrent and video files
29staticRouter.use(cors()) 9staticRouter.use(cors())
30 10
31/*
32 Cors is very important to let other servers access torrent and video files
33*/
34
35// Videos path for webseed 11// Videos path for webseed
36staticRouter.use( 12staticRouter.use(
37 STATIC_PATHS.WEBSEED, 13 STATIC_PATHS.WEBSEED,
@@ -45,7 +21,6 @@ staticRouter.use(
45// HLS 21// HLS
46staticRouter.use( 22staticRouter.use(
47 STATIC_PATHS.STREAMING_PLAYLISTS.HLS, 23 STATIC_PATHS.STREAMING_PLAYLISTS.HLS,
48 cors(),
49 express.static(HLS_STREAMING_PLAYLIST_DIRECTORY, { fallthrough: false }) // 404 if the file does not exist 24 express.static(HLS_STREAMING_PLAYLIST_DIRECTORY, { fallthrough: false }) // 404 if the file does not exist
50) 25)
51 26
@@ -56,248 +31,8 @@ staticRouter.use(
56 express.static(thumbnailsPhysicalPath, { maxAge: STATIC_MAX_AGE.SERVER, fallthrough: false }) // 404 if the file does not exist 31 express.static(thumbnailsPhysicalPath, { maxAge: STATIC_MAX_AGE.SERVER, fallthrough: false }) // 404 if the file does not exist
57) 32)
58 33
59// robots.txt service
60staticRouter.get('/robots.txt',
61 cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS),
62 (_, res: express.Response) => {
63 res.type('text/plain')
64
65 return res.send(CONFIG.INSTANCE.ROBOTS)
66 }
67)
68
69staticRouter.all('/teapot',
70 getCup,
71 asyncMiddleware(serveIndexHTML)
72)
73
74// security.txt service
75staticRouter.get('/security.txt',
76 (_, res: express.Response) => {
77 return res.redirect(HttpStatusCode.MOVED_PERMANENTLY_301, '/.well-known/security.txt')
78 }
79)
80
81staticRouter.get('/.well-known/security.txt',
82 cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT),
83 (_, res: express.Response) => {
84 res.type('text/plain')
85 return res.send(CONFIG.INSTANCE.SECURITYTXT + CONFIG.INSTANCE.SECURITYTXT_CONTACT)
86 }
87)
88
89// nodeinfo service
90staticRouter.use('/.well-known/nodeinfo',
91 cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
92 (_, res: express.Response) => {
93 return res.json({
94 links: [
95 {
96 rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
97 href: WEBSERVER.URL + '/nodeinfo/2.0.json'
98 }
99 ]
100 })
101 }
102)
103staticRouter.use('/nodeinfo/:version.json',
104 cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
105 asyncMiddleware(generateNodeinfo)
106)
107
108// dnt-policy.txt service (see https://www.eff.org/dnt-policy)
109staticRouter.use('/.well-known/dnt-policy.txt',
110 cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY),
111 (_, res: express.Response) => {
112 res.type('text/plain')
113
114 return res.sendFile(join(root(), 'dist/server/static/dnt-policy/dnt-policy-1.0.txt'))
115 }
116)
117
118// dnt service (see https://www.w3.org/TR/tracking-dnt/#status-resource)
119staticRouter.use('/.well-known/dnt/',
120 (_, res: express.Response) => {
121 res.json({ tracking: 'N' })
122 }
123)
124
125staticRouter.use('/.well-known/change-password',
126 (_, res: express.Response) => {
127 res.redirect('/my-account/settings')
128 }
129)
130
131staticRouter.use('/.well-known/host-meta',
132 (_, res: express.Response) => {
133 res.type('application/xml')
134
135 const xml = '<?xml version="1.0" encoding="UTF-8"?>\n' +
136 '<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n' +
137 ` <Link rel="lrdd" type="application/xrd+xml" template="${WEBSERVER.URL}/.well-known/webfinger?resource={uri}"/>\n` +
138 '</XRD>'
139
140 res.send(xml).end()
141 }
142)
143
144// --------------------------------------------------------------------------- 34// ---------------------------------------------------------------------------
145 35
146export { 36export {
147 staticRouter 37 staticRouter
148} 38}
149
150// ---------------------------------------------------------------------------
151
152async function generateNodeinfo (req: express.Request, res: express.Response) {
153 const { totalVideos } = await VideoModel.getStats()
154 const { totalLocalVideoComments } = await VideoCommentModel.getStats()
155 const { totalUsers, totalMonthlyActiveUsers, totalHalfYearActiveUsers } = await UserModel.getStats()
156
157 if (!req.params.version || req.params.version !== '2.0') {
158 return res.fail({
159 status: HttpStatusCode.NOT_FOUND_404,
160 message: 'Nodeinfo schema version not handled'
161 })
162 }
163
164 const json = {
165 version: '2.0',
166 software: {
167 name: 'peertube',
168 version: PEERTUBE_VERSION
169 },
170 protocols: [
171 'activitypub'
172 ],
173 services: {
174 inbound: [],
175 outbound: [
176 'atom1.0',
177 'rss2.0'
178 ]
179 },
180 openRegistrations: CONFIG.SIGNUP.ENABLED,
181 usage: {
182 users: {
183 total: totalUsers,
184 activeMonth: totalMonthlyActiveUsers,
185 activeHalfyear: totalHalfYearActiveUsers
186 },
187 localPosts: totalVideos,
188 localComments: totalLocalVideoComments
189 },
190 metadata: {
191 taxonomy: {
192 postsName: 'Videos'
193 },
194 nodeName: CONFIG.INSTANCE.NAME,
195 nodeDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
196 nodeConfig: {
197 search: {
198 remoteUri: {
199 users: CONFIG.SEARCH.REMOTE_URI.USERS,
200 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
201 }
202 },
203 plugin: {
204 registered: ServerConfigManager.Instance.getRegisteredPlugins()
205 },
206 theme: {
207 registered: ServerConfigManager.Instance.getRegisteredThemes(),
208 default: getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
209 },
210 email: {
211 enabled: isEmailEnabled()
212 },
213 contactForm: {
214 enabled: CONFIG.CONTACT_FORM.ENABLED
215 },
216 transcoding: {
217 hls: {
218 enabled: CONFIG.TRANSCODING.HLS.ENABLED
219 },
220 webtorrent: {
221 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
222 },
223 enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('vod')
224 },
225 live: {
226 enabled: CONFIG.LIVE.ENABLED,
227 transcoding: {
228 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
229 enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('live')
230 }
231 },
232 import: {
233 videos: {
234 http: {
235 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
236 },
237 torrent: {
238 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
239 }
240 }
241 },
242 autoBlacklist: {
243 videos: {
244 ofUsers: {
245 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
246 }
247 }
248 },
249 avatar: {
250 file: {
251 size: {
252 max: CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max
253 },
254 extensions: CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME
255 }
256 },
257 video: {
258 image: {
259 extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
260 size: {
261 max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
262 }
263 },
264 file: {
265 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
266 }
267 },
268 videoCaption: {
269 file: {
270 size: {
271 max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
272 },
273 extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
274 }
275 },
276 user: {
277 videoQuota: CONFIG.USER.VIDEO_QUOTA,
278 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
279 },
280 trending: {
281 videos: {
282 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
283 }
284 },
285 tracker: {
286 enabled: CONFIG.TRACKER.ENABLED
287 }
288 }
289 }
290 } as HttpNodeinfoDiasporaSoftwareNsSchema20
291
292 res.contentType('application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"')
293 .send(json)
294 .end()
295}
296
297function getCup (req: express.Request, res: express.Response, next: express.NextFunction) {
298 res.status(HttpStatusCode.I_AM_A_TEAPOT_418)
299 res.setHeader('Accept-Additions', 'Non-Dairy;1,Sugar;1')
300 res.setHeader('Safe', 'if-sepia-awake')
301
302 return next()
303}
diff --git a/server/controllers/well-known.ts b/server/controllers/well-known.ts
new file mode 100644
index 000000000..f467bd629
--- /dev/null
+++ b/server/controllers/well-known.ts
@@ -0,0 +1,76 @@
1import cors from 'cors'
2import express from 'express'
3import { join } from 'path'
4import { root } from '@shared/core-utils'
5import { CONFIG } from '../initializers/config'
6import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
7import { cacheRoute } from '../middlewares/cache/cache'
8
9const wellKnownRouter = express.Router()
10
11wellKnownRouter.use(cors())
12
13wellKnownRouter.get('/.well-known/security.txt',
14 cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT),
15 (_, res: express.Response) => {
16 res.type('text/plain')
17 return res.send(CONFIG.INSTANCE.SECURITYTXT + CONFIG.INSTANCE.SECURITYTXT_CONTACT)
18 }
19)
20
21// nodeinfo service
22wellKnownRouter.use('/.well-known/nodeinfo',
23 cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
24 (_, res: express.Response) => {
25 return res.json({
26 links: [
27 {
28 rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
29 href: WEBSERVER.URL + '/nodeinfo/2.0.json'
30 }
31 ]
32 })
33 }
34)
35
36// dnt-policy.txt service (see https://www.eff.org/dnt-policy)
37wellKnownRouter.use('/.well-known/dnt-policy.txt',
38 cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY),
39 (_, res: express.Response) => {
40 res.type('text/plain')
41
42 return res.sendFile(join(root(), 'dist/server/static/dnt-policy/dnt-policy-1.0.txt'))
43 }
44)
45
46// dnt service (see https://www.w3.org/TR/tracking-dnt/#status-resource)
47wellKnownRouter.use('/.well-known/dnt/',
48 (_, res: express.Response) => {
49 res.json({ tracking: 'N' })
50 }
51)
52
53wellKnownRouter.use('/.well-known/change-password',
54 (_, res: express.Response) => {
55 res.redirect('/my-account/settings')
56 }
57)
58
59wellKnownRouter.use('/.well-known/host-meta',
60 (_, res: express.Response) => {
61 res.type('application/xml')
62
63 const xml = '<?xml version="1.0" encoding="UTF-8"?>\n' +
64 '<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n' +
65 ` <Link rel="lrdd" type="application/xrd+xml" template="${WEBSERVER.URL}/.well-known/webfinger?resource={uri}"/>\n` +
66 '</XRD>'
67
68 res.send(xml).end()
69 }
70)
71
72// ---------------------------------------------------------------------------
73
74export {
75 wellKnownRouter
76}