Compare commits

...

83 Commits

Author SHA1 Message Date
ljw
30d254eaef fix #55 2024-11-09 20:14:14 +08:00
ljw
bb8a936ade add build_test.yml 2024-11-08 16:09:50 +08:00
ljw
61044fd30b add build_test.yml 2024-11-08 16:05:43 +08:00
ljw
22a4546d0f add cmd 2024-11-08 15:54:49 +08:00
ljw
07450416ed fix #52 & add auto refresh token #53 2024-11-07 10:46:00 +08:00
0d6db0d2a1 Merge pull request #52 from IamTaoChen/fix/bug
fix: cannot delete user
2024-11-07 10:21:50 +08:00
Tao Chen
ab30b3407b add error information 2024-11-06 15:06:15 +08:00
Tao Chen
7a4c735803 fix: cannot delete user 2024-11-06 14:36:12 +08:00
ljw
654c764019 fix migrate 2024-11-05 21:07:39 +08:00
ljw
7101139250 fix migrate 2024-11-05 21:07:31 +08:00
ljw
793614841a fix migrate 2024-11-05 21:03:32 +08:00
ljw
94f2ac5b2a fix username length #48 2024-11-05 11:57:16 +08:00
ljw
d4d39eecaa up oauth re 2024-11-05 11:48:35 +08:00
ljw
8af01c859c Merge branch 'oauth_re' of https://github.com/IamTaoChen/rustdesk-api into IamTaoChen-oauth_re 2024-11-05 08:24:48 +08:00
Tao Chen
3af92d03e8 modify google ro re-use oidc 2024-11-04 21:30:58 +08:00
Tao Chen
f17d891302 fix: delete check 2024-11-03 22:23:24 +08:00
Tao Chen
51623436b0 fix: call us.IsAdmin(u) to check admin 2024-11-03 21:59:17 +08:00
Tao Chen
1b7c7eef8f fix google 2024-11-03 18:04:28 +08:00
Tao Chen
15e4c7e522 re-use responseLoginSuccess 2024-11-03 17:25:27 +08:00
Tao Chen
120ab132a9 fix: last admin shouldn't be deleted, disabled or demoted 2024-11-03 17:19:05 +08:00
Tao Chen
0b52e8faa8 fix: Github AvatarUrl to OauthUser 2024-11-03 16:49:28 +08:00
Tao Chen
60eaaf390a add err for RegisterByOauth 2024-11-03 16:49:03 +08:00
Tao Chen
9101badb1c fronted for docker-dev 2024-11-03 16:34:50 +08:00
Tao Chen
7082111b34 optimize /admin/login-options 2024-11-03 05:37:34 +08:00
Tao Chen
a156f2681e chroe 2024-11-03 05:34:19 +08:00
Tao Chen
7cb823c957 add Avatar to OauthUser 2024-11-03 05:33:59 +08:00
Tao Chen
817f612224 low case email 2024-11-03 05:25:10 +08:00
Tao Chen
1cbaf9d6bc const var for op name 2024-11-03 05:13:22 +08:00
Tao Chen
d353f9837f fix: email from github 2024-11-03 05:11:31 +08:00
Tao Chen
14beef72fc fix: Email of Register 2024-11-03 05:07:17 +08:00
Tao Chen
91a33fe7f6 fix: RegisterByOauth without Email 2024-11-03 04:35:39 +08:00
ljw
7ad7a0ff41 fix #45 2024-11-02 18:49:16 +08:00
Tao Chen
d646469f71 set user_id=0 at peers, when the user is deleted 2024-11-02 08:24:07 +08:00
Tao Chen
4f616ffff1 When login, peer doesn't exist, it should create 2024-11-02 08:19:44 +08:00
Tao Chen
64400fba61 delete the token when delete a peer 2024-11-02 08:02:03 +08:00
Tao Chen
fc15e8c63d add MyPeers for user 2024-11-02 07:35:26 +08:00
Tao Chen
cbba1e5317 add email for register 2024-11-02 05:43:55 +08:00
Tao Chen
75b997dcc4 add DeviceId to userToken 2024-11-02 05:42:47 +08:00
Tao Chen
853063c59b logout should unbind uuid and uid of peer 2024-11-02 05:07:41 +08:00
Tao Chen
5c65edb8fa fix bug ValidateOauthProvider location 2024-11-02 04:20:00 +08:00
Tao Chen
7707cc116f re-construct oauth 2024-11-02 04:01:28 +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
64 changed files with 3423 additions and 843 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

271
.github/workflows/build_test.yml vendored Normal file
View File

@@ -0,0 +1,271 @@
name: Build Test
on:
workflow_dispatch:
inputs:
BASE_IMAGE_NAMESPACE:
description: 'Base image namespace (Default: Your Github username)'
required: false
default: ''
DOCKERHUB_IMAGE_NAMESPACE:
description: 'Docker Hub image namespace (Default: Your Github username)'
required: false
default: ''
GHCR_IMAGE_NAMESPACE:
description: 'GitHub Container Registry image namespace (Default: Your Github username)'
required: false
default: ''
SKIP_DOCKER_HUB:
description: 'Set to true to skip pushing to Docker Hub (default: false)'
required: false
default: 'false'
SKIP_GHCR:
description: 'Set to true to skip pushing to GHCR (default: false)'
required: false
default: 'false'
WEBCLIENT_SOURCE_LOCATION:
description: 'Web Client API Repository'
required: true
default: 'https://github.com/lejianwen/rustdesk-api-web'
env:
LATEST_TAG: latest
WEBCLIENT_SOURCE_LOCATION: ${{ github.event.inputs.WEBCLIENT_SOURCE_LOCATION || 'https://github.com/lejianwen/rustdesk-api-web' }}
BASE_IMAGE_NAMESPACE: ${{ github.event.inputs.BASE_IMAGE_NAMESPACE || github.actor }}
DOCKERHUB_IMAGE_NAMESPACE: ${{ github.event.inputs.DOCKERHUB_IMAGE_NAMESPACE || github.actor }}
GHCR_IMAGE_NAMESPACE: ${{ github.event.inputs.GHCR_IMAGE_NAMESPACE || github.actor }}
SKIP_DOCKER_HUB: ${{ github.event.inputs.SKIP_DOCKER_HUB || 'false' }}
SKIP_GHCR: ${{ github.event.inputs.SKIP_GHCR || 'false' }}
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", file_ext: "tar.gz" }
- { platform: "arm64", goos: "linux", file_ext: "tar.gz" }
- { platform: "armv7l", goos: "linux", file_ext: "tar.gz" }
- { platform: "amd64", goos: "windows", file_ext: "zip" }
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: build rustdesk-api-web
run: |
git clone ${{ env.WEBCLIENT_SOURCE_LOCATION }}
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.job.goos }}-${{ matrix.job.platform }}
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.job.goos }}" = "windows" ]; then
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} 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.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
else
if [ "${{ matrix.job.platform }}" = "arm64" ]; then
wget https://musl.cc/aarch64-linux-musl-cross.tgz
tar -xf aarch64-linux-musl-cross.tgz
export PATH=$PATH:$PWD/aarch64-linux-musl-cross/bin
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=aarch64-linux-musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
elif [ "${{ matrix.job.platform }}" = "armv7l" ]; then
wget https://musl.cc/armv7l-linux-musleabihf-cross.tgz
tar -xf armv7l-linux-musleabihf-cross.tgz
export PATH=$PATH:$PWD/armv7l-linux-musleabihf-cross/bin
GOOS=${{ matrix.job.goos }} GOARCH=arm GOARM=7 CC=armv7l-linux-musleabihf-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
else
sudo apt-get install musl musl-dev musl-tools -y
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
fi
tar -czf ${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
tag_name: test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docker:
name: Push Docker Image
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", docker_platform: "linux/amd64" }
- { platform: "arm64", goos: "linux", docker_platform: "linux/arm64" }
- { platform: "armv7l", goos: "linux", docker_platform: "linux/arm/v7" }
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if SKIP_DOCKER_HUB is false
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Log in to GitHub Container Registry
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: vars
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "TAG=test" >> $GITHUB_ENV # Default to 'test' if not a tag
fi
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api
- name: Download binaries
uses: actions/download-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: ./
- name: Unzip binaries
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
file ${{ matrix.job.platform }}/apimain
- name: Build and push Docker image to Docker Hub ${{ matrix.job.platform }}
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
#
docker-manifest:
name: Push Docker Manifest
needs: docker
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: vars
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "TAG=test" >> $GITHUB_ENV # Default to 'test' if not a tag
fi
- name: Log in to Docker Hub
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if Docker Hub push is enabled
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Log in to GitHub Container Registry
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-armv7l,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
push: true
- name: Create and push manifest GHCR (:version)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-armv7l,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
push: true
amend: true

View File

@@ -1,95 +0,0 @@
name: Build and Release
on:
workflow_dispatch:
# 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 }}

94
Dockerfile.dev Normal file
View File

@@ -0,0 +1,94 @@
# 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
ARG FREONTEND_GIT_REPO=https://github.com/lejianwen/rustdesk-api-web.git
ARG FRONTEND_GIT_BRANCH=master
# Clone the frontend repository
RUN git clone -b $FRONTEND_GIT_BRANCH $FREONTEND_GIT_REPO .
# 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 - i18n
- Web Admin - Web Admin
- 用户管理 - 用户管理
@@ -92,7 +92,7 @@
#### 登录 #### 登录
- 添加了`github``google`授权登录需要在后台配置好就可以用了具体可看后台OAuth配置 - 添加了`github`, `google` 以及`OIDC`授权登录需要在后台配置好就可以用了具体可看后台OAuth配置
- 添加了web后台授权登录,点击后直接登录后台就自动登录客户端了 - 添加了web后台授权登录,点击后直接登录后台就自动登录客户端了
![pc_login](docs/pc_login.png) ![pc_login](docs/pc_login.png)
@@ -124,8 +124,10 @@
4. 可以直接打开webclient方便使用也可以分享给游客游客可以直接通过webclient远程到设备 4. 可以直接打开webclient方便使用也可以分享给游客游客可以直接通过webclient远程到设备
![web_webclient](docs/admin_webclient.png) ![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) ![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` - `github oauth app`在`Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers) 中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
- `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback` - `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback`
@@ -157,6 +159,7 @@
lang: "en" lang: "en"
app: app:
web-client: 1 # 1:启用 0:禁用 web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
gin: gin:
api-addr: "0.0.0.0:21114" api-addr: "0.0.0.0:21114"
mode: "release" mode: "release"
@@ -194,6 +197,7 @@ proxy:
| TZ | 时区 | Asia/Shanghai | | TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` | | RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 | | RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| -----GIN配置----- | ---------- | ---------- | | -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 | | RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----------GORM配置---------------- | ------------------------------------ | --------------------------- | | -----------GORM配置---------------- | ------------------------------------ | --------------------------- |

View File

@@ -18,7 +18,7 @@ desktop software that provides self-hosted solutions.
- Login - Login
- Address Book - Address Book
- Groups - 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 - i18n
- Web Admin - Web Admin
- User Management - User Management
@@ -93,7 +93,7 @@ Basic implementation of the PC client's primary interfaces.Supports the Personal
#### Login #### 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. configuration section for details.
- Added authorization login for the web admin panel. - 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. 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) ![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. the admin panel.
![web_admin_oauth](docs/en_img/web_admin_oauth.png) ![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` - Create a `GitHub OAuth App`
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers). at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oauth/callback`, - Set the `Authorization callback URL` to `http://<your server[:port]>/api/oauth/callback`,
@@ -163,6 +165,7 @@ installation are `admin` `admin`, please change the password immediately.
lang: "en" lang: "en"
app: app:
web-client: 1 # web client route 1:open 0:close web-client: 1 # web client route 1:open 0:close
register: false #register enable
gin: gin:
api-addr: "0.0.0.0:21114" api-addr: "0.0.0.0:21114"
mode: "release" mode: "release"
@@ -200,6 +203,7 @@ The prefix for variable names is `RUSTDESK_API`. If environment variables exist,
| TZ | timezone | Asia/Shanghai | | TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` | | 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_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 ----- | --------------------------------------- | ----------------------------- | | ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 | | RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- | | ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |

38
build.sh Normal file → Executable file
View File

@@ -1,16 +1,46 @@
#!/bin/sh #!/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 GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct go env -w GOPROXY=https://goproxy.cn,direct
go env -w CGO_ENABLED=1 go env -w CGO_ENABLED=1
go env -w GOOS=linux go env -w GOOS=linux
go env -w GOARCH=amd64 go env -w GOARCH=${GOARCH}
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
# 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 go build -o release/apimain cmd/apimain.go
# Copy resource files to the release directory
cp -ar resources release/ cp -ar resources release/
cp -ar docs release/ cp -ar docs release/
cp -ar conf release/ cp -ar conf release/
# Create necessary directory structures
mkdir -p release/data mkdir -p release/data
mkdir -p release/runtime mkdir -p release/runtime
echo "Build and setup completed successfully."

View File

@@ -14,6 +14,9 @@ import (
"fmt" "fmt"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/spf13/cobra"
"os"
"strconv"
) )
// @title 管理系统API // @title 管理系统API
@@ -26,9 +29,79 @@ import (
// @securitydefinitions.apikey BearerAuth // @securitydefinitions.apikey BearerAuth
// @in header // @in header
// @name Authorization // @name Authorization
var rootCmd = &cobra.Command{
Use: "apimain",
Short: "RUSTDESK API SERVER",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
InitGlobal()
},
Run: func(cmd *cobra.Command, args []string) {
//gin
http.ApiInit()
},
}
var resetPwdCmd = &cobra.Command{
Use: "reset-admin-pwd [pwd]",
Example: "reset-admin-pwd 123456",
Short: "Reset Admin Password",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
pwd := args[0]
admin := service.AllService.UserService.InfoById(1)
err := service.AllService.UserService.UpdatePassword(admin, pwd)
if err != nil {
fmt.Printf("reset password fail! %v \n", err)
return
}
fmt.Printf("reset password success! \n")
},
}
var resetUserPwdCmd = &cobra.Command{
Use: "reset-pwd [userId] [pwd]",
Example: "reset-pwd 2 123456",
Short: "Reset User Password",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
userId := args[0]
pwd := args[1]
uid, err := strconv.Atoi(userId)
if err != nil {
fmt.Printf("userId must be int! \n")
return
}
if uid <= 0 {
fmt.Printf("userId must be greater than 0! \n")
return
}
u := service.AllService.UserService.InfoById(uint(uid))
err = service.AllService.UserService.UpdatePassword(u, pwd)
if err != nil {
fmt.Printf("reset password fail! %v \n", err)
return
}
fmt.Printf("reset password success! \n")
},
}
func init() {
rootCmd.PersistentFlags().StringVarP(&global.ConfigPath, "config", "c", "./conf/config.yaml", "choose config file")
rootCmd.AddCommand(resetPwdCmd, resetUserPwdCmd)
}
func main() { func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func InitGlobal() {
//配置解析 //配置解析
global.Viper = config.Init(&global.Config) global.Viper = config.Init(&global.Config, global.ConfigPath)
//从配置文件中加载密钥
config.LoadKeyFile(&global.Config.Rustdesk)
//日志 //日志
global.Logger = logger.New(&logger.Config{ global.Logger = logger.New(&logger.Config{
@@ -94,14 +167,9 @@ func main() {
//locker //locker
global.Lock = lock.NewLocal() global.Lock = lock.NewLocal()
//gin
http.ApiInit()
} }
func DatabaseAutoUpdate() { func DatabaseAutoUpdate() {
version := 240 version := 246
db := global.DB db := global.DB
@@ -146,6 +214,24 @@ func DatabaseAutoUpdate() {
if v.Version < uint(version) { if v.Version < uint(version) {
Migrate(uint(version)) Migrate(uint(version))
} }
// 245迁移
if v.Version < 245 {
//oauths 表的 oauth_type 字段设置为 op同样的值
db.Exec("update oauths set oauth_type = op")
db.Exec("update oauths set issuer = 'https://accounts.google.com' where op = 'google'")
db.Exec("update user_thirds set oauth_type = third_type, op = third_type")
//通过email迁移旧的google授权
uts := make([]model.UserThird, 0)
db.Where("oauth_type = ?", "google").Find(&uts)
for _, ut := range uts {
if ut.UserId > 0 {
db.Model(&model.User{}).Where("id = ?", ut.UserId).Update("email", ut.OpenId)
}
}
}
if v.Version < 246 {
db.Exec("update oauths set issuer = 'https://accounts.google.com' where op = 'google' and issuer is null")
}
} }
} }

View File

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

View File

@@ -1,7 +1,6 @@
package config package config
import ( import (
"flag"
"fmt" "fmt"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/spf13/viper" "github.com/spf13/viper"
@@ -15,7 +14,8 @@ const (
) )
type App struct { type App struct {
WebClient int `mapstructure:"web-client"` WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
} }
type Config struct { type Config struct {
@@ -34,18 +34,15 @@ type Config struct {
} }
// Init 初始化配置 // Init 初始化配置
func Init(rowVal interface{}) *viper.Viper { func Init(rowVal interface{}, path string) *viper.Viper {
var config string if path == "" {
flag.StringVar(&config, "c", "", "choose config file.") path = DefaultConfig
flag.Parse()
if config == "" { // 优先级: 命令行 > 默认值
config = DefaultConfig
} }
v := viper.New() v := viper.GetViper()
v.AutomaticEnv() v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
v.SetEnvPrefix("RUSTDESK_API") v.SetEnvPrefix("RUSTDESK_API")
v.SetConfigFile(config) v.SetConfigFile(path)
v.SetConfigType("yaml") v.SetConfigType("yaml")
err := v.ReadInConfig() err := v.ReadInConfig()
if err != nil { if err != nil {
@@ -62,6 +59,7 @@ func Init(rowVal interface{}) *viper.Viper {
if err := v.Unmarshal(rowVal); err != nil { if err := v.Unmarshal(rowVal); err != nil {
fmt.Println(err) fmt.Println(err)
} }
return v return v
} }

View File

@@ -11,3 +11,10 @@ type GoogleOauth struct {
ClientSecret string `mapstructure:"client-secret"` ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"` 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 package config
import (
"os"
)
type Rustdesk struct { type Rustdesk struct {
IdServer string `mapstructure:"id-server"` IdServer string `mapstructure:"id-server"`
RelayServer string `mapstructure:"relay-server"` RelayServer string `mapstructure:"relay-server"`
ApiServer string `mapstructure:"api-server"` ApiServer string `mapstructure:"api-server"`
Key string `mapstructure:"key"` Key string `mapstructure:"key"`
KeyFile string `mapstructure:"key-file"`
Personal int `mapstructure:"personal"` 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
}
}

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

