91 lines
2.4 KiB
Go
91 lines
2.4 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
type ctxKey string
|
|
|
|
const userIDKey ctxKey = "uid"
|
|
|
|
// JWT صدور و اعتبارسنجی توکن stateless.
|
|
type JWT struct {
|
|
secret []byte
|
|
ttl time.Duration
|
|
}
|
|
|
|
func NewJWT(secret string, ttl time.Duration) *JWT {
|
|
return &JWT{secret: []byte(secret), ttl: ttl}
|
|
}
|
|
|
|
// Issue توکن برای کاربر صادر میکند. ability مثل "user" یا "admin".
|
|
func (j *JWT) Issue(userID int64, ability string) (string, error) {
|
|
claims := jwt.MapClaims{
|
|
"sub": strconv.FormatInt(userID, 10),
|
|
"abl": ability,
|
|
"exp": time.Now().Add(j.ttl).Unix(),
|
|
"iat": time.Now().Unix(),
|
|
}
|
|
tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
return tok.SignedString(j.secret)
|
|
}
|
|
|
|
func (j *JWT) parse(tokenStr string) (int64, error) {
|
|
tok, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
|
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, errors.New("unexpected signing method")
|
|
}
|
|
return j.secret, nil
|
|
})
|
|
if err != nil || !tok.Valid {
|
|
return 0, errors.New("invalid token")
|
|
}
|
|
claims, ok := tok.Claims.(jwt.MapClaims)
|
|
if !ok {
|
|
return 0, errors.New("invalid claims")
|
|
}
|
|
sub, _ := claims["sub"].(string)
|
|
id, err := strconv.ParseInt(sub, 10, 64)
|
|
if err != nil {
|
|
return 0, errors.New("invalid sub")
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// Verify توکن را اعتبارسنجی کرده و شناسه کاربر را برمیگرداند (برای WebSocket).
|
|
func (j *JWT) Verify(token string) (int64, error) {
|
|
return j.parse(token)
|
|
}
|
|
|
|
// Middleware احراز هویت Bearer JWT.
|
|
func (j *JWT) Middleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
h := r.Header.Get("Authorization")
|
|
tokenStr := strings.TrimPrefix(h, "Bearer ")
|
|
if tokenStr == h || tokenStr == "" {
|
|
http.Error(w, `{"message":"unauthorized"}`, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
id, err := j.parse(tokenStr)
|
|
if err != nil {
|
|
http.Error(w, `{"message":"unauthorized"}`, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
ctx := context.WithValue(r.Context(), userIDKey, id)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
// UserID شناسه کاربر را از context (پس از Middleware) برمیگرداند.
|
|
func UserID(ctx context.Context) (int64, bool) {
|
|
id, ok := ctx.Value(userIDKey).(int64)
|
|
return id, ok
|
|
}
|