mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 09:06:36 +08:00
refactor(auth): modularize auth plugin with physical subpackages and decoupled services
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package service implements domain business services and orchestration for the auth plugin.
|
||||
package service
|
||||
|
||||
import (
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/plugins/domain/auth/consts"
|
||||
"Wavelet/plugins/domain/auth/model/dto"
|
||||
"Wavelet/plugins/domain/auth/pow"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CaptchaManager orchestrates challenge generation and solution validation.
|
||||
type CaptchaManager struct {
|
||||
secret []byte
|
||||
store pow.Store
|
||||
settingsMgr *CapSettingsManager
|
||||
}
|
||||
|
||||
// NewCaptchaManager creates a new CAPTCHA Manager.
|
||||
func NewCaptchaManager(secret []byte, store pow.Store, settingsMgr *CapSettingsManager) *CaptchaManager {
|
||||
return &CaptchaManager{
|
||||
secret: secret,
|
||||
store: store,
|
||||
settingsMgr: settingsMgr,
|
||||
}
|
||||
}
|
||||
|
||||
// SetSecret updates the shared secret used for PoW generation and validation.
|
||||
func (m *CaptchaManager) SetSecret(secret []byte) {
|
||||
m.secret = secret
|
||||
}
|
||||
|
||||
// Generate creates a challenge response.
|
||||
func (m *CaptchaManager) Generate(ctx context.Context, scope string) (*pow.ChallengeResponse, error) {
|
||||
settings, err := m.settingsMgr.Current(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
challengeConfig := pow.ChallengeConfig{
|
||||
Count: settings.ChallengeCount,
|
||||
Size: settings.ChallengeSize,
|
||||
Difficulty: settings.ChallengeDifficulty,
|
||||
Expires: settings.ChallengeTTL,
|
||||
}
|
||||
return pow.GenerateChallenge(m.secret, challengeConfig, scope)
|
||||
}
|
||||
|
||||
// Redeem verifies PoW solutions and returns a one-time redeem token.
|
||||
func (m *CaptchaManager) Redeem(ctx context.Context, token string, solutions []int, scope string) (*dto.RedeemResponse, error) {
|
||||
sigHex := pow.JwtSigHex(token)
|
||||
if sigHex == "" {
|
||||
return &dto.RedeemResponse{Success: false, Error: consts.RedeemErrInvalidToken}, nil
|
||||
}
|
||||
|
||||
nonceKey := "cap:nonce:" + sigHex
|
||||
|
||||
payload, err := pow.VerifyChallengeSolutions(token, solutions, m.secret, scope)
|
||||
if err != nil {
|
||||
return &dto.RedeemResponse{Success: false, Error: err.Error()}, nil //nolint:nilerr // validation errors returned as response
|
||||
}
|
||||
|
||||
now := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
nonceTTL := time.Duration(payload.Expires-now) * time.Millisecond
|
||||
if nonceTTL < time.Second {
|
||||
nonceTTL = time.Second
|
||||
}
|
||||
|
||||
set, err := m.store.SetNX(ctx, nonceKey, "1", nonceTTL)
|
||||
if err != nil {
|
||||
return &dto.RedeemResponse{Success: false, Error: consts.RedeemErrNonceStoreFailed}, err
|
||||
}
|
||||
if !set {
|
||||
return &dto.RedeemResponse{Success: false, Error: consts.RedeemErrAlreadyRedeemed}, nil
|
||||
}
|
||||
|
||||
settings, err := m.settingsMgr.Current(ctx)
|
||||
if err != nil {
|
||||
return &dto.RedeemResponse{Success: false, Error: consts.RedeemErrSettingsLoad}, err
|
||||
}
|
||||
|
||||
id := pow.RandomHex(consts.RedeemTokenIDLength)
|
||||
verToken := pow.RandomHex(consts.RedeemVerTokenLength)
|
||||
verHashBytes := sha256.Sum256([]byte(verToken))
|
||||
verHashHex := hex.EncodeToString(verHashBytes[:])
|
||||
|
||||
tokenKey := "cap:token:" + id + ":" + verHashHex
|
||||
tokenExpires := time.Now().Add(settings.TokenTTL)
|
||||
storeVal := strconv.FormatInt(tokenExpires.UnixNano(), 10) + "|" + scope
|
||||
|
||||
if err := m.store.Set(ctx, tokenKey, storeVal, settings.TokenTTL); err != nil {
|
||||
return &dto.RedeemResponse{Success: false, Error: consts.RedeemErrTokenStoreFailed}, err
|
||||
}
|
||||
|
||||
return &dto.RedeemResponse{
|
||||
Success: true,
|
||||
Token: id + ":" + verToken,
|
||||
Expires: tokenExpires.UnixNano() / int64(time.Millisecond),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyToken validates and consumes the redeem token (single-use).
|
||||
func (m *CaptchaManager) VerifyToken(ctx context.Context, token, expectedScope string) (bool, error) {
|
||||
if token == "" {
|
||||
return false, nil
|
||||
}
|
||||
parts := strings.Split(token, ":")
|
||||
if len(parts) != consts.TokenPartsCount {
|
||||
return false, nil
|
||||
}
|
||||
id := parts[0]
|
||||
verToken := parts[1]
|
||||
|
||||
verHashBytes := sha256.Sum256([]byte(verToken))
|
||||
verHashHex := hex.EncodeToString(verHashBytes[:])
|
||||
|
||||
tokenKey := "cap:token:" + id + ":" + verHashHex
|
||||
|
||||
if m.store == nil {
|
||||
return false, nil
|
||||
}
|
||||
val, exists, err := m.store.GetAndDelete(ctx, tokenKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
valParts := strings.Split(val, "|")
|
||||
if len(valParts) != consts.ValuePartsCount {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
expNano, err := strconv.ParseInt(valParts[0], 10, 64)
|
||||
if err != nil {
|
||||
return false, nil //nolint:nilerr // invalid format is failure
|
||||
}
|
||||
tokenScope := valParts[1]
|
||||
|
||||
if expectedScope != "" && tokenScope != expectedScope {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if time.Now().UnixNano() > expNano {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CaptchaServiceImpl implements contracts.CaptchaService.
|
||||
type CaptchaServiceImpl struct {
|
||||
manager *CaptchaManager
|
||||
verifyMiddleware func(scope string) any
|
||||
challengeHandler any
|
||||
redeemHandler any
|
||||
}
|
||||
|
||||
// NewCaptchaService creates a new CaptchaServiceImpl.
|
||||
func NewCaptchaService(mgr *CaptchaManager, verifyMiddleware func(scope string) any, challengeHandler any, redeemHandler any) contracts.CaptchaService {
|
||||
return &CaptchaServiceImpl{
|
||||
manager: mgr,
|
||||
verifyMiddleware: verifyMiddleware,
|
||||
challengeHandler: challengeHandler,
|
||||
redeemHandler: redeemHandler,
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyMiddleware returns the captcha verification middleware.
|
||||
func (s *CaptchaServiceImpl) VerifyMiddleware(scope string) any {
|
||||
if s.verifyMiddleware != nil {
|
||||
return s.verifyMiddleware(scope)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChallengeHandler returns the challenge HTTP handler.
|
||||
func (s *CaptchaServiceImpl) ChallengeHandler() any {
|
||||
return s.challengeHandler
|
||||
}
|
||||
|
||||
// RedeemHandler returns the redeem HTTP handler.
|
||||
func (s *CaptchaServiceImpl) RedeemHandler() any {
|
||||
return s.redeemHandler
|
||||
}
|
||||
Reference in New Issue
Block a user