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.
@@ -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
+ }