Compare commits

...

43 Commits

Author SHA1 Message Date
ljw
7ad7a0ff41 fix #45 2024-11-02 18:49:16 +08:00
ljw
737fe749de load key from file 2024-11-01 14:56:11 +08:00
b9c6f17e3f Merge pull request #42 from IamTaoChen/docker
Docker Optimize
2024-11-01 13:10:59 +08:00
Tao Chen
af6a340003 add more 2024-11-01 01:33:08 +08:00
Tao Chen
8ba3bee944 optimize scripts 2024-11-01 01:33:05 +08:00
Tao Chen
153c3566b6 optimize build speed, like cache and mirror 2024-11-01 01:32:45 +08:00
Tao Chen
97a4753f4f add ARG CONTRY=CN to improve the alpinelinux install speed 2024-11-01 00:29:33 +08:00
Tao Chen
74c3899b55 add bash to run dev-docker 2024-11-01 00:28:43 +08:00
ljw
273ac6d1a8 add remove user token #34 2024-10-31 22:29:12 +08:00
5edbb39a63 Merge pull request #40 from IamTaoChen/resetEmptyPassWD
Reset empty password
2024-10-31 18:46:34 +08:00
Tao Chen
0c974c4113 Merge branch 'master' into resetEmptyPassWD 2024-10-31 16:35:32 +08:00
Tao Chen
46657a525d ommit check old passwd if password is empty 2024-10-31 16:23:06 +08:00
Tao Chen
b36aa6f917 add IsPasswordEmpty... 2024-10-31 16:22:42 +08:00
ljw
cddb0ebef9 up readme #28 2024-10-31 15:48:36 +08:00
ljw
788c4e3531 up readme #28 2024-10-31 15:47:39 +08:00
ljw
47f9ad8274 add register 2024-10-31 15:14:30 +08:00
ljw
855beb7fa9 up oauth 2024-10-31 14:03:48 +08:00
f57816b1b0 Merge pull request #36 from IamTaoChen/oidc-for-web
OIDC for web
2024-10-31 11:10:46 +08:00
Tao Chen
ff08fefc30 rename build stage 2024-10-31 09:21:43 +08:00
Tao Chen
f792ab9055 add some /admin/ to surport web OIDC 2024-10-31 09:21:30 +08:00
ljw
63af103a4e fix buidconfirm 2024-10-30 20:59:51 +08:00
ljw
0a36d44cec up del user 2024-10-30 19:34:56 +08:00
a1f4e1de84 Merge pull request #32 from IamTaoChen/bug/odic-user
delete user from user_thirds and update README
2024-10-30 19:08:50 +08:00
Tao Chen
05b20d47db modify delete user 2024-10-30 16:33:01 +08:00
Tao Chen
6b746f13d1 update README 2024-10-30 16:31:47 +08:00
Tao Chen
e838d5bcd2 update README for OIDC 2024-10-30 16:29:49 +08:00
Tao Chen
0dcc21260e delete user from user_thirds, too 2024-10-30 15:59:33 +08:00
ljw
3c30ad145c up v 2024-10-30 15:46:12 +08:00
ljw
06b0a8e873 add docker-compose-dev.yaml 2024-10-30 15:34:45 +08:00
b7de2ccadd Merge pull request #30 from IamTaoChen/oidc
Add General OIDC Login
2024-10-30 14:40:10 +08:00
Tao Chen
b52c5cfca1 bind oidc ThirdEmail 2024-10-29 23:09:54 +08:00
Tao Chen
fe910c37cf fix: spelling 2024-10-29 23:00:17 +08:00
Tao Chen
337ef330eb fix bug 2024-10-29 18:48:37 +08:00
Tao Chen
ffa47177aa fix bug - oidc scopes 2024-10-29 18:46:45 +08:00
ljw
46a76853c3 fix oauth register #26 #23 2024-10-29 15:31:27 +08:00
Tao Chen
2cd7dfb2b3 fix bug 2024-10-29 14:27:15 +08:00
Tao Chen
fee2808bca try add oidc 2024-10-29 11:51:01 +08:00
Tao Chen
49e5eb186a optimize docker 2024-10-29 11:50:55 +08:00
Tao Chen
dee2865466 optimize build.sh 2024-10-29 10:58:17 +08:00
ljw
eb340b2615 add last online ip #24 2024-10-28 20:24:34 +08:00
ljw
e714549a95 up address book add version #20 2024-10-28 19:48:47 +08:00
ljw
a1367bcd3d up peer update 2024-10-28 19:15:13 +08:00
ljw
642351dafd fix bug #27 2024-10-28 16:08:33 +08:00
53 changed files with 2156 additions and 393 deletions

29
.dockerignore Normal file
View File

@@ -0,0 +1,29 @@
# Ignore Docker Compose configuration files
docker-compose.yaml
docker-compose-dev.yaml
# Ignore development Dockerfile
Dockerfile
Dockerfile.dev
docker-dev.sh
# Ignore the data directory
data/
# Ignore version control system directories
.git/
# Ignore log and temporary files
*.log
*.tmp
*.swp
# Ignore editor/IDE configuration files
.vscode/
.idea/
# Ignore binaries and build cache
release/
bin/
*.exe
*.out

91
Dockerfile.dev Normal file
View File

@@ -0,0 +1,91 @@
# Use build arguments for Go version and architecture
ARG GO_VERSION=1.22
ARG BUILDARCH=amd64
# Stage 1: Builder Stage
# FROM golang:${GO_VERSION}-alpine AS builder
FROM crazymax/xgo:${GO_VERSION} AS builder-backend
# Set up working directory
WORKDIR /app
# Step 1: Copy the source code
COPY . .
# use --mount=type=cache,target=/go/pkg/mod to cache the go mod
# Step 2: Download dependencies
RUN --mount=type=cache,target=/go/pkg/mod \
go mod tidy && go mod download && go install github.com/swaggo/swag/cmd/swag@latest
# Step 3: Run swag build script
RUN --mount=type=cache,target=/go/pkg/mod \
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
# Step 4: Build the Go application with CGO enabled and specified ldflags
RUN --mount=type=cache,target=/go/pkg/mod \
CGO_ENABLED=1 GOOS=linux go build -a \
-ldflags "-s -w --extldflags '-static -fpic'" \
-installsuffix cgo -o release/apimain cmd/apimain.go
# Stage 2: Frontend Build Stage (builder2)
FROM node:18-alpine AS builder-admin-frontend
# Set working directory
WORKDIR /frontend
ARG COUNTRY
# Install required tools without caching index to minimize image size
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating the repositories"; \
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
fi && \
apk update && apk add --no-cache git
# Clone the frontend repository
RUN git clone https://github.com/lejianwen/rustdesk-api-web .
# Install required tools without caching index to minimize image size
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating NPM_CONFIG_REGISTRY"; \
export NPM_CONFIG_REGISTRY="https://mirrors.huaweicloud.com/repository/npm/"; \
fi && \
npm install && npm run build
# Stage 2: Final Image
FROM alpine:latest
# Set up working directory
WORKDIR /app
# Install necessary runtime dependencies
# Install required tools without caching index to minimize image size
ARG COUNTRY
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating the repositories"; \
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
fi && \
apk update && apk add --no-cache tzdata file
# Copy the built application and resources from the builder stage
COPY --from=builder-backend /app/release /app/
COPY --from=builder-backend /app/conf /app/conf/
COPY --from=builder-backend /app/resources /app/resources/
COPY --from=builder-backend /app/docs /app/docs/
# Copy frontend build from builder2 stage
COPY --from=builder-admin-frontend /frontend/dist/ /app/resources/admin/
# Ensure the binary is correctly built and linked
RUN file /app/apimain && \
mkdir -p /app/data && \
mkdir -p /app/runtime
# Set up a volume for persistent data
VOLUME /app/data
# Expose the necessary port
EXPOSE 21114
# Define the command to run the application
CMD ["./apimain"]

View File

