Compare commits

...

25 Commits

Author SHA1 Message Date
lejianwen
ee0cbabffc fix(admin): Admin hello 2025-02-20 19:37:34 +08:00
lejianwen
d6a5af890a style: No need exec sql on version 261 2025-02-20 19:22:22 +08:00
lejianwen
dc313441e5 fix: Js content-type 2025-02-19 16:04:03 +08:00
Tao Chen
c75320f4f4 feat(oidc): add pkce (#150) 2025-02-19 09:31:25 +08:00
lejianwen
c788f78416 docs: Readme 2025-02-17 10:59:38 +08:00
lejianwen
49cf954d4a fix(config)!: Token expire time (#145)
将配置中的过期时间单位统一为time.Duration,可以设置为`h`,`m`,`s`
2025-02-17 10:49:59 +08:00
lejianwen
014e3db54f docs: Readme 2025-02-16 21:06:55 +08:00
lejianwen
6d9c245c81 fix(webclient): port 2025-02-16 14:17:37 +08:00
lejianwen
7fa9b79f31 fix(webclient): port 2025-02-16 14:11:36 +08:00
lejianwen
c7f3d13b7f fix(webclient): port 2025-02-16 13:33:06 +08:00
lejianwen
46f08a89d2 feat: Login by pwd can be disable
---

Closes: #141
2025-02-16 13:06:45 +08:00
lejianwen
0dcfedb4dc fix(webclient)!: Webclient path is /ws/(relay|id) (#73 #143 #140)
Webclient 的反代引发了很多问题,现在将在HTTPS下的path固定为`/ws/(relay|id)`

---

Closes: #143 #140
2025-02-16 12:41:32 +08:00
lejianwen
918bf85a2d style: middleware name 2025-02-12 22:09:52 +08:00
lejianwen
99db5f7190 fix(admin): Admin web title 2025-02-12 21:10:07 +08:00
lejianwen
18eff791b2 style: Module name 2025-02-12 19:46:39 +08:00
lejianwen
624dcacac5 style: generate 2025-02-12 16:56:41 +08:00
lejianwen
878d5fd27c style: Remove generate 2025-02-12 16:25:04 +08:00
lejianwen
4b893ce0e8 docs: Docs 2025-02-12 16:14:51 +08:00
lejianwen
472524f836 style: Module name 2025-02-12 16:07:51 +08:00
lejianwen
dbf8b23b15 fix: Config watchConfig (#135)
---
Closes: #135
2025-02-10 10:13:34 +08:00
lejianwen
79a5dd53ae fix: User disabled can not work (#133)
---
Closes: #133
2025-02-10 10:13:15 +08:00
Tao Chen
8a5b20685c fix: When OIDC and LDAP work togethar (#132 #134)
* fix OIDC create user if LDAP enable

* `newUser.GroupId = 1` for ldap

* fix
2025-02-10 10:08:49 +08:00
lejianwen
5a9c972de0 docs: Readme 2025-02-09 21:13:01 +08:00
Tao Chen
fc0e67122d docs: add LDAP info (#130) 2025-02-09 19:36:31 +08:00
lejianwen
eb642f66ca docs: Readme 2025-02-07 18:14:12 +08:00
98 changed files with 753 additions and 524 deletions

View File

@@ -4,12 +4,12 @@
本项目使用 Go 实现了 RustDesk 的 API并包含了 Web Admin 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。
<div align=center>
<div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/>
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://img.shields.io/badge/i18n-7-green"/>
<img src="https://goreportcard.com/badge/github.com/lejianwen/rustdesk-api/v2"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div>
@@ -20,7 +20,10 @@
- 登录
- 地址簿
- 群组
- 授权登录,支持`github`, `google``OIDC` 登录,支持`web后台`授权登录
- 授权登录
- 支持`github`, `google``OIDC` 登录,
- 支持`web后台`授权登录
- 支持`LDAP`(AD和OpenLDAP已测试), 如果API Server配置了LDAP
- i18n
- Web Admin
- 用户管理
@@ -29,6 +32,7 @@
- 标签管理
- 群组管理
- Oauth 管理
- 配置LDAP, 配置文件或者环境变量
- 登录日志
- 链接日志
- 文件传输日志
@@ -107,6 +111,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验证失败返回本地用户
### Web Client:
@@ -151,6 +156,9 @@ 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"
@@ -180,7 +188,7 @@ proxy:
host: ""
jwt:
key: ""
expire-duration: 360000
expire-duration: 168h
ldap:
enable: false
url: "ldap://ldap.example.com:389"
@@ -208,43 +216,44 @@ ldap:
环境变量和配置文件`conf/config.yaml`中的配置一一对应,变量名前缀是`RUSTDESK_API`
下面表格并未全部列出,可以参考`conf/config.yaml`中的配置。
| 变量名 | 说明 | 示例 |
|---------------------------------------------------|--------------------------------------------------------------------------------|------------------------------|
| TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长(秒) | `3600` |
| -----ADMIN配置----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | 后台欢迎语文件,如果内容多,使用文件更方便。<br>会覆盖`RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----GORM配置----- | ---------- | --------------------------- |
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql默认sqlite | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 |
| -----MYSQL配置----- | ---------- | ---------- |
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
| -----RUSTDESK配置----- | ---------- | ---------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` |
| ----PROXY配置----- | ---------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
| ----JWT配置---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT<br/>如果没使用`lejianwen/rustdesk-server`中的`MUST_LOGIN`,建议设置为空 | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | 360000 |
| 变量名 | 说明 | 示例 |
|--------------------------------------------------------|--------------------------------------------------------------------------------|------------------------------|
| TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长 | `168h` |
| RUSTDESK_API_APP_DISABLE_PWD_LOGIN | 是否禁用密码登录; `true`, `false` 默认`false` | `false` |
| -----ADMIN配置----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | 后台欢迎语文件,如果内容多,使用文件更方便。<br>会覆盖`RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----GORM配置----- | ---------- | --------------------------- |
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql默认sqlite | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 |
| -----MYSQL配置----- | ---------- | ---------- |
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
| -----RUSTDESK配置----- | ---------- | ---------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdeskkey | 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` |
| ----PROXY配置----- | ---------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
| ----JWT配置---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT<br/>如果没使用`lejianwen/rustdesk-server`中的`MUST_LOGIN`,建议设置为空 | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | `168h` |
### 运行
@@ -367,4 +376,4 @@ ldap:
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a>
## 如果觉得这个项目对你有帮助,请给一个star,谢谢!
## 感谢你的支持!如果这个项目对你有帮助,请点个⭐️鼓励一下,谢谢!

View File

@@ -8,7 +8,7 @@ desktop software that provides self-hosted solutions.
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://img.shields.io/badge/i18n-7-green"/>
<img src="https://goreportcard.com/badge/github.com/lejianwen/rustdesk-api/v2"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div>
@@ -19,7 +19,10 @@ desktop software that provides self-hosted solutions.
- Login
- Address Book
- Groups
- Authorized login, supports `GitHub`, `Google` and `OIDC` login, supports `web admin` authorized login
- Authorized login,
- supports `GitHub`, `Google` and `OIDC` login,
- supports `web admin` authorized login,
- supports LDAP(test AD and openladp) if API Server config
- i18n
- Web Admin
- User Management
@@ -28,6 +31,7 @@ desktop software that provides self-hosted solutions.
- Tag Management
- Group Management
- OAuth Management
- LDAP Config by config file or ENV
- Login Logs
- Connection Logs
- File Transfer Logs
@@ -107,8 +111,8 @@ displaying data.Frontend code is available at [rustdesk-api-web](https://github.
![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:
1. If you're already logged into the admin panel, the web client will log in automatically.
@@ -151,6 +155,9 @@ 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"
@@ -208,43 +215,44 @@ ldap:
The environment variables correspond one-to-one with the configurations in the `conf/config.yaml` file. The prefix for variable names is `RUSTDESK_API`.
The table below does not list all configurations. Please refer to the configurations in `conf/config.yaml`.
| Variable Name | Description | Example |
|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|
| TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 |
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token expire duration(second) | `3600` |
| ----- ADMIN Configuration----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` |
| ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
| ----JWT---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | Custom JWT KEY, if empty JWT is not enabled.<br/>If `MUST_LOGIN` from `lejianwen/rustdesk-server` is not used, it is recommended to leave it empty. | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT expire duration | 360000 |
| Variable Name | Description | Example |
|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|
| TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 |
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token expire duration | `168h` |
| RUSTDESK_API_APP_DISABLE_PWD_LOGIN | disable password login | `false` |
| ----- ADMIN Configuration----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| 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` |
| ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
| ----JWT---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | Custom JWT KEY, if empty JWT is not enabled.<br/>If `MUST_LOGIN` from `lejianwen/rustdesk-server` is not used, it is recommended to leave it empty. | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT expire duration | `168h` |
### Installation Steps
@@ -367,4 +375,4 @@ Thanks to everyone who contributed!
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a>
## If you find this project helpful, please give it a star, thank you!
## Thanks for your support! If you find this project useful, please give it a ⭐️. Thank you!

View File

@@ -1,24 +1,23 @@
package main
import (
"Gwen/config"
"Gwen/global"
"Gwen/http"
"Gwen/lib/cache"
"Gwen/lib/jwt"
"Gwen/lib/lock"
"Gwen/lib/logger"
"Gwen/lib/orm"
"Gwen/lib/upload"
"Gwen/model"
"Gwen/service"
"Gwen/utils"
"github.com/go-redis/redis/v8"
"github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http"
"github.com/lejianwen/rustdesk-api/v2/lib/cache"
"github.com/lejianwen/rustdesk-api/v2/lib/jwt"
"github.com/lejianwen/rustdesk-api/v2/lib/lock"
"github.com/lejianwen/rustdesk-api/v2/lib/logger"
"github.com/lejianwen/rustdesk-api/v2/lib/orm"
"github.com/lejianwen/rustdesk-api/v2/lib/upload"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/spf13/cobra"
"os"
"strconv"
"time"
)
// @title 管理系统API
@@ -162,13 +161,12 @@ func InitGlobal() {
//jwt
//fmt.Println(global.Config.Jwt.PrivateKey)
global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration*time.Second)
global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration)
//locker
global.Lock = lock.NewLocal()
}
func DatabaseAutoUpdate() {
version := 260
version := 261
db := global.DB
@@ -186,12 +184,12 @@ func DatabaseAutoUpdate() {
// 获取底层的 *sql.DB 对象,并确保在程序退出时关闭连接
sqlDBWithoutDB, err := dbWithoutDB.DB()
if err != nil {
global.Logger.Error("获取底层 *sql.DB 对象失败: %v\n", err)
global.Logger.Errorf("获取底层 *sql.DB 对象失败: %v", err)
return
}
defer func() {
if err := sqlDBWithoutDB.Close(); err != nil {
global.Logger.Error("关闭连接失败: %v\n", err)
global.Logger.Errorf("关闭连接失败: %v", err)
}
}()
@@ -212,6 +210,7 @@ func DatabaseAutoUpdate() {
if v.Version < uint(version) {
Migrate(uint(version))
}
// 245迁移
if v.Version < 245 {
//oauths 表的 oauth_type 字段设置为 op同样的值
@@ -234,7 +233,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{},

View File

@@ -1 +1 @@
### 👏👏👏 你好 ***{{username}}*** 欢迎使用 [RustDesk Api](https://github.com/lejianwen/rustdesk-api)
### 👏👏👏 你好 ***{{username}}*** 欢迎使用 [RustDesk API](https://github.com/lejianwen/rustdesk-api)

View File

@@ -3,8 +3,9 @@ app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
show-swagger: 0 # 1:启用 0:禁用
token-expire: 360000
token-expire: 168h
web-sso: true #web auth sso
disable-pwd-login: false #禁用密码登录
admin:
title: "RustDesk Api Admin"
hello-file: "./conf/admin/hello.html" #优先使用file
@@ -40,7 +41,7 @@ proxy:
host: "http://127.0.0.1:1080"
jwt:
key: ""
expire-duration: 360000
expire-duration: 168h
ldap:
enable: false
url: "ldap://ldap.example.com:389"

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/spf13/viper"
"strings"
"time"
)
const (
@@ -13,11 +14,12 @@ const (
)
type App struct {
WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
ShowSwagger int `mapstructure:"show-swagger"`
TokenExpire int `mapstructure:"token-expire"`
WebSso bool `mapstructure:"web-sso"`
WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
ShowSwagger int `mapstructure:"show-swagger"`
TokenExpire time.Duration `mapstructure:"token-expire"`
WebSso bool `mapstructure:"web-sso"`
DisablePwdLogin bool `mapstructure:"disable-pwd-login"`
}
type Admin struct {
Title string `mapstructure:"title"`
@@ -56,22 +58,23 @@ func Init(rowVal *Config, path string) *viper.Viper {
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
v.WatchConfig()
/*
//监听配置修改没什么必要
v.OnConfigChange(func(e fsnotify.Event) {
//配置文件修改监听
fmt.Println("config file changed:", e.Name)
if err2 := v.Unmarshal(rowVal); err2 != nil {
fmt.Println(err2)
}
rowVal.Rustdesk.LoadKeyFile()
rowVal.Rustdesk.ParsePort()
})
v.WatchConfig()
//监听配置修改没什么必要
v.OnConfigChange(func(e fsnotify.Event) {
//配置文件修改监听
fmt.Println("config file changed:", e.Name)
if err2 := v.Unmarshal(rowVal); err2 != nil {
fmt.Println(err2)
}
rowVal.Rustdesk.LoadKeyFile()
rowVal.Rustdesk.ParsePort()
})
*/
if err := v.Unmarshal(rowVal); err != nil {
fmt.Println(err)
panic(fmt.Errorf("Fatal error config: %s \n", err))
}
rowVal.Rustdesk.LoadKeyFile()
rowVal.Rustdesk.ParsePort()

View File

@@ -1,16 +1,16 @@
package config
type LdapUser struct {
BaseDn string `mapstructure:"base-dn"` // The base DN of the user for searching
EnableAttr string `mapstructure:"enable-attr"` // The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
EnableAttrValue string `mapstructure:"enable-attr-value"` // The value of the enable attribute when the user is enabled. If you are using AD, just leave it random str, it will be ignored.
Filter string `mapstructure:"filter"`
Username string `mapstructure:"username"`
Email string `mapstructure:"email"`
FirstName string `mapstructure:"first-name"`
LastName string `mapstructure:"last-name"`
Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database
AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group
BaseDn string `mapstructure:"base-dn"` // The base DN of the user for searching
EnableAttr string `mapstructure:"enable-attr"` // The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
EnableAttrValue string `mapstructure:"enable-attr-value"` // The value of the enable attribute when the user is enabled. If you are using AD, just leave it random str, it will be ignored.
Filter string `mapstructure:"filter"`
Username string `mapstructure:"username"`
Email string `mapstructure:"email"`
FirstName string `mapstructure:"first-name"`
LastName string `mapstructure:"last-name"`
Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database
AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group
}
// type LdapGroup struct {
@@ -24,13 +24,13 @@ type LdapUser struct {
// }
type Ldap struct {
Enable bool `mapstructure:"enable"`
Url string `mapstructure:"url"`
TLS bool `mapstructure:"tls"`
TlsVerify bool `mapstructure:"tls-verify"`
BaseDn string `mapstructure:"base-dn"`
BindDn string `mapstructure:"bind-dn"`
BindPassword string `mapstructure:"bind-password"`
User LdapUser `mapstructure:"user"`
Enable bool `mapstructure:"enable"`
Url string `mapstructure:"url"`
TLS bool `mapstructure:"tls"`
TlsVerify bool `mapstructure:"tls-verify"`
BaseDn string `mapstructure:"base-dn"`
BindDn string `mapstructure:"bind-dn"`
BindPassword string `mapstructure:"bind-password"`
User LdapUser `mapstructure:"user"`
// Group LdapGroup `mapstructure:"group"`
}
}

View File

@@ -17,4 +17,4 @@ type OidcOauth struct {
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"`
}
}

View File

@@ -1783,7 +1783,7 @@ const docTemplateadmin = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/Gwen_http_request_admin.Login"
"$ref": "#/definitions/github_com_lejianwen_rustdesk-api_http_request_admin.Login"
}
}
],
@@ -5104,27 +5104,6 @@ const docTemplateadmin = `{
}
},
"definitions": {
"Gwen_http_request_admin.Login": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"captcha": {
"type": "string"
},
"password": {
"type": "string"
},
"platform": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"admin.AddressBookForm": {
"type": "object",
"required": [
@@ -5542,6 +5521,27 @@ const docTemplateadmin = `{
}
}
},
"github_com_lejianwen_rustdesk-api_http_request_admin.Login": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"captcha": {
"type": "string"
},
"password": {
"type": "string"
},
"platform": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"model.AddressBook": {
"type": "object",
"properties": {

View File

@@ -1776,7 +1776,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/Gwen_http_request_admin.Login"
"$ref": "#/definitions/github_com_lejianwen_rustdesk-api_http_request_admin.Login"
}
}
],
@@ -5097,27 +5097,6 @@
}
},
"definitions": {
"Gwen_http_request_admin.Login": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"captcha": {
"type": "string"
},
"password": {
"type": "string"
},
"platform": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"admin.AddressBookForm": {
"type": "object",
"required": [
@@ -5535,6 +5514,27 @@
}
}
},
"github_com_lejianwen_rustdesk-api_http_request_admin.Login": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"captcha": {
"type": "string"
},
"password": {
"type": "string"
},
"platform": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"model.AddressBook": {
"type": "object",
"properties": {

View File

@@ -1,19 +1,5 @@
basePath: /api
definitions:
Gwen_http_request_admin.Login:
properties:
captcha:
type: string
password:
type: string
platform:
type: string
username:
type: string
required:
- password
- username
type: object
admin.AddressBookForm:
properties:
alias:
@@ -292,6 +278,20 @@ definitions:
required:
- ids
type: object
github_com_lejianwen_rustdesk-api_http_request_admin.Login:
properties:
captcha:
type: string
password:
type: string
platform:
type: string
username:
type: string
required:
- password
- username
type: object
model.AddressBook:
properties:
alias:
@@ -1830,7 +1830,7 @@ paths:
name: body
required: true
schema:
$ref: '#/definitions/Gwen_http_request_admin.Login'
$ref: '#/definitions/github_com_lejianwen_rustdesk-api_http_request_admin.Login'
produces:
- application/json
responses:

View File

@@ -1,4 +1,4 @@
package Gwen
package main
//go:generate swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
//go:generate swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api

View File

@@ -1,3 +1,3 @@
package Gwen
package main
//go:generate go run cmd/apimain.go

View File

@@ -1,15 +1,15 @@
package global
import (
"Gwen/config"
"Gwen/lib/cache"
"Gwen/lib/jwt"
"Gwen/lib/lock"
"Gwen/lib/upload"
"github.com/gin-gonic/gin"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
"github.com/go-redis/redis/v8"
"github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/lib/cache"
"github.com/lejianwen/rustdesk-api/v2/lib/jwt"
"github.com/lejianwen/rustdesk-api/v2/lib/lock"
"github.com/lejianwen/rustdesk-api/v2/lib/upload"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"

View File

@@ -15,7 +15,6 @@ func InitI18n() {
fileInfos, err := os.ReadDir(dir)
if err != nil {
panic(err)
return
}
for _, fileInfo := range fileInfos {
//如果文件名不是.toml结尾

2
go.mod
View File

@@ -1,4 +1,4 @@
module Gwen
module github.com/lejianwen/rustdesk-api/v2
go 1.22

View File

@@ -1,13 +1,13 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"encoding/json"
_ "encoding/json"
"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"
"gorm.io/gorm"
"strconv"
)

View File

@@ -1,12 +1,12 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)

View File

@@ -1,12 +1,12 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)

View File

@@ -1,12 +1,12 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)

View File

@@ -1,10 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/response"
"Gwen/service"
"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,7 +61,22 @@ func (co *Config) AppConfig(c *gin.Context) {
// @Security token
func (co *Config) AdminConfig(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
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

@@ -1,11 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/response"
"Gwen/lib/upload"
"fmt"
"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/lib/upload"
"os"
"time"
)

View File

@@ -1,11 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"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"
)

View File

@@ -1,16 +1,16 @@
package admin
import (
"Gwen/global"
"Gwen/http/controller/api"
"Gwen/http/request/admin"
apiReq "Gwen/http/request/api"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service"
"fmt"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/controller/api"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
apiReq "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/mojocn/base64Captcha"
"sync"
"time"
@@ -152,6 +152,10 @@ var loginLimiter = NewLoginLimiter(3, 5*time.Minute)
// @Router /admin/login [post]
// @Security token
func (ct *Login) Login(c *gin.Context) {
if global.Config.App.DisablePwdLogin {
response.Fail(c, 101, response.TranslateMsg(c, "PwdLoginDisabled"))
return
}
f := &admin.Login{}
err := c.ShouldBindJSON(f)
clientIp := c.ClientIP()
@@ -182,15 +186,20 @@ func (ct *Login) Login(c *gin.Context) {
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp))
loginLimiter.RecordFailure(clientIp)
if loginLimiter.NeedsCaptcha(clientIp) {
// 移除原验证码,重新生成
loginLimiter.RemoveCaptcha(clientIp)
response.Fail(c, 110, response.TranslateMsg(c, "UsernameOrPasswordError"))
return
}
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
return
}
if !service.AllService.UserService.CheckUserEnable(u) {
if loginLimiter.NeedsCaptcha(clientIp) {
loginLimiter.RemoveCaptcha(clientIp)
}
response.Fail(c, 101, response.TranslateMsg(c, "UserDisabled"))
return
}
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: model.LoginLogClientWebAdmin,
@@ -274,13 +283,13 @@ func (ct *Login) OidcAuth(c *gin.Context) {
return
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
err, state, verifier, 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,
@@ -288,10 +297,11 @@ func (ct *Login) OidcAuth(c *gin.Context) {
// DeviceOs: ct.Platform(c),
DeviceOs: f.DeviceInfo.Os,
Uuid: f.Uuid,
Verifier: verifier,
}, 5*60)
response.Success(c, gin.H{
"code": code,
"code": state,
"url": url,
})
}

View File

@@ -1,12 +1,12 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)

View File

@@ -1,12 +1,12 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"encoding/json"
"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"
"gorm.io/gorm"
)

View File

@@ -1,12 +1,12 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)

