forked from nixcn/nixcn-cms
@@ -28,5 +28,5 @@ SECRETS_TURNSTILE_SECRET=0x4AAAAAACI5pgVONOZ0rzyAYsdUcoOBF8w
|
|||||||
|
|
||||||
TTL_MAGIC_LINK_TTL=10m
|
TTL_MAGIC_LINK_TTL=10m
|
||||||
TTL_ACCESS_TTL=15s
|
TTL_ACCESS_TTL=15s
|
||||||
TTL_REFRESH_TTL=7d
|
TTL_REFRESH_TTL=168h
|
||||||
TTL_CHECKIN_COSE_TTL=10m
|
TTL_CHECKIN_COSE_TTL=10m
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ type secrets struct {
|
|||||||
|
|
||||||
type ttl struct {
|
type ttl struct {
|
||||||
MagicLinkTTL string `yaml:"magic_link_ttl"`
|
MagicLinkTTL string `yaml:"magic_link_ttl"`
|
||||||
JwtTTL string `yaml:"jwt_ttl"`
|
AccessTTL string `yaml:"access_ttl"`
|
||||||
RefreshTTL string `yaml:"refresh_ttl"`
|
RefreshTTL string `yaml:"refresh_ttl"`
|
||||||
CheckinCodeTTL string `yaml:"checkin_code_ttl"`
|
CheckinCodeTTL string `yaml:"checkin_code_ttl"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"nixcn-cms/data"
|
"nixcn-cms/data"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
UserID uuid.UUID
|
|
||||||
Application string
|
Application string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,9 +23,9 @@ type JwtClaims struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate jwt clames
|
// Generate jwt clames
|
||||||
func (self *Token) NewClaims() JwtClaims {
|
func (self *Token) NewClaims(userId uuid.UUID) JwtClaims {
|
||||||
return JwtClaims{
|
return JwtClaims{
|
||||||
UserID: self.UserID,
|
UserID: userId,
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(viper.GetDuration("ttl.access_ttl"))),
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(viper.GetDuration("ttl.access_ttl"))),
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
@@ -38,8 +35,8 @@ func (self *Token) NewClaims() JwtClaims {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate access token
|
// Generate access token
|
||||||
func (self *Token) GenerateAccessToken() (string, error) {
|
func (self *Token) GenerateAccessToken(userId uuid.UUID) (string, error) {
|
||||||
claims := self.NewClaims()
|
claims := self.NewClaims(userId)
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
secret := viper.GetString("secrets.jwt_secret")
|
secret := viper.GetString("secrets.jwt_secret")
|
||||||
signedToken, err := token.SignedString([]byte(secret))
|
signedToken, err := token.SignedString([]byte(secret))
|
||||||
@@ -59,9 +56,9 @@ func (self *Token) GenerateRefreshToken() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Issue both access and refresh token
|
// Issue both access and refresh token
|
||||||
func (self *Token) IssueTokens() (string, string, error) {
|
func (self *Token) IssueTokens(userId uuid.UUID) (string, string, error) {
|
||||||
// Gen atk
|
// Gen atk
|
||||||
access, err := self.GenerateAccessToken()
|
access, err := self.GenerateAccessToken(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
@@ -80,14 +77,14 @@ func (self *Token) IssueTokens() (string, string, error) {
|
|||||||
if err := data.Redis.Set(
|
if err := data.Redis.Set(
|
||||||
ctx,
|
ctx,
|
||||||
"refresh:"+refresh,
|
"refresh:"+refresh,
|
||||||
self.UserID.String(),
|
userId.String(),
|
||||||
ttl,
|
ttl,
|
||||||
).Err(); err != nil {
|
).Err(); err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// user -> refresh tokens
|
// user -> refresh tokens
|
||||||
userSetKey := "user:" + self.UserID.String() + ":refresh_tokens"
|
userSetKey := "user:" + userId.String() + ":refresh_tokens"
|
||||||
|
|
||||||
if err := data.Redis.SAdd(
|
if err := data.Redis.SAdd(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -109,49 +106,52 @@ func (self *Token) RefreshAccessToken(refreshToken string) (string, error) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
key := "refresh:" + refreshToken
|
key := "refresh:" + refreshToken
|
||||||
|
|
||||||
userIDStr, err := data.Redis.Get(ctx, key).Result()
|
userIdStr, err := data.Redis.Get(ctx, key).Result()
|
||||||
if err != nil {
|
|
||||||
if err == redis.Nil {
|
|
||||||
return "", errors.New("invalid refresh token")
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, err := uuid.Parse(userIDStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
self.UserID = userID
|
userId, err := uuid.Parse(userIdStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
// Generate access token
|
// Generate access token
|
||||||
return self.GenerateAccessToken()
|
return self.GenerateAccessToken(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Token) RenewRefreshToken(refreshToken string) (string, error) {
|
func (self *Token) RenewRefreshToken(refreshToken string) (string, error) {
|
||||||
err := self.RevokeRefreshToken(refreshToken)
|
ctx := context.Background()
|
||||||
|
ttl := viper.GetDuration("ttl.refresh_ttl")
|
||||||
|
|
||||||
|
key := "refresh:" + refreshToken
|
||||||
|
userIdStr, err := data.Redis.Get(ctx, key).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh, err := self.GenerateRefreshToken()
|
refresh, err := self.GenerateRefreshToken()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
// Store to redis
|
err = self.RevokeRefreshToken(refreshToken)
|
||||||
ctx := context.Background()
|
if err != nil {
|
||||||
ttl := viper.GetDuration("ttl.refresh_ttl")
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
// refresh -> user
|
// refresh -> user
|
||||||
if err := data.Redis.Set(
|
if err := data.Redis.Set(
|
||||||
ctx,
|
ctx,
|
||||||
"refresh:"+refresh,
|
"refresh:"+refresh,
|
||||||
self.UserID.String(),
|
userIdStr,
|
||||||
ttl,
|
ttl,
|
||||||
).Err(); err != nil {
|
).Err(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// user -> refresh tokens
|
// user -> refresh tokens
|
||||||
userSetKey := "user:" + self.UserID.String() + ":refresh_tokens"
|
userSetKey := "user:" + userIdStr + ":refresh_tokens"
|
||||||
|
|
||||||
if err := data.Redis.SAdd(
|
if err := data.Redis.SAdd(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -99,10 +99,9 @@ func VerifyMagicLink(c *gin.Context) {
|
|||||||
|
|
||||||
// Generate jwt
|
// Generate jwt
|
||||||
JwtTool := cryptography.Token{
|
JwtTool := cryptography.Token{
|
||||||
UserID: user.UserId,
|
|
||||||
Application: viper.GetString("server.application"),
|
Application: viper.GetString("server.application"),
|
||||||
}
|
}
|
||||||
accessToken, refreshToken, err := JwtTool.IssueTokens()
|
accessToken, refreshToken, err := JwtTool.IssueTokens(user.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{
|
c.JSON(500, gin.H{
|
||||||
"status": "error generating tokens",
|
"status": "error generating tokens",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func Refresh(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(400, gin.H{"error": "invalid request"})
|
c.JSON(400, gin.H{"status": "invalid request"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,18 +23,13 @@ func Refresh(c *gin.Context) {
|
|||||||
|
|
||||||
access, err := JwtTool.RefreshAccessToken(req.RefreshToken)
|
access, err := JwtTool.RefreshAccessToken(req.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(401, gin.H{"error": "invalid refresh token"})
|
c.JSON(401, gin.H{"status": "invalid refresh token"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = JwtTool.RevokeRefreshToken(req.RefreshToken)
|
refresh, err := JwtTool.RenewRefreshToken(req.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{"status": "cannot revoke refresh token"})
|
c.JSON(500, gin.H{"statis": "error renew refresh token"})
|
||||||
}
|
|
||||||
|
|
||||||
refresh, err := JwtTool.GenerateRefreshToken()
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(401, gin.H{"status": "cannot generate new refresh token"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
package event
|
package event
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"nixcn-cms/middleware"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
func Handler(r *gin.RouterGroup) {
|
func Handler(r *gin.RouterGroup) {
|
||||||
|
r.Use(middleware.JWTAuth())
|
||||||
r.GET("/info", Info)
|
r.GET("/info", Info)
|
||||||
|
r.GET("/query", Query)
|
||||||
}
|
}
|
||||||
|
|||||||
36
service/event/query.go
Normal file
36
service/event/query.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/data"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Query(c *gin.Context) {
|
||||||
|
userId, ok := c.Get("user_id")
|
||||||
|
if !ok {
|
||||||
|
c.JSON(400, gin.H{"status": "could not found user_id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eventId, ok := c.GetQuery("event_id")
|
||||||
|
if !ok {
|
||||||
|
c.JSON(400, gin.H{"status": "could not found event_id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := new(data.User)
|
||||||
|
err := data.GetByUserId(userId.(uuid.UUID))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(404, gin.H{"status": "cannot found user"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if data.Checkin[eventId] == nil {
|
||||||
|
c.JSON(404, gin.H{"status": "cannot found user checked in"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"checkin_time": data.Checkin[eventId],
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func Handler(r *gin.RouterGroup) {
|
func Handler(r *gin.RouterGroup) {
|
||||||
r.Use(middleware.JWTAuth())
|
r.Use(middleware.JWTAuth())
|
||||||
r.GET("/info", Info)
|
r.GET("/info", Info)
|
||||||
r.POST("/checkin", Checkin)
|
r.GET("/checkin", Checkin)
|
||||||
r.POST("/checkin/submit", CheckinSubmit)
|
r.POST("/checkin/submit", CheckinSubmit)
|
||||||
r.PATCH("/update", Update)
|
r.PATCH("/update", Update)
|
||||||
r.GET("/list", List)
|
r.GET("/list", List)
|
||||||
|
|||||||
Reference in New Issue
Block a user