@@ -19,7 +19,7 @@
- 登录
- 地址簿
- 群组
- 授权登录,支持`github``google`登录,支持`web后台`授权登录
- 授权登录,支持`github`, `google``OIDC` 登录,支持`web后台`授权登录
- i18n
- Web Admin
- 用户管理
@@ -92,7 +92,7 @@
#### 登录
- 添加了`github``google`授权登录需要在后台配置好就可以用了具体可看后台OAuth配置
- 添加了`github`, `google` 以及`OIDC`授权登录需要在后台配置好就可以用了具体可看后台OAuth配置
- 添加了web后台授权登录,点击后直接登录后台就自动登录客户端了
![pc_login](docs/pc_login.png)
@@ -124,8 +124,10 @@
4. 可以直接打开webclient方便使用也可以分享给游客游客可以直接通过webclient远程到设备
![web_webclient](docs/admin_webclient.png)
5. Oauth,暂时只支持了`Github``Google`, 需要创建一个`OAuth App`,然后配置到后台
5. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台
![web_admin_oauth](docs/web_admin_oauth.png)
- 对于`Google` 和 `Github`, `Issuer` 和 `Scopes`不需要填写.
- 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email` 和`preferred_username`
- `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`
@@ -157,6 +159,7 @@
lang: "en"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
@@ -194,6 +197,7 @@ proxy:
| TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----------GORM配置---------------- | ------------------------------------ | --------------------------- |

View File

@@ -18,7 +18,7 @@ desktop software that provides self-hosted solutions.
- Login
- Address Book
- Groups
- Authorized login, supports `GitHub` and `Google` login, supports `web admin` authorized login
- Authorized login, supports `GitHub`, `Google` and `OIDC` login, supports `web admin` authorized login
- i18n
- Web Admin
- User Management
@@ -93,7 +93,7 @@ Basic implementation of the PC client's primary interfaces.Supports the Personal
#### Login
- Added `GitHub` and `Google` login, which can be used after configuration in the admin panel. See the OAuth
- Added `GitHub`, `Google` and `OIDC` 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.
@@ -128,9 +128,11 @@ installation are `admin` `admin`, please change the password immediately.
4. You can directly launch the client or open the web client for convenience; you can also share it with guests, who can remotely access the device via the web client.
![web_webclient](docs/en_img/admin_webclient.png)
5. OAuth support: Currently, `GitHub` and `Google` is supported. You need to create an `OAuth App` and configure it in
5. OAuth support: Currently, `GitHub`, `Google` and `OIDC` are supported. You need to create an `OAuth App` and configure it in
the admin panel.
![web_admin_oauth](docs/en_img/web_admin_oauth.png)
- For `Google` and `Github`, you don't need to fill the `Issuer` and `Scpoes`
- For `OIDC`, you must set the `Issuer`. And `Scopes` is optional which default is `openid,email,profile`, please make sure this `Oauth App` can access `sub`, `email` and `preferred_username`
- Create a `GitHub OAuth App`
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`,
@@ -163,6 +165,7 @@ installation are `admin` `admin`, please change the password immediately.
lang: "en"
app:
web-client: 1 # web client route 1:open 0:close
register: false #register enable
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
@@ -200,6 +203,7 @@ The prefix for variable names is `RUSTDESK_API`. If environment variables exist,
| TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, deault 1 | 1 |
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |

38
build.sh Normal file → Executable file
View File

@@ -1,16 +1,46 @@
#!/bin/sh
rm release -rf
set -e
# Automatically get the current environment's GOARCH; if not defined, use the detected system architecture
GOARCH=${GOARCH:-$(go env GOARCH)}
DOCS="true"
# Safely remove the old release directory
rm -rf release
# Set Go environment variables
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go env -w CGO_ENABLED=1
go env -w GOOS=linux
go env -w GOARCH=amd64
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 GOARCH=${GOARCH}
# Generate Swagger documentation if DOCS is not empty
if [ -n "${DOCS}" ]; then
# Check if swag is installed
if ! command -v swag &> /dev/null; then
echo "swag command not found. Please install it using:"
echo "go install github.com/swaggo/swag/cmd/swag@latest"
echo "Skipping Swagger documentation generation due to missing swag tool."
else
echo "Generating Swagger documentation..."
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
fi
else
echo "Skipping Swagger documentation generation due to DOCS is empty."
fi
# Compile the Go code and output it to the release directory
go build -o release/apimain cmd/apimain.go
# Copy resource files to the release directory
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
# Create necessary directory structures
mkdir -p release/data
mkdir -p release/runtime
echo "Build and setup completed successfully."

View File