View File

@@ -1,12 +1,12 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)

View File

@@ -1,12 +1,12 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)

View File

@@ -1,10 +1,10 @@
package my
import (
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"time"
)

View File

@@ -1,11 +1,11 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"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"
"gorm.io/gorm"
)

View File

@@ -1,11 +1,11 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"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"
"gorm.io/gorm"
)

View File

@@ -1,12 +1,12 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
adminReq "Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
adminReq "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"
)
@@ -43,20 +43,21 @@ func (o *Oauth) ToBind(c *gin.Context) {
return
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
err, state, verifier, 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.OauthActionTypeBind,
Op: f.Op,
UserId: u.Id,
Op: f.Op,
UserId: u.Id,
Verifier: verifier,
}, 5*60)
response.Success(c, gin.H{
"code": code,
"code": state,
"url": url,
})
}

View File

@@ -1,11 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"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"
"gorm.io/gorm"
"strconv"
"time"

View File

@@ -1,12 +1,12 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
)
type Rustdesk struct {

View File

@@ -1,11 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"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"
"gorm.io/gorm"
)

View File

@@ -1,11 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"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"
"gorm.io/gorm"
"strconv"
)

View File

@@ -1,13 +1,13 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service"
"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"
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)

View File

@@ -1,12 +1,12 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"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/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)

