-
Notifications
You must be signed in to change notification settings - Fork 0
/
api.js
311 lines (258 loc) · 8.47 KB
/
api.js
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
const { send, json } = require('micro')
const { router, get, post } = require('microrouter')
const cors = require('micro-cors')()
const crypto = require('crypto')
const DNS = require('./dns')
const Redis = require('./lib/redis')
//// Redis Layout
// user:token - list of all zones owned by the token TODO
// zone - top level list of zones
// zone:name - token & txt record
// zone:records:name - list of records
////
/// INIT server on startup
const run = async () => {
let zones = await Redis.hgetall('zone')
const allRecords = await getZones(zones)
Object.keys(allRecords).map(zone => {
// Re-init Zones
DNS.addZone(zone)
// Re-init Records
Object.keys(allRecords[zone]).map(item =>
DNS.addRecord(zone, allRecords[zone][item])
)
})
}
run()
//// ROUTES
// Returns all zone records
const zoneGet = async (req, res) => {
const response = await json(req)
// Validate name
if (response.zone.slice(-1) === '.')
return sendError(res, 'Please provide Zone without trailing period (.)')
// Validate token
if (!response.token)
return sendError(res, 'Please make sure to provide your token!')
try {
// Verify Domain & Token
await verifyOwnership(response.zone, response.token)
console.log('authd')
// RM settings
const obj = { [response.zone]: true }
const records = await getZones(obj)
return send(res, 201, {
success: true,
records
})
} catch (e) {
return sendError(res, e)
}
}
// Creates a new zone
const zoneAdd = async (req, res) => {
const response = await json(req)
// Validate name
if (response.zone.slice(-1) === '.' || response.zone.slice(0) === '.')
return sendError(res, 'Please provide Zone without periods')
// Validate token
if (!response.token)
return sendError(res, 'Please make sure to provide your token!')
// Check if name is fully setup
if ((await Redis.hexists('zone:' + response.zone, 'token')) === 1)
return sendError(res, 'This name is already managed by this server')
try {
// Get pre-existing claims on the name
let challengeObject = await Redis.hmget(
'zone:' + response.zone,
'challenge'
)
// If no challenges then provision now object
if (challengeObject[0] === null) challengeObject = []
// check if user has already tried to add domain
const alreadyChallenged = challengeObject.find(
item => item.token === response.token
)
if (alreadyChallenged)
return send(res, 201, { success: true, record: alreadyChallenged.txt })
// Create challenge object
const userObject = {
token: response.token,
txt: 'hsdns-' + crypto.randomBytes(20).toString('hex')
}
challengeObject.push(userObject)
// Save challenge
const setObject = await Redis.hmset(
'zone:' + response.zone,
'challenge',
challengeObject
)
return send(res, 201, { success: true, record: userObject.txt })
} catch (e) {
return sendError(res, e)
}
}
// Removes a zone
const zoneRemove = async (req, res) => {
const response = await json(req)
// Validate name
if (response.zone.slice(-1) === '.')
return sendError(res, 'Please provide Zone without trailing period (.)')
// Validate token
if (!response.token)
return sendError(res, 'Please make sure to provide your token!')
// Don't delete a name that doesn't exist
if ((await Redis.exists('zone:' + response.zone)) === 0)
return sendError(
res,
"This zone doesn't exist. Try adding it before deleting it"
)
try {
// Verify Domain & Token
await verifyOwnership(response.zone, response.token)
// RM settings
DNS.removeZone(response.zone)
await Redis.del('zone:' + response.zone)
await Redis.del('zone:records:' + response.zone)
await Redis.hdel('zone', response.zone)
return send(res, 201, { success: true })
} catch (e) {
return sendError(res, e)
}
}
// Adds a record to a zone
const add = async (req, res) => {
const response = await json(req)
// Validate name
if (response.zone.slice(-1) === '.')
return sendError(res, 'Please provide Zone without trailing period (.)')
// Validate token
if (!response.token)
return sendError(res, 'Please make sure to provide your token!')
// Validate Record
if (!response.record) return sendError(res, 'Please make you add a record!')
try {
// Validate ownership
await verifyOwnership(response.zone, response.token)
const recordID = crypto.randomBytes(10).toString('hex')
// Add record to zone
const result = await DNS.addRecord(response.zone, response.record)
// Add record to zone
await Redis.hmset(
'zone:records:' + response.zone,
recordID,
response.record
)
return send(res, 201, { success: true, record: recordID })
} catch (e) {
return sendError(res, e)
}
}
// Removes a record from a zone
const remove = async (req, res) => {
const response = await json(req)
// Validate name
if (response.zone.slice(-1) === '.')
return sendError(res, 'Please provide Zone without trailing period (.)')
// Validate token
if (!response.token)
return sendError(res, 'Please make sure to provide your token!')
// Validate token
if (!response.record)
return sendError(res, 'Please make sure to provide the record ID!')
try {
// Validate ownership
await verifyOwnership(response.zone, response.token)
// Remove all records
const rmChallenge = await Redis.hdel(
'zone:records:' + response.zone,
response.record
)
// Fetch all records
const fetchRecords = await Redis.hgetall('zone:records:' + response.zone)
// Clear Zone
// BNS Zone() should really have an rm option
DNS.clearZone(response.zone)
// Re-init all records
Object.keys(fetchRecords).map(item =>
DNS.addRecord(response.zone, fetchRecords[item])
)
return send(res, 201, { success: true })
} catch (e) {
return sendError(res, e)
}
}
// Resolve a HNS query
const resolve = async (req, res) => {
const success = await DNS.resolve(req.params.name, req.params.type)
send(res, 201, { success })
}
/////////////// HELPERS ////////////////////
const verifyOwnership = async (zone, token) => {
// Get name's info
const zoneInfo = await Redis.hgetall('zone:' + zone)
// Resolve zone's TXT
const resolved = await DNS.resolve(zone + '.', 'TXT') // Change this to TXT
// Get the challenge TXT
let record = null
if (resolved.answer[0]) {
resolved.answer.map(object => {
if (object.data.txt) {
object.data.txt.map(item => {
if (item.substring(0, 5) === 'hsdns') record = item
})
}
})
}
// Check if the HSD has the verifcation TXT (CHANGE ME)
if (!record)
throw 'Unable to verify you own the domain. \nMake your record was entered in the Urkle tree.'
// If there is challenge info check to see if there is a rightful owner
if (zoneInfo.challenge) {
const owner = JSON.parse(zoneInfo.challenge).find(
items => items.txt === record
)
if (owner) {
console.log('Owner Found')
const setZoneRecord = await Redis.hmset('zone', zone, true)
const setUserRecord = await Redis.hmset('user:' + owner.token, zone, true)
const setToken = await Redis.hmset('zone:' + zone, 'token', owner.token)
const setTxt = await Redis.hmset('zone:' + zone, 'txt', owner.txt)
const rmChallenge = await Redis.hdel('zone:' + zone, 'challenge')
// Finally add the award
DNS.addZone(zone)
} else {
throw "Record found, its just that you don't own it 😬"
}
}
// Check if the user token matches the owner's token
const zoneToken = await Redis.hmget('zone:' + zone, 'token')
if (zoneToken[0] !== token) throw 'Invalid token.'
return
}
const getZones = async zones => {
let result = {}
const zone = Object.keys(zones).map(async key => {
result[key] = {}
console.log
const records = await Redis.hgetall('zone:records:' + key)
for (let [id, value] of Object.entries(records)) {
result[key][id] = value
}
})
await Promise.all(zone)
return result
}
const sendError = (res, error) => {
return send(res, 400, { error })
}
module.exports = cors(
router(
post('/zone-info', zoneGet), // Get zone info
post('/zone-add', zoneAdd), // Add a new zone
post('/zone-remove', zoneRemove), // Remove a whole zone
post('/record-add', add), // Add a new record
post('/record-remove', remove), // Remove a record
get('/resolve/:name/:type', resolve) // Resolve a HSD name
)
)