From dd3fb3e9a74e5796b1cb4490b7b939119973bd28 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 24 Feb 2021 23:25:54 +0800 Subject: [PATCH] feat: support user sign up Signed-off-by: Jianhui Zhao --- config/config.go | 47 +++++++++++----------- frontend/src/locales/en.json | 8 +++- frontend/src/locales/zh-CN.json | 8 +++- frontend/src/router/index.ts | 2 +- frontend/src/views/Home.vue | 22 ++++++++++- frontend/src/views/Login.vue | 41 +++++++++++++++---- frontend/vue.config.js | 3 ++ go.mod | 1 + go.sum | 2 + http.go | 70 +++++++++++++++++++++++++++++++-- main.go | 10 ++--- rttys.conf | 7 ++-- 12 files changed, 171 insertions(+), 50 deletions(-) diff --git a/config/config.go b/config/config.go index df69d76..5f568c9 100644 --- a/config/config.go +++ b/config/config.go @@ -12,19 +12,18 @@ import ( // Config struct type Config struct { - AddrDev string - AddrUser string - AddrWeb string - WebRedirURL string - WebPort int - SslCert string - SslKey string - SslCacert string // mTLS for device - HTTPUsername string - HTTPPassword string - Token string - FontSize int - WhiteList map[string]bool + AddrDev string + AddrUser string + AddrWeb string + WebRedirURL string + WebPort int + SslCert string + SslKey string + SslCacert string // mTLS for device + Token string + FontSize int + WhiteList map[string]bool + DB string } func getConfigOpt(yamlCfg *yaml.File, name string, opt interface{}) { @@ -44,16 +43,15 @@ func getConfigOpt(yamlCfg *yaml.File, name string, opt interface{}) { // Parse config func Parse(c *cli.Context) *Config { cfg := &Config{ - AddrDev: c.String("addr-dev"), - AddrUser: c.String("addr-user"), - AddrWeb: c.String("addr-web"), - WebRedirURL: c.String("web-redir-url"), - SslCert: c.String("ssl-cert"), - SslKey: c.String("ssl-key"), - SslCacert: c.String("ssl-cacert"), - HTTPUsername: c.String("http-username"), - HTTPPassword: c.String("http-password"), - Token: c.String("token"), + AddrDev: c.String("addr-dev"), + AddrUser: c.String("addr-user"), + AddrWeb: c.String("addr-web"), + WebRedirURL: c.String("web-redir-url"), + SslCert: c.String("ssl-cert"), + SslKey: c.String("ssl-key"), + SslCacert: c.String("ssl-cacert"), + Token: c.String("token"), + DB: c.String("db"), } cfg.WhiteList = make(map[string]bool) @@ -77,9 +75,8 @@ func Parse(c *cli.Context) *Config { getConfigOpt(yamlCfg, "ssl-cert", &cfg.SslCert) getConfigOpt(yamlCfg, "ssl-key", &cfg.SslKey) getConfigOpt(yamlCfg, "ssl-cacert", &cfg.SslCacert) - getConfigOpt(yamlCfg, "http-username", &cfg.HTTPUsername) - getConfigOpt(yamlCfg, "http-password", &cfg.HTTPPassword) getConfigOpt(yamlCfg, "token", &cfg.Token) + getConfigOpt(yamlCfg, "db", &cfg.DB) getConfigOpt(yamlCfg, "font-size", &cfg.FontSize) val, err := yamlCfg.Get("white-list") diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 7dcff13..d115101 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -4,14 +4,20 @@ "Enter username...": "Enter username...", "Password": "Password", "Enter password...": "Enter password...", - "Login": "Login", + "Sign in": "Sign in", "Reset": "Reset", "username is required": "username is required", + "password is required": "password is required", "Signin Fail! username or password wrong.": "Signin Fail! username or password wrong.", "Refresh List": "Refresh List", "Please enter the filter key...": "Please enter the filter key...", "Execute command": "Execute command", "device-count": "Online Device: {count}", + "Sign out": "Sign out", + "Sign up": "Sign up", + "New to Rttys?": "New to Rttys?", + "Already have an account?":"Already have an account?", + "Sign up Fail.": "Sign up Fail.", "No devices connected": "No devices connected", "Device ID": "Device ID", "Connected time": "Connected time", diff --git a/frontend/src/locales/zh-CN.json b/frontend/src/locales/zh-CN.json index 1abb772..87aab14 100644 --- a/frontend/src/locales/zh-CN.json +++ b/frontend/src/locales/zh-CN.json @@ -4,14 +4,20 @@ "Enter username...": "请输入用户名...", "Password": "密码", "Enter password...": "请输入密码", - "Login": "登录", + "Sign in": "登录", "Reset": "复位", "username is required": "用户名为必填项", + "password is required": "密码为必填项", "Signin Fail! username or password wrong.": "登录失败,用户名或密码错误", "Refresh List": "刷新列表", "Please enter the filter key...": "请输入关键字进行过滤...", "Execute command": "执行命令", "device-count": "在线设备数: {count}", + "Sign out": "退出", + "Sign up": "注册", + "New to Rttys?": "新用户?", + "Already have an account?":"已经有一个账号?", + "Sign up Fail.": "注册失败", "No devices connected": "没有设备连接", "Device ID": "设备ID", "Connected time": "已连接时长", diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 0e86a15..e154a6e 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -42,7 +42,7 @@ router.beforeEach((to, from, next) => { return; } - if (to.path !== '/login' && !sessionStorage.getItem('rtty-sid')) { + if (to.path !== '/login' && !sessionStorage.getItem('rttys-sid')) { router.push('/login'); return; } diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index bc5d56e..99d99bf 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -6,7 +6,18 @@ {{$t('Execute command')}} -
{{ $t('device-count', {count: devlists.length}) }}
+
+ {{ $t('device-count', {count: devlists.length}) }} + + + {{ username }} + + + + {{ $t('Sign out') }} + + +
@@ -160,6 +171,7 @@ } }) export default class Home extends Vue { + username = ''; filterString = ''; loading = true; devlists = []; @@ -207,6 +219,12 @@ return ''; } + handleUserCommand(command: string) { + if (command === 'logout') { + this.$router.push('/login'); + } + } + handleSearch() { this.filtered = this.devlists.filter((d: DeviceInfo) => { const filterString = this.filterString.toLowerCase(); @@ -365,6 +383,8 @@ } mounted() { + this.username = sessionStorage.getItem('rttys-username') || ''; + this.getDevices(); } } diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index c1f8c05..67ef5e0 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -10,10 +10,12 @@ show-password @keyup.enter.native="handleSubmit"/> - {{ $t('Login') }} + {{ signup ? $t('Sign up') : $t('Sign in') }} {{ $t('Reset') }} +

{{ $t('Already have an account?') }}{{ $t('Sign in') }}

+

{{ $t('New to Rttys?') }}{{ $t('Sign up') }}

@@ -23,13 +25,16 @@ @Component export default class Login extends Vue { + signup = false; + formData = { username: '', password: '' }; ruleValidate = { - username: [{required: true, trigger: 'blur', message: ''}] + username: [{required: true, trigger: 'blur', message: ''}], + password: [{required: true, trigger: 'blur', message: ''}] }; handleSubmit() { @@ -39,12 +44,26 @@ username: this.formData.username, password: this.formData.password }; - this.axios.post('/signin', params).then(res => { - sessionStorage.setItem('rtty-sid', res.data); - this.$router.push('/'); - }).catch(() => { - this.$message.error(this.$t('Signin Fail! username or password wrong.').toString()); - }); + + if (this.signup) { + this.axios.post('/signup', params).then(() => { + this.reset(); + this.signup = false; + this.$router.push('/login'); + }).catch(() => { + this.reset(); + this.$message.error(this.$t('Sign up Fail.').toString()); + }); + } else { + this.axios.post('/signin', params).then(res => { + sessionStorage.setItem('rttys-sid', res.data.sid); + sessionStorage.setItem('rttys-username', res.data.username); + sessionStorage.setItem('rttys-admin', res.data.admin); + this.$router.push('/'); + }).catch(() => { + this.$message.error(this.$t('Signin Fail! username or password wrong.').toString()); + }); + } } }); } @@ -55,6 +74,12 @@ mounted() { this.ruleValidate['username'][0].message = this.$t('username is required').toString(); + this.ruleValidate['password'][0].message = this.$t('password is required').toString(); + } + + created() { + this.signup = this.$route.query.signup === '1'; + sessionStorage.removeItem('rttys-sid'); } } diff --git a/frontend/vue.config.js b/frontend/vue.config.js index f502c4f..14a4622 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -21,6 +21,9 @@ module.exports = { '/signin': { target: 'http://127.0.0.1:5913' }, + '/signup': { + target: 'http://127.0.0.1:5913' + }, '/cmd/*': { target: 'http://127.0.0.1:5913' }, diff --git a/go.mod b/go.mod index 949ba2c..30b3690 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/json-iterator/go v1.1.9 github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28 github.com/mattn/go-colorable v0.1.4 + github.com/mattn/go-sqlite3 v1.14.6 github.com/rs/zerolog v1.18.0 github.com/urfave/cli/v2 v2.3.0 golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d diff --git a/go.sum b/go.sum index 9bd9713..7dc01c0 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= diff --git a/http.go b/http.go index 0aed53c..06f9c87 100644 --- a/http.go +++ b/http.go @@ -1,6 +1,7 @@ package main import ( + "database/sql" "embed" "net" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" + _ "github.com/mattn/go-sqlite3" "github.com/rs/zerolog/log" "github.com/zhaojh329/rttys/cache" "github.com/zhaojh329/rttys/config" @@ -33,12 +35,22 @@ func allowOrigin(w http.ResponseWriter) { } func httpLogin(cfg *config.Config, creds *credentials) bool { - if cfg.HTTPUsername != creds.Username { + if creds.Username == "" || creds.Password == "" { return false } - if cfg.HTTPPassword != "" { - return cfg.HTTPPassword == creds.Password + db, err := sql.Open("sqlite3", cfg.DB) + if err != nil { + log.Error().Msg(err.Error()) + return false + } + defer db.Close() + + cnt := 0 + + db.QueryRow("SELECT COUNT(*) FROM account WHERE username = ? AND password = ?", creds.Username, creds.Password).Scan(&cnt) + if cnt == 0 { + return false } return true @@ -69,9 +81,22 @@ func httpAuth(c *gin.Context) bool { return true } +func initDb(cfg *config.Config) { + db, err := sql.Open("sqlite3", cfg.DB) + if err != nil { + log.Error().Msg(err.Error()) + return + } + defer db.Close() + + db.Exec("CREATE TABLE IF NOT EXISTS account(username TEXT PRIMARY KEY NOT NULL, password TEXT NOT NULL)") +} + func httpStart(br *broker) { cfg := br.cfg + initDb(cfg) + httpSessions = cache.New(30*time.Minute, 5*time.Second) gin.SetMode(gin.ReleaseMode) @@ -167,13 +192,50 @@ func httpStart(br *broker) { httpSessions.Set(sid, true, 0) c.SetCookie("sid", sid, 0, "", "", false, true) - c.String(http.StatusOK, sid) + + c.JSON(http.StatusOK, gin.H{ + "sid": sid, + "username": creds.Username, + }) return } c.Status(http.StatusForbidden) }) + r.POST("/signup", func(c *gin.Context) { + var creds credentials + + err := jsoniter.NewDecoder(c.Request.Body).Decode(&creds) + if err != nil { + c.Status(http.StatusBadRequest) + return + } + + db, err := sql.Open("sqlite3", cfg.DB) + if err != nil { + log.Error().Msg(err.Error()) + c.Status(http.StatusInternalServerError) + return + } + defer db.Close() + + _, err = db.Exec("INSERT INTO account values(?,?)", creds.Username, creds.Password) + if err != nil { + log.Error().Msg(err.Error()) + + if strings.Contains(err.Error(), "UNIQUE constraint failed") { + c.Status(http.StatusForbidden) + } else { + c.Status(http.StatusInternalServerError) + } + + return + } + + c.Status(http.StatusOK) + }) + r.NoRoute(func(c *gin.Context) { if !strings.HasPrefix(c.Request.URL.Path, "/frontend/dist/") { c.Request.URL.Path = "/frontend/dist" + c.Request.URL.Path diff --git a/main.go b/main.go index 1c16f29..9c9b006 100644 --- a/main.go +++ b/main.go @@ -18,11 +18,6 @@ func runRttys(c *cli.Context) { cfg := config.Parse(c) - if cfg.HTTPUsername == "" { - fmt.Println("You must configure the http username by commandline or config file") - os.Exit(1) - } - log.Info().Msg("Go Version: " + runtime.Version()) log.Info().Msgf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH) @@ -131,6 +126,11 @@ func main() { Value: "", Usage: "white list(device IDs separated by spaces or *)", }, + &cli.StringFlag{ + Name: "db", + Value: "rttys.db", + Usage: "sqlite3 database path", + }, }, Action: func(c *cli.Context) error { runRttys(c) diff --git a/rttys.conf b/rttys.conf index b474e28..3ffbeec 100644 --- a/rttys.conf +++ b/rttys.conf @@ -3,10 +3,6 @@ #addr-web: :5914 #web-redir-url: -# Auth for http -http-username: rttys -http-password: rttys - #ssl-cert: /etc/rttys/rttys.crt #ssl-key: /etc/rttys/rttys.key @@ -20,3 +16,6 @@ http-password: rttys # http://localhost:5913/connect/rtty1 #white-list: "*" #white-list: rtty1 rtty2 + +# sqlite3 database path +#db: /etc/rttys/rttys.db