Compare commits

...

14 Commits

Author SHA1 Message Date
lejianwen
2ade0dda42 chore: Noelware/docker-manifest-action 2025-04-25 16:20:36 +08:00
lejianwen
a87ae5cf65 chore: Noelware/docker-manifest-action 2025-04-25 14:34:45 +08:00
lejianwen
fe7b8b53a6 style: Oauth page languages 2025-04-24 21:52:43 +08:00
lejianwen
b929f3efdb style: Remove useless configurations 2025-04-15 10:52:46 +08:00
lejianwen
f847fc076f fix: Low case (#149) 2025-04-15 10:46:21 +08:00
lejianwen
60d0a701ce fix: Share pwd 2025-04-15 10:09:56 +08:00
lejianwen
0dedaf6824 feat: Peer share to group 2025-04-14 19:12:40 +08:00
lejianwen
ab231b3fed feat: Add SysInfoVer endpoint and AppService for version retrieval 2025-04-07 16:38:21 +08:00
lejianwen
e7f28cca36 fix: Update peer based on the UUID (#180) 2025-04-02 09:50:16 +08:00
lejianwen
505e8aac4b feat: Add Korean translations validator (#168) 2025-04-02 09:42:29 +08:00
lejianwen
746e2a6052 fix: Get Uuids 2025-03-15 21:02:47 +08:00
lejianwen
dc03d5d83d style: Update peer last online time logic (#173) 2025-03-15 21:02:08 +08:00
lejianwen
b770ab178d feat(admin): Add filter by ip and username (#172) 2025-03-15 19:49:49 +08:00
Tao Chen
fd7e022e88 fix: rm varify password accidentally (#176) 2025-03-15 19:40:02 +08:00
34 changed files with 265 additions and 126 deletions

View File

@@ -380,7 +380,7 @@ jobs:
- name: Create and push manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
@@ -390,7 +390,7 @@ jobs:
- name: Create and push manifest GHCR (:version)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
@@ -401,7 +401,7 @@ jobs:
- name: Create and push manifest Docker Hub (:latest)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64,
@@ -411,7 +411,7 @@ jobs:
- name: Create and push manifest GHCR (:latest)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64,
@@ -422,7 +422,7 @@ jobs:
- name: Create and push Full S6 manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
@@ -433,7 +433,7 @@ jobs:
- name: Create and push Full S6 manifest GHCR (:latest)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,

View File

@@ -317,7 +317,7 @@ jobs:
- name: Create and push manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
@@ -327,7 +327,7 @@ jobs:
- name: Create and push manifest GHCR (:version)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,

View File

@@ -76,7 +76,6 @@ COPY --from=builder-backend /app/release /app/
COPY --from=builder-backend /app/conf /app/conf/
COPY --from=builder-backend /app/resources /app/resources/
COPY --from=builder-backend /app/docs /app/docs/
COPY --from=builder-backend /app/http/templates /app/http/templates
# Copy frontend build from builder2 stage
COPY --from=builder-admin-frontend /frontend/dist/ /app/resources/admin/

View File

@@ -64,21 +64,3 @@ ldap:
sync: false # If true, the user will be synchronized to the database when the user logs in. If false, the user will be synchronized to the database when the user be created.
admin-group: "cn=admin,dc=example,dc=com" # The group name of the admin group, if the user is in this group, the user will be an admin.
redis:
addr: "127.0.0.1:6379"
password: ""
db: 0
cache:
type: "file"
file-dir: "./runtime/cache"
redis-addr: "127.0.0.1:6379"
redis-pwd: ""
redis-db: 0
oss:
access-key-id: ""
access-key-secret: ""
host: ""
callback-url: ""
expire-time: 30
max-byte: 10240

View File

@@ -14,6 +14,7 @@ import (
en_translations "github.com/go-playground/validator/v10/translations/en"
es_translations "github.com/go-playground/validator/v10/translations/es"
fr_translations "github.com/go-playground/validator/v10/translations/fr"
ko_translations "github.com/go-playground/validator/v10/translations/ko"
ru_translations "github.com/go-playground/validator/v10/translations/ru"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
zh_tw_translations "github.com/go-playground/validator/v10/translations/zh_tw"
@@ -51,8 +52,7 @@ func ApiInitValidator() {
panic(err)
}
//validate没有ko的翻译使用zh的翻译
err = zh_translations.RegisterDefaultTranslations(validate, koTrans)
err = ko_translations.RegisterDefaultTranslations(validate, koTrans)
if err != nil {
panic(err)
}

13
go.mod
View File

@@ -10,7 +10,7 @@ require (
github.com/gin-gonic/gin v1.9.0
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.11.2
github.com/go-playground/validator/v10 v10.26.0
github.com/go-redis/redis/v8 v8.11.4
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
@@ -22,7 +22,7 @@ require (
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
golang.org/x/oauth2 v0.23.0
golang.org/x/text v0.21.0
golang.org/x/text v0.22.0
gorm.io/driver/mysql v1.5.7
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.7
@@ -38,6 +38,7 @@ require (
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/coreos/go-oidc/v3 v3.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
@@ -56,7 +57,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
@@ -75,10 +76,10 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect

View File

@@ -120,7 +120,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionR
//check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId {
return "ParamsError", false
return "CannotShareToSelf", false
}
tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 {
@@ -135,7 +135,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionR
return "ParamsError", false
}
// 重复检查
ex := service.AllService.AddressBookService.RulePersonalInfoByToIdAndCid(t.ToId, t.CollectionId)
ex := service.AllService.AddressBookService.RuleInfoByToIdAndCid(t.Type, t.ToId, t.CollectionId)
if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false
}

View File

@@ -100,21 +100,21 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
//check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId {
return "ParamsError", false
return "CannotShareToSelf", false
}
tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 {
return "ItemNotFound", false
}
//非管理员不能分享给非本组织用户
if tou.GroupId != u.GroupId {
return "NoAccess", false
}
//if tou.GroupId != u.GroupId {
// return "NoAccess", false
//}
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
//非管理员不能分享给其他组
if t.ToId != u.GroupId {
return "NoAccess", false
}
//if t.ToId != u.GroupId {
// return "NoAccess", false
//}
tog := service.AllService.GroupService.InfoById(t.ToId)
if tog.Id == 0 {
@@ -124,7 +124,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
return "ParamsError", false
}
// 重复检查
ex := service.AllService.AddressBookService.RulePersonalInfoByToIdAndCid(t.ToId, t.CollectionId)
ex := service.AllService.AddressBookService.RuleInfoByToIdAndCid(t.Type, t.ToId, t.CollectionId)
if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false
}

View File

@@ -108,6 +108,12 @@ func (ct *Peer) List(c *gin.Context) {
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
if query.Username != "" {
tx.Where("username like ?", "%"+query.Username+"%")
}
if query.Ip != "" {
tx.Where("last_online_ip like ?", "%"+query.Ip+"%")
}
})
response.Success(c, res)
}

View File

@@ -296,32 +296,12 @@ func (ct *User) MyOauth(c *gin.Context) {
// groupUsers
func (ct *User) GroupUsers(c *gin.Context) {
q := &admin.GroupUsersQuery{}
if err := c.ShouldBindJSON(q); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
gid := u.GroupId
uid := u.Id
if service.AllService.UserService.IsAdmin(u) && q.UserId > 0 {
nu := service.AllService.UserService.InfoById(q.UserId)
gid = nu.GroupId
uid = q.UserId
}
res := service.AllService.UserService.List(1, 999, func(tx *gorm.DB) {
tx.Where("group_id = ?", gid)
aG := service.AllService.GroupService.List(1, 999, nil)
aU := service.AllService.UserService.List(1, 9999, nil)
response.Success(c, gin.H{
"groups": aG.Groups,
"users": aU.Users,
})
var data []*adResp.GroupUsersPayload
for _, _u := range res.Users {
gup := &adResp.GroupUsersPayload{}
gup.FromUser(_u)
if _u.Id == uid {
gup.Status = 0
}
data = append(data, gup)
}
response.Success(c, data)
}
// Register

View File

@@ -7,7 +7,6 @@ import (
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http"
"os"
"time"
)
@@ -56,7 +55,7 @@ func (i *Index) Heartbeat(c *gin.Context) {
return
}
//如果在40s以内则不更新
if time.Now().Unix()-peer.LastOnlineTime > 40 {
if time.Now().Unix()-peer.LastOnlineTime >= 30 {
upp := &model.Peer{RowId: peer.RowId, LastOnlineTime: time.Now().Unix(), LastOnlineIp: c.ClientIP()}
service.AllService.PeerService.Update(upp)
}
@@ -74,13 +73,9 @@ func (i *Index) Heartbeat(c *gin.Context) {
// @Router /version [get]
func (i *Index) Version(c *gin.Context) {
//读取resources/version文件
v, err := os.ReadFile("resources/version")
if err != nil {
response.Fail(c, 101, err.Error())
return
}
v := service.AllService.AppService.GetAppVersion()
response.Success(
c,
string(v),
v,
)
}

View File

@@ -8,6 +8,8 @@ import (
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n"
"net/http"
)
@@ -145,7 +147,8 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
state := c.Query("state")
if state == "" {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateParamMsg(c, "ParamIsEmpty", "state"),
"message": "ParamIsEmpty",
"sub_message": "state",
})
return
}
@@ -155,7 +158,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
oauthCache := oauthService.GetOauthCache(cacheKey)
if oauthCache == nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "OauthExpired"),
"message": "OauthExpired",
})
return
}
@@ -169,7 +172,8 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
err, oauthUser := oauthService.Callback(code, verifier, op, nonce)
if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "OauthFailed") + response.TranslateMsg(c, err.Error()),
"message": "OauthFailed",
"sub_message": err.Error(),
})
return
}
@@ -182,7 +186,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
utr := oauthService.UserThirdInfo(op, openid)
if utr.UserId > 0 {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "OauthHasBindOtherUser"),
"message": "OauthHasBindOtherUser",
})
return
}
@@ -190,7 +194,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
user = service.AllService.UserService.InfoById(userId)
if user == nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "ItemNotFound"),
"message": "ItemNotFound",
})
return
}
@@ -198,12 +202,12 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
err := oauthService.BindOauthUser(userId, oauthUser, op)
if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "BindFail"),
"message": "BindFail",
})
return
}
c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": response.TranslateMsg(c, "BindSuccess"),
"message": "BindSuccess",
})
return
@@ -211,7 +215,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//登录
if userId != 0 {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "OauthHasBeenSuccess"),
"message": "OauthHasBeenSuccess",
})
return
}
@@ -230,7 +234,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op)
if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, err.Error()),
"message": err.Error(),
})
return
}
@@ -252,14 +256,50 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
return
}
c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": response.TranslateMsg(c, "OauthSuccess"),
"message": "OauthSuccess",
})
return
} else {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "ParamsError"),
"message": "ParamsError",
})
return
}
}
type MessageParams struct {
Lang string `json:"lang" form:"lang"`
Title string `json:"title" form:"title"`
Msg string `json:"msg" form:"msg"`
}
func (o *Oauth) Message(c *gin.Context) {
mp := &MessageParams{}
if err := c.ShouldBindQuery(mp); err != nil {
return
}
localizer := global.Localizer(mp.Lang)
res := ""
if mp.Title != "" {
title, err := localizer.LocalizeMessage(&i18n.Message{
ID: mp.Title,
})
if err == nil {
res = utils.StringConcat(";title='", title, "';")
}
}
if mp.Msg != "" {
msg, err := localizer.LocalizeMessage(&i18n.Message{
ID: mp.Msg,
})
if err == nil {
res = utils.StringConcat(res, "msg = '", msg, "';")
}
}
//返回js内容
c.Header("Content-Type", "application/javascript")
c.String(http.StatusOK, res)
}

