Skip to content

Commit 90022ce

Browse files
LinkinStarsdashuai
authored andcommitted
fix: integrate API key authentication into existing services and routes
1 parent 50a9680 commit 90022ce

9 files changed

Lines changed: 163 additions & 7 deletions

File tree

cmd/wire_gen.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package middleware
21+
22+
import (
23+
"github.com/apache/answer/internal/base/handler"
24+
"github.com/apache/answer/internal/base/reason"
25+
"github.com/gin-gonic/gin"
26+
"github.com/segmentfault/pacman/errors"
27+
)
28+
29+
// AuthAPIKey middleware to authenticate API key
30+
func (am *AuthUserMiddleware) AuthAPIKey() gin.HandlerFunc {
31+
return func(ctx *gin.Context) {
32+
token := ExtractToken(ctx)
33+
if len(token) == 0 {
34+
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
35+
ctx.Abort()
36+
return
37+
}
38+
pass, err := am.authService.AuthAPIKey(ctx, ctx.Request.Method == "GET", token)
39+
if err != nil {
40+
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
41+
ctx.Abort()
42+
return
43+
}
44+
if !pass {
45+
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
46+
ctx.Abort()
47+
return
48+
}
49+
ctx.Next()
50+
}
51+
}

internal/base/server/http.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,10 @@ func NewHTTPServer(debug bool,
113113
agent.RegisterAuthAdminRouter(adminauthV1)
114114
return nil
115115
})
116+
117+
// mcp
118+
mcpAPIGroup := r.Group(uiConf.APIBaseURL + "/answer/api/v1")
119+
mcpAPIGroup.Use(authUserMiddleware.AuthMcpEnable(), authUserMiddleware.AuthAPIKey())
120+
answerRouter.RegisterMCPRouter(mcpAPIGroup)
116121
return r
117122
}

internal/cli/reset_password.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/apache/answer/internal/base/conf"
3333
"github.com/apache/answer/internal/base/data"
3434
"github.com/apache/answer/internal/base/path"
35+
"github.com/apache/answer/internal/repo/api_key"
3536
"github.com/apache/answer/internal/repo/auth"
3637
"github.com/apache/answer/internal/repo/user"
3738
authService "github.com/apache/answer/internal/service/auth"
@@ -95,7 +96,8 @@ func ResetPassword(ctx context.Context, dataDirPath string, opts *ResetPasswordO
9596

9697
userRepo := user.NewUserRepo(dataData)
9798
authRepo := auth.NewAuthRepo(dataData)
98-
authSvc := authService.NewAuthService(authRepo)
99+
apiKeyRepo := api_key.NewAPIKeyRepo(dataData)
100+
authSvc := authService.NewAuthService(authRepo, apiKeyRepo)
99101

100102
email := strings.TrimSpace(opts.Email)
101103
if email == "" {

internal/migrations/init.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func (m *Mentor) InitDB() error {
8787
m.do("init site info security", m.initSiteInfoSecurityConfig)
8888
m.do("init default content", m.initDefaultContent)
8989
m.do("init default badges", m.initDefaultBadges)
90+
m.do("init default ai config", m.initSiteInfoAI)
91+
m.do("init default MCP config", m.initSiteInfoMCP)
9092
return m.err
9193
}
9294

@@ -606,3 +608,29 @@ func (m *Mentor) initDefaultBadges() {
606608
}
607609
}
608610
}
611+
612+
func (m *Mentor) initSiteInfoAI() {
613+
content := &schema.SiteAIReq{
614+
PromptConfig: &schema.AIPromptConfig{
615+
ZhCN: constant.DefaultAIPromptConfigZhCN,
616+
EnUS: constant.DefaultAIPromptConfigEnUS,
617+
},
618+
}
619+
writeDataBytes, _ := json.Marshal(content)
620+
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
621+
Type: constant.SiteTypeAI,
622+
Content: string(writeDataBytes),
623+
Status: 1,
624+
})
625+
}
626+
func (m *Mentor) initSiteInfoMCP() {
627+
content := &schema.SiteMCPReq{
628+
Enabled: true,
629+
}
630+
writeDataBytes, _ := json.Marshal(content)
631+
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
632+
Type: constant.SiteTypeMCP,
633+
Content: string(writeDataBytes),
634+
Status: 1,
635+
})
636+
}

internal/migrations/init_data.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ var (
7676
&entity.BadgeAward{},
7777
&entity.FileRecord{},
7878
&entity.PluginKVStorage{},
79+
&entity.APIKey{},
80+
&entity.AIConversation{},
81+
&entity.AIConversationRecord{},
7982
}
8083

