index.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. class ZLLAuth1Frontend {
  2. constructor(apiroot, latencyFixTime = 10) {
  3. this.latencyFixTime = latencyFixTime
  4. if (apiroot.substr(apiroot.length - 1, 1) === "/") {
  5. this.apiroot = apiroot
  6. } else {
  7. this.apiroot = apiroot + "/"
  8. }
  9. this.cryptReady = false
  10. this.axios = require("axios")
  11. this.sm2 = require("sm-crypto").sm2
  12. this.isLogin = false
  13. this.jwt = {
  14. token: "",
  15. issueTime: 0,
  16. expireTime: 0,
  17. localTime: 0,
  18. ttl: 0,
  19. intervalObject: undefined,
  20. }
  21. this.logoutCallback = undefined
  22. this.setFunc = undefined
  23. this.clearFunc = undefined
  24. }
  25. UsePersistedStorage(getFunc, setFunc, clearFunc) {
  26. this.setFunc = setFunc
  27. this.clearFunc = clearFunc
  28. var jdata = getFunc()
  29. if (jdata) {
  30. try {
  31. var data = JSON.parse(jdata)
  32. if (data.isLogin) {
  33. var now = Date.now()
  34. var ts = now - data.jwt.localTime
  35. var nst = data.jwt.issueTime + ts / 1000 + this.latencyFixTime
  36. if (nst >= data.jwt.expireTime) {
  37. console.warn("[ZLLAuth1-Frontend] <", Date.now(), "> Jwt in persisted storage is expired.")
  38. } else {
  39. this.isLogin = true
  40. this.jwt.token = data.jwt.token
  41. this.jwt.issueTime = data.jwt.issueTime
  42. this.jwt.expireTime = data.jwt.expireTime
  43. this.jwt.localTime = data.jwt.localTime
  44. this.jwt.ttl = data.jwt.ttl
  45. if (data.autoRenew) {
  46. var remainTime = data.jwt.expireTime - now - latencyFixTime;
  47. if (remainTime < data.jwt.ttl / 2) {
  48. this.__intervalRenewJwt(this)
  49. } else {
  50. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Set login token auto renewing at ", Date.now() + (this.jwt.ttl / 2), ".")
  51. this.jwt.intervalObject = setTimeout(this.__intervalRenewJwt, this.jwt.ttl * 500, this)
  52. }
  53. }
  54. }
  55. }
  56. } catch (err) {
  57. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Can not parse persisted storage data: ", err)
  58. }
  59. }
  60. }
  61. __saveToPersistedStorage() {
  62. if (this.isLogin) {
  63. var autoRenew = false
  64. if (this.jwt.intervalObject) {
  65. autoRenew = true
  66. }
  67. var jdata = {
  68. isLogin: true,
  69. autoRenew: autoRenew,
  70. jwt: {
  71. token: this.jwt.token,
  72. localTime: this.jwt.localTime,
  73. issueTime: this.jwt.issueTime,
  74. expireTime: this.jwt.expireTime,
  75. localTime: this.jwt.localTime,
  76. ttl: this.jwt.ttl,
  77. }
  78. }
  79. var jsonStr = JSON.stringify(jdata)
  80. if (this.setFunc) {
  81. this.setFunc(jsonStr)
  82. }
  83. }
  84. }
  85. InitSecure() {
  86. return new Promise((resolve) => {
  87. if (this.cryptReady) {
  88. resolve()
  89. } else {
  90. this.axios.get(this.apiroot + "get_encrypt_info.maki")
  91. .then((res) => {
  92. if (res.data.encrypt_info) {
  93. this.encrypt_info = res.data.encrypt_info
  94. this.cryptReady = true
  95. resolve()
  96. } else {
  97. throw new Error("failed get encryption info from server")
  98. }
  99. })
  100. .catch((err) => {
  101. throw err
  102. })
  103. }
  104. })
  105. }
  106. SetLogoutCallback(callback) {
  107. this.logoutCallback = callback
  108. }
  109. ClearLogoutCallback() {
  110. this.logoutCallback = undefined
  111. }
  112. LoginWithAutoInitSecure(loginData, autoRenew = false) {
  113. this.Logout()
  114. return new Promise((resolve, reject) => {
  115. if (this.cryptReady) {
  116. this.Login(loginData, autoRenew)
  117. .then((res) => {
  118. resolve(res)
  119. }, (err) => {
  120. reject(err)
  121. })
  122. .catch((err) => {
  123. throw err
  124. })
  125. } else {
  126. this.InitSecure()
  127. .then(() => {
  128. this.Login(loginData, autoRenew)
  129. .then((res) => {
  130. resolve(res)
  131. }, (err) => {
  132. reject(err)
  133. })
  134. .catch((err) => {
  135. throw err
  136. })
  137. })
  138. .catch((err) => {
  139. throw err
  140. })
  141. }
  142. })
  143. }
  144. Login(loginData, autoRenew = false) {
  145. this.Logout()
  146. return new Promise((resolve, reject) => {
  147. if (!this.cryptReady) {
  148. throw new Error("zllauthv1 frontend secure module not init")
  149. }
  150. var jsonStr = JSON.stringify(loginData)
  151. var cm = 1;
  152. if (this.encrypt_info.cipherMode === "C1C3C2") {
  153. cm = 1;
  154. } else {
  155. cm = 0;
  156. }
  157. var ct = this.sm2.doEncrypt(jsonStr, this.encrypt_info.pubkey, cm)
  158. var fullCt = this.encrypt_info.header + ct
  159. var apiData = { login_data: fullCt }
  160. this.axios.post(this.apiroot + "login.maki", apiData)
  161. .then((res) => {
  162. if (res.data.suc) {
  163. if (res.data.login_suc) {
  164. this.isLogin = true
  165. this.jwt.token = res.data.jwt.token
  166. this.jwt.localTime = Date.now()
  167. this.jwt.issueTime = res.data.jwt.issue_time
  168. this.jwt.expireTime = res.data.jwt.expire_time
  169. this.jwt.ttl = res.data.jwt.ttl
  170. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Login success.")
  171. if (autoRenew) {
  172. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Set login token auto renewing at ", Date.now() + (this.jwt.ttl / 2), ".")
  173. this.jwt.intervalObject = setTimeout(this.__intervalRenewJwt, this.jwt.ttl * 500, this)
  174. }
  175. if (this.setFunc) {
  176. this.__saveToPersistedStorage()
  177. }
  178. resolve()
  179. } else {
  180. reject(this.__wrapLoginError(res.data))
  181. }
  182. } else {
  183. reject({
  184. "ecode": "internal_error",
  185. "emsg": {
  186. "zh_CN": "内部错误(若您是专业人士,需要了解错误细节,详见浏览器脚本控制台)",
  187. "en_US": "Internal error (if you want detail, please se browser's script console)"
  188. },
  189. "raw_response": res.data
  190. })
  191. }
  192. })
  193. .catch((err) => {
  194. throw err
  195. })
  196. })
  197. }
  198. DoOtherEncryptedRequestWithAutoInitSecure(reqArgsData, url) {
  199. return new Promise((resolve, reject) => {
  200. if (this.cryptReady) {
  201. this.DoOtherEncryptedRequest(reqArgsData, url)
  202. .then((res) => {
  203. resolve(res)
  204. }, (err) => {
  205. reject(err)
  206. })
  207. .catch((err) => {
  208. throw err
  209. })
  210. } else {
  211. this.InitSecure()
  212. .then(() => {
  213. this.DoOtherEncryptedRequest(reqArgsData, url)
  214. .then((res) => {
  215. resolve(res)
  216. }, (err) => {
  217. reject(err)
  218. })
  219. .catch((err) => {
  220. throw err
  221. })
  222. })
  223. .catch((err) => {
  224. throw err
  225. })
  226. }
  227. })
  228. }
  229. DoOtherEncryptedRequest(reqArgsData, url) {
  230. return new Promise((resolve, reject) => {
  231. if (!this.cryptReady) {
  232. throw new Error("zllauthv1 frontend secure module not init")
  233. }
  234. var jsonStr = JSON.stringify(reqArgsData)
  235. var cm = 1;
  236. if (this.encrypt_info.cipherMode === "C1C3C2") {
  237. cm = 1;
  238. } else {
  239. cm = 0;
  240. }
  241. var ct = this.sm2.doEncrypt(jsonStr, this.encrypt_info.pubkey, cm)
  242. var fullCt = this.encrypt_info.header + ct
  243. var apiData = { login_data: fullCt }
  244. this.axios.post(url, apiData)
  245. .then((res) => {
  246. if (res.data.suc) {
  247. if(res.data.data){
  248. resolve(res.data.data)
  249. }else{
  250. reject({
  251. "ecode": "internal_error",
  252. "emsg": {
  253. "zh_CN": "内部错误(若您是专业人士,需要了解错误细节,详见浏览器脚本控制台)",
  254. "en_US": "Internal error (if you want detail, please se browser's script console)"
  255. },
  256. "raw_response": res.data
  257. })
  258. }
  259. } else {
  260. reject({
  261. "ecode": "internal_error",
  262. "emsg": {
  263. "zh_CN": "内部错误(若您是专业人士,需要了解错误细节,详见浏览器脚本控制台)",
  264. "en_US": "Internal error (if you want detail, please se browser's script console)"
  265. },
  266. "raw_response": res.data
  267. })
  268. }
  269. })
  270. .catch((err) => {
  271. throw err
  272. })
  273. })
  274. }
  275. __wrapLoginError(eresp) {
  276. switch (eresp.login_ecode) {
  277. case "invalid_username_or_password": {
  278. return {
  279. "ecode": eresp.ecode,
  280. "emsg": {
  281. "zh_CN": "用户名或密码错误",
  282. "en_US": "Wrong username or password"
  283. },
  284. "raw_response": eresp
  285. }
  286. }
  287. case "permission_denied": {
  288. return {
  289. "ecode": eresp.ecode,
  290. "emsg": {
  291. "zh_CN": "您的权限不足",
  292. "en_US": "Permission denied"
  293. },
  294. "raw_response": eresp
  295. }
  296. }
  297. case "user_banned": {
  298. return {
  299. "ecode": eresp.ecode,
  300. "emsg": {
  301. "zh_CN": "该用户已被禁止登录",
  302. "en_US": "This user is banned"
  303. },
  304. "raw_response": eresp
  305. }
  306. }
  307. default: {
  308. return {
  309. "ecode": "internal_error",
  310. "emsg": {
  311. "zh_CN": "内部错误(若您是专业人士,需要了解错误细节,详见浏览器脚本控制台)",
  312. "en_US": "Internal error (if you want detail, please se browser's script console)"
  313. },
  314. "raw_response": eresp
  315. }
  316. }
  317. }
  318. }
  319. __intervalRenewJwt(that) {
  320. that.jwt.intervalObject = undefined
  321. that.RenewJwt(true)
  322. .then(() => {
  323. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Renew login token success.")
  324. }, (err) => {
  325. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Renew login token fail:", err)
  326. })
  327. .catch((err) => {
  328. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Renew login token fail:", err)
  329. })
  330. }
  331. RenewJwt(autoRenew = false) {
  332. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Try renewing login token...")
  333. return new Promise((resolve, reject) => {
  334. this.axios.post(this.apiroot + "renew_jwt.maki", { old_jwt: this.jwt.token })
  335. .then((res) => {
  336. if (res.data.suc) {
  337. if (res.data.renew_suc) {
  338. this.jwt.token = res.data.jwt.token
  339. this.jwt.localTime = Date.now()
  340. this.jwt.issueTime = res.data.jwt.issue_time
  341. this.jwt.expireTime = res.data.jwt.expire_time
  342. this.jwt.ttl = res.data.jwt.ttl
  343. if (autoRenew) {
  344. console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Set login token auto renewing at ", Date.now() + (this.jwt.ttl / 2), ".")
  345. this.jwt.intervalObject = setTimeout(this.__intervalRenewJwt, this.jwt.ttl * 500, this)
  346. }
  347. if (this.setFunc) {
  348. this.__saveToPersistedStorage()
  349. }
  350. resolve()
  351. } else {
  352. this.Logout()
  353. reject(res.data)
  354. }
  355. } else {
  356. this.Logout()
  357. reject(res.data)
  358. }
  359. })
  360. .catch((err) => {
  361. this.Logout()
  362. throw err
  363. })
  364. })
  365. }
  366. Logout() {
  367. if (this.jwt) {
  368. if (this.jwt.intervalObject) {
  369. clearTimeout(this.jwt.intervalObject)
  370. }
  371. }
  372. this.isLogin = false
  373. this.jwt = {
  374. token: "",
  375. issueTime: 0,
  376. expireTime: 0,
  377. localTime: 0,
  378. ttl: 0,
  379. intervalObject: undefined,
  380. }
  381. if (this.logoutCallback) {
  382. this.logoutCallback()
  383. }
  384. if (this.clearFunc) {
  385. this.clearFunc()
  386. }
  387. }
  388. GetJwt() {
  389. if (this.isLogin) {
  390. if (this.jwt.token) {
  391. var now = Date.now()
  392. var ts = now - this.jwt.localTime
  393. var nst = this.jwt.issueTime + ts / 1000 + this.latencyFixTime
  394. if (nst >= this.jwt.expireTime) {
  395. console.warn("[ZLLAuth1-Frontend] <", Date.now(), "> Jwt expired!")
  396. this.Logout()
  397. return {
  398. success: false,
  399. errcode: "jwt_expired"
  400. }
  401. }
  402. return {
  403. success: true,
  404. token: this.jwt.token,
  405. }
  406. } else {
  407. return {
  408. success: false,
  409. errcode: "not_login"
  410. }
  411. }
  412. } else {
  413. return {
  414. success: false,
  415. errcode: "not_login"
  416. }
  417. }
  418. }
  419. UnmanagedLogin(loginData) {
  420. return new Promise((resolve, reject) => {
  421. if (this.cryptReady) {
  422. this.__unmanagedLogin_doLogin(loginData)
  423. .then((res) => {
  424. resolve(res)
  425. }, (err) => {
  426. reject(err)
  427. })
  428. .catch((err) => {
  429. throw err
  430. })
  431. } else {
  432. this.InitSecure()
  433. .then(() => {
  434. this.__unmanagedLogin_doLogin(loginData)
  435. .then((res) => {
  436. resolve(res)
  437. }, (err) => {
  438. reject(err)
  439. })
  440. .catch((err) => {
  441. throw err
  442. })
  443. })
  444. .catch((err) => {
  445. throw err
  446. })
  447. }
  448. })
  449. }
  450. __unmanagedLogin_doLogin(loginData) {
  451. return new Promise((resolve, reject) => {
  452. if (!this.cryptReady) {
  453. throw new Error("zllauthv1 frontend secure module not init")
  454. }
  455. var jsonStr = JSON.stringify(loginData)
  456. var cm = 1;
  457. if (this.encrypt_info.cipherMode === "C1C3C2") {
  458. cm = 1;
  459. } else {
  460. cm = 0;
  461. }
  462. var ct = this.sm2.doEncrypt(jsonStr, this.encrypt_info.pubkey, cm)
  463. var fullCt = this.encrypt_info.header + ct
  464. var apiData = { login_data: fullCt }
  465. this.axios.post(this.apiroot + "login.maki", apiData)
  466. .then((res) => {
  467. if (res.data.suc) {
  468. if (res.data.login_suc) {
  469. resolve(res.data.jwt)
  470. } else {
  471. reject(this.__wrapLoginError(res.data))
  472. }
  473. } else {
  474. reject({
  475. "ecode": "internal_error",
  476. "emsg": {
  477. "zh_CN": "内部错误(若您是专业人士,需要了解错误细节,详见浏览器脚本控制台)",
  478. "en_US": "Internal error (if you want detail, please se browser's script console)"
  479. },
  480. "raw_response": res.data
  481. })
  482. }
  483. })
  484. .catch((err) => {
  485. throw err
  486. })
  487. })
  488. }
  489. }
  490. module.exports = {
  491. ZLLAuth1Frontend
  492. }