386 lines
13 KiB
JavaScript
386 lines
13 KiB
JavaScript
var AWS = require('../core');
|
|
var CognitoIdentity = require('../../clients/cognitoidentity');
|
|
var STS = require('../../clients/sts');
|
|
|
|
/**
|
|
* Represents credentials retrieved from STS Web Identity Federation using
|
|
* the Amazon Cognito Identity service.
|
|
*
|
|
* By default this provider gets credentials using the
|
|
* {AWS.CognitoIdentity.getCredentialsForIdentity} service operation, which
|
|
* requires either an `IdentityId` or an `IdentityPoolId` (Amazon Cognito
|
|
* Identity Pool ID), which is used to call {AWS.CognitoIdentity.getId} to
|
|
* obtain an `IdentityId`. If the identity or identity pool is not configured in
|
|
* the Amazon Cognito Console to use IAM roles with the appropriate permissions,
|
|
* then additionally a `RoleArn` is required containing the ARN of the IAM trust
|
|
* policy for the Amazon Cognito role that the user will log into. If a `RoleArn`
|
|
* is provided, then this provider gets credentials using the
|
|
* {AWS.STS.assumeRoleWithWebIdentity} service operation, after first getting an
|
|
* Open ID token from {AWS.CognitoIdentity.getOpenIdToken}.
|
|
*
|
|
* In addition, if this credential provider is used to provide authenticated
|
|
* login, the `Logins` map may be set to the tokens provided by the respective
|
|
* identity providers. See {constructor} for an example on creating a credentials
|
|
* object with proper property values.
|
|
*
|
|
* ## Refreshing Credentials from Identity Service
|
|
*
|
|
* In addition to AWS credentials expiring after a given amount of time, the
|
|
* login token from the identity provider will also expire. Once this token
|
|
* expires, it will not be usable to refresh AWS credentials, and another
|
|
* token will be needed. The SDK does not manage refreshing of the token value,
|
|
* but this can be done through a "refresh token" supported by most identity
|
|
* providers. Consult the documentation for the identity provider for refreshing
|
|
* tokens. Once the refreshed token is acquired, you should make sure to update
|
|
* this new token in the credentials object's {params} property. The following
|
|
* code will update the WebIdentityToken, assuming you have retrieved an updated
|
|
* token from the identity provider:
|
|
*
|
|
* ```javascript
|
|
* AWS.config.credentials.params.Logins['graph.facebook.com'] = updatedToken;
|
|
* ```
|
|
*
|
|
* Future calls to `credentials.refresh()` will now use the new token.
|
|
*
|
|
* @!attribute params
|
|
* @return [map] the map of params passed to
|
|
* {AWS.CognitoIdentity.getId},
|
|
* {AWS.CognitoIdentity.getOpenIdToken}, and
|
|
* {AWS.STS.assumeRoleWithWebIdentity}. To update the token, set the
|
|
* `params.WebIdentityToken` property.
|
|
* @!attribute data
|
|
* @return [map] the raw data response from the call to
|
|
* {AWS.CognitoIdentity.getCredentialsForIdentity}, or
|
|
* {AWS.STS.assumeRoleWithWebIdentity}. Use this if you want to get
|
|
* access to other properties from the response.
|
|
* @!attribute identityId
|
|
* @return [String] the Cognito ID returned by the last call to
|
|
* {AWS.CognitoIdentity.getOpenIdToken}. This ID represents the actual
|
|
* final resolved identity ID from Amazon Cognito.
|
|
*/
|
|
AWS.CognitoIdentityCredentials = AWS.util.inherit(AWS.Credentials, {
|
|
/**
|
|
* @api private
|
|
*/
|
|
localStorageKey: {
|
|
id: 'aws.cognito.identity-id.',
|
|
providers: 'aws.cognito.identity-providers.'
|
|
},
|
|
|
|
/**
|
|
* Creates a new credentials object.
|
|
* @example Creating a new credentials object
|
|
* AWS.config.credentials = new AWS.CognitoIdentityCredentials({
|
|
*
|
|
* // either IdentityPoolId or IdentityId is required
|
|
* // See the IdentityPoolId param for AWS.CognitoIdentity.getID (linked below)
|
|
* // See the IdentityId param for AWS.CognitoIdentity.getCredentialsForIdentity
|
|
* // or AWS.CognitoIdentity.getOpenIdToken (linked below)
|
|
* IdentityPoolId: 'us-east-1:1699ebc0-7900-4099-b910-2df94f52a030',
|
|
* IdentityId: 'us-east-1:128d0a74-c82f-4553-916d-90053e4a8b0f'
|
|
*
|
|
* // optional, only necessary when the identity pool is not configured
|
|
* // to use IAM roles in the Amazon Cognito Console
|
|
* // See the RoleArn param for AWS.STS.assumeRoleWithWebIdentity (linked below)
|
|
* RoleArn: 'arn:aws:iam::1234567890:role/MYAPP-CognitoIdentity',
|
|
*
|
|
* // optional tokens, used for authenticated login
|
|
* // See the Logins param for AWS.CognitoIdentity.getID (linked below)
|
|
* Logins: {
|
|
* 'graph.facebook.com': 'FBTOKEN',
|
|
* 'www.amazon.com': 'AMAZONTOKEN',
|
|
* 'accounts.google.com': 'GOOGLETOKEN',
|
|
* 'api.twitter.com': 'TWITTERTOKEN',
|
|
* 'www.digits.com': 'DIGITSTOKEN'
|
|
* },
|
|
*
|
|
* // optional name, defaults to web-identity
|
|
* // See the RoleSessionName param for AWS.STS.assumeRoleWithWebIdentity (linked below)
|
|
* RoleSessionName: 'web',
|
|
*
|
|
* // optional, only necessary when application runs in a browser
|
|
* // and multiple users are signed in at once, used for caching
|
|
* LoginId: 'example@gmail.com'
|
|
*
|
|
* }, {
|
|
* // optionally provide configuration to apply to the underlying service clients
|
|
* // if configuration is not provided, then configuration will be pulled from AWS.config
|
|
*
|
|
* // region should match the region your identity pool is located in
|
|
* region: 'us-east-1',
|
|
*
|
|
* // specify timeout options
|
|
* httpOptions: {
|
|
* timeout: 100
|
|
* }
|
|
* });
|
|
* @see AWS.CognitoIdentity.getId
|
|
* @see AWS.CognitoIdentity.getCredentialsForIdentity
|
|
* @see AWS.STS.assumeRoleWithWebIdentity
|
|
* @see AWS.CognitoIdentity.getOpenIdToken
|
|
* @see AWS.Config
|
|
* @note If a region is not provided in the global AWS.config, or
|
|
* specified in the `clientConfig` to the CognitoIdentityCredentials
|
|
* constructor, you may encounter a 'Missing credentials in config' error
|
|
* when calling making a service call.
|
|
*/
|
|
constructor: function CognitoIdentityCredentials(params, clientConfig) {
|
|
AWS.Credentials.call(this);
|
|
this.expired = true;
|
|
this.params = params;
|
|
this.data = null;
|
|
this._identityId = null;
|
|
this._clientConfig = AWS.util.copy(clientConfig || {});
|
|
this.loadCachedId();
|
|
var self = this;
|
|
Object.defineProperty(this, 'identityId', {
|
|
get: function() {
|
|
self.loadCachedId();
|
|
return self._identityId || self.params.IdentityId;
|
|
},
|
|
set: function(identityId) {
|
|
self._identityId = identityId;
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Refreshes credentials using {AWS.CognitoIdentity.getCredentialsForIdentity},
|
|
* or {AWS.STS.assumeRoleWithWebIdentity}.
|
|
*
|
|
* @callback callback function(err)
|
|
* Called when the STS service responds (or fails). When
|
|
* this callback is called with no error, it means that the credentials
|
|
* information has been loaded into the object (as the `accessKeyId`,
|
|
* `secretAccessKey`, and `sessionToken` properties).
|
|
* @param err [Error] if an error occurred, this value will be filled
|
|
* @see AWS.Credentials.get
|
|
*/
|
|
refresh: function refresh(callback) {
|
|
this.coalesceRefresh(callback || AWS.util.fn.callback);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
* @param callback
|
|
*/
|
|
load: function load(callback) {
|
|
var self = this;
|
|
self.createClients();
|
|
self.data = null;
|
|
self._identityId = null;
|
|
self.getId(function(err) {
|
|
if (!err) {
|
|
if (!self.params.RoleArn) {
|
|
self.getCredentialsForIdentity(callback);
|
|
} else {
|
|
self.getCredentialsFromSTS(callback);
|
|
}
|
|
} else {
|
|
self.clearIdOnNotAuthorized(err);
|
|
callback(err);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Clears the cached Cognito ID associated with the currently configured
|
|
* identity pool ID. Use this to manually invalidate your cache if
|
|
* the identity pool ID was deleted.
|
|
*/
|
|
clearCachedId: function clearCache() {
|
|
this._identityId = null;
|
|
delete this.params.IdentityId;
|
|
|
|
var poolId = this.params.IdentityPoolId;
|
|
var loginId = this.params.LoginId || '';
|
|
delete this.storage[this.localStorageKey.id + poolId + loginId];
|
|
delete this.storage[this.localStorageKey.providers + poolId + loginId];
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
clearIdOnNotAuthorized: function clearIdOnNotAuthorized(err) {
|
|
var self = this;
|
|
if (err.code == 'NotAuthorizedException') {
|
|
self.clearCachedId();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Retrieves a Cognito ID, loading from cache if it was already retrieved
|
|
* on this device.
|
|
*
|
|
* @callback callback function(err, identityId)
|
|
* @param err [Error, null] an error object if the call failed or null if
|
|
* it succeeded.
|
|
* @param identityId [String, null] if successful, the callback will return
|
|
* the Cognito ID.
|
|
* @note If not loaded explicitly, the Cognito ID is loaded and stored in
|
|
* localStorage in the browser environment of a device.
|
|
* @api private
|
|
*/
|
|
getId: function getId(callback) {
|
|
var self = this;
|
|
if (typeof self.params.IdentityId === 'string') {
|
|
return callback(null, self.params.IdentityId);
|
|
}
|
|
|
|
self.cognito.getId(function(err, data) {
|
|
if (!err && data.IdentityId) {
|
|
self.params.IdentityId = data.IdentityId;
|
|
callback(null, data.IdentityId);
|
|
} else {
|
|
callback(err);
|
|
}
|
|
});
|
|
},
|
|
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
loadCredentials: function loadCredentials(data, credentials) {
|
|
if (!data || !credentials) return;
|
|
credentials.expired = false;
|
|
credentials.accessKeyId = data.Credentials.AccessKeyId;
|
|
credentials.secretAccessKey = data.Credentials.SecretKey;
|
|
credentials.sessionToken = data.Credentials.SessionToken;
|
|
credentials.expireTime = data.Credentials.Expiration;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
getCredentialsForIdentity: function getCredentialsForIdentity(callback) {
|
|
var self = this;
|
|
self.cognito.getCredentialsForIdentity(function(err, data) {
|
|
if (!err) {
|
|
self.cacheId(data);
|
|
self.data = data;
|
|
self.loadCredentials(self.data, self);
|
|
} else {
|
|
self.clearIdOnNotAuthorized(err);
|
|
}
|
|
callback(err);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
getCredentialsFromSTS: function getCredentialsFromSTS(callback) {
|
|
var self = this;
|
|
self.cognito.getOpenIdToken(function(err, data) {
|
|
if (!err) {
|
|
self.cacheId(data);
|
|
self.params.WebIdentityToken = data.Token;
|
|
self.webIdentityCredentials.refresh(function(webErr) {
|
|
if (!webErr) {
|
|
self.data = self.webIdentityCredentials.data;
|
|
self.sts.credentialsFrom(self.data, self);
|
|
}
|
|
callback(webErr);
|
|
});
|
|
} else {
|
|
self.clearIdOnNotAuthorized(err);
|
|
callback(err);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
loadCachedId: function loadCachedId() {
|
|
var self = this;
|
|
|
|
// in the browser we source default IdentityId from localStorage
|
|
if (AWS.util.isBrowser() && !self.params.IdentityId) {
|
|
var id = self.getStorage('id');
|
|
if (id && self.params.Logins) {
|
|
var actualProviders = Object.keys(self.params.Logins);
|
|
var cachedProviders =
|
|
(self.getStorage('providers') || '').split(',');
|
|
|
|
// only load ID if at least one provider used this ID before
|
|
var intersect = cachedProviders.filter(function(n) {
|
|
return actualProviders.indexOf(n) !== -1;
|
|
});
|
|
if (intersect.length !== 0) {
|
|
self.params.IdentityId = id;
|
|
}
|
|
} else if (id) {
|
|
self.params.IdentityId = id;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
createClients: function() {
|
|
var clientConfig = this._clientConfig;
|
|
this.webIdentityCredentials = this.webIdentityCredentials ||
|
|
new AWS.WebIdentityCredentials(this.params, clientConfig);
|
|
if (!this.cognito) {
|
|
var cognitoConfig = AWS.util.merge({}, clientConfig);
|
|
cognitoConfig.params = this.params;
|
|
this.cognito = new CognitoIdentity(cognitoConfig);
|
|
}
|
|
this.sts = this.sts || new STS(clientConfig);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
cacheId: function cacheId(data) {
|
|
this._identityId = data.IdentityId;
|
|
this.params.IdentityId = this._identityId;
|
|
|
|
// cache this IdentityId in browser localStorage if possible
|
|
if (AWS.util.isBrowser()) {
|
|
this.setStorage('id', data.IdentityId);
|
|
|
|
if (this.params.Logins) {
|
|
this.setStorage('providers', Object.keys(this.params.Logins).join(','));
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
getStorage: function getStorage(key) {
|
|
return this.storage[this.localStorageKey[key] + this.params.IdentityPoolId + (this.params.LoginId || '')];
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
setStorage: function setStorage(key, val) {
|
|
try {
|
|
this.storage[this.localStorageKey[key] + this.params.IdentityPoolId + (this.params.LoginId || '')] = val;
|
|
} catch (_) {}
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
storage: (function() {
|
|
try {
|
|
var storage = AWS.util.isBrowser() && window.localStorage !== null && typeof window.localStorage === 'object' ?
|
|
window.localStorage : {};
|
|
|
|
// Test set/remove which would throw an error in Safari's private browsing
|
|
storage['aws.test-storage'] = 'foobar';
|
|
delete storage['aws.test-storage'];
|
|
|
|
return storage;
|
|
} catch (_) {
|
|
return {};
|
|
}
|
|
})()
|
|
});
|