8184
roles = []*entity.Role{

internal/router/answer_api_router.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type AnswerAPIRouter struct {
6161
aiController *controller.AIController
6262
aiConversationController *controller.AIConversationController
6363
aiConversationAdminController *controller_admin.AIConversationAdminController
64+
mcpController *controller.MCPController
6465
}
6566

6667
func NewAnswerAPIRouter(
@@ -98,6 +99,7 @@ func NewAnswerAPIRouter(
9899
aiController *controller.AIController,
99100
aiConversationController *controller.AIConversationController,
100101
aiConversationAdminController *controller_admin.AIConversationAdminController,
102+
mcpController *controller.MCPController,
101103
) *AnswerAPIRouter {
102104
return &AnswerAPIRouter{
103105
langController: langController,
@@ -134,6 +136,7 @@ func NewAnswerAPIRouter(
134136
aiController: aiController,
135137
aiConversationController: aiConversationController,
136138
aiConversationAdminController: aiConversationAdminController,
139+
mcpController: mcpController,
137140
}
138141
}
139142

internal/router/mcp_router.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package router
21+
22+
import (
23+
"github.com/apache/answer/internal/schema/mcp_tools"
24+
"github.com/gin-gonic/gin"
25+
"github.com/mark3labs/mcp-go/server"
26+
)
27+
28+
func (a *AnswerAPIRouter) RegisterMCPRouter(r *gin.RouterGroup) {
29+
s := server.NewMCPServer("Answer Enterprise MCP Server", "1.0.0")
30+
31+
s.AddTool(mcp_tools.NewQuestionsTool(), a.mcpController.MCPQuestionsHandler())
32+
s.AddTool(mcp_tools.NewAnswersTool(), a.mcpController.MCPAnswersHandler())
33+
s.AddTool(mcp_tools.NewCommentsTool(), a.mcpController.MCPCommentsHandler())
34+
s.AddTool(mcp_tools.NewTagsTool(), a.mcpController.MCPTagsHandler())
35+
s.AddTool(mcp_tools.NewTagDetailTool(), a.mcpController.MCPTagDetailsHandler())
36+
s.AddTool(mcp_tools.NewUserTool(), a.mcpController.MCPUserDetailsHandler())
37+
38+
sseServer := server.NewSSEServer(s,
39+
server.WithSSEEndpoint("/answer/api/v1/mcp/see"),
40+
server.WithMessageEndpoint("/answer/api/v1/mcp/message"),
41+
)
42+
r.GET("/mcp/sse", gin.WrapH(sseServer.SSEHandler()))
43+
r.POST("/mcp/message", gin.WrapH(sseServer.MessageHandler()))
44+
}

internal/service/auth/auth.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import (
2323
"context"
2424

2525
"github.com/apache/answer/internal/entity"
26+
"github.com/apache/answer/internal/service/apikey"
2627
"github.com/apache/answer/pkg/token"
2728
"github.com/apache/answer/plugin"
29+
"github.com/segmentfault/pacman/log"
2830
)
2931

3032
// AuthRepo auth repository
@@ -46,13 +48,15 @@ type AuthRepo interface {
4648

4749
// AuthService kit service
4850
type AuthService struct {
49-
authRepo AuthRepo
51+
authRepo AuthRepo
52+
apiKeyRepo apikey.APIKeyRepo
5053
}
5154

5255
// NewAuthService email service
53-
func NewAuthService(authRepo AuthRepo) *AuthService {
56+
func NewAuthService(authRepo AuthRepo, apiKeyRepo apikey.APIKeyRepo) *AuthService {
5457
return &AuthService{
55-
authRepo: authRepo,
58+
authRepo: authRepo,
59+
apiKeyRepo: apiKeyRepo,
5660
}
5761
}
5862

@@ -152,3 +156,19 @@ func (as *AuthService) SetAdminUserCacheInfo(ctx context.Context, accessToken st
152156
func (as *AuthService) RemoveAdminUserCacheInfo(ctx context.Context, accessToken string) (err error) {
153157
return as.authRepo.RemoveAdminUserCacheInfo(ctx, accessToken)
154158
}
159+
func (as *AuthService) AuthAPIKey(ctx context.Context, read bool, apiKey string) (pass bool, err error) {
160+
apiKeyInfo, exist, err := as.apiKeyRepo.GetAPIKey(ctx, apiKey)
161+
if err != nil {
162+
return false, err
163+
}
164+
if !exist {
165+
return false, nil
166+
}
167+
// If the request is not read-only, check if the API key has write permissions
168+
if !read && apiKeyInfo.Scope == "read-only" {
169+
log.Warnf("API key %s does not have write permissions", apiKeyInfo.AccessKey)
170+
return false, nil
171+
}
172+
log.Infof("API key %s is valid, scope: %s", apiKeyInfo.AccessKey, apiKeyInfo.Scope)
173+
return true, nil
174+
}

0 commit comments

Comments
 (0)