|
@@ -0,0 +1,430 @@
|
|
|
+class ZLLAuth1Frontend {
|
|
|
+ constructor(apiroot, latencyFixTime = 10) {
|
|
|
+ this.latencyFixTime = latencyFixTime
|
|
|
+ if (apiroot.substr(apiroot.length - 1, 1) === "/") {
|
|
|
+ this.apiroot = apiroot
|
|
|
+ } else {
|
|
|
+ this.apiroot = apiroot + "/"
|
|
|
+ }
|
|
|
+ this.cryptReady = false
|
|
|
+ this.axios = require("axios")
|
|
|
+ this.sm2 = require("sm-crypto").sm2
|
|
|
+ this.isLogin = false
|
|
|
+ this.jwt = {
|
|
|
+ token: "",
|
|
|
+ issueTime: 0,
|
|
|
+ expireTime: 0,
|
|
|
+ localTime: 0,
|
|
|
+ ttl: 0,
|
|
|
+ intervalObject: undefined,
|
|
|
+ }
|
|
|
+ this.logoutCallback = undefined
|
|
|
+ this.setFunc = undefined
|
|
|
+ this.clearFunc = undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ UsePersistedStorage(getFunc, setFunc, clearFunc) {
|
|
|
+ this.setFunc = setFunc
|
|
|
+ this.clearFunc = clearFunc
|
|
|
+ var jdata = getFunc()
|
|
|
+ if (jdata) {
|
|
|
+ try {
|
|
|
+ var data = JSON.parse(jdata)
|
|
|
+ if (data.isLogin) {
|
|
|
+ var now = Date.now()
|
|
|
+ var ts = now - data.jwt.localTime
|
|
|
+ var nst = data.jwt.issueTime + ts / 1000 + this.latencyFixTime
|
|
|
+ if (nst >= data.jwt.expireTime) {
|
|
|
+ console.warn("[ZLLAuth1-Frontend] <", Date.now(), "> Jwt in persisted storage is expired.")
|
|
|
+ } else {
|
|
|
+ this.isLogin = true
|
|
|
+ this.jwt.token = data.jwt.token
|
|
|
+ this.jwt.issueTime = data.jwt.issueTime
|
|
|
+ this.jwt.expireTime = data.jwt.expireTime
|
|
|
+ this.jwt.localTime = data.jwt.localTime
|
|
|
+ this.jwt.ttl = data.jwt.ttl
|
|
|
+ if (data.autoRenew) {
|
|
|
+ var remainTime = data.jwt.expireTime - now - latencyFixTime;
|
|
|
+ if (remainTime < data.jwt.ttl / 2) {
|
|
|
+ this.__intervalRenewJwt(this)
|
|
|
+ } else {
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Set login token auto renewing at ", Date.now() + (this.jwt.ttl / 2), ".")
|
|
|
+ this.jwt.intervalObject = setTimeout(this.__intervalRenewJwt, this.jwt.ttl * 500, this)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Can not parse persisted storage data: ", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ __saveToPersistedStorage() {
|
|
|
+ if (this.isLogin) {
|
|
|
+ var autoRenew = false
|
|
|
+ if (this.jwt.intervalObject) {
|
|
|
+ autoRenew = true
|
|
|
+ }
|
|
|
+ var jdata = {
|
|
|
+ isLogin: true,
|
|
|
+ autoRenew: autoRenew,
|
|
|
+ jwt: {
|
|
|
+ token: this.jwt.token,
|
|
|
+ localTime: this.jwt.localTime,
|
|
|
+ issueTime: this.jwt.issueTime,
|
|
|
+ expireTime: this.jwt.expireTime,
|
|
|
+ localTime: this.jwt.localTime,
|
|
|
+ ttl: this.jwt.ttl,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var jsonStr = JSON.stringify(jdata)
|
|
|
+ if (this.setFunc) {
|
|
|
+ this.setFunc(jsonStr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ InitSecure() {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ if (this.cryptReady) {
|
|
|
+ resolve()
|
|
|
+ } else {
|
|
|
+ this.axios.get(this.apiroot + "get_encrypt_info.maki")
|
|
|
+ .then((res) => {
|
|
|
+ if (res.data.encrypt_info) {
|
|
|
+ this.encrypt_info = res.data.encrypt_info
|
|
|
+ this.cryptReady = true
|
|
|
+ resolve()
|
|
|
+ } else {
|
|
|
+ throw new Error("failed get encryption info from server")
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ SetLogoutCallback(callback) {
|
|
|
+ this.logoutCallback = callback
|
|
|
+ }
|
|
|
+
|
|
|
+ ClearLogoutCallback() {
|
|
|
+ this.logoutCallback = undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ LoginWithAutoInitSecure(loginData, autoRenew = false) {
|
|
|
+ this.Logout()
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ if (this.cryptReady) {
|
|
|
+ this.Login(loginData, autoRenew)
|
|
|
+ .then((res) => {
|
|
|
+ resolve(res)
|
|
|
+ }, (err) => {
|
|
|
+ reject(err)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.InitSecure()
|
|
|
+ .then(() => {
|
|
|
+ this.Login(loginData, autoRenew)
|
|
|
+ .then((res) => {
|
|
|
+ resolve(res)
|
|
|
+ }, (err) => {
|
|
|
+ reject(err)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ Login(loginData, autoRenew = false) {
|
|
|
+ this.Logout()
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ if (!this.cryptReady) {
|
|
|
+ throw new Error("zllauthv1 frontend secure module not init")
|
|
|
+ }
|
|
|
+ var jsonStr = JSON.stringify(loginData)
|
|
|
+ var cm = 1;
|
|
|
+ if (this.encrypt_info.cipherMode === "C1C3C2") {
|
|
|
+ cm = 1;
|
|
|
+ } else {
|
|
|
+ cm = 0;
|
|
|
+ }
|
|
|
+ var ct = this.sm2.doEncrypt(jsonStr, this.encrypt_info.pubkey, cm)
|
|
|
+ var fullCt = this.encrypt_info.header + ct
|
|
|
+ var apiData = { login_data: fullCt }
|
|
|
+ this.axios.post(this.apiroot + "login.maki", apiData)
|
|
|
+ .then((res) => {
|
|
|
+ if (res.data.suc) {
|
|
|
+ if (res.data.login_suc) {
|
|
|
+ this.isLogin = true
|
|
|
+ this.jwt.token = res.data.jwt.token
|
|
|
+ this.jwt.localTime = Date.now()
|
|
|
+ this.jwt.issueTime = res.data.jwt.issue_time
|
|
|
+ this.jwt.expireTime = res.data.jwt.expire_time
|
|
|
+ this.jwt.ttl = res.data.jwt.ttl
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Login success.")
|
|
|
+ if (autoRenew) {
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Set login token auto renewing at ", Date.now() + (this.jwt.ttl / 2), ".")
|
|
|
+ this.jwt.intervalObject = setTimeout(this.__intervalRenewJwt, this.jwt.ttl * 500, this)
|
|
|
+ }
|
|
|
+ if (this.setFunc) {
|
|
|
+ this.__saveToPersistedStorage()
|
|
|
+ }
|
|
|
+ resolve()
|
|
|
+ } else {
|
|
|
+ reject(this.__wrapLoginError(res.data))
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ reject({
|
|
|
+ "ecode": "internal_error",
|
|
|
+ "emsg": {
|
|
|
+ "zh_CN": "内部错误(若您是专业人士,需要了解错误细节,详见浏览器脚本控制台)",
|
|
|
+ "en_US": "Internal error (if you want detail, please se browser's script console)"
|
|
|
+ },
|
|
|
+ "raw_response": res.data
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ __wrapLoginError(eresp) {
|
|
|
+ switch (eresp.login_ecode) {
|
|
|
+ case "invalid_username_or_password": {
|
|
|
+ return {
|
|
|
+ "ecode": eresp.ecode,
|
|
|
+ "emsg": {
|
|
|
+ "zh_CN": "用户名或密码错误",
|
|
|
+ "en_US": "Wrong username or password"
|
|
|
+ },
|
|
|
+ "raw_response": eresp
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case "permission_denied": {
|
|
|
+ return {
|
|
|
+ "ecode": eresp.ecode,
|
|
|
+ "emsg": {
|
|
|
+ "zh_CN": "您的权限不足",
|
|
|
+ "en_US": "Permission denied"
|
|
|
+ },
|
|
|
+ "raw_response": eresp
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case "user_banned": {
|
|
|
+ return {
|
|
|
+ "ecode": eresp.ecode,
|
|
|
+ "emsg": {
|
|
|
+ "zh_CN": "该用户已被禁止登录",
|
|
|
+ "en_US": "This user is banned"
|
|
|
+ },
|
|
|
+ "raw_response": eresp
|
|
|
+ }
|
|
|
+ }
|
|
|
+ default: {
|
|
|
+ return {
|
|
|
+ "ecode": "internal_error",
|
|
|
+ "emsg": {
|
|
|
+ "zh_CN": "内部错误(若您是专业人士,需要了解错误细节,详见浏览器脚本控制台)",
|
|
|
+ "en_US": "Internal error (if you want detail, please se browser's script console)"
|
|
|
+ },
|
|
|
+ "raw_response": eresp
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ __intervalRenewJwt(that) {
|
|
|
+ that.jwt.intervalObject = undefined
|
|
|
+ that.RenewJwt(true)
|
|
|
+ .then(() => {
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Renew login token success.")
|
|
|
+ }, (err) => {
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Renew login token fail:", err)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Renew login token fail:", err)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ RenewJwt(autoRenew = false) {
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Try renewing login token...")
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ this.axios.post(this.apiroot + "renew_jwt.maki", { old_jwt: this.jwt.token })
|
|
|
+ .then((res) => {
|
|
|
+ if (res.data.suc) {
|
|
|
+ if (res.data.renew_suc) {
|
|
|
+ this.jwt.token = res.data.jwt.token
|
|
|
+ this.jwt.localTime = Date.now()
|
|
|
+ this.jwt.issueTime = res.data.jwt.issue_time
|
|
|
+ this.jwt.expireTime = res.data.jwt.expire_time
|
|
|
+ this.jwt.ttl = res.data.jwt.ttl
|
|
|
+ if (autoRenew) {
|
|
|
+ console.debug("[ZLLAuth1-Frontend] <", Date.now(), "> Set login token auto renewing at ", Date.now() + (this.jwt.ttl / 2), ".")
|
|
|
+ this.jwt.intervalObject = setTimeout(this.__intervalRenewJwt, this.jwt.ttl * 500, this)
|
|
|
+ }
|
|
|
+ if (this.setFunc) {
|
|
|
+ this.__saveToPersistedStorage()
|
|
|
+ }
|
|
|
+ resolve()
|
|
|
+ } else {
|
|
|
+ this.Logout()
|
|
|
+ reject(res.data)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.Logout()
|
|
|
+ reject(res.data)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ this.Logout()
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ Logout() {
|
|
|
+ if (this.jwt) {
|
|
|
+ if (this.jwt.intervalObject) {
|
|
|
+ clearTimeout(this.jwt.intervalObject)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.isLogin = false
|
|
|
+ this.jwt = {
|
|
|
+ token: "",
|
|
|
+ issueTime: 0,
|
|
|
+ expireTime: 0,
|
|
|
+ localTime: 0,
|
|
|
+ ttl: 0,
|
|
|
+ intervalObject: undefined,
|
|
|
+ }
|
|
|
+ if (this.logoutCallback) {
|
|
|
+ this.logoutCallback()
|
|
|
+ }
|
|
|
+ if (this.clearFunc) {
|
|
|
+ this.clearFunc()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ GetJwt() {
|
|
|
+ if (this.isLogin) {
|
|
|
+ if (this.jwt.token) {
|
|
|
+ var now = Date.now()
|
|
|
+ var ts = now - this.jwt.localTime
|
|
|
+ var nst = this.jwt.issueTime + ts / 1000 + this.latencyFixTime
|
|
|
+ if (nst >= this.jwt.expireTime) {
|
|
|
+ console.warn("[ZLLAuth1-Frontend] <", Date.now(), "> Jwt expired!")
|
|
|
+ this.Logout()
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ errcode: "jwt_expired"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ success: true,
|
|
|
+ token: this.jwt.token,
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ errcode: "not_login"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ errcode: "not_login"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ UnmanagedLogin(loginData) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ if (this.cryptReady) {
|
|
|
+ this.__unmanagedLogin_doLogin(loginData)
|
|
|
+ .then((res) => {
|
|
|
+ resolve(res)
|
|
|
+ }, (err) => {
|
|
|
+ reject(err)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.InitSecure()
|
|
|
+ .then(() => {
|
|
|
+ this.__unmanagedLogin_doLogin(loginData)
|
|
|
+ .then((res) => {
|
|
|
+ resolve(res)
|
|
|
+ }, (err) => {
|
|
|
+ reject(err)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ __unmanagedLogin_doLogin(loginData) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ if (!this.cryptReady) {
|
|
|
+ throw new Error("zllauthv1 frontend secure module not init")
|
|
|
+ }
|
|
|
+ var jsonStr = JSON.stringify(loginData)
|
|
|
+ var cm = 1;
|
|
|
+ if (this.encrypt_info.cipherMode === "C1C3C2") {
|
|
|
+ cm = 1;
|
|
|
+ } else {
|
|
|
+ cm = 0;
|
|
|
+ }
|
|
|
+ var ct = this.sm2.doEncrypt(jsonStr, this.encrypt_info.pubkey, cm)
|
|
|
+ var fullCt = this.encrypt_info.header + ct
|
|
|
+ var apiData = { login_data: fullCt }
|
|
|
+ this.axios.post(this.apiroot + "login.maki", apiData)
|
|
|
+ .then((res) => {
|
|
|
+ if (res.data.suc) {
|
|
|
+ if (res.data.login_suc) {
|
|
|
+ resolve(res.data.jwt)
|
|
|
+ } else {
|
|
|
+ reject(this.__wrapLoginError(res.data))
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ reject({
|
|
|
+ "ecode": "internal_error",
|
|
|
+ "emsg": {
|
|
|
+ "zh_CN": "内部错误(若您是专业人士,需要了解错误细节,详见浏览器脚本控制台)",
|
|
|
+ "en_US": "Internal error (if you want detail, please se browser's script console)"
|
|
|
+ },
|
|
|
+ "raw_response": res.data
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ throw err
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = {
|
|
|
+ ZLLAuth1Frontend
|
|
|
+}
|