Compare commits

..

16 Commits

Author SHA1 Message Date
ljw
a4b413dadb add oauth loginlog & fix bugs 2024-09-19 10:44:49 +08:00
ljw
ebd1feb8d1 update README.md 2024-09-18 21:27:44 +08:00
ljw
815ec0f75d update Dockerfile 2024-09-18 10:48:04 +08:00
ljw
4c7919e6b7 add .gitattributes 2024-09-14 15:15:15 +08:00
ljw
f3218d481c add .gitattributes 2024-09-14 15:13:18 +08:00
ljw
5e6e90788b add .gitattributes 2024-09-14 15:11:33 +08:00
ljw
579a6400ca add .gitattributes 2024-09-14 15:10:55 +08:00
ljw
db4ad69d8f add .gitattributes 2024-09-14 15:07:35 +08:00
ljw
b1293189ad add .gitattributes 2024-09-14 15:06:50 +08:00
ljw
c8b3d41d9d add README_EN.md 2024-09-14 08:57:47 +08:00
ljw
fefb679cfc up docker 2024-09-14 07:59:35 +08:00
ljw
352941a20f up README.md 2024-09-13 22:51:13 +08:00
ljw
3006f5bd7c up env 2024-09-13 22:39:00 +08:00
ljw
d9ed2127da up env 2024-09-13 22:09:46 +08:00
ljw
f2ee00d49e up readme 2024-09-13 21:18:18 +08:00
ljw
c827a4e335 add Dockerfile & up readme 2024-09-13 21:01:37 +08:00
55 changed files with 3870 additions and 190 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
resources/web/**/* linguist-vendored

38
Dockerfile Normal file
View File

