Compare commits

...

37 Commits

Author SHA1 Message Date
lejianwen
46bb44f0ab fix(webclient): DefaultIdServerPort undefined (#238) 2025-05-16 20:14:36 +08:00
lejianwen
2f1380f24a fix(webclient): Remove license warning (#235) 2025-05-13 13:11:19 +08:00
lejianwen
ece3328e94 feat(webclient): Web client to 1.4.0 2025-05-12 20:16:08 +08:00
lejianwen
fdd26d87be fix: PageSize (#225) 2025-05-06 19:08:18 +08:00
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
lejianwen
ac5df6826b fix: Init database err (#166) 2025-03-04 18:14:25 +08:00
lejianwen
91908859bc docs: Readme 2025-03-04 16:30:12 +08:00
lejianwen
9c8822f857 fix: templates 2025-03-04 16:14:34 +08:00
lejianwen
9709da7fb6 style(webclient): ws-host 2025-03-04 15:48:25 +08:00
lejianwen
1852f10131 feat(config): add ws-host configuration (#156) 2025-03-04 15:43:25 +08:00
Tao Chen
77d7b43e21 fix: Fix/ldap tls (#162)
* optimize and fix tls of LDAP

* fix
2025-03-02 22:59:01 +08:00
lejianwen
7410b539a0 style(service): refactor global dependencies to use local variables 2025-02-26 19:15:38 +08:00
lejianwen
0403d71502 feat(oauth): Oauth nonce (#148) 2025-02-26 16:36:53 +08:00
lejianwen
2af1d93a7d feat(oauth): Oauth callback page beautification (#115) 2025-02-25 13:51:49 +08:00
lejianwen
76281ad761 feat(api): Add device group for 1.3.8 2025-02-23 21:35:42 +08:00
lejianwen
d1ec9b4916 feat(api): Add /device-group/accessible for 1.3.8 2025-02-23 15:32:44 +08:00
lejianwen
448e7460a9 style(oidc): Oidc style 2025-02-21 09:49:41 +08:00
lejianwen
ee0cbabffc fix(admin): Admin hello 2025-02-20 19:37:34 +08:00
lejianwen
d6a5af890a style: No need exec sql on version 261 2025-02-20 19:22:22 +08:00
lejianwen
dc313441e5 fix: Js content-type 2025-02-19 16:04:03 +08:00
Tao Chen
c75320f4f4 feat(oidc): add pkce (#150) 2025-02-19 09:31:25 +08:00
lejianwen
c788f78416 docs: Readme 2025-02-17 10:59:38 +08:00
lejianwen
49cf954d4a fix(config)!: Token expire time (#145)
将配置中的过期时间单位统一为time.Duration,可以设置为`h`,`m`,`s`
2025-02-17 10:49:59 +08:00
lejianwen
014e3db54f docs: Readme 2025-02-16 21:06:55 +08:00
75 changed files with 94144 additions and 86733 deletions

View File

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

View File

@@ -4,12 +4,12 @@
本项目使用 Go 实现了 RustDesk 的 API并包含了 Web Admin 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。 本项目使用 Go 实现了 RustDesk 的 API并包含了 Web Admin 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。
<div align=center> <div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/> <img src="https://img.shields.io/badge/golang-1.22-blue"/>
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/> <img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/> <img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/> <img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://img.shields.io/badge/i18n-7-green"/> <img src="https://goreportcard.com/badge/github.com/lejianwen/rustdesk-api/v2"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/> <img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div> </div>
@@ -108,8 +108,7 @@
* 可以官方指令 * 可以官方指令
* 可以添加自定义指令 * 可以添加自定义指令
* 可以执行自定义指令 * 可以执行自定义指令
![rustdesk_command_advance](./docs/rustdesk_command_advance.png)
11. **LDAP 支持**, 当在API Server上设置了LDAP(已测试AD和LDAP),可以通过LDAP中的用户信息进行登录 https://github.com/lejianwen/rustdesk-api/issues/114 ,如果LDAP验证失败返回本地用户 11. **LDAP 支持**, 当在API Server上设置了LDAP(已测试AD和LDAP),可以通过LDAP中的用户信息进行登录 https://github.com/lejianwen/rustdesk-api/issues/114 ,如果LDAP验证失败返回本地用户
@@ -146,69 +145,11 @@
### 相关配置 ### 相关配置
* [配置文件](./conf/config.yaml)
* 参考`conf/config.yaml`配置文件,修改相关配置。 * 参考`conf/config.yaml`配置文件,修改相关配置。
* 如果`gorm.type``sqlite`则不需要配置mysql相关配置。 * 如果`gorm.type``sqlite`则不需要配置mysql相关配置。
* 语言如果不设置默认为`zh-CN` * 语言如果不设置默认为`zh-CN`
```yaml
lang: "en"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
show-swagger: 0 #是否显示swagger文档
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk"
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
personal: 1
logger:
path: "./runtime/log.txt"
level: "warn" #trace,debug,info,warn,error,fatal
report-caller: true
proxy:
enable: false
host: ""
jwt:
key: ""
expire-duration: 360000
ldap:
enable: false
url: "ldap://ldap.example.com:389"
tls: false
tls-verify: false
base-dn: "dc=example,dc=com"
bind-dn: "cn=admin,dc=example,dc=com"
bind-password: "password"
user:
base-dn: "ou=users,dc=example,dc=com"
enable-attr: "" #The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
enable-attr-value: "" # The value of the enable attribute when the user is enabled. If you are using AD, just set random value, it will be ignored.
filter: "(cn=*)"
username: "uid" # The attribute name of the user for usernamem if you are using AD, it should be "sAMAccountName"
email: "mail"
first-name: "givenName"
last-name: "sn"
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.
```
### 环境变量 ### 环境变量
环境变量和配置文件`conf/config.yaml`中的配置一一对应,变量名前缀是`RUSTDESK_API` 环境变量和配置文件`conf/config.yaml`中的配置一一对应,变量名前缀是`RUSTDESK_API`
下面表格并未全部列出,可以参考`conf/config.yaml`中的配置。 下面表格并未全部列出,可以参考`conf/config.yaml`中的配置。
@@ -220,7 +161,8 @@ ldap:
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 | | RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` | | RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` | | RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长(秒) | `3600` | | RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长 | `168h` |
| RUSTDESK_API_APP_DISABLE_PWD_LOGIN | 是否禁用密码登录; `true`, `false` 默认`false` | `false` |
| -----ADMIN配置----- | ---------- | ---------- | | -----ADMIN配置----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` | | RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | | | RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | |
@@ -244,12 +186,13 @@ ldap:
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 | | RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` | | RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT<br/>_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` | | RUSTDESK_API_RUSTDESK_WEBCLIENT<br/>_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` |
| RUSTDESK_API_RUSTDESK_WS_HOST | 自定义Websocket Host | `wss://192.168.1.123:1234` |
| ----PROXY配置----- | ---------- | ---------- | | ----PROXY配置----- | ---------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` | | RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` | | RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
| ----JWT配置---- | -------- | -------- | | ----JWT配置---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT<br/>如果没使用`lejianwen/rustdesk-server`中的`MUST_LOGIN`,建议设置为空 | | | RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT<br/>如果没使用`lejianwen/rustdesk-server`中的`MUST_LOGIN`,建议设置为空 | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | 360000 | | RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | `168h` |
### 运行 ### 运行

View File

@@ -8,7 +8,7 @@ desktop software that provides self-hosted solutions.
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/> <img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/> <img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/> <img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://img.shields.io/badge/i18n-7-green"/> <img src="https://goreportcard.com/badge/github.com/lejianwen/rustdesk-api/v2"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/> <img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div> </div>
@@ -109,8 +109,6 @@ displaying data.Frontend code is available at [rustdesk-api-web](https://github.
* Custom commands can be added * Custom commands can be added
* Custom commands can be executed * Custom commands can be executed
![rustdesk_command_advance](./docs/en_img/rustdesk_command_advance.png)
11. **LDAP Support**, When you setup the LDAP(test for OpenLDAP and AD), you can login with the LDAP's user. https://github.com/lejianwen/rustdesk-api/issues/114 , if LDAP fail fallback local user 11. **LDAP Support**, When you setup the LDAP(test for OpenLDAP and AD), you can login with the LDAP's user. https://github.com/lejianwen/rustdesk-api/issues/114 , if LDAP fail fallback local user
### Web Client: ### Web Client:
@@ -145,110 +143,55 @@ displaying data.Frontend code is available at [rustdesk-api-web](https://github.
### Configuration ### Configuration
* [Config File](./conf/config.yaml)
* Modify the configuration in `conf/config.yaml`. * Modify the configuration in `conf/config.yaml`.
* If `gorm.type` is set to `sqlite`, MySQL-related configurations are not required. * If `gorm.type` is set to `sqlite`, MySQL-related configurations are not required.
* Language support: `en` and `zh-CN` are supported. The default is `zh-CN`. * Language support: `en` and `zh-CN` are supported. The default is `zh-CN`.
```yaml
lang: "en"
app:
web-client: 1 # web client route 1:open 0:close
register: false #register enable
show-swagger: 0 #show swagger 1:open 0:close
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk"
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
personal: 1
logger:
path: "./runtime/log.txt"
level: "warn" #trace,debug,info,warn,error,fatal
report-caller: true
proxy:
enable: false
host: ""
jwt:
key: ""
expire-duration: 360000
ldap:
enable: false
url: "ldap://ldap.example.com:389"
tls: false
tls-verify: false
base-dn: "dc=example,dc=com"
bind-dn: "cn=admin,dc=example,dc=com"
bind-password: "password"
user:
base-dn: "ou=users,dc=example,dc=com"
enable-attr: "" #The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
enable-attr-value: "" # The value of the enable attribute when the user is enabled. If you are using AD, just set random value, it will be ignored.
filter: "(cn=*)"
username: "uid" # The attribute name of the user for usernamem if you are using AD, it should be "sAMAccountName"
email: "mail"
first-name: "givenName"
last-name: "sn"
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.
```
### Environment Variables ### Environment Variables
The environment variables correspond one-to-one with the configurations in the `conf/config.yaml` file. The prefix for variable names is `RUSTDESK_API`. The environment variables correspond one-to-one with the configurations in the `conf/config.yaml` file. The prefix for variable names is `RUSTDESK_API`.
The table below does not list all configurations. Please refer to the configurations in `conf/config.yaml`. The table below does not list all configurations. Please refer to the configurations in `conf/config.yaml`.
| Variable Name | Description | Example | | Variable Name | Description | Example |
|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------| |--------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|
| TZ | timezone | Asia/Shanghai | | TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` | | RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 | | RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 |
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` | | RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` | | RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token expire duration(second) | `3600` | | RUSTDESK_API_APP_TOKEN_EXPIRE | token expire duration | `168h` |
| ----- ADMIN Configuration----- | ---------- | ---------- | | RUSTDESK_API_APP_DISABLE_PWD_LOGIN | disable password login | `false` |
| RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` | | ----- ADMIN Configuration----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | | | RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` | | RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | |
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- | | RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 | | ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- | | RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite | | ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 | | RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 | | RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 | | RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- | | RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root | | ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 | | RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 | | RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk | | RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- | | RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 | | ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 | | RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 | | RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 | | RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` | | RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
| RUSTDESK_API_RUSTDESK<br/>_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` | | RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` |
| ---- PROXY ----- | --------------- | ---------- | | RUSTDESK_API_RUSTDESK<br/>_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` | | RUSTDESK_API_RUSTDESK_WS_HOST | Custom Websocket Host | `wss://192.168.1.123:1234` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` | | ---- PROXY ----- | --------------- | ---------- |
| ----JWT---- | -------- | -------- | | RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_JWT_KEY | Custom JWT KEY, if empty JWT is not enabled.<br/>If `MUST_LOGIN` from `lejianwen/rustdesk-server` is not used, it is recommended to leave it empty. | | | RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT expire duration | 360000 | | ----JWT---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | Custom JWT KEY, if empty JWT is not enabled.<br/>If `MUST_LOGIN` from `lejianwen/rustdesk-server` is not used, it is recommended to leave it empty. | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT expire duration | `168h` |
### Installation Steps ### Installation Steps

View File

@@ -18,7 +18,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
"strconv" "strconv"
"time"
) )
// @title 管理系统API // @title 管理系统API
@@ -52,6 +51,10 @@ var resetPwdCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
pwd := args[0] pwd := args[0]
admin := service.AllService.UserService.InfoById(1) admin := service.AllService.UserService.InfoById(1)
if admin.Id == 0 {
global.Logger.Warn("user not found! ")
return
}
err := service.AllService.UserService.UpdatePassword(admin, pwd) err := service.AllService.UserService.UpdatePassword(admin, pwd)
if err != nil { if err != nil {
global.Logger.Error("reset password fail! ", err) global.Logger.Error("reset password fail! ", err)
@@ -78,6 +81,10 @@ var resetUserPwdCmd = &cobra.Command{
return return
} }
u := service.AllService.UserService.InfoById(uint(uid)) u := service.AllService.UserService.InfoById(uint(uid))
if u.Id == 0 {
global.Logger.Warn("user not found! ")
return
}
err = service.AllService.UserService.UpdatePassword(u, pwd) err = service.AllService.UserService.UpdatePassword(u, pwd)
if err != nil { if err != nil {
global.Logger.Warn("reset password fail! ", err) global.Logger.Warn("reset password fail! ", err)
@@ -145,7 +152,6 @@ func InitGlobal() {
MaxOpenConns: global.Config.Gorm.MaxOpenConns, MaxOpenConns: global.Config.Gorm.MaxOpenConns,
}) })
} }
DatabaseAutoUpdate()
//validator //validator
global.ApiInitValidator() global.ApiInitValidator()
@@ -162,13 +168,17 @@ func InitGlobal() {
//jwt //jwt
//fmt.Println(global.Config.Jwt.PrivateKey) //fmt.Println(global.Config.Jwt.PrivateKey)
global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration*time.Second) global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration)
//locker //locker
global.Lock = lock.NewLocal() global.Lock = lock.NewLocal()
//service
service.New(&global.Config, global.DB, global.Logger, global.Jwt, global.Lock)
DatabaseAutoUpdate()
} }
func DatabaseAutoUpdate() { func DatabaseAutoUpdate() {
version := 260 version := 262
db := global.DB db := global.DB
@@ -212,6 +222,7 @@ func DatabaseAutoUpdate() {
if v.Version < uint(version) { if v.Version < uint(version) {
Migrate(uint(version)) Migrate(uint(version))
} }
// 245迁移 // 245迁移
if v.Version < 245 { if v.Version < 245 {
//oauths 表的 oauth_type 字段设置为 op同样的值 //oauths 表的 oauth_type 字段设置为 op同样的值
@@ -234,7 +245,7 @@ func DatabaseAutoUpdate() {
} }
func Migrate(version uint) { func Migrate(version uint) {
global.Logger.Info("migrating....", version) global.Logger.Info("Migrating....", version)
err := global.DB.AutoMigrate( err := global.DB.AutoMigrate(
&model.Version{}, &model.Version{},
&model.User{}, &model.User{},
@@ -252,6 +263,7 @@ func Migrate(version uint) {
&model.AddressBookCollection{}, &model.AddressBookCollection{},
&model.AddressBookCollectionRule{}, &model.AddressBookCollectionRule{},
&model.ServerCmd{}, &model.ServerCmd{},
&model.DeviceGroup{},
) )
if err != nil { if err != nil {
global.Logger.Error("migrate err :=>", err) global.Logger.Error("migrate err :=>", err)

View File

@@ -3,7 +3,7 @@ app:
web-client: 1 # 1:启用 0:禁用 web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册 register: false #是否开启注册
show-swagger: 0 # 1:启用 0:禁用 show-swagger: 0 # 1:启用 0:禁用
token-expire: 360000 token-expire: 168h
web-sso: true #web auth sso web-sso: true #web auth sso
disable-pwd-login: false #禁用密码登录 disable-pwd-login: false #禁用密码登录
admin: admin:
@@ -32,6 +32,7 @@ rustdesk:
key-file: "/data/id_ed25519.pub" key-file: "/data/id_ed25519.pub"
personal: 1 personal: 1
webclient-magic-queryonline: 0 webclient-magic-queryonline: 0
ws-host: "" #eg: wss://192.168.1.3:4443
logger: logger:
path: "./runtime/log.txt" path: "./runtime/log.txt"
level: "info" #trace,debug,info,warn,error,fatal level: "info" #trace,debug,info,warn,error,fatal
@@ -41,11 +42,11 @@ proxy:
host: "http://127.0.0.1:1080" host: "http://127.0.0.1:1080"
jwt: jwt:
key: "" key: ""
expire-duration: 360000 expire-duration: 168h
ldap: ldap:
enable: false enable: false
url: "ldap://ldap.example.com:389" url: "ldap://ldap.example.com:389"
tls: false tls-ca-file: ""
tls-verify: false tls-verify: false
base-dn: "dc=example,dc=com" base-dn: "dc=example,dc=com"
bind-dn: "cn=admin,dc=example,dc=com" bind-dn: "cn=admin,dc=example,dc=com"
@@ -63,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. 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. 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

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"strings" "strings"
"time"
) )
const ( const (
@@ -13,12 +14,12 @@ const (
) )
type App struct { type App struct {
WebClient int `mapstructure:"web-client"` WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"` Register bool `mapstructure:"register"`
ShowSwagger int `mapstructure:"show-swagger"` ShowSwagger int `mapstructure:"show-swagger"`
TokenExpire int `mapstructure:"token-expire"` TokenExpire time.Duration `mapstructure:"token-expire"`
WebSso bool `mapstructure:"web-sso"` WebSso bool `mapstructure:"web-sso"`
DisablePwdLogin bool `mapstructure:"disable-pwd-login"` DisablePwdLogin bool `mapstructure:"disable-pwd-login"`
} }
type Admin struct { type Admin struct {
Title string `mapstructure:"title"` Title string `mapstructure:"title"`
@@ -73,7 +74,7 @@ func Init(rowVal *Config, path string) *viper.Viper {
}) })
*/ */
if err := v.Unmarshal(rowVal); err != nil { if err := v.Unmarshal(rowVal); err != nil {
fmt.Println(err) panic(fmt.Errorf("Fatal error config: %s \n", err))
} }
rowVal.Rustdesk.LoadKeyFile() rowVal.Rustdesk.LoadKeyFile()
rowVal.Rustdesk.ParsePort() rowVal.Rustdesk.ParsePort()

View File

@@ -26,7 +26,7 @@ type LdapUser struct {
type Ldap struct { type Ldap struct {
Enable bool `mapstructure:"enable"` Enable bool `mapstructure:"enable"`
Url string `mapstructure:"url"` Url string `mapstructure:"url"`
TLS bool `mapstructure:"tls"` TlsCaFile string `mapstructure:"tls-ca-file"`
TlsVerify bool `mapstructure:"tls-verify"` TlsVerify bool `mapstructure:"tls-verify"`
BaseDn string `mapstructure:"base-dn"` BaseDn string `mapstructure:"base-dn"`
BindDn string `mapstructure:"bind-dn"` BindDn string `mapstructure:"bind-dn"`

View File

@@ -21,7 +21,8 @@ type Rustdesk struct {
KeyFile string `mapstructure:"key-file"` KeyFile string `mapstructure:"key-file"`
Personal int `mapstructure:"personal"` Personal int `mapstructure:"personal"`
//webclient-magic-queryonline //webclient-magic-queryonline
WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"` WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"`
WsHost string `mapstructure:"ws-host"`
} }
func (rd *Rustdesk) LoadKeyFile() { func (rd *Rustdesk) LoadKeyFile() {

View File

@@ -1407,6 +1407,280 @@ const docTemplateadmin = `{
} }
} }
}, },
"/admin/device_group/create": {
"post": {
"security": [
{
"token": []
}
],
"description": "创建设备群组",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备群组"
],
"summary": "创建设备群组",
"parameters": [
{
"description": "设备群组信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.DeviceGroupForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.DeviceGroup"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/device_group/delete": {
"post": {
"security": [
{
"token": []
}
],
"description": "设备群组删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备群组"
],
"summary": "设备群组删除",
"parameters": [
{
"description": "群组信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.DeviceGroupForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/device_group/detail/{id}": {
"get": {
"security": [
{
"token": []
}
],
"description": "设备群组详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备群组"
],
"summary": "设备群组详情",
"parameters": [
{
"type": "integer",
"description": "ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Group"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/device_group/list": {
"get": {
"security": [
{
"token": []
}
],
"description": "群组列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"群组"
],
"summary": "群组列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "页大小",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.GroupList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/device_group/update": {
"post": {
"security": [
{
"token": []
}
],
"description": "设备群组编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备群组"
],
"summary": "设备群组编辑",
"parameters": [
{
"description": "群组信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.DeviceGroupForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Group"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/file/oss_token": { "/admin/file/oss_token": {
"get": { "get": {
"security": [ "security": [
@@ -1783,7 +2057,7 @@ const docTemplateadmin = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/github_com_lejianwen_rustdesk-api_http_request_admin.Login" "$ref": "#/definitions/github_com_lejianwen_rustdesk-api_v2_http_request_admin.Login"
} }
} }
], ],
@@ -5219,6 +5493,20 @@ const docTemplateadmin = `{
} }
} }
}, },
"admin.DeviceGroupForm": {
"type": "object",
"required": [
"name"
],
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"admin.GroupForm": { "admin.GroupForm": {
"type": "object", "type": "object",
"required": [ "required": [
@@ -5306,6 +5594,12 @@ const docTemplateadmin = `{
"op": { "op": {
"type": "string" "type": "string"
}, },
"pkce_enable": {
"type": "boolean"
},
"pkce_method": {
"type": "string"
},
"redirect_url": { "redirect_url": {
"type": "string" "type": "string"
}, },
@@ -5334,6 +5628,9 @@ const docTemplateadmin = `{
"cpu": { "cpu": {
"type": "string" "type": "string"
}, },
"group_id": {
"type": "integer"
},
"hostname": { "hostname": {
"type": "string" "type": "string"
}, },
@@ -5521,7 +5818,7 @@ const docTemplateadmin = `{
} }
} }
}, },
"github_com_lejianwen_rustdesk-api_http_request_admin.Login": { "github_com_lejianwen_rustdesk-api_v2_http_request_admin.Login": {
"type": "object", "type": "object",
"required": [ "required": [
"password", "password",
@@ -5842,6 +6139,23 @@ const docTemplateadmin = `{
} }
} }
}, },
"model.DeviceGroup": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"model.Group": { "model.Group": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -5973,6 +6287,12 @@ const docTemplateadmin = `{
"op": { "op": {
"type": "string" "type": "string"
}, },
"pkce_enable": {
"type": "boolean"
},
"pkce_method": {
"type": "string"
},
"redirect_url": { "redirect_url": {
"type": "string" "type": "string"
}, },
@@ -6013,6 +6333,9 @@ const docTemplateadmin = `{
"created_at": { "created_at": {
"type": "string" "type": "string"
}, },
"group_id": {
"type": "integer"
},
"hostname": { "hostname": {
"type": "string" "type": "string"
}, },

View File

@@ -1400,6 +1400,280 @@
} }
} }
}, },
"/admin/device_group/create": {
"post": {
"security": [
{
"token": []
}
],
"description": "创建设备群组",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备群组"
],
"summary": "创建设备群组",
"parameters": [
{
"description": "设备群组信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.DeviceGroupForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.DeviceGroup"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/device_group/delete": {
"post": {
"security": [
{
"token": []
}
],
"description": "设备群组删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备群组"
],
"summary": "设备群组删除",
"parameters": [
{
"description": "群组信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.DeviceGroupForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/device_group/detail/{id}": {
"get": {
"security": [
{
"token": []
}
],
"description": "设备群组详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备群组"
],
"summary": "设备群组详情",
"parameters": [
{
"type": "integer",
"description": "ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Group"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/device_group/list": {
"get": {
"security": [
{
"token": []
}
],
"description": "群组列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"群组"
],
"summary": "群组列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "页大小",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.GroupList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/device_group/update": {
"post": {
"security": [
{
"token": []
}
],
"description": "设备群组编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备群组"
],
"summary": "设备群组编辑",
"parameters": [
{
"description": "群组信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.DeviceGroupForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Group"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/file/oss_token": { "/admin/file/oss_token": {
"get": { "get": {
"security": [ "security": [
@@ -1776,7 +2050,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/github_com_lejianwen_rustdesk-api_http_request_admin.Login" "$ref": "#/definitions/github_com_lejianwen_rustdesk-api_v2_http_request_admin.Login"
} }
} }
], ],
@@ -5212,6 +5486,20 @@
} }
} }
}, },
"admin.DeviceGroupForm": {
"type": "object",
"required": [
"name"
],
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"admin.GroupForm": { "admin.GroupForm": {
"type": "object", "type": "object",
"required": [ "required": [
@@ -5299,6 +5587,12 @@
"op": { "op": {
"type": "string" "type": "string"
}, },
"pkce_enable": {
"type": "boolean"
},
"pkce_method": {
"type": "string"
},
"redirect_url": { "redirect_url": {
"type": "string" "type": "string"
}, },
@@ -5327,6 +5621,9 @@
"cpu": { "cpu": {
"type": "string" "type": "string"
}, },
"group_id": {
"type": "integer"
},
"hostname": { "hostname": {
"type": "string" "type": "string"
}, },
@@ -5514,7 +5811,7 @@
} }
} }
}, },
"github_com_lejianwen_rustdesk-api_http_request_admin.Login": { "github_com_lejianwen_rustdesk-api_v2_http_request_admin.Login": {
"type": "object", "type": "object",
"required": [ "required": [
"password", "password",
@@ -5835,6 +6132,23 @@
} }
} }
}, },
"model.DeviceGroup": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"model.Group": { "model.Group": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -5966,6 +6280,12 @@
"op": { "op": {
"type": "string" "type": "string"
}, },
"pkce_enable": {
"type": "boolean"
},
"pkce_method": {
"type": "string"
},
"redirect_url": { "redirect_url": {
"type": "string" "type": "string"
}, },
@@ -6006,6 +6326,9 @@
"created_at": { "created_at": {
"type": "string" "type": "string"
}, },
"group_id": {
"type": "integer"
},
"hostname": { "hostname": {
"type": "string" "type": "string"
}, },

View File

@@ -77,6 +77,15 @@ definitions:
- new_password - new_password
- old_password - old_password
type: object type: object
admin.DeviceGroupForm:
properties:
id:
type: integer
name:
type: string
required:
- name
type: object
admin.GroupForm: admin.GroupForm:
properties: properties:
id: id:
@@ -130,6 +139,10 @@ definitions:
type: string type: string
op: op:
type: string type: string
pkce_enable:
type: boolean
pkce_method:
type: string
redirect_url: redirect_url:
type: string type: string
scopes: scopes:
@@ -153,6 +166,8 @@ definitions:
properties: properties:
cpu: cpu:
type: string type: string
group_id:
type: integer
hostname: hostname:
type: string type: string
id: id:
@@ -278,7 +293,7 @@ definitions:
required: required:
- ids - ids
type: object type: object
github_com_lejianwen_rustdesk-api_http_request_admin.Login: github_com_lejianwen_rustdesk-api_v2_http_request_admin.Login:
properties: properties:
captcha: captcha:
type: string type: string
@@ -492,6 +507,17 @@ definitions:
total: total:
type: integer type: integer
type: object type: object
model.DeviceGroup:
properties:
created_at:
type: string
id:
type: integer
name:
type: string
updated_at:
type: string
type: object
model.Group: model.Group:
properties: properties:
created_at: created_at:
@@ -579,6 +605,10 @@ definitions:
type: string type: string
op: op:
type: string type: string
pkce_enable:
type: boolean
pkce_method:
type: string
redirect_url: redirect_url:
type: string type: string
scopes: scopes:
@@ -605,6 +635,8 @@ definitions:
type: string type: string
created_at: created_at:
type: string type: string
group_id:
type: integer
hostname: hostname:
type: string type: string
id: id:
@@ -1610,6 +1642,167 @@ paths:
summary: RUSTDESK服务配置 summary: RUSTDESK服务配置
tags: tags:
- ADMIN - ADMIN
/admin/device_group/create:
post:
consumes:
- application/json
description: 创建设备群组
parameters:
- description: 设备群组信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.DeviceGroupForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.DeviceGroup'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建设备群组
tags:
- 设备群组
/admin/device_group/delete:
post:
consumes:
- application/json
description: 设备群组删除
parameters:
- description: 群组信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.DeviceGroupForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 设备群组删除
tags:
- 设备群组
/admin/device_group/detail/{id}:
get:
consumes:
- application/json
description: 设备群组详情
parameters:
- description: ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.Group'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 设备群组详情
tags:
- 设备群组
/admin/device_group/list:
get:
consumes:
- application/json
description: 群组列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.GroupList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 群组列表
tags:
- 群组
/admin/device_group/update:
post:
consumes:
- application/json
description: 设备群组编辑
parameters:
- description: 群组信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.DeviceGroupForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.Group'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 设备群组编辑
tags:
- 设备群组
/admin/file/oss_token: /admin/file/oss_token:
get: get:
consumes: consumes:
@@ -1830,7 +2023,7 @@ paths:
name: body name: body
required: true required: true
schema: schema:
$ref: '#/definitions/github_com_lejianwen_rustdesk-api_http_request_admin.Login' $ref: '#/definitions/github_com_lejianwen_rustdesk-api_v2_http_request_admin.Login'
produces: produces:
- application/json - application/json
responses: responses:

View File

@@ -767,6 +767,66 @@ const docTemplateapi = `{
} }
} }
}, },
"/device-group/accessible": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "机器",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"群组"
],
"summary": "设备",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "每页数量",
"name": "pageSize",
"in": "query"
},
{
"type": "integer",
"description": "状态",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "accessible",
"name": "accessible",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.DataResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/heartbeat": { "/heartbeat": {
"post": { "post": {
"description": "心跳", "description": "心跳",

View File

@@ -760,6 +760,66 @@
} }
} }
}, },
"/device-group/accessible": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "机器",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"群组"
],
"summary": "设备",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "每页数量",
"name": "pageSize",
"in": "query"
},
{
"type": "integer",
"description": "状态",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "accessible",
"name": "accessible",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.DataResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/heartbeat": { "/heartbeat": {
"post": { "post": {
"description": "心跳", "description": "心跳",

View File

@@ -671,6 +671,44 @@ paths:
summary: 用户信息 summary: 用户信息
tags: tags:
- 用户 - 用户
/device-group/accessible:
get:
consumes:
- application/json
description: 机器
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 每页数量
in: query
name: pageSize
type: integer
- description: 状态
in: query
name: status
type: integer
- description: accessible
in: query
name: accessible
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.DataResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- BearerAuth: []
summary: 设备
tags:
- 群组
/heartbeat: /heartbeat:
post: post:
consumes: consumes:

View File

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

15
go.mod
View File

@@ -10,7 +10,7 @@ require (
github.com/gin-gonic/gin v1.9.0 github.com/gin-gonic/gin v1.9.0
github.com/go-playground/locales v0.14.1 github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.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/go-redis/redis/v8 v8.11.4
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
@@ -22,7 +22,7 @@ require (
github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3
golang.org/x/oauth2 v0.23.0 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/mysql v1.5.7
gorm.io/driver/sqlite v1.5.6 gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.7 gorm.io/gorm v1.25.7
@@ -36,9 +36,12 @@ require (
github.com/bytedance/sonic v1.8.0 // indirect github.com/bytedance/sonic v1.8.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 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/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/gin-contrib/sse v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-ldap/ldap/v3 v3.4.10 // indirect github.com/go-ldap/ldap/v3 v3.4.10 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect
@@ -54,7 +57,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // 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/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
@@ -73,10 +76,10 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // 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/image v0.13.0 // indirect
golang.org/x/net v0.33.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.63.2 // 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 //check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal { if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId { if t.ToId == t.UserId {
return "ParamsError", false return "CannotShareToSelf", false
} }
tou := service.AllService.UserService.InfoById(t.ToId) tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 { if tou.Id == 0 {
@@ -135,7 +135,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionR
return "ParamsError", false 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 { if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false return "ItemExists", false
} }

View File

@@ -4,6 +4,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/response" "github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/service"
"os" "os"
"strings" "strings"
@@ -60,13 +61,22 @@ func (co *Config) AppConfig(c *gin.Context) {
// @Security token // @Security token
func (co *Config) AdminConfig(c *gin.Context) { func (co *Config) AdminConfig(c *gin.Context) {
u := service.AllService.UserService.CurUser(c) u := &model.User{}
if u == nil || u.Id == 0 { token := c.GetHeader("api-token")
if token != "" {
u, _ = service.AllService.UserService.InfoByAccessToken(token)
if !service.AllService.UserService.CheckUserEnable(u) {
u.Id = 0
}
}
if u.Id == 0 {
response.Success(c, &gin.H{ response.Success(c, &gin.H{
"title": global.Config.Admin.Title, "title": global.Config.Admin.Title,
}) })
return return
} }
hello := global.Config.Admin.Hello hello := global.Config.Admin.Hello
helloFile := global.Config.Admin.HelloFile helloFile := global.Config.Admin.HelloFile
if helloFile != "" { if helloFile != "" {

View File

@@ -0,0 +1,160 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"strconv"
)
type DeviceGroup struct {
}
// Detail 设备群组
// @Tags 设备群组
// @Summary 设备群组详情
// @Description 设备群组详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/detail/{id} [get]
// @Security token
func (ct *DeviceGroup) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
u := service.AllService.GroupService.DeviceGroupInfoById(uint(iid))
if u.Id > 0 {
response.Success(c, u)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建设备群组
// @Tags 设备群组
// @Summary 创建设备群组
// @Description 创建设备群组
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "设备群组信息"
// @Success 200 {object} response.Response{data=model.DeviceGroup}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/create [post]
// @Security token
func (ct *DeviceGroup) Create(c *gin.Context) {
f := &admin.DeviceGroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToDeviceGroup()
err := service.AllService.GroupService.DeviceGroupCreate(u)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// List 列表
// @Tags 群组
// @Summary 群组列表
// @Description 群组列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response{data=model.GroupList}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/list [get]
// @Security token
func (ct *DeviceGroup) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.GroupService.DeviceGroupList(query.Page, query.PageSize, nil)
response.Success(c, res)
}
// Update 编辑
// @Tags 设备群组
// @Summary 设备群组编辑
// @Description 设备群组编辑
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "群组信息"
// @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/update [post]
// @Security token
func (ct *DeviceGroup) Update(c *gin.Context) {
f := &admin.DeviceGroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToDeviceGroup()
err := service.AllService.GroupService.DeviceGroupUpdate(u)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 设备群组
// @Summary 设备群组删除
// @Description 设备群组删除
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "群组信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/device_group/delete [post]
// @Security token
func (ct *DeviceGroup) Delete(c *gin.Context) {
f := &admin.DeviceGroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.GroupService.DeviceGroupInfoById(f.Id)
if u.Id > 0 {
err := service.AllService.GroupService.DeviceGroupDelete(u)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -283,13 +283,13 @@ func (ct *Login) OidcAuth(c *gin.Context) {
return return
} }
err, code, url := service.AllService.OauthService.BeginAuth(f.Op) err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error())) response.Error(c, response.TranslateMsg(c, err.Error()))
return return
} }
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{ service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin, Action: service.OauthActionTypeLogin,
Op: f.Op, Op: f.Op,
Id: f.Id, Id: f.Id,
@@ -297,10 +297,12 @@ func (ct *Login) OidcAuth(c *gin.Context) {
// DeviceOs: ct.Platform(c), // DeviceOs: ct.Platform(c),
DeviceOs: f.DeviceInfo.Os, DeviceOs: f.DeviceInfo.Os,
Uuid: f.Uuid, Uuid: f.Uuid,
Verifier: verifier,
Nonce: nonce,
}, 5*60) }, 5*60)
response.Success(c, gin.H{ response.Success(c, gin.H{
"code": code, "code": state,
"url": url, "url": url,
}) })
} }

View File

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

View File

@@ -43,20 +43,22 @@ func (o *Oauth) ToBind(c *gin.Context) {
return return
} }
err, code, url := service.AllService.OauthService.BeginAuth(f.Op) err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error())) response.Error(c, response.TranslateMsg(c, err.Error()))
return return
} }
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{ service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{
Action: service.OauthActionTypeBind, Action: service.OauthActionTypeBind,
Op: f.Op, Op: f.Op,
UserId: u.Id, UserId: u.Id,
Verifier: verifier,
Nonce: nonce,
}, 5*60) }, 5*60)
response.Success(c, gin.H{ response.Success(c, gin.H{
"code": code, "code": state,
"url": url, "url": url,
}) })
} }

View File

@@ -108,6 +108,12 @@ func (ct *Peer) List(c *gin.Context) {
if query.Uuids != "" { if query.Uuids != "" {
tx.Where("uuid in (?)", 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) response.Success(c, res)
} }

View File

@@ -296,32 +296,12 @@ func (ct *User) MyOauth(c *gin.Context) {
// groupUsers // groupUsers
func (ct *User) GroupUsers(c *gin.Context) { func (ct *User) GroupUsers(c *gin.Context) {
q := &admin.GroupUsersQuery{} aG := service.AllService.GroupService.List(1, 999, nil)
if err := c.ShouldBindJSON(q); err != nil { aU := service.AllService.UserService.List(1, 9999, nil)
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Success(c, gin.H{
return "groups": aG.Groups,
} "users": aU.Users,
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)
}) })
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 // Register

View File

@@ -45,7 +45,7 @@ func (g *Group) Users(c *gin.Context) {
userList = service.AllService.UserService.ListByGroupId(u.GroupId, q.Page, q.PageSize) userList = service.AllService.UserService.ListByGroupId(u.GroupId, q.Page, q.PageSize)
} }
var data []*apiResp.UserPayload data := make([]*apiResp.UserPayload, 0, len(userList.Users))
for _, user := range userList.Users { for _, user := range userList.Users {
up := &apiResp.UserPayload{} up := &apiResp.UserPayload{}
up.FromUser(user) up.FromUser(user)
@@ -88,21 +88,30 @@ func (g *Group) Peers(c *gin.Context) {
users = service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId) users = service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId)
} }
namesById := make(map[uint]string) namesById := make(map[uint]string, len(users))
userIds := make([]uint, 0) userIds := make([]uint, 0, len(users))
for _, user := range users { for _, user := range users {
namesById[user.Id] = user.Username namesById[user.Id] = user.Username
userIds = append(userIds, user.Id) userIds = append(userIds, user.Id)
} }
dGroupNameById := make(map[uint]string)
allGroup := service.AllService.GroupService.DeviceGroupList(1, 999, nil)
for _, group := range allGroup.DeviceGroups {
dGroupNameById[group.Id] = group.Name
}
peerList := service.AllService.PeerService.ListByUserIds(userIds, q.Page, q.PageSize) peerList := service.AllService.PeerService.ListByUserIds(userIds, q.Page, q.PageSize)
var data []*apiResp.GroupPeerPayload data := make([]*apiResp.GroupPeerPayload, 0, len(peerList.Peers))
for _, peer := range peerList.Peers { for _, peer := range peerList.Peers {
uname, ok := namesById[peer.UserId] uname, ok := namesById[peer.UserId]
if !ok { if !ok {
uname = "" uname = ""
} }
dGroupName, ok2 := dGroupNameById[peer.GroupId]
if !ok2 {
dGroupName = ""
}
pp := &apiResp.GroupPeerPayload{} pp := &apiResp.GroupPeerPayload{}
pp.FromPeer(peer, uname) pp.FromPeer(peer, uname, dGroupName)
data = append(data, pp) data = append(data, pp)
} }
@@ -111,3 +120,31 @@ func (g *Group) Peers(c *gin.Context) {
Data: data, Data: data,
}) })
} }
// Device
// @Tags 群组
// @Summary 设备
// @Description 机器
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param status query int false "状态"
// @Param accessible query string false "accessible"
// @Success 200 {object} response.DataResponse
// @Failure 500 {object} response.Response
// @Router /device-group/accessible [get]
// @Security BearerAuth
func (g *Group) Device(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) {
response.Error(c, "Permission denied")
return
}
allGroup := service.AllService.GroupService.DeviceGroupList(1, 999, nil)
c.JSON(http.StatusOK, response.DataResponse{
Total: 0,
Data: allGroup.DeviceGroups,
})
}

