Compare commits

...

52 Commits

Author SHA1 Message Date
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
45 changed files with 1956 additions and 727 deletions

View File

@@ -1,8 +1,11 @@
# 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/

View File

@@ -12,17 +12,19 @@ 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 go mod tidy && go mod download
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: Install swag and Run the build script
RUN 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
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
# Build the Go application with CGO enabled and specified ldflags
RUN CGO_ENABLED=1 GOOS=linux go build -a \
# 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
@@ -32,13 +34,27 @@ FROM node:18-alpine AS builder-admin-frontend
# Set working directory
WORKDIR /frontend
RUN apk update && apk add git --no-cache
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 https://github.com/lejianwen/rustdesk-api-web .
# Install npm dependencies and build the frontend
RUN npm install && npm run build
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
@@ -47,7 +63,13 @@ FROM alpine:latest
WORKDIR /app
# Install necessary runtime dependencies
RUN apk add --no-cache tzdata file
# 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/

View File

@@ -159,6 +159,7 @@
lang: "en"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
@@ -196,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

@@ -165,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"
@@ -202,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 ----- | --------------------------------------- | ----------------------------- |

View File

@@ -101,7 +101,7 @@ func main() {
}
func DatabaseAutoUpdate() {
version := 243
version := 246
db := global.DB
@@ -146,6 +146,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")
}
}
}

View File

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

View File

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

View File

@@ -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
}
}

View File

@@ -3,6 +3,10 @@ services:
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:

35
docker-dev.sh Executable file
View File

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

View File