@@ -0,0 +1,38 @@
FROM golang:1.22-alpine as builder
RUN set -eux; \
apk add --no-cache git gcc build-base sqlite-dev npm nodejs; \
git clone https://github.com/lejianwen/rustdesk-api-web; \
git clone https://github.com/lejianwen/rustdesk-api; \
#先编译后台
cd rustdesk-api-web; \
npm install; \
npm run build; \
cd ..; \
mkdir -p rustdesk-api/resources/admin; \
cp -ar rustdesk-api-web/dist/* rustdesk-api/resources/admin; \
cd rustdesk-api; \
go mod tidy; \
go install github.com/swaggo/swag/cmd/swag@latest; \
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin; \
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api; \
go env -w GO111MODULE=on;\
go env -w CGO_ENABLED=1;\
go env -w GOOS=linux;\
go env -w GOARCH=amd64;\
go env -w CGO_LDFLAGS="-static"; \
go build -o release/apimain cmd/apimain.go; \
cp -ar resources release/; \
mkdir -p release/resources/public; \
cp -ar docs release/; \
cp -ar conf release/; \
mkdir -p release/data; \
mkdir -p release/runtime;
VOLUME /app/data
FROM alpine
WORKDIR /app
COPY --from=builder /go/rustdesk-api/release /app/
EXPOSE 21114
CMD ["./apimain"]

188
README.md
View File

@@ -1,54 +1,84 @@
# RustDesk API
[English Doc](README_EN.md)
本项目使用 Go 实现了 RustDesk 的 API并包含了 Web UI 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。
<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"/>
</div>
## 使用前准备
### Rustdesk
### [Rustdesk](https://github.com/rustdesk/rustdesk)
1. PC客户端使用的是 ***1.3.0***,经测试 ***1.2.6+*** 都可以
2. server端必须指定key不能用自带的生成的key,否则可能链接不上或者超时
```bash
hbbs -r <relay-server-ip[:port]> -k 123456789
hbbr -k 123456789
hbbs -r <relay-server-ip[:port]> -k <key>
hbbr -k <key>
```
比如
```bash
hbbs -r <relay-server-ip[:port]> -k abc1234567
hbbr -k abc1234567
```
## 功能
### **API 服务**: 基本实现了PC端基础的接口。
### API 服务: 基本实现了PC端基础的接口。
#### 登录
- 添加了`github`登录需要在后台配置好就可以用了具体可看后台OAuth配置
- 添加了web后台授权登录
![pc_login](docs/pc_login.png)
#### 地址簿
![pc_ab](docs/pc_ab.png)
#### 群组,群组分为`共享组`和`普通组`,共享组中所有人都能看到小组成员的地址,普通组只有管理员能看到所有小组成员的地址
![pc_gr](docs/pc_gr.png)
### **Web UI**: 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。
***前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
***初次安装管理员为用户名密码为admin admin请即时更改密码***
***后台访问地址是`http://<your server>:21114/_admin/`初次安装管理员为用户名密码为`admin` `admin`,请即时更改密码***
1. 管理员界面
![web_admin](docs/web_admin.png)
2. 普通用户界面
![web_user](docs/web_user.png)
3. 更改密码在右上角
![web_resetpwd](docs/web_resetpwd.png)
![web_user](docs/web_admin_user.png)
3. 右上角也可以更改密码
![web_resetpwd](docs/web_resetpwd.png)
4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组``普通组`
![web_admin_gr](docs/web_admin_gr.png)
5. 可以直接打开webclient方便使用
![web_webclient](docs/admin_webclient.png)
### **Web 客户端**:
### **Web Client**:
1. 如果已经登录了后台web client将自动直接登录
2. 如果没登录后台点击右上角登录即可api server已经自动配置好了
3. 登录后台后会将地址簿自动保存到web client中方便使用
![webclient_conf](docs/webclient_conf.png)
3. 登录后会自动同步ID服务器和KEY
4. 登录后会将地址簿自动保存到web client中方便使用
### **自动化文档**: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
1. 后台文档 <youer server>/admin/swagger/index.html
2. PC端文档 <youer server>/swagger/index.html
1. 后台文档 `<youer server>/admin/swagger/index.html`
2. PC端文档 `<youer server>/swagger/index.html`
![api_swag](docs/api_swag.png)
## 安装与运行
@@ -62,6 +92,7 @@ gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
@@ -78,12 +109,131 @@ rustdesk:
key: "123456789"
```
* 环境变量,变量名前缀是RUSTDESK_API环境变量如果存在将覆盖配置文件中的配置
| 变量名 | 说明 | 示例 |
|:------------------------------------|:-------------------------------------|-----------------------------|
| -----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 |
| -----MYSQL配置----- | -----数据库类型为sqlite时不用填----- | ---------- |
| 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 |
### 安装步骤
#### docker运行
1. 直接docker运行,配置可以通过挂载配置文件`/app/conf/config.yaml`来修改,或者通过环境变量覆盖配置文件中的配置
```bash
docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=123456789 \
lejianwen/rustdesk-api
```
2. 使用`docker compose`
- 简单示例
```docker-compose
services:
rustdesk-api:
container_name: rustdesk-api
environment:
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
- 根据rustdesk提供的示例加上自己的rustdesk-api
```docker-compose
networks:
rustdesk-net:
external: false
services:
hbbs:
container_name: hbbs
ports:
- 21115:21115
- 21116:21116 # 自定义 hbbs 映射端口
- 21116:21116/udp # 自定义 hbbs 映射端口
- 21118:21118 # web client 需要
image: rustdesk/rustdesk-server
command: hbbs -r <relay-server-ip[:port]> -k 123456789 # 填入个人域名或 IP + hbbr 暴露端口
volumes:
- /data/rustdesk/hbbs:/root # 自定义挂载目录
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # 自定义 hbbr 映射端口
image: rustdesk/rustdesk-server
command: hbbr -k 123456789
#command: hbbr
volumes:
- /data/rustdesk/hbbr:/root # 自定义挂载目录
networks:
- rustdesk-net
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
rustdesk-api:
container_name: rustdesk-api
environment:
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
#### 下载release直接运行
下载地址[release](https://github.com/lejianwen/rustdesk-api/releases)
#### 源码安装
1. 克隆仓库
@@ -91,15 +241,19 @@ rustdesk:
git clone https://github.com/lejianwen/rustdesk-api.git
cd rustdesk-api
```
2. 安装依赖
```bash
go mod tidy
#安装swag如果不需要生成文档可以不安装
go install github.com/swaggo/swag/cmd/swag@latest
```
3. 编译后台前端,前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)中
```bash
cd resources
mkdir -p admin
git clone https://github.com/lejianwen/rustdesk-api-web
cd rustdesk-api-web
npm install
@@ -114,5 +268,11 @@ rustdesk:
go generate generate_api.go
```
5. 编译,如果想自己编译,先cd到项目根目录然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release`
目录下生成对应的可执行文件。
目录下生成对应的可执行文件。直接运行编译后的可执行文件即可。
6. 打开浏览器访问`http://<your server>:21114/_admin/`,默认用户名密码为`admin`,请及时更改密码。
## 其他
- [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [webclient](https://hub.docker.com/r/keyurbhole/flutter_web_desk)

215
README_EN.md Normal file
View File

@@ -0,0 +1,215 @@
# RustDesk API
This project implements RustDesk's API using Go and includes a Web UI and Web client. RustDesk is a remote desktop software that provides a self-hosting solution.
<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"/>
</div>
## Preparation
### [Rustdesk](https://github.com/rustdesk/rustdesk)
1. The PC client version used is ***1.3.0***, and versions ***1.2.6+*** have been tested to work.
2. The server must specify a key and not use the built-in generated key; otherwise, connection issues or timeouts may occur.
```bash
hbbs -r <relay-server-ip[:port]> -k 123456789
hbbr -k 123456789
```
## Features
### **API Service**: Implements the basic interfaces for the PC client.
![pc_ab](docs/pc_ab.png)
![pc_gr](docs/pc_gr.png)
### **Web UI**: Uses a front-end and back-end separation, providing a user-friendly management interface primarily for administration and display.
***The front-end code is available at [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web).***
***The admin panel can be accessed at `http://<your server>:21114/_admin/` with default credentials of `admin admin`. Please change the password promptly.***
1. Admin interface
![web_admin](docs/web_admin.png)
2. Regular user interface
![web_user](docs/web_user.png)
3. Password can be changed from the top-right corner
![web_resetpwd](docs/web_resetpwd.png)
4. Groups can be customized for easier management. Two types of groups are currently supported: `Shared Group` and `Regular Group`.
![web_admin_gr](docs/web_admin_gr.png)
### **Web Client**:
1. If you are already logged in to the admin panel, the web client will automatically log in.
2. If not logged in, click the login button in the top-right corner; the API server will be auto-configured.
3. Once logged into the admin panel, the address book will be saved automatically in the web client for convenience.
![webclient_conf](docs/webclient_conf.png)
### **Automated Documentation**: API documentation is generated using Swag, making it easier for developers to understand and use the API.
1. Admin documentation: `<your server>/admin/swagger/index.html`
2. PC client documentation: `<your server>/swagger/index.html`
![api_swag](docs/api_swag.png)
## Installation and Running
### Configuration
* Refer to the `conf/config.yaml` file to modify relevant configurations. If `gorm.type` is `sqlite`, MySQL configurations are not required.
```yaml
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk"
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
```
### Installation Steps
#### Running with Docker
1. Run directly using Docker:
```bash
docker run -d --name rustdesk-api -p 21114:21114 -v /data/rustdesk/api:/app/data lejianwen/rustdesk-api
```
- Environment variables with the prefix `RUSTDESK_API` can be set.
| Variable Name | Description | Example |
|----------------------------------------|--------------------------------------------------|------------------------------|
| -----------GORM Configuration----------| -------------------------------------------------| -----------------------------|
| RUSTDESK_API_GORM_TYPE | Database type, either `sqlite` or `mysql`. Default is `sqlite` | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum number of idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum number of open connections | 100 |
| -----------MySQL Configuration---------| ---Not required if using `sqlite`--- | |
| 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 |
2. Use `docker-compose`, adding your RustDesk API configuration to the provided RustDesk example:
```docker-compose
networks:
rustdesk-net:
external: false
services:
hbbs:
container_name: hbbs
ports:
- 21115:21115
- 21116:21116 # Custom hbbs port mapping
- 21116:21116/udp # Custom hbbs port mapping
- 21118:21118 # Required for web client
image: rustdesk/rustdesk-server
command: hbbs -r <relay-server-ip[:port]> -k 123456789 # Use your domain or IP + hbbr exposed port
volumes:
- /data/rustdesk/hbbs:/root # Custom mount directory
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # Custom hbbr port mapping
image: rustdesk/rustdesk-server
command: hbbr -k 123456789
volumes:
- /data/rustdesk/hbbr:/root # Custom mount directory
networks:
- rustdesk-net
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
rustdesk-api:
container_name: rustdesk-api
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data # Mount database for easy backups
networks:
- rustdesk-net
restart: unless-stopped
```
#### Running from Release
Download the release from [release](https://github.com/lejianwen/rustdesk-api/releases).
#### Source Installation
1. Clone the repository:
```bash
git clone https://github.com/lejianwen/rustdesk-api.git
cd rustdesk-api
```
2. Install dependencies:
```bash
go mod tidy
# Install Swag if you need to generate documentation; otherwise, you can skip this step
go install github.com/swaggo/swag/cmd/swag@latest
```
3. Build the admin front-end (the front-end code is in [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)):
```bash
cd resources
mkdir -p admin
git clone https://github.com/lejianwen/rustdesk-api-web
cd rustdesk-api-web
npm install
npm run build
cp -ar dist/* ../admin/
```
4. Run:
```bash
# Run directly
go run cmd/apimain.go
# Or generate and run the API using generate_api.go
go generate generate_api.go
```
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 directly.
6. Open your browser and visit `http://<your server>:21114/_admin/`, with default credentials `admin admin`. Please change the password promptly.
## Miscellaneous
- [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [webclient](https://hub.docker.com/r/keyurbhole/flutter_web_desk)

View File

@@ -1,4 +1,5 @@
@echo off
rmdir /s /q release
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go env -w CGO_ENABLED=1

View File

@@ -1,5 +1,6 @@
#!/bin/sh
rm release -rf
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go env -w CGO_ENABLED=1
@@ -11,5 +12,5 @@ go build -o release/apimain cmd/apimain.go
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
mkdir release/data
mkdir release/runtime
mkdir -p release/data
mkdir -p release/runtime

View File

@@ -33,9 +33,7 @@ import (
// @name Authorization
func main() {
//配置解析
global.Viper = config.Init(&global.Config, func() {
fmt.Println(global.Config)
})
global.Viper = config.Init(&global.Config)
//日志
global.Logger = logger.New(&logger.Config{
@@ -159,7 +157,7 @@ func ApiInitValidator() {
}
func DatabaseAutoUpdate() {
version := 100
version := 103
db := global.DB
@@ -217,6 +215,9 @@ func Migrate(version uint) {
&model.AddressBook{},
&model.Peer{},
&model.Group{},
&model.UserThird{},
&model.Oauth{},
&model.LoginLog{},
)
if err != nil {
fmt.Println("migrate err :=>", err)

View File

@@ -2,33 +2,34 @@ gin:
api-addr: "0.0.0.0:21114"
mode: "release" #release,debug,test
resources-path: 'resources' #对外静态文件目录
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk2"
rustdesk:
id-server: "124.220.134.240:21116"
relay-server: "124.220.134.240:21117"
api-server: "http://127.0.0.1:21114"
key: "ljw19891989"
redis:
addr: "127.0.0.1:6379"
username: ""
password: ""
db: 0
addr: ""
dbname: ""
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
logger:
path: "./runtime/log.txt"
level: "error" #trace,debug,info,warn,error,fatal
report-caller: true
redis:
addr: "127.0.0.1:6379"
password: ""
db: 0
cache:
type: "file"
file-dir: "./runtime/cache"
redis-addr: "127.0.0.1:6379"
redis-pwd: "ljw19891989"
redis-pwd: ""
redis-db: 0
oss:
access-key-id: ""

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"strings"
)
const (
@@ -26,7 +27,7 @@ type Config struct {
}
// Init 初始化配置
func Init(rowVal interface{}, cb func()) *viper.Viper {
func Init(rowVal interface{}) *viper.Viper {
var config string
flag.StringVar(&config, "c", "", "choose config file.")
flag.Parse()
@@ -34,6 +35,9 @@ func Init(rowVal interface{}, cb func()) *viper.Viper {
config = DefaultConfig
}
v := viper.New()
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
v.SetEnvPrefix("RUSTDESK_API")
v.SetConfigFile(config)
v.SetConfigType("yaml")
err := v.ReadInConfig()
@@ -47,10 +51,19 @@ func Init(rowVal interface{}, cb func()) *viper.Viper {
if err2 := v.Unmarshal(rowVal); err2 != nil {
fmt.Println(err2)
}
cb()
})
if err := v.Unmarshal(rowVal); err != nil {
fmt.Println(err)
}
return v
}
// ReadEnv 读取环境变量
func ReadEnv(rowVal interface{}) *viper.Viper {
v := viper.New()
v.AutomaticEnv()
if err := v.Unmarshal(rowVal); err != nil {
fmt.Println(err)
}
return v
}

View File

@@ -5,4 +5,5 @@ type Gin struct {
AdminAddr string `mapstructure:"admin-addr"`
Mode string
ResourcesPath string `mapstructure:"resources-path"`
TrustProxy string `mapstructure:"trust-proxy"`
}

13
config/oauth.go Normal file
View File

@@ -0,0 +1,13 @@
package config
type GithubOauth struct {
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"`
}
type GoogleOauth struct {
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"`
}

14
docker-compose.yaml Normal file
View File

@@ -0,0 +1,14 @@
services:
rustdesk-api:
image: lejianwen/rustdesk-api
container_name: rustdesk-api
environment:
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
restart: unless-stopped

View File

@@ -709,6 +709,172 @@ const docTemplateadmin = `{
}
}
},
"/admin/loginLog/delete": {
"post": {
"security": [
{
"token": []
}
],
"description": "登录日志删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录日志"
],
"summary": "登录日志删除",
"parameters": [
{
"description": "登录日志信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/model.LoginLog"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/loginLog/detail/{id}": {
"get": {
"security": [
{
"token": []
}
],
"description": "登录日志详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录日志"
],
"summary": "登录日志详情",
"parameters": [
{
"type": "integer",
"description": "ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.LoginLog"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/loginLog/list": {
"get": {
"security": [
{
"token": []
}
],
"description": "登录日志列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录日志"
],
"summary": "登录日志列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "页大小",
"name": "page_size",
"in": "query"
},
{
"type": "integer",
"description": "用户ID",
"name": "user_id",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.LoginLogList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/logout": {
"post": {
"description": "登出",
@@ -738,6 +904,280 @@ const docTemplateadmin = `{
}
}
},
"/admin/oauth/create": {
"post": {
"security": [
{
"token": []
}
],
"description": "创建Oauth",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "创建Oauth",
"parameters": [
{
"description": "Oauth信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.OauthForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Oauth"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/oauth/delete": {
"post": {
"security": [
{
"token": []
}
],
"description": "Oauth删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "Oauth删除",
"parameters": [
{
"description": "Oauth信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.OauthForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/oauth/detail/{id}": {
"get": {
"security": [
{
"token": []
}
],
"description": "Oauth详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "Oauth详情",
"parameters": [
{
"type": "integer",
"description": "ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Oauth"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/oauth/list": {
"get": {
"security": [
{
"token": []
}
],
"description": "Oauth列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "Oauth列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "页大小",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.OauthList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/oauth/update": {
"post": {
"security": [
{
"token": []
}
],
"description": "Oauth编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "Oauth编辑",
"parameters": [
{
"description": "Oauth信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.OauthForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.OauthList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/create": {
"post": {
"security": [
@@ -1646,6 +2086,55 @@ const docTemplateadmin = `{
}
}
},
"/admin/user/myOauth": {
"get": {
"security": [
{
"token": []
}
],
"description": "我的授权",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户"
],
"summary": "我的授权",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/admin.UserOauthItem"
}
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/user/update": {
"post": {
"security": [
@@ -1760,6 +2249,9 @@ const docTemplateadmin = `{
"password": {
"type": "string"
},
"platform": {
"type": "string"
},
"username": {
"type": "string"
}
@@ -1877,6 +2369,35 @@ const docTemplateadmin = `{
}
}
},
"admin.OauthForm": {
"type": "object",
"required": [
"client_id",
"client_secret",
"op",
"redirect_url"
],
"properties": {
"auto_register": {
"type": "boolean"
},
"client_id": {
"type": "string"
},
"client_secret": {
"type": "string"
},
"id": {
"type": "integer"
},
"op": {
"type": "string"
},
"redirect_url": {
"type": "string"
}
}
},
"admin.PeerForm": {
"type": "object",
"properties": {
@@ -1971,6 +2492,17 @@ const docTemplateadmin = `{
}
}
},
"admin.UserOauthItem": {
"type": "object",
"properties": {
"status": {
"type": "integer"
},
"third_type": {
"type": "string"
}
}
},
"admin.UserPasswordForm": {
"type": "object",
"required": [
@@ -2110,6 +2642,110 @@ const docTemplateadmin = `{
}
}
},
"model.LoginLog": {
"type": "object",
"properties": {
"client": {
"description": "webadmin,webclient,app,",
"type": "string"
},
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"ip": {
"type": "string"
},
"platform": {
"description": "windows,linux,mac,android,ios",
"type": "string"
},
"type": {
"description": "account,oauth",
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
},
"uuid": {
"type": "string"
}
}
},
"model.LoginLogList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.LoginLog"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"model.Oauth": {
"type": "object",
"properties": {
"auto_register": {
"type": "boolean"
},
"client_id": {
"type": "string"
},
"client_secret": {
"type": "string"
},
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"op": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"model.OauthList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Oauth"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"model.Peer": {
"type": "object",
"properties": {

View File

@@ -702,6 +702,172 @@
}
}
},
"/admin/loginLog/delete": {
"post": {
"security": [
{
"token": []
}
],
"description": "登录日志删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录日志"
],
"summary": "登录日志删除",
"parameters": [
{
"description": "登录日志信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/model.LoginLog"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/loginLog/detail/{id}": {
"get": {
"security": [
{
"token": []
}
],
"description": "登录日志详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录日志"
],
"summary": "登录日志详情",
"parameters": [
{
"type": "integer",
"description": "ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.LoginLog"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/loginLog/list": {
"get": {
"security": [
{
"token": []
}
],
"description": "登录日志列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录日志"
],
"summary": "登录日志列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "页大小",
"name": "page_size",
"in": "query"
},
{
"type": "integer",
"description": "用户ID",
"name": "user_id",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.LoginLogList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/logout": {
"post": {
"description": "登出",
@@ -731,6 +897,280 @@
}
}
},
"/admin/oauth/create": {
"post": {
"security": [
{
"token": []
}
],
"description": "创建Oauth",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "创建Oauth",
"parameters": [
{
"description": "Oauth信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.OauthForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Oauth"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/oauth/delete": {
"post": {
"security": [
{
"token": []
}
],
"description": "Oauth删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "Oauth删除",
"parameters": [
{
"description": "Oauth信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.OauthForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/oauth/detail/{id}": {
"get": {
"security": [
{
"token": []
}
],
"description": "Oauth详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "Oauth详情",
"parameters": [
{
"type": "integer",
"description": "ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Oauth"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/oauth/list": {
"get": {
"security": [
{
"token": []
}
],
"description": "Oauth列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "Oauth列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "页大小",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.OauthList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/oauth/update": {
"post": {
"security": [
{
"token": []
}
],
"description": "Oauth编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "Oauth编辑",
"parameters": [
{
"description": "Oauth信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.OauthForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.OauthList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/create": {
"post": {
"security": [
@@ -1639,6 +2079,55 @@
}
}
},
"/admin/user/myOauth": {
"get": {
"security": [
{
"token": []
}
],
"description": "我的授权",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户"
],
"summary": "我的授权",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/admin.UserOauthItem"
}
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/user/update": {
"post": {
"security": [
@@ -1753,6 +2242,9 @@
"password": {
"type": "string"
},
"platform": {
"type": "string"
},
"username": {
"type": "string"
}
@@ -1870,6 +2362,35 @@
}
}
},
"admin.OauthForm": {
"type": "object",
"required": [
"client_id",
"client_secret",
"op",
"redirect_url"
],
"properties": {
"auto_register": {
"type": "boolean"
},
"client_id": {
"type": "string"
},
"client_secret": {
"type": "string"
},
"id": {
"type": "integer"
},
"op": {
"type": "string"
},
"redirect_url": {
"type": "string"
}
}
},
"admin.PeerForm": {
"type": "object",
"properties": {
@@ -1964,6 +2485,17 @@
}
}
},
"admin.UserOauthItem": {
"type": "object",
"properties": {
"status": {
"type": "integer"
},
"third_type": {
"type": "string"
}
}
},
"admin.UserPasswordForm": {
"type": "object",
"required": [
@@ -2103,6 +2635,110 @@
}
}
},
"model.LoginLog": {
"type": "object",
"properties": {
"client": {
"description": "webadmin,webclient,app,",
"type": "string"
},
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"ip": {
"type": "string"
},
"platform": {
"description": "windows,linux,mac,android,ios",
"type": "string"
},
"type": {
"description": "account,oauth",
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
},
"uuid": {
"type": "string"
}
}
},
"model.LoginLogList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.LoginLog"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"model.Oauth": {
"type": "object",
"properties": {
"auto_register": {
"type": "boolean"
},
"client_id": {
"type": "string"
},
"client_secret": {
"type": "string"
},
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"op": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"model.OauthList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Oauth"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"model.Peer": {
"type": "object",
"properties": {

View File

@@ -4,6 +4,8 @@ definitions:
properties:
password:
type: string
platform:
type: string
username:
type: string
required:
@@ -85,6 +87,26 @@ definitions:
username:
type: string
type: object
admin.OauthForm:
properties:
auto_register:
type: boolean
client_id:
type: string
client_secret:
type: string
id:
type: integer
op:
type: string
redirect_url:
type: string
required:
- client_id
- client_secret
- op
- redirect_url
type: object
admin.PeerForm:
properties:
cpu:
@@ -148,6 +170,13 @@ definitions:
- status
- username
type: object
admin.UserOauthItem:
properties:
status:
type: integer
third_type:
type: string
type: object
admin.UserPasswordForm:
properties:
id:
@@ -240,6 +269,75 @@ definitions:
total:
type: integer
type: object
model.LoginLog:
properties:
client:
description: webadmin,webclient,app,
type: string
created_at:
type: string
id:
type: integer
ip:
type: string
platform:
description: windows,linux,mac,android,ios
type: string
type:
description: account,oauth
type: string
updated_at:
type: string
user_id:
type: integer
uuid:
type: string
type: object
model.LoginLogList:
properties:
list:
items:
$ref: '#/definitions/model.LoginLog'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
type: object
model.Oauth:
properties:
auto_register:
type: boolean
client_id:
type: string
client_secret:
type: string
created_at:
type: string
id:
type: integer
op:
type: string
redirect_url:
type: string
updated_at:
type: string
type: object
model.OauthList:
properties:
list:
items:
$ref: '#/definitions/model.Oauth'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
type: object
model.Peer:
properties:
cpu:
@@ -782,6 +880,105 @@ paths:
summary: 登录
tags:
- 登录
/admin/loginLog/delete:
post:
consumes:
- application/json
description: 登录日志删除
parameters:
- description: 登录日志信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.LoginLog'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录日志删除
tags:
- 登录日志
/admin/loginLog/detail/{id}:
get:
consumes:
- application/json
description: 登录日志详情
parameters:
- description: ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.LoginLog'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录日志详情
tags:
- 登录日志
/admin/loginLog/list:
get:
consumes:
- application/json
description: 登录日志列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
- description: 用户ID
in: query
name: user_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.LoginLogList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录日志列表
tags:
- 登录日志
/admin/logout:
post:
consumes:
@@ -801,6 +998,167 @@ paths:
summary: 登出
tags:
- 登录
/admin/oauth/create:
post:
consumes:
- application/json
description: 创建Oauth
parameters:
- description: Oauth信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.OauthForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.Oauth'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建Oauth
tags:
- Oauth
/admin/oauth/delete:
post:
consumes:
- application/json
description: Oauth删除
parameters:
- description: Oauth信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.OauthForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: Oauth删除
tags:
- Oauth
/admin/oauth/detail/{id}:
get:
consumes:
- application/json
description: Oauth详情
parameters:
- description: ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.Oauth'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: Oauth详情
tags:
- Oauth
/admin/oauth/list:
get:
consumes:
- application/json
description: Oauth列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.OauthList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: Oauth列表
tags:
- Oauth
/admin/oauth/update:
post:
consumes:
- application/json
description: Oauth编辑
parameters:
- description: Oauth信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.OauthForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.OauthList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: Oauth编辑
tags:
- Oauth
/admin/peer/create:
post:
consumes:
@@ -1338,6 +1696,34 @@ paths:
summary: 管理员列表
tags:
- 用户
/admin/user/myOauth:
get:
consumes:
- application/json
description: 我的授权
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
items:
$ref: '#/definitions/admin.UserOauthItem'
type: array
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 我的授权
tags:
- 用户
/admin/user/update:
post:
consumes:

BIN
docs/admin_webclient.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -233,40 +233,6 @@ const docTemplateapi = `{
}
}
},
"/currentUser": {
"get": {
"security": [
{
"token": []
}
],
"description": "用户信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户"
],
"summary": "用户信息",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.UserPayload"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/heartbeat": {
"post": {
"description": "心跳",
@@ -394,6 +360,122 @@ 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"
}
}
}
}
},
"/oauth/login": {
"get": {
"description": "WebOauthLogin",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "WebOauthLogin",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/oidc/auth": {
"post": {
"description": "OidcAuth",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuth",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.LoginRes"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/oidc/auth-query": {
"get": {
"description": "OidcAuthQuery",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuthQuery",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.LoginRes"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/peers": {
"get": {
"security": [
@@ -656,21 +738,50 @@ const docTemplateapi = `{
}
}
},
"api.DeviceInfoInLogin": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"os": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"api.LoginForm": {
"type": "object",
"required": [
"username"
],
"properties": {
"autoLogin": {
"type": "boolean"
},
"deviceInfo": {
"$ref": "#/definitions/api.DeviceInfoInLogin"
},
"id": {
"type": "string"
},
"password": {
"type": "string",
"maxLength": 20,
"minLength": 4
},
"type": {
"type": "string"
},
"username": {
"type": "string",
"maxLength": 10,
"minLength": 4
},
"uuid": {
"type": "string"
}
}
},
@@ -729,6 +840,10 @@ const docTemplateapi = `{
"email": {
"type": "string"
},
"info": {
"type": "object",
"additionalProperties": true
},
"is_admin": {
"type": "boolean"
},

View File

@@ -226,40 +226,6 @@
}
}
},
"/currentUser": {
"get": {
"security": [
{
"token": []
}
],
"description": "用户信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户"
],
"summary": "用户信息",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.UserPayload"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/heartbeat": {
"post": {
"description": "心跳",
@@ -387,6 +353,122 @@
}
}
},
"/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"
}
}
}
}
},
"/oauth/login": {
"get": {
"description": "WebOauthLogin",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "WebOauthLogin",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/oidc/auth": {
"post": {
"description": "OidcAuth",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuth",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.LoginRes"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/oidc/auth-query": {
"get": {
"description": "OidcAuthQuery",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuthQuery",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.LoginRes"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/peers": {
"get": {
"security": [
@@ -649,21 +731,50 @@
}
}
},
"api.DeviceInfoInLogin": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"os": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"api.LoginForm": {
"type": "object",
"required": [
"username"
],
"properties": {
"autoLogin": {
"type": "boolean"
},
"deviceInfo": {
"$ref": "#/definitions/api.DeviceInfoInLogin"
},
"id": {
"type": "string"
},
"password": {
"type": "string",
"maxLength": 20,
"minLength": 4
},
"type": {
"type": "string"
},
"username": {
"type": "string",
"maxLength": 10,
"minLength": 4
},
"uuid": {
"type": "string"
}
}
},
@@ -722,6 +833,10 @@
"email": {
"type": "string"
},
"info": {
"type": "object",
"additionalProperties": true
},
"is_admin": {
"type": "boolean"
},

View File

@@ -6,16 +6,35 @@ definitions:
example: '{"tags":["tag1","tag2","tag3"],"peers":[{"id":"abc","username":"abv-l","hostname":"","platform":"Windows","alias":"","tags":["tag1","tag2"],"hash":"hash"}],"tag_colors":"{\"tag1\":4288585374,\"tag2\":4278238420,\"tag3\":4291681337}"}'
type: string
type: object
api.DeviceInfoInLogin:
properties:
name:
type: string
os:
type: string
type:
type: string
type: object
api.LoginForm:
properties:
autoLogin:
type: boolean
deviceInfo:
$ref: '#/definitions/api.DeviceInfoInLogin'
id:
type: string
password:
maxLength: 20
minLength: 4
type: string
type:
type: string
username:
maxLength: 10
minLength: 4
type: string
uuid:
type: string
required:
- username
type: object
@@ -55,6 +74,9 @@ definitions:
properties:
email:
type: string
info:
additionalProperties: true
type: object
is_admin:
type: boolean
name:
@@ -242,27 +264,6 @@ paths:
summary: 用户信息
tags:
- 用户
/currentUser:
get:
consumes:
- application/json
description: 用户信息
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.UserPayload'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 用户信息
tags:
- 用户
/heartbeat:
post:
consumes:
@@ -346,6 +347,82 @@ paths:
summary: 登出
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
/oauth/login:
get:
consumes:
- application/json
description: WebOauthLogin
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
summary: WebOauthLogin
tags:
- Oauth
/oidc/auth:
post:
consumes:
- application/json
description: OidcAuth
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.LoginRes'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: OidcAuth
tags:
- Oauth
/oidc/auth-query:
get:
consumes:
- application/json
description: OidcAuthQuery
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.LoginRes'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: OidcAuthQuery
tags:
- Oauth
/peers:
get:
consumes:

BIN
docs/pc_login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/web_admin_user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 13 KiB

3
generate_run.go Normal file
View File

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

12
go.mod
View File

@@ -23,6 +23,7 @@ require (
)
require (
cloud.google.com/go/compute/metadata v0.5.1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@@ -61,12 +62,13 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@@ -5,8 +5,8 @@ import (
"Gwen/http/request/admin"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service"
"fmt"
"github.com/gin-gonic/gin"
)
@@ -25,7 +25,6 @@ type Login struct {
// @Router /admin/login [post]
// @Security token
func (ct *Login) Login(c *gin.Context) {
fmt.Println("login")
f := &admin.Login{}
err := c.ShouldBindJSON(f)
if err != nil {
@@ -45,7 +44,14 @@ func (ct *Login) Login(c *gin.Context) {
return
}
ut := service.AllService.UserService.Login(u)
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: "webadmin",
Uuid: "",
Ip: c.ClientIP(),
Type: "account",
Platform: f.Platform,
})
response.Success(c, &adResp.LoginPayload{
Token: ut.Token,

View File

@@ -0,0 +1,110 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strconv"
)
type LoginLog struct {
}
// Detail 登录日志
// @Tags 登录日志
// @Summary 登录日志详情
// @Description 登录日志详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.LoginLog}
// @Failure 500 {object} response.Response
// @Router /admin/loginLog/detail/{id} [get]
// @Security token
func (ct *LoginLog) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
u := service.AllService.LoginLogService.InfoById(uint(iid))
if u.Id > 0 {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
return
}
// List 列表
// @Tags 登录日志
// @Summary 登录日志列表
// @Description 登录日志列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param user_id query int false "用户ID"
// @Success 200 {object} response.Response{data=model.LoginLogList}
// @Failure 500 {object} response.Response
// @Router /admin/loginLog/list [get]
// @Security token
func (ct *LoginLog) List(c *gin.Context) {
query := &admin.LoginLogQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
})
response.Success(c, res)
}
// Delete 删除
// @Tags 登录日志
// @Summary 登录日志删除
// @Description 登录日志删除
// @Accept json
// @Produce json
// @Param body body model.LoginLog true "登录日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/loginLog/delete [post]
// @Security token
func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
l := service.AllService.LoginLogService.InfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id {
response.Fail(c, 101, "无权限")
return
}
if l.Id > 0 {
err := service.AllService.LoginLogService.Delete(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, "信息不存在")
}

View File

@@ -0,0 +1,291 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
adminReq "Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"strconv"
)
type Oauth struct {
}
// Info
func (o *Oauth) Info(c *gin.Context) {
code := c.Query("code")
if code == "" {
response.Fail(c, 101, "参数错误")
return
}
v := service.AllService.OauthService.GetOauthCache(code)
if v == nil {
response.Fail(c, 101, "信息不存在")
return
}
response.Success(c, v)
}
func (o *Oauth) ToBind(c *gin.Context) {
f := &adminReq.BindOauthForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, "参数错误")
return
}
u := service.AllService.UserService.CurUser(c)
utr := service.AllService.UserService.UserThirdInfo(u.Id, f.Op)
if utr.Id > 0 {
response.Fail(c, 101, "已绑定过了")
return
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, err.Error())
return
}
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
Action: service.OauthActionTypeBind,
Op: f.Op,
UserId: u.Id,
}, 5*60)
response.Success(c, gin.H{
"code": code,
"url": url,
})
}
// Confirm 确认授权登录
func (o *Oauth) Confirm(c *gin.Context) {
j := &adminReq.OauthConfirmForm{}
err := c.ShouldBindJSON(j)
if err != nil {
response.Fail(c, 101, "参数错误"+err.Error())
return
}
if j.Code == "" {
response.Fail(c, 101, "参数错误: code 不存在")
return
}
v := service.AllService.OauthService.GetOauthCache(j.Code)
if v == nil {
response.Fail(c, 101, "授权已过期")
return
}
u := service.AllService.UserService.CurUser(c)
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(j.Code, v, 0)
response.Success(c, v)
}
func (o *Oauth) BindConfirm(c *gin.Context) {
j := &adminReq.OauthConfirmForm{}
err := c.ShouldBindJSON(j)
if err != nil {
response.Fail(c, 101, "参数错误"+err.Error())
return
}
if j.Code == "" {
response.Fail(c, 101, "参数错误: code 不存在")
return
}
v := service.AllService.OauthService.GetOauthCache(j.Code)
if v == nil {
response.Fail(c, 101, "授权已过期")
return
}
u := service.AllService.UserService.CurUser(c)
err = service.AllService.OauthService.BindGithubUser(v.ThirdOpenId, v.ThirdOpenId, u.Id)
if err != nil {
response.Fail(c, 101, "绑定失败,请重试")
return
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(j.Code, v, 0)
response.Success(c, v)
}
func (o *Oauth) Unbind(c *gin.Context) {
f := &adminReq.UnBindOauthForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, "参数错误")
return
}
u := service.AllService.UserService.CurUser(c)
utr := service.AllService.UserService.UserThirdInfo(u.Id, f.Op)
if utr.Id == 0 {
response.Fail(c, 101, "未绑定")
return
}
if f.Op == model.OauthTypeGithub {
err = service.AllService.OauthService.UnBindGithubUser(u.Id)
if err != nil {
response.Fail(c, 101, "解绑失败")
return
}
}
response.Success(c, nil)
}
// Detail Oauth
// @Tags Oauth
// @Summary Oauth详情
// @Description Oauth详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.Oauth}
// @Failure 500 {object} response.Response
// @Router /admin/oauth/detail/{id} [get]
// @Security token
func (o *Oauth) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
u := service.AllService.OauthService.InfoById(uint(iid))
if u.Id > 0 {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
return
}
// Create 创建Oauth
// @Tags Oauth
// @Summary 创建Oauth
// @Description 创建Oauth
// @Accept json
// @Produce json
// @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response{data=model.Oauth}
// @Failure 500 {object} response.Response
// @Router /admin/oauth/create [post]
// @Security token
func (o *Oauth) Create(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误"+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.OauthService.InfoByOp(f.Op)
if ex.Id > 0 {
response.Fail(c, 101, "已存在"+f.Op)
return
}
u := f.ToOauth()
err := service.AllService.OauthService.Create(u)
if err != nil {
response.Fail(c, 101, "创建失败")
return
}
response.Success(c, u)
}
// List 列表
// @Tags Oauth
// @Summary Oauth列表
// @Description Oauth列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response{data=model.OauthList}
// @Failure 500 {object} response.Response
// @Router /admin/oauth/list [get]
// @Security token
func (o *Oauth) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
return
}
res := service.AllService.OauthService.List(query.Page, query.PageSize, nil)
response.Success(c, res)
}
// Update 编辑
// @Tags Oauth
// @Summary Oauth编辑
// @Description Oauth编辑
// @Accept json
// @Produce json
// @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response{data=model.OauthList}
// @Failure 500 {object} response.Response
// @Router /admin/oauth/update [post]
// @Security token
func (o *Oauth) Update(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
return
}
if f.Id == 0 {
response.Fail(c, 101, "参数错误")
return
}
errList := global.Validator.ValidStruct(f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToOauth()
err := service.AllService.OauthService.Update(u)
if err != nil {
response.Fail(c, 101, "更新失败")
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags Oauth
// @Summary Oauth删除
// @Description Oauth删除
// @Accept json
// @Produce json
// @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/oauth/delete [post]
// @Security token
func (o *Oauth) Delete(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.OauthService.InfoById(f.Id)
if u.Id > 0 {
err := service.AllService.OauthService.Delete(u)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, "信息不存在")
}

View File

@@ -259,3 +259,37 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
}
response.Success(c, nil)
}
// MyOauth
// @Tags 用户
// @Summary 我的授权
// @Description 我的授权
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=[]adResp.UserOauthItem}
// @Failure 500 {object} response.Response
// @Router /admin/user/myOauth [get]
// @Security token
func (ct *User) MyOauth(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
oal := service.AllService.OauthService.List(1, 100, nil)
ops := make([]string, 0)
for _, oa := range oal.Oauths {
ops = append(ops, oa.Op)
}
uts := service.AllService.UserService.UserThirdsByUserId(u.Id)
var res []*adResp.UserOauthItem
for _, oa := range oal.Oauths {
item := &adResp.UserOauthItem{
ThirdType: oa.Op,
}
for _, ut := range uts {
if ut.ThirdType == oa.Op {
item.Status = 1
break
}
}
res = append(res, item)
}
response.Success(c, res)
}

View File

@@ -7,7 +7,6 @@ import (
"Gwen/model"
"Gwen/service"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
@@ -66,7 +65,6 @@ func (a *Ab) UpAb(c *gin.Context) {
abf := &requstform.AddressBookForm{}
err := c.ShouldBindJSON(&abf)
if err != nil {
fmt.Println(err)
response.Error(c, "参数错误")
return
}
@@ -93,7 +91,6 @@ func (a *Ab) UpAb(c *gin.Context) {
tc := map[string]uint{}
err = json.Unmarshal([]byte(abd.TagColors), &tc)
if err != nil {
fmt.Println(err)
response.Error(c, "系统错误")
return
} else {
@@ -134,7 +131,6 @@ func (a *Ab) TagAdd(c *gin.Context) {
t := &model.Tag{}
err := c.ShouldBindJSON(t)
if err != nil {
fmt.Println(err)
response.Error(c, "参数错误")
return

View File

@@ -5,7 +5,9 @@ import (
"Gwen/http/request/api"
"Gwen/http/response"
apiResp "Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"encoding/json"
"github.com/gin-gonic/gin"
"net/http"
)
@@ -26,8 +28,9 @@ type Login struct {
func (l *Login) Login(c *gin.Context) {
f := &api.LoginForm{}
err := c.ShouldBindJSON(f)
//fmt.Println(f)
if err != nil {
response.Error(c, "系统错误")
response.Error(c, "参数错误")
return
}
@@ -44,7 +47,20 @@ func (l *Login) Login(c *gin.Context) {
return
}
ut := service.AllService.UserService.Login(u)
//根据refer判断是webclient还是app
ref := c.GetHeader("referer")
if ref != "" {
f.DeviceInfo.Type = "webclient"
}
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: f.DeviceInfo.Type,
Uuid: f.Uuid,
Ip: c.ClientIP(),
Type: model.LoginLogTypeAccount,
Platform: f.DeviceInfo.Os,
})
c.JSON(http.StatusOK, apiResp.LoginRes{
AccessToken: ut.Token,
@@ -63,11 +79,31 @@ func (l *Login) Login(c *gin.Context) {
// @Failure 500 {object} response.ErrorResponse
// @Router /login-options [post]
func (l *Login) LoginOptions(c *gin.Context) {
test := []string{
//"common-oidc/[{\"name\":\"google\"},{\"name\":\"github\"},{\"name\":\"facebook\"},{\"name\":\"网页授权登录\",\"icon\":\"\"}]",
//"oidc/myapp",
oauthOks := []string{}
err, _ := service.AllService.OauthService.GetOauthConfig(model.OauthTypeGithub)
if err == nil {
oauthOks = append(oauthOks, model.OauthTypeGithub)
}
c.JSON(http.StatusOK, test)
err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeGoogle)
if err == nil {
oauthOks = append(oauthOks, model.OauthTypeGoogle)
}
oauthOks = append(oauthOks, model.OauthTypeWebauth)
var oidcItems []map[string]string
for _, v := range oauthOks {
oidcItems = append(oidcItems, map[string]string{"name": v})
}
common, err := json.Marshal(oidcItems)
if err != nil {
response.Error(c, "参数错误")
return
}
var res []string
res = append(res, "common-oidc/"+string(common))
for _, v := range oauthOks {
res = append(res, "oidc/"+v)
}
c.JSON(http.StatusOK, res)
}
// Logout

View File

@@ -0,0 +1,222 @@
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"
"net/http"
"strconv"
)
type Oauth struct {
}
// OidcAuth
// @Tags Oauth
// @Summary OidcAuth
// @Description OidcAuth
// @Accept json
// @Produce json
// @Success 200 {object} apiResp.LoginRes
// @Failure 500 {object} response.ErrorResponse
// @Router /oidc/auth [post]
func (o *Oauth) OidcAuth(c *gin.Context) {
f := &api.OidcAuthRequest{}
err := c.ShouldBindJSON(&f)
if err != nil {
response.Error(c, "参数错误")
return
}
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub {
response.Error(c, "参数错误")
return
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, err.Error())
return
}
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin,
Id: f.Id,
Op: f.Op,
Uuid: f.Uuid,
DeviceName: f.DeviceInfo.Name,
DeviceOs: f.DeviceInfo.Os,
DeviceType: f.DeviceInfo.Type,
}, 5*60)
//fmt.Println("code url", code, url)
c.JSON(http.StatusOK, gin.H{
"code": code,
"url": url,
})
}
// OidcAuthQuery
// @Tags Oauth
// @Summary OidcAuthQuery
// @Description OidcAuthQuery
// @Accept json
// @Produce json
// @Success 200 {object} apiResp.LoginRes
// @Failure 500 {object} response.ErrorResponse
// @Router /oidc/auth-query [get]
func (o *Oauth) OidcAuthQuery(c *gin.Context) {
q := &api.OidcAuthQuery{}
err := c.ShouldBindQuery(q)
if err != nil {
response.Error(c, "参数错误")
return
}
v := service.AllService.OauthService.GetOauthCache(q.Code)
if v == nil {
response.Error(c, "授权已过期,请重新授权")
return
}
if v.UserId == 0 {
//正在授权
c.JSON(http.StatusOK, gin.H{})
return
}
u := service.AllService.UserService.InfoById(v.UserId)
//fmt.Println("auth success u", u)
if u.Id > 0 {
service.AllService.OauthService.DeleteOauthCache(q.Code)
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: v.DeviceType,
Uuid: v.Uuid,
Ip: c.ClientIP(),
Type: model.LoginLogTypeOauth,
Platform: v.DeviceOs,
})
c.JSON(http.StatusOK, apiResp.LoginRes{
AccessToken: ut.Token,
Type: "access_token",
User: *(&apiResp.UserPayload{}).FromUser(u),
})
return
}
response.Error(c, "用户不存在")
}
// OauthCallback 回调
// @Tags Oauth
// @Summary OauthCallback
// @Description OauthCallback
// @Accept json
// @Produce json
// @Success 200 {object} apiResp.LoginRes
// @Failure 500 {object} response.ErrorResponse
// @Router /oauth/callback [get]
func (o *Oauth) OauthCallback(c *gin.Context) {
state := c.Query("state")
if state == "" {
c.String(http.StatusInternalServerError, "state为空")
return
}
cacheKey := state
//从缓存中获取
v := service.AllService.OauthService.GetOauthCache(cacheKey)
if v == nil {
c.String(http.StatusInternalServerError, "授权已过期,请重新授权")
return
}
ty := v.Op
ac := v.Action
//fmt.Println("ty ac ", ty, ac)
if ty == model.OauthTypeGithub {
code := c.Query("code")
err, userData := service.AllService.OauthService.GithubCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, "授权失败:"+err.Error())
return
}
if ac == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, strconv.Itoa(userData.Id))
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, "已经绑定其他账号")
return
}
//绑定
u := service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, "用户不存在")
return
}
//绑定github
err = service.AllService.OauthService.BindGithubUser(strconv.Itoa(userData.Id), userData.Login, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, "绑定失败")
return
}
c.String(http.StatusOK, "绑定成功")
return
}
//登录
if ac == service.OauthActionTypeLogin {
if v.UserId != 0 {
c.String(http.StatusInternalServerError, "授权已经成功")
return
}
u := service.AllService.UserService.InfoByGithubId(strconv.Itoa(userData.Id))
if u == nil {
oa := service.AllService.OauthService.InfoByOp(ty)
if !*oa.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
v.ThirdName = userData.Login
v.ThirdOpenId = strconv.Itoa(userData.Id)
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
c.Redirect(http.StatusFound, url)
return
}
//自动注册
u = service.AllService.UserService.RegisterByGithub(userData.Login, int64(userData.Id))
if u.Id == 0 {
c.String(http.StatusInternalServerError, "注册失败")
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
c.String(http.StatusOK, "授权成功")
return
}
//返回js
c.Header("Content-Type", "text/html; charset=utf-8")
c.String(http.StatusOK, "授权错误")
//up := &apiResp.UserPayload{}
//c.JSON(http.StatusOK, apiResp.LoginRes{
// AccessToken: ut.Token,
// Type: "access_token",
// User: *up.FromUser(u),
//})
}
}
// WebOauthLogin
// @Tags Oauth
// @Summary WebOauthLogin
// @Description WebOauthLogin
// @Accept json
// @Produce json
// @Success 200 {string} string
// @Failure 500 {string} string
// @Router /oauth/login [get]
func (o *Oauth) WebOauthLogin(c *gin.Context) {
}

View File

@@ -21,11 +21,11 @@ type User struct {
// @Failure 500 {object} response.Response
// @Router /currentUser [get]
// @Security token
func (u *User) currentUser(c *gin.Context) {
user := service.AllService.UserService.CurUser(c)
up := (&apiResp.UserPayload{}).FromUser(user)
c.JSON(http.StatusOK, up)
}
//func (u *User) currentUser(c *gin.Context) {
// user := service.AllService.UserService.CurUser(c)
// up := (&apiResp.UserPayload{}).FromUser(user)
// c.JSON(http.StatusOK, up)
//}
// Info 用户信息
// @Tags 用户

View File

@@ -36,7 +36,7 @@ func (i *WebClient) ServerConfig(c *gin.Context) {
gin.H{
"id_server": global.Config.Rustdesk.IdServer,
"key": global.Config.Rustdesk.Key,
//"peers": peers,
"peers": peers,
},
)
}

View File

@@ -53,7 +53,13 @@ const autoWriteServer = () => {
}
if (res.data.peers) {
localStorage.setItem('peers', JSON.stringify(res.data.peers))
oldPeers = JSON.parse(localStorage.getItem('peers')) || {}
Object.keys(res.data.peers).forEach(k => {
if(!oldPeers[k]) {
oldPeers[k] = res.data.peers[k]
}
})
localStorage.setItem('peers', JSON.stringify(oldPeers))
}
}
})

View File

@@ -7,12 +7,23 @@ import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"strings"
)
func ApiInit() {
gin.SetMode(global.Config.Gin.Mode)
g := gin.New()
//[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
//Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
if global.Config.Gin.TrustProxy != "" {
pro := strings.Split(global.Config.Gin.TrustProxy, ",")
err := g.SetTrustedProxies(pro)
if err != nil {
panic(err)
}
}
if global.Config.Gin.Mode == gin.ReleaseMode {
//修改gin Recovery日志 输出为logger的输出点
if global.Logger != nil {

View File

@@ -17,6 +17,13 @@ func RustAuth() gin.HandlerFunc {
c.Abort()
return
}
if len(token) <= 7 {
c.JSON(401, gin.H{
"error": "Unauthorized",
})
c.Abort()
return
}
//提取token格式是Bearer {token}
//这里只是简单的提取
token = token[7:]

View File

@@ -3,4 +3,11 @@ package admin
type Login struct {
Username string `json:"username" validate:"required" label:"用户名"`
Password string `json:"password,omitempty" validate:"required" label:"密码"`
Platform string `json:"platform" label:"平台"`
}
type LoginLogQuery struct {
UserId int `form:"user_id"`
IsMy int `form:"is_my"`
PageQuery
}

View File

@@ -0,0 +1,34 @@
package admin
import "Gwen/model"
type BindOauthForm struct {
Op string `json:"op" binding:"required"`
}
type OauthConfirmForm struct {
Code string `json:"code" binding:"required"`
}
type UnBindOauthForm struct {
Op string `json:"op" binding:"required"`
}
type OauthForm struct {
Id uint `json:"id"`
Op string `json:"op" validate:"required"`
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"`
}
func (of *OauthForm) ToOauth() *model.Oauth {
oa := &model.Oauth{
Op: of.Op,
ClientId: of.ClientId,
ClientSecret: of.ClientSecret,
RedirectUrl: of.RedirectUrl,
AutoRegister: of.AutoRegister,
}
oa.Id = of.Id
return oa
}

14
http/request/api/oauth.go Normal file
View File

@@ -0,0 +1,14 @@
package api
type OidcAuthRequest struct {
DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"`
Id string `json:"id" label:"id"`
Op string `json:"op" label:"op"`
Uuid string `json:"uuid" label:"uuid"`
}
type OidcAuthQuery struct {
Code string `json:"code" form:"code" label:"code"`
Id string `json:"id" form:"id" label:"id"`
Uuid string `json:"uuid" form:"uuid" label:"uuid"`
}

View File

@@ -21,9 +21,21 @@ package api
bytes hwid = 14;
}
*/
type DeviceInfoInLogin struct {
Name string `json:"name" label:"name"`
Os string `json:"os" label:"os"`
Type string `json:"type" label:"type"`
}
type LoginForm struct {
Username string `json:"username" validate:"required,gte=4,lte=10" label:"用户名"`
Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"`
AutoLogin bool `json:"autoLogin" label:"自动登录"`
DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"`
Id string `json:"id" label:"id"`
Type string `json:"type" label:"type"`
Uuid string `json:"uuid" label:"uuid"`
Username string `json:"username" validate:"required,gte=4,lte=10" label:"用户名"`
Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"`
}
type UserListQuery struct {

View File

@@ -8,6 +8,11 @@ type LoginPayload struct {
}
var UserRouteNames = []string{
"MyTagList", "MyAddressBookList",
"MyTagList", "MyAddressBookList", "MyInfo",
}
var AdminRouteNames = []string{"*"}
type UserOauthItem struct {
ThirdType string `json:"third_type"`
Status int `json:"status"`
}

View File

@@ -19,17 +19,19 @@ UserStatus status;
bool isAdmin = false;
*/
type UserPayload struct {
Name string `json:"name"`
Email string `json:"email"`
Note string `json:"note"`
IsAdmin *bool `json:"is_admin"`
Status int `json:"status"`
Name string `json:"name"`
Email string `json:"email"`
Note string `json:"note"`
IsAdmin *bool `json:"is_admin"`
Status int `json:"status"`
Info map[string]interface{} `json:"info"`
}
func (up *UserPayload) FromUser(user *model.User) *UserPayload {
up.Name = user.Username
up.IsAdmin = user.IsAdmin
up.Status = int(user.Status)
up.Info = map[string]interface{}{}
return up
}
@@ -50,6 +52,6 @@ type LoginRes struct {
Type string `json:"type"`
AccessToken string `json:"access_token"`
User UserPayload `json:"user"`
Secret string `json:"secret"`
TfaType string `json:"tfa_type"`
Secret string `json:"secret,omitempty"`
TfaType string `json:"tfa_type,omitempty"`
}

View File

@@ -5,33 +5,6 @@ import (
"time"
)
// type T struct {
// Field1 struct {
// ViewStyle string `json:"view-style"`
// Tm int64 `json:"tm"`
// Info struct {
// Username string `json:"username"`
// Hostname string `json:"hostname"`
// Platform string `json:"platform"`
// Displays []struct {
// X int `json:"x"`
// Y int `json:"y"`
// Width int `json:"width"`
// Height int `json:"height"`
// Name string `json:"name"`
// Online bool `json:"online"`
// } `json:"displays"`
// CurrentDisplay int `json:"current_display"`
// SasEnabled bool `json:"sas_enabled"`
// Version string `json:"version"`
// ConnId int `json:"conn_id"`
// Features struct {
// PrivacyMode bool `json:"privacy_mode"`
// } `json:"features"`
// } `json:"info"`
// } `json:"1799928825"`
// }
type WebClientPeerPayload struct {
ViewStyle string `json:"view-style"`
Tm int64 `json:"tm"`
@@ -46,7 +19,8 @@ type WebClientPeerInfoPayload struct {
func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) {
wcpp.ViewStyle = "shrink"
wcpp.Tm = time.Now().UnixNano()
//24小时前
wcpp.Tm = time.Now().Add(-time.Hour * 24).UnixNano()
wcpp.Info = WebClientPeerInfoPayload{
Username: a.Username,
Hostname: a.Hostname,

View File

@@ -25,6 +25,8 @@ func Init(g *gin.Engine) {
TagBind(adg)
AddressBookBind(adg)
PeerBind(adg)
OauthBind(adg)
LoginLogBind(adg)
rs := &admin.Rustdesk{}
adg.GET("/server-config", rs.ServerConfig)
@@ -44,6 +46,7 @@ func UserBind(rg *gin.RouterGroup) {
cont := &admin.User{}
aR.GET("/current", cont.Current)
aR.POST("/changeCurPwd", cont.ChangeCurPwd)
aR.POST("/myOauth", cont.MyOauth)
}
aRP := rg.Group("/user").Use(middleware.AdminPrivilege())
{
@@ -104,6 +107,35 @@ func PeerBind(rg *gin.RouterGroup) {
}
}
func OauthBind(rg *gin.RouterGroup) {
aR := rg.Group("/oauth")
{
cont := &admin.Oauth{}
aR.POST("/confirm", cont.Confirm)
aR.POST("/bind", cont.ToBind)
aR.POST("/bindConfirm", cont.BindConfirm)
aR.POST("/unbind", cont.Unbind)
aR.GET("/info", cont.Info)
}
arp := aR.Use(middleware.AdminPrivilege())
{
cont := &admin.Oauth{}
arp.GET("/list", cont.List)
arp.GET("/detail/:id", cont.Detail)
arp.POST("/create", cont.Create)
arp.POST("/update", cont.Update)
arp.POST("/delete", cont.Delete)
}
}
func LoginLogBind(rg *gin.RouterGroup) {
aR := rg.Group("/login_log")
cont := &admin.LoginLog{}
aR.GET("/list", cont.List)
aR.POST("/delete", cont.Delete)
}
/*
func FileBind(rg *gin.RouterGroup) {
aR := rg.Group("/file")

View File

@@ -19,7 +19,7 @@ func ApiInit(g *gin.Engine) {
frg := g.Group("/api")
frg.Use(middleware.Cors())
//frg.Use(middleware.Cors())
frg.OPTIONS("/*any", nil)
i := &api.Index{}
@@ -34,6 +34,16 @@ func ApiInit(g *gin.Engine) {
frg.POST("/login", l.Login)
}
{
o := &api.Oauth{}
// [method:POST] [uri:/api/oidc/auth]
frg.POST("/oidc/auth", o.OidcAuth)
// [method:GET] [uri:/api/oidc/auth-query?code=abc&id=xxxxx&uuid=xxxxx]
frg.GET("/oidc/auth-query", o.OidcAuthQuery)
//api/oauth/callback
frg.GET("/oauth/callback", o.OauthCallback)
frg.GET("/oauth/login", o.OauthCallback)
}
{
pe := &api.Peer{}
//提交系统信息

23
model/loginLog.go Normal file
View File

@@ -0,0 +1,23 @@
package model
type LoginLog struct {
IdModel
UserId uint `json:"user_id"`
Client string `json:"client"` //webadmin,webclient,app,
Uuid string `json:"uuid"`
Ip string `json:"ip"`
Type string `json:"type"` //account,oauth
Platform string `json:"platform"` //windows,linux,mac,android,ios
TimeModel
}
const (
LoginLogTypeAccount = "account"
LoginLogTypeOauth = "oauth"
)
type LoginLogList struct {
LoginLogs []*LoginLog `json:"list"`
Pagination
}

22
model/oauth.go Normal file
View File

@@ -0,0 +1,22 @@
package model
type Oauth struct {
IdModel
Op string `json:"op"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectUrl string `json:"redirect_url"`
AutoRegister *bool `json:"auto_register"`
TimeModel
}
const (
OauthTypeGithub = "github"
OauthTypeGoogle = "google"
OauthTypeWebauth = "webauth"
)
type OauthList struct {
Oauths []*Oauth `json:"list"`
Pagination
}

12
model/userThird.go Normal file
View File

@@ -0,0 +1,12 @@
package model
type UserThird struct {
IdModel
UserId uint `json:"user_id" gorm:"not null;index"`
OpenId string `json:"open_id" gorm:"not null;index"`
UnionId string `json:"union_id" gorm:"not null;"`
ThirdType string `json:"third_type" gorm:"not null;"`
ThirdEmail string `json:"third_email"`
ThirdName string `json:"third_name"`
TimeModel
}

45
service/loginLog.go Normal file
View File

@@ -0,0 +1,45 @@
package service
import (
"Gwen/global"
"Gwen/model"
"gorm.io/gorm"
)
type LoginLogService struct {
}
// InfoById 根据用户id取用户信息
func (us *LoginLogService) InfoById(id uint) *model.LoginLog {
u := &model.LoginLog{}
global.DB.Where("id = ?", id).First(u)
return u
}
func (us *LoginLogService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.LoginLogList) {
res = &model.LoginLogList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.LoginLog{})
if where != nil {
where(tx)
}
tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize))
tx.Find(&res.LoginLogs)
return
}
// Create 创建
func (us *LoginLogService) Create(u *model.LoginLog) error {
res := global.DB.Create(u).Error
return res
}
func (us *LoginLogService) Delete(u *model.LoginLog) error {
return global.DB.Delete(u).Error
}
// Update 更新
func (us *LoginLogService) Update(u *model.LoginLog) error {
return global.DB.Model(u).Updates(u).Error
}

256
service/oauth.go Normal file
View File

@@ -0,0 +1,256 @@
package service
import (
"Gwen/global"
"Gwen/model"
"Gwen/utils"
"context"
"encoding/json"
"errors"
"fmt"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"golang.org/x/oauth2/google"
"gorm.io/gorm"
"io"
"strconv"
"sync"
"time"
)
type OauthService struct {
}
type GithubUserdata struct {
AvatarUrl string `json:"avatar_url"`
Bio string `json:"bio"`
Blog string `json:"blog"`
Collaborators int `json:"collaborators"`
Company interface{} `json:"company"`
CreatedAt time.Time `json:"created_at"`
DiskUsage int `json:"disk_usage"`
Email interface{} `json:"email"`
EventsUrl string `json:"events_url"`
Followers int `json:"followers"`
FollowersUrl string `json:"followers_url"`
Following int `json:"following"`
FollowingUrl string `json:"following_url"`
GistsUrl string `json:"gists_url"`
GravatarId string `json:"gravatar_id"`
Hireable interface{} `json:"hireable"`
HtmlUrl string `json:"html_url"`
Id int `json:"id"`
Location interface{} `json:"location"`
Login string `json:"login"`
Name string `json:"name"`
NodeId string `json:"node_id"`
NotificationEmail interface{} `json:"notification_email"`
OrganizationsUrl string `json:"organizations_url"`
OwnedPrivateRepos int `json:"owned_private_repos"`
Plan struct {
Collaborators int `json:"collaborators"`
Name string `json:"name"`
PrivateRepos int `json:"private_repos"`
Space int `json:"space"`
} `json:"plan"`
PrivateGists int `json:"private_gists"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsUrl string `json:"received_events_url"`
ReposUrl string `json:"repos_url"`
SiteAdmin bool `json:"site_admin"`
StarredUrl string `json:"starred_url"`
SubscriptionsUrl string `json:"subscriptions_url"`
TotalPrivateRepos int `json:"total_private_repos"`
//TwitterUsername interface{} `json:"twitter_username"`
TwoFactorAuthentication bool `json:"two_factor_authentication"`
Type string `json:"type"`
UpdatedAt time.Time `json:"updated_at"`
Url string `json:"url"`
}
type OauthCacheItem struct {
UserId uint `json:"user_id"`
Id string `json:"id"` //rustdesk的设备ID
Op string `json:"op"`
Action string `json:"action"`
Uuid string `json:"uuid"`
DeviceName string `json:"device_name"`
DeviceOs string `json:"device_os"`
DeviceType string `json:"device_type"`
ThirdOpenId string `json:"third_open_id"`
ThirdName string `json:"third_name"`
ThirdEmail string `json:"third_email"`
}
var OauthCache = &sync.Map{}
const (
OauthActionTypeLogin = "login"
OauthActionTypeBind = "bind"
)
func (os *OauthService) GetOauthCache(key string) *OauthCacheItem {
v, ok := OauthCache.Load(key)
if !ok {
return nil
}
return v.(*OauthCacheItem)
}
func (os *OauthService) SetOauthCache(key string, item *OauthCacheItem, expire uint) {
OauthCache.Store(key, item)
if expire > 0 {
go func() {
time.Sleep(time.Duration(expire) * time.Second)
os.DeleteOauthCache(key)
}()
}
}
func (os *OauthService) DeleteOauthCache(key string) {
OauthCache.Delete(key)
}
func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
code = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10)
if op == model.OauthTypeWebauth {
url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + code
//url = "http://localhost:8888/_admin/#/oauth/" + code
return nil, code, url
}
err, conf := os.GetOauthConfig(op)
if err == nil {
return err, code, conf.AuthCodeURL(code)
}
return errors.New("op错误"), code, ""
}
// GetOauthConfig 获取配置
func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
if op == model.OauthTypeGithub {
g := os.InfoByOp(model.OauthTypeGithub)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("配置不存在"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: github.Endpoint,
Scopes: []string{"read:user", "user:email"},
}
}
if op == model.OauthTypeGoogle {
g := os.InfoByOp(model.OauthTypeGoogle)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("配置不存在"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: google.Endpoint,
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
}
}
return errors.New("op错误"), nil
}
func (os *OauthService) GithubCallback(code string) (error error, userData *GithubUserdata) {
err, oauthConfig := os.GetOauthConfig(model.OauthTypeGithub)
if err != nil {
return err, nil
}
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
global.Logger.Warn(fmt.Printf("oauthConfig.Exchange() failed: %s\n", err))
error = errors.New("获取token失败")
return
}
// 创建一个 HTTP 客户端,并将 access_token 添加到 Authorization 头中
client := oauthConfig.Client(context.Background(), token)
resp, err := client.Get("https://api.github.com/user")
if err != nil {
global.Logger.Warn("failed getting user info: %s\n", err)
error = errors.New("获取user info失败")
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
global.Logger.Warn("failed closing response body: %s\n", err)
}
}(resp.Body)
// 在这里处理 GitHub 用户信息
if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil {
global.Logger.Warn("failed decoding user info: %s\n", err)
error = errors.New("解析user info失败")
return
}
return
}
func (os *OauthService) UserThirdInfo(op, openid string) *model.UserThird {
ut := &model.UserThird{}
global.DB.Where("open_id = ? and third_type = ?", openid, op).First(ut)
return ut
}
func (os *OauthService) BindGithubUser(openid, username string, userId uint) error {
utr := &model.UserThird{
OpenId: openid,
ThirdType: model.OauthTypeGithub,
ThirdName: username,
UserId: userId,
}
return global.DB.Create(utr).Error
}
func (os *OauthService) UnBindGithubUser(userid uint) error {
return global.DB.Where("user_id = ? and third_type = ?", userid, model.OauthTypeGithub).Delete(&model.UserThird{}).Error
}
// InfoById 根据id取用户信息
func (os *OauthService) InfoById(id uint) *model.Oauth {
u := &model.Oauth{}
global.DB.Where("id = ?", id).First(u)
return u
}
// InfoByOp 根据op取用户信息
func (os *OauthService) InfoByOp(op string) *model.Oauth {
u := &model.Oauth{}
global.DB.Where("op = ?", op).First(u)
return u
}
func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.OauthList) {
res = &model.OauthList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Oauth{})
if where != nil {
where(tx)
}
tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize))
tx.Find(&res.Oauths)
return
}
// Create 创建
func (os *OauthService) Create(u *model.Oauth) error {
res := global.DB.Create(u).Error
return res
}
func (os *OauthService) Delete(u *model.Oauth) error {
return global.DB.Delete(u).Error
}
// Update 更新
func (os *OauthService) Update(u *model.Oauth) error {
return global.DB.Model(u).Updates(u).Error
}

View File

@@ -13,6 +13,8 @@ type Service struct {
*TagService
*PeerService
*GroupService
*OauthService
*LoginLogService
}
func New() *Service {

View File

@@ -7,6 +7,8 @@ import (
"Gwen/utils"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"math/rand"
"strconv"
"time"
)
@@ -51,11 +53,11 @@ func (us *UserService) InfoByAccessToken(token string) *model.User {
// GenerateToken 生成token
func (us *UserService) GenerateToken(u *model.User) string {
return utils.Md5(u.Username + u.Password + time.Now().String())
return utils.Md5(u.Username + time.Now().String())
}
// Login 登录
func (us *UserService) Login(u *model.User) *model.UserToken {
func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserToken {
token := us.GenerateToken(u)
ut := &model.UserToken{
UserId: u.Id,
@@ -63,6 +65,7 @@ func (us *UserService) Login(u *model.User) *model.UserToken {
ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(),
}
global.DB.Create(ut)
global.DB.Create(llog)
return ut
}
@@ -169,3 +172,70 @@ func (us *UserService) RouteNames(u *model.User) []string {
}
return adResp.UserRouteNames
}
// InfoByGithubId 根据githubid取用户信息
func (us *UserService) InfoByGithubId(githubId string) *model.User {
ut := AllService.OauthService.UserThirdInfo(model.OauthTypeGithub, githubId)
if ut.Id == 0 {
return nil
}
u := us.InfoById(ut.UserId)
if u.Id == 0 {
return nil
}
return u
}
// RegisterByGithub 注册
func (us *UserService) RegisterByGithub(githubName string, githubId int64) *model.User {
tx := global.DB.Begin()
ut := &model.UserThird{
OpenId: strconv.FormatInt(githubId, 10),
ThirdName: githubName,
ThirdType: model.OauthTypeGithub,
}
//global.DB.Where("open_id = ?", githubId).First(ut)
//这种情况不应该出现如果出现说明有bug
//if ut.Id != 0 {
// u := &model.User{}
// global.DB.Where("id = ?", ut.UserId).First(u)
// tx.Commit()
// return u
//}
username := us.GenerateUsernameByOauth(githubName)
u := &model.User{
Username: username,
GroupId: 1,
}
global.DB.Create(u)
ut.UserId = u.Id
global.DB.Create(ut)
tx.Commit()
return u
}
// GenerateUsernameByOauth 生成用户名
func (us *UserService) GenerateUsernameByOauth(name string) string {
u := &model.User{}
global.DB.Where("username = ?", name).First(u)
if u.Id == 0 {
return name
}
name = name + strconv.FormatInt(rand.Int63n(10), 10)
return us.GenerateUsernameByOauth(name)
}
// UserThirdsByUserId
func (us *UserService) UserThirdsByUserId(userId uint) (res []*model.UserThird) {
global.DB.Where("user_id = ?", userId).Find(&res)
return res
}
func (us *UserService) UserThirdInfo(userId uint, op string) *model.UserThird {
ut := &model.UserThird{}
global.DB.Where("user_id = ? and third_type = ?", userId, op).First(ut)
return ut
}

View File

@@ -4,6 +4,7 @@ import (
"crypto/md5"
"encoding/json"
"fmt"
"math/rand"
"reflect"
"runtime/debug"
)
@@ -61,3 +62,14 @@ func SafeGo(f interface{}, params ...interface{}) {
funcValue.Call(paramsValue)
}()
}
// RandomString 生成随机字符串
func RandomString(n int) string {
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
length := len(letterBytes)
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(length)]
}
return string(b)
}