Compare commits

...

16 Commits

Author SHA1 Message Date
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
45 changed files with 1918 additions and 467 deletions

View File

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

View File

@@ -108,8 +108,7 @@
* 可以官方指令
* 可以添加自定义指令
* 可以执行自定义指令
![rustdesk_command_advance](./docs/rustdesk_command_advance.png)
11. **LDAP 支持**, 当在API Server上设置了LDAP(已测试AD和LDAP),可以通过LDAP中的用户信息进行登录 https://github.com/lejianwen/rustdesk-api/issues/114 ,如果LDAP验证失败返回本地用户
@@ -146,72 +145,11 @@
### 相关配置
* [配置文件](./conf/config.yaml)
* 参考`conf/config.yaml`配置文件,修改相关配置。
* 如果`gorm.type``sqlite`则不需要配置mysql相关配置。
* 语言如果不设置默认为`zh-CN`
```yaml
lang: "en"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
show-swagger: 0 #是否显示swagger文档
web-sso: true #是否显示web sso
token-expire: 168h #token有效时长
disable-pwd-login: false #是否禁用密码登录
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: 168h
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`中的配置。
@@ -248,6 +186,7 @@ ldap:
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
| 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_WS_HOST | 自定义Websocket Host | `wss://192.168.1.123:1234` |
| ----PROXY配置----- | ---------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |

View File

