Compare commits

..

45 Commits

Author SHA1 Message Date
ljw
62a22c697d up web client can get pwd if exist 2024-09-24 10:15:04 +08:00
ljw
be497a5aa7 add docker.yml 2024-09-23 14:43:43 +08:00
ljw
fc3e16bc63 up README.md 2024-09-23 10:21:43 +08:00
ljw
df35912461 up release.yml 2024-09-23 10:13:44 +08:00
ljw
0ddc66a854 up release.yml 2024-09-23 10:10:29 +08:00
ljw
716d557d66 up test.yml 2024-09-23 10:07:10 +08:00
ljw
f9edcb9d47 up test.yml 2024-09-22 21:40:17 +08:00
ljw
d4623c5bc9 up test.yml 2024-09-22 21:28:43 +08:00
ljw
02ba4a3330 up test.yml 2024-09-22 20:30:12 +08:00
ljw
a72d4eaf78 up test.yml 2024-09-22 19:46:37 +08:00
ljw
4d3abb2dc0 up test.yml 2024-09-22 17:58:42 +08:00
ad6a8d1f7a Update test.yml 2024-09-21 22:49:06 +08:00
a5c8c2ac97 Update test.yml 2024-09-21 21:55:36 +08:00
1237004cb1 Update test.yml 2024-09-21 21:44:43 +08:00
c2bcd17df7 Update go.yml 2024-09-21 21:43:31 +08:00
bb49cbdd50 Update test.yml 2024-09-21 21:40:36 +08:00
6ad770a824 Create test.yml 2024-09-21 21:35:55 +08:00
593eeb3ac3 Update go.yml 2024-09-21 21:17:03 +08:00
6a7be3ef84 Create go.yml 2024-09-21 21:13:54 +08:00
ljw
3addc12f45 bug fix 2024-09-20 20:46:32 +08:00
ljw
1e3403e3c5 add google oauth 2024-09-20 12:13:15 +08:00
ljw
ae4672174a up 2024-09-20 10:14:38 +08:00
ljw
f935fed5f7 up readme 2024-09-20 10:12:25 +08:00
ljw
989990c869 up Docker image add tzdata 2024-09-20 10:10:04 +08:00
ljw
8b8a44e46b fix 2024-09-20 09:05:15 +08:00
ljw
2ed2884118 up README_EN.md 2024-09-19 12:05:53 +08:00
ljw
03bd34cadd up README_EN.md 2024-09-19 12:03:18 +08:00
ljw
1c3f87fecb up README.md 2024-09-19 11:07:54 +08:00
ljw
d45e85d29b up README.md 2024-09-19 11:02:54 +08:00
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
62 changed files with 4270 additions and 215 deletions

1
.gitattributes vendored Normal file
View File

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

38
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Build and Push Docker Image
on:
push:
tags:
- 'v*.*.*' # 仅当推送标签(例如 v1.0.0)时触发
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Extract version from tag
id: vars
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: lejianwen/rustdesk-api:latest, lejianwen/rustdesk-api:${{ env.TAG }}

27
.github/workflows/go.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22'
- name: mod tidy
run: go mod tidy
- name: Build
run: go build -v -o release/apimain cmd/apimain.go
- name: Test
run: go test -v cmd/apimain.go

