diff --git a/README.md b/README.md index 76a3057..18d2438 100644 --- a/README.md +++ b/README.md @@ -19,46 +19,66 @@ 2. server端必须指定key,不能用自带的生成的key,否则可能链接不上或者超时 ```bash -hbbs -r -k 123456789 -hbbr -k 123456789 +hbbs -r -k +hbbr -k +``` + +比如 + +```bash +hbbs -r -k abc1234567 +hbbr -k abc1234567 ``` ## 功能 -### **API 服务**: 基本实现了PC端基础的接口。 +### API 服务: 基本实现了PC端基础的接口。 + +#### 登录 + +- 添加了`github`登录,需要在后台配置好就可以用了,具体可看后台OAuth配置 +- 添加了web后台授权登录 + +![pc_login](docs/pc_login.png) + +#### 地址簿 ![pc_ab](docs/pc_ab.png) + +#### 群组,群组分为`共享组`和`普通组`,共享组中所有人都能看到小组成员的地址,普通组只有管理员能看到所有小组成员的地址 + ![pc_gr](docs/pc_gr.png) ### **Web UI**: 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。 ***前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)*** -***后台访问地址是`http://:21114/_admin/`初次安装管理员为用户名密码为`admin admin`,请即时更改密码*** +***后台访问地址是`http://:21114/_admin/`初次安装管理员为用户名密码为`admin` `admin`,请即时更改密码*** 1. 管理员界面 ![web_admin](docs/web_admin.png) 2. 普通用户界面 - ![web_user](docs/web_user.png) -3. 更改密码在右上角 - + ![web_user](docs/web_admin_user.png) +3. 右上角也可以更改密码 ![web_resetpwd](docs/web_resetpwd.png) 4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组` - ![web_admin_gr](docs/web_admin_gr.png) +5. 可以直接打开webclient,方便使用 + ![web_webclient](docs/admin_webclient.png) -### **Web 客户端**: +### **Web Client**: 1. 如果已经登录了后台,web client将自动直接登录 2. 如果没登录后台,点击右上角登录即可,api server已经自动配置好了 -3. 登录后台后,会将地址簿自动保存到web client中,方便使用 ![webclient_conf](docs/webclient_conf.png) +3. 登录后,会自动同步ID服务器和KEY +4. 登录后,会将地址簿自动保存到web client中,方便使用 ### **自动化文档**: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。 -1. 后台文档 /admin/swagger/index.html -2. PC端文档 /swagger/index.html +1. 后台文档 `/admin/swagger/index.html` +2. PC端文档 `/swagger/index.html` ![api_swag](docs/api_swag.png) ## 安装与运行 @@ -72,6 +92,7 @@ gin: api-addr: "0.0.0.0:21114" mode: "release" resources-path: 'resources' + trust-proxy: "" gorm: type: "sqlite" max-idle-conns: 10 @@ -88,36 +109,67 @@ rustdesk: key: "123456789" ``` -### 安装步骤 - -#### docker运行 - -1. 直接docker运行 - -```bash -docker run -d --name rustdesk-api -p 21114:21114 -v /data/rustdesk/api:/app/data lejianwen/rustdesk-api -``` - -- 环境变量,变量名前缀是RUSTDESK_API +* 环境变量,变量名前缀是RUSTDESK_API,环境变量如果存在将覆盖配置文件中的配置 | 变量名 | 说明 | 示例 | |:------------------------------------|:-------------------------------------|-----------------------------| +| -----GIN配置----- | ---------- | ---------- | +| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表,以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 | | -----------GORM配置------------------ | ------------------------------------ | --------------------------- | | RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql,默认sqlite | sqlite | | RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 | | RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 | -| -----------MYSQL配置----------------- | --------数据库类型为sqlite时不用填------- | ---------- | +| -----MYSQL配置----- | -----数据库类型为sqlite时不用填----- | ---------- | | RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root | | RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 | | RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 | | RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk | -| -----------RUSTDESK配置-------------- | ----------------------------------- | ---------- | +| -----RUSTDESK配置----- | --------------- | ---------- | | RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 | | RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 | | RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 | | RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 | -2. 使用`docker compose`,根据rustdesk提供的示例加上自己的rustdesk-api +### 安装步骤 + +#### docker运行 + +1. 直接docker运行,配置可以通过挂载配置文件`/app/conf/config.yaml`来修改,或者通过环境变量覆盖配置文件中的配置 + +```bash +docker run -d --name rustdesk-api -p 21114:21114 \ +-v /data/rustdesk/api:/app/data \ +-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \ +-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \ +-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \ +-e RUSTDESK_API_RUSTDESK_KEY=123456789 \ +lejianwen/rustdesk-api +``` + +2. 使用`docker compose` + +- 简单示例 + +```docker-compose +services: + rustdesk-api: + container_name: rustdesk-api + environment: + - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 + - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 + - RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 + - RUSTDESK_API_RUSTDESK_KEY=123456789 + ports: + - 21114:21114 + image: lejianwen/rustdesk-api + volumes: + - /data/rustdesk/api:/app/data #将数据库挂载出来方便备份 + networks: + - rustdesk-net + restart: unless-stopped +``` + +- 根据rustdesk提供的示例加上自己的rustdesk-api ```docker-compose networks: diff --git a/cmd/apimain.go b/cmd/apimain.go index 21cc313..ec32dbb 100644 --- a/cmd/apimain.go +++ b/cmd/apimain.go @@ -157,7 +157,7 @@ func ApiInitValidator() { } func DatabaseAutoUpdate() { - version := 100 + version := 103 db := global.DB @@ -215,6 +215,9 @@ func Migrate(version uint) { &model.AddressBook{}, &model.Peer{}, &model.Group{}, + &model.UserThird{}, + &model.Oauth{}, + &model.LoginLog{}, ) if err != nil { fmt.Println("migrate err :=>", err) diff --git a/conf/config.yaml b/conf/config.yaml index 976bee4..021a331 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -2,6 +2,7 @@ gin: api-addr: "0.0.0.0:21114" mode: "release" #release,debug,test resources-path: 'resources' #对外静态文件目录 + trust-proxy: "" gorm: type: "sqlite" max-idle-conns: 10 @@ -16,14 +17,14 @@ rustdesk: relay-server: "192.168.1.66:21117" api-server: "http://192.168.1.66:21114" key: "123456789" -redis: - addr: "127.0.0.1:6379" - password: "" - db: 0 logger: path: "./runtime/log.txt" level: "error" #trace,debug,info,warn,error,fatal report-caller: true +redis: + addr: "127.0.0.1:6379" + password: "" + db: 0 cache: type: "file" file-dir: "./runtime/cache" diff --git a/config/gin.go b/config/gin.go index eac5788..1cc0ea9 100644 --- a/config/gin.go +++ b/config/gin.go @@ -5,4 +5,5 @@ type Gin struct { AdminAddr string `mapstructure:"admin-addr"` Mode string ResourcesPath string `mapstructure:"resources-path"` + TrustProxy string `mapstructure:"trust-proxy"` } diff --git a/config/oauth.go b/config/oauth.go new file mode 100644 index 0000000..81108aa --- /dev/null +++ b/config/oauth.go @@ -0,0 +1,13 @@ +package config + +type GithubOauth struct { + ClientId string `mapstructure:"client-id"` + ClientSecret string `mapstructure:"client-secret"` + RedirectUrl string `mapstructure:"redirect-url"` +} + +type GoogleOauth struct { + ClientId string `mapstructure:"client-id"` + ClientSecret string `mapstructure:"client-secret"` + RedirectUrl string `mapstructure:"redirect-url"` +} diff --git a/docs/admin/admin_docs.go b/docs/admin/admin_docs.go index 80db6ec..7d179ff 100644 --- a/docs/admin/admin_docs.go +++ b/docs/admin/admin_docs.go @@ -709,6 +709,172 @@ const docTemplateadmin = `{ } } }, + "/admin/loginLog/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "登录日志删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录日志" + ], + "summary": "登录日志删除", + "parameters": [ + { + "description": "登录日志信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.LoginLog" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/loginLog/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "登录日志详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录日志" + ], + "summary": "登录日志详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.LoginLog" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/loginLog/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "登录日志列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录日志" + ], + "summary": "登录日志列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "用户ID", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.LoginLogList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/admin/logout": { "post": { "description": "登出", @@ -738,6 +904,280 @@ const docTemplateadmin = `{ } } }, + "/admin/oauth/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建Oauth", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "创建Oauth", + "parameters": [ + { + "description": "Oauth信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.OauthForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Oauth" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/oauth/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "Oauth删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "Oauth删除", + "parameters": [ + { + "description": "Oauth信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.OauthForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/oauth/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "Oauth详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "Oauth详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Oauth" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/oauth/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "Oauth列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "Oauth列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.OauthList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/oauth/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "Oauth编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "Oauth编辑", + "parameters": [ + { + "description": "Oauth信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.OauthForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.OauthList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/admin/peer/create": { "post": { "security": [ @@ -1646,6 +2086,55 @@ const docTemplateadmin = `{ } } }, + "/admin/user/myOauth": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "我的授权", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "我的授权", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/admin.UserOauthItem" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/admin/user/update": { "post": { "security": [ @@ -1760,6 +2249,9 @@ const docTemplateadmin = `{ "password": { "type": "string" }, + "platform": { + "type": "string" + }, "username": { "type": "string" } @@ -1877,6 +2369,35 @@ const docTemplateadmin = `{ } } }, + "admin.OauthForm": { + "type": "object", + "required": [ + "client_id", + "client_secret", + "op", + "redirect_url" + ], + "properties": { + "auto_register": { + "type": "boolean" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "op": { + "type": "string" + }, + "redirect_url": { + "type": "string" + } + } + }, "admin.PeerForm": { "type": "object", "properties": { @@ -1971,6 +2492,17 @@ const docTemplateadmin = `{ } } }, + "admin.UserOauthItem": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "third_type": { + "type": "string" + } + } + }, "admin.UserPasswordForm": { "type": "object", "required": [ @@ -2110,6 +2642,110 @@ const docTemplateadmin = `{ } } }, + "model.LoginLog": { + "type": "object", + "properties": { + "client": { + "description": "webadmin,webclient,app,", + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "ip": { + "type": "string" + }, + "platform": { + "description": "windows,linux,mac,android,ios", + "type": "string" + }, + "type": { + "description": "account,oauth", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + }, + "uuid": { + "type": "string" + } + } + }, + "model.LoginLogList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.LoginLog" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.Oauth": { + "type": "object", + "properties": { + "auto_register": { + "type": "boolean" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "op": { + "type": "string" + }, + "redirect_url": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "model.OauthList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Oauth" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, "model.Peer": { "type": "object", "properties": { diff --git a/docs/admin/admin_swagger.json b/docs/admin/admin_swagger.json index c711955..f0a5677 100644 --- a/docs/admin/admin_swagger.json +++ b/docs/admin/admin_swagger.json @@ -702,6 +702,172 @@ } } }, + "/admin/loginLog/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "登录日志删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录日志" + ], + "summary": "登录日志删除", + "parameters": [ + { + "description": "登录日志信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.LoginLog" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/loginLog/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "登录日志详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录日志" + ], + "summary": "登录日志详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.LoginLog" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/loginLog/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "登录日志列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录日志" + ], + "summary": "登录日志列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "用户ID", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.LoginLogList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/admin/logout": { "post": { "description": "登出", @@ -731,6 +897,280 @@ } } }, + "/admin/oauth/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建Oauth", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "创建Oauth", + "parameters": [ + { + "description": "Oauth信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.OauthForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Oauth" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/oauth/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "Oauth删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "Oauth删除", + "parameters": [ + { + "description": "Oauth信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.OauthForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/oauth/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "Oauth详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "Oauth详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Oauth" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/oauth/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "Oauth列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "Oauth列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.OauthList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/oauth/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "Oauth编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "Oauth编辑", + "parameters": [ + { + "description": "Oauth信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.OauthForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.OauthList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/admin/peer/create": { "post": { "security": [ @@ -1639,6 +2079,55 @@ } } }, + "/admin/user/myOauth": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "我的授权", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "我的授权", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/admin.UserOauthItem" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/admin/user/update": { "post": { "security": [ @@ -1753,6 +2242,9 @@ "password": { "type": "string" }, + "platform": { + "type": "string" + }, "username": { "type": "string" } @@ -1870,6 +2362,35 @@ } } }, + "admin.OauthForm": { + "type": "object", + "required": [ + "client_id", + "client_secret", + "op", + "redirect_url" + ], + "properties": { + "auto_register": { + "type": "boolean" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "op": { + "type": "string" + }, + "redirect_url": { + "type": "string" + } + } + }, "admin.PeerForm": { "type": "object", "properties": { @@ -1964,6 +2485,17 @@ } } }, + "admin.UserOauthItem": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "third_type": { + "type": "string" + } + } + }, "admin.UserPasswordForm": { "type": "object", "required": [ @@ -2103,6 +2635,110 @@ } } }, + "model.LoginLog": { + "type": "object", + "properties": { + "client": { + "description": "webadmin,webclient,app,", + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "ip": { + "type": "string" + }, + "platform": { + "description": "windows,linux,mac,android,ios", + "type": "string" + }, + "type": { + "description": "account,oauth", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + }, + "uuid": { + "type": "string" + } + } + }, + "model.LoginLogList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.LoginLog" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.Oauth": { + "type": "object", + "properties": { + "auto_register": { + "type": "boolean" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "op": { + "type": "string" + }, + "redirect_url": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "model.OauthList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Oauth" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, "model.Peer": { "type": "object", "properties": { diff --git a/docs/admin/admin_swagger.yaml b/docs/admin/admin_swagger.yaml index 0a0584b..5b0dc56 100644 --- a/docs/admin/admin_swagger.yaml +++ b/docs/admin/admin_swagger.yaml @@ -4,6 +4,8 @@ definitions: properties: password: type: string + platform: + type: string username: type: string required: @@ -85,6 +87,26 @@ definitions: username: type: string type: object + admin.OauthForm: + properties: + auto_register: + type: boolean + client_id: + type: string + client_secret: + type: string + id: + type: integer + op: + type: string + redirect_url: + type: string + required: + - client_id + - client_secret + - op + - redirect_url + type: object admin.PeerForm: properties: cpu: @@ -148,6 +170,13 @@ definitions: - status - username type: object + admin.UserOauthItem: + properties: + status: + type: integer + third_type: + type: string + type: object admin.UserPasswordForm: properties: id: @@ -240,6 +269,75 @@ definitions: total: type: integer type: object + model.LoginLog: + properties: + client: + description: webadmin,webclient,app, + type: string + created_at: + type: string + id: + type: integer + ip: + type: string + platform: + description: windows,linux,mac,android,ios + type: string + type: + description: account,oauth + type: string + updated_at: + type: string + user_id: + type: integer + uuid: + type: string + type: object + model.LoginLogList: + properties: + list: + items: + $ref: '#/definitions/model.LoginLog' + type: array + page: + type: integer + page_size: + type: integer + total: + type: integer + type: object + model.Oauth: + properties: + auto_register: + type: boolean + client_id: + type: string + client_secret: + type: string + created_at: + type: string + id: + type: integer + op: + type: string + redirect_url: + type: string + updated_at: + type: string + type: object + model.OauthList: + properties: + list: + items: + $ref: '#/definitions/model.Oauth' + type: array + page: + type: integer + page_size: + type: integer + total: + type: integer + type: object model.Peer: properties: cpu: @@ -782,6 +880,105 @@ paths: summary: 登录 tags: - 登录 + /admin/loginLog/delete: + post: + consumes: + - application/json + description: 登录日志删除 + parameters: + - description: 登录日志信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/model.LoginLog' + 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/loginLog/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.LoginLog' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 登录日志详情 + tags: + - 登录日志 + /admin/loginLog/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.LoginLogList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 登录日志列表 + tags: + - 登录日志 /admin/logout: post: consumes: @@ -801,6 +998,167 @@ paths: summary: 登出 tags: - 登录 + /admin/oauth/create: + post: + consumes: + - application/json + description: 创建Oauth + parameters: + - description: Oauth信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.OauthForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Oauth' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 创建Oauth + tags: + - Oauth + /admin/oauth/delete: + post: + consumes: + - application/json + description: Oauth删除 + parameters: + - description: Oauth信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.OauthForm' + 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: Oauth删除 + tags: + - Oauth + /admin/oauth/detail/{id}: + get: + consumes: + - application/json + description: Oauth详情 + 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.Oauth' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: Oauth详情 + tags: + - Oauth + /admin/oauth/list: + get: + consumes: + - application/json + description: Oauth列表 + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 页大小 + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.OauthList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: Oauth列表 + tags: + - Oauth + /admin/oauth/update: + post: + consumes: + - application/json + description: Oauth编辑 + parameters: + - description: Oauth信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.OauthForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.OauthList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: Oauth编辑 + tags: + - Oauth /admin/peer/create: post: consumes: @@ -1338,6 +1696,34 @@ paths: summary: 管理员列表 tags: - 用户 + /admin/user/myOauth: + get: + consumes: + - application/json + description: 我的授权 + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + items: + $ref: '#/definitions/admin.UserOauthItem' + type: array + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 我的授权 + tags: + - 用户 /admin/user/update: post: consumes: diff --git a/docs/admin_webclient.png b/docs/admin_webclient.png new file mode 100644 index 0000000..3ac239a Binary files /dev/null and b/docs/admin_webclient.png differ diff --git a/docs/api/api_docs.go b/docs/api/api_docs.go index 57ecdef..ecd463d 100644 --- a/docs/api/api_docs.go +++ b/docs/api/api_docs.go @@ -233,40 +233,6 @@ const docTemplateapi = `{ } } }, - "/currentUser": { - "get": { - "security": [ - { - "token": [] - } - ], - "description": "用户信息", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "用户" - ], - "summary": "用户信息", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/api.UserPayload" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.Response" - } - } - } - } - }, "/heartbeat": { "post": { "description": "心跳", @@ -394,6 +360,122 @@ const docTemplateapi = `{ } } }, + "/oauth/callback": { + "get": { + "description": "OauthCallback", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "OauthCallback", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginRes" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/oauth/login": { + "get": { + "description": "WebOauthLogin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "WebOauthLogin", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/oidc/auth": { + "post": { + "description": "OidcAuth", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "OidcAuth", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginRes" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/oidc/auth-query": { + "get": { + "description": "OidcAuthQuery", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "OidcAuthQuery", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginRes" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/peers": { "get": { "security": [ @@ -656,21 +738,50 @@ const docTemplateapi = `{ } } }, + "api.DeviceInfoInLogin": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "os": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "api.LoginForm": { "type": "object", "required": [ "username" ], "properties": { + "autoLogin": { + "type": "boolean" + }, + "deviceInfo": { + "$ref": "#/definitions/api.DeviceInfoInLogin" + }, + "id": { + "type": "string" + }, "password": { "type": "string", "maxLength": 20, "minLength": 4 }, + "type": { + "type": "string" + }, "username": { "type": "string", "maxLength": 10, "minLength": 4 + }, + "uuid": { + "type": "string" } } }, @@ -729,6 +840,10 @@ const docTemplateapi = `{ "email": { "type": "string" }, + "info": { + "type": "object", + "additionalProperties": true + }, "is_admin": { "type": "boolean" }, diff --git a/docs/api/api_swagger.json b/docs/api/api_swagger.json index ffed862..cb5b075 100644 --- a/docs/api/api_swagger.json +++ b/docs/api/api_swagger.json @@ -226,40 +226,6 @@ } } }, - "/currentUser": { - "get": { - "security": [ - { - "token": [] - } - ], - "description": "用户信息", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "用户" - ], - "summary": "用户信息", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/api.UserPayload" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.Response" - } - } - } - } - }, "/heartbeat": { "post": { "description": "心跳", @@ -387,6 +353,122 @@ } } }, + "/oauth/callback": { + "get": { + "description": "OauthCallback", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "OauthCallback", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginRes" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/oauth/login": { + "get": { + "description": "WebOauthLogin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "WebOauthLogin", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/oidc/auth": { + "post": { + "description": "OidcAuth", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "OidcAuth", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginRes" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/oidc/auth-query": { + "get": { + "description": "OidcAuthQuery", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Oauth" + ], + "summary": "OidcAuthQuery", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginRes" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/peers": { "get": { "security": [ @@ -649,21 +731,50 @@ } } }, + "api.DeviceInfoInLogin": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "os": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "api.LoginForm": { "type": "object", "required": [ "username" ], "properties": { + "autoLogin": { + "type": "boolean" + }, + "deviceInfo": { + "$ref": "#/definitions/api.DeviceInfoInLogin" + }, + "id": { + "type": "string" + }, "password": { "type": "string", "maxLength": 20, "minLength": 4 }, + "type": { + "type": "string" + }, "username": { "type": "string", "maxLength": 10, "minLength": 4 + }, + "uuid": { + "type": "string" } } }, @@ -722,6 +833,10 @@ "email": { "type": "string" }, + "info": { + "type": "object", + "additionalProperties": true + }, "is_admin": { "type": "boolean" }, diff --git a/docs/api/api_swagger.yaml b/docs/api/api_swagger.yaml index e5f718f..9106e51 100644 --- a/docs/api/api_swagger.yaml +++ b/docs/api/api_swagger.yaml @@ -6,16 +6,35 @@ definitions: example: '{"tags":["tag1","tag2","tag3"],"peers":[{"id":"abc","username":"abv-l","hostname":"","platform":"Windows","alias":"","tags":["tag1","tag2"],"hash":"hash"}],"tag_colors":"{\"tag1\":4288585374,\"tag2\":4278238420,\"tag3\":4291681337}"}' type: string type: object + api.DeviceInfoInLogin: + properties: + name: + type: string + os: + type: string + type: + type: string + type: object api.LoginForm: properties: + autoLogin: + type: boolean + deviceInfo: + $ref: '#/definitions/api.DeviceInfoInLogin' + id: + type: string password: maxLength: 20 minLength: 4 type: string + type: + type: string username: maxLength: 10 minLength: 4 type: string + uuid: + type: string required: - username type: object @@ -55,6 +74,9 @@ definitions: properties: email: type: string + info: + additionalProperties: true + type: object is_admin: type: boolean name: @@ -242,27 +264,6 @@ paths: summary: 用户信息 tags: - 用户 - /currentUser: - get: - consumes: - - application/json - description: 用户信息 - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/api.UserPayload' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.Response' - security: - - token: [] - summary: 用户信息 - tags: - - 用户 /heartbeat: post: consumes: @@ -346,6 +347,82 @@ paths: summary: 登出 tags: - 登录 + /oauth/callback: + get: + consumes: + - application/json + description: OauthCallback + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.LoginRes' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: OauthCallback + tags: + - Oauth + /oauth/login: + get: + consumes: + - application/json + description: WebOauthLogin + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: WebOauthLogin + tags: + - Oauth + /oidc/auth: + post: + consumes: + - application/json + description: OidcAuth + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.LoginRes' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: OidcAuth + tags: + - Oauth + /oidc/auth-query: + get: + consumes: + - application/json + description: OidcAuthQuery + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.LoginRes' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: OidcAuthQuery + tags: + - Oauth /peers: get: consumes: diff --git a/docs/pc_login.png b/docs/pc_login.png new file mode 100644 index 0000000..acc8c1c Binary files /dev/null and b/docs/pc_login.png differ diff --git a/docs/web_admin.png b/docs/web_admin.png index 81c0f62..6be19fe 100644 Binary files a/docs/web_admin.png and b/docs/web_admin.png differ diff --git a/docs/web_admin_user.png b/docs/web_admin_user.png new file mode 100644 index 0000000..442b3fb Binary files /dev/null and b/docs/web_admin_user.png differ diff --git a/docs/web_user.png b/docs/web_user.png deleted file mode 100644 index dd405d8..0000000 Binary files a/docs/web_user.png and /dev/null differ diff --git a/docs/webclient_conf.png b/docs/webclient_conf.png index a6e0526..384baba 100644 Binary files a/docs/webclient_conf.png and b/docs/webclient_conf.png differ diff --git a/generate_run.go b/generate_run.go new file mode 100644 index 0000000..294974c --- /dev/null +++ b/generate_run.go @@ -0,0 +1,3 @@ +package Gwen + +//go:generate go run cmd/apimain.go diff --git a/go.mod b/go.mod index 8dda0cd..0e074bc 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( ) require ( + cloud.google.com/go/compute/metadata v0.5.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect @@ -61,12 +62,13 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.9 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.7.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.63.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/http/controller/admin/login.go b/http/controller/admin/login.go index 1640923..9ebfc19 100644 --- a/http/controller/admin/login.go +++ b/http/controller/admin/login.go @@ -5,6 +5,7 @@ import ( "Gwen/http/request/admin" "Gwen/http/response" adResp "Gwen/http/response/admin" + "Gwen/model" "Gwen/service" "github.com/gin-gonic/gin" ) @@ -43,7 +44,14 @@ func (ct *Login) Login(c *gin.Context) { return } - ut := service.AllService.UserService.Login(u) + ut := service.AllService.UserService.Login(u, &model.LoginLog{ + UserId: u.Id, + Client: "webadmin", + Uuid: "", + Ip: c.ClientIP(), + Type: "account", + Platform: f.Platform, + }) response.Success(c, &adResp.LoginPayload{ Token: ut.Token, diff --git a/http/controller/admin/loginLog.go b/http/controller/admin/loginLog.go new file mode 100644 index 0000000..7f6c771 --- /dev/null +++ b/http/controller/admin/loginLog.go @@ -0,0 +1,110 @@ +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 LoginLog struct { +} + +// Detail 登录日志 +// @Tags 登录日志 +// @Summary 登录日志详情 +// @Description 登录日志详情 +// @Accept json +// @Produce json +// @Param id path int true "ID" +// @Success 200 {object} response.Response{data=model.LoginLog} +// @Failure 500 {object} response.Response +// @Router /admin/loginLog/detail/{id} [get] +// @Security token +func (ct *LoginLog) Detail(c *gin.Context) { + id := c.Param("id") + iid, _ := strconv.Atoi(id) + u := service.AllService.LoginLogService.InfoById(uint(iid)) + if u.Id > 0 { + response.Success(c, u) + return + } + response.Fail(c, 101, "信息不存在") + return +} + +// 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.LoginLogList} +// @Failure 500 {object} response.Response +// @Router /admin/loginLog/list [get] +// @Security token +func (ct *LoginLog) List(c *gin.Context) { + query := &admin.LoginLogQuery{} + if err := c.ShouldBindQuery(query); err != nil { + response.Fail(c, 101, "参数错误") + return + } + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 { + query.UserId = int(u.Id) + } + res := service.AllService.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) { + if query.UserId > 0 { + tx.Where("user_id = ?", query.UserId) + } + }) + response.Success(c, res) +} + +// Delete 删除 +// @Tags 登录日志 +// @Summary 登录日志删除 +// @Description 登录日志删除 +// @Accept json +// @Produce json +// @Param body body model.LoginLog true "登录日志信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/loginLog/delete [post] +// @Security token +func (ct *LoginLog) Delete(c *gin.Context) { + f := &model.LoginLog{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "系统错误") + return + } + id := f.Id + errList := global.Validator.ValidVar(id, "required,gt=0") + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + l := service.AllService.LoginLogService.InfoById(f.Id) + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id { + response.Fail(c, 101, "无权限") + return + } + if l.Id > 0 { + err := service.AllService.LoginLogService.Delete(l) + if err == nil { + response.Success(c, nil) + return + } + response.Fail(c, 101, err.Error()) + return + } + response.Fail(c, 101, "信息不存在") +} diff --git a/http/controller/admin/oauth.go b/http/controller/admin/oauth.go new file mode 100644 index 0000000..5bb3f39 --- /dev/null +++ b/http/controller/admin/oauth.go @@ -0,0 +1,291 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/request/admin" + adminReq "Gwen/http/request/admin" + "Gwen/http/response" + "Gwen/model" + "Gwen/service" + "github.com/gin-gonic/gin" + "strconv" +) + +type Oauth struct { +} + +// Info +func (o *Oauth) Info(c *gin.Context) { + code := c.Query("code") + if code == "" { + response.Fail(c, 101, "参数错误") + return + } + v := service.AllService.OauthService.GetOauthCache(code) + if v == nil { + response.Fail(c, 101, "信息不存在") + return + } + response.Success(c, v) +} + +func (o *Oauth) ToBind(c *gin.Context) { + f := &adminReq.BindOauthForm{} + err := c.ShouldBindJSON(f) + if err != nil { + response.Fail(c, 101, "参数错误") + return + } + u := service.AllService.UserService.CurUser(c) + + utr := service.AllService.UserService.UserThirdInfo(u.Id, f.Op) + if utr.Id > 0 { + response.Fail(c, 101, "已绑定过了") + return + } + + err, code, url := service.AllService.OauthService.BeginAuth(f.Op) + if err != nil { + response.Error(c, err.Error()) + return + } + + service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{ + Action: service.OauthActionTypeBind, + Op: f.Op, + UserId: u.Id, + }, 5*60) + + response.Success(c, gin.H{ + "code": code, + "url": url, + }) +} + +// Confirm 确认授权登录 +func (o *Oauth) Confirm(c *gin.Context) { + j := &adminReq.OauthConfirmForm{} + err := c.ShouldBindJSON(j) + if err != nil { + response.Fail(c, 101, "参数错误"+err.Error()) + return + } + if j.Code == "" { + response.Fail(c, 101, "参数错误: code 不存在") + return + } + v := service.AllService.OauthService.GetOauthCache(j.Code) + if v == nil { + response.Fail(c, 101, "授权已过期") + return + } + u := service.AllService.UserService.CurUser(c) + v.UserId = u.Id + service.AllService.OauthService.SetOauthCache(j.Code, v, 0) + response.Success(c, v) +} + +func (o *Oauth) BindConfirm(c *gin.Context) { + j := &adminReq.OauthConfirmForm{} + err := c.ShouldBindJSON(j) + if err != nil { + response.Fail(c, 101, "参数错误"+err.Error()) + return + } + if j.Code == "" { + response.Fail(c, 101, "参数错误: code 不存在") + return + } + v := service.AllService.OauthService.GetOauthCache(j.Code) + if v == nil { + response.Fail(c, 101, "授权已过期") + return + } + u := service.AllService.UserService.CurUser(c) + err = service.AllService.OauthService.BindGithubUser(v.ThirdOpenId, v.ThirdOpenId, u.Id) + if err != nil { + response.Fail(c, 101, "绑定失败,请重试") + return + } + + v.UserId = u.Id + service.AllService.OauthService.SetOauthCache(j.Code, v, 0) + response.Success(c, v) +} + +func (o *Oauth) Unbind(c *gin.Context) { + f := &adminReq.UnBindOauthForm{} + err := c.ShouldBindJSON(f) + if err != nil { + response.Fail(c, 101, "参数错误") + return + } + u := service.AllService.UserService.CurUser(c) + utr := service.AllService.UserService.UserThirdInfo(u.Id, f.Op) + if utr.Id == 0 { + response.Fail(c, 101, "未绑定") + return + } + if f.Op == model.OauthTypeGithub { + err = service.AllService.OauthService.UnBindGithubUser(u.Id) + if err != nil { + response.Fail(c, 101, "解绑失败") + return + } + } + response.Success(c, nil) +} + +// Detail Oauth +// @Tags Oauth +// @Summary Oauth详情 +// @Description Oauth详情 +// @Accept json +// @Produce json +// @Param id path int true "ID" +// @Success 200 {object} response.Response{data=model.Oauth} +// @Failure 500 {object} response.Response +// @Router /admin/oauth/detail/{id} [get] +// @Security token +func (o *Oauth) Detail(c *gin.Context) { + id := c.Param("id") + iid, _ := strconv.Atoi(id) + u := service.AllService.OauthService.InfoById(uint(iid)) + if u.Id > 0 { + response.Success(c, u) + return + } + response.Fail(c, 101, "信息不存在") + return +} + +// Create 创建Oauth +// @Tags Oauth +// @Summary 创建Oauth +// @Description 创建Oauth +// @Accept json +// @Produce json +// @Param body body admin.OauthForm true "Oauth信息" +// @Success 200 {object} response.Response{data=model.Oauth} +// @Failure 500 {object} response.Response +// @Router /admin/oauth/create [post] +// @Security token +func (o *Oauth) Create(c *gin.Context) { + f := &admin.OauthForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误"+err.Error()) + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + + ex := service.AllService.OauthService.InfoByOp(f.Op) + if ex.Id > 0 { + response.Fail(c, 101, "已存在"+f.Op) + return + } + + u := f.ToOauth() + err := service.AllService.OauthService.Create(u) + if err != nil { + response.Fail(c, 101, "创建失败") + return + } + response.Success(c, u) +} + +// List 列表 +// @Tags Oauth +// @Summary Oauth列表 +// @Description Oauth列表 +// @Accept json +// @Produce json +// @Param page query int false "页码" +// @Param page_size query int false "页大小" +// @Success 200 {object} response.Response{data=model.OauthList} +// @Failure 500 {object} response.Response +// @Router /admin/oauth/list [get] +// @Security token +func (o *Oauth) List(c *gin.Context) { + query := &admin.PageQuery{} + if err := c.ShouldBindQuery(query); err != nil { + response.Fail(c, 101, "参数错误") + return + } + res := service.AllService.OauthService.List(query.Page, query.PageSize, nil) + response.Success(c, res) +} + +// Update 编辑 +// @Tags Oauth +// @Summary Oauth编辑 +// @Description Oauth编辑 +// @Accept json +// @Produce json +// @Param body body admin.OauthForm true "Oauth信息" +// @Success 200 {object} response.Response{data=model.OauthList} +// @Failure 500 {object} response.Response +// @Router /admin/oauth/update [post] +// @Security token +func (o *Oauth) Update(c *gin.Context) { + f := &admin.OauthForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + if f.Id == 0 { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := f.ToOauth() + err := service.AllService.OauthService.Update(u) + if err != nil { + response.Fail(c, 101, "更新失败") + return + } + response.Success(c, nil) +} + +// Delete 删除 +// @Tags Oauth +// @Summary Oauth删除 +// @Description Oauth删除 +// @Accept json +// @Produce json +// @Param body body admin.OauthForm true "Oauth信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/oauth/delete [post] +// @Security token +func (o *Oauth) Delete(c *gin.Context) { + f := &admin.OauthForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "系统错误") + return + } + id := f.Id + errList := global.Validator.ValidVar(id, "required,gt=0") + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := service.AllService.OauthService.InfoById(f.Id) + if u.Id > 0 { + err := service.AllService.OauthService.Delete(u) + if err == nil { + response.Success(c, nil) + return + } + response.Fail(c, 101, err.Error()) + return + } + response.Fail(c, 101, "信息不存在") +} diff --git a/http/controller/admin/user.go b/http/controller/admin/user.go index 45bcce6..18b03a3 100644 --- a/http/controller/admin/user.go +++ b/http/controller/admin/user.go @@ -259,3 +259,37 @@ func (ct *User) ChangeCurPwd(c *gin.Context) { } response.Success(c, nil) } + +// MyOauth +// @Tags 用户 +// @Summary 我的授权 +// @Description 我的授权 +// @Accept json +// @Produce json +// @Success 200 {object} response.Response{data=[]adResp.UserOauthItem} +// @Failure 500 {object} response.Response +// @Router /admin/user/myOauth [get] +// @Security token +func (ct *User) MyOauth(c *gin.Context) { + u := service.AllService.UserService.CurUser(c) + oal := service.AllService.OauthService.List(1, 100, nil) + ops := make([]string, 0) + for _, oa := range oal.Oauths { + ops = append(ops, oa.Op) + } + uts := service.AllService.UserService.UserThirdsByUserId(u.Id) + var res []*adResp.UserOauthItem + for _, oa := range oal.Oauths { + item := &adResp.UserOauthItem{ + ThirdType: oa.Op, + } + for _, ut := range uts { + if ut.ThirdType == oa.Op { + item.Status = 1 + break + } + } + res = append(res, item) + } + response.Success(c, res) +} diff --git a/http/controller/api/ab.go b/http/controller/api/ab.go index ddfb026..6d19408 100644 --- a/http/controller/api/ab.go +++ b/http/controller/api/ab.go @@ -7,7 +7,6 @@ import ( "Gwen/model" "Gwen/service" "encoding/json" - "fmt" "github.com/gin-gonic/gin" "net/http" ) @@ -66,7 +65,6 @@ func (a *Ab) UpAb(c *gin.Context) { abf := &requstform.AddressBookForm{} err := c.ShouldBindJSON(&abf) if err != nil { - fmt.Println(err) response.Error(c, "参数错误") return } @@ -93,7 +91,6 @@ func (a *Ab) UpAb(c *gin.Context) { tc := map[string]uint{} err = json.Unmarshal([]byte(abd.TagColors), &tc) if err != nil { - fmt.Println(err) response.Error(c, "系统错误") return } else { @@ -134,7 +131,6 @@ func (a *Ab) TagAdd(c *gin.Context) { t := &model.Tag{} err := c.ShouldBindJSON(t) if err != nil { - fmt.Println(err) response.Error(c, "参数错误") return diff --git a/http/controller/api/login.go b/http/controller/api/login.go index 5fd1c65..088e45e 100644 --- a/http/controller/api/login.go +++ b/http/controller/api/login.go @@ -5,7 +5,9 @@ import ( "Gwen/http/request/api" "Gwen/http/response" apiResp "Gwen/http/response/api" + "Gwen/model" "Gwen/service" + "encoding/json" "github.com/gin-gonic/gin" "net/http" ) @@ -26,8 +28,9 @@ type Login struct { func (l *Login) Login(c *gin.Context) { f := &api.LoginForm{} err := c.ShouldBindJSON(f) + //fmt.Println(f) if err != nil { - response.Error(c, "系统错误") + response.Error(c, "参数错误") return } @@ -44,7 +47,20 @@ func (l *Login) Login(c *gin.Context) { return } - ut := service.AllService.UserService.Login(u) + //根据refer判断是webclient还是app + ref := c.GetHeader("referer") + if ref != "" { + f.DeviceInfo.Type = "webclient" + } + + ut := service.AllService.UserService.Login(u, &model.LoginLog{ + UserId: u.Id, + Client: f.DeviceInfo.Type, + Uuid: f.Uuid, + Ip: c.ClientIP(), + Type: model.LoginLogTypeAccount, + Platform: f.DeviceInfo.Os, + }) c.JSON(http.StatusOK, apiResp.LoginRes{ AccessToken: ut.Token, @@ -63,11 +79,31 @@ func (l *Login) Login(c *gin.Context) { // @Failure 500 {object} response.ErrorResponse // @Router /login-options [post] func (l *Login) LoginOptions(c *gin.Context) { - test := []string{ - //"common-oidc/[{\"name\":\"google\"},{\"name\":\"github\"},{\"name\":\"facebook\"},{\"name\":\"网页授权登录\",\"icon\":\"\"}]", - //"oidc/myapp", + oauthOks := []string{} + err, _ := service.AllService.OauthService.GetOauthConfig(model.OauthTypeGithub) + if err == nil { + oauthOks = append(oauthOks, model.OauthTypeGithub) } - c.JSON(http.StatusOK, test) + err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeGoogle) + if err == nil { + oauthOks = append(oauthOks, model.OauthTypeGoogle) + } + oauthOks = append(oauthOks, model.OauthTypeWebauth) + var oidcItems []map[string]string + for _, v := range oauthOks { + oidcItems = append(oidcItems, map[string]string{"name": v}) + } + common, err := json.Marshal(oidcItems) + if err != nil { + response.Error(c, "参数错误") + return + } + var res []string + res = append(res, "common-oidc/"+string(common)) + for _, v := range oauthOks { + res = append(res, "oidc/"+v) + } + c.JSON(http.StatusOK, res) } // Logout diff --git a/http/controller/api/ouath.go b/http/controller/api/ouath.go new file mode 100644 index 0000000..6744119 --- /dev/null +++ b/http/controller/api/ouath.go @@ -0,0 +1,222 @@ +package api + +import ( + "Gwen/global" + "Gwen/http/request/api" + "Gwen/http/response" + apiResp "Gwen/http/response/api" + "Gwen/model" + "Gwen/service" + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +type Oauth struct { +} + +// OidcAuth +// @Tags Oauth +// @Summary OidcAuth +// @Description OidcAuth +// @Accept json +// @Produce json +// @Success 200 {object} apiResp.LoginRes +// @Failure 500 {object} response.ErrorResponse +// @Router /oidc/auth [post] +func (o *Oauth) OidcAuth(c *gin.Context) { + f := &api.OidcAuthRequest{} + err := c.ShouldBindJSON(&f) + if err != nil { + response.Error(c, "参数错误") + return + } + if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub { + response.Error(c, "参数错误") + return + } + + err, code, url := service.AllService.OauthService.BeginAuth(f.Op) + if err != nil { + response.Error(c, err.Error()) + return + } + + service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{ + Action: service.OauthActionTypeLogin, + Id: f.Id, + Op: f.Op, + Uuid: f.Uuid, + DeviceName: f.DeviceInfo.Name, + DeviceOs: f.DeviceInfo.Os, + DeviceType: f.DeviceInfo.Type, + }, 5*60) + //fmt.Println("code url", code, url) + c.JSON(http.StatusOK, gin.H{ + "code": code, + "url": url, + }) +} + +// OidcAuthQuery +// @Tags Oauth +// @Summary OidcAuthQuery +// @Description OidcAuthQuery +// @Accept json +// @Produce json +// @Success 200 {object} apiResp.LoginRes +// @Failure 500 {object} response.ErrorResponse +// @Router /oidc/auth-query [get] +func (o *Oauth) OidcAuthQuery(c *gin.Context) { + q := &api.OidcAuthQuery{} + err := c.ShouldBindQuery(q) + if err != nil { + response.Error(c, "参数错误") + return + } + v := service.AllService.OauthService.GetOauthCache(q.Code) + if v == nil { + response.Error(c, "授权已过期,请重新授权") + 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) + if u.Id > 0 { + service.AllService.OauthService.DeleteOauthCache(q.Code) + ut := service.AllService.UserService.Login(u, &model.LoginLog{ + UserId: u.Id, + Client: v.DeviceType, + Uuid: v.Uuid, + Ip: c.ClientIP(), + Type: model.LoginLogTypeOauth, + Platform: v.DeviceOs, + }) + c.JSON(http.StatusOK, apiResp.LoginRes{ + AccessToken: ut.Token, + Type: "access_token", + User: *(&apiResp.UserPayload{}).FromUser(u), + }) + return + } + response.Error(c, "用户不存在") +} + +// OauthCallback 回调 +// @Tags Oauth +// @Summary OauthCallback +// @Description OauthCallback +// @Accept json +// @Produce json +// @Success 200 {object} apiResp.LoginRes +// @Failure 500 {object} response.ErrorResponse +// @Router /oauth/callback [get] +func (o *Oauth) OauthCallback(c *gin.Context) { + state := c.Query("state") + if state == "" { + c.String(http.StatusInternalServerError, "state为空") + return + } + + cacheKey := state + //从缓存中获取 + v := service.AllService.OauthService.GetOauthCache(cacheKey) + if v == nil { + c.String(http.StatusInternalServerError, "授权已过期,请重新授权") + return + } + + ty := v.Op + ac := v.Action + //fmt.Println("ty ac ", ty, ac) + if ty == model.OauthTypeGithub { + code := c.Query("code") + err, userData := service.AllService.OauthService.GithubCallback(code) + if err != nil { + c.String(http.StatusInternalServerError, "授权失败:"+err.Error()) + return + } + if ac == service.OauthActionTypeBind { + //fmt.Println("bind", ty, userData) + utr := service.AllService.OauthService.UserThirdInfo(ty, strconv.Itoa(userData.Id)) + if utr.UserId > 0 { + c.String(http.StatusInternalServerError, "已经绑定其他账号") + return + } + //绑定 + u := service.AllService.UserService.InfoById(v.UserId) + if u == nil { + c.String(http.StatusInternalServerError, "用户不存在") + return + } + //绑定github + err = service.AllService.OauthService.BindGithubUser(strconv.Itoa(userData.Id), userData.Login, v.UserId) + if err != nil { + c.String(http.StatusInternalServerError, "绑定失败") + return + } + c.String(http.StatusOK, "绑定成功") + return + } + //登录 + if ac == service.OauthActionTypeLogin { + if v.UserId != 0 { + c.String(http.StatusInternalServerError, "授权已经成功") + 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, int64(userData.Id)) + if u.Id == 0 { + c.String(http.StatusInternalServerError, "注册失败") + return + } + } + + v.UserId = u.Id + service.AllService.OauthService.SetOauthCache(cacheKey, v, 0) + c.String(http.StatusOK, "授权成功") + return + } + + //返回js + c.Header("Content-Type", "text/html; charset=utf-8") + c.String(http.StatusOK, "授权错误") + //up := &apiResp.UserPayload{} + //c.JSON(http.StatusOK, apiResp.LoginRes{ + // AccessToken: ut.Token, + // Type: "access_token", + // User: *up.FromUser(u), + //}) + + } + +} + +// WebOauthLogin +// @Tags Oauth +// @Summary WebOauthLogin +// @Description WebOauthLogin +// @Accept json +// @Produce json +// @Success 200 {string} string +// @Failure 500 {string} string +// @Router /oauth/login [get] +func (o *Oauth) WebOauthLogin(c *gin.Context) { + +} diff --git a/http/controller/api/user.go b/http/controller/api/user.go index a8ccc20..b89071f 100644 --- a/http/controller/api/user.go +++ b/http/controller/api/user.go @@ -21,11 +21,11 @@ type User struct { // @Failure 500 {object} response.Response // @Router /currentUser [get] // @Security token -func (u *User) currentUser(c *gin.Context) { - user := service.AllService.UserService.CurUser(c) - up := (&apiResp.UserPayload{}).FromUser(user) - c.JSON(http.StatusOK, up) -} +//func (u *User) currentUser(c *gin.Context) { +// user := service.AllService.UserService.CurUser(c) +// up := (&apiResp.UserPayload{}).FromUser(user) +// c.JSON(http.StatusOK, up) +//} // Info 用户信息 // @Tags 用户 diff --git a/http/controller/api/webClient.go b/http/controller/api/webClient.go index 34c73c0..b9b36c9 100644 --- a/http/controller/api/webClient.go +++ b/http/controller/api/webClient.go @@ -36,7 +36,7 @@ func (i *WebClient) ServerConfig(c *gin.Context) { gin.H{ "id_server": global.Config.Rustdesk.IdServer, "key": global.Config.Rustdesk.Key, - //"peers": peers, + "peers": peers, }, ) } diff --git a/http/controller/web/index.go b/http/controller/web/index.go index 7e549ec..cee6467 100644 --- a/http/controller/web/index.go +++ b/http/controller/web/index.go @@ -53,7 +53,13 @@ const autoWriteServer = () => { } if (res.data.peers) { - localStorage.setItem('peers', JSON.stringify(res.data.peers)) + oldPeers = JSON.parse(localStorage.getItem('peers')) || {} + Object.keys(res.data.peers).forEach(k => { + if(!oldPeers[k]) { + oldPeers[k] = res.data.peers[k] + } + }) + localStorage.setItem('peers', JSON.stringify(oldPeers)) } } }) diff --git a/http/http.go b/http/http.go index 3908339..6104805 100644 --- a/http/http.go +++ b/http/http.go @@ -7,12 +7,23 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "net/http" + "strings" ) func ApiInit() { gin.SetMode(global.Config.Gin.Mode) g := gin.New() + //[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. + //Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details. + if global.Config.Gin.TrustProxy != "" { + pro := strings.Split(global.Config.Gin.TrustProxy, ",") + err := g.SetTrustedProxies(pro) + if err != nil { + panic(err) + } + } + if global.Config.Gin.Mode == gin.ReleaseMode { //修改gin Recovery日志 输出为logger的输出点 if global.Logger != nil { diff --git a/http/middleware/rustauth.go b/http/middleware/rustauth.go index a627968..bc4639d 100644 --- a/http/middleware/rustauth.go +++ b/http/middleware/rustauth.go @@ -17,6 +17,13 @@ func RustAuth() gin.HandlerFunc { c.Abort() return } + if len(token) <= 7 { + c.JSON(401, gin.H{ + "error": "Unauthorized", + }) + c.Abort() + return + } //提取token,格式是Bearer {token} //这里只是简单的提取 token = token[7:] diff --git a/http/request/admin/login.go b/http/request/admin/login.go index 691f7a7..002ef78 100644 --- a/http/request/admin/login.go +++ b/http/request/admin/login.go @@ -3,4 +3,11 @@ package admin type Login struct { Username string `json:"username" validate:"required" label:"用户名"` Password string `json:"password,omitempty" validate:"required" label:"密码"` + Platform string `json:"platform" label:"平台"` +} + +type LoginLogQuery struct { + UserId int `form:"user_id"` + IsMy int `form:"is_my"` + PageQuery } diff --git a/http/request/admin/oauth.go b/http/request/admin/oauth.go new file mode 100644 index 0000000..11698ee --- /dev/null +++ b/http/request/admin/oauth.go @@ -0,0 +1,34 @@ +package admin + +import "Gwen/model" + +type BindOauthForm struct { + Op string `json:"op" binding:"required"` +} + +type OauthConfirmForm struct { + Code string `json:"code" binding:"required"` +} +type UnBindOauthForm struct { + Op string `json:"op" binding:"required"` +} +type OauthForm struct { + Id uint `json:"id"` + Op string `json:"op" validate:"required"` + ClientId string `json:"client_id" validate:"required"` + ClientSecret string `json:"client_secret" validate:"required"` + RedirectUrl string `json:"redirect_url" validate:"required"` + AutoRegister *bool `json:"auto_register"` +} + +func (of *OauthForm) ToOauth() *model.Oauth { + oa := &model.Oauth{ + Op: of.Op, + ClientId: of.ClientId, + ClientSecret: of.ClientSecret, + RedirectUrl: of.RedirectUrl, + AutoRegister: of.AutoRegister, + } + oa.Id = of.Id + return oa +} diff --git a/http/request/api/oauth.go b/http/request/api/oauth.go new file mode 100644 index 0000000..823deb7 --- /dev/null +++ b/http/request/api/oauth.go @@ -0,0 +1,14 @@ +package api + +type OidcAuthRequest struct { + DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"` + Id string `json:"id" label:"id"` + Op string `json:"op" label:"op"` + Uuid string `json:"uuid" label:"uuid"` +} + +type OidcAuthQuery struct { + Code string `json:"code" form:"code" label:"code"` + Id string `json:"id" form:"id" label:"id"` + Uuid string `json:"uuid" form:"uuid" label:"uuid"` +} diff --git a/http/request/api/user.go b/http/request/api/user.go index 13a0eb4..6327191 100644 --- a/http/request/api/user.go +++ b/http/request/api/user.go @@ -21,9 +21,21 @@ package api bytes hwid = 14; } */ + +type DeviceInfoInLogin struct { + Name string `json:"name" label:"name"` + Os string `json:"os" label:"os"` + Type string `json:"type" label:"type"` +} + type LoginForm struct { - Username string `json:"username" validate:"required,gte=4,lte=10" label:"用户名"` - Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"` + AutoLogin bool `json:"autoLogin" label:"自动登录"` + DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"` + Id string `json:"id" label:"id"` + Type string `json:"type" label:"type"` + Uuid string `json:"uuid" label:"uuid"` + Username string `json:"username" validate:"required,gte=4,lte=10" label:"用户名"` + Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"` } type UserListQuery struct { diff --git a/http/response/admin/user.go b/http/response/admin/user.go index 27e92a9..488ebe9 100644 --- a/http/response/admin/user.go +++ b/http/response/admin/user.go @@ -8,6 +8,11 @@ type LoginPayload struct { } var UserRouteNames = []string{ - "MyTagList", "MyAddressBookList", + "MyTagList", "MyAddressBookList", "MyInfo", } var AdminRouteNames = []string{"*"} + +type UserOauthItem struct { + ThirdType string `json:"third_type"` + Status int `json:"status"` +} diff --git a/http/response/api/user.go b/http/response/api/user.go index dcea9b7..b48a4be 100644 --- a/http/response/api/user.go +++ b/http/response/api/user.go @@ -19,17 +19,19 @@ UserStatus status; bool isAdmin = false; */ type UserPayload struct { - Name string `json:"name"` - Email string `json:"email"` - Note string `json:"note"` - IsAdmin *bool `json:"is_admin"` - Status int `json:"status"` + Name string `json:"name"` + Email string `json:"email"` + Note string `json:"note"` + IsAdmin *bool `json:"is_admin"` + Status int `json:"status"` + Info map[string]interface{} `json:"info"` } func (up *UserPayload) FromUser(user *model.User) *UserPayload { up.Name = user.Username up.IsAdmin = user.IsAdmin up.Status = int(user.Status) + up.Info = map[string]interface{}{} return up } @@ -50,6 +52,6 @@ type LoginRes struct { Type string `json:"type"` AccessToken string `json:"access_token"` User UserPayload `json:"user"` - Secret string `json:"secret"` - TfaType string `json:"tfa_type"` + Secret string `json:"secret,omitempty"` + TfaType string `json:"tfa_type,omitempty"` } diff --git a/http/response/api/webClient.go b/http/response/api/webClient.go index 9245e91..39a4575 100644 --- a/http/response/api/webClient.go +++ b/http/response/api/webClient.go @@ -5,33 +5,6 @@ import ( "time" ) -// type T struct { -// Field1 struct { -// ViewStyle string `json:"view-style"` -// Tm int64 `json:"tm"` -// Info struct { -// Username string `json:"username"` -// Hostname string `json:"hostname"` -// Platform string `json:"platform"` -// Displays []struct { -// X int `json:"x"` -// Y int `json:"y"` -// Width int `json:"width"` -// Height int `json:"height"` -// Name string `json:"name"` -// Online bool `json:"online"` -// } `json:"displays"` -// CurrentDisplay int `json:"current_display"` -// SasEnabled bool `json:"sas_enabled"` -// Version string `json:"version"` -// ConnId int `json:"conn_id"` -// Features struct { -// PrivacyMode bool `json:"privacy_mode"` -// } `json:"features"` -// } `json:"info"` -// } `json:"1799928825"` -// } - type WebClientPeerPayload struct { ViewStyle string `json:"view-style"` Tm int64 `json:"tm"` @@ -46,7 +19,8 @@ type WebClientPeerInfoPayload struct { func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) { wcpp.ViewStyle = "shrink" - wcpp.Tm = time.Now().UnixNano() + //24小时前 + wcpp.Tm = time.Now().Add(-time.Hour * 24).UnixNano() wcpp.Info = WebClientPeerInfoPayload{ Username: a.Username, Hostname: a.Hostname, diff --git a/http/router/admin.go b/http/router/admin.go index df93210..ea89e39 100644 --- a/http/router/admin.go +++ b/http/router/admin.go @@ -25,6 +25,8 @@ func Init(g *gin.Engine) { TagBind(adg) AddressBookBind(adg) PeerBind(adg) + OauthBind(adg) + LoginLogBind(adg) rs := &admin.Rustdesk{} adg.GET("/server-config", rs.ServerConfig) @@ -44,6 +46,7 @@ func UserBind(rg *gin.RouterGroup) { cont := &admin.User{} aR.GET("/current", cont.Current) aR.POST("/changeCurPwd", cont.ChangeCurPwd) + aR.POST("/myOauth", cont.MyOauth) } aRP := rg.Group("/user").Use(middleware.AdminPrivilege()) { @@ -104,6 +107,35 @@ func PeerBind(rg *gin.RouterGroup) { } } +func OauthBind(rg *gin.RouterGroup) { + aR := rg.Group("/oauth") + { + cont := &admin.Oauth{} + aR.POST("/confirm", cont.Confirm) + aR.POST("/bind", cont.ToBind) + aR.POST("/bindConfirm", cont.BindConfirm) + aR.POST("/unbind", cont.Unbind) + aR.GET("/info", cont.Info) + } + arp := aR.Use(middleware.AdminPrivilege()) + { + cont := &admin.Oauth{} + arp.GET("/list", cont.List) + arp.GET("/detail/:id", cont.Detail) + arp.POST("/create", cont.Create) + arp.POST("/update", cont.Update) + arp.POST("/delete", cont.Delete) + + } + +} +func LoginLogBind(rg *gin.RouterGroup) { + aR := rg.Group("/login_log") + cont := &admin.LoginLog{} + aR.GET("/list", cont.List) + aR.POST("/delete", cont.Delete) +} + /* func FileBind(rg *gin.RouterGroup) { aR := rg.Group("/file") diff --git a/http/router/api.go b/http/router/api.go index aa4695f..1341f47 100644 --- a/http/router/api.go +++ b/http/router/api.go @@ -19,7 +19,7 @@ func ApiInit(g *gin.Engine) { frg := g.Group("/api") - frg.Use(middleware.Cors()) + //frg.Use(middleware.Cors()) frg.OPTIONS("/*any", nil) i := &api.Index{} @@ -34,6 +34,16 @@ func ApiInit(g *gin.Engine) { frg.POST("/login", l.Login) } + { + o := &api.Oauth{} + // [method:POST] [uri:/api/oidc/auth] + frg.POST("/oidc/auth", o.OidcAuth) + // [method:GET] [uri:/api/oidc/auth-query?code=abc&id=xxxxx&uuid=xxxxx] + frg.GET("/oidc/auth-query", o.OidcAuthQuery) + //api/oauth/callback + frg.GET("/oauth/callback", o.OauthCallback) + frg.GET("/oauth/login", o.OauthCallback) + } { pe := &api.Peer{} //提交系统信息 diff --git a/model/loginLog.go b/model/loginLog.go new file mode 100644 index 0000000..8405c2f --- /dev/null +++ b/model/loginLog.go @@ -0,0 +1,23 @@ +package model + +type LoginLog struct { + IdModel + UserId uint `json:"user_id"` + Client string `json:"client"` //webadmin,webclient,app, + Uuid string `json:"uuid"` + Ip string `json:"ip"` + Type string `json:"type"` //account,oauth + Platform string `json:"platform"` //windows,linux,mac,android,ios + + TimeModel +} + +const ( + LoginLogTypeAccount = "account" + LoginLogTypeOauth = "oauth" +) + +type LoginLogList struct { + LoginLogs []*LoginLog `json:"list"` + Pagination +} diff --git a/model/oauth.go b/model/oauth.go new file mode 100644 index 0000000..64de80c --- /dev/null +++ b/model/oauth.go @@ -0,0 +1,22 @@ +package model + +type Oauth struct { + IdModel + Op string `json:"op"` + ClientId string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectUrl string `json:"redirect_url"` + AutoRegister *bool `json:"auto_register"` + TimeModel +} + +const ( + OauthTypeGithub = "github" + OauthTypeGoogle = "google" + OauthTypeWebauth = "webauth" +) + +type OauthList struct { + Oauths []*Oauth `json:"list"` + Pagination +} diff --git a/model/userThird.go b/model/userThird.go new file mode 100644 index 0000000..4e967b9 --- /dev/null +++ b/model/userThird.go @@ -0,0 +1,12 @@ +package model + +type UserThird struct { + IdModel + UserId uint `json:"user_id" gorm:"not null;index"` + OpenId string `json:"open_id" gorm:"not null;index"` + UnionId string `json:"union_id" gorm:"not null;"` + ThirdType string `json:"third_type" gorm:"not null;"` + ThirdEmail string `json:"third_email"` + ThirdName string `json:"third_name"` + TimeModel +} diff --git a/service/loginLog.go b/service/loginLog.go new file mode 100644 index 0000000..96ec23b --- /dev/null +++ b/service/loginLog.go @@ -0,0 +1,45 @@ +package service + +import ( + "Gwen/global" + "Gwen/model" + "gorm.io/gorm" +) + +type LoginLogService struct { +} + +// InfoById 根据用户id取用户信息 +func (us *LoginLogService) InfoById(id uint) *model.LoginLog { + u := &model.LoginLog{} + global.DB.Where("id = ?", id).First(u) + return u +} + +func (us *LoginLogService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.LoginLogList) { + res = &model.LoginLogList{} + res.Page = int64(page) + res.PageSize = int64(pageSize) + tx := global.DB.Model(&model.LoginLog{}) + if where != nil { + where(tx) + } + tx.Count(&res.Total) + tx.Scopes(Paginate(page, pageSize)) + tx.Find(&res.LoginLogs) + return +} + +// Create 创建 +func (us *LoginLogService) Create(u *model.LoginLog) error { + res := global.DB.Create(u).Error + return res +} +func (us *LoginLogService) Delete(u *model.LoginLog) error { + return global.DB.Delete(u).Error +} + +// Update 更新 +func (us *LoginLogService) Update(u *model.LoginLog) error { + return global.DB.Model(u).Updates(u).Error +} diff --git a/service/oauth.go b/service/oauth.go new file mode 100644 index 0000000..19f4352 --- /dev/null +++ b/service/oauth.go @@ -0,0 +1,256 @@ +package service + +import ( + "Gwen/global" + "Gwen/model" + "Gwen/utils" + "context" + "encoding/json" + "errors" + "fmt" + "golang.org/x/oauth2" + "golang.org/x/oauth2/github" + "golang.org/x/oauth2/google" + "gorm.io/gorm" + "io" + "strconv" + "sync" + "time" +) + +type OauthService struct { +} + +type GithubUserdata struct { + AvatarUrl string `json:"avatar_url"` + Bio string `json:"bio"` + Blog string `json:"blog"` + Collaborators int `json:"collaborators"` + Company interface{} `json:"company"` + CreatedAt time.Time `json:"created_at"` + DiskUsage int `json:"disk_usage"` + Email interface{} `json:"email"` + EventsUrl string `json:"events_url"` + Followers int `json:"followers"` + FollowersUrl string `json:"followers_url"` + Following int `json:"following"` + FollowingUrl string `json:"following_url"` + GistsUrl string `json:"gists_url"` + GravatarId string `json:"gravatar_id"` + Hireable interface{} `json:"hireable"` + HtmlUrl string `json:"html_url"` + Id int `json:"id"` + Location interface{} `json:"location"` + Login string `json:"login"` + Name string `json:"name"` + NodeId string `json:"node_id"` + NotificationEmail interface{} `json:"notification_email"` + OrganizationsUrl string `json:"organizations_url"` + OwnedPrivateRepos int `json:"owned_private_repos"` + Plan struct { + Collaborators int `json:"collaborators"` + Name string `json:"name"` + PrivateRepos int `json:"private_repos"` + Space int `json:"space"` + } `json:"plan"` + PrivateGists int `json:"private_gists"` + PublicGists int `json:"public_gists"` + PublicRepos int `json:"public_repos"` + ReceivedEventsUrl string `json:"received_events_url"` + ReposUrl string `json:"repos_url"` + SiteAdmin bool `json:"site_admin"` + StarredUrl string `json:"starred_url"` + SubscriptionsUrl string `json:"subscriptions_url"` + TotalPrivateRepos int `json:"total_private_repos"` + //TwitterUsername interface{} `json:"twitter_username"` + TwoFactorAuthentication bool `json:"two_factor_authentication"` + Type string `json:"type"` + UpdatedAt time.Time `json:"updated_at"` + Url string `json:"url"` +} + +type OauthCacheItem struct { + UserId uint `json:"user_id"` + Id string `json:"id"` //rustdesk的设备ID + Op string `json:"op"` + Action string `json:"action"` + Uuid string `json:"uuid"` + DeviceName string `json:"device_name"` + DeviceOs string `json:"device_os"` + DeviceType string `json:"device_type"` + ThirdOpenId string `json:"third_open_id"` + ThirdName string `json:"third_name"` + ThirdEmail string `json:"third_email"` +} + +var OauthCache = &sync.Map{} + +const ( + OauthActionTypeLogin = "login" + OauthActionTypeBind = "bind" +) + +func (os *OauthService) GetOauthCache(key string) *OauthCacheItem { + v, ok := OauthCache.Load(key) + if !ok { + return nil + } + return v.(*OauthCacheItem) +} + +func (os *OauthService) SetOauthCache(key string, item *OauthCacheItem, expire uint) { + OauthCache.Store(key, item) + if expire > 0 { + go func() { + time.Sleep(time.Duration(expire) * time.Second) + os.DeleteOauthCache(key) + }() + } +} + +func (os *OauthService) DeleteOauthCache(key string) { + OauthCache.Delete(key) +} + +func (os *OauthService) BeginAuth(op string) (error error, code, url string) { + code = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10) + + if op == model.OauthTypeWebauth { + url = global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + code + //url = "http://localhost:8888/_admin/#/oauth/" + code + return nil, code, url + } + err, conf := os.GetOauthConfig(op) + if err == nil { + return err, code, conf.AuthCodeURL(code) + } + + return errors.New("op错误"), code, "" +} + +// GetOauthConfig 获取配置 +func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) { + if op == model.OauthTypeGithub { + g := os.InfoByOp(model.OauthTypeGithub) + if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" { + return errors.New("配置不存在"), nil + } + return nil, &oauth2.Config{ + ClientID: g.ClientId, + ClientSecret: g.ClientSecret, + RedirectURL: g.RedirectUrl, + 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 == "" { + return errors.New("配置不存在"), 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"}, + } + } + return errors.New("op错误"), nil +} + +func (os *OauthService) GithubCallback(code string) (error error, userData *GithubUserdata) { + err, oauthConfig := os.GetOauthConfig(model.OauthTypeGithub) + if err != nil { + return err, nil + } + token, err := oauthConfig.Exchange(context.Background(), code) + if err != nil { + global.Logger.Warn(fmt.Printf("oauthConfig.Exchange() failed: %s\n", err)) + error = errors.New("获取token失败") + return + } + + // 创建一个 HTTP 客户端,并将 access_token 添加到 Authorization 头中 + client := oauthConfig.Client(context.Background(), token) + resp, err := client.Get("https://api.github.com/user") + if err != nil { + global.Logger.Warn("failed getting user info: %s\n", err) + error = errors.New("获取user info失败") + return + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + global.Logger.Warn("failed closing response body: %s\n", err) + } + }(resp.Body) + + // 在这里处理 GitHub 用户信息 + if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { + global.Logger.Warn("failed decoding user info: %s\n", err) + error = errors.New("解析user info失败") + return + } + return +} + +func (os *OauthService) UserThirdInfo(op, openid string) *model.UserThird { + ut := &model.UserThird{} + global.DB.Where("open_id = ? and third_type = ?", openid, op).First(ut) + return ut +} + +func (os *OauthService) BindGithubUser(openid, username string, userId uint) error { + utr := &model.UserThird{ + OpenId: openid, + ThirdType: model.OauthTypeGithub, + ThirdName: username, + UserId: userId, + } + return global.DB.Create(utr).Error +} +func (os *OauthService) UnBindGithubUser(userid uint) error { + return global.DB.Where("user_id = ? and third_type = ?", userid, model.OauthTypeGithub).Delete(&model.UserThird{}).Error +} + +// InfoById 根据id取用户信息 +func (os *OauthService) InfoById(id uint) *model.Oauth { + u := &model.Oauth{} + global.DB.Where("id = ?", id).First(u) + return u +} + +// InfoByOp 根据op取用户信息 +func (os *OauthService) InfoByOp(op string) *model.Oauth { + u := &model.Oauth{} + global.DB.Where("op = ?", op).First(u) + return u +} +func (os *OauthService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.OauthList) { + res = &model.OauthList{} + res.Page = int64(page) + res.PageSize = int64(pageSize) + tx := global.DB.Model(&model.Oauth{}) + if where != nil { + where(tx) + } + tx.Count(&res.Total) + tx.Scopes(Paginate(page, pageSize)) + tx.Find(&res.Oauths) + return +} + +// Create 创建 +func (os *OauthService) Create(u *model.Oauth) error { + res := global.DB.Create(u).Error + return res +} +func (os *OauthService) Delete(u *model.Oauth) error { + return global.DB.Delete(u).Error +} + +// Update 更新 +func (os *OauthService) Update(u *model.Oauth) error { + return global.DB.Model(u).Updates(u).Error +} diff --git a/service/service.go b/service/service.go index a8a4f03..c25388d 100644 --- a/service/service.go +++ b/service/service.go @@ -13,6 +13,8 @@ type Service struct { *TagService *PeerService *GroupService + *OauthService + *LoginLogService } func New() *Service { diff --git a/service/user.go b/service/user.go index 9b36b40..aeff619 100644 --- a/service/user.go +++ b/service/user.go @@ -7,6 +7,8 @@ import ( "Gwen/utils" "github.com/gin-gonic/gin" "gorm.io/gorm" + "math/rand" + "strconv" "time" ) @@ -51,11 +53,11 @@ func (us *UserService) InfoByAccessToken(token string) *model.User { // GenerateToken 生成token func (us *UserService) GenerateToken(u *model.User) string { - return utils.Md5(u.Username + u.Password + time.Now().String()) + return utils.Md5(u.Username + time.Now().String()) } // Login 登录 -func (us *UserService) Login(u *model.User) *model.UserToken { +func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserToken { token := us.GenerateToken(u) ut := &model.UserToken{ UserId: u.Id, @@ -63,6 +65,7 @@ func (us *UserService) Login(u *model.User) *model.UserToken { ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(), } global.DB.Create(ut) + global.DB.Create(llog) return ut } @@ -169,3 +172,70 @@ func (us *UserService) RouteNames(u *model.User) []string { } return adResp.UserRouteNames } + +// InfoByGithubId 根据githubid取用户信息 +func (us *UserService) InfoByGithubId(githubId string) *model.User { + ut := AllService.OauthService.UserThirdInfo(model.OauthTypeGithub, githubId) + if ut.Id == 0 { + return nil + } + u := us.InfoById(ut.UserId) + if u.Id == 0 { + return nil + } + return u +} + +// RegisterByGithub 注册 +func (us *UserService) RegisterByGithub(githubName string, githubId int64) *model.User { + tx := global.DB.Begin() + ut := &model.UserThird{ + OpenId: strconv.FormatInt(githubId, 10), + ThirdName: githubName, + ThirdType: model.OauthTypeGithub, + } + //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(githubName) + u := &model.User{ + Username: username, + GroupId: 1, + } + global.DB.Create(u) + + ut.UserId = u.Id + global.DB.Create(ut) + + tx.Commit() + return u +} + +// GenerateUsernameByOauth 生成用户名 +func (us *UserService) GenerateUsernameByOauth(name string) string { + u := &model.User{} + global.DB.Where("username = ?", name).First(u) + if u.Id == 0 { + return name + } + name = name + strconv.FormatInt(rand.Int63n(10), 10) + return us.GenerateUsernameByOauth(name) +} + +// UserThirdsByUserId +func (us *UserService) UserThirdsByUserId(userId uint) (res []*model.UserThird) { + global.DB.Where("user_id = ?", userId).Find(&res) + return res +} + +func (us *UserService) UserThirdInfo(userId uint, op string) *model.UserThird { + ut := &model.UserThird{} + global.DB.Where("user_id = ? and third_type = ?", userId, op).First(ut) + return ut +} diff --git a/utils/tools.go b/utils/tools.go index c58e275..fcb69ba 100644 --- a/utils/tools.go +++ b/utils/tools.go @@ -4,6 +4,7 @@ import ( "crypto/md5" "encoding/json" "fmt" + "math/rand" "reflect" "runtime/debug" ) @@ -61,3 +62,14 @@ func SafeGo(f interface{}, params ...interface{}) { funcValue.Call(paramsValue) }() } + +// RandomString 生成随机字符串 +func RandomString(n int) string { + const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + length := len(letterBytes) + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(length)] + } + return string(b) +}