@@ -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 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
### Web Client:
@@ -145,71 +143,11 @@ displaying data.Frontend code is available at [rustdesk-api-web](https://github.
### Configuration
* [Config File](./conf/config.yaml)
* Modify the configuration in `conf/config.yaml`.
* 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`.
```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
web-sso: true #web sso
token-expire: 168h #token expire duration
disable-pwd-login: false #disable password login
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
The environment variables correspond one-to-one with the configurations in the `conf/config.yaml` file. The prefix for variable names is `RUSTDESK_API`.
@@ -247,6 +185,7 @@ The table below does not list all configurations. Please refer to the configurat
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` |
| 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_WS_HOST | Custom Websocket Host | `wss://192.168.1.123:1234` |
| ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |

View File

@@ -51,6 +51,10 @@ var resetPwdCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
pwd := args[0]
admin := service.AllService.UserService.InfoById(1)
if admin.Id == 0 {
global.Logger.Warn("user not found! ")
return
}
err := service.AllService.UserService.UpdatePassword(admin, pwd)
if err != nil {
global.Logger.Error("reset password fail! ", err)
@@ -77,6 +81,10 @@ var resetUserPwdCmd = &cobra.Command{
return
}
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)
if err != nil {
global.Logger.Warn("reset password fail! ", err)
@@ -144,7 +152,6 @@ func InitGlobal() {
MaxOpenConns: global.Config.Gorm.MaxOpenConns,
})
}
DatabaseAutoUpdate()
//validator
global.ApiInitValidator()
@@ -164,9 +171,14 @@ func InitGlobal() {
global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration)
//locker
global.Lock = lock.NewLocal()
//service
service.New(&global.Config, global.DB, global.Logger, global.Jwt, global.Lock)
DatabaseAutoUpdate()
}
func DatabaseAutoUpdate() {
version := 260
version := 262
db := global.DB
@@ -210,6 +222,7 @@ func DatabaseAutoUpdate() {
if v.Version < uint(version) {
Migrate(uint(version))
}
// 245迁移
if v.Version < 245 {
//oauths 表的 oauth_type 字段设置为 op同样的值
@@ -232,7 +245,7 @@ func DatabaseAutoUpdate() {
}
func Migrate(version uint) {
global.Logger.Info("migrating....", version)
global.Logger.Info("Migrating....", version)
err := global.DB.AutoMigrate(
&model.Version{},
&model.User{},
@@ -250,6 +263,7 @@ func Migrate(version uint) {
&model.AddressBookCollection{},
&model.AddressBookCollectionRule{},
&model.ServerCmd{},
&model.DeviceGroup{},
)
if err != nil {
global.Logger.Error("migrate err :=>", err)

View File

@@ -32,6 +32,7 @@ rustdesk:
key-file: "/data/id_ed25519.pub"
personal: 1
webclient-magic-queryonline: 0
ws-host: "" #eg: wss://192.168.1.3:4443
logger:
path: "./runtime/log.txt"
level: "info" #trace,debug,info,warn,error,fatal
@@ -45,7 +46,7 @@ jwt:
ldap:
enable: false
url: "ldap://ldap.example.com:389"
tls: false
tls-ca-file: ""
tls-verify: false
base-dn: "dc=example,dc=com"
bind-dn: "cn=admin,dc=example,dc=com"

View File

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

View File

@@ -21,7 +21,8 @@ type Rustdesk struct {
KeyFile string `mapstructure:"key-file"`
Personal int `mapstructure:"personal"`
//webclient-magic-queryonline
WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"`
WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"`
WsHost string `mapstructure:"ws-host"`
}
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": {
"get": {
"security": [
@@ -1783,7 +2057,7 @@ const docTemplateadmin = `{
"in": "body",
"required": true,
"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": {
"type": "object",
"required": [
@@ -5306,6 +5594,12 @@ const docTemplateadmin = `{
"op": {
"type": "string"
},
"pkce_enable": {
"type": "boolean"
},
"pkce_method": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
@@ -5334,6 +5628,9 @@ const docTemplateadmin = `{
"cpu": {
"type": "string"
},
"group_id": {
"type": "integer"
},
"hostname": {
"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",
"required": [
"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": {
"type": "object",
"properties": {
@@ -5973,6 +6287,12 @@ const docTemplateadmin = `{
"op": {
"type": "string"
},
"pkce_enable": {
"type": "boolean"
},
"pkce_method": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
@@ -6013,6 +6333,9 @@ const docTemplateadmin = `{
"created_at": {
"type": "string"
},
"group_id": {
"type": "integer"
},
"hostname": {
"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": {
"get": {
"security": [
@@ -1776,7 +2050,7 @@
"in": "body",
"required": true,
"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": {
"type": "object",
"required": [
@@ -5299,6 +5587,12 @@
"op": {
"type": "string"
},
"pkce_enable": {
"type": "boolean"
},
"pkce_method": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
@@ -5327,6 +5621,9 @@
"cpu": {
"type": "string"
},
"group_id": {
"type": "integer"
},
"hostname": {
"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",
"required": [
"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": {
"type": "object",
"properties": {
@@ -5966,6 +6280,12 @@
"op": {
"type": "string"
},
"pkce_enable": {
"type": "boolean"
},
"pkce_method": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
@@ -6006,6 +6326,9 @@
"created_at": {
"type": "string"
},
"group_id": {
"type": "integer"
},
"hostname": {
"type": "string"
},

View File

@@ -77,6 +77,15 @@ definitions:
- new_password
- old_password
type: object
admin.DeviceGroupForm:
properties:
id:
type: integer
name:
type: string
required:
- name
type: object
admin.GroupForm:
properties:
id:
@@ -130,6 +139,10 @@ definitions:
type: string
op:
type: string
pkce_enable:
type: boolean
pkce_method:
type: string
redirect_url:
type: string
scopes:
@@ -153,6 +166,8 @@ definitions:
properties:
cpu:
type: string
group_id:
type: integer
hostname:
type: string
id:
@@ -278,7 +293,7 @@ definitions:
required:
- ids
type: object
github_com_lejianwen_rustdesk-api_http_request_admin.Login:
github_com_lejianwen_rustdesk-api_v2_http_request_admin.Login:
properties:
captcha:
type: string
@@ -492,6 +507,17 @@ definitions:
total:
type: integer
type: object
model.DeviceGroup:
properties:
created_at:
type: string
id:
type: integer
name:
type: string
updated_at:
type: string
type: object
model.Group:
properties:
created_at:
@@ -579,6 +605,10 @@ definitions:
type: string
op:
type: string
pkce_enable:
type: boolean
pkce_method:
type: string
redirect_url:
type: string
scopes:
@@ -605,6 +635,8 @@ definitions:
type: string
created_at:
type: string
group_id:
type: integer
hostname:
type: string
id:
@@ -1610,6 +1642,167 @@ paths:
summary: RUSTDESK服务配置
tags:
- 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:
get:
consumes:
@@ -1830,7 +2023,7 @@ paths:
name: body
required: true
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:
- application/json
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": {
"post": {
"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": {
"post": {
"description": "心跳",

View File

@@ -671,6 +671,44 @@ paths:
summary: 用户信息
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:
post:
consumes:

2
go.mod
View File

@@ -36,9 +36,11 @@ require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // 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/gin-contrib/sse v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-ldap/ldap/v3 v3.4.10 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect

View File

@@ -4,6 +4,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"os"
"strings"
@@ -60,13 +61,22 @@ func (co *Config) AppConfig(c *gin.Context) {
// @Security token
func (co *Config) AdminConfig(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
if u == nil || u.Id == 0 {
u := &model.User{}
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{
"title": global.Config.Admin.Title,
})
return
}
hello := global.Config.Admin.Hello
helloFile := global.Config.Admin.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
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin,
Op: f.Op,
Id: f.Id,
@@ -297,10 +297,12 @@ func (ct *Login) OidcAuth(c *gin.Context) {
// DeviceOs: ct.Platform(c),
DeviceOs: f.DeviceInfo.Os,
Uuid: f.Uuid,
Verifier: verifier,
Nonce: nonce,
}, 5*60)
response.Success(c, gin.H{
"code": code,
"code": state,
"url": url,
})
}

View File

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

View File

@@ -45,7 +45,7 @@ func (g *Group) Users(c *gin.Context) {
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 {
up := &apiResp.UserPayload{}
up.FromUser(user)
@@ -88,21 +88,30 @@ func (g *Group) Peers(c *gin.Context) {
users = service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId)
}
namesById := make(map[uint]string)
userIds := make([]uint, 0)
namesById := make(map[uint]string, len(users))
userIds := make([]uint, 0, len(users))
for _, user := range users {
namesById[user.Id] = user.Username
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)
var data []*apiResp.GroupPeerPayload
data := make([]*apiResp.GroupPeerPayload, 0, len(peerList.Peers))
for _, peer := range peerList.Peers {
uname, ok := namesById[peer.UserId]
if !ok {
uname = ""
}
dGroupName, ok2 := dGroupNameById[peer.GroupId]
if !ok2 {
dGroupName = ""
}
pp := &apiResp.GroupPeerPayload{}
pp.FromPeer(peer, uname)
pp.FromPeer(peer, uname, dGroupName)
data = append(data, pp)
}
@@ -111,3 +120,31 @@ func (g *Group) Peers(c *gin.Context) {
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

@@ -32,15 +32,14 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
}
oauthService := service.AllService.OauthService
var code string
var url string
err, code, url = oauthService.BeginAuth(f.Op)
err, state, verifier, nonce, url := oauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin,
Id: f.Id,
Op: f.Op,
@@ -48,10 +47,12 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
DeviceName: f.DeviceInfo.Name,
DeviceOs: f.DeviceInfo.Os,
DeviceType: f.DeviceInfo.Type,
Verifier: verifier,
Nonce: nonce,
}, 5*60)
//fmt.Println("code url", code, url)
c.JSON(http.StatusOK, gin.H{
"code": code,
"code": state,
"url": url,
})
}
@@ -143,7 +144,9 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
func (o *Oauth) OauthCallback(c *gin.Context) {
state := c.Query("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
}
cacheKey := state
@@ -151,17 +154,23 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//从缓存中获取
oauthCache := oauthService.GetOauthCache(cacheKey)
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
}
nonce := oauthCache.Nonce
op := oauthCache.Op
action := oauthCache.Action
verifier := oauthCache.Verifier
var user *model.User
// 获取用户信息
code := c.Query("code")
err, oauthUser := oauthService.Callback(code, op)
err, oauthUser := oauthService.Callback(code, verifier, op, nonce)
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
}
userId := oauthCache.UserId
@@ -172,28 +181,38 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
// 检查此openid是否已经绑定过
utr := oauthService.UserThirdInfo(op, openid)
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
}
//绑定
user = service.AllService.UserService.InfoById(userId)
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
}
//绑定
err := oauthService.BindOauthUser(userId, oauthUser, op)
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
}
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": response.TranslateMsg(c, "BindSuccess"),
})
return
} else if action == service.OauthActionTypeLogin {
//登录
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
}
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)
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
}
}
@@ -230,10 +251,14 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
c.Redirect(http.StatusFound, url)
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": response.TranslateMsg(c, "OauthSuccess"),
})
return
} else {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ParamsError"))
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": response.TranslateMsg(c, "ParamsError"),
})
return
}

View File

@@ -1,9 +1,9 @@
package web
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"strconv"
)
type Index struct {
@@ -15,13 +15,21 @@ func (i *Index) Index(c *gin.Context) {
func (i *Index) ConfigJs(c *gin.Context) {
apiServer := global.Config.Rustdesk.ApiServer
magicQueryonline := strconv.Itoa(global.Config.Rustdesk.WebclientMagicQueryonline)
tmp := `
localStorage.setItem('api-server', "` + apiServer + `")
const ws2_prefix = 'wc-'
localStorage.setItem(ws2_prefix+'api-server', "` + apiServer + `")
magicQueryonline := global.Config.Rustdesk.WebclientMagicQueryonline
tmp := fmt.Sprintf(`localStorage.setItem('api-server', '%v');
const ws2_prefix = 'wc-';
localStorage.setItem(ws2_prefix+'api-server', '%v');
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)
}

View File

@@ -22,3 +22,15 @@ func (gf *GroupForm) ToGroup() *model.Group {
group.Type = gf.Type
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"`
RedirectUrl string `json:"redirect_url" validate:"required"`
AutoRegister *bool `json:"auto_register"`
PkceEnable *bool `json:"pkce_enable"`
PkceMethod string `json:"pkce_method"`
}
func (of *OauthForm) ToOauth() *model.Oauth {
@@ -36,6 +38,8 @@ func (of *OauthForm) ToOauth() *model.Oauth {
AutoRegister: of.AutoRegister,
Issuer: of.Issuer,
Scopes: of.Scopes,
PkceEnable: of.PkceEnable,
PkceMethod: of.PkceMethod,
}
oa.Id = of.Id
return oa

View File

@@ -12,6 +12,7 @@ type PeerForm struct {
Username string `json:"username"`
Uuid string `json:"uuid"`
Version string `json:"version"`
GroupId uint `json:"group_id"`
}
type PeerBatchDeleteForm struct {
@@ -30,6 +31,7 @@ func (f *PeerForm) ToPeer() *model.Peer {
Username: f.Username,
Uuid: f.Uuid,
Version: f.Version,
GroupId: f.GroupId,
}
}

View File

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

View File

@@ -49,7 +49,7 @@ func Init(g *gin.Engine) {
MyBind(adg)
RustdeskCmdBind(adg)
DeviceGroupBind(adg)
//访问静态文件
//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) {
aR := rg.Group("/tag").Use(middleware.AdminPrivilege())
{

View File

@@ -18,6 +18,8 @@ func ApiInit(g *gin.Engine) {
if global.Config.App.ShowSwagger == 1 {
g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api")))
}
// 加载 HTML 模板
g.LoadHTMLGlob("resources/templates/*")
frg := g.Group("/api")
@@ -79,6 +81,8 @@ func ApiInit(g *gin.Engine) {
gr := &api.Group{}
frg.GET("/users", gr.Users)
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)
}
PersonalRoutes(frg)
//访问静态文件
g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/public/upload"))

View File

@@ -16,3 +16,14 @@ type GroupList struct {
Groups []*Group `json:"list"`
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"
OauthTypeOidc string = "oidc"
OauthTypeWebauth string = "webauth"
PKCEMethodS256 string = "S256"
PKCEMethodPlain string = "plain"
)
// Validate the oauth type
@@ -41,6 +43,8 @@ type Oauth struct {
AutoRegister *bool `json:"auto_register"`
Scopes string `json:"scopes"`
Issuer string `json:"issuer"`
PkceEnable *bool `json:"pkce_enable"`
PkceMethod string `json:"pkce_method"`
TimeModel
}
@@ -68,6 +72,13 @@ func (oa *Oauth) FormatOauthInfo() error {
if oauthType == OauthTypeGoogle && issuer == "" {
oa.Issuer = IssuerGoogle
}
if oa.PkceEnable == nil {
oa.PkceEnable = new(bool)
*oa.PkceEnable = false
}
if oa.PkceMethod == "" {
oa.PkceMethod = PKCEMethodS256
}
return nil
}

View File

@@ -14,6 +14,7 @@ type Peer struct {
User *User `json:"user,omitempty"`
LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"`
LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"`
GroupId uint `json:"group_id" gorm:"default:0;not null;index"`
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) {
if (window.ws_host) {
return window.ws_host + "/ws/" + (isRelay ? "relay" : "id")
}
const p = isHttps() ? "wss://" : "ws://"
const [domain, uriport] = uri.split(":")
if (!isHttps()) {

View File

@@ -3,7 +3,6 @@ package service
import (
"encoding/json"
"github.com/google/uuid"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
"strings"
@@ -14,24 +13,24 @@ type AddressBookService struct {
func (s *AddressBookService) Info(id string) *model.AddressBook {
p := &model.AddressBook{}
global.DB.Where("id = ?", id).First(p)
DB.Where("id = ?", id).First(p)
return p
}
func (s *AddressBookService) InfoByUserIdAndId(userid uint, id string) *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
}
func (s *AddressBookService) InfoByUserIdAndIdAndCid(userid uint, id string, cid uint) *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
}
func (s *AddressBookService) InfoByRowId(id uint) *model.AddressBook {
p := &model.AddressBook{}
global.DB.Where("row_id = ?", id).First(p)
DB.Where("row_id = ?", id).First(p)
return p
}
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
func (s *AddressBookService) AddAddressBook(ab *model.AddressBook) error {
return global.DB.Create(ab).Error
return DB.Create(ab).Error
}
// UpdateAddressBook
func (s *AddressBookService) UpdateAddressBook(abs []*model.AddressBook, userId uint) error {
//比较peers和数据库中的数据如果peers中的数据在数据库中不存在则添加如果存在则更新如果数据库中的数据在peers中不存在则删除
// 开始事务
tx := global.DB.Begin()
tx := DB.Begin()
//1. 获取数据库中的数据
var dbABs []*model.AddressBook
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.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AddressBook{})
tx := DB.Model(&model.AddressBook{})
if where != nil {
where(tx)
}
@@ -129,38 +128,38 @@ func (s *AddressBookService) FromPeer(peer *model.Peer) (a *model.AddressBook) {
// Create 创建
func (s *AddressBookService) Create(u *model.AddressBook) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
func (s *AddressBookService) Delete(u *model.AddressBook) error {
return global.DB.Delete(u).Error
return DB.Delete(u).Error
}
// Update 更新
func (s *AddressBookService) Update(u *model.AddressBook) error {
return global.DB.Model(u).Updates(u).Error
return DB.Model(u).Updates(u).Error
}
// UpdateByMap 更新
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 更新
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 分享
func (s *AddressBookService) ShareByWebClient(m *model.ShareRecord) error {
m.ShareToken = uuid.New().String()
return global.DB.Create(m).Error
return DB.Create(m).Error
}
// SharedPeer
func (s *AddressBookService) SharedPeer(shareToken string) *model.ShareRecord {
m := &model.ShareRecord{}
global.DB.Where("share_token = ?", shareToken).First(m)
DB.Where("share_token = ?", shareToken).First(m)
return m
}
@@ -190,7 +189,7 @@ func (s *AddressBookService) ListCollection(page, pageSize uint, where func(tx *
res = &model.AddressBookCollectionList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AddressBookCollection{})
tx := DB.Model(&model.AddressBookCollection{})
if where != nil {
where(tx)
}
@@ -200,7 +199,7 @@ func (s *AddressBookService) ListCollection(page, pageSize uint, where func(tx *
return
}
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
}
@@ -212,20 +211,20 @@ func (s *AddressBookService) ListCollectionByUserId(userId uint) (res *model.Add
}
func (s *AddressBookService) CollectionInfoById(id uint) *model.AddressBookCollection {
p := &model.AddressBookCollection{}
global.DB.Where("id = ?", id).First(p)
DB.Where("id = ?", id).First(p)
return p
}
func (s *AddressBookService) CollectionReadRules(user *model.User) (res []*model.AddressBookCollectionRule) {
// personalRules
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)
res = append(res, personalRules...)
//group
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)
res = append(res, groupRules...)
return
@@ -238,7 +237,7 @@ func (s *AddressBookService) UserMaxRule(user *model.User, uid, cid uint) int {
}
max := 0
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)
if personalRules.Id != 0 {
max = personalRules.Rule
@@ -248,7 +247,7 @@ func (s *AddressBookService) UserMaxRule(user *model.User, uid, cid uint) int {
}
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)
if groupRules.Id != 0 {
if groupRules.Rule > max {
@@ -272,16 +271,16 @@ func (s *AddressBookService) CheckUserFullControlPrivilege(user *model.User, uid
}
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 {
return global.DB.Model(t).Updates(t).Error
return DB.Model(t).Updates(t).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.AddressBook{})
tx.Delete(t)
@@ -290,23 +289,23 @@ func (s *AddressBookService) DeleteCollection(t *model.AddressBookCollection) er
func (s *AddressBookService) RuleInfoById(u uint) *model.AddressBookCollectionRule {
p := &model.AddressBookCollectionRule{}
global.DB.Where("id = ?", u).First(p)
DB.Where("id = ?", u).First(p)
return p
}
func (s *AddressBookService) RulePersonalInfoByToIdAndCid(toid, cid uint) *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
}
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 {
res := &model.AddressBookCollectionRuleList{}
res.Page = int64(page)
res.PageSize = int64(size)
tx := global.DB.Model(&model.AddressBookCollectionRule{})
tx := DB.Model(&model.AddressBookCollectionRule{})
if f != nil {
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 {
return global.DB.Model(t).Updates(t).Error
return DB.Model(t).Updates(t).Error
}
func (s *AddressBookService) DeleteRule(t *model.AddressBookCollectionRule) error {
return global.DB.Delete(t).Error
return DB.Delete(t).Error
}
// CheckCollectionOwner 检查Collection的所有者
@@ -336,5 +335,5 @@ func (s *AddressBookService) BatchUpdateTags(abs []*model.AddressBook, tags []st
ids = append(ids, ab.RowId)
}
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
import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)
@@ -13,7 +12,7 @@ func (as *AuditService) AuditConnList(page, pageSize uint, where func(tx *gorm.D
res = &model.AuditConnList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AuditConn{})
tx := DB.Model(&model.AuditConn{})
if where != nil {
where(tx)
}
@@ -25,36 +24,36 @@ func (as *AuditService) AuditConnList(page, pageSize uint, where func(tx *gorm.D
// Create 创建
func (as *AuditService) CreateAuditConn(u *model.AuditConn) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
func (as *AuditService) DeleteAuditConn(u *model.AuditConn) error {
return global.DB.Delete(u).Error
return DB.Delete(u).Error
}
// Update 更新
func (as *AuditService) UpdateAuditConn(u *model.AuditConn) error {
return global.DB.Model(u).Updates(u).Error
return DB.Model(u).Updates(u).Error
}
// InfoByPeerIdAndConnId
func (as *AuditService) InfoByPeerIdAndConnId(peerId string, connId int64) (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
}
// ConnInfoById
func (as *AuditService) ConnInfoById(id uint) (res *model.AuditConn) {
res = &model.AuditConn{}
global.DB.Where("id = ?", id).First(res)
DB.Where("id = ?", id).First(res)
return
}
// FileInfoById
func (as *AuditService) FileInfoById(id uint) (res *model.AuditFile) {
res = &model.AuditFile{}
global.DB.Where("id = ?", id).First(res)
DB.Where("id = ?", id).First(res)
return
}
@@ -62,7 +61,7 @@ func (as *AuditService) AuditFileList(page, pageSize uint, where func(tx *gorm.D
res = &model.AuditFileList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AuditFile{})
tx := DB.Model(&model.AuditFile{})
if where != nil {
where(tx)
}
@@ -74,22 +73,22 @@ func (as *AuditService) AuditFileList(page, pageSize uint, where func(tx *gorm.D
// CreateAuditFile
func (as *AuditService) CreateAuditFile(u *model.AuditFile) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
func (as *AuditService) DeleteAuditFile(u *model.AuditFile) error {
return global.DB.Delete(u).Error
return DB.Delete(u).Error
}
// Update 更新
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 {
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 {
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
import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)
@@ -12,7 +11,7 @@ type GroupService struct {
// InfoById 根据用户id取用户信息
func (us *GroupService) InfoById(id uint) *model.Group {
u := &model.Group{}
global.DB.Where("id = ?", id).First(u)
DB.Where("id = ?", id).First(u)
return u
}
@@ -20,7 +19,7 @@ func (us *GroupService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
res = &model.GroupList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Group{})
tx := DB.Model(&model.Group{})
if where != nil {
where(tx)
}
@@ -32,14 +31,47 @@ func (us *GroupService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
// Create 创建
func (us *GroupService) Create(u *model.Group) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
func (us *GroupService) Delete(u *model.Group) error {
return global.DB.Delete(u).Error
return DB.Delete(u).Error
}
// Update 更新
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 (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/url"
"os"
"strconv"
"strings"
"github.com/go-ldap/ldap/v3"
"github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
)
var (
ErrUrlParseFailed = errors.New("UrlParseFailed")
ErrFileReadFailed = errors.New("FileReadFailed")
ErrLdapNotEnabled = errors.New("LdapNotEnabled")
ErrLdapUserDisabled = errors.New("UserDisabledAtLdap")
ErrLdapUserNotFound = errors.New("UserNotFound")
@@ -68,21 +72,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.
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 {
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
if err = conn.Bind(username, password); err != nil {
fmt.Println("Bind failed")
conn.Close()
return nil, errors.Join(ErrLdapBindService, err)
}
@@ -114,7 +135,7 @@ func (ls *LdapService) Authenticate(username, password string) (*model.User, err
if !ldapUser.Enabled {
return nil, ErrLdapUserDisabled
}
cfg := &global.Config.Ldap
cfg := &Config.Ldap
user, err := ls.mapToLocalUser(cfg, ldapUser)
if err != nil {
return nil, errors.Join(ErrLdapToLocalUserFailed, err)
@@ -135,7 +156,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
// If needed, you can set a random password here.
newUser.IsAdmin = &isAdmin
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 userService.InfoByUsername(lu.Username), nil
@@ -164,7 +185,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).
func (ls *LdapService) IsUsernameExists(username string) bool {
cfg := &global.Config.Ldap
cfg := &Config.Ldap
if !cfg.Enable {
return false
}
@@ -177,7 +198,7 @@ func (ls *LdapService) IsUsernameExists(username string) bool {
// IsEmailExists checks if an email exists in LDAP (can be useful for local registration checks).
func (ls *LdapService) IsEmailExists(email string) bool {
cfg := &global.Config.Ldap
cfg := &Config.Ldap
if !cfg.Enable {
return false
}
@@ -190,7 +211,7 @@ func (ls *LdapService) IsEmailExists(email string) bool {
// GetUserInfoByUsernameLdap returns the user info from LDAP for the given username.
func (ls *LdapService) GetUserInfoByUsernameLdap(username string) (*LdapUser, error) {
cfg := &global.Config.Ldap
cfg := &Config.Ldap
if !cfg.Enable {
return nil, ErrLdapNotEnabled
}
@@ -210,12 +231,12 @@ func (ls *LdapService) GetUserInfoByUsernameLocal(username string) (*model.User,
if err != nil {
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.
func (ls *LdapService) GetUserInfoByEmailLdap(email string) (*LdapUser, error) {
cfg := &global.Config.Ldap
cfg := &Config.Ldap
if !cfg.Enable {
return nil, ErrLdapNotEnabled
}
@@ -235,7 +256,7 @@ func (ls *LdapService) GetUserInfoByEmailLocal(email string) (*model.User, error
if err != nil {
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.
@@ -453,12 +474,12 @@ func (ls *LdapService) isUserEnabled(cfg *config.Ldap, ldapUser *LdapUser) bool
// Account is disabled if the ACCOUNTDISABLE flag (0x2) is set
const ACCOUNTDISABLE = 0x2
ldapUser.Enabled = (userAccountControl&ACCOUNTDISABLE == 0)
ldapUser.Enabled = userAccountControl&ACCOUNTDISABLE == 0
return ldapUser.Enabled
}
// For other attributes, perform a direct comparison with the expected value
ldapUser.Enabled = (ldapUser.EnableAttrValue == enableAttrValue)
ldapUser.Enabled = ldapUser.EnableAttrValue == enableAttrValue
return ldapUser.Enabled
}

View File

@@ -1,7 +1,6 @@
package service
import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)
@@ -12,7 +11,7 @@ type LoginLogService struct {
// InfoById 根据用户id取用户信息
func (us *LoginLogService) InfoById(id uint) *model.LoginLog {
u := &model.LoginLog{}
global.DB.Where("id = ?", id).First(u)
DB.Where("id = ?", id).First(u)
return u
}
@@ -20,7 +19,7 @@ func (us *LoginLogService) List(page, pageSize uint, where func(tx *gorm.DB)) (r
res = &model.LoginLogList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.LoginLog{})
tx := DB.Model(&model.LoginLog{})
if where != nil {
where(tx)
}
@@ -32,20 +31,20 @@ func (us *LoginLogService) List(page, pageSize uint, where func(tx *gorm.DB)) (r
// Create 创建
func (us *LoginLogService) Create(u *model.LoginLog) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
func (us *LoginLogService) Delete(u *model.LoginLog) error {
return global.DB.Delete(u).Error
return DB.Delete(u).Error
}
// Update 更新
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 {
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 {
@@ -54,5 +53,5 @@ func (us *LoginLogService) SoftDelete(l *model.LoginLog) 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"
"encoding/json"
"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/utils"
"golang.org/x/oauth2"
@@ -45,6 +45,8 @@ type OauthCacheItem struct {
Username string `json:"username"`
Name string `json:"name"`
Email string `json:"email"`
Verifier string `json:"verifier"` // used for oauth pkce
Nonce string `json:"nonce"`
}
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) {
OauthCache.Store(key, item)
if expire > 0 {
go func() {
time.Sleep(time.Duration(expire) * time.Second)
time.AfterFunc(time.Duration(expire)*time.Second, func() {
os.DeleteOauthCache(key)
}()
})
}
}
@@ -92,151 +93,192 @@ func (os *OauthService) DeleteOauthCache(key string) {
OauthCache.Delete(key)
}
func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
code = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10)
if op == string(model.OauthTypeWebauth) {
url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + code
func (os *OauthService) BeginAuth(op string) (error error, state, verifier, nonce, url string) {
state = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10)
verifier = ""
nonce = ""
if op == model.OauthTypeWebauth {
url = Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + state
//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 {
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) FetchOidcEndpoint(issuer string) (error, OidcEndpoint) {
configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
func (os *OauthService) FetchOidcProvider(issuer string) (error, *oidc.Provider) {
// Get the HTTP client (with or without proxy based on configuration)
client := getHTTPClientWithProxy()
resp, err := client.Get(configURL)
ctx := oidc.ClientContext(context.Background(), client)
provider, err := oidc.NewProvider(ctx, issuer)
if err != nil {
return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{}
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{}
return err, nil
}
var endpoint OidcEndpoint
if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil {
return errors.New("failed to parse OIDC configuration"), OidcEndpoint{}
}
return nil, endpoint
return nil, provider
}
func (os *OauthService) FetchOidcEndpointByOp(op string) (error, OidcEndpoint) {
oauthInfo := os.InfoByOp(op)
if oauthInfo.Issuer == "" {
return errors.New("issuer is empty"), OidcEndpoint{}
}
return os.FetchOidcEndpoint(oauthInfo.Issuer)
func (os *OauthService) GithubProvider() *oidc.Provider {
return (&oidc.ProviderConfig{
IssuerURL: "",
AuthURL: github.Endpoint.AuthURL,
TokenURL: github.Endpoint.TokenURL,
DeviceAuthURL: github.Endpoint.DeviceAuthURL,
UserInfoURL: model.UserEndpointGithub,
JWKSURL: "",
Algorithms: nil,
}).NewProvider(context.Background())
}
// GetOauthConfig retrieves the OAuth2 configuration based on the provider name
func (os *OauthService) GetOauthConfig(op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config) {
err, oauthInfo, oauthConfig = os.getOauthConfigGeneral(op)
if err != nil {
return err, nil, nil
func (os *OauthService) GetOauthConfig(op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config, provider *oidc.Provider) {
//err, oauthInfo, oauthConfig = os.getOauthConfigGeneral(op)
oauthInfo = os.InfoByOp(op)
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
oauthType := oauthInfo.OauthType
err = model.ValidateOauthType(oauthType)
if err != nil {
return err, nil, nil
return err, nil, nil, nil
}
switch oauthType {
case model.OauthTypeGithub:
oauthConfig.Endpoint = github.Endpoint
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:
var endpoint OidcEndpoint
err, endpoint = os.FetchOidcEndpoint(oauthInfo.Issuer)
err, provider = os.FetchOidcProvider(oauthInfo.Issuer)
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)
default:
return errors.New("unsupported OAuth type"), 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 errors.New("unsupported OAuth type"), nil, nil, nil
}
return nil, oauthInfo, oauthConfig, provider
}
func getHTTPClientWithProxy() *http.Client {
//todo add timeout
if global.Config.Proxy.Enable {
if global.Config.Proxy.Host == "" {
global.Logger.Warn("Proxy is enabled but proxy host is empty.")
//add timeout 30s
timeout := time.Duration(60) * time.Second
if Config.Proxy.Enable {
if Config.Proxy.Host == "" {
Logger.Warn("Proxy is enabled but proxy host is empty.")
return http.DefaultClient
}
proxyURL, err := url.Parse(global.Config.Proxy.Host)
proxyURL, err := url.Parse(Config.Proxy.Host)
if err != nil {
global.Logger.Warn("Invalid proxy URL: ", err)
Logger.Warn("Invalid proxy URL: ", err)
return http.DefaultClient
}
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
return &http.Client{Transport: transport}
return &http.Client{Transport: transport, Timeout: timeout}
}
return http.DefaultClient
}
func (os *OauthService) callbackBase(oauthConfig *oauth2.Config, code string, userEndpoint string, userData interface{}) (err error, client *http.Client) {
func (os *OauthService) callbackBase(oauthConfig *oauth2.Config, provider *oidc.Provider, code string, verifier string, nonce string, userData interface{}) (err error, client *http.Client) {
// 设置代理客户端
httpClient := getHTTPClientWithProxy()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
// 使用 code 换取 token
var token *oauth2.Token
token, err = oauthConfig.Exchange(ctx, code)
exchangeOpts := make([]oauth2.AuthCodeOption, 0, 1)
if verifier != "" {
exchangeOpts = append(exchangeOpts, oauth2.VerifierOption(verifier))
}
token, err := oauthConfig.Exchange(ctx, code, exchangeOpts...)
if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
Logger.Warn("oauthConfig.Exchange() failed: ", err)
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)
resp, err := client.Get(userEndpoint)
resp, err := client.Get(provider.UserInfoEndpoint())
if err != nil {
global.Logger.Warn("failed getting user info: ", err)
Logger.Warn("failed getting user info: ", err)
return errors.New("GetOauthUserInfoError"), nil
}
defer func() {
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 {
global.Logger.Warn("failed decoding user info: ", err)
Logger.Warn("failed decoding user info: ", err)
return errors.New("DecodeOauthUserInfoError"), nil
}
@@ -244,9 +286,9 @@ func (os *OauthService) callbackBase(oauthConfig *oauth2.Config, code string, us
}
// 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{}
err, client := os.callbackBase(oauthConfig, code, model.UserEndpointGithub, user)
err, client := os.callbackBase(oauthConfig, provider, code, verifier, nonce, user)
if err != nil {
return err, nil
}
@@ -258,19 +300,17 @@ func (os *OauthService) githubCallback(oauthConfig *oauth2.Config, code string)
}
// 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{}
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 nil, user.ToOauthUser()
}
// Callback: Get user information by code and op(Oauth provider)
func (os *OauthService) Callback(code string, op string) (err error, oauthUser *model.OauthUser) {
var oauthInfo *model.Oauth
var oauthConfig *oauth2.Config
err, oauthInfo, oauthConfig = os.GetOauthConfig(op)
func (os *OauthService) Callback(code, verifier, op, nonce string) (err error, oauthUser *model.OauthUser) {
err, oauthInfo, oauthConfig, provider := os.GetOauthConfig(op)
// oauthType is already validated in GetOauthConfig
if err != nil {
return err, nil
@@ -278,13 +318,9 @@ func (os *OauthService) Callback(code string, op string) (err error, oauthUser *
oauthType := oauthInfo.OauthType
switch oauthType {
case model.OauthTypeGithub:
err, oauthUser = os.githubCallback(oauthConfig, code)
err, oauthUser = os.githubCallback(oauthConfig, provider, code, verifier, nonce)
case model.OauthTypeOidc, model.OauthTypeGoogle:
err, endpoint := os.FetchOidcEndpoint(oauthInfo.Issuer)
if err != nil {
return err, nil
}
err, oauthUser = os.oidcCallback(oauthConfig, code, endpoint.UserInfo)
err, oauthUser = os.oidcCallback(oauthConfig, provider, code, verifier, nonce)
default:
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 {
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
}
@@ -305,7 +341,7 @@ func (os *OauthService) BindOauthUser(userId uint, oauthUser *model.OauthUser, o
return err
}
utr.FromOauthUser(userId, oauthUser, oauthType, op)
return global.DB.Create(utr).Error
return DB.Create(utr).Error
}
// UnBindOauthUser: Unbind third party account
@@ -315,25 +351,25 @@ func (os *OauthService) UnBindOauthUser(userId uint, op string) error {
// UnBindThird: Unbind third party account
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
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信息
func (os *OauthService) InfoById(id uint) *model.Oauth {
oauthInfo := &model.Oauth{}
global.DB.Where("id = ?", id).First(oauthInfo)
DB.Where("id = ?", id).First(oauthInfo)
return oauthInfo
}
// InfoByOp 根据op获取Oauth信息
func (os *OauthService) InfoByOp(op string) *model.Oauth {
oauthInfo := &model.Oauth{}
global.DB.Where("op = ?", op).First(oauthInfo)
DB.Where("op = ?", op).First(oauthInfo)
return oauthInfo
}
@@ -356,7 +392,7 @@ func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
res = &model.OauthList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Oauth{})
tx := DB.Model(&model.Oauth{})
if where != nil {
where(tx)
}
@@ -369,7 +405,7 @@ func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
// GetTypeByOp 根据op获取OauthType
func (os *OauthService) GetTypeByOp(op string) (error, string) {
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 nil, oauthInfo.OauthType
@@ -387,7 +423,7 @@ func (os *OauthService) ValidateOauthProvider(op string) error {
func (os *OauthService) IsOauthProviderExist(op string) bool {
oauthInfo := &model.Oauth{}
// 使用 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 true
@@ -399,11 +435,11 @@ func (os *OauthService) Create(oauthInfo *model.Oauth) error {
if err != nil {
return err
}
res := global.DB.Create(oauthInfo).Error
res := DB.Create(oauthInfo).Error
return res
}
func (os *OauthService) Delete(oauthInfo *model.Oauth) error {
return global.DB.Delete(oauthInfo).Error
return DB.Delete(oauthInfo).Error
}
// Update 更新
@@ -412,13 +448,13 @@ func (os *OauthService) Update(oauthInfo *model.Oauth) error {
if err != nil {
return err
}
return global.DB.Model(oauthInfo).Updates(oauthInfo).Error
return DB.Model(oauthInfo).Updates(oauthInfo).Error
}
// GetOauthProviders 获取所有的provider
func (os *OauthService) GetOauthProviders() []string {
var res []string
global.DB.Model(&model.Oauth{}).Pluck("op", &res)
DB.Model(&model.Oauth{}).Pluck("op", &res)
return res
}

View File

@@ -1,7 +1,6 @@
package service
import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)
@@ -12,24 +11,24 @@ type PeerService struct {
// FindById 根据id查找
func (ps *PeerService) FindById(id string) *model.Peer {
p := &model.Peer{}
global.DB.Where("id = ?", id).First(p)
DB.Where("id = ?", id).First(p)
return p
}
func (ps *PeerService) FindByUuid(uuid string) *model.Peer {
p := &model.Peer{}
global.DB.Where("uuid = ?", uuid).First(p)
DB.Where("uuid = ?", uuid).First(p)
return p
}
func (ps *PeerService) InfoByRowId(id uint) *model.Peer {
p := &model.Peer{}
global.DB.Where("row_id = ?", id).First(p)
DB.Where("row_id = ?", id).First(p)
return p
}
// FindByUserIdAndUuid 根据用户id和uuid查找peer
func (ps *PeerService) FindByUserIdAndUuid(uuid string, userId uint) *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
}
@@ -43,7 +42,7 @@ func (ps *PeerService) UuidBindUserId(deviceId string, uuid string, userId uint)
} else {
// 不存在则创建
/*if deviceId != "" {
global.DB.Create(&model.Peer{
DB.Create(&model.Peer{
Id: deviceId,
Uuid: uuid,
UserId: userId,
@@ -56,13 +55,13 @@ func (ps *PeerService) UuidBindUserId(deviceId string, uuid string, userId uint)
func (ps *PeerService) UuidUnbindUserId(uuid string, userId uint) {
peer := ps.FindByUserIdAndUuid(uuid, userId)
if peer.RowId > 0 {
global.DB.Model(peer).Update("user_id", 0)
DB.Model(peer).Update("user_id", 0)
}
}
// EraseUserId 清除用户id, 用于用户删除
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取列表
@@ -70,7 +69,7 @@ func (ps *PeerService) ListByUserIds(userIds []uint, page, pageSize uint) (res *
res = &model.PeerList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Peer{})
tx := DB.Model(&model.Peer{})
tx.Where("user_id in (?)", userIds)
tx.Count(&res.Total)
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.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Peer{})
tx := DB.Model(&model.Peer{})
if where != nil {
where(tx)
}
@@ -106,14 +105,14 @@ func (ps *PeerService) ListFilterByUserId(page, pageSize uint, where func(tx *go
// Create 创建
func (ps *PeerService) Create(u *model.Peer) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
// Delete 删除, 同时也应该删除token
func (ps *PeerService) Delete(u *model.Peer) error {
uuid := u.Uuid
err := global.DB.Delete(u).Error
err := DB.Delete(u).Error
if err != nil {
return err
}
@@ -124,7 +123,7 @@ func (ps *PeerService) Delete(u *model.Peer) error {
// GetUuidListByIDs 根据ids获取uuid列表
func (ps *PeerService) GetUuidListByIDs(ids []uint) ([]string, error) {
var uuids []string
err := global.DB.Model(&model.Peer{}).
err := DB.Model(&model.Peer{}).
Where("row_id in (?)", ids).
Pluck("uuid", &uuids).Error
return uuids, err
@@ -133,7 +132,7 @@ func (ps *PeerService) GetUuidListByIDs(ids []uint) ([]string, error) {
// BatchDelete 批量删除, 同时也应该删除token
func (ps *PeerService) BatchDelete(ids []uint) error {
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 {
return err
}
@@ -143,5 +142,5 @@ func (ps *PeerService) BatchDelete(ids []uint) error {
// Update 更新
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 (
"fmt"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"net"
"time"
@@ -15,7 +14,7 @@ func (is *ServerCmdService) List(page, pageSize uint) (res *model.ServerCmdList)
res = &model.ServerCmdList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.ServerCmd{})
tx := DB.Model(&model.ServerCmd{})
tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize))
tx.Find(&res.ServerCmds)
@@ -25,18 +24,18 @@ func (is *ServerCmdService) List(page, pageSize uint) (res *model.ServerCmdList)
// Info
func (is *ServerCmdService) Info(id uint) *model.ServerCmd {
u := &model.ServerCmd{}
global.DB.Where("id = ?", id).First(u)
DB.Where("id = ?", id).First(u)
return u
}
// Delete
func (is *ServerCmdService) Delete(u *model.ServerCmd) error {
return global.DB.Delete(u).Error
return DB.Delete(u).Error
}
// Create
func (is *ServerCmdService) Create(u *model.ServerCmd) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
@@ -45,9 +44,9 @@ func (is *ServerCmdService) SendCmd(target string, cmd string, arg string) (stri
port := 0
switch target {
case model.ServerCmdTargetIdServer:
port = global.Config.Rustdesk.IdServerPort - 1
port = Config.Rustdesk.IdServerPort - 1
case model.ServerCmdTargetRelayServer:
port = global.Config.Rustdesk.RelayServerPort
port = Config.Rustdesk.RelayServerPort
}
//组装命令
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))
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
}
defer conn.Close()
//发送命令
_, err = conn.Write([]byte(cmd))
if err != nil {
global.Logger.Debugf("%s send cmd failed: %v", ty, err)
Logger.Debugf("%s send cmd failed: %v", ty, err)
return "", err
}
time.Sleep(100 * time.Millisecond)
@@ -88,12 +87,12 @@ func (is *ServerCmdService) SendSocketCmd(ty string, port int, cmd string) (stri
buf := make([]byte, 1024)
n, err := conn.Read(buf)
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 string(buf[:n]), nil
}
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
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"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
@@ -21,12 +25,31 @@ type Service struct {
*LdapService
}
func New() *Service {
all := new(Service)
return all
type Dependencies struct {
Config *config.Config
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 {
return func(db *gorm.DB) *gorm.DB {

View File

@@ -1,7 +1,6 @@
package service
import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)
@@ -12,7 +11,7 @@ type ShareRecordService struct {
// InfoById 根据用户id取用户信息
func (srs *ShareRecordService) InfoById(id uint) *model.ShareRecord {
u := &model.ShareRecord{}
global.DB.Where("id = ?", id).First(u)
DB.Where("id = ?", id).First(u)
return u
}
@@ -20,7 +19,7 @@ func (srs *ShareRecordService) List(page, pageSize uint, where func(tx *gorm.DB)
res = &model.ShareRecordList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.ShareRecord{})
tx := DB.Model(&model.ShareRecord{})
if where != nil {
where(tx)
}
@@ -32,18 +31,18 @@ func (srs *ShareRecordService) List(page, pageSize uint, where func(tx *gorm.DB)
// Create 创建
func (srs *ShareRecordService) Create(u *model.ShareRecord) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
func (srs *ShareRecordService) Delete(u *model.ShareRecord) error {
return global.DB.Delete(u).Error
return DB.Delete(u).Error
}
// Update 更新
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 {
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
import (
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)
@@ -11,12 +10,12 @@ type TagService struct {
func (s *TagService) Info(id uint) *model.Tag {
p := &model.Tag{}
global.DB.Where("id = ?", id).First(p)
DB.Where("id = ?", id).First(p)
return p
}
func (s *TagService) InfoByUserIdAndNameAndCollectionId(userid uint, name string, cid uint) *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
}
@@ -34,7 +33,7 @@ func (s *TagService) ListByUserIdAndCollectionId(userId, cid uint) (res *model.T
return
}
func (s *TagService) UpdateTags(userId uint, tags map[string]uint) {
tx := global.DB.Begin()
tx := DB.Begin()
//先查询所有tag
var allTags []*model.Tag
tx.Where("user_id = ?", userId).Find(&allTags)
@@ -66,7 +65,7 @@ func (s *TagService) UpdateTags(userId uint, tags map[string]uint) {
// InfoById 根据用户id取用户信息
func (s *TagService) InfoById(id uint) *model.Tag {
u := &model.Tag{}
global.DB.Where("id = ?", id).First(u)
DB.Where("id = ?", id).First(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.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Tag{})
tx := DB.Model(&model.Tag{})
if where != nil {
where(tx)
}
@@ -86,14 +85,14 @@ func (s *TagService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *mo
// Create 创建
func (s *TagService) Create(u *model.Tag) error {
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
func (s *TagService) Delete(u *model.Tag) error {
return global.DB.Delete(u).Error
return DB.Delete(u).Error
}
// Update 更新
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 (
"errors"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/utils"
"math/rand"
@@ -20,43 +19,43 @@ type UserService struct {
// InfoById 根据用户id取用户信息
func (us *UserService) InfoById(id uint) *model.User {
u := &model.User{}
global.DB.Where("id = ?", id).First(u)
DB.Where("id = ?", id).First(u)
return u
}
// InfoByUsername 根据用户名取用户信息
func (us *UserService) InfoByUsername(un string) *model.User {
u := &model.User{}
global.DB.Where("username = ?", un).First(u)
DB.Where("username = ?", un).First(u)
return u
}
// InfoByEmail 根据邮箱取用户信息
func (us *UserService) InfoByEmail(email string) *model.User {
u := &model.User{}
global.DB.Where("email = ?", email).First(u)
DB.Where("email = ?", email).First(u)
return u
}
// InfoByOpenid 根据openid取用户信息
func (us *UserService) InfoByOpenid(openid string) *model.User {
u := &model.User{}
global.DB.Where("openid = ?", openid).First(u)
DB.Where("openid = ?", openid).First(u)
return u
}
// InfoByUsernamePassword 根据用户名密码取用户信息
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)
if err == nil {
return u
}
global.Logger.Errorf("LDAP authentication failed, %v", err)
global.Logger.Warn("Fallback to local database")
Logger.Errorf("LDAP authentication failed, %v", err)
Logger.Warn("Fallback to local database")
}
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
}
@@ -64,21 +63,21 @@ func (us *UserService) InfoByUsernamePassword(username, password string) *model.
func (us *UserService) InfoByAccessToken(token string) (*model.User, *model.UserToken) {
u := &model.User{}
ut := &model.UserToken{}
global.DB.Where("token = ?", token).First(ut)
DB.Where("token = ?", token).First(ut)
if ut.Id == 0 {
return u, ut
}
if ut.ExpiredAt < time.Now().Unix() {
return u, ut
}
global.DB.Where("id = ?", ut.UserId).First(u)
DB.Where("id = ?", ut.UserId).First(u)
return u, ut
}
// GenerateToken 生成token
func (us *UserService) GenerateToken(u *model.User) string {
if len(global.Jwt.Key) > 0 {
return global.Jwt.GenerateToken(u.Id)
if len(Jwt.Key) > 0 {
return Jwt.GenerateToken(u.Id)
}
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,
ExpiredAt: us.UserTokenExpireTimestamp(),
}
global.DB.Create(ut)
DB.Create(ut)
llog.UserTokenId = ut.UserId
global.DB.Create(llog)
DB.Create(llog)
if llog.Uuid != "" {
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.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.User{})
tx := DB.Model(&model.User{})
if where != nil {
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) {
global.DB.Where("id in ?", ids).Find(&res)
DB.Where("id in ?", ids).Find(&res)
return res
}
@@ -141,14 +140,14 @@ func (us *UserService) ListByGroupId(groupId, page, pageSize uint) (res *model.U
// ListIdsByGroupId 根据组id取用户id列表
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
}
// ListIdAndNameByGroupId 根据组id取用户id和用户名列表
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
}
@@ -170,14 +169,14 @@ func (us *UserService) Create(u *model.User) error {
}
u.Username = us.formatUsername(u.Username)
u.Password = us.EncryptPassword(u.Password)
res := global.DB.Create(u).Error
res := DB.Create(u).Error
return res
}
// GetUuidByToken 根据token和user取uuid
func (us *UserService) GetUuidByToken(u *model.User, token string) string {
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 {
return ""
}
@@ -187,7 +186,7 @@ func (us *UserService) GetUuidByToken(u *model.User, token string) string {
// Logout 退出登录 -> 删除token, 解绑uuid
func (us *UserService) Logout(u *model.User, token string) error {
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 {
return err
}
@@ -203,7 +202,7 @@ func (us *UserService) Delete(u *model.User) error {
if userCount <= 1 && us.IsAdmin(u) {
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 {
tx.Rollback()
@@ -232,7 +231,7 @@ func (us *UserService) Delete(u *model.User) error {
tx.Commit()
// 删除关联的peer
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
@@ -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 global.DB.Model(u).Updates(u).Error
return DB.Model(u).Updates(u).Error
}
// FlushToken 清空token
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
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
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 更新密码
func (us *UserService) UpdatePassword(u *model.User, password string) error {
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 {
return err
}
@@ -306,8 +305,8 @@ func (us *UserService) InfoByOauthId(op string, openId string) *model.User {
// RegisterByOauth 注册
func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (error, *model.User) {
global.Lock.Lock("registerByOauth")
defer global.Lock.UnLock("registerByOauth")
Lock.Lock("registerByOauth")
defer Lock.UnLock("registerByOauth")
ut := AllService.OauthService.UserThirdInfo(op, oauthUser.OpenId)
if ut.Id != 0 {
return nil, us.InfoById(ut.UserId)
@@ -335,12 +334,12 @@ func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (e
}
if user.Id != 0 {
ut.FromOauthUser(user.Id, oauthUser, oauthType, op)
global.DB.Create(ut)
DB.Create(ut)
return nil, user
}
}
tx := global.DB.Begin()
tx := DB.Begin()
ut = &model.UserThird{}
ut.FromOauthUser(0, oauthUser, oauthType, op)
// The initial username should be formatted
@@ -372,27 +371,27 @@ func (us *UserService) GenerateUsernameByOauth(name string) string {
// UserThirdsByUserId
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
}
func (us *UserService) UserThirdInfo(userId uint, op string) *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
}
// FindLatestUserIdFromLoginLogByUuid 根据uuid查找最后登录的用户id
func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string) uint {
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
}
// IsPasswordEmptyById 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyById(id uint) bool {
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 u.Password == ""
@@ -401,7 +400,7 @@ func (us *UserService) IsPasswordEmptyById(id uint) bool {
// IsPasswordEmptyByUsername 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyByUsername(username string) bool {
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 u.Password == ""
@@ -431,7 +430,7 @@ func (us *UserService) TokenList(page uint, size uint, f func(tx *gorm.DB)) *mod
res := &model.UserTokenList{}
res.Page = int64(page)
res.PageSize = int64(size)
tx := global.DB.Model(&model.UserToken{})
tx := DB.Model(&model.UserToken{})
if f != nil {
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 {
ut := &model.UserToken{}
global.DB.Where("id = ?", id).First(ut)
DB.Where("id = ?", id).First(ut)
return ut
}
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
@@ -461,20 +460,20 @@ func (us *UserService) formatUsername(username string) string {
// Helper functions, getUserCount
func (us *UserService) getUserCount() int64 {
var count int64
global.DB.Model(&model.User{}).Count(&count)
DB.Model(&model.User{}).Count(&count)
return count
}
// helper functions, getAdminUserCount
func (us *UserService) getAdminUserCount() 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
}
// UserTokenExpireTimestamp 生成用户token过期时间
func (us *UserService) UserTokenExpireTimestamp() int64 {
exp := global.Config.App.TokenExpire
exp := Config.App.TokenExpire
if exp == 0 {
//默认七天
exp = 604800
@@ -484,7 +483,7 @@ func (us *UserService) UserTokenExpireTimestamp() int64 {
func (us *UserService) RefreshAccessToken(ut *model.UserToken) {
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) {
if ut.ExpiredAt-time.Now().Unix() < 86400 {
@@ -493,11 +492,11 @@ func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
}
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) {
return global.Jwt.ParseToken(token)
return Jwt.ParseToken(token)
}
// IsUsernameExists 判断用户名是否存在, it will check the internal database and LDAP(if enabled)
@@ -507,7 +506,7 @@ func (us *UserService) IsUsernameExists(username string) bool {
func (us *UserService) IsUsernameExistsLocal(username string) bool {
u := &model.User{}
global.DB.Where("username = ?", username).First(u)
DB.Where("username = ?", username).First(u)
return u.Id != 0
}