95
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Build and Release
on:
push:
tags:
- 'v*.*.*' # 当推送带有版本号的 tag例如 v1.0.0)时触发工作流
#on:
# push:
# branches: [ "master" ]
# pull_request:
# branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [ linux, windows ] # 指定要构建的操作系统
goarch: [ amd64 ] # 指定架构
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go environment
uses: actions/setup-go@v4
with:
go-version: '1.22' # 选择 Go 版本
- name: Set up npm
uses: actions/setup-node@v2
with:
node-version: '20'
- name: install gcc zip musl
run: |
if [ "${{ matrix.goos }}" = "windows" ]; then
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
else
sudo apt-get install musl musl-dev musl-tools -y
fi
- name: build rustdesk-api-web
run: |
git clone https://github.com/lejianwen/rustdesk-api-web
cd rustdesk-api-web
npm install
npm run build
mkdir ../resources/admin/ -p
cp -ar dist/* ../resources/admin/
- name: tidy
run: go mod tidy
- name: swag
run: |
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
- name: Build for ${{ matrix.goos }}-${{ matrix.goarch }}
run: |
mkdir release -p
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
mkdir -p release/data
mkdir -p release/runtime
if [ "${{ matrix.goos }}" = "windows" ]; then
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain.exe ./cmd/apimain.go
zip -r ${{ matrix.goos}}-${{ matrix.goarch }}.zip ./release
else
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
tar -czf ${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz ./release
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: myapp-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
${{ matrix.goos}}-${{ matrix.goarch }}.zip
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
${{ matrix.goos}}-${{ matrix.goarch }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

35
Dockerfile Normal file
View File

@@ -0,0 +1,35 @@
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;\
CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -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
RUN apk add --no-cache tzdata
COPY --from=builder /go/rustdesk-api/release /app/
EXPOSE 21114
CMD ["./apimain"]

203
README.md
View File

@@ -1,54 +1,91 @@
# 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"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/release.yml/badge.svg"/>
</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
```
```bash
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`和`google`授权登录需要在后台配置好就可以用了具体可看后台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>[:port]/_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)
6. Oauth,暂时只支持了`Github`和`Google`, 需要创建一个`OAuth App`,然后配置到后台
![web_admin_oauth](docs/web_admin_oauth.png)
- `github oauth app`在`Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
- `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback`
,比如`http://127.0.0.1:21114/api/oauth/callback`
### **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 +99,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 +116,135 @@ rustdesk:
key: "123456789"
```
* 环境变量,变量名前缀是RUSTDESK_API环境变量如果存在将覆盖配置文件中的配置
| 变量名 | 说明 | 示例 |
|:------------------------------------|:-------------------------------------|-----------------------------|
| TZ | 时区 | Asia/Shanghai |
| -----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 TZ=Asia/Shanghai \
-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:
- TZ=Asia/Shanghai
- 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:
- TZ=Asia/Shanghai
- 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 +252,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 +279,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)

295
README_EN.md Normal file
View File

@@ -0,0 +1,295 @@
# RustDesk API
This project implements the RustDesk API using Go, and includes both a web UI and web client. RustDesk is a remote
desktop software that provides self-hosted solutions.
<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>
## Prerequisites
### [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 auto-generated key, otherwise there may be connection failures or
timeouts.
```bash
hbbs -r <relay-server-ip[:port]> -k <key>
hbbr -k <key>
```
Example:
```bash
hbbs -r <relay-server-ip[:port]> -k abc1234567
hbbr -k abc1234567
```
## Features
### API Service: Basic implementation of the PC client's primary interfaces.
#### Login
- Added `GitHub` and `Google` login, which can be used after configuration in the admin panel. See the OAuth configuration section
for details.
- Added authorization login for the web admin panel.
![pc_login](docs/pc_login.png)
#### Address Book
![pc_ab](docs/pc_ab.png)
#### Groups: Groups are divided into `shared groups` and `regular groups`. In shared groups, everyone can see the addresses of all group members, while in regular groups, only administrators can see all members' addresses.
![pc_gr](docs/pc_gr.png)
### **Web UI**: The frontend and backend are separated to provide a user-friendly management interface, primarily for managing and displaying data.
***Frontend code is available at [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
***Admin panel URL: `http://<your server>[:port]/_admin/`. The default username and password for the initial
installation are `admin` `admin`, please change the password immediately.***
1. Admin interface:
![web_admin](docs/web_admin.png)
2. Regular user interface:
![web_user](docs/web_admin_user.png)
3. You can change your password from the top right corner:
![web_resetpwd](docs/web_resetpwd.png)
4. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`.
![web_admin_gr](docs/web_admin_gr.png)
5. You can open the web client directly for convenience:
![web_webclient](docs/admin_webclient.png)
6. OAuth support: Currently, `GitHub` and `Google` is supported. You need to create an `OAuth App` and configure it in the admin
panel.
![web_admin_oauth](docs/web_admin_oauth.png)
- Create a `GitHub OAuth App`
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oauth/callback`,
e.g., `http://127.0.0.1:21114/api/oauth/callback`.
### **Web Client**:
1. If you're already logged into the admin panel, the web client will log in automatically.
2. If you're not logged in, simply click the login button at the top right corner, and the API server will be
pre-configured.
![webclient_conf](docs/webclient_conf.png)
3. After logging in, the ID server and key will be automatically synced.
4. The address book will also be automatically saved to the web client for convenient use.
### **Automated Documentation** : API documentation is generated using Swag, making it easier for developers to understand and use the API.
1. Admin panel docs: `<your server>/admin/swagger/index.html`
2. PC client docs: `<your server>/swagger/index.html`
![api_swag](docs/api_swag.png)
## Installation and Setup
### Configuration
* Modify the configuration in `conf/config.yaml`. If `gorm.type` is set to `sqlite`, MySQL-related configurations are
not required.
```yaml
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk"
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
```
* Environment variables, with the prefix `RUSTDESK_API`, will override the settings in the configuration file if
present.
| Variable Name | Description | Example |
|------------------------------------|-----------------------------------------------------------|--------------------------------|
| ----- GIN Configuration ----- | --------------------------------------- | ------------------------------ |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ------------------------------ |
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ------------------------------ |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ------------------------------ |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
### Installation Steps
#### Running via Docker
1. Run directly with Docker. Configuration can be modified by mounting the config file `/app/conf/config.yaml`, or by
using environment variables to override settings.
```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. Using `docker-compose`
- Simple example:
```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 # Mount the database for easy backup
networks:
- rustdesk-net
restart: unless-stopped
```
- Example with RustDesk's official Docker Compose file, adding your `rustdesk-api` service:
```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
```
#### 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"
username: ""
password: ""
addr: ""
dbname: ""
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"
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: "warn" #trace,debug,info,warn,error,fatal
report-caller: true
redis:
addr: "127.0.0.1:6379"
password: ""
db: 0
logger:
path: "./runtime/log.txt"
level: "error" #trace,debug,info,warn,error,fatal
report-caller: true
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"`
}

15
docker-compose.yaml Normal file
View File

@@ -0,0 +1,15 @@
services:
rustdesk-api:
image: lejianwen/rustdesk-api
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- 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: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/web_admin_oauth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 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

@@ -17,12 +17,14 @@ require (
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
golang.org/x/oauth2 v0.23.0
gorm.io/driver/mysql v1.5.7
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.7
)
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 +63,12 @@ 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/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

@@ -65,7 +65,7 @@ func (ct *Tag) Create(c *gin.Context) {
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) {
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
}
err := service.AllService.TagService.Create(t)

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,279 @@
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"
"strings"
)
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
} else 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, strconv.Itoa(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, "授权错误")
}
if ty == model.OauthTypeGoogle {
code := c.Query("code")
err, userData := service.AllService.OauthService.GoogleCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, "授权失败:"+err.Error())
return
}
//将空格替换成_
googleName := strings.Replace(userData.Name, " ", "_", -1)
if ac == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Email)
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, "已经绑定其他账号")
return
}
//绑定
u := service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, "用户不存在")
return
}
//绑定
err = service.AllService.OauthService.BindGoogleUser(userData.Email, googleName, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, "绑定失败")
return
}
c.String(http.StatusOK, "绑定成功")
return
} else if ac == service.OauthActionTypeLogin {
if v.UserId != 0 {
c.String(http.StatusInternalServerError, "授权已经成功")
return
}
u := service.AllService.UserService.InfoByGoogleEmail(userData.Email)
if u == nil {
oa := service.AllService.OauthService.InfoByOp(ty)
if !*oa.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
v.ThirdName = googleName
v.ThirdOpenId = userData.Email
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
c.Redirect(http.StatusFound, url)
return
}
//自动注册
u = service.AllService.UserService.RegisterByGoogle(googleName, userData.Email)
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
}
}
c.String(http.StatusInternalServerError, "授权配置错误,请联系管理员")
}
// 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

@@ -12,6 +12,15 @@ func (i *Index) ConfigJs(c *gin.Context) {
apiServer := global.Config.Rustdesk.ApiServer
tmp := `
function stringToUint8Array(str){
var arr = [];
for (var i = 0, j = str.length; i < j; ++i) {
arr.push(str.charCodeAt(i));
}
var tmpUint8Array = new Uint8Array(arr);
return tmpUint8Array
}
window._gwen = {}
window._gwen.kv = {}
function getQueryVariable() {
@@ -53,7 +62,26 @@ const autoWriteServer = () => {
}
if (res.data.peers) {
localStorage.setItem('peers', JSON.stringify(res.data.peers))
oldPeers = JSON.parse(localStorage.getItem('peers')) || {}
let needUpdate = false
Object.keys(res.data.peers).forEach(k => {
if(!oldPeers[k]) {
oldPeers[k] = res.data.peers[k]
needUpdate = true
}else{
oldPeers[k].info = res.data.peers[k].info
}
if (oldPeers[k].info && oldPeers[k].info.hash&&!oldPeers[k].password ) {
let p1 = window.atob(oldPeers[k].info.hash)
const pwd = stringToUint8Array(p1)
oldPeers[k].password = pwd.toString()
oldPeers[k].remember = true
}
})
localStorage.setItem('peers', JSON.stringify(oldPeers))
if(needUpdate) {
window.location.reload()
}
}
}
})

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
}

View File

@@ -6,7 +6,7 @@ type TagForm struct {
Id uint `json:"id"`
Name string `json:"name" validate:"required"`
Color uint `json:"color" validate:"required"`
UserId uint `json:"user_id" validate:"required"`
UserId uint `json:"user_id"`
}
func (f *TagForm) FromTag(group *model.Tag) *TagForm {

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"`
@@ -42,14 +15,17 @@ type WebClientPeerInfoPayload struct {
Username string `json:"username"`
Hostname string `json:"hostname"`
Platform string `json:"platform"`
Hash string `json:"hash"`
}
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,
Platform: a.Platform,
Hash: a.Hash,
}
}

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,9 +19,6 @@ func ApiInit(g *gin.Engine) {
frg := g.Group("/api")
frg.Use(middleware.Cors())
frg.OPTIONS("/*any", nil)
i := &api.Index{}
frg.GET("/", i.Index)
@@ -34,6 +31,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
}

View File

@@ -42,25 +42,6 @@
<link rel="modulepreload" href="js/dist/vendor.js">
<script src="yuv-canvas-1.2.6.js"></script>
<script>
// localStorage.setItem('api-server','http://127.0.0.1:21114')
// localStorage.setItem('access_token','0bf218eefa2ac1cdfd72b67f14862bb8')
// window.setByName
// localStorage.setItem('user_info', JSON.stringify({name: "admin", email: "", note: "", is_admin: true, status: 1}))
//peers
// localStorage.setItem('peers',JSON.stringify(
// {
// "1799928825":{"view-style":"shrink","tm":1726154051748,"info":{"username":"android","hostname":"Xiaomi-MI 8 SE","platform":"Android","displays":[{"x":0,"y":0,"width":1080,"height":2244,"name":"Android","online":true}],"current_display":0,"sas_enabled":false,"version":"1.3.0","conn_id":0,"features":{"privacy_mode":false}}},
// "ljwzhuwo":{"view-style":"shrink","tm":1726153696760,"info":{"username":"lejianwen","hostname":"zhuwo","platform":"Windows","displays":[{"x":0,"y":0,"width":2560,"height":1440,"name":"\\\\.\\DISPLAY1","online":true}],"current_display":0,"sas_enabled":false,"version":"1.2.6","conn_id":0,"features":{"privacy_mode":true}}},
// "shenglan":{"view-style":"shrink","tm":1726154006341,"info":{"username":"ttt","hostname":"test","platform":"Windows"}}
// }
// )
// )
//{"1799928825":{"view-style":"shrink","tm":1726154051748,"info":{"username":"android","hostname":"Xiaomi-MI 8 SE","platform":"Android","displays":[{"x":0,"y":0,"width":1080,"height":2244,"name":"Android","online":true}],"current_display":0,"sas_enabled":false,"version":"1.3.0","conn_id":0,"features":{"privacy_mode":false}}},"ljwzhuwo":{"view-style":"shrink","tm":1726153696760,"info":{"username":"lejianwen","hostname":"zhuwo","platform":"Windows","displays":[{"x":0,"y":0,"width":2560,"height":1440,"name":"\\\\.\\DISPLAY1","online":true}],"current_display":0,"sas_enabled":false,"version":"1.2.6","conn_id":0,"features":{"privacy_mode":true}}},"shenglan":{"view-style":"shrink","tm":1726154006341}}
</script>
<style>
.loading {
display: flex;

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
}

303
service/oauth.go Normal file
View File

@@ -0,0 +1,303 @@
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 GoogleUserdata struct {
Email string `json:"email"`
FamilyName string `json:"family_name"`
GivenName string `json:"given_name"`
Id string `json:"id"`
Name string `json:"name"`
Picture string `json:"picture"`
VerifiedEmail bool `json:"verified_email"`
}
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) GoogleCallback(code string) (error error, userData *GoogleUserdata) {
err, oauthConfig := os.GetOauthConfig(model.OauthTypeGoogle)
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://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
global.Logger.Warn("failed getting user info: %s\n", err)
error = errors.New("获取user info失败 " + err.Error())
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
global.Logger.Warn("failed closing response body: %s\n", err)
}
}(resp.Body)
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失败" + err.Error())
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 {
return os.BindOauthUser(model.OauthTypeGithub, openid, username, userId)
}
func (os *OauthService) BindGoogleUser(email, username string, userId uint) error {
return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId)
}
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
utr := &model.UserThird{
OpenId: openid,
ThirdType: thirdType,
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,90 @@ func (us *UserService) RouteNames(u *model.User) []string {
}
return adResp.UserRouteNames
}
// InfoByGithubId 根据githubid取用户信息
func (us *UserService) InfoByGithubId(githubId string) *model.User {
return us.InfoByOauthId(model.OauthTypeGithub, githubId)
}
// InfoByGoogleEmail 根据googleid取用户信息
func (us *UserService) InfoByGoogleEmail(email string) *model.User {
return us.InfoByOauthId(model.OauthTypeGithub, email)
}
// InfoByOauthId 根据oauth取用户信息
func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
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 string) *model.User {
return us.RegisterByOauth(model.OauthTypeGithub, githubName, githubId)
}
// RegisterByGoogle 注册
func (us *UserService) RegisterByGoogle(name string, email string) *model.User {
return us.RegisterByOauth(model.OauthTypeGoogle, name, email)
}
// RegisterByOauth 注册
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User {
tx := global.DB.Begin()
ut := &model.UserThird{
OpenId: uid,
ThirdName: thirdName,
ThirdType: thirdType,
}
//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(thirdName)
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)
}