Skip to content

Commit

Permalink
Merge pull request #1 from mc-cloud-town/Fix/member-UUID-api
Browse files Browse the repository at this point in the history
Fix/member UUID api
  • Loading branch information
a3510377 authored May 3, 2024
2 parents 027c1d1 + bfa8e94 commit 1e5fab7
Show file tree
Hide file tree
Showing 12 changed files with 418 additions and 31 deletions.
1 change: 1 addition & 0 deletions server/api/manage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package api
65 changes: 34 additions & 31 deletions server/api/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,26 @@ type OnlineUUIDStruct struct {
Name string `json:"name"`
}

func getOne(name string) (onlineUUID *OnlineUUIDStruct, err error) {
resp, err := http.Get("https://api.mojang.com/users/profiles/minecraft/" + name)
if err != nil {
return
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return
}

fmt.Println(string(body))
if err = json.Unmarshal(body, onlineUUID); err != nil {
return
}

return
}

func fetchOnlineUUIDs(names []string) map[string]string {
result := map[string]string{}
var wg sync.WaitGroup
Expand All @@ -128,39 +148,22 @@ func fetchOnlineUUIDs(names []string) map[string]string {
}

wg.Add(1)
getOne := func(name string) error {
resp, err := http.Get("https://api.mojang.com/users/profiles/minecraft/" + name)
if err != nil {
return err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

onlineUUID := OnlineUUIDStruct{}
if err := json.Unmarshal(body, &onlineUUID); err != nil {
return err
}

mu.Lock()
defer mu.Unlock()
if onlineUUID.ID != name {
result[name] = DEFAULT_UUID
}
result[onlineUUID.Name] = onlineUUID.ID

return nil
}
getOnes := func(name ...string) {
for _, n := range name {
if err := getOne(n); err != nil {
getOnes := func(names ...string) {
for _, name := range names {
if onlineUUID, err := getOne(name); err != nil {
if errors.Is(err, os.ErrNotExist) {
log.WithError(err).Error(fmt.Sprintf("Failed to find online UUID of %s", name))
continue
}
log.WithError(err).Error(fmt.Sprintf("Failed to get the UUID of %s", name))
} else {
mu.Lock()
result[n] = DEFAULT_UUID
if onlineUUID.ID != name {
result[name] = DEFAULT_UUID
log.Warn(fmt.Sprintf("Member ID error, get %s but real is %s", name, onlineUUID.ID))
}
result[onlineUUID.Name] = onlineUUID.ID
mu.Unlock()
log.WithError(err).Error(fmt.Sprintf("Failed to get the UUID of %s", n))
}
}
}
Expand Down
1 change: 1 addition & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Config struct {
Address string `yaml:"address"`
AllowOrigins []string `yaml:"allow_origins"`
MemberFile string `yaml:"member_file"`
JwtExpireDay int `yaml:"jwt_expire_day"`
} `yaml:"api"`
Cache struct {
Root string `yaml:"root"`
Expand Down
1 change: 1 addition & 0 deletions server/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ api:
allow_origins:
- '*'
member_file: data/data/members.yaml
jwt_expire_day: 7
cache:
root: ./data/api/cache
uuid_file: uuid.json
Expand Down
62 changes: 62 additions & 0 deletions server/config/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package config

import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
)

var (
jwtPublicKey *ecdsa.PublicKey
jwtPrivateKey *ecdsa.PrivateKey
)

func GetJwtPrivateKey() *ecdsa.PrivateKey {
return jwtPrivateKey
}

func GetJwtPublicKey() *ecdsa.PublicKey {
return jwtPublicKey
}

func LoadPrivateKey(path string) error {
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read private key file: %w", err)
}

block, _ := pem.Decode(content)
if block == nil {
return fmt.Errorf("failed to decode private key")
}

key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}

jwtPrivateKey = key
return nil
}

func LoadPublicKey(path string) error {
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read public key file: %w", err)
}

block, _ := pem.Decode(content)
if block == nil {
return fmt.Errorf("failed to decode public key")
}

key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse public key: %w", err)
}

jwtPublicKey = key.(*ecdsa.PublicKey)
return nil
}
2 changes: 2 additions & 0 deletions server/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ go 1.22.0
toolchain go1.22.2

require (
github.com/deckarep/golang-set/v2 v2.6.0
github.com/gin-gonic/gin v1.9.1
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/mattn/go-colorable v0.1.13
github.com/sirupsen/logrus v1.9.3
gopkg.in/yaml.v2 v2.4.0
Expand Down
4 changes: 4 additions & 0 deletions server/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.7.1 h1:s9SIppU/rk8enVvkzwiC2VK3UZ/0NNGsWfUKvV55rqs=
Expand All @@ -29,6 +31,8 @@ github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand Down
1 change: 1 addition & 0 deletions server/model/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package model
80 changes: 80 additions & 0 deletions server/pkg/auth/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package auth

import (
"errors"
"time"

"server/config"

"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)

const JWT_ISSUER = "ctec-api-server"

var ErrTokenValidation = errors.New("token validation failed")

type JwtCustomClaims struct {
jwt.RegisteredClaims
ID uint `json:"id"`
}

type JwtToken struct {
Token string `json:"token"`
Claims JwtCustomClaims `json:"claims"`
}

func New() *JwtToken {
return &JwtToken{}
}

// CreateUserToken creates a token for the user
func CreateUserToken(ID uint) (*JwtToken, error) {
expiresAt := time.Now().Add(time.Hour * 24 * time.Duration(config.Get().API.JwtExpireDay))
claims := JwtCustomClaims{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: JWT_ISSUER,
ExpiresAt: jwt.NewNumericDate(expiresAt),
},
ID: ID,
}

t := jwt.New(jwt.SigningMethodES256)
t.Claims = &claims
tokenString, err := t.SignedString(config.GetJwtPrivateKey())
if err != nil {
return nil, err
}

return &JwtToken{Token: tokenString, Claims: claims}, nil
}

// ParseToken parses the token and returns the claims
func (j *JwtToken) ParseToken(token string) (*JwtCustomClaims, error) {
t, err := jwt.ParseWithClaims(token, &JwtCustomClaims{}, func(t *jwt.Token) (any, error) {
return config.GetJwtPublicKey(), nil
})
if err != nil {
return nil, err
}

claims, ok := t.Claims.(*JwtCustomClaims)
if !ok || !t.Valid {
return nil, ErrTokenValidation
}

return claims, nil
}

// JwtClaims returns the claims from the token
func (j *JwtToken) JwtClaims(c *gin.Context) (*JwtCustomClaims, error) {
token := c.GetHeader("Authorization")
claims, err := j.ParseToken(token)
return claims, err
}

// JwtUserId returns the user ID from the token
func (j *JwtToken) JwtUserId(c *gin.Context) uint {
claims, _ := j.JwtClaims(c)
return claims.ID
}
16 changes: 16 additions & 0 deletions server/pkg/database/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package database

import (
"strconv"
"time"
)

type Model struct {
ID uint `gorm:"primarykey" json:"id,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}

func (m *Model) IDtoString() string {
return strconv.Itoa(int(m.ID))
}
Loading

0 comments on commit 1e5fab7

Please sign in to comment.