View File

@@ -30,7 +30,7 @@ func (p *Peer) SysInfo(c *gin.Context) {
return
}
fpe := f.ToPeer()
pe := service.AllService.PeerService.FindById(f.Id)
pe := service.AllService.PeerService.FindByUuid(f.Uuid)
if pe.RowId == 0 {
pe = f.ToPeer()
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid)
@@ -56,3 +56,9 @@ func (p *Peer) SysInfo(c *gin.Context) {
//直接响应文本
c.String(http.StatusOK, "SYSINFO_UPDATED")
}
func (p *Peer) SysInfoVer(c *gin.Context) {
//读取resources/version文件
v := service.AllService.AppService.GetAppVersion()
c.String(http.StatusOK, v)
}

View File

@@ -41,6 +41,8 @@ type PeerQuery struct {
Id string `json:"id" form:"id"`
Hostname string `json:"hostname" form:"hostname"`
Uuids string `json:"uuids" form:"uuids"`
Ip string `json:"ip" form:"ip"`
Username string `json:"username" form:"username"`
}
type SimpleDataQuery struct {

View File

@@ -22,15 +22,3 @@ type UserOauthItem struct {
Op string `json:"op"`
Status int `json:"status"`
}
type GroupUsersPayload struct {
Id uint `json:"id"`
Username string `json:"username"`
Status int `json:"status"`
}
func (g *GroupUsersPayload) FromUser(user *model.User) {
g.Id = user.Id
g.Username = user.Username
g.Status = 1
}

View File

@@ -48,11 +48,13 @@ func ApiInit(g *gin.Engine) {
//api/oauth/callback
frg.GET("/oauth/callback", o.OauthCallback)
frg.GET("/oauth/login", o.OauthCallback)
frg.GET("/oauth/msg", o.Message)
}
{
pe := &api.Peer{}
//提交系统信息
frg.POST("/sysinfo", pe.SysInfo)
frg.POST("/sysinfo_ver", pe.SysInfoVer)
}
if global.Config.App.WebClient == 1 {

View File

@@ -137,4 +137,9 @@ other = "Captcha error."
[PwdLoginDisabled]
description = "Password login disabled."
one = "Password login disabled."
other = "Password login disabled."
other = "Password login disabled."
[CannotShareToSelf]
description = "Cannot share to self."
one = "Cannot share to self."
other = "Cannot share to self."

View File

@@ -146,4 +146,9 @@ other = "Error de captcha."
[PwdLoginDisabled]
description = "Password login disabled."
one = "Inicio de sesión con contraseña deshabilitado."
other = "Inicio de sesión con contraseña deshabilitado."
other = "Inicio de sesión con contraseña deshabilitado."
[CannotShareToSelf]
description = "Cannot share to self."
one = "No se puede compartir con uno mismo."
other = "No se puede compartir con uno mismo."

View File

@@ -146,4 +146,9 @@ other = "Erreur de captcha."
[PwdLoginDisabled]
description = "Password login disabled."
one = "Connexion par mot de passe désactivée."
other = "Connexion par mot de passe désactivée."
other = "Connexion par mot de passe désactivée."
[CannotShareToSelf]
description = "Cannot share to self."
one = "Impossible de partager avec soi-même."
other = "Impossible de partager avec soi-même."

View File

@@ -141,3 +141,8 @@ other = "Captcha 오류."
description = "Password login disabled."
one = "비밀번호 로그인이 비활성화되었습니다."
other = "비밀번호 로그인이 비활성화되었습니다."
[CannotShareToSelf]
description = "Cannot share to self."
one = "자기 자신에게 공유할 수 없습니다."
other = "자기 자신에게 공유할 수 없습니다."

View File

@@ -146,4 +146,9 @@ other = "Ошибка капчи."
[PwdLoginDisabled]
description = "Password login disabled."
one = "Вход по паролю отключен."
other = "Вход по паролю отключен."
other = "Вход по паролю отключен."
[CannotShareToSelf]
description = "Cannot share to self."
one = "Нельзя поделиться с собой."
other = "Нельзя поделиться с собой."

View File

@@ -139,4 +139,9 @@ other = "验证码错误。"
[PwdLoginDisabled]
description = "Password login disabled."
one = "密码登录已禁用。"
other = "密码登录已禁用。"
other = "密码登录已禁用。"
[CannotShareToSelf]
description = "Cannot share to self."
one = "不能共享给自己。"
other = "不能共享给自己。"

View File

@@ -140,3 +140,8 @@ other = "驗證碼錯誤。"
description = "Password login disabled."
one = "密碼登錄已禁用。"
other = "密碼登錄已禁用。"
[CannotShareToSelf]
description = "Cannot share to self."
one = "無法共享給自己。"
other = "無法共享給自己。"

View File

@@ -1,9 +1,9 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>授权失败 - RustDesk API</title>
<title>OauthFailed - RustDesk API</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
@@ -57,17 +57,25 @@
}
</style>
<link rel="stylesheet" href="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/6.0.0/css/all.min.css">
<script>
var lang = navigator.language || navigator.userLanguage || 'zh-CN';
var title = 'OauthFailed'
var msg = '{{.message}}'
var btn = 'Close'
document.writeln('<script src="/api/oauth/msg?lang=' + lang + '&msg=' + msg + '&title=OauthFailed"><\/script>');
</script>
</head>
<body>
<div class="success-container">
<i class="fas fa-triangle-exclamation checkmark"></i>
<h1>授权失败!</h1>
<p>{{.message}}</p>
<a href="javascript:window.close()" class="return-link">关闭页面</a>
<i class="fas fa-triangle-exclamation checkmark"></i>
<h1 id="h1"></h1>
<p id="msg"></p>
<a href="javascript:window.close()" class="return-link" id="btn">Close</a>
</div>
<script>
document.title = title + ' - RustDesk API';
document.getElementById('h1').innerText = title;
document.getElementById('msg').innerText = msg;
</script>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>授权成功 - RustDesk API</title>
<title>OauthSuccess - RustDesk API</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
@@ -56,18 +56,27 @@
background-color: #45a049;
}
</style>
<script>
var lang = navigator.language || navigator.userLanguage || 'zh-CN';
var title = 'OauthSuccess'
var msg = '{{.message}}'
var btn = 'Close'
document.writeln('<script src="/api/oauth/msg?lang=' + lang + '&msg=' + msg + '&title=OauthSuccess"><\/script>');
</script>
</head>
<body>
<div class="success-container">
<i class="fas fa-check-circle checkmark"></i>
<h1>授权成功!</h1>
<p>您已成功授权访问您的账户。</p>
<p>现在可以关闭本页面或返回应用继续操作。</p>
<a href="javascript:window.close()" class="return-link">关闭页面</a>
<h1 id="h1"></h1>
<!-- <p>您已成功授权访问您的账户。</p>-->
<!-- <p>现在可以关闭本页面或返回应用继续操作。</p>-->
<a href="javascript:window.close()" class="return-link">Close</a>
</div>
<script>
document.title = title + ' - RustDesk API';
document.getElementById('h1').innerText = title;
document.getElementById('msg').innerText = msg;
</script>
</body>
</html>