View File

@@ -1,16 +1,16 @@
package api
import (
"Gwen/global"
requstform "Gwen/http/request/api"
"Gwen/http/response"
"Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"Gwen/utils"
"encoding/json"
"errors"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"net/http"
"strconv"
"strings"

View File

@@ -1,12 +1,12 @@
package api
import (
request "Gwen/http/request/api"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
request "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"time"
)

View File

@@ -1,12 +1,12 @@
package api
import (
apiReq "Gwen/http/request/api"
"Gwen/http/response"
apiResp "Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
apiReq "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http"
)

View File

@@ -1,11 +1,11 @@
package api
import (
requstform "Gwen/http/request/api"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http"
"os"
"time"

View File

@@ -1,15 +1,15 @@
package api
import (
"Gwen/global"
"Gwen/http/request/api"
"Gwen/http/response"
apiResp "Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http"
)
@@ -27,6 +27,10 @@ type Login struct {
// @Failure 500 {object} response.ErrorResponse
// @Router /login [post]
func (l *Login) Login(c *gin.Context) {
if global.Config.App.DisablePwdLogin {
response.Error(c, response.TranslateMsg(c, "PwdLoginDisabled"))
return
}
f := &api.LoginForm{}
err := c.ShouldBindJSON(f)
//fmt.Println(f)
@@ -51,6 +55,11 @@ func (l *Login) Login(c *gin.Context) {
return
}
if !service.AllService.UserService.CheckUserEnable(u) {
response.Error(c, response.TranslateMsg(c, "UserDisabled"))
return
}
//根据refer判断是webclient还是app
ref := c.GetHeader("referer")
if ref != "" {

View File

@@ -1,13 +1,13 @@
package api
import (
"Gwen/global"
"Gwen/http/request/api"
"Gwen/http/response"
apiResp "Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http"
)
@@ -32,15 +32,16 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
}
oauthService := service.AllService.OauthService
var code string
var state string
var url string
err, code, url = oauthService.BeginAuth(f.Op)
var verifier string
err, state, verifier, 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 +49,11 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
DeviceName: f.DeviceInfo.Name,
DeviceOs: f.DeviceInfo.Os,
DeviceType: f.DeviceInfo.Type,
Verifier: verifier,
}, 5*60)
//fmt.Println("code url", code, url)
c.JSON(http.StatusOK, gin.H{
"code": code,
"code": state,
"url": url,
})
}
@@ -156,10 +158,11 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
}
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)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
return

