mojang.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /**
  2. * Mojang
  3. *
  4. * This module serves as a minimal wrapper for Mojang's REST api.
  5. *
  6. * @module mojang
  7. */
  8. // Requirements
  9. const request = require('request')
  10. // Constants
  11. const minecraftAgent = {
  12. name: 'Minecraft',
  13. version: 1
  14. }
  15. const authpath = 'https://authserver.mojang.com'
  16. const statuses = [
  17. {
  18. service: 'minecraft.net',
  19. status: 'grey',
  20. name: 'Minecraft.net'
  21. },
  22. {
  23. service: 'api.mojang.com',
  24. status: 'grey',
  25. name: 'Public API'
  26. },
  27. {
  28. service: 'textures.minecraft.net',
  29. status: 'grey',
  30. name: 'Minecraft Skins'
  31. },
  32. {
  33. service: 'authserver.mojang.com',
  34. status: 'grey',
  35. name: 'Authentication Service'
  36. },
  37. {
  38. service: 'sessionserver.mojang.com',
  39. status: 'grey',
  40. name: 'Multiplayer Session Service'
  41. },
  42. {
  43. service: 'account.mojang.com',
  44. status: 'grey',
  45. name: 'Mojang accounts website'
  46. }
  47. ]
  48. // Functions
  49. /**
  50. * Converts a Mojang status color to a hex value. Valid statuses
  51. * are 'green', 'yellow', 'red', and 'grey'. Grey is a custom status
  52. * to our project which represents an unknown status.
  53. *
  54. * @param {string} status A valid status code.
  55. * @returns {string} The hex color of the status code.
  56. */
  57. exports.statusToHex = function(status){
  58. switch(status.toLowerCase()){
  59. case 'green':
  60. return '#a5c325'
  61. case 'yellow':
  62. return '#eac918'
  63. case 'red':
  64. return '#c32625'
  65. case 'grey':
  66. default:
  67. return '#848484'
  68. }
  69. }
  70. /**
  71. * Retrieves the status of Mojang's services.
  72. * The response is condensed into a single object. Each service is
  73. * a key, where the value is an object containing a status and name
  74. * property.
  75. *
  76. * @see http://wiki.vg/Mojang_API#API_Status
  77. */
  78. exports.status = function(){
  79. return new Promise(function(fulfill, reject) {
  80. request.get('https://status.mojang.com/check',
  81. {
  82. json: true
  83. },
  84. function(error, response, body){
  85. if(error || response.statusCode !== 200){
  86. console.warn('Unable to retrieve Mojang status.')
  87. console.debug('Error while retrieving Mojang statuses:', error)
  88. reject(error || response.statusCode)
  89. } else {
  90. for(let i=0; i<body.length; i++){
  91. const key = Object.keys(body[i])[0]
  92. inner:
  93. for(let j=0; j<statuses.length; j++){
  94. if(statuses[j].service === key) {
  95. statuses[j].status = body[i][key]
  96. break inner
  97. }
  98. }
  99. }
  100. fulfill(statuses)
  101. }
  102. })
  103. })
  104. }
  105. /**
  106. * Authenticate a user with their Mojang credentials.
  107. *
  108. * @param {string} username The user's username, this is often an email.
  109. * @param {string} password The user's password.
  110. * @param {string} clientToken The launcher's Client Token.
  111. * @param {boolean} requestUser Optional. Adds user object to the reponse.
  112. * @param {Object} agent Optional. Provided by default. Adds user info to the response.
  113. *
  114. * @see http://wiki.vg/Authentication#Authenticate
  115. */
  116. exports.authenticate = function(username, password, clientToken, requestUser = true, agent = minecraftAgent){
  117. return new Promise(function(fulfill, reject){
  118. request.post(authpath + '/authenticate',
  119. {
  120. json: true,
  121. body: {
  122. agent,
  123. username,
  124. password,
  125. clientToken,
  126. requestUser
  127. }
  128. },
  129. function(error, response, body){
  130. if(error){
  131. console.error('Error during authentication.', error)
  132. reject(error)
  133. } else {
  134. if(response.statusCode === 200){
  135. fulfill(body)
  136. } else {
  137. reject(body || {code: 'ENOTFOUND'})
  138. }
  139. }
  140. })
  141. })
  142. }
  143. /**
  144. * Validate an access token. This should always be done before launching.
  145. * The client token should match the one used to create the access token.
  146. *
  147. * @param {string} accessToken The access token to validate.
  148. * @param {string} clientToken The launcher's client token.
  149. *
  150. * @see http://wiki.vg/Authentication#Validate
  151. */
  152. exports.validate = function(accessToken, clientToken){
  153. return new Promise(function(fulfill, reject){
  154. request.post(authpath + '/validate',
  155. {
  156. json: true,
  157. body: {
  158. accessToken,
  159. clientToken
  160. }
  161. },
  162. function(error, response, body){
  163. if(error){
  164. console.error('Error during validation.', error)
  165. reject(error)
  166. } else {
  167. if(response.statusCode === 403){
  168. fulfill(false)
  169. } else {
  170. // 204 if valid
  171. fulfill(true)
  172. }
  173. }
  174. })
  175. })
  176. }
  177. /**
  178. * Invalidates an access token. The clientToken must match the
  179. * token used to create the provided accessToken.
  180. *
  181. * @param {string} accessToken The access token to invalidate.
  182. * @param {string} clientToken The launcher's client token.
  183. *
  184. * @see http://wiki.vg/Authentication#Invalidate
  185. */
  186. exports.invalidate = function(accessToken, clientToken){
  187. return new Promise(function(fulfill, reject){
  188. request.post(authpath + '/invalidate',
  189. {
  190. json: true,
  191. body: {
  192. accessToken,
  193. clientToken
  194. }
  195. },
  196. function(error, response, body){
  197. if(error){
  198. console.error('Error during invalidation.', error)
  199. reject(error)
  200. } else {
  201. if(response.statusCode === 204){
  202. fulfill()
  203. } else {
  204. reject(body)
  205. }
  206. }
  207. })
  208. })
  209. }
  210. /**
  211. * Refresh a user's authentication. This should be used to keep a user logged
  212. * in without asking them for their credentials again. A new access token will
  213. * be generated using a recent invalid access token. See Wiki for more info.
  214. *
  215. * @param {string} accessToken The old access token.
  216. * @param {string} clientToken The launcher's client token.
  217. * @param {boolean} requestUser Optional. Adds user object to the reponse.
  218. *
  219. * @see http://wiki.vg/Authentication#Refresh
  220. */
  221. exports.refresh = function(accessToken, clientToken, requestUser = true){
  222. return new Promise(function(fulfill, reject){
  223. request.post(authpath + '/refresh',
  224. {
  225. json: true,
  226. body: {
  227. accessToken,
  228. clientToken,
  229. requestUser
  230. }
  231. },
  232. function(error, response, body){
  233. if(error){
  234. console.error('Error during refresh.', error)
  235. reject(error)
  236. } else {
  237. if(response.statusCode === 200){
  238. fulfill(body)
  239. } else {
  240. reject(body)
  241. }
  242. }
  243. })
  244. })
  245. }