123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /*!
  2. * Copyright (c) 2015-2016, Okta, Inc. and/or its affiliates. All rights reserved.
  3. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
  4. *
  5. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
  6. * Unless required by applicable law or agreed to in writing, software
  7. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  8. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. *
  10. * See the License for the specific language governing permissions and limitations under the License.
  11. */
  12. /* eslint-disable complexity */
  13. /* eslint-disable max-statements */
  14. require('./vendor/polyfills');
  15. var Q = require('q');
  16. var oauthUtil = require('./oauthUtil');
  17. var util = require('./util');
  18. var tx = require('./tx');
  19. var session = require('./session');
  20. var cookies = require('./cookies');
  21. var token = require('./token');
  22. var AuthSdkError = require('./errors/AuthSdkError');
  23. var config = require('./config');
  24. var TokenManager = require('./TokenManager');
  25. var http = require('./http');
  26. function OktaAuthBuilder(args) {
  27. var sdk = this;
  28. if (!args) {
  29. throw new AuthSdkError('No arguments passed to constructor. ' +
  30. 'Required usage: new OktaAuth(args)');
  31. }
  32. if (!args.url) {
  33. throw new AuthSdkError('No url passed to constructor. ' +
  34. 'Required usage: new OktaAuth({url: "https://sample.okta.com"})');
  35. }
  36. if (args.url.indexOf('-admin.') !== -1) {
  37. throw new AuthSdkError('URL passed to constructor contains "-admin" in subdomain. ' +
  38. 'Required usage: new OktaAuth({url: "https://dev-12345.okta.com})');
  39. }
  40. this.options = {
  41. url: util.removeTrailingSlash(args.url),
  42. clientId: args.clientId,
  43. issuer: util.removeTrailingSlash(args.issuer),
  44. authorizeUrl: util.removeTrailingSlash(args.authorizeUrl),
  45. userinfoUrl: util.removeTrailingSlash(args.userinfoUrl),
  46. redirectUri: args.redirectUri,
  47. ajaxRequest: args.ajaxRequest,
  48. transformErrorXHR: args.transformErrorXHR,
  49. headers: args.headers
  50. };
  51. this.userAgent = 'okta-auth-js-' + config.SDK_VERSION;
  52. // Digital clocks will drift over time, so the server
  53. // can misalign with the time reported by the browser.
  54. // The maxClockSkew allows relaxing the time-based
  55. // validation of tokens (in seconds, not milliseconds).
  56. // It currently defaults to 300, because 5 min is the
  57. // default maximum tolerance allowed by Kerberos.
  58. // (https://technet.microsoft.com/en-us/library/cc976357.aspx)
  59. if (!args.maxClockSkew && args.maxClockSkew !== 0) {
  60. this.options.maxClockSkew = config.DEFAULT_MAX_CLOCK_SKEW;
  61. } else {
  62. this.options.maxClockSkew = args.maxClockSkew;
  63. }
  64. sdk.session = {
  65. close: util.bind(session.closeSession, null, sdk),
  66. exists: util.bind(session.sessionExists, null, sdk),
  67. get: util.bind(session.getSession, null, sdk),
  68. refresh: util.bind(session.refreshSession, null, sdk),
  69. setCookieAndRedirect: util.bind(session.setCookieAndRedirect, null, sdk)
  70. };
  71. sdk.tx = {
  72. status: util.bind(tx.transactionStatus, null, sdk),
  73. resume: util.bind(tx.resumeTransaction, null, sdk),
  74. exists: util.bind(tx.transactionExists, null, sdk)
  75. };
  76. // This is exposed so we can mock document.cookie in our tests
  77. sdk.tx.exists._getCookie = function(name) {
  78. return cookies.getCookie(name);
  79. };
  80. sdk.idToken = {
  81. authorize: util.deprecateWrap('Use token.getWithoutPrompt, token.getWithPopup, or token.getWithRedirect ' +
  82. 'instead of idToken.authorize.', util.bind(token.getToken, null, sdk)),
  83. verify: util.deprecateWrap('Use token.verify instead of idToken.verify', util.bind(token.verifyIdToken, null, sdk)),
  84. refresh: util.deprecateWrap('Use token.refresh instead of idToken.refresh',
  85. util.bind(token.refreshIdToken, null, sdk)),
  86. decode: util.deprecateWrap('Use token.decode instead of idToken.decode', token.decodeToken)
  87. };
  88. // This is exposed so we can mock window.location.href in our tests
  89. sdk.idToken.authorize._getLocationHref = function() {
  90. return window.location.href;
  91. };
  92. sdk.token = {
  93. getWithoutPrompt: util.bind(token.getWithoutPrompt, null, sdk),
  94. getWithPopup: util.bind(token.getWithPopup, null, sdk),
  95. getWithRedirect: util.bind(token.getWithRedirect, null, sdk),
  96. parseFromUrl: util.bind(token.parseFromUrl, null, sdk),
  97. decode: token.decodeToken,
  98. refresh: util.bind(token.refreshToken, null, sdk),
  99. getUserInfo: util.bind(token.getUserInfo, null, sdk),
  100. verify: util.bind(token.verifyToken, null, sdk)
  101. };
  102. // This is exposed so we can set window.location in our tests
  103. sdk.token.getWithRedirect._setLocation = function(url) {
  104. window.location = url;
  105. };
  106. // This is exposed so we can mock getting window.history in our tests
  107. sdk.token.parseFromUrl._getHistory = function() {
  108. return window.history;
  109. };
  110. // This is exposed so we can mock getting window.location in our tests
  111. sdk.token.parseFromUrl._getLocation = function() {
  112. return window.location;
  113. };
  114. // This is exposed so we can mock getting window.document in our tests
  115. sdk.token.parseFromUrl._getDocument = function() {
  116. return window.document;
  117. };
  118. sdk.fingerprint._getUserAgent = function() {
  119. return navigator.userAgent;
  120. };
  121. var isWindowsPhone = /windows phone|iemobile|wpdesktop/i;
  122. sdk.features.isFingerprintSupported = function() {
  123. var agent = sdk.fingerprint._getUserAgent();
  124. return agent && !isWindowsPhone.test(agent);
  125. };
  126. sdk.tokenManager = new TokenManager(sdk, args.tokenManager);
  127. }
  128. var proto = OktaAuthBuilder.prototype;
  129. proto.features = {};
  130. proto.features.isPopupPostMessageSupported = function() {
  131. var isIE8or9 = document.documentMode && document.documentMode < 10;
  132. if (window.postMessage && !isIE8or9) {
  133. return true;
  134. }
  135. return false;
  136. };
  137. proto.features.isTokenVerifySupported = function() {
  138. return typeof crypto !== 'undefined' && crypto.subtle && typeof Uint8Array !== 'undefined';
  139. };
  140. // { username, password, (relayState), (context) }
  141. proto.signIn = function (opts) {
  142. var sdk = this;
  143. opts = util.clone(opts || {});
  144. function postToTransaction(options) {
  145. delete opts.sendFingerprint;
  146. return tx.postToTransaction(sdk, '/api/v1/authn', opts, options);
  147. }
  148. if (!opts.sendFingerprint) {
  149. return postToTransaction();
  150. }
  151. return sdk.fingerprint()
  152. .then(function(fingerprint) {
  153. return postToTransaction({
  154. headers: {
  155. 'X-Device-Fingerprint': fingerprint
  156. }
  157. });
  158. });
  159. };
  160. proto.signOut = function () {
  161. return this.session.close();
  162. };
  163. // { username, (relayState) }
  164. proto.forgotPassword = function (opts) {
  165. return tx.postToTransaction(this, '/api/v1/authn/recovery/password', opts);
  166. };
  167. // { username, (relayState) }
  168. proto.unlockAccount = function (opts) {
  169. return tx.postToTransaction(this, '/api/v1/authn/recovery/unlock', opts);
  170. };
  171. // { recoveryToken }
  172. proto.verifyRecoveryToken = function (opts) {
  173. return tx.postToTransaction(this, '/api/v1/authn/recovery/token', opts);
  174. };
  175. // { resource, (rel), (requestContext)}
  176. proto.webfinger = function (opts) {
  177. var url = '/.well-known/webfinger' + util.toQueryParams(opts);
  178. var options = {
  179. headers: {
  180. 'Accept': 'application/jrd+json'
  181. }
  182. };
  183. return http.get(this, url, options);
  184. };
  185. proto.fingerprint = function(options) {
  186. options = options || {};
  187. var sdk = this;
  188. if (!sdk.features.isFingerprintSupported()) {
  189. return Q.reject(new AuthSdkError('Fingerprinting is not supported on this device'));
  190. }
  191. var deferred = Q.defer();
  192. var iframe = document.createElement('iframe');
  193. iframe.style.display = 'none';
  194. function listener(e) {
  195. if (!e || !e.data || e.origin !== sdk.options.url) {
  196. return;
  197. }
  198. try {
  199. var msg = JSON.parse(e.data);
  200. } catch (err) {
  201. return deferred.reject(new AuthSdkError('Unable to parse iframe response'));
  202. }
  203. if (!msg) { return; }
  204. if (msg.type === 'FingerprintAvailable') {
  205. return deferred.resolve(msg.fingerprint);
  206. }
  207. if (msg.type === 'FingerprintServiceReady') {
  208. e.source.postMessage(JSON.stringify({
  209. type: 'GetFingerprint'
  210. }), e.origin);
  211. }
  212. }
  213. oauthUtil.addListener(window, 'message', listener);
  214. iframe.src = sdk.options.url + '/auth/services/devicefingerprint';
  215. document.body.appendChild(iframe);
  216. var timeout = setTimeout(function() {
  217. deferred.reject(new AuthSdkError('Fingerprinting timed out'));
  218. }, options.timeout || 15000);
  219. return deferred.promise.fin(function() {
  220. clearTimeout(timeout);
  221. oauthUtil.removeListener(window, 'message', listener);
  222. if (document.body.contains(iframe)) {
  223. iframe.parentElement.removeChild(iframe);
  224. }
  225. });
  226. };
  227. module.exports = function(ajaxRequest) {
  228. function OktaAuth(args) {
  229. if (!(this instanceof OktaAuth)) {
  230. return new OktaAuth(args);
  231. }
  232. if (args && !args.ajaxRequest) {
  233. args.ajaxRequest = ajaxRequest;
  234. }
  235. util.bind(OktaAuthBuilder, this)(args);
  236. }
  237. OktaAuth.prototype = OktaAuthBuilder.prototype;
  238. OktaAuth.prototype.constructor = OktaAuth;
  239. return OktaAuth;
  240. };