@@ -0,0 +1,24 @@
services:
rustdesk-api:
build:
context: .
dockerfile: Dockerfile.dev
args:
COUNTRY: CN
FREONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git
FRONTEND_GIT_BRANCH: master
# 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 - TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 - 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 - RUSTDESK_API_RUSTDESK_KEY=123456789
ports: ports:
- 21114:21114 - 21114:21114
volumes: volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份 - ./data/rustdesk/api:/app/data # database
# - ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源
restart: unless-stopped 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": [] "token": []
} }
], ],
"description": "创建地址簿集合", "description": "创建地址簿名称",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "创建地址簿集合", "tags": [
"地址簿名称"
],
"summary": "创建地址簿名称",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合信息", "description": "地址簿名称信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -407,17 +410,20 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "地址簿集合删除", "description": "地址簿名称删除",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合删除", "tags": [
"地址簿名称"
],
"summary": "地址簿名称删除",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合信息", "description": "地址簿名称信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -449,14 +455,17 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "地址簿集合详情", "description": "地址簿名称详情",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合详情", "tags": [
"地址簿名称"
],
"summary": "地址簿名称详情",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@@ -501,14 +510,17 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "地址簿集合列表", "description": "地址簿名称列表",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合列表", "tags": [
"地址簿名称"
],
"summary": "地址簿名称列表",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@@ -570,17 +582,20 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "地址簿集合编辑", "description": "地址簿名称编辑",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合编辑", "tags": [
"地址簿名称"
],
"summary": "地址簿名称编辑",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合信息", "description": "地址簿名称信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -624,17 +639,20 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "创建地址簿集合规则", "description": "创建地址簿规则",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "创建地址簿集合规则", "tags": [
"地址簿规则"
],
"summary": "创建地址簿规则",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合规则信息", "description": "地址簿规则信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -678,17 +696,20 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "地址簿集合规则删除", "description": "地址簿规则删除",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合规则删除", "tags": [
"地址簿规则"
],
"summary": "地址簿规则删除",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合规则信息", "description": "地址簿规则信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -720,14 +741,17 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "地址簿集合规则详情", "description": "地址簿规则详情",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合规则详情", "tags": [
"地址簿规则"
],
"summary": "地址簿规则详情",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@@ -772,14 +796,17 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "地址簿集合规则列表", "description": "地址簿规则列表",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合规则列表", "tags": [
"地址簿规则"
],
"summary": "地址簿规则列表",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@@ -847,17 +874,20 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "地址簿集合规则编辑", "description": "地址簿规则编辑",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合规则编辑", "tags": [
"地址簿规则"
],
"summary": "地址簿规则编辑",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合规则信息", "description": "地址簿规则信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "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": { "post": {
"security": [ "security": [
{ {
@@ -1498,7 +1560,7 @@ const docTemplateadmin = `{
} }
} }
}, },
"/admin/loginLog/detail/{id}": { "/admin/login_log/detail/{id}": {
"get": { "get": {
"security": [ "security": [
{ {
@@ -1553,7 +1615,7 @@ const docTemplateadmin = `{
} }
} }
}, },
"/admin/loginLog/list": { "/admin/login_log/list": {
"get": { "get": {
"security": [ "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": { "/admin/peer/create": {
"post": { "post": {
"security": [ "security": [
@@ -1986,7 +2150,7 @@ const docTemplateadmin = `{
"token": [] "token": []
} }
], ],
"description": "批量设备删除", "description": "设备删除",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1996,15 +2160,15 @@ const docTemplateadmin = `{
"tags": [ "tags": [
"设备" "设备"
], ],
"summary": "批量设备删除", "summary": "设备删除",
"parameters": [ "parameters": [
{ {
"description": "设备id", "description": "设备信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm" "$ref": "#/definitions/admin.PeerForm"
} }
} }
], ],
@@ -2127,6 +2291,12 @@ const docTemplateadmin = `{
"description": "主机名", "description": "主机名",
"name": "hostname", "name": "hostname",
"in": "query" "in": "query"
},
{
"type": "string",
"description": "uuids 用逗号分隔",
"name": "uuids",
"in": "query"
} }
], ],
"responses": { "responses": {
@@ -2897,6 +3067,90 @@ const docTemplateadmin = `{
} }
} }
}, },
"/admin/user/myPeer": {
"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": "时间",
"name": "time_ago",
"in": "query"
},
{
"type": "string",
"description": "ID",
"name": "id",
"in": "query"
},
{
"type": "string",
"description": "主机名",
"name": "hostname",
"in": "query"
},
{
"type": "string",
"description": "uuids 用逗号分隔",
"name": "uuids",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.PeerList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/user/update": { "/admin/user/update": {
"post": { "post": {
"security": [ "security": [
@@ -2998,6 +3252,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": { "definitions": {
@@ -3126,6 +3491,12 @@ const docTemplateadmin = `{
"admin.LoginPayload": { "admin.LoginPayload": {
"type": "object", "type": "object",
"properties": { "properties": {
"avatar": {
"type": "string"
},
"email": {
"type": "string"
},
"nickname": { "nickname": {
"type": "string" "type": "string"
}, },
@@ -3148,7 +3519,7 @@ const docTemplateadmin = `{
"required": [ "required": [
"client_id", "client_id",
"client_secret", "client_secret",
"op", "oauth_type",
"redirect_url" "redirect_url"
], ],
"properties": { "properties": {
@@ -3164,11 +3535,20 @@ const docTemplateadmin = `{
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"issuer": {
"type": "string"
},
"oauth_type": {
"type": "string"
},
"op": { "op": {
"type": "string" "type": "string"
}, },
"redirect_url": { "redirect_url": {
"type": "string" "type": "string"
},
"scopes": {
"type": "string"
} }
} }
}, },
@@ -3280,6 +3660,10 @@ const docTemplateadmin = `{
"avatar": { "avatar": {
"type": "string" "type": "string"
}, },
"email": {
"description": "validate:\"required,email\" email不强制",
"type": "string"
},
"group_id": { "group_id": {
"type": "integer" "type": "integer"
}, },
@@ -3304,18 +3688,18 @@ const docTemplateadmin = `{
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 10, "maxLength": 10,
"minLength": 4 "minLength": 2
} }
} }
}, },
"admin.UserOauthItem": { "admin.UserOauthItem": {
"type": "object", "type": "object",
"properties": { "properties": {
"op": {
"type": "string"
},
"status": { "status": {
"type": "integer" "type": "integer"
},
"third_type": {
"type": "string"
} }
} }
}, },
@@ -3686,6 +4070,9 @@ const docTemplateadmin = `{
"created_at": { "created_at": {
"type": "string" "type": "string"
}, },
"device_id": {
"type": "string"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@@ -3706,6 +4093,9 @@ const docTemplateadmin = `{
"user_id": { "user_id": {
"type": "integer" "type": "integer"
}, },
"user_token_id": {
"type": "integer"
},
"uuid": { "uuid": {
"type": "string" "type": "string"
} }
@@ -3749,12 +4139,21 @@ const docTemplateadmin = `{
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"issuer": {
"type": "string"
},
"oauth_type": {
"type": "string"
},
"op": { "op": {
"type": "string" "type": "string"
}, },
"redirect_url": { "redirect_url": {
"type": "string" "type": "string"
}, },
"scopes": {
"type": "string"
},
"updated_at": { "updated_at": {
"type": "string" "type": "string"
} }
@@ -3795,6 +4194,9 @@ const docTemplateadmin = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_online_ip": {
"type": "string"
},
"last_online_time": { "last_online_time": {
"type": "integer" "type": "integer"
}, },
@@ -3921,6 +4323,9 @@ const docTemplateadmin = `{
"created_at": { "created_at": {
"type": "string" "type": "string"
}, },
"email": {
"type": "string"
},
"group_id": { "group_id": {
"type": "integer" "type": "integer"
}, },
@@ -3964,6 +4369,63 @@ const docTemplateadmin = `{
} }
} }
}, },
"model.UserToken": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"device_id": {
"type": "string"
},
"device_uuid": {
"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": { "response.Response": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -346,17 +346,20 @@
"token": [] "token": []
} }
], ],
"description": "创建地址簿集合", "description": "创建地址簿名称",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "创建地址簿集合", "tags": [
"地址簿名称"
],
"summary": "创建地址簿名称",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合信息", "description": "地址簿名称信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -400,17 +403,20 @@
"token": [] "token": []
} }
], ],
"description": "地址簿集合删除", "description": "地址簿名称删除",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合删除", "tags": [
"地址簿名称"
],
"summary": "地址簿名称删除",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合信息", "description": "地址簿名称信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -442,14 +448,17 @@
"token": [] "token": []
} }
], ],
"description": "地址簿集合详情", "description": "地址簿名称详情",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合详情", "tags": [
"地址簿名称"
],
"summary": "地址簿名称详情",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@@ -494,14 +503,17 @@
"token": [] "token": []
} }
], ],
"description": "地址簿集合列表", "description": "地址簿名称列表",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合列表", "tags": [
"地址簿名称"
],
"summary": "地址簿名称列表",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@@ -563,17 +575,20 @@
"token": [] "token": []
} }
], ],
"description": "地址簿集合编辑", "description": "地址簿名称编辑",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合编辑", "tags": [
"地址簿名称"
],
"summary": "地址簿名称编辑",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合信息", "description": "地址簿名称信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -617,17 +632,20 @@
"token": [] "token": []
} }
], ],
"description": "创建地址簿集合规则", "description": "创建地址簿规则",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "创建地址簿集合规则", "tags": [
"地址簿规则"
],
"summary": "创建地址簿规则",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合规则信息", "description": "地址簿规则信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -671,17 +689,20 @@
"token": [] "token": []
} }
], ],
"description": "地址簿集合规则删除", "description": "地址簿规则删除",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合规则删除", "tags": [
"地址簿规则"
],
"summary": "地址簿规则删除",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合规则信息", "description": "地址簿规则信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -713,14 +734,17 @@
"token": [] "token": []
} }
], ],
"description": "地址簿集合规则详情", "description": "地址簿规则详情",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合规则详情", "tags": [
"地址簿规则"
],
"summary": "地址簿规则详情",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@@ -765,14 +789,17 @@
"token": [] "token": []
} }
], ],
"description": "地址簿集合规则列表", "description": "地址簿规则列表",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合规则列表", "tags": [
"地址簿规则"
],
"summary": "地址簿规则列表",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@@ -840,17 +867,20 @@
"token": [] "token": []
} }
], ],
"description": "地址簿集合规则编辑", "description": "地址簿规则编辑",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "地址簿集合规则编辑", "tags": [
"地址簿规则"
],
"summary": "地址簿规则编辑",
"parameters": [ "parameters": [
{ {
"description": "地址簿集合规则信息", "description": "地址簿规则信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "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": { "post": {
"security": [ "security": [
{ {
@@ -1491,7 +1553,7 @@
} }
} }
}, },
"/admin/loginLog/detail/{id}": { "/admin/login_log/detail/{id}": {
"get": { "get": {
"security": [ "security": [
{ {
@@ -1546,7 +1608,7 @@
} }
} }
}, },
"/admin/loginLog/list": { "/admin/login_log/list": {
"get": { "get": {
"security": [ "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": { "/admin/peer/create": {
"post": { "post": {
"security": [ "security": [
@@ -1979,7 +2143,7 @@
"token": [] "token": []
} }
], ],
"description": "批量设备删除", "description": "设备删除",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -1989,15 +2153,15 @@
"tags": [ "tags": [
"设备" "设备"
], ],
"summary": "批量设备删除", "summary": "设备删除",
"parameters": [ "parameters": [
{ {
"description": "设备id", "description": "设备信息",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm" "$ref": "#/definitions/admin.PeerForm"
} }
} }
], ],
@@ -2120,6 +2284,12 @@
"description": "主机名", "description": "主机名",
"name": "hostname", "name": "hostname",
"in": "query" "in": "query"
},
{
"type": "string",
"description": "uuids 用逗号分隔",
"name": "uuids",
"in": "query"
} }
], ],
"responses": { "responses": {
@@ -2890,6 +3060,90 @@
} }
} }
}, },
"/admin/user/myPeer": {
"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": "时间",
"name": "time_ago",
"in": "query"
},
{
"type": "string",
"description": "ID",
"name": "id",
"in": "query"
},
{
"type": "string",
"description": "主机名",
"name": "hostname",
"in": "query"
},
{
"type": "string",
"description": "uuids 用逗号分隔",
"name": "uuids",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.PeerList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/user/update": { "/admin/user/update": {
"post": { "post": {
"security": [ "security": [
@@ -2991,6 +3245,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": { "definitions": {
@@ -3119,6 +3484,12 @@
"admin.LoginPayload": { "admin.LoginPayload": {
"type": "object", "type": "object",
"properties": { "properties": {
"avatar": {
"type": "string"
},
"email": {
"type": "string"
},
"nickname": { "nickname": {
"type": "string" "type": "string"
}, },
@@ -3141,7 +3512,7 @@
"required": [ "required": [
"client_id", "client_id",
"client_secret", "client_secret",
"op", "oauth_type",
"redirect_url" "redirect_url"
], ],
"properties": { "properties": {
@@ -3157,11 +3528,20 @@
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"issuer": {
"type": "string"
},
"oauth_type": {
"type": "string"
},
"op": { "op": {
"type": "string" "type": "string"
}, },
"redirect_url": { "redirect_url": {
"type": "string" "type": "string"
},
"scopes": {
"type": "string"
} }
} }
}, },
@@ -3273,6 +3653,10 @@
"avatar": { "avatar": {
"type": "string" "type": "string"
}, },
"email": {
"description": "validate:\"required,email\" email不强制",
"type": "string"
},
"group_id": { "group_id": {
"type": "integer" "type": "integer"
}, },
@@ -3297,18 +3681,18 @@
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 10, "maxLength": 10,
"minLength": 4 "minLength": 2
} }
} }
}, },
"admin.UserOauthItem": { "admin.UserOauthItem": {
"type": "object", "type": "object",
"properties": { "properties": {
"op": {
"type": "string"
},
"status": { "status": {
"type": "integer" "type": "integer"
},
"third_type": {
"type": "string"
} }
} }
}, },
@@ -3679,6 +4063,9 @@
"created_at": { "created_at": {
"type": "string" "type": "string"
}, },
"device_id": {
"type": "string"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@@ -3699,6 +4086,9 @@
"user_id": { "user_id": {
"type": "integer" "type": "integer"
}, },
"user_token_id": {
"type": "integer"
},
"uuid": { "uuid": {
"type": "string" "type": "string"
} }
@@ -3742,12 +4132,21 @@
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"issuer": {
"type": "string"
},
"oauth_type": {
"type": "string"
},
"op": { "op": {
"type": "string" "type": "string"
}, },
"redirect_url": { "redirect_url": {
"type": "string" "type": "string"
}, },
"scopes": {
"type": "string"
},
"updated_at": { "updated_at": {
"type": "string" "type": "string"
} }
@@ -3788,6 +4187,9 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_online_ip": {
"type": "string"
},
"last_online_time": { "last_online_time": {
"type": "integer" "type": "integer"
}, },
@@ -3914,6 +4316,9 @@
"created_at": { "created_at": {
"type": "string" "type": "string"
}, },
"email": {
"type": "string"
},
"group_id": { "group_id": {
"type": "integer" "type": "integer"
}, },
@@ -3957,6 +4362,63 @@
} }
} }
}, },
"model.UserToken": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"device_id": {
"type": "string"
},
"device_uuid": {
"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": { "response.Response": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -84,6 +84,10 @@ definitions:
type: object type: object
admin.LoginPayload: admin.LoginPayload:
properties: properties:
avatar:
type: string
email:
type: string
nickname: nickname:
type: string type: string
route_names: route_names:
@@ -105,14 +109,20 @@ definitions:
type: string type: string
id: id:
type: integer type: integer
issuer:
type: string
oauth_type:
type: string
op: op:
type: string type: string
redirect_url: redirect_url:
type: string type: string
scopes:
type: string
required: required:
- client_id - client_id
- client_secret - client_secret
- op - oauth_type
- redirect_url - redirect_url
type: object type: object
admin.PeerBatchDeleteForm: admin.PeerBatchDeleteForm:
@@ -184,6 +194,9 @@ definitions:
properties: properties:
avatar: avatar:
type: string type: string
email:
description: validate:"required,email" email不强制
type: string
group_id: group_id:
type: integer type: integer
id: id:
@@ -199,7 +212,7 @@ definitions:
minimum: 0 minimum: 0
username: username:
maxLength: 10 maxLength: 10
minLength: 4 minLength: 2
type: string type: string
required: required:
- group_id - group_id
@@ -208,10 +221,10 @@ definitions:
type: object type: object
admin.UserOauthItem: admin.UserOauthItem:
properties: properties:
op:
type: string
status: status:
type: integer type: integer
third_type:
type: string
type: object type: object
admin.UserPasswordForm: admin.UserPasswordForm:
properties: properties:
@@ -458,6 +471,8 @@ definitions:
type: string type: string
created_at: created_at:
type: string type: string
device_id:
type: string
id: id:
type: integer type: integer
ip: ip:
@@ -472,6 +487,8 @@ definitions:
type: string type: string
user_id: user_id:
type: integer type: integer
user_token_id:
type: integer
uuid: uuid:
type: string type: string
type: object type: object
@@ -500,10 +517,16 @@ definitions:
type: string type: string
id: id:
type: integer type: integer
issuer:
type: string
oauth_type:
type: string
op: op:
type: string type: string
redirect_url: redirect_url:
type: string type: string
scopes:
type: string
updated_at: updated_at:
type: string type: string
type: object type: object
@@ -530,6 +553,8 @@ definitions:
type: string type: string
id: id:
type: string type: string
last_online_ip:
type: string
last_online_time: last_online_time:
type: integer type: integer
memory: memory:
@@ -615,6 +640,8 @@ definitions:
type: string type: string
created_at: created_at:
type: string type: string
email:
type: string
group_id: group_id:
type: integer type: integer
id: id:
@@ -643,6 +670,43 @@ definitions:
total: total:
type: integer type: integer
type: object type: object
model.UserToken:
properties:
created_at:
type: string
device_id:
type: string
device_uuid:
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: response.Response:
properties: properties:
code: code:
@@ -858,9 +922,9 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: 创建地址簿集合 description: 创建地址簿名称
parameters: parameters:
- description: 地址簿集合信息 - description: 地址簿名称信息
in: body in: body
name: body name: body
required: true required: true
@@ -884,14 +948,16 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 创建地址簿集合 summary: 创建地址簿名称
tags:
- 地址簿名称
/admin/address_book_collection/delete: /admin/address_book_collection/delete:
post: post:
consumes: consumes:
- application/json - application/json
description: 地址簿集合删除 description: 地址簿名称删除
parameters: parameters:
- description: 地址簿集合信息 - description: 地址簿名称信息
in: body in: body
name: body name: body
required: true required: true
@@ -910,12 +976,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 地址簿集合删除 summary: 地址簿名称删除
tags:
- 地址簿名称
/admin/address_book_collection/detail/{id}: /admin/address_book_collection/detail/{id}:
get: get:
consumes: consumes:
- application/json - application/json
description: 地址簿集合详情 description: 地址簿名称详情
parameters: parameters:
- description: ID - description: ID
in: path in: path
@@ -940,12 +1008,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 地址簿集合详情 summary: 地址簿名称详情
tags:
- 地址簿名称
/admin/address_book_collection/list: /admin/address_book_collection/list:
get: get:
consumes: consumes:
- application/json - application/json
description: 地址簿集合列表 description: 地址簿名称列表
parameters: parameters:
- description: 页码 - description: 页码
in: query in: query
@@ -981,14 +1051,16 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 地址簿集合列表 summary: 地址簿名称列表
tags:
- 地址簿名称
/admin/address_book_collection/update: /admin/address_book_collection/update:
post: post:
consumes: consumes:
- application/json - application/json
description: 地址簿集合编辑 description: 地址簿名称编辑
parameters: parameters:
- description: 地址簿集合信息 - description: 地址簿名称信息
in: body in: body
name: body name: body
required: true required: true
@@ -1012,14 +1084,16 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 地址簿集合编辑 summary: 地址簿名称编辑
tags:
- 地址簿名称
/admin/address_book_collection_rule/create: /admin/address_book_collection_rule/create:
post: post:
consumes: consumes:
- application/json - application/json
description: 创建地址簿集合规则 description: 创建地址簿规则
parameters: parameters:
- description: 地址簿集合规则信息 - description: 地址簿规则信息
in: body in: body
name: body name: body
required: true required: true
@@ -1043,14 +1117,16 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 创建地址簿集合规则 summary: 创建地址簿规则
tags:
- 地址簿规则
/admin/address_book_collection_rule/delete: /admin/address_book_collection_rule/delete:
post: post:
consumes: consumes:
- application/json - application/json
description: 地址簿集合规则删除 description: 地址簿规则删除
parameters: parameters:
- description: 地址簿集合规则信息 - description: 地址簿规则信息
in: body in: body
name: body name: body
required: true required: true
@@ -1069,12 +1145,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 地址簿集合规则删除 summary: 地址簿规则删除
tags:
- 地址簿规则
/admin/address_book_collection_rule/detail/{id}: /admin/address_book_collection_rule/detail/{id}:
get: get:
consumes: consumes:
- application/json - application/json
description: 地址簿集合规则详情 description: 地址簿规则详情
parameters: parameters:
- description: ID - description: ID
in: path in: path
@@ -1099,12 +1177,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 地址簿集合规则详情 summary: 地址簿规则详情
tags:
- 地址簿规则
/admin/address_book_collection_rule/list: /admin/address_book_collection_rule/list:
get: get:
consumes: consumes:
- application/json - application/json
description: 地址簿集合规则列表 description: 地址簿规则列表
parameters: parameters:
- description: 页码 - description: 页码
in: query in: query
@@ -1144,14 +1224,16 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 地址簿集合规则列表 summary: 地址簿规则列表
tags:
- 地址簿规则
/admin/address_book_collection_rule/update: /admin/address_book_collection_rule/update:
post: post:
consumes: consumes:
- application/json - application/json
description: 地址簿集合规则编辑 description: 地址簿规则编辑
parameters: parameters:
- description: 地址簿集合规则信息 - description: 地址簿规则信息
in: body in: body
name: body name: body
required: true required: true
@@ -1175,7 +1257,9 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 地址簿集合规则编辑 summary: 地址簿规则编辑
tags:
- 地址簿规则
/admin/app-config: /admin/app-config:
get: get:
consumes: consumes:
@@ -1510,7 +1594,28 @@ paths:
summary: 登录 summary: 登录
tags: 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: post:
consumes: consumes:
- application/json - application/json
@@ -1538,7 +1643,7 @@ paths:
summary: 登录日志删除 summary: 登录日志删除
tags: tags:
- 登录日志 - 登录日志
/admin/loginLog/detail/{id}: /admin/login_log/detail/{id}:
get: get:
consumes: consumes:
- application/json - application/json
@@ -1570,7 +1675,7 @@ paths:
summary: 登录日志详情 summary: 登录日志详情
tags: tags:
- 登录日志 - 登录日志
/admin/loginLog/list: /admin/login_log/list:
get: get:
consumes: consumes:
- application/json - application/json
@@ -1789,6 +1894,69 @@ paths:
summary: Oauth编辑 summary: Oauth编辑
tags: tags:
- Oauth - 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: /admin/peer/create:
post: post:
consumes: consumes:
@@ -1826,14 +1994,14 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: 批量设备删除 description: 设备删除
parameters: parameters:
- description: 设备id - description: 设备信息
in: body in: body
name: body name: body
required: true required: true
schema: schema:
$ref: '#/definitions/admin.PeerBatchDeleteForm' $ref: '#/definitions/admin.PeerForm'
produces: produces:
- application/json - application/json
responses: responses:
@@ -1847,7 +2015,7 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: 批量设备删除 summary: 设备删除
tags: tags:
- 设备 - 设备
/admin/peer/detail/{id}: /admin/peer/detail/{id}:
@@ -1908,6 +2076,10 @@ paths:
in: query in: query
name: hostname name: hostname
type: string type: string
- description: uuids 用逗号分隔
in: query
name: uuids
type: string
produces: produces:
- application/json - application/json
responses: responses:
@@ -2366,6 +2538,57 @@ paths:
summary: 我的授权 summary: 我的授权
tags: tags:
- 用户 - 用户
/admin/user/myPeer:
get:
consumes:
- application/json
description: 设备列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
- description: 时间
in: query
name: time_ago
type: integer
- description: ID
in: query
name: id
type: string
- description: 主机名
in: query
name: hostname
type: string
- description: uuids 用逗号分隔
in: query
name: uuids
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.PeerList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 设备列表
tags:
- 设备
/admin/user/update: /admin/user/update:
post: post:
consumes: consumes:
@@ -2427,6 +2650,73 @@ paths:
summary: 修改密码 summary: 修改密码
tags: 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: securityDefinitions:
BearerAuth: BearerAuth:
in: header in: header

View File

@@ -834,7 +834,7 @@ const docTemplateapi = `{
} }
}, },
"/login-options": { "/login-options": {
"post": { "get": {
"description": "登录选项", "description": "登录选项",
"consumes": [ "consumes": [
"application/json" "application/json"
@@ -1365,7 +1365,7 @@ const docTemplateapi = `{
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 10, "maxLength": 10,
"minLength": 4 "minLength": 2
}, },
"uuid": { "uuid": {
"type": "string" "type": "string"

View File

@@ -827,7 +827,7 @@
} }
}, },
"/login-options": { "/login-options": {
"post": { "get": {
"description": "登录选项", "description": "登录选项",
"consumes": [ "consumes": [
"application/json" "application/json"
@@ -1358,7 +1358,7 @@
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 10, "maxLength": 10,
"minLength": 4 "minLength": 2
}, },
"uuid": { "uuid": {
"type": "string" "type": "string"

View File

@@ -69,7 +69,7 @@ definitions:
type: string type: string
username: username:
maxLength: 10 maxLength: 10
minLength: 4 minLength: 2
type: string type: string
uuid: uuid:
type: string type: string
@@ -715,7 +715,7 @@ paths:
tags: tags:
- 登录 - 登录
/login-options: /login-options:
post: get:
consumes: consumes:
- application/json - application/json
description: 登录选项 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

@@ -17,13 +17,14 @@ import (
) )
var ( var (
DB *gorm.DB DB *gorm.DB
Logger *logrus.Logger Logger *logrus.Logger
Config config.Config ConfigPath string = ""
Viper *viper.Viper Config config.Config
Redis *redis.Client Viper *viper.Viper
Cache cache.Handler Redis *redis.Client
Validator struct { Cache cache.Handler
Validator struct {
Validate *validator.Validate Validate *validator.Validate
UT *ut.UniversalTranslator UT *ut.UniversalTranslator
VTrans ut.Translator VTrans ut.Translator

3
go.mod
View File

@@ -16,6 +16,7 @@ require (
github.com/google/uuid v1.1.2 github.com/google/uuid v1.1.2
github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.9.0 github.com/spf13/viper v1.9.0
github.com/swaggo/files v1.0.1 github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/gin-swagger v1.6.0
@@ -28,7 +29,6 @@ require (
) )
require ( require (
cloud.google.com/go/compute/metadata v0.5.1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@@ -44,6 +44,7 @@ require (
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.0 // indirect github.com/goccy/go-json v0.10.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,9 @@ package admin
import ( import (
"Gwen/global" "Gwen/global"
"Gwen/http/controller/api"
"Gwen/http/request/admin" "Gwen/http/request/admin"
apiReq "Gwen/http/request/api"
"Gwen/http/response" "Gwen/http/response"
adResp "Gwen/http/response/admin" adResp "Gwen/http/response/admin"
"Gwen/model" "Gwen/model"
@@ -50,19 +52,14 @@ func (ct *Login) Login(c *gin.Context) {
ut := service.AllService.UserService.Login(u, &model.LoginLog{ ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id, UserId: u.Id,
Client: "webadmin", Client: model.LoginLogClientWebAdmin,
Uuid: "", Uuid: "", //must be empty
Ip: c.ClientIP(), Ip: c.ClientIP(),
Type: "account", Type: model.LoginLogTypeAccount,
Platform: f.Platform, Platform: f.Platform,
}) })
response.Success(c, &adResp.LoginPayload{ responseLoginSuccess(c, u, ut.Token)
Token: ut.Token,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
} }
// Logout 登出 // Logout 登出
@@ -82,3 +79,86 @@ func (ct *Login) Logout(c *gin.Context) {
} }
response.Success(c, nil) 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) {
ops := service.AllService.OauthService.GetOauthProviders()
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
}
responseLoginSuccess(c, u, ut.Token)
}
func responseLoginSuccess(c *gin.Context, u *model.User, token string) {
lp := &adResp.LoginPayload{}
lp.FromUser(u)
lp.Token = token
lp.RouteNames = service.AllService.UserService.RouteNames(u)
response.Success(c, lp)
}

View File

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

View File

@@ -5,7 +5,6 @@ import (
"Gwen/http/request/admin" "Gwen/http/request/admin"
adminReq "Gwen/http/request/admin" adminReq "Gwen/http/request/admin"
"Gwen/http/response" "Gwen/http/response"
"Gwen/model"
"Gwen/service" "Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strconv" "strconv"
@@ -96,21 +95,23 @@ func (o *Oauth) BindConfirm(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return return
} }
v := service.AllService.OauthService.GetOauthCache(j.Code) oauthService := service.AllService.OauthService
if v == nil { oauthCache := oauthService.GetOauthCache(j.Code)
if oauthCache == nil {
response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired")) response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired"))
return return
} }
u := service.AllService.UserService.CurUser(c) oauthUser := oauthCache.ToOauthUser()
err = service.AllService.OauthService.BindGithubUser(v.ThirdOpenId, v.ThirdOpenId, u.Id) user := service.AllService.UserService.CurUser(c)
err = oauthService.BindOauthUser(user.Id, oauthUser, oauthCache.Op)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "BindFail")) response.Fail(c, 101, response.TranslateMsg(c, "BindFail"))
return return
} }
v.UserId = u.Id oauthCache.UserId = user.Id
service.AllService.OauthService.SetOauthCache(j.Code, v, 0) oauthService.SetOauthCache(j.Code, oauthCache, 0)
response.Success(c, v) response.Success(c, oauthCache)
} }
func (o *Oauth) Unbind(c *gin.Context) { func (o *Oauth) Unbind(c *gin.Context) {
@@ -126,21 +127,11 @@ func (o *Oauth) Unbind(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return return
} }
if f.Op == model.OauthTypeGithub { err = service.AllService.OauthService.UnBindOauthUser(u.Id, f.Op)
err = service.AllService.OauthService.UnBindGithubUser(u.Id) if err != nil {
if err != nil { response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) return
return
}
} }
if f.Op == model.OauthTypeGoogle {
err = service.AllService.OauthService.UnBindGoogleUser(u.Id)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}
response.Success(c, nil) response.Success(c, nil)
} }
@@ -189,15 +180,18 @@ func (o *Oauth) Create(c *gin.Context) {
response.Fail(c, 101, errList[0]) response.Fail(c, 101, errList[0])
return return
} }
u := f.ToOauth()
ex := service.AllService.OauthService.InfoByOp(f.Op) err := u.FormatOauthInfo()
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
ex := service.AllService.OauthService.InfoByOp(u.Op)
if ex.Id > 0 { if ex.Id > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists")) response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
return return
} }
err = service.AllService.OauthService.Create(u)
u := f.ToOauth()
err := service.AllService.OauthService.Create(u)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return

