✨ Reply token
This commit is contained in:
		| @@ -7,8 +7,6 @@ import ( | |||||||
| 	"git.solsynth.dev/hypernet/messaging/pkg/internal/http/exts" | 	"git.solsynth.dev/hypernet/messaging/pkg/internal/http/exts" | ||||||
| 	"git.solsynth.dev/hypernet/messaging/pkg/internal/models" | 	"git.solsynth.dev/hypernet/messaging/pkg/internal/models" | ||||||
| 	"git.solsynth.dev/hypernet/messaging/pkg/internal/services" | 	"git.solsynth.dev/hypernet/messaging/pkg/internal/services" | ||||||
| 	"git.solsynth.dev/hypernet/nexus/pkg/nex/sec" |  | ||||||
| 	authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models" |  | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| 	jsoniter "github.com/json-iterator/go" | 	jsoniter "github.com/json-iterator/go" | ||||||
| @@ -20,13 +18,23 @@ import ( | |||||||
| // It did not support all the features of the message event | // It did not support all the features of the message event | ||||||
| // But it just works | // But it just works | ||||||
| func quickReply(c *fiber.Ctx) error { | func quickReply(c *fiber.Ctx) error { | ||||||
| 	if err := sec.EnsureAuthenticated(c); err != nil { | 	replyTk := c.Query("replyToken") | ||||||
| 		return err | 	if len(replyTk) == 0 { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, "reply token is required") | ||||||
| 	} | 	} | ||||||
| 	user := c.Locals("user").(authm.Account) |  | ||||||
|  | 	claims, err := services.ParseReplyToken(replyTk) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("reply token is invaild: %v", err)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	channelId, _ := c.ParamsInt("channelId", 0) | 	channelId, _ := c.ParamsInt("channelId", 0) | ||||||
| 	eventId, _ := c.ParamsInt("eventId", 0) | 	eventId, _ := c.ParamsInt("eventId", 0) | ||||||
|  |  | ||||||
|  | 	if claims.EventID != uint(eventId) { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, "reply token is invaild, event id mismatch") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	var data struct { | 	var data struct { | ||||||
| 		Type string                  `json:"type" validate:"required"` | 		Type string                  `json:"type" validate:"required"` | ||||||
| 		Body models.EventMessageBody `json:"body"` | 		Body models.EventMessageBody `json:"body"` | ||||||
| @@ -43,7 +51,7 @@ func quickReply(c *fiber.Ctx) error { | |||||||
| 		return fiber.NewError(fiber.StatusBadRequest, "empty message was not allowed") | 		return fiber.NewError(fiber.StatusBadRequest, "empty message was not allowed") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	channel, member, err := services.GetChannelIdentityWithID(uint(channelId), user.ID) | 	channel, member, err := services.GetChannelIdentityWithID(uint(channelId), claims.UserID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("channel / member not found: %v", err.Error())) | 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("channel / member not found: %v", err.Error())) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -180,14 +180,16 @@ func NotifyMessageEvent(members []models.ChannelMember, event models.Event) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	displayTitle := fmt.Sprintf("%s (%s)", event.Sender.Nick, event.Channel.DisplayText()) | 	displayTitle := fmt.Sprintf("%s (%s)", event.Sender.Nick, event.Channel.DisplayText()) | ||||||
|  | 	replyToken, err := CreateReplyToken(event.ID, event.Sender.AccountID) | ||||||
|  |  | ||||||
| 	metadata := map[string]any{ | 	metadata := map[string]any{ | ||||||
| 		"avatar":     event.Sender.Avatar, | 		"avatar":      event.Sender.Avatar, | ||||||
| 		"user_id":    event.Sender.AccountID, | 		"user_id":     event.Sender.AccountID, | ||||||
| 		"user_name":  event.Sender.Name, | 		"user_name":   event.Sender.Name, | ||||||
| 		"user_nick":  event.Sender.Nick, | 		"user_nick":   event.Sender.Nick, | ||||||
| 		"channel_id": event.ChannelID, | 		"channel_id":  event.ChannelID, | ||||||
| 		"event_id":   event.ID, | 		"event_id":    event.ID, | ||||||
|  | 		"reply_token": replyToken, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(pendingUsers) > 0 { | 	if len(pendingUsers) > 0 { | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								pkg/internal/services/reply_token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								pkg/internal/services/reply_token.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | package services | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/golang-jwt/jwt/v5" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ReplyClaims struct { | ||||||
|  | 	UserID  uint `json:"user_id"` | ||||||
|  | 	EventID uint `json:"event_id"` | ||||||
|  | 	jwt.RegisteredClaims | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func CreateReplyToken(eventId uint, userId uint) (string, error) { | ||||||
|  | 	claims := ReplyClaims{ | ||||||
|  | 		UserID:  userId, | ||||||
|  | 		EventID: eventId, | ||||||
|  | 		RegisteredClaims: jwt.RegisteredClaims{ | ||||||
|  | 			Issuer:    "messaging", | ||||||
|  | 			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 7)), | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims) | ||||||
|  | 	tks, err := token.SignedString([]byte(viper.GetString("security.reply_token_secret"))) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", fmt.Errorf("failed to sign token: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return tks, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ParseReplyToken(tk string) (ReplyClaims, error) { | ||||||
|  | 	var claims ReplyClaims | ||||||
|  | 	token, err := jwt.ParseWithClaims(tk, &claims, func(token *jwt.Token) (interface{}, error) { | ||||||
|  | 		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { | ||||||
|  | 			return nil, fmt.Errorf("unexpected signing method: %v", token.Method) | ||||||
|  | 		} | ||||||
|  | 		return []byte(viper.GetString("security.reply_token_secret")), nil | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return claims, err | ||||||
|  | 	} | ||||||
|  | 	if !token.Valid { | ||||||
|  | 		return claims, fmt.Errorf("invalid token") | ||||||
|  | 	} | ||||||
|  | 	return claims, nil | ||||||
|  | } | ||||||
| @@ -19,3 +19,4 @@ max_participants = 20 | |||||||
|  |  | ||||||
| [security] | [security] | ||||||
| internal_public_key = "keys/internal_public_key.pem" | internal_public_key = "keys/internal_public_key.pem" | ||||||
|  | reply_token_secret = "QSu8iYYLDkGj10H3FtNI7OabpGl8N7Rg6rBagofQN4Uza3nIrXpVzDEfAiU1G2Yn" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user