Skip to content

Commit

Permalink
Merge pull request #26 from idoknow/feat/oauth-server
Browse files Browse the repository at this point in the history
Feat: OAuth 2.0 Server
  • Loading branch information
RockChinQ authored Jul 27, 2024
2 parents fe7efc9 + 960a327 commit 7c0c8e8
Show file tree
Hide file tree
Showing 25 changed files with 943 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-latest-backend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 构建完成。"
}'
10 changes: 9 additions & 1 deletion backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package config

import (
"github.com/spf13/viper"

"github.com/google/uuid"
)

type Config struct {
Expand All @@ -12,9 +14,14 @@ func SetDefault() {
viper.SetDefault("backend.port", "8080")

// jwt
viper.SetDefault("auth.jwt.secret", "campux")
viper.SetDefault("auth.jwt.secret", uuid.New().String())
viper.SetDefault("auth.jwt.expire", 3600*6)

// oauth2
viper.SetDefault("oauth2.server.code_secret", uuid.New().String())
viper.SetDefault("oauth2.server.access_secret", uuid.New().String())
viper.SetDefault("oauth2.server.ak_expire", 3600*24*14)

// 服务token
viper.SetDefault("service.token", "campux")
viper.SetDefault("service.bots", []int64{123456789})
Expand All @@ -38,6 +45,7 @@ func SetDefault() {
viper.SetDefault("mq.redis.stream.new_post", "campux_new_post")
viper.SetDefault("mq.redis.stream.post_cancel", "campux_post_cancel")
viper.SetDefault("mq.redis.hash.post_publish_status", "campux_post_publish_status")
viper.SetDefault("mq.redis.prefix.oauth2_code", "campux_oauth2_code")

}

Expand Down
119 changes: 119 additions & 0 deletions backend/controller/admapi.go
Original file line number Diff line number Diff line change
@@ -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)
}
8 changes: 6 additions & 2 deletions backend/controller/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ func NewApiController(
as service.AccountService,
ps service.PostService,
ms service.MiscService,
ads service.AdminService,
oas service.OAuth2Service,
) *APIController {
r := gin.Default()

Expand Down Expand Up @@ -63,6 +65,8 @@ func NewApiController(
NewAccountRouter(rg, as)
NewPostRouter(rg, ps, as)
NewMiscRouter(rg, ms)
NewAdminRouter(rg, ads)
NewOAuth2Router(rg, oas)

return &APIController{
R: r,
Expand Down Expand Up @@ -165,7 +169,7 @@ func (ar *APIRouter) GetUin(c *gin.Context) (int64, error) {
// 删除Bearer
jwtToken = jwtToken[7:]

uin, err := util.ParseJWTToken(jwtToken)
uin, err := util.ParseUserJWTToken(jwtToken)

return uin, err
} else {
Expand All @@ -176,7 +180,7 @@ func (ar *APIRouter) GetUin(c *gin.Context) (int64, error) {
return -1, err
}

uin, err := util.ParseJWTToken(jwtToken)
uin, err := util.ParseUserJWTToken(jwtToken)

return uin, err
}
Expand Down
24 changes: 24 additions & 0 deletions backend/controller/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,27 @@ 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"`
}

type OAuth2AuthorizeBody struct {
// 应用id
ClientID string `json:"client_id" binding:"required"`
}

type OAuth2GetAccessTokenBody struct {
// 应用id
ClientID string `json:"client_id" binding:"required"`

// 应用密钥
ClientSecret string `json:"client_secret" binding:"required"`

// 授权码
Code string `json:"code" binding:"required"`
}
110 changes: 110 additions & 0 deletions backend/controller/oauthapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package controller

import (
"github.com/RockChinQ/Campux/backend/service"
"github.com/gin-gonic/gin"
)

type OAuth2Router struct {
APIRouter
OAuth2Service service.OAuth2Service
}

func NewOAuth2Router(rg *gin.RouterGroup, oas service.OAuth2Service) *OAuth2Router {

oar := &OAuth2Router{
OAuth2Service: oas,
}

group := rg.Group("/oauth2")

group.GET("/get-app-info", oar.GetOAuth2AppInfo)
group.GET("/authorize", oar.Authorize)
group.POST("/get-access-token", oar.GetAccessToken)

return oar
}

func (oar *OAuth2Router) GetOAuth2AppInfo(c *gin.Context) {
clientID := c.Query("client_id")

app, err := oar.OAuth2Service.GetOAuth2AppByClientID(clientID)

if err != nil {
oar.Fail(c, 1, err.Error())
return
}

if app == nil {
oar.Fail(c, 2, "此应用未注册")
return
}

oar.Success(c, gin.H{
"client_id": app.ClientID,
"name": app.Name,
})
}

func (oar *OAuth2Router) Authorize(c *gin.Context) {

uin, err := oar.Auth(c, UserOnly)

if err != nil {
return
}

clientID := c.Query("client_id")

if clientID == "" {
oar.Fail(c, 1, "未提供 client_id")
return
}

// 检查是否存在这个应用
app, err := oar.OAuth2Service.GetOAuth2AppByClientID(clientID)

if err != nil {
oar.Fail(c, 2, err.Error())
return
}

if app == nil {
oar.Fail(c, 3, "此应用未注册")
return
}

// 计算code
code, err := oar.OAuth2Service.GenerateCode(app.ClientID, uin)

if err != nil {
oar.Fail(c, 4, err.Error())
return
}

oar.Success(c, gin.H{
"code": code,
})
}

func (oar *OAuth2Router) GetAccessToken(c *gin.Context) {

var body OAuth2GetAccessTokenBody

if err := c.ShouldBindJSON(&body); err != nil {
oar.Fail(c, 1, err.Error())
return
}

// 检查code
ak, err := oar.OAuth2Service.GetAccessToken(body.ClientID, body.ClientSecret, body.Code)

if err != nil {
oar.Fail(c, 2, err.Error())
return
}

oar.Success(c, gin.H{
"access_token": ak,
})
}
4 changes: 3 additions & 1 deletion backend/core/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ func NewApplication() *Application {
as := service.NewAccountService(*db)
ps := service.NewPostService(*db, *fs, *msq)
ms := service.NewMiscService(*db)
ads := service.NewAdminService(*db)
oas := service.NewOAuth2Service(*db, *msq)

err := ScheduleRoutines(*db, *msq)
if err != nil {
panic(err)
}

return &Application{
API: controller.NewApiController(*as, *ps, *ms),
API: controller.NewApiController(*as, *ps, *ms, *ads, *oas),
}
}

Expand Down
Loading

0 comments on commit 7c0c8e8

Please sign in to comment.