View File

@@ -79,6 +79,7 @@ func (ct *Peer) Create(c *gin.Context) {
// @Param time_ago query int false "时间" // @Param time_ago query int false "时间"
// @Param id query string false "ID" // @Param id query string false "ID"
// @Param hostname query string false "主机名" // @Param hostname query string false "主机名"
// @Param uuids query string false "uuids 用逗号分隔"
// @Success 200 {object} response.Response{data=model.PeerList} // @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/list [get] // @Router /admin/peer/list [get]
@@ -104,6 +105,9 @@ func (ct *Peer) List(c *gin.Context) {
if query.Hostname != "" { if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%") tx.Where("hostname like ?", "%"+query.Hostname+"%")
} }
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
}) })
response.Success(c, res) response.Success(c, res)
} }
@@ -188,7 +192,7 @@ func (ct *Peer) Delete(c *gin.Context) {
// @Param body body admin.PeerBatchDeleteForm true "设备id" // @Param body body admin.PeerBatchDeleteForm true "设备id"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/delete [post] // @Router /admin/peer/batchDelete [post]
// @Security token // @Security token
func (ct *Peer) BatchDelete(c *gin.Context) { func (ct *Peer) BatchDelete(c *gin.Context) {
f := &admin.PeerBatchDeleteForm{} f := &admin.PeerBatchDeleteForm{}
@@ -207,3 +211,21 @@ func (ct *Peer) BatchDelete(c *gin.Context) {
} }
response.Success(c, nil) 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,10 +5,12 @@ import (
"Gwen/http/request/admin" "Gwen/http/request/admin"
"Gwen/http/response" "Gwen/http/response"
adResp "Gwen/http/response/admin" adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service" "Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
"strconv" "strconv"
"time"
) )
type User struct { type User struct {
@@ -215,12 +217,7 @@ func (ct *User) Current(c *gin.Context) {
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
token, _ := c.Get("token") token, _ := c.Get("token")
t := token.(string) t := token.(string)
response.Success(c, &adResp.LoginPayload{ responseLoginSuccess(c, u, t)
Token: t,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
} }
// ChangeCurPwd 修改当前用户密码 // ChangeCurPwd 修改当前用户密码
@@ -247,10 +244,14 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword) // If the password is not empty, the old password is verified
if u.Password != oldPwd { // otherwise, the old password is not verified
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError")) if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
return 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) err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
if err != nil { if err != nil {
@@ -281,10 +282,10 @@ func (ct *User) MyOauth(c *gin.Context) {
var res []*adResp.UserOauthItem var res []*adResp.UserOauthItem
for _, oa := range oal.Oauths { for _, oa := range oal.Oauths {
item := &adResp.UserOauthItem{ item := &adResp.UserOauthItem{
ThirdType: oa.Op, Op: oa.Op,
} }
for _, ut := range uts { for _, ut := range uts {
if ut.ThirdType == oa.Op { if ut.Op == oa.Op {
item.Status = 1 item.Status = 1
break break
} }
@@ -294,6 +295,51 @@ func (ct *User) MyOauth(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// List 列表
// @Tags 设备
// @Summary 设备列表
// @Description 设备列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @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/user/myPeer [get]
// @Security token
func (ct *User) MyPeer(c *gin.Context) {
query := &admin.PeerQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
res := service.AllService.PeerService.ListFilterByUserId(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.TimeAgo > 0 {
lt := time.Now().Unix() - int64(query.TimeAgo)
tx.Where("last_online_time < ?", lt)
}
if query.TimeAgo < 0 {
lt := time.Now().Unix() + int64(query.TimeAgo)
tx.Where("last_online_time > ?", lt)
}
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
}, u.Id)
response.Success(c, res)
}
// groupUsers // groupUsers
func (ct *User) GroupUsers(c *gin.Context) { func (ct *User) GroupUsers(c *gin.Context) {
q := &admin.GroupUsersQuery{} q := &admin.GroupUsersQuery{}
@@ -323,3 +369,35 @@ func (ct *User) GroupUsers(c *gin.Context) {
} }
response.Success(c, data) 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.Email, 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,
})
responseLoginSuccess(c, u, ut.Token)
}

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] // @Router /ab/peer/update/{guid} [put]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) PeerUpdate(c *gin.Context) { func (a *Ab) PeerUpdate(c *gin.Context) {
//f := &gin.H{} f := gin.H{}
f := &requstform.PersonalAddressBookForm{} //f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f) err := c.ShouldBindJSON(&f)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
@@ -709,17 +709,33 @@ func (a *Ab) PeerUpdate(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "NoAccess")) response.Error(c, response.TranslateMsg(c, "NoAccess"))
return return
} }
//fmt.Println(f) //fmt.Println(f)
//return //判断f["Id"]是否存在
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, f.Id, cid) 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 { if ab == nil || ab.RowId == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound")) response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return return
} }
nab := f.ToAddressBook() //允许的字段
nab.RowId = ab.RowId allowUp := []string{"password", "hash", "tags", "alias"}
err = service.AllService.AddressBookService.Update(nab) //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 { if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return

View File

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

View File

@@ -54,12 +54,13 @@ func (l *Login) Login(c *gin.Context) {
//根据refer判断是webclient还是app //根据refer判断是webclient还是app
ref := c.GetHeader("referer") ref := c.GetHeader("referer")
if ref != "" { if ref != "" {
f.DeviceInfo.Type = "webclient" f.DeviceInfo.Type = model.LoginLogClientWeb
} }
ut := service.AllService.UserService.Login(u, &model.LoginLog{ ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id, UserId: u.Id,
Client: f.DeviceInfo.Type, Client: f.DeviceInfo.Type,
DeviceId: f.Id,
Uuid: f.Uuid, Uuid: f.Uuid,
Ip: c.ClientIP(), Ip: c.ClientIP(),
Type: model.LoginLogTypeAccount, Type: model.LoginLogTypeAccount,
@@ -81,20 +82,12 @@ func (l *Login) Login(c *gin.Context) {
// @Produce json // @Produce json
// @Success 200 {object} []string // @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /login-options [post] // @Router /login-options [get]
func (l *Login) LoginOptions(c *gin.Context) { func (l *Login) LoginOptions(c *gin.Context) {
oauthOks := []string{} ops := service.AllService.OauthService.GetOauthProviders()
err, _ := service.AllService.OauthService.GetOauthConfig(model.OauthTypeGithub) ops = append(ops, model.OauthTypeWebauth)
if err == nil {
oauthOks = append(oauthOks, model.OauthTypeGithub)
}
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 var oidcItems []map[string]string
for _, v := range oauthOks { for _, v := range ops {
oidcItems = append(oidcItems, map[string]string{"name": v}) oidcItems = append(oidcItems, map[string]string{"name": v})
} }
common, err := json.Marshal(oidcItems) common, err := json.Marshal(oidcItems)
@@ -104,7 +97,7 @@ func (l *Login) LoginOptions(c *gin.Context) {
} }
var res []string var res []string
res = append(res, "common-oidc/"+string(common)) res = append(res, "common-oidc/"+string(common))
for _, v := range oauthOks { for _, v := range ops {
res = append(res, "oidc/"+v) res = append(res, "oidc/"+v)
} }
c.JSON(http.StatusOK, res) c.JSON(http.StatusOK, res)

View File

@@ -9,8 +9,6 @@ import (
"Gwen/service" "Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strconv"
"strings"
) )
type Oauth struct { type Oauth struct {
@@ -32,12 +30,11 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub {
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op) oauthService := service.AllService.OauthService
var code string
var url string
err, code, url = oauthService.BeginAuth(f.Op)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error())) response.Error(c, response.TranslateMsg(c, err.Error()))
return return
@@ -59,6 +56,60 @@ 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,
DeviceId: v.Id,
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 // OidcAuthQuery
// @Tags Oauth // @Tags Oauth
// @Summary OidcAuthQuery // @Summary OidcAuthQuery
@@ -69,33 +120,10 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /oidc/auth-query [get] // @Router /oidc/auth-query [get]
func (o *Oauth) OidcAuthQuery(c *gin.Context) { func (o *Oauth) OidcAuthQuery(c *gin.Context) {
q := &api.OidcAuthQuery{} u, ut := o.OidcAuthQueryPre(c)
err := c.ShouldBindQuery(q) if u == nil || ut == nil {
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return 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{ c.JSON(http.StatusOK, apiResp.LoginRes{
AccessToken: ut.Token, AccessToken: ut.Token,
Type: "access_token", Type: "access_token",
@@ -118,142 +146,95 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
c.String(http.StatusInternalServerError, response.TranslateParamMsg(c, "ParamIsEmpty", "state")) c.String(http.StatusInternalServerError, response.TranslateParamMsg(c, "ParamIsEmpty", "state"))
return return
} }
cacheKey := state cacheKey := state
oauthService := service.AllService.OauthService
//从缓存中获取 //从缓存中获取
v := service.AllService.OauthService.GetOauthCache(cacheKey) oauthCache := oauthService.GetOauthCache(cacheKey)
if v == nil { if oauthCache == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthExpired")) c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthExpired"))
return return
} }
op := oauthCache.Op
ty := v.Op action := oauthCache.Action
ac := v.Action var user *model.User
//fmt.Println("ty ac ", ty, ac) // 获取用户信息
if ty == model.OauthTypeGithub { code := c.Query("code")
code := c.Query("code") err, oauthUser := oauthService.Callback(code, op)
err, userData := service.AllService.OauthService.GithubCallback(code) if err != nil {
if err != nil { c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error())) return
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
}
} }
userId := oauthCache.UserId
openid := oauthUser.OpenId
if action == service.OauthActionTypeBind {
if ty == model.OauthTypeGoogle { //fmt.Println("bind", ty, userData)
code := c.Query("code") // 检查此openid是否已经绑定过
err, userData := service.AllService.OauthService.GoogleCallback(code) utr := oauthService.UserThirdInfo(op, openid)
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
user = service.AllService.UserService.InfoById(userId)
if user == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定
err := oauthService.BindOauthUser(userId, oauthUser, op)
if err != nil { if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error())) c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return return
} }
//将空格替换成_ c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
googleName := strings.Replace(userData.Name, " ", "_", -1) return
if ac == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData) } else if action == service.OauthActionTypeLogin {
utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Email) //登录
if utr.UserId > 0 { if userId != 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser")) c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
return
}
user = service.AllService.UserService.InfoByOauthId(op, openid)
if user == nil {
oauthConfig := oauthService.InfoByOp(op)
if !*oauthConfig.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
oauthCache.UpdateFromOauthUser(oauthUser)
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
c.Redirect(http.StatusFound, url)
return return
} }
//绑定
u := service.AllService.UserService.InfoById(v.UserId) //自动注册
if u == nil { err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op)
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定
err = service.AllService.OauthService.BindGoogleUser(userData.Email, googleName, v.UserId)
if err != nil { if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail")) c.String(http.StatusInternalServerError, response.TranslateMsg(c, err.Error()))
return return
} }
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess")) }
return oauthCache.UserId = user.Id
} else if ac == service.OauthActionTypeLogin { oauthService.SetOauthCache(cacheKey, oauthCache, 0)
if v.UserId != 0 { // 如果是webadmin登录成功后跳转到webadmin
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess")) if oauthCache.DeviceType == model.LoginLogClientWebAdmin {
return /*service.AllService.UserService.Login(u, &model.LoginLog{
} UserId: u.Id,
u := service.AllService.UserService.InfoByGoogleEmail(userData.Email) Client: "webadmin",
if u == nil { Uuid: "", //must be empty
oa := service.AllService.OauthService.InfoByOp(ty) Ip: c.ClientIP(),
if !*oa.AutoRegister { Type: model.LoginLogTypeOauth,
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定") Platform: oauthService.DeviceOs,
})*/
v.ThirdName = googleName url := global.Config.Rustdesk.ApiServer + "/_admin/#/"
v.ThirdOpenId = userData.Email c.Redirect(http.StatusFound, url)
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"))
return 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

@@ -17,7 +17,7 @@ func AdminAuth() gin.HandlerFunc {
c.Abort() c.Abort()
return return
} }
user := service.AllService.UserService.InfoByAccessToken(token) user, ut := service.AllService.UserService.InfoByAccessToken(token)
if user.Id == 0 { if user.Id == 0 {
response.Fail(c, 403, "请先登录") response.Fail(c, 403, "请先登录")
c.Abort() c.Abort()
@@ -26,6 +26,8 @@ func AdminAuth() gin.HandlerFunc {
c.Set("curUser", user) c.Set("curUser", user)
c.Set("token", token) c.Set("token", token)
//如果时间小于1天,token自动续期
service.AllService.UserService.AutoRefreshAccessToken(ut)
c.Next() c.Next()
} }

View File

@@ -28,7 +28,7 @@ func RustAuth() gin.HandlerFunc {
//这里只是简单的提取 //这里只是简单的提取
token = token[7:] token = token[7:]
//验证token //验证token
user := service.AllService.UserService.InfoByAccessToken(token) user, ut := service.AllService.UserService.InfoByAccessToken(token)
if user.Id == 0 { if user.Id == 0 {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
"error": "Unauthorized", "error": "Unauthorized",
@@ -46,6 +46,9 @@ func RustAuth() gin.HandlerFunc {
c.Set("curUser", user) c.Set("curUser", user)
c.Set("token", token) c.Set("token", token)
service.AllService.UserService.AutoRefreshAccessToken(ut)
c.Next() c.Next()
} }
} }

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,8 @@ import (
type UserForm struct { type UserForm struct {
Id uint `json:"id"` Id uint `json:"id"`
Username string `json:"username" validate:"required,gte=4,lte=10"` Username string `json:"username" validate:"required,gte=2,lte=10"`
Email string `json:"email"` //validate:"required,email" email不强制
//Password string `json:"password" validate:"required,gte=4,lte=20"` //Password string `json:"password" validate:"required,gte=4,lte=20"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
@@ -19,6 +20,7 @@ func (uf *UserForm) FromUser(user *model.User) *UserForm {
uf.Id = user.Id uf.Id = user.Id
uf.Username = user.Username uf.Username = user.Username
uf.Nickname = user.Nickname uf.Nickname = user.Nickname
uf.Email = user.Email
uf.Avatar = user.Avatar uf.Avatar = user.Avatar
uf.GroupId = user.GroupId uf.GroupId = user.GroupId
uf.IsAdmin = user.IsAdmin uf.IsAdmin = user.IsAdmin
@@ -30,6 +32,7 @@ func (uf *UserForm) ToUser() *model.User {
user.Id = uf.Id user.Id = uf.Id
user.Username = uf.Username user.Username = uf.Username
user.Nickname = uf.Nickname user.Nickname = uf.Nickname
user.Email = uf.Email
user.Avatar = uf.Avatar user.Avatar = uf.Avatar
user.GroupId = uf.GroupId user.GroupId = uf.GroupId
user.IsAdmin = uf.IsAdmin user.IsAdmin = uf.IsAdmin
@@ -59,3 +62,10 @@ type GroupUsersQuery struct {
IsMy int `json:"is_my"` IsMy int `json:"is_my"`
UserId uint `json:"user_id"` UserId uint `json:"user_id"`
} }
type RegisterForm struct {
Username string `json:"username" validate:"required,gte=2,lte=10"`
Email string `json:"email"` // validate:"required,email"
Password string `json:"password" validate:"required,gte=4,lte=20"`
ConfirmPassword string `json:"confirm_password" validate:"required,gte=4,lte=20"`
}

View File

@@ -34,7 +34,7 @@ type LoginForm struct {
Id string `json:"id" label:"id"` Id string `json:"id" label:"id"`
Type string `json:"type" label:"type"` Type string `json:"type" label:"type"`
Uuid string `json:"uuid" label:"uuid"` Uuid string `json:"uuid" label:"uuid"`
Username string `json:"username" validate:"required,gte=4,lte=10" label:"用户名"` Username string `json:"username" validate:"required,gte=2,lte=10" label:"用户名"`
Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"` Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"`
} }

View File

@@ -4,19 +4,28 @@ import "Gwen/model"
type LoginPayload struct { type LoginPayload struct {
Username string `json:"username"` Username string `json:"username"`
Email string `json:"email"`
Avatar string `json:"avatar"`
Token string `json:"token"` Token string `json:"token"`
RouteNames []string `json:"route_names"` RouteNames []string `json:"route_names"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
} }
func (lp *LoginPayload) FromUser(user *model.User) {
lp.Username = user.Username
lp.Email = user.Email
lp.Avatar = user.Avatar
lp.Nickname = user.Nickname
}
var UserRouteNames = []string{ var UserRouteNames = []string{
"MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection", "MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection", "MyPeer",
} }
var AdminRouteNames = []string{"*"} var AdminRouteNames = []string{"*"}
type UserOauthItem struct { type UserOauthItem struct {
ThirdType string `json:"third_type"` Op string `json:"op"`
Status int `json:"status"` Status int `json:"status"`
} }
type GroupUsersPayload struct { type GroupUsersPayload struct {

View File

@@ -29,6 +29,7 @@ type UserPayload struct {
func (up *UserPayload) FromUser(user *model.User) *UserPayload { func (up *UserPayload) FromUser(user *model.User) *UserPayload {
up.Name = user.Username up.Name = user.Username
up.Email = user.Email
up.IsAdmin = user.IsAdmin up.IsAdmin = user.IsAdmin
up.Status = int(user.Status) up.Status = int(user.Status)
up.Info = map[string]interface{}{} up.Info = map[string]interface{}{}

View File

@@ -17,7 +17,7 @@ func Init(g *gin.Engine) {
adg := g.Group("/api/admin") adg := g.Group("/api/admin")
LoginBind(adg) LoginBind(adg)
adg.POST("/user/register", (&admin.User{}).Register)
adg.Use(middleware.AdminAuth()) adg.Use(middleware.AdminAuth())
//FileBind(adg) //FileBind(adg)
UserBind(adg) UserBind(adg)
@@ -30,10 +30,10 @@ func Init(g *gin.Engine) {
AuditBind(adg) AuditBind(adg)
AddressBookCollectionBind(adg) AddressBookCollectionBind(adg)
AddressBookCollectionRuleBind(adg) AddressBookCollectionRuleBind(adg)
UserTokenBind(adg)
rs := &admin.Rustdesk{} rs := &admin.Rustdesk{}
adg.GET("/server-config", rs.ServerConfig) adg.GET("/server-config", rs.ServerConfig)
adg.GET("/app-config", rs.AppConfig) adg.GET("/app-config", rs.AppConfig)
//访问静态文件 //访问静态文件
//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload")) //g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
} }
@@ -41,6 +41,9 @@ func LoginBind(rg *gin.RouterGroup) {
cont := &admin.Login{} cont := &admin.Login{}
rg.POST("/login", cont.Login) rg.POST("/login", cont.Login)
rg.POST("/logout", cont.Logout) 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) { func UserBind(rg *gin.RouterGroup) {
@@ -50,6 +53,7 @@ func UserBind(rg *gin.RouterGroup) {
aR.GET("/current", cont.Current) aR.GET("/current", cont.Current)
aR.POST("/changeCurPwd", cont.ChangeCurPwd) aR.POST("/changeCurPwd", cont.ChangeCurPwd)
aR.POST("/myOauth", cont.MyOauth) aR.POST("/myOauth", cont.MyOauth)
aR.GET("/myPeer", cont.MyPeer)
aR.POST("/groupUsers", cont.GroupUsers) aR.POST("/groupUsers", cont.GroupUsers)
} }
aRP := rg.Group("/user").Use(middleware.AdminPrivilege()) aRP := rg.Group("/user").Use(middleware.AdminPrivilege())
@@ -105,6 +109,8 @@ func AddressBookBind(rg *gin.RouterGroup) {
} }
func PeerBind(rg *gin.RouterGroup) { func PeerBind(rg *gin.RouterGroup) {
aR := rg.Group("/peer") aR := rg.Group("/peer")
aR.POST("/simpleData", (&admin.Peer{}).SimpleData)
aR.Use(middleware.AdminPrivilege())
{ {
cont := &admin.Peer{} cont := &admin.Peer{}
aR.GET("/list", cont.List) aR.GET("/list", cont.List)
@@ -112,9 +118,7 @@ func PeerBind(rg *gin.RouterGroup) {
aR.POST("/create", cont.Create) aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update) aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete) aR.POST("/delete", cont.Delete)
aR.POST("/batchDelete", cont.BatchDelete)
arp := aR.Use(middleware.AdminPrivilege())
arp.POST("/batchDelete", cont.BatchDelete)
} }
} }
@@ -178,6 +182,12 @@ func AddressBookCollectionRuleBind(rg *gin.RouterGroup) {
aR.POST("/delete", cont.Delete) 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) { func FileBind(rg *gin.RouterGroup) {

View File

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

View File

@@ -2,16 +2,23 @@ package model
type LoginLog struct { type LoginLog struct {
IdModel IdModel
UserId uint `json:"user_id"` UserId uint `json:"user_id" gorm:"default:0;not null;"`
Client string `json:"client"` //webadmin,webclient,app, Client string `json:"client"` //webadmin,webclient,app,
Uuid string `json:"uuid"` DeviceId string `json:"device_id"`
Ip string `json:"ip"` Uuid string `json:"uuid"`
Type string `json:"type"` //account,oauth Ip string `json:"ip"`
Platform string `json:"platform"` //windows,linux,mac,android,ios 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 TimeModel
} }
const (
LoginLogClientWebAdmin = "webadmin"
LoginLogClientWeb = "webclient"
LoginLogClientApp = "app"
)
const ( const (
LoginLogTypeAccount = "account" LoginLogTypeAccount = "account"
LoginLogTypeOauth = "oauth" LoginLogTypeOauth = "oauth"

View File

@@ -1,20 +1,145 @@
package model package model
import (
"errors"
"strconv"
"strings"
)
const OIDC_DEFAULT_SCOPES = "openid,profile,email"
const (
// make sure the value shouldbe lowercase
OauthTypeGithub string = "github"
OauthTypeGoogle string = "google"
OauthTypeOidc string = "oidc"
OauthTypeWebauth string = "webauth"
)
// Validate the oauth type
func ValidateOauthType(oauthType string) error {
switch oauthType {
case OauthTypeGithub, OauthTypeGoogle, OauthTypeOidc, OauthTypeWebauth:
return nil
default:
return errors.New("invalid Oauth type")
}
}
const (
UserEndpointGithub string = "https://api.github.com/user"
IssuerGoogle string = "https://accounts.google.com"
)
type Oauth struct { type Oauth struct {
IdModel IdModel
Op string `json:"op"` Op string `json:"op"`
OauthType string `json:"oauth_type"`
ClientId string `json:"client_id"` ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"` ClientSecret string `json:"client_secret"`
RedirectUrl string `json:"redirect_url"` RedirectUrl string `json:"redirect_url"`
AutoRegister *bool `json:"auto_register"` AutoRegister *bool `json:"auto_register"`
Scopes string `json:"scopes"`
Issuer string `json:"issuer"`
TimeModel TimeModel
} }
const ( // Helper function to format oauth info, it's used in the update and create method
OauthTypeGithub = "github" func (oa *Oauth) FormatOauthInfo() error {
OauthTypeGoogle = "google" oauthType := strings.TrimSpace(oa.OauthType)
OauthTypeWebauth = "webauth" err := ValidateOauthType(oa.OauthType)
) if err != nil {
return err
}
switch oauthType {
case OauthTypeGithub:
oa.Op = OauthTypeGithub
case OauthTypeGoogle:
oa.Op = OauthTypeGoogle
}
// check if the op is empty, set the default value
op := strings.TrimSpace(oa.Op)
if op == "" && oauthType == OauthTypeOidc {
oa.Op = OauthTypeOidc
}
// check the issuer, if the oauth type is google and the issuer is empty, set the issuer to the default value
issuer := strings.TrimSpace(oa.Issuer)
// If the oauth type is google and the issuer is empty, set the issuer to the default value
if oauthType == OauthTypeGoogle && issuer == "" {
oa.Issuer = IssuerGoogle
}
return nil
}
type OauthUser struct {
OpenId string `json:"open_id" gorm:"not null;index"`
Name string `json:"name"`
Username string `json:"username"`
Email string `json:"email"`
VerifiedEmail bool `json:"verified_email,omitempty"`
Picture string `json:"picture,omitempty"`
}
func (ou *OauthUser) ToUser(user *User, overideUsername bool) {
if overideUsername {
user.Username = ou.Username
}
user.Email = ou.Email
user.Nickname = ou.Name
user.Avatar = ou.Picture
}
type OauthUserBase struct {
Name string `json:"name"`
Email string `json:"email"`
}
type OidcUser struct {
OauthUserBase
Sub string `json:"sub"`
VerifiedEmail bool `json:"email_verified"`
PreferredUsername string `json:"preferred_username"`
Picture string `json:"picture"`
}
func (ou *OidcUser) ToOauthUser() *OauthUser {
var username string
// 使用 PreferredUsername如果不存在降级到 Email 前缀
if ou.PreferredUsername != "" {
username = ou.PreferredUsername
} else {
username = strings.ToLower(ou.Email)
}
return &OauthUser{
OpenId: ou.Sub,
Name: ou.Name,
Username: username,
Email: ou.Email,
VerifiedEmail: ou.VerifiedEmail,
Picture: ou.Picture,
}
}
type GithubUser struct {
OauthUserBase
Id int `json:"id"`
Login string `json:"login"`
AvatarUrl string `json:"avatar_url"`
VerifiedEmail bool `json:"verified_email"`
}
func (gu *GithubUser) ToOauthUser() *OauthUser {
username := strings.ToLower(gu.Login)
return &OauthUser{
OpenId: strconv.Itoa(gu.Id),
Name: gu.Name,
Username: username,
Email: gu.Email,
VerifiedEmail: gu.VerifiedEmail,
Picture: gu.AvatarUrl,
}
}
type OauthList struct { type OauthList struct {
Oauths []*Oauth `json:"list"` Oauths []*Oauth `json:"list"`

View File

@@ -11,8 +11,9 @@ type Peer struct {
Uuid string `json:"uuid" gorm:"default:'';not null;index"` Uuid string `json:"uuid" gorm:"default:'';not null;index"`
Version string `json:"version" gorm:"default:'';not null;"` Version string `json:"version" gorm:"default:'';not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"` 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;"` LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"`
LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"`
TimeModel TimeModel
} }

View File

@@ -2,7 +2,9 @@ package model
type User struct { type User struct {
IdModel IdModel
Username string `json:"username" gorm:"default:'';not null;index,unique"` Username string `json:"username" gorm:"default:'';not null;uniqueIndex"`
Email string `json:"email" gorm:"default:'';not null;index"`
// Email string `json:"email" `
Password string `json:"-" gorm:"default:'';not null;"` Password string `json:"-" gorm:"default:'';not null;"`
Nickname string `json:"nickname" gorm:"default:'';not null;"` Nickname string `json:"nickname" gorm:"default:'';not null;"`
Avatar string `json:"avatar" gorm:"default:'';not null;"` Avatar string `json:"avatar" gorm:"default:'';not null;"`
@@ -12,6 +14,15 @@ type User struct {
TimeModel TimeModel
} }
// BeforeSave 钩子用于确保 email 字段有合理的默认值
//func (u *User) BeforeSave(tx *gorm.DB) (err error) {
// // 如果 email 为空,设置为默认值
// if u.Email == "" {
// u.Email = fmt.Sprintf("%s@example.com", u.Username)
// }
// return nil
//}
type UserList struct { type UserList struct {
Users []*User `json:"list,omitempty"` Users []*User `json:"list,omitempty"`
Pagination Pagination

View File

@@ -1,12 +1,26 @@
package model package model
import (
"strings"
)
type UserThird struct { type UserThird struct {
IdModel IdModel
UserId uint `json:"user_id" gorm:"not null;index"` UserId uint `json:"user_id" gorm:"not null;index"`
OpenId string `json:"open_id" gorm:"not null;index"` OauthUser
UnionId string `json:"union_id" gorm:"not null;"` UnionId string `json:"union_id" gorm:"default:'';not null;"`
ThirdType string `json:"third_type" gorm:"not null;"` // OauthType string `json:"oauth_type" gorm:"not null;"`
ThirdEmail string `json:"third_email"` ThirdType string `json:"third_type" gorm:"default:'';not null;"` //deprecated
ThirdName string `json:"third_name"` OauthType string `json:"oauth_type" gorm:"default:'';not null;"`
Op string `json:"op" gorm:"default:'';not null;"`
TimeModel TimeModel
} }
func (u *UserThird) FromOauthUser(userId uint, oauthUser *OauthUser, oauthType string, op string) {
u.UserId = userId
u.OauthUser = *oauthUser
u.OauthType = oauthType
u.Op = op
// make sure email is lower case
u.Email = strings.ToLower(u.Email)
}

View File

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

View File

@@ -119,3 +119,7 @@ other = "Default Group"
description = "Share group" description = "Share group"
one = "Share Group" one = "Share Group"
other = "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." description = "Share group."
one = "공유 그룹" one = "공유 그룹"
other = "공유 그룹" other = "공유 그룹"
[RegisterClosed]
description = "Register closed."
one = "가입이 종료되었습니다."
other = "가입이 종료되었습니다."

View File

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

View File

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

View File

@@ -127,6 +127,16 @@ func (s *AddressBookService) Delete(u *model.AddressBook) error {
// Update 更新 // Update 更新
func (s *AddressBookService) Update(u *model.AddressBook) error { 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 return global.DB.Model(u).Select("*").Omit("created_at").Updates(u).Error
} }

View File

@@ -9,12 +9,14 @@ import (
"errors" "errors"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/github" "golang.org/x/oauth2/github"
"golang.org/x/oauth2/google" // "golang.org/x/oauth2/google"
"gorm.io/gorm" "gorm.io/gorm"
"io" // "io"
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
) )
@@ -22,74 +24,36 @@ import (
type OauthService struct { type OauthService struct {
} }
type GithubUserdata struct { // Define a struct to parse the .well-known/openid-configuration response
AvatarUrl string `json:"avatar_url"` type OidcEndpoint struct {
Bio string `json:"bio"` Issuer string `json:"issuer"`
Blog string `json:"blog"` AuthURL string `json:"authorization_endpoint"`
Collaborators int `json:"collaborators"` TokenURL string `json:"token_endpoint"`
Company interface{} `json:"company"` UserInfo string `json:"userinfo_endpoint"`
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 { type OauthCacheItem struct {
UserId uint `json:"user_id"` UserId uint `json:"user_id"`
Id string `json:"id"` //rustdesk的设备ID Id string `json:"id"` //rustdesk的设备ID
Op string `json:"op"` Op string `json:"op"`
Action string `json:"action"` Action string `json:"action"`
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
DeviceName string `json:"device_name"` DeviceName string `json:"device_name"`
DeviceOs string `json:"device_os"` DeviceOs string `json:"device_os"`
DeviceType string `json:"device_type"` DeviceType string `json:"device_type"`
ThirdOpenId string `json:"third_open_id"` OpenId string `json:"open_id"`
ThirdName string `json:"third_name"` Username string `json:"username"`
ThirdEmail string `json:"third_email"` Name string `json:"name"`
Email string `json:"email"`
}
func (oci *OauthCacheItem) ToOauthUser() *model.OauthUser {
return &model.OauthUser{
OpenId: oci.OpenId,
Username: oci.Username,
Name: oci.Name,
Email: oci.Email,
}
} }
var OauthCache = &sync.Map{} var OauthCache = &sync.Map{}
@@ -99,6 +63,13 @@ const (
OauthActionTypeBind = "bind" OauthActionTypeBind = "bind"
) )
func (oci *OauthCacheItem) UpdateFromOauthUser(oauthUser *model.OauthUser) {
oci.OpenId = oauthUser.OpenId
oci.Username = oauthUser.Username
oci.Name = oauthUser.Name
oci.Email = oauthUser.Email
}
func (os *OauthService) GetOauthCache(key string) *OauthCacheItem { func (os *OauthService) GetOauthCache(key string) *OauthCacheItem {
v, ok := OauthCache.Load(key) v, ok := OauthCache.Load(key)
if !ok { if !ok {
@@ -123,52 +94,101 @@ func (os *OauthService) DeleteOauthCache(key string) {
func (os *OauthService) BeginAuth(op string) (error error, code, url string) { func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
code = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10) code = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10)
if op == string(model.OauthTypeWebauth) {
if op == model.OauthTypeWebauth {
url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + code url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + code
//url = "http://localhost:8888/_admin/#/oauth/" + code //url = "http://localhost:8888/_admin/#/oauth/" + code
return nil, code, url return nil, code, url
} }
err, conf := os.GetOauthConfig(op) err, _, oauthConfig := os.GetOauthConfig(op)
if err == nil { if err == nil {
return err, code, conf.AuthCodeURL(code) return err, code, oauthConfig.AuthCodeURL(code)
} }
return err, code, "" return err, code, ""
} }
// GetOauthConfig 获取配置 // Method to fetch OIDC configuration dynamically
func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) { func (os *OauthService) FetchOidcEndpoint(issuer string) (error, OidcEndpoint) {
if op == model.OauthTypeGithub { configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
g := os.InfoByOp(model.OauthTypeGithub)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" { // Get the HTTP client (with or without proxy based on configuration)
return errors.New("ConfigNotFound"), nil client := getHTTPClientWithProxy()
}
return nil, &oauth2.Config{ resp, err := client.Get(configURL)
ClientID: g.ClientId, if err != nil {
ClientSecret: g.ClientSecret, return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{}
RedirectURL: g.RedirectUrl,
Endpoint: github.Endpoint,
Scopes: []string{"read:user", "user:email"},
}
} }
if op == model.OauthTypeGoogle { defer resp.Body.Close()
g := os.InfoByOp(model.OauthTypeGoogle)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" { if resp.StatusCode != http.StatusOK {
return errors.New("ConfigNotFound"), nil return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{}
} }
return nil, &oauth2.Config{
ClientID: g.ClientId, var endpoint OidcEndpoint
ClientSecret: g.ClientSecret, if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil {
RedirectURL: g.RedirectUrl, return errors.New("failed to parse OIDC configuration"), OidcEndpoint{}
Endpoint: google.Endpoint, }
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
} return nil, endpoint
}
func (os *OauthService) FetchOidcEndpointByOp(op string) (error, OidcEndpoint) {
oauthInfo := os.InfoByOp(op)
if oauthInfo.Issuer == "" {
return errors.New("issuer is empty"), OidcEndpoint{}
}
return os.FetchOidcEndpoint(oauthInfo.Issuer)
}
// GetOauthConfig retrieves the OAuth2 configuration based on the provider name
func (os *OauthService) GetOauthConfig(op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config) {
err, oauthInfo, oauthConfig = os.getOauthConfigGeneral(op)
if err != nil {
return err, nil, nil
}
// Maybe should validate the oauthConfig here
oauthType := oauthInfo.OauthType
err = model.ValidateOauthType(oauthType)
if err != nil {
return err, nil, nil
}
switch oauthType {
case model.OauthTypeGithub:
oauthConfig.Endpoint = github.Endpoint
oauthConfig.Scopes = []string{"read:user", "user:email"}
case model.OauthTypeOidc, model.OauthTypeGoogle:
var endpoint OidcEndpoint
err, endpoint = os.FetchOidcEndpoint(oauthInfo.Issuer)
if err != nil {
return err, nil, nil
}
oauthConfig.Endpoint = oauth2.Endpoint{AuthURL: endpoint.AuthURL, TokenURL: endpoint.TokenURL}
oauthConfig.Scopes = os.constructScopes(oauthInfo.Scopes)
default:
return errors.New("unsupported OAuth type"), nil, nil
}
return nil, oauthInfo, oauthConfig
}
// GetOauthConfig retrieves the OAuth2 configuration based on the provider name
func (os *OauthService) getOauthConfigGeneral(op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config) {
oauthInfo = os.InfoByOp(op)
if oauthInfo.Id == 0 || oauthInfo.ClientId == "" || oauthInfo.ClientSecret == "" {
return errors.New("ConfigNotFound"), nil, nil
}
// If the redirect URL is empty, use the default redirect URL
if oauthInfo.RedirectUrl == "" {
oauthInfo.RedirectUrl = global.Config.Rustdesk.ApiServer + "/api/oidc/callback"
}
return nil, oauthInfo, &oauth2.Config{
ClientID: oauthInfo.ClientId,
ClientSecret: oauthInfo.ClientSecret,
RedirectURL: oauthInfo.RedirectUrl,
} }
return errors.New("ConfigNotFound"), nil
} }
func getHTTPClientWithProxy() *http.Client { func getHTTPClientWithProxy() *http.Client {
//todo add timeout
if global.Config.Proxy.Enable { if global.Config.Proxy.Enable {
if global.Config.Proxy.Host == "" { if global.Config.Proxy.Host == "" {
global.Logger.Warn("Proxy is enabled but proxy host is empty.") global.Logger.Warn("Proxy is enabled but proxy host is empty.")
@@ -187,134 +207,151 @@ func getHTTPClientWithProxy() *http.Client {
return http.DefaultClient return http.DefaultClient
} }
func (os *OauthService) GithubCallback(code string) (error error, userData *GithubUserdata) { func (os *OauthService) callbackBase(oauthConfig *oauth2.Config, code string, userEndpoint string, userData interface{}) (err error, client *http.Client) {
err, oauthConfig := os.GetOauthConfig(model.OauthTypeGithub)
if err != nil {
return err, nil
}
// 使用代理配置创建 HTTP 客户端 // 设置代理客户端
httpClient := getHTTPClientWithProxy() httpClient := getHTTPClientWithProxy()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient) ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
token, err := oauthConfig.Exchange(ctx, code) // 使用 code 换取 token
var token *oauth2.Token
token, err = oauthConfig.Exchange(ctx, code)
if err != nil { if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err) global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
error = errors.New("GetOauthTokenError") return errors.New("GetOauthTokenError"), nil
return
} }
// 使用带有代理的 HTTP 客户端获取用户信息 // 获取用户信息
client := oauthConfig.Client(ctx, token) client = oauthConfig.Client(ctx, token)
resp, err := client.Get("https://api.github.com/user") resp, err := client.Get(userEndpoint)
if err != nil { if err != nil {
global.Logger.Warn("failed getting user info: ", err) global.Logger.Warn("failed getting user info: ", err)
error = errors.New("GetOauthUserInfoError") return errors.New("GetOauthUserInfoError"), nil
return
} }
defer func(Body io.ReadCloser) { defer func() {
err := Body.Close() if closeErr := resp.Body.Close(); closeErr != nil {
if err != nil { global.Logger.Warn("failed closing response body: ", closeErr)
global.Logger.Warn("failed closing response body: ", err)
} }
}(resp.Body) }()
// 解析用户信息 // 解析用户信息
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil { if err = json.NewDecoder(resp.Body).Decode(userData); err != nil {
global.Logger.Warn("failed decoding user info: ", err) global.Logger.Warn("failed decoding user info: ", err)
error = errors.New("DecodeOauthUserInfoError") return errors.New("DecodeOauthUserInfoError"), nil
return
} }
return
return nil, client
} }
func (os *OauthService) GoogleCallback(code string) (error error, userData *GoogleUserdata) { // githubCallback github回调
err, oauthConfig := os.GetOauthConfig(model.OauthTypeGoogle) func (os *OauthService) githubCallback(oauthConfig *oauth2.Config, code string) (error, *model.OauthUser) {
var user = &model.GithubUser{}
err, client := os.callbackBase(oauthConfig, code, model.UserEndpointGithub, user)
if err != nil { if err != nil {
return err, nil return err, nil
} }
err = os.getGithubPrimaryEmail(client, user)
// 使用代理配置创建 HTTP 客户端
httpClient := getHTTPClientWithProxy()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
token, err := oauthConfig.Exchange(ctx, code)
if err != nil { if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err) return err, nil
error = errors.New("GetOauthTokenError")
return
} }
return nil, user.ToOauthUser()
// 使用带有代理的 HTTP 客户端获取用户信息
client := oauthConfig.Client(ctx, token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/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 { // oidcCallback oidc回调, 通过code获取用户信息
func (os *OauthService) oidcCallback(oauthConfig *oauth2.Config, code string, userInfoEndpoint string) (error, *model.OauthUser) {
var user = &model.OidcUser{}
if err, _ := os.callbackBase(oauthConfig, code, userInfoEndpoint, user); err != nil {
return err, nil
}
return nil, user.ToOauthUser()
}
// Callback: Get user information by code and op(Oauth provider)
func (os *OauthService) Callback(code string, op string) (err error, oauthUser *model.OauthUser) {
var oauthInfo *model.Oauth
var oauthConfig *oauth2.Config
err, oauthInfo, oauthConfig = os.GetOauthConfig(op)
// oauthType is already validated in GetOauthConfig
if err != nil {
return err, nil
}
oauthType := oauthInfo.OauthType
switch oauthType {
case model.OauthTypeGithub:
err, oauthUser = os.githubCallback(oauthConfig, code)
case model.OauthTypeOidc, model.OauthTypeGoogle:
err, endpoint := os.FetchOidcEndpoint(oauthInfo.Issuer)
if err != nil {
return err, nil
}
err, oauthUser = os.oidcCallback(oauthConfig, code, endpoint.UserInfo)
default:
return errors.New("unsupported OAuth type"), nil
}
return err, oauthUser
}
func (os *OauthService) UserThirdInfo(op string, openId string) *model.UserThird {
ut := &model.UserThird{} ut := &model.UserThird{}
global.DB.Where("open_id = ? and third_type = ?", openid, op).First(ut) global.DB.Where("open_id = ? and op = ?", openId, op).First(ut)
return ut return ut
} }
func (os *OauthService) BindGithubUser(openid, username string, userId uint) error { // BindOauthUser: Bind third party account
return os.BindOauthUser(model.OauthTypeGithub, openid, username, userId) func (os *OauthService) BindOauthUser(userId uint, oauthUser *model.OauthUser, op string) error {
} utr := &model.UserThird{}
err, oauthType := os.GetTypeByOp(op)
func (os *OauthService) BindGoogleUser(email, username string, userId uint) error { if err != nil {
return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId) return err
}
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
utr := &model.UserThird{
OpenId: openid,
ThirdType: thirdType,
ThirdName: username,
UserId: userId,
} }
utr.FromOauthUser(userId, oauthUser, oauthType, op)
return global.DB.Create(utr).Error return global.DB.Create(utr).Error
} }
func (os *OauthService) UnBindGithubUser(userid uint) error { // UnBindOauthUser: Unbind third party account
return os.UnBindThird(model.OauthTypeGithub, userid) func (os *OauthService) UnBindOauthUser(userId uint, op string) error {
} return os.UnBindThird(op, userId)
func (os *OauthService) UnBindGoogleUser(userid uint) error {
return os.UnBindThird(model.OauthTypeGoogle, 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
} }
// InfoById 根据id取用户信息 // UnBindThird: Unbind third party account
func (os *OauthService) UnBindThird(op string, userId uint) error {
return global.DB.Where("user_id = ? and op = ?", userId, op).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获取Oauth信息
func (os *OauthService) InfoById(id uint) *model.Oauth { func (os *OauthService) InfoById(id uint) *model.Oauth {
u := &model.Oauth{} oauthInfo := &model.Oauth{}
global.DB.Where("id = ?", id).First(u) global.DB.Where("id = ?", id).First(oauthInfo)
return u return oauthInfo
} }
// InfoByOp 根据op取用户信息 // InfoByOp 根据op获取Oauth信息
func (os *OauthService) InfoByOp(op string) *model.Oauth { func (os *OauthService) InfoByOp(op string) *model.Oauth {
u := &model.Oauth{} oauthInfo := &model.Oauth{}
global.DB.Where("op = ?", op).First(u) global.DB.Where("op = ?", op).First(oauthInfo)
return u return oauthInfo
} }
// Helper function to get scopes by operation
func (os *OauthService) getScopesByOp(op string) []string {
scopes := os.InfoByOp(op).Scopes
return os.constructScopes(scopes)
}
// Helper function to construct scopes
func (os *OauthService) constructScopes(scopes string) []string {
scopes = strings.TrimSpace(scopes)
if scopes == "" {
scopes = model.OIDC_DEFAULT_SCOPES
}
return strings.Split(scopes, ",")
}
func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.OauthList) { func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.OauthList) {
res = &model.OauthList{} res = &model.OauthList{}
res.Page = int64(page) res.Page = int64(page)
@@ -329,16 +366,95 @@ func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
return return
} }
// GetTypeByOp 根据op获取OauthType
func (os *OauthService) GetTypeByOp(op string) (error, string) {
oauthInfo := &model.Oauth{}
if global.DB.Where("op = ?", op).First(oauthInfo).Error != nil {
return fmt.Errorf("OAuth provider with op '%s' not found", op), ""
}
return nil, oauthInfo.OauthType
}
// ValidateOauthProvider 验证Oauth提供者是否正确
func (os *OauthService) ValidateOauthProvider(op string) error {
if !os.IsOauthProviderExist(op) {
return fmt.Errorf("OAuth provider with op '%s' not found", op)
}
return nil
}
// IsOauthProviderExist 验证Oauth提供者是否存在
func (os *OauthService) IsOauthProviderExist(op string) bool {
oauthInfo := &model.Oauth{}
// 使用 Gorm 的 Take 方法查找符合条件的记录
if err := global.DB.Where("op = ?", op).Take(oauthInfo).Error; err != nil {
return false
}
return true
}
// Create 创建 // Create 创建
func (os *OauthService) Create(u *model.Oauth) error { func (os *OauthService) Create(oauthInfo *model.Oauth) error {
res := global.DB.Create(u).Error err := oauthInfo.FormatOauthInfo()
if err != nil {
return err
}
res := global.DB.Create(oauthInfo).Error
return res return res
} }
func (os *OauthService) Delete(u *model.Oauth) error { func (os *OauthService) Delete(oauthInfo *model.Oauth) error {
return global.DB.Delete(u).Error return global.DB.Delete(oauthInfo).Error
} }
// Update 更新 // Update 更新
func (os *OauthService) Update(u *model.Oauth) error { func (os *OauthService) Update(oauthInfo *model.Oauth) error {
return global.DB.Model(u).Updates(u).Error err := oauthInfo.FormatOauthInfo()
if err != nil {
return err
}
return global.DB.Model(oauthInfo).Updates(oauthInfo).Error
}
// GetOauthProviders 获取所有的provider
func (os *OauthService) GetOauthProviders() []string {
var res []string
global.DB.Model(&model.Oauth{}).Pluck("op", &res)
return res
}
// getGithubPrimaryEmail: Get the primary email of the user from Github
func (os *OauthService) getGithubPrimaryEmail(client *http.Client, githubUser *model.GithubUser) error {
// the client is already set with the token
resp, err := client.Get("https://api.github.com/user/emails")
if err != nil {
return fmt.Errorf("failed to fetch emails: %w", err)
}
defer resp.Body.Close()
// check the response status code
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch emails: %s", resp.Status)
}
// decode the response
var emails []struct {
Email string `json:"email"`
Primary bool `json:"primary"`
Verified bool `json:"verified"`
}
if err := json.NewDecoder(resp.Body).Decode(&emails); err != nil {
return fmt.Errorf("failed to decode response: %w", err)
}
// find the primary verified email
for _, e := range emails {
if e.Primary && e.Verified {
githubUser.Email = e.Email
githubUser.VerifiedEmail = e.Verified
return nil
}
}
return fmt.Errorf("no primary verified email found")
} }

View File

@@ -26,15 +26,45 @@ func (ps *PeerService) InfoByRowId(id uint) *model.Peer {
return p return p
} }
// FindByUserIdAndUuid 根据用户id和uuid查找peer
func (ps *PeerService) FindByUserIdAndUuid(uuid string, userId uint) *model.Peer {
p := &model.Peer{}
global.DB.Where("uuid = ? and user_id = ?", uuid, userId).First(p)
return p
}
// UuidBindUserId 绑定用户id // UuidBindUserId 绑定用户id
func (ps *PeerService) UuidBindUserId(uuid string, userId uint) { func (ps *PeerService) UuidBindUserId(deviceId string, uuid string, userId uint) {
peer := ps.FindByUuid(uuid) peer := ps.FindByUuid(uuid)
// 如果存在则更新
if peer.RowId > 0 { if peer.RowId > 0 {
peer.UserId = userId peer.UserId = userId
ps.Update(peer) ps.Update(peer)
} else {
// 不存在则创建
/*if deviceId != "" {
global.DB.Create(&model.Peer{
Id: deviceId,
Uuid: uuid,
UserId: userId,
})
}*/
} }
} }
// UuidUnbindUserId 解绑用户id, 用于用户注销
func (ps *PeerService) UuidUnbindUserId(uuid string, userId uint) {
peer := ps.FindByUserIdAndUuid(uuid, userId)
if peer.RowId > 0 {
global.DB.Model(peer).Update("user_id", 0)
}
}
// EraseUserId 清除用户id, 用于用户删除
func (ps *PeerService) EraseUserId(userId uint) error {
return global.DB.Model(&model.Peer{}).Where("user_id = ?", userId).Update("user_id", 0).Error
}
// ListByUserIds 根据用户id取列表 // ListByUserIds 根据用户id取列表
func (ps *PeerService) ListByUserIds(userIds []uint, page, pageSize uint) (res *model.PeerList) { func (ps *PeerService) ListByUserIds(userIds []uint, page, pageSize uint) (res *model.PeerList) {
res = &model.PeerList{} res = &model.PeerList{}
@@ -62,18 +92,53 @@ func (ps *PeerService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *
return return
} }
// ListFilterByUserId 根据用户id过滤Peer列表
func (ps *PeerService) ListFilterByUserId(page, pageSize uint, where func(tx *gorm.DB), userId uint) (res *model.PeerList) {
userWhere := func(tx *gorm.DB) {
tx.Where("user_id = ?", userId)
// 如果还有额外的筛选条件,执行它
if where != nil {
where(tx)
}
}
return ps.List(page, pageSize, userWhere)
}
// Create 创建 // Create 创建
func (ps *PeerService) Create(u *model.Peer) error { func (ps *PeerService) Create(u *model.Peer) error {
res := global.DB.Create(u).Error res := global.DB.Create(u).Error
return res return res
} }
// Delete 删除, 同时也应该删除token
func (ps *PeerService) Delete(u *model.Peer) error { func (ps *PeerService) Delete(u *model.Peer) error {
return global.DB.Delete(u).Error uuid := u.Uuid
err := global.DB.Delete(u).Error
if err != nil {
return err
}
// 删除token
return AllService.UserService.FlushTokenByUuid(uuid)
} }
// BatchDelete // GetUuidListByIDs 根据ids获取uuid列表
func (ps *PeerService) GetUuidListByIDs(ids []uint) ([]string, error) {
var uuids []string
err := global.DB.Model(&model.Peer{}).
Where("row_id in (?)", ids).
Pluck("uuid", &uuids).Error
return uuids, err
}
// BatchDelete 批量删除, 同时也应该删除token
func (ps *PeerService) BatchDelete(ids []uint) error { func (ps *PeerService) BatchDelete(ids []uint) error {
return global.DB.Where("row_id in (?)", ids).Delete(&model.Peer{}).Error uuids, err := ps.GetUuidListByIDs(ids)
err = global.DB.Where("row_id in (?)", ids).Delete(&model.Peer{}).Error
if err != nil {
return err
}
// 删除token
return AllService.UserService.FlushTokenByUuids(uuids)
} }
// Update 更新 // Update 更新

View File

@@ -5,10 +5,12 @@ import (
adResp "Gwen/http/response/admin" adResp "Gwen/http/response/admin"
"Gwen/model" "Gwen/model"
"Gwen/utils" "Gwen/utils"
"errors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
"math/rand" "math/rand"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -21,12 +23,21 @@ func (us *UserService) InfoById(id uint) *model.User {
global.DB.Where("id = ?", id).First(u) global.DB.Where("id = ?", id).First(u)
return u return u
} }
// InfoByUsername 根据用户名取用户信息
func (us *UserService) InfoByUsername(un string) *model.User { func (us *UserService) InfoByUsername(un string) *model.User {
u := &model.User{} u := &model.User{}
global.DB.Where("username = ?", un).First(u) global.DB.Where("username = ?", un).First(u)
return u return u
} }
// InfoByEmail 根据邮箱取用户信息
func (us *UserService) InfoByEmail(email string) *model.User {
u := &model.User{}
global.DB.Where("email = ?", email).First(u)
return u
}
// InfoByOpenid 根据openid取用户信息 // InfoByOpenid 根据openid取用户信息
func (us *UserService) InfoByOpenid(openid string) *model.User { func (us *UserService) InfoByOpenid(openid string) *model.User {
u := &model.User{} u := &model.User{}
@@ -42,18 +53,18 @@ func (us *UserService) InfoByUsernamePassword(username, password string) *model.
} }
// InfoByAccesstoken 根据accesstoken取用户信息 // InfoByAccesstoken 根据accesstoken取用户信息
func (us *UserService) InfoByAccessToken(token string) *model.User { func (us *UserService) InfoByAccessToken(token string) (*model.User, *model.UserToken) {
u := &model.User{} u := &model.User{}
ut := &model.UserToken{} ut := &model.UserToken{}
global.DB.Where("token = ?", token).First(ut) global.DB.Where("token = ?", token).First(ut)
if ut.Id == 0 { if ut.Id == 0 {
return u return u, ut
} }
if ut.ExpiredAt < time.Now().Unix() { if ut.ExpiredAt < time.Now().Unix() {
return u return u, ut
} }
global.DB.Where("id = ?", ut.UserId).First(u) global.DB.Where("id = ?", ut.UserId).First(u)
return u return u, ut
} }
// GenerateToken 生成token // GenerateToken 生成token
@@ -65,14 +76,17 @@ func (us *UserService) GenerateToken(u *model.User) string {
func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserToken { func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserToken {
token := us.GenerateToken(u) token := us.GenerateToken(u)
ut := &model.UserToken{ ut := &model.UserToken{
UserId: u.Id, UserId: u.Id,
Token: token, Token: token,
ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(), DeviceUuid: llog.Uuid,
DeviceId: llog.DeviceId,
ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(),
} }
global.DB.Create(ut) global.DB.Create(ut)
llog.UserTokenId = ut.UserId
global.DB.Create(llog) global.DB.Create(llog)
if llog.Uuid != "" { if llog.Uuid != "" {
AllService.PeerService.UuidBindUserId(llog.Uuid, u.Id) AllService.PeerService.UuidBindUserId(llog.DeviceId, llog.Uuid, u.Id)
} }
return ut return ut
} }
@@ -139,21 +153,88 @@ func (us *UserService) CheckUserEnable(u *model.User) bool {
// Create 创建 // Create 创建
func (us *UserService) Create(u *model.User) error { func (us *UserService) Create(u *model.User) error {
// The initial username should be formatted, and the username should be unique
u.Username = us.formatUsername(u.Username)
u.Password = us.EncryptPassword(u.Password) u.Password = us.EncryptPassword(u.Password)
res := global.DB.Create(u).Error res := global.DB.Create(u).Error
return res return res
} }
// Logout 退出登录 // GetUuidByToken 根据token和user取uuid
func (us *UserService) Logout(u *model.User, token string) error { func (us *UserService) GetUuidByToken(u *model.User, token string) string {
return global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error ut := &model.UserToken{}
err := global.DB.Where("user_id = ? and token = ?", u.Id, token).First(ut).Error
if err != nil {
return ""
}
return ut.DeviceUuid
} }
// Logout 退出登录 -> 删除token, 解绑uuid
func (us *UserService) Logout(u *model.User, token string) error {
uuid := us.GetUuidByToken(u, token)
err := global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error
if err != nil {
return err
}
if uuid != "" {
AllService.PeerService.UuidUnbindUserId(uuid, u.Id)
}
return nil
}
// Delete 删除用户和oauth信息
func (us *UserService) Delete(u *model.User) error { func (us *UserService) Delete(u *model.User) error {
return global.DB.Delete(u).Error userCount := us.getAdminUserCount()
if userCount <= 1 && us.IsAdmin(u) {
return errors.New("The last admin user cannot be deleted")
}
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()
// 删除关联的peer
if err := AllService.PeerService.EraseUserId(u.Id); err != nil {
global.Logger.Warn("User deleted successfully, but failed to unlink peer.")
return nil
}
return nil
} }
// Update 更新 // Update 更新
func (us *UserService) Update(u *model.User) error { func (us *UserService) Update(u *model.User) error {
currentUser := us.InfoById(u.Id)
// 如果当前用户是管理员并且 IsAdmin 不为空,进行检查
if us.IsAdmin(currentUser) {
adminCount := us.getAdminUserCount()
// 如果这是唯一的管理员,确保不能禁用或取消管理员权限
if adminCount <= 1 && (!us.IsAdmin(u) || u.Status == model.COMMON_STATUS_DISABLED) {
return errors.New("The last admin user cannot be disabled or demoted")
}
}
return global.DB.Model(u).Updates(u).Error return global.DB.Model(u).Updates(u).Error
} }
@@ -162,6 +243,16 @@ func (us *UserService) FlushToken(u *model.User) error {
return global.DB.Where("user_id = ?", u.Id).Delete(&model.UserToken{}).Error return global.DB.Where("user_id = ?", u.Id).Delete(&model.UserToken{}).Error
} }
// FlushTokenByUuid 清空token
func (us *UserService) FlushTokenByUuid(uuid string) error {
return global.DB.Where("device_uuid = ?", uuid).Delete(&model.UserToken{}).Error
}
// FlushTokenByUuids 清空token
func (us *UserService) FlushTokenByUuids(uuids []string) error {
return global.DB.Where("device_uuid in (?)", uuids).Delete(&model.UserToken{}).Error
}
// UpdatePassword 更新密码 // UpdatePassword 更新密码
func (us *UserService) UpdatePassword(u *model.User, password string) error { func (us *UserService) UpdatePassword(u *model.User, password string) error {
u.Password = us.EncryptPassword(password) u.Password = us.EncryptPassword(password)
@@ -186,19 +277,9 @@ func (us *UserService) RouteNames(u *model.User) []string {
return adResp.UserRouteNames return adResp.UserRouteNames
} }
// InfoByGithubId 根据githubid取用户信息 // InfoByOauthId 根据oauth的name和openId取用户信息
func (us *UserService) InfoByGithubId(githubId string) *model.User { func (us *UserService) InfoByOauthId(op string, openId string) *model.User {
return us.InfoByOauthId(model.OauthTypeGithub, githubId) ut := AllService.OauthService.UserThirdInfo(op, openId)
}
// 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 { if ut.Id == 0 {
return nil return nil
} }
@@ -209,45 +290,53 @@ func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
return u 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 注册 // RegisterByOauth 注册
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User { func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (error, *model.User) {
tx := global.DB.Begin() global.Lock.Lock("registerByOauth")
ut := &model.UserThird{ defer global.Lock.UnLock("registerByOauth")
OpenId: uid, ut := AllService.OauthService.UserThirdInfo(op, oauthUser.OpenId)
ThirdName: thirdName, if ut.Id != 0 {
ThirdType: thirdType, return nil, us.InfoById(ut.UserId)
}
err, oauthType := AllService.OauthService.GetTypeByOp(op)
if err != nil {
return err, nil
}
//check if this email has been registered
email := oauthUser.Email
// only email is not empty
if email != "" {
email = strings.ToLower(email)
// update email to oauthUser, in case it contain upper case
oauthUser.Email = email
user := us.InfoByEmail(email)
if user.Id != 0 {
ut.FromOauthUser(user.Id, oauthUser, oauthType, op)
global.DB.Create(ut)
return nil, user
}
} }
//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) tx := global.DB.Begin()
u := &model.User{ ut = &model.UserThird{}
Username: username, ut.FromOauthUser(0, oauthUser, oauthType, op)
// The initial username should be formatted
username := us.formatUsername(oauthUser.Username)
usernameUnique := us.GenerateUsernameByOauth(username)
user := &model.User{
Username: usernameUnique,
GroupId: 1, GroupId: 1,
} }
global.DB.Create(u) oauthUser.ToUser(user, false)
tx.Create(user)
ut.UserId = u.Id if user.Id == 0 {
global.DB.Create(ut) tx.Rollback()
return errors.New("OauthRegisterFailed"), user
}
ut.UserId = user.Id
tx.Create(ut)
tx.Commit() tx.Commit()
return u return nil, user
} }
// GenerateUsernameByOauth 生成用户名 // GenerateUsernameByOauth 生成用户名
@@ -269,7 +358,7 @@ func (us *UserService) UserThirdsByUserId(userId uint) (res []*model.UserThird)
func (us *UserService) UserThirdInfo(userId uint, op string) *model.UserThird { func (us *UserService) UserThirdInfo(userId uint, op string) *model.UserThird {
ut := &model.UserThird{} ut := &model.UserThird{}
global.DB.Where("user_id = ? and third_type = ?", userId, op).First(ut) global.DB.Where("user_id = ? and op = ?", userId, op).First(ut)
return ut return ut
} }
@@ -279,3 +368,93 @@ func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string) uint {
global.DB.Where("uuid = ?", uuid).Order("id desc").First(llog) global.DB.Where("uuid = ?", uuid).Order("id desc").First(llog)
return llog.UserId 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)
}
// Register 注册
func (us *UserService) Register(username string, email string, password string) *model.User {
u := &model.User{
Username: username,
Email: email,
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
}
// Helper functions, used for formatting username
func (us *UserService) formatUsername(username string) string {
username = strings.ReplaceAll(username, " ", "")
username = strings.ToLower(username)
return username
}
// Helper functions, getUserCount
func (us *UserService) getUserCount() int64 {
var count int64
global.DB.Model(&model.User{}).Count(&count)
return count
}
// helper functions, getAdminUserCount
func (us *UserService) getAdminUserCount() int64 {
var count int64
global.DB.Model(&model.User{}).Where("is_admin = ?", true).Count(&count)
return count
}
func (us *UserService) RefreshAccessToken(ut *model.UserToken) {
ut.ExpiredAt = time.Now().Add(time.Hour * 24 * 7).Unix()
global.DB.Model(ut).Update("expired_at", ut.ExpiredAt)
}
func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
if ut.ExpiredAt-time.Now().Unix() < 86400 {
us.RefreshAccessToken(ut)
}
}

View File

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