View File

@@ -1,11 +1,11 @@
package api
import (
requstform "Gwen/http/request/api"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http"
)

View File

@@ -1,9 +1,9 @@
package api
import (
apiResp "Gwen/http/response/api"
"Gwen/service"
"github.com/gin-gonic/gin"
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http"
)

View File

@@ -1,11 +1,11 @@
package api
import (
"Gwen/global"
"Gwen/http/response"
"Gwen/http/response/api"
"Gwen/service"
"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/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/service"
"time"
)

View File

@@ -1,8 +1,8 @@
package web
import (
"Gwen/global"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"strconv"
)
@@ -23,5 +23,6 @@ localStorage.setItem(ws2_prefix+'api-server', "` + apiServer + `")
window.webclient_magic_queryonline = ` + magicQueryonline + ``
c.Header("Content-Type", "application/javascript")
c.String(200, tmp)
}

View File

@@ -1,10 +1,10 @@
package http
import (
"Gwen/global"
"Gwen/http/middleware"
"Gwen/http/router"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/middleware"
"github.com/lejianwen/rustdesk-api/v2/http/router"
"github.com/sirupsen/logrus"
"net/http"
"strings"

View File

@@ -1,13 +1,13 @@
package middleware
import (
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
)
// AdminAuth 后台权限验证中间件
func AdminAuth() gin.HandlerFunc {
// BackendUserAuth 后台权限验证中间件
func BackendUserAuth() gin.HandlerFunc {
return func(c *gin.Context) {
//测试先关闭
@@ -24,6 +24,14 @@ func AdminAuth() gin.HandlerFunc {
return
}
if !service.AllService.UserService.CheckUserEnable(user) {
c.JSON(401, gin.H{
"error": "Unauthorized",
})
c.Abort()
return
}
c.Set("curUser", user)
c.Set("token", token)
//如果时间小于1天,token自动续期

View File

@@ -1,9 +1,9 @@
package middleware
import (
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
)
// AdminPrivilege ...

View File

@@ -1,10 +1,10 @@
package middleware
import (
"Gwen/global"
"Gwen/http/response"
"Gwen/service"
"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/service"
)
func JwtAuth() gin.HandlerFunc {

View File

@@ -1,8 +1,8 @@
package middleware
import (
"Gwen/global"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/sirupsen/logrus"
)

View File

@@ -1,9 +1,9 @@
package middleware
import (
"Gwen/global"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/service"
)
func RustAuth() gin.HandlerFunc {

View File

@@ -1,8 +1,8 @@
package admin
import (
"Gwen/model"
"encoding/json"
"github.com/lejianwen/rustdesk-api/v2/model"
)
type AddressBookForm struct {

View File

@@ -1,6 +1,6 @@
package admin
import "Gwen/model"
import "github.com/lejianwen/rustdesk-api/v2/model"
type GroupForm struct {
Id uint `json:"id"`

View File

@@ -1,7 +1,7 @@
package admin
import (
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/model"
)
type BindOauthForm struct {
@@ -15,27 +15,31 @@ type UnBindOauthForm struct {
Op string `json:"op" binding:"required"`
}
type OauthForm struct {
Id uint `json:"id"`
Op string `json:"op" validate:"omitempty"`
OauthType string `json:"oauth_type" validate:"required"`
Issuer string `json:"issuer" validate:"omitempty,url"`
Scopes string `json:"scopes" validate:"omitempty"`
ClientId string `json:"client_id" validate:"required"`
ClientSecret string `json:"client_secret" validate:"required"`
RedirectUrl string `json:"redirect_url" validate:"required"`
AutoRegister *bool `json:"auto_register"`
Id uint `json:"id"`
Op string `json:"op" validate:"omitempty"`
OauthType string `json:"oauth_type" validate:"required"`
Issuer string `json:"issuer" validate:"omitempty,url"`
Scopes string `json:"scopes" validate:"omitempty"`
ClientId string `json:"client_id" validate:"required"`
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 {
oa := &model.Oauth{
Op: of.Op,
OauthType: of.OauthType,
OauthType: of.OauthType,
ClientId: of.ClientId,
ClientSecret: of.ClientSecret,
RedirectUrl: of.RedirectUrl,
AutoRegister: of.AutoRegister,
Issuer: of.Issuer,
Scopes: of.Scopes,
PkceEnable: of.PkceEnable,
PkceMethod: of.PkceMethod,
}
oa.Id = of.Id
return oa

View File

@@ -1,6 +1,6 @@
package admin
import "Gwen/model"
import "github.com/lejianwen/rustdesk-api/v2/model"
type PeerForm struct {
RowId uint `json:"row_id" `

View File

@@ -1,6 +1,6 @@
package admin
import "Gwen/model"
import "github.com/lejianwen/rustdesk-api/v2/model"
type TagForm struct {
Id uint `json:"id"`

View File

@@ -1,7 +1,7 @@
package admin
import (
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/model"
)
type UserForm struct {

View File

@@ -1,9 +1,9 @@
package api
import (
"Gwen/global"
"Gwen/model"
"encoding/json"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"strconv"
)

View File

@@ -1,6 +1,6 @@
package api
import "Gwen/model"
import "github.com/lejianwen/rustdesk-api/v2/model"
type AddressBookFormData struct {
Tags []string `json:"tags"`

View File

@@ -1,6 +1,6 @@
package admin
import "Gwen/model"
import "github.com/lejianwen/rustdesk-api/v2/model"
type LoginPayload struct {
Username string `json:"username"`

View File

@@ -1,6 +1,6 @@
package api
import "Gwen/model"
import "github.com/lejianwen/rustdesk-api/v2/model"
type AbList struct {
Peers []*model.AddressBook `json:"peers,omitempty"`

View File

@@ -1,6 +1,6 @@
package api
import "Gwen/model"
import "github.com/lejianwen/rustdesk-api/v2/model"
/*
GroupPeerPayload

View File

@@ -1,6 +1,6 @@
package api
import "Gwen/model"
import "github.com/lejianwen/rustdesk-api/v2/model"
/*
pub enum UserStatus {

View File

@@ -1,7 +1,7 @@
package api
import (
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/model"
"time"
)

View File

@@ -1,9 +1,9 @@
package response
import (
"Gwen/global"
"fmt"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/nicksnyder/go-i18n/v2/i18n"
"net/http"
)

View File

@@ -1,12 +1,12 @@
package router
import (
_ "Gwen/docs/admin"
"Gwen/global"
"Gwen/http/controller/admin"
"Gwen/http/controller/admin/my"
"Gwen/http/middleware"
"github.com/gin-gonic/gin"
_ "github.com/lejianwen/rustdesk-api/v2/docs/admin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/controller/admin"
"github.com/lejianwen/rustdesk-api/v2/http/controller/admin/my"
"github.com/lejianwen/rustdesk-api/v2/http/middleware"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
@@ -22,7 +22,10 @@ func Init(g *gin.Engine) {
adg := g.Group("/api/admin")
LoginBind(adg)
adg.POST("/user/register", (&admin.User{}).Register)
adg.Use(middleware.AdminAuth())
ConfigBind(adg)
adg.Use(middleware.BackendUserAuth())
//FileBind(adg)
UserBind(adg)
GroupBind(adg)
@@ -35,7 +38,6 @@ func Init(g *gin.Engine) {
AddressBookCollectionBind(adg)
AddressBookCollectionRuleBind(adg)
UserTokenBind(adg)
ConfigBind(adg)
//deprecated by ConfigBind
//rs := &admin.Rustdesk{}
@@ -221,9 +223,13 @@ func UserTokenBind(rg *gin.RouterGroup) {
func ConfigBind(rg *gin.RouterGroup) {
aR := rg.Group("/config")
rs := &admin.Config{}
aR.GET("/admin", rs.AdminConfig)
aR.Use(middleware.BackendUserAuth())
aR.GET("/server", rs.ServerConfig)
aR.GET("/app", rs.AppConfig)
aR.GET("/admin", rs.AdminConfig)
}
/*

View File

@@ -1,11 +1,11 @@
package router
import (
_ "Gwen/docs/api"
"Gwen/global"
"Gwen/http/controller/api"
"Gwen/http/middleware"
"github.com/gin-gonic/gin"
_ "github.com/lejianwen/rustdesk-api/v2/docs/api"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/controller/api"
"github.com/lejianwen/rustdesk-api/v2/http/middleware"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"net/http"

View File

@@ -1,9 +1,9 @@
package router
import (
"Gwen/global"
"Gwen/http/controller/web"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/controller/web"
"net/http"
)

View File

@@ -1,8 +1,8 @@
package orm
import (
"Gwen/global"
"fmt"
"github.com/lejianwen/rustdesk-api/v2/global"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"

View File

@@ -1,8 +1,8 @@
package orm
import (
"Gwen/global"
"fmt"
"github.com/lejianwen/rustdesk-api/v2/global"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"

View File

@@ -1,6 +1,6 @@
package model
import "Gwen/model/custom_types"
import "github.com/lejianwen/rustdesk-api/v2/model/custom_types"
// final String id;
// String hash; // personal ab hash password

View File

@@ -1,7 +1,7 @@
package model
import (
"Gwen/model/custom_types"
"github.com/lejianwen/rustdesk-api/v2/model/custom_types"
)
type StatusCode int

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

@@ -2,11 +2,11 @@ package model
type UserToken struct {
IdModel
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
DeviceUuid string `json:"device_uuid" gorm:"default:'';omitempty;"`
DeviceId string `json:"device_id" gorm:"default:'';omitempty;"`
Token string `json:"token" gorm:"default:'';not null;index"`
ExpiredAt int64 `json:"expired_at" gorm:"default:0;not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
DeviceUuid string `json:"device_uuid" gorm:"default:'';omitempty;"`
DeviceId string `json:"device_id" gorm:"default:'';omitempty;"`
Token string `json:"token" gorm:"default:'';not null;index"`
ExpiredAt int64 `json:"expired_at" gorm:"default:0;not null;"`
TimeModel
}

