Merge pull request #354 from bjdgyc/dev

Dev
This commit is contained in:
bjdgyc 2025-02-27 15:21:45 +08:00 committed by GitHub
commit 8e78ef5c94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 1711 additions and 580 deletions

View File

@ -20,7 +20,7 @@ jobs:
- name: Set up Go 1.x - name: Set up Go 1.x
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: '1.20' go-version: '1.22'
go-version-file: 'server/go.mod' go-version-file: 'server/go.mod'
cache-dependency-path: 'server/go.sum' cache-dependency-path: 'server/go.sum'

View File

@ -12,6 +12,10 @@
AnyLink 是一个企业级远程办公 sslvpn 的软件,可以支持多人同时在线使用。 AnyLink 是一个企业级远程办公 sslvpn 的软件,可以支持多人同时在线使用。
使用 AnyLink你可以随时随地安全的访问你的内部网络。
With AnyLink, you can securely access your internal network anytime and anywhere.
## Repo ## Repo
> github: https://github.com/bjdgyc/anylink > github: https://github.com/bjdgyc/anylink
@ -26,7 +30,8 @@ AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannop
AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSA 或 ECC 证书,可以使用私有自签证书,可以通过 Let's Encrypt 和 TrustAsia AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSA 或 ECC 证书,可以使用私有自签证书,可以通过 Let's Encrypt 和 TrustAsia
申请免费的 SSL 证书。 申请免费的 SSL 证书。
AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18、Ubuntu 20、Ubuntu 20、AnolisOS 8 测试通过,如需要安装在其他系统,需要服务端支持
tun/tap
功能、ip 设置命令、iptables命令。 功能、ip 设置命令、iptables命令。
## Screenshot ## Screenshot
@ -160,11 +165,12 @@ sudo ./anylink
> >
> 数据库表结构自动生成,无需手动导入(请赋予 DDL 权限) > 数据库表结构自动生成,无需手动导入(请赋予 DDL 权限)
| db_type | db_source | | db_type | db_source |
|----------|--------------------------------------------------------| |----------|----------------------------------------------------------------------------------------------------------------------|
| sqlite3 | ./conf/anylink.db | | sqlite3 | ./conf/anylink.db |
| mysql | user:password@tcp(127.0.0.1:3306)/anylink?charset=utf8 | | mysql | user:password@tcp(127.0.0.1:3306)/anylink?charset=utf8<br/>user:password@tcp(127.0.0.1:3306)/anylink?charset=utf8mb4 |
| postgres | user:password@localhost/anylink?sslmode=verify-full | | postgres | postgres://user:password@localhost/anylink?sslmode=verify-full |
| mssql | sqlserver://user:password@localhost?database=anylink&connection+timeout=30 |
> 示例配置文件 > 示例配置文件
> >
@ -390,7 +396,17 @@ ipv4_end = "10.1.2.200"
docker run -it --rm bjdgyc/anylink tool -d docker run -it --rm bjdgyc/anylink tool -d
``` ```
6. 启动容器 6. iptables兼容设置
```bash
# 默认 iptables 使用 nf_tables 设置转发规则,如果内核低于 4.19 版本,需要特殊配置
docker run -itd --name anylink --privileged \
-e IPTABLES_LEGACY=on \
-p 443:443 -p 8800:8800 -p 443:443/udp \
--restart=always \
bjdgyc/anylink
```
7. 启动容器
```bash ```bash
# 默认启动 # 默认启动
docker run -itd --name anylink --privileged \ docker run -itd --name anylink --privileged \
@ -410,7 +426,7 @@ ipv4_end = "10.1.2.200"
docker restart anylink docker restart anylink
``` ```
6. 使用自定义参数启动容器 8. 使用自定义参数启动容器
```bash ```bash
# 参数可以参考 ./anylink tool -d # 参数可以参考 ./anylink tool -d
# 可以使用命令行参数 或者 环境变量 配置 # 可以使用命令行参数 或者 环境变量 配置

View File

@ -1,5 +1,7 @@
#!/bin/bash #!/bin/bash
action=$1
ver=$(cat version) ver=$(cat version)
echo $ver echo $ver
@ -18,6 +20,8 @@ docker build -t bjdgyc/anylink:latest --no-cache --progress=plain \
echo "docker tag latest $ver" echo "docker tag latest $ver"
docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver
if [[ $action == "cntest" ]]; then
# docker tag bjdgyc/anylink:$ver registry.cn-hangzhou.aliyuncs.com/bjdgyc/anylink:test-$ver docker tag bjdgyc/anylink:$ver registry.cn-hangzhou.aliyuncs.com/bjdgyc/anylink:test-$ver
# docker push registry.cn-hangzhou.aliyuncs.com/bjdgyc/anylink:test-$ver docker push registry.cn-hangzhou.aliyuncs.com/bjdgyc/anylink:test-$ver
echo registry.cn-hangzhou.aliyuncs.com/bjdgyc/anylink:test-$ver
fi

View File

@ -12,6 +12,7 @@ services:
- 443:443/udp - 443:443/udp
environment: environment:
LINK_LOG_LEVEL: info LINK_LOG_LEVEL: info
#IPTABLES_LEGACY: "on"
command: command:
- --conf=/app/conf/server.toml - --conf=/app/conf/server.toml
#volumes: #volumes:

View File

@ -12,45 +12,50 @@
> >
> 需要展示主页的同学可以在QQ群 直接联系我添加。 > 需要展示主页的同学可以在QQ群 直接联系我添加。
| 昵称 | 主页 / 联系方式 | | 昵称 | 主页 / 联系方式 |
|-------------|------------------------------| |--------------------|------------------------------|
| 代码 oo8 | | | 代码 oo8 | |
| 甘磊 | https://github.com/ganlei333 | | 甘磊 | https://github.com/ganlei333 |
| Oo@ | https://github.com/chooop | | Oo@ | https://github.com/chooop |
| 虚极静笃 | | | 虚极静笃 | |
| 请喝可乐 | | | 请喝可乐 | |
| 加油加油 | | | 加油加油 | |
| 李建 | | | 李建 | |
| lanbin | | | lanbin | |
| 乐在东途 | | | 乐在东途 | |
| 孤鸿 | | | 孤鸿 | |
| 刘国华 | | | 刘国华 | |
| 改名好无聊 | | | 改名好无聊 | |
| 全能互联网专家 | | | 全能互联网专家 | |
| JCM | | | JCM | |
| Eh... | | | Eh... | |
| 沉 | | | 沉 | |
| 刘国华 | | | 刘国华 | |
| 忧郁的豚骨拉面 | | | 忧郁的豚骨拉面 | |
| 张小旋当爹地 | | | 张小旋当爹地 | |
| 对方正在输入 | | | 对方正在输入 | |
| Ronny | | | Ronny | |
| 奔跑的少年 | | | 奔跑的少年 | |
| ZBW | | | ZBW | |
| 悲鸣 | | | 悲鸣 | |
| 谢谢 | | | 谢谢 | |
| 云思科技 | | | 云思科技 | |
| 哆啦A伟(张佳伟) | | | 哆啦A伟(张佳伟) | |
| 人类的悲欢并不相通 | | | 人类的悲欢并不相通 | |
| 做人要低调 | | | 做人要低调 | |
| 洛洛 | | | 洛洛 | |
| Dragon Liao | | | Dragon Liao | |
| 诸葛御风 | | | 诸葛御风 | |
| 杨杨得亿 | | | 杨杨得亿 | |
| Thanataos | | | Thanataos | |
| 憨大叔 | | | 憨大叔 | |
| 明月 | | | 明月 | |
| Amis | | | Amis | |
| Blake | |
| 刘国华 | |
| ZBW | |
| 全能互联网专家 | |
| 广播.会议.音响.无纸化.物联网中控 | |

View File

@ -117,3 +117,18 @@ anylink: tun模式 tcp传输
> 客户端tls加密协议、隧道header头都会占用一定带宽 > 客户端tls加密协议、隧道header头都会占用一定带宽
### 登录防爆说明
```
1.用户 A 在 IP 1.2.3.4 上尝试登录:
用户 A 在 IP 1.2.3.4 上尝试登录失败 5 次,触发了该 IP 上的用户 A 锁定 5 分钟。
在这 5 分钟内,用户 A 从 IP 1.2.3.4 无法进行新的登录尝试。
2.用户 A 更换 IP 到 1.2.3.5 继续尝试登录:
用户 A 在 IP 1.2.3.5 上继续尝试登录,并且累计失败 20 次,触发了全局用户 A 锁定 5 分钟。
在这 5 分钟内,用户 A 从任何 IP 地址都无法进行新的登录尝试。
3.IP 1.2.3.4 上多个用户尝试登录:
如果从 IP 1.2.3.4 上累计有 40 次失败登录尝试(无论来自多少不同的用户),触发了该 IP 的全局锁定 5 分钟。
在这 5 分钟内,从 IP 1.2.3.4 的所有登录尝试都将被拒绝。
如果在 N 分钟内没有新的失败尝试,失败计数会在 N 分钟后(*_reset_time重置。
```

View File

