mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 22:26:38 +08:00
279 lines
8.2 KiB
Go
279 lines
8.2 KiB
Go
// 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,
|
|
}
|
|
}
|