View File

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

View File

@@ -8,6 +8,8 @@ import (
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api" apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n"
"net/http" "net/http"
) )
@@ -32,15 +34,14 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
} }
oauthService := service.AllService.OauthService oauthService := service.AllService.OauthService
var code string
var url string err, state, verifier, nonce, url := oauthService.BeginAuth(f.Op)
err, code, url = oauthService.BeginAuth(f.Op)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error())) response.Error(c, response.TranslateMsg(c, err.Error()))
return return
} }
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{ service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin, Action: service.OauthActionTypeLogin,
Id: f.Id, Id: f.Id,
Op: f.Op, Op: f.Op,
@@ -48,10 +49,12 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
DeviceName: f.DeviceInfo.Name, DeviceName: f.DeviceInfo.Name,
DeviceOs: f.DeviceInfo.Os, DeviceOs: f.DeviceInfo.Os,
DeviceType: f.DeviceInfo.Type, DeviceType: f.DeviceInfo.Type,
Verifier: verifier,
Nonce: nonce,
}, 5*60) }, 5*60)
//fmt.Println("code url", code, url) //fmt.Println("code url", code, url)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"code": code, "code": state,
"url": url, "url": url,
}) })
} }
@@ -143,7 +146,10 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
func (o *Oauth) OauthCallback(c *gin.Context) { func (o *Oauth) OauthCallback(c *gin.Context) {
state := c.Query("state") state := c.Query("state")
if state == "" { if state == "" {
c.String(http.StatusInternalServerError, response.TranslateParamMsg(c, "ParamIsEmpty", "state")) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "ParamIsEmpty",
"sub_message": "state",
})
return return
} }
cacheKey := state cacheKey := state
@@ -151,17 +157,24 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//从缓存中获取 //从缓存中获取
oauthCache := oauthService.GetOauthCache(cacheKey) oauthCache := oauthService.GetOauthCache(cacheKey)
if oauthCache == nil { if oauthCache == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthExpired")) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "OauthExpired",
})
return return
} }
nonce := oauthCache.Nonce
op := oauthCache.Op op := oauthCache.Op
action := oauthCache.Action action := oauthCache.Action
verifier := oauthCache.Verifier
var user *model.User var user *model.User
// 获取用户信息 // 获取用户信息
code := c.Query("code") code := c.Query("code")
err, oauthUser := oauthService.Callback(code, op) err, oauthUser := oauthService.Callback(code, verifier, op, nonce)
if err != nil { if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error())) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "OauthFailed",
"sub_message": err.Error(),
})
return return
} }
userId := oauthCache.UserId userId := oauthCache.UserId
@@ -172,28 +185,38 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
// 检查此openid是否已经绑定过 // 检查此openid是否已经绑定过
utr := oauthService.UserThirdInfo(op, openid) utr := oauthService.UserThirdInfo(op, openid)
if utr.UserId > 0 { if utr.UserId > 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser")) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "OauthHasBindOtherUser",
})
return return
} }
//绑定 //绑定
user = service.AllService.UserService.InfoById(userId) user = service.AllService.UserService.InfoById(userId)
if user == nil { if user == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound")) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "ItemNotFound",
})
return return
} }
//绑定 //绑定
err := oauthService.BindOauthUser(userId, oauthUser, op) err := oauthService.BindOauthUser(userId, oauthUser, op)
if err != nil { if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail")) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "BindFail",
})
return return
} }
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess")) c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": "BindSuccess",
})
return return
} else if action == service.OauthActionTypeLogin { } else if action == service.OauthActionTypeLogin {
//登录 //登录
if userId != 0 { if userId != 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess")) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "OauthHasBeenSuccess",
})
return return
} }
user = service.AllService.UserService.InfoByOauthId(op, openid) user = service.AllService.UserService.InfoByOauthId(op, openid)
@@ -210,7 +233,9 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//自动注册 //自动注册
err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op) err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op)
if err != nil { if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, err.Error())) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": err.Error(),
})
return return
} }
} }
@@ -230,11 +255,51 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
c.Redirect(http.StatusFound, url) c.Redirect(http.StatusFound, url)
return return
} }
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess")) c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": "OauthSuccess",
})
return return
} else { } else {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ParamsError")) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "ParamsError",
})
return 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 return
} }
fpe := f.ToPeer() fpe := f.ToPeer()
pe := service.AllService.PeerService.FindById(f.Id) pe := service.AllService.PeerService.FindByUuid(f.Uuid)
if pe.RowId == 0 { if pe.RowId == 0 {
pe = f.ToPeer() pe = f.ToPeer()
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid) 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") 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

@@ -1,9 +1,9 @@
package web package web
import ( import (
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/global"
"strconv"
) )
type Index struct { type Index struct {
@@ -15,13 +15,21 @@ func (i *Index) Index(c *gin.Context) {
func (i *Index) ConfigJs(c *gin.Context) { func (i *Index) ConfigJs(c *gin.Context) {
apiServer := global.Config.Rustdesk.ApiServer apiServer := global.Config.Rustdesk.ApiServer
magicQueryonline := strconv.Itoa(global.Config.Rustdesk.WebclientMagicQueryonline) magicQueryonline := global.Config.Rustdesk.WebclientMagicQueryonline
tmp := ` tmp := fmt.Sprintf(`localStorage.setItem('api-server', '%v');
localStorage.setItem('api-server', "` + apiServer + `") const ws2_prefix = 'wc-';
const ws2_prefix = 'wc-' localStorage.setItem(ws2_prefix+'api-server', '%v');
localStorage.setItem(ws2_prefix+'api-server', "` + apiServer + `")
window.webclient_magic_queryonline = ` + magicQueryonline + `` window.webclient_magic_queryonline = %d;
window.ws_host = '%v';
`, apiServer, apiServer, magicQueryonline, global.Config.Rustdesk.WsHost)
// tmp := `
//localStorage.setItem('api-server', "` + apiServer + `")
//const ws2_prefix = 'wc-'
//localStorage.setItem(ws2_prefix+'api-server', "` + apiServer + `")
//
//window.webclient_magic_queryonline = ` + magicQueryonline + ``
c.Header("Content-Type", "application/javascript")
c.String(200, tmp) c.String(200, tmp)
} }

View File

@@ -22,3 +22,15 @@ func (gf *GroupForm) ToGroup() *model.Group {
group.Type = gf.Type group.Type = gf.Type
return group return group
} }
type DeviceGroupForm struct {
Id uint `json:"id"`
Name string `json:"name" validate:"required"`
}
func (gf *DeviceGroupForm) ToDeviceGroup() *model.DeviceGroup {
group := &model.DeviceGroup{}
group.Id = gf.Id
group.Name = gf.Name
return group
}

View File

@@ -24,6 +24,8 @@ type OauthForm struct {
ClientSecret string `json:"client_secret" validate:"required"` ClientSecret string `json:"client_secret" validate:"required"`
RedirectUrl string `json:"redirect_url" validate:"required"` RedirectUrl string `json:"redirect_url" validate:"required"`
AutoRegister *bool `json:"auto_register"` AutoRegister *bool `json:"auto_register"`
PkceEnable *bool `json:"pkce_enable"`
PkceMethod string `json:"pkce_method"`
} }
func (of *OauthForm) ToOauth() *model.Oauth { func (of *OauthForm) ToOauth() *model.Oauth {
@@ -36,6 +38,8 @@ func (of *OauthForm) ToOauth() *model.Oauth {
AutoRegister: of.AutoRegister, AutoRegister: of.AutoRegister,
Issuer: of.Issuer, Issuer: of.Issuer,
Scopes: of.Scopes, Scopes: of.Scopes,
PkceEnable: of.PkceEnable,
PkceMethod: of.PkceMethod,
} }
oa.Id = of.Id oa.Id = of.Id
return oa return oa

View File

@@ -12,6 +12,7 @@ type PeerForm struct {
Username string `json:"username"` Username string `json:"username"`
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
Version string `json:"version"` Version string `json:"version"`
GroupId uint `json:"group_id"`
} }
type PeerBatchDeleteForm struct { type PeerBatchDeleteForm struct {
@@ -30,6 +31,7 @@ func (f *PeerForm) ToPeer() *model.Peer {
Username: f.Username, Username: f.Username,
Uuid: f.Uuid, Uuid: f.Uuid,
Version: f.Version, Version: f.Version,
GroupId: f.GroupId,
} }
} }
@@ -39,6 +41,8 @@ type PeerQuery struct {
Id string `json:"id" form:"id"` Id string `json:"id" form:"id"`
Hostname string `json:"hostname" form:"hostname"` Hostname string `json:"hostname" form:"hostname"`
Uuids string `json:"uuids" form:"uuids"` Uuids string `json:"uuids" form:"uuids"`
Ip string `json:"ip" form:"ip"`
Username string `json:"username" form:"username"`
} }
type SimpleDataQuery struct { type SimpleDataQuery struct {

View File

@@ -40,14 +40,14 @@ type LoginForm struct {
type UserListQuery struct { type UserListQuery struct {
Page uint `json:"page" form:"page" validate:"required" label:"页码"` Page uint `json:"page" form:"page" validate:"required" label:"页码"`
PageSize uint `json:"page_size" form:"page_size" validate:"required" label:"每页数量"` PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"每页数量"`
Status int `json:"status" form:"status" label:"状态"` Status int `json:"status" form:"status" label:"状态"`
Accessible string `json:"accessible" form:"accessible"` Accessible string `json:"accessible" form:"accessible"`
} }
type PeerListQuery struct { type PeerListQuery struct {
Page uint `json:"page" form:"page" validate:"required" label:"页码"` Page uint `json:"page" form:"page" validate:"required" label:"页码"`
PageSize uint `json:"page_size" form:"page_size" validate:"required" label:"每页数量"` PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"每页数量"`
Status int `json:"status" form:"status" label:"状态"` Status int `json:"status" form:"status" label:"状态"`
Accessible string `json:"accessible" form:"accessible"` Accessible string `json:"accessible" form:"accessible"`
} }

View File

@@ -22,15 +22,3 @@ type UserOauthItem struct {
Op string `json:"op"` Op string `json:"op"`
Status int `json:"status"` 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

@@ -32,12 +32,13 @@ https://github.com/rustdesk/rustdesk/blob/master/flutter/lib/common/hbbs/hbbs.da
} }
*/ */
type GroupPeerPayload struct { type GroupPeerPayload struct {
Id string `json:"id"` Id string `json:"id"`
Info *PeerPayloadInfo `json:"info"` Info *PeerPayloadInfo `json:"info"`
Status int `json:"status"` Status int `json:"status"`
User string `json:"user"` User string `json:"user"`
UserName string `json:"user_name"` UserName string `json:"user_name"`
Note string `json:"note"` Note string `json:"note"`
DeviceGroupName string `json:"device_group_name"`
} }
type PeerPayloadInfo struct { type PeerPayloadInfo struct {
DeviceName string `json:"device_name"` DeviceName string `json:"device_name"`
@@ -59,7 +60,7 @@ func (gpp *GroupPeerPayload) FromAddressBook(a *model.AddressBook, username stri
gpp.UserName = username gpp.UserName = username
} }
func (gpp *GroupPeerPayload) FromPeer(p *model.Peer, username string) { func (gpp *GroupPeerPayload) FromPeer(p *model.Peer, username string, dGroupName string) {
gpp.Id = p.Id gpp.Id = p.Id
gpp.Info = &PeerPayloadInfo{ gpp.Info = &PeerPayloadInfo{
DeviceName: p.Hostname, DeviceName: p.Hostname,
@@ -68,4 +69,5 @@ func (gpp *GroupPeerPayload) FromPeer(p *model.Peer, username string) {
} }
gpp.Note = "" gpp.Note = ""
gpp.UserName = username gpp.UserName = username
gpp.DeviceGroupName = dGroupName
} }

View File

@@ -49,7 +49,7 @@ func Init(g *gin.Engine) {
MyBind(adg) MyBind(adg)
RustdeskCmdBind(adg) RustdeskCmdBind(adg)
DeviceGroupBind(adg)
//访问静态文件 //访问静态文件
//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload")) //g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
} }
@@ -106,6 +106,18 @@ func GroupBind(rg *gin.RouterGroup) {
} }
} }
func DeviceGroupBind(rg *gin.RouterGroup) {
aR := rg.Group("/device_group").Use(middleware.AdminPrivilege())
{
cont := &admin.DeviceGroup{}
aR.GET("/list", cont.List)
aR.GET("/detail/:id", cont.Detail)
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
}
}
func TagBind(rg *gin.RouterGroup) { func TagBind(rg *gin.RouterGroup) {
aR := rg.Group("/tag").Use(middleware.AdminPrivilege()) aR := rg.Group("/tag").Use(middleware.AdminPrivilege())
{ {

View File

@@ -18,6 +18,8 @@ func ApiInit(g *gin.Engine) {
if global.Config.App.ShowSwagger == 1 { if global.Config.App.ShowSwagger == 1 {
g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api"))) g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api")))
} }
// 加载 HTML 模板
g.LoadHTMLGlob("resources/templates/*")
frg := g.Group("/api") frg := g.Group("/api")
@@ -46,11 +48,13 @@ func ApiInit(g *gin.Engine) {
//api/oauth/callback //api/oauth/callback
frg.GET("/oauth/callback", o.OauthCallback) frg.GET("/oauth/callback", o.OauthCallback)
frg.GET("/oauth/login", o.OauthCallback) frg.GET("/oauth/login", o.OauthCallback)
frg.GET("/oauth/msg", o.Message)
} }
{ {
pe := &api.Peer{} pe := &api.Peer{}
//提交系统信息 //提交系统信息
frg.POST("/sysinfo", pe.SysInfo) frg.POST("/sysinfo", pe.SysInfo)
frg.POST("/sysinfo_ver", pe.SysInfoVer)
} }
if global.Config.App.WebClient == 1 { if global.Config.App.WebClient == 1 {
@@ -79,6 +83,8 @@ func ApiInit(g *gin.Engine) {
gr := &api.Group{} gr := &api.Group{}
frg.GET("/users", gr.Users) frg.GET("/users", gr.Users)
frg.GET("/peers", gr.Peers) frg.GET("/peers", gr.Peers)
// /api/device-group/accessible?current=1&pageSize=100
frg.GET("/device-group/accessible", gr.Device)
} }
{ {
@@ -88,6 +94,7 @@ func ApiInit(g *gin.Engine) {
//更新地址 //更新地址
frg.POST("/ab", ab.UpAb) frg.POST("/ab", ab.UpAb)
} }
PersonalRoutes(frg) PersonalRoutes(frg)
//访问静态文件 //访问静态文件
g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/public/upload")) g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/public/upload"))

View File

@@ -16,3 +16,14 @@ type GroupList struct {
Groups []*Group `json:"list"` Groups []*Group `json:"list"`
Pagination Pagination
} }
type DeviceGroup struct {
IdModel
Name string `json:"name" gorm:"default:'';not null;"`
TimeModel
}
type DeviceGroupList struct {
DeviceGroups []*DeviceGroup `json:"list"`
Pagination
}