@@ -353,17 +353,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "创建地址簿集合",
"description": "创建地址簿名称",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "创建地址簿集合",
"tags": [
"地址簿名称"
],
"summary": "创建地址簿名称",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -407,17 +410,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合删除",
"description": "地址簿名称删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合删除",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称删除",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -449,14 +455,17 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合详情",
"description": "地址簿名称详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合详情",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称详情",
"parameters": [
{
"type": "integer",
@@ -501,14 +510,17 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合列表",
"description": "地址簿名称列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合列表",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称列表",
"parameters": [
{
"type": "integer",
@@ -570,17 +582,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合编辑",
"description": "地址簿名称编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合编辑",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称编辑",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -624,17 +639,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "创建地址簿集合规则",
"description": "创建地址簿规则",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "创建地址簿集合规则",
"tags": [
"地址簿规则"
],
"summary": "创建地址簿规则",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -678,17 +696,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合规则删除",
"description": "地址簿规则删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则删除",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则删除",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -720,14 +741,17 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合规则详情",
"description": "地址簿规则详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则详情",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则详情",
"parameters": [
{
"type": "integer",
@@ -772,14 +796,17 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合规则列表",
"description": "地址簿规则列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则列表",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则列表",
"parameters": [
{
"type": "integer",
@@ -847,17 +874,20 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "地址簿集合规则编辑",
"description": "地址簿规则编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则编辑",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则编辑",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -1485,7 +1515,7 @@ const docTemplateadmin = `{
}
}
},
"/admin/loginLog/delete": {
"/admin/login_log/delete": {
"post": {
"security": [
{
@@ -1530,7 +1560,7 @@ const docTemplateadmin = `{
}
}
},
"/admin/loginLog/detail/{id}": {
"/admin/login_log/detail/{id}": {
"get": {
"security": [
{
@@ -1585,7 +1615,7 @@ const docTemplateadmin = `{
}
}
},
"/admin/loginLog/list": {
"/admin/login_log/list": {
"get": {
"security": [
{
@@ -2011,6 +2041,51 @@ const docTemplateadmin = `{
}
}
},
"/admin/peer/batchDelete": {
"post": {
"security": [
{
"token": []
}
],
"description": "批量设备删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备"
],
"summary": "批量设备删除",
"parameters": [
{
"description": "设备id",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/create": {
"post": {
"security": [
@@ -2075,7 +2150,7 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "批量设备删除",
"description": "设备删除",
"consumes": [
"application/json"
],
@@ -2085,15 +2160,15 @@ const docTemplateadmin = `{
"tags": [
"设备"
],
"summary": "批量设备删除",
"summary": "设备删除",
"parameters": [
{
"description": "设备id",
"description": "设备信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
"$ref": "#/definitions/admin.PeerForm"
}
}
],
@@ -2216,6 +2291,12 @@ const docTemplateadmin = `{
"description": "主机名",
"name": "hostname",
"in": "query"
},
{
"type": "string",
"description": "uuids 用逗号分隔",
"name": "uuids",
"in": "query"
}
],
"responses": {
@@ -3087,6 +3168,117 @@ const docTemplateadmin = `{
}
}
}
},
"/admin/user_token/delete": {
"post": {
"security": [
{
"token": []
}
],
"description": "登录凭证删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录凭证"
],
"summary": "登录凭证删除",
"parameters": [
{
"description": "登录凭证信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/model.UserToken"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/user_token/list": {
"get": {
"security": [
{
"token": []
}
],
"description": "登录凭证列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录凭证"
],
"summary": "登录凭证列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "页大小",
"name": "page_size",
"in": "query"
},
{
"type": "integer",
"description": "用户ID",
"name": "user_id",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.UserTokenList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
}
},
"definitions": {
@@ -3801,6 +3993,9 @@ const docTemplateadmin = `{
"user_id": {
"type": "integer"
},
"user_token_id": {
"type": "integer"
},
"uuid": {
"type": "string"
}
@@ -4068,6 +4263,49 @@ const docTemplateadmin = `{
}
}
},
"model.UserToken": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"expired_at": {
"type": "integer"
},
"id": {
"type": "integer"
},
"token": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
}
}
},
"model.UserTokenList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.UserToken"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"response.ErrorResponse": {
"type": "object",
"properties": {

View File

@@ -346,17 +346,20 @@
"token": []
}
],
"description": "创建地址簿集合",
"description": "创建地址簿名称",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "创建地址簿集合",
"tags": [
"地址簿名称"
],
"summary": "创建地址簿名称",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -400,17 +403,20 @@
"token": []
}
],
"description": "地址簿集合删除",
"description": "地址簿名称删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合删除",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称删除",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -442,14 +448,17 @@
"token": []
}
],
"description": "地址簿集合详情",
"description": "地址簿名称详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合详情",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称详情",
"parameters": [
{
"type": "integer",
@@ -494,14 +503,17 @@
"token": []
}
],
"description": "地址簿集合列表",
"description": "地址簿名称列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合列表",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称列表",
"parameters": [
{
"type": "integer",
@@ -563,17 +575,20 @@
"token": []
}
],
"description": "地址簿集合编辑",
"description": "地址簿名称编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合编辑",
"tags": [
"地址簿名称"
],
"summary": "地址簿名称编辑",
"parameters": [
{
"description": "地址簿集合信息",
"description": "地址簿名称信息",
"name": "body",
"in": "body",
"required": true,
@@ -617,17 +632,20 @@
"token": []
}
],
"description": "创建地址簿集合规则",
"description": "创建地址簿规则",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "创建地址簿集合规则",
"tags": [
"地址簿规则"
],
"summary": "创建地址簿规则",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -671,17 +689,20 @@
"token": []
}
],
"description": "地址簿集合规则删除",
"description": "地址簿规则删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则删除",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则删除",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -713,14 +734,17 @@
"token": []
}
],
"description": "地址簿集合规则详情",
"description": "地址簿规则详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则详情",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则详情",
"parameters": [
{
"type": "integer",
@@ -765,14 +789,17 @@
"token": []
}
],
"description": "地址簿集合规则列表",
"description": "地址簿规则列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则列表",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则列表",
"parameters": [
{
"type": "integer",
@@ -840,17 +867,20 @@
"token": []
}
],
"description": "地址簿集合规则编辑",
"description": "地址簿规则编辑",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "地址簿集合规则编辑",
"tags": [
"地址簿规则"
],
"summary": "地址簿规则编辑",
"parameters": [
{
"description": "地址簿集合规则信息",
"description": "地址簿规则信息",
"name": "body",
"in": "body",
"required": true,
@@ -1478,7 +1508,7 @@
}
}
},
"/admin/loginLog/delete": {
"/admin/login_log/delete": {
"post": {
"security": [
{
@@ -1523,7 +1553,7 @@
}
}
},
"/admin/loginLog/detail/{id}": {
"/admin/login_log/detail/{id}": {
"get": {
"security": [
{
@@ -1578,7 +1608,7 @@
}
}
},
"/admin/loginLog/list": {
"/admin/login_log/list": {
"get": {
"security": [
{
@@ -2004,6 +2034,51 @@
}
}
},
"/admin/peer/batchDelete": {
"post": {
"security": [
{
"token": []
}
],
"description": "批量设备删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"设备"
],
"summary": "批量设备删除",
"parameters": [
{
"description": "设备id",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/create": {
"post": {
"security": [
@@ -2068,7 +2143,7 @@
"token": []
}
],
"description": "批量设备删除",
"description": "设备删除",
"consumes": [
"application/json"
],
@@ -2078,15 +2153,15 @@
"tags": [
"设备"
],
"summary": "批量设备删除",
"summary": "设备删除",
"parameters": [
{
"description": "设备id",
"description": "设备信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
"$ref": "#/definitions/admin.PeerForm"
}
}
],
@@ -2209,6 +2284,12 @@
"description": "主机名",
"name": "hostname",
"in": "query"
},
{
"type": "string",
"description": "uuids 用逗号分隔",
"name": "uuids",
"in": "query"
}
],
"responses": {
@@ -3080,6 +3161,117 @@
}
}
}
},
"/admin/user_token/delete": {
"post": {
"security": [
{
"token": []
}
],
"description": "登录凭证删除",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录凭证"
],
"summary": "登录凭证删除",
"parameters": [
{
"description": "登录凭证信息",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/model.UserToken"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/user_token/list": {
"get": {
"security": [
{
"token": []
}
],
"description": "登录凭证列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录凭证"
],
"summary": "登录凭证列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "页大小",
"name": "page_size",
"in": "query"
},
{
"type": "integer",
"description": "用户ID",
"name": "user_id",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.UserTokenList"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
}
},
"definitions": {
@@ -3794,6 +3986,9 @@
"user_id": {
"type": "integer"
},
"user_token_id": {
"type": "integer"
},
"uuid": {
"type": "string"
}
@@ -4061,6 +4256,49 @@
}
}
},
"model.UserToken": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"expired_at": {
"type": "integer"
},
"id": {
"type": "integer"
},
"token": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
}
}
},
"model.UserTokenList": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.UserToken"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"response.ErrorResponse": {
"type": "object",
"properties": {

View File

@@ -476,6 +476,8 @@ definitions:
type: string
user_id:
type: integer
user_token_id:
type: integer
uuid:
type: string
type: object
@@ -653,6 +655,34 @@ definitions:
total:
type: integer
type: object
model.UserToken:
properties:
created_at:
type: string
expired_at:
type: integer
id:
type: integer
token:
type: string
updated_at:
type: string
user_id:
type: integer
type: object
model.UserTokenList:
properties:
list:
items:
$ref: '#/definitions/model.UserToken'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
type: object
response.ErrorResponse:
properties:
error:
@@ -873,9 +903,9 @@ paths:
post:
consumes:
- application/json
description: 创建地址簿集合
description: 创建地址簿名称
parameters:
- description: 地址簿集合信息
- description: 地址簿名称信息
in: body
name: body
required: true
@@ -899,14 +929,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿集合
summary: 创建地址簿名称
tags:
- 地址簿名称
/admin/address_book_collection/delete:
post:
consumes:
- application/json
description: 地址簿集合删除
description: 地址簿名称删除
parameters:
- description: 地址簿集合信息
- description: 地址簿名称信息
in: body
name: body
required: true
@@ -925,12 +957,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合删除
summary: 地址簿名称删除
tags:
- 地址簿名称
/admin/address_book_collection/detail/{id}:
get:
consumes:
- application/json
description: 地址簿集合详情
description: 地址簿名称详情
parameters:
- description: ID
in: path
@@ -955,12 +989,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合详情
summary: 地址簿名称详情
tags:
- 地址簿名称
/admin/address_book_collection/list:
get:
consumes:
- application/json
description: 地址簿集合列表
description: 地址簿名称列表
parameters:
- description: 页码
in: query
@@ -996,14 +1032,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合列表
summary: 地址簿名称列表
tags:
- 地址簿名称
/admin/address_book_collection/update:
post:
consumes:
- application/json
description: 地址簿集合编辑
description: 地址簿名称编辑
parameters:
- description: 地址簿集合信息
- description: 地址簿名称信息
in: body
name: body
required: true
@@ -1027,14 +1065,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合编辑
summary: 地址簿名称编辑
tags:
- 地址簿名称
/admin/address_book_collection_rule/create:
post:
consumes:
- application/json
description: 创建地址簿集合规则
description: 创建地址簿规则
parameters:
- description: 地址簿集合规则信息
- description: 地址簿规则信息
in: body
name: body
required: true
@@ -1058,14 +1098,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿集合规则
summary: 创建地址簿规则
tags:
- 地址簿规则
/admin/address_book_collection_rule/delete:
post:
consumes:
- application/json
description: 地址簿集合规则删除
description: 地址簿规则删除
parameters:
- description: 地址簿集合规则信息
- description: 地址簿规则信息
in: body
name: body
required: true
@@ -1084,12 +1126,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则删除
summary: 地址簿规则删除
tags:
- 地址簿规则
/admin/address_book_collection_rule/detail/{id}:
get:
consumes:
- application/json
description: 地址簿集合规则详情
description: 地址簿规则详情
parameters:
- description: ID
in: path
@@ -1114,12 +1158,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则详情
summary: 地址簿规则详情
tags:
- 地址簿规则
/admin/address_book_collection_rule/list:
get:
consumes:
- application/json
description: 地址簿集合规则列表
description: 地址簿规则列表
parameters:
- description: 页码
in: query
@@ -1159,14 +1205,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则列表
summary: 地址簿规则列表
tags:
- 地址簿规则
/admin/address_book_collection_rule/update:
post:
consumes:
- application/json
description: 地址簿集合规则编辑
description: 地址簿规则编辑
parameters:
- description: 地址簿集合规则信息
- description: 地址簿规则信息
in: body
name: body
required: true
@@ -1190,7 +1238,9 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则编辑
summary: 地址簿规则编辑
tags:
- 地址簿规则
/admin/app-config:
get:
consumes:
@@ -1546,7 +1596,7 @@ paths:
summary: 登录选项
tags:
- 登录
/admin/loginLog/delete:
/admin/login_log/delete:
post:
consumes:
- application/json
@@ -1574,7 +1624,7 @@ paths:
summary: 登录日志删除
tags:
- 登录日志
/admin/loginLog/detail/{id}:
/admin/login_log/detail/{id}:
get:
consumes:
- application/json
@@ -1606,7 +1656,7 @@ paths:
summary: 登录日志详情
tags:
- 登录日志
/admin/loginLog/list:
/admin/login_log/list:
get:
consumes:
- application/json
@@ -1860,6 +1910,34 @@ paths:
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:
@@ -1897,14 +1975,14 @@ paths:
post:
consumes:
- application/json
description: 批量设备删除
description: 设备删除
parameters:
- description: 设备id
- description: 设备信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.PeerBatchDeleteForm'
$ref: '#/definitions/admin.PeerForm'
produces:
- application/json
responses:
@@ -1918,7 +1996,7 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 批量设备删除
summary: 设备删除
tags:
- 设备
/admin/peer/detail/{id}:
@@ -1979,6 +2057,10 @@ paths:
in: query
name: hostname
type: string
- description: uuids 用逗号分隔
in: query
name: uuids
type: string
produces:
- application/json
responses:
@@ -2498,6 +2580,73 @@ paths:
summary: 修改密码
tags:
- 用户
/admin/user_token/delete:
post:
consumes:
- application/json
description: 登录凭证删除
parameters:
- description: 登录凭证信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.UserToken'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录凭证删除
tags:
- 登录凭证
/admin/user_token/list:
get:
consumes:
- application/json
description: 登录凭证列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
- description: 用户ID
in: query
name: user_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.UserTokenList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录凭证列表
tags:
- 登录凭证
securityDefinitions:
BearerAuth:
in: header

View File

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

View File

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

View File

@@ -11,7 +11,6 @@ import (
"Gwen/service"
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type Login struct {
@@ -53,19 +52,14 @@ func (ct *Login) Login(c *gin.Context) {
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: "webadmin",
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 登出
@@ -96,14 +90,11 @@ func (ct *Login) Logout(c *gin.Context) {
// @Failure 500 {object} response.ErrorResponse
// @Router /admin/login-options [post]
func (ct *Login) LoginOptions(c *gin.Context) {
res := service.AllService.OauthService.List(1, 100, func(tx *gorm.DB) {
tx.Select("op").Order("id")
ops := service.AllService.OauthService.GetOauthProviders()
response.Success(c, gin.H{
"ops": ops,
"register": global.Config.App.Register,
})
var ops []string
for _, v := range res.Oauths {
ops = append(ops, v.Op)
}
response.Success(c, ops)
}
// OidcAuth
@@ -160,12 +151,14 @@ func (ct *Login) OidcAuthQuery(c *gin.Context) {
if ut == nil {
return
}
//fmt.Println("u:", u)
//fmt.Println("ut:", ut)
response.Success(c, &adResp.LoginPayload{
Token: ut.Token,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
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{}
@@ -78,7 +78,7 @@ func (ct *LoginLog) List(c *gin.Context) {
// @Param body body model.LoginLog true "登录日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/loginLog/delete [post]
// @Router /admin/login_log/delete [post]
// @Security token
func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{}

View File

@@ -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.BindOauthUser(v.Op, v.ThirdOpenId, v.ThirdName, 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,28 +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
}
}
if f.Op == model.OauthTypeOidc {
err = service.AllService.OauthService.UnBindOidcUser(u.Id)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}
response.Success(c, nil)
}
@@ -196,15 +180,18 @@ 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

View File

@@ -79,6 +79,7 @@ func (ct *Peer) Create(c *gin.Context) {
// @Param time_ago query int false "时间"
// @Param id query string false "ID"
// @Param hostname query string false "主机名"
// @Param uuids query string false "uuids 用逗号分隔"
// @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response
// @Router /admin/peer/list [get]
@@ -104,6 +105,9 @@ func (ct *Peer) List(c *gin.Context) {
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
})
response.Success(c, res)
}
@@ -188,7 +192,7 @@ func (ct *Peer) Delete(c *gin.Context) {
// @Param body body admin.PeerBatchDeleteForm true "设备id"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/peer/delete [post]
// @Router /admin/peer/batchDelete [post]
// @Security token
func (ct *Peer) BatchDelete(c *gin.Context) {
f := &admin.PeerBatchDeleteForm{}

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 {
@@ -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
}
@@ -294,6 +295,51 @@ 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{}
@@ -323,3 +369,35 @@ func (ct *User) GroupUsers(c *gin.Context) {
}
response.Success(c, data)
}
// Register
func (ct *User) Register(c *gin.Context) {
if !global.Config.App.Register {
response.Fail(c, 101, response.TranslateMsg(c, "RegisterClosed"))
return
}
f := &admin.RegisterForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.Register(f.Username, f.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

@@ -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,
@@ -83,22 +84,10 @@ func (l *Login) Login(c *gin.Context) {
// @Failure 500 {object} response.ErrorResponse
// @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)
}
err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeOidc)
if err == nil {
oauthOks = append(oauthOks, model.OauthTypeOidc)
}
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)
@@ -108,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,13 +30,11 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//fmt.Println(f)
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub && f.Op != model.OauthTypeOidc {
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
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
@@ -98,6 +94,7 @@ func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken)
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,
@@ -149,70 +146,43 @@ 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
var u *model.User
openid := ""
thirdName := ""
//fmt.Println("ty ac ", ty, ac)
if ty == model.OauthTypeGithub {
code := c.Query("code")
err, userData := service.AllService.OauthService.GithubCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
return
}
openid = strconv.Itoa(userData.Id)
thirdName = userData.Login
} else if ty == model.OauthTypeGoogle {
code := c.Query("code")
err, userData := service.AllService.OauthService.GoogleCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
return
}
openid = userData.Email
//将空格替换成_
thirdName = strings.Replace(userData.Name, " ", "_", -1)
} else if ty == model.OauthTypeOidc {
code := c.Query("code")
err, userData := service.AllService.OauthService.OidcCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
return
}
openid = userData.Sub
thirdName = userData.PreferredUsername
} else {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ParamsError"))
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
}
if ac == service.OauthActionTypeBind {
userId := oauthCache.UserId
openid := oauthUser.OpenId
if action == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, openid)
// 检查此openid是否已经绑定过
utr := oauthService.UserThirdInfo(op, openid)
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
u = service.AllService.UserService.InfoById(v.UserId)
if u == nil {
user = service.AllService.UserService.InfoById(userId)
if user == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定
err := service.AllService.OauthService.BindOauthUser(ty, openid, thirdName, v.UserId)
err := oauthService.BindOauthUser(userId, oauthUser, op)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
@@ -220,42 +190,41 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return
} else if ac == service.OauthActionTypeLogin {
} else if action == service.OauthActionTypeLogin {
//登录
if v.UserId != 0 {
if userId != 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
return
}
u = service.AllService.UserService.InfoByGithubId(openid)
if u == nil {
oa := service.AllService.OauthService.InfoByOp(ty)
if !*oa.AutoRegister {
user = service.AllService.UserService.InfoByOauthId(op, openid)
if user == nil {
oauthConfig := oauthService.InfoByOp(op)
if !*oauthConfig.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
v.ThirdName = thirdName
v.ThirdOpenId = openid
oauthCache.UpdateFromOauthUser(oauthUser)
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
c.Redirect(http.StatusFound, url)
return
}
//自动注册
u = service.AllService.UserService.RegisterByOauth(ty, thirdName, openid)
if u.Id == 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, err.Error()))
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
oauthCache.UserId = user.Id
oauthService.SetOauthCache(cacheKey, oauthCache, 0)
// 如果是webadmin登录成功后跳转到webadmin
if v.DeviceType == "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: v.DeviceOs,
Platform: oauthService.DeviceOs,
})*/
url := global.Config.Rustdesk.ApiServer + "/_admin/#/"
c.Redirect(http.StatusFound, url)

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

@@ -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,19 +15,21 @@ type UnBindOauthForm struct {
Op string `json:"op" binding:"required"`
}
type OauthForm struct {
Id uint `json:"id"`
Op string `json:"op" validate:"required"`
Issuer string `json:"issuer" validate:"omitempty,url"`
Scopes string `json:"scopes" validate:"omitempty"`
ClientId string `json:"client_id" validate:"required"`
ClientSecret string `json:"client_secret" validate:"required"`
RedirectUrl string `json:"redirect_url" validate:"required"`
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,

View File

@@ -38,6 +38,7 @@ 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 {

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
@@ -59,3 +62,10 @@ 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

@@ -4,19 +4,28 @@ 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", "MyAddressBookCollection",
"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 {

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)
@@ -30,6 +30,7 @@ func Init(g *gin.Engine) {
AuditBind(adg)
AddressBookCollectionBind(adg)
AddressBookCollectionRuleBind(adg)
UserTokenBind(adg)
rs := &admin.Rustdesk{}
adg.GET("/server-config", rs.ServerConfig)
adg.GET("/app-config", rs.AppConfig)
@@ -52,6 +53,7 @@ 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())
@@ -107,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)
@@ -114,10 +118,7 @@ func PeerBind(rg *gin.RouterGroup) {
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
aR.POST("/simpleData", cont.SimpleData)
arp := aR.Use(middleware.AdminPrivilege())
arp.POST("/batchDelete", cont.BatchDelete)
aR.POST("/batchDelete", cont.BatchDelete)
}
}
@@ -181,6 +182,12 @@ func AddressBookCollectionRuleBind(rg *gin.RouterGroup) {
aR.POST("/delete", cont.Delete)
}
}
func UserTokenBind(rg *gin.RouterGroup) {
aR := rg.Group("/user_token").Use(middleware.AdminPrivilege())
cont := &admin.UserToken{}
aR.GET("/list", cont.List)
aR.POST("/delete", cont.Delete)
}
/*
func FileBind(rg *gin.RouterGroup) {

View File

@@ -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,23 +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"`
Issuer string `json:"issuer"`
TimeModel
}
const (
OauthTypeGithub = "github"
OauthTypeGoogle = "google"
OauthTypeOidc = "oidc"
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

@@ -2,7 +2,9 @@ package model
type User struct {
IdModel
Username string `json:"username" gorm:"default:'';not null;uniqueIndex"`
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

@@ -9,9 +9,10 @@ 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"
@@ -20,6 +21,9 @@ import (
"time"
)
type OauthService struct {
}
// Define a struct to parse the .well-known/openid-configuration response
type OidcEndpoint struct {
Issuer string `json:"issuer"`
@@ -28,85 +32,28 @@ type OidcEndpoint struct {
UserInfo string `json:"userinfo_endpoint"`
}
type OauthService struct {
}
type GithubUserdata struct {
AvatarUrl string `json:"avatar_url"`
Bio string `json:"bio"`
Blog string `json:"blog"`
Collaborators int `json:"collaborators"`
Company interface{} `json:"company"`
CreatedAt time.Time `json:"created_at"`
DiskUsage int `json:"disk_usage"`
Email interface{} `json:"email"`
EventsUrl string `json:"events_url"`
Followers int `json:"followers"`
FollowersUrl string `json:"followers_url"`
Following int `json:"following"`
FollowingUrl string `json:"following_url"`
GistsUrl string `json:"gists_url"`
GravatarId string `json:"gravatar_id"`
Hireable interface{} `json:"hireable"`
HtmlUrl string `json:"html_url"`
Id int `json:"id"`
Location interface{} `json:"location"`
Login string `json:"login"`
Name string `json:"name"`
NodeId string `json:"node_id"`
NotificationEmail interface{} `json:"notification_email"`
OrganizationsUrl string `json:"organizations_url"`
OwnedPrivateRepos int `json:"owned_private_repos"`
Plan struct {
Collaborators int `json:"collaborators"`
Name string `json:"name"`
PrivateRepos int `json:"private_repos"`
Space int `json:"space"`
} `json:"plan"`
PrivateGists int `json:"private_gists"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsUrl string `json:"received_events_url"`
ReposUrl string `json:"repos_url"`
SiteAdmin bool `json:"site_admin"`
StarredUrl string `json:"starred_url"`
SubscriptionsUrl string `json:"subscriptions_url"`
TotalPrivateRepos int `json:"total_private_repos"`
//TwitterUsername interface{} `json:"twitter_username"`
TwoFactorAuthentication bool `json:"two_factor_authentication"`
Type string `json:"type"`
UpdatedAt time.Time `json:"updated_at"`
Url string `json:"url"`
}
type GoogleUserdata struct {
Email string `json:"email"`
FamilyName string `json:"family_name"`
GivenName string `json:"given_name"`
Id string `json:"id"`
Name string `json:"name"`
Picture string `json:"picture"`
VerifiedEmail bool `json:"verified_email"`
}
type OidcUserdata struct {
Sub string `json:"sub"`
Email string `json:"email"`
VerifiedEmail bool `json:"email_verified"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
}
type OauthCacheItem struct {
UserId uint `json:"user_id"`
Id string `json:"id"` //rustdesk的设备ID
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{}
@@ -116,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 {
@@ -140,22 +94,21 @@ 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, ""
}
// Method to fetch OIDC configuration dynamically
func FetchOidcConfig(issuer string) (error, OidcEndpoint) {
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)
@@ -179,76 +132,58 @@ func FetchOidcConfig(issuer string) (error, OidcEndpoint) {
return nil, endpoint
}
// GetOauthConfig retrieves the OAuth2 configuration based on the provider type
func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
switch op {
case model.OauthTypeGithub:
return os.getGithubConfig()
case model.OauthTypeGoogle:
return os.getGoogleConfig()
case model.OauthTypeOidc:
return os.getOidcConfig()
default:
return errors.New("unsupported OAuth type"), nil
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)
}
// Helper function to get GitHub OAuth2 configuration
func (os *OauthService) getGithubConfig() (error, *oauth2.Config) {
g := os.InfoByOp(model.OauthTypeGithub)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("ConfigNotFound"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: github.Endpoint,
Scopes: []string{"read:user", "user:email"},
}
}
// Helper function to get Google OAuth2 configuration
func (os *OauthService) getGoogleConfig() (error, *oauth2.Config) {
g := os.InfoByOp(model.OauthTypeGoogle)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("ConfigNotFound"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: google.Endpoint,
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
}
}
// Helper function to get OIDC OAuth2 configuration
func (os *OauthService) getOidcConfig() (error, *oauth2.Config) {
g := os.InfoByOp(model.OauthTypeOidc)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" || g.Issuer == "" {
return errors.New("ConfigNotFound"), nil
}
// Set scopes
scopes := strings.TrimSpace(g.Scopes)
if scopes == "" {
scopes = "openid,profile,email"
}
scopeList := strings.Split(scopes, ",")
err, endpoint := FetchOidcConfig(g.Issuer)
// 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
return err, nil, nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
ClientSecret: g.ClientSecret,
RedirectURL: g.RedirectUrl,
Endpoint: oauth2.Endpoint{
AuthURL: endpoint.AuthURL,
TokenURL: endpoint.TokenURL,
},
Scopes: scopeList,
// 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,
}
}
@@ -272,194 +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)
if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
error = errors.New("GetOauthTokenError")
return
}
// 使用带有代理的 HTTP 客户端获取用户信息
client := oauthConfig.Client(ctx, token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
global.Logger.Warn("failed getting user info: ", err)
error = errors.New("GetOauthUserInfoError")
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
global.Logger.Warn("failed closing response body: ", err)
}
}(resp.Body)
// 解析用户信息
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
global.Logger.Warn("failed decoding user info: ", err)
error = errors.New("DecodeOauthUserInfoError")
return
}
return
}
func (os *OauthService) OidcCallback(code string) (error error, userData *OidcUserdata) {
err, oauthConfig := os.GetOauthConfig(model.OauthTypeOidc)
err = os.getGithubPrimaryEmail(client, user)
if err != nil {
return err, nil
}
// 使用代理配置创建 HTTP 客户端
httpClient := getHTTPClientWithProxy()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
token, err := oauthConfig.Exchange(ctx, code)
if err != nil {
global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
error = errors.New("GetOauthTokenError")
return
}
// 使用带有代理的 HTTP 客户端获取用户信息
client := oauthConfig.Client(ctx, token)
g := os.InfoByOp(model.OauthTypeOidc)
err, endpoint := FetchOidcConfig(g.Issuer)
if err != nil {
global.Logger.Warn("failed fetching OIDC configuration: ", err)
error = errors.New("FetchOidcConfigError")
return
}
resp, err := client.Get(endpoint.UserInfo)
if err != nil {
global.Logger.Warn("failed getting user info: ", err)
error = errors.New("GetOauthUserInfoError")
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
global.Logger.Warn("failed closing response body: ", err)
}
}(resp.Body)
// 解析用户信息
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
global.Logger.Warn("failed decoding user info: ", err)
error = errors.New("DecodeOauthUserInfoError")
return
}
return
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) BindOidcUser(sub, username string, userId uint) error {
return os.BindOauthUser(model.OauthTypeOidc, sub, username, userId)
}
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
utr := &model.UserThird{
OpenId: openid,
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)
// UnBindOauthUser: Unbind third party account
func (os *OauthService) UnBindOauthUser(userId uint, op string) error {
return os.UnBindThird(op, userId)
}
func (os *OauthService) UnBindGoogleUser(userid uint) error {
return os.UnBindThird(model.OauthTypeGoogle, userid)
}
func (os *OauthService) UnBindOidcUser(userid uint) error {
return os.UnBindThird(model.OauthTypeOidc, userid)
}
func (os *OauthService) UnBindThird(thirdType string, userid uint) error {
return global.DB.Where("user_id = ? and third_type = ?", userid, thirdType).Delete(&model.UserThird{}).Error
// 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
func (os *OauthService) DeleteUserByUserId(userId uint) error {
return global.DB.Where("user_id = ?", userId).Delete(&model.UserThird{}).Error
}
// InfoById 根据id取用户信息
// 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)
@@ -474,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

@@ -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
}
@@ -139,18 +153,42 @@ 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 退出登录
// 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 {
return global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).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 {
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 {
@@ -178,11 +216,25 @@ func (us *UserService) Delete(u *model.User) error {
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
}
@@ -191,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)
@@ -215,24 +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)
}
// InfoByOidcSub 根据oidc取用户信息
func (us *UserService) InfoByOidcSub(sub string) *model.User {
return us.InfoByOauthId(model.OauthTypeOidc, sub)
}
// InfoByOauthId 根据oauth取用户信息
func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
// 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
}
@@ -243,55 +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)
}
// RegisterByOidc 注册, use PreferredUsername as username, sub as openid
func (us *UserService) RegisterByOidc(PreferredUsername string, sub string) *model.User {
return us.RegisterByOauth(model.OauthTypeOidc, PreferredUsername, sub)
}
// RegisterByOauth 注册
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User {
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(thirdType, uid)
ut := AllService.OauthService.UserThirdInfo(op, oauthUser.OpenId)
if ut.Id != 0 {
u := &model.User{}
global.DB.Where("id = ?", ut.UserId).First(u)
return u
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
}
}
tx := global.DB.Begin()
ut = &model.UserThird{
OpenId: uid,
ThirdName: thirdName,
ThirdType: thirdType,
}
username := us.GenerateUsernameByOauth(thirdName)
u := &model.User{
Username: username,
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,
}
tx.Create(u)
if u.Id == 0 {
oauthUser.ToUser(user, false)
tx.Create(user)
if user.Id == 0 {
tx.Rollback()
return u
return errors.New("OauthRegisterFailed"), user
}
ut.UserId = u.Id
ut.UserId = user.Id
tx.Create(ut)
tx.Commit()
return u
return nil, user
}
// GenerateUsernameByOauth 生成用户名
@@ -313,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
}
@@ -323,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)
}
}