UNPKG

13.3 kBJavaScriptView Raw
1var AWS = require('../core');
2var CognitoIdentity = require('../../clients/cognitoidentity');
3var 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 */
61AWS.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});