mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-14 15:07:33 +08:00
add a special token type for MCP
This commit is contained in:
+44
-1
@@ -46,7 +46,7 @@ var (
|
||||
// TokenListHandler returns available token list of current user
|
||||
func (a *TokensApi) TokenListHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
tokens, err := a.tokens.GetAllUnexpiredNormalTokensByUid(c, uid)
|
||||
tokens, err := a.tokens.GetAllUnexpiredNormalAndMCPTokensByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[tokens.TokenListHandler] failed to get all tokens for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -69,6 +69,10 @@ func (a *TokensApi) TokenListHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
tokenResp.IsCurrent = true
|
||||
}
|
||||
|
||||
if token.TokenType == core.USER_TOKEN_TYPE_MCP && token.UserAgent != services.TokenUserAgentCreatedViaCli {
|
||||
tokenResp.UserAgent = services.TokenUserAgentForMCP
|
||||
}
|
||||
|
||||
tokenResps[i] = tokenResp
|
||||
}
|
||||
|
||||
@@ -77,6 +81,45 @@ func (a *TokensApi) TokenListHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
return tokenResps, nil
|
||||
}
|
||||
|
||||
// TokenGenerateMCPHandler generates a new MCP token for current user
|
||||
func (a *TokensApi) TokenGenerateMCPHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var generateMCPTokenReq models.TokenGenerateMCPRequest
|
||||
err := c.ShouldBindJSON(&generateMCPTokenReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[tokens.TokenGenerateMCPHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[tokens.TokenGenerateMCPHandler] failed to get user \"uid:%d\" info, because %s", uid, err.Error())
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
if !a.users.IsPasswordEqualsUserPassword(generateMCPTokenReq.Password, user) {
|
||||
return nil, errs.ErrUserPasswordWrong
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateMCPToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[tokens.TokenGenerateMCPHandler] failed to create mcp token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrTokenGenerating)
|
||||
}
|
||||
|
||||
log.Infof(c, "[tokens.TokenGenerateMCPHandler] user \"uid:%d\" has generated mcp token, new token will be expired at %d", user.Uid, claims.ExpiresAt)
|
||||
|
||||
generateMCPTokenResp := &models.TokenGenerateMCPResponse{
|
||||
Token: token,
|
||||
MCPUrl: a.CurrentConfig().RootUrl + "mcp",
|
||||
}
|
||||
|
||||
return generateMCPTokenResp, nil
|
||||
}
|
||||
|
||||
// TokenRevokeCurrentHandler revokes current token of current user
|
||||
func (a *TokensApi) TokenRevokeCurrentHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
_, claims, err := a.tokens.ParseTokenByHeader(c)
|
||||
|
||||
+12
-3
@@ -394,7 +394,7 @@ func (l *UserDataCli) ListUserTokens(c *core.CliContext, username string) ([]*mo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokens, err := l.tokens.GetAllUnexpiredNormalTokensByUid(c, uid)
|
||||
tokens, err := l.tokens.GetAllUnexpiredNormalAndMCPTokensByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.CliErrorf(c, "[user_data.ListUserTokens] failed to get tokens of user \"%s\", because %s", username, err.Error())
|
||||
@@ -405,7 +405,7 @@ func (l *UserDataCli) ListUserTokens(c *core.CliContext, username string) ([]*mo
|
||||
}
|
||||
|
||||
// CreateNewUserToken returns a new token for the specified user
|
||||
func (l *UserDataCli) CreateNewUserToken(c *core.CliContext, username string) (*models.TokenRecord, string, error) {
|
||||
func (l *UserDataCli) CreateNewUserToken(c *core.CliContext, username string, tokenType string) (*models.TokenRecord, string, error) {
|
||||
if username == "" {
|
||||
log.CliErrorf(c, "[user_data.CreateNewUserToken] user name is empty")
|
||||
return nil, "", errs.ErrUsernameIsEmpty
|
||||
@@ -418,7 +418,16 @@ func (l *UserDataCli) CreateNewUserToken(c *core.CliContext, username string) (*
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
token, tokenRecord, err := l.tokens.CreateTokenViaCli(c, user)
|
||||
var token string
|
||||
var tokenRecord *models.TokenRecord
|
||||
|
||||
if tokenType == "mcp" {
|
||||
token, tokenRecord, err = l.tokens.CreateMCPTokenViaCli(c, user)
|
||||
} else if tokenType == "normal" {
|
||||
token, tokenRecord, err = l.tokens.CreateTokenViaCli(c, user)
|
||||
} else {
|
||||
return nil, "", errs.ErrParameterInvalid
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.CliErrorf(c, "[user_data.CreateNewUserToken] failed to create token for user \"%s\", because %s", username, err.Error())
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
USER_TOKEN_TYPE_REQUIRE_2FA TokenType = 2
|
||||
USER_TOKEN_TYPE_EMAIL_VERIFY TokenType = 3
|
||||
USER_TOKEN_TYPE_PASSWORD_RESET TokenType = 4
|
||||
USER_TOKEN_TYPE_MCP TokenType = 5
|
||||
)
|
||||
|
||||
// UserTokenClaims represents user token
|
||||
|
||||
@@ -94,6 +94,25 @@ func JWTResetPasswordAuthorization(c *core.WebContext) {
|
||||
c.Next()
|
||||
}
|
||||
|
||||
// JWTMCPAuthorization verifies whether current request is valid by jwt mcp token in header
|
||||
func JWTMCPAuthorization(c *core.WebContext) {
|
||||
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_HEADER)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_MCP {
|
||||
log.Warnf(c, "[authorization.jwtAuthorization] user \"uid:%d\" token type (%d) is not mcp token", claims.Uid, claims.Type)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetTokenClaims(claims)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func jwtAuthorization(c *core.WebContext, source TokenSourceType) {
|
||||
claims, err := getTokenClaims(c, source)
|
||||
|
||||
@@ -109,7 +128,7 @@ func jwtAuthorization(c *core.WebContext, source TokenSourceType) {
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
|
||||
log.Warnf(c, "[authorization.jwtAuthorization] user \"uid:%d\" token type is invalid", claims.Uid)
|
||||
log.Warnf(c, "[authorization.jwtAuthorization] user \"uid:%d\" token type (%d) is invalid", claims.Uid, claims.Type)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -17,11 +17,22 @@ type TokenRecord struct {
|
||||
LastSeenUnixTime int64
|
||||
}
|
||||
|
||||
// TokenGenerateMCPRequest represents all parameters of mcp token generation request
|
||||
type TokenGenerateMCPRequest struct {
|
||||
Password string `json:"password" binding:"omitempty,min=6,max=128"`
|
||||
}
|
||||
|
||||
// TokenRevokeRequest represents all parameters of token revoking request
|
||||
type TokenRevokeRequest struct {
|
||||
TokenId string `json:"tokenId" binding:"required,notBlank"`
|
||||
}
|
||||
|
||||
// TokenGenerateMCPResponse represents all response parameters of generated mcp token
|
||||
type TokenGenerateMCPResponse struct {
|
||||
Token string `json:"token"`
|
||||
MCPUrl string `json:"mcpUrl"`
|
||||
}
|
||||
|
||||
// TokenRefreshResponse represents all parameters of token refreshing request
|
||||
type TokenRefreshResponse struct {
|
||||
NewToken string `json:"newToken,omitempty"`
|
||||
|
||||
+26
-4
@@ -19,6 +19,14 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// TokenUserAgentCreatedViaCli is the user agent of token created via cli
|
||||
const TokenUserAgentCreatedViaCli = "ezbookkeeping Cli"
|
||||
|
||||
// TokenUserAgentForMCP is the user agent for MCP token
|
||||
const TokenUserAgentForMCP = "ezbookkeeping MCP"
|
||||
|
||||
const tokenMaxExpiredAtUnixTime = int64(253402300799) // 9999-12-31 23:59:59 UTC
|
||||
|
||||
// TokenService represents user token service
|
||||
type TokenService struct {
|
||||
ServiceUsingDB
|
||||
@@ -49,8 +57,8 @@ func (s *TokenService) GetAllTokensByUid(c core.Context, uid int64) ([]*models.T
|
||||
return tokenRecords, err
|
||||
}
|
||||
|
||||
// GetAllUnexpiredNormalTokensByUid returns all available token models of given user
|
||||
func (s *TokenService) GetAllUnexpiredNormalTokensByUid(c core.Context, uid int64) ([]*models.TokenRecord, error) {
|
||||
// GetAllUnexpiredNormalAndMCPTokensByUid returns all available token models of given user
|
||||
func (s *TokenService) GetAllUnexpiredNormalAndMCPTokensByUid(c core.Context, uid int64) ([]*models.TokenRecord, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -58,7 +66,7 @@ func (s *TokenService) GetAllUnexpiredNormalTokensByUid(c core.Context, uid int6
|
||||
now := time.Now().Unix()
|
||||
|
||||
var tokenRecords []*models.TokenRecord
|
||||
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time", "last_seen_unix_time").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, now).Find(&tokenRecords)
|
||||
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time", "last_seen_unix_time").Where("uid=? AND (token_type=? OR token_type=?) AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, core.USER_TOKEN_TYPE_MCP, now).Find(&tokenRecords)
|
||||
|
||||
return tokenRecords, err
|
||||
}
|
||||
@@ -80,7 +88,7 @@ func (s *TokenService) ParseTokenByCookie(c *core.WebContext, tokenCookieName st
|
||||
|
||||
// CreateTokenViaCli generates a new normal token and saves to database
|
||||
func (s *TokenService) CreateTokenViaCli(c *core.CliContext, user *models.User) (string, *models.TokenRecord, error) {
|
||||
token, _, tokenRecord, err := s.createToken(c, user, core.USER_TOKEN_TYPE_NORMAL, "ezbookkeeping Cli", s.CurrentConfig().TokenExpiredTimeDuration)
|
||||
token, _, tokenRecord, err := s.createToken(c, user, core.USER_TOKEN_TYPE_NORMAL, TokenUserAgentCreatedViaCli, s.CurrentConfig().TokenExpiredTimeDuration)
|
||||
return token, tokenRecord, err
|
||||
}
|
||||
|
||||
@@ -120,6 +128,20 @@ func (s *TokenService) CreatePasswordResetTokenWithoutUserAgent(c core.Context,
|
||||
return token, claims, err
|
||||
}
|
||||
|
||||
// CreateMCPToken generates a new MCP token and saves to database
|
||||
func (s *TokenService) CreateMCPToken(c *core.WebContext, user *models.User) (string, *core.UserTokenClaims, error) {
|
||||
tokenExpiredTimeDuration := time.Unix(tokenMaxExpiredAtUnixTime, 0).Sub(time.Now())
|
||||
token, claims, _, err := s.createToken(c, user, core.USER_TOKEN_TYPE_MCP, s.getUserAgent(c), tokenExpiredTimeDuration)
|
||||
return token, claims, err
|
||||
}
|
||||
|
||||
// CreateMCPTokenViaCli generates a new MCP token and saves to database
|
||||
func (s *TokenService) CreateMCPTokenViaCli(c *core.CliContext, user *models.User) (string, *models.TokenRecord, error) {
|
||||
tokenExpiredTimeDuration := time.Unix(tokenMaxExpiredAtUnixTime, 0).Sub(time.Now())
|
||||
token, _, tokenRecord, err := s.createToken(c, user, core.USER_TOKEN_TYPE_MCP, TokenUserAgentCreatedViaCli, tokenExpiredTimeDuration)
|
||||
return token, tokenRecord, err
|
||||
}
|
||||
|
||||
// UpdateTokenLastSeen updates the last seen time of specified token
|
||||
func (s *TokenService) UpdateTokenLastSeen(c core.Context, tokenRecord *models.TokenRecord) error {
|
||||
if tokenRecord.Uid <= 0 {
|
||||
|
||||
Reference in New Issue
Block a user