Source: box-node-sdk.ts

  1. /**
  2. * @fileoverview Box SDK for Node.js
  3. */
  4. // ------------------------------------------------------------------------------
  5. // Requirements
  6. // ------------------------------------------------------------------------------
  7. import { EventEmitter } from 'events';
  8. import * as qs from 'querystring';
  9. import CCGAPISession = require('./sessions/ccg-session');
  10. import APIRequestManager = require('./api-request-manager');
  11. import BoxClient = require('./box-client');
  12. import TokenManager = require('./token-manager');
  13. const Config = require('./util/config'),
  14. BasicAPISession = require('./sessions/basic-session'),
  15. PersistentAPISession = require('./sessions/persistent-session'),
  16. AppAuthSession = require('./sessions/app-auth-session'),
  17. Webhooks = require('./managers/webhooks');
  18. // ------------------------------------------------------------------------------
  19. // Typedefs and Callbacks
  20. // ------------------------------------------------------------------------------
  21. /**
  22. * Object representing interface functions for PersistentClient to interact with the consumer app's central storage layer.
  23. * @typedef {Object} TokenStore
  24. * @property {ReadTokenInfoFromStore} read - read TokenInfo from app central store.
  25. * @property {WriteTokenInfoToStore} write - write TokenInfo to the app's central store.
  26. * @property {ClearTokenInfoFromStore} clear - delete TokenInfo from the app's central store.
  27. */
  28. /**
  29. * Acquires TokenInfo from the consumer app's central store.
  30. * @typedef {Function} ReadTokenInfoFromStore
  31. * @param {Function} callback - err if store read issue occurred, otherwise propagates a TokenInfo object
  32. */
  33. /**
  34. * Writes TokenInfo to the consumer app's central store
  35. * @typedef {Function} WriteTokenInfoToStore
  36. * @param {TokenInfo} tokenInfo - the token info to be written
  37. * @param {Function} callback - err if store write issue occurred, otherwise propagates null err
  38. * and null result to indicate success
  39. */
  40. /**
  41. * Clears TokenInfo from the consumer app's central store
  42. * @typedef {Function} ClearTokenInfoFromStore
  43. * @param {Function} callback - err if store delete issue occurred, otherwise propagates null err
  44. * and null result to indicate success
  45. */
  46. type TokenStore = object /* FIXME */;
  47. type UserConfigurationOptions = object /* FIXME */;
  48. type TokenRequestOptions = object /* FIXME */;
  49. type CCGConfig = {
  50. boxSubjectType: "user" | "enterprise",
  51. boxSubjectId: string
  52. }
  53. // ------------------------------------------------------------------------------
  54. // Private
  55. // ------------------------------------------------------------------------------
  56. // ------------------------------------------------------------------------------
  57. // Public
  58. // ------------------------------------------------------------------------------
  59. /**
  60. * A backend NodeJS SDK to interact with the Box V2 API.
  61. * This is the single entry point for all SDK consumer interactions. This is the only file that a 3rd party app
  62. * should require. All other components are private and reached out to via this component.
  63. * 1. Provides getters to spawn client instances for users to interact with the Box API.
  64. * 2. Provides manual capability to acquire tokens via token grant endpoints.
  65. * However, it is recommended to use clients to do this for you.
  66. * 3. Emits notification events about relevant request/response events. Useful for logging Box API interactions.
  67. * Notification events: request retries, exceeding max retries, permanent failures.
  68. *
  69. * @param {UserConfigurationOptions} params User settings used to initialize and customize the SDK
  70. * @constructor
  71. */
  72. class BoxSDKNode extends EventEmitter {
  73. accessLevels: any /* FIXME */;
  74. collaborationRoles: any /* FIXME */;
  75. CURRENT_USER_ID: any /* FIXME */;
  76. config: any /* FIXME */;
  77. _eventBus!: EventEmitter;
  78. requestManager!: APIRequestManager;
  79. tokenManager!: TokenManager;
  80. ccgSession!: CCGAPISession;
  81. /**
  82. * Expose the BoxClient property enumerations to the SDK as a whole. This allows
  83. * the consumer to access and use these values from anywhere in their application
  84. * (like a helper) by requiring the SDK, instead of needing to pass the client.
  85. */
  86. static accessLevels = BoxSDKNode.prototype.accessLevels;
  87. static collaborationRoles = BoxSDKNode.prototype.collaborationRoles;
  88. static CURRENT_USER_ID = BoxSDKNode.prototype.CURRENT_USER_ID;
  89. /**
  90. * Expose Webhooks.validateMessage() to the SDK as a whole. This allows
  91. * the consumer to call BoxSDK.validateWebhookMessage() by just requiring the SDK,
  92. * instead of needing to create a client (which is not needed to validate messages).
  93. */
  94. static validateWebhookMessage = Webhooks.validateMessage;
  95. constructor(params: UserConfigurationOptions) {
  96. super();
  97. const eventBus = new EventEmitter();
  98. const self = this;
  99. eventBus.on('response', function () {
  100. const args: any /* FIXME */ = [].slice.call(arguments);
  101. args.unshift('response');
  102. self.emit.apply(self, args);
  103. });
  104. // Setup the configuration with the given params
  105. this.config = new Config(params);
  106. this._eventBus = eventBus;
  107. this._setup();
  108. }
  109. /**
  110. * Setup the SDK instance by instantiating necessary objects with current
  111. * configuration values.
  112. *
  113. * @returns {void}
  114. * @private
  115. */
  116. _setup() {
  117. // Instantiate the request manager
  118. this.requestManager = new APIRequestManager(this.config, this._eventBus);
  119. // Initialize the rest of the SDK with the given configuration
  120. this.tokenManager = new TokenManager(this.config, this.requestManager);
  121. this.ccgSession = new CCGAPISession(
  122. this.config,
  123. this.tokenManager
  124. );
  125. }
  126. /**
  127. * Gets the BoxSDKNode instance by passing boxAppSettings json downloaded from the developer console.
  128. *
  129. * @param {Object} appConfig boxAppSettings object retrieved from Dev Console.
  130. * @returns {BoxSDKNode} an instance that has been preconfigured with the values from the Dev Console
  131. */
  132. static getPreconfiguredInstance(appConfig: any /* FIXME */) {
  133. if (typeof appConfig.boxAppSettings !== 'object') {
  134. throw new TypeError(
  135. 'Configuration does not include boxAppSettings object.'
  136. );
  137. }
  138. const boxAppSettings = appConfig.boxAppSettings;
  139. const webhooks = appConfig.webhooks;
  140. if (typeof webhooks === 'object') {
  141. Webhooks.setSignatureKeys(webhooks.primaryKey, webhooks.secondaryKey);
  142. }
  143. const params: {
  144. clientID?: string;
  145. clientSecret?: string;
  146. appAuth?: {
  147. keyID?: string;
  148. privateKey?: string;
  149. passphrase?: string;
  150. };
  151. enterpriseID?: string;
  152. } = {};
  153. if (typeof boxAppSettings.clientID === 'string') {
  154. params.clientID = boxAppSettings.clientID;
  155. }
  156. if (typeof boxAppSettings.clientSecret === 'string') {
  157. params.clientSecret = boxAppSettings.clientSecret;
  158. }
  159. // Only try to assign app auth settings if they are present
  160. // Some configurations do not include them (but might include other info, e.g. webhooks)
  161. if (
  162. typeof boxAppSettings.appAuth === 'object' &&
  163. boxAppSettings.appAuth.publicKeyID
  164. ) {
  165. params.appAuth = {
  166. keyID: boxAppSettings.appAuth.publicKeyID, // Assign publicKeyID to keyID
  167. privateKey: boxAppSettings.appAuth.privateKey,
  168. };
  169. const passphrase = boxAppSettings.appAuth.passphrase;
  170. if (typeof passphrase === 'string') {
  171. params.appAuth.passphrase = passphrase;
  172. }
  173. }
  174. if (typeof appConfig.enterpriseID === 'string') {
  175. params.enterpriseID = appConfig.enterpriseID;
  176. }
  177. return new BoxSDKNode(params);
  178. }
  179. /**
  180. * Updates the SDK configuration with new parameters.
  181. *
  182. * @param {UserConfigurationOptions} params User settings
  183. * @returns {void}
  184. */
  185. configure(params: UserConfigurationOptions) {
  186. this.config = this.config.extend(params);
  187. this._setup();
  188. }
  189. /**
  190. * Returns a Box Client with a Basic API Session. The client is able to make requests on behalf of a user.
  191. * A basic session has no access to a user's refresh token. Because of this, once the session's tokens
  192. * expire the client cannot recover and a new session will need to be generated.
  193. *
  194. * @param {string} accessToken A user's Box API access token
  195. * @returns {BoxClient} Returns a new Box Client paired to a new BasicAPISession
  196. */
  197. getBasicClient(accessToken: string) {
  198. const apiSession = new BasicAPISession(accessToken, this.tokenManager);
  199. return new BoxClient(apiSession, this.config, this.requestManager);
  200. }
  201. /**
  202. * Returns a Box Client with a Basic API Session. The client is able to make requests on behalf of a user.
  203. * A basic session has no access to a user's refresh token. Because of this, once the session's tokens
  204. * expire the client cannot recover and a new session will need to be generated.
  205. *
  206. * @param {string} accessToken A user's Box API access token
  207. * @returns {BoxClient} Returns a new Box Client paired to a new BasicAPISession
  208. */
  209. static getBasicClient(accessToken: string) {
  210. return new BoxSDKNode({
  211. clientID: '',
  212. clientSecret: '',
  213. }).getBasicClient(accessToken);
  214. }
  215. /**
  216. * Returns a Box Client with a persistent API session. A persistent API session helps manage the user's tokens,
  217. * and can refresh them automatically if the access token expires. If a central data-store is given, the session
  218. * can read & write tokens to it.
  219. *
  220. * NOTE: If tokenInfo or tokenStore are formatted incorrectly, this method will throw an error. If you
  221. * haven't explicitly created either of these objects or are otherwise not completly confident in their validity,
  222. * you should wrap your call to getPersistentClient in a try-catch to handle any potential errors.
  223. *
  224. * @param {TokenInfo} tokenInfo A tokenInfo object to use for authentication
  225. * @param {TokenStore} [tokenStore] An optional token store for reading/writing tokens to session
  226. * @returns {BoxClient} Returns a new Box Client paired to a new PersistentAPISession
  227. */
  228. getPersistentClient(tokenInfo: any /* FIXME */, tokenStore?: TokenStore) {
  229. const apiSession = new PersistentAPISession(
  230. tokenInfo,
  231. tokenStore,
  232. this.config,
  233. this.tokenManager
  234. );
  235. return new BoxClient(apiSession, this.config, this.requestManager);
  236. }
  237. /**
  238. * Returns a Box Client configured to use Client Credentials Grant for a service account. Requires enterprise ID
  239. * to be set when configuring SDK instance.
  240. *
  241. * @returns {BoxClient} Returns a new Box Client paired to a AnonymousAPISession. All Anonymous API Sessions share the
  242. * same tokens, which allows them to refresh them efficiently and reduce load on both the application and
  243. * the API.
  244. */
  245. getAnonymousClient() {
  246. if (!this.config.enterpriseID) {
  247. throw new Error('Enterprise ID must be passed');
  248. }
  249. return this._getCCGClient({boxSubjectType: "enterprise", boxSubjectId: this.config.enterpriseID});
  250. }
  251. /**
  252. * Returns a Box Client configured to use Client Credentials Grant for a specified user.
  253. *
  254. * @param userId the user ID to use when getting the access token
  255. * @returns {BoxClient} Returns a new Box Client paired to a AnonymousAPISession. All Anonymous API Sessions share the
  256. * same tokens, which allows them to refresh them efficiently and reduce load on both the application and
  257. * the API.
  258. */
  259. getCCGClientForUser(userId: string) {
  260. return this._getCCGClient({boxSubjectType: "user", boxSubjectId: userId})
  261. }
  262. _getCCGClient(config: CCGConfig) {
  263. const anonymousTokenManager = new TokenManager(
  264. {
  265. ...this.config,
  266. ...config
  267. },
  268. this.requestManager
  269. );
  270. const newAnonymousSession = new CCGAPISession(
  271. this.config,
  272. anonymousTokenManager
  273. );
  274. return new BoxClient(
  275. newAnonymousSession,
  276. this.config,
  277. this.requestManager
  278. );
  279. }
  280. /**
  281. * Create a new client using App Auth for the given entity. This allows either
  282. * managing App Users (as the enterprise) or performing operations as the App
  283. * Users or Managed Users themselves (as a user).
  284. *
  285. * @param {string} type The type of entity to operate as, "enterprise" or "user"
  286. * @param {string} [id] (Optional) The Box ID of the entity to operate as
  287. * @param {TokenStore} [tokenStore] (Optional) the token store to use for caching tokens
  288. * @returns {BoxClient} A new client authorized as the app user or enterprise
  289. */
  290. getAppAuthClient(type: string, id?: string, tokenStore?: TokenStore) {
  291. if (type === 'enterprise' && !id) {
  292. if (this.config.enterpriseID) {
  293. id = this.config.enterpriseID;
  294. } else {
  295. throw new Error('Enterprise ID must be passed');
  296. }
  297. }
  298. const appAuthSession = new AppAuthSession(
  299. type,
  300. id,
  301. this.config,
  302. this.tokenManager,
  303. tokenStore
  304. );
  305. return new BoxClient(appAuthSession, this.config, this.requestManager);
  306. }
  307. /**
  308. * Generate the URL for the authorize page to send users to for the first leg of
  309. * the OAuth2 flow.
  310. *
  311. * @param {Object} params The OAuth2 parameters
  312. * @returns {string} The authorize page URL
  313. */
  314. getAuthorizeURL(params: { client_id?: string }) {
  315. params.client_id = this.config.clientID;
  316. return `${this.config.authorizeRootURL}/oauth2/authorize?${qs.stringify(
  317. params
  318. )}`;
  319. }
  320. /**
  321. * Acquires token info using an authorization code
  322. *
  323. * @param {string} authorizationCode - authorization code issued by Box
  324. * @param {TokenRequestOptions} [options] - Sets optional behavior for the token grant, null for default behavior
  325. * @param {Function} [callback] - passed a TokenInfo object if tokens were granted successfully
  326. * @returns {Promise<TokenInfo>} Promise resolving to the token info
  327. */
  328. getTokensAuthorizationCodeGrant(
  329. authorizationCode: string,
  330. options?: TokenRequestOptions | null,
  331. callback?: Function
  332. ) {
  333. return this.tokenManager
  334. .getTokensAuthorizationCodeGrant(
  335. authorizationCode,
  336. options as any /* FIXME */
  337. )
  338. .asCallback(callback);
  339. }
  340. /**
  341. * Refreshes the access and refresh tokens for a given refresh token.
  342. *
  343. * @param {string} refreshToken - A valid OAuth refresh token
  344. * @param {TokenRequestOptions} [options] - Sets optional behavior for the token grant, null for default behavior
  345. * @param {Function} [callback] - passed a TokenInfo object if tokens were granted successfully
  346. * @returns {Promise<TokenInfo>} Promise resolving to the token info
  347. */
  348. getTokensRefreshGrant(
  349. refreshToken: string,
  350. options?: TokenRequestOptions | Function | null,
  351. callback?: Function
  352. ) {
  353. if (typeof options === 'function') {
  354. callback = options;
  355. options = null;
  356. }
  357. return this.tokenManager
  358. .getTokensRefreshGrant(refreshToken, options as any /* FIXME */)
  359. .asCallback(callback);
  360. }
  361. /**
  362. * Gets tokens for enterprise administration of app users
  363. * @param {string} enterpriseID The ID of the enterprise to generate a token for
  364. * @param {TokenRequestOptions} [options] - Sets optional behavior for the token grant, null for default behavior
  365. * @param {Function} [callback] Passed the tokens if successful
  366. * @returns {Promise<TokenInfo>} Promise resolving to the token info
  367. */
  368. getEnterpriseAppAuthTokens(
  369. enterpriseID: string,
  370. options?: TokenRequestOptions | Function | null,
  371. callback?: Function
  372. ) {
  373. if (typeof options === 'function') {
  374. callback = options;
  375. options = null;
  376. }
  377. if (!enterpriseID) {
  378. if (this.config.enterpriseID) {
  379. enterpriseID = this.config.enterpriseID;
  380. } else {
  381. throw new Error('Enterprise id must be passed');
  382. }
  383. }
  384. return this.tokenManager
  385. .getTokensJWTGrant('enterprise', enterpriseID, options as any /* FIXME */)
  386. .asCallback(callback);
  387. }
  388. /**
  389. * Gets tokens for App Users via a JWT grant
  390. * @param {string} userID The ID of the App User to generate a token for
  391. * @param {TokenRequestOptions} [options] - Sets optional behavior for the token grant, null for default behavior
  392. * @param {Function} [callback] Passed the tokens if successful
  393. * @returns {Promise<TokentInfo>} Promise resolving to the token info
  394. */
  395. getAppUserTokens(
  396. userID: string,
  397. options?: TokenRequestOptions | Function | null,
  398. callback?: Function
  399. ) {
  400. if (typeof options === 'function') {
  401. callback = options;
  402. options = null;
  403. }
  404. return this.tokenManager
  405. .getTokensJWTGrant('user', userID, options as any /* FIXME */)
  406. .asCallback(callback);
  407. }
  408. /**
  409. * Revokes a token pair associated with a given access or refresh token.
  410. *
  411. * @param {string} token - A valid access or refresh token to revoke
  412. * @param {TokenRequestOptions} [options] - Sets optional behavior for the token grant, null for default behavior
  413. * @param {Function} [callback] - If err, revoke failed. Otherwise, revoke succeeded.
  414. * @returns {Promise<TokenInfo>} Promise resolving to the token info
  415. */
  416. revokeTokens(
  417. token: string,
  418. options?: TokenRequestOptions | Function | null,
  419. callback?: Function
  420. ) {
  421. if (typeof options === 'function') {
  422. callback = options;
  423. options = null;
  424. }
  425. return this.tokenManager
  426. .revokeTokens(token, options as any /* FIXME */)
  427. .asCallback(callback);
  428. }
  429. }
  430. /**
  431. * Expose the BoxClient property enumerations to the SDK as a whole. This allows
  432. * the consumer to access and use these values from anywhere in their application
  433. * (like a helper) by requiring the SDK, instead of needing to pass the client.
  434. */
  435. BoxSDKNode.prototype.accessLevels = BoxClient.prototype.accessLevels;
  436. BoxSDKNode.prototype.collaborationRoles =
  437. BoxClient.prototype.collaborationRoles;
  438. BoxSDKNode.prototype.CURRENT_USER_ID = BoxClient.prototype.CURRENT_USER_ID;
  439. /** @module box-node-sdk/lib/box-node-sdk */
  440. export = BoxSDKNode;