View File

@@ -14,6 +14,8 @@ const (
OauthTypeGoogle string = "google" OauthTypeGoogle string = "google"
OauthTypeOidc string = "oidc" OauthTypeOidc string = "oidc"
OauthTypeWebauth string = "webauth" OauthTypeWebauth string = "webauth"
PKCEMethodS256 string = "S256"
PKCEMethodPlain string = "plain"
) )
// Validate the oauth type // Validate the oauth type
@@ -41,6 +43,8 @@ type Oauth struct {
AutoRegister *bool `json:"auto_register"` AutoRegister *bool `json:"auto_register"`
Scopes string `json:"scopes"` Scopes string `json:"scopes"`
Issuer string `json:"issuer"` Issuer string `json:"issuer"`
PkceEnable *bool `json:"pkce_enable"`
PkceMethod string `json:"pkce_method"`
TimeModel TimeModel
} }
@@ -68,6 +72,13 @@ func (oa *Oauth) FormatOauthInfo() error {
if oauthType == OauthTypeGoogle && issuer == "" { if oauthType == OauthTypeGoogle && issuer == "" {
oa.Issuer = IssuerGoogle oa.Issuer = IssuerGoogle
} }
if oa.PkceEnable == nil {
oa.PkceEnable = new(bool)
*oa.PkceEnable = false
}
if oa.PkceMethod == "" {
oa.PkceMethod = PKCEMethodS256
}
return nil return nil
} }

View File

@@ -14,6 +14,7 @@ type Peer struct {
User *User `json:"user,omitempty"` User *User `json:"user,omitempty"`
LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"` LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"`
LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"` LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"`
GroupId uint `json:"group_id" gorm:"default:0;not null;index"`
TimeModel TimeModel
} }

View File

@@ -137,4 +137,9 @@ other = "Captcha error."
[PwdLoginDisabled] [PwdLoginDisabled]
description = "Password login disabled." description = "Password login disabled."
one = "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] [PwdLoginDisabled]
description = "Password login disabled." description = "Password login disabled."
one = "Inicio de sesión con contraseña deshabilitado." 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] [PwdLoginDisabled]
description = "Password login disabled." description = "Password login disabled."
one = "Connexion par mot de passe désactivée." 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." description = "Password login disabled."
one = "비밀번호 로그인이 비활성화되었습니다." one = "비밀번호 로그인이 비활성화되었습니다."
other = "비밀번호 로그인이 비활성화되었습니다." other = "비밀번호 로그인이 비활성화되었습니다."
[CannotShareToSelf]
description = "Cannot share to self."
one = "자기 자신에게 공유할 수 없습니다."
other = "자기 자신에게 공유할 수 없습니다."

View File

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

View File

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

View File

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

View File

View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OauthFailed - RustDesk API</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.success-container {
text-align: center;
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-width: 400px;
width: 90%;
}
.checkmark {
color: #ba363a;
font-size: 4rem;
margin-bottom: 1rem;
}
h1 {
color: #333;
margin-bottom: 1rem;
}
p {
color: #666;
line-height: 1.6;
margin-bottom: 1.5rem;
}
.return-link {
display: inline-block;
padding: 10px 20px;
background-color: #ba363a;
color: white;
text-decoration: none;
border-radius: 5px;
transition: background-color 0.3s;
}
.return-link:hover {
background-color: #ba363a;
}
</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 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

@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OauthSuccess - RustDesk API</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.success-container {
text-align: center;
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-width: 400px;
width: 90%;
}
.checkmark {
color: #4CAF50;
font-size: 4rem;
margin-bottom: 1rem;
}
h1 {
color: #333;
margin-bottom: 1rem;
}
p {
color: #666;
line-height: 1.6;
margin-bottom: 1.5rem;
}
.return-link {
display: inline-block;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 5px;
transition: background-color 0.3s;
}
.return-link:hover {
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 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

@@ -38,5 +38,21 @@
"asset": "assets/address_book.ttf" "asset": "assets/address_book.ttf"
} }
] ]
},
{
"family": "DeviceGroup",
"fonts": [
{
"asset": "assets/device_group.ttf"
}
]
},
{
"family": "More",
"fonts": [
{
"asset": "assets/more.ttf"
}
]
} }
] ]

Binary file not shown.

BIN
resources/web2/assets/assets/more.ttf vendored Normal file

Binary file not shown.

View File