View File

@@ -46,7 +46,7 @@ if (share_token) {
password: peer.tmppwd,
}*/
//修改location
window.location.href = `/webclient2/#/${peer.info.id}?password=${peer.tmppwd}`
window.location.href = `/webclient2/#/${peer.info.id}?password=${encodeURIComponent(peer.tmppwd)}`
}
})
}

View File

@@ -123149,7 +123149,7 @@ q=t.p
p=A.a([B.e7,A.Es(A.Q(o,A.T(A.x("Version")+": "+l,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o),B.f,o,o,o,o,o,o,new A.C(0,4,0,4),o,o,o,o)),A.Es(A.Q(o,A.T(A.x("Build Date")+": "+k,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o),B.f,o,o,o,o,o,o,new A.C(0,4,0,4),o,o,o,o))],q)
p.push(A.e1(!1,o,!0,A.Q(o,A.T(A.x("Privacy Statement"),o,o,o,o,o,o,o,o,B.jH,o,o,o,o,o,o),B.f,o,o,o,o,o,o,new A.C(0,4,0,4),o,o,o,o),o,!0,o,o,o,o,o,o,o,o,o,o,o,new A.aYg(),o,o,o,o,o,o,o))
p.push(A.e1(!1,o,!0,A.Q(o,A.T(A.x("Website"),o,o,o,o,o,o,o,o,B.jH,o,o,o,o,o,o),B.f,o,o,o,o,o,o,new A.C(0,4,0,4),o,o,o,o),o,!0,o,o,o,o,o,o,o,o,o,o,o,new A.aYh(),o,o,o,o,o,o,o))
p.push(A.e1(!1,o,!0,A.Q(o,A.T(A.x("Rustdesk Api"),o,o,o,o,o,o,o,o,B.jH,o,o,o,o,o,o),B.f,o,o,o,o,o,o,new A.C(0,4,0,4),o,o,o,o),o,!0,o,o,o,o,o,o,o,o,o,o,o,new A.ljw(),o,o,o,o,o,o,o))
p.push(A.e1(!1,o,!0,A.Q(o,A.T(A.x("Rustdesk API"),o,o,o,o,o,o,o,o,B.jH,o,o,o,o,o,o),B.f,o,o,o,o,o,o,new A.C(0,4,0,4),o,o,o,o),o,!0,o,o,o,o,o,o,o,o,o,o,o,new A.ljw(),o,o,o,o,o,o,o))
p.push(A.Q(o,A.Q(o,A.Es(A.aA(A.a([A.bf(A.b8(A.a([A.T("Copyright \xa9 "+B.d.W(new A.cC(Date.now(),0,!1).l(0),0,4)+" Purslane Ltd.\n"+m,o,o,o,o,o,o,o,o,B.ap7,o,o,o,o,o,o),A.T(A.x("Slogan_tip"),o,o,o,o,o,o,o,o,A.bj(o,o,B.m,o,o,o,o,o,o,o,o,o,o,o,B.vz,o,o,!0,o,o,o,o,o,o,o,o),o,o,o,o,o,o)],q),B.ai,o,B.k,B.j,o),1)],q),B.i,o,B.k,B.j,o)),B.f,o,o,B.Q3,o,o,o,o,B.a0e,o,o,o),B.f,o,o,o,o,o,o,new A.C(0,4,0,4),o,o,o,o))
return A.mn(A.jX(A.a([A.Q(o,A.b8(p,B.ai,o,B.k,B.j,o),B.f,o,o,o,o,o,o,new A.C(15,0,0,0),o,o,o,o)],q),r),B.u,new A.e8(0,!0,o,o,o,n,s),o,o,o,!1,B.T)},
$S:768}

