From 0f8e26f815506205017fc6e9863c76952c38f14a Mon Sep 17 00:00:00 2001 From: RockChinQ <1010553892@qq.com> Date: Sat, 27 Jul 2024 15:32:07 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20oauth2=20app=20=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-latest-backend.yaml | 2 +- backend/controller/admapi.go | 119 ++++++++++++++++ backend/controller/api.go | 2 + backend/controller/dto.go | 8 ++ backend/core/app.go | 3 +- backend/database/mongo.go | 60 ++++++++ backend/database/po.go | 8 ++ backend/service/admin.go | 52 +++++++ backend/service/errors.go | 3 + backend/util/string.go | 14 ++ frontend/package.json | 1 + frontend/src/components/BanRecordCard.vue | 2 +- frontend/src/components/OAuthAppCard.vue | 111 +++++++++++++++ frontend/src/pages/admin.vue | 150 +++++++++++++++++++- 14 files changed, 529 insertions(+), 6 deletions(-) create mode 100644 backend/controller/admapi.go create mode 100644 backend/service/admin.go create mode 100644 frontend/src/components/OAuthAppCard.vue diff --git a/.github/workflows/build-latest-backend.yaml b/.github/workflows/build-latest-backend.yaml index 61ef7b8..6efcde7 100644 --- a/.github/workflows/build-latest-backend.yaml +++ b/.github/workflows/build-latest-backend.yaml @@ -56,5 +56,5 @@ jobs: --header 'Authorization: Bearer ${{ secrets.ONEBOT_V11_TOKEN }}' \ --data '{ "group_id": ${{ secrets.ONEBOT_V11_GROUP_ID }}, - "message": "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 构建完成。" + "message": "Campux 构建完成。" }' \ No newline at end of file diff --git a/backend/controller/admapi.go b/backend/controller/admapi.go new file mode 100644 index 0000000..e9e028f --- /dev/null +++ b/backend/controller/admapi.go @@ -0,0 +1,119 @@ +package controller + +import ( + "github.com/RockChinQ/Campux/backend/database" + "github.com/RockChinQ/Campux/backend/service" + "github.com/gin-gonic/gin" +) + +type AdminRouter struct { + APIRouter + AdminService service.AdminService +} + +func NewAdminRouter(rg *gin.RouterGroup, as service.AdminService) *AdminRouter { + ar := &AdminRouter{ + AdminService: as, + } + + group := rg.Group("/admin") + + // bind routes + group.POST("/add-oauth2-app", ar.AddOAuth2App) + group.GET("/get-oauth2-apps", ar.GetOAuth2AppList) + group.DELETE("/del-oauth2-app/:id", ar.DeleteOAuth2App) + + return ar +} + +// 添加一个OAuth2应用 +func (ar *AdminRouter) AddOAuth2App(c *gin.Context) { + + uin, err := ar.Auth(c, UserOnly) + + if err != nil { + return + } + + if !ar.AdminService.CheckUserGroup(uin, []database.UserGroup{ + database.USER_GROUP_ADMIN, + }) { + ar.StatusCode(c, 401, "权限不足") + return + } + + // 取body的json里的appname + var body OAuth2AppCreateBody + + if err := c.ShouldBindJSON(&body); err != nil { + ar.Fail(c, 1, err.Error()) + return + } + + // 创建OAuth2应用 + app, err := ar.AdminService.AddOAuth2App(body.Name, body.Emoji) + + if err != nil { + ar.Fail(c, 2, err.Error()) + return + } + + ar.Success(c, app) +} + +// 获取 OAuth2 应用列表 +func (ar *AdminRouter) GetOAuth2AppList(c *gin.Context) { + uin, err := ar.Auth(c, UserOnly) + + if err != nil { + return + } + + if !ar.AdminService.CheckUserGroup(uin, []database.UserGroup{ + database.USER_GROUP_ADMIN, + }) { + ar.StatusCode(c, 401, "权限不足") + return + } + + // 获取OAuth2应用列表 + list, err := ar.AdminService.GetOAuth2Apps() + + if err != nil { + ar.Fail(c, 1, err.Error()) + return + } + + ar.Success(c, gin.H{ + "list": list, + }) +} + +// 删除一个OAuth2应用 +func (ar *AdminRouter) DeleteOAuth2App(c *gin.Context) { + uin, err := ar.Auth(c, UserOnly) + + if err != nil { + return + } + + if !ar.AdminService.CheckUserGroup(uin, []database.UserGroup{ + database.USER_GROUP_ADMIN, + }) { + ar.StatusCode(c, 401, "权限不足") + return + } + + // 取路由参数 + appID := c.Param("id") + + // 删除OAuth2应用 + err = ar.AdminService.DeleteOAuth2App(appID) + + if err != nil { + ar.Fail(c, 1, err.Error()) + return + } + + ar.Success(c, nil) +} diff --git a/backend/controller/api.go b/backend/controller/api.go index 6b48eda..1139907 100644 --- a/backend/controller/api.go +++ b/backend/controller/api.go @@ -21,6 +21,7 @@ func NewApiController( as service.AccountService, ps service.PostService, ms service.MiscService, + ads service.AdminService, ) *APIController { r := gin.Default() @@ -63,6 +64,7 @@ func NewApiController( NewAccountRouter(rg, as) NewPostRouter(rg, ps, as) NewMiscRouter(rg, ms) + NewAdminRouter(rg, ads) return &APIController{ R: r, diff --git a/backend/controller/dto.go b/backend/controller/dto.go index eb8d7f8..c461a2e 100644 --- a/backend/controller/dto.go +++ b/backend/controller/dto.go @@ -151,3 +151,11 @@ type GetBanListBody struct { // 时间排序 TimeOrder *int `json:"time_order" binding:"required"` } + +type OAuth2AppCreateBody struct { + // 名称 + Name string `json:"name" binding:"required"` + + // emoji + Emoji string `json:"emoji" binding:"required"` +} diff --git a/backend/core/app.go b/backend/core/app.go index a79120e..96e5229 100644 --- a/backend/core/app.go +++ b/backend/core/app.go @@ -27,6 +27,7 @@ func NewApplication() *Application { as := service.NewAccountService(*db) ps := service.NewPostService(*db, *fs, *msq) ms := service.NewMiscService(*db) + ads := service.NewAdminService(*db) err := ScheduleRoutines(*db, *msq) if err != nil { @@ -34,7 +35,7 @@ func NewApplication() *Application { } return &Application{ - API: controller.NewApiController(*as, *ps, *ms), + API: controller.NewApiController(*as, *ps, *ms, *ads), } } diff --git a/backend/database/mongo.go b/backend/database/mongo.go index c611674..6d13db1 100644 --- a/backend/database/mongo.go +++ b/backend/database/mongo.go @@ -18,6 +18,7 @@ const ( POST_VERBOSE_COLLECTION = "post_verbose" METADATA_COLLECTION = "metadata" BAN_LIST_COLLECTION = "ban_list" + OAUTH_APP_COLLECTION = "oauth_app" ) type Metadata struct { @@ -563,3 +564,62 @@ func (m *MongoDBManager) GetMetadata(key string) (string, error) { } return meta.Value, nil } + +func (m *MongoDBManager) AddOAuth2App(app *OAuthAppPO) error { + _, err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).InsertOne(context.TODO(), app) + return err +} + +func (m *MongoDBManager) GetOAuth2App(clientID string) (*OAuthAppPO, error) { + var app OAuthAppPO + err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).FindOne( + context.TODO(), + bson.M{"client_id": clientID}, + ).Decode(&app) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, nil + } + return nil, err + } + return &app, nil +} + +func (m *MongoDBManager) GetOAuth2AppByName(name string) (*OAuthAppPO, error) { + // 若不存在,返回 nil, nil + var app OAuthAppPO + + err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).FindOne( + context.TODO(), + bson.M{"name": name}, + ).Decode(&app) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, nil + } + return nil, err + } + return &app, nil +} + +// list +func (m *MongoDBManager) GetOAuth2Apps() ([]OAuthAppPO, error) { + var apps []OAuthAppPO + cursor, err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).Find(context.TODO(), bson.M{}) + if err != nil { + return nil, err + } + defer cursor.Close(context.Background()) + + err = cursor.All(context.Background(), &apps) + if err != nil { + return nil, err + } + + return apps, nil +} + +func (m *MongoDBManager) DeleteOAuth2App(clientID string) error { + _, err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).DeleteOne(context.TODO(), bson.M{"client_id": clientID}) + return err +} diff --git a/backend/database/po.go b/backend/database/po.go index 5b5d6b5..5502982 100644 --- a/backend/database/po.go +++ b/backend/database/po.go @@ -84,3 +84,11 @@ const ( REVIEW_OPTION_APPROVE ReviewOption = "approve" REVIEW_OPTION_REJECT ReviewOption = "reject" ) + +type OAuthAppPO struct { + Name string `json:"name" bson:"name"` // 应用名称 + Emoji string `json:"emoji" bson:"emoji"` // Emoji + ClientID string `json:"client_id" bson:"client_id"` // 客户端ID + ClientSecret string `json:"client_secret" bson:"client_secret"` // 客户端密钥 + CreatedAt time.Time `json:"created_at" bson:"created_at"` // CST时间 +} diff --git a/backend/service/admin.go b/backend/service/admin.go new file mode 100644 index 0000000..88ac97f --- /dev/null +++ b/backend/service/admin.go @@ -0,0 +1,52 @@ +package service + +import ( + "github.com/RockChinQ/Campux/backend/database" + "github.com/RockChinQ/Campux/backend/util" + "github.com/google/uuid" +) + +type AdminService struct { + CommonService +} + +func NewAdminService(db database.MongoDBManager) *AdminService { + return &AdminService{ + CommonService: CommonService{ + DB: db, + }, + } +} + +func (as *AdminService) AddOAuth2App(name, emoji string) (*database.OAuthAppPO, error) { + check, err := as.DB.GetOAuth2AppByName(name) + + if err != nil { + return nil, err + } + + if check != nil { + return nil, ErrOAuth2AppAlreadyExist + } + + app := &database.OAuthAppPO{ + Name: name, + Emoji: emoji, + ClientID: util.RandomString(16), + ClientSecret: uuid.New().String(), + CreatedAt: util.GetCSTTime(), + } + + err = as.DB.AddOAuth2App(app) + + return app, err +} + +func (as *AdminService) GetOAuth2Apps() ([]database.OAuthAppPO, error) { + return as.DB.GetOAuth2Apps() +} + +// delete +func (as *AdminService) DeleteOAuth2App(appID string) error { + return as.DB.DeleteOAuth2App(appID) +} diff --git a/backend/service/errors.go b/backend/service/errors.go index 4bf8c6c..d680e98 100644 --- a/backend/service/errors.go +++ b/backend/service/errors.go @@ -13,3 +13,6 @@ var ErrPasswordIncorrect = errors.New("密码错误") // 不允许的图片后缀 var ErrInvalidImageSuffix = errors.New("不允许的图片后缀") + +// OAuth2应用名称已存在 +var ErrOAuth2AppAlreadyExist = errors.New("OAuth2应用名称已存在") diff --git a/backend/util/string.go b/backend/util/string.go index b0320b9..eef5e9d 100644 --- a/backend/util/string.go +++ b/backend/util/string.go @@ -1,5 +1,7 @@ package util +import "math/rand" + func StringInSlice(str string, list []string) bool { for _, v := range list { if v == str { @@ -8,3 +10,15 @@ func StringInSlice(str string, list []string) bool { } return false } + +func RandomString(length int) string { + list := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + + var result []byte + + for i := 0; i < length; i++ { + result = append(result, list[rand.Intn(len(list))]) + } + + return string(result) +} diff --git a/frontend/package.json b/frontend/package.json index 1cc48d5..dd5dab8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "roboto-fontface": "*", "vue": "^3.4.0", "vue-cookies": "^1.8.4", + "vue3-emoji-picker": "^1.1.8", "vuetify": "^3.5.0", "vuex": "^4.1.0" }, diff --git a/frontend/src/components/BanRecordCard.vue b/frontend/src/components/BanRecordCard.vue index 1287ff9..60fac4c 100644 --- a/frontend/src/components/BanRecordCard.vue +++ b/frontend/src/components/BanRecordCard.vue @@ -30,7 +30,7 @@ + + \ No newline at end of file diff --git a/frontend/src/pages/admin.vue b/frontend/src/pages/admin.vue index 97acc0c..6d75453 100644 --- a/frontend/src/pages/admin.vue +++ b/frontend/src/pages/admin.vue @@ -7,6 +7,7 @@ 🪪 账号 🚫 封禁记录 + 🔑 OAuth 2 应用 @@ -23,7 +24,7 @@ - 查找 + 查找 - 查找 + 查找 @@ -61,6 +62,22 @@ + +
+ +
+ 新建 OAuth2 应用 + 刷新 +
+ +
+ + +
+
+
@@ -77,13 +94,40 @@ + + + 新建 OAuth2 应用 + + +
+

{{ newOAuthApp.emoji }}

+ +
+
+ + 取消 + 确定 + +
+
+