@ -38,7 +38,9 @@ LABEL maintainer="github.com/bjdgyc"
ARG CN="no" ARG CN="no"
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
ENV ANYLINK_IN_CONTAINER=true #开关变量 on off
ENV ANYLINK_IN_CONTAINER="on"
ENV IPTABLES_LEGACY="off"
WORKDIR /app WORKDIR /app
COPY docker/init_release.sh /tmp/ COPY docker/init_release.sh /tmp/

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
var1=$1 var1=$1
#set -x #set -x
@ -19,13 +19,19 @@ case $var1 in
#iptables -nL -t nat #iptables -nL -t nat
# 启动服务 先判断配置文件是否存在 # 启动服务 先判断配置文件是否存在
if [ ! -f /app/conf/profile.xml ]; then if [[ ! -f /app/conf/profile.xml ]]; then
/bin/cp -r /home/conf-bak/* /app/conf/ /bin/cp -r /home/conf-bak/* /app/conf/
echo "After the configuration file is initialized, the container will be forcibly exited. Restart the container." echo "After the configuration file is initialized, the container will be forcibly exited. Restart the container."
echo "配置文件初始化完成后,容器会强制退出,请重新启动容器。" echo "配置文件初始化完成后,容器会强制退出,请重新启动容器。"
exit 1 exit 1
fi fi
# 兼容老版本 iptables
if [[ $IPTABLES_LEGACY == "on" ]]; then
rm /sbin/iptables
ln -s /sbin/iptables-legacy /sbin/iptables
fi
exec /app/anylink "$@" exec /app/anylink "$@"
;; ;;
esac esac

View File

@ -13,10 +13,11 @@ fi
# docker 启动使用 4.19 以上内核 # docker 启动使用 4.19 以上内核
apk add --no-cache ca-certificates bash iproute2 tzdata iptables apk add --no-cache ca-certificates bash iproute2 tzdata iptables
# alpine:3.19 兼容老版 iptables # alpine:3.19 兼容老版 iptables
apk add --no-cache iptables-legacy apk add --no-cache iptables-legacy
rm /sbin/iptables
ln -s /sbin/iptables-legacy /sbin/iptables #rm /sbin/iptables
#ln -s /sbin/iptables-legacy /sbin/iptables
chmod +x /app/docker_entrypoint.sh chmod +x /app/docker_entrypoint.sh

465
server/admin/lockmanager.go Normal file
View File

@ -0,0 +1,465 @@
package admin
import (
"encoding/json"
"io"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/bjdgyc/anylink/base"
)
type LockInfo struct {
Description string `json:"description"` // 锁定原因
Username string `json:"username"` // 用户名
IP string `json:"ip"` // IP 地址
State *LockState `json:"state"` // 锁定状态信息
}
type LockState struct {
Locked bool `json:"locked"` // 是否锁定
FailureCount int `json:"attempts"` // 失败次数
LockTime time.Time `json:"lock_time"` // 锁定截止时间
LastAttempt time.Time `json:"lastAttempt"` // 最后一次尝试的时间
}
type IPWhitelists struct {
IP net.IP
CIDR *net.IPNet
}
type LockManager struct {
mu sync.Mutex
// LoginStatus sync.Map // 登录状态
ipLocks map[string]*LockState // 全局IP锁定状态
userLocks map[string]*LockState // 全局用户锁定状态
ipUserLocks map[string]map[string]*LockState // 单用户IP锁定状态
ipWhitelists []IPWhitelists // 全局IP白名单包含IP地址和CIDR范围
cleanupTicker *time.Ticker
}
var lockmanager *LockManager
var once sync.Once
func GetLockManager() *LockManager {
once.Do(func() {
lockmanager = &LockManager{
// LoginStatus: sync.Map{},
ipLocks: make(map[string]*LockState),
userLocks: make(map[string]*LockState),
ipUserLocks: make(map[string]map[string]*LockState),
ipWhitelists: make([]IPWhitelists, 0),
}
})
return lockmanager
}
const defaultGlobalLockStateExpirationTime = 3600
func InitLockManager() {
lm := GetLockManager()
if base.Cfg.AntiBruteForce {
if base.Cfg.GlobalLockStateExpirationTime <= 0 {
base.Cfg.GlobalLockStateExpirationTime = defaultGlobalLockStateExpirationTime
}
lm.StartCleanupTicker()
lm.InitIPWhitelist()
}
}
func GetLocksInfo(w http.ResponseWriter, r *http.Request) {
lm := GetLockManager()
locksInfo := lm.GetLocksInfo()
RespSucess(w, locksInfo)
}
func UnlockUser(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
lockinfo := LockInfo{}
if err := json.Unmarshal(body, &lockinfo); err != nil {
RespError(w, RespInternalErr, err)
return
}
if lockinfo.State == nil {
RespError(w, RespInternalErr, "未找到锁定用户!")
return
}
lm := GetLockManager()
lm.mu.Lock()
defer lm.mu.Unlock()
// 根据用户名和IP查找锁定状态
var state *LockState
switch {
case lockinfo.IP == "" && lockinfo.Username != "":
state = lm.userLocks[lockinfo.Username] // 全局用户锁定
case lockinfo.Username != "" && lockinfo.IP != "":
if userIPMap, exists := lm.ipUserLocks[lockinfo.Username]; exists {
state = userIPMap[lockinfo.IP] // 单用户 IP 锁定
}
default:
state = lm.ipLocks[lockinfo.IP] // 全局 IP 锁定
}
if state == nil || !state.Locked {
RespError(w, RespInternalErr, "锁定状态未找到或已解锁")
return
}
lm.Unlock(state)
base.Info("解锁成功:", lockinfo.Description, lockinfo.Username, lockinfo.IP)
RespSucess(w, "解锁成功!")
}
func (lm *LockManager) GetLocksInfo() []LockInfo {
var locksInfo []LockInfo
lm.mu.Lock()
defer lm.mu.Unlock()
for ip, state := range lm.ipLocks {
if base.Cfg.MaxGlobalIPBanCount > 0 && state.Locked {
info := LockInfo{
Description: "全局IP锁定",
Username: "",
IP: ip,
State: &LockState{
Locked: state.Locked,
FailureCount: state.FailureCount,
LockTime: state.LockTime,
LastAttempt: state.LastAttempt,
},
}
locksInfo = append(locksInfo, info)
}
}
for username, state := range lm.userLocks {
if base.Cfg.MaxGlobalUserBanCount > 0 && state.Locked {
info := LockInfo{
Description: "全局用户锁定",
Username: username,
IP: "",
State: &LockState{
Locked: state.Locked,
FailureCount: state.FailureCount,
LockTime: state.LockTime,
LastAttempt: state.LastAttempt,
},
}
locksInfo = append(locksInfo, info)
}
}
for username, ipStates := range lm.ipUserLocks {
for ip, state := range ipStates {
if base.Cfg.MaxBanCount > 0 && state.Locked {
info := LockInfo{
Description: "单用户IP锁定",
Username: username,
IP: ip,
State: &LockState{
Locked: state.Locked,
FailureCount: state.FailureCount,
LockTime: state.LockTime,
LastAttempt: state.LastAttempt,
},
}
locksInfo = append(locksInfo, info)
}
}
}
return locksInfo
}
// 初始化IP白名单
func (lm *LockManager) InitIPWhitelist() {
ipWhitelist := strings.Split(base.Cfg.IPWhitelist, ",")
for _, ipWhitelist := range ipWhitelist {
ipWhitelist = strings.TrimSpace(ipWhitelist)
if ipWhitelist == "" {
continue
}
_, ipNet, err := net.ParseCIDR(ipWhitelist)
if err == nil {
lm.ipWhitelists = append(lm.ipWhitelists, IPWhitelists{CIDR: ipNet})
continue
}
ip := net.ParseIP(ipWhitelist)
if ip != nil {
lm.ipWhitelists = append(lm.ipWhitelists, IPWhitelists{IP: ip})
continue
}
}
}
// 检查 IP 是否在白名单中
func (lm *LockManager) IsWhitelisted(ip string) bool {
clientIP := net.ParseIP(ip)
if clientIP == nil {
return false
}
for _, ipWhitelist := range lm.ipWhitelists {
if ipWhitelist.CIDR != nil && ipWhitelist.CIDR.Contains(clientIP) {
return true
}
if ipWhitelist.IP != nil && ipWhitelist.IP.Equal(clientIP) {
return true
}
}
return false
}
func (lm *LockManager) StartCleanupTicker() {
lm.cleanupTicker = time.NewTicker(1 * time.Minute)
go func() {
for range lm.cleanupTicker.C {
lm.CleanupExpiredLocks()
}
}()
}
// 定期清理过期的锁定
func (lm *LockManager) CleanupExpiredLocks() {
now := time.Now()
lm.mu.Lock()
defer lm.mu.Unlock()
for ip, state := range lm.ipLocks {
if !lm.CheckLockState(state, now, base.Cfg.GlobalIPBanResetTime) ||
now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
delete(lm.ipLocks, ip)
}
}
for user, state := range lm.userLocks {
if !lm.CheckLockState(state, now, base.Cfg.GlobalUserBanResetTime) ||
now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
delete(lm.userLocks, user)
}
}
for user, ipMap := range lm.ipUserLocks {
for ip, state := range ipMap {
if !lm.CheckLockState(state, now, base.Cfg.BanResetTime) ||
now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
delete(ipMap, ip)
if len(ipMap) == 0 {
delete(lm.ipUserLocks, user)
}
}
}
}
}
// 检查全局 IP 锁定
func (lm *LockManager) CheckGlobalIPLock(ip string, now time.Time) bool {
lm.mu.Lock()
defer lm.mu.Unlock()
state, exists := lm.ipLocks[ip]
if !exists {
return false
}
return lm.CheckLockState(state, now, base.Cfg.GlobalIPBanResetTime)
}
// 检查全局用户锁定
func (lm *LockManager) CheckGlobalUserLock(username string, now time.Time) bool {
// 我也不知道为什么cisco anyconnect每次连接会先传一个空用户请求····
if username == "" {
return false
}
lm.mu.Lock()
defer lm.mu.Unlock()
state, exists := lm.userLocks[username]
if !exists {
return false
}
return lm.CheckLockState(state, now, base.Cfg.GlobalUserBanResetTime)
}
// 检查单个用户的 IP 锁定
func (lm *LockManager) CheckUserIPLock(username, ip string, now time.Time) bool {
// 我也不知道为什么cisco anyconnect每次连接会先传一个空用户请求····
if username == "" {
return false
}
lm.mu.Lock()
defer lm.mu.Unlock()
userIPMap, userExists := lm.ipUserLocks[username]
if !userExists {
return false
}
state, ipExists := userIPMap[ip]
if !ipExists {
return false
}
return lm.CheckLockState(state, now, base.Cfg.BanResetTime)
}
// 更新全局 IP 锁定状态
func (lm *LockManager) UpdateGlobalIPLock(ip string, now time.Time, success bool) {
lm.mu.Lock()
defer lm.mu.Unlock()
state, exists := lm.ipLocks[ip]
if !exists {
state = &LockState{}
lm.ipLocks[ip] = state
}
lm.UpdateLockState(state, now, success, base.Cfg.MaxGlobalIPBanCount, base.Cfg.GlobalIPLockTime)
}
// 更新全局用户锁定状态
func (lm *LockManager) UpdateGlobalUserLock(username string, now time.Time, success bool) {
// 我也不知道为什么cisco anyconnect每次连接会先传一个空用户请求····
if username == "" {
return
}
lm.mu.Lock()
defer lm.mu.Unlock()
state, exists := lm.userLocks[username]
if !exists {
state = &LockState{}
lm.userLocks[username] = state
}
lm.UpdateLockState(state, now, success, base.Cfg.MaxGlobalUserBanCount, base.Cfg.GlobalUserLockTime)
}
// 更新单个用户的 IP 锁定状态
func (lm *LockManager) UpdateUserIPLock(username, ip string, now time.Time, success bool) {
// 我也不知道为什么cisco anyconnect每次连接会先传一个空用户请求····
if username == "" {
return
}
lm.mu.Lock()
defer lm.mu.Unlock()
userIPMap, userExists := lm.ipUserLocks[username]
if !userExists {
userIPMap = make(map[string]*LockState)
lm.ipUserLocks[username] = userIPMap
}
state, ipExists := userIPMap[ip]
if !ipExists {
state = &LockState{}
userIPMap[ip] = state
}
lm.UpdateLockState(state, now, success, base.Cfg.MaxBanCount, base.Cfg.LockTime)
}
// 更新锁定状态
func (lm *LockManager) UpdateLockState(state *LockState, now time.Time, success bool, maxBanCount, lockTime int) {
if success {
lm.Unlock(state) // 成功登录后解锁
} else {
state.FailureCount++
if state.FailureCount >= maxBanCount {
state.LockTime = now.Add(time.Duration(lockTime) * time.Second)
state.Locked = true // 超过阈值时锁定
}
}
state.LastAttempt = now
}
// 检查锁定状态
func (lm *LockManager) CheckLockState(state *LockState, now time.Time, resetTime int) bool {
if state == nil || state.LastAttempt.IsZero() {
return false
}
// 如果超过锁定时间,重置锁定状态
if !state.LockTime.IsZero() && now.After(state.LockTime) {
lm.Unlock(state) // 锁定期过后解锁
return false
}
// 如果超过窗口时间,重置失败计数
if now.Sub(state.LastAttempt) > time.Duration(resetTime)*time.Second {
state.FailureCount = 0
return false
}
return state.Locked
}
// 解锁
func (lm *LockManager) Unlock(state *LockState) {
state.FailureCount = 0
state.LockTime = time.Time{}
state.Locked = false
}
// 检查锁定状态
func (lm *LockManager) CheckLocked(username, ipaddr string) bool {
if !base.Cfg.AntiBruteForce {
return true
}
ip, _, err := net.SplitHostPort(ipaddr) // 提取纯 IP 地址,去掉端口号
if err != nil {
base.Error("检查锁定状态失败,提取IP地址错误:", ipaddr)
return true
}
now := time.Now()
// 检查IP是否在白名单中
if lm.IsWhitelisted(ip) {
return true
}
// 检查全局 IP 锁定
if base.Cfg.MaxGlobalIPBanCount > 0 && lm.CheckGlobalIPLock(ip, now) {
base.Warn("IP", ip, "is globally locked. Try again later.")
return false
}
// 检查全局用户锁定
if base.Cfg.MaxGlobalUserBanCount > 0 && lm.CheckGlobalUserLock(username, now) {
base.Warn("User", username, "is globally locked. Try again later.")
return false
}
// 检查单个用户的 IP 锁定
if base.Cfg.MaxBanCount > 0 && lm.CheckUserIPLock(username, ip, now) {
base.Warn("IP", ip, "is locked for user", username, "Try again later.")
return false
}
return true
}
// 更新用户登录状态
func (lm *LockManager) UpdateLoginStatus(username, ipaddr string, loginStatus bool) {
ip, _, err := net.SplitHostPort(ipaddr) // 提取纯 IP 地址,去掉端口号
if err != nil {
base.Error("更新登录状态失败,提取IP地址错误:", ipaddr)
return
}
now := time.Now()
// 更新用户登录状态
lm.UpdateGlobalIPLock(ip, now, loginStatus)
lm.UpdateGlobalUserLock(username, now, loginStatus)
lm.UpdateUserIPLock(username, ip, now, loginStatus)
}

View File

@ -87,6 +87,8 @@ func StartAdmin() {
r.HandleFunc("/group/auth_login", GroupAuthLogin) r.HandleFunc("/group/auth_login", GroupAuthLogin)
r.HandleFunc("/statsinfo/list", StatsInfoList) r.HandleFunc("/statsinfo/list", StatsInfoList)
r.HandleFunc("/locksinfo/list", GetLocksInfo)
r.HandleFunc("/locksinfo/unlok", UnlockUser)
// pprof // pprof
if base.Cfg.Pprof { if base.Cfg.Pprof {

View File

@ -2,9 +2,11 @@ package base
import ( import (
"fmt" "fmt"
"github.com/bjdgyc/anylink/pkg/utils"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings"
) )
const ( const (
@ -31,28 +33,29 @@ var (
type ServerConfig struct { type ServerConfig struct {
// LinkAddr string `json:"link_addr"` // LinkAddr string `json:"link_addr"`
Conf string `json:"conf"` Conf string `json:"conf"`
Profile string `json:"profile"` Profile string `json:"profile"`
ProfileName string `json:"profile_name"` ProfileName string `json:"profile_name"`
ServerAddr string `json:"server_addr"` ServerAddr string `json:"server_addr"`
ServerDTLSAddr string `json:"server_dtls_addr"` ServerDTLS bool `json:"server_dtls"`
ServerDTLS bool `json:"server_dtls"` ServerDTLSAddr string `json:"server_dtls_addr"`
AdminAddr string `json:"admin_addr"` AdvertiseDTLSAddr string `json:"advertise_dtls_addr"`
ProxyProtocol bool `json:"proxy_protocol"` AdminAddr string `json:"admin_addr"`
DbType string `json:"db_type"` ProxyProtocol bool `json:"proxy_protocol"`
DbSource string `json:"db_source"` DbType string `json:"db_type"`
CertFile string `json:"cert_file"` DbSource string `json:"db_source"`
CertKey string `json:"cert_key"` CertFile string `json:"cert_file"`
FilesPath string `json:"files_path"` CertKey string `json:"cert_key"`
LogPath string `json:"log_path"` FilesPath string `json:"files_path"`
LogLevel string `json:"log_level"` LogPath string `json:"log_path"`
HttpServerLog bool `json:"http_server_log"` LogLevel string `json:"log_level"`
Pprof bool `json:"pprof"` HttpServerLog bool `json:"http_server_log"`
Issuer string `json:"issuer"` Pprof bool `json:"pprof"`
AdminUser string `json:"admin_user"` Issuer string `json:"issuer"`
AdminPass string `json:"admin_pass"` AdminUser string `json:"admin_user"`
AdminOtp string `json:"admin_otp"` AdminPass string `json:"admin_pass"`
JwtSecret string `json:"jwt_secret"` AdminOtp string `json:"admin_otp"`
JwtSecret string `json:"jwt_secret"`
LinkMode string `json:"link_mode"` // tun tap macvtap ipvtap LinkMode string `json:"link_mode"` // tun tap macvtap ipvtap
Ipv4Master string `json:"ipv4_master"` // eth0 Ipv4Master string `json:"ipv4_master"` // eth0
@ -84,6 +87,24 @@ type ServerConfig struct {
DisplayError bool `json:"display_error"` DisplayError bool `json:"display_error"`
ExcludeExportIp bool `json:"exclude_export_ip"` ExcludeExportIp bool `json:"exclude_export_ip"`
AuthAloneOtp bool `json:"auth_alone_otp"`
AntiBruteForce bool `json:"anti_brute_force"`
IPWhitelist string `json:"ip_whitelist"`
MaxBanCount int `json:"max_ban_score"`
BanResetTime int `json:"ban_reset_time"`
LockTime int `json:"lock_time"`
MaxGlobalUserBanCount int `json:"max_global_user_ban_count"`
GlobalUserBanResetTime int `json:"global_user_ban_reset_time"`
GlobalUserLockTime int `json:"global_user_lock_time"`
MaxGlobalIPBanCount int `json:"max_global_ip_ban_count"`
GlobalIPBanResetTime int `json:"global_ip_ban_reset_time"`
GlobalIPLockTime int `json:"global_ip_lock_time"`
GlobalLockStateExpirationTime int `json:"global_lock_state_expiration_time"`
} }
func initServerCfg() { func initServerCfg() {
@ -106,6 +127,15 @@ func initServerCfg() {
if Cfg.JwtSecret == defaultJwt { if Cfg.JwtSecret == defaultJwt {
fmt.Fprintln(os.Stderr, "=== 使用默认的jwt_secret有安全风险请设置新的jwt_secret ===") fmt.Fprintln(os.Stderr, "=== 使用默认的jwt_secret有安全风险请设置新的jwt_secret ===")
// 安全问题,自动生成新的密钥
jwtSecret, _ := utils.RandSecret(40, 60)
jwtSecret = strings.Trim(jwtSecret, "=")
Cfg.JwtSecret = jwtSecret
}
if Cfg.AdvertiseDTLSAddr == "" {
Cfg.AdvertiseDTLSAddr = Cfg.ServerDTLSAddr
} }
fmt.Printf("ServerCfg: %+v \n", Cfg) fmt.Printf("ServerCfg: %+v \n", Cfg)

View File

@ -26,6 +26,7 @@ var configs = []config{
{Typ: cfgStr, Name: "server_addr", Usage: "TCP服务监听地址(任意端口)", ValStr: ":443"}, {Typ: cfgStr, Name: "server_addr", Usage: "TCP服务监听地址(任意端口)", ValStr: ":443"},
{Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false}, {Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false},
{Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址(任意端口)", ValStr: ":443"}, {Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址(任意端口)", ValStr: ":443"},
{Typ: cfgStr, Name: "advertise_dtls_addr", Usage: "DTLS对外映射端口(为空则与server_dtls_addr相同)", ValStr: ""},
{Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"}, {Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"},
{Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false}, {Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false},
{Typ: cfgStr, Name: "db_type", Usage: "数据库类型 [sqlite3 mysql postgres]", ValStr: "sqlite3"}, {Typ: cfgStr, Name: "db_type", Usage: "数据库类型 [sqlite3 mysql postgres]", ValStr: "sqlite3"},
@ -71,6 +72,24 @@ var configs = []config{
{Typ: cfgBool, Name: "display_error", Usage: "客户端显示详细错误信息(线上环境慎开启)", ValBool: false}, {Typ: cfgBool, Name: "display_error", Usage: "客户端显示详细错误信息(线上环境慎开启)", ValBool: false},
{Typ: cfgBool, Name: "exclude_export_ip", Usage: "排除出口ip路由(出口ip不加密传输)", ValBool: true}, {Typ: cfgBool, Name: "exclude_export_ip", Usage: "排除出口ip路由(出口ip不加密传输)", ValBool: true},
{Typ: cfgBool, Name: "auth_alone_otp", Usage: "登录单独验证OTP窗口", ValBool: false},
{Typ: cfgBool, Name: "anti_brute_force", Usage: "是否开启防爆功能", ValBool: true},
{Typ: cfgStr, Name: "ip_whitelist", Usage: "全局IP白名单,多个用逗号分隔支持单IP和CIDR范围", ValStr: "192.168.90.1,172.16.0.0/24"},
{Typ: cfgInt, Name: "max_ban_score", Usage: "单位时间内最大尝试次数0为关闭该功能", ValInt: 5},
{Typ: cfgInt, Name: "ban_reset_time", Usage: "设置单位时间(秒),超过则重置计数", ValInt: 10},
{Typ: cfgInt, Name: "lock_time", Usage: "超过最大尝试次数后的锁定时长(秒)", ValInt: 300},
{Typ: cfgInt, Name: "max_global_user_ban_count", Usage: "全局用户单位时间内最大尝试次数0为关闭该功能", ValInt: 20},
{Typ: cfgInt, Name: "global_user_ban_reset_time", Usage: "全局用户设置单位时间(秒)", ValInt: 600},
{Typ: cfgInt, Name: "global_user_lock_time", Usage: "全局用户锁定时间(秒)", ValInt: 300},
{Typ: cfgInt, Name: "max_global_ip_ban_count", Usage: "全局IP单位时间内最大尝试次数0为关闭该功能", ValInt: 40},
{Typ: cfgInt, Name: "global_ip_ban_reset_time", Usage: "全局IP设置单位时间(秒)", ValInt: 1200},
{Typ: cfgInt, Name: "global_ip_lock_time", Usage: "全局IP锁定时间(秒)", ValInt: 300},
{Typ: cfgInt, Name: "global_lock_state_expiration_time", Usage: "全局锁定状态的保存生命周期(秒),超过则删除记录", ValInt: 3600},
} }
var envs = map[string]string{} var envs = map[string]string{}

View File

@ -22,7 +22,7 @@ var (
func initMod() { func initMod() {
container := os.Getenv(inContainerKey) container := os.Getenv(inContainerKey)
if container == "true" { if container == "on" {
InContainer = true InContainer = true
} }
log.Println("InContainer", InContainer) log.Println("InContainer", InContainer)

View File

@ -44,6 +44,8 @@ server_addr = ":443"
server_dtls = false server_dtls = false
#UDP监听地址(任意端口) #UDP监听地址(任意端口)
server_dtls_addr = ":443" server_dtls_addr = ":443"
#DTLS对外映射端口(为空则与server_dtls_addr相同)
advertise_dtls_addr = ""
#后台服务监听地址 #后台服务监听地址
admin_addr = ":8800" admin_addr = ":8800"
#开启tcp proxy protocol协议 #开启tcp proxy protocol协议
@ -111,3 +113,40 @@ display_error = false
#排除出口ip路由(出口ip不加密传输) #排除出口ip路由(出口ip不加密传输)
exclude_export_ip = true exclude_export_ip = true
#登录单独验证OTP窗口
auth_alone_otp = false
#防爆破全局开关
anti_brute_force = true
#全局IP白名单,多个用逗号分隔支持单IP和CIDR范围
ip_whitelist = "192.168.90.1,172.16.0.0/24"
#锁定时间最好不要超过单位时间
#单位时间内最大尝试次数0为关闭该功能
max_ban_score = 5
#设置单位时间(秒),超过则重置计数
ban_reset_time = 600
#超过最大尝试次数后的锁定时长(秒)
lock_time = 300
#全局用户单位时间内最大尝试次数,0为关闭该功能
max_global_user_ban_count = 20
#全局用户设置单位时间(秒)
global_user_ban_reset_time = 600
#全局用户锁定时间(秒)
global_user_lock_time = 300
#全局IP单位时间内最大尝试次数0为关闭该功能
max_global_ip_ban_count = 40
#全局IP设置单位时间(秒)
global_ip_ban_reset_time = 1200
#全局IP锁定时间(秒)
global_ip_lock_time = 300
#全局锁定状态的保存生命周期(秒),超过则删除记录
global_lock_state_expiration_time = 3600

View File

@ -53,8 +53,5 @@ ipv4_end = "192.168.90.200"
#是否自动添加nat #是否自动添加nat
iptables_nat = true iptables_nat = true
#客户端显示详细错误信息(线上环境慎开启) #客户端显示详细错误信息(线上环境慎开启)
display_error = true display_error = true

View File

@ -400,12 +400,3 @@ func buildNameToCertificate(cert *tls.Certificate) {
nameToCertificate[san] = cert nameToCertificate[san] = cert
} }
} }
// func Scrypt(passwd string) string {
// salt := []byte{0xc8, 0x28, 0xf2, 0x58, 0xa7, 0x6a, 0xad, 0x7b}
// hashPasswd, err := scrypt.Key([]byte(passwd), salt, 1<<15, 8, 1, 32)
// if err != nil {
// return err.Error()
// }
// return base64.StdEncoding.EncodeToString(hashPasswd)
// }

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
_ "github.com/denisenkom/go-mssqldb"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq" _ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
@ -22,13 +23,14 @@ func GetXdb() *xorm.Engine {
func initDb() { func initDb() {
var err error var err error
xdb, err = xorm.NewEngine(base.Cfg.DbType, base.Cfg.DbSource) xdb, err = xorm.NewEngine(base.Cfg.DbType, base.Cfg.DbSource)
// 初始化xorm时区
xdb.DatabaseTZ = time.Local
xdb.TZLocation = time.Local
if err != nil { if err != nil {
base.Fatal(err) base.Fatal(err)
} }
// 初始化xorm时区
xdb.DatabaseTZ = time.Local
xdb.TZLocation = time.Local
if base.Cfg.ShowSQL { if base.Cfg.ShowSQL {
xdb.ShowSQL(true) xdb.ShowSQL(true)
} }
@ -203,7 +205,8 @@ const accountMail = `<p>您好:</p>
<ul> <ul>
<li>请使用OTP软件扫描动态码二维码</li> <li>请使用OTP软件扫描动态码二维码</li>
<li>然后使用anyconnect客户端进行登陆</li> <li>然后使用anyconnect客户端进行登陆</li>
<li>登陆密码为 PIN码+动态码(中间没有+)</li> <li>登陆密码为 PIN </li>
<li>OTP密码为扫码后生成的动态码</li>
</ul> </ul>
</div> </div>
<p> <p>

View File

@ -340,7 +340,8 @@ func GroupAuthLogin(name, pwd string, authData map[string]interface{}) error {
if err != nil { if err != nil {
return err return err
} }
err = auth.checkUser(name, pwd, g) ext := map[string]interface{}{}
err = auth.checkUser(name, pwd, g, ext)
return err return err
} }

View File

@ -30,7 +30,7 @@ type User struct {
Nickname string `json:"nickname" xorm:"varchar(255)"` Nickname string `json:"nickname" xorm:"varchar(255)"`
Email string `json:"email" xorm:"varchar(255)"` Email string `json:"email" xorm:"varchar(255)"`
// Password string `json:"password"` // Password string `json:"password"`
PinCode string `json:"pin_code" xorm:"varchar(32)"` PinCode string `json:"pin_code" xorm:"varchar(64)"`
LimitTime *time.Time `json:"limittime,omitempty" xorm:"Datetime limittime"` // 值为null时前端不显示 LimitTime *time.Time `json:"limittime,omitempty" xorm:"Datetime limittime"` // 值为null时前端不显示
OtpSecret string `json:"otp_secret" xorm:"varchar(255)"` OtpSecret string `json:"otp_secret" xorm:"varchar(255)"`
DisableOtp bool `json:"disable_otp" xorm:"Bool"` // 禁用otp DisableOtp bool `json:"disable_otp" xorm:"Bool"` // 禁用otp

View File

@ -6,6 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/utils" "github.com/bjdgyc/anylink/pkg/utils"
"github.com/xlzd/gotp" "github.com/xlzd/gotp"
) )
@ -67,7 +68,9 @@ func SetUser(v *User) error {
} }
// 验证用户登录信息 // 验证用户登录信息
func CheckUser(name, pwd, group string) error { func CheckUser(name, pwd, group string, ext map[string]interface{}) error {
base.Trace("CheckUser", name, pwd, group, ext)
// 获取登入的group数据 // 获取登入的group数据
groupData := &Group{} groupData := &Group{}
err := One("Name", group, groupData) err := One("Name", group, groupData)
@ -81,7 +84,7 @@ func CheckUser(name, pwd, group string) error {
authType := groupData.Auth["type"].(string) authType := groupData.Auth["type"].(string)
// 本地认证方式 // 本地认证方式
if authType == "local" { if authType == "local" {
return checkLocalUser(name, pwd, group) return checkLocalUser(name, pwd, group, ext)
} }
// 其它认证方式, 支持自定义 // 其它认证方式, 支持自定义
_, ok := authRegistry[authType] _, ok := authRegistry[authType]
@ -89,11 +92,11 @@ func CheckUser(name, pwd, group string) error {
return fmt.Errorf("%s %s", "未知的认证方式: ", authType) return fmt.Errorf("%s %s", "未知的认证方式: ", authType)
} }
auth := makeInstance(authType).(IUserAuth) auth := makeInstance(authType).(IUserAuth)
return auth.checkUser(name, pwd, groupData) return auth.checkUser(name, pwd, groupData, ext)
} }
// 验证本地用户登录信息 // 验证本地用户登录信息
func checkLocalUser(name, pwd, group string) error { func checkLocalUser(name, pwd, group string, ext map[string]interface{}) error {
// TODO 严重问题 // TODO 严重问题
// return nil // return nil
@ -115,18 +118,29 @@ func checkLocalUser(name, pwd, group string) error {
if !utils.InArrStr(v.Groups, group) { if !utils.InArrStr(v.Groups, group) {
return fmt.Errorf("%s %s", name, "用户组错误") return fmt.Errorf("%s %s", name, "用户组错误")
} }
// 判断otp信息
pinCode := pwd pinCode := pwd
if !v.DisableOtp { if base.Cfg.AuthAloneOtp == false {
pinCode = pwd[:pl-6] // 判断otp信息
otp := pwd[pl-6:] if !v.DisableOtp {
if !checkOtp(name, otp, v.OtpSecret) { pinCode = pwd[:pl-6]
return fmt.Errorf("%s %s", name, "动态码错误") otp := pwd[pl-6:]
if !CheckOtp(name, otp, v.OtpSecret) {
return fmt.Errorf("%s %s", name, "动态码错误")
}
} }
} }
// 判断用户密码 // 判断用户密码
if pinCode != v.PinCode { // 兼容明文密码
if len(v.PinCode) != 60 {
if pinCode != v.PinCode {
return fmt.Errorf("%s %s", name, "密码错误")
}
return nil
}
// 密文密码
if !utils.PasswordVerify(pinCode, v.PinCode) {
return fmt.Errorf("%s %s", name, "密码错误") return fmt.Errorf("%s %s", name, "密码错误")
} }
@ -171,7 +185,7 @@ func init() {
} }
// 判断令牌信息 // 判断令牌信息
func checkOtp(name, otp, secret string) bool { func CheckOtp(name, otp, secret string) bool {
key := fmt.Sprintf("%s:%s", name, otp) key := fmt.Sprintf("%s:%s", name, otp)
userOtpMux.Lock() userOtpMux.Lock()
@ -190,3 +204,23 @@ func checkOtp(name, otp, secret string) bool {
return verify return verify
} }
// 插入数据库前加密密码
func (u *User) BeforeInsert() {
hashedPassword, err := utils.PasswordHash(u.PinCode)
if err != nil {
base.Error(err)
}
u.PinCode = hashedPassword
}
// 更新数据库前加密密码
func (u *User) BeforeUpdate() {
if len(u.PinCode) != 60 {
hashedPassword, err := utils.PasswordHash(u.PinCode)
if err != nil {
base.Error(err)
}
u.PinCode = hashedPassword
}
}

View File

@ -3,11 +3,12 @@ package dbdata
import ( import (
"testing" "testing"
"github.com/bjdgyc/anylink/base"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/xlzd/gotp"
) )
func TestCheckUser(t *testing.T) { func TestCheckUser(t *testing.T) {
base.Test()
ast := assert.New(t) ast := assert.New(t)
preIpData() preIpData()
@ -25,20 +26,24 @@ func TestCheckUser(t *testing.T) {
ast.Equal(g.RouteInclude[0].IpMask, "192.168.1.0/255.255.255.0") ast.Equal(g.RouteInclude[0].IpMask, "192.168.1.0/255.255.255.0")
// 添加一个用户 // 添加一个用户
u := User{Username: "aaa", Groups: []string{group}, Status: 1} pincode := "a123456"
u := User{Username: "aaa", PinCode: pincode, Groups: []string{group}, Status: 1}
err = SetUser(&u) err = SetUser(&u)
ast.Nil(err) ast.Nil(err)
// 验证 PinCode + OtpSecret // 验证 PinCode + OtpSecret
totp := gotp.NewDefaultTOTP(u.OtpSecret) // totp := gotp.NewDefaultTOTP(u.OtpSecret)
secret := totp.Now() // secret := totp.Now()
err = CheckUser("aaa", u.PinCode+secret, group) // err = CheckUser("aaa", u.PinCode+secret, group)
ast.Nil(err) // ast.Nil(err)
// 单独验证密码 // 单独验证密码
u.DisableOtp = true u.DisableOtp = true
_ = SetUser(&u) _ = SetUser(&u)
err = CheckUser("aaa", u.PinCode, group) ext := map[string]any{
"mac_addr": "",
}
err = CheckUser("aaa", pincode, group, ext)
ast.Nil(err) ast.Nil(err)
// 添加一个radius组 // 添加一个radius组
@ -53,9 +58,9 @@ func TestCheckUser(t *testing.T) {
g2 := Group{Name: group2, Status: 1, ClientDns: dns, RouteInclude: route, Auth: authData} g2 := Group{Name: group2, Status: 1, ClientDns: dns, RouteInclude: route, Auth: authData}
err = SetGroup(&g2) err = SetGroup(&g2)
ast.Nil(err) ast.Nil(err)
err = CheckUser("aaa", "bbbbbbb", group2) err = CheckUser("aaa", "bbbbbbb", group2, ext)
if ast.NotNil(err) { if ast.NotNil(err) {
ast.Equal("aaa Radius服务器连接异常, 请检测服务器和端口", err.Error()) ast.Contains(err.Error(), "aaa Radius服务器连接异常")
} }
// 添加用户策略 // 添加用户策略
dns2 := []ValData{{Val: "8.8.8.8"}} dns2 := []ValData{{Val: "8.8.8.8"}}
@ -63,7 +68,7 @@ func TestCheckUser(t *testing.T) {
p1 := Policy{Username: "aaa", Status: 1, ClientDns: dns2, RouteInclude: route2} p1 := Policy{Username: "aaa", Status: 1, ClientDns: dns2, RouteInclude: route2}
err = SetPolicy(&p1) err = SetPolicy(&p1)
ast.Nil(err) ast.Nil(err)
err = CheckUser("aaa", u.PinCode, group) err = CheckUser("aaa", pincode, group, ext)
ast.Nil(err) ast.Nil(err)
// 添加一个ldap组 // 添加一个ldap组
group3 := "group3" group3 := "group3"
@ -83,7 +88,7 @@ func TestCheckUser(t *testing.T) {
g3 := Group{Name: group3, Status: 1, ClientDns: dns, RouteInclude: route, Auth: authData} g3 := Group{Name: group3, Status: 1, ClientDns: dns, RouteInclude: route, Auth: authData}
err = SetGroup(&g3) err = SetGroup(&g3)
ast.Nil(err) ast.Nil(err)
err = CheckUser("aaa", "bbbbbbb", group3) err = CheckUser("aaa", "bbbbbbb", group3, ext)
if ast.NotNil(err) { if ast.NotNil(err) {
ast.Equal("aaa LDAP服务器连接异常, 请检测服务器和端口", err.Error()) ast.Equal("aaa LDAP服务器连接异常, 请检测服务器和端口", err.Error())
} }

View File

@ -9,7 +9,7 @@ var authRegistry = make(map[string]reflect.Type)
type IUserAuth interface { type IUserAuth interface {
checkData(authData map[string]interface{}) error checkData(authData map[string]interface{}) error
checkUser(name, pwd string, g *Group) error checkUser(name, pwd string, g *Group, ext map[string]interface{}) error
} }
func makeInstance(name string) interface{} { func makeInstance(name string) interface{} {

View File

@ -61,7 +61,7 @@ func (auth AuthLdap) checkData(authData map[string]interface{}) error {
return nil return nil
} }
func (auth AuthLdap) checkUser(name, pwd string, g *Group) error { func (auth AuthLdap) checkUser(name, pwd string, g *Group, ext map[string]interface{}) error {
pl := len(pwd) pl := len(pwd)
if name == "" || pl < 1 { if name == "" || pl < 1 {
return fmt.Errorf("%s %s", name, "密码错误") return fmt.Errorf("%s %s", name, "密码错误")

View File

@ -9,6 +9,7 @@ import (
"reflect" "reflect"
"time" "time"
"github.com/bjdgyc/anylink/base"
"layeh.com/radius" "layeh.com/radius"
"layeh.com/radius/rfc2865" "layeh.com/radius/rfc2865"
) )
@ -40,7 +41,7 @@ func (auth AuthRadius) checkData(authData map[string]interface{}) error {
return nil return nil
} }
func (auth AuthRadius) checkUser(name, pwd string, g *Group) error { func (auth AuthRadius) checkUser(name, pwd string, g *Group, ext map[string]interface{}) error {
pl := len(pwd) pl := len(pwd)
if name == "" || pl < 1 { if name == "" || pl < 1 {
return fmt.Errorf("%s %s", name, "密码错误") return fmt.Errorf("%s %s", name, "密码错误")
@ -74,15 +75,24 @@ func (auth AuthRadius) checkUser(name, pwd string, g *Group) error {
return fmt.Errorf("%s %s", name, "Radius set nasip 出现错误") return fmt.Errorf("%s %s", name, "Radius set nasip 出现错误")
} }
} }
macAddr := ext["mac_addr"].(string)
base.Trace("AuthRadius", ext, macAddr)
if macAddr != "" {
err = rfc2865.CallingStationID_AddString(packet, macAddr)
if err != nil {
return fmt.Errorf("%s %s", name, "Radius set CallingStationID 出现错误")
}
}
ctx, done := context.WithTimeout(context.Background(), 3*time.Second) ctx, done := context.WithTimeout(context.Background(), 3*time.Second)
defer done() defer done()
response, err := radius.Exchange(ctx, packet, auth.Addr) response, err := radius.Exchange(ctx, packet, auth.Addr)
if err != nil { if err != nil {
return fmt.Errorf("%s %s", name, "Radius服务器连接异常, 请检测服务器和端口") return fmt.Errorf("%s %s %s", name, "Radius服务器连接异常, 请检测服务器和端口", err)
} }
if response.Code != radius.CodeAccessAccept { if response.Code != radius.CodeAccessAccept {
return fmt.Errorf("%s %s", name, "Radius用户名或密码错误") return fmt.Errorf("%s %s", name, "Radius用户名或密码错误")
} }
return nil return nil
} }

View File

@ -5,82 +5,84 @@ go 1.22
require ( require (
github.com/arl/statsviz v0.6.0 github.com/arl/statsviz v0.6.0
github.com/deckarep/golang-set v1.8.0 github.com/deckarep/golang-set v1.8.0
github.com/go-acme/lego/v4 v4.15.0 github.com/denisenkom/go-mssqldb v0.12.3
github.com/go-acme/lego/v4 v4.19.2
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/go-ldap/ldap v3.0.3+incompatible github.com/go-ldap/ldap v3.0.3+incompatible
github.com/go-sql-driver/mysql v1.8.0 github.com/go-sql-driver/mysql v1.8.1
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.1
github.com/google/gopacket v1.1.19 github.com/google/gopacket v1.1.19
github.com/gorilla/handlers v1.5.2 github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/ivpusic/grpool v1.0.0 github.com/ivpusic/grpool v1.0.0
github.com/lanrenwo/lzsgo v0.0.2 github.com/lanrenwo/lzsgo v0.0.2
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.24
github.com/orcaman/concurrent-map v1.0.0 github.com/orcaman/concurrent-map v1.0.0
github.com/pion/dtls/v2 v2.2.10 github.com/pion/dtls/v2 v2.2.12
github.com/pion/logging v0.2.2 github.com/pion/logging v0.2.2
github.com/pires/go-proxyproto v0.7.0 github.com/pires/go-proxyproto v0.8.0
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/spf13/cast v1.6.0 github.com/spf13/cast v1.7.0
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.9.0
github.com/xhit/go-simple-mail/v2 v2.16.0 github.com/xhit/go-simple-mail/v2 v2.16.0
github.com/xlzd/gotp v0.1.0 github.com/xlzd/gotp v0.1.0
github.com/xuri/excelize/v2 v2.8.1 github.com/xuri/excelize/v2 v2.9.0
golang.org/x/crypto v0.26.0 golang.org/x/crypto v0.28.0
golang.org/x/net v0.25.0 golang.org/x/net v0.30.0
golang.org/x/text v0.17.0 golang.org/x/text v0.19.0
golang.org/x/time v0.5.0 golang.org/x/time v0.7.0
layeh.com/radius v0.0.0-20231213012653-1006025d24f8 layeh.com/radius v0.0.0-20231213012653-1006025d24f8
xorm.io/xorm v1.3.8 xorm.io/xorm v1.3.9
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.62.690 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.63.48 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudflare/cloudflare-go v0.89.0 // indirect github.com/cloudflare/cloudflare-go v0.109.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-test/deep v1.1.0 // indirect github.com/go-test/deep v1.1.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/miekg/dns v1.1.58 // indirect github.com/miekg/dns v1.1.62 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pion/transport/v2 v2.2.4 // indirect github.com/pion/transport/v2 v2.2.10 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/pion/transport/v3 v3.0.7 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.873 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1036 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.873 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1036 // indirect
github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 // indirect github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/mod v0.17.0 // indirect golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/tools v0.26.0 // indirect
) )
require ( require (
github.com/coreos/go-iptables v0.7.0 github.com/coreos/go-iptables v0.8.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/websocket v1.5.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
@ -91,17 +93,17 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.3 // indirect github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect github.com/tklauser/numcpus v0.9.0 // indirect
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
golang.org/x/sys v0.23.0 // indirect golang.org/x/sys v0.26.0 // indirect
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@ -3,20 +3,23 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/aliyun/alibaba-cloud-sdk-go v1.62.690 h1:9ChlXyXZxVeHXPlsUqquutxxmrEGhNmfQiOW/YKIbq8= github.com/aliyun/alibaba-cloud-sdk-go v1.63.48 h1:f57dtSI1BJfF5FO7JJ7ljMh79Ae64nbbDfUOey5PBa0=
github.com/aliyun/alibaba-cloud-sdk-go v1.62.690/go.mod h1:CJJYa1ZMxjlN/NbXEwmejEnBkhi0DV+Yb3B2lxf+74o= github.com/aliyun/alibaba-cloud-sdk-go v1.63.48/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE=
github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cloudflare/cloudflare-go v0.89.0 h1:3zoVntC8xmUR/weFEcNE1RizdW4LRZdQnJ/AN8DDa1U= github.com/cloudflare/cloudflare-go v0.109.0 h1:Wjp+RfJD1lidIFUlrTBqUQnCBrUnmVsLxgzWYiURueg=
github.com/cloudflare/cloudflare-go v0.89.0/go.mod h1:eyuehb1i6BNRc+ZwaTZAiRHeE+4jbKvHAns19oGeakg= github.com/cloudflare/cloudflare-go v0.109.0/go.mod h1:m492eNahT/9MsN7Ppnoge8AaI7QhVFtEgVm3I9HJFeU=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -24,41 +27,47 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-acme/lego/v4 v4.15.0 h1:A7MHEU3b+TDFqhC/HmzMJnzPbyeaYvMZQBbqgvbThhU= github.com/go-acme/lego/v4 v4.19.2 h1:Y8hrmMvWETdqzzkRly7m98xtPJJivWFsgWi8fcvZo+Y=
github.com/go-acme/lego/v4 v4.15.0/go.mod h1:eeGhjW4zWT7Ccqa3sY7ayEqFLCAICx+mXgkMHKIkLxg= github.com/go-acme/lego/v4 v4.19.2/go.mod h1:wtDe3dDkmV4/oI2nydpNXSJpvV10J9RCyZ6MbYxNtlQ=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk= github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -66,7 +75,6 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@ -81,15 +89,8 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
@ -98,12 +99,10 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ivpusic/grpool v1.0.0 h1:+FCiCo3GhfsvzfXuJWnpJUNb/VaqyYVgG8C+qvh07Rc= github.com/ivpusic/grpool v1.0.0 h1:+FCiCo3GhfsvzfXuJWnpJUNb/VaqyYVgG8C+qvh07Rc=
github.com/ivpusic/grpool v1.0.0/go.mod h1:WPmiAI5ExAn06vg+0JzyPzXMQutJmpb7TrBtyLJkOHQ= github.com/ivpusic/grpool v1.0.0/go.mod h1:WPmiAI5ExAn06vg+0JzyPzXMQutJmpb7TrBtyLJkOHQ=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
@ -124,22 +123,20 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@ -152,16 +149,20 @@ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:Ff
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY= github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pion/dtls/v2 v2.2.10 h1:u2Axk+FyIR1VFTPurktB+1zoEPGIW3bmyj3LEFrXjAA= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
github.com/pion/dtls/v2 v2.2.10/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo=
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -173,8 +174,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@ -182,8 +183,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
@ -198,37 +199,37 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.873 h1:VdQ+lQ98CFwbmfQj02iMVWWzdwnKubuICIa2QhzQBJI= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1036 h1:B3GO+IBOrjrq8sN5bT9e8GMHWguHkyyGdNEos6cp5cE=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.873/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1036/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.873 h1:qawAviStyrk6b2bHvRvKxmiiL8Cu6KujtzAHktLqdFI= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1036 h1:lpDbM5GqLY67hGkndcRfledZf14sJaY+LOcHp5I4BkQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.873/go.mod h1:wqyJy+a/L53zH8pRDNOF+7t9iZhSPANmYSuUfvVp9NE= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1036/go.mod h1:WuuxNlel804BVTQYrE9qauuiu2462Va04PlIr8/FVJY=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 h1:flbMkdl6HxQkLs6DDhH1UkcnFpNBOu70391STjMS0O4= github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 h1:flbMkdl6HxQkLs6DDhH1UkcnFpNBOu70391STjMS0O4=
github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
@ -236,16 +237,17 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA= github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0= github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ= github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE= github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4= github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@ -257,44 +259,47 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -307,6 +312,7 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -316,9 +322,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -326,19 +331,19 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -347,8 +352,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -367,7 +372,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@ -377,6 +381,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -405,5 +410,5 @@ modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.3.8 h1:CJmplmWqfSRpLWSPMmqz+so8toBp3m7ehuRehIWedZo= xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
xorm.io/xorm v1.3.8/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw= xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=

View File

@ -0,0 +1,121 @@
package handler
import (
"context"
"encoding/xml"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/bjdgyc/anylink/base"
)
// var lockManager = admin.GetLockManager()
const loginStatusKey = "login_status"
type HttpContext struct {
LoginStatus bool // 登录状态
}
// 防爆破中间件
func antiBruteForce(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, old_r *http.Request) {
// 防爆破功能全局开关
if !base.Cfg.AntiBruteForce {
next.ServeHTTP(w, old_r)
return
}
// 非并发安全
hc := &HttpContext{}
ctx := context.WithValue(context.Background(), loginStatusKey, hc)
r := old_r.WithContext(ctx)
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
cr := ClientRequest{}
err = xml.Unmarshal(body, &cr)
if err != nil {
http.Error(w, "Failed to parse XML", http.StatusBadRequest)
return
}
username := cr.Auth.Username
if r.URL.Path == "/otp-verification" {
sessionID, err := GetCookie(r, "auth-session-id")
if err != nil {
http.Error(w, "Invalid session, please login again", http.StatusUnauthorized)
return
}
sessionData, err := SessStore.GetAuthSession(sessionID)
if err != nil {
http.Error(w, "Invalid session, please login again", http.StatusUnauthorized)
return
}
username = sessionData.ClientRequest.Auth.Username
}
ip, _, err := net.SplitHostPort(r.RemoteAddr) // 提取纯 IP 地址,去掉端口号
if err != nil {
http.Error(w, "Unable to parse IP address", http.StatusInternalServerError)
return
}
now := time.Now()
// 检查IP是否在白名单中
if lockManager.IsWhitelisted(ip) {
r.Body = io.NopCloser(strings.NewReader(string(body)))
next.ServeHTTP(w, r)
return
}
// 检查全局 IP 锁定
if base.Cfg.MaxGlobalIPBanCount > 0 && lockManager.CheckGlobalIPLock(ip, now) {
base.Warn("IP", ip, "is globally locked. Try again later.")
http.Error(w, "Account globally locked due to too many failed attempts. Try again later.", http.StatusTooManyRequests)
return
}
// 检查全局用户锁定
if base.Cfg.MaxGlobalUserBanCount > 0 && lockManager.CheckGlobalUserLock(username, now) {
base.Warn("User", username, "is globally locked. Try again later.")
http.Error(w, "Account globally locked due to too many failed attempts. Try again later.", http.StatusTooManyRequests)
return
}
// 检查单个用户的 IP 锁定
if base.Cfg.MaxBanCount > 0 && lockManager.CheckUserIPLock(username, ip, now) {
base.Warn("IP", ip, "is locked for user", username, "Try again later.")
http.Error(w, "Account locked due to too many failed attempts. Try again later.", http.StatusTooManyRequests)
return
}
// 重新设置请求体以便后续处理器可以访问
r.Body = io.NopCloser(strings.NewReader(string(body)))
// 调用下一个处理器
next.ServeHTTP(w, r)
// 检查登录状态
// Status, _ := lockManager.LoginStatus.Load(loginStatusKey)
// loginStatus, _ := Status.(bool)
loginStatus := hc.LoginStatus
// 更新用户登录状态
lockManager.UpdateGlobalIPLock(ip, now, loginStatus)
lockManager.UpdateGlobalUserLock(username, now, loginStatus)
lockManager.UpdateUserIPLock(username, ip, now, loginStatus)
// 清除登录状态
// lockManager.LoginStatus.Delete(loginStatusKey)
})
}

View File

@ -2,11 +2,9 @@ package handler
import ( import (
"bytes" "bytes"
"crypto/md5"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
"net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"strings" "strings"
@ -46,7 +44,10 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
} }
defer r.Body.Close() defer r.Body.Close()
cr := ClientRequest{} cr := &ClientRequest{
RemoteAddr: r.RemoteAddr,
UserAgent: userAgent,
}
err = xml.Unmarshal(body, &cr) err = xml.Unmarshal(body, &cr)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
@ -76,8 +77,15 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
// 锁定状态判断
if !lockManager.CheckLocked(cr.Auth.Username, r.RemoteAddr) {
w.WriteHeader(http.StatusTooManyRequests)
return
}
// 用户活动日志 // 用户活动日志
ua := dbdata.UserActLog{ ua := &dbdata.UserActLog{
Username: cr.Auth.Username, Username: cr.Auth.Username,
GroupName: cr.GroupSelect, GroupName: cr.GroupSelect,
RemoteAddr: r.RemoteAddr, RemoteAddr: r.RemoteAddr,
@ -85,13 +93,24 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
DeviceType: cr.DeviceId.DeviceType, DeviceType: cr.DeviceId.DeviceType,
PlatformVersion: cr.DeviceId.PlatformVersion, PlatformVersion: cr.DeviceId.PlatformVersion,
} }
sessionData := &AuthSession{
ClientRequest: cr,
UserActLog: ua,
}
// TODO 用户密码校验 // TODO 用户密码校验
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect) ext := map[string]interface{}{"mac_addr": cr.MacAddressList.MacAddress}
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect, ext)
if err != nil { if err != nil {
// lockManager.LoginStatus.Store(loginStatusKey, false) // 记录登录失败状态
// hc := r.Context().Value(loginStatusKey).(*HttpContext)
// hc.LoginStatus = false
lockManager.UpdateLoginStatus(cr.Auth.Username, r.RemoteAddr, false) // 记录登录失败状态
base.Warn(err, r.RemoteAddr) base.Warn(err, r.RemoteAddr)
ua.Info = err.Error() ua.Info = err.Error()
ua.Status = dbdata.UserAuthFail ua.Status = dbdata.UserAuthFail
dbdata.UserActLogIns.Add(ua, userAgent) dbdata.UserActLogIns.Add(*ua, userAgent)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNamesNormal(), Error: "用户名或密码错误"} data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNamesNormal(), Error: "用户名或密码错误"}
@ -101,72 +120,66 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
tplRequest(tpl_request, w, data) tplRequest(tpl_request, w, data)
return return
} }
dbdata.UserActLogIns.Add(ua, userAgent) dbdata.UserActLogIns.Add(*ua, userAgent)
// if !ok {
// w.WriteHeader(http.StatusOK)
// data := RequestData{Group: cr.GroupSelect, Groups: base.Cfg.UserGroups, Error: "请先激活用户"}
// tplRequest(tpl_request, w, data)
// return
// }
// 创建新的session信息 v := &dbdata.User{}
sess := sessdata.NewSession("") err = dbdata.One("Username", cr.Auth.Username, v)
sess.Username = cr.Auth.Username
sess.Group = cr.GroupSelect
oriMac := cr.MacAddressList.MacAddress
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
sess.UserAgent = userAgent
sess.DeviceType = ua.DeviceType
sess.PlatformVersion = ua.PlatformVersion
sess.RemoteAddr = r.RemoteAddr
// 获取客户端mac地址
sess.UniqueMac = true
macHw, err := net.ParseMAC(oriMac)
if err != nil { if err != nil {
var sum [16]byte base.Info("正在使用第三方认证方式登录")
if sess.UniqueIdGlobal != "" { CreateSession(w, r, sessionData)
sum = md5.Sum([]byte(sess.UniqueIdGlobal)) return
} else {
sum = md5.Sum([]byte(sess.Token))
sess.UniqueMac = false
}
macHw = sum[0:5] // 5个byte
macHw = append([]byte{0x02}, macHw...)
sess.MacAddr = macHw.String()
} }
sess.MacHw = macHw // 用户otp验证
// 统一macAddr的格式 if base.Cfg.AuthAloneOtp && !v.DisableOtp {
sess.MacAddr = macHw.String() // lockManager.LoginStatus.Store(loginStatusKey, true) // 重置OTP验证计数
// hc := r.Context().Value(loginStatusKey).(*HttpContext)
// hc.LoginStatus = true
lockManager.UpdateLoginStatus(cr.Auth.Username, r.RemoteAddr, true) // 重置OTP验证计数
other := &dbdata.SettingOther{} sessionID, err := GenerateSessionID()
_ = dbdata.SettingGet(other) if err != nil {
rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token, base.Error("Failed to generate session ID: ", err)
Banner: other.Banner, ProfileName: base.Cfg.ProfileName, ProfileHash: profileHash, CertHash: certHash} http.Error(w, "Failed to generate session ID", http.StatusInternalServerError)
w.WriteHeader(http.StatusOK) return
tplRequest(tpl_complete, w, rd) }
base.Info("login", cr.Auth.Username, userAgent)
sessionData.ClientRequest.Auth.OtpSecret = v.OtpSecret
SessStore.SaveAuthSession(sessionID, sessionData)
SetCookie(w, "auth-session-id", sessionID, 0)
data := RequestData{}
w.WriteHeader(http.StatusOK)
tplRequest(tpl_otp, w, data)
return
}
CreateSession(w, r, sessionData)
} }
const ( const (
tpl_request = iota tpl_request = iota
tpl_complete tpl_complete
tpl_otp
) )
func tplRequest(typ int, w io.Writer, data RequestData) { func tplRequest(typ int, w io.Writer, data RequestData) {
if typ == tpl_request { switch typ {
case tpl_request:
t, _ := template.New("auth_request").Parse(auth_request) t, _ := template.New("auth_request").Parse(auth_request)
_ = t.Execute(w, data) _ = t.Execute(w, data)
return case tpl_complete:
if data.Banner != "" {
buf := new(bytes.Buffer)
_ = xml.EscapeText(buf, []byte(data.Banner))
data.Banner = buf.String()
}
t, _ := template.New("auth_complete").Parse(auth_complete)
_ = t.Execute(w, data)
case tpl_otp:
t, _ := template.New("auth_otp").Parse(auth_otp)
_ = t.Execute(w, data)
} }
if data.Banner != "" {
buf := new(bytes.Buffer)
_ = xml.EscapeText(buf, []byte(data.Banner))
data.Banner = buf.String()
}
t, _ := template.New("auth_complete").Parse(auth_complete)
_ = t.Execute(w, data)
} }
// 设置输出信息 // 设置输出信息

View File

@ -0,0 +1,253 @@
package handler
import (
"crypto/md5"
"encoding/xml"
"fmt"
"io"
"net"
"net/http"
"sync"
"github.com/bjdgyc/anylink/admin"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata"
)
var SessStore = NewSessionStore()
var lockManager = admin.GetLockManager()
// const maxOtpErrCount = 3
type AuthSession struct {
ClientRequest *ClientRequest
UserActLog *dbdata.UserActLog
// OtpErrCount atomic.Uint32 // otp错误次数
}
// 存储临时会话信息
type SessionStore struct {
session map[string]*AuthSession
mu sync.Mutex
}
func NewSessionStore() *SessionStore {
return &SessionStore{
session: make(map[string]*AuthSession),
}
}
func (s *SessionStore) SaveAuthSession(sessionID string, session *AuthSession) {
s.mu.Lock()
defer s.mu.Unlock()
s.session[sessionID] = session
}
func (s *SessionStore) GetAuthSession(sessionID string) (*AuthSession, error) {
s.mu.Lock()
defer s.mu.Unlock()
session, exists := s.session[sessionID]
if !exists {
return nil, fmt.Errorf("auth session not found")
}
return session, nil
}
func (s *SessionStore) DeleteAuthSession(sessionID string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.session, sessionID)
}
// func (a *AuthSession) AddOtpErrCount(i int) int {
// newI := a.OtpErrCount.Add(uint32(i))
// return int(newI)
// }
func GenerateSessionID() (string, error) {
sessionID := utils.RandomRunes(32)
if sessionID == "" {
return "", fmt.Errorf("failed to generate session ID")
}
return sessionID, nil
}
func SetCookie(w http.ResponseWriter, name, value string, maxAge int) {
cookie := &http.Cookie{
Name: name,
Value: value,
MaxAge: maxAge,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)
}
func GetCookie(r *http.Request, name string) (string, error) {
cookie, err := r.Cookie(name)
if err != nil {
return "", fmt.Errorf("failed to get cookie: %v", err)
}
return cookie.Value, nil
}
func DeleteCookie(w http.ResponseWriter, name string) {
cookie := &http.Cookie{
Name: name,
Value: "",
MaxAge: -1,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
}
http.SetCookie(w, cookie)
}
func CreateSession(w http.ResponseWriter, r *http.Request, authSession *AuthSession) {
// lockManager.LoginStatus.Store(loginStatusKey, true) // 更新登录成功状态
// hc := r.Context().Value(loginStatusKey).(*HttpContext)
// hc.LoginStatus = true
cr := authSession.ClientRequest
ua := authSession.UserActLog
lockManager.UpdateLoginStatus(cr.Auth.Username, r.RemoteAddr, true) // 更新登录成功状态
sess := sessdata.NewSession("")
sess.Username = cr.Auth.Username
sess.Group = cr.GroupSelect
oriMac := cr.MacAddressList.MacAddress
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
sess.UserAgent = cr.UserAgent
sess.DeviceType = ua.DeviceType
sess.PlatformVersion = ua.PlatformVersion
sess.RemoteAddr = cr.RemoteAddr
// 获取客户端mac地址
sess.UniqueMac = true
macHw, err := net.ParseMAC(oriMac)
if err != nil {
var sum [16]byte
if sess.UniqueIdGlobal != "" {
sum = md5.Sum([]byte(sess.UniqueIdGlobal))
} else {
sum = md5.Sum([]byte(sess.Token))
sess.UniqueMac = false
}
macHw = sum[0:5] // 5个byte
macHw = append([]byte{0x02}, macHw...)
sess.MacAddr = macHw.String()
}
sess.MacHw = macHw
// 统一macAddr的格式
sess.MacAddr = macHw.String()
other := &dbdata.SettingOther{}
dbdata.SettingGet(other)
rd := RequestData{
SessionId: sess.Sid,
SessionToken: sess.Sid + "@" + sess.Token,
Banner: other.Banner,
ProfileName: base.Cfg.ProfileName,
ProfileHash: profileHash,
CertHash: certHash,
}
w.WriteHeader(http.StatusOK)
tplRequest(tpl_complete, w, rd)
base.Info("login", cr.Auth.Username, cr.UserAgent)
}
func LinkAuth_otp(w http.ResponseWriter, r *http.Request) {
sessionID, err := GetCookie(r, "auth-session-id")
if err != nil {
http.Error(w, "Invalid session, please login again", http.StatusUnauthorized)
return
}
sessionData, err := SessStore.GetAuthSession(sessionID)
if err != nil {
http.Error(w, "Invalid session, please login again", http.StatusUnauthorized)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
base.Error(err)
SessStore.DeleteAuthSession(sessionID)
w.WriteHeader(http.StatusBadRequest)
return
}
defer r.Body.Close()
cr := ClientRequest{}
err = xml.Unmarshal(body, &cr)
if err != nil {
base.Error(err)
SessStore.DeleteAuthSession(sessionID)
w.WriteHeader(http.StatusBadRequest)
return
}
ua := sessionData.UserActLog
username := sessionData.ClientRequest.Auth.Username
otpSecret := sessionData.ClientRequest.Auth.OtpSecret
otp := cr.Auth.SecondaryPassword
// 锁定状态判断
if !lockManager.CheckLocked(username, r.RemoteAddr) {
w.WriteHeader(http.StatusTooManyRequests)
SessStore.DeleteAuthSession(sessionID)
return
}
// 动态码错误
if !dbdata.CheckOtp(username, otp, otpSecret) {
// if sessionData.AddOtpErrCount(1) > maxOtpErrCount {
// SessStore.DeleteAuthSession(sessionID)
// http.Error(w, "TooManyError, please login again", http.StatusBadRequest)
// return
// }
// lockManager.LoginStatus.Store(loginStatusKey, false) // 记录登录失败状态
// hc := r.Context().Value(loginStatusKey).(*HttpContext)
// hc.LoginStatus = false
lockManager.UpdateLoginStatus(username, r.RemoteAddr, false) // 记录登录失败状态
base.Warn("OTP 动态码错误", username, r.RemoteAddr)
ua.Info = "OTP 动态码错误"
ua.Status = dbdata.UserAuthFail
dbdata.UserActLogIns.Add(*ua, sessionData.ClientRequest.UserAgent)
w.WriteHeader(http.StatusOK)
data := RequestData{Error: "请求错误"}
if base.Cfg.DisplayError {
data.Error = "OTP 动态码错误"
}
tplRequest(tpl_otp, w, data)
return
}
CreateSession(w, r, sessionData)
// 删除临时会话信息
SessStore.DeleteAuthSession(sessionID)
// DeleteCookie(w, "auth-session-id")
}
var auth_otp = `<?xml version="1.0" encoding="UTF-8"?>
<config-auth client="vpn" type="auth-request" aggregate-auth-version="2">
<auth id="otp-verification">
<title>OTP 动态码验证</title>
<message>请输入您的 OTP 动态码</message>
{{if .Error}}
<error id="otp-verification" param1="{{.Error}}" param2="">验证失败: %s</error>
{{end}}
<form method="post" action="/otp-verification">
<input type="password" name="secondary_password" label="OTPCode:"/>
</form>
</auth>
</config-auth>`

View File

@ -17,6 +17,8 @@ type ClientRequest struct {
Version string `xml:"version"` // 客户端版本号 Version string `xml:"version"` // 客户端版本号
GroupAccess string `xml:"group-access"` // 请求的地址 GroupAccess string `xml:"group-access"` // 请求的地址
GroupSelect string `xml:"group-select"` // 选择的组名 GroupSelect string `xml:"group-select"` // 选择的组名
RemoteAddr string `xml:"remote_addr"`
UserAgent string `xml:"user_agent"`
SessionId string `xml:"session-id"` SessionId string `xml:"session-id"`
SessionToken string `xml:"session-token"` SessionToken string `xml:"session-token"`
Auth auth `xml:"auth"` Auth auth `xml:"auth"`
@ -27,6 +29,7 @@ type ClientRequest struct {
type auth struct { type auth struct {
Username string `xml:"username"` Username string `xml:"username"`
Password string `xml:"password"` Password string `xml:"password"`
OtpSecret string `xml:"otp_secret"`
SecondaryPassword string `xml:"secondary_password"` SecondaryPassword string `xml:"secondary_password"`
} }

View File

@ -87,8 +87,8 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
cSess.CstpDpd = cstpDpd cSess.CstpDpd = cstpDpd
dtlsPort := "443" dtlsPort := "443"
if strings.Contains(base.Cfg.ServerDTLSAddr, ":") { if strings.Contains(base.Cfg.AdvertiseDTLSAddr, ":") {
ss := strings.Split(base.Cfg.ServerDTLSAddr, ":") ss := strings.Split(base.Cfg.AdvertiseDTLSAddr, ":")
dtlsPort = ss[1] dtlsPort = ss[1]
} }

View File

@ -106,6 +106,9 @@ func logAudit(userName string, pl *sessdata.Payload) {
if err := recover(); err != nil { if err := recover(); err != nil {
base.Error("logAudit is panic: ", err, "\n", string(debug.Stack()), "\n", pl.Data) base.Error("logAudit is panic: ", err, "\n", string(debug.Stack()), "\n", pl.Data)
} }
}()
defer func() {
putPayload(pl) putPayload(pl)
}() }()

View File

@ -112,8 +112,11 @@ func initRoute() http.Handler {
r.HandleFunc("/", LinkHome).Methods(http.MethodGet) r.HandleFunc("/", LinkHome).Methods(http.MethodGet)
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost) r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
// r.Handle("/", antiBruteForce(http.HandlerFunc(LinkAuth))).Methods(http.MethodPost)
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect) r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet) r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet)
r.HandleFunc("/otp-verification", LinkAuth_otp).Methods(http.MethodPost)
// r.Handle("/otp-verification", antiBruteForce(http.HandlerFunc(LinkAuth_otp))).Methods(http.MethodPost)
r.HandleFunc(fmt.Sprintf("/profile_%s.xml", base.Cfg.ProfileName), func(w http.ResponseWriter, r *http.Request) { r.HandleFunc(fmt.Sprintf("/profile_%s.xml", base.Cfg.ProfileName), func(w http.ResponseWriter, r *http.Request) {
b, _ := os.ReadFile(base.Cfg.Profile) b, _ := os.ReadFile(base.Cfg.Profile)
w.Write(b) w.Write(b)

View File

@ -17,6 +17,8 @@ func Start() {
sessdata.Start() sessdata.Start()
cron.Start() cron.Start()
admin.InitLockManager() //初始化防爆破定时器和IP白名单
// 开启服务器转发 // 开启服务器转发
err := execCmd([]string{"sysctl -w net.ipv4.ip_forward=1"}) err := execCmd([]string{"sysctl -w net.ipv4.ip_forward=1"})
if err != nil { if err != nil {

View File

@ -14,6 +14,10 @@ func PasswordHash(password string) (string, error) {
} }
func PasswordVerify(password, hash string) bool { func PasswordVerify(password, hash string) bool {
// 保留老用户明文验证
if len(hash) != 60 {
return password == hash
}
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil return err == nil
} }

View File

@ -1,7 +1,10 @@
package utils package utils
import ( import (
crand "crypto/rand"
"encoding/hex"
"fmt" "fmt"
"log"
"math/rand" "math/rand"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -83,9 +86,7 @@ func HumanByte(bf interface{}) string {
func RandomRunes(length int) string { func RandomRunes(length int) string {
letterRunes := []rune("abcdefghijklmnpqrstuvwxy1234567890") letterRunes := []rune("abcdefghijklmnpqrstuvwxy1234567890")
bytes := make([]rune, length) bytes := make([]rune, length)
for i := range bytes { for i := range bytes {
bytes[i] = letterRunes[rand.Intn(len(letterRunes))] bytes[i] = letterRunes[rand.Intn(len(letterRunes))]
} }
@ -93,6 +94,17 @@ func RandomRunes(length int) string {
return string(bytes) return string(bytes)
} }
func RandomHex(length int) string {
b := make([]byte, length)
_, err := crand.Read(b)
if err != nil {
log.Println(err)
return ""
}
return hex.EncodeToString(b)
}
func ParseName(name string) string { func ParseName(name string) string {
name = strings.ReplaceAll(name, " ", "-") name = strings.ReplaceAll(name, " ", "-")
name = strings.ReplaceAll(name, "'", "-") name = strings.ReplaceAll(name, "'", "-")

View File

@ -13,11 +13,9 @@ import (
var ( var (
IpPool = &ipPoolConfig{} IpPool = &ipPoolConfig{}
ipActive = map[string]bool{} ipActive = map[string]bool{}
// ipKeep and ipLease ipAddr => type // ipKeep and ipLease ipAddr => macAddr
// ipLease = map[string]bool{} // ipKeep = map[string]string{}
ipPoolMux sync.Mutex ipPoolMux sync.Mutex
// 记录循环点
loopCurIp uint32
) )
type ipPoolConfig struct { type ipPoolConfig struct {
@ -73,21 +71,27 @@ func initIpPool() {
// func getIpLease() { // func getIpLease() {
// xdb := dbdata.GetXdb() // xdb := dbdata.GetXdb()
// keepIpMaps := []dbdata.IpMap{} // keepIpMaps := []dbdata.IpMap{}
// sNow := time.Now().Add(-1 * time.Duration(base.Cfg.IpLease) * time.Second) // // sNow := time.Now().Add(-1 * time.Duration(base.Cfg.IpLease) * time.Second)
// err := xdb.Cols("ip_addr").Where("keep=?", true). // err := xdb.Cols("ip_addr", "mac_addr").Where("keep=?", true).Find(&keepIpMaps)
// Or("unique_mac=? and last_login>?", true, sNow).Find(&keepIpMaps)
// if err != nil { // if err != nil {
// base.Error(err) // base.Error(err)
// } // }
// // fmt.Println(keepIpMaps) // log.Println(keepIpMaps)
// ipPoolMux.Lock() // ipPoolMux.Lock()
// ipLease = map[string]bool{} // ipKeep = map[string]string{}
// for _, v := range keepIpMaps { // for _, v := range keepIpMaps {
// ipLease[v.IpAddr] = true // ipKeep[v.IpAddr] = v.MacAddr
// } // }
// ipPoolMux.Unlock() // ipPoolMux.Unlock()
// } // }
func ipInPool(ip net.IP) bool {
if utils.Ip2long(ip) >= IpPool.IpLongMin && utils.Ip2long(ip) <= IpPool.IpLongMax {
return true
}
return false
}
// AcquireIp 获取动态ip // AcquireIp 获取动态ip
func AcquireIp(username, macAddr string, uniqueMac bool) (newIp net.IP) { func AcquireIp(username, macAddr string, uniqueMac bool) (newIp net.IP) {
base.Trace("AcquireIp start:", username, macAddr, uniqueMac) base.Trace("AcquireIp start:", username, macAddr, uniqueMac)
@ -95,6 +99,7 @@ func AcquireIp(username, macAddr string, uniqueMac bool) (newIp net.IP) {
defer func() { defer func() {
ipPoolMux.Unlock() ipPoolMux.Unlock()
base.Trace("AcquireIp end:", username, macAddr, uniqueMac, newIp) base.Trace("AcquireIp end:", username, macAddr, uniqueMac, newIp)
base.Info("AcquireIp ip:", username, macAddr, uniqueMac, newIp)
}() }()
var ( var (
@ -102,6 +107,7 @@ func AcquireIp(username, macAddr string, uniqueMac bool) (newIp net.IP) {
tNow = time.Now() tNow = time.Now()
) )
// 获取到客户端 macAddr 的情况
if uniqueMac { if uniqueMac {
// 判断是否已经分配过 // 判断是否已经分配过
mi := &dbdata.IpMap{} mi := &dbdata.IpMap{}
@ -124,9 +130,9 @@ func AcquireIp(username, macAddr string, uniqueMac bool) (newIp net.IP) {
_, ok := ipActive[ipStr] _, ok := ipActive[ipStr]
// 检测原有ip是否在新的ip池内 // 检测原有ip是否在新的ip池内
// IpPool.Ipv4IPNet.Contains(ip) && // IpPool.Ipv4IPNet.Contains(ip) &&
if !ok && // ip符合规范
utils.Ip2long(ip) >= IpPool.IpLongMin && // 检测原有ip是否在新的ip池内
utils.Ip2long(ip) <= IpPool.IpLongMax { if !ok && ipInPool(ip) {
mi.Username = username mi.Username = username
mi.LastLogin = tNow mi.LastLogin = tNow
mi.UniqueMac = uniqueMac mi.UniqueMac = uniqueMac
@ -135,61 +141,81 @@ func AcquireIp(username, macAddr string, uniqueMac bool) (newIp net.IP) {
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
// 删除当前macAddr
mi = &dbdata.IpMap{MacAddr: macAddr}
_ = dbdata.Del(mi)
} else { // ip保留
// 没有获取到mac的情况 if mi.Keep {
ipMaps := []dbdata.IpMap{} base.Error(username, macAddr, ipStr, "保留ip不匹配CIDR")
err = dbdata.FindWhere(&ipMaps, 50, 1, "username=? and unique_mac=?", username, false)
if err != nil {
// 没有查询到数据
if dbdata.CheckErrNotFound(err) {
return loopIp(username, macAddr, uniqueMac)
}
// 查询报错
base.Error(err)
return nil return nil
} }
// 遍历mac记录 // 删除当前macAddr
for _, mi := range ipMaps { mi = &dbdata.IpMap{MacAddr: macAddr}
ipStr := mi.IpAddr _ = dbdata.Del(mi)
ip := net.ParseIP(ipStr) return loopIp(username, macAddr, uniqueMac)
}
// 跳过活跃连接 // 没有获取到mac的情况
if _, ok := ipActive[ipStr]; ok { ipMaps := []dbdata.IpMap{}
continue err = dbdata.FindWhere(&ipMaps, 30, 1, "username=?", username)
} if err != nil {
// 跳过保留ip // 没有查询到数据
if mi.Keep { if dbdata.CheckErrNotFound(err) {
continue return loopIp(username, macAddr, uniqueMac)
} }
// 没有mac的 不需要验证租期 // 查询报错
// mi.LastLogin.Before(leaseTime) && base.Error(err)
if utils.Ip2long(ip) >= IpPool.IpLongMin && return nil
utils.Ip2long(ip) <= IpPool.IpLongMax { }
mi.LastLogin = tNow
mi.MacAddr = macAddr // 遍历mac记录
mi.UniqueMac = uniqueMac for _, mi := range ipMaps {
// 回写db数据 ipStr := mi.IpAddr
_ = dbdata.Set(mi) ip := net.ParseIP(ipStr)
ipActive[ipStr] = true
return ip // 跳过活跃连接
} if _, ok := ipActive[ipStr]; ok {
continue
}
// 跳过保留ip
if mi.Keep {
continue
}
if mi.UniqueMac {
continue
}
// 没有mac的 不需要验证租期
// mi.LastLogin.Before(leaseTime) &&
if ipInPool(ip) {
mi.Username = username
mi.LastLogin = tNow
mi.MacAddr = macAddr
mi.UniqueMac = uniqueMac
// 回写db数据
_ = dbdata.Set(mi)
ipActive[ipStr] = true
return ip
} }
} }
return loopIp(username, macAddr, uniqueMac) return loopIp(username, macAddr, uniqueMac)
} }
var (
// 记录循环点
loopCurIp uint32
loopFarIp *dbdata.IpMap
)
func loopIp(username, macAddr string, uniqueMac bool) net.IP { func loopIp(username, macAddr string, uniqueMac bool) net.IP {
var ( var (
i uint32 i uint32
ip net.IP ip net.IP
) )
// 重新赋值
loopFarIp = &dbdata.IpMap{LastLogin: time.Now()}
i, ip = loopLong(loopCurIp, IpPool.IpLongMax, username, macAddr, uniqueMac) i, ip = loopLong(loopCurIp, IpPool.IpLongMax, username, macAddr, uniqueMac)
if ip != nil { if ip != nil {
loopCurIp = i loopCurIp = i
@ -202,6 +228,22 @@ func loopIp(username, macAddr string, uniqueMac bool) net.IP {
return ip return ip
} }
// ip分配完,从头开始
loopCurIp = IpPool.IpLongMin
if loopFarIp.Id > 0 {
// 使用最早登陆的 ip
ipStr := loopFarIp.IpAddr
ip = net.ParseIP(ipStr)
mi := &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, UniqueMac: uniqueMac, Username: username, LastLogin: time.Now()}
// 回写db数据
_ = dbdata.Set(mi)
ipActive[ipStr] = true
return ip
}
// 全都在线,没有数据可用
base.Warn("no ip available, please see ip_map table row", username, macAddr) base.Warn("no ip available, please see ip_map table row", username, macAddr)
return nil return nil
} }
@ -247,6 +289,7 @@ func loopLong(start, end uint32, username, macAddr string, uniqueMac bool) (uint
// 判断租期 // 判断租期
if mi.LastLogin.Before(leaseTime) { if mi.LastLogin.Before(leaseTime) {
// 存在记录,说明已经超过租期,可以直接使用 // 存在记录,说明已经超过租期,可以直接使用
mi.Username = username
mi.LastLogin = tNow mi.LastLogin = tNow
mi.MacAddr = macAddr mi.MacAddr = macAddr
mi.UniqueMac = uniqueMac mi.UniqueMac = uniqueMac
@ -255,6 +298,10 @@ func loopLong(start, end uint32, username, macAddr string, uniqueMac bool) (uint
ipActive[ipStr] = true ipActive[ipStr] = true
return i, ip return i, ip
} }
// 其他情况判断最早登陆
if mi.LastLogin.Before(loopFarIp.LastLogin) {
loopFarIp = mi
}
} }
return 0, nil return 0, nil

View File

@ -11,21 +11,22 @@ import (
) )
type Online struct { type Online struct {
Token string `json:"token"` Token string `json:"token"`
Username string `json:"username"` Username string `json:"username"`
Group string `json:"group"` Group string `json:"group"`
MacAddr string `json:"mac_addr"` MacAddr string `json:"mac_addr"`
UniqueMac bool `json:"unique_mac"` UniqueMac bool `json:"unique_mac"`
Ip net.IP `json:"ip"` Ip net.IP `json:"ip"`
RemoteAddr string `json:"remote_addr"` RemoteAddr string `json:"remote_addr"`
TunName string `json:"tun_name"` TransportProtocol string `json:"transport_protocol"`
Mtu int `json:"mtu"` TunName string `json:"tun_name"`
Client string `json:"client"` Mtu int `json:"mtu"`
BandwidthUp string `json:"bandwidth_up"` Client string `json:"client"`
BandwidthDown string `json:"bandwidth_down"` BandwidthUp string `json:"bandwidth_up"`
BandwidthUpAll string `json:"bandwidth_up_all"` BandwidthDown string `json:"bandwidth_down"`
BandwidthDownAll string `json:"bandwidth_down_all"` BandwidthUpAll string `json:"bandwidth_up_all"`
LastLogin time.Time `json:"last_login"` BandwidthDownAll string `json:"bandwidth_down_all"`
LastLogin time.Time `json:"last_login"`
} }
type Onlines []Online type Onlines []Online
@ -90,22 +91,28 @@ func GetOnlineSess(search_cate string, search_text string, show_sleeper bool) []
} }
if show_sleeper || v.IsActive { if show_sleeper || v.IsActive {
transportProtocol := "TCP"
dSess := cSess.GetDtlsSession()
if dSess != nil {
transportProtocol = "UDP"
}
val := Online{ val := Online{
Token: v.Token, Token: v.Token,
Ip: cSess.IpAddr, Ip: cSess.IpAddr,
Username: v.Username, Username: v.Username,
Group: v.Group, Group: v.Group,
MacAddr: v.MacAddr, MacAddr: v.MacAddr,
UniqueMac: v.UniqueMac, UniqueMac: v.UniqueMac,
RemoteAddr: cSess.RemoteAddr, RemoteAddr: cSess.RemoteAddr,
TunName: cSess.IfName, TransportProtocol: transportProtocol,
Mtu: cSess.Mtu, TunName: cSess.IfName,
Client: cSess.Client, Mtu: cSess.Mtu,
BandwidthUp: utils.HumanByte(cSess.BandwidthUpPeriod.Load()) + "/s", Client: cSess.Client,
BandwidthDown: utils.HumanByte(cSess.BandwidthDownPeriod.Load()) + "/s", BandwidthUp: utils.HumanByte(cSess.BandwidthUpPeriod.Load()) + "/s",
BandwidthUpAll: utils.HumanByte(cSess.BandwidthUpAll.Load()), BandwidthDown: utils.HumanByte(cSess.BandwidthDownPeriod.Load()) + "/s",
BandwidthDownAll: utils.HumanByte(cSess.BandwidthDownAll.Load()), BandwidthUpAll: utils.HumanByte(cSess.BandwidthUpAll.Load()),
LastLogin: v.LastLogin, BandwidthDownAll: utils.HumanByte(cSess.BandwidthDownAll.Load()),
LastLogin: v.LastLogin,
} }
datas = append(datas, val) datas = append(datas, val)
} }

View File

@ -2,7 +2,6 @@ package sessdata
import ( import (
"fmt" "fmt"
"math/rand"
"net" "net"
"strconv" "strconv"
"strings" "strings"
@ -12,6 +11,7 @@ import (
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
mapset "github.com/deckarep/golang-set" mapset "github.com/deckarep/golang-set"
) )
@ -91,10 +91,6 @@ type Session struct {
CSess *ConnSession CSess *ConnSession
} }
func init() {
rand.Seed(time.Now().UnixNano())
}
func checkSession() { func checkSession() {
// 检测过期的session // 检测过期的session
go func() { go func() {
@ -144,28 +140,16 @@ func CloseUserLimittimeSession() {
} }
} }
func GenToken() string {
// 生成32位的 token
bToken := make([]byte, 32)
rand.Read(bToken)
return fmt.Sprintf("%x", bToken)
}
func NewSession(token string) *Session { func NewSession(token string) *Session {
if token == "" { if token == "" {
btoken := make([]byte, 32) token = utils.RandomHex(32)
rand.Read(btoken)
token = fmt.Sprintf("%x", btoken)
} }
// 生成 dtlsn session_id // 生成 dtlsn session_id
dtlsid := make([]byte, 32)
rand.Read(dtlsid)
sess := &Session{ sess := &Session{
Sid: fmt.Sprintf("%d", time.Now().Unix()), Sid: fmt.Sprintf("%d", time.Now().Unix()),
Token: token, Token: token,
DtlsSid: fmt.Sprintf("%x", dtlsid), DtlsSid: utils.RandomHex(32),
LastLogin: time.Now(), LastLogin: time.Now(),
} }

View File

@ -1 +1 @@
0.12.2 0.13.1

View File

@ -7,17 +7,9 @@
<!--<div class="layout-aside" :style="aside_style">--> <!--<div class="layout-aside" :style="aside_style">-->
<el-menu :collapse="!is_active" <el-menu :collapse="!is_active" :default-active="route_path" :style="is_active ? 'width:200px' : ''" router
:default-active="route_path" class="layout-menu" :collapse-transition="false" background-color="#545c64" text-color="#fff"
:style="is_active?'width:200px':''" active-text-color="#ffd04b">
router
class="layout-menu"
:collapse-transition="false"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu-item index="/admin/home"> <el-menu-item index="/admin/home">
<i class="el-icon-s-home"></i> <i class="el-icon-s-home"></i>
<span slot="title">首页</span> <span slot="title">首页</span>
@ -44,6 +36,7 @@
<el-menu-item index="/admin/user/list">用户列表</el-menu-item> <el-menu-item index="/admin/user/list">用户列表</el-menu-item>
<el-menu-item index="/admin/user/policy">用户策略</el-menu-item> <el-menu-item index="/admin/user/policy">用户策略</el-menu-item>
<el-menu-item index="/admin/user/online">在线用户</el-menu-item> <el-menu-item index="/admin/user/online">在线用户</el-menu-item>
<el-menu-item index="/admin/user/lockmanager">锁定管理</el-menu-item>
<el-menu-item index="/admin/user/ip_map">IP映射</el-menu-item> <el-menu-item index="/admin/user/ip_map">IP映射</el-menu-item>
</el-submenu> </el-submenu>

View File

@ -48,6 +48,7 @@
label="唯一MAC"> label="唯一MAC">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.unique_mac" type="success"></el-tag> <el-tag v-if="scope.row.unique_mac" type="success"></el-tag>
<el-tag v-else type="info"></el-tag>
</template> </template>
</el-table-column> </el-table-column>

View File

@ -3,22 +3,13 @@
<el-card> <el-card>
<el-form :inline="true"> <el-form :inline="true">
<el-form-item> <el-form-item>
<el-button <el-button size="small" type="primary" icon="el-icon-plus" @click="handleEdit('')">添加
size="small"
type="primary"
icon="el-icon-plus"
@click="handleEdit('')">添加
</el-button> </el-button>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-dropdown size="small" placement="bottom"> <el-dropdown size="small" placement="bottom">
<el-upload <el-upload class="uploaduser" action="uploaduser" accept=".xlsx, .xls" :http-request="upLoadUser" :limit="1"
class="uploaduser" :show-file-list="false">
action="uploaduser"
accept=".xlsx, .xls"
:http-request="upLoadUser"
:limit="1"
:show-file-list="false">
<el-button size="small" icon="el-icon-upload2" type="primary">批量添加</el-button> <el-button size="small" icon="el-icon-upload2" type="primary">批量添加</el-button>
</el-upload> </el-upload>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
@ -32,79 +23,45 @@
</el-form-item> </el-form-item>
<el-form-item label="用户名或姓名或邮箱:"> <el-form-item label="用户名或姓名或邮箱:">
<el-input size="small" v-model="searchData" placeholder="请输入内容" <el-input size="small" v-model="searchData" placeholder="请输入内容"
@keydown.enter.native="searchEnterFun"></el-input> @keydown.enter.native="searchEnterFun"></el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button <el-button size="small" type="primary" icon="el-icon-search" @click="handleSearch()">搜索
size="small"
type="primary"
icon="el-icon-search"
@click="handleSearch()">搜索
</el-button> </el-button>
<el-button <el-button size="small" icon="el-icon-refresh" @click="reset">重置搜索
size="small"
icon="el-icon-refresh"
@click="reset">重置搜索
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table <el-table ref="multipleTable" :data="tableData" border>
ref="multipleTable"
:data="tableData"
border>
<el-table-column <el-table-column sortable="true" prop="id" label="ID" width="60">
sortable="true"
prop="id"
label="ID"
width="60">
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="username" label="用户名" width="150">
prop="username"
label="用户名"
width="150">
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="nickname" label="姓名" width="100">
prop="nickname"
label="姓名"
width="100">
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="email" label="邮箱">
prop="email"
label="邮箱">
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="otp_secret" label="OTP密钥" width="110">
prop="otp_secret"
label="OTP密钥"
width="110">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button v-if="!scope.row.disable_otp" type="text" icon="el-icon-view" @click="getOtpImg(scope.row)">
v-if="!scope.row.disable_otp"
type="text"
icon="el-icon-view"
@click="getOtpImg(scope.row)">
{{ scope.row.otp_secret.substring(0, 6) }} {{ scope.row.otp_secret.substring(0, 6) }}
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="groups" label="用户组">
prop="groups"
label="用户组">
<template slot-scope="scope"> <template slot-scope="scope">
<el-row v-for="item in scope.row.groups" :key="item">{{ item }}</el-row> <el-row v-for="item in scope.row.groups" :key="item">{{ item }}</el-row>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="status" label="状态" width="70">
prop="status"
label="状态"
width="70">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.status === 1" type="success">可用</el-tag> <el-tag v-if="scope.row.status === 1" type="success">可用</el-tag>
<el-tag v-if="scope.row.status === 0" type="danger">停用</el-tag> <el-tag v-if="scope.row.status === 0" type="danger">停用</el-tag>
@ -112,20 +69,12 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="updated_at" label="更新时间" :formatter="tableDateFormat">
prop="updated_at"
label="更新时间"
:formatter="tableDateFormat">
</el-table-column> </el-table-column>
<el-table-column <el-table-column label="操作" width="210">
label="操作"
width="210">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button size="mini" type="primary" @click="handleEdit(scope.row)">编辑
size="mini"
type="primary"
@click="handleEdit(scope.row)">编辑
</el-button> </el-button>
<!-- <el-popconfirm <!-- <el-popconfirm
@ -139,14 +88,8 @@
</el-button> </el-button>
</el-popconfirm>--> </el-popconfirm>-->
<el-popconfirm <el-popconfirm class="m-left-10" @confirm="handleDel(scope.row)" title="确定要删除用户吗?">
class="m-left-10" <el-button slot="reference" size="mini" type="danger">删除
@confirm="handleDel(scope.row)"
title="确定要删除用户吗?">
<el-button
slot="reference"
size="mini"
type="danger">删除
</el-button> </el-button>
</el-popconfirm> </el-popconfirm>
@ -156,34 +99,20 @@
<div class="sh-20"></div> <div class="sh-20"></div>
<el-pagination <el-pagination background layout="prev, pager, next" :pager-count="11" @current-change="pageChange"
background :current-page="page" :total="count">
layout="prev, pager, next"
:pager-count="11"
@current-change="pageChange"
:current-page="page"
:total="count">
</el-pagination> </el-pagination>
</el-card> </el-card>
<el-dialog <el-dialog title="OTP密钥" :visible.sync="otpImgData.visible" width="350px" center>
title="OTP密钥"
:visible.sync="otpImgData.visible"
width="350px"
center>
<div style="text-align: center">{{ otpImgData.username }} : {{ otpImgData.nickname }}</div> <div style="text-align: center">{{ otpImgData.username }} : {{ otpImgData.nickname }}</div>
<img :src="otpImgData.base64Img" alt="otp-img"/> <img :src="otpImgData.base64Img" alt="otp-img" />
</el-dialog> </el-dialog>
<!--新增修改弹出框--> <!--新增修改弹出框-->
<el-dialog <el-dialog :close-on-click-modal="false" title="用户" :visible="user_edit_dialog" @close="disVisible" width="650px"
:close-on-click-modal="false" center>
title="用户"
:visible="user_edit_dialog"
@close="disVisible"
width="650px"
center>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm"> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
<el-form-item label="用户ID" prop="id"> <el-form-item label="用户ID" prop="id">
@ -204,21 +133,13 @@
</el-form-item> </el-form-item>
<el-form-item label="过期时间" prop="limittime"> <el-form-item label="过期时间" prop="limittime">
<el-date-picker <el-date-picker v-model="ruleForm.limittime" type="date" size="small" align="center" style="width:130px"
v-model="ruleForm.limittime" :picker-options="pickerOptions" placeholder="选择日期">
type="date"
size="small"
align="center"
style="width:130px"
:picker-options="pickerOptions"
placeholder="选择日期">
</el-date-picker> </el-date-picker>
</el-form-item> </el-form-item>
<el-form-item label="禁用OTP" prop="disable_otp"> <el-form-item label="禁用OTP" prop="disable_otp">
<el-switch <el-switch v-model="ruleForm.disable_otp" active-text="开启OTP后用户密码为PIN码,OTP密码为扫码后生成的动态码">
v-model="ruleForm.disable_otp"
active-text="开启OTP后用户密码为【PIN码+OTP动态码】(中间没有+号)">
</el-switch> </el-switch>
</el-form-item> </el-form-item>
@ -233,8 +154,7 @@
</el-form-item> </el-form-item>
<el-form-item label="发送邮件" prop="send_email"> <el-form-item label="发送邮件" prop="send_email">
<el-switch <el-switch v-model="ruleForm.send_email">
v-model="ruleForm.send_email">
</el-switch> </el-switch>
</el-form-item> </el-form-item>
@ -286,7 +206,7 @@ export default {
} }
}, },
searchData: '', searchData: '',
otpImgData: {visible: false, username: '', nickname: '', base64Img: ''}, otpImgData: { visible: false, username: '', nickname: '', base64Img: '' },
ruleForm: { ruleForm: {
send_email: true, send_email: true,
status: 1, status: 1,
@ -294,30 +214,30 @@ export default {
}, },
rules: { rules: {
username: [ username: [
{required: true, message: '请输入用户名', trigger: 'blur'}, { required: true, message: '请输入用户名', trigger: 'blur' },
{max: 50, message: '长度小于 50 个字符', trigger: 'blur'} { max: 50, message: '长度小于 50 个字符', trigger: 'blur' }
], ],
nickname: [ nickname: [
{required: true, message: '请输入用户姓名', trigger: 'blur'} { required: true, message: '请输入用户姓名', trigger: 'blur' }
], ],
email: [ email: [
{required: true, message: '请输入用户邮箱', trigger: 'blur'}, { required: true, message: '请输入用户邮箱', trigger: 'blur' },
{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']} { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
], ],
password: [ password: [
{min: 6, message: '长度大于 6 个字符', trigger: 'blur'} { min: 6, message: '长度大于 6 个字符', trigger: 'blur' }
], ],
pin_code: [ pin_code: [
{min: 6, message: 'PIN码大于 6 个字符', trigger: 'blur'} { min: 6, message: 'PIN码大于 6 个字符', trigger: 'blur' }
], ],
date1: [ date1: [
{type: 'date', required: true, message: '请选择日期', trigger: 'change'} { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
], ],
groups: [ groups: [
{type: 'array', required: true, message: '请至少选择一个组', trigger: 'change'} { type: 'array', required: true, message: '请至少选择一个组', trigger: 'change' }
], ],
status: [ status: [
{required: true} { required: true }
], ],
}, },
} }
@ -473,6 +393,4 @@ export default {
} }
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -0,0 +1,109 @@
<template>
<div id="lock-manager">
<el-card>
<div slot="header">
<el-button type="primary" @click="getLocks">刷新信息</el-button>
</div>
<el-table :data="locksInfo" style="width: 100%" border>
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column prop="username" label="用户名"></el-table-column>
<el-table-column prop="ip" label="IP地址"></el-table-column>
<el-table-column prop="state.locked" label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.state.locked ? 'danger' : 'success'">
{{ scope.row.state.locked ? '已锁定' : '未锁定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="state.attempts" label="失败次数"></el-table-column>
<el-table-column prop="state.lock_time" label="锁定截止时间">
<template slot-scope="scope">
{{ formatDate(scope.row.state.lock_time) }}
</template>
</el-table-column>
<el-table-column prop="state.lastAttempt" label="最后尝试时间">
<template slot-scope="scope">
{{ formatDate(scope.row.state.lastAttempt) }}
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<div class="button">
<el-button size="small" type="danger" @click="unlock(scope.row)">
解锁
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'LockManager',
data() {
return {
locksInfo: []
};
},
methods: {
getLocks() {
axios.get('/locksinfo/list')
.then(response => {
this.locksInfo = response.data.data;
})
.catch(error => {
console.error('Failed to get locks info:', error);
this.$message.error('无法获取锁信息,请稍后再试。');
});
},
unlock(lock) {
const lockInfo = {
state: { locked: false },
username: lock.username,
ip: lock.ip,
description: lock.description
};
axios.post('/locksinfo/unlok', lockInfo)
.then(() => {
this.$message.success('解锁成功!');
this.getLocks();
})
.catch(error => {
console.error('Failed to unlock:', error);
this.$message.error('解锁失败,请稍后再试。');
});
},
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(date);
}
},
created() {
this.getLocks();
}
};
</script>
<style scoped>
.button {
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -86,6 +86,7 @@
label="唯一MAC"> label="唯一MAC">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.unique_mac" type="success"></el-tag> <el-tag v-if="scope.row.unique_mac" type="success"></el-tag>
<el-tag v-else type="info"></el-tag>
</template> </template>
</el-table-column> </el-table-column>
@ -99,7 +100,10 @@
prop="remote_addr" prop="remote_addr"
label="远端地址"> label="远端地址">
</el-table-column> </el-table-column>
<el-table-column
prop="transport_protocol"
label="传输协议">
</el-table-column>
<el-table-column <el-table-column
prop="tun_name" prop="tun_name"
label="虚拟网卡"> label="虚拟网卡">

View File

@ -1,35 +1,36 @@
import Vue from "vue"; import Vue from "vue";
import VueRouter from "vue-router"; import VueRouter from "vue-router";
import {getToken} from "./token"; import { getToken } from "./token";
Vue.use(VueRouter) Vue.use(VueRouter)
const routes = [ const routes = [
{path: '/login', component: () => import('@/pages/Login')}, { path: '/login', component: () => import('@/pages/Login') },
{ {
path: '/admin', path: '/admin',
component: () => import('@/layout/Layout'), component: () => import('@/layout/Layout'),
redirect: '/admin/home', redirect: '/admin/home',
children: [ children: [
{path: 'home', component: () => import('@/pages/Home')}, { path: 'home', component: () => import('@/pages/Home') },
{path: 'set/system', component: () => import('@/pages/set/System')}, { path: 'set/system', component: () => import('@/pages/set/System') },
{path: 'set/soft', component: () => import('@/pages/set/Soft')}, { path: 'set/soft', component: () => import('@/pages/set/Soft') },
{path: 'set/other', component: () => import('@/pages/set/Other')}, { path: 'set/other', component: () => import('@/pages/set/Other') },
{path: 'set/audit', component: () => import('@/pages/set/Audit')}, { path: 'set/audit', component: () => import('@/pages/set/Audit') },
{path: 'user/list', component: () => import('@/pages/user/List')}, { path: 'user/list', component: () => import('@/pages/user/List') },
{path: 'user/policy', component: () => import('@/pages/user/Policy')}, { path: 'user/policy', component: () => import('@/pages/user/Policy') },
{path: 'user/online', component: () => import('@/pages/user/Online')}, { path: 'user/online', component: () => import('@/pages/user/Online') },
{path: 'user/ip_map', component: () => import('@/pages/user/IpMap')}, { path: 'user/ip_map', component: () => import('@/pages/user/IpMap') },
{ path: 'user/lockmanager', component: () => import('@/pages/user/LockManager') },
{path: 'group/list', component: () => import('@/pages/group/List')}, { path: 'group/list', component: () => import('@/pages/group/List') },
], ],
}, },
{path: '*', redirect: '/admin/home'}, { path: '*', redirect: '/admin/home' },
] ]
// 3. 创建 router 实例,然后传 `routes` 配置 // 3. 创建 router 实例,然后传 `routes` 配置
@ -64,7 +65,7 @@ router.beforeEach((to, from, next) => {
} }
if (to.path === "/login") { if (to.path === "/login") {
next({path: '/admin/home'}); next({ path: '/admin/home' });
return; return;
} }