zry 1 рік тому
батько
коміт
3345dde413
2 змінених файлів з 431 додано та 0 видалено
  1. 1 0
      .gitignore
  2. 430 0
      index.js

+ 1 - 0
.gitignore

@@ -28,3 +28,4 @@ build/Release
 # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
 node_modules
 
+yarn.lock

+ 430 - 0
index.js

@@ -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
+}