1 | import fastify from 'fastify'
|
2 | import auth from 'basic-auth'
|
3 | import fastifyStatic from 'fastify-static'
|
4 | import path from 'path'
|
5 |
|
6 | import connectionsHandler from './connections'
|
7 | import configHandler from './config'
|
8 | import controlHandler from './control'
|
9 | import discordHandler from './discord'
|
10 | import messengerHandler from './messenger'
|
11 | import { Reply } from '../types/fastify'
|
12 |
|
13 | const log = logger.withScope('api')
|
14 |
|
15 | export default function runServer () {
|
16 | const app = fastify()
|
17 |
|
18 | app.register(fastifyStatic, {
|
19 | root: path.join(__dirname, '..', '..', 'node_modules'),
|
20 | prefix: '/node_modules/',
|
21 | decorateReply: false
|
22 | })
|
23 |
|
24 | app.register(fastifyStatic, {
|
25 | root: path.join(__dirname, '..', '..', 'static'),
|
26 | prefix: '/static/'
|
27 | })
|
28 |
|
29 | app.decorateReply('sendError', function (this: Reply, code: number, message: string) {
|
30 | this.code(code).send(new Error(message))
|
31 | })
|
32 |
|
33 | if (config.api.key) {
|
34 | app.addHook('preHandler', (request, reply, next) => {
|
35 | const { authorization } = request.headers
|
36 |
|
37 | if (!authorization) {
|
38 | reply.sendError(403, 'API key missing')
|
39 | return
|
40 | }
|
41 | if (!authorization.startsWith('Bearer ')) {
|
42 | reply.sendError(403, 'API key incorrect')
|
43 | return
|
44 | }
|
45 |
|
46 | const key = authorization.split(' ')[1]
|
47 | if (key !== config.api.key) {
|
48 | reply.sendError(403, 'API key incorrect')
|
49 | return
|
50 | }
|
51 |
|
52 | next()
|
53 | })
|
54 | } else {
|
55 | app.addHook('preHandler', (request, reply, next) => {
|
56 | const send401 = () => reply
|
57 | .code(401)
|
58 | .header('WWW-Authenticate', 'Basic realm="Miscord API"')
|
59 | .header('Content-Type', 'text/html')
|
60 | .send('<h1>Forbidden</h1>')
|
61 |
|
62 | if (!request.headers.authorization) {
|
63 | send401()
|
64 | return
|
65 | }
|
66 |
|
67 | const creds = auth.parse(request.headers.authorization)
|
68 | if (!creds) {
|
69 | send401()
|
70 | return
|
71 | }
|
72 |
|
73 | const { name, pass } = creds
|
74 | log.debug('login', { name, pass })
|
75 | if (name === config.api.username && pass === config.api.password) return next()
|
76 |
|
77 | send401()
|
78 | })
|
79 | }
|
80 |
|
81 | app.get('/', (request, reply) => {
|
82 | reply.sendFile('dashboard/index.html')
|
83 | })
|
84 | app.get('/config-discord/', (request, reply) => {
|
85 | reply.sendFile('dashboard/config-discord.html')
|
86 | })
|
87 | app.get('/config-messenger/', (request, reply) => {
|
88 | reply.sendFile('dashboard/config-messenger.html')
|
89 | })
|
90 | app.get('/config-miscellaneous/', (request, reply) => {
|
91 | reply.sendFile('dashboard/config-miscellaneous.html')
|
92 | })
|
93 | app.get('/connections/', (request, reply) => {
|
94 | reply.sendFile('dashboard/connections.html')
|
95 | })
|
96 | app.register(connectionsHandler, { prefix: '/connections' })
|
97 | app.register(configHandler, { prefix: '/config' })
|
98 | app.register(controlHandler, { prefix: '/control' })
|
99 | app.register(discordHandler, { prefix: '/discord' })
|
100 | app.register(messengerHandler, { prefix: '/messenger' })
|
101 |
|
102 | const port = Number(config.api.port || 9448)
|
103 |
|
104 | app.listen(port, '0.0.0.0')
|
105 | .then(() => log.success(`API is listening on port ${port}`))
|
106 | .catch(err => {
|
107 | if (err.code === 'EADDRINUSE') {
|
108 | app.listen(port + 1, '0.0.0.0')
|
109 | .then(() => log.success(`API is listening on port ${port + 1}`))
|
110 | } else {
|
111 | throw err
|
112 | }
|
113 | })
|
114 | return app
|
115 | }
|