mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 00:56:37 +08:00
refactor(auth): modularize auth plugin with physical subpackages and decoupled services
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
// 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/dao"
|
||||
"Wavelet/plugins/domain/auth/model/entity"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// HashToken computes SHA-256 hex digest of access token.
|
||||
func HashToken(token string) string {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(token))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// UserIDExtractor extracts user ID from a request context.
|
||||
type UserIDExtractor func(ctx context.Context) (uint64, bool)
|
||||
|
||||
// AuthServiceImpl implements contracts.AuthService.
|
||||
type AuthServiceImpl struct {
|
||||
dao *dao.DAO
|
||||
requireAuthMiddleware any
|
||||
requireAdminMiddleware any
|
||||
disallowTokenMiddleware any
|
||||
userIDExtractor UserIDExtractor
|
||||
}
|
||||
|
||||
// NewAuthService creates a new AuthServiceImpl.
|
||||
func NewAuthService(
|
||||
d *dao.DAO,
|
||||
requireAuth any,
|
||||
requireAdmin any,
|
||||
disallowToken any,
|
||||
extractor UserIDExtractor,
|
||||
) *AuthServiceImpl {
|
||||
return &AuthServiceImpl{
|
||||
dao: d,
|
||||
requireAuthMiddleware: requireAuth,
|
||||
requireAdminMiddleware: requireAdmin,
|
||||
disallowTokenMiddleware: disallowToken,
|
||||
userIDExtractor: extractor,
|
||||
}
|
||||
}
|
||||
|
||||
// SetMiddlewareHandlers wires middleware handlers into AuthService after controller initialization.
|
||||
func (s *AuthServiceImpl) SetMiddlewareHandlers(requireAuth, requireAdmin, disallowToken any, extractor UserIDExtractor) {
|
||||
s.requireAuthMiddleware = requireAuth
|
||||
s.requireAdminMiddleware = requireAdmin
|
||||
s.disallowTokenMiddleware = disallowToken
|
||||
s.userIDExtractor = extractor
|
||||
}
|
||||
|
||||
// RequireAuthMiddleware returns the authentication check middleware.
|
||||
func (s *AuthServiceImpl) RequireAuthMiddleware() any {
|
||||
return s.requireAuthMiddleware
|
||||
}
|
||||
|
||||
// RequireAdminMiddleware returns the admin authorization middleware.
|
||||
func (s *AuthServiceImpl) RequireAdminMiddleware() any {
|
||||
return s.requireAdminMiddleware
|
||||
}
|
||||
|
||||
// DisallowTokenAuthMiddleware returns the token rejection middleware.
|
||||
func (s *AuthServiceImpl) DisallowTokenAuthMiddleware() any {
|
||||
return s.disallowTokenMiddleware
|
||||
}
|
||||
|
||||
// GetCurrentUser 从 context 中读取登录用户。
|
||||
func (s *AuthServiceImpl) GetCurrentUser(ctx context.Context) (*contracts.UserDTO, error) {
|
||||
if v := ctx.Value(contracts.AuthUserObjKey); v != nil {
|
||||
if u, ok := v.(*contracts.UserDTO); ok && u != nil {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New(consts.ErrUserNotInContext)
|
||||
}
|
||||
|
||||
// GetCurrentUserID 从请求登录态中读取用户 ID。
|
||||
func (s *AuthServiceImpl) GetCurrentUserID(ctx context.Context) (uint64, error) {
|
||||
if s.userIDExtractor != nil {
|
||||
if userID, ok := s.userIDExtractor(ctx); ok {
|
||||
return userID, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New(consts.ErrUserNotInContext)
|
||||
}
|
||||
|
||||
// VerifyToken 验证访问令牌并返回对应的用户。
|
||||
func (s *AuthServiceImpl) VerifyToken(ctx context.Context, token string) (*contracts.UserDTO, error) {
|
||||
if token == "" {
|
||||
return nil, errors.New(consts.ErrEmptyToken)
|
||||
}
|
||||
|
||||
tokenHash := HashToken(token)
|
||||
tokenRecord, err := s.dao.GetCachedToken(ctx, tokenHash)
|
||||
if err != nil {
|
||||
tokenRecord, err = s.dao.GetAccessTokenByHash(ctx, tokenHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.dao.SetCachedToken(ctx, tokenHash, tokenRecord)
|
||||
}
|
||||
|
||||
user, err := s.dao.GetCachedUser(ctx, tokenRecord.UserID)
|
||||
if err != nil || user == nil || !user.IsActive {
|
||||
user, err = s.dao.GetActiveUserByID(ctx, tokenRecord.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.dao.SetCachedUser(ctx, tokenRecord.UserID, user)
|
||||
}
|
||||
|
||||
if user.Username == consts.SystemUsername {
|
||||
return nil, errors.New(consts.ErrSystemUserTokenNotAllowed)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// CreateSession establishes an authenticated session.
|
||||
func (s *AuthServiceImpl) CreateSession(_ context.Context, _ uint64, _ map[string]any) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// RevokeUserSessions revokes active sessions and cached tokens for a user.
|
||||
func (s *AuthServiceImpl) RevokeUserSessions(ctx context.Context, userID uint64) error {
|
||||
s.dao.InvalidateCachedUser(ctx, userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RevokeToken invalidates a cached token by its hash.
|
||||
func (s *AuthServiceImpl) RevokeToken(ctx context.Context, tokenHash string) error {
|
||||
s.dao.InvalidateCachedToken(ctx, tokenHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InvalidateCachedUser invalidates cached user profile.
|
||||
func (s *AuthServiceImpl) InvalidateCachedUser(ctx context.Context, userID uint64) {
|
||||
s.dao.InvalidateCachedUser(ctx, userID)
|
||||
}
|
||||
|
||||
// InvalidateCachedToken invalidates cached access token.
|
||||
func (s *AuthServiceImpl) InvalidateCachedToken(ctx context.Context, tokenHash string) {
|
||||
s.dao.InvalidateCachedToken(ctx, tokenHash)
|
||||
}
|
||||
|
||||
// ListAuthSources lists all configured authentication sources.
|
||||
func (s *AuthServiceImpl) ListAuthSources(ctx context.Context) ([]contracts.AuthSourceViewDTO, error) {
|
||||
sources, err := s.dao.ListAllAuthSources(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
views := make([]contracts.AuthSourceViewDTO, len(sources))
|
||||
for i := range sources {
|
||||
views[i] = contracts.AuthSourceViewDTO{
|
||||
ID: sources[i].ID,
|
||||
Name: sources[i].Name,
|
||||
Type: sources[i].Type,
|
||||
DisplayName: sources[i].DisplayName,
|
||||
IsActive: sources[i].IsActive,
|
||||
IconURL: sources[i].IconURL,
|
||||
ClientSecretConfigured: sources[i].ClientSecret != "",
|
||||
}
|
||||
}
|
||||
return views, nil
|
||||
}
|
||||
|
||||
// CreateAuthSource creates a new authentication source.
|
||||
func (s *AuthServiceImpl) CreateAuthSource(ctx context.Context, source contracts.AuthSourceDTO) (*contracts.AuthSourceDTO, error) {
|
||||
model := entity.AuthSource{
|
||||
ID: source.ID,
|
||||
Name: source.Name,
|
||||
Type: source.Type,
|
||||
DisplayName: source.DisplayName,
|
||||
ClientID: source.ClientID,
|
||||
ClientSecret: source.ClientSecret,
|
||||
OpenIDDiscoveryURL: source.OpenIDDiscoveryURL,
|
||||
Scopes: source.Scopes,
|
||||
IconURL: source.IconURL,
|
||||
IsActive: source.IsActive,
|
||||
}
|
||||
|
||||
if err := model.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.dao.CreateAuthSource(ctx, &model); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
model.Sanitize()
|
||||
return toAuthSourceDTO(&model), nil
|
||||
}
|
||||
|
||||
// UpdateAuthSource updates an existing authentication source.
|
||||
func (s *AuthServiceImpl) UpdateAuthSource(ctx context.Context, id uint64, source contracts.AuthSourceDTO) (*contracts.AuthSourceDTO, error) {
|
||||
existing, err := s.dao.GetAuthSourceByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing.DisplayName = source.DisplayName
|
||||
existing.ClientID = source.ClientID
|
||||
if source.ClientSecret != "" {
|
||||
existing.ClientSecret = source.ClientSecret
|
||||
}
|
||||
existing.OpenIDDiscoveryURL = source.OpenIDDiscoveryURL
|
||||
existing.Scopes = source.Scopes
|
||||
existing.IconURL = source.IconURL
|
||||
|
||||
if err := existing.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.dao.SaveAuthSource(ctx, existing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing.Sanitize()
|
||||
return toAuthSourceDTO(existing), nil
|
||||
}
|
||||
|
||||
// DeleteAuthSource deletes an authentication source.
|
||||
func (s *AuthServiceImpl) DeleteAuthSource(ctx context.Context, id uint64) error {
|
||||
existing, err := s.dao.GetAuthSourceByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.dao.DeleteAuthSource(ctx, existing)
|
||||
}
|
||||
|
||||
// ToggleAuthSource toggles active status of an authentication source.
|
||||
func (s *AuthServiceImpl) ToggleAuthSource(ctx context.Context, id uint64) (*contracts.AuthSourceDTO, error) {
|
||||
existing, err := s.dao.GetAuthSourceByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing.IsActive = !existing.IsActive
|
||||
if err := s.dao.SaveAuthSource(ctx, existing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing.Sanitize()
|
||||
return toAuthSourceDTO(existing), nil
|
||||
}
|
||||
|
||||
func toAuthSourceDTO(s *entity.AuthSource) *contracts.AuthSourceDTO {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return &contracts.AuthSourceDTO{
|
||||
ID: s.ID,
|
||||
Name: s.Name,
|
||||
Type: s.Type,
|
||||
DisplayName: s.DisplayName,
|
||||
ClientID: s.ClientID,
|
||||
ClientSecret: s.ClientSecret,
|
||||
OpenIDDiscoveryURL: s.OpenIDDiscoveryURL,
|
||||
Scopes: s.Scopes,
|
||||
IconURL: s.IconURL,
|
||||
IsActive: s.IsActive,
|
||||
CreatedAt: s.CreatedAt,
|
||||
UpdatedAt: s.UpdatedAt,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user