@@ -101,7 +101,7 @@ func main() {
}
func DatabaseAutoUpdate() {
version := 240
version := 244
db := global.DB

View File

@@ -1,6 +1,7 @@
lang: "zh-CN"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
gin:
api-addr: "0.0.0.0:21114"
mode: "release" #release,debug,test
@@ -19,7 +20,8 @@ rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://127.0.0.1:21114"
key: "123456789"
key: ""
key-file: "./conf/data/id_ed25519.pub"
personal: 1
logger:
path: "./runtime/log.txt"

View File

@@ -15,7 +15,8 @@ const (
)
type App struct {
WebClient int `mapstructure:"web-client"`
WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
}
type Config struct {
@@ -62,6 +63,7 @@ func Init(rowVal interface{}) *viper.Viper {
if err := v.Unmarshal(rowVal); err != nil {
fmt.Println(err)
}
LoadKeyFile(&rowVal.(*Config).Rustdesk)
return v
}

View File

@@ -11,3 +11,10 @@ type GoogleOauth struct {
ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"`
}
type OidcOauth struct {
Issuer string `mapstructure:"issuer"`
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"`
}

View File

@@ -1,9 +1,30 @@
package config
import (
"os"
)
type Rustdesk struct {
IdServer string `mapstructure:"id-server"`
RelayServer string `mapstructure:"relay-server"`
ApiServer string `mapstructure:"api-server"`
Key string `mapstructure:"key"`
KeyFile string `mapstructure:"key-file"`
Personal int `mapstructure:"personal"`
}
func LoadKeyFile(rustdesk *Rustdesk) {
// Load key file
if rustdesk.Key != "" {
return
}
if rustdesk.KeyFile != "" {
// Load key from file
b, err := os.ReadFile(rustdesk.KeyFile)
if err != nil {
return
}
rustdesk.Key = string(b)
return
}
}

22
docker-compose-dev.yaml Normal file
View File

@@ -0,0 +1,22 @@
services:
rustdesk-api:
build:
context: .
dockerfile: Dockerfile.dev
args:
COUNTRY: CN
# 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://127.0.0.1:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
volumes:
- ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份
- ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源
restart: unless-stopped

View File

@@ -6,10 +6,12 @@ services:
- 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_API_SERVER=http://127.0.0.1:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
- ./data/rustdesk/api:/app/data # database
# - ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源
restart: unless-stopped

35
docker-dev.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
set -e
# Define Docker Compose file and cache option
COMPOSE_FILE_NAME="docker-compose-dev.yaml"
CACHE=""
# Uncomment the next line to enable no-cache option
# CACHE="--no-cache"
# Define the base Docker Compose command
DCS="docker compose -f ${COMPOSE_FILE_NAME}"
# Function to build and start services
build_and_run() {
echo "Building services..."
if ! $DCS build ${CACHE}; then
echo "Error: Failed to build services"
exit 1
fi
echo "Starting services..."
if ! $DCS up -d; then
echo "Error: Failed to start services"
exit 1
fi
echo "Services started successfully"
echo "If you want to stop the services, run"
echo "docker compose -f ${COMPOSE_FILE_NAME} down"
echo "If you want to see the logs, run"
echo "docker compose -f ${COMPOSE_FILE_NAME} logs -f"
}
# Execute build and start function
build_and_run

View File

@@ -353,17 +353,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "创建地址簿集合",
"description": "创建地址簿名称",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "创建地址簿集合",
"tags": [
"地址簿名称"
],
"summary": "创建地址簿名称",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -407,17 +410,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合删除",
"description": "地址簿名称删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合删除",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称删除",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -449,14 +455,17 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合详情",
"description": "地址簿名称详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合详情",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称详情",
"parameters": [
{
"type": "integer",
@@ -501,14 +510,17 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合列表",
"description": "地址簿名称列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合列表",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称列表",
"parameters": [
{
"type": "integer",
@@ -570,17 +582,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合编辑",
"description": "地址簿名称编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合编辑",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称编辑",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -624,17 +639,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "创建地址簿集合规则",
"description": "创建地址簿规则",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "创建地址簿集合规则",
"tags": [
"地址簿规则"
],
"summary": "创建地址簿规则",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -678,17 +696,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合规则删除",
"description": "地址簿规则删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则删除",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则删除",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -720,14 +741,17 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合规则详情",
"description": "地址簿规则详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则详情",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则详情",
"parameters": [
{
"type": "integer",
@@ -772,14 +796,17 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合规则列表",
"description": "地址簿规则列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则列表",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则列表",
"parameters": [
{
"type": "integer",
@@ -847,17 +874,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合规则编辑",
"description": "地址簿规则编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则编辑",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则编辑",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -1453,7 +1483,39 @@ const docTemplateadmin = `{
}
}
},
"/admin/loginLog/delete": {
"/admin/login-options": {
"post": {
"description": "登录选项",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录"
],
"summary": "登录选项",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/admin/login_log/delete": {
"post": {
"security": [
{
@@ -1498,7 +1560,7 @@ const docTemplateadmin = `{
}
}
},
"/admin/loginLog/detail/{id}": {
"/admin/login_log/detail/{id}": {
"get": {
"security": [
{
@@ -1553,7 +1615,7 @@ const docTemplateadmin = `{
}
}
},
"/admin/loginLog/list": {
"/admin/login_log/list": {
"get": {
"security": [
{
@@ -1922,6 +1984,108 @@ const docTemplateadmin = `{
}
}
},
"/admin/oidc/auth": {
"post": {
"description": "OidcAuth",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuth",
"responses": {}
}
},
"/admin/oidc/auth-query": {
"get": {
"description": "OidcAuthQuery",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuthQuery",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/admin.LoginPayload"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/batchDelete": {
"post": {
"security": [
{
"token": []
}
],
"description": "批量设备删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备"
],
"summary": "批量设备删除",
"parameters": [
{
"description": "设备id",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/create": {
"post": {
"security": [
@@ -1986,7 +2150,7 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "批量设备删除",
"description": "设备删除",
"consumes": [
"application/json"
],
@@ -1996,15 +2160,15 @@ const docTemplateadmin = `{
"tags": [
"设备"
],
"summary": "批量设备删除",
"summary": "设备删除",
"parameters": [
{
"description": "设备id",
"description": "设备信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
"$ref": "#/definitions/admin.PeerForm"
}
}
],
@@ -2127,6 +2291,12 @@ const docTemplateadmin = `{
"description": "主机名",
"name": "hostname",
"in": "query"
},
{
"type": "string",
"description": "uuids 用逗号分隔",
"name": "uuids",
"in": "query"
}
],
"responses": {
@@ -2998,6 +3168,117 @@ const docTemplateadmin = `{
}
}
}
},
"/admin/user_token/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.UserToken"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/user_token/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.UserTokenList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
}
},
"definitions": {
@@ -3164,11 +3445,17 @@ const docTemplateadmin = `{
"id": {
"type": "integer"
},
"issuer": {
"type": "string"
},
"op": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
"scopes": {
"type": "string"
}
}
},
@@ -3706,6 +3993,9 @@ const docTemplateadmin = `{
"user_id": {
"type": "integer"
},
"user_token_id": {
"type": "integer"
},
"uuid": {
"type": "string"
}
@@ -3749,12 +4039,18 @@ const docTemplateadmin = `{
"id": {
"type": "integer"
},
"issuer": {
"type": "string"
},
"op": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
"scopes": {
"type": "string"
},
"updated_at": {
"type": "string"
}
@@ -3795,6 +4091,9 @@ const docTemplateadmin = `{
"id": {
"type": "string"
},
"last_online_ip": {
"type": "string"
},
"last_online_time": {
"type": "integer"
},
@@ -3964,6 +4263,57 @@ const docTemplateadmin = `{
}
}
},
"model.UserToken": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"expired_at": {
"type": "integer"
},
"id": {
"type": "integer"
},
"token": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
}
}
},
"model.UserTokenList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.UserToken"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"response.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
},
"response.Response": {
"type": "object",
"properties": {

View File

@@ -346,17 +346,20 @@
"token": []
}
],
"description": "创建地址簿集合",
"description": "创建地址簿名称",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "创建地址簿集合",
"tags": [
"地址簿名称"
],
"summary": "创建地址簿名称",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -400,17 +403,20 @@
"token": []
}
],
"description": "地址簿集合删除",
"description": "地址簿名称删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合删除",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称删除",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -442,14 +448,17 @@
"token": []
}
],
"description": "地址簿集合详情",
"description": "地址簿名称详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合详情",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称详情",
"parameters": [
{
"type": "integer",
@@ -494,14 +503,17 @@
"token": []
}
],
"description": "地址簿集合列表",
"description": "地址簿名称列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合列表",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称列表",
"parameters": [
{
"type": "integer",
@@ -563,17 +575,20 @@
"token": []
}
],
"description": "地址簿集合编辑",
"description": "地址簿名称编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合编辑",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称编辑",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -617,17 +632,20 @@
"token": []
}
],
"description": "创建地址簿集合规则",
"description": "创建地址簿规则",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "创建地址簿集合规则",
"tags": [
"地址簿规则"
],
"summary": "创建地址簿规则",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -671,17 +689,20 @@
"token": []
}
],
"description": "地址簿集合规则删除",
"description": "地址簿规则删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则删除",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则删除",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -713,14 +734,17 @@
"token": []
}
],
"description": "地址簿集合规则详情",
"description": "地址簿规则详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则详情",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则详情",
"parameters": [
{
"type": "integer",
@@ -765,14 +789,17 @@
"token": []
}
],
"description": "地址簿集合规则列表",
"description": "地址簿规则列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则列表",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则列表",
"parameters": [
{
"type": "integer",
@@ -840,17 +867,20 @@
"token": []
}
],
"description": "地址簿集合规则编辑",
"description": "地址簿规则编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则编辑",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则编辑",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -1446,7 +1476,39 @@
}
}
},
"/admin/loginLog/delete": {
"/admin/login-options": {
"post": {
"description": "登录选项",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录"
],
"summary": "登录选项",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/admin/login_log/delete": {
"post": {
"security": [
{
@@ -1491,7 +1553,7 @@
}
}
},
"/admin/loginLog/detail/{id}": {
"/admin/login_log/detail/{id}": {
"get": {
"security": [
{
@@ -1546,7 +1608,7 @@
}
}
},
"/admin/loginLog/list": {
"/admin/login_log/list": {
"get": {
"security": [
{
@@ -1915,6 +1977,108 @@
}
}
},
"/admin/oidc/auth": {
"post": {
"description": "OidcAuth",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuth",
"responses": {}
}
},
"/admin/oidc/auth-query": {
"get": {
"description": "OidcAuthQuery",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuthQuery",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/admin.LoginPayload"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/batchDelete": {
"post": {
"security": [
{
"token": []
}
],
"description": "批量设备删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备"
],
"summary": "批量设备删除",
"parameters": [
{
"description": "设备id",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/create": {
"post": {
"security": [
@@ -1979,7 +2143,7 @@
"token": []
}
],
"description": "批量设备删除",
"description": "设备删除",
"consumes": [
"application/json"
],
@@ -1989,15 +2153,15 @@
"tags": [
"设备"
],
"summary": "批量设备删除",
"summary": "设备删除",
"parameters": [
{
"description": "设备id",
"description": "设备信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
"$ref": "#/definitions/admin.PeerForm"
}
}
],
@@ -2120,6 +2284,12 @@
"description": "主机名",
"name": "hostname",
"in": "query"
},
{
"type": "string",
"description": "uuids 用逗号分隔",
"name": "uuids",
"in": "query"
}
],
"responses": {
@@ -2991,6 +3161,117 @@
}
}
}
},
"/admin/user_token/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.UserToken"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/user_token/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.UserTokenList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
}
},
"definitions": {
@@ -3157,11 +3438,17 @@
"id": {
"type": "integer"
},
"issuer": {
"type": "string"
},
"op": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
"scopes": {
"type": "string"
}
}
},
@@ -3699,6 +3986,9 @@
"user_id": {
"type": "integer"
},
"user_token_id": {
"type": "integer"
},
"uuid": {
"type": "string"
}
@@ -3742,12 +4032,18 @@
"id": {
"type": "integer"
},
"issuer": {
"type": "string"
},
"op": {
"type": "string"
},
"redirect_url": {
"type": "string"
},
"scopes": {
"type": "string"
},
"updated_at": {
"type": "string"
}
@@ -3788,6 +4084,9 @@
"id": {
"type": "string"
},
"last_online_ip": {
"type": "string"
},
"last_online_time": {
"type": "integer"
},
@@ -3957,6 +4256,57 @@
}
}
},
"model.UserToken": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"expired_at": {
"type": "integer"
},
"id": {
"type": "integer"
},
"token": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
}
}
},
"model.UserTokenList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.UserToken"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"response.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
},
"response.Response": {
"type": "object",
"properties": {

View File

@@ -105,10 +105,14 @@ definitions:
type: string
id:
type: integer
issuer:
type: string
op:
type: string
redirect_url:
type: string
scopes:
type: string
required:
- client_id
- client_secret
@@ -472,6 +476,8 @@ definitions:
type: string
user_id:
type: integer
user_token_id:
type: integer
uuid:
type: string
type: object
@@ -500,10 +506,14 @@ definitions:
type: string
id:
type: integer
issuer:
type: string
op:
type: string
redirect_url:
type: string
scopes:
type: string
updated_at:
type: string
type: object
@@ -530,6 +540,8 @@ definitions:
type: string
id:
type: string
last_online_ip:
type: string
last_online_time:
type: integer
memory:
@@ -643,6 +655,39 @@ definitions:
total:
type: integer
type: object
model.UserToken:
properties:
created_at:
type: string
expired_at:
type: integer
id:
type: integer
token:
type: string
updated_at:
type: string
user_id:
type: integer
type: object
model.UserTokenList:
properties:
list:
items:
$ref: '#/definitions/model.UserToken'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
type: object
response.ErrorResponse:
properties:
error:
type: string
type: object
response.Response:
properties:
code:
@@ -858,9 +903,9 @@ paths:
post:
consumes:
- application/json
description: 创建地址簿集合
description: 创建地址簿名称
parameters:
- description: 地址簿集合信息
- description: 地址簿名称信息
in: body
name: body
required: true
@@ -884,14 +929,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿集合
summary: 创建地址簿名称
tags:
- 地址簿名称
/admin/address_book_collection/delete:
post:
consumes:
- application/json
description: 地址簿集合删除
description: 地址簿名称删除
parameters:
- description: 地址簿集合信息
- description: 地址簿名称信息
in: body
name: body
required: true
@@ -910,12 +957,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合删除
summary: 地址簿名称删除
tags:
- 地址簿名称
/admin/address_book_collection/detail/{id}:
get:
consumes:
- application/json
description: 地址簿集合详情
description: 地址簿名称详情
parameters:
- description: ID
in: path
@@ -940,12 +989,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合详情
summary: 地址簿名称详情
tags:
- 地址簿名称
/admin/address_book_collection/list:
get:
consumes:
- application/json
description: 地址簿集合列表
description: 地址簿名称列表
parameters:
- description: 页码
in: query
@@ -981,14 +1032,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合列表
summary: 地址簿名称列表
tags:
- 地址簿名称
/admin/address_book_collection/update:
post:
consumes:
- application/json
description: 地址簿集合编辑
description: 地址簿名称编辑
parameters:
- description: 地址簿集合信息
- description: 地址簿名称信息
in: body
name: body
required: true
@@ -1012,14 +1065,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合编辑
summary: 地址簿名称编辑
tags:
- 地址簿名称
/admin/address_book_collection_rule/create:
post:
consumes:
- application/json
description: 创建地址簿集合规则
description: 创建地址簿规则
parameters:
- description: 地址簿集合规则信息
- description: 地址簿规则信息
in: body
name: body
required: true
@@ -1043,14 +1098,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿集合规则
summary: 创建地址簿规则
tags:
- 地址簿规则
/admin/address_book_collection_rule/delete:
post:
consumes:
- application/json
description: 地址簿集合规则删除
description: 地址簿规则删除
parameters:
- description: 地址簿集合规则信息
- description: 地址簿规则信息
in: body
name: body
required: true
@@ -1069,12 +1126,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则删除
summary: 地址簿规则删除
tags:
- 地址簿规则
/admin/address_book_collection_rule/detail/{id}:
get:
consumes:
- application/json
description: 地址簿集合规则详情
description: 地址簿规则详情
parameters:
- description: ID
in: path
@@ -1099,12 +1158,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则详情
summary: 地址簿规则详情
tags:
- 地址簿规则
/admin/address_book_collection_rule/list:
get:
consumes:
- application/json
description: 地址簿集合规则列表
description: 地址簿规则列表
parameters:
- description: 页码
in: query
@@ -1144,14 +1205,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则列表
summary: 地址簿规则列表
tags:
- 地址簿规则
/admin/address_book_collection_rule/update:
post:
consumes:
- application/json
description: 地址簿集合规则编辑
description: 地址簿规则编辑
parameters:
- description: 地址簿集合规则信息
- description: 地址簿规则信息
in: body
name: body
required: true
@@ -1175,7 +1238,9 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则编辑
summary: 地址簿规则编辑
tags:
- 地址簿规则
/admin/app-config:
get:
consumes:
@@ -1510,7 +1575,28 @@ paths:
summary: 登录
tags:
- 登录
/admin/loginLog/delete:
/admin/login-options:
post:
consumes:
- application/json
description: 登录选项
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: 登录选项
tags:
- 登录
/admin/login_log/delete:
post:
consumes:
- application/json
@@ -1538,7 +1624,7 @@ paths:
summary: 登录日志删除
tags:
- 登录日志
/admin/loginLog/detail/{id}:
/admin/login_log/detail/{id}:
get:
consumes:
- application/json
@@ -1570,7 +1656,7 @@ paths:
summary: 登录日志详情
tags:
- 登录日志
/admin/loginLog/list:
/admin/login_log/list:
get:
consumes:
- application/json
@@ -1789,6 +1875,69 @@ paths:
summary: Oauth编辑
tags:
- Oauth
/admin/oidc/auth:
post:
consumes:
- application/json
description: OidcAuth
produces:
- application/json
responses: {}
summary: OidcAuth
tags:
- Oauth
/admin/oidc/auth-query:
get:
consumes:
- application/json
description: OidcAuthQuery
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/admin.LoginPayload'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: OidcAuthQuery
tags:
- Oauth
/admin/peer/batchDelete:
post:
consumes:
- application/json
description: 批量设备删除
parameters:
- description: 设备id
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.PeerBatchDeleteForm'
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/peer/create:
post:
consumes:
@@ -1826,14 +1975,14 @@ paths:
post:
consumes:
- application/json
description: 批量设备删除
description: 设备删除
parameters:
- description: 设备id
- description: 设备信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.PeerBatchDeleteForm'
$ref: '#/definitions/admin.PeerForm'
produces:
- application/json
responses:
@@ -1847,7 +1996,7 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 批量设备删除
summary: 设备删除
tags:
- 设备
/admin/peer/detail/{id}:
@@ -1908,6 +2057,10 @@ paths:
in: query
name: hostname
type: string
- description: uuids 用逗号分隔
in: query
name: uuids
type: string
produces:
- application/json
responses:
@@ -2427,6 +2580,73 @@ paths:
summary: 修改密码
tags:
- 用户
/admin/user_token/delete:
post:
consumes:
- application/json
description: 登录凭证删除
parameters:
- description: 登录凭证信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.UserToken'
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/user_token/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.UserTokenList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录凭证列表
tags:
- 登录凭证
securityDefinitions:
BearerAuth:
in: header

View File

@@ -834,7 +834,7 @@ const docTemplateapi = `{
}
},
"/login-options": {
"post": {
"get": {
"description": "登录选项",
"consumes": [
"application/json"

View File

@@ -827,7 +827,7 @@
}
},
"/login-options": {
"post": {
"get": {
"description": "登录选项",
"consumes": [
"application/json"

View File

@@ -715,7 +715,7 @@ paths:
tags:
- 登录
/login-options:
post:
get:
consumes:
- application/json
description: 登录选项

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -234,7 +234,7 @@ func (ct *AddressBook) Update(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AddressBookService.Update(t)
err := service.AllService.AddressBookService.UpdateAll(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return

View File

@@ -14,10 +14,10 @@ import (
type AddressBookCollection struct {
}
// Detail 地址簿集合
// @AddressBookCollections 地址簿集合
// @Summary 地址簿集合详情
// @Description 地址簿集合详情
// Detail 地址簿名称
// @Tags 地址簿名称
// @Summary 地址簿名称详情
// @Description 地址簿名称详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
@@ -42,13 +42,13 @@ func (abc *AddressBookCollection) Detail(c *gin.Context) {
return
}
// Create 创建地址簿集合
// @AddressBookCollections 地址簿集合
// @Summary 创建地址簿集合
// @Description 创建地址簿集合
// Create 创建地址簿名称
// @Tags 地址簿名称
// @Summary 创建地址簿名称
// @Description 创建地址簿名称
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿集合信息"
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/create [post]
@@ -79,9 +79,9 @@ func (abc *AddressBookCollection) Create(c *gin.Context) {
}
// List 列表
// @AddressBookCollections 地址簿集合
// @Summary 地址簿集合列表
// @Description 地址簿集合列表
// @Tags 地址簿名称
// @Summary 地址簿名称列表
// @Description 地址簿名称列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
@@ -111,12 +111,12 @@ func (abc *AddressBookCollection) List(c *gin.Context) {
}
// Update 编辑
// @AddressBookCollections 地址簿集合
// @Summary 地址簿集合编辑
// @Description 地址簿集合编辑
// @Tags 地址簿名称
// @Summary 地址簿名称编辑
// @Description 地址簿名称编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿集合信息"
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/update [post]
@@ -151,12 +151,12 @@ func (abc *AddressBookCollection) Update(c *gin.Context) {
}
// Delete 删除
// @AddressBookCollections 地址簿集合
// @Summary 地址簿集合删除
// @Description 地址簿集合删除
// @Tags 地址簿名称
// @Summary 地址簿名称删除
// @Description 地址簿名称删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿集合信息"
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/delete [post]

View File

@@ -15,9 +15,9 @@ type AddressBookCollectionRule struct {
}
// List 列表
// @AddressBookCollectionRule 地址簿集合规则
// @Summary 地址簿集合规则列表
// @Description 地址簿集合规则列表
// @Tags 地址簿规则
// @Summary 地址簿规则列表
// @Description 地址簿规则列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
@@ -51,10 +51,10 @@ func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
response.Success(c, res)
}
// Detail 地址簿集合规则
// @AddressBookCollectionRule 地址簿集合规则
// @Summary 地址簿集合规则详情
// @Description 地址簿集合规则详情
// Detail 地址簿规则
// @Tags 地址簿规则
// @Summary 地址簿规则详情
// @Description 地址簿规则详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
@@ -79,13 +79,13 @@ func (abcr *AddressBookCollectionRule) Detail(c *gin.Context) {
return
}
// Create 创建地址簿集合规则
// @AddressBookCollectionRule 地址簿集合规则
// @Summary 创建地址簿集合规则
// @Description 创建地址簿集合规则
// Create 创建地址簿规则
// @Tags 地址簿规则
// @Summary 创建地址簿规则
// @Description 创建地址簿规则
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿集合规则信息"
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/create [post]
@@ -169,12 +169,12 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
}
// Update 编辑
// @AddressBookCollectionRule 地址簿集合规则
// @Summary 地址簿集合规则编辑
// @Description 地址簿集合规则编辑
// @Tags 地址簿规则
// @Summary 地址簿规则编辑
// @Description 地址簿规则编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿集合规则信息"
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/update [post]
@@ -210,12 +210,12 @@ func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
}
// Delete 删除
// @AddressBookCollectionRule 地址簿集合规则
// @Summary 地址簿集合规则删除
// @Description 地址簿集合规则删除
// @Tags 地址簿规则
// @Summary 地址簿规则删除
// @Description 地址簿规则删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿集合规则信息"
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/delete [post]

View File

@@ -2,13 +2,16 @@ package admin
import (
"Gwen/global"
"Gwen/http/controller/api"
"Gwen/http/request/admin"
apiReq "Gwen/http/request/api"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service"
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type Login struct {
@@ -50,10 +53,10 @@ func (ct *Login) Login(c *gin.Context) {
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: "webadmin",
Uuid: "",
Client: model.LoginLogClientWebAdmin,
Uuid: "", //must be empty
Ip: c.ClientIP(),
Type: "account",
Type: model.LoginLogTypeAccount,
Platform: f.Platform,
})
@@ -82,3 +85,90 @@ func (ct *Login) Logout(c *gin.Context) {
}
response.Success(c, nil)
}
// LoginOptions
// @Tags 登录
// @Summary 登录选项
// @Description 登录选项
// @Accept json
// @Produce json
// @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse
// @Router /admin/login-options [post]
func (ct *Login) LoginOptions(c *gin.Context) {
res := service.AllService.OauthService.List(1, 100, func(tx *gorm.DB) {
tx.Select("op").Order("id")
})
var ops []string
for _, v := range res.Oauths {
ops = append(ops, v.Op)
}
response.Success(c, gin.H{
"ops": ops,
"register": global.Config.App.Register,
})
}
// OidcAuth
// @Tags Oauth
// @Summary OidcAuth
// @Description OidcAuth
// @Accept json
// @Produce json
// @Router /admin/oidc/auth [post]
func (ct *Login) OidcAuth(c *gin.Context) {
// o := &api.Oauth{}
// o.OidcAuth(c)
f := &apiReq.OidcAuthRequest{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin,
Op: f.Op,
Id: f.Id,
DeviceType: "webadmin",
// DeviceOs: ct.Platform(c),
DeviceOs: f.DeviceInfo.Os,
Uuid: f.Uuid,
}, 5*60)
response.Success(c, gin.H{
"code": code,
"url": url,
})
}
// OidcAuthQuery
// @Tags Oauth
// @Summary OidcAuthQuery
// @Description OidcAuthQuery
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=adResp.LoginPayload}
// @Failure 500 {object} response.Response
// @Router /admin/oidc/auth-query [get]
func (ct *Login) OidcAuthQuery(c *gin.Context) {
o := &api.Oauth{}
u, ut := o.OidcAuthQueryPre(c)
if ut == nil {
return
}
//fmt.Println("u:", u)
//fmt.Println("ut:", ut)
response.Success(c, &adResp.LoginPayload{
Token: ut.Token,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
}

View File

@@ -23,7 +23,7 @@ type LoginLog struct {
// @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]
// @Router /admin/login_log/detail/{id} [get]
// @Security token
func (ct *LoginLog) Detail(c *gin.Context) {
id := c.Param("id")
@@ -48,7 +48,7 @@ func (ct *LoginLog) Detail(c *gin.Context) {
// @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]
// @Router /admin/login_log/list [get]
// @Security token
func (ct *LoginLog) List(c *gin.Context) {
query := &admin.LoginLogQuery{}
@@ -78,7 +78,7 @@ func (ct *LoginLog) List(c *gin.Context) {
// @Param body body model.LoginLog true "登录日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/loginLog/delete [post]
// @Router /admin/login_log/delete [post]
// @Security token
func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{}

View File

@@ -102,7 +102,7 @@ func (o *Oauth) BindConfirm(c *gin.Context) {
return
}
u := service.AllService.UserService.CurUser(c)
err = service.AllService.OauthService.BindGithubUser(v.ThirdOpenId, v.ThirdOpenId, u.Id)
err = service.AllService.OauthService.BindOauthUser(v.Op, v.ThirdOpenId, v.ThirdName, u.Id)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "BindFail"))
return
@@ -140,6 +140,13 @@ func (o *Oauth) Unbind(c *gin.Context) {
return
}
}
if f.Op == model.OauthTypeOidc {
err = service.AllService.OauthService.UnBindOidcUser(u.Id)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}
response.Success(c, nil)
}

View File

@@ -79,6 +79,7 @@ func (ct *Peer) Create(c *gin.Context) {
// @Param time_ago query int false "时间"
// @Param id query string false "ID"
// @Param hostname query string false "主机名"
// @Param uuids query string false "uuids 用逗号分隔"
// @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response
// @Router /admin/peer/list [get]
@@ -104,6 +105,9 @@ func (ct *Peer) List(c *gin.Context) {
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
})
response.Success(c, res)
}
@@ -188,7 +192,7 @@ func (ct *Peer) Delete(c *gin.Context) {
// @Param body body admin.PeerBatchDeleteForm true "设备id"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/peer/delete [post]
// @Router /admin/peer/batchDelete [post]
// @Security token
func (ct *Peer) BatchDelete(c *gin.Context) {
f := &admin.PeerBatchDeleteForm{}
@@ -207,3 +211,21 @@ func (ct *Peer) BatchDelete(c *gin.Context) {
}
response.Success(c, nil)
}
func (ct *Peer) SimpleData(c *gin.Context) {
f := &admin.SimpleDataQuery{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
res := service.AllService.PeerService.List(1, 99999, func(tx *gorm.DB) {
//可以公开的情报
tx.Select("id,version")
tx.Where("id in (?)", f.Ids)
})
response.Success(c, res)
}

View File

@@ -5,6 +5,7 @@ import (
"Gwen/http/request/admin"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -247,10 +248,14 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
return
}
u := service.AllService.UserService.CurUser(c)
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
if u.Password != oldPwd {
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return
// If the password is not empty, the old password is verified
// otherwise, the old password is not verified
if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
if u.Password != oldPwd {
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return
}
}
err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
if err != nil {
@@ -323,3 +328,40 @@ func (ct *User) GroupUsers(c *gin.Context) {
}
response.Success(c, data)
}
// Register
func (ct *User) Register(c *gin.Context) {
if !global.Config.App.Register {
response.Fail(c, 101, response.TranslateMsg(c, "RegisterClosed"))
return
}
f := &admin.RegisterForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.Register(f.Username, f.Password)
if u == nil || u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed"))
return
}
// 注册成功后自动登录
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: model.LoginLogClientWebAdmin,
Uuid: "",
Ip: c.ClientIP(),
Type: model.LoginLogTypeAccount,
})
response.Success(c, &adResp.LoginPayload{
Token: ut.Token,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
}

View File

@@ -0,0 +1,83 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type UserToken struct {
}
// 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.UserTokenList}
// @Failure 500 {object} response.Response
// @Router /admin/user_token/list [get]
// @Security token
func (ct *UserToken) List(c *gin.Context) {
query := &admin.LoginTokenQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.UserService.TokenList(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
tx.Order("id desc")
})
response.Success(c, res)
}
// Delete 删除
// @Tags 登录凭证
// @Summary 登录凭证删除
// @Description 登录凭证删除
// @Accept json
// @Produce json
// @Param body body model.UserToken true "登录凭证信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/user_token/delete [post]
// @Security token
func (ct *UserToken) Delete(c *gin.Context) {
f := &model.UserToken{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
l := service.AllService.UserService.TokenInfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if l.Id > 0 {
err := service.AllService.UserService.DeleteToken(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -689,9 +689,9 @@ func (a *Ab) PeerDel(c *gin.Context) {
// @Router /ab/peer/update/{guid} [put]
// @Security BearerAuth
func (a *Ab) PeerUpdate(c *gin.Context) {
//f := &gin.H{}
f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f)
f := gin.H{}
//f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(&f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
@@ -709,17 +709,33 @@ func (a *Ab) PeerUpdate(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
//fmt.Println(f)
//return
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, f.Id, cid)
//判断f["Id"]是否存在
fid, ok := f["id"]
if !ok {
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
fidstr := fid.(string)
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, fidstr, cid)
if ab == nil || ab.RowId == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
nab := f.ToAddressBook()
nab.RowId = ab.RowId
err = service.AllService.AddressBookService.Update(nab)
//允许的字段
allowUp := []string{"password", "hash", "tags", "alias"}
//f中的字段如果不在allowUp中就删除
for k := range f {
if !utils.InArray(k, allowUp) {
delete(f, k)
}
}
//fmt.Println(f)
if tags, _ok := f["tags"]; _ok {
f["tags"], _ = json.Marshal(tags)
}
err = service.AllService.AddressBookService.UpdateByMap(ab, f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return

View File

@@ -54,10 +54,9 @@ func (i *Index) Heartbeat(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
return
}
//如果在一分钟以内则不更新
if time.Now().Unix()-peer.LastOnlineTime > 60 {
peer.LastOnlineTime = time.Now().Unix()
upp := &model.Peer{RowId: peer.RowId, LastOnlineTime: peer.LastOnlineTime}
//如果在40s以内则不更新
if time.Now().Unix()-peer.LastOnlineTime > 40 {
upp := &model.Peer{RowId: peer.RowId, LastOnlineTime: time.Now().Unix(), LastOnlineIp: c.ClientIP()}
service.AllService.PeerService.Update(upp)
}
c.JSON(http.StatusOK, gin.H{})

View File

@@ -54,7 +54,7 @@ func (l *Login) Login(c *gin.Context) {
//根据refer判断是webclient还是app
ref := c.GetHeader("referer")
if ref != "" {
f.DeviceInfo.Type = "webclient"
f.DeviceInfo.Type = model.LoginLogClientWeb
}
ut := service.AllService.UserService.Login(u, &model.LoginLog{
@@ -81,7 +81,7 @@ func (l *Login) Login(c *gin.Context) {
// @Produce json
// @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse
// @Router /login-options [post]
// @Router /login-options [get]
func (l *Login) LoginOptions(c *gin.Context) {
oauthOks := []string{}
err, _ := service.AllService.OauthService.GetOauthConfig(model.OauthTypeGithub)
@@ -92,6 +92,10 @@ func (l *Login) LoginOptions(c *gin.Context) {
if err == nil {
oauthOks = append(oauthOks, model.OauthTypeGoogle)
}
err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeOidc)
if err == nil {
oauthOks = append(oauthOks, model.OauthTypeOidc)
}
oauthOks = append(oauthOks, model.OauthTypeWebauth)
var oidcItems []map[string]string
for _, v := range oauthOks {

View File

@@ -32,7 +32,8 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub {
//fmt.Println(f)
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub && f.Op != model.OauthTypeOidc {
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
@@ -59,6 +60,59 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
})
}
func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken) {
var u *model.User
var ut *model.UserToken
q := &api.OidcAuthQuery{}
// 解析查询参数并处理错误
if err := c.ShouldBindQuery(q); err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+": "+err.Error())
return nil, nil
}
// 获取 OAuth 缓存
v := service.AllService.OauthService.GetOauthCache(q.Code)
if v == nil {
response.Error(c, response.TranslateMsg(c, "OauthExpired"))
return nil, nil
}
// 如果 UserId 为 0说明还在授权中
if v.UserId == 0 {
c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress, please login and bind"})
return nil, nil
}
// 获取用户信息
u = service.AllService.UserService.InfoById(v.UserId)
if u == nil {
response.Error(c, response.TranslateMsg(c, "UserNotFound"))
return nil, nil
}
// 删除 OAuth 缓存
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,
})
if ut == nil {
response.Error(c, response.TranslateMsg(c, "LoginFailed"))
return nil, nil
}
// 返回用户令牌
return u, ut
}
// OidcAuthQuery
// @Tags Oauth
// @Summary OidcAuthQuery
@@ -69,33 +123,10 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
// @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, response.TranslateMsg(c, "ParamsError")+err.Error())
u, ut := o.OidcAuthQueryPre(c)
if u == nil || ut == nil {
return
}
v := service.AllService.OauthService.GetOauthCache(q.Code)
if v == nil {
response.Error(c, response.TranslateMsg(c, "OauthExpired"))
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)
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",
@@ -129,7 +160,11 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
ty := v.Op
ac := v.Action
var u *model.User
openid := ""
thirdName := ""
//fmt.Println("ty ac ", ty, ac)
if ty == model.OauthTypeGithub {
code := c.Query("code")
err, userData := service.AllService.OauthService.GithubCallback(code)
@@ -137,123 +172,100 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, 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, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
u := service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定github
err = service.AllService.OauthService.BindGithubUser(strconv.Itoa(userData.Id), userData.Login, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return
} else if ac == service.OauthActionTypeLogin {
//登录
if v.UserId != 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
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, response.TranslateMsg(c, "OauthRegisterFailed"))
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
return
}
}
if ty == model.OauthTypeGoogle {
openid = strconv.Itoa(userData.Id)
thirdName = userData.Login
} else if ty == model.OauthTypeGoogle {
code := c.Query("code")
err, userData := service.AllService.OauthService.GoogleCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
return
}
openid = userData.Email
//将空格替换成_
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, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
u := service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定
err = service.AllService.OauthService.BindGoogleUser(userData.Email, googleName, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return
} else if ac == service.OauthActionTypeLogin {
if v.UserId != 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
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, response.TranslateMsg(c, "OauthRegisterFailed"))
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
thirdName = strings.Replace(userData.Name, " ", "_", -1)
} else if ty == model.OauthTypeOidc {
code := c.Query("code")
err, userData := service.AllService.OauthService.OidcCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
return
}
openid = userData.Sub
thirdName = userData.PreferredUsername
} else {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ParamsError"))
return
}
if ac == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, openid)
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
u = service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定
err := service.AllService.OauthService.BindOauthUser(ty, openid, thirdName, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return
} else if ac == service.OauthActionTypeLogin {
//登录
if v.UserId != 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
return
}
u = service.AllService.UserService.InfoByGithubId(openid)
if u == nil {
oa := service.AllService.OauthService.InfoByOp(ty)
if !*oa.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
v.ThirdName = thirdName
v.ThirdOpenId = openid
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
c.Redirect(http.StatusFound, url)
return
}
//自动注册
u = service.AllService.UserService.RegisterByOauth(ty, thirdName, openid)
if u.Id == 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
// 如果是webadmin登录成功后跳转到webadmin
if v.DeviceType == "webadmin" {
/*service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: "webadmin",
Uuid: "", //must be empty
Ip: c.ClientIP(),
Type: model.LoginLogTypeOauth,
Platform: v.DeviceOs,
})*/
url := global.Config.Rustdesk.ApiServer + "/_admin/#/"
c.Redirect(http.StatusFound, url)
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
return
} else {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ParamsError"))
return
}
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError"))
}

View File

@@ -11,3 +11,7 @@ type LoginLogQuery struct {
IsMy int `form:"is_my"`
PageQuery
}
type LoginTokenQuery struct {
UserId int `form:"user_id"`
PageQuery
}

View File

@@ -15,6 +15,8 @@ type UnBindOauthForm struct {
type OauthForm struct {
Id uint `json:"id"`
Op string `json:"op" validate:"required"`
Issuer string `json:"issuer" validate:"omitempty,url"`
Scopes string `json:"scopes" validate:"omitempty"`
ClientId string `json:"client_id" validate:"required"`
ClientSecret string `json:"client_secret" validate:"required"`
RedirectUrl string `json:"redirect_url" validate:"required"`
@@ -28,6 +30,8 @@ func (of *OauthForm) ToOauth() *model.Oauth {
ClientSecret: of.ClientSecret,
RedirectUrl: of.RedirectUrl,
AutoRegister: of.AutoRegister,
Issuer: of.Issuer,
Scopes: of.Scopes,
}
oa.Id = of.Id
return oa

View File

@@ -38,4 +38,9 @@ type PeerQuery struct {
TimeAgo int `json:"time_ago" form:"time_ago"`
Id string `json:"id" form:"id"`
Hostname string `json:"hostname" form:"hostname"`
Uuids string `json:"uuids" form:"uuids"`
}
type SimpleDataQuery struct {
Ids []string `json:"ids" form:"ids"`
}

View File

@@ -59,3 +59,9 @@ type GroupUsersQuery struct {
IsMy int `json:"is_my"`
UserId uint `json:"user_id"`
}
type RegisterForm struct {
Username string `json:"username" validate:"required,gte=4,lte=10"`
Password string `json:"password" validate:"required,gte=4,lte=20"`
ConfirmPassword string `json:"confirm_password" validate:"required,gte=4,lte=20"`
}

View File

@@ -17,7 +17,7 @@ func Init(g *gin.Engine) {
adg := g.Group("/api/admin")
LoginBind(adg)
adg.POST("/user/register", (&admin.User{}).Register)
adg.Use(middleware.AdminAuth())
//FileBind(adg)
UserBind(adg)
@@ -30,10 +30,10 @@ func Init(g *gin.Engine) {
AuditBind(adg)
AddressBookCollectionBind(adg)
AddressBookCollectionRuleBind(adg)
UserTokenBind(adg)
rs := &admin.Rustdesk{}
adg.GET("/server-config", rs.ServerConfig)
adg.GET("/app-config", rs.AppConfig)
//访问静态文件
//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
}
@@ -41,6 +41,9 @@ func LoginBind(rg *gin.RouterGroup) {
cont := &admin.Login{}
rg.POST("/login", cont.Login)
rg.POST("/logout", cont.Logout)
rg.GET("/login-options", cont.LoginOptions)
rg.POST("/oidc/auth", cont.OidcAuth)
rg.GET("/oidc/auth-query", cont.OidcAuthQuery)
}
func UserBind(rg *gin.RouterGroup) {
@@ -105,6 +108,8 @@ func AddressBookBind(rg *gin.RouterGroup) {
}
func PeerBind(rg *gin.RouterGroup) {
aR := rg.Group("/peer")
aR.POST("/simpleData", (&admin.Peer{}).SimpleData)
aR.Use(middleware.AdminPrivilege())
{
cont := &admin.Peer{}
aR.GET("/list", cont.List)
@@ -112,9 +117,7 @@ func PeerBind(rg *gin.RouterGroup) {
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
arp := aR.Use(middleware.AdminPrivilege())
arp.POST("/batchDelete", cont.BatchDelete)
aR.POST("/batchDelete", cont.BatchDelete)
}
}
@@ -178,6 +181,12 @@ func AddressBookCollectionRuleBind(rg *gin.RouterGroup) {
aR.POST("/delete", cont.Delete)
}
}
func UserTokenBind(rg *gin.RouterGroup) {
aR := rg.Group("/user_token").Use(middleware.AdminPrivilege())
cont := &admin.UserToken{}
aR.GET("/list", cont.List)
aR.POST("/delete", cont.Delete)
}
/*
func FileBind(rg *gin.RouterGroup) {

View File

@@ -51,30 +51,22 @@ func TestLocal_GetLock(t *testing.T) {
func TestLocal_Lock(t *testing.T) {
l := NewLocal()
wg := sync.WaitGroup{}
wg.Add(3)
m := 10
wg.Add(m)
i := 0
go func() {
l.Lock("key")
fmt.Println("l1", i)
i++
l.UnLock("key")
wg.Done()
}()
go func() {
l.Lock("key")
fmt.Println("l2", i)
i++
l.UnLock("key")
wg.Done()
}()
go func() {
l.Lock("key")
fmt.Println("l3", i)
i++
l.UnLock("key")
wg.Done()
}()
for j := 0; j < m; j++ {
go func() {
l.Lock("key")
//fmt.Println(j, i)
i++
fmt.Println(j, i)
l.UnLock("key")
wg.Done()
}()
}
wg.Wait()
fmt.Println(i)
}
func TestSyncMap(t *testing.T) {

View File

@@ -2,16 +2,22 @@ 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
UserId uint `json:"user_id" gorm:"default:0;not null;"`
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
UserTokenId uint `json:"user_token_id" gorm:"default:0;not null;"`
TimeModel
}
const (
LoginLogClientWebAdmin = "webadmin"
LoginLogClientWeb = "webclient"
LoginLogClientApp = "app"
)
const (
LoginLogTypeAccount = "account"
LoginLogTypeOauth = "oauth"

View File

@@ -7,12 +7,15 @@ type Oauth struct {
ClientSecret string `json:"client_secret"`
RedirectUrl string `json:"redirect_url"`
AutoRegister *bool `json:"auto_register"`
Scopes string `json:"scopes"`
Issuer string `json:"issuer"`
TimeModel
}
const (
OauthTypeGithub = "github"
OauthTypeGoogle = "google"
OauthTypeOidc = "oidc"
OauthTypeWebauth = "webauth"
)

View File

@@ -11,8 +11,9 @@ type Peer struct {
Uuid string `json:"uuid" gorm:"default:'';not null;index"`
Version string `json:"version" gorm:"default:'';not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
User User `json:"user,omitempty" gorm:""`
User *User `json:"user,omitempty"`
LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"`
LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"`
TimeModel
}

View File

@@ -2,7 +2,7 @@ package model
type User struct {
IdModel
Username string `json:"username" gorm:"default:'';not null;index,unique"`
Username string `json:"username" gorm:"default:'';not null;uniqueIndex"`
Password string `json:"-" gorm:"default:'';not null;"`
Nickname string `json:"nickname" gorm:"default:'';not null;"`
Avatar string `json:"avatar" gorm:"default:'';not null;"`

View File

@@ -7,3 +7,8 @@ type UserToken struct {
ExpiredAt int64 `json:"expired_at" gorm:"default:0;not null;"`
TimeModel
}
type UserTokenList struct {
UserTokens []UserToken `json:"list"`
Pagination
}

View File

@@ -119,3 +119,7 @@ other = "Default Group"
description = "Share group"
one = "Share Group"
other = "Share Group"
[RegisterClosed]
description = "Register closed."
one = "Register closed."
other = "Register closed."

View File

@@ -121,3 +121,8 @@ other = "기본 그룹"
description = "Share group."
one = "공유 그룹"
other = "공유 그룹"
[RegisterClosed]
description = "Register closed."
one = "가입이 종료되었습니다."
other = "가입이 종료되었습니다."

View File

@@ -127,3 +127,8 @@ other = "Группа по умолчанию"
description = "Share group."
one = "Общая группа"
other = "Общая группа"
[RegisterClosed]
description = "Register closed."
one = "Регистрация закрыта."
other = "Регистрация закрыта."

View File

@@ -121,3 +121,7 @@ other = "默认组"
description = "Share group."
one = "共享组"
other = "共享组"
[RegisterClosed]
description = "Register closed."
one = "注册已关闭。"
other = "注册已关闭。"

View File

@@ -127,6 +127,16 @@ func (s *AddressBookService) Delete(u *model.AddressBook) error {
// Update 更新
func (s *AddressBookService) Update(u *model.AddressBook) error {
return global.DB.Model(u).Updates(u).Error
}
// UpdateByMap 更新
func (s *AddressBookService) UpdateByMap(u *model.AddressBook, data map[string]interface{}) error {
return global.DB.Model(u).Updates(data).Error
}
// UpdateAll 更新
func (s *AddressBookService) UpdateAll(u *model.AddressBook) error {
return global.DB.Model(u).Select("*").Omit("created_at").Updates(u).Error
}

View File

@@ -15,10 +15,19 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
)
// Define a struct to parse the .well-known/openid-configuration response
type OidcEndpoint struct {
Issuer string `json:"issuer"`
AuthURL string `json:"authorization_endpoint"`
TokenURL string `json:"token_endpoint"`
UserInfo string `json:"userinfo_endpoint"`
}
type OauthService struct {
}
@@ -78,6 +87,14 @@ type GoogleUserdata struct {
Picture string `json:"picture"`
VerifiedEmail bool `json:"verified_email"`
}
type OidcUserdata struct {
Sub string `json:"sub"`
Email string `json:"email"`
VerifiedEmail bool `json:"email_verified"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
}
type OauthCacheItem struct {
UserId uint `json:"user_id"`
Id string `json:"id"` //rustdesk的设备ID
@@ -137,38 +154,106 @@ func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
return err, code, ""
}
// GetOauthConfig 获取配置
// Method to fetch OIDC configuration dynamically
func FetchOidcConfig(issuer string) (error, OidcEndpoint) {
configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
// Get the HTTP client (with or without proxy based on configuration)
client := getHTTPClientWithProxy()
resp, err := client.Get(configURL)
if err != nil {
return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{}
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{}
}
var endpoint OidcEndpoint
if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil {
return errors.New("failed to parse OIDC configuration"), OidcEndpoint{}
}
return nil, endpoint
}
// GetOauthConfig retrieves the OAuth2 configuration based on the provider type
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("ConfigNotFound"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: github.Endpoint,
Scopes: []string{"read:user", "user:email"},
}
switch op {
case model.OauthTypeGithub:
return os.getGithubConfig()
case model.OauthTypeGoogle:
return os.getGoogleConfig()
case model.OauthTypeOidc:
return os.getOidcConfig()
default:
return errors.New("unsupported OAuth type"), nil
}
if op == model.OauthTypeGoogle {
g := os.InfoByOp(model.OauthTypeGoogle)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("ConfigNotFound"), 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"},
}
}
// Helper function to get GitHub OAuth2 configuration
func (os *OauthService) getGithubConfig() (error, *oauth2.Config) {
g := os.InfoByOp(model.OauthTypeGithub)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("ConfigNotFound"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: github.Endpoint,
Scopes: []string{"read:user", "user:email"},
}
}
// Helper function to get Google OAuth2 configuration
func (os *OauthService) getGoogleConfig() (error, *oauth2.Config) {
g := os.InfoByOp(model.OauthTypeGoogle)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("ConfigNotFound"), 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"},
}
}
// Helper function to get OIDC OAuth2 configuration
func (os *OauthService) getOidcConfig() (error, *oauth2.Config) {
g := os.InfoByOp(model.OauthTypeOidc)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" || g.Issuer == "" {
return errors.New("ConfigNotFound"), nil
}
// Set scopes
scopes := strings.TrimSpace(g.Scopes)
if scopes == "" {
scopes = "openid,profile,email"
}
scopeList := strings.Split(scopes, ",")
err, endpoint := FetchOidcConfig(g.Issuer)
if err != nil {
return err, nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: oauth2.Endpoint{
AuthURL: endpoint.AuthURL,
TokenURL: endpoint.TokenURL,
},
Scopes: scopeList,
}
return errors.New("ConfigNotFound"), nil
}
func getHTTPClientWithProxy() *http.Client {
//todo add timeout
if global.Config.Proxy.Enable {
if global.Config.Proxy.Host == "" {
global.Logger.Warn("Proxy is enabled but proxy host is empty.")
@@ -269,6 +354,53 @@ func (os *OauthService) GoogleCallback(code string) (error error, userData *Goog
return
}
func (os *OauthService) OidcCallback(code string) (error error, userData *OidcUserdata) {
err, oauthConfig := os.GetOauthConfig(model.OauthTypeOidc)
if err != nil {
return err, nil
}
// 使用代理配置创建 HTTP 客户端
httpClient := getHTTPClientWithProxy()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
token, err := oauthConfig.Exchange(ctx, code)
if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
error = errors.New("GetOauthTokenError")
return
}
// 使用带有代理的 HTTP 客户端获取用户信息
client := oauthConfig.Client(ctx, token)
g := os.InfoByOp(model.OauthTypeOidc)
err, endpoint := FetchOidcConfig(g.Issuer)
if err != nil {
global.Logger.Warn("failed fetching OIDC configuration: ", err)
error = errors.New("FetchOidcConfigError")
return
}
resp, err := client.Get(endpoint.UserInfo)
if err != nil {
global.Logger.Warn("failed getting user info: ", err)
error = errors.New("GetOauthUserInfoError")
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
global.Logger.Warn("failed closing response body: ", err)
}
}(resp.Body)
// 解析用户信息
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
global.Logger.Warn("failed decoding user info: ", err)
error = errors.New("DecodeOauthUserInfoError")
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)
@@ -282,6 +414,11 @@ func (os *OauthService) BindGithubUser(openid, username string, userId uint) err
func (os *OauthService) BindGoogleUser(email, username string, userId uint) error {
return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId)
}
func (os *OauthService) BindOidcUser(sub, username string, userId uint) error {
return os.BindOauthUser(model.OauthTypeOidc, sub, username, userId)
}
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
utr := &model.UserThird{
OpenId: openid,
@@ -298,10 +435,18 @@ func (os *OauthService) UnBindGithubUser(userid uint) error {
func (os *OauthService) UnBindGoogleUser(userid uint) error {
return os.UnBindThird(model.OauthTypeGoogle, userid)
}
func (os *OauthService) UnBindOidcUser(userid uint) error {
return os.UnBindThird(model.OauthTypeOidc, userid)
}
func (os *OauthService) UnBindThird(thirdType string, userid uint) error {
return global.DB.Where("user_id = ? and third_type = ?", userid, thirdType).Delete(&model.UserThird{}).Error
}
// DeleteUserByUserId: When user is deleted, delete all third party bindings
func (os *OauthService) DeleteUserByUserId(userid uint) error {
return global.DB.Where("user_id = ?", userid).Delete(&model.UserThird{}).Error
}
// InfoById 根据id取用户信息
func (os *OauthService) InfoById(id uint) *model.Oauth {
u := &model.Oauth{}

View File

@@ -70,6 +70,7 @@ func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserTok
ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(),
}
global.DB.Create(ut)
llog.UserTokenId = ut.UserId
global.DB.Create(llog)
if llog.Uuid != "" {
AllService.PeerService.UuidBindUserId(llog.Uuid, u.Id)
@@ -148,8 +149,37 @@ func (us *UserService) Create(u *model.User) error {
func (us *UserService) Logout(u *model.User, token string) error {
return global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error
}
// Delete 删除用户和oauth信息
func (us *UserService) Delete(u *model.User) error {
return global.DB.Delete(u).Error
tx := global.DB.Begin()
// 删除用户
if err := tx.Delete(u).Error; err != nil {
tx.Rollback()
return err
}
// 删除关联的 OAuth 信息
if err := tx.Where("user_id = ?", u.Id).Delete(&model.UserThird{}).Error; err != nil {
tx.Rollback()
return err
}
// 删除关联的ab
if err := tx.Where("user_id = ?", u.Id).Delete(&model.AddressBook{}).Error; err != nil {
tx.Rollback()
return err
}
// 删除关联的abc
if err := tx.Where("user_id = ?", u.Id).Delete(&model.AddressBookCollection{}).Error; err != nil {
tx.Rollback()
return err
}
// 删除关联的abcr
if err := tx.Where("user_id = ?", u.Id).Delete(&model.AddressBookCollectionRule{}).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
// Update 更新
@@ -196,6 +226,11 @@ func (us *UserService) InfoByGoogleEmail(email string) *model.User {
return us.InfoByOauthId(model.OauthTypeGithub, email)
}
// InfoByOidcSub 根据oidc取用户信息
func (us *UserService) InfoByOidcSub(sub string) *model.User {
return us.InfoByOauthId(model.OauthTypeOidc, sub)
}
// InfoByOauthId 根据oauth取用户信息
func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
@@ -219,32 +254,42 @@ func (us *UserService) RegisterByGoogle(name string, email string) *model.User {
return us.RegisterByOauth(model.OauthTypeGoogle, name, email)
}
// RegisterByOidc 注册, use PreferredUsername as username, sub as openid
func (us *UserService) RegisterByOidc(PreferredUsername string, sub string) *model.User {
return us.RegisterByOauth(model.OauthTypeOidc, PreferredUsername, sub)
}
// RegisterByOauth 注册
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User {
global.Lock.Lock("registerByOauth")
defer global.Lock.UnLock("registerByOauth")
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
if ut.Id != 0 {
u := &model.User{}
global.DB.Where("id = ?", ut.UserId).First(u)
return u
}
tx := global.DB.Begin()
ut := &model.UserThird{
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)
tx.Create(u)
if u.Id == 0 {
tx.Rollback()
return u
}
ut.UserId = u.Id
global.DB.Create(ut)
tx.Create(ut)
tx.Commit()
return u
@@ -279,3 +324,60 @@ func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string) uint {
global.DB.Where("uuid = ?", uuid).Order("id desc").First(llog)
return llog.UserId
}
// IsPasswordEmptyById 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyById(id uint) bool {
u := &model.User{}
if global.DB.Where("id = ?", id).First(u).Error != nil {
return false
}
return u.Password == ""
}
// IsPasswordEmptyByUsername 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyByUsername(username string) bool {
u := &model.User{}
if global.DB.Where("username = ?", username).First(u).Error != nil {
return false
}
return u.Password == ""
}
// IsPasswordEmptyByUser 判断密码是否为空,主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyByUser(u *model.User) bool {
return us.IsPasswordEmptyById(u.Id)
}
func (us *UserService) Register(username string, password string) *model.User {
u := &model.User{
Username: username,
Password: us.EncryptPassword(password),
GroupId: 1,
}
global.DB.Create(u)
return u
}
func (us *UserService) TokenList(page uint, size uint, f func(tx *gorm.DB)) *model.UserTokenList {
res := &model.UserTokenList{}
res.Page = int64(page)
res.PageSize = int64(size)
tx := global.DB.Model(&model.UserToken{})
if f != nil {
f(tx)
}
tx.Count(&res.Total)
tx.Scopes(Paginate(page, size))
tx.Find(&res.UserTokens)
return res
}
func (us *UserService) TokenInfoById(id uint) *model.UserToken {
ut := &model.UserToken{}
global.DB.Where("id = ?", id).First(ut)
return ut
}
func (us *UserService) DeleteToken(l *model.UserToken) error {
return global.DB.Delete(l).Error
}

View File

@@ -91,3 +91,12 @@ func Values[K comparable, V any](m map[K]V) []V {
}
return values
}
func InArray(k string, arr []string) bool {
for _, v := range arr {
if k == v {
return true
}
}
return false
}