refac: removed 7 direct and 12 indirect go modules for custom code instead
This commit is contained in:
1 parent
cfcf108488
commit
c68e9a7858
73 files changed
+2455
-428
No files matched your search
@@ -0,0 +1,170 @@
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"maps"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SessionLifetime = 24 * time.Hour
|
||||
|
||||
algorithmHS256 = "HS256"
|
||||
tokenType = "JWT"
|
||||
maxTokenBytes = 4096
|
||||
segmentCount = 3
|
||||
segmentSep = "."
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoToken = errors.New("no token found")
|
||||
ErrInvalid = errors.New("token is invalid")
|
||||
ErrExpired = errors.New("token is expired")
|
||||
ErrNotYetValid = errors.New("token is not yet valid")
|
||||
)
|
||||
|
||||
type Claims map[string]any
|
||||
|
||||
type header struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ,omitempty"`
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
secret []byte
|
||||
clock func() time.Time
|
||||
}
|
||||
|
||||
type Option func(*Auth)
|
||||
|
||||
func WithClock(clock func() time.Time) Option {
|
||||
return func(a *Auth) { a.clock = clock }
|
||||
}
|
||||
|
||||
func New(secret []byte, options ...Option) *Auth {
|
||||
auth := &Auth{secret: secret, clock: time.Now}
|
||||
for _, option := range options {
|
||||
option(auth)
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
func (a *Auth) Encode(claims map[string]any) (string, error) {
|
||||
payload := maps.Clone(claims)
|
||||
if payload == nil {
|
||||
payload = map[string]any{}
|
||||
}
|
||||
now := a.clock()
|
||||
if _, ok := payload["iat"]; !ok {
|
||||
payload["iat"] = now.Unix()
|
||||
}
|
||||
if _, ok := payload["exp"]; !ok {
|
||||
payload["exp"] = now.Add(SessionLifetime).Unix()
|
||||
}
|
||||
|
||||
headerSegment, err := encodeSegment(header{Algorithm: algorithmHS256, Type: tokenType})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
payloadSegment, err := encodeSegment(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signingInput := headerSegment + segmentSep + payloadSegment
|
||||
return signingInput + segmentSep + a.sign(signingInput), nil
|
||||
}
|
||||
|
||||
func (a *Auth) Decode(token string) (Claims, error) {
|
||||
if token == "" {
|
||||
return nil, ErrNoToken
|
||||
}
|
||||
if len(token) > maxTokenBytes {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
segments := strings.Split(token, segmentSep)
|
||||
if len(segments) != segmentCount {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
var tokenHeader header
|
||||
if err := decodeSegment(segments[0], &tokenHeader); err != nil || tokenHeader.Algorithm != algorithmHS256 {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
signingInput := segments[0] + segmentSep + segments[1]
|
||||
if !hmac.Equal([]byte(a.sign(signingInput)), []byte(segments[2])) {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
var claims Claims
|
||||
if err := decodeSegment(segments[1], &claims); err != nil || claims == nil {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
if err := a.validateTimes(claims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (a *Auth) validateTimes(claims Claims) error {
|
||||
now := a.clock()
|
||||
|
||||
expiry, present, err := numericDate(claims, "exp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if present && !now.Before(expiry) {
|
||||
return ErrExpired
|
||||
}
|
||||
|
||||
notBefore, present, err := numericDate(claims, "nbf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if present && now.Before(notBefore) {
|
||||
return ErrNotYetValid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func numericDate(claims Claims, name string) (time.Time, bool, error) {
|
||||
raw, present := claims[name]
|
||||
if !present {
|
||||
return time.Time{}, false, nil
|
||||
}
|
||||
seconds, ok := raw.(float64)
|
||||
if !ok {
|
||||
return time.Time{}, false, ErrInvalid
|
||||
}
|
||||
return time.Unix(int64(seconds), 0), true, nil
|
||||
}
|
||||
|
||||
func (a *Auth) sign(signingInput string) string {
|
||||
mac := hmac.New(sha256.New, a.secret)
|
||||
mac.Write([]byte(signingInput))
|
||||
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
func encodeSegment(value any) (string, error) {
|
||||
encoded, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(encoded), nil
|
||||
}
|
||||
|
||||
func decodeSegment(segment string, into any) error {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(segment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(decoded, into)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"RenewCMS/api/pkg/apperr"
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
cookieName = "jwt"
|
||||
bearerPrefix = "bearer "
|
||||
)
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
type verdict struct {
|
||||
claims Claims
|
||||
err error
|
||||
}
|
||||
|
||||
func Verifier(auth *Auth) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
claims, err := auth.Decode(tokenFromRequest(r))
|
||||
next.ServeHTTP(w, r.WithContext(NewContext(r.Context(), claims, err)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Authenticator(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := FromContext(r.Context()); err != nil {
|
||||
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context, claims Claims, err error) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, verdict{claims: claims, err: err})
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) (Claims, error) {
|
||||
stored, ok := ctx.Value(contextKey{}).(verdict)
|
||||
switch {
|
||||
case !ok:
|
||||
return nil, ErrNoToken
|
||||
case stored.err != nil:
|
||||
return nil, stored.err
|
||||
case stored.claims == nil:
|
||||
return nil, ErrNoToken
|
||||
}
|
||||
return stored.claims, nil
|
||||
}
|
||||
|
||||
func tokenFromRequest(r *http.Request) string {
|
||||
if token := tokenFromHeader(r); token != "" {
|
||||
return token
|
||||
}
|
||||
return tokenFromCookie(r)
|
||||
}
|
||||
|
||||
func tokenFromHeader(r *http.Request) string {
|
||||
header := r.Header.Get("Authorization")
|
||||
if len(header) <= len(bearerPrefix) || !strings.EqualFold(header[:len(bearerPrefix)], bearerPrefix) {
|
||||
return ""
|
||||
}
|
||||
return header[len(bearerPrefix):]
|
||||
}
|
||||
|
||||
func tokenFromCookie(r *http.Request) string {
|
||||
cookie, err := r.Cookie(cookieName)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return cookie.Value
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
package claims
|
||||
|
||||
import (
|
||||
"RenewCMS/api/pkg/authtoken"
|
||||
"math"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
)
|
||||
|
||||
func UserId(r *http.Request) (uint32, bool) {
|
||||
_, claims, err := jwtauth.FromContext(r.Context())
|
||||
claims, err := authtoken.FromContext(r.Context())
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
@@ -31,7 +30,7 @@ func UserId(r *http.Request) (uint32, bool) {
|
||||
}
|
||||
|
||||
func TokenVersion(r *http.Request) (uint32, bool) {
|
||||
_, claims, err := jwtauth.FromContext(r.Context())
|
||||
claims, err := authtoken.FromContext(r.Context())
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user