node-opensrs 0.0.1
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/.github/dependabot.yml +11 -0
- package/.github/workflows/npm-publish.yml +33 -0
- package/README.md +54 -0
- package/index.js +295 -0
- package/package.json +22 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "npm" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
+
|
|
4
|
+
name: Publish to NPM
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [created]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
# build:
|
|
12
|
+
# runs-on: ubuntu-latest
|
|
13
|
+
# steps:
|
|
14
|
+
# - uses: actions/checkout@v3
|
|
15
|
+
# - uses: actions/setup-node@v3
|
|
16
|
+
# with:
|
|
17
|
+
# node-version: 16
|
|
18
|
+
# - run: npm ci
|
|
19
|
+
# - run: npm test
|
|
20
|
+
|
|
21
|
+
publish:
|
|
22
|
+
# needs: build
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v3
|
|
26
|
+
- uses: actions/setup-node@v3
|
|
27
|
+
with:
|
|
28
|
+
node-version: 16
|
|
29
|
+
registry-url: https://registry.npmjs.org/
|
|
30
|
+
- run: npm ci
|
|
31
|
+
- run: npm publish
|
|
32
|
+
env:
|
|
33
|
+
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
node-opensrs
|
|
2
|
+
============
|
|
3
|
+
|
|
4
|
+
This is a simple implementation of the OpenSRS API in node.js
|
|
5
|
+
|
|
6
|
+
Installation
|
|
7
|
+
============
|
|
8
|
+
```shell
|
|
9
|
+
npm i node-opensrs
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Getting started
|
|
13
|
+
===============
|
|
14
|
+
```js
|
|
15
|
+
const opensrs = require('node-opensrs')(options)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Documentation
|
|
19
|
+
=============
|
|
20
|
+
Configuration Options
|
|
21
|
+
---------------------
|
|
22
|
+
| Option | Description |
|
|
23
|
+
|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
24
|
+
| `email.credentials` | This is an OpenSRS credentials object. See https://email.opensrs.guide/docs/authenticate for more info. |
|
|
25
|
+
| `email.apiUrl` | URL for the email API. This should be https://admin.b.hostedemail.com/api or https://admin.a.hostedemail.com/api depending on your cluster. See https://email.opensrs.guide/docs/sending-requests |
|
|
26
|
+
| `domains.apiKey` | API key for the domains API. See https://domains.opensrs.guide/docs/quickstart |
|
|
27
|
+
| `domains.apiUrl` | URL for the domains API. This should be https://rr-n1-tor.opensrs.net:55443 for production or https://horizon.opensrs.net:55443 for testing. |
|
|
28
|
+
| `domains.username` | Your OpenSRS reseller username. |
|
|
29
|
+
|
|
30
|
+
API Functions
|
|
31
|
+
-------------
|
|
32
|
+
These functions are implementations of the OpenSRS API. All functions will return a promise. For more details on each function, see the OpenSRS documentation.
|
|
33
|
+
|
|
34
|
+
**Email API**
|
|
35
|
+
|
|
36
|
+
- `opensrs.mail.authenticate()`
|
|
37
|
+
- `opensrs.mail.getDomain(domain)`
|
|
38
|
+
- `opensrs.mail.addDomain(domain)`
|
|
39
|
+
- `opensrs.mail.searchUsers(domain)`
|
|
40
|
+
- `opensrs.mail.changeUser(user, attributes)`
|
|
41
|
+
- `opensrs.mail.deleteUser(user)`
|
|
42
|
+
|
|
43
|
+
**Domains and SSL API**
|
|
44
|
+
- `opensrs.domains.getDomainsContacts(domains)`
|
|
45
|
+
- `opensrs.domains.updateContacts(params)`
|
|
46
|
+
- `opensrs.domains.getPrice(params)`
|
|
47
|
+
- `opensrs.events.poll(limit)`
|
|
48
|
+
- `opensrs.events.ack(eventId)`
|
|
49
|
+
|
|
50
|
+
Official Documentation
|
|
51
|
+
======================
|
|
52
|
+
The official OpenSRS API documentation can be found at the following links:
|
|
53
|
+
- Email: https://email.opensrs.guide/
|
|
54
|
+
- Domains and SSL: https://domains.opensrs.guide/
|
package/index.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
let axios = require('axios');
|
|
2
|
+
const xmlBuilder = require('xmlbuilder2');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
module.exports = (options) => ({
|
|
6
|
+
mail: {
|
|
7
|
+
authenticate: async () => {
|
|
8
|
+
let {data} = await axios.post(`${options.mail.apiUrl}/authenticate`, {
|
|
9
|
+
credentials: options.mail.credentials
|
|
10
|
+
})
|
|
11
|
+
return data && data.success;
|
|
12
|
+
},
|
|
13
|
+
getDomain: async (domain) => {
|
|
14
|
+
let {data} = await axios.post(`${options.mail.apiUrl}/get_domain`, {
|
|
15
|
+
credentials: options.mail.credentials,
|
|
16
|
+
domain
|
|
17
|
+
})
|
|
18
|
+
return data && data.success && data;
|
|
19
|
+
},
|
|
20
|
+
addDomain: async (domain) => {
|
|
21
|
+
let {data} = await axios.post(`${options.mail.apiUrl}/change_domain`, {
|
|
22
|
+
credentials: options.mail.credentials,
|
|
23
|
+
domain,
|
|
24
|
+
attributes: {},
|
|
25
|
+
create_only: true
|
|
26
|
+
})
|
|
27
|
+
if(!data || !data.success) console.log(data && data.success || 'Unknown error enabling email')
|
|
28
|
+
return data && data.success;
|
|
29
|
+
},
|
|
30
|
+
searchUsers: async (domain) => {
|
|
31
|
+
let {data} = await axios.post(`${options.mail.apiUrl}/search_users`, {
|
|
32
|
+
credentials: options.mail.credentials,
|
|
33
|
+
criteria:{domain}
|
|
34
|
+
})
|
|
35
|
+
return data && data.success && data;
|
|
36
|
+
},
|
|
37
|
+
/*
|
|
38
|
+
changeUser will create a new user or edit an existing one
|
|
39
|
+
type - Can be mailbox, forward, or filter
|
|
40
|
+
password - send as plain text
|
|
41
|
+
*/
|
|
42
|
+
changeUser: async (user, attributes) => {
|
|
43
|
+
let {data} = await axios.post(`${options.mail.apiUrl}/change_user`, {
|
|
44
|
+
credentials: options.mail.credentials,
|
|
45
|
+
user,
|
|
46
|
+
attributes
|
|
47
|
+
})
|
|
48
|
+
if(!data || !data.success)
|
|
49
|
+
console.error(data)
|
|
50
|
+
return data && data.success;
|
|
51
|
+
},
|
|
52
|
+
deleteUser: async (user) => {
|
|
53
|
+
let {data} = await axios.post(`${options.mail.apiUrl}/delete_user`, {
|
|
54
|
+
credentials: options.mail.credentials,
|
|
55
|
+
user
|
|
56
|
+
})
|
|
57
|
+
if(!data || !data.success)
|
|
58
|
+
console.error(data)
|
|
59
|
+
return data && data.success;
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
domains: {
|
|
63
|
+
getDomainsContacts: async (domains) => {
|
|
64
|
+
if(!domains) throw new Error('Domains parameter required')
|
|
65
|
+
if(typeof domains == 'string')
|
|
66
|
+
domains = [domains]
|
|
67
|
+
let xml = buildXmlRequest({
|
|
68
|
+
dt_assoc: {
|
|
69
|
+
item: [
|
|
70
|
+
{'@key': 'protocol', '#': 'XCP'},
|
|
71
|
+
{'@key': 'object', '#': 'DOMAIN'},
|
|
72
|
+
{'@key': 'action', '#': 'GET_DOMAINS_CONTACTS'},
|
|
73
|
+
{'@key': 'attributes', '#': {
|
|
74
|
+
dt_assoc: {
|
|
75
|
+
item: {'@key': 'domain_list', '#': {
|
|
76
|
+
dt_array: {
|
|
77
|
+
item: domains.map((domain, i) => ({'@key': i, '#': domain}))
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
let {data} = await axios.post(options.domains.apiUrl, xml, {
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'text/xml',
|
|
88
|
+
'X-Username': options.domains.username,
|
|
89
|
+
'X-Signature': getSignature(xml, options.domains.apiKey),
|
|
90
|
+
'Content-Length': xml.length
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
let contacts = parseXml(data);
|
|
94
|
+
return contacts.body.attributes
|
|
95
|
+
},
|
|
96
|
+
updateContacts: async (params) => {
|
|
97
|
+
if(!params)
|
|
98
|
+
throw new Error('Missing required paramaters for updateContacts()')
|
|
99
|
+
let xml = buildXmlRequest({
|
|
100
|
+
dt_assoc: {
|
|
101
|
+
item: [
|
|
102
|
+
{'@key': 'protocol', '#': 'XCP'},
|
|
103
|
+
{'@key': 'object', '#': 'DOMAIN'},
|
|
104
|
+
{'@key': 'action', '#': 'UPDATE_CONTACTS'},
|
|
105
|
+
{'@key': 'attributes', '#': {
|
|
106
|
+
dt_assoc: {
|
|
107
|
+
item: [
|
|
108
|
+
{'@key': 'domain', '#': params.domain},
|
|
109
|
+
Object.keys(params).filter((key) => ['affect_domains','report_email', 'domains'].includes(key)).map((key) => ({
|
|
110
|
+
'@key': key,
|
|
111
|
+
'#': params[key]
|
|
112
|
+
})),
|
|
113
|
+
{'@key': 'types', '#': {
|
|
114
|
+
dt_array: {
|
|
115
|
+
item: (params.types || Object.keys(params.contact_set)).map((type, i) => ({'@key': i, '#': type}))
|
|
116
|
+
}
|
|
117
|
+
}},
|
|
118
|
+
{'@key': 'contact_set', '#': {
|
|
119
|
+
dt_assoc: {
|
|
120
|
+
item: Object.keys(params.contact_set).map((type) => ({
|
|
121
|
+
'@key': type,
|
|
122
|
+
'#': {
|
|
123
|
+
dt_assoc: {
|
|
124
|
+
item: Object.keys(params.contact_set[type]).map((prop) => ({
|
|
125
|
+
'@key': prop,
|
|
126
|
+
'#': params.contact_set[type][prop]
|
|
127
|
+
}))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}))
|
|
131
|
+
}
|
|
132
|
+
}}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
}}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
let {data} = await axios.post(options.domains.apiUrl, xml, {
|
|
140
|
+
headers: {
|
|
141
|
+
'Content-Type': 'text/xml',
|
|
142
|
+
'X-Username': options.domains.username,
|
|
143
|
+
'X-Signature': getSignature(xml, options.domains.apiKey),
|
|
144
|
+
'Content-Length': xml.length
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
},
|
|
148
|
+
getPrice: async (params) => {
|
|
149
|
+
if(!params || !params.domain)
|
|
150
|
+
throw new Error('Missing required paramaters for getPrice()')
|
|
151
|
+
let xml = buildXmlRequest({
|
|
152
|
+
dt_assoc: {
|
|
153
|
+
item: [
|
|
154
|
+
{'@key': 'protocol', '#': 'XCP'},
|
|
155
|
+
{'@key': 'object', '#': 'DOMAIN'},
|
|
156
|
+
{'@key': 'action', '#': 'GET_PRICE'},
|
|
157
|
+
{'@key': 'attributes', '#': {
|
|
158
|
+
dt_assoc: {
|
|
159
|
+
item: [
|
|
160
|
+
Object.keys(params).map((key) => ({
|
|
161
|
+
'@key': key,
|
|
162
|
+
'#': params[key]
|
|
163
|
+
}))
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
}}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
let {data} = await axios.post(options.domains.apiUrl, xml, {
|
|
171
|
+
headers: {
|
|
172
|
+
'Content-Type': 'text/xml',
|
|
173
|
+
'X-Username': options.domains.username,
|
|
174
|
+
'X-Signature': getSignature(xml, options.domains.apiKey),
|
|
175
|
+
'Content-Length': xml.length
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
let price = parseXml(data);
|
|
179
|
+
return price.body.attributes
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
events: {
|
|
183
|
+
poll: async (limit) => {
|
|
184
|
+
let xml = buildXmlRequest({
|
|
185
|
+
dt_assoc: {
|
|
186
|
+
item: [
|
|
187
|
+
{'@key': 'protocol', '#': 'XCP'},
|
|
188
|
+
{'@key': 'object', '#': 'EVENT'},
|
|
189
|
+
{'@key': 'action', '#': 'POLL'},
|
|
190
|
+
{'@key': 'attributes', '#': {
|
|
191
|
+
dt_assoc: {
|
|
192
|
+
item: {'@key': 'limit', '#': limit || 1}
|
|
193
|
+
}
|
|
194
|
+
}}
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
let {data} = await axios.post(options.domains.apiUrl, xml, {
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': 'text/xml',
|
|
201
|
+
'X-Username': options.domains.username,
|
|
202
|
+
'X-Signature': getSignature(xml, options.domains.apiKey),
|
|
203
|
+
'Content-Length': xml.length
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
let e = parseXml(data).body.attributes;
|
|
207
|
+
if(!e.events)
|
|
208
|
+
return [];
|
|
209
|
+
// If only one event returned. Otherwise it will look like: {total: 2, events: {0: ..., 1: ...}}
|
|
210
|
+
if(e.events.object)
|
|
211
|
+
return [e.events]
|
|
212
|
+
return Object.keys(e.events).map(k => e.events[k])
|
|
213
|
+
},
|
|
214
|
+
ack: async (eventId) => {
|
|
215
|
+
if(!eventId) throw new Error('eventId must be defined');
|
|
216
|
+
let xml = buildXmlRequest({
|
|
217
|
+
dt_assoc: {
|
|
218
|
+
item: [
|
|
219
|
+
{'@key': 'protocol', '#': 'XCP'},
|
|
220
|
+
{'@key': 'object', '#': 'EVENT'},
|
|
221
|
+
{'@key': 'action', '#': 'ACK'},
|
|
222
|
+
{'@key': 'attributes', '#': {
|
|
223
|
+
dt_assoc: {
|
|
224
|
+
item: {'@key': 'event_id', '#': eventId}
|
|
225
|
+
}
|
|
226
|
+
}}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
let {data} = await axios.post(options.domains.apiUrl, xml, {
|
|
231
|
+
headers: {
|
|
232
|
+
'Content-Type': 'text/xml',
|
|
233
|
+
'X-Username': options.domains.username,
|
|
234
|
+
'X-Signature': getSignature(xml, options.domains.apiKey),
|
|
235
|
+
'Content-Length': xml.length
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
function getSignature(xml, key){
|
|
243
|
+
let signature = crypto.createHash('md5').update(xml + key).digest("hex")
|
|
244
|
+
return crypto.createHash('md5').update(signature + key).digest("hex")
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function buildXmlRequest(data_block){
|
|
248
|
+
return xmlBuilder.create({
|
|
249
|
+
encoding: 'UTF-8',
|
|
250
|
+
standalone: true
|
|
251
|
+
}).ele({
|
|
252
|
+
OPS_envelope: {
|
|
253
|
+
header: { version: '0.9' },
|
|
254
|
+
body: { data_block }
|
|
255
|
+
}
|
|
256
|
+
}).dtd({ sysID: 'ops.dtd' }).end({ prettyPrint: true });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function parseXml(data){
|
|
260
|
+
if(typeof data == 'string')
|
|
261
|
+
data = xmlBuilder.create(data).end({ format: 'object' })
|
|
262
|
+
if(data instanceof Array){
|
|
263
|
+
let obj = {}
|
|
264
|
+
data.map((item) => parseXml(item)).forEach((item => obj = Object.assign(obj, item)))
|
|
265
|
+
return obj;
|
|
266
|
+
}
|
|
267
|
+
if(data.hasOwnProperty('@key') && data.hasOwnProperty('#')){
|
|
268
|
+
if(typeof data['#'] == 'string')
|
|
269
|
+
return {[data['@key']]: data['#']}
|
|
270
|
+
return {[data['@key']]: parseXml(data['#'])}
|
|
271
|
+
}
|
|
272
|
+
let out = {}
|
|
273
|
+
for(let key in data){
|
|
274
|
+
if(key == '@key') continue;
|
|
275
|
+
if(typeof data[key] == 'string')
|
|
276
|
+
out[key] = data[key]
|
|
277
|
+
else
|
|
278
|
+
out[key] = parseXml(data[key])
|
|
279
|
+
}
|
|
280
|
+
if(data.hasOwnProperty('@key'))
|
|
281
|
+
return {[data['@key']]: out}
|
|
282
|
+
return flattenObject(out)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function flattenObject(object){
|
|
286
|
+
if(!object || typeof object !== 'object') return object;
|
|
287
|
+
if(Object.keys(object).length == 0)
|
|
288
|
+
return null;
|
|
289
|
+
if(Object.keys(object).length == 1)
|
|
290
|
+
return flattenObject(object[Object.keys(object).pop()])
|
|
291
|
+
let flat = {}
|
|
292
|
+
for(let key in object)
|
|
293
|
+
flat[key] = flattenObject(object[key])
|
|
294
|
+
return flat
|
|
295
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-opensrs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "OpenSRS api for Node.js",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/johndc7/node-opensrs.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"opensrs"
|
|
15
|
+
],
|
|
16
|
+
"author": "John Callahan",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"axios": "^1.5.1",
|
|
20
|
+
"xmlbuilder2": "^3.1.1"
|
|
21
|
+
}
|
|
22
|
+
}
|