View File

@@ -133,3 +133,8 @@ other = "Captcha required."
description = "Captcha error."
one = "Captcha error."
other = "Captcha error."
[PwdLoginDisabled]
description = "Password login disabled."
one = "Password login disabled."
other = "Password login disabled."

View File

@@ -141,4 +141,9 @@ other = "Captcha requerido."
[CaptchaError]
description = "Captcha error."
one = "Error de captcha."
other = "Error de captcha."
other = "Error de captcha."
[PwdLoginDisabled]
description = "Password login disabled."
one = "Inicio de sesión con contraseña deshabilitado."
other = "Inicio de sesión con contraseña deshabilitado."

View File

@@ -142,3 +142,8 @@ other = "Captcha requis."
description = "Captcha error."
one = "Erreur de captcha."
other = "Erreur de captcha."
[PwdLoginDisabled]
description = "Password login disabled."
one = "Connexion par mot de passe désactivée."
other = "Connexion par mot de passe désactivée."

View File

@@ -135,4 +135,9 @@ other = "Captcha가 필요합니다."
[CaptchaError]
description = "Captcha error."
one = "Captcha 오류."
other = "Captcha 오류."
other = "Captcha 오류."
[PwdLoginDisabled]
description = "Password login disabled."
one = "비밀번호 로그인이 비활성화되었습니다."
other = "비밀번호 로그인이 비활성화되었습니다."

