cognito_identity_credentials.js 13 KB

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