Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
281
native/desktop/maplefile/internal/service/auth/service.go
Normal file
281
native/desktop/maplefile/internal/service/auth/service.go
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/maplefile/client"
|
||||
domainSession "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/session"
|
||||
"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/usecase/session"
|
||||
"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/utils"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
apiClient *client.Client
|
||||
createSessionUC *session.CreateUseCase
|
||||
getSessionUC *session.GetByIdUseCase
|
||||
deleteSessionUC *session.DeleteUseCase
|
||||
saveSessionUC *session.SaveUseCase
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// ProvideService creates the auth service for Wire
|
||||
func ProvideService(
|
||||
apiClient *client.Client,
|
||||
createSessionUC *session.CreateUseCase,
|
||||
getSessionUC *session.GetByIdUseCase,
|
||||
deleteSessionUC *session.DeleteUseCase,
|
||||
saveSessionUC *session.SaveUseCase,
|
||||
logger *zap.Logger,
|
||||
) *Service {
|
||||
svc := &Service{
|
||||
apiClient: apiClient,
|
||||
createSessionUC: createSessionUC,
|
||||
getSessionUC: getSessionUC,
|
||||
deleteSessionUC: deleteSessionUC,
|
||||
saveSessionUC: saveSessionUC,
|
||||
logger: logger.Named("auth-service"),
|
||||
}
|
||||
|
||||
// Set up token refresh callback to persist new tokens to session
|
||||
apiClient.OnTokenRefresh(func(accessToken, refreshToken, accessTokenExpiryDate string) {
|
||||
svc.handleTokenRefresh(accessToken, refreshToken, accessTokenExpiryDate)
|
||||
})
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// handleTokenRefresh is called when the API client automatically refreshes the access token
|
||||
func (s *Service) handleTokenRefresh(accessToken, refreshToken, accessTokenExpiryDate string) {
|
||||
// Get the current session
|
||||
existingSession, err := s.getSessionUC.Execute()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get session during token refresh callback", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if existingSession == nil {
|
||||
s.logger.Warn("No session found during token refresh callback")
|
||||
return
|
||||
}
|
||||
|
||||
// Update the session with new tokens
|
||||
existingSession.AccessToken = accessToken
|
||||
existingSession.RefreshToken = refreshToken
|
||||
|
||||
// Parse the actual expiry date from the response instead of using hardcoded value
|
||||
if accessTokenExpiryDate != "" {
|
||||
expiryTime, parseErr := time.Parse(time.RFC3339, accessTokenExpiryDate)
|
||||
if parseErr != nil {
|
||||
s.logger.Warn("Failed to parse access token expiry date, using default 15m",
|
||||
zap.String("expiry_date", accessTokenExpiryDate),
|
||||
zap.Error(parseErr))
|
||||
existingSession.ExpiresAt = time.Now().Add(15 * time.Minute)
|
||||
} else {
|
||||
existingSession.ExpiresAt = expiryTime
|
||||
s.logger.Debug("Using actual token expiry from response",
|
||||
zap.Time("expiry_time", expiryTime))
|
||||
}
|
||||
} else {
|
||||
s.logger.Warn("No access token expiry date in refresh response, using default 15m")
|
||||
existingSession.ExpiresAt = time.Now().Add(15 * time.Minute)
|
||||
}
|
||||
|
||||
// Save updated session
|
||||
if err := s.saveSessionUC.Execute(existingSession); err != nil {
|
||||
s.logger.Error("Failed to save session after token refresh", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("Session updated with refreshed tokens", zap.String("email", utils.MaskEmail(existingSession.Email)))
|
||||
}
|
||||
|
||||
// RequestOTT requests a one-time token for login
|
||||
func (s *Service) RequestOTT(ctx context.Context, email string) error {
|
||||
_, err := s.apiClient.RequestOTT(ctx, email)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to request OTT", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
s.logger.Info("OTT requested successfully", zap.String("email", utils.MaskEmail(email)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyOTT verifies the one-time token and returns the encrypted challenge
|
||||
func (s *Service) VerifyOTT(ctx context.Context, email, ott string) (*client.VerifyOTTResponse, error) {
|
||||
resp, err := s.apiClient.VerifyOTT(ctx, email, ott)
|
||||
if err != nil {
|
||||
s.logger.Error("OTT verification failed", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
s.logger.Info("OTT verified successfully", zap.String("email", utils.MaskEmail(email)))
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CompleteLogin completes the login process with OTT and challenge
|
||||
func (s *Service) CompleteLogin(ctx context.Context, input *client.CompleteLoginInput) (*client.LoginResponse, error) {
|
||||
// Complete login via API
|
||||
resp, err := s.apiClient.CompleteLogin(ctx, input)
|
||||
if err != nil {
|
||||
s.logger.Error("Login failed", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse expiration time from response
|
||||
var expiresIn time.Duration
|
||||
if resp.AccessTokenExpiryDate != "" {
|
||||
expiryTime, parseErr := time.Parse(time.RFC3339, resp.AccessTokenExpiryDate)
|
||||
if parseErr != nil {
|
||||
s.logger.Warn("Failed to parse access token expiry date, using default 15m",
|
||||
zap.String("expiry_date", resp.AccessTokenExpiryDate),
|
||||
zap.Error(parseErr))
|
||||
expiresIn = 15 * time.Minute // Default to 15 minutes (backend default)
|
||||
} else {
|
||||
expiresIn = time.Until(expiryTime)
|
||||
s.logger.Info("Parsed access token expiry",
|
||||
zap.Time("expiry_time", expiryTime),
|
||||
zap.Duration("expires_in", expiresIn))
|
||||
}
|
||||
} else {
|
||||
s.logger.Warn("No access token expiry date in response, using default 15m")
|
||||
expiresIn = 15 * time.Minute // Default to 15 minutes (backend default)
|
||||
}
|
||||
|
||||
// Use email as userID for now (can be improved later)
|
||||
userID := input.Email
|
||||
|
||||
// Save session locally via use case
|
||||
err = s.createSessionUC.Execute(
|
||||
userID,
|
||||
input.Email,
|
||||
resp.AccessToken,
|
||||
resp.RefreshToken,
|
||||
expiresIn,
|
||||
)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save session", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("User logged in successfully", zap.String("email", utils.MaskEmail(input.Email)))
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Logout removes the local session
|
||||
func (s *Service) Logout(ctx context.Context) error {
|
||||
// Delete local session
|
||||
err := s.deleteSessionUC.Execute()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to delete session", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("User logged out successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCurrentSession retrieves the current user session
|
||||
func (s *Service) GetCurrentSession(ctx context.Context) (*domainSession.Session, error) {
|
||||
sess, err := s.getSessionUC.Execute()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get session", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
// UpdateSession updates the current session
|
||||
func (s *Service) UpdateSession(ctx context.Context, sess *domainSession.Session) error {
|
||||
return s.saveSessionUC.Execute(sess)
|
||||
}
|
||||
|
||||
// IsLoggedIn checks if a user is currently logged in
|
||||
func (s *Service) IsLoggedIn(ctx context.Context) (bool, error) {
|
||||
sess, err := s.getSessionUC.Execute()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if sess == nil {
|
||||
return false, nil
|
||||
}
|
||||
return sess.IsValid(), nil
|
||||
}
|
||||
|
||||
// RestoreSession restores tokens to the API client from a persisted session
|
||||
// This is used on app startup to resume a session from a previous run
|
||||
func (s *Service) RestoreSession(ctx context.Context, sess *domainSession.Session) error {
|
||||
if sess == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore tokens to API client
|
||||
s.apiClient.SetTokens(sess.AccessToken, sess.RefreshToken)
|
||||
s.logger.Info("Session restored to API client",
|
||||
zap.String("user_id", sess.UserID),
|
||||
zap.String("email", utils.MaskEmail(sess.Email)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register creates a new user account
|
||||
func (s *Service) Register(ctx context.Context, input *client.RegisterInput) error {
|
||||
_, err := s.apiClient.Register(ctx, input)
|
||||
if err != nil {
|
||||
s.logger.Error("Registration failed", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
s.logger.Info("User registered successfully", zap.String("email", utils.MaskEmail(input.Email)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyEmail verifies the email with the verification code
|
||||
func (s *Service) VerifyEmail(ctx context.Context, input *client.VerifyEmailInput) error {
|
||||
_, err := s.apiClient.VerifyEmailCode(ctx, input)
|
||||
if err != nil {
|
||||
s.logger.Error("Email verification failed", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
s.logger.Info("Email verified successfully", zap.String("email", utils.MaskEmail(input.Email)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAPIClient returns the API client instance
|
||||
// This allows other parts of the application to make authenticated API calls
|
||||
func (s *Service) GetAPIClient() *client.Client {
|
||||
return s.apiClient
|
||||
}
|
||||
|
||||
// InitiateRecovery initiates the account recovery process
|
||||
func (s *Service) InitiateRecovery(ctx context.Context, email, method string) (*client.RecoveryInitiateResponse, error) {
|
||||
resp, err := s.apiClient.RecoveryInitiate(ctx, email, method)
|
||||
if err != nil {
|
||||
s.logger.Error("Recovery initiation failed", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
s.logger.Info("Recovery initiated successfully", zap.String("email", utils.MaskEmail(email)))
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// VerifyRecovery verifies the recovery challenge
|
||||
func (s *Service) VerifyRecovery(ctx context.Context, input *client.RecoveryVerifyInput) (*client.RecoveryVerifyResponse, error) {
|
||||
resp, err := s.apiClient.RecoveryVerify(ctx, input)
|
||||
if err != nil {
|
||||
s.logger.Error("Recovery verification failed", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
s.logger.Info("Recovery verification successful")
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CompleteRecovery completes the account recovery and resets credentials
|
||||
func (s *Service) CompleteRecovery(ctx context.Context, input *client.RecoveryCompleteInput) (*client.RecoveryCompleteResponse, error) {
|
||||
resp, err := s.apiClient.RecoveryComplete(ctx, input)
|
||||
if err != nil {
|
||||
s.logger.Error("Recovery completion failed", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
s.logger.Info("Recovery completed successfully")
|
||||
return resp, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue