Merge branch 'master' into resetEmptyPassWD

This commit is contained in:
Tao Chen
2024-10-31 16:35:32 +08:00
committed by GitHub
27 changed files with 563 additions and 243 deletions

View File

@@ -159,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"
@@ -196,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

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

View File

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

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

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 {

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

@@ -1,9 +1,6 @@
services: services:
rustdesk-api: rustdesk-api:
build: image: lejianwen/rustdesk-api
context: .
dockerfile: Dockerfile.dev
# image: lejianwen/rustdesk-api
container_name: rustdesk-api container_name: rustdesk-api
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
@@ -14,7 +11,7 @@ services:
ports: ports:
- 21114:21114 - 21114:21114
volumes: volumes:
- ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份 - ./data/rustdesk/api:/app/data # database
- ./conf:/app/conf # config # - ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源 # - ./resources:/app/resources # 静态资源
restart: unless-stopped restart: unless-stopped

View File

@@ -1453,6 +1453,38 @@ const docTemplateadmin = `{
} }
} }
}, },
"/admin/login-options": {
"post": {
"description": "登录选项",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录"
],
"summary": "登录选项",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/admin/loginLog/delete": { "/admin/loginLog/delete": {
"post": { "post": {
"security": [ "security": [
@@ -1922,6 +1954,63 @@ const docTemplateadmin = `{
} }
} }
}, },
"/admin/oidc/auth": {
"post": {
"description": "OidcAuth",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuth",
"responses": {}
}
},
"/admin/oidc/auth-query": {
"get": {
"description": "OidcAuthQuery",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuthQuery",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/admin.LoginPayload"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/create": { "/admin/peer/create": {
"post": { "post": {
"security": [ "security": [
@@ -3164,11 +3253,17 @@ const docTemplateadmin = `{
"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"
} }
} }
}, },
@@ -3749,12 +3844,18 @@ const docTemplateadmin = `{
"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"
} }
@@ -3795,6 +3896,9 @@ const docTemplateadmin = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_online_ip": {
"type": "string"
},
"last_online_time": { "last_online_time": {
"type": "integer" "type": "integer"
}, },
@@ -3964,6 +4068,14 @@ const docTemplateadmin = `{
} }
} }
}, },
"response.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
},
"response.Response": { "response.Response": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -1446,6 +1446,38 @@
} }
} }
}, },
"/admin/login-options": {
"post": {
"description": "登录选项",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"登录"
],
"summary": "登录选项",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/admin/loginLog/delete": { "/admin/loginLog/delete": {
"post": { "post": {
"security": [ "security": [
@@ -1915,6 +1947,63 @@
} }
} }
}, },
"/admin/oidc/auth": {
"post": {
"description": "OidcAuth",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuth",
"responses": {}
}
},
"/admin/oidc/auth-query": {
"get": {
"description": "OidcAuthQuery",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Oauth"
],
"summary": "OidcAuthQuery",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/admin.LoginPayload"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/admin/peer/create": { "/admin/peer/create": {
"post": { "post": {
"security": [ "security": [
@@ -3157,11 +3246,17 @@
"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"
} }
} }
}, },
@@ -3742,12 +3837,18 @@
"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"
} }
@@ -3788,6 +3889,9 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_online_ip": {
"type": "string"
},
"last_online_time": { "last_online_time": {
"type": "integer" "type": "integer"
}, },
@@ -3957,6 +4061,14 @@
} }
} }
}, },
"response.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
},
"response.Response": { "response.Response": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -105,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
@@ -500,10 +504,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
@@ -530,6 +538,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:
@@ -643,6 +653,11 @@ definitions:
total: total:
type: integer type: integer
type: object type: object
response.ErrorResponse:
properties:
error:
type: string
type: object
response.Response: response.Response:
properties: properties:
code: code:
@@ -1510,6 +1525,27 @@ paths:
summary: 登录 summary: 登录
tags: tags:
- 登录 - 登录
/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/loginLog/delete: /admin/loginLog/delete:
post: post:
consumes: consumes:
@@ -1789,6 +1825,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:

View File

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

View File

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

View File

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

View File

@@ -2,15 +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"
apiReq "Gwen/http/request/api"
"Gwen/http/controller/api"
"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 {
@@ -85,7 +86,6 @@ func (ct *Login) Logout(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// LoginOptions // LoginOptions
// @Tags 登录 // @Tags 登录
// @Summary 登录选项 // @Summary 登录选项
@@ -95,13 +95,20 @@ func (ct *Login) Logout(c *gin.Context) {
// @Success 200 {object} []string // @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /admin/login-options [post] // @Router /admin/login-options [post]
// 直接调用/api/login的LoginOptions方法
func (ct *Login) LoginOptions(c *gin.Context) { func (ct *Login) LoginOptions(c *gin.Context) {
l := &api.Login{} res := service.AllService.OauthService.List(1, 100, func(tx *gorm.DB) {
l.LoginOptions(c) 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 // OidcAuth
// @Tags Oauth // @Tags Oauth
// @Summary OidcAuth // @Summary OidcAuth
@@ -126,13 +133,13 @@ func (ct *Login) OidcAuth(c *gin.Context) {
} }
service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{ service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin, Action: service.OauthActionTypeLogin,
Op: f.Op, Op: f.Op,
Id: f.Id, Id: f.Id,
DeviceType: "webadmin", DeviceType: "webadmin",
// DeviceOs: ct.Platform(c), // DeviceOs: ct.Platform(c),
DeviceOs: f.DeviceInfo.Os, DeviceOs: f.DeviceInfo.Os,
Uuid: f.Uuid, Uuid: f.Uuid,
}, 5*60) }, 5*60)
response.Success(c, gin.H{ response.Success(c, gin.H{
@@ -141,8 +148,6 @@ func (ct *Login) OidcAuth(c *gin.Context) {
}) })
} }
// OidcAuthQuery // OidcAuthQuery
// @Tags Oauth // @Tags Oauth
// @Summary OidcAuthQuery // @Summary OidcAuthQuery
@@ -158,12 +163,12 @@ func (ct *Login) OidcAuthQuery(c *gin.Context) {
if ut == nil { if ut == nil {
return return
} }
fmt.Println("u:", u) //fmt.Println("u:", u)
fmt.Println("ut:", ut) //fmt.Println("ut:", ut)
response.Success(c, &adResp.LoginPayload{ response.Success(c, &adResp.LoginPayload{
Token: ut.Token, Token: ut.Token,
Username: u.Username, Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u), RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname, Nickname: u.Nickname,
}) })
} }

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

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

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

View File

@@ -32,6 +32,7 @@ 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
} }
//fmt.Println(f)
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub && f.Op != model.OauthTypeOidc { 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
@@ -79,7 +80,7 @@ func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken)
// 如果 UserId 为 0说明还在授权中 // 如果 UserId 为 0说明还在授权中
if v.UserId == 0 { if v.UserId == 0 {
c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress"}) c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress, please login and bind"})
return nil, nil return nil, nil
} }
@@ -123,6 +124,9 @@ func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken)
// @Router /oidc/auth-query [get] // @Router /oidc/auth-query [get]
func (o *Oauth) OidcAuthQuery(c *gin.Context) { func (o *Oauth) OidcAuthQuery(c *gin.Context) {
u, ut := o.OidcAuthQueryPre(c) u, ut := o.OidcAuthQueryPre(c)
if u == nil || ut == nil {
return
}
c.JSON(http.StatusOK, apiResp.LoginRes{ c.JSON(http.StatusOK, apiResp.LoginRes{
AccessToken: ut.Token, AccessToken: ut.Token,
Type: "access_token", Type: "access_token",
@@ -157,7 +161,10 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
ty := v.Op ty := v.Op
ac := v.Action ac := v.Action
var u *model.User 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)
@@ -165,59 +172,8 @@ 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))
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
}
} else if ty == model.OauthTypeGoogle { } else 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)
@@ -225,60 +181,9 @@ 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
} }
openid = userData.Email
//将空格替换成_ //将空格替换成_
googleName := strings.Replace(userData.Name, " ", "_", -1) thirdName = strings.Replace(userData.Name, " ", "_", -1)
if ac == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Email)
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
u = service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定
err = service.AllService.OauthService.BindGoogleUser(userData.Email, googleName, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return
} else if ac == service.OauthActionTypeLogin {
if v.UserId != 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
return
}
u = service.AllService.UserService.InfoByGoogleEmail(userData.Email)
if u == nil {
oa := service.AllService.OauthService.InfoByOp(ty)
if !*oa.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
v.ThirdName = googleName
v.ThirdOpenId = userData.Email
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
c.Redirect(http.StatusFound, url)
return
}
//自动注册
u = service.AllService.UserService.RegisterByGoogle(googleName, userData.Email)
if u.Id == 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
return
}
}
// v.UserId = u.Id
// service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
// c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
// return
}
} else if ty == model.OauthTypeOidc { } else if ty == model.OauthTypeOidc {
code := c.Query("code") code := c.Query("code")
err, userData := service.AllService.OauthService.OidcCallback(code) err, userData := service.AllService.OauthService.OidcCallback(code)
@@ -286,85 +191,81 @@ 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
} }
//将空格替换成_ openid = userData.Sub
// OidcName := strings.Replace(userData.Name, " ", "_", -1) thirdName = userData.PreferredUsername
if ac == service.OauthActionTypeBind { } else {
//fmt.Println("bind", ty, userData) c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ParamsError"))
utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Sub) return
if utr.UserId > 0 { }
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser")) if ac == service.OauthActionTypeBind {
return
} //fmt.Println("bind", ty, userData)
//绑定 utr := service.AllService.OauthService.UserThirdInfo(ty, openid)
u = service.AllService.UserService.InfoById(v.UserId) if utr.UserId > 0 {
if u == nil { c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定, user preffered_username as username
err = service.AllService.OauthService.BindOidcUser(userData.Sub, userData.PreferredUsername, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return return
} else if ac == service.OauthActionTypeLogin { }
if v.UserId != 0 { //绑定
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess")) 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 return
} }
u = service.AllService.UserService.InfoByOidcSub(userData.Sub)
if u == nil {
oa := service.AllService.OauthService.InfoByOp(ty)
if !*oa.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
v.ThirdName = userData.PreferredUsername //自动注册
v.ThirdOpenId = userData.Sub u = service.AllService.UserService.RegisterByOauth(ty, thirdName, openid)
v.ThirdEmail = userData.Email if u.Id == 0 {
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
c.Redirect(http.StatusFound, url) return
return
}
//自动注册
u = service.AllService.UserService.RegisterByOidc(userData.PreferredUsername, userData.Sub)
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
} }
} v.UserId = u.Id
// 如果u为空说明没有绑定用户 service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
if u == nil { // 如果是webadmin登录成功后跳转到webadmin
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError")) 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 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: "account",
Platform: v.DeviceOs,
})
url := global.Config.Rustdesk.ApiServer + "/_admin/#/"
c.Redirect(http.StatusFound, url)
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
return
} }

View File

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

View File

@@ -17,7 +17,7 @@ func Init(g *gin.Engine) {
adg := g.Group("/api/admin") 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)

View File

@@ -12,6 +12,12 @@ type LoginLog struct {
TimeModel TimeModel
} }
const (
LoginLogClientWebAdmin = "webadmin"
LoginLogClientWeb = "webclient"
LoginLogClientApp = "app"
)
const ( const (
LoginLogTypeAccount = "account" LoginLogTypeAccount = "account"
LoginLogTypeOauth = "oauth" LoginLogTypeOauth = "oauth"

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

@@ -15,9 +15,9 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
"strings"
) )
// Define a struct to parse the .well-known/openid-configuration response // Define a struct to parse the .well-known/openid-configuration response
@@ -88,10 +88,10 @@ type GoogleUserdata struct {
VerifiedEmail bool `json:"verified_email"` VerifiedEmail bool `json:"verified_email"`
} }
type OidcUserdata struct { type OidcUserdata struct {
Sub string `json:"sub"` Sub string `json:"sub"`
Email string `json:"email"` Email string `json:"email"`
VerifiedEmail bool `json:"email_verified"` VerifiedEmail bool `json:"email_verified"`
Name string `json:"name"` Name string `json:"name"`
PreferredUsername string `json:"preferred_username"` PreferredUsername string `json:"preferred_username"`
} }
@@ -156,27 +156,27 @@ func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
// Method to fetch OIDC configuration dynamically // Method to fetch OIDC configuration dynamically
func FetchOidcConfig(issuer string) (error, OidcEndpoint) { func FetchOidcConfig(issuer string) (error, OidcEndpoint) {
configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration" configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
// Get the HTTP client (with or without proxy based on configuration) // Get the HTTP client (with or without proxy based on configuration)
client := getHTTPClientWithProxy() client := getHTTPClientWithProxy()
resp, err := client.Get(configURL) resp, err := client.Get(configURL)
if err != nil { if err != nil {
return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{} return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{}
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{} return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{}
} }
var endpoint OidcEndpoint var endpoint OidcEndpoint
if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil { if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil {
return errors.New("failed to parse OIDC configuration"), OidcEndpoint{} return errors.New("failed to parse OIDC configuration"), OidcEndpoint{}
} }
return nil, endpoint return nil, endpoint
} }
// GetOauthConfig retrieves the OAuth2 configuration based on the provider type // GetOauthConfig retrieves the OAuth2 configuration based on the provider type
@@ -253,6 +253,7 @@ func (os *OauthService) getOidcConfig() (error, *oauth2.Config) {
} }
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.")
@@ -446,7 +447,6 @@ func (os *OauthService) DeleteUserByUserId(userid uint) error {
return global.DB.Where("user_id = ?", userid).Delete(&model.UserThird{}).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

@@ -151,15 +151,34 @@ func (us *UserService) Logout(u *model.User, token string) error {
// Delete 删除用户和oauth信息 // Delete 删除用户和oauth信息
func (us *UserService) Delete(u *model.User) error { func (us *UserService) Delete(u *model.User) error {
// 删除用户 tx := global.DB.Begin()
if err := global.DB.Delete(u).Error; err != nil { // 删除用户
return err if err := tx.Delete(u).Error; err != nil {
} tx.Rollback()
// 删除关联的 OAuth 信息 return err
if err := AllService.OauthService.DeleteUserByUserId(u.Id); err != nil { }
return err // 删除关联的 OAuth 信息
} if err := tx.Where("user_id = ?", u.Id).Delete(&model.UserThird{}).Error; err != nil {
return 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 更新
@@ -262,14 +281,14 @@ func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.
Username: username, Username: username,
GroupId: 1, GroupId: 1,
} }
global.DB.Create(u) tx.Create(u)
if u.Id == 0 { if u.Id == 0 {
tx.Rollback() tx.Rollback()
return u 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
@@ -328,3 +347,12 @@ func (us *UserService) IsPasswordEmptyByUser(u *model.User) bool {
return us.IsPasswordEmptyById(u.Id) 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
}