mirror of
https://github.com/lejianwen/rustdesk-api.git
synced 2026-02-16 11:10:53 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e318f1fd58 | ||
|
|
ee19eb9729 | ||
|
|
fcdea1ad6d | ||
|
|
c88d8cc359 | ||
|
|
ade6e6355a | ||
|
|
9b769b99dc | ||
|
|
c14c4d478b | ||
|
|
9d08c61390 | ||
|
|
6f092472b1 | ||
|
|
4876746f7a | ||
|
|
05d2d1642a | ||
|
|
59fdd6424b | ||
|
|
0feee5115f | ||
|
|
65f0a9e3cf | ||
|
|
77836a4e56 | ||
|
|
09f8316bf1 | ||
|
|
c52706e621 | ||
|
|
17dcff4f43 | ||
|
|
0b39c4e104 | ||
|
|
ee176b314e | ||
|
|
1ffc9c4a5b | ||
|
|
1257246552 |
@@ -42,11 +42,11 @@ RUN if [ "$COUNTRY" = "CN" ] ; then \
|
|||||||
fi && \
|
fi && \
|
||||||
apk update && apk add --no-cache git
|
apk update && apk add --no-cache git
|
||||||
|
|
||||||
ARG FREONTEND_GIT_REPO=https://github.com/lejianwen/rustdesk-api-web.git
|
ARG FRONTEND_GIT_REPO=https://github.com/lejianwen/rustdesk-api-web.git
|
||||||
ARG FRONTEND_GIT_BRANCH=master
|
ARG FRONTEND_GIT_BRANCH=master
|
||||||
# Clone the frontend repository
|
# Clone the frontend repository
|
||||||
|
|
||||||
RUN git clone -b $FRONTEND_GIT_BRANCH $FREONTEND_GIT_REPO .
|
RUN git clone -b $FRONTEND_GIT_BRANCH $FRONTEND_GIT_REPO .
|
||||||
|
|
||||||
# Install required tools without caching index to minimize image size
|
# Install required tools without caching index to minimize image size
|
||||||
RUN if [ "$COUNTRY" = "CN" ] ; then \
|
RUN if [ "$COUNTRY" = "CN" ] ; then \
|
||||||
@@ -91,4 +91,4 @@ VOLUME /app/data
|
|||||||
EXPOSE 21114
|
EXPOSE 21114
|
||||||
|
|
||||||
# Define the command to run the application
|
# Define the command to run the application
|
||||||
CMD ["./apimain"]
|
CMD ["./apimain"]
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
[English Doc](README_EN.md)
|
[English Doc](README_EN.md)
|
||||||
|
|
||||||
本项目使用 Go 实现了 RustDesk 的 API,并包含了 Web Admin 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。
|
本项目使用 Go 实现了 RustDesk 的 API,并包含了 Web Admin 和 Web 客户端。
|
||||||
|
|
||||||
|
|
||||||
<div align=center>
|
<div align=center>
|
||||||
<img src="https://img.shields.io/badge/golang-1.22-blue"/>
|
<img src="https://img.shields.io/badge/golang-1.22-blue"/>
|
||||||
@@ -13,6 +14,14 @@
|
|||||||
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
|
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## 搭配[lejianwen/rustdesk-server]使用更佳。
|
||||||
|
> [lejianwen/rustdesk-server]fork自RustDesk Server官方仓库
|
||||||
|
> 1. 解决了使用API链接超时问题
|
||||||
|
> 2. 可以强制登录后才能发起链接
|
||||||
|
> 3. 支持客户端websocket
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 特性
|
# 特性
|
||||||
|
|
||||||
- PC端API
|
- PC端API
|
||||||
@@ -94,8 +103,8 @@
|
|||||||
- 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email` 和`preferred_username`
|
- 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email` 和`preferred_username`
|
||||||
- `github oauth app`在`Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
|
- `github oauth app`在`Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
|
||||||
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
|
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
|
||||||
- `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback`
|
- `Authorization callback URL`填写`http://<your server[:port]>/api/oidc/callback`
|
||||||
,比如`http://127.0.0.1:21114/api/oauth/callback`
|
,比如`http://127.0.0.1:21114/api/oidc/callback`
|
||||||
7. 登录日志
|
7. 登录日志
|
||||||
8. 链接日志
|
8. 链接日志
|
||||||
9. 文件传输日志
|
9. 文件传输日志
|
||||||
@@ -182,6 +191,7 @@
|
|||||||
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
|
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
|
||||||
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
|
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
|
||||||
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
|
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
|
||||||
|
| RUSTDESK_API_MYSQL_TLS | 是否启用TLS, 可选值: `true`, `false`, `skip-verify`, `custom` | `false` |
|
||||||
| -----RUSTDESK配置----- | ---------- | ---------- |
|
| -----RUSTDESK配置----- | ---------- | ---------- |
|
||||||
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
|
| 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_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
|
||||||
@@ -255,6 +265,12 @@
|
|||||||
#或者使用generate_api.go生成api并运行
|
#或者使用generate_api.go生成api并运行
|
||||||
go generate generate_api.go
|
go generate generate_api.go
|
||||||
```
|
```
|
||||||
|
> 注意:使用 `go run` 或编译后的二进制时,当前目录下必须存在 `conf` 和 `resources`
|
||||||
|
> 目录。如果在其他目录运行,可通过 `-c` 和环境变量
|
||||||
|
> `RUSTDESK_API_GIN_RESOURCES_PATH` 指定绝对路径,例如:
|
||||||
|
> ```bash
|
||||||
|
> RUSTDESK_API_GIN_RESOURCES_PATH=/opt/rustdesk-api/resources ./apimain -c /opt/rustdesk-api/conf/config.yaml
|
||||||
|
> ```
|
||||||
5. 编译,如果想自己编译,先cd到项目根目录,然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release`
|
5. 编译,如果想自己编译,先cd到项目根目录,然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release`
|
||||||
目录下生成对应的可执行文件。直接运行编译后的可执行文件即可。
|
目录下生成对应的可执行文件。直接运行编译后的可执行文件即可。
|
||||||
|
|
||||||
@@ -319,3 +335,5 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
## 感谢你的支持!如果这个项目对你有帮助,请点个⭐️鼓励一下,谢谢!
|
## 感谢你的支持!如果这个项目对你有帮助,请点个⭐️鼓励一下,谢谢!
|
||||||
|
|
||||||
|
[lejianwen/rustdesk-server]: https://github.com/lejianwen/rustdesk-server
|
||||||
32
README_EN.md
32
README_EN.md
@@ -12,6 +12,13 @@ desktop software that provides self-hosted solutions.
|
|||||||
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
|
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## Better used with [lejianwen/rustdesk-server].
|
||||||
|
> [lejianwen/rustdesk-server] is a fork of the official RustDesk Server repository.
|
||||||
|
> 1. Solves the API connection timeout issue.
|
||||||
|
> 2. Can enforce login before initiating a connection.
|
||||||
|
> 3. Supports client websocket.
|
||||||
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- PC API
|
- PC API
|
||||||
@@ -94,8 +101,8 @@ displaying data.Frontend code is available at [rustdesk-api-web](https://github.
|
|||||||
- For `OIDC`, you must set the `Issuer`. And `Scopes` is optional which default is `openid,email,profile`, please make sure this `Oauth App` can access `sub`, `email` and `preferred_username`
|
- For `OIDC`, you must set the `Issuer`. And `Scopes` is optional which default is `openid,email,profile`, please make sure this `Oauth App` can access `sub`, `email` and `preferred_username`
|
||||||
- Create a `GitHub OAuth App`
|
- Create a `GitHub OAuth App`
|
||||||
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
|
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
|
||||||
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oauth/callback`,
|
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oidc/callback`,
|
||||||
e.g., `http://127.0.0.1:21114/api/oauth/callback`.
|
e.g., `http://127.0.0.1:21114/api/oidc/callback`.
|
||||||
|
|
||||||
7. Login logs
|
7. Login logs
|
||||||
8. Connection logs
|
8. Connection logs
|
||||||
@@ -181,6 +188,7 @@ The table below does not list all configurations. Please refer to the configurat
|
|||||||
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
|
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
|
||||||
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
|
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
|
||||||
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
|
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
|
||||||
|
| RUSTDESK_API_MYSQL_TLS | Whether to enable TLS, optional values: `true`, `false`, `skip-verify`, `custom` | `false` |
|
||||||
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
|
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||||
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
|
| 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_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
|
||||||
@@ -251,10 +259,17 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
|
|||||||
4. Run:
|
4. Run:
|
||||||
```bash
|
```bash
|
||||||
# Run directly
|
# Run directly
|
||||||
go run cmd/apimain.go
|
go run cmd/apimain.go
|
||||||
# Or generate and run the API using generate_api.go
|
# Or generate and run the API using generate_api.go
|
||||||
go generate generate_api.go
|
go generate generate_api.go
|
||||||
```
|
```
|
||||||
|
> **Note:** When using `go run` or the compiled binary, the `conf` and `resources`
|
||||||
|
> directories must exist relative to the current working directory. If you run
|
||||||
|
> the program from another location, specify absolute paths with `-c` and the
|
||||||
|
> `RUSTDESK_API_GIN_RESOURCES_PATH` environment variable. Example:
|
||||||
|
> ```bash
|
||||||
|
> RUSTDESK_API_GIN_RESOURCES_PATH=/opt/rustdesk-api/resources ./apimain -c /opt/rustdesk-api/conf/config.yaml
|
||||||
|
> ```
|
||||||
|
|
||||||
5. To compile, change to the project root directory. For Windows, run `build.bat`, and for Linux, run `build.sh`. After
|
5. To compile, change to the project root directory. For Windows, run `build.bat`, and for Linux, run `build.sh`. After
|
||||||
compiling, the corresponding executables will be generated in the `release` directory. Run the compiled executables
|
compiling, the corresponding executables will be generated in the `release` directory. Run the compiled executables
|
||||||
@@ -317,4 +332,7 @@ Thanks to everyone who contributed!
|
|||||||
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
|
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Thanks for your support! If you find this project useful, please give it a ⭐️. Thank you!
|
## Thanks for your support! If you find this project useful, please give it a ⭐️. Thank you!
|
||||||
|
|
||||||
|
|
||||||
|
[lejianwen/rustdesk-server]: https://github.com/lejianwen/rustdesk-server
|
||||||
@@ -2,6 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/config"
|
"github.com/lejianwen/rustdesk-api/v2/config"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/global"
|
"github.com/lejianwen/rustdesk-api/v2/global"
|
||||||
@@ -17,11 +21,10 @@ import (
|
|||||||
"github.com/lejianwen/rustdesk-api/v2/utils"
|
"github.com/lejianwen/rustdesk-api/v2/utils"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DatabaseVersion = 265
|
||||||
|
|
||||||
// @title 管理系统API
|
// @title 管理系统API
|
||||||
// @version 1.0
|
// @version 1.0
|
||||||
// @description 接口
|
// @description 接口
|
||||||
@@ -142,11 +145,12 @@ func InitGlobal() {
|
|||||||
//gorm
|
//gorm
|
||||||
if global.Config.Gorm.Type == config.TypeMysql {
|
if global.Config.Gorm.Type == config.TypeMysql {
|
||||||
|
|
||||||
dsn := fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
dsn := fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
|
||||||
global.Config.Mysql.Username,
|
global.Config.Mysql.Username,
|
||||||
global.Config.Mysql.Password,
|
global.Config.Mysql.Password,
|
||||||
global.Config.Mysql.Addr,
|
global.Config.Mysql.Addr,
|
||||||
global.Config.Mysql.Dbname,
|
global.Config.Mysql.Dbname,
|
||||||
|
global.Config.Mysql.Tls,
|
||||||
)
|
)
|
||||||
|
|
||||||
global.DB = orm.NewMysql(&orm.MysqlConfig{
|
global.DB = orm.NewMysql(&orm.MysqlConfig{
|
||||||
@@ -210,7 +214,7 @@ func InitGlobal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DatabaseAutoUpdate() {
|
func DatabaseAutoUpdate() {
|
||||||
version := 262
|
version := DatabaseVersion
|
||||||
|
|
||||||
db := global.DB
|
db := global.DB
|
||||||
|
|
||||||
@@ -342,7 +346,11 @@ func Migrate(version uint) {
|
|||||||
// 生成随机密码
|
// 生成随机密码
|
||||||
pwd := utils.RandomString(8)
|
pwd := utils.RandomString(8)
|
||||||
global.Logger.Info("Admin Password Is: ", pwd)
|
global.Logger.Info("Admin Password Is: ", pwd)
|
||||||
admin.Password = service.AllService.UserService.EncryptPassword(pwd)
|
var err error
|
||||||
|
admin.Password, err = utils.EncryptPassword(pwd)
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Fatalf("failed to generate admin password: %v", err)
|
||||||
|
}
|
||||||
global.DB.Create(admin)
|
global.DB.Create(admin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ mysql:
|
|||||||
password: ""
|
password: ""
|
||||||
addr: ""
|
addr: ""
|
||||||
dbname: ""
|
dbname: ""
|
||||||
|
tls: "false" # true / false / skip-verify / custom
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
host: "127.0.0.1"
|
host: "127.0.0.1"
|
||||||
@@ -80,4 +81,4 @@ ldap:
|
|||||||
last-name: "sn"
|
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.
|
sync: false # If true, the user will be synchronized to the database when the user logs in. If false, the user will be synchronized to the database when the user be created.
|
||||||
admin-group: "cn=admin,dc=example,dc=com" # The group name of the admin group, if the user is in this group, the user will be an admin.
|
admin-group: "cn=admin,dc=example,dc=com" # The group name of the admin group, if the user is in this group, the user will be an admin.
|
||||||
|
allow-group: "cn=users,dc=example,dc=com" # The group name of the users group, if the user is in this group, the user will be an login.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type Mysql struct {
|
|||||||
Username string `mapstructure:"username"`
|
Username string `mapstructure:"username"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
Dbname string `mapstructure:"dbname"`
|
Dbname string `mapstructure:"dbname"`
|
||||||
|
Tls string `mapstructure:"tls"` // true / false / skip-verify / custom
|
||||||
}
|
}
|
||||||
|
|
||||||
type Postgresql struct {
|
type Postgresql struct {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type LdapUser struct {
|
|||||||
LastName string `mapstructure:"last-name"`
|
LastName string `mapstructure:"last-name"`
|
||||||
Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database
|
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
|
AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group
|
||||||
|
AllowGroup string `mapstructure:"allow-group"` // Which group is allowed to login
|
||||||
}
|
}
|
||||||
|
|
||||||
// type LdapGroup struct {
|
// type LdapGroup struct {
|
||||||
|
|||||||
@@ -3,24 +3,20 @@ package config
|
|||||||
type GithubOauth struct {
|
type GithubOauth struct {
|
||||||
ClientId string `mapstructure:"client-id"`
|
ClientId string `mapstructure:"client-id"`
|
||||||
ClientSecret string `mapstructure:"client-secret"`
|
ClientSecret string `mapstructure:"client-secret"`
|
||||||
RedirectUrl string `mapstructure:"redirect-url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GoogleOauth struct {
|
type GoogleOauth struct {
|
||||||
ClientId string `mapstructure:"client-id"`
|
ClientId string `mapstructure:"client-id"`
|
||||||
ClientSecret string `mapstructure:"client-secret"`
|
ClientSecret string `mapstructure:"client-secret"`
|
||||||
RedirectUrl string `mapstructure:"redirect-url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OidcOauth struct {
|
type OidcOauth struct {
|
||||||
Issuer string `mapstructure:"issuer"`
|
Issuer string `mapstructure:"issuer"`
|
||||||
ClientId string `mapstructure:"client-id"`
|
ClientId string `mapstructure:"client-id"`
|
||||||
ClientSecret string `mapstructure:"client-secret"`
|
ClientSecret string `mapstructure:"client-secret"`
|
||||||
RedirectUrl string `mapstructure:"redirect-url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LinuxdoOauth struct {
|
type LinuxdoOauth struct {
|
||||||
ClientId string `mapstructure:"client-id"`
|
ClientId string `mapstructure:"client-id"`
|
||||||
ClientSecret string `mapstructure:"client-secret"`
|
ClientSecret string `mapstructure:"client-secret"`
|
||||||
RedirectUrl string `mapstructure:"redirect-url"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ services:
|
|||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile.dev
|
||||||
args:
|
args:
|
||||||
COUNTRY: CN
|
COUNTRY: CN
|
||||||
FREONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git
|
FRONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git
|
||||||
FRONTEND_GIT_BRANCH: master
|
FRONTEND_GIT_BRANCH: master
|
||||||
# image: lejianwen/rustdesk-api
|
# image: lejianwen/rustdesk-api
|
||||||
container_name: rustdesk-api
|
container_name: rustdesk-api
|
||||||
@@ -21,4 +21,4 @@ services:
|
|||||||
- ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份
|
- ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份
|
||||||
- ./conf:/app/conf # config
|
- ./conf:/app/conf # config
|
||||||
# - ./resources:/app/resources # 静态资源
|
# - ./resources:/app/resources # 静态资源
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -5569,8 +5569,7 @@ const docTemplateadmin = `{
|
|||||||
"required": [
|
"required": [
|
||||||
"client_id",
|
"client_id",
|
||||||
"client_secret",
|
"client_secret",
|
||||||
"oauth_type",
|
"oauth_type"
|
||||||
"redirect_url"
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"auto_register": {
|
"auto_register": {
|
||||||
@@ -5600,9 +5599,6 @@ const docTemplateadmin = `{
|
|||||||
"pkce_method": {
|
"pkce_method": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"redirect_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"scopes": {
|
"scopes": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -6296,9 +6292,6 @@ const docTemplateadmin = `{
|
|||||||
"pkce_method": {
|
"pkce_method": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"redirect_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"scopes": {
|
"scopes": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5562,8 +5562,7 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"client_id",
|
"client_id",
|
||||||
"client_secret",
|
"client_secret",
|
||||||
"oauth_type",
|
"oauth_type"
|
||||||
"redirect_url"
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"auto_register": {
|
"auto_register": {
|
||||||
@@ -5593,9 +5592,6 @@
|
|||||||
"pkce_method": {
|
"pkce_method": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"redirect_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"scopes": {
|
"scopes": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -6289,9 +6285,6 @@
|
|||||||
"pkce_method": {
|
"pkce_method": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"redirect_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"scopes": {
|
"scopes": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -6595,4 +6588,4 @@
|
|||||||
"in": "header"
|
"in": "header"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,15 +143,12 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
pkce_method:
|
pkce_method:
|
||||||
type: string
|
type: string
|
||||||
redirect_url:
|
|
||||||
type: string
|
|
||||||
scopes:
|
scopes:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- client_id
|
- client_id
|
||||||
- client_secret
|
- client_secret
|
||||||
- oauth_type
|
- oauth_type
|
||||||
- redirect_url
|
|
||||||
type: object
|
type: object
|
||||||
admin.PeerBatchDeleteForm:
|
admin.PeerBatchDeleteForm:
|
||||||
properties:
|
properties:
|
||||||
@@ -611,8 +608,6 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
pkce_method:
|
pkce_method:
|
||||||
type: string
|
type: string
|
||||||
redirect_url:
|
|
||||||
type: string
|
|
||||||
scopes:
|
scopes:
|
||||||
type: string
|
type: string
|
||||||
updated_at:
|
updated_at:
|
||||||
|
|||||||
@@ -954,35 +954,6 @@ const docTemplateapi = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/oauth/callback": {
|
|
||||||
"get": {
|
|
||||||
"description": "OauthCallback",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Oauth"
|
|
||||||
],
|
|
||||||
"summary": "OauthCallback",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.LoginRes"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.ErrorResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/oidc/auth": {
|
"/oidc/auth": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "OidcAuth",
|
"description": "OidcAuth",
|
||||||
@@ -1041,6 +1012,35 @@ const docTemplateapi = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/oidc/callback": {
|
||||||
|
"get": {
|
||||||
|
"description": "OauthCallback",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Oauth"
|
||||||
|
],
|
||||||
|
"summary": "OauthCallback",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.LoginRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/peers": {
|
"/peers": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|||||||
@@ -947,35 +947,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/oauth/callback": {
|
|
||||||
"get": {
|
|
||||||
"description": "OauthCallback",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Oauth"
|
|
||||||
],
|
|
||||||
"summary": "OauthCallback",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.LoginRes"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.ErrorResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/oidc/auth": {
|
"/oidc/auth": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "OidcAuth",
|
"description": "OidcAuth",
|
||||||
@@ -1034,6 +1005,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/oidc/callback": {
|
||||||
|
"get": {
|
||||||
|
"description": "OauthCallback",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Oauth"
|
||||||
|
],
|
||||||
|
"summary": "OauthCallback",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.LoginRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/peers": {
|
"/peers": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|||||||
@@ -792,25 +792,6 @@ paths:
|
|||||||
summary: 登出
|
summary: 登出
|
||||||
tags:
|
tags:
|
||||||
- 登录
|
- 登录
|
||||||
/oauth/callback:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: OauthCallback
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.LoginRes'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.ErrorResponse'
|
|
||||||
summary: OauthCallback
|
|
||||||
tags:
|
|
||||||
- Oauth
|
|
||||||
/oidc/auth:
|
/oidc/auth:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -849,6 +830,25 @@ paths:
|
|||||||
summary: OidcAuthQuery
|
summary: OidcAuthQuery
|
||||||
tags:
|
tags:
|
||||||
- Oauth
|
- Oauth
|
||||||
|
/oidc/callback:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: OauthCallback
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.LoginRes'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: OauthCallback
|
||||||
|
tags:
|
||||||
|
- Oauth
|
||||||
/peers:
|
/peers:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -25,6 +25,7 @@ require (
|
|||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/gin-swagger v1.6.0
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
github.com/swaggo/swag v1.16.3
|
github.com/swaggo/swag v1.16.3
|
||||||
|
golang.org/x/crypto v0.33.0
|
||||||
golang.org/x/oauth2 v0.23.0
|
golang.org/x/oauth2 v0.23.0
|
||||||
golang.org/x/text v0.22.0
|
golang.org/x/text v0.22.0
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
@@ -84,7 +85,6 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
golang.org/x/crypto v0.33.0 // indirect
|
|
||||||
golang.org/x/image v0.13.0 // indirect
|
golang.org/x/image v0.13.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.11.0 // indirect
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func (f *File) Notify(c *gin.Context) {
|
|||||||
|
|
||||||
res := global.Oss.Verify(c.Request)
|
res := global.Oss.Verify(c.Request)
|
||||||
if !res {
|
if !res {
|
||||||
response.Fail(c, 101, "权限错误")
|
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fm := &FileBack{}
|
fm := &FileBack{}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package admin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/global"
|
"github.com/lejianwen/rustdesk-api/v2/global"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/http/controller/api"
|
"github.com/lejianwen/rustdesk-api/v2/http/controller/api"
|
||||||
@@ -168,6 +169,8 @@ func (ct *Login) LoginOptions(c *gin.Context) {
|
|||||||
"ops": ops,
|
"ops": ops,
|
||||||
"register": global.Config.App.Register,
|
"register": global.Config.App.Register,
|
||||||
"need_captcha": needCaptcha,
|
"need_captcha": needCaptcha,
|
||||||
|
"disable_pwd": global.Config.App.DisablePwdLogin,
|
||||||
|
"auto_oidc": global.Config.App.DisablePwdLogin && len(ops) == 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,10 +98,10 @@ func (abc *AddressBookCollection) Update(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
u := service.AllService.UserService.CurUser(c)
|
u := service.AllService.UserService.CurUser(c)
|
||||||
if f.UserId != u.Id {
|
//if f.UserId != u.Id {
|
||||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
// response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||||
return
|
// return
|
||||||
}
|
//}
|
||||||
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
|
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
|
||||||
if ex.Id == 0 {
|
if ex.Id == 0 {
|
||||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/global"
|
"github.com/lejianwen/rustdesk-api/v2/global"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
|
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
|
||||||
adminReq "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/http/response"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/service"
|
"github.com/lejianwen/rustdesk-api/v2/service"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Oauth struct {
|
type Oauth struct {
|
||||||
@@ -68,16 +69,16 @@ func (o *Oauth) Confirm(c *gin.Context) {
|
|||||||
j := &adminReq.OauthConfirmForm{}
|
j := &adminReq.OauthConfirmForm{}
|
||||||
err := c.ShouldBindJSON(j)
|
err := c.ShouldBindJSON(j)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Fail(c, 101, "参数错误"+err.Error())
|
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if j.Code == "" {
|
if j.Code == "" {
|
||||||
response.Fail(c, 101, "参数错误: code 不存在")
|
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
v := service.AllService.OauthService.GetOauthCache(j.Code)
|
v := service.AllService.OauthService.GetOauthCache(j.Code)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
response.Fail(c, 101, "授权已过期")
|
response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u := service.AllService.UserService.CurUser(c)
|
u := service.AllService.UserService.CurUser(c)
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ func (ct *Peer) List(c *gin.Context) {
|
|||||||
if query.Ip != "" {
|
if query.Ip != "" {
|
||||||
tx.Where("last_online_ip like ?", "%"+query.Ip+"%")
|
tx.Where("last_online_ip like ?", "%"+query.Ip+"%")
|
||||||
}
|
}
|
||||||
|
if query.Alias != "" {
|
||||||
|
tx.Where("alias like ?", "%"+query.Alias+"%")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
response.Success(c, res)
|
response.Success(c, res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
|
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/model"
|
"github.com/lejianwen/rustdesk-api/v2/model"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/service"
|
"github.com/lejianwen/rustdesk-api/v2/service"
|
||||||
|
"github.com/lejianwen/rustdesk-api/v2/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@@ -243,11 +244,10 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
u := service.AllService.UserService.CurUser(c)
|
u := service.AllService.UserService.CurUser(c)
|
||||||
// If the password is not empty, the old password is verified
|
// Verify the old password only when the account already has one set
|
||||||
// otherwise, the old password is not verified
|
|
||||||
if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
|
if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
|
||||||
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
|
ok, _, err := utils.VerifyPassword(u.Password, f.OldPassword)
|
||||||
if u.Password != oldPwd {
|
if err != nil || !ok {
|
||||||
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
|
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (i *Index) Heartbeat(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{})
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peer := service.AllService.PeerService.FindByUuid(info.Uuid)
|
peer := service.AllService.PeerService.FindById(info.Id)
|
||||||
if peer == nil || peer.RowId == 0 {
|
if peer == nil || peer.RowId == 0 {
|
||||||
c.JSON(http.StatusOK, gin.H{})
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/global"
|
"github.com/lejianwen/rustdesk-api/v2/global"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/http/request/api"
|
"github.com/lejianwen/rustdesk-api/v2/http/request/api"
|
||||||
@@ -10,7 +12,6 @@ import (
|
|||||||
"github.com/lejianwen/rustdesk-api/v2/service"
|
"github.com/lejianwen/rustdesk-api/v2/service"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/utils"
|
"github.com/lejianwen/rustdesk-api/v2/utils"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Oauth struct {
|
type Oauth struct {
|
||||||
@@ -79,7 +80,8 @@ func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken)
|
|||||||
|
|
||||||
// 如果 UserId 为 0,说明还在授权中
|
// 如果 UserId 为 0,说明还在授权中
|
||||||
if v.UserId == 0 {
|
if v.UserId == 0 {
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress, please login and bind"})
|
//fix: 1.4.2 webclient oidc
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress, please login and bind", "error": "No authed oidc is found"})
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +144,7 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} apiResp.LoginRes
|
// @Success 200 {object} apiResp.LoginRes
|
||||||
// @Failure 500 {object} response.ErrorResponse
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
// @Router /oauth/callback [get]
|
// @Router /oidc/callback [get]
|
||||||
func (o *Oauth) OauthCallback(c *gin.Context) {
|
func (o *Oauth) OauthCallback(c *gin.Context) {
|
||||||
state := c.Query("state")
|
state := c.Query("state")
|
||||||
if state == "" {
|
if state == "" {
|
||||||
@@ -225,8 +227,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
|
|||||||
if !*oauthConfig.AutoRegister {
|
if !*oauthConfig.AutoRegister {
|
||||||
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
|
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
|
||||||
oauthCache.UpdateFromOauthUser(oauthUser)
|
oauthCache.UpdateFromOauthUser(oauthUser)
|
||||||
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
|
c.Redirect(http.StatusFound, "/_admin/#/oauth/bind/"+cacheKey)
|
||||||
c.Redirect(http.StatusFound, url)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,8 +252,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
|
|||||||
Type: model.LoginLogTypeOauth,
|
Type: model.LoginLogTypeOauth,
|
||||||
Platform: oauthService.DeviceOs,
|
Platform: oauthService.DeviceOs,
|
||||||
})*/
|
})*/
|
||||||
url := global.Config.Rustdesk.ApiServer + "/_admin/#/"
|
c.Redirect(http.StatusFound, "/_admin/#/")
|
||||||
c.Redirect(http.StatusFound, url)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.HTML(http.StatusOK, "oauth_success.html", gin.H{
|
c.HTML(http.StatusOK, "oauth_success.html", gin.H{
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ func (p *Peer) SysInfo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
fpe := f.ToPeer()
|
fpe := f.ToPeer()
|
||||||
pe := service.AllService.PeerService.FindByUuid(f.Uuid)
|
pe := service.AllService.PeerService.FindById(f.Id)
|
||||||
if pe.RowId == 0 {
|
if pe.RowId == 0 {
|
||||||
pe = f.ToPeer()
|
pe = f.ToPeer()
|
||||||
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid)
|
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid, pe.Id)
|
||||||
err = service.AllService.PeerService.Create(pe)
|
err = service.AllService.PeerService.Create(pe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||||
@@ -42,7 +42,7 @@ func (p *Peer) SysInfo(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if pe.UserId == 0 {
|
if pe.UserId == 0 {
|
||||||
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid)
|
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid, pe.Id)
|
||||||
}
|
}
|
||||||
fpe.RowId = pe.RowId
|
fpe.RowId = pe.RowId
|
||||||
fpe.UserId = pe.UserId
|
fpe.UserId = pe.UserId
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ func BackendUserAuth() gin.HandlerFunc {
|
|||||||
//测试先关闭
|
//测试先关闭
|
||||||
token := c.GetHeader("api-token")
|
token := c.GetHeader("api-token")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
response.Fail(c, 403, "请先登录")
|
response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, ut := service.AllService.UserService.InfoByAccessToken(token)
|
user, ut := service.AllService.UserService.InfoByAccessToken(token)
|
||||||
if user.Id == 0 {
|
if user.Id == 0 {
|
||||||
response.Fail(c, 403, "请先登录")
|
response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func AdminPrivilege() gin.HandlerFunc {
|
|||||||
u := service.AllService.UserService.CurUser(c)
|
u := service.AllService.UserService.CurUser(c)
|
||||||
|
|
||||||
if !service.AllService.UserService.IsAdmin(u) {
|
if !service.AllService.UserService.IsAdmin(u) {
|
||||||
response.Fail(c, 403, "无权限")
|
response.Fail(c, 403, response.TranslateMsg(c, "NoAccess"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,18 +12,18 @@ func JwtAuth() gin.HandlerFunc {
|
|||||||
//测试先关闭
|
//测试先关闭
|
||||||
token := c.GetHeader("api-token")
|
token := c.GetHeader("api-token")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
response.Fail(c, 403, "请先登录")
|
response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uid, err := global.Jwt.ParseToken(token)
|
uid, err := global.Jwt.ParseToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Fail(c, 403, "请先登录")
|
response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
response.Fail(c, 403, "请先登录")
|
response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -34,12 +34,12 @@ func JwtAuth() gin.HandlerFunc {
|
|||||||
// Username: "测试用户",
|
// Username: "测试用户",
|
||||||
//}
|
//}
|
||||||
if user.Id == 0 {
|
if user.Id == 0 {
|
||||||
response.Fail(c, 403, "请先登录")
|
response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !service.AllService.UserService.CheckUserEnable(user) {
|
if !service.AllService.UserService.CheckUserEnable(user) {
|
||||||
response.Fail(c, 101, "你已被禁用")
|
response.Fail(c, 101, response.TranslateMsg(c, "Banned"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ type OauthForm struct {
|
|||||||
Scopes string `json:"scopes" validate:"omitempty"`
|
Scopes string `json:"scopes" validate:"omitempty"`
|
||||||
ClientId string `json:"client_id" validate:"required"`
|
ClientId string `json:"client_id" validate:"required"`
|
||||||
ClientSecret string `json:"client_secret" validate:"required"`
|
ClientSecret string `json:"client_secret" validate:"required"`
|
||||||
RedirectUrl string `json:"redirect_url" validate:"required"`
|
|
||||||
AutoRegister *bool `json:"auto_register"`
|
AutoRegister *bool `json:"auto_register"`
|
||||||
PkceEnable *bool `json:"pkce_enable"`
|
PkceEnable *bool `json:"pkce_enable"`
|
||||||
PkceMethod string `json:"pkce_method"`
|
PkceMethod string `json:"pkce_method"`
|
||||||
@@ -34,7 +33,6 @@ func (of *OauthForm) ToOauth() *model.Oauth {
|
|||||||
OauthType: of.OauthType,
|
OauthType: of.OauthType,
|
||||||
ClientId: of.ClientId,
|
ClientId: of.ClientId,
|
||||||
ClientSecret: of.ClientSecret,
|
ClientSecret: of.ClientSecret,
|
||||||
RedirectUrl: of.RedirectUrl,
|
|
||||||
AutoRegister: of.AutoRegister,
|
AutoRegister: of.AutoRegister,
|
||||||
Issuer: of.Issuer,
|
Issuer: of.Issuer,
|
||||||
Scopes: of.Scopes,
|
Scopes: of.Scopes,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type PeerForm struct {
|
|||||||
Uuid string `json:"uuid"`
|
Uuid string `json:"uuid"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
GroupId uint `json:"group_id"`
|
GroupId uint `json:"group_id"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PeerBatchDeleteForm struct {
|
type PeerBatchDeleteForm struct {
|
||||||
@@ -32,6 +33,7 @@ func (f *PeerForm) ToPeer() *model.Peer {
|
|||||||
Uuid: f.Uuid,
|
Uuid: f.Uuid,
|
||||||
Version: f.Version,
|
Version: f.Version,
|
||||||
GroupId: f.GroupId,
|
GroupId: f.GroupId,
|
||||||
|
Alias: f.Alias,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +45,7 @@ type PeerQuery struct {
|
|||||||
Uuids string `json:"uuids" form:"uuids"`
|
Uuids string `json:"uuids" form:"uuids"`
|
||||||
Ip string `json:"ip" form:"ip"`
|
Ip string `json:"ip" form:"ip"`
|
||||||
Username string `json:"username" form:"username"`
|
Username string `json:"username" form:"username"`
|
||||||
|
Alias string `json:"alias" form:"alias"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SimpleDataQuery struct {
|
type SimpleDataQuery struct {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type UserForm struct {
|
|||||||
GroupId uint `json:"group_id" validate:"required"`
|
GroupId uint `json:"group_id" validate:"required"`
|
||||||
IsAdmin *bool `json:"is_admin" `
|
IsAdmin *bool `json:"is_admin" `
|
||||||
Status model.StatusCode `json:"status" validate:"required,gte=0"`
|
Status model.StatusCode `json:"status" validate:"required,gte=0"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uf *UserForm) FromUser(user *model.User) *UserForm {
|
func (uf *UserForm) FromUser(user *model.User) *UserForm {
|
||||||
@@ -25,6 +26,7 @@ func (uf *UserForm) FromUser(user *model.User) *UserForm {
|
|||||||
uf.GroupId = user.GroupId
|
uf.GroupId = user.GroupId
|
||||||
uf.IsAdmin = user.IsAdmin
|
uf.IsAdmin = user.IsAdmin
|
||||||
uf.Status = user.Status
|
uf.Status = user.Status
|
||||||
|
uf.Remark = user.Remark
|
||||||
return uf
|
return uf
|
||||||
}
|
}
|
||||||
func (uf *UserForm) ToUser() *model.User {
|
func (uf *UserForm) ToUser() *model.User {
|
||||||
@@ -37,6 +39,7 @@ func (uf *UserForm) ToUser() *model.User {
|
|||||||
user.GroupId = uf.GroupId
|
user.GroupId = uf.GroupId
|
||||||
user.IsAdmin = uf.IsAdmin
|
user.IsAdmin = uf.IsAdmin
|
||||||
user.Status = uf.Status
|
user.Status = uf.Status
|
||||||
|
user.Remark = uf.Remark
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ func ApiInit(g *gin.Engine) {
|
|||||||
frg.GET("/oauth/callback", o.OauthCallback)
|
frg.GET("/oauth/callback", o.OauthCallback)
|
||||||
frg.GET("/oauth/login", o.OauthCallback)
|
frg.GET("/oauth/login", o.OauthCallback)
|
||||||
frg.GET("/oauth/msg", o.Message)
|
frg.GET("/oauth/msg", o.Message)
|
||||||
|
|
||||||
|
frg.GET("/oidc/callback", o.OauthCallback)
|
||||||
|
frg.GET("/oidc/login", o.OauthCallback)
|
||||||
|
frg.GET("/oidc/msg", o.Message)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
pe := &api.Peer{}
|
pe := &api.Peer{}
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ func ValidateOauthType(oauthType string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UserEndpointGithub string = "https://api.github.com/user"
|
UserEndpointGithub string = "https://api.github.com/user"
|
||||||
UserEndpointLinuxdo string = "https://connect.linux.do/api/user"
|
UserEndpointLinuxdo string = "https://connect.linux.do/api/user"
|
||||||
IssuerGoogle string = "https://accounts.google.com"
|
IssuerGoogle string = "https://accounts.google.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Oauth struct {
|
type Oauth struct {
|
||||||
@@ -41,12 +41,12 @@ type Oauth struct {
|
|||||||
OauthType string `json:"oauth_type"`
|
OauthType string `json:"oauth_type"`
|
||||||
ClientId string `json:"client_id"`
|
ClientId string `json:"client_id"`
|
||||||
ClientSecret string `json:"client_secret"`
|
ClientSecret string `json:"client_secret"`
|
||||||
RedirectUrl string `json:"redirect_url"`
|
//RedirectUrl string `json:"redirect_url"`
|
||||||
AutoRegister *bool `json:"auto_register"`
|
AutoRegister *bool `json:"auto_register"`
|
||||||
Scopes string `json:"scopes"`
|
Scopes string `json:"scopes"`
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
PkceEnable *bool `json:"pkce_enable"`
|
PkceEnable *bool `json:"pkce_enable"`
|
||||||
PkceMethod string `json:"pkce_method"`
|
PkceMethod string `json:"pkce_method"`
|
||||||
TimeModel
|
TimeModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Peer struct {
|
|||||||
LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"`
|
LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"`
|
||||||
LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"`
|
LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"`
|
||||||
GroupId uint `json:"group_id" gorm:"default:0;not null;index"`
|
GroupId uint `json:"group_id" gorm:"default:0;not null;index"`
|
||||||
|
Alias string `json:"alias" gorm:"default:'';not null;index"`
|
||||||
TimeModel
|
TimeModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type User struct {
|
|||||||
GroupId uint `json:"group_id" gorm:"default:0;not null;index"`
|
GroupId uint `json:"group_id" gorm:"default:0;not null;index"`
|
||||||
IsAdmin *bool `json:"is_admin" gorm:"default:0;not null;"`
|
IsAdmin *bool `json:"is_admin" gorm:"default:0;not null;"`
|
||||||
Status StatusCode `json:"status" gorm:"default:1;not null;"`
|
Status StatusCode `json:"status" gorm:"default:1;not null;"`
|
||||||
|
Remark string `json:"remark" gorm:"default:'';not null;"`
|
||||||
TimeModel
|
TimeModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ description = "No access."
|
|||||||
one = "No access."
|
one = "No access."
|
||||||
other = "No access."
|
other = "No access."
|
||||||
|
|
||||||
|
[NeedLogin]
|
||||||
|
description = "Need login."
|
||||||
|
one = "Please log in first."
|
||||||
|
other = "Please log in first."
|
||||||
|
|
||||||
[UsernameOrPasswordError]
|
[UsernameOrPasswordError]
|
||||||
description = "Username or password error."
|
description = "Username or password error."
|
||||||
one = "Username or password error."
|
one = "Username or password error."
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ description = "No access."
|
|||||||
one = "Sin acceso."
|
one = "Sin acceso."
|
||||||
other = "Sin acceso."
|
other = "Sin acceso."
|
||||||
|
|
||||||
|
[NeedLogin]
|
||||||
|
description = "Need login."
|
||||||
|
one = "Por favor inicie sesión primero."
|
||||||
|
other = "Por favor inicie sesión primero."
|
||||||
|
|
||||||
[UsernameOrPasswordError]
|
[UsernameOrPasswordError]
|
||||||
description = "Username or password error."
|
description = "Username or password error."
|
||||||
one = "Error de usuario o contraseña."
|
one = "Error de usuario o contraseña."
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ description = "No access."
|
|||||||
one = "Aucun d'access."
|
one = "Aucun d'access."
|
||||||
other = "Aucun d'access."
|
other = "Aucun d'access."
|
||||||
|
|
||||||
|
[NeedLogin]
|
||||||
|
description = "Need login."
|
||||||
|
one = "Veuillez d'abord vous connecter."
|
||||||
|
other = "Veuillez d'abord vous connecter."
|
||||||
|
|
||||||
[UsernameOrPasswordError]
|
[UsernameOrPasswordError]
|
||||||
description = "Username or password error."
|
description = "Username or password error."
|
||||||
one = "Nom d'utilisateur ou de mot de passe incorrect."
|
one = "Nom d'utilisateur ou de mot de passe incorrect."
|
||||||
@@ -161,4 +166,4 @@ other = "Banni."
|
|||||||
[RegisterSuccessWaitAdminConfirm]
|
[RegisterSuccessWaitAdminConfirm]
|
||||||
description = "Register success wait admin confirm."
|
description = "Register success wait admin confirm."
|
||||||
one = "Inscription réussie, veuillez attendre la confirmation de l'administrateur."
|
one = "Inscription réussie, veuillez attendre la confirmation de l'administrateur."
|
||||||
other = "Inscription réussie, veuillez attendre la confirmation de l'administrateur."
|
other = "Inscription réussie, veuillez attendre la confirmation de l'administrateur."
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ description = "No access."
|
|||||||
one = "접근할 수 없습니다."
|
one = "접근할 수 없습니다."
|
||||||
other = "접근할 수 없습니다."
|
other = "접근할 수 없습니다."
|
||||||
|
|
||||||
|
[NeedLogin]
|
||||||
|
description = "Need login."
|
||||||
|
one = "먼저 로그인해주세요."
|
||||||
|
other = "먼저 로그인해주세요."
|
||||||
|
|
||||||
[UsernameOrPasswordError]
|
[UsernameOrPasswordError]
|
||||||
description = "Username or password error."
|
description = "Username or password error."
|
||||||
one = "사용자 이름이나 비밀번호가 올바르지 않습니다."
|
one = "사용자 이름이나 비밀번호가 올바르지 않습니다."
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ description = "No access."
|
|||||||
one = "Нет доступа."
|
one = "Нет доступа."
|
||||||
other = "Нет доступа."
|
other = "Нет доступа."
|
||||||
|
|
||||||
|
[NeedLogin]
|
||||||
|
description = "Need login."
|
||||||
|
one = "Пожалуйста, войдите в систему."
|
||||||
|
other = "Пожалуйста, войдите в систему."
|
||||||
|
|
||||||
[UsernameOrPasswordError]
|
[UsernameOrPasswordError]
|
||||||
description = "Username or password error."
|
description = "Username or password error."
|
||||||
one = "Неправильное имя пользователя или пароль."
|
one = "Неправильное имя пользователя или пароль."
|
||||||
@@ -161,4 +166,4 @@ other = "Заблокировано."
|
|||||||
[RegisterSuccessWaitAdminConfirm]
|
[RegisterSuccessWaitAdminConfirm]
|
||||||
description = "Register success wait admin confirm."
|
description = "Register success wait admin confirm."
|
||||||
one = "Регистрация прошла успешно, ожидайте подтверждения администратора."
|
one = "Регистрация прошла успешно, ожидайте подтверждения администратора."
|
||||||
other = "Регистрация прошла успешно, ожидайте подтверждения администратора."
|
other = "Регистрация прошла успешно, ожидайте подтверждения администратора."
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ description = "No access."
|
|||||||
one = "无权限。"
|
one = "无权限。"
|
||||||
other = "无权限。"
|
other = "无权限。"
|
||||||
|
|
||||||
|
[NeedLogin]
|
||||||
|
description = "Need login."
|
||||||
|
one = "请先登录。"
|
||||||
|
other = "请先登录。"
|
||||||
|
|
||||||
[UsernameOrPasswordError]
|
[UsernameOrPasswordError]
|
||||||
description = "Username or password error."
|
description = "Username or password error."
|
||||||
one = "用户名或密码错误。"
|
one = "用户名或密码错误。"
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ other = "測試2 {{.P0}}"
|
|||||||
|
|
||||||
[ParamsError]
|
[ParamsError]
|
||||||
description = "Params validation failed."
|
description = "Params validation failed."
|
||||||
one = "引數錯誤。"
|
one = "參數驗證失敗。"
|
||||||
other = "引數錯誤。"
|
other = "參數驗證失敗。"
|
||||||
|
|
||||||
[OperationFailed]
|
[OperationFailed]
|
||||||
description = "OperationFailed."
|
description = "OperationFailed."
|
||||||
@@ -20,18 +20,23 @@ other = "操作成功。"
|
|||||||
|
|
||||||
[ItemExists]
|
[ItemExists]
|
||||||
description = "Item already exists."
|
description = "Item already exists."
|
||||||
one = "資料已存在。"
|
one = "項目已存在。"
|
||||||
other = "資料已存在。"
|
other = "項目已存在。"
|
||||||
|
|
||||||
[ItemNotFound]
|
[ItemNotFound]
|
||||||
description = "Item not found."
|
description = "Item not found."
|
||||||
one = "資料不存在。"
|
one = "找不到項目。"
|
||||||
other = "資料不存在。"
|
other = "找不到項目。"
|
||||||
|
|
||||||
[NoAccess]
|
[NoAccess]
|
||||||
description = "No access."
|
description = "No access."
|
||||||
one = "無許可權。"
|
one = "無權限存取。"
|
||||||
other = "無許可權。"
|
other = "無權限存取。"
|
||||||
|
|
||||||
|
[NeedLogin]
|
||||||
|
description = "Need login."
|
||||||
|
one = "請先登入。"
|
||||||
|
other = "請先登入。"
|
||||||
|
|
||||||
[UsernameOrPasswordError]
|
[UsernameOrPasswordError]
|
||||||
description = "Username or password error."
|
description = "Username or password error."
|
||||||
@@ -45,24 +50,23 @@ other = "系統錯誤。"
|
|||||||
|
|
||||||
[ConfigNotFound]
|
[ConfigNotFound]
|
||||||
description = "Config not found."
|
description = "Config not found."
|
||||||
one = "配置不存在。"
|
one = "找不到設定。"
|
||||||
other = "配置不存在。"
|
other = "找不到設定。"
|
||||||
|
|
||||||
#授權過期
|
|
||||||
[OauthExpired]
|
[OauthExpired]
|
||||||
description = "Oauth expired."
|
description = "Oauth expired."
|
||||||
one = "授權過期,請重新授權。"
|
one = "OAuth 已過期,請重試。"
|
||||||
other = "授權過期,請重新授權。"
|
other = "OAuth 已過期,請重試。"
|
||||||
|
|
||||||
[OauthFailed]
|
[OauthFailed]
|
||||||
description = "Oauth failed."
|
description = "Oauth failed."
|
||||||
one = "授權失敗。"
|
one = "OAuth 失敗。"
|
||||||
other = "授權失敗。"
|
other = "OAuth 失敗。"
|
||||||
|
|
||||||
[OauthHasBindOtherUser]
|
[OauthHasBindOtherUser]
|
||||||
description = "Oauth has bind other user."
|
description = "Oauth has bind other user."
|
||||||
one = "授權已繫結其他使用者。"
|
one = "OAuth 已綁定其他使用者。"
|
||||||
other = "授權已繫結其他使用者。"
|
other = "OAuth 已綁定其他使用者。"
|
||||||
|
|
||||||
[ParamIsEmpty]
|
[ParamIsEmpty]
|
||||||
description = "Param is empty."
|
description = "Param is empty."
|
||||||
@@ -71,56 +75,64 @@ other = "{{.P0}} 為空。"
|
|||||||
|
|
||||||
[BindFail]
|
[BindFail]
|
||||||
description = "Bind fail."
|
description = "Bind fail."
|
||||||
one = "繫結失敗。"
|
one = "綁定失敗。"
|
||||||
other = "繫結失敗。"
|
other = "綁定失敗。"
|
||||||
|
|
||||||
[BindSuccess]
|
[BindSuccess]
|
||||||
description = "Bind success."
|
description = "Bind success."
|
||||||
one = "繫結成功。"
|
one = "綁定成功。"
|
||||||
other = "繫結成功。"
|
other = "綁定成功。"
|
||||||
|
|
||||||
[OauthHasBeenSuccess]
|
[OauthHasBeenSuccess]
|
||||||
description = "Oauth has been success."
|
description = "Oauth has been success."
|
||||||
one = "授權已成功。"
|
one = "OAuth 已成功。"
|
||||||
other = "授權已成功。"
|
other = "OAuth 已成功。"
|
||||||
|
|
||||||
[OauthSuccess]
|
[OauthSuccess]
|
||||||
description = "Oauth success."
|
description = "Oauth success."
|
||||||
one = "授權成功。"
|
one = "OAuth 成功。"
|
||||||
other = "授權成功。"
|
other = "OAuth 成功。"
|
||||||
|
|
||||||
[OauthRegisterSuccess]
|
[OauthRegisterSuccess]
|
||||||
description = "Oauth register success."
|
description = "Oauth register success."
|
||||||
one = "授權註冊成功。"
|
one = "OAuth 註冊成功。"
|
||||||
other = "授權註冊成功。"
|
other = "OAuth 註冊成功。"
|
||||||
|
|
||||||
[OauthRegisterFailed]
|
[OauthRegisterFailed]
|
||||||
description = "Oauth register failed."
|
description = "Oauth register failed."
|
||||||
one = "授權註冊失敗。"
|
one = "OAuth 註冊失敗。"
|
||||||
other = "授權註冊失敗。"
|
other = "OAuth 註冊失敗。"
|
||||||
|
|
||||||
[GetOauthTokenError]
|
[GetOauthTokenError]
|
||||||
description = "Get oauth token error."
|
description = "Get oauth token error."
|
||||||
one = "獲取授權token失敗。"
|
one = "取得 OAuth 權杖錯誤。"
|
||||||
other = "獲取授權token失敗。"
|
other = "取得 OAuth 權杖錯誤。"
|
||||||
|
|
||||||
[GetOauthUserInfoError]
|
[GetOauthUserInfoError]
|
||||||
description = "Get oauth user info error."
|
description = "Get oauth user info error."
|
||||||
one = "獲取授權使用者資訊失敗。"
|
one = "取得 OAuth 使用者資訊錯誤。"
|
||||||
other = "獲取授權使用者資訊失敗。"
|
other = "取得 OAuth 使用者資訊錯誤。"
|
||||||
|
|
||||||
[DecodeOauthUserInfoError]
|
[DecodeOauthUserInfoError]
|
||||||
description = "Decode oauth user info error."
|
description = "Decode oauth user info error."
|
||||||
one = "解析授權使用者資訊失敗。"
|
one = "解析 OAuth 使用者資訊錯誤。"
|
||||||
other = "解析授權使用者資訊失敗。"
|
other = "解析 OAuth 使用者資訊錯誤。"
|
||||||
|
|
||||||
[OldPasswordError]
|
[OldPasswordError]
|
||||||
description = "Old password error."
|
description = "Old password error."
|
||||||
one = "舊密碼錯誤。"
|
one = "舊密碼錯誤。"
|
||||||
other = "舊密碼錯誤。"
|
other = "舊密碼錯誤。"
|
||||||
|
|
||||||
|
|
||||||
[DefaultGroup]
|
[DefaultGroup]
|
||||||
description = "Default group."
|
description = "Default group."
|
||||||
one = "預設組"
|
one = "預設群組"
|
||||||
other = "預設組"
|
other = "預設群組"
|
||||||
|
|
||||||
[ShareGroup]
|
[ShareGroup]
|
||||||
description = "Share group."
|
description = "Share group."
|
||||||
one = "共享組"
|
one = "共享群組"
|
||||||
other = "共享組"
|
other = "共享群組"
|
||||||
|
|
||||||
[RegisterClosed]
|
[RegisterClosed]
|
||||||
description = "Register closed."
|
description = "Register closed."
|
||||||
one = "註冊已關閉。"
|
one = "註冊已關閉。"
|
||||||
@@ -138,20 +150,20 @@ other = "驗證碼錯誤。"
|
|||||||
|
|
||||||
[PwdLoginDisabled]
|
[PwdLoginDisabled]
|
||||||
description = "Password login disabled."
|
description = "Password login disabled."
|
||||||
one = "密碼登錄已禁用。"
|
one = "密碼登入已停用。"
|
||||||
other = "密碼登錄已禁用。"
|
other = "密碼登入已停用。"
|
||||||
|
|
||||||
[CannotShareToSelf]
|
[CannotShareToSelf]
|
||||||
description = "Cannot share to self."
|
description = "Cannot share to self."
|
||||||
one = "無法共享給自己。"
|
one = "無法分享給自己。"
|
||||||
other = "無法共享給自己。"
|
other = "無法分享給自己。"
|
||||||
|
|
||||||
[Banned]
|
[Banned]
|
||||||
description = "Banned."
|
description = "Banned."
|
||||||
one = "禁止使用。"
|
one = "已被禁用。"
|
||||||
other = "禁止使用。"
|
other = "已被禁用。"
|
||||||
|
|
||||||
[RegisterSuccessWaitAdminConfirm]
|
[RegisterSuccessWaitAdminConfirm]
|
||||||
description = "Register success wait admin confirm."
|
description = "Register success, wait admin confirm."
|
||||||
one = "註冊成功,請等待管理員確認。"
|
one = "註冊成功,等待管理員確認。"
|
||||||
other = "註冊成功,請等待管理員確認。"
|
other = "註冊成功,等待管理員確認。"
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
var title = 'OauthFailed'
|
var title = 'OauthFailed'
|
||||||
var msg = '{{.message}}'
|
var msg = '{{.message}}'
|
||||||
var btn = 'Close'
|
var btn = 'Close'
|
||||||
document.writeln('<script src="/api/oauth/msg?lang=' + lang + '&msg=' + msg + '&title=OauthFailed"><\/script>');
|
document.writeln('<script src="/api/oidc/msg?lang=' + lang + '&msg=' + msg + '&title=OauthFailed"><\/script>');
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
var title = 'OauthSuccess'
|
var title = 'OauthSuccess'
|
||||||
var msg = '{{.message}}'
|
var msg = '{{.message}}'
|
||||||
var btn = 'Close'
|
var btn = 'Close'
|
||||||
document.writeln('<script src="/api/oauth/msg?lang=' + lang + '&msg=' + msg + '&title=OauthSuccess"><\/script>');
|
document.writeln('<script src="/api/oidc/msg?lang=' + lang + '&msg=' + msg + '&title=OauthSuccess"><\/script>');
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
23
resources/web2/index.html
vendored
23
resources/web2/index.html
vendored
@@ -32,7 +32,7 @@
|
|||||||
<title>RustDesk</title>
|
<title>RustDesk</title>
|
||||||
<script src="/webclient-config/index.js"></script>
|
<script src="/webclient-config/index.js"></script>
|
||||||
<link rel="manifest" href="manifest.json"/>
|
<link rel="manifest" href="manifest.json"/>
|
||||||
<script type="module" crossorigin src="js/dist/index.js?v=ddbe54f1"></script>
|
<script type="module" crossorigin src="js/dist/index.js?v=bd4ac5e9"></script>
|
||||||
<link rel="modulepreload" href="js/dist/vendor.js?v=0b990c6e"/>
|
<link rel="modulepreload" href="js/dist/vendor.js?v=0b990c6e"/>
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
@@ -319,26 +319,5 @@
|
|||||||
</script>
|
</script>
|
||||||
<script src="libs/stream/ponyfill.min.js"></script>
|
<script src="libs/stream/ponyfill.min.js"></script>
|
||||||
<script src="libs/stream/StreamSaver.min.js"></script>
|
<script src="libs/stream/StreamSaver.min.js"></script>
|
||||||
<script src="libs/firebase-app.js?8.10.1"></script>
|
|
||||||
<script src="libs/firebase-analytics.js?8.10.1"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Your web app's Firebase configuration
|
|
||||||
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
|
|
||||||
const firebaseConfig = {
|
|
||||||
apiKey: "AIzaSyCgehIZk1aFP0E7wZtYRRqrfvNiNAF39-A",
|
|
||||||
authDomain: "rustdesk.firebaseapp.com",
|
|
||||||
databaseURL: "https://rustdesk.firebaseio.com",
|
|
||||||
projectId: "rustdesk",
|
|
||||||
storageBucket: "rustdesk.appspot.com",
|
|
||||||
messagingSenderId: "768133699366",
|
|
||||||
appId: "1:768133699366:web:d50faf0792cb208d7993e7",
|
|
||||||
measurementId: "G-9PEH85N6ZQ",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize Firebase
|
|
||||||
firebase.initializeApp(firebaseConfig);
|
|
||||||
firebase.analytics();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
5825
resources/web2/js/dist/index.js
vendored
5825
resources/web2/js/dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
5862
resources/web2/js/dist/lang.js
vendored
5862
resources/web2/js/dist/lang.js
vendored
File diff suppressed because it is too large
Load Diff
188657
resources/web2/main.dart.js
vendored
188657
resources/web2/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@@ -137,6 +137,17 @@ func (ls *LdapService) Authenticate(username, password string) (*model.User, err
|
|||||||
return nil, ErrLdapUserDisabled
|
return nil, ErrLdapUserDisabled
|
||||||
}
|
}
|
||||||
cfg := &Config.Ldap
|
cfg := &Config.Ldap
|
||||||
|
|
||||||
|
// Skip allow-group check for admins
|
||||||
|
isAdmin := ls.isUserAdmin(cfg, ldapUser)
|
||||||
|
|
||||||
|
// non-admins only check if allow-group is configured
|
||||||
|
if !isAdmin && cfg.User.AllowGroup != "" {
|
||||||
|
if !ls.isUserInGroup(cfg, ldapUser, cfg.User.AllowGroup) {
|
||||||
|
return nil, errors.New("user not in allowed group")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = ls.verifyCredentials(cfg, ldapUser.Dn, password)
|
err = ls.verifyCredentials(cfg, ldapUser.Dn, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -148,6 +159,46 @@ func (ls *LdapService) Authenticate(username, password string) (*model.User, err
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isUserInGroup checks if the user is a member of the specified group. by_sw
|
||||||
|
func (ls *LdapService) isUserInGroup(cfg *config.Ldap, ldapUser *LdapUser, groupDN string) bool {
|
||||||
|
// Check "memberOf" directly
|
||||||
|
if len(ldapUser.MemberOf) > 0 {
|
||||||
|
for _, group := range ldapUser.MemberOf {
|
||||||
|
if strings.EqualFold(group, groupDN) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For "member" attribute, perform a reverse search on the group
|
||||||
|
member := "member"
|
||||||
|
userDN := ldap.EscapeFilter(ldapUser.Dn)
|
||||||
|
groupDN = ldap.EscapeFilter(groupDN)
|
||||||
|
groupFilter := fmt.Sprintf("(%s=%s)", member, userDN)
|
||||||
|
|
||||||
|
// Create the LDAP search request
|
||||||
|
groupSearchRequest := ldap.NewSearchRequest(
|
||||||
|
groupDN,
|
||||||
|
ldap.ScopeWholeSubtree,
|
||||||
|
ldap.NeverDerefAliases,
|
||||||
|
0, // Unlimited search results
|
||||||
|
0, // No time limit
|
||||||
|
false, // Return both attributes and DN
|
||||||
|
groupFilter,
|
||||||
|
[]string{"dn"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perform the group search
|
||||||
|
groupResult, err := ls.searchResult(cfg, groupSearchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any results are returned, the user is part of the group
|
||||||
|
return len(groupResult.Entries) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// mapToLocalUser checks whether the user exists locally; if not, creates one.
|
// mapToLocalUser checks whether the user exists locally; if not, creates one.
|
||||||
// If the user exists and Ldap.Sync is enabled, it updates local info.
|
// If the user exists and Ldap.Sync is enabled, it updates local info.
|
||||||
func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.User, error) {
|
func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.User, error) {
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/model"
|
"github.com/lejianwen/rustdesk-api/v2/model"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/utils"
|
"github.com/lejianwen/rustdesk-api/v2/utils"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/github"
|
"golang.org/x/oauth2/github"
|
||||||
|
|
||||||
// "golang.org/x/oauth2/google"
|
// "golang.org/x/oauth2/google"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
// "io"
|
// "io"
|
||||||
@@ -173,14 +175,10 @@ func (os *OauthService) GetOauthConfig(op string) (err error, oauthInfo *model.O
|
|||||||
if oauthInfo.Id == 0 || oauthInfo.ClientId == "" || oauthInfo.ClientSecret == "" {
|
if oauthInfo.Id == 0 || oauthInfo.ClientId == "" || oauthInfo.ClientSecret == "" {
|
||||||
return errors.New("ConfigNotFound"), nil, nil, nil
|
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{
|
oauthConfig = &oauth2.Config{
|
||||||
ClientID: oauthInfo.ClientId,
|
ClientID: oauthInfo.ClientId,
|
||||||
ClientSecret: oauthInfo.ClientSecret,
|
ClientSecret: oauthInfo.ClientSecret,
|
||||||
RedirectURL: oauthInfo.RedirectUrl,
|
RedirectURL: Config.Rustdesk.ApiServer + "/api/oidc/callback",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maybe should validate the oauthConfig here
|
// Maybe should validate the oauthConfig here
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/lejianwen/rustdesk-api/v2/model"
|
|
||||||
"github.com/lejianwen/rustdesk-api/v2/utils"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/lejianwen/rustdesk-api/v2/model"
|
||||||
|
"github.com/lejianwen/rustdesk-api/v2/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,7 +55,18 @@ func (us *UserService) InfoByUsernamePassword(username, password string) *model.
|
|||||||
Logger.Warn("Fallback to local database")
|
Logger.Warn("Fallback to local database")
|
||||||
}
|
}
|
||||||
u := &model.User{}
|
u := &model.User{}
|
||||||
DB.Where("username = ? and password = ?", username, us.EncryptPassword(password)).First(u)
|
DB.Where("username = ?", username).First(u)
|
||||||
|
if u.Id == 0 {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
ok, newHash, err := utils.VerifyPassword(u.Password, password)
|
||||||
|
if err != nil || !ok {
|
||||||
|
return &model.User{}
|
||||||
|
}
|
||||||
|
if newHash != "" {
|
||||||
|
DB.Model(u).Update("password", newHash)
|
||||||
|
u.Password = newHash
|
||||||
|
}
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,11 +162,6 @@ func (us *UserService) ListIdAndNameByGroupId(groupId uint) (res []*model.User)
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptPassword 加密密码
|
|
||||||
func (us *UserService) EncryptPassword(password string) string {
|
|
||||||
return utils.Md5(password + "rustdesk-api")
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckUserEnable 判断用户是否禁用
|
// CheckUserEnable 判断用户是否禁用
|
||||||
func (us *UserService) CheckUserEnable(u *model.User) bool {
|
func (us *UserService) CheckUserEnable(u *model.User) bool {
|
||||||
return u.Status == model.COMMON_STATUS_ENABLE
|
return u.Status == model.COMMON_STATUS_ENABLE
|
||||||
@@ -168,7 +174,11 @@ func (us *UserService) Create(u *model.User) error {
|
|||||||
return errors.New("UsernameExists")
|
return errors.New("UsernameExists")
|
||||||
}
|
}
|
||||||
u.Username = us.formatUsername(u.Username)
|
u.Username = us.formatUsername(u.Username)
|
||||||
u.Password = us.EncryptPassword(u.Password)
|
var err error
|
||||||
|
u.Password, err = utils.EncryptPassword(u.Password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
res := DB.Create(u).Error
|
res := DB.Create(u).Error
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@@ -268,8 +278,12 @@ func (us *UserService) FlushTokenByUuids(uuids []string) error {
|
|||||||
|
|
||||||
// UpdatePassword 更新密码
|
// UpdatePassword 更新密码
|
||||||
func (us *UserService) UpdatePassword(u *model.User, password string) error {
|
func (us *UserService) UpdatePassword(u *model.User, password string) error {
|
||||||
u.Password = us.EncryptPassword(password)
|
var err error
|
||||||
err := DB.Model(u).Update("password", u.Password).Error
|
u.Password, err = utils.EncryptPassword(password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = DB.Model(u).Update("password", u.Password).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -381,10 +395,10 @@ func (us *UserService) UserThirdInfo(userId uint, op string) *model.UserThird {
|
|||||||
return ut
|
return ut
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindLatestUserIdFromLoginLogByUuid 根据uuid查找最后登录的用户id
|
// FindLatestUserIdFromLoginLogByUuid 根据uuid和设备id查找最后登录的用户id
|
||||||
func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string) uint {
|
func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string, deviceId string) uint {
|
||||||
llog := &model.LoginLog{}
|
llog := &model.LoginLog{}
|
||||||
DB.Where("uuid = ?", uuid).Order("id desc").First(llog)
|
DB.Where("uuid = ? and device_id = ?", uuid, deviceId).Order("id desc").First(llog)
|
||||||
return llog.UserId
|
return llog.UserId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,8 +500,9 @@ func (us *UserService) RefreshAccessToken(ut *model.UserToken) {
|
|||||||
ut.ExpiredAt = us.UserTokenExpireTimestamp()
|
ut.ExpiredAt = us.UserTokenExpireTimestamp()
|
||||||
DB.Model(ut).Update("expired_at", ut.ExpiredAt)
|
DB.Model(ut).Update("expired_at", ut.ExpiredAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
|
func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
|
||||||
if ut.ExpiredAt-time.Now().Unix() < 86400 {
|
if ut.ExpiredAt-time.Now().Unix() < Config.App.TokenExpire.Milliseconds()/3000 {
|
||||||
us.RefreshAccessToken(ut)
|
us.RefreshAccessToken(ut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var capdString = base64Captcha.NewDriverString(50, 150, 0, 5, 4, "123456789abcdefghijklmnopqrstuvwxyz", nil, nil, nil)
|
var capdString = base64Captcha.NewDriverString(50, 150, 0, 5, 4, "123456789abcdefghijklmnopqrstuvwxyz", nil, nil,
|
||||||
|
[]string{"3Dumb.ttf", "ApothecaryFont.ttf", "Comismsh.ttf", "Flim-Flam.ttf", "RitaSmith.ttf", "wqy-microhei.ttc"})
|
||||||
|
|
||||||
var capdMath = base64Captcha.NewDriverMath(50, 150, 3, 10, nil, nil, nil)
|
var capdMath = base64Captcha.NewDriverMath(50, 150, 3, 10, nil, nil, nil)
|
||||||
|
|
||||||
|
|||||||
42
utils/password.go
Normal file
42
utils/password.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncryptPassword hashes the input password using bcrypt.
|
||||||
|
// An error is returned if hashing fails.
|
||||||
|
func EncryptPassword(password string) (string, error) {
|
||||||
|
bs, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(bs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPassword checks the input password against the stored hash.
|
||||||
|
// When a legacy MD5 hash is provided, the password is rehashed with bcrypt
|
||||||
|
// and the new hash is returned. Any internal bcrypt error is returned.
|
||||||
|
func VerifyPassword(hash, input string) (bool, string, error) {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(input))
|
||||||
|
if err == nil {
|
||||||
|
return true, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalidPrefixErr bcrypt.InvalidHashPrefixError
|
||||||
|
if errors.As(err, &invalidPrefixErr) || errors.Is(err, bcrypt.ErrHashTooShort) {
|
||||||
|
// Try fallback to legacy MD5 hash verification
|
||||||
|
if hash == Md5(input+"rustdesk-api") {
|
||||||
|
newHash, err2 := bcrypt.GenerateFromPassword([]byte(input), bcrypt.DefaultCost)
|
||||||
|
if err2 != nil {
|
||||||
|
return true, "", err2
|
||||||
|
}
|
||||||
|
return true, string(newHash), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||||
|
return false, "", nil
|
||||||
|
}
|
||||||
|
return false, "", err
|
||||||
|
}
|
||||||
40
utils/password_test.go
Normal file
40
utils/password_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVerifyPasswordMD5(t *testing.T) {
|
||||||
|
hash := Md5("secret" + "rustdesk-api")
|
||||||
|
ok, newHash, err := VerifyPassword(hash, "secret")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("md5 verify failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok || newHash == "" {
|
||||||
|
t.Fatalf("md5 migration failed")
|
||||||
|
}
|
||||||
|
if bcrypt.CompareHashAndPassword([]byte(newHash), []byte("secret")) != nil {
|
||||||
|
t.Fatalf("invalid rehash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyPasswordBcrypt(t *testing.T) {
|
||||||
|
b, _ := bcrypt.GenerateFromPassword([]byte("pass"), bcrypt.DefaultCost)
|
||||||
|
ok, newHash, err := VerifyPassword(string(b), "pass")
|
||||||
|
if err != nil || !ok || newHash != "" {
|
||||||
|
t.Fatalf("bcrypt verify failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyPasswordMigrate(t *testing.T) {
|
||||||
|
md5hash := Md5("mypass" + "rustdesk-api")
|
||||||
|
ok, newHash, err := VerifyPassword(md5hash, "mypass")
|
||||||
|
if err != nil || !ok || newHash == "" {
|
||||||
|
t.Fatalf("expected bcrypt rehash")
|
||||||
|
}
|
||||||
|
if bcrypt.CompareHashAndPassword([]byte(newHash), []byte("mypass")) != nil {
|
||||||
|
t.Fatalf("rehash not valid bcrypt")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
crand "crypto/rand"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -69,8 +69,12 @@ func RandomString(n int) string {
|
|||||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
length := len(letterBytes)
|
length := len(letterBytes)
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
for i := range b {
|
randomBytes := make([]byte, n)
|
||||||
b[i] = letterBytes[rand.Intn(length)]
|
if _, err := crand.Read(randomBytes); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for i, rb := range randomBytes {
|
||||||
|
b[i] = letterBytes[int(rb)%length]
|
||||||
}
|
}
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user