mirror of
https://github.com/netfun2000/rttys_zhaojh329.git
synced 2026-02-27 09:53:24 +08:00
feat: support user sign up
Signed-off-by: Jianhui Zhao <zhaojh329@gmail.com>
This commit is contained in:
+22
-25
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "已连接时长",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,18 @@
|
||||
<el-button style="margin-right: 4px;" @click="showCmdForm" type="primary" :disabled="cmdStatus.execing > 0">
|
||||
{{$t('Execute command')}}
|
||||
</el-button>
|
||||
<div style="float: right; color: #3399ff; font-size: 16px">{{ $t('device-count', {count: devlists.length}) }}</div>
|
||||
<div style="float: right; margin-right: 10px">
|
||||
<span style="margin-right: 20px; color: #3399ff; font-size: 24px">{{ $t('device-count', {count: devlists.length}) }}</span>
|
||||
<el-dropdown @command="handleUserCommand">
|
||||
<span class="el-dropdown-link">
|
||||
<span style="color: #3399ff; font-size: 24px">{{ username }}</span>
|
||||
<i class="el-icon-arrow-down el-icon--right" style="color: #3399ff; font-size: 24px"/>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="logout">{{ $t('Sign out') }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="filtered"
|
||||
style="margin-top: 10px; width: 100%" :empty-text="$t('No devices connected')"
|
||||
@selection-change='handleSelection'>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
show-password @keyup.enter.native="handleSubmit"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" style="width: 70%" @click="handleSubmit">{{ $t('Login') }}</el-button>
|
||||
<el-button type="primary" style="width: 70%" @click="handleSubmit">{{ signup ? $t('Sign up') : $t('Sign in') }}</el-button>
|
||||
<el-button type="warning" @click="reset">{{ $t('Reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<p v-if="signup" style="text-align: center">{{ $t('Already have an account?') }}<a href="/login" style="text-decoration: none; color: #1c7cd6">{{ $t('Sign in') }}</a></p>
|
||||
<p v-else style="text-align: center">{{ $t('New to Rttys?') }}<a href="/login?signup=1" style="text-decoration: none; color: #1c7cd6">{{ $t('Sign up') }}</a></p>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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'
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+3
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user