Compare commits

...

86 Commits

Author SHA1 Message Date
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
ljw
90b9b5adba up 2024-10-28 14:54:00 +08:00
ljw
5bf4bbe45f add address book name &
add share address book
2024-10-28 14:51:07 +08:00
ljw
036f928fa3 up readme 2024-10-23 11:09:28 +08:00
ljw
94e7b31fb6 fix group 2024-10-23 11:09:13 +08:00
74 changed files with 6244 additions and 864 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
- Web Admin
- 用户管理
@@ -92,7 +92,7 @@
#### 登录
- 添加了`github``google`授权登录需要在后台配置好就可以用了具体可看后台OAuth配置
- 添加了`github`, `google` 以及`OIDC`授权登录需要在后台配置好就可以用了具体可看后台OAuth配置
- 添加了web后台授权登录,点击后直接登录后台就自动登录客户端了
![pc_login](docs/pc_login.png)
@@ -121,11 +121,13 @@
3. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组`
![web_admin_gr](docs/web_admin_gr.png)
4. You can directly launch the client, or open the web client for convenient use; you can also share it with guests, allowing them to remotely access the device through the web client.
4. 可以直接打开webclient,方便使用;也可以分享给游客,游客可以直接通过webclient远程到设备
![web_webclient](docs/admin_webclient.png)
5. Oauth,暂时只支持了`Github``Google`, 需要创建一个`OAuth App`,然后配置到后台
5. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台
![web_admin_oauth](docs/web_admin_oauth.png)
- 对于`Google` 和 `Github`, `Issuer` 和 `Scopes`不需要填写.
- 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email` 和`preferred_username`
- `github oauth app`在`Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
- `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback`
@@ -157,6 +159,7 @@
lang: "en"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
@@ -194,6 +197,7 @@ proxy:
| TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----------GORM配置---------------- | ------------------------------------ | --------------------------- |

View File

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

38
build.sh Normal file → Executable file
View File

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

View File

@@ -14,6 +14,9 @@ import (
"fmt"
"github.com/go-redis/redis/v8"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/spf13/cobra"
"os"
"strconv"
)
// @title 管理系统API
@@ -26,9 +29,79 @@ import (
// @securitydefinitions.apikey BearerAuth
// @in header
// @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() {
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{
@@ -94,14 +167,9 @@ func main() {
//locker
global.Lock = lock.NewLocal()
//gin
http.ApiInit()
}
func DatabaseAutoUpdate() {
version := 235
version := 246
db := global.DB
@@ -146,6 +214,24 @@ func DatabaseAutoUpdate() {
if v.Version < 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")
}
}
}
@@ -165,6 +251,8 @@ func Migrate(version uint) {
&model.ShareRecord{},
&model.AuditConn{},
&model.AuditFile{},
&model.AddressBookCollection{},
&model.AddressBookCollectionRule{},
)
if err != nil {
fmt.Println("migrate err :=>", err)

View File

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

View File

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

View File

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

View File

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

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
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
- ./data/rustdesk/api:/app/data # database
# - ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源
restart: unless-stopped

35
docker-dev.sh Executable file
View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,8 @@ definitions:
properties:
alias:
type: string
collection_id:
type: integer
forceAlwaysRelay:
type: boolean
hash:
@@ -82,6 +84,10 @@ definitions:
type: object
admin.LoginPayload:
properties:
avatar:
type: string
email:
type: string
nickname:
type: string
route_names:
@@ -103,14 +109,20 @@ definitions:
type: string
id:
type: integer
issuer:
type: string
oauth_type:
type: string
op:
type: string
redirect_url:
type: string
scopes:
type: string
required:
- client_id
- client_secret
- op
- oauth_type
- redirect_url
type: object
admin.PeerBatchDeleteForm:
@@ -164,6 +176,8 @@ definitions:
type: object
admin.TagForm:
properties:
collection_id:
type: integer
color:
type: integer
id:
@@ -180,6 +194,9 @@ definitions:
properties:
avatar:
type: string
email:
description: validate:"required,email" email不强制
type: string
group_id:
type: integer
id:
@@ -195,7 +212,7 @@ definitions:
minimum: 0
username:
maxLength: 10
minLength: 4
minLength: 2
type: string
required:
- group_id
@@ -204,10 +221,10 @@ definitions:
type: object
admin.UserOauthItem:
properties:
op:
type: string
status:
type: integer
third_type:
type: string
type: object
admin.UserPasswordForm:
properties:
@@ -225,6 +242,10 @@ definitions:
properties:
alias:
type: string
collection:
$ref: '#/definitions/model.AddressBookCollection'
collection_id:
type: integer
created_at:
type: string
forceAlwaysRelay:
@@ -262,6 +283,64 @@ definitions:
username:
type: string
type: object
model.AddressBookCollection:
properties:
created_at:
type: string
id:
type: integer
name:
type: string
updated_at:
type: string
user_id:
type: integer
required:
- name
type: object
model.AddressBookCollectionList:
properties:
list:
items:
$ref: '#/definitions/model.AddressBookCollection'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
type: object
model.AddressBookCollectionRule:
properties:
collection_id:
type: integer
created_at:
type: string
id:
type: integer
rule:
description: '0: 无 1: 读 2: 读写 3: 完全控制'
maximum: 3
minimum: 1
type: integer
to_id:
type: integer
type:
description: '1: 个人 2: 群组'
maximum: 2
minimum: 1
type: integer
updated_at:
type: string
user_id:
type: integer
required:
- collection_id
- rule
- to_id
- type
type: object
model.AddressBookList:
properties:
list:
@@ -392,6 +471,8 @@ definitions:
type: string
created_at:
type: string
device_id:
type: string
id:
type: integer
ip:
@@ -406,6 +487,8 @@ definitions:
type: string
user_id:
type: integer
user_token_id:
type: integer
uuid:
type: string
type: object
@@ -434,10 +517,16 @@ definitions:
type: string
id:
type: integer
issuer:
type: string
oauth_type:
type: string
op:
type: string
redirect_url:
type: string
scopes:
type: string
updated_at:
type: string
type: object
@@ -464,6 +553,8 @@ definitions:
type: string
id:
type: string
last_online_ip:
type: string
last_online_time:
type: integer
memory:
@@ -511,6 +602,10 @@ definitions:
- COMMON_STATUS_DISABLED
model.Tag:
properties:
collection:
$ref: '#/definitions/model.AddressBookCollection'
collection_id:
type: integer
color:
description: color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色,
可以转成rgba
@@ -545,6 +640,8 @@ definitions:
type: string
created_at:
type: string
email:
type: string
group_id:
type: integer
id:
@@ -573,6 +670,43 @@ definitions:
total:
type: integer
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:
properties:
code:
@@ -784,6 +918,348 @@ paths:
summary: 地址簿编辑
tags:
- 地址簿
/admin/address_book_collection/create:
post:
consumes:
- application/json
description: 创建地址簿名称
parameters:
- description: 地址簿名称信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollection'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿名称
tags:
- 地址簿名称
/admin/address_book_collection/delete:
post:
consumes:
- application/json
description: 地址簿名称删除
parameters:
- description: 地址簿名称信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollection'
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/address_book_collection/detail/{id}:
get:
consumes:
- application/json
description: 地址簿名称详情
parameters:
- description: ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿名称详情
tags:
- 地址簿名称
/admin/address_book_collection/list:
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: is_my
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.AddressBookCollectionList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿名称列表
tags:
- 地址簿名称
/admin/address_book_collection/update:
post:
consumes:
- application/json
description: 地址簿名称编辑
parameters:
- description: 地址簿名称信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollection'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿名称编辑
tags:
- 地址簿名称
/admin/address_book_collection_rule/create:
post:
consumes:
- application/json
description: 创建地址簿规则
parameters:
- description: 地址簿规则信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollectionRule'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿规则
tags:
- 地址簿规则
/admin/address_book_collection_rule/delete:
post:
consumes:
- application/json
description: 地址簿规则删除
parameters:
- description: 地址簿规则信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollectionRule'
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/address_book_collection_rule/detail/{id}:
get:
consumes:
- application/json
description: 地址簿规则详情
parameters:
- description: ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollectionRule'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿规则详情
tags:
- 地址簿规则
/admin/address_book_collection_rule/list:
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: is_my
type: integer
- description: 用户id
in: query
name: user_id
type: integer
- description: 地址簿集合id
in: query
name: collection_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollectionList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿规则列表
tags:
- 地址簿规则
/admin/address_book_collection_rule/update:
post:
consumes:
- application/json
description: 地址簿规则编辑
parameters:
- description: 地址簿规则信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollectionRule'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿规则编辑
tags:
- 地址簿规则
/admin/app-config:
get:
consumes:
@@ -1118,7 +1594,28 @@ paths:
summary: 登录
tags:
- 登录
/admin/loginLog/delete:
/admin/login-options:
post:
consumes:
- application/json
description: 登录选项
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: 登录选项
tags:
- 登录
/admin/login_log/delete:
post:
consumes:
- application/json
@@ -1146,7 +1643,7 @@ paths:
summary: 登录日志删除
tags:
- 登录日志
/admin/loginLog/detail/{id}:
/admin/login_log/detail/{id}:
get:
consumes:
- application/json
@@ -1178,7 +1675,7 @@ paths:
summary: 登录日志详情
tags:
- 登录日志
/admin/loginLog/list:
/admin/login_log/list:
get:
consumes:
- application/json
@@ -1397,6 +1894,69 @@ paths:
summary: Oauth编辑
tags:
- Oauth
/admin/oidc/auth:
post:
consumes:
- application/json
description: OidcAuth
produces:
- application/json
responses: {}
summary: OidcAuth
tags:
- Oauth
/admin/oidc/auth-query:
get:
consumes:
- application/json
description: OidcAuthQuery
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/admin.LoginPayload'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: OidcAuthQuery
tags:
- Oauth
/admin/peer/batchDelete:
post:
consumes:
- application/json
description: 批量设备删除
parameters:
- description: 设备id
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.PeerBatchDeleteForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 批量设备删除
tags:
- 设备
/admin/peer/create:
post:
consumes:
@@ -1434,14 +1994,14 @@ paths:
post:
consumes:
- application/json
description: 批量设备删除
description: 设备删除
parameters:
- description: 设备id
- description: 设备信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.PeerBatchDeleteForm'
$ref: '#/definitions/admin.PeerForm'
produces:
- application/json
responses:
@@ -1455,7 +2015,7 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 批量设备删除
summary: 设备删除
tags:
- 设备
/admin/peer/detail/{id}:
@@ -1516,6 +2076,10 @@ paths:
in: query
name: hostname
type: string
- description: uuids 用逗号分隔
in: query
name: uuids
type: string
produces:
- application/json
responses:
@@ -1974,6 +2538,57 @@ paths:
summary: 我的授权
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:
post:
consumes:
@@ -2035,6 +2650,73 @@ paths:
summary: 修改密码
tags:
- 用户
/admin/user_token/delete:
post:
consumes:
- application/json
description: 登录凭证删除
parameters:
- description: 登录凭证信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.UserToken'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录凭证删除
tags:
- 登录凭证
/admin/user_token/list:
get:
consumes:
- application/json
description: 登录凭证列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
- description: 用户ID
in: query
name: user_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.UserTokenList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录凭证列表
tags:
- 登录凭证
securityDefinitions:
BearerAuth:
in: header

View File

@@ -834,7 +834,7 @@ const docTemplateapi = `{
}
},
"/login-options": {
"post": {
"get": {
"description": "登录选项",
"consumes": [
"application/json"
@@ -1365,7 +1365,7 @@ const docTemplateapi = `{
"username": {
"type": "string",
"maxLength": 10,
"minLength": 4
"minLength": 2
},
"uuid": {
"type": "string"
@@ -1445,9 +1445,38 @@ const docTemplateapi = `{
}
}
},
"model.AddressBookCollection": {
"type": "object",
"required": [
"name"
],
"properties": {
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
}
}
},
"model.Tag": {
"type": "object",
"properties": {
"collection": {
"$ref": "#/definitions/model.AddressBookCollection"
},
"collection_id": {
"type": "integer"
},
"color": {
"description": "color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色, 可以转成rgba",
"type": "integer"

View File

@@ -827,7 +827,7 @@
}
},
"/login-options": {
"post": {
"get": {
"description": "登录选项",
"consumes": [
"application/json"
@@ -1358,7 +1358,7 @@
"username": {
"type": "string",
"maxLength": 10,
"minLength": 4
"minLength": 2
},
"uuid": {
"type": "string"
@@ -1438,9 +1438,38 @@
}
}
},
"model.AddressBookCollection": {
"type": "object",
"required": [
"name"
],
"properties": {
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
}
}
},
"model.Tag": {
"type": "object",
"properties": {
"collection": {
"$ref": "#/definitions/model.AddressBookCollection"
},
"collection_id": {
"type": "integer"
},
"color": {
"description": "color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色, 可以转成rgba",
"type": "integer"

View File

@@ -69,7 +69,7 @@ definitions:
type: string
username:
maxLength: 10
minLength: 4
minLength: 2
type: string
uuid:
type: string
@@ -124,8 +124,27 @@ definitions:
status:
type: integer
type: object
model.AddressBookCollection:
properties:
created_at:
type: string
id:
type: integer
name:
type: string
updated_at:
type: string
user_id:
type: integer
required:
- name
type: object
model.Tag:
properties:
collection:
$ref: '#/definitions/model.AddressBookCollection'
collection_id:
type: integer
color:
description: color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色,
可以转成rgba
@@ -696,7 +715,7 @@ paths:
tags:
- 登录
/login-options:
post:
get:
consumes:
- application/json
description: 登录选项

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

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

3
go.mod
View File

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

View File

@@ -70,9 +70,14 @@ func (ct *AddressBook) Create(c *gin.Context) {
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
}
ex := service.AllService.AddressBookService.InfoByUserIdAndId(t.UserId, t.Id)
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
if ex.RowId > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemExist"))
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
return
}
@@ -81,7 +86,7 @@ func (ct *AddressBook) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
response.Success(c, nil)
}
// BatchCreate 批量创建地址簿
@@ -113,7 +118,7 @@ func (ct *AddressBook) BatchCreate(c *gin.Context) {
continue
}
for _, ft := range f.Tags {
exTag := service.AllService.TagService.InfoByUserIdAndName(fu, ft)
exTag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(fu, ft, 0)
if exTag.Id == 0 {
service.AllService.TagService.Create(&model.Tag{
UserId: fu,
@@ -161,6 +166,9 @@ func (ct *AddressBook) List(c *gin.Context) {
query.UserId = int(u.Id)
}
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
})
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
@@ -173,7 +181,20 @@ func (ct *AddressBook) List(c *gin.Context) {
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
abCIds := make([]uint, 0)
for _, ab := range res.AddressBooks {
abCIds = append(abCIds, ab.CollectionId)
}
//获取地址簿名称
//cRes := service.AllService.AddressBookService.ListCollection(1, 999, func(tx *gorm.DB) {
// tx.Where("id in ?", abCIds)
//})
//
response.Success(c, res)
}
@@ -209,7 +230,11 @@ func (ct *AddressBook) Update(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.Update(t)
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AddressBookService.UpdateAll(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
@@ -241,6 +266,10 @@ func (ct *AddressBook) Delete(c *gin.Context) {
return
}
t := service.AllService.AddressBookService.InfoByRowId(f.RowId)
if t.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))

View File

@@ -0,0 +1,192 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strconv"
)
type AddressBookCollection struct {
}
// Detail 地址簿名称
// @Tags 地址簿名称
// @Summary 地址簿名称详情
// @Description 地址簿名称详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/detail/{id} [get]
// @Security token
func (abc *AddressBookCollection) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.CollectionInfoById(uint(iid))
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建地址簿名称
// @Tags 地址簿名称
// @Summary 创建地址簿名称
// @Description 创建地址簿名称
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/create [post]
// @Security token
func (abc *AddressBookCollection) Create(c *gin.Context) {
f := &model.AddressBookCollection{}
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
}
//t := f.ToAddressBookCollection()
t := f
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
}
err := service.AllService.AddressBookService.CreateCollection(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// List 列表
// @Tags 地址簿名称
// @Summary 地址簿名称列表
// @Description 地址簿名称列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param is_my query int false "是否是我的"
// @Param user_id query int false "用户id"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/list [get]
// @Security token
func (abc *AddressBookCollection) List(c *gin.Context) {
query := &admin.AddressBookCollectionQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.AddressBookService.ListCollection(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
})
response.Success(c, res)
}
// Update 编辑
// @Tags 地址簿名称
// @Summary 地址簿名称编辑
// @Description 地址簿名称编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/update [post]
// @Security token
func (abc *AddressBookCollection) Update(c *gin.Context) {
f := &model.AddressBookCollection{}
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
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f //f.ToAddressBookCollection()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.UpdateCollection(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 地址簿名称
// @Summary 地址簿名称删除
// @Description 地址簿名称删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/delete [post]
// @Security token
func (abc *AddressBookCollection) Delete(c *gin.Context) {
f := &model.AddressBookCollection{}
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
}
t := service.AllService.AddressBookService.CollectionInfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if u.Id > 0 {
err := service.AllService.AddressBookService.DeleteCollection(t)
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

@@ -0,0 +1,251 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strconv"
)
type AddressBookCollectionRule struct {
}
// List 列表
// @Tags 地址簿规则
// @Summary 地址簿规则列表
// @Description 地址簿规则列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param is_my query int false "是否是我的"
// @Param user_id query int false "用户id"
// @Param collection_id query int false "地址簿集合id"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/list [get]
// @Security token
func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
query := &admin.AddressBookCollectionRuleQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.AddressBookService.ListRules(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
if query.CollectionId > 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}
// Detail 地址簿规则
// @Tags 地址簿规则
// @Summary 地址簿规则详情
// @Description 地址簿规则详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionRule}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/detail/{id} [get]
// @Security token
func (abcr *AddressBookCollectionRule) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.RuleInfoById(uint(iid))
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建地址簿规则
// @Tags 地址簿规则
// @Summary 创建地址簿规则
// @Description 创建地址簿规则
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/create [post]
// @Security token
func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
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
}
if f.Type != model.ShareAddressBookRuleTypePersonal && f.Type != model.ShareAddressBookRuleTypeGroup {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
//t := f.ToAddressBookCollection()
t := f
u := service.AllService.UserService.CurUser(c)
if t.UserId == 0 {
t.UserId = u.Id
}
msg, res := abcr.CheckForm(u, t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
}
err := service.AllService.AddressBookService.CreateRule(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.AddressBookCollectionRule) (string, bool) {
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
return "NoAccess", false
}
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
return "ParamsError", false
}
//check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId {
return "ParamsError", false
}
tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 {
return "ItemNotFound", false
}
//非管理员不能分享给非本组织用户
if tou.GroupId != u.GroupId && !service.AllService.UserService.IsAdmin(u) {
return "NoAccess", false
}
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
if t.ToId != u.GroupId && !service.AllService.UserService.IsAdmin(u) {
return "NoAccess", false
}
tog := service.AllService.GroupService.InfoById(t.ToId)
if tog.Id == 0 {
return "ItemNotFound", false
}
} else {
return "ParamsError", false
}
// 重复检查
ex := service.AllService.AddressBookService.RulePersonalInfoByToIdAndCid(t.ToId, t.CollectionId)
if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false
}
if t.Id > 0 && ex.Id > 0 && t.Id != ex.Id {
return "ItemExists", false
}
return "", true
}
// Update 编辑
// @Tags 地址簿规则
// @Summary 地址簿规则编辑
// @Description 地址簿规则编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/update [post]
// @Security token
func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
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
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f //f.ToAddressBookCollection()
u := service.AllService.UserService.CurUser(c)
msg, res := abcr.CheckForm(u, t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
}
err := service.AllService.AddressBookService.UpdateRule(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 地址簿规则
// @Summary 地址簿规则删除
// @Description 地址簿规则删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/delete [post]
// @Security token
func (abcr *AddressBookCollectionRule) Delete(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
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
}
t := service.AllService.AddressBookService.RuleInfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.Id > 0 {
err := service.AllService.AddressBookService.DeleteRule(t)
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

@@ -40,6 +40,7 @@ func (a *Audit) ConnList(c *gin.Context) {
if query.FromPeer != "" {
tx.Where("from_peer like ?", "%"+query.FromPeer+"%")
}
tx.Order("id desc")
})
response.Success(c, res)
}
@@ -107,6 +108,7 @@ func (a *Audit) FileList(c *gin.Context) {
if query.FromPeer != "" {
tx.Where("from_peer like ?", "%"+query.FromPeer+"%")
}
tx.Order("id desc")
})
response.Success(c, res)
}

