Compare commits

...

23 Commits

Author SHA1 Message Date
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
47 changed files with 1998 additions and 520 deletions

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>
@@ -109,7 +109,6 @@
* 可以添加自定义指令 * 可以添加自定义指令
* 可以执行自定义指令 * 可以执行自定义指令
![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"

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:

2
go.mod
View File

@@ -36,9 +36,11 @@ 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/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

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

@@ -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

@@ -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

@@ -56,7 +56,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)
} }

View File

@@ -32,15 +32,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 +47,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 +144,9 @@ 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": response.TranslateParamMsg(c, "ParamIsEmpty", "state"),
})
return return
} }
cacheKey := state cacheKey := state
@@ -151,17 +154,23 @@ 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": response.TranslateMsg(c, "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": response.TranslateMsg(c, "OauthFailed") + response.TranslateMsg(c, err.Error()),
})
return return
} }
userId := oauthCache.UserId userId := oauthCache.UserId
@@ -172,28 +181,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": response.TranslateMsg(c, "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": response.TranslateMsg(c, "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": response.TranslateMsg(c, "BindFail"),
})
return return
} }
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess")) c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": response.TranslateMsg(c, "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": response.TranslateMsg(c, "OauthHasBeenSuccess"),
})
return return
} }
user = service.AllService.UserService.InfoByOauthId(op, openid) user = service.AllService.UserService.InfoByOauthId(op, openid)
@@ -210,7 +229,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": response.TranslateMsg(c, err.Error()),
})
return return
} }
} }
@@ -230,10 +251,14 @@ 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": response.TranslateMsg(c, "OauthSuccess"),
})
return return
} else { } else {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ParamsError")) c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "ParamsError"),
})
return return
} }

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

@@ -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")
@@ -79,6 +81,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 +92,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

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>授权失败 - 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">
</head>
<body>
<div class="success-container">
<i class="fas fa-triangle-exclamation checkmark"></i>
<h1>授权失败!</h1>
<p>{{.message}}</p>
<a href="javascript:window.close()" class="return-link">关闭页面</a>
</div>
<script>
</script>
</body>
</html>

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>授权成功 - 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>
</head>
<body>
<div class="success-container">
<i class="fas fa-check-circle checkmark"></i>
<h1>授权成功!</h1>
<p>您已成功授权访问您的账户。</p>
<p>现在可以关闭本页面或返回应用继续操作。</p>
<a href="javascript:window.close()" class="return-link">关闭页面</a>
</div>
<script>
</script>
</body>
</html>

View File

@@ -11088,6 +11088,9 @@ function R4(u = !1) {
} }
function getUriFromRs(uri, isRelay = false, roffset = 0) { function getUriFromRs(uri, isRelay = false, roffset = 0) {
if (window.ws_host) {
return window.ws_host + "/ws/" + (isRelay ? "relay" : "id")
}
const p = isHttps() ? "wss://" : "ws://" const p = isHttps() ? "wss://" : "ws://"
const [domain, uriport] = uri.split(":") const [domain, uriport] = uri.split(":")
if (!isHttps()) { if (!isHttps()) {

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,23 @@ 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 {
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 = ?", model.ShareAddressBookRuleTypePersonal, 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 +316,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 +335,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
} }

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"
) )
@@ -21,12 +25,31 @@ type Service struct {
*LdapService *LdapService
} }
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
} }