mirror of https://github.com/bjdgyc/anylink.git
commit
730c34b2a4
|
@ -62,7 +62,9 @@ func GroupDetail(w http.ResponseWriter, r *http.Request) {
|
||||||
RespError(w, RespInternalErr, err)
|
RespError(w, RespInternalErr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(data.Auth) == 0 {
|
||||||
|
data.Auth["type"] = "local"
|
||||||
|
}
|
||||||
RespSucess(w, data)
|
RespSucess(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package dbdata
|
package dbdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
@ -32,19 +33,27 @@ type ValData struct {
|
||||||
Note string `json:"note"`
|
Note string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AuthRadius struct {
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
// type Group struct {
|
// type Group struct {
|
||||||
// Id int `json:"id" xorm:"pk autoincr not null"`
|
// Id int `json:"id" xorm:"pk autoincr not null"`
|
||||||
// Name string `json:"name" xorm:"not null unique"`
|
// Name string `json:"name" xorm:"varchar(60) not null unique"`
|
||||||
// Note string `json:"note"`
|
// Note string `json:"note" xorm:"varchar(255)"`
|
||||||
// AllowLan bool `json:"allow_lan"`
|
// AllowLan bool `json:"allow_lan" xorm:"Bool"`
|
||||||
// ClientDns []ValData `json:"client_dns"`
|
// ClientDns []ValData `json:"client_dns" xorm:"Text"`
|
||||||
// RouteInclude []ValData `json:"route_include"`
|
// RouteInclude []ValData `json:"route_include" xorm:"Text"`
|
||||||
// RouteExclude []ValData `json:"route_exclude"`
|
// RouteExclude []ValData `json:"route_exclude" xorm:"Text"`
|
||||||
// LinkAcl []GroupLinkAcl `json:"link_acl"`
|
// DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"`
|
||||||
// Bandwidth int `json:"bandwidth"` // 带宽限制
|
// DsIncludeDomains string `json:"ds_include_domains" xorm:"Text"`
|
||||||
// Status int8 `json:"status"` // 1正常
|
// LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"`
|
||||||
// CreatedAt time.Time `json:"created_at"`
|
// Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制
|
||||||
// UpdatedAt time.Time `json:"updated_at"`
|
// Auth map[string]interface{} `json:"auth" xorm:"not null default '{}' varchar(255)"` // 认证方式
|
||||||
|
// Status int8 `json:"status" xorm:"Int"` // 1正常
|
||||||
|
// CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
|
||||||
|
// UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func GetGroupNames() []string {
|
func GetGroupNames() []string {
|
||||||
|
@ -145,6 +154,24 @@ func SetGroup(g *Group) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("排除域名有误:" + err.Error())
|
return errors.New("排除域名有误:" + err.Error())
|
||||||
}
|
}
|
||||||
|
// 处理认证方式的逻辑
|
||||||
|
defAuth := map[string]interface{}{
|
||||||
|
"type": "local",
|
||||||
|
}
|
||||||
|
if len(g.Auth) == 0 {
|
||||||
|
g.Auth = defAuth
|
||||||
|
}
|
||||||
|
switch g.Auth["type"] {
|
||||||
|
case "local":
|
||||||
|
g.Auth = defAuth
|
||||||
|
case "radius":
|
||||||
|
err = checkRadiusData(g.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("#" + fmt.Sprintf("%s", g.Auth["type"]) + "#未知的认证类型")
|
||||||
|
}
|
||||||
|
|
||||||
g.UpdatedAt = time.Now()
|
g.UpdatedAt = time.Now()
|
||||||
if g.Id > 0 {
|
if g.Id > 0 {
|
||||||
|
@ -167,6 +194,24 @@ func parseIpNet(s string) (string, *net.IPNet, error) {
|
||||||
|
|
||||||
return ipMask, ipNet, nil
|
return ipMask, ipNet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkRadiusData(auth map[string]interface{}) error {
|
||||||
|
radisConf := AuthRadius{}
|
||||||
|
bodyBytes, err := json.Marshal(auth["radius"])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Radius的密钥/服务器地址填写有误")
|
||||||
|
}
|
||||||
|
json.Unmarshal(bodyBytes, &radisConf)
|
||||||
|
if !ValidateIpPort(radisConf.Addr) {
|
||||||
|
return errors.New("Radius的服务器地址填写有误")
|
||||||
|
}
|
||||||
|
// freeradius官网最大8000字符, 这里限制200
|
||||||
|
if len(radisConf.Secret) < 8 || len(radisConf.Secret) > 200 {
|
||||||
|
return errors.New("Radius的密钥长度需在8~200个字符之间")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func CheckDomainNames(domains string) error {
|
func CheckDomainNames(domains string) error {
|
||||||
if domains == "" {
|
if domains == "" {
|
||||||
return nil
|
return nil
|
||||||
|
@ -187,3 +232,8 @@ func ValidateDomainName(domain string) bool {
|
||||||
RegExp := regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+[A-Za-z]{2,18}$`)
|
RegExp := regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+[A-Za-z]{2,18}$`)
|
||||||
return RegExp.MatchString(domain)
|
return RegExp.MatchString(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateIpPort(addr string) bool {
|
||||||
|
RegExp := regexp.MustCompile(`^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\:([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])$$`)
|
||||||
|
return RegExp.MatchString(addr)
|
||||||
|
}
|
||||||
|
|
|
@ -6,20 +6,21 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
Id int `json:"id" xorm:"pk autoincr not null"`
|
Id int `json:"id" xorm:"pk autoincr not null"`
|
||||||
Name string `json:"name" xorm:"varchar(60) not null unique"`
|
Name string `json:"name" xorm:"varchar(60) not null unique"`
|
||||||
Note string `json:"note" xorm:"varchar(255)"`
|
Note string `json:"note" xorm:"varchar(255)"`
|
||||||
AllowLan bool `json:"allow_lan" xorm:"Bool"`
|
AllowLan bool `json:"allow_lan" xorm:"Bool"`
|
||||||
ClientDns []ValData `json:"client_dns" xorm:"Text"`
|
ClientDns []ValData `json:"client_dns" xorm:"Text"`
|
||||||
RouteInclude []ValData `json:"route_include" xorm:"Text"`
|
RouteInclude []ValData `json:"route_include" xorm:"Text"`
|
||||||
RouteExclude []ValData `json:"route_exclude" xorm:"Text"`
|
RouteExclude []ValData `json:"route_exclude" xorm:"Text"`
|
||||||
DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"`
|
DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"`
|
||||||
DsIncludeDomains string `json:"ds_include_domains" xorm:"Text"`
|
DsIncludeDomains string `json:"ds_include_domains" xorm:"Text"`
|
||||||
LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"`
|
LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"`
|
||||||
Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制
|
Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制
|
||||||
Status int8 `json:"status" xorm:"Int"` // 1正常
|
Auth map[string]interface{} `json:"auth" xorm:"not null default '{}' varchar(255)"` // 认证方式
|
||||||
CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
|
Status int8 `json:"status" xorm:"Int"` // 1正常
|
||||||
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
|
CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package dbdata
|
package dbdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -8,6 +10,8 @@ import (
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/pkg/utils"
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
"github.com/xlzd/gotp"
|
"github.com/xlzd/gotp"
|
||||||
|
"layeh.com/radius"
|
||||||
|
"layeh.com/radius/rfc2865"
|
||||||
)
|
)
|
||||||
|
|
||||||
// type User struct {
|
// type User struct {
|
||||||
|
@ -68,6 +72,29 @@ func SetUser(v *User) error {
|
||||||
|
|
||||||
// 验证用户登陆信息
|
// 验证用户登陆信息
|
||||||
func CheckUser(name, pwd, group string) error {
|
func CheckUser(name, pwd, group string) error {
|
||||||
|
// 获取登入的group数据
|
||||||
|
groupData := &Group{}
|
||||||
|
err := One("Name", group, groupData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s %s", name, "No用户组")
|
||||||
|
}
|
||||||
|
// 初始化Auth
|
||||||
|
if len(groupData.Auth) == 0 {
|
||||||
|
groupData.Auth["type"] = "local"
|
||||||
|
}
|
||||||
|
switch groupData.Auth["type"] {
|
||||||
|
case "", "local":
|
||||||
|
return checkLocalUser(name, pwd, group)
|
||||||
|
case "radius":
|
||||||
|
return checkRadiusUser(name, pwd, groupData.Auth)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s %s", name, "无效的认证类型")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证本地用户登陆信息
|
||||||
|
func checkLocalUser(name, pwd, group string) error {
|
||||||
// TODO 严重问题
|
// TODO 严重问题
|
||||||
// return nil
|
// return nil
|
||||||
|
|
||||||
|
@ -108,6 +135,35 @@ func CheckUser(name, pwd, group string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkRadiusUser(name string, pwd string, auth map[string]interface{}) error {
|
||||||
|
if _, ok := auth["radius"]; !ok {
|
||||||
|
fmt.Errorf("%s %s", name, "Radius的radius值不存在")
|
||||||
|
}
|
||||||
|
radiusConf := AuthRadius{}
|
||||||
|
bodyBytes, err := json.Marshal(auth["radius"])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Errorf("%s %s", name, "Radius Marshal出现错误")
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(bodyBytes, &radiusConf)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Errorf("%s %s", name, "Radius Unmarshal出现错误")
|
||||||
|
}
|
||||||
|
// radius认证时,设置超时3秒
|
||||||
|
packet := radius.New(radius.CodeAccessRequest, []byte(radiusConf.Secret))
|
||||||
|
rfc2865.UserName_SetString(packet, name)
|
||||||
|
rfc2865.UserPassword_SetString(packet, pwd)
|
||||||
|
ctx, done := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer done()
|
||||||
|
response, err := radius.Exchange(ctx, packet, radiusConf.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s %s", name, "Radius服务器连接异常, 请检测服务器和端口")
|
||||||
|
}
|
||||||
|
if response.Code != radius.CodeAccessAccept {
|
||||||
|
return fmt.Errorf("%s %s", name, "Radius:用户名或密码错误")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
userOtpMux = sync.Mutex{}
|
userOtpMux = sync.Mutex{}
|
||||||
userOtp = map[string]time.Time{}
|
userOtp = map[string]time.Time{}
|
||||||
|
|
|
@ -25,6 +25,7 @@ require (
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||||
|
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||||
xorm.io/xorm v1.2.2
|
xorm.io/xorm v1.2.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,25 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="认证方式" name="authtype">
|
||||||
|
<el-form-item label="认证" prop="authtype">
|
||||||
|
<el-radio-group v-model="ruleForm.auth.type">
|
||||||
|
<el-radio label="local" border>本地</el-radio>
|
||||||
|
<el-radio label="radius" border>Radius</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Radius密钥" v-if="ruleForm.auth.type == 'radius'">
|
||||||
|
<el-col :span="10">
|
||||||
|
<el-input v-model="ruleForm.auth.radius.secret"></el-input>
|
||||||
|
</el-col>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Radius服务器" v-if="ruleForm.auth.type == 'radius'">
|
||||||
|
<el-col :span="10">
|
||||||
|
<el-input v-model="ruleForm.auth.radius.addr" placeholder="输入IP和端口 192.168.2.1:1812"></el-input>
|
||||||
|
</el-col>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane label="路由设置" name="route">
|
<el-tab-pane label="路由设置" name="route">
|
||||||
<el-form-item label="包含路由" prop="route_include">
|
<el-form-item label="包含路由" prop="route_include">
|
||||||
<el-row class="msg-info">
|
<el-row class="msg-info">
|
||||||
|
@ -300,7 +319,7 @@
|
||||||
<el-button type="primary" @click="submitForm('ruleForm')">保存</el-button>
|
<el-button type="primary" @click="submitForm('ruleForm')">保存</el-button>
|
||||||
<el-button @click="disVisible">取消</el-button>
|
<el-button @click="disVisible">取消</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -318,7 +337,8 @@ export default {
|
||||||
this.$emit('update:route_name', ['用户组信息', '用户组列表'])
|
this.$emit('update:route_name', ['用户组信息', '用户组列表'])
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getData(1)
|
this.getData(1);
|
||||||
|
this.setAuthData();
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -335,21 +355,17 @@ export default {
|
||||||
route_include: [{val: 'all', note: '默认全局代理'}],
|
route_include: [{val: 'all', note: '默认全局代理'}],
|
||||||
route_exclude: [],
|
route_exclude: [],
|
||||||
link_acl: [],
|
link_acl: [],
|
||||||
|
auth : {"type":'local'}
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
name: [
|
name: [
|
||||||
{required: true, message: '请输入用户名', trigger: 'blur'},
|
{required: true, message: '请输入组名', trigger: 'blur'},
|
||||||
{max: 30, message: '长度小于 30 个字符', trigger: 'blur'}
|
{max: 30, message: '长度小于 30 个字符', trigger: 'blur'}
|
||||||
],
|
],
|
||||||
bandwidth: [
|
bandwidth: [
|
||||||
{required: true, message: '请输入用户姓名', trigger: 'blur'},
|
{required: true, message: '请输入带宽限制', trigger: 'blur'},
|
||||||
{type: 'number', message: '年龄必须为数字值'}
|
{type: 'number', message: '带宽限制必须为数字值'}
|
||||||
],
|
],
|
||||||
email: [
|
|
||||||
{required: true, message: '请输入用户邮箱', trigger: 'blur'},
|
|
||||||
{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
|
|
||||||
],
|
|
||||||
|
|
||||||
status: [
|
status: [
|
||||||
{required: true}
|
{required: true}
|
||||||
],
|
],
|
||||||
|
@ -357,6 +373,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
setAuthData(row) {
|
||||||
|
var defAuthData = {"type":'local',
|
||||||
|
"radius":{"addr":"", "secret":""},
|
||||||
|
}
|
||||||
|
if (this.ruleForm.auth.type == "local" || !row) {
|
||||||
|
this.ruleForm.auth = defAuthData;
|
||||||
|
}
|
||||||
|
},
|
||||||
handleDel(row) {
|
handleDel(row) {
|
||||||
axios.post('/group/del?id=' + row.id).then(resp => {
|
axios.post('/group/del?id=' + row.id).then(resp => {
|
||||||
const rdata = resp.data;
|
const rdata = resp.data;
|
||||||
|
@ -378,15 +402,16 @@ export default {
|
||||||
this.activeTab = "general"
|
this.activeTab = "general"
|
||||||
this.user_edit_dialog = true
|
this.user_edit_dialog = true
|
||||||
if (!row) {
|
if (!row) {
|
||||||
|
this.setAuthData(row)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.get('/group/detail', {
|
axios.get('/group/detail', {
|
||||||
params: {
|
params: {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
}
|
}
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
this.ruleForm = resp.data.data
|
this.ruleForm = resp.data.data;
|
||||||
|
this.setAuthData(resp.data.data);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.$message.error('哦,请求出错');
|
this.$message.error('哦,请求出错');
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -431,7 +456,6 @@ export default {
|
||||||
console.log('error submit!!');
|
console.log('error submit!!');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.post('/group/set', this.ruleForm).then(resp => {
|
axios.post('/group/set', this.ruleForm).then(resp => {
|
||||||
const rdata = resp.data;
|
const rdata = resp.data;
|
||||||
if (rdata.code === 0) {
|
if (rdata.code === 0) {
|
||||||
|
|
Loading…
Reference in New Issue