View File

@@ -63,7 +63,7 @@ func (ct *Group) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
response.Success(c, nil)
}
// List 列表

View File

@@ -2,7 +2,9 @@ package admin
import (
"Gwen/global"
"Gwen/http/controller/api"
"Gwen/http/request/admin"
apiReq "Gwen/http/request/api"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/model"
@@ -50,19 +52,14 @@ func (ct *Login) Login(c *gin.Context) {
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: "webadmin",
Uuid: "",
Client: model.LoginLogClientWebAdmin,
Uuid: "", //must be empty
Ip: c.ClientIP(),
Type: "account",
Type: model.LoginLogTypeAccount,
Platform: f.Platform,
})
response.Success(c, &adResp.LoginPayload{
Token: ut.Token,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
responseLoginSuccess(c, u, ut.Token)
}
// Logout 登出
@@ -82,3 +79,86 @@ func (ct *Login) Logout(c *gin.Context) {
}
response.Success(c, nil)
}
// LoginOptions
// @Tags 登录
// @Summary 登录选项
// @Description 登录选项
// @Accept json
// @Produce json
// @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse
// @Router /admin/login-options [post]
func (ct *Login) LoginOptions(c *gin.Context) {
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"
// @Success 200 {object} response.Response{data=model.LoginLog}
// @Failure 500 {object} response.Response
// @Router /admin/loginLog/detail/{id} [get]
// @Router /admin/login_log/detail/{id} [get]
// @Security token
func (ct *LoginLog) Detail(c *gin.Context) {
id := c.Param("id")
@@ -48,7 +48,7 @@ func (ct *LoginLog) Detail(c *gin.Context) {
// @Param user_id query int false "用户ID"
// @Success 200 {object} response.Response{data=model.LoginLogList}
// @Failure 500 {object} response.Response
// @Router /admin/loginLog/list [get]
// @Router /admin/login_log/list [get]
// @Security token
func (ct *LoginLog) List(c *gin.Context) {
query := &admin.LoginLogQuery{}
@@ -64,6 +64,7 @@ func (ct *LoginLog) List(c *gin.Context) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
tx.Order("id desc")
})
response.Success(c, res)
}
@@ -77,7 +78,7 @@ func (ct *LoginLog) List(c *gin.Context) {
// @Param body body model.LoginLog true "登录日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/loginLog/delete [post]
// @Router /admin/login_log/delete [post]
// @Security token
func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{}

View File

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

View File

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

View File

@@ -73,7 +73,7 @@ func (ct *Tag) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
response.Success(c, nil)
}
// List 列表
@@ -101,9 +101,15 @@ func (ct *Tag) List(c *gin.Context) {
query.UserId = int(u.Id)
}
res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
})
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}