@@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from. href value below to reflect the base path you are serving from.
@@ -16,195 +16,196 @@
--> -->
<base href="/webclient2/" /> <base href="/webclient2/" />
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<meta content="IE=Edge" http-equiv="X-UA-Compatible" /> <meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
<meta name="description" content="Remote Desktop." /> <meta name="description" content="Remote Desktop."/>
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="RustDesk" /> <meta name="apple-mobile-web-app-title" content="RustDesk"/>
<link rel="apple-touch-icon" href="icons/Icon-192.png?v=1a7ad736" /> <link rel="apple-touch-icon" href="icons/Icon-192.png?v=1a7ad736"/>
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="favicon.svg?v=8fcccd9a" /> <link rel="icon" type="image/svg+xml" href="favicon.svg?v=8fcccd9a"/>
<title>RustDesk</title> <title>RustDesk</title>
<script src="/webclient-config/index.js"></script> <script src="/webclient-config/index.js"></script>
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="manifest.json"/>
<script type="module" crossorigin src="js/dist/index.js?v=cabfd933"></script> <script type="module" crossorigin src="js/dist/index.js?v=ddbe54f1"></script>
<link rel="modulepreload" href="js/dist/vendor.js?v=0b990c6e" /> <link rel="modulepreload" href="js/dist/vendor.js?v=0b990c6e"/>
<style> <style>
html, html,
body, body,
#root { #root {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
}
#root {
background-repeat: no-repeat;
background-size: 100% auto;
}
.loading-title {
font-size: 1.1rem;
}
.loading-sub-title {
margin-top: 20px;
font-size: 1rem;
color: #888;
}
.page-loading-warp {
display: flex;
align-items: center;
justify-content: center;
padding: 26px;
}
.ant-spin {
position: absolute;
display: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
color: #1890ff;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
text-align: center;
list-style: none;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s
cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-webkit-font-feature-settings: "tnum";
font-feature-settings: "tnum";
}
.ant-spin-spinning {
position: static;
display: inline-block;
opacity: 1;
}
.ant-spin-dot {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
font-size: 20px;
}
.ant-spin-dot-item {
position: absolute;
display: block;
width: 9px;
height: 9px;
background-color: #1890ff;
border-radius: 100%;
-webkit-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
opacity: 0.3;
-webkit-animation: antspinmove 1s infinite linear alternate;
animation: antSpinMove 1s infinite linear alternate;
}
.ant-spin-dot-item:nth-child(1) {
top: 0;
left: 0;
}
.ant-spin-dot-item:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.ant-spin-dot-item:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.ant-spin-dot-item:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.ant-spin-dot-spin {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: antrotate 1.2s infinite linear;
animation: antRotate 1.2s infinite linear;
}
.ant-spin-lg .ant-spin-dot {
width: 32px;
height: 32px;
font-size: 32px;
}
.ant-spin-lg .ant-spin-dot i {
width: 14px;
height: 14px;
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.ant-spin-blur {
background: #fff;
opacity: 0.5;
} }
}
@-webkit-keyframes antSpinMove { #root {
to { background-repeat: no-repeat;
opacity: 1; background-size: 100% auto;
} }
}
@keyframes antSpinMove { .loading-title {
to { font-size: 1.1rem;
opacity: 1;
} }
}
@-webkit-keyframes antRotate { .loading-sub-title {
to { margin-top: 20px;
-webkit-transform: rotate(405deg); font-size: 1rem;
transform: rotate(405deg); color: #888;
} }
}
@keyframes antRotate { .page-loading-warp {
to { display: flex;
-webkit-transform: rotate(405deg); align-items: center;
transform: rotate(405deg); justify-content: center;
padding: 26px;
}
.ant-spin {
position: absolute;
display: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
color: #1890ff;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
text-align: center;
list-style: none;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-webkit-font-feature-settings: "tnum";
font-feature-settings: "tnum";
}
.ant-spin-spinning {
position: static;
display: inline-block;
opacity: 1;
}
.ant-spin-dot {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
font-size: 20px;
}
.ant-spin-dot-item {
position: absolute;
display: block;
width: 9px;
height: 9px;
background-color: #1890ff;
border-radius: 100%;
-webkit-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
opacity: 0.3;
-webkit-animation: antspinmove 1s infinite linear alternate;
animation: antSpinMove 1s infinite linear alternate;
}
.ant-spin-dot-item:nth-child(1) {
top: 0;
left: 0;
}
.ant-spin-dot-item:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.ant-spin-dot-item:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.ant-spin-dot-item:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.ant-spin-dot-spin {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: antrotate 1.2s infinite linear;
animation: antRotate 1.2s infinite linear;
}
.ant-spin-lg .ant-spin-dot {
width: 32px;
height: 32px;
font-size: 32px;
}
.ant-spin-lg .ant-spin-dot i {
width: 14px;
height: 14px;
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.ant-spin-blur {
background: #fff;
opacity: 0.5;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
} }
}
</style> </style>
</head> </head>
<body> <body>
<div id="root"> <div id="root">
<div <div
id="div-background" id="div-background"
style=" style="
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -212,117 +213,119 @@
height: 100%; height: 100%;
min-height: 420px; min-height: 420px;
" "
> >
<img src="./favicon.svg?v=8fcccd9a" alt="logo" width="256" /> <img src="./favicon.svg?v=8fcccd9a" alt="logo" width="256"/>
<div class="page-loading-warp"> <div class="page-loading-warp">
<div class="ant-spin ant-spin-lg ant-spin-spinning"> <div class="ant-spin ant-spin-lg ant-spin-spinning">
<span class="ant-spin-dot ant-spin-dot-spin"> <span class="ant-spin-dot ant-spin-dot-spin">
<i class="ant-spin-dot-item"></i> <i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i> <i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i> <i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i>
</span> </span>
</div> </div>
</div> </div>
<div <div
style="display: flex; align-items: center; justify-content: center" style="display: flex; align-items: center; justify-content: center"
> >
<img src="./favicon.svg?v=8fcccd9a" width="32" style="margin-right: 8px" /> <img src="./favicon.svg?v=8fcccd9a" width="32" style="margin-right: 8px"/>
<span id="span-text">RustDesk Web Client V2 Preview</span> <span id="span-text">RustDesk Web Client V2 Preview</span>
</div> </div>
</div>
</div> </div>
<!-- This script installs service_worker.js to provide PWA functionality to </div>
application. For more information, see: <!-- This script installs service_worker.js to provide PWA functionality to
https://developers.google.com/web/fundamentals/primers/service-workers --> application. For more information, see:
<script> https://developers.google.com/web/fundamentals/primers/service-workers -->
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") <script>
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches .matches
? "dark" ? "dark"
: "light"; : "light";
const myTheme = localStorage.getItem("wc-option:local:theme"); const myTheme = localStorage.getItem("wc-option:local:theme");
const them = myTheme || systemTheme; const them = myTheme || systemTheme;
const divBackground = document.querySelector("#div-background"); const divBackground = document.querySelector("#div-background");
if (divBackground) { if (divBackground) {
divBackground.style.backgroundColor = them === "dark" ? "#000" : "#fff"; divBackground.style.backgroundColor = them === "dark" ? "#000" : "#fff";
} }
const spanConsole = document.querySelector("#span-text"); const spanConsole = document.querySelector("#span-text");
if (spanConsole) { if (spanConsole) {
spanConsole.style.color = them === "dark" ? "#fff" : "#000"; spanConsole.style.color = them === "dark" ? "#fff" : "#000";
} }
const serviceWorkerVersion = "3267265270"; const serviceWorkerVersion = "461457302";
var scriptLoaded = false; var scriptLoaded = false;
function loadMainDartJs() {
function loadMainDartJs() {
if (scriptLoaded) { if (scriptLoaded) {
return; return;
} }
scriptLoaded = true; scriptLoaded = true;
var scriptTag = document.createElement("script"); var scriptTag = document.createElement("script");
scriptTag.src = "main.dart.js?v=060a626e"; scriptTag.src = "main.dart.js?v=6d16cb80";
scriptTag.type = "application/javascript"; scriptTag.type = "application/javascript";
document.body.append(scriptTag); document.body.append(scriptTag);
} }
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
// Service workers are supported. Use them. // Service workers are supported. Use them.
window.addEventListener("load", function () { window.addEventListener("load", function () {
// Wait for registration to finish before dropping the <script> tag. // Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times, // Otherwise, the browser will load the script multiple times,
// potentially different versions. // potentially different versions.
var serviceWorkerUrl = var serviceWorkerUrl =
"flutter_service_worker.js?v=" + serviceWorkerVersion; "flutter_service_worker.js?v=" + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => { navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
function waitForActivation(serviceWorker) { function waitForActivation(serviceWorker) {
serviceWorker.addEventListener("statechange", () => { serviceWorker.addEventListener("statechange", () => {
if (serviceWorker.state == "activated") { if (serviceWorker.state == "activated") {
console.log("Installed new service worker."); console.log("Installed new service worker.");
loadMainDartJs(); loadMainDartJs();
}
});
} }
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log("New service worker available.");
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log("Loading app from service worker.");
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time, if (!reg.active && (reg.installing || reg.waiting)) {
// fallback to plaint <script> tag. // No active web worker and we have installed or are installing
setTimeout(() => { // one for the first time. Simply wait for it to activate.
if (!scriptLoaded) { waitForActivation(reg.installing || reg.waiting);
console.warn( } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
"Failed to load app from service worker. Falling back to plain <script> tag." // When the app updates the serviceWorkerVersion changes, so we
); // need to ask the service worker to update.
loadMainDartJs(); console.log("New service worker available.");
} reg.update();
}, 4000); waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log("Loading app from service worker.");
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
"Failed to load app from service worker. Falling back to plain <script> tag."
);
loadMainDartJs();
}
}, 4000);
}); });
} else { } else {
// Service workers not supported. Just drop the <script> tag. // Service workers not supported. Just drop the <script> tag.
loadMainDartJs(); loadMainDartJs();
} }
</script> </script>
<script src="libs/stream/ponyfill.min.js"></script> <script src="libs/stream/ponyfill.min.js"></script>
<script src="libs/stream/StreamSaver.min.js"></script> <script src="libs/stream/StreamSaver.min.js"></script>
<script src="libs/firebase-app.js?8.10.1"></script> <script src="libs/firebase-app.js?8.10.1"></script>
<script src="libs/firebase-analytics.js?8.10.1"></script> <script src="libs/firebase-analytics.js?8.10.1"></script>
<script> <script>
// Your web app's Firebase configuration // Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional // For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = { const firebaseConfig = {
apiKey: "AIzaSyCgehIZk1aFP0E7wZtYRRqrfvNiNAF39-A", apiKey: "AIzaSyCgehIZk1aFP0E7wZtYRRqrfvNiNAF39-A",
authDomain: "rustdesk.firebaseapp.com", authDomain: "rustdesk.firebaseapp.com",
databaseURL: "https://rustdesk.firebaseio.com", databaseURL: "https://rustdesk.firebaseio.com",
@@ -331,11 +334,11 @@
messagingSenderId: "768133699366", messagingSenderId: "768133699366",
appId: "1:768133699366:web:d50faf0792cb208d7993e7", appId: "1:768133699366:web:d50faf0792cb208d7993e7",
measurementId: "G-9PEH85N6ZQ", measurementId: "G-9PEH85N6ZQ",
}; };
// Initialize Firebase // Initialize Firebase
firebase.initializeApp(firebaseConfig); firebase.initializeApp(firebaseConfig);
firebase.analytics(); firebase.analytics();
</script> </script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
window._gwen = {} window._gwen = {}
window._gwen.kv = {} window._gwen.kv = {}
//fix 语言
if(!localStorage.getItem('wc-option:local:lang') && navigator.language){
localStorage.setItem('wc-option:local:lang', navigator.language.toLowerCase())
}
const storage_prefix = 'wc-' const storage_prefix = 'wc-'
const apiserver = localStorage.getItem('wc-api-server') const apiserver = localStorage.getItem('wc-api-server')
@@ -46,7 +52,7 @@ if (share_token) {
password: peer.tmppwd, password: peer.tmppwd,
}*/ }*/
//修改location //修改location
window.location.href = `/webclient2/#/${peer.info.id}?password=${peer.tmppwd}` window.location.href = `/webclient2/#/${peer.info.id}?password=${encodeURIComponent(peer.tmppwd)}`
} }
}) })
} }

