Compare commits

..

39 Commits

Author SHA1 Message Date
ljw
273ac6d1a8 add remove user token #34 2024-10-31 22:29:12 +08:00
5edbb39a63 Merge pull request #40 from IamTaoChen/resetEmptyPassWD
Reset empty password
2024-10-31 18:46:34 +08:00
Tao Chen
0c974c4113 Merge branch 'master' into resetEmptyPassWD 2024-10-31 16:35:32 +08:00
Tao Chen
46657a525d ommit check old passwd if password is empty 2024-10-31 16:23:06 +08:00
Tao Chen
b36aa6f917 add IsPasswordEmpty... 2024-10-31 16:22:42 +08:00
ljw
cddb0ebef9 up readme #28 2024-10-31 15:48:36 +08:00
ljw
788c4e3531 up readme #28 2024-10-31 15:47:39 +08:00
ljw
47f9ad8274 add register 2024-10-31 15:14:30 +08:00
ljw
855beb7fa9 up oauth 2024-10-31 14:03:48 +08:00
f57816b1b0 Merge pull request #36 from IamTaoChen/oidc-for-web
OIDC for web
2024-10-31 11:10:46 +08:00
Tao Chen
ff08fefc30 rename build stage 2024-10-31 09:21:43 +08:00
Tao Chen
f792ab9055 add some /admin/ to surport web OIDC 2024-10-31 09:21:30 +08:00
ljw
63af103a4e fix buidconfirm 2024-10-30 20:59:51 +08:00
ljw
0a36d44cec up del user 2024-10-30 19:34:56 +08:00
a1f4e1de84 Merge pull request #32 from IamTaoChen/bug/odic-user
delete user from user_thirds and update README
2024-10-30 19:08:50 +08:00
Tao Chen
05b20d47db modify delete user 2024-10-30 16:33:01 +08:00
Tao Chen
6b746f13d1 update README 2024-10-30 16:31:47 +08:00
Tao Chen
e838d5bcd2 update README for OIDC 2024-10-30 16:29:49 +08:00
Tao Chen
0dcc21260e delete user from user_thirds, too 2024-10-30 15:59:33 +08:00
ljw
3c30ad145c up v 2024-10-30 15:46:12 +08:00
ljw
06b0a8e873 add docker-compose-dev.yaml 2024-10-30 15:34:45 +08:00
b7de2ccadd Merge pull request #30 from IamTaoChen/oidc
Add General OIDC Login
2024-10-30 14:40:10 +08:00
Tao Chen
b52c5cfca1 bind oidc ThirdEmail 2024-10-29 23:09:54 +08:00
Tao Chen
fe910c37cf fix: spelling 2024-10-29 23:00:17 +08:00
Tao Chen
337ef330eb fix bug 2024-10-29 18:48:37 +08:00
Tao Chen
ffa47177aa fix bug - oidc scopes 2024-10-29 18:46:45 +08:00
ljw
46a76853c3 fix oauth register #26 #23 2024-10-29 15:31:27 +08:00
Tao Chen
2cd7dfb2b3 fix bug 2024-10-29 14:27:15 +08:00
Tao Chen
fee2808bca try add oidc 2024-10-29 11:51:01 +08:00
Tao Chen
49e5eb186a optimize docker 2024-10-29 11:50:55 +08:00
Tao Chen
dee2865466 optimize build.sh 2024-10-29 10:58:17 +08:00
ljw
eb340b2615 add last online ip #24 2024-10-28 20:24:34 +08:00
ljw
e714549a95 up address book add version #20 2024-10-28 19:48:47 +08:00
ljw
a1367bcd3d up peer update 2024-10-28 19:15:13 +08:00
ljw
642351dafd fix bug #27 2024-10-28 16:08:33 +08:00
ljw
90b9b5adba up 2024-10-28 14:54:00 +08:00
ljw
5bf4bbe45f add address book name &
add share address book
2024-10-28 14:51:07 +08:00
ljw
036f928fa3 up readme 2024-10-23 11:09:28 +08:00
ljw
94e7b31fb6 fix group 2024-10-23 11:09:13 +08:00
62 changed files with 4683 additions and 398 deletions