View File

@@ -5,10 +5,12 @@ import (
"Gwen/http/request/admin"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strconv"
"time"
)
type User struct {
@@ -65,7 +67,7 @@ func (ct *User) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
response.Success(c, nil)
}
// List 列表
@@ -215,12 +217,7 @@ func (ct *User) Current(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
token, _ := c.Get("token")
t := token.(string)
response.Success(c, &adResp.LoginPayload{
Token: t,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
responseLoginSuccess(c, u, t)
}
// ChangeCurPwd 修改当前用户密码
@@ -247,10 +244,14 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
return
}
u := service.AllService.UserService.CurUser(c)
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
if u.Password != oldPwd {
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return
// If the password is not empty, the old password is verified
// otherwise, the old password is not verified
if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
if u.Password != oldPwd {
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return
}
}
err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
if err != nil {
@@ -281,10 +282,10 @@ func (ct *User) MyOauth(c *gin.Context) {
var res []*adResp.UserOauthItem
for _, oa := range oal.Oauths {
item := &adResp.UserOauthItem{
ThirdType: oa.Op,
Op: oa.Op,
}
for _, ut := range uts {
if ut.ThirdType == oa.Op {
if ut.Op == oa.Op {
item.Status = 1
break
}
@@ -293,3 +294,110 @@ func (ct *User) MyOauth(c *gin.Context) {
}
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
func (ct *User) GroupUsers(c *gin.Context) {
q := &admin.GroupUsersQuery{}
if err := c.ShouldBindJSON(q); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
gid := u.GroupId
uid := u.Id
if service.AllService.UserService.IsAdmin(u) && q.UserId > 0 {
nu := service.AllService.UserService.InfoById(q.UserId)
gid = nu.GroupId
uid = q.UserId
}
res := service.AllService.UserService.List(1, 999, func(tx *gorm.DB) {
tx.Where("group_id = ?", gid)
})
var data []*adResp.GroupUsersPayload
for _, _u := range res.Users {
gup := &adResp.GroupUsersPayload{}
gup.FromUser(_u)
if _u.Id == uid {
gup.Status = 0
}
data = append(data, gup)
}
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

@@ -7,10 +7,13 @@ import (
"Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"Gwen/utils"
"encoding/json"
"errors"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"strings"
)
type Ab struct {
@@ -112,6 +115,35 @@ func (a *Ab) Tags(c *gin.Context) {
c.JSON(http.StatusOK, tags.Tags)
}
// PTags
// @Tags 地址[Personal]
// @Summary 标签
// @Description 标签
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {object} model.TagList
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/tags/{guid} [post]
// @Security BearerAuth
func (a *Ab) PTags(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserReadPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tags := service.AllService.TagService.ListByUserIdAndCollectionId(uid, cid)
c.JSON(http.StatusOK, tags.Tags)
}
// TagAdd
// @Tags 地址[Personal]
// @Summary 标签添加
@@ -124,19 +156,35 @@ func (a *Ab) Tags(c *gin.Context) {
// @Router /ab/tag/add/{guid} [post]
// @Security BearerAuth
func (a *Ab) TagAdd(c *gin.Context) {
t := &model.Tag{}
err := c.ShouldBindJSON(t)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Name)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Name, cid)
if tag != nil && tag.Id != 0 {
response.Error(c, response.TranslateMsg(c, "ItemExists"))
return
}
t.UserId = u.Id
t.UserId = uid
t.CollectionId = cid
err = service.AllService.TagService.Create(t)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
@@ -157,6 +205,7 @@ func (a *Ab) TagAdd(c *gin.Context) {
// @Router /ab/tag/rename/{guid} [put]
// @Security BearerAuth
func (a *Ab) TagRename(c *gin.Context) {
t := &requstform.TagRenameForm{}
err := c.ShouldBindJSON(t)
if err != nil {
@@ -164,12 +213,25 @@ func (a *Ab) TagRename(c *gin.Context) {
return
}
u := service.AllService.UserService.CurUser(c)
tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Old)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Old, cid)
if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
ntag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.New)
ntag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.New, cid)
if ntag != nil && ntag.Id != 0 {
response.Error(c, response.TranslateMsg(c, "ItemExists"))
return
@@ -202,7 +264,20 @@ func (a *Ab) TagUpdate(c *gin.Context) {
return
}
u := service.AllService.UserService.CurUser(c)
tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Name)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Name, cid)
if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
@@ -228,6 +303,7 @@ func (a *Ab) TagUpdate(c *gin.Context) {
// @Router /ab/tag/{guid} [delete]
// @Security BearerAuth
func (a *Ab) TagDel(c *gin.Context) {
t := &[]string{}
err := c.ShouldBind(t)
if err != nil {
@@ -236,8 +312,21 @@ func (a *Ab) TagDel(c *gin.Context) {
}
//fmt.Println(t)
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserFullControlPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
for _, name := range *t {
tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, name)
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, name, cid)
if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
@@ -272,7 +361,7 @@ func (a *Ab) Personal(c *gin.Context) {
rule = json['rule'] ?? 0;
*/
if global.Config.Rustdesk.Personal == 1 {
guid := strconv.Itoa(int(user.GroupId)) + "-" + strconv.Itoa(int(user.Id))
guid := a.ComposeGuid(user.GroupId, user.Id, 0)
//如果返回了guid后面的请求会有变化
c.JSON(http.StatusOK, gin.H{
"guid": guid,
@@ -315,38 +404,139 @@ func (a *Ab) Settings(c *gin.Context) {
// @Router /ab/shared/profiles [post]
// @Security BearerAuth
func (a *Ab) SharedProfiles(c *gin.Context) {
//AbProfile.fromJson(Map<String, dynamic> json)
//: guid = json['guid'] ?? '',
// name = json['name'] ?? '',
// owner = json['owner'] ?? '',
// note = json['note'] ?? '',
// rule = json['rule'] ?? 0;
//暂时没必要返回数据,可能是为了共享地址簿
/*item := map[string]interface{}{
"guid": "1",
"name": "admin",
"owner": "admin",
"note": "admin11",
"rule": 3,
var res []*api.SharedProfilesPayload
user := service.AllService.UserService.CurUser(c)
myAbCollectionList := service.AllService.AddressBookService.ListCollectionByUserId(user.Id)
for _, ab := range myAbCollectionList.AddressBookCollection {
res = append(res, &api.SharedProfilesPayload{
Guid: a.ComposeGuid(user.GroupId, user.Id, ab.Id),
Name: ab.Name,
Owner: user.Username,
Rule: model.ShareAddressBookRuleRuleFullControl,
})
}
item2 := map[string]interface{}{
"guid": "2",
"name": "admin2",
"owner": "admin2",
"note": "admin22",
"rule": 2,
allAbIds := make(map[uint]int) //用map去重并保留最大Rule
allUserIds := make(map[uint]*model.User)
rules := service.AllService.AddressBookService.CollectionReadRules(user)
for _, rule := range rules {
//先判断是否存在
r, ok := allAbIds[rule.CollectionId]
if ok {
//再判断权限大小
if r < rule.Rule {
allAbIds[rule.CollectionId] = rule.Rule
}
} else {
allAbIds[rule.CollectionId] = rule.Rule
allUserIds[rule.UserId] = nil
}
}
abids := utils.Keys(allAbIds)
collections := service.AllService.AddressBookService.ListCollectionByIds(abids)
ids := utils.Keys(allUserIds)
allUsers := service.AllService.UserService.ListByIds(ids)
for _, u := range allUsers {
allUserIds[u.Id] = u
}
for _, collection := range collections {
_u, ok := allUserIds[collection.UserId]
if !ok {
continue
}
res = append(res, &api.SharedProfilesPayload{
Guid: a.ComposeGuid(_u.GroupId, _u.Id, collection.Id),
Name: collection.Name,
Owner: _u.Username,
Rule: allAbIds[collection.Id],
})
}
c.JSON(http.StatusOK, gin.H{
"total": 2,
"data": []interface{}{item, item2},
})*/
c.JSON(http.StatusOK, gin.H{
"total": 0,
"data": nil,
"total": 0, //len(res),
"data": res,
})
}
// ParseGuid
func (a *Ab) ParseGuid(guid string) (gid, uid, cid uint) {
//用-切割 guid
guids := strings.Split(guid, "-")
if len(guids) < 2 {
return 0, 0, 0
}
if len(guids) != 3 {
cid = 0
} else {
s, err := strconv.Atoi(guids[2])
if err != nil {
return 0, 0, 0
}
cid = uint(s)
}
g, err := strconv.Atoi(guids[0])
if err != nil {
return 0, 0, 0
}
gid = uint(g)
u, err := strconv.Atoi(guids[1])
if err != nil {
return 0, 0, 0
}
uid = uint(u)
return
}
// ComposeGuid
func (a *Ab) ComposeGuid(gid, uid, cid uint) string {
return strconv.Itoa(int(gid)) + "-" + strconv.Itoa(int(uid)) + "-" + strconv.Itoa(int(cid))
}
// CheckGuid
func (a *Ab) CheckGuid(cu *model.User, guid string) (gid, uid, cid uint, err error) {
gid, uid, cid = a.ParseGuid(guid)
err = nil
if gid == 0 || uid == 0 {
err = errors.New("ParamsError")
return
}
u := &model.User{}
if cu.Id == uid {
u = cu
} else {
u = service.AllService.UserService.InfoById(uid)
}
if u == nil || u.Id == 0 {
err = errors.New("ParamsError")
return
}
if u.GroupId != gid {
err = errors.New("ParamsError")
return
}
if cid == 0 && cu.Id != uid {
err = errors.New("ParamsError")
return
}
if cid > 0 {
c := service.AllService.AddressBookService.CollectionInfoById(cid)
if c == nil || c.Id == 0 {
err = errors.New("ParamsError")
return
}
if c.UserId != uid {
err = errors.New("ParamsError")
return
}
}
return
}
// Peers
// @Tags 地址[Personal]
// @Summary 地址列表
@@ -361,8 +551,21 @@ func (a *Ab) SharedProfiles(c *gin.Context) {
// @Router /ab/peers [post]
// @Security BearerAuth
func (a *Ab) Peers(c *gin.Context) {
user := service.AllService.UserService.CurUser(c)
al := service.AllService.AddressBookService.ListByUserId(user.Id, 1, 1000)
u := service.AllService.UserService.CurUser(c)
guid := c.Query("ab")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserReadPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
al := service.AllService.AddressBookService.ListByUserIdAndCollectionId(uid, cid, 1, 1000)
c.JSON(http.StatusOK, gin.H{
"total": al.Total,
"data": al.AddressBooks,
@@ -370,24 +573,6 @@ func (a *Ab) Peers(c *gin.Context) {
})
}
// PTags
// @Tags 地址[Personal]
// @Summary 标签
// @Description 标签
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {object} model.TagList
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/tags/{guid} [post]
// @Security BearerAuth
func (a *Ab) PTags(c *gin.Context) {
user := service.AllService.UserService.CurUser(c)
tags := service.AllService.TagService.ListByUserId(user.Id)
c.JSON(http.StatusOK, tags.Tags)
}
// PeerAdd
// @Tags 地址[Personal]
// @Summary 添加地址
@@ -402,18 +587,31 @@ func (a *Ab) PTags(c *gin.Context) {
func (a *Ab) PeerAdd(c *gin.Context) {
// forceAlwaysRelay永远是字符串"false"
//f := &gin.H{}
//guid := c.Param("guid")
f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//fmt.Println(f)
u := service.AllService.UserService.CurUser(c)
f.UserId = u.Id
ab := f.ToAddressBook()
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
//fmt.Println(f)
f.UserId = uid
ab := f.ToAddressBook()
ab.CollectionId = cid
if ab.Platform == "" || ab.Username == "" || ab.Hostname == "" {
peer := service.AllService.PeerService.FindById(ab.Id)
if peer.RowId != 0 {
@@ -450,8 +648,21 @@ func (a *Ab) PeerDel(c *gin.Context) {
return
}
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserFullControlPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
for _, id := range *f {
ab := service.AllService.AddressBookService.InfoByUserIdAndId(u.Id, id)
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, id, cid)
if ab == nil || ab.RowId == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
@@ -478,24 +689,53 @@ func (a *Ab) PeerDel(c *gin.Context) {
// @Router /ab/peer/update/{guid} [put]
// @Security BearerAuth
func (a *Ab) PeerUpdate(c *gin.Context) {
//f := &gin.H{}
f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f)
f := gin.H{}
//f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(&f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//fmt.Println(f)
//return
u := service.AllService.UserService.CurUser(c)
ab := service.AllService.AddressBookService.InfoByUserIdAndId(u.Id, f.Id)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
//fmt.Println(f)
//判断f["Id"]是否存在
fid, ok := f["id"]
if !ok {
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
fidstr := fid.(string)
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, fidstr, cid)
if ab == nil || ab.RowId == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
nab := f.ToAddressBook()
nab.RowId = ab.RowId
err = service.AllService.AddressBookService.Update(nab)
//允许的字段
allowUp := []string{"password", "hash", "tags", "alias"}
//f中的字段如果不在allowUp中就删除
for k := range f {
if !utils.InArray(k, allowUp) {
delete(f, k)
}
}
//fmt.Println(f)
if tags, _ok := f["tags"]; _ok {
f["tags"], _ = json.Marshal(tags)
}
err = service.AllService.AddressBookService.UpdateByMap(ab, f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return

View File

@@ -28,23 +28,23 @@ type Group struct {
// @Router /users [get]
// @Security BearerAuth
func (g *Group) Users(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
if !*u.IsAdmin {
gr := service.AllService.GroupService.InfoById(u.GroupId)
if gr.Type != model.GroupTypeShare {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
}
q := &apiReq.UserListQuery{}
err := c.ShouldBindQuery(&q)
if err != nil {
response.Error(c, err.Error())
return
}
userList := service.AllService.UserService.ListByGroupId(u.GroupId, q.Page, q.PageSize)
u := service.AllService.UserService.CurUser(c)
gr := service.AllService.GroupService.InfoById(u.GroupId)
userList := &model.UserList{}
if !*u.IsAdmin && gr.Type != model.GroupTypeShare {
//仅能获取到自己
userList.Users = append(userList.Users, u)
userList.Total = 1
} else {
userList = service.AllService.UserService.ListByGroupId(u.GroupId, q.Page, q.PageSize)
}
var data []*apiResp.UserPayload
for _, user := range userList.Users {
up := &apiResp.UserPayload{}
@@ -73,23 +73,21 @@ func (g *Group) Users(c *gin.Context) {
// @Security BearerAuth
func (g *Group) Peers(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
if !*u.IsAdmin {
gr := service.AllService.GroupService.InfoById(u.GroupId)
if gr.Type != model.GroupTypeShare {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
}
q := &apiReq.PeerListQuery{}
err := c.ShouldBindQuery(&q)
if err != nil {
response.Error(c, err.Error())
return
}
gr := service.AllService.GroupService.InfoById(u.GroupId)
users := make([]*model.User, 0, 1)
if !*u.IsAdmin && gr.Type != model.GroupTypeShare {
//仅能获取到自己
users = append(users, u)
} else {
users = service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId)
}
users := service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId)
namesById := make(map[uint]string)
userIds := make([]uint, 0)
for _, user := range users {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,6 +23,7 @@ type AddressBookForm struct {
Online bool `json:"online"`
LoginName string `json:"loginName" `
SameServer bool `json:"sameServer"`
CollectionId uint `json:"collection_id"`
}
func (a AddressBookForm) ToAddressBook() *model.AddressBook {
@@ -46,6 +47,7 @@ func (a AddressBookForm) ToAddressBook() *model.AddressBook {
Online: a.Online,
LoginName: a.LoginName,
SameServer: a.SameServer,
CollectionId: a.CollectionId,
}
}
@@ -72,17 +74,19 @@ func (a AddressBookForm) ToAddressBooks() []*model.AddressBook {
Online: a.Online,
LoginName: a.LoginName,
SameServer: a.SameServer,
CollectionId: a.CollectionId,
})
}
return abs
}
type AddressBookQuery struct {
UserId int `form:"user_id"`
IsMy int `form:"is_my"`
Username string `form:"username"`
Hostname string `form:"hostname"`
Id string `form:"id"`
UserId int `form:"user_id"`
CollectionId *int `form:"collection_id"`
IsMy int `form:"is_my"`
Username string `form:"username"`
Hostname string `form:"hostname"`
Id string `form:"id"`
PageQuery
}
@@ -102,3 +106,19 @@ func (sbwcf ShareByWebClientForm) ToShareRecord() *model.ShareRecord {
Expire: sbwcf.Expire,
}
}
type AddressBookCollectionQuery struct {
UserId int `form:"user_id"`
IsMy int `form:"is_my"`
PageQuery
}
type AddressBookCollectionSimpleListQuery struct {
UserIds []uint `form:"user_ids"`
}
type AddressBookCollectionRuleQuery struct {
UserId int `form:"user_id"`
CollectionId int `form:"collection_id"`
IsMy int `form:"is_my"`
PageQuery
}

View File

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

View File

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

View File

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

View File

@@ -3,10 +3,11 @@ package admin
import "Gwen/model"
type TagForm struct {
Id uint `json:"id"`
Name string `json:"name" validate:"required"`
Color uint `json:"color" validate:"required"`
UserId uint `json:"user_id"`
Id uint `json:"id"`
Name string `json:"name" validate:"required"`
Color uint `json:"color" validate:"required"`
UserId uint `json:"user_id"`
CollectionId uint `json:"collection_id"`
}
func (f *TagForm) FromTag(group *model.Tag) *TagForm {
@@ -14,6 +15,7 @@ func (f *TagForm) FromTag(group *model.Tag) *TagForm {
f.Name = group.Name
f.Color = group.Color
f.UserId = group.UserId
f.CollectionId = group.CollectionId
return f
}
@@ -23,11 +25,13 @@ func (f *TagForm) ToTag() *model.Tag {
i.Name = f.Name
i.Color = f.Color
i.UserId = f.UserId
i.CollectionId = f.CollectionId
return i
}
type TagQuery struct {
UserId int `form:"user_id"`
IsMy int `form:"is_my"`
UserId int `form:"user_id"`
IsMy int `form:"is_my"`
CollectionId *int `form:"collection_id"`
PageQuery
}

View File

@@ -6,7 +6,8 @@ import (
type UserForm struct {
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"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
@@ -19,6 +20,7 @@ func (uf *UserForm) FromUser(user *model.User) *UserForm {
uf.Id = user.Id
uf.Username = user.Username
uf.Nickname = user.Nickname
uf.Email = user.Email
uf.Avatar = user.Avatar
uf.GroupId = user.GroupId
uf.IsAdmin = user.IsAdmin
@@ -30,6 +32,7 @@ func (uf *UserForm) ToUser() *model.User {
user.Id = uf.Id
user.Username = uf.Username
user.Nickname = uf.Nickname
user.Email = uf.Email
user.Avatar = uf.Avatar
user.GroupId = uf.GroupId
user.IsAdmin = uf.IsAdmin
@@ -55,3 +58,14 @@ type ChangeCurPasswordForm struct {
OldPassword string `json:"old_password" validate:"required,gte=4,lte=20"`
NewPassword string `json:"new_password" validate:"required,gte=4,lte=20"`
}
type GroupUsersQuery struct {
IsMy int `json:"is_my"`
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"`
Type string `json:"type" label:"type"`
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:"密码"`
}

View File

@@ -1,18 +1,41 @@
package admin
import "Gwen/model"
type LoginPayload struct {
Username string `json:"username"`
Email string `json:"email"`
Avatar string `json:"avatar"`
Token string `json:"token"`
RouteNames []string `json:"route_names"`
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{
"MyTagList", "MyAddressBookList", "MyInfo",
"MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection", "MyPeer",
}
var AdminRouteNames = []string{"*"}
type UserOauthItem struct {
ThirdType string `json:"third_type"`
Status int `json:"status"`
Op string `json:"op"`
Status int `json:"status"`
}
type GroupUsersPayload struct {
Id uint `json:"id"`
Username string `json:"username"`
Status int `json:"status"`
}
func (g *GroupUsersPayload) FromUser(user *model.User) {
g.Id = user.Id
g.Username = user.Username
g.Status = 1
}

View File

@@ -7,3 +7,11 @@ type AbList struct {
Tags []string `json:"tags,omitempty"`
TagColors string `json:"tag_colors,omitempty"`
}
type SharedProfilesPayload struct {
Guid string `json:"guid"`
Name string `json:"name"`
Owner string `json:"owner"`
Note string `json:"note"`
Rule int `json:"rule"`
}

View File

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

View File

@@ -17,7 +17,7 @@ func Init(g *gin.Engine) {
adg := g.Group("/api/admin")
LoginBind(adg)
adg.POST("/user/register", (&admin.User{}).Register)
adg.Use(middleware.AdminAuth())
//FileBind(adg)
UserBind(adg)
@@ -28,10 +28,12 @@ func Init(g *gin.Engine) {
OauthBind(adg)
LoginLogBind(adg)
AuditBind(adg)
AddressBookCollectionBind(adg)
AddressBookCollectionRuleBind(adg)
UserTokenBind(adg)
rs := &admin.Rustdesk{}
adg.GET("/server-config", rs.ServerConfig)
adg.GET("/app-config", rs.AppConfig)
//访问静态文件
//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
}
@@ -39,6 +41,9 @@ func LoginBind(rg *gin.RouterGroup) {
cont := &admin.Login{}
rg.POST("/login", cont.Login)
rg.POST("/logout", cont.Logout)
rg.GET("/login-options", cont.LoginOptions)
rg.POST("/oidc/auth", cont.OidcAuth)
rg.GET("/oidc/auth-query", cont.OidcAuthQuery)
}
func UserBind(rg *gin.RouterGroup) {
@@ -48,6 +53,8 @@ func UserBind(rg *gin.RouterGroup) {
aR.GET("/current", cont.Current)
aR.POST("/changeCurPwd", cont.ChangeCurPwd)
aR.POST("/myOauth", cont.MyOauth)
aR.GET("/myPeer", cont.MyPeer)
aR.POST("/groupUsers", cont.GroupUsers)
}
aRP := rg.Group("/user").Use(middleware.AdminPrivilege())
{
@@ -102,6 +109,8 @@ func AddressBookBind(rg *gin.RouterGroup) {
}
func PeerBind(rg *gin.RouterGroup) {
aR := rg.Group("/peer")
aR.POST("/simpleData", (&admin.Peer{}).SimpleData)
aR.Use(middleware.AdminPrivilege())
{
cont := &admin.Peer{}
aR.GET("/list", cont.List)
@@ -109,9 +118,7 @@ func PeerBind(rg *gin.RouterGroup) {
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
arp := aR.Use(middleware.AdminPrivilege())
arp.POST("/batchDelete", cont.BatchDelete)
aR.POST("/batchDelete", cont.BatchDelete)
}
}
@@ -152,6 +159,35 @@ func AuditBind(rg *gin.RouterGroup) {
afR.GET("/list", cont.FileList)
afR.POST("/delete", cont.FileDelete)
}
func AddressBookCollectionBind(rg *gin.RouterGroup) {
aR := rg.Group("/address_book_collection")
{
cont := &admin.AddressBookCollection{}
aR.GET("/list", cont.List)
aR.GET("/detail/:id", cont.Detail)
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
}
}
func AddressBookCollectionRuleBind(rg *gin.RouterGroup) {
aR := rg.Group("/address_book_collection_rule")
{
cont := &admin.AddressBookCollectionRule{}
aR.GET("/list", cont.List)
aR.GET("/detail/:id", cont.Detail)
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
}
}
func UserTokenBind(rg *gin.RouterGroup) {
aR := rg.Group("/user_token").Use(middleware.AdminPrivilege())
cont := &admin.UserToken{}
aR.GET("/list", cont.List)
aR.POST("/delete", cont.Delete)
}
/*
func FileBind(rg *gin.RouterGroup) {

View File

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

View File

@@ -19,22 +19,24 @@ import "Gwen/model/custom_types"
// AddressBook 有些字段是Personal才会上传的
type AddressBook struct {
RowId uint `gorm:"primaryKey" json:"row_id"`
Id string `json:"id" gorm:"default:0;not null;index"`
Username string `json:"username" gorm:"default:'';not null;"`
Password string `json:"password" gorm:"default:'';not null;"`
Hostname string `json:"hostname" gorm:"default:'';not null;"`
Alias string `json:"alias" gorm:"default:'';not null;"`
Platform string `json:"platform" gorm:"default:'';not null;"`
Tags custom_types.AutoJson `json:"tags" gorm:"not null;" swaggertype:"array,string"`
Hash string `json:"hash" gorm:"default:'';not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
ForceAlwaysRelay bool `json:"forceAlwaysRelay" gorm:"default:0;not null;"`
RdpPort string `json:"rdpPort" gorm:"default:'';not null;"`
RdpUsername string `json:"rdpUsername" gorm:"default:'';not null;"`
Online bool `json:"online" gorm:"default:0;not null;"`
LoginName string `json:"loginName" gorm:"default:'';not null;"`
SameServer bool `json:"sameServer" gorm:"default:0;not null;"`
RowId uint `gorm:"primaryKey" json:"row_id"`
Id string `json:"id" gorm:"default:0;not null;index"`
Username string `json:"username" gorm:"default:'';not null;"`
Password string `json:"password" gorm:"default:'';not null;"`
Hostname string `json:"hostname" gorm:"default:'';not null;"`
Alias string `json:"alias" gorm:"default:'';not null;"`
Platform string `json:"platform" gorm:"default:'';not null;"`
Tags custom_types.AutoJson `json:"tags" gorm:"not null;" swaggertype:"array,string"`
Hash string `json:"hash" gorm:"default:'';not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
ForceAlwaysRelay bool `json:"forceAlwaysRelay" gorm:"default:0;not null;"`
RdpPort string `json:"rdpPort" gorm:"default:'';not null;"`
RdpUsername string `json:"rdpUsername" gorm:"default:'';not null;"`
Online bool `json:"online" gorm:"default:0;not null;"`
LoginName string `json:"loginName" gorm:"default:'';not null;"`
SameServer bool `json:"sameServer" gorm:"default:0;not null;"`
CollectionId uint `json:"collection_id" gorm:"default:0;not null;index"`
Collection *AddressBookCollection `json:"collection,omitempty"`
TimeModel
}
@@ -42,3 +44,37 @@ type AddressBookList struct {
AddressBooks []*AddressBook `json:"list"`
Pagination
}
type AddressBookCollection struct {
IdModel
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
Name string `json:"name" gorm:"default:'';not null;" validate:"required"`
TimeModel
}
type AddressBookCollectionList struct {
AddressBookCollection []*AddressBookCollection `json:"list"`
Pagination
}
type AddressBookCollectionRule struct {
IdModel
UserId uint `json:"user_id" gorm:"default:0;not null;"`
CollectionId uint `json:"collection_id" gorm:"default:0;not null;index" validate:"required"`
Rule int `json:"rule" gorm:"default:0;not null;" validate:"required,gte=1,lte=3"` // 0: 无 1: 读 2: 读写 3: 完全控制
Type int `json:"type" gorm:"default:1;not null;" validate:"required,gte=1,lte=2"` // 1: 个人 2: 群组
ToId uint `json:"to_id" gorm:"default:0;not null;" validate:"required,gt=0"`
TimeModel
}
type AddressBookCollectionRuleList struct {
AddressBookCollectionRule []*AddressBookCollectionRule `json:"list"`
Pagination
}
const (
ShareAddressBookRuleTypePersonal = 1
ShareAddressBookRuleTypeGroup = 2
)
const (
ShareAddressBookRuleRuleRead = 1
ShareAddressBookRuleRuleReadWrite = 2
ShareAddressBookRuleRuleFullControl = 3
)

View File

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

View File

@@ -1,20 +1,145 @@
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 {
IdModel
Op string `json:"op"`
OauthType string `json:"oauth_type"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectUrl string `json:"redirect_url"`
AutoRegister *bool `json:"auto_register"`
Scopes string `json:"scopes"`
Issuer string `json:"issuer"`
TimeModel
}
const (
OauthTypeGithub = "github"
OauthTypeGoogle = "google"
OauthTypeWebauth = "webauth"
)
// Helper function to format oauth info, it's used in the update and create method
func (oa *Oauth) FormatOauthInfo() error {
oauthType := strings.TrimSpace(oa.OauthType)
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 {
Oauths []*Oauth `json:"list"`

View File

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

View File

@@ -2,9 +2,11 @@ package model
type Tag struct {
IdModel
Name string `json:"name" gorm:"default:'';not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
Color uint `json:"color" gorm:"default:0;not null;"` //color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色, 可以转成rgba
Name string `json:"name" gorm:"default:'';not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
Color uint `json:"color" gorm:"default:0;not null;"` //color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色, 可以转成rgba
CollectionId uint `json:"collection_id" gorm:"default:0;not null;index"`
Collection *AddressBookCollection `json:"collection,omitempty"`
TimeModel
}

View File

@@ -2,7 +2,9 @@ package model
type User struct {
IdModel
Username string `json:"username" gorm:"default:'';not null;index,unique"`
Username string `json:"username" gorm:"default:'';not null;uniqueIndex"`
Email string `json:"email" gorm:"default:'';not null;index"`
// Email string `json:"email" `
Password string `json:"-" gorm:"default:'';not null;"`
Nickname string `json:"nickname" gorm:"default:'';not null;"`
Avatar string `json:"avatar" gorm:"default:'';not null;"`
@@ -12,6 +14,15 @@ type User struct {
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 {
Users []*User `json:"list,omitempty"`
Pagination

View File

@@ -1,12 +1,26 @@
package model
import (
"strings"
)
type UserThird struct {
IdModel
UserId uint `json:"user_id" gorm:"not null;index"`
OpenId string `json:"open_id" gorm:"not null;index"`
UnionId string `json:"union_id" gorm:"not null;"`
ThirdType string `json:"third_type" gorm:"not null;"`
ThirdEmail string `json:"third_email"`
ThirdName string `json:"third_name"`
UserId uint `json:"user_id" gorm:"not null;index"`
OauthUser
UnionId string `json:"union_id" gorm:"default:'';not null;"`
// OauthType string `json:"oauth_type" gorm:"not null;"`
ThirdType string `json:"third_type" gorm:"default:'';not null;"` //deprecated
OauthType string `json:"oauth_type" gorm:"default:'';not null;"`
Op string `json:"op" gorm:"default:'';not null;"`
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 {
IdModel
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
Token string `json:"token" gorm:"default:'';not null;index"`
ExpiredAt int64 `json:"expired_at" gorm:"default:0;not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
DeviceUuid string `json:"device_uuid" gorm:"default:'';omitempty;"`
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
}
type UserTokenList struct {
UserTokens []UserToken `json:"list"`
Pagination
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,6 +22,12 @@ func (s *AddressBookService) InfoByUserIdAndId(userid uint, id string) *model.Ad
global.DB.Where("user_id = ? and id = ?", userid, id).First(p)
return p
}
func (s *AddressBookService) InfoByUserIdAndIdAndCid(userid uint, id string, cid uint) *model.AddressBook {
p := &model.AddressBook{}
global.DB.Where("user_id = ? and id = ? and collection_id = ?", userid, id, cid).First(p)
return p
}
func (s *AddressBookService) InfoByRowId(id uint) *model.AddressBook {
p := &model.AddressBook{}
global.DB.Where("row_id = ?", id).First(p)
@@ -96,7 +102,7 @@ func (s *AddressBookService) UpdateAddressBook(abs []*model.AddressBook, userId
}
func (t *AddressBookService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.AddressBookList) {
func (s *AddressBookService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.AddressBookList) {
res = &model.AddressBookList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
@@ -111,34 +117,44 @@ func (t *AddressBookService) List(page, pageSize uint, where func(tx *gorm.DB))
}
// Create 创建
func (t *AddressBookService) Create(u *model.AddressBook) error {
func (s *AddressBookService) Create(u *model.AddressBook) error {
res := global.DB.Create(u).Error
return res
}
func (t *AddressBookService) Delete(u *model.AddressBook) error {
func (s *AddressBookService) Delete(u *model.AddressBook) error {
return global.DB.Delete(u).Error
}
// Update 更新
func (t *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
}
// ShareByWebClient 分享
func (t *AddressBookService) ShareByWebClient(m *model.ShareRecord) error {
func (s *AddressBookService) ShareByWebClient(m *model.ShareRecord) error {
m.ShareToken = uuid.New().String()
return global.DB.Create(m).Error
}
// SharedPeer
func (t *AddressBookService) SharedPeer(shareToken string) *model.ShareRecord {
func (s *AddressBookService) SharedPeer(shareToken string) *model.ShareRecord {
m := &model.ShareRecord{}
global.DB.Where("share_token = ?", shareToken).First(m)
return m
}
// PlatformFromOs
func (t *AddressBookService) PlatformFromOs(os string) string {
func (s *AddressBookService) PlatformFromOs(os string) string {
if strings.Contains(os, "Android") || strings.Contains(os, "android") {
return "Android"
}
@@ -153,3 +169,152 @@ func (t *AddressBookService) PlatformFromOs(os string) string {
}
return ""
}
func (s *AddressBookService) ListByUserIdAndCollectionId(userId, cid, page, pageSize uint) (res *model.AddressBookList) {
res = s.List(page, pageSize, func(tx *gorm.DB) {
tx.Where("user_id = ? and collection_id = ?", userId, cid)
})
return
}
func (s *AddressBookService) ListCollection(page, pageSize uint, where func(tx *gorm.DB)) (res *model.AddressBookCollectionList) {
res = &model.AddressBookCollectionList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.AddressBookCollection{})
if where != nil {
where(tx)
}
tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize))
tx.Find(&res.AddressBookCollection)
return
}
func (s *AddressBookService) ListCollectionByIds(ids []uint) (res []*model.AddressBookCollection) {
global.DB.Where("id in ?", ids).Find(&res)
return res
}
func (s *AddressBookService) ListCollectionByUserId(userId uint) (res *model.AddressBookCollectionList) {
res = s.ListCollection(1, 100, func(tx *gorm.DB) {
tx.Where("user_id = ?", userId)
})
return
}
func (s *AddressBookService) CollectionInfoById(id uint) *model.AddressBookCollection {
p := &model.AddressBookCollection{}
global.DB.Where("id = ?", id).First(p)
return p
}
func (s *AddressBookService) CollectionReadRules(user *model.User) (res []*model.AddressBookCollectionRule) {
// personalRules
var personalRules []*model.AddressBookCollectionRule
tx2 := global.DB.Model(&model.AddressBookCollectionRule{})
tx2.Where("type = ? and to_id = ? and rule > 0", model.ShareAddressBookRuleTypePersonal, user.Id).Find(&personalRules)
res = append(res, personalRules...)
//group
var groupRules []*model.AddressBookCollectionRule
tx3 := global.DB.Model(&model.AddressBookCollectionRule{})
tx3.Where("type = ? and to_id = ? and rule > 0", model.ShareAddressBookRuleTypeGroup, user.GroupId).Find(&groupRules)
res = append(res, groupRules...)
return
}
func (s *AddressBookService) UserMaxRule(user *model.User, uid, cid uint) int {
// ismy?
if user.Id == uid {
return model.ShareAddressBookRuleRuleFullControl
}
max := 0
personalRules := &model.AddressBookCollectionRule{}
tx := global.DB.Model(personalRules)
tx.Where("type = ? and collection_id = ? and to_id = ?", model.ShareAddressBookRuleTypePersonal, cid, user.Id).First(&personalRules)
if personalRules.Id != 0 {
max = personalRules.Rule
if max == model.ShareAddressBookRuleRuleFullControl {
return max
}
}
groupRules := &model.AddressBookCollectionRule{}
tx2 := global.DB.Model(groupRules)
tx2.Where("type = ? and collection_id = ? and to_id = ?", model.ShareAddressBookRuleTypeGroup, cid, user.GroupId).First(&groupRules)
if groupRules.Id != 0 {
if groupRules.Rule > max {
max = groupRules.Rule
}
if max == model.ShareAddressBookRuleRuleFullControl {
return max
}
}
return max
}
func (s *AddressBookService) CheckUserReadPrivilege(user *model.User, uid, cid uint) bool {
return s.UserMaxRule(user, uid, cid) >= model.ShareAddressBookRuleRuleRead
}
func (s *AddressBookService) CheckUserWritePrivilege(user *model.User, uid, cid uint) bool {
return s.UserMaxRule(user, uid, cid) >= model.ShareAddressBookRuleRuleReadWrite
}
func (s *AddressBookService) CheckUserFullControlPrivilege(user *model.User, uid, cid uint) bool {
return s.UserMaxRule(user, uid, cid) >= model.ShareAddressBookRuleRuleFullControl
}
func (s *AddressBookService) CreateCollection(t *model.AddressBookCollection) error {
return global.DB.Create(t).Error
}
func (s *AddressBookService) UpdateCollection(t *model.AddressBookCollection) error {
return global.DB.Model(t).Updates(t).Error
}
func (s *AddressBookService) DeleteCollection(t *model.AddressBookCollection) error {
//删除集合下的所有规则、地址簿,再删除集合
tx := global.DB.Begin()
tx.Where("collection_id = ?", t.Id).Delete(&model.AddressBookCollectionRule{})
tx.Where("collection_id = ?", t.Id).Delete(&model.AddressBook{})
tx.Delete(t)
return tx.Commit().Error
}
func (s *AddressBookService) RuleInfoById(u uint) *model.AddressBookCollectionRule {
p := &model.AddressBookCollectionRule{}
global.DB.Where("id = ?", u).First(p)
return p
}
func (s *AddressBookService) RulePersonalInfoByToIdAndCid(toid, cid uint) *model.AddressBookCollectionRule {
p := &model.AddressBookCollectionRule{}
global.DB.Where("type = ? and to_id = ? and collection_id = ?", model.ShareAddressBookRuleTypePersonal, toid, cid).First(p)
return p
}
func (s *AddressBookService) CreateRule(t *model.AddressBookCollectionRule) error {
return global.DB.Create(t).Error
}
func (s *AddressBookService) ListRules(page uint, size uint, f func(tx *gorm.DB)) *model.AddressBookCollectionRuleList {
res := &model.AddressBookCollectionRuleList{}
res.Page = int64(page)
res.PageSize = int64(size)
tx := global.DB.Model(&model.AddressBookCollectionRule{})
if f != nil {
f(tx)
}
tx.Count(&res.Total)
tx.Scopes(Paginate(page, size))
tx.Find(&res.AddressBookCollectionRule)
return res
}
func (s *AddressBookService) UpdateRule(t *model.AddressBookCollectionRule) error {
return global.DB.Model(t).Updates(t).Error
}
func (s *AddressBookService) DeleteRule(t *model.AddressBookCollectionRule) error {
return global.DB.Delete(t).Error
}
// CheckCollectionOwner 检查Collection的所有者
func (s *AddressBookService) CheckCollectionOwner(uid uint, cid uint) bool {
p := s.CollectionInfoById(cid)
return p.UserId == uid
}

View File

@@ -9,12 +9,14 @@ import (
"errors"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"golang.org/x/oauth2/google"
// "golang.org/x/oauth2/google"
"gorm.io/gorm"
"io"
// "io"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
)
@@ -22,74 +24,36 @@ import (
type OauthService struct {
}
type GithubUserdata struct {
AvatarUrl string `json:"avatar_url"`
Bio string `json:"bio"`
Blog string `json:"blog"`
Collaborators int `json:"collaborators"`
Company interface{} `json:"company"`
CreatedAt time.Time `json:"created_at"`
DiskUsage int `json:"disk_usage"`
Email interface{} `json:"email"`
EventsUrl string `json:"events_url"`
Followers int `json:"followers"`
FollowersUrl string `json:"followers_url"`
Following int `json:"following"`
FollowingUrl string `json:"following_url"`
GistsUrl string `json:"gists_url"`
GravatarId string `json:"gravatar_id"`
Hireable interface{} `json:"hireable"`
HtmlUrl string `json:"html_url"`
Id int `json:"id"`
Location interface{} `json:"location"`
Login string `json:"login"`
Name string `json:"name"`
NodeId string `json:"node_id"`
NotificationEmail interface{} `json:"notification_email"`
OrganizationsUrl string `json:"organizations_url"`
OwnedPrivateRepos int `json:"owned_private_repos"`
Plan struct {
Collaborators int `json:"collaborators"`
Name string `json:"name"`
PrivateRepos int `json:"private_repos"`
Space int `json:"space"`
} `json:"plan"`
PrivateGists int `json:"private_gists"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsUrl string `json:"received_events_url"`
ReposUrl string `json:"repos_url"`
SiteAdmin bool `json:"site_admin"`
StarredUrl string `json:"starred_url"`
SubscriptionsUrl string `json:"subscriptions_url"`
TotalPrivateRepos int `json:"total_private_repos"`
//TwitterUsername interface{} `json:"twitter_username"`
TwoFactorAuthentication bool `json:"two_factor_authentication"`
Type string `json:"type"`
UpdatedAt time.Time `json:"updated_at"`
Url string `json:"url"`
}
type GoogleUserdata struct {
Email string `json:"email"`
FamilyName string `json:"family_name"`
GivenName string `json:"given_name"`
Id string `json:"id"`
Name string `json:"name"`
Picture string `json:"picture"`
VerifiedEmail bool `json:"verified_email"`
// Define a struct to parse the .well-known/openid-configuration response
type OidcEndpoint struct {
Issuer string `json:"issuer"`
AuthURL string `json:"authorization_endpoint"`
TokenURL string `json:"token_endpoint"`
UserInfo string `json:"userinfo_endpoint"`
}
type OauthCacheItem struct {
UserId uint `json:"user_id"`
Id string `json:"id"` //rustdesk的设备ID
Op string `json:"op"`
Action string `json:"action"`
Uuid string `json:"uuid"`
DeviceName string `json:"device_name"`
DeviceOs string `json:"device_os"`
DeviceType string `json:"device_type"`
ThirdOpenId string `json:"third_open_id"`
ThirdName string `json:"third_name"`
ThirdEmail string `json:"third_email"`
UserId uint `json:"user_id"`
Id string `json:"id"` //rustdesk的设备ID
Op string `json:"op"`
Action string `json:"action"`
Uuid string `json:"uuid"`
DeviceName string `json:"device_name"`
DeviceOs string `json:"device_os"`
DeviceType string `json:"device_type"`
OpenId string `json:"open_id"`
Username string `json:"username"`
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{}
@@ -99,6 +63,13 @@ const (
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 {
v, ok := OauthCache.Load(key)
if !ok {
@@ -123,52 +94,101 @@ func (os *OauthService) DeleteOauthCache(key string) {
func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
code = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10)
if op == model.OauthTypeWebauth {
if op == string(model.OauthTypeWebauth) {
url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + code
//url = "http://localhost:8888/_admin/#/oauth/" + code
return nil, code, url
}
err, conf := os.GetOauthConfig(op)
err, _, oauthConfig := os.GetOauthConfig(op)
if err == nil {
return err, code, conf.AuthCodeURL(code)
return err, code, oauthConfig.AuthCodeURL(code)
}
return err, code, ""
}
// GetOauthConfig 获取配置
func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
if op == model.OauthTypeGithub {
g := os.InfoByOp(model.OauthTypeGithub)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("ConfigNotFound"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: github.Endpoint,
Scopes: []string{"read:user", "user:email"},
}
// Method to fetch OIDC configuration dynamically
func (os *OauthService) FetchOidcEndpoint(issuer string) (error, OidcEndpoint) {
configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
// Get the HTTP client (with or without proxy based on configuration)
client := getHTTPClientWithProxy()
resp, err := client.Get(configURL)
if err != nil {
return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{}
}
if op == model.OauthTypeGoogle {
g := os.InfoByOp(model.OauthTypeGoogle)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("ConfigNotFound"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: google.Endpoint,
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{}
}
var endpoint OidcEndpoint
if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil {
return errors.New("failed to parse OIDC configuration"), OidcEndpoint{}
}
return nil, endpoint
}
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 {
//todo add timeout
if global.Config.Proxy.Enable {
if global.Config.Proxy.Host == "" {
global.Logger.Warn("Proxy is enabled but proxy host is empty.")
@@ -187,134 +207,151 @@ func getHTTPClientWithProxy() *http.Client {
return http.DefaultClient
}
func (os *OauthService) GithubCallback(code string) (error error, userData *GithubUserdata) {
err, oauthConfig := os.GetOauthConfig(model.OauthTypeGithub)
if err != nil {
return err, nil
}
func (os *OauthService) callbackBase(oauthConfig *oauth2.Config, code string, userEndpoint string, userData interface{}) (err error, client *http.Client) {
// 使用代理配置创建 HTTP 客户端
// 设置代理客户端
httpClient := getHTTPClientWithProxy()
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 {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
error = errors.New("GetOauthTokenError")
return
return errors.New("GetOauthTokenError"), nil
}
// 使用带有代理的 HTTP 客户端获取用户信息
client := oauthConfig.Client(ctx, token)
resp, err := client.Get("https://api.github.com/user")
// 获取用户信息
client = oauthConfig.Client(ctx, token)
resp, err := client.Get(userEndpoint)
if err != nil {
global.Logger.Warn("failed getting user info: ", err)
error = errors.New("GetOauthUserInfoError")
return
return errors.New("GetOauthUserInfoError"), nil
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
global.Logger.Warn("failed closing response body: ", err)
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
global.Logger.Warn("failed closing response body: ", closeErr)
}
}(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)
error = errors.New("DecodeOauthUserInfoError")
return
return errors.New("DecodeOauthUserInfoError"), nil
}
return
return nil, client
}
func (os *OauthService) GoogleCallback(code string) (error error, userData *GoogleUserdata) {
err, oauthConfig := os.GetOauthConfig(model.OauthTypeGoogle)
// githubCallback github回调
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 {
return err, nil
}
// 使用代理配置创建 HTTP 客户端
httpClient := getHTTPClientWithProxy()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
token, err := oauthConfig.Exchange(ctx, code)
err = os.getGithubPrimaryEmail(client, user)
if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
error = errors.New("GetOauthTokenError")
return
return err, nil
}
// 使用带有代理的 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
return nil, user.ToOauthUser()
}
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{}
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
}
func (os *OauthService) BindGithubUser(openid, username string, userId uint) error {
return os.BindOauthUser(model.OauthTypeGithub, openid, username, userId)
}
func (os *OauthService) BindGoogleUser(email, username string, userId uint) error {
return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId)
}
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
utr := &model.UserThird{
OpenId: openid,
ThirdType: thirdType,
ThirdName: username,
UserId: userId,
// BindOauthUser: Bind third party account
func (os *OauthService) BindOauthUser(userId uint, oauthUser *model.OauthUser, op string) error {
utr := &model.UserThird{}
err, oauthType := os.GetTypeByOp(op)
if err != nil {
return err
}
utr.FromOauthUser(userId, oauthUser, oauthType, op)
return global.DB.Create(utr).Error
}
func (os *OauthService) UnBindGithubUser(userid uint) error {
return os.UnBindThird(model.OauthTypeGithub, 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
// UnBindOauthUser: Unbind third party account
func (os *OauthService) UnBindOauthUser(userId uint, op string) error {
return os.UnBindThird(op, userId)
}
// 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 {
u := &model.Oauth{}
global.DB.Where("id = ?", id).First(u)
return u
oauthInfo := &model.Oauth{}
global.DB.Where("id = ?", id).First(oauthInfo)
return oauthInfo
}
// InfoByOp 根据op取用户信息
// InfoByOp 根据op获取Oauth信息
func (os *OauthService) InfoByOp(op string) *model.Oauth {
u := &model.Oauth{}
global.DB.Where("op = ?", op).First(u)
return u
oauthInfo := &model.Oauth{}
global.DB.Where("op = ?", op).First(oauthInfo)
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) {
res = &model.OauthList{}
res.Page = int64(page)
@@ -329,16 +366,95 @@ func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res
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 创建
func (os *OauthService) Create(u *model.Oauth) error {
res := global.DB.Create(u).Error
func (os *OauthService) Create(oauthInfo *model.Oauth) error {
err := oauthInfo.FormatOauthInfo()
if err != nil {
return err
}
res := global.DB.Create(oauthInfo).Error
return res
}
func (os *OauthService) Delete(u *model.Oauth) error {
return global.DB.Delete(u).Error
func (os *OauthService) Delete(oauthInfo *model.Oauth) error {
return global.DB.Delete(oauthInfo).Error
}
// Update 更新
func (os *OauthService) Update(u *model.Oauth) error {
return global.DB.Model(u).Updates(u).Error
func (os *OauthService) Update(oauthInfo *model.Oauth) 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,43 @@ func (ps *PeerService) InfoByRowId(id uint) *model.Peer {
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
func (ps *PeerService) UuidBindUserId(uuid string, userId uint) {
func (ps *PeerService) UuidBindUserId(deviceId string, uuid string, userId uint) {
peer := ps.FindByUuid(uuid)
// 如果存在则更新
if peer.RowId > 0 {
peer.UserId = userId
ps.Update(peer)
} else {
// 不存在则创建
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取列表
func (ps *PeerService) ListByUserIds(userIds []uint, page, pageSize uint) (res *model.PeerList) {
res = &model.PeerList{}
@@ -62,21 +90,57 @@ func (ps *PeerService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *
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 创建
func (ps *PeerService) Create(u *model.Peer) error {
res := global.DB.Create(u).Error
return res
}
// Delete 删除, 同时也应该删除token
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 {
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 更新
func (ps *PeerService) Update(u *model.Peer) error {
return global.DB.Model(u).Updates(u).Error
}

View File

@@ -14,9 +14,9 @@ func (s *TagService) Info(id uint) *model.Tag {
global.DB.Where("id = ?", id).First(p)
return p
}
func (s *TagService) InfoByUserIdAndName(userid uint, name string) *model.Tag {
func (s *TagService) InfoByUserIdAndNameAndCollectionId(userid uint, name string, cid uint) *model.Tag {
p := &model.Tag{}
global.DB.Where("user_id = ? and name = ?", userid, name).First(p)
global.DB.Where("user_id = ? and name = ? and collection_id = ?", userid, name, cid).First(p)
return p
}
@@ -26,7 +26,12 @@ func (s *TagService) ListByUserId(userId uint) (res *model.TagList) {
})
return
}
func (s *TagService) ListByUserIdAndCollectionId(userId, cid uint) (res *model.TagList) {
res = s.List(1, 1000, func(tx *gorm.DB) {
tx.Where("user_id = ? and collection_id = ?", userId, cid)
})
return
}
func (s *TagService) UpdateTags(userId uint, tags map[string]uint) {
tx := global.DB.Begin()
//先查询所有tag
@@ -58,13 +63,13 @@ func (s *TagService) UpdateTags(userId uint, tags map[string]uint) {
}
// InfoById 根据用户id取用户信息
func (t *TagService) InfoById(id uint) *model.Tag {
func (s *TagService) InfoById(id uint) *model.Tag {
u := &model.Tag{}
global.DB.Where("id = ?", id).First(u)
return u
}
func (t *TagService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.TagList) {
func (s *TagService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.TagList) {
res = &model.TagList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
@@ -79,15 +84,15 @@ func (t *TagService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *mo
}
// Create 创建
func (t *TagService) Create(u *model.Tag) error {
func (s *TagService) Create(u *model.Tag) error {
res := global.DB.Create(u).Error
return res
}
func (t *TagService) Delete(u *model.Tag) error {
func (s *TagService) Delete(u *model.Tag) error {
return global.DB.Delete(u).Error
}
// Update 更新
func (t *TagService) Update(u *model.Tag) error {
return global.DB.Model(u).Updates(u).Error
func (s *TagService) Update(u *model.Tag) error {
return global.DB.Model(u).Select("*").Omit("created_at").Updates(u).Error
}

View File

@@ -5,10 +5,12 @@ import (
adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/utils"
"errors"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"math/rand"
"strconv"
"strings"
"time"
)
@@ -21,12 +23,21 @@ func (us *UserService) InfoById(id uint) *model.User {
global.DB.Where("id = ?", id).First(u)
return u
}
// InfoByUsername 根据用户名取用户信息
func (us *UserService) InfoByUsername(un string) *model.User {
u := &model.User{}
global.DB.Where("username = ?", un).First(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取用户信息
func (us *UserService) InfoByOpenid(openid string) *model.User {
u := &model.User{}
@@ -42,18 +53,18 @@ func (us *UserService) InfoByUsernamePassword(username, password string) *model.
}
// InfoByAccesstoken 根据accesstoken取用户信息
func (us *UserService) InfoByAccessToken(token string) *model.User {
func (us *UserService) InfoByAccessToken(token string) (*model.User, *model.UserToken) {
u := &model.User{}
ut := &model.UserToken{}
global.DB.Where("token = ?", token).First(ut)
if ut.Id == 0 {
return u
return u, ut
}
if ut.ExpiredAt < time.Now().Unix() {
return u
return u, ut
}
global.DB.Where("id = ?", ut.UserId).First(u)
return u
return u, ut
}
// 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 {
token := us.GenerateToken(u)
ut := &model.UserToken{
UserId: u.Id,
Token: token,
ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(),
UserId: u.Id,
Token: token,
DeviceUuid: llog.Uuid,
DeviceId: llog.DeviceId,
ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(),
}
global.DB.Create(ut)
llog.UserTokenId = ut.UserId
global.DB.Create(llog)
if llog.Uuid != "" {
AllService.PeerService.UuidBindUserId(llog.Uuid, u.Id)
AllService.PeerService.UuidBindUserId(llog.DeviceId, llog.Uuid, u.Id)
}
return ut
}
@@ -101,6 +115,11 @@ func (us *UserService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *
return
}
func (us *UserService) ListByIds(ids []uint) (res []*model.User) {
global.DB.Where("id in ?", ids).Find(&res)
return res
}
// ListByGroupId 根据组id取用户列表
func (us *UserService) ListByGroupId(groupId, page, pageSize uint) (res *model.UserList) {
res = us.List(page, pageSize, func(tx *gorm.DB) {
@@ -134,21 +153,88 @@ func (us *UserService) CheckUserEnable(u *model.User) bool {
// Create 创建
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)
res := global.DB.Create(u).Error
return res
}
// Logout 退出登录
func (us *UserService) Logout(u *model.User, token string) error {
return global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error
// GetUuidByToken 根据token和user取uuid
func (us *UserService) GetUuidByToken(u *model.User, token string) string {
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 {
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 更新
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
}
@@ -157,6 +243,16 @@ func (us *UserService) FlushToken(u *model.User) 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 更新密码
func (us *UserService) UpdatePassword(u *model.User, password string) error {
u.Password = us.EncryptPassword(password)
@@ -181,19 +277,9 @@ func (us *UserService) RouteNames(u *model.User) []string {
return adResp.UserRouteNames
}
// InfoByGithubId 根据githubid取用户信息
func (us *UserService) InfoByGithubId(githubId string) *model.User {
return us.InfoByOauthId(model.OauthTypeGithub, githubId)
}
// InfoByGoogleEmail 根据googleid取用户信息
func (us *UserService) InfoByGoogleEmail(email string) *model.User {
return us.InfoByOauthId(model.OauthTypeGithub, email)
}
// InfoByOauthId 根据oauth取用户信息
func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
// InfoByOauthId 根据oauth的name和openId取用户信息
func (us *UserService) InfoByOauthId(op string, openId string) *model.User {
ut := AllService.OauthService.UserThirdInfo(op, openId)
if ut.Id == 0 {
return nil
}
@@ -204,45 +290,53 @@ func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
return u
}
// RegisterByGithub 注册
func (us *UserService) RegisterByGithub(githubName string, githubId string) *model.User {
return us.RegisterByOauth(model.OauthTypeGithub, githubName, githubId)
}
// RegisterByGoogle 注册
func (us *UserService) RegisterByGoogle(name string, email string) *model.User {
return us.RegisterByOauth(model.OauthTypeGoogle, name, email)
}
// RegisterByOauth 注册
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User {
tx := global.DB.Begin()
ut := &model.UserThird{
OpenId: uid,
ThirdName: thirdName,
ThirdType: thirdType,
func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (error, *model.User) {
global.Lock.Lock("registerByOauth")
defer global.Lock.UnLock("registerByOauth")
ut := AllService.OauthService.UserThirdInfo(op, oauthUser.OpenId)
if ut.Id != 0 {
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)
u := &model.User{
Username: username,
tx := global.DB.Begin()
ut = &model.UserThird{}
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,
}
global.DB.Create(u)
ut.UserId = u.Id
global.DB.Create(ut)
oauthUser.ToUser(user, false)
tx.Create(user)
if user.Id == 0 {
tx.Rollback()
return errors.New("OauthRegisterFailed"), user
}
ut.UserId = user.Id
tx.Create(ut)
tx.Commit()
return u
return nil, user
}
// GenerateUsernameByOauth 生成用户名
@@ -264,7 +358,7 @@ func (us *UserService) UserThirdsByUserId(userId uint) (res []*model.UserThird)
func (us *UserService) UserThirdInfo(userId uint, op string) *model.UserThird {
ut := &model.UserThird{}
global.DB.Where("user_id = ? and third_type = ?", userId, op).First(ut)
global.DB.Where("user_id = ? and op = ?", userId, op).First(ut)
return ut
}
@@ -274,3 +368,93 @@ func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string) uint {
global.DB.Where("uuid = ?", uuid).Order("id desc").First(llog)
return llog.UserId
}
// IsPasswordEmptyById 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyById(id uint) bool {
u := &model.User{}
if global.DB.Where("id = ?", id).First(u).Error != nil {
return false
}
return u.Password == ""
}
// IsPasswordEmptyByUsername 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyByUsername(username string) bool {
u := &model.User{}
if global.DB.Where("username = ?", username).First(u).Error != nil {
return false
}
return u.Password == ""
}
// IsPasswordEmptyByUser 判断密码是否为空,主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyByUser(u *model.User) bool {
return us.IsPasswordEmptyById(u.Id)
}
// 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

@@ -73,3 +73,30 @@ func RandomString(n int) string {
}
return string(b)
}
// Keys 泛型函数K 是键类型V 是值类型
func Keys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
// Values 泛型函数K 是键类型V 是值类型
func Values[K comparable, V any](m map[K]V) []V {
values := make([]V, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}
func InArray(k string, arr []string) bool {
for _, v := range arr {
if k == v {
return true
}
}
return false
}