1 | var AWS = require('../core');
|
2 | var CognitoIdentity = require('../../clients/cognitoidentity');
|
3 | var STS = require('../../clients/sts');
|
4 |
|
5 | /**
|
6 | * Represents credentials retrieved from STS Web Identity Federation using
|
7 | * the Amazon Cognito Identity service.
|
8 | *
|
9 | * By default this provider gets credentials using the
|
10 | * {AWS.CognitoIdentity.getCredentialsForIdentity} service operation, which
|
11 | * requires either an `IdentityId` or an `IdentityPoolId` (Amazon Cognito
|
12 | * Identity Pool ID), which is used to call {AWS.CognitoIdentity.getId} to
|
13 | * obtain an `IdentityId`. If the identity or identity pool is not configured in
|
14 | * the Amazon Cognito Console to use IAM roles with the appropriate permissions,
|
15 | * then additionally a `RoleArn` is required containing the ARN of the IAM trust
|
16 | * policy for the Amazon Cognito role that the user will log into. If a `RoleArn`
|
17 | * is provided, then this provider gets credentials using the
|
18 | * {AWS.STS.assumeRoleWithWebIdentity} service operation, after first getting an
|
19 | * Open ID token from {AWS.CognitoIdentity.getOpenIdToken}.
|
20 | *
|
21 | * In addition, if this credential provider is used to provide authenticated
|
22 | * login, the `Logins` map may be set to the tokens provided by the respective
|
23 | * identity providers. See {constructor} for an example on creating a credentials
|
24 | * object with proper property values.
|
25 | *
|
26 | * ## Refreshing Credentials from Identity Service
|
27 | *
|
28 | * In addition to AWS credentials expiring after a given amount of time, the
|
29 | * login token from the identity provider will also expire. Once this token
|
30 | * expires, it will not be usable to refresh AWS credentials, and another
|
31 | * token will be needed. The SDK does not manage refreshing of the token value,
|
32 | * but this can be done through a "refresh token" supported by most identity
|
33 | * providers. Consult the documentation for the identity provider for refreshing
|
34 | * tokens. Once the refreshed token is acquired, you should make sure to update
|
35 | * this new token in the credentials object's {params} property. The following
|
36 | * code will update the WebIdentityToken, assuming you have retrieved an updated
|
37 | * token from the identity provider:
|
38 | *
|
39 | * ```javascript
|
40 | * AWS.config.credentials.params.Logins['graph.facebook.com'] = updatedToken;
|
41 | * ```
|
42 | *
|
43 | * Future calls to `credentials.refresh()` will now use the new token.
|
44 | *
|
45 | * @!attribute params
|
46 | * @return [map] the map of params passed to
|
47 | * {AWS.CognitoIdentity.getId},
|
48 | * {AWS.CognitoIdentity.getOpenIdToken}, and
|
49 | * {AWS.STS.assumeRoleWithWebIdentity}. To update the token, set the
|
50 | * `params.WebIdentityToken` property.
|
51 | * @!attribute data
|
52 | * @return [map] the raw data response from the call to
|
53 | * {AWS.CognitoIdentity.getCredentialsForIdentity}, or
|
54 | * {AWS.STS.assumeRoleWithWebIdentity}. Use this if you want to get
|
55 | * access to other properties from the response.
|
56 | * @!attribute identityId
|
57 | * @return [String] the Cognito ID returned by the last call to
|
58 | * {AWS.CognitoIdentity.getOpenIdToken}. This ID represents the actual
|
59 | * final resolved identity ID from Amazon Cognito.
|
60 | */
|
61 | AWS.CognitoIdentityCredentials = AWS.util.inherit(AWS.Credentials, {
|
62 | /**
|
63 | * @api private
|
64 | */
|
65 | localStorageKey: {
|
66 | id: 'aws.cognito.identity-id.',
|
67 | providers: 'aws.cognito.identity-providers.'
|
68 | },
|
69 |
|
70 | /**
|
71 | * Creates a new credentials object.
|
72 | * @example Creating a new credentials object
|
73 | * AWS.config.credentials = new AWS.CognitoIdentityCredentials({
|
74 | *
|
75 | * // either IdentityPoolId or IdentityId is required
|
76 | * // See the IdentityPoolId param for AWS.CognitoIdentity.getID (linked below)
|
77 | * // See the IdentityId param for AWS.CognitoIdentity.getCredentialsForIdentity
|
78 | * // or AWS.CognitoIdentity.getOpenIdToken (linked below)
|
79 | * IdentityPoolId: 'us-east-1:1699ebc0-7900-4099-b910-2df94f52a030',
|
80 | * IdentityId: 'us-east-1:128d0a74-c82f-4553-916d-90053e4a8b0f'
|
81 | *
|
82 | * // optional, only necessary when the identity pool is not configured
|
83 | * // to use IAM roles in the Amazon Cognito Console
|
84 | * // See the RoleArn param for AWS.STS.assumeRoleWithWebIdentity (linked below)
|
85 | * RoleArn: 'arn:aws:iam::1234567890:role/MYAPP-CognitoIdentity',
|
86 | *
|
87 | * // optional tokens, used for authenticated login
|
88 | * // See the Logins param for AWS.CognitoIdentity.getID (linked below)
|
89 | * Logins: {
|
90 | * 'graph.facebook.com': 'FBTOKEN',
|
91 | * 'www.amazon.com': 'AMAZONTOKEN',
|
92 | * 'accounts.google.com': 'GOOGLETOKEN',
|
93 | * 'api.twitter.com': 'TWITTERTOKEN',
|
94 | * 'www.digits.com': 'DIGITSTOKEN'
|
95 | * },
|
96 | *
|
97 | * // optional name, defaults to web-identity
|
98 | * // See the RoleSessionName param for AWS.STS.assumeRoleWithWebIdentity (linked below)
|
99 | * RoleSessionName: 'web',
|
100 | *
|
101 | * // optional, only necessary when application runs in a browser
|
102 | * // and multiple users are signed in at once, used for caching
|
103 | * LoginId: 'example@gmail.com'
|
104 | *
|
105 | * }, {
|
106 | * // optionally provide configuration to apply to the underlying service clients
|
107 | * // if configuration is not provided, then configuration will be pulled from AWS.config
|
108 | *
|
109 | * // region should match the region your identity pool is located in
|
110 | * region: 'us-east-1',
|
111 | *
|
112 | * // specify timeout options
|
113 | * httpOptions: {
|
114 | * timeout: 100
|
115 | * }
|
116 | * });
|
117 | * @see AWS.CognitoIdentity.getId
|
118 | * @see AWS.CognitoIdentity.getCredentialsForIdentity
|
119 | * @see AWS.STS.assumeRoleWithWebIdentity
|
120 | * @see AWS.CognitoIdentity.getOpenIdToken
|
121 | * @see AWS.Config
|
122 | * @note If a region is not provided in the global AWS.config, or
|
123 | * specified in the `clientConfig` to the CognitoIdentityCredentials
|
124 | * constructor, you may encounter a 'Missing credentials in config' error
|
125 | * when calling making a service call.
|
126 | */
|
127 | constructor: function CognitoIdentityCredentials(params, clientConfig) {
|
128 | AWS.Credentials.call(this);
|
129 | this.expired = true;
|
130 | this.params = params;
|
131 | this.data = null;
|
132 | this._identityId = null;
|
133 | this._clientConfig = AWS.util.copy(clientConfig || {});
|
134 | this.loadCachedId();
|
135 | var self = this;
|
136 | Object.defineProperty(this, 'identityId', {
|
137 | get: function() {
|
138 | self.loadCachedId();
|
139 | return self._identityId || self.params.IdentityId;
|
140 | },
|
141 | set: function(identityId) {
|
142 | self._identityId = identityId;
|
143 | }
|
144 | });
|
145 | },
|
146 |
|
147 | /**
|
148 | * Refreshes credentials using {AWS.CognitoIdentity.getCredentialsForIdentity},
|
149 | * or {AWS.STS.assumeRoleWithWebIdentity}.
|
150 | *
|
151 | * @callback callback function(err)
|
152 | * Called when the STS service responds (or fails). When
|
153 | * this callback is called with no error, it means that the credentials
|
154 | * information has been loaded into the object (as the `accessKeyId`,
|
155 | * `secretAccessKey`, and `sessionToken` properties).
|
156 | * @param err [Error] if an error occurred, this value will be filled
|
157 | * @see AWS.Credentials.get
|
158 | */
|
159 | refresh: function refresh(callback) {
|
160 | this.coalesceRefresh(callback || AWS.util.fn.callback);
|
161 | },
|
162 |
|
163 | /**
|
164 | * @api private
|
165 | * @param callback
|
166 | */
|
167 | load: function load(callback) {
|
168 | var self = this;
|
169 | self.createClients();
|
170 | self.data = null;
|
171 | self._identityId = null;
|
172 | self.getId(function(err) {
|
173 | if (!err) {
|
174 | if (!self.params.RoleArn) {
|
175 | self.getCredentialsForIdentity(callback);
|
176 | } else {
|
177 | self.getCredentialsFromSTS(callback);
|
178 | }
|
179 | } else {
|
180 | self.clearIdOnNotAuthorized(err);
|
181 | callback(err);
|
182 | }
|
183 | });
|
184 | },
|
185 |
|
186 | /**
|
187 | * Clears the cached Cognito ID associated with the currently configured
|
188 | * identity pool ID. Use this to manually invalidate your cache if
|
189 | * the identity pool ID was deleted.
|
190 | */
|
191 | clearCachedId: function clearCache() {
|
192 | this._identityId = null;
|
193 | delete this.params.IdentityId;
|
194 |
|
195 | var poolId = this.params.IdentityPoolId;
|
196 | var loginId = this.params.LoginId || '';
|
197 | delete this.storage[this.localStorageKey.id + poolId + loginId];
|
198 | delete this.storage[this.localStorageKey.providers + poolId + loginId];
|
199 | },
|
200 |
|
201 | /**
|
202 | * @api private
|
203 | */
|
204 | clearIdOnNotAuthorized: function clearIdOnNotAuthorized(err) {
|
205 | var self = this;
|
206 | if (err.code == 'NotAuthorizedException') {
|
207 | self.clearCachedId();
|
208 | }
|
209 | },
|
210 |
|
211 | /**
|
212 | * Retrieves a Cognito ID, loading from cache if it was already retrieved
|
213 | * on this device.
|
214 | *
|
215 | * @callback callback function(err, identityId)
|
216 | * @param err [Error, null] an error object if the call failed or null if
|
217 | * it succeeded.
|
218 | * @param identityId [String, null] if successful, the callback will return
|
219 | * the Cognito ID.
|
220 | * @note If not loaded explicitly, the Cognito ID is loaded and stored in
|
221 | * localStorage in the browser environment of a device.
|
222 | * @api private
|
223 | */
|
224 | getId: function getId(callback) {
|
225 | var self = this;
|
226 | if (typeof self.params.IdentityId === 'string') {
|
227 | return callback(null, self.params.IdentityId);
|
228 | }
|
229 |
|
230 | self.cognito.getId(function(err, data) {
|
231 | if (!err && data.IdentityId) {
|
232 | self.params.IdentityId = data.IdentityId;
|
233 | callback(null, data.IdentityId);
|
234 | } else {
|
235 | callback(err);
|
236 | }
|
237 | });
|
238 | },
|
239 |
|
240 |
|
241 | /**
|
242 | * @api private
|
243 | */
|
244 | loadCredentials: function loadCredentials(data, credentials) {
|
245 | if (!data || !credentials) return;
|
246 | credentials.expired = false;
|
247 | credentials.accessKeyId = data.Credentials.AccessKeyId;
|
248 | credentials.secretAccessKey = data.Credentials.SecretKey;
|
249 | credentials.sessionToken = data.Credentials.SessionToken;
|
250 | credentials.expireTime = data.Credentials.Expiration;
|
251 | },
|
252 |
|
253 | /**
|
254 | * @api private
|
255 | */
|
256 | getCredentialsForIdentity: function getCredentialsForIdentity(callback) {
|
257 | var self = this;
|
258 | self.cognito.getCredentialsForIdentity(function(err, data) {
|
259 | if (!err) {
|
260 | self.cacheId(data);
|
261 | self.data = data;
|
262 | self.loadCredentials(self.data, self);
|
263 | } else {
|
264 | self.clearIdOnNotAuthorized(err);
|
265 | }
|
266 | callback(err);
|
267 | });
|
268 | },
|
269 |
|
270 | /**
|
271 | * @api private
|
272 | */
|
273 | getCredentialsFromSTS: function getCredentialsFromSTS(callback) {
|
274 | var self = this;
|
275 | self.cognito.getOpenIdToken(function(err, data) {
|
276 | if (!err) {
|
277 | self.cacheId(data);
|
278 | self.params.WebIdentityToken = data.Token;
|
279 | self.webIdentityCredentials.refresh(function(webErr) {
|
280 | if (!webErr) {
|
281 | self.data = self.webIdentityCredentials.data;
|
282 | self.sts.credentialsFrom(self.data, self);
|
283 | }
|
284 | callback(webErr);
|
285 | });
|
286 | } else {
|
287 | self.clearIdOnNotAuthorized(err);
|
288 | callback(err);
|
289 | }
|
290 | });
|
291 | },
|
292 |
|
293 | /**
|
294 | * @api private
|
295 | */
|
296 | loadCachedId: function loadCachedId() {
|
297 | var self = this;
|
298 |
|
299 | // in the browser we source default IdentityId from localStorage
|
300 | if (AWS.util.isBrowser() && !self.params.IdentityId) {
|
301 | var id = self.getStorage('id');
|
302 | if (id && self.params.Logins) {
|
303 | var actualProviders = Object.keys(self.params.Logins);
|
304 | var cachedProviders =
|
305 | (self.getStorage('providers') || '').split(',');
|
306 |
|
307 | // only load ID if at least one provider used this ID before
|
308 | var intersect = cachedProviders.filter(function(n) {
|
309 | return actualProviders.indexOf(n) !== -1;
|
310 | });
|
311 | if (intersect.length !== 0) {
|
312 | self.params.IdentityId = id;
|
313 | }
|
314 | } else if (id) {
|
315 | self.params.IdentityId = id;
|
316 | }
|
317 | }
|
318 | },
|
319 |
|
320 | /**
|
321 | * @api private
|
322 | */
|
323 | createClients: function() {
|
324 | var clientConfig = this._clientConfig;
|
325 | this.webIdentityCredentials = this.webIdentityCredentials ||
|
326 | new AWS.WebIdentityCredentials(this.params, clientConfig);
|
327 | if (!this.cognito) {
|
328 | var cognitoConfig = AWS.util.merge({}, clientConfig);
|
329 | cognitoConfig.params = this.params;
|
330 | this.cognito = new CognitoIdentity(cognitoConfig);
|
331 | }
|
332 | this.sts = this.sts || new STS(clientConfig);
|
333 | },
|
334 |
|
335 | /**
|
336 | * @api private
|
337 | */
|
338 | cacheId: function cacheId(data) {
|
339 | this._identityId = data.IdentityId;
|
340 | this.params.IdentityId = this._identityId;
|
341 |
|
342 | // cache this IdentityId in browser localStorage if possible
|
343 | if (AWS.util.isBrowser()) {
|
344 | this.setStorage('id', data.IdentityId);
|
345 |
|
346 | if (this.params.Logins) {
|
347 | this.setStorage('providers', Object.keys(this.params.Logins).join(','));
|
348 | }
|
349 | }
|
350 | },
|
351 |
|
352 | /**
|
353 | * @api private
|
354 | */
|
355 | getStorage: function getStorage(key) {
|
356 | return this.storage[this.localStorageKey[key] + this.params.IdentityPoolId + (this.params.LoginId || '')];
|
357 | },
|
358 |
|
359 | /**
|
360 | * @api private
|
361 | */
|
362 | setStorage: function setStorage(key, val) {
|
363 | try {
|
364 | this.storage[this.localStorageKey[key] + this.params.IdentityPoolId + (this.params.LoginId || '')] = val;
|
365 | } catch (_) {}
|
366 | },
|
367 |
|
368 | /**
|
369 | * @api private
|
370 | */
|
371 | storage: (function() {
|
372 | try {
|
373 | var storage = AWS.util.isBrowser() && window.localStorage !== null && typeof window.localStorage === 'object' ?
|
374 | window.localStorage : {};
|
375 |
|
376 | // Test set/remove which would throw an error in Safari's private browsing
|
377 | storage['aws.test-storage'] = 'foobar';
|
378 | delete storage['aws.test-storage'];
|
379 |
|
380 | return storage;
|
381 | } catch (_) {
|
382 | return {};
|
383 | }
|
384 | })()
|
385 | });
|