26
.dockerignore Normal file
View File

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

72
Dockerfile.dev Normal file
View File

@@ -0,0 +1,72 @@
# Use build arguments for Go version and architecture
ARG GO_VERSION=1.22
ARG BUILDARCH=amd64
# Stage 1: Builder Stage
# FROM golang:${GO_VERSION}-alpine AS builder
FROM crazymax/xgo:${GO_VERSION} AS builder-backend
# Set up working directory
WORKDIR /app
# Step 1: Copy the source code
COPY . .
# Step 2: Download dependencies
RUN go mod tidy && go mod download
# Step 3: Install swag and Run the build script
RUN go install github.com/swaggo/swag/cmd/swag@latest && \
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin && \
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
# Build the Go application with CGO enabled and specified ldflags
RUN CGO_ENABLED=1 GOOS=linux go build -a \
-ldflags "-s -w --extldflags '-static -fpic'" \
-installsuffix cgo -o release/apimain cmd/apimain.go
# Stage 2: Frontend Build Stage (builder2)
FROM node:18-alpine AS builder-admin-frontend
# Set working directory
WORKDIR /frontend
RUN apk update && apk add git --no-cache
# 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
# Stage 2: Final Image
FROM alpine:latest
# Set up working directory
WORKDIR /app
# Install necessary runtime dependencies
RUN apk add --no-cache tzdata file
# Copy the built application and resources from the builder stage
COPY --from=builder-backend /app/release /app/
COPY --from=builder-backend /app/conf /app/conf/
COPY --from=builder-backend /app/resources /app/resources/
COPY --from=builder-backend /app/docs /app/docs/
# Copy frontend build from builder2 stage
COPY --from=builder-admin-frontend /frontend/dist/ /app/resources/admin/
# Ensure the binary is correctly built and linked
RUN file /app/apimain && \
mkdir -p /app/data && \
mkdir -p /app/runtime
# Set up a volume for persistent data
VOLUME /app/data
# Expose the necessary port
EXPOSE 21114
# Define the command to run the application
CMD ["./apimain"]

View File

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

View File

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

38
build.sh Normal file → Executable file
View File

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

View File