View File

@@ -141,4 +141,9 @@ other = "Требуется капча."
[CaptchaError]
description = "Captcha error."
one = "Ошибка капчи."
other = "Ошибка капчи."
other = "Ошибка капчи."
[PwdLoginDisabled]
description = "Password login disabled."
one = "Вход по паролю отключен."
other = "Вход по паролю отключен."

View File

@@ -134,4 +134,9 @@ other = "需要验证码。"
[CaptchaError]
description = "Captcha error."
one = "验证码错误。"
other = "验证码错误。"
other = "验证码错误。"
[PwdLoginDisabled]
description = "Password login disabled."
one = "密码登录已禁用。"
other = "密码登录已禁用。"

View File

@@ -134,4 +134,9 @@ other = "需要驗證碼。"
[CaptchaError]
description = "Captcha error."
one = "驗證碼錯誤。"
other = "驗證碼錯誤。"
other = "驗證碼錯誤。"
[PwdLoginDisabled]
description = "Password login disabled."
one = "密碼登錄已禁用。"
other = "密碼登錄已禁用。"

View File

@@ -11090,16 +11090,24 @@ function R4(u = !1) {
function getUriFromRs(uri, isRelay = false, roffset = 0) {
const p = isHttps() ? "wss://" : "ws://"
const [domain, uriport] = uri.split(":")
if (isHttps() && (!uriport)) {
return p + domain + "/ws/" + (isRelay ? "relay" : "id");
if (!isHttps()) {
// http 直接走端口
if (uriport) {
const port = parseInt(uriport)
return p + domain + ":" + (port + (isRelay ? roffset || 3 : 2))
}
return p + domain + ":" + (defaultIdServerPort + (isRelay ? 3 : 2))
}
if (uriport) {
const port = parseInt(uriport);
uri = domain + ":" + (port + (isRelay ? roffset || 3 : 2))
} else uri += ":" + (defaultIdServerPort + (isRelay ? 3 : 2));
return p + uri
// https 分情况
if (!window.location.port) {
// 443
return p + domain + "/ws/" + (isRelay ? "relay" : "id")
}
// 非443
return p + domain + ":" + window.location.port + "/ws/" + (isRelay ? "relay" : "id")
}
function isHttps() {
return window.location.protocol === "https:"
}

View File

@@ -1,10 +1,10 @@
package service
import (
"Gwen/global"
"Gwen/model"
"encoding/json"
"github.com/google/uuid"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
"strings"
)

View File

@@ -1,8 +1,8 @@
package service
import (
"Gwen/global"
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)

View File

@@ -1,8 +1,8 @@
package service
import (
"Gwen/global"
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)

View File

@@ -4,13 +4,28 @@ import (
"crypto/tls"
"errors"
"fmt"
"github.com/go-ldap/ldap/v3"
"strconv"
"strings"
"Gwen/config"
"Gwen/global"
"Gwen/model"
"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 (
ErrLdapNotEnabled = errors.New("LdapNotEnabled")
ErrLdapUserDisabled = errors.New("UserDisabledAtLdap")
ErrLdapUserNotFound = errors.New("UserNotFound")
ErrLdapMailNotMatch = errors.New("MailNotMatch")
ErrLdapConnectFailed = errors.New("LdapConnectFailed")
ErrLdapSearchFailed = errors.New("LdapSearchRequestFailed")
ErrLdapTlsFailed = errors.New("LdapStartTLSFailed")
ErrLdapBindService = errors.New("LdapBindServiceFailed")
ErrLdapBindFailed = errors.New("LdapBindFailed")
ErrLdapToLocalUserFailed = errors.New("LdapToLocalUserFailed")
ErrLdapCreateUserFailed = errors.New("LdapCreateUserFailed")
)
// LdapService is responsible for LDAP authentication and user synchronization.
@@ -43,6 +58,11 @@ func (lu *LdapUser) ToUser(u *model.User) *model.User {
u.Username = lu.Username
u.Email = lu.Email
u.Nickname = lu.Name()
if lu.Enabled {
u.Status = model.COMMON_STATUS_ENABLE
} else {
u.Status = model.COMMON_STATUS_DISABLED
}
return u
}
@@ -50,21 +70,21 @@ func (lu *LdapUser) ToUser(u *model.User) *model.User {
func (ls *LdapService) connectAndBind(cfg *config.Ldap, username, password string) (*ldap.Conn, error) {
conn, err := ldap.DialURL(cfg.Url)
if err != nil {
return nil, fmt.Errorf("failed to dial LDAP: %w", err)
return nil, errors.Join(ErrLdapConnectFailed, err)
}
if cfg.TLS {
// WARNING: InsecureSkipVerify: true is not recommended for production
if err = conn.StartTLS(&tls.Config{InsecureSkipVerify: !cfg.TlsVerify}); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to start TLS: %w", err)
return nil, errors.Join(ErrLdapTlsFailed, err)
}
}
// Bind as the "service" user
if err = conn.Bind(username, password); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to bind with service account: %w", err)
return nil, errors.Join(ErrLdapBindService, err)
}
return conn, nil
}
@@ -87,29 +107,17 @@ func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password st
// Authenticate checks the provided username and password against LDAP.
// Returns the corresponding *model.User if successful, or an error if not.
func (ls *LdapService) Authenticate(username, password string) (*model.User, error) {
cfg := &global.Config.Ldap
// 1. Use a service bind to search for the user DN
sr, err := ls.usernameSearchResult(cfg, username)
ldapUser, err := ls.GetUserInfoByUsernameLdap(username)
if err != nil {
return nil, fmt.Errorf("LDAP search request failed: %w", err)
return nil, err
}
if len(sr.Entries) != 1 {
return nil, errors.New("user does not exist or too many entries returned")
}
entry := sr.Entries[0]
userDN := entry.DN
err = ls.verifyCredentials(cfg, userDN, password)
if err != nil {
return nil, fmt.Errorf("LDAP authentication failed: %w", err)
}
ldapUser := ls.userResultToLdapUser(cfg, entry)
if !ldapUser.Enabled {
return nil, errors.New("UserDisabledAtLdap")
return nil, ErrLdapUserDisabled
}
cfg := &global.Config.Ldap
user, err := ls.mapToLocalUser(cfg, ldapUser)
if err != nil {
return nil, fmt.Errorf("failed to map LDAP user to local user: %w", err)
return nil, errors.Join(ErrLdapToLocalUserFailed, err)
}
return user, nil
}
@@ -126,8 +134,9 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
// Typically, you dont store LDAP user passwords locally.
// If needed, you can set a random password here.
newUser.IsAdmin = &isAdmin
newUser.GroupId = 1
if err := global.DB.Create(newUser).Error; err != nil {
return nil, fmt.Errorf("failed to create new user: %w", err)
return nil, errors.Join(ErrLdapCreateUserFailed, err)
}
return userService.InfoByUsername(lu.Username), nil
}
@@ -137,6 +146,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
originalEmail := localUser.Email
originalNickname := localUser.Nickname
originalIsAdmin := localUser.IsAdmin
originalStatus := localUser.Status
lu.ToUser(localUser) // merges LDAP data into the existing user
localUser.IsAdmin = &isAdmin
if err := userService.Update(localUser); err != nil {
@@ -144,6 +154,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
localUser.Email = originalEmail
localUser.Nickname = originalNickname
localUser.IsAdmin = originalIsAdmin
localUser.Status = originalStatus
}
}
@@ -177,6 +188,56 @@ func (ls *LdapService) IsEmailExists(email string) bool {
return len(sr.Entries) > 0
}
// GetUserInfoByUsernameLdap returns the user info from LDAP for the given username.
func (ls *LdapService) GetUserInfoByUsernameLdap(username string) (*LdapUser, error) {
cfg := &global.Config.Ldap
if !cfg.Enable {
return nil, ErrLdapNotEnabled
}
sr, err := ls.usernameSearchResult(cfg, username)
if err != nil {
return nil, errors.Join(ErrLdapSearchFailed, err)
}
if len(sr.Entries) != 1 {
return nil, ErrLdapUserNotFound
}
return ls.userResultToLdapUser(cfg, sr.Entries[0]), nil
}
// GetUserInfoByUsernameLocal returns the user info from LDAP for the given username. If the user exists, it will sync the user info to the local database.
func (ls *LdapService) GetUserInfoByUsernameLocal(username string) (*model.User, error) {
ldapUser, err := ls.GetUserInfoByUsernameLdap(username)
if err != nil {
return &model.User{}, err
}
return ls.mapToLocalUser(&global.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
if !cfg.Enable {
return nil, ErrLdapNotEnabled
}
sr, err := ls.emailSearchResult(cfg, email)
if err != nil {
return nil, errors.Join(ErrLdapSearchFailed, err)
}
if len(sr.Entries) != 1 {
return nil, ErrLdapUserNotFound
}
return ls.userResultToLdapUser(cfg, sr.Entries[0]), nil
}
// GetUserInfoByEmailLocal returns the user info from LDAP for the given email. if the user exists, it will synchronize the user information to local database.
func (ls *LdapService) GetUserInfoByEmailLocal(email string) (*model.User, error) {
ldapUser, err := ls.GetUserInfoByEmailLdap(email)
if err != nil {
return &model.User{}, err
}
return ls.mapToLocalUser(&global.Config.Ldap, ldapUser)
}
// usernameSearchResult returns the search result for the given username.
func (ls *LdapService) usernameSearchResult(cfg *config.Ldap, username string) (*ldap.SearchResult, error) {
// Build the combined filter for the username

View File

@@ -1,8 +1,8 @@
package service
import (
"Gwen/global"
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)

View File

@@ -1,12 +1,12 @@
package service
import (
"Gwen/global"
"Gwen/model"
"Gwen/utils"
"context"
"encoding/json"
"errors"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/utils"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
// "golang.org/x/oauth2/google"
@@ -45,6 +45,7 @@ type OauthCacheItem struct {
Username string `json:"username"`
Name string `json:"name"`
Email string `json:"email"`
Verifier string `json:"verifier"` // used for oauth pkce
}
func (oci *OauthCacheItem) ToOauthUser() *model.OauthUser {
@@ -92,19 +93,32 @@ 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)
func (os *OauthService) BeginAuth(op string) (error error, state, verifier, url string) {
state = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10)
verifier = ""
if op == string(model.OauthTypeWebauth) {
url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + code
url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + state
//url = "http://localhost:8888/_admin/#/oauth/" + code
return nil, code, url
return nil, state, verifier, 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)
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, oauthConfig.AuthCodeURL(state, extras...)
}
return err, code, ""
return err, state, verifier, ""
}
// Method to fetch OIDC configuration dynamically
@@ -207,15 +221,20 @@ func getHTTPClientWithProxy() *http.Client {
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, code string, verifier string, userEndpoint string, userData interface{}) (err error, client *http.Client) {
// 设置代理客户端
httpClient := getHTTPClientWithProxy()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
var exchangeOpts []oauth2.AuthCodeOption
if verifier != "" {
exchangeOpts = []oauth2.AuthCodeOption{oauth2.VerifierOption(verifier)}
}
// 使用 code 换取 token
var token *oauth2.Token
token, err = oauthConfig.Exchange(ctx, code)
token, err = oauthConfig.Exchange(ctx, code, exchangeOpts...)
if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
return errors.New("GetOauthTokenError"), nil
@@ -244,9 +263,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, code string, verifier string) (error, *model.OauthUser) {
var user = &model.GithubUser{}
err, client := os.callbackBase(oauthConfig, code, model.UserEndpointGithub, user)
err, client := os.callbackBase(oauthConfig, code, verifier, model.UserEndpointGithub, user)
if err != nil {
return err, nil
}
@@ -258,16 +277,16 @@ 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, code string, verifier string, userInfoEndpoint string) (error, *model.OauthUser) {
var user = &model.OidcUser{}
if err, _ := os.callbackBase(oauthConfig, code, userInfoEndpoint, user); err != nil {
if err, _ := os.callbackBase(oauthConfig, code, verifier, userInfoEndpoint, 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) {
func (os *OauthService) Callback(code, verifier, op string) (err error, oauthUser *model.OauthUser) {
var oauthInfo *model.Oauth
var oauthConfig *oauth2.Config
err, oauthInfo, oauthConfig = os.GetOauthConfig(op)
@@ -278,13 +297,13 @@ 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, code, verifier)
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, code, verifier, endpoint.UserInfo)
default:
return errors.New("unsupported OAuth type"), nil
}

View File

@@ -1,8 +1,8 @@
package service
import (
"Gwen/global"
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)

View File

@@ -1,9 +1,9 @@
package service
import (
"Gwen/global"
"Gwen/model"
"fmt"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"net"
"time"
)

View File

@@ -1,7 +1,7 @@
package service
import (
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)

View File

@@ -1,8 +1,8 @@
package service
import (
"Gwen/global"
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)

View File

@@ -1,8 +1,8 @@
package service
import (
"Gwen/global"
"Gwen/model"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"gorm.io/gorm"
)

View File

@@ -1,16 +1,17 @@
package service
import (
"Gwen/global"
"Gwen/model"
"Gwen/utils"
"errors"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/utils"
"math/rand"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type UserService struct {
@@ -51,7 +52,7 @@ func (us *UserService) InfoByUsernamePassword(username, password string) *model.
if err == nil {
return u
}
global.Logger.Error("LDAP authentication failed, %v", err)
global.Logger.Errorf("LDAP authentication failed, %v", err)
global.Logger.Warn("Fallback to local database")
}
u := &model.User{}
@@ -279,7 +280,7 @@ func (us *UserService) UpdatePassword(u *model.User, password string) error {
// IsAdmin 是否管理员
func (us *UserService) IsAdmin(u *model.User) bool {
return *u.IsAdmin
return u != nil && *u.IsAdmin
}
// RouteNames
@@ -322,7 +323,16 @@ func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (e
email = strings.ToLower(email)
// update email to oauthUser, in case it contain upper case
oauthUser.Email = email
user := us.InfoByEmail(email)
// call this, if find user by email, it will update the email to local database
user, ldapErr := AllService.LdapService.GetUserInfoByEmailLocal(email)
// If we enable ldap, and the error is not ErrLdapUserNotFound, return the error because we could not sure if the user is not found in ldap
if !(errors.Is(ldapErr, ErrLdapNotEnabled) || errors.Is(ldapErr, ErrLdapUserNotFound) || ldapErr == nil) {
return ldapErr, user
}
if user.Id == 0 {
// this means the user is not found in ldap, maybe ldao is not enabled
user = us.InfoByEmail(email)
}
if user.Id != 0 {
ut.FromOauthUser(user.Id, oauthUser, oauthType, op)
global.DB.Create(ut)
@@ -466,9 +476,10 @@ func (us *UserService) getAdminUserCount() int64 {
func (us *UserService) UserTokenExpireTimestamp() int64 {
exp := global.Config.App.TokenExpire
if exp == 0 {
exp = 3600 * 24 * 7
//默认七天
exp = 604800
}
return time.Now().Add(time.Second * time.Duration(exp)).Unix()
return time.Now().Add(exp).Unix()
}
func (us *UserService) RefreshAccessToken(ut *model.UserToken) {
@@ -491,8 +502,15 @@ func (us *UserService) VerifyJWT(token string) (uint, error) {
// IsUsernameExists 判断用户名是否存在, it will check the internal database and LDAP(if enabled)
func (us *UserService) IsUsernameExists(username string) bool {
return us.IsUsernameExistsLocal(username) || AllService.LdapService.IsUsernameExists(username)
}
func (us *UserService) IsUsernameExistsLocal(username string) bool {
u := &model.User{}
global.DB.Where("username = ?", username).First(u)
existsInLdap := AllService.LdapService.IsUsernameExists(username)
return u.Id != 0 || existsInLdap
return u.Id != 0
}
func (us *UserService) IsEmailExistsLdap(email string) bool {
return AllService.LdapService.IsEmailExists(email)
}