UNPKG

8.21 kBJavaScriptView Raw
1var AWS = require('../core');
2
3/**
4 * @api private
5 */
6var service = null;
7
8/**
9 * @api private
10 */
11var api = {
12 signatureVersion: 'v4',
13 signingName: 'rds-db'
14};
15
16/**
17 * @api private
18 */
19var requiredAuthTokenOptions = {
20 region: 'string',
21 hostname: 'string',
22 port: 'number',
23 username: 'string'
24};
25
26/**
27 * A signer object can be used to generate an auth token to a database.
28 */
29AWS.RDS.Signer = AWS.util.inherit({
30 /**
31 * Creates a signer object can be used to generate an auth token.
32 *
33 * @option options credentials [AWS.Credentials] the AWS credentials
34 * to sign requests with. Uses the default credential provider chain
35 * if not specified.
36 * @option options hostname [String] the hostname of the database to connect to.
37 * @option options port [Number] the port number the database is listening on.
38 * @option options region [String] the region the database is located in.
39 * @option options username [String] the username to login as.
40 * @example Passing in options to constructor
41 * var signer = new AWS.RDS.Signer({
42 * credentials: new AWS.SharedIniFileCredentials({profile: 'default'}),
43 * region: 'us-east-1',
44 * hostname: 'db.us-east-1.rds.amazonaws.com',
45 * port: 8000,
46 * username: 'name'
47 * });
48 */
49 constructor: function Signer(options) {
50 this.options = options || {};
51 },
52
53 /**
54 * @api private
55 * Strips the protocol from a url.
56 */
57 convertUrlToAuthToken: function convertUrlToAuthToken(url) {
58 // we are always using https as the protocol
59 var protocol = 'https://';
60 if (url.indexOf(protocol) === 0) {
61 return url.substring(protocol.length);
62 }
63 },
64
65 /**
66 * @overload getAuthToken(options = {}, [callback])
67 * Generate an auth token to a database.
68 * @note You must ensure that you have static or previously resolved
69 * credentials if you call this method synchronously (with no callback),
70 * otherwise it may not properly sign the request. If you cannot guarantee
71 * this (you are using an asynchronous credential provider, i.e., EC2
72 * IAM roles), you should always call this method with an asynchronous
73 * callback.
74 *
75 * @param options [map] The fields to use when generating an auth token.
76 * Any options specified here will be merged on top of any options passed
77 * to AWS.RDS.Signer:
78 *
79 * * **credentials** (AWS.Credentials) — the AWS credentials
80 * to sign requests with. Uses the default credential provider chain
81 * if not specified.
82 * * **hostname** (String) — the hostname of the database to connect to.
83 * * **port** (Number) — the port number the database is listening on.
84 * * **region** (String) — the region the database is located in.
85 * * **username** (String) — the username to login as.
86 * @return [String] if called synchronously (with no callback), returns the
87 * auth token.
88 * @return [null] nothing is returned if a callback is provided.
89 * @callback callback function (err, token)
90 * If a callback is supplied, it is called when an auth token has been generated.
91 * @param err [Error] the error object returned from the signer.
92 * @param token [String] the auth token.
93 *
94 * @example Generating an auth token synchronously
95 * var signer = new AWS.RDS.Signer({
96 * // configure options
97 * region: 'us-east-1',
98 * username: 'default',
99 * hostname: 'db.us-east-1.amazonaws.com',
100 * port: 8000
101 * });
102 * var token = signer.getAuthToken({
103 * // these options are merged with those defined when creating the signer, overriding in the case of a duplicate option
104 * // credentials are not specified here or when creating the signer, so default credential provider will be used
105 * username: 'test' // overriding username
106 * });
107 * @example Generating an auth token asynchronously
108 * var signer = new AWS.RDS.Signer({
109 * // configure options
110 * region: 'us-east-1',
111 * username: 'default',
112 * hostname: 'db.us-east-1.amazonaws.com',
113 * port: 8000
114 * });
115 * signer.getAuthToken({
116 * // these options are merged with those defined when creating the signer, overriding in the case of a duplicate option
117 * // credentials are not specified here or when creating the signer, so default credential provider will be used
118 * username: 'test' // overriding username
119 * }, function(err, token) {
120 * if (err) {
121 * // handle error
122 * } else {
123 * // use token
124 * }
125 * });
126 *
127 */
128 getAuthToken: function getAuthToken(options, callback) {
129 if (typeof options === 'function' && callback === undefined) {
130 callback = options;
131 options = {};
132 }
133 var self = this;
134 var hasCallback = typeof callback === 'function';
135 // merge options with existing options
136 options = AWS.util.merge(this.options, options);
137 // validate options
138 var optionsValidation = this.validateAuthTokenOptions(options);
139 if (optionsValidation !== true) {
140 if (hasCallback) {
141 return callback(optionsValidation, null);
142 }
143 throw optionsValidation;
144 }
145
146 // 15 minutes
147 var expires = 900;
148 // create service to generate a request from
149 var serviceOptions = {
150 region: options.region,
151 endpoint: new AWS.Endpoint(options.hostname + ':' + options.port),
152 paramValidation: false,
153 signatureVersion: 'v4'
154 };
155 if (options.credentials) {
156 serviceOptions.credentials = options.credentials;
157 }
158 service = new AWS.Service(serviceOptions);
159 // ensure the SDK is using sigv4 signing (config is not enough)
160 service.api = api;
161
162 var request = service.makeRequest();
163 // add listeners to request to properly build auth token
164 this.modifyRequestForAuthToken(request, options);
165
166 if (hasCallback) {
167 request.presign(expires, function(err, url) {
168 if (url) {
169 url = self.convertUrlToAuthToken(url);
170 }
171 callback(err, url);
172 });
173 } else {
174 var url = request.presign(expires);
175 return this.convertUrlToAuthToken(url);
176 }
177 },
178
179 /**
180 * @api private
181 * Modifies a request to allow the presigner to generate an auth token.
182 */
183 modifyRequestForAuthToken: function modifyRequestForAuthToken(request, options) {
184 request.on('build', request.buildAsGet);
185 var httpRequest = request.httpRequest;
186 httpRequest.body = AWS.util.queryParamsToString({
187 Action: 'connect',
188 DBUser: options.username
189 });
190 },
191
192 /**
193 * @api private
194 * Validates that the options passed in contain all the keys with values of the correct type that
195 * are needed to generate an auth token.
196 */
197 validateAuthTokenOptions: function validateAuthTokenOptions(options) {
198 // iterate over all keys in options
199 var message = '';
200 options = options || {};
201 for (var key in requiredAuthTokenOptions) {
202 if (!Object.prototype.hasOwnProperty.call(requiredAuthTokenOptions, key)) {
203 continue;
204 }
205 if (typeof options[key] !== requiredAuthTokenOptions[key]) {
206 message += 'option \'' + key + '\' should have been type \'' + requiredAuthTokenOptions[key] + '\', was \'' + typeof options[key] + '\'.\n';
207 }
208 }
209 if (message.length) {
210 return AWS.util.error(new Error(), {
211 code: 'InvalidParameter',
212 message: message
213 });
214 }
215 return true;
216 }
217});