Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,146 @@
// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/me/delete.go
package me
import (
"context"
"errors"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants"
dom_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/user"
svc_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/user"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
sstring "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/security/securestring"
)
type DeleteMeRequestDTO struct {
Password string `json:"password"`
}
type DeleteMeService interface {
Execute(sessCtx context.Context, req *DeleteMeRequestDTO) error
}
type deleteMeServiceImpl struct {
config *config.Configuration
logger *zap.Logger
completeUserDeletionService svc_user.CompleteUserDeletionService
}
func NewDeleteMeService(
config *config.Configuration,
logger *zap.Logger,
completeUserDeletionService svc_user.CompleteUserDeletionService,
) DeleteMeService {
logger = logger.Named("DeleteMeService")
return &deleteMeServiceImpl{
config: config,
logger: logger,
completeUserDeletionService: completeUserDeletionService,
}
}
func (svc *deleteMeServiceImpl) Execute(sessCtx context.Context, req *DeleteMeRequestDTO) error {
//
// STEP 1: Validation
//
if req == nil {
svc.logger.Warn("Failed validation with nil request")
return httperror.NewForBadRequestWithSingleField("non_field_error", "Password is required")
}
e := make(map[string]string)
if req.Password == "" {
e["password"] = "Password is required"
}
if len(e) != 0 {
svc.logger.Warn("Failed validation",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get required from context.
//
sessionUserID, ok := sessCtx.Value(constants.SessionUserID).(gocql.UUID)
if !ok {
svc.logger.Error("Failed getting local user id",
zap.Any("error", "Not found in context: user_id"))
return errors.New("user id not found in context")
}
// Defend against admin deleting themselves
sessionUserRole, _ := sessCtx.Value(constants.SessionUserRole).(int8)
if sessionUserRole == dom_user.UserRoleRoot {
svc.logger.Warn("admin is not allowed to delete themselves",
zap.Any("error", ""))
return httperror.NewForForbiddenWithSingleField("message", "admins do not have permission to delete themselves")
}
//
// STEP 3: Verify password (intent confirmation).
//
securePassword, err := sstring.NewSecureString(req.Password)
if err != nil {
svc.logger.Error("Failed to create secure string", zap.Any("error", err))
return err
}
defer securePassword.Wipe()
// NOTE: In this E2EE architecture, the server does not store password hashes.
// Password verification happens client-side during key derivation.
// The frontend must verify the password locally before calling this endpoint
// by successfully deriving the KEK and decrypting the master key.
// If the password is wrong, the client-side decryption will fail.
//
// The password field in the request serves as a confirmation that the user
// intentionally wants to delete their account (not cryptographic verification).
_ = securePassword // Password used for user intent confirmation
//
// STEP 4: Execute GDPR right-to-be-forgotten complete deletion
//
svc.logger.Info("Starting GDPR right-to-be-forgotten complete user deletion",
zap.String("user_id", sessionUserID.String()))
deletionReq := &svc_user.CompleteUserDeletionRequest{
UserID: sessionUserID,
Password: req.Password,
}
result, err := svc.completeUserDeletionService.Execute(sessCtx, deletionReq)
if err != nil {
svc.logger.Error("Failed to complete user deletion",
zap.Error(err),
zap.String("user_id", sessionUserID.String()))
return err
}
//
// SUCCESS: User account and all data permanently deleted (GDPR compliant)
//
svc.logger.Info("User account successfully deleted (GDPR right-to-be-forgotten)",
zap.String("user_id", sessionUserID.String()),
zap.Int("files_deleted", result.FilesDeleted),
zap.Int("collections_deleted", result.CollectionsDeleted),
zap.Int("s3_objects_deleted", result.S3ObjectsDeleted),
zap.Int("memberships_removed", result.MembershipsRemoved),
zap.Int64("data_size_bytes", result.TotalDataSizeBytes),
zap.Int("non_fatal_errors", len(result.Errors)))
if len(result.Errors) > 0 {
svc.logger.Warn("Deletion completed with non-fatal errors",
zap.Strings("errors", result.Errors))
}
return nil
}