163526
resources/web2/main.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,6 @@ package service
import ( import (
"encoding/json" "encoding/json"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm" "gorm.io/gorm"
"strings" "strings"
@@ -14,24 +13,24 @@ type AddressBookService struct {
func (s *AddressBookService) Info(id string) *model.AddressBook { func (s *AddressBookService) Info(id string) *model.AddressBook {
p := &model.AddressBook{} p := &model.AddressBook{}
global.DB.Where("id = ?", id).First(p) DB.Where("id = ?", id).First(p)
return p return p
} }
func (s *AddressBookService) InfoByUserIdAndId(userid uint, id string) *model.AddressBook { func (s *AddressBookService) InfoByUserIdAndId(userid uint, id string) *model.AddressBook {
p := &model.AddressBook{} p := &model.AddressBook{}
global.DB.Where("user_id = ? and id = ?", userid, id).First(p) DB.Where("user_id = ? and id = ?", userid, id).First(p)
return p return p
} }
func (s *AddressBookService) InfoByUserIdAndIdAndCid(userid uint, id string, cid uint) *model.AddressBook { func (s *AddressBookService) InfoByUserIdAndIdAndCid(userid uint, id string, cid uint) *model.AddressBook {
p := &model.AddressBook{} p := &model.AddressBook{}
global.DB.Where("user_id = ? and id = ? and collection_id = ?", userid, id, cid).First(p) DB.Where("user_id = ? and id = ? and collection_id = ?", userid, id, cid).First(p)
return p return p
} }
func (s *AddressBookService) InfoByRowId(id uint) *model.AddressBook { func (s *AddressBookService) InfoByRowId(id uint) *model.AddressBook {
p := &model.AddressBook{} p := &model.AddressBook{}
global.DB.Where("row_id = ?", id).First(p) DB.Where("row_id = ?", id).First(p)
return p return p
} }
func (s *AddressBookService) ListByUserId(userId, page, pageSize uint) (res *model.AddressBookList) { func (s *AddressBookService) ListByUserId(userId, page, pageSize uint) (res *model.AddressBookList) {
@@ -49,14 +48,14 @@ func (s *AddressBookService) ListByUserIds(userIds []uint, page, pageSize uint)
// AddAddressBook // AddAddressBook
func (s *AddressBookService) AddAddressBook(ab *model.AddressBook) error { func (s *AddressBookService) AddAddressBook(ab *model.AddressBook) error {
return global.DB.Create(ab).Error return DB.Create(ab).Error
} }
// UpdateAddressBook // UpdateAddressBook
func (s *AddressBookService) UpdateAddressBook(abs []*model.AddressBook, userId uint) error { func (s *AddressBookService) UpdateAddressBook(abs []*model.AddressBook, userId uint) error {
//比较peers和数据库中的数据如果peers中的数据在数据库中不存在则添加如果存在则更新如果数据库中的数据在peers中不存在则删除 //比较peers和数据库中的数据如果peers中的数据在数据库中不存在则添加如果存在则更新如果数据库中的数据在peers中不存在则删除
// 开始事务 // 开始事务
tx := global.DB.Begin() tx := DB.Begin()
//1. 获取数据库中的数据 //1. 获取数据库中的数据
var dbABs []*model.AddressBook var dbABs []*model.AddressBook
tx.Where("user_id = ?", userId).Find(&dbABs) tx.Where("user_id = ?", userId).Find(&dbABs)
@@ -107,7 +106,7 @@ func (s *AddressBookService) List(page, pageSize uint, where func(tx *gorm.DB))
res = &model.AddressBookList{} res = &model.AddressBookList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AddressBook{}) tx := DB.Model(&model.AddressBook{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -129,38 +128,38 @@ func (s *AddressBookService) FromPeer(peer *model.Peer) (a *model.AddressBook) {
// Create 创建 // Create 创建
func (s *AddressBookService) Create(u *model.AddressBook) error { func (s *AddressBookService) Create(u *model.AddressBook) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
func (s *AddressBookService) Delete(u *model.AddressBook) error { func (s *AddressBookService) Delete(u *model.AddressBook) error {
return global.DB.Delete(u).Error return DB.Delete(u).Error
} }
// Update 更新 // Update 更新
func (s *AddressBookService) Update(u *model.AddressBook) error { func (s *AddressBookService) Update(u *model.AddressBook) error {
return global.DB.Model(u).Updates(u).Error return DB.Model(u).Updates(u).Error
} }
// UpdateByMap 更新 // UpdateByMap 更新
func (s *AddressBookService) UpdateByMap(u *model.AddressBook, data map[string]interface{}) error { func (s *AddressBookService) UpdateByMap(u *model.AddressBook, data map[string]interface{}) error {
return global.DB.Model(u).Updates(data).Error return DB.Model(u).Updates(data).Error
} }
// UpdateAll 更新 // UpdateAll 更新
func (s *AddressBookService) UpdateAll(u *model.AddressBook) error { func (s *AddressBookService) UpdateAll(u *model.AddressBook) error {
return global.DB.Model(u).Select("*").Omit("created_at").Updates(u).Error return DB.Model(u).Select("*").Omit("created_at").Updates(u).Error
} }
// ShareByWebClient 分享 // ShareByWebClient 分享
func (s *AddressBookService) ShareByWebClient(m *model.ShareRecord) error { func (s *AddressBookService) ShareByWebClient(m *model.ShareRecord) error {
m.ShareToken = uuid.New().String() m.ShareToken = uuid.New().String()
return global.DB.Create(m).Error return DB.Create(m).Error
} }
// SharedPeer // SharedPeer
func (s *AddressBookService) SharedPeer(shareToken string) *model.ShareRecord { func (s *AddressBookService) SharedPeer(shareToken string) *model.ShareRecord {
m := &model.ShareRecord{} m := &model.ShareRecord{}
global.DB.Where("share_token = ?", shareToken).First(m) DB.Where("share_token = ?", shareToken).First(m)
return m return m
} }
@@ -190,7 +189,7 @@ func (s *AddressBookService) ListCollection(page, pageSize uint, where func(tx *
res = &model.AddressBookCollectionList{} res = &model.AddressBookCollectionList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AddressBookCollection{}) tx := DB.Model(&model.AddressBookCollection{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -200,7 +199,7 @@ func (s *AddressBookService) ListCollection(page, pageSize uint, where func(tx *
return return
} }
func (s *AddressBookService) ListCollectionByIds(ids []uint) (res []*model.AddressBookCollection) { func (s *AddressBookService) ListCollectionByIds(ids []uint) (res []*model.AddressBookCollection) {
global.DB.Where("id in ?", ids).Find(&res) DB.Where("id in ?", ids).Find(&res)
return res return res
} }
@@ -212,20 +211,20 @@ func (s *AddressBookService) ListCollectionByUserId(userId uint) (res *model.Add
} }
func (s *AddressBookService) CollectionInfoById(id uint) *model.AddressBookCollection { func (s *AddressBookService) CollectionInfoById(id uint) *model.AddressBookCollection {
p := &model.AddressBookCollection{} p := &model.AddressBookCollection{}
global.DB.Where("id = ?", id).First(p) DB.Where("id = ?", id).First(p)
return p return p
} }
func (s *AddressBookService) CollectionReadRules(user *model.User) (res []*model.AddressBookCollectionRule) { func (s *AddressBookService) CollectionReadRules(user *model.User) (res []*model.AddressBookCollectionRule) {
// personalRules // personalRules
var personalRules []*model.AddressBookCollectionRule var personalRules []*model.AddressBookCollectionRule
tx2 := global.DB.Model(&model.AddressBookCollectionRule{}) tx2 := DB.Model(&model.AddressBookCollectionRule{})
tx2.Where("type = ? and to_id = ? and rule > 0", model.ShareAddressBookRuleTypePersonal, user.Id).Find(&personalRules) tx2.Where("type = ? and to_id = ? and rule > 0", model.ShareAddressBookRuleTypePersonal, user.Id).Find(&personalRules)
res = append(res, personalRules...) res = append(res, personalRules...)
//group //group
var groupRules []*model.AddressBookCollectionRule var groupRules []*model.AddressBookCollectionRule
tx3 := global.DB.Model(&model.AddressBookCollectionRule{}) tx3 := DB.Model(&model.AddressBookCollectionRule{})
tx3.Where("type = ? and to_id = ? and rule > 0", model.ShareAddressBookRuleTypeGroup, user.GroupId).Find(&groupRules) tx3.Where("type = ? and to_id = ? and rule > 0", model.ShareAddressBookRuleTypeGroup, user.GroupId).Find(&groupRules)
res = append(res, groupRules...) res = append(res, groupRules...)
return return
@@ -238,7 +237,7 @@ func (s *AddressBookService) UserMaxRule(user *model.User, uid, cid uint) int {
} }
max := 0 max := 0
personalRules := &model.AddressBookCollectionRule{} personalRules := &model.AddressBookCollectionRule{}
tx := global.DB.Model(personalRules) tx := DB.Model(personalRules)
tx.Where("type = ? and collection_id = ? and to_id = ?", model.ShareAddressBookRuleTypePersonal, cid, user.Id).First(&personalRules) tx.Where("type = ? and collection_id = ? and to_id = ?", model.ShareAddressBookRuleTypePersonal, cid, user.Id).First(&personalRules)
if personalRules.Id != 0 { if personalRules.Id != 0 {
max = personalRules.Rule max = personalRules.Rule
@@ -248,7 +247,7 @@ func (s *AddressBookService) UserMaxRule(user *model.User, uid, cid uint) int {
} }
groupRules := &model.AddressBookCollectionRule{} groupRules := &model.AddressBookCollectionRule{}
tx2 := global.DB.Model(groupRules) tx2 := DB.Model(groupRules)
tx2.Where("type = ? and collection_id = ? and to_id = ?", model.ShareAddressBookRuleTypeGroup, cid, user.GroupId).First(&groupRules) tx2.Where("type = ? and collection_id = ? and to_id = ?", model.ShareAddressBookRuleTypeGroup, cid, user.GroupId).First(&groupRules)
if groupRules.Id != 0 { if groupRules.Id != 0 {
if groupRules.Rule > max { if groupRules.Rule > max {
@@ -272,16 +271,16 @@ func (s *AddressBookService) CheckUserFullControlPrivilege(user *model.User, uid
} }
func (s *AddressBookService) CreateCollection(t *model.AddressBookCollection) error { func (s *AddressBookService) CreateCollection(t *model.AddressBookCollection) error {
return global.DB.Create(t).Error return DB.Create(t).Error
} }
func (s *AddressBookService) UpdateCollection(t *model.AddressBookCollection) error { func (s *AddressBookService) UpdateCollection(t *model.AddressBookCollection) error {
return global.DB.Model(t).Updates(t).Error return DB.Model(t).Updates(t).Error
} }
func (s *AddressBookService) DeleteCollection(t *model.AddressBookCollection) error { func (s *AddressBookService) DeleteCollection(t *model.AddressBookCollection) error {
//删除集合下的所有规则、地址簿,再删除集合 //删除集合下的所有规则、地址簿,再删除集合
tx := global.DB.Begin() tx := DB.Begin()
tx.Where("collection_id = ?", t.Id).Delete(&model.AddressBookCollectionRule{}) tx.Where("collection_id = ?", t.Id).Delete(&model.AddressBookCollectionRule{})
tx.Where("collection_id = ?", t.Id).Delete(&model.AddressBook{}) tx.Where("collection_id = ?", t.Id).Delete(&model.AddressBook{})
tx.Delete(t) tx.Delete(t)
@@ -290,23 +289,26 @@ func (s *AddressBookService) DeleteCollection(t *model.AddressBookCollection) er
func (s *AddressBookService) RuleInfoById(u uint) *model.AddressBookCollectionRule { func (s *AddressBookService) RuleInfoById(u uint) *model.AddressBookCollectionRule {
p := &model.AddressBookCollectionRule{} p := &model.AddressBookCollectionRule{}
global.DB.Where("id = ?", u).First(p) DB.Where("id = ?", u).First(p)
return p return p
} }
func (s *AddressBookService) RulePersonalInfoByToIdAndCid(toid, cid uint) *model.AddressBookCollectionRule { 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{} p := &model.AddressBookCollectionRule{}
global.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 return p
} }
func (s *AddressBookService) CreateRule(t *model.AddressBookCollectionRule) error { func (s *AddressBookService) CreateRule(t *model.AddressBookCollectionRule) error {
return global.DB.Create(t).Error return DB.Create(t).Error
} }
func (s *AddressBookService) ListRules(page uint, size uint, f func(tx *gorm.DB)) *model.AddressBookCollectionRuleList { func (s *AddressBookService) ListRules(page uint, size uint, f func(tx *gorm.DB)) *model.AddressBookCollectionRuleList {
res := &model.AddressBookCollectionRuleList{} res := &model.AddressBookCollectionRuleList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(size) res.PageSize = int64(size)
tx := global.DB.Model(&model.AddressBookCollectionRule{}) tx := DB.Model(&model.AddressBookCollectionRule{})
if f != nil { if f != nil {
f(tx) f(tx)
} }
@@ -317,11 +319,11 @@ func (s *AddressBookService) ListRules(page uint, size uint, f func(tx *gorm.DB)
} }
func (s *AddressBookService) UpdateRule(t *model.AddressBookCollectionRule) error { func (s *AddressBookService) UpdateRule(t *model.AddressBookCollectionRule) error {
return global.DB.Model(t).Updates(t).Error return DB.Model(t).Updates(t).Error
} }
func (s *AddressBookService) DeleteRule(t *model.AddressBookCollectionRule) error { func (s *AddressBookService) DeleteRule(t *model.AddressBookCollectionRule) error {
return global.DB.Delete(t).Error return DB.Delete(t).Error
} }
// CheckCollectionOwner 检查Collection的所有者 // CheckCollectionOwner 检查Collection的所有者
@@ -336,5 +338,5 @@ func (s *AddressBookService) BatchUpdateTags(abs []*model.AddressBook, tags []st
ids = append(ids, ab.RowId) ids = append(ids, ab.RowId)
} }
tagsv, _ := json.Marshal(tags) tagsv, _ := json.Marshal(tags)
return global.DB.Model(&model.AddressBook{}).Where("row_id in ?", ids).Update("tags", tagsv).Error return DB.Model(&model.AddressBook{}).Where("row_id in ?", ids).Update("tags", tagsv).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

@@ -1,7 +1,6 @@
package service package service
import ( import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -13,7 +12,7 @@ func (as *AuditService) AuditConnList(page, pageSize uint, where func(tx *gorm.D
res = &model.AuditConnList{} res = &model.AuditConnList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AuditConn{}) tx := DB.Model(&model.AuditConn{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -25,36 +24,36 @@ func (as *AuditService) AuditConnList(page, pageSize uint, where func(tx *gorm.D
// Create 创建 // Create 创建
func (as *AuditService) CreateAuditConn(u *model.AuditConn) error { func (as *AuditService) CreateAuditConn(u *model.AuditConn) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
func (as *AuditService) DeleteAuditConn(u *model.AuditConn) error { func (as *AuditService) DeleteAuditConn(u *model.AuditConn) error {
return global.DB.Delete(u).Error return DB.Delete(u).Error
} }
// Update 更新 // Update 更新
func (as *AuditService) UpdateAuditConn(u *model.AuditConn) error { func (as *AuditService) UpdateAuditConn(u *model.AuditConn) error {
return global.DB.Model(u).Updates(u).Error return DB.Model(u).Updates(u).Error
} }
// InfoByPeerIdAndConnId // InfoByPeerIdAndConnId
func (as *AuditService) InfoByPeerIdAndConnId(peerId string, connId int64) (res *model.AuditConn) { func (as *AuditService) InfoByPeerIdAndConnId(peerId string, connId int64) (res *model.AuditConn) {
res = &model.AuditConn{} res = &model.AuditConn{}
global.DB.Where("peer_id = ? and conn_id = ?", peerId, connId).First(res) DB.Where("peer_id = ? and conn_id = ?", peerId, connId).First(res)
return return
} }
// ConnInfoById // ConnInfoById
func (as *AuditService) ConnInfoById(id uint) (res *model.AuditConn) { func (as *AuditService) ConnInfoById(id uint) (res *model.AuditConn) {
res = &model.AuditConn{} res = &model.AuditConn{}
global.DB.Where("id = ?", id).First(res) DB.Where("id = ?", id).First(res)
return return
} }
// FileInfoById // FileInfoById
func (as *AuditService) FileInfoById(id uint) (res *model.AuditFile) { func (as *AuditService) FileInfoById(id uint) (res *model.AuditFile) {
res = &model.AuditFile{} res = &model.AuditFile{}
global.DB.Where("id = ?", id).First(res) DB.Where("id = ?", id).First(res)
return return
} }
@@ -62,7 +61,7 @@ func (as *AuditService) AuditFileList(page, pageSize uint, where func(tx *gorm.D
res = &model.AuditFileList{} res = &model.AuditFileList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AuditFile{}) tx := DB.Model(&model.AuditFile{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -74,22 +73,22 @@ func (as *AuditService) AuditFileList(page, pageSize uint, where func(tx *gorm.D
// CreateAuditFile // CreateAuditFile
func (as *AuditService) CreateAuditFile(u *model.AuditFile) error { func (as *AuditService) CreateAuditFile(u *model.AuditFile) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
func (as *AuditService) DeleteAuditFile(u *model.AuditFile) error { func (as *AuditService) DeleteAuditFile(u *model.AuditFile) error {
return global.DB.Delete(u).Error return DB.Delete(u).Error
} }
// Update 更新 // Update 更新
func (as *AuditService) UpdateAuditFile(u *model.AuditFile) error { func (as *AuditService) UpdateAuditFile(u *model.AuditFile) error {
return global.DB.Model(u).Updates(u).Error return DB.Model(u).Updates(u).Error
} }
func (as *AuditService) BatchDeleteAuditConn(ids []uint) error { func (as *AuditService) BatchDeleteAuditConn(ids []uint) error {
return global.DB.Where("id in (?)", ids).Delete(&model.AuditConn{}).Error return DB.Where("id in (?)", ids).Delete(&model.AuditConn{}).Error
} }
func (as *AuditService) BatchDeleteAuditFile(ids []uint) error { func (as *AuditService) BatchDeleteAuditFile(ids []uint) error {
return global.DB.Where("id in (?)", ids).Delete(&model.AuditFile{}).Error return DB.Where("id in (?)", ids).Delete(&model.AuditFile{}).Error
} }

View File

@@ -1,7 +1,6 @@
package service package service
import ( import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -12,7 +11,7 @@ type GroupService struct {
// InfoById 根据用户id取用户信息 // InfoById 根据用户id取用户信息
func (us *GroupService) InfoById(id uint) *model.Group { func (us *GroupService) InfoById(id uint) *model.Group {
u := &model.Group{} u := &model.Group{}
global.DB.Where("id = ?", id).First(u) DB.Where("id = ?", id).First(u)
return u return u
} }
@@ -20,7 +19,7 @@ func (us *GroupService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
res = &model.GroupList{} res = &model.GroupList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Group{}) tx := DB.Model(&model.Group{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -32,14 +31,47 @@ func (us *GroupService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
// Create 创建 // Create 创建
func (us *GroupService) Create(u *model.Group) error { func (us *GroupService) Create(u *model.Group) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
func (us *GroupService) Delete(u *model.Group) error { func (us *GroupService) Delete(u *model.Group) error {
return global.DB.Delete(u).Error return DB.Delete(u).Error
} }
// Update 更新 // Update 更新
func (us *GroupService) Update(u *model.Group) error { func (us *GroupService) Update(u *model.Group) error {
return global.DB.Model(u).Updates(u).Error return DB.Model(u).Updates(u).Error
}
// DeviceGroupInfoById 根据用户id取用户信息
func (us *GroupService) DeviceGroupInfoById(id uint) *model.DeviceGroup {
u := &model.DeviceGroup{}
DB.Where("id = ?", id).First(u)
return u
}
func (us *GroupService) DeviceGroupList(page, pageSize uint, where func(tx *gorm.DB)) (res *model.DeviceGroupList) {
res = &model.DeviceGroupList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := DB.Model(&model.DeviceGroup{})
if where != nil {
where(tx)
}
tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize))
tx.Find(&res.DeviceGroups)
return
}
func (us *GroupService) DeviceGroupCreate(u *model.DeviceGroup) error {
res := DB.Create(u).Error
return res
}
func (us *GroupService) DeviceGroupDelete(u *model.DeviceGroup) error {
return DB.Delete(u).Error
}
func (us *GroupService) DeviceGroupUpdate(u *model.DeviceGroup) error {
return DB.Model(u).Updates(u).Error
} }

View File

@@ -2,19 +2,23 @@ package service
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"net/url"
"os"
"strconv" "strconv"
"strings" "strings"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/lejianwen/rustdesk-api/v2/config" "github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
) )
var ( var (
ErrUrlParseFailed = errors.New("UrlParseFailed")
ErrFileReadFailed = errors.New("FileReadFailed")
ErrLdapNotEnabled = errors.New("LdapNotEnabled") ErrLdapNotEnabled = errors.New("LdapNotEnabled")
ErrLdapUserDisabled = errors.New("UserDisabledAtLdap") ErrLdapUserDisabled = errors.New("UserDisabledAtLdap")
ErrLdapUserNotFound = errors.New("UserNotFound") ErrLdapUserNotFound = errors.New("UserNotFound")
@@ -26,6 +30,7 @@ var (
ErrLdapBindFailed = errors.New("LdapBindFailed") ErrLdapBindFailed = errors.New("LdapBindFailed")
ErrLdapToLocalUserFailed = errors.New("LdapToLocalUserFailed") ErrLdapToLocalUserFailed = errors.New("LdapToLocalUserFailed")
ErrLdapCreateUserFailed = errors.New("LdapCreateUserFailed") ErrLdapCreateUserFailed = errors.New("LdapCreateUserFailed")
ErrLdapPasswordNotMatch = errors.New("PasswordNotMatch")
) )
// LdapService is responsible for LDAP authentication and user synchronization. // LdapService is responsible for LDAP authentication and user synchronization.
@@ -68,21 +73,38 @@ func (lu *LdapUser) ToUser(u *model.User) *model.User {
// connectAndBind creates an LDAP connection, optionally starts TLS, and then binds using the provided credentials. // connectAndBind creates an LDAP connection, optionally starts TLS, and then binds using the provided credentials.
func (ls *LdapService) connectAndBind(cfg *config.Ldap, username, password string) (*ldap.Conn, error) { func (ls *LdapService) connectAndBind(cfg *config.Ldap, username, password string) (*ldap.Conn, error) {
conn, err := ldap.DialURL(cfg.Url) u, err := url.Parse(cfg.Url)
if err != nil {
return nil, errors.Join(ErrUrlParseFailed, err)
}
var conn *ldap.Conn
if u.Scheme == "ldaps" {
// WARNING: InsecureSkipVerify: true is not recommended for production
tlsConfig := &tls.Config{InsecureSkipVerify: !cfg.TlsVerify}
if cfg.TlsCaFile != "" {
caCert, err := os.ReadFile(cfg.TlsCaFile)
if err != nil {
return nil, errors.Join(ErrFileReadFailed, err)
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
return nil, errors.Join(ErrLdapTlsFailed, errors.New("failed to append CA certificate"))
}
tlsConfig.RootCAs = caCertPool
}
conn, err = ldap.DialURL(cfg.Url, ldap.DialWithTLSConfig(tlsConfig))
} else {
conn, err = ldap.DialURL(cfg.Url)
}
if err != nil { if err != nil {
return nil, errors.Join(ErrLdapConnectFailed, err) return nil, errors.Join(ErrLdapConnectFailed, err)
} }
if cfg.TLS {
// WARNING: InsecureSkipVerify: true is not recommended for production
if err = conn.StartTLS(&tls.Config{InsecureSkipVerify: !cfg.TlsVerify}); err != nil {
conn.Close()
return nil, errors.Join(ErrLdapTlsFailed, err)
}
}
// Bind as the "service" user // Bind as the "service" user
if err = conn.Bind(username, password); err != nil { if err = conn.Bind(username, password); err != nil {
fmt.Println("Bind failed")
conn.Close() conn.Close()
return nil, errors.Join(ErrLdapBindService, err) return nil, errors.Join(ErrLdapBindService, err)
} }
@@ -98,7 +120,7 @@ func (ls *LdapService) connectAndBindAdmin(cfg *config.Ldap) (*ldap.Conn, error)
func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password string) error { func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password string) error {
ldapConn, err := ls.connectAndBind(cfg, username, password) ldapConn, err := ls.connectAndBind(cfg, username, password)
if err != nil { if err != nil {
return err return ErrLdapPasswordNotMatch
} }
defer ldapConn.Close() defer ldapConn.Close()
return nil return nil
@@ -114,7 +136,11 @@ func (ls *LdapService) Authenticate(username, password string) (*model.User, err
if !ldapUser.Enabled { if !ldapUser.Enabled {
return nil, ErrLdapUserDisabled return nil, ErrLdapUserDisabled
} }
cfg := &global.Config.Ldap cfg := &Config.Ldap
err = ls.verifyCredentials(cfg, ldapUser.Dn, password)
if err != nil {
return nil, err
}
user, err := ls.mapToLocalUser(cfg, ldapUser) user, err := ls.mapToLocalUser(cfg, ldapUser)
if err != nil { if err != nil {
return nil, errors.Join(ErrLdapToLocalUserFailed, err) return nil, errors.Join(ErrLdapToLocalUserFailed, err)
@@ -135,7 +161,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
// If needed, you can set a random password here. // If needed, you can set a random password here.
newUser.IsAdmin = &isAdmin newUser.IsAdmin = &isAdmin
newUser.GroupId = 1 newUser.GroupId = 1
if err := global.DB.Create(newUser).Error; err != nil { if err := DB.Create(newUser).Error; err != nil {
return nil, errors.Join(ErrLdapCreateUserFailed, err) return nil, errors.Join(ErrLdapCreateUserFailed, err)
} }
return userService.InfoByUsername(lu.Username), nil return userService.InfoByUsername(lu.Username), nil
@@ -164,7 +190,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
// IsUsernameExists checks if a username exists in LDAP (can be useful for local registration checks). // IsUsernameExists checks if a username exists in LDAP (can be useful for local registration checks).
func (ls *LdapService) IsUsernameExists(username string) bool { func (ls *LdapService) IsUsernameExists(username string) bool {
cfg := &global.Config.Ldap cfg := &Config.Ldap
if !cfg.Enable { if !cfg.Enable {
return false return false
} }
@@ -177,7 +203,7 @@ func (ls *LdapService) IsUsernameExists(username string) bool {
// IsEmailExists checks if an email exists in LDAP (can be useful for local registration checks). // IsEmailExists checks if an email exists in LDAP (can be useful for local registration checks).
func (ls *LdapService) IsEmailExists(email string) bool { func (ls *LdapService) IsEmailExists(email string) bool {
cfg := &global.Config.Ldap cfg := &Config.Ldap
if !cfg.Enable { if !cfg.Enable {
return false return false
} }
@@ -190,7 +216,7 @@ func (ls *LdapService) IsEmailExists(email string) bool {
// GetUserInfoByUsernameLdap returns the user info from LDAP for the given username. // GetUserInfoByUsernameLdap returns the user info from LDAP for the given username.
func (ls *LdapService) GetUserInfoByUsernameLdap(username string) (*LdapUser, error) { func (ls *LdapService) GetUserInfoByUsernameLdap(username string) (*LdapUser, error) {
cfg := &global.Config.Ldap cfg := &Config.Ldap
if !cfg.Enable { if !cfg.Enable {
return nil, ErrLdapNotEnabled return nil, ErrLdapNotEnabled
} }
@@ -210,12 +236,12 @@ func (ls *LdapService) GetUserInfoByUsernameLocal(username string) (*model.User,
if err != nil { if err != nil {
return &model.User{}, err return &model.User{}, err
} }
return ls.mapToLocalUser(&global.Config.Ldap, ldapUser) return ls.mapToLocalUser(&Config.Ldap, ldapUser)
} }
// GetUserInfoByEmailLdap returns the user info from LDAP for the given email. // GetUserInfoByEmailLdap returns the user info from LDAP for the given email.
func (ls *LdapService) GetUserInfoByEmailLdap(email string) (*LdapUser, error) { func (ls *LdapService) GetUserInfoByEmailLdap(email string) (*LdapUser, error) {
cfg := &global.Config.Ldap cfg := &Config.Ldap
if !cfg.Enable { if !cfg.Enable {
return nil, ErrLdapNotEnabled return nil, ErrLdapNotEnabled
} }
@@ -235,7 +261,7 @@ func (ls *LdapService) GetUserInfoByEmailLocal(email string) (*model.User, error
if err != nil { if err != nil {
return &model.User{}, err return &model.User{}, err
} }
return ls.mapToLocalUser(&global.Config.Ldap, ldapUser) return ls.mapToLocalUser(&Config.Ldap, ldapUser)
} }
// usernameSearchResult returns the search result for the given username. // usernameSearchResult returns the search result for the given username.
@@ -453,12 +479,12 @@ func (ls *LdapService) isUserEnabled(cfg *config.Ldap, ldapUser *LdapUser) bool
// Account is disabled if the ACCOUNTDISABLE flag (0x2) is set // Account is disabled if the ACCOUNTDISABLE flag (0x2) is set
const ACCOUNTDISABLE = 0x2 const ACCOUNTDISABLE = 0x2
ldapUser.Enabled = (userAccountControl&ACCOUNTDISABLE == 0) ldapUser.Enabled = userAccountControl&ACCOUNTDISABLE == 0
return ldapUser.Enabled return ldapUser.Enabled
} }
// For other attributes, perform a direct comparison with the expected value // For other attributes, perform a direct comparison with the expected value
ldapUser.Enabled = (ldapUser.EnableAttrValue == enableAttrValue) ldapUser.Enabled = ldapUser.EnableAttrValue == enableAttrValue
return ldapUser.Enabled return ldapUser.Enabled
} }

View File

@@ -1,7 +1,6 @@
package service package service
import ( import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -12,7 +11,7 @@ type LoginLogService struct {
// InfoById 根据用户id取用户信息 // InfoById 根据用户id取用户信息
func (us *LoginLogService) InfoById(id uint) *model.LoginLog { func (us *LoginLogService) InfoById(id uint) *model.LoginLog {
u := &model.LoginLog{} u := &model.LoginLog{}
global.DB.Where("id = ?", id).First(u) DB.Where("id = ?", id).First(u)
return u return u
} }
@@ -20,7 +19,7 @@ func (us *LoginLogService) List(page, pageSize uint, where func(tx *gorm.DB)) (r
res = &model.LoginLogList{} res = &model.LoginLogList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.LoginLog{}) tx := DB.Model(&model.LoginLog{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -32,20 +31,20 @@ func (us *LoginLogService) List(page, pageSize uint, where func(tx *gorm.DB)) (r
// Create 创建 // Create 创建
func (us *LoginLogService) Create(u *model.LoginLog) error { func (us *LoginLogService) Create(u *model.LoginLog) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
func (us *LoginLogService) Delete(u *model.LoginLog) error { func (us *LoginLogService) Delete(u *model.LoginLog) error {
return global.DB.Delete(u).Error return DB.Delete(u).Error
} }
// Update 更新 // Update 更新
func (us *LoginLogService) Update(u *model.LoginLog) error { func (us *LoginLogService) Update(u *model.LoginLog) error {
return global.DB.Model(u).Updates(u).Error return DB.Model(u).Updates(u).Error
} }
func (us *LoginLogService) BatchDelete(ids []uint) error { func (us *LoginLogService) BatchDelete(ids []uint) error {
return global.DB.Where("id in (?)", ids).Delete(&model.LoginLog{}).Error return DB.Where("id in (?)", ids).Delete(&model.LoginLog{}).Error
} }
func (us *LoginLogService) SoftDelete(l *model.LoginLog) error { func (us *LoginLogService) SoftDelete(l *model.LoginLog) error {
@@ -54,5 +53,5 @@ func (us *LoginLogService) SoftDelete(l *model.LoginLog) error {
} }
func (us *LoginLogService) BatchSoftDelete(uid uint, ids []uint) error { func (us *LoginLogService) BatchSoftDelete(uid uint, ids []uint) error {
return global.DB.Model(&model.LoginLog{}).Where("user_id = ? and id in (?)", uid, ids).Update("is_deleted", model.IsDeletedYes).Error return DB.Model(&model.LoginLog{}).Where("user_id = ? and id in (?)", uid, ids).Update("is_deleted", model.IsDeletedYes).Error
} }

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/lejianwen/rustdesk-api/v2/global" "github.com/coreos/go-oidc/v3/oidc"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/utils" "github.com/lejianwen/rustdesk-api/v2/utils"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@@ -45,6 +45,8 @@ type OauthCacheItem struct {
Username string `json:"username"` Username string `json:"username"`
Name string `json:"name"` Name string `json:"name"`
Email string `json:"email"` Email string `json:"email"`
Verifier string `json:"verifier"` // used for oauth pkce
Nonce string `json:"nonce"`
} }
func (oci *OauthCacheItem) ToOauthUser() *model.OauthUser { func (oci *OauthCacheItem) ToOauthUser() *model.OauthUser {
@@ -81,10 +83,9 @@ func (os *OauthService) GetOauthCache(key string) *OauthCacheItem {
func (os *OauthService) SetOauthCache(key string, item *OauthCacheItem, expire uint) { func (os *OauthService) SetOauthCache(key string, item *OauthCacheItem, expire uint) {
OauthCache.Store(key, item) OauthCache.Store(key, item)
if expire > 0 { if expire > 0 {
go func() { time.AfterFunc(time.Duration(expire)*time.Second, func() {
time.Sleep(time.Duration(expire) * time.Second)
os.DeleteOauthCache(key) os.DeleteOauthCache(key)
}() })
} }
} }
@@ -92,151 +93,192 @@ func (os *OauthService) DeleteOauthCache(key string) {
OauthCache.Delete(key) OauthCache.Delete(key)
} }
func (os *OauthService) BeginAuth(op string) (error error, code, url string) { func (os *OauthService) BeginAuth(op string) (error error, state, verifier, nonce, url string) {
code = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10) state = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10)
if op == string(model.OauthTypeWebauth) { verifier = ""
url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + code nonce = ""
if op == model.OauthTypeWebauth {
url = Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + state
//url = "http://localhost:8888/_admin/#/oauth/" + code //url = "http://localhost:8888/_admin/#/oauth/" + code
return nil, code, url return nil, state, verifier, nonce, url
} }
err, _, oauthConfig := os.GetOauthConfig(op) err, oauthInfo, oauthConfig, _ := os.GetOauthConfig(op)
if err == nil { if err == nil {
return err, code, oauthConfig.AuthCodeURL(code) extras := make([]oauth2.AuthCodeOption, 0, 3)
nonce = utils.RandomString(10)
extras = append(extras, oauth2.SetAuthURLParam("nonce", nonce))
if oauthInfo.PkceEnable != nil && *oauthInfo.PkceEnable {
extras = append(extras, oauth2.AccessTypeOffline)
verifier = oauth2.GenerateVerifier()
switch oauthInfo.PkceMethod {
case model.PKCEMethodS256:
extras = append(extras, oauth2.S256ChallengeOption(verifier))
case model.PKCEMethodPlain:
// oauth2 does not have a plain challenge option, so we add it manually
extras = append(extras, oauth2.SetAuthURLParam("code_challenge_method", "plain"), oauth2.SetAuthURLParam("code_challenge", verifier))
}
}
return err, state, verifier, nonce, oauthConfig.AuthCodeURL(state, extras...)
} }
return err, code, "" return err, state, verifier, nonce, ""
} }
// Method to fetch OIDC configuration dynamically func (os *OauthService) FetchOidcProvider(issuer string) (error, *oidc.Provider) {
func (os *OauthService) FetchOidcEndpoint(issuer string) (error, OidcEndpoint) {
configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
// Get the HTTP client (with or without proxy based on configuration) // Get the HTTP client (with or without proxy based on configuration)
client := getHTTPClientWithProxy() client := getHTTPClientWithProxy()
resp, err := client.Get(configURL) ctx := oidc.ClientContext(context.Background(), client)
provider, err := oidc.NewProvider(ctx, issuer)
if err != nil { if err != nil {
return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{} return err, nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{}
} }
var endpoint OidcEndpoint return nil, provider
if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil {
return errors.New("failed to parse OIDC configuration"), OidcEndpoint{}
}
return nil, endpoint
} }
func (os *OauthService) FetchOidcEndpointByOp(op string) (error, OidcEndpoint) { func (os *OauthService) GithubProvider() *oidc.Provider {
oauthInfo := os.InfoByOp(op) return (&oidc.ProviderConfig{
if oauthInfo.Issuer == "" { IssuerURL: "",
return errors.New("issuer is empty"), OidcEndpoint{} AuthURL: github.Endpoint.AuthURL,
} TokenURL: github.Endpoint.TokenURL,
return os.FetchOidcEndpoint(oauthInfo.Issuer) DeviceAuthURL: github.Endpoint.DeviceAuthURL,
UserInfoURL: model.UserEndpointGithub,
JWKSURL: "",
Algorithms: nil,
}).NewProvider(context.Background())
} }
// GetOauthConfig retrieves the OAuth2 configuration based on the provider name // GetOauthConfig retrieves the OAuth2 configuration based on the provider name
func (os *OauthService) GetOauthConfig(op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config) { func (os *OauthService) GetOauthConfig(op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config, provider *oidc.Provider) {
err, oauthInfo, oauthConfig = os.getOauthConfigGeneral(op) //err, oauthInfo, oauthConfig = os.getOauthConfigGeneral(op)
if err != nil { oauthInfo = os.InfoByOp(op)
return err, nil, nil if oauthInfo.Id == 0 || oauthInfo.ClientId == "" || oauthInfo.ClientSecret == "" {
return errors.New("ConfigNotFound"), nil, nil, nil
} }
// If the redirect URL is empty, use the default redirect URL
if oauthInfo.RedirectUrl == "" {
oauthInfo.RedirectUrl = Config.Rustdesk.ApiServer + "/api/oidc/callback"
}
oauthConfig = &oauth2.Config{
ClientID: oauthInfo.ClientId,
ClientSecret: oauthInfo.ClientSecret,
RedirectURL: oauthInfo.RedirectUrl,
}
// Maybe should validate the oauthConfig here // Maybe should validate the oauthConfig here
oauthType := oauthInfo.OauthType oauthType := oauthInfo.OauthType
err = model.ValidateOauthType(oauthType) err = model.ValidateOauthType(oauthType)
if err != nil { if err != nil {
return err, nil, nil return err, nil, nil, nil
} }
switch oauthType { switch oauthType {
case model.OauthTypeGithub: case model.OauthTypeGithub:
oauthConfig.Endpoint = github.Endpoint oauthConfig.Endpoint = github.Endpoint
oauthConfig.Scopes = []string{"read:user", "user:email"} oauthConfig.Scopes = []string{"read:user", "user:email"}
provider = os.GithubProvider()
//case model.OauthTypeGoogle: //google单独出来可以少一次FetchOidcEndpoint请求
// oauthConfig.Endpoint = google.Endpoint
// oauthConfig.Scopes = os.constructScopes(oauthInfo.Scopes)
case model.OauthTypeOidc, model.OauthTypeGoogle: case model.OauthTypeOidc, model.OauthTypeGoogle:
var endpoint OidcEndpoint err, provider = os.FetchOidcProvider(oauthInfo.Issuer)
err, endpoint = os.FetchOidcEndpoint(oauthInfo.Issuer)
if err != nil { if err != nil {
return err, nil, nil return err, nil, nil, nil
} }
oauthConfig.Endpoint = oauth2.Endpoint{AuthURL: endpoint.AuthURL, TokenURL: endpoint.TokenURL} oauthConfig.Endpoint = provider.Endpoint()
oauthConfig.Scopes = os.constructScopes(oauthInfo.Scopes) oauthConfig.Scopes = os.constructScopes(oauthInfo.Scopes)
default: default:
return errors.New("unsupported OAuth type"), nil, nil return errors.New("unsupported OAuth type"), nil, nil, nil
}
return nil, oauthInfo, oauthConfig
}
// GetOauthConfig retrieves the OAuth2 configuration based on the provider name
func (os *OauthService) getOauthConfigGeneral(op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config) {
oauthInfo = os.InfoByOp(op)
if oauthInfo.Id == 0 || oauthInfo.ClientId == "" || oauthInfo.ClientSecret == "" {
return errors.New("ConfigNotFound"), nil, nil
}
// If the redirect URL is empty, use the default redirect URL
if oauthInfo.RedirectUrl == "" {
oauthInfo.RedirectUrl = global.Config.Rustdesk.ApiServer + "/api/oidc/callback"
}
return nil, oauthInfo, &oauth2.Config{
ClientID: oauthInfo.ClientId,
ClientSecret: oauthInfo.ClientSecret,
RedirectURL: oauthInfo.RedirectUrl,
} }
return nil, oauthInfo, oauthConfig, provider
} }
func getHTTPClientWithProxy() *http.Client { func getHTTPClientWithProxy() *http.Client {
//todo add timeout //add timeout 30s
if global.Config.Proxy.Enable { timeout := time.Duration(60) * time.Second
if global.Config.Proxy.Host == "" { if Config.Proxy.Enable {
global.Logger.Warn("Proxy is enabled but proxy host is empty.") if Config.Proxy.Host == "" {
Logger.Warn("Proxy is enabled but proxy host is empty.")
return http.DefaultClient return http.DefaultClient
} }
proxyURL, err := url.Parse(global.Config.Proxy.Host) proxyURL, err := url.Parse(Config.Proxy.Host)
if err != nil { if err != nil {
global.Logger.Warn("Invalid proxy URL: ", err) Logger.Warn("Invalid proxy URL: ", err)
return http.DefaultClient return http.DefaultClient
} }
transport := &http.Transport{ transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL), Proxy: http.ProxyURL(proxyURL),
} }
return &http.Client{Transport: transport} return &http.Client{Transport: transport, Timeout: timeout}
} }
return http.DefaultClient return http.DefaultClient
} }
func (os *OauthService) callbackBase(oauthConfig *oauth2.Config, provider *oidc.Provider, code string, verifier string, nonce string, userData interface{}) (err error, client *http.Client) {
func (os *OauthService) callbackBase(oauthConfig *oauth2.Config, code string, userEndpoint string, userData interface{}) (err error, client *http.Client) {
// 设置代理客户端 // 设置代理客户端
httpClient := getHTTPClientWithProxy() httpClient := getHTTPClientWithProxy()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient) ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
// 使用 code 换取 token exchangeOpts := make([]oauth2.AuthCodeOption, 0, 1)
var token *oauth2.Token if verifier != "" {
token, err = oauthConfig.Exchange(ctx, code) exchangeOpts = append(exchangeOpts, oauth2.VerifierOption(verifier))
}
token, err := oauthConfig.Exchange(ctx, code, exchangeOpts...)
if err != nil { if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err) Logger.Warn("oauthConfig.Exchange() failed: ", err)
return errors.New("GetOauthTokenError"), nil return errors.New("GetOauthTokenError"), nil
} }
// 获取 ID Token github没有id_token
rawIDToken, ok := token.Extra("id_token").(string)
if ok && rawIDToken != "" {
// 验证 ID Token
v := provider.Verifier(&oidc.Config{ClientID: oauthConfig.ClientID})
idToken, err2 := v.Verify(ctx, rawIDToken)
if err2 != nil {
Logger.Warn("IdTokenVerifyError: ", err2)
return errors.New("IdTokenVerifyError"), nil
}
if nonce != "" {
// 验证 nonce
var claims struct {
Nonce string `json:"nonce"`
}
if err2 = idToken.Claims(&claims); err2 != nil {
Logger.Warn("Failed to parse ID Token claims: ", err)
return errors.New("IDTokenClaimsError"), nil
}
if claims.Nonce != nonce {
Logger.Warn("Nonce does not match")
return errors.New("NonceDoesNotMatch"), nil
}
}
}
// 获取用户信息 // 获取用户信息
client = oauthConfig.Client(ctx, token) client = oauthConfig.Client(ctx, token)
resp, err := client.Get(userEndpoint) resp, err := client.Get(provider.UserInfoEndpoint())
if err != nil { if err != nil {
global.Logger.Warn("failed getting user info: ", err) Logger.Warn("failed getting user info: ", err)
return errors.New("GetOauthUserInfoError"), nil return errors.New("GetOauthUserInfoError"), nil
} }
defer func() { defer func() {
if closeErr := resp.Body.Close(); closeErr != nil { if closeErr := resp.Body.Close(); closeErr != nil {
global.Logger.Warn("failed closing response body: ", closeErr) Logger.Warn("failed closing response body: ", closeErr)
} }
}() }()
// 解析用户信息 // 解析用户信息
if err = json.NewDecoder(resp.Body).Decode(userData); err != nil { if err = json.NewDecoder(resp.Body).Decode(userData); err != nil {
global.Logger.Warn("failed decoding user info: ", err) Logger.Warn("failed decoding user info: ", err)
return errors.New("DecodeOauthUserInfoError"), nil return errors.New("DecodeOauthUserInfoError"), nil
} }
@@ -244,9 +286,9 @@ func (os *OauthService) callbackBase(oauthConfig *oauth2.Config, code string, us
} }
// githubCallback github回调 // githubCallback github回调
func (os *OauthService) githubCallback(oauthConfig *oauth2.Config, code string) (error, *model.OauthUser) { func (os *OauthService) githubCallback(oauthConfig *oauth2.Config, provider *oidc.Provider, code, verifier, nonce string) (error, *model.OauthUser) {
var user = &model.GithubUser{} var user = &model.GithubUser{}
err, client := os.callbackBase(oauthConfig, code, model.UserEndpointGithub, user) err, client := os.callbackBase(oauthConfig, provider, code, verifier, nonce, user)
if err != nil { if err != nil {
return err, nil return err, nil
} }
@@ -258,19 +300,17 @@ func (os *OauthService) githubCallback(oauthConfig *oauth2.Config, code string)
} }
// oidcCallback oidc回调, 通过code获取用户信息 // oidcCallback oidc回调, 通过code获取用户信息
func (os *OauthService) oidcCallback(oauthConfig *oauth2.Config, code string, userInfoEndpoint string) (error, *model.OauthUser) { func (os *OauthService) oidcCallback(oauthConfig *oauth2.Config, provider *oidc.Provider, code, verifier, nonce string) (error, *model.OauthUser) {
var user = &model.OidcUser{} var user = &model.OidcUser{}
if err, _ := os.callbackBase(oauthConfig, code, userInfoEndpoint, user); err != nil { if err, _ := os.callbackBase(oauthConfig, provider, code, verifier, nonce, user); err != nil {
return err, nil return err, nil
} }
return nil, user.ToOauthUser() return nil, user.ToOauthUser()
} }
// Callback: Get user information by code and op(Oauth provider) // Callback: Get user information by code and op(Oauth provider)
func (os *OauthService) Callback(code string, op string) (err error, oauthUser *model.OauthUser) { func (os *OauthService) Callback(code, verifier, op, nonce string) (err error, oauthUser *model.OauthUser) {
var oauthInfo *model.Oauth err, oauthInfo, oauthConfig, provider := os.GetOauthConfig(op)
var oauthConfig *oauth2.Config
err, oauthInfo, oauthConfig = os.GetOauthConfig(op)
// oauthType is already validated in GetOauthConfig // oauthType is already validated in GetOauthConfig
if err != nil { if err != nil {
return err, nil return err, nil
@@ -278,13 +318,9 @@ func (os *OauthService) Callback(code string, op string) (err error, oauthUser *
oauthType := oauthInfo.OauthType oauthType := oauthInfo.OauthType
switch oauthType { switch oauthType {
case model.OauthTypeGithub: case model.OauthTypeGithub:
err, oauthUser = os.githubCallback(oauthConfig, code) err, oauthUser = os.githubCallback(oauthConfig, provider, code, verifier, nonce)
case model.OauthTypeOidc, model.OauthTypeGoogle: case model.OauthTypeOidc, model.OauthTypeGoogle:
err, endpoint := os.FetchOidcEndpoint(oauthInfo.Issuer) err, oauthUser = os.oidcCallback(oauthConfig, provider, code, verifier, nonce)
if err != nil {
return err, nil
}
err, oauthUser = os.oidcCallback(oauthConfig, code, endpoint.UserInfo)
default: default:
return errors.New("unsupported OAuth type"), nil return errors.New("unsupported OAuth type"), nil
} }
@@ -293,7 +329,7 @@ func (os *OauthService) Callback(code string, op string) (err error, oauthUser *
func (os *OauthService) UserThirdInfo(op string, openId string) *model.UserThird { func (os *OauthService) UserThirdInfo(op string, openId string) *model.UserThird {
ut := &model.UserThird{} ut := &model.UserThird{}
global.DB.Where("open_id = ? and op = ?", openId, op).First(ut) DB.Where("open_id = ? and op = ?", openId, op).First(ut)
return ut return ut
} }
@@ -305,7 +341,7 @@ func (os *OauthService) BindOauthUser(userId uint, oauthUser *model.OauthUser, o
return err return err
} }
utr.FromOauthUser(userId, oauthUser, oauthType, op) utr.FromOauthUser(userId, oauthUser, oauthType, op)
return global.DB.Create(utr).Error return DB.Create(utr).Error
} }
// UnBindOauthUser: Unbind third party account // UnBindOauthUser: Unbind third party account
@@ -315,25 +351,25 @@ func (os *OauthService) UnBindOauthUser(userId uint, op string) error {
// UnBindThird: Unbind third party account // UnBindThird: Unbind third party account
func (os *OauthService) UnBindThird(op string, userId uint) error { func (os *OauthService) UnBindThird(op string, userId uint) error {
return global.DB.Where("user_id = ? and op = ?", userId, op).Delete(&model.UserThird{}).Error return DB.Where("user_id = ? and op = ?", userId, op).Delete(&model.UserThird{}).Error
} }
// DeleteUserByUserId: When user is deleted, delete all third party bindings // DeleteUserByUserId: When user is deleted, delete all third party bindings
func (os *OauthService) DeleteUserByUserId(userId uint) error { func (os *OauthService) DeleteUserByUserId(userId uint) error {
return global.DB.Where("user_id = ?", userId).Delete(&model.UserThird{}).Error return DB.Where("user_id = ?", userId).Delete(&model.UserThird{}).Error
} }
// InfoById 根据id获取Oauth信息 // InfoById 根据id获取Oauth信息
func (os *OauthService) InfoById(id uint) *model.Oauth { func (os *OauthService) InfoById(id uint) *model.Oauth {
oauthInfo := &model.Oauth{} oauthInfo := &model.Oauth{}
global.DB.Where("id = ?", id).First(oauthInfo) DB.Where("id = ?", id).First(oauthInfo)
return oauthInfo return oauthInfo
} }
// InfoByOp 根据op获取Oauth信息 // InfoByOp 根据op获取Oauth信息
func (os *OauthService) InfoByOp(op string) *model.Oauth { func (os *OauthService) InfoByOp(op string) *model.Oauth {
oauthInfo := &model.Oauth{} oauthInfo := &model.Oauth{}
global.DB.Where("op = ?", op).First(oauthInfo) DB.Where("op = ?", op).First(oauthInfo)
return oauthInfo return oauthInfo
} }
@@ -356,7 +392,7 @@ func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
res = &model.OauthList{} res = &model.OauthList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Oauth{}) tx := DB.Model(&model.Oauth{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -369,7 +405,7 @@ func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
// GetTypeByOp 根据op获取OauthType // GetTypeByOp 根据op获取OauthType
func (os *OauthService) GetTypeByOp(op string) (error, string) { func (os *OauthService) GetTypeByOp(op string) (error, string) {
oauthInfo := &model.Oauth{} oauthInfo := &model.Oauth{}
if global.DB.Where("op = ?", op).First(oauthInfo).Error != nil { if DB.Where("op = ?", op).First(oauthInfo).Error != nil {
return fmt.Errorf("OAuth provider with op '%s' not found", op), "" return fmt.Errorf("OAuth provider with op '%s' not found", op), ""
} }
return nil, oauthInfo.OauthType return nil, oauthInfo.OauthType
@@ -387,7 +423,7 @@ func (os *OauthService) ValidateOauthProvider(op string) error {
func (os *OauthService) IsOauthProviderExist(op string) bool { func (os *OauthService) IsOauthProviderExist(op string) bool {
oauthInfo := &model.Oauth{} oauthInfo := &model.Oauth{}
// 使用 Gorm 的 Take 方法查找符合条件的记录 // 使用 Gorm 的 Take 方法查找符合条件的记录
if err := global.DB.Where("op = ?", op).Take(oauthInfo).Error; err != nil { if err := DB.Where("op = ?", op).Take(oauthInfo).Error; err != nil {
return false return false
} }
return true return true
@@ -399,11 +435,11 @@ func (os *OauthService) Create(oauthInfo *model.Oauth) error {
if err != nil { if err != nil {
return err return err
} }
res := global.DB.Create(oauthInfo).Error res := DB.Create(oauthInfo).Error
return res return res
} }
func (os *OauthService) Delete(oauthInfo *model.Oauth) error { func (os *OauthService) Delete(oauthInfo *model.Oauth) error {
return global.DB.Delete(oauthInfo).Error return DB.Delete(oauthInfo).Error
} }
// Update 更新 // Update 更新
@@ -412,13 +448,13 @@ func (os *OauthService) Update(oauthInfo *model.Oauth) error {
if err != nil { if err != nil {
return err return err
} }
return global.DB.Model(oauthInfo).Updates(oauthInfo).Error return DB.Model(oauthInfo).Updates(oauthInfo).Error
} }
// GetOauthProviders 获取所有的provider // GetOauthProviders 获取所有的provider
func (os *OauthService) GetOauthProviders() []string { func (os *OauthService) GetOauthProviders() []string {
var res []string var res []string
global.DB.Model(&model.Oauth{}).Pluck("op", &res) DB.Model(&model.Oauth{}).Pluck("op", &res)
return res return res
} }

View File

@@ -1,7 +1,6 @@
package service package service
import ( import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -12,24 +11,24 @@ type PeerService struct {
// FindById 根据id查找 // FindById 根据id查找
func (ps *PeerService) FindById(id string) *model.Peer { func (ps *PeerService) FindById(id string) *model.Peer {
p := &model.Peer{} p := &model.Peer{}
global.DB.Where("id = ?", id).First(p) DB.Where("id = ?", id).First(p)
return p return p
} }
func (ps *PeerService) FindByUuid(uuid string) *model.Peer { func (ps *PeerService) FindByUuid(uuid string) *model.Peer {
p := &model.Peer{} p := &model.Peer{}
global.DB.Where("uuid = ?", uuid).First(p) DB.Where("uuid = ?", uuid).First(p)
return p return p
} }
func (ps *PeerService) InfoByRowId(id uint) *model.Peer { func (ps *PeerService) InfoByRowId(id uint) *model.Peer {
p := &model.Peer{} p := &model.Peer{}
global.DB.Where("row_id = ?", id).First(p) DB.Where("row_id = ?", id).First(p)
return p return p
} }
// FindByUserIdAndUuid 根据用户id和uuid查找peer // FindByUserIdAndUuid 根据用户id和uuid查找peer
func (ps *PeerService) FindByUserIdAndUuid(uuid string, userId uint) *model.Peer { func (ps *PeerService) FindByUserIdAndUuid(uuid string, userId uint) *model.Peer {
p := &model.Peer{} p := &model.Peer{}
global.DB.Where("uuid = ? and user_id = ?", uuid, userId).First(p) DB.Where("uuid = ? and user_id = ?", uuid, userId).First(p)
return p return p
} }
@@ -43,7 +42,7 @@ func (ps *PeerService) UuidBindUserId(deviceId string, uuid string, userId uint)
} else { } else {
// 不存在则创建 // 不存在则创建
/*if deviceId != "" { /*if deviceId != "" {
global.DB.Create(&model.Peer{ DB.Create(&model.Peer{
Id: deviceId, Id: deviceId,
Uuid: uuid, Uuid: uuid,
UserId: userId, UserId: userId,
@@ -56,13 +55,13 @@ func (ps *PeerService) UuidBindUserId(deviceId string, uuid string, userId uint)
func (ps *PeerService) UuidUnbindUserId(uuid string, userId uint) { func (ps *PeerService) UuidUnbindUserId(uuid string, userId uint) {
peer := ps.FindByUserIdAndUuid(uuid, userId) peer := ps.FindByUserIdAndUuid(uuid, userId)
if peer.RowId > 0 { if peer.RowId > 0 {
global.DB.Model(peer).Update("user_id", 0) DB.Model(peer).Update("user_id", 0)
} }
} }
// EraseUserId 清除用户id, 用于用户删除 // EraseUserId 清除用户id, 用于用户删除
func (ps *PeerService) EraseUserId(userId uint) error { func (ps *PeerService) EraseUserId(userId uint) error {
return global.DB.Model(&model.Peer{}).Where("user_id = ?", userId).Update("user_id", 0).Error return DB.Model(&model.Peer{}).Where("user_id = ?", userId).Update("user_id", 0).Error
} }
// ListByUserIds 根据用户id取列表 // ListByUserIds 根据用户id取列表
@@ -70,7 +69,7 @@ func (ps *PeerService) ListByUserIds(userIds []uint, page, pageSize uint) (res *
res = &model.PeerList{} res = &model.PeerList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Peer{}) tx := DB.Model(&model.Peer{})
tx.Where("user_id in (?)", userIds) tx.Where("user_id in (?)", userIds)
tx.Count(&res.Total) tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize)) tx.Scopes(Paginate(page, pageSize))
@@ -82,7 +81,7 @@ func (ps *PeerService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *
res = &model.PeerList{} res = &model.PeerList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Peer{}) tx := DB.Model(&model.Peer{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -106,14 +105,14 @@ func (ps *PeerService) ListFilterByUserId(page, pageSize uint, where func(tx *go
// Create 创建 // Create 创建
func (ps *PeerService) Create(u *model.Peer) error { func (ps *PeerService) Create(u *model.Peer) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
// Delete 删除, 同时也应该删除token // Delete 删除, 同时也应该删除token
func (ps *PeerService) Delete(u *model.Peer) error { func (ps *PeerService) Delete(u *model.Peer) error {
uuid := u.Uuid uuid := u.Uuid
err := global.DB.Delete(u).Error err := DB.Delete(u).Error
if err != nil { if err != nil {
return err return err
} }
@@ -124,16 +123,23 @@ func (ps *PeerService) Delete(u *model.Peer) error {
// GetUuidListByIDs 根据ids获取uuid列表 // GetUuidListByIDs 根据ids获取uuid列表
func (ps *PeerService) GetUuidListByIDs(ids []uint) ([]string, error) { func (ps *PeerService) GetUuidListByIDs(ids []uint) ([]string, error) {
var uuids []string var uuids []string
err := global.DB.Model(&model.Peer{}). err := DB.Model(&model.Peer{}).
Where("row_id in (?)", ids). Where("row_id in (?)", ids).
Pluck("uuid", &uuids).Error 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 // BatchDelete 批量删除, 同时也应该删除token
func (ps *PeerService) BatchDelete(ids []uint) error { func (ps *PeerService) BatchDelete(ids []uint) error {
uuids, err := ps.GetUuidListByIDs(ids) uuids, err := ps.GetUuidListByIDs(ids)
err = global.DB.Where("row_id in (?)", ids).Delete(&model.Peer{}).Error err = DB.Where("row_id in (?)", ids).Delete(&model.Peer{}).Error
if err != nil { if err != nil {
return err return err
} }
@@ -143,5 +149,5 @@ func (ps *PeerService) BatchDelete(ids []uint) error {
// Update 更新 // Update 更新
func (ps *PeerService) Update(u *model.Peer) error { func (ps *PeerService) Update(u *model.Peer) error {
return global.DB.Model(u).Updates(u).Error return DB.Model(u).Updates(u).Error
} }

View File

@@ -2,7 +2,6 @@ package service
import ( import (
"fmt" "fmt"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"net" "net"
"time" "time"
@@ -15,7 +14,7 @@ func (is *ServerCmdService) List(page, pageSize uint) (res *model.ServerCmdList)
res = &model.ServerCmdList{} res = &model.ServerCmdList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.ServerCmd{}) tx := DB.Model(&model.ServerCmd{})
tx.Count(&res.Total) tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize)) tx.Scopes(Paginate(page, pageSize))
tx.Find(&res.ServerCmds) tx.Find(&res.ServerCmds)
@@ -25,18 +24,18 @@ func (is *ServerCmdService) List(page, pageSize uint) (res *model.ServerCmdList)
// Info // Info
func (is *ServerCmdService) Info(id uint) *model.ServerCmd { func (is *ServerCmdService) Info(id uint) *model.ServerCmd {
u := &model.ServerCmd{} u := &model.ServerCmd{}
global.DB.Where("id = ?", id).First(u) DB.Where("id = ?", id).First(u)
return u return u
} }
// Delete // Delete
func (is *ServerCmdService) Delete(u *model.ServerCmd) error { func (is *ServerCmdService) Delete(u *model.ServerCmd) error {
return global.DB.Delete(u).Error return DB.Delete(u).Error
} }
// Create // Create
func (is *ServerCmdService) Create(u *model.ServerCmd) error { func (is *ServerCmdService) Create(u *model.ServerCmd) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
@@ -45,9 +44,9 @@ func (is *ServerCmdService) SendCmd(target string, cmd string, arg string) (stri
port := 0 port := 0
switch target { switch target {
case model.ServerCmdTargetIdServer: case model.ServerCmdTargetIdServer:
port = global.Config.Rustdesk.IdServerPort - 1 port = Config.Rustdesk.IdServerPort - 1
case model.ServerCmdTargetRelayServer: case model.ServerCmdTargetRelayServer:
port = global.Config.Rustdesk.RelayServerPort port = Config.Rustdesk.RelayServerPort
} }
//组装命令 //组装命令
cmd = cmd + " " + arg cmd = cmd + " " + arg
@@ -73,14 +72,14 @@ func (is *ServerCmdService) SendSocketCmd(ty string, port int, cmd string) (stri
} }
conn, err := net.Dial(tcp, fmt.Sprintf("%s:%v", addr, port)) conn, err := net.Dial(tcp, fmt.Sprintf("%s:%v", addr, port))
if err != nil { if err != nil {
global.Logger.Debugf("%s connect to id server failed: %v", ty, err) Logger.Debugf("%s connect to id server failed: %v", ty, err)
return "", err return "", err
} }
defer conn.Close() defer conn.Close()
//发送命令 //发送命令
_, err = conn.Write([]byte(cmd)) _, err = conn.Write([]byte(cmd))
if err != nil { if err != nil {
global.Logger.Debugf("%s send cmd failed: %v", ty, err) Logger.Debugf("%s send cmd failed: %v", ty, err)
return "", err return "", err
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@@ -88,12 +87,12 @@ func (is *ServerCmdService) SendSocketCmd(ty string, port int, cmd string) (stri
buf := make([]byte, 1024) buf := make([]byte, 1024)
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil && err.Error() != "EOF" { if err != nil && err.Error() != "EOF" {
global.Logger.Debugf("%s read response failed: %v", ty, err) Logger.Debugf("%s read response failed: %v", ty, err)
return "", err return "", err
} }
return string(buf[:n]), nil return string(buf[:n]), nil
} }
func (is *ServerCmdService) Update(f *model.ServerCmd) error { func (is *ServerCmdService) Update(f *model.ServerCmd) error {
return global.DB.Model(f).Updates(f).Error return DB.Model(f).Updates(f).Error
} }

View File

@@ -1,7 +1,11 @@
package service package service
import ( import (
"github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/lib/jwt"
"github.com/lejianwen/rustdesk-api/v2/lib/lock"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -19,14 +23,34 @@ type Service struct {
*ShareRecordService *ShareRecordService
*ServerCmdService *ServerCmdService
*LdapService *LdapService
*AppService
} }
func New() *Service { type Dependencies struct {
all := new(Service) Config *config.Config
return all DB *gorm.DB
Logger *log.Logger
Jwt *jwt.Jwt
Lock *lock.Locker
} }
var AllService = New() var Config *config.Config
var DB *gorm.DB
var Logger *log.Logger
var Jwt *jwt.Jwt
var Lock lock.Locker
var AllService *Service
func New(c *config.Config, g *gorm.DB, l *log.Logger, j *jwt.Jwt, lo lock.Locker) *Service {
Config = c
DB = g
Logger = l
Jwt = j
Lock = lo
AllService = new(Service)
return AllService
}
func Paginate(page, pageSize uint) func(db *gorm.DB) *gorm.DB { func Paginate(page, pageSize uint) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB {

View File

@@ -1,7 +1,6 @@
package service package service
import ( import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -12,7 +11,7 @@ type ShareRecordService struct {
// InfoById 根据用户id取用户信息 // InfoById 根据用户id取用户信息
func (srs *ShareRecordService) InfoById(id uint) *model.ShareRecord { func (srs *ShareRecordService) InfoById(id uint) *model.ShareRecord {
u := &model.ShareRecord{} u := &model.ShareRecord{}
global.DB.Where("id = ?", id).First(u) DB.Where("id = ?", id).First(u)
return u return u
} }
@@ -20,7 +19,7 @@ func (srs *ShareRecordService) List(page, pageSize uint, where func(tx *gorm.DB)
res = &model.ShareRecordList{} res = &model.ShareRecordList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.ShareRecord{}) tx := DB.Model(&model.ShareRecord{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -32,18 +31,18 @@ func (srs *ShareRecordService) List(page, pageSize uint, where func(tx *gorm.DB)
// Create 创建 // Create 创建
func (srs *ShareRecordService) Create(u *model.ShareRecord) error { func (srs *ShareRecordService) Create(u *model.ShareRecord) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
func (srs *ShareRecordService) Delete(u *model.ShareRecord) error { func (srs *ShareRecordService) Delete(u *model.ShareRecord) error {
return global.DB.Delete(u).Error return DB.Delete(u).Error
} }
// Update 更新 // Update 更新
func (srs *ShareRecordService) Update(u *model.ShareRecord) error { func (srs *ShareRecordService) Update(u *model.ShareRecord) error {
return global.DB.Model(u).Updates(u).Error return DB.Model(u).Updates(u).Error
} }
func (srs *ShareRecordService) BatchDelete(ids []uint) error { func (srs *ShareRecordService) BatchDelete(ids []uint) error {
return global.DB.Where("id in (?)", ids).Delete(&model.ShareRecord{}).Error return DB.Where("id in (?)", ids).Delete(&model.ShareRecord{}).Error
} }

View File

@@ -1,7 +1,6 @@
package service package service
import ( import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -11,12 +10,12 @@ type TagService struct {
func (s *TagService) Info(id uint) *model.Tag { func (s *TagService) Info(id uint) *model.Tag {
p := &model.Tag{} p := &model.Tag{}
global.DB.Where("id = ?", id).First(p) DB.Where("id = ?", id).First(p)
return p return p
} }
func (s *TagService) InfoByUserIdAndNameAndCollectionId(userid uint, name string, cid uint) *model.Tag { func (s *TagService) InfoByUserIdAndNameAndCollectionId(userid uint, name string, cid uint) *model.Tag {
p := &model.Tag{} p := &model.Tag{}
global.DB.Where("user_id = ? and name = ? and collection_id = ?", userid, name, cid).First(p) DB.Where("user_id = ? and name = ? and collection_id = ?", userid, name, cid).First(p)
return p return p
} }
@@ -34,7 +33,7 @@ func (s *TagService) ListByUserIdAndCollectionId(userId, cid uint) (res *model.T
return return
} }
func (s *TagService) UpdateTags(userId uint, tags map[string]uint) { func (s *TagService) UpdateTags(userId uint, tags map[string]uint) {
tx := global.DB.Begin() tx := DB.Begin()
//先查询所有tag //先查询所有tag
var allTags []*model.Tag var allTags []*model.Tag
tx.Where("user_id = ?", userId).Find(&allTags) tx.Where("user_id = ?", userId).Find(&allTags)
@@ -66,7 +65,7 @@ func (s *TagService) UpdateTags(userId uint, tags map[string]uint) {
// InfoById 根据用户id取用户信息 // InfoById 根据用户id取用户信息
func (s *TagService) InfoById(id uint) *model.Tag { func (s *TagService) InfoById(id uint) *model.Tag {
u := &model.Tag{} u := &model.Tag{}
global.DB.Where("id = ?", id).First(u) DB.Where("id = ?", id).First(u)
return u return u
} }
@@ -74,7 +73,7 @@ func (s *TagService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *mo
res = &model.TagList{} res = &model.TagList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Tag{}) tx := DB.Model(&model.Tag{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -86,14 +85,14 @@ func (s *TagService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *mo
// Create 创建 // Create 创建
func (s *TagService) Create(u *model.Tag) error { func (s *TagService) Create(u *model.Tag) error {
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
func (s *TagService) Delete(u *model.Tag) error { func (s *TagService) Delete(u *model.Tag) error {
return global.DB.Delete(u).Error return DB.Delete(u).Error
} }
// Update 更新 // Update 更新
func (s *TagService) Update(u *model.Tag) error { func (s *TagService) Update(u *model.Tag) error {
return global.DB.Model(u).Select("*").Omit("created_at").Updates(u).Error return DB.Model(u).Select("*").Omit("created_at").Updates(u).Error
} }

View File

@@ -2,7 +2,6 @@ package service
import ( import (
"errors" "errors"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/utils" "github.com/lejianwen/rustdesk-api/v2/utils"
"math/rand" "math/rand"
@@ -20,43 +19,43 @@ type UserService struct {
// InfoById 根据用户id取用户信息 // InfoById 根据用户id取用户信息
func (us *UserService) InfoById(id uint) *model.User { func (us *UserService) InfoById(id uint) *model.User {
u := &model.User{} u := &model.User{}
global.DB.Where("id = ?", id).First(u) DB.Where("id = ?", id).First(u)
return u return u
} }
// InfoByUsername 根据用户名取用户信息 // InfoByUsername 根据用户名取用户信息
func (us *UserService) InfoByUsername(un string) *model.User { func (us *UserService) InfoByUsername(un string) *model.User {
u := &model.User{} u := &model.User{}
global.DB.Where("username = ?", un).First(u) DB.Where("username = ?", un).First(u)
return u return u
} }
// InfoByEmail 根据邮箱取用户信息 // InfoByEmail 根据邮箱取用户信息
func (us *UserService) InfoByEmail(email string) *model.User { func (us *UserService) InfoByEmail(email string) *model.User {
u := &model.User{} u := &model.User{}
global.DB.Where("email = ?", email).First(u) DB.Where("email = ?", email).First(u)
return u return u
} }
// InfoByOpenid 根据openid取用户信息 // InfoByOpenid 根据openid取用户信息
func (us *UserService) InfoByOpenid(openid string) *model.User { func (us *UserService) InfoByOpenid(openid string) *model.User {
u := &model.User{} u := &model.User{}
global.DB.Where("openid = ?", openid).First(u) DB.Where("openid = ?", openid).First(u)
return u return u
} }
// InfoByUsernamePassword 根据用户名密码取用户信息 // InfoByUsernamePassword 根据用户名密码取用户信息
func (us *UserService) InfoByUsernamePassword(username, password string) *model.User { func (us *UserService) InfoByUsernamePassword(username, password string) *model.User {
if global.Config.Ldap.Enable { if Config.Ldap.Enable {
u, err := AllService.LdapService.Authenticate(username, password) u, err := AllService.LdapService.Authenticate(username, password)
if err == nil { if err == nil {
return u return u
} }
global.Logger.Errorf("LDAP authentication failed, %v", err) Logger.Errorf("LDAP authentication failed, %v", err)
global.Logger.Warn("Fallback to local database") Logger.Warn("Fallback to local database")
} }
u := &model.User{} u := &model.User{}
global.DB.Where("username = ? and password = ?", username, us.EncryptPassword(password)).First(u) DB.Where("username = ? and password = ?", username, us.EncryptPassword(password)).First(u)
return u return u
} }
@@ -64,21 +63,21 @@ func (us *UserService) InfoByUsernamePassword(username, password string) *model.
func (us *UserService) InfoByAccessToken(token string) (*model.User, *model.UserToken) { func (us *UserService) InfoByAccessToken(token string) (*model.User, *model.UserToken) {
u := &model.User{} u := &model.User{}
ut := &model.UserToken{} ut := &model.UserToken{}
global.DB.Where("token = ?", token).First(ut) DB.Where("token = ?", token).First(ut)
if ut.Id == 0 { if ut.Id == 0 {
return u, ut return u, ut
} }
if ut.ExpiredAt < time.Now().Unix() { if ut.ExpiredAt < time.Now().Unix() {
return u, ut return u, ut
} }
global.DB.Where("id = ?", ut.UserId).First(u) DB.Where("id = ?", ut.UserId).First(u)
return u, ut return u, ut
} }
// GenerateToken 生成token // GenerateToken 生成token
func (us *UserService) GenerateToken(u *model.User) string { func (us *UserService) GenerateToken(u *model.User) string {
if len(global.Jwt.Key) > 0 { if len(Jwt.Key) > 0 {
return global.Jwt.GenerateToken(u.Id) return Jwt.GenerateToken(u.Id)
} }
return utils.Md5(u.Username + time.Now().String()) return utils.Md5(u.Username + time.Now().String())
} }
@@ -93,9 +92,9 @@ func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserTok
DeviceId: llog.DeviceId, DeviceId: llog.DeviceId,
ExpiredAt: us.UserTokenExpireTimestamp(), ExpiredAt: us.UserTokenExpireTimestamp(),
} }
global.DB.Create(ut) DB.Create(ut)
llog.UserTokenId = ut.UserId llog.UserTokenId = ut.UserId
global.DB.Create(llog) DB.Create(llog)
if llog.Uuid != "" { if llog.Uuid != "" {
AllService.PeerService.UuidBindUserId(llog.DeviceId, llog.Uuid, u.Id) AllService.PeerService.UuidBindUserId(llog.DeviceId, llog.Uuid, u.Id)
} }
@@ -116,7 +115,7 @@ func (us *UserService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *
res = &model.UserList{} res = &model.UserList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(pageSize) res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.User{}) tx := DB.Model(&model.User{})
if where != nil { if where != nil {
where(tx) where(tx)
} }
@@ -127,7 +126,7 @@ func (us *UserService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *
} }
func (us *UserService) ListByIds(ids []uint) (res []*model.User) { func (us *UserService) ListByIds(ids []uint) (res []*model.User) {
global.DB.Where("id in ?", ids).Find(&res) DB.Where("id in ?", ids).Find(&res)
return res return res
} }
@@ -141,14 +140,14 @@ func (us *UserService) ListByGroupId(groupId, page, pageSize uint) (res *model.U
// ListIdsByGroupId 根据组id取用户id列表 // ListIdsByGroupId 根据组id取用户id列表
func (us *UserService) ListIdsByGroupId(groupId uint) (ids []uint) { func (us *UserService) ListIdsByGroupId(groupId uint) (ids []uint) {
global.DB.Model(&model.User{}).Where("group_id = ?", groupId).Pluck("id", &ids) DB.Model(&model.User{}).Where("group_id = ?", groupId).Pluck("id", &ids)
return ids return ids
} }
// ListIdAndNameByGroupId 根据组id取用户id和用户名列表 // ListIdAndNameByGroupId 根据组id取用户id和用户名列表
func (us *UserService) ListIdAndNameByGroupId(groupId uint) (res []*model.User) { func (us *UserService) ListIdAndNameByGroupId(groupId uint) (res []*model.User) {
global.DB.Model(&model.User{}).Where("group_id = ?", groupId).Select("id, username").Find(&res) DB.Model(&model.User{}).Where("group_id = ?", groupId).Select("id, username").Find(&res)
return res return res
} }
@@ -170,14 +169,14 @@ func (us *UserService) Create(u *model.User) error {
} }
u.Username = us.formatUsername(u.Username) u.Username = us.formatUsername(u.Username)
u.Password = us.EncryptPassword(u.Password) u.Password = us.EncryptPassword(u.Password)
res := global.DB.Create(u).Error res := DB.Create(u).Error
return res return res
} }
// GetUuidByToken 根据token和user取uuid // GetUuidByToken 根据token和user取uuid
func (us *UserService) GetUuidByToken(u *model.User, token string) string { func (us *UserService) GetUuidByToken(u *model.User, token string) string {
ut := &model.UserToken{} ut := &model.UserToken{}
err := global.DB.Where("user_id = ? and token = ?", u.Id, token).First(ut).Error err := DB.Where("user_id = ? and token = ?", u.Id, token).First(ut).Error
if err != nil { if err != nil {
return "" return ""
} }
@@ -187,7 +186,7 @@ func (us *UserService) GetUuidByToken(u *model.User, token string) string {
// Logout 退出登录 -> 删除token, 解绑uuid // Logout 退出登录 -> 删除token, 解绑uuid
func (us *UserService) Logout(u *model.User, token string) error { func (us *UserService) Logout(u *model.User, token string) error {
uuid := us.GetUuidByToken(u, token) uuid := us.GetUuidByToken(u, token)
err := global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error err := DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error
if err != nil { if err != nil {
return err return err
} }
@@ -203,7 +202,7 @@ func (us *UserService) Delete(u *model.User) error {
if userCount <= 1 && us.IsAdmin(u) { if userCount <= 1 && us.IsAdmin(u) {
return errors.New("The last admin user cannot be deleted") return errors.New("The last admin user cannot be deleted")
} }
tx := global.DB.Begin() tx := DB.Begin()
// 删除用户 // 删除用户
if err := tx.Delete(u).Error; err != nil { if err := tx.Delete(u).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -232,7 +231,7 @@ func (us *UserService) Delete(u *model.User) error {
tx.Commit() tx.Commit()
// 删除关联的peer // 删除关联的peer
if err := AllService.PeerService.EraseUserId(u.Id); err != nil { if err := AllService.PeerService.EraseUserId(u.Id); err != nil {
global.Logger.Warn("User deleted successfully, but failed to unlink peer.") Logger.Warn("User deleted successfully, but failed to unlink peer.")
return nil return nil
} }
return nil return nil
@@ -249,28 +248,28 @@ func (us *UserService) Update(u *model.User) error {
return errors.New("The last admin user cannot be disabled or demoted") return errors.New("The last admin user cannot be disabled or demoted")
} }
} }
return global.DB.Model(u).Updates(u).Error return DB.Model(u).Updates(u).Error
} }
// FlushToken 清空token // FlushToken 清空token
func (us *UserService) FlushToken(u *model.User) error { func (us *UserService) FlushToken(u *model.User) error {
return global.DB.Where("user_id = ?", u.Id).Delete(&model.UserToken{}).Error return DB.Where("user_id = ?", u.Id).Delete(&model.UserToken{}).Error
} }
// FlushTokenByUuid 清空token // FlushTokenByUuid 清空token
func (us *UserService) FlushTokenByUuid(uuid string) error { func (us *UserService) FlushTokenByUuid(uuid string) error {
return global.DB.Where("device_uuid = ?", uuid).Delete(&model.UserToken{}).Error return DB.Where("device_uuid = ?", uuid).Delete(&model.UserToken{}).Error
} }
// FlushTokenByUuids 清空token // FlushTokenByUuids 清空token
func (us *UserService) FlushTokenByUuids(uuids []string) error { func (us *UserService) FlushTokenByUuids(uuids []string) error {
return global.DB.Where("device_uuid in (?)", uuids).Delete(&model.UserToken{}).Error return DB.Where("device_uuid in (?)", uuids).Delete(&model.UserToken{}).Error
} }
// UpdatePassword 更新密码 // UpdatePassword 更新密码
func (us *UserService) UpdatePassword(u *model.User, password string) error { func (us *UserService) UpdatePassword(u *model.User, password string) error {
u.Password = us.EncryptPassword(password) u.Password = us.EncryptPassword(password)
err := global.DB.Model(u).Update("password", u.Password).Error err := DB.Model(u).Update("password", u.Password).Error
if err != nil { if err != nil {
return err return err
} }
@@ -306,8 +305,8 @@ func (us *UserService) InfoByOauthId(op string, openId string) *model.User {
// RegisterByOauth 注册 // RegisterByOauth 注册
func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (error, *model.User) { func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (error, *model.User) {
global.Lock.Lock("registerByOauth") Lock.Lock("registerByOauth")
defer global.Lock.UnLock("registerByOauth") defer Lock.UnLock("registerByOauth")
ut := AllService.OauthService.UserThirdInfo(op, oauthUser.OpenId) ut := AllService.OauthService.UserThirdInfo(op, oauthUser.OpenId)
if ut.Id != 0 { if ut.Id != 0 {
return nil, us.InfoById(ut.UserId) return nil, us.InfoById(ut.UserId)
@@ -335,12 +334,12 @@ func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (e
} }
if user.Id != 0 { if user.Id != 0 {
ut.FromOauthUser(user.Id, oauthUser, oauthType, op) ut.FromOauthUser(user.Id, oauthUser, oauthType, op)
global.DB.Create(ut) DB.Create(ut)
return nil, user return nil, user
} }
} }
tx := global.DB.Begin() tx := DB.Begin()
ut = &model.UserThird{} ut = &model.UserThird{}
ut.FromOauthUser(0, oauthUser, oauthType, op) ut.FromOauthUser(0, oauthUser, oauthType, op)
// The initial username should be formatted // The initial username should be formatted
@@ -372,27 +371,27 @@ func (us *UserService) GenerateUsernameByOauth(name string) string {
// UserThirdsByUserId // UserThirdsByUserId
func (us *UserService) UserThirdsByUserId(userId uint) (res []*model.UserThird) { func (us *UserService) UserThirdsByUserId(userId uint) (res []*model.UserThird) {
global.DB.Where("user_id = ?", userId).Find(&res) DB.Where("user_id = ?", userId).Find(&res)
return res return res
} }
func (us *UserService) UserThirdInfo(userId uint, op string) *model.UserThird { func (us *UserService) UserThirdInfo(userId uint, op string) *model.UserThird {
ut := &model.UserThird{} ut := &model.UserThird{}
global.DB.Where("user_id = ? and op = ?", userId, op).First(ut) DB.Where("user_id = ? and op = ?", userId, op).First(ut)
return ut return ut
} }
// FindLatestUserIdFromLoginLogByUuid 根据uuid查找最后登录的用户id // FindLatestUserIdFromLoginLogByUuid 根据uuid查找最后登录的用户id
func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string) uint { func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string) uint {
llog := &model.LoginLog{} llog := &model.LoginLog{}
global.DB.Where("uuid = ?", uuid).Order("id desc").First(llog) DB.Where("uuid = ?", uuid).Order("id desc").First(llog)
return llog.UserId return llog.UserId
} }
// IsPasswordEmptyById 根据用户id判断密码是否为空主要用于第三方登录的自动注册 // IsPasswordEmptyById 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyById(id uint) bool { func (us *UserService) IsPasswordEmptyById(id uint) bool {
u := &model.User{} u := &model.User{}
if global.DB.Where("id = ?", id).First(u).Error != nil { if DB.Where("id = ?", id).First(u).Error != nil {
return false return false
} }
return u.Password == "" return u.Password == ""
@@ -401,7 +400,7 @@ func (us *UserService) IsPasswordEmptyById(id uint) bool {
// IsPasswordEmptyByUsername 根据用户id判断密码是否为空主要用于第三方登录的自动注册 // IsPasswordEmptyByUsername 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyByUsername(username string) bool { func (us *UserService) IsPasswordEmptyByUsername(username string) bool {
u := &model.User{} u := &model.User{}
if global.DB.Where("username = ?", username).First(u).Error != nil { if DB.Where("username = ?", username).First(u).Error != nil {
return false return false
} }
return u.Password == "" return u.Password == ""
@@ -431,7 +430,7 @@ func (us *UserService) TokenList(page uint, size uint, f func(tx *gorm.DB)) *mod
res := &model.UserTokenList{} res := &model.UserTokenList{}
res.Page = int64(page) res.Page = int64(page)
res.PageSize = int64(size) res.PageSize = int64(size)
tx := global.DB.Model(&model.UserToken{}) tx := DB.Model(&model.UserToken{})
if f != nil { if f != nil {
f(tx) f(tx)
} }
@@ -443,12 +442,12 @@ func (us *UserService) TokenList(page uint, size uint, f func(tx *gorm.DB)) *mod
func (us *UserService) TokenInfoById(id uint) *model.UserToken { func (us *UserService) TokenInfoById(id uint) *model.UserToken {
ut := &model.UserToken{} ut := &model.UserToken{}
global.DB.Where("id = ?", id).First(ut) DB.Where("id = ?", id).First(ut)
return ut return ut
} }
func (us *UserService) DeleteToken(l *model.UserToken) error { func (us *UserService) DeleteToken(l *model.UserToken) error {
return global.DB.Delete(l).Error return DB.Delete(l).Error
} }
// Helper functions, used for formatting username // Helper functions, used for formatting username
@@ -461,29 +460,30 @@ func (us *UserService) formatUsername(username string) string {
// Helper functions, getUserCount // Helper functions, getUserCount
func (us *UserService) getUserCount() int64 { func (us *UserService) getUserCount() int64 {
var count int64 var count int64
global.DB.Model(&model.User{}).Count(&count) DB.Model(&model.User{}).Count(&count)
return count return count
} }
// helper functions, getAdminUserCount // helper functions, getAdminUserCount
func (us *UserService) getAdminUserCount() int64 { func (us *UserService) getAdminUserCount() int64 {
var count int64 var count int64
global.DB.Model(&model.User{}).Where("is_admin = ?", true).Count(&count) DB.Model(&model.User{}).Where("is_admin = ?", true).Count(&count)
return count return count
} }
// UserTokenExpireTimestamp 生成用户token过期时间 // UserTokenExpireTimestamp 生成用户token过期时间
func (us *UserService) UserTokenExpireTimestamp() int64 { func (us *UserService) UserTokenExpireTimestamp() int64 {
exp := global.Config.App.TokenExpire exp := Config.App.TokenExpire
if exp == 0 { if exp == 0 {
exp = 3600 * 24 * 7 //默认七天
exp = 604800
} }
return time.Now().Add(time.Second * time.Duration(exp)).Unix() return time.Now().Add(exp).Unix()
} }
func (us *UserService) RefreshAccessToken(ut *model.UserToken) { func (us *UserService) RefreshAccessToken(ut *model.UserToken) {
ut.ExpiredAt = us.UserTokenExpireTimestamp() ut.ExpiredAt = us.UserTokenExpireTimestamp()
global.DB.Model(ut).Update("expired_at", ut.ExpiredAt) DB.Model(ut).Update("expired_at", ut.ExpiredAt)
} }
func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) { func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
if ut.ExpiredAt-time.Now().Unix() < 86400 { if ut.ExpiredAt-time.Now().Unix() < 86400 {
@@ -492,11 +492,11 @@ func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
} }
func (us *UserService) BatchDeleteUserToken(ids []uint) error { func (us *UserService) BatchDeleteUserToken(ids []uint) error {
return global.DB.Where("id in ?", ids).Delete(&model.UserToken{}).Error return DB.Where("id in ?", ids).Delete(&model.UserToken{}).Error
} }
func (us *UserService) VerifyJWT(token string) (uint, error) { func (us *UserService) VerifyJWT(token string) (uint, error) {
return global.Jwt.ParseToken(token) return Jwt.ParseToken(token)
} }
// IsUsernameExists 判断用户名是否存在, it will check the internal database and LDAP(if enabled) // IsUsernameExists 判断用户名是否存在, it will check the internal database and LDAP(if enabled)
@@ -506,7 +506,7 @@ func (us *UserService) IsUsernameExists(username string) bool {
func (us *UserService) IsUsernameExistsLocal(username string) bool { func (us *UserService) IsUsernameExistsLocal(username string) bool {
u := &model.User{} u := &model.User{}
global.DB.Where("username = ?", username).First(u) DB.Where("username = ?", username).First(u)
return u.Id != 0 return u.Id != 0
} }

View File

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