@@ -101,7 +101,7 @@ func main() {
} }
func DatabaseAutoUpdate() { func DatabaseAutoUpdate() {
version := 235 version := 244
db := global.DB db := global.DB
@@ -165,6 +165,8 @@ func Migrate(version uint) {
&model.ShareRecord{}, &model.ShareRecord{},
&model.AuditConn{}, &model.AuditConn{},
&model.AuditFile{}, &model.AuditFile{},
&model.AddressBookCollection{},
&model.AddressBookCollectionRule{},
) )
if err != nil { if err != nil {
fmt.Println("migrate err :=>", err) fmt.Println("migrate err :=>", err)

View File

@@ -1,6 +1,7 @@
lang: "zh-CN" lang: "zh-CN"
app: app:
web-client: 1 # 1:启用 0:禁用 web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
gin: gin:
api-addr: "0.0.0.0:21114" api-addr: "0.0.0.0:21114"
mode: "release" #release,debug,test mode: "release" #release,debug,test
@@ -27,7 +28,7 @@ logger:
report-caller: true report-caller: true
proxy: proxy:
enable: false enable: false
host: "" host: "http://127.0.0.1:1080"
redis: redis:
addr: "127.0.0.1:6379" addr: "127.0.0.1:6379"
password: "" password: ""

View File

@@ -15,7 +15,8 @@ const (
) )
type App struct { type App struct {
WebClient int `mapstructure:"web-client"` WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
} }
type Config struct { type Config struct {

View File

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

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

@@ -0,0 +1,20 @@
services:
rustdesk-api:
build:
context: .
dockerfile: Dockerfile.dev
# image: lejianwen/rustdesk-api
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
volumes:
- ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份
- ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源
restart: unless-stopped

View File

@@ -6,10 +6,12 @@ services:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 - RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789 - RUSTDESK_API_RUSTDESK_KEY=123456789
ports: ports:
- 21114:21114 - 21114:21114
volumes: volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份 - ./data/rustdesk/api:/app/data # database
# - ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源
restart: unless-stopped restart: unless-stopped

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,8 @@ definitions:
properties: properties:
alias: alias:
type: string type: string
collection_id:
type: integer
forceAlwaysRelay: forceAlwaysRelay:
type: boolean type: boolean
hash: hash:
@@ -103,10 +105,14 @@ definitions:
type: string type: string
id: id:
type: integer type: integer
issuer:
type: string
op: op:
type: string type: string
redirect_url: redirect_url:
type: string type: string
scopes:
type: string
required: required:
- client_id - client_id
- client_secret - client_secret
@@ -164,6 +170,8 @@ definitions:
type: object type: object
admin.TagForm: admin.TagForm:
properties: properties:
collection_id:
type: integer
color: color:
type: integer type: integer
id: id:
@@ -225,6 +233,10 @@ definitions:
properties: properties:
alias: alias:
type: string type: string
collection:
$ref: '#/definitions/model.AddressBookCollection'
collection_id:
type: integer
created_at: created_at:
type: string type: string
forceAlwaysRelay: forceAlwaysRelay:
@@ -262,6 +274,64 @@ definitions:
username: username:
type: string type: string
type: object type: object
model.AddressBookCollection:
properties:
created_at:
type: string
id:
type: integer
name:
type: string
updated_at:
type: string
user_id:
type: integer
required:
- name
type: object
model.AddressBookCollectionList:
properties:
list:
items:
$ref: '#/definitions/model.AddressBookCollection'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
type: object
model.AddressBookCollectionRule:
properties:
collection_id:
type: integer
created_at:
type: string
id:
type: integer
rule:
description: '0: 无 1: 读 2: 读写 3: 完全控制'
maximum: 3
minimum: 1
type: integer
to_id:
type: integer
type:
description: '1: 个人 2: 群组'
maximum: 2
minimum: 1
type: integer
updated_at:
type: string
user_id:
type: integer
required:
- collection_id
- rule
- to_id
- type
type: object
model.AddressBookList: model.AddressBookList:
properties: properties:
list: list:
@@ -406,6 +476,8 @@ definitions:
type: string type: string
user_id: user_id:
type: integer type: integer
user_token_id:
type: integer
uuid: uuid:
type: string type: string
type: object type: object
@@ -434,10 +506,14 @@ definitions:
type: string type: string
id: id:
type: integer type: integer
issuer:
type: string
op: op:
type: string type: string
redirect_url: redirect_url:
type: string type: string
scopes:
type: string
updated_at: updated_at:
type: string type: string
type: object type: object
@@ -464,6 +540,8 @@ definitions:
type: string type: string
id: id:
type: string type: string
last_online_ip:
type: string
last_online_time: last_online_time:
type: integer type: integer
memory: memory:
@@ -511,6 +589,10 @@ definitions:
- COMMON_STATUS_DISABLED - COMMON_STATUS_DISABLED
model.Tag: model.Tag:
properties: properties:
collection:
$ref: '#/definitions/model.AddressBookCollection'
collection_id:
type: integer
color: color:
description: color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色, description: color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色,
可以转成rgba 可以转成rgba
@@ -573,6 +655,39 @@ definitions:
total: total:
type: integer type: integer
type: object type: object
model.UserToken:
properties:
created_at:
type: string
expired_at:
type: integer
id:
type: integer
token:
type: string
updated_at:
type: string
user_id:
type: integer
type: object
model.UserTokenList:
properties:
list:
items:
$ref: '#/definitions/model.UserToken'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
type: object
response.ErrorResponse:
properties:
error:
type: string
type: object
response.Response: response.Response:
properties: properties:
code: code:
@@ -784,6 +899,328 @@ paths:
summary: 地址簿编辑 summary: 地址簿编辑
tags: tags:
- 地址簿 - 地址簿
/admin/address_book_collection/create:
post:
consumes:
- application/json
description: 创建地址簿集合
parameters:
- description: 地址簿集合信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollection'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿集合
/admin/address_book_collection/delete:
post:
consumes:
- application/json
description: 地址簿集合删除
parameters:
- description: 地址簿集合信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollection'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合删除
/admin/address_book_collection/detail/{id}:
get:
consumes:
- application/json
description: 地址簿集合详情
parameters:
- description: ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合详情
/admin/address_book_collection/list:
get:
consumes:
- application/json
description: 地址簿集合列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
- description: 是否是我的
in: query
name: is_my
type: integer
- description: 用户id
in: query
name: user_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollectionList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合列表
/admin/address_book_collection/update:
post:
consumes:
- application/json
description: 地址簿集合编辑
parameters:
- description: 地址簿集合信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollection'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合编辑
/admin/address_book_collection_rule/create:
post:
consumes:
- application/json
description: 创建地址簿集合规则
parameters:
- description: 地址簿集合规则信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollectionRule'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿集合规则
/admin/address_book_collection_rule/delete:
post:
consumes:
- application/json
description: 地址簿集合规则删除
parameters:
- description: 地址簿集合规则信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollectionRule'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则删除
/admin/address_book_collection_rule/detail/{id}:
get:
consumes:
- application/json
description: 地址簿集合规则详情
parameters:
- description: ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollectionRule'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则详情
/admin/address_book_collection_rule/list:
get:
consumes:
- application/json
description: 地址簿集合规则列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
- description: 是否是我的
in: query
name: is_my
type: integer
- description: 用户id
in: query
name: user_id
type: integer
- description: 地址簿集合id
in: query
name: collection_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollectionList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则列表
/admin/address_book_collection_rule/update:
post:
consumes:
- application/json
description: 地址簿集合规则编辑
parameters:
- description: 地址簿集合规则信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.AddressBookCollectionRule'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.AddressBookCollection'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 地址簿集合规则编辑
/admin/app-config: /admin/app-config:
get: get:
consumes: consumes:
@@ -1118,7 +1555,28 @@ paths:
summary: 登录 summary: 登录
tags: tags:
- 登录 - 登录
/admin/loginLog/delete: /admin/login-options:
post:
consumes:
- application/json
description: 登录选项
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: 登录选项
tags:
- 登录
/admin/login_log/delete:
post: post:
consumes: consumes:
- application/json - application/json
@@ -1146,7 +1604,7 @@ paths:
summary: 登录日志删除 summary: 登录日志删除
tags: tags:
- 登录日志 - 登录日志
/admin/loginLog/detail/{id}: /admin/login_log/detail/{id}:
get: get:
consumes: consumes:
- application/json - application/json
@@ -1178,7 +1636,7 @@ paths:
summary: 登录日志详情 summary: 登录日志详情
tags: tags:
- 登录日志 - 登录日志
/admin/loginLog/list: /admin/login_log/list:
get: get:
consumes: consumes:
- application/json - application/json
@@ -1397,6 +1855,41 @@ paths:
summary: Oauth编辑 summary: Oauth编辑
tags: tags:
- Oauth - Oauth
/admin/oidc/auth:
post:
consumes:
- application/json
description: OidcAuth
produces:
- application/json
responses: {}
summary: OidcAuth
tags:
- Oauth
/admin/oidc/auth-query:
get:
consumes:
- application/json
description: OidcAuthQuery
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/admin.LoginPayload'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: OidcAuthQuery
tags:
- Oauth
/admin/peer/create: /admin/peer/create:
post: post:
consumes: consumes:
@@ -1516,6 +2009,10 @@ paths:
in: query in: query
name: hostname name: hostname
type: string type: string
- description: uuids 用逗号分隔
in: query
name: uuids
type: string
produces: produces:
- application/json - application/json
responses: responses:
@@ -2035,6 +2532,73 @@ paths:
summary: 修改密码 summary: 修改密码
tags: tags:
- 用户 - 用户
/admin/user_token/delete:
post:
consumes:
- application/json
description: 登录凭证删除
parameters:
- description: 登录凭证信息
in: body
name: body
required: true
schema:
$ref: '#/definitions/model.UserToken'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录凭证删除
tags:
- 登录凭证
/admin/user_token/list:
get:
consumes:
- application/json
description: 登录凭证列表
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 页大小
in: query
name: page_size
type: integer
- description: 用户ID
in: query
name: user_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/model.UserTokenList'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 登录凭证列表
tags:
- 登录凭证
securityDefinitions: securityDefinitions:
BearerAuth: BearerAuth:
in: header in: header

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -102,7 +102,7 @@ func (o *Oauth) BindConfirm(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
err = service.AllService.OauthService.BindGithubUser(v.ThirdOpenId, v.ThirdOpenId, u.Id) err = service.AllService.OauthService.BindOauthUser(v.Op, v.ThirdOpenId, v.ThirdName, u.Id)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "BindFail")) response.Fail(c, 101, response.TranslateMsg(c, "BindFail"))
return return
@@ -140,6 +140,13 @@ func (o *Oauth) Unbind(c *gin.Context) {
return 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) response.Success(c, nil)
} }
@@ -202,7 +209,7 @@ func (o *Oauth) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, u) response.Success(c, nil)
} }
// List 列表 // List 列表

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import (
"Gwen/http/request/admin" "Gwen/http/request/admin"
"Gwen/http/response" "Gwen/http/response"
adResp "Gwen/http/response/admin" adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service" "Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
@@ -65,7 +66,7 @@ func (ct *User) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, u) response.Success(c, nil)
} }
// List 列表 // List 列表
@@ -247,10 +248,14 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword) // If the password is not empty, the old password is verified
if u.Password != oldPwd { // otherwise, the old password is not verified
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError")) if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
return oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
if u.Password != oldPwd {
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return
}
} }
err := service.AllService.UserService.UpdatePassword(u, f.NewPassword) err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
if err != nil { if err != nil {
@@ -293,3 +298,70 @@ func (ct *User) MyOauth(c *gin.Context) {
} }
response.Success(c, res) response.Success(c, res)
} }
// groupUsers
func (ct *User) GroupUsers(c *gin.Context) {
q := &admin.GroupUsersQuery{}
if err := c.ShouldBindJSON(q); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
gid := u.GroupId
uid := u.Id
if service.AllService.UserService.IsAdmin(u) && q.UserId > 0 {
nu := service.AllService.UserService.InfoById(q.UserId)
gid = nu.GroupId
uid = q.UserId
}
res := service.AllService.UserService.List(1, 999, func(tx *gorm.DB) {
tx.Where("group_id = ?", gid)
})
var data []*adResp.GroupUsersPayload
for _, _u := range res.Users {
gup := &adResp.GroupUsersPayload{}
gup.FromUser(_u)
if _u.Id == uid {
gup.Status = 0
}
data = append(data, gup)
}
response.Success(c, data)
}
// Register
func (ct *User) Register(c *gin.Context) {
if !global.Config.App.Register {
response.Fail(c, 101, response.TranslateMsg(c, "RegisterClosed"))
return
}
f := &admin.RegisterForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.Register(f.Username, f.Password)
if u == nil || u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed"))
return
}
// 注册成功后自动登录
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: model.LoginLogClientWebAdmin,
Uuid: "",
Ip: c.ClientIP(),
Type: model.LoginLogTypeAccount,
})
response.Success(c, &adResp.LoginPayload{
Token: ut.Token,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
package admin package admin
import "Gwen/model"
type LoginPayload struct { type LoginPayload struct {
Username string `json:"username"` Username string `json:"username"`
Token string `json:"token"` Token string `json:"token"`
@@ -8,7 +10,7 @@ type LoginPayload struct {
} }
var UserRouteNames = []string{ var UserRouteNames = []string{
"MyTagList", "MyAddressBookList", "MyInfo", "MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection",
} }
var AdminRouteNames = []string{"*"} var AdminRouteNames = []string{"*"}
@@ -16,3 +18,15 @@ type UserOauthItem struct {
ThirdType string `json:"third_type"` ThirdType string `json:"third_type"`
Status int `json:"status"` Status int `json:"status"`
} }
type GroupUsersPayload struct {
Id uint `json:"id"`
Username string `json:"username"`
Status int `json:"status"`
}
func (g *GroupUsersPayload) FromUser(user *model.User) {
g.Id = user.Id
g.Username = user.Username
g.Status = 1
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -70,6 +70,7 @@ func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserTok
ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(), ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(),
} }
global.DB.Create(ut) global.DB.Create(ut)
llog.UserTokenId = ut.UserId
global.DB.Create(llog) global.DB.Create(llog)
if llog.Uuid != "" { if llog.Uuid != "" {
AllService.PeerService.UuidBindUserId(llog.Uuid, u.Id) AllService.PeerService.UuidBindUserId(llog.Uuid, u.Id)
@@ -101,6 +102,11 @@ func (us *UserService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *
return return
} }
func (us *UserService) ListByIds(ids []uint) (res []*model.User) {
global.DB.Where("id in ?", ids).Find(&res)
return res
}
// ListByGroupId 根据组id取用户列表 // ListByGroupId 根据组id取用户列表
func (us *UserService) ListByGroupId(groupId, page, pageSize uint) (res *model.UserList) { func (us *UserService) ListByGroupId(groupId, page, pageSize uint) (res *model.UserList) {
res = us.List(page, pageSize, func(tx *gorm.DB) { res = us.List(page, pageSize, func(tx *gorm.DB) {
@@ -143,8 +149,37 @@ func (us *UserService) Create(u *model.User) error {
func (us *UserService) Logout(u *model.User, token string) error { func (us *UserService) Logout(u *model.User, token string) error {
return global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error return global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error
} }
// Delete 删除用户和oauth信息
func (us *UserService) Delete(u *model.User) error { func (us *UserService) Delete(u *model.User) error {
return global.DB.Delete(u).Error tx := global.DB.Begin()
// 删除用户
if err := tx.Delete(u).Error; err != nil {
tx.Rollback()
return err
}
// 删除关联的 OAuth 信息
if err := tx.Where("user_id = ?", u.Id).Delete(&model.UserThird{}).Error; err != nil {
tx.Rollback()
return err
}
// 删除关联的ab
if err := tx.Where("user_id = ?", u.Id).Delete(&model.AddressBook{}).Error; err != nil {
tx.Rollback()
return err
}
// 删除关联的abc
if err := tx.Where("user_id = ?", u.Id).Delete(&model.AddressBookCollection{}).Error; err != nil {
tx.Rollback()
return err
}
// 删除关联的abcr
if err := tx.Where("user_id = ?", u.Id).Delete(&model.AddressBookCollectionRule{}).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
} }
// Update 更新 // Update 更新
@@ -191,6 +226,11 @@ func (us *UserService) InfoByGoogleEmail(email string) *model.User {
return us.InfoByOauthId(model.OauthTypeGithub, email) return us.InfoByOauthId(model.OauthTypeGithub, email)
} }
// InfoByOidcSub 根据oidc取用户信息
func (us *UserService) InfoByOidcSub(sub string) *model.User {
return us.InfoByOauthId(model.OauthTypeOidc, sub)
}
// InfoByOauthId 根据oauth取用户信息 // InfoByOauthId 根据oauth取用户信息
func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User { func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
ut := AllService.OauthService.UserThirdInfo(thirdType, uid) ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
@@ -214,32 +254,42 @@ func (us *UserService) RegisterByGoogle(name string, email string) *model.User {
return us.RegisterByOauth(model.OauthTypeGoogle, name, email) 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 注册 // RegisterByOauth 注册
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User { func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User {
global.Lock.Lock("registerByOauth")
defer global.Lock.UnLock("registerByOauth")
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
if ut.Id != 0 {
u := &model.User{}
global.DB.Where("id = ?", ut.UserId).First(u)
return u
}
tx := global.DB.Begin() tx := global.DB.Begin()
ut := &model.UserThird{ ut = &model.UserThird{
OpenId: uid, OpenId: uid,
ThirdName: thirdName, ThirdName: thirdName,
ThirdType: thirdType, ThirdType: thirdType,
} }
//global.DB.Where("open_id = ?", githubId).First(ut)
//这种情况不应该出现如果出现说明有bug
//if ut.Id != 0 {
// u := &model.User{}
// global.DB.Where("id = ?", ut.UserId).First(u)
// tx.Commit()
// return u
//}
username := us.GenerateUsernameByOauth(thirdName) username := us.GenerateUsernameByOauth(thirdName)
u := &model.User{ u := &model.User{
Username: username, Username: username,
GroupId: 1, GroupId: 1,
} }
global.DB.Create(u) tx.Create(u)
if u.Id == 0 {
tx.Rollback()
return u
}
ut.UserId = u.Id ut.UserId = u.Id
global.DB.Create(ut) tx.Create(ut)
tx.Commit() tx.Commit()
return u return u
@@ -274,3 +324,60 @@ func (us *UserService) FindLatestUserIdFromLoginLogByUuid(uuid string) uint {
global.DB.Where("uuid = ?", uuid).Order("id desc").First(llog) global.DB.Where("uuid = ?", uuid).Order("id desc").First(llog)
return llog.UserId return llog.UserId
} }
// IsPasswordEmptyById 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyById(id uint) bool {
u := &model.User{}
if global.DB.Where("id = ?", id).First(u).Error != nil {
return false
}
return u.Password == ""
}
// IsPasswordEmptyByUsername 根据用户id判断密码是否为空主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyByUsername(username string) bool {
u := &model.User{}
if global.DB.Where("username = ?", username).First(u).Error != nil {
return false
}
return u.Password == ""
}
// IsPasswordEmptyByUser 判断密码是否为空,主要用于第三方登录的自动注册
func (us *UserService) IsPasswordEmptyByUser(u *model.User) bool {
return us.IsPasswordEmptyById(u.Id)
}
func (us *UserService) Register(username string, password string) *model.User {
u := &model.User{
Username: username,
Password: us.EncryptPassword(password),
GroupId: 1,
}
global.DB.Create(u)
return u
}
func (us *UserService) TokenList(page uint, size uint, f func(tx *gorm.DB)) *model.UserTokenList {
res := &model.UserTokenList{}
res.Page = int64(page)
res.PageSize = int64(size)
tx := global.DB.Model(&model.UserToken{})
if f != nil {
f(tx)
}
tx.Count(&res.Total)
tx.Scopes(Paginate(page, size))
tx.Find(&res.UserTokens)
return res
}
func (us *UserService) TokenInfoById(id uint) *model.UserToken {
ut := &model.UserToken{}
global.DB.Where("id = ?", id).First(ut)
return ut
}
func (us *UserService) DeleteToken(l *model.UserToken) error {
return global.DB.Delete(l).Error
}

View File

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