View File

@@ -293,8 +293,11 @@ func (s *AddressBookService) RuleInfoById(u uint) *model.AddressBookCollectionRu
return p
}
func (s *AddressBookService) RulePersonalInfoByToIdAndCid(toid, cid uint) *model.AddressBookCollectionRule {
return s.RuleInfoByToIdAndCid(model.ShareAddressBookRuleTypePersonal, toid, cid)
}
func (s *AddressBookService) RuleInfoByToIdAndCid(t int, toid, cid uint) *model.AddressBookCollectionRule {
p := &model.AddressBookCollectionRule{}
DB.Where("type = ? and to_id = ? and collection_id = ?", model.ShareAddressBookRuleTypePersonal, toid, cid).First(p)
DB.Where("type = ? and to_id = ? and collection_id = ?", t, toid, cid).First(p)
return p
}
func (s *AddressBookService) CreateRule(t *model.AddressBookCollectionRule) error {

28
service/app.go Normal file
View File

@@ -0,0 +1,28 @@
package service
import (
"os"
"sync"
)
type AppService struct {
}
var version = ""
var once = &sync.Once{}
func (a *AppService) GetAppVersion() string {
if version != "" {
return version
}
once.Do(func() {
v, err := os.ReadFile("resources/version")
if err != nil {
return
}
version = string(v)
})
return version
}

33
service/app_test.go Normal file
View File

@@ -0,0 +1,33 @@
package service
import (
"sync"
"testing"
)
// TestGetAppVersion
func TestGetAppVersion(t *testing.T) {
s := &AppService{}
v := s.GetAppVersion()
// 打印结果
t.Logf("App Version: %s", v)
}
func TestMultipleGetAppVersion(t *testing.T) {
s := &AppService{}
//并发测试
// 使用 WaitGroup 等待所有 goroutine 完成
wg := sync.WaitGroup{}
wg.Add(10) // 启动 10 个 goroutine
// 启动 10 个 goroutine
for i := 0; i < 10; i++ {
go func() {
defer wg.Done() // 完成后减少计数
v := s.GetAppVersion()
// 打印结果
t.Logf("App Version: %s", v)
}()
}
// 等待所有 goroutine 完成
wg.Wait()
}

View File

@@ -30,6 +30,7 @@ var (
ErrLdapBindFailed = errors.New("LdapBindFailed")
ErrLdapToLocalUserFailed = errors.New("LdapToLocalUserFailed")
ErrLdapCreateUserFailed = errors.New("LdapCreateUserFailed")
ErrLdapPasswordNotMatch = errors.New("PasswordNotMatch")
)
// LdapService is responsible for LDAP authentication and user synchronization.
@@ -119,7 +120,7 @@ func (ls *LdapService) connectAndBindAdmin(cfg *config.Ldap) (*ldap.Conn, error)
func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password string) error {
ldapConn, err := ls.connectAndBind(cfg, username, password)
if err != nil {
return err
return ErrLdapPasswordNotMatch
}
defer ldapConn.Close()
return nil
@@ -136,6 +137,10 @@ func (ls *LdapService) Authenticate(username, password string) (*model.User, err
return nil, ErrLdapUserDisabled
}
cfg := &Config.Ldap
err = ls.verifyCredentials(cfg, ldapUser.Dn, password)
if err != nil {
return nil, err
}
user, err := ls.mapToLocalUser(cfg, ldapUser)
if err != nil {
return nil, errors.Join(ErrLdapToLocalUserFailed, err)

View File

@@ -126,7 +126,14 @@ func (ps *PeerService) GetUuidListByIDs(ids []uint) ([]string, error) {
err := DB.Model(&model.Peer{}).
Where("row_id in (?)", ids).
Pluck("uuid", &uuids).Error
return uuids, err
//过滤uuids中的空字符串
var newUuids []string
for _, uuid := range uuids {
if uuid != "" {
newUuids = append(newUuids, uuid)
}
}
return newUuids, err
}
// BatchDelete 批量删除, 同时也应该删除token

View File

@@ -23,6 +23,7 @@ type Service struct {
*ShareRecordService
*ServerCmdService
*LdapService
*AppService
}
type Dependencies struct {

View File

@@ -7,6 +7,7 @@ import (
"math/rand"
"reflect"
"runtime/debug"
"strings"
)
func Md5(str string) string {
@@ -100,3 +101,11 @@ func InArray(k string, arr []string) bool {
}
return false
}
func StringConcat(strs ...string) string {
var builder strings.Builder
for _, str := range strs {
builder.WriteString(str)
}
return builder.String()
}