2025-01-19 06:02:07 -06:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
2025-02-19 14:28:45 +01:00
|
|
|
"bytes"
|
2025-03-27 17:46:10 +01:00
|
|
|
"context"
|
2025-01-19 06:02:07 -06:00
|
|
|
"crypto/tls"
|
2025-02-19 14:28:45 +01:00
|
|
|
"encoding/base64"
|
2025-02-25 20:34:13 +01:00
|
|
|
"errors"
|
2025-01-19 06:02:07 -06:00
|
|
|
"fmt"
|
2025-02-19 14:28:45 +01:00
|
|
|
"io"
|
2025-01-19 06:02:07 -06:00
|
|
|
"log"
|
2025-02-19 14:28:45 +01:00
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
2025-01-19 06:02:07 -06:00
|
|
|
"strings"
|
2025-03-27 17:46:10 +01:00
|
|
|
"time"
|
2025-01-19 06:02:07 -06:00
|
|
|
|
|
|
|
|
"github.com/go-ldap/ldap/v3"
|
2025-02-05 18:08:01 +01:00
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
2025-01-19 06:02:07 -06:00
|
|
|
"gorm.io/gorm"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type LdapService struct {
|
|
|
|
|
db *gorm.DB
|
|
|
|
|
appConfigService *AppConfigService
|
|
|
|
|
userService *UserService
|
|
|
|
|
groupService *UserGroupService
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewLdapService(db *gorm.DB, appConfigService *AppConfigService, userService *UserService, groupService *UserGroupService) *LdapService {
|
|
|
|
|
return &LdapService{db: db, appConfigService: appConfigService, userService: userService, groupService: groupService}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *LdapService) createClient() (*ldap.Conn, error) {
|
2025-04-10 04:41:22 -07:00
|
|
|
dbConfig := s.appConfigService.GetDbConfig()
|
|
|
|
|
|
|
|
|
|
if !dbConfig.LdapEnabled.IsTrue() {
|
2025-01-19 06:02:07 -06:00
|
|
|
return nil, fmt.Errorf("LDAP is not enabled")
|
|
|
|
|
}
|
2025-04-10 04:41:22 -07:00
|
|
|
|
2025-01-19 06:02:07 -06:00
|
|
|
// Setup LDAP connection
|
2025-04-10 04:41:22 -07:00
|
|
|
ldapURL := dbConfig.LdapUrl.Value
|
|
|
|
|
skipTLSVerify := dbConfig.LdapSkipCertVerify.IsTrue()
|
2025-04-06 06:04:08 -07:00
|
|
|
client, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{
|
|
|
|
|
InsecureSkipVerify: skipTLSVerify, //nolint:gosec
|
|
|
|
|
}))
|
2025-01-19 06:02:07 -06:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to connect to LDAP: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Bind as service account
|
2025-04-10 04:41:22 -07:00
|
|
|
bindDn := dbConfig.LdapBindDn.Value
|
|
|
|
|
bindPassword := dbConfig.LdapBindPassword.Value
|
2025-01-19 06:02:07 -06:00
|
|
|
err = client.Bind(bindDn, bindPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to bind to LDAP: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return client, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
func (s *LdapService) SyncAll(ctx context.Context) error {
|
|
|
|
|
// Start a transaction
|
|
|
|
|
tx := s.db.Begin()
|
2025-04-09 14:05:53 +02:00
|
|
|
defer func() {
|
|
|
|
|
tx.Rollback()
|
|
|
|
|
}()
|
2025-04-06 06:04:08 -07:00
|
|
|
|
|
|
|
|
err := s.SyncUsers(ctx, tx)
|
2025-01-19 06:02:07 -06:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to sync users: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
err = s.SyncGroups(ctx, tx)
|
2025-01-19 06:02:07 -06:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to sync groups: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
// Commit the changes
|
|
|
|
|
err = tx.Commit().Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to commit changes to database: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-19 06:02:07 -06:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-27 17:46:10 +01:00
|
|
|
//nolint:gocognit
|
2025-04-06 06:04:08 -07:00
|
|
|
func (s *LdapService) SyncGroups(ctx context.Context, tx *gorm.DB) error {
|
2025-04-10 04:41:22 -07:00
|
|
|
dbConfig := s.appConfigService.GetDbConfig()
|
|
|
|
|
|
2025-01-19 06:02:07 -06:00
|
|
|
// Setup LDAP connection
|
|
|
|
|
client, err := s.createClient()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to create LDAP client: %w", err)
|
|
|
|
|
}
|
|
|
|
|
defer client.Close()
|
|
|
|
|
|
|
|
|
|
searchAttrs := []string{
|
2025-04-10 04:41:22 -07:00
|
|
|
dbConfig.LdapAttributeGroupName.Value,
|
|
|
|
|
dbConfig.LdapAttributeGroupUniqueIdentifier.Value,
|
|
|
|
|
dbConfig.LdapAttributeGroupMember.Value,
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
|
2025-04-10 04:41:22 -07:00
|
|
|
searchReq := ldap.NewSearchRequest(
|
|
|
|
|
dbConfig.LdapBase.Value,
|
|
|
|
|
ldap.ScopeWholeSubtree,
|
|
|
|
|
0, 0, 0, false,
|
|
|
|
|
dbConfig.LdapUserGroupSearchFilter.Value,
|
|
|
|
|
searchAttrs,
|
|
|
|
|
[]ldap.Control{},
|
|
|
|
|
)
|
2025-01-19 06:02:07 -06:00
|
|
|
result, err := client.Search(searchReq)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to query LDAP: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a mapping for groups that exist
|
|
|
|
|
ldapGroupIDs := make(map[string]bool)
|
|
|
|
|
|
|
|
|
|
for _, value := range result.Entries {
|
|
|
|
|
var membersUserId []string
|
|
|
|
|
|
2025-04-10 04:41:22 -07:00
|
|
|
ldapId := value.GetAttributeValue(dbConfig.LdapAttributeGroupUniqueIdentifier.Value)
|
2025-03-23 13:30:12 -05:00
|
|
|
|
|
|
|
|
// Skip groups without a valid LDAP ID
|
|
|
|
|
if ldapId == "" {
|
2025-04-10 04:41:22 -07:00
|
|
|
log.Printf("Skipping LDAP group without a valid unique identifier (attribute: %s)", dbConfig.LdapAttributeGroupUniqueIdentifier.Value)
|
2025-03-23 13:30:12 -05:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-19 06:02:07 -06:00
|
|
|
ldapGroupIDs[ldapId] = true
|
|
|
|
|
|
|
|
|
|
// Try to find the group in the database
|
|
|
|
|
var databaseGroup model.UserGroup
|
2025-04-06 06:04:08 -07:00
|
|
|
tx.WithContext(ctx).Where("ldap_id = ?", ldapId).First(&databaseGroup)
|
2025-01-19 06:02:07 -06:00
|
|
|
|
|
|
|
|
// Get group members and add to the correct Group
|
2025-04-10 04:41:22 -07:00
|
|
|
groupMembers := value.GetAttributeValues(dbConfig.LdapAttributeGroupMember.Value)
|
2025-01-19 06:02:07 -06:00
|
|
|
for _, member := range groupMembers {
|
|
|
|
|
// Normal output of this would be CN=username,ou=people,dc=example,dc=com
|
|
|
|
|
// Splitting at the "=" and "," then just grabbing the username for that string
|
|
|
|
|
singleMember := strings.Split(strings.Split(member, "=")[1], ",")[0]
|
|
|
|
|
|
|
|
|
|
var databaseUser model.User
|
2025-04-06 06:04:08 -07:00
|
|
|
err := tx.WithContext(ctx).Where("username = ? AND ldap_id IS NOT NULL", singleMember).First(&databaseUser).Error
|
2025-02-25 20:34:13 +01:00
|
|
|
if err != nil {
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
// The user collides with a non-LDAP user, so we skip it
|
|
|
|
|
continue
|
|
|
|
|
} else {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2025-02-11 10:25:00 -06:00
|
|
|
|
2025-01-19 06:02:07 -06:00
|
|
|
membersUserId = append(membersUserId, databaseUser.ID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
syncGroup := dto.UserGroupCreateDto{
|
2025-04-10 04:41:22 -07:00
|
|
|
Name: value.GetAttributeValue(dbConfig.LdapAttributeGroupName.Value),
|
|
|
|
|
FriendlyName: value.GetAttributeValue(dbConfig.LdapAttributeGroupName.Value),
|
|
|
|
|
LdapID: value.GetAttributeValue(dbConfig.LdapAttributeGroupUniqueIdentifier.Value),
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if databaseGroup.ID == "" {
|
2025-04-06 06:04:08 -07:00
|
|
|
newGroup, err := s.groupService.createInternal(ctx, syncGroup, tx)
|
2025-01-19 06:02:07 -06:00
|
|
|
if err != nil {
|
2025-04-06 06:04:08 -07:00
|
|
|
log.Printf("Error syncing group %s: %v", syncGroup.Name, err)
|
|
|
|
|
continue
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
2025-04-06 06:04:08 -07:00
|
|
|
|
|
|
|
|
_, err = s.groupService.updateUsersInternal(ctx, newGroup.ID, membersUserId, tx)
|
2025-03-27 17:46:10 +01:00
|
|
|
if err != nil {
|
2025-04-06 06:04:08 -07:00
|
|
|
log.Printf("Error syncing group %s: %v", syncGroup.Name, err)
|
|
|
|
|
continue
|
2025-03-27 17:46:10 +01:00
|
|
|
}
|
2025-04-06 06:04:08 -07:00
|
|
|
} else {
|
|
|
|
|
_, err = s.groupService.updateInternal(ctx, databaseGroup.ID, syncGroup, true, tx)
|
2025-01-19 06:02:07 -06:00
|
|
|
if err != nil {
|
2025-04-06 06:04:08 -07:00
|
|
|
log.Printf("Error syncing group %s: %v", syncGroup.Name, err)
|
|
|
|
|
continue
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
_, err = s.groupService.updateUsersInternal(ctx, databaseGroup.ID, membersUserId, tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Error syncing group %s: %v", syncGroup.Name, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all LDAP groups from the database
|
|
|
|
|
var ldapGroupsInDb []model.UserGroup
|
2025-04-06 06:04:08 -07:00
|
|
|
err = tx.
|
|
|
|
|
WithContext(ctx).
|
|
|
|
|
Find(&ldapGroupsInDb, "ldap_id IS NOT NULL").
|
|
|
|
|
Select("ldap_id").
|
|
|
|
|
Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Failed to fetch groups from database: %v", err)
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete groups that no longer exist in LDAP
|
|
|
|
|
for _, group := range ldapGroupsInDb {
|
|
|
|
|
if _, exists := ldapGroupIDs[*group.LdapID]; !exists {
|
2025-04-06 06:04:08 -07:00
|
|
|
err = tx.
|
|
|
|
|
WithContext(ctx).
|
|
|
|
|
Delete(&model.UserGroup{}, "ldap_id = ?", group.LdapID).
|
|
|
|
|
Error
|
|
|
|
|
if err != nil {
|
2025-01-19 06:02:07 -06:00
|
|
|
log.Printf("Failed to delete group %s with: %v", group.Name, err)
|
|
|
|
|
} else {
|
|
|
|
|
log.Printf("Deleted group %s", group.Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-27 17:46:10 +01:00
|
|
|
//nolint:gocognit
|
2025-04-06 06:04:08 -07:00
|
|
|
func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB) error {
|
2025-04-10 04:41:22 -07:00
|
|
|
dbConfig := s.appConfigService.GetDbConfig()
|
|
|
|
|
|
2025-01-19 06:02:07 -06:00
|
|
|
// Setup LDAP connection
|
|
|
|
|
client, err := s.createClient()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to create LDAP client: %w", err)
|
|
|
|
|
}
|
|
|
|
|
defer client.Close()
|
|
|
|
|
|
|
|
|
|
searchAttrs := []string{
|
|
|
|
|
"memberOf",
|
|
|
|
|
"sn",
|
|
|
|
|
"cn",
|
2025-04-10 04:41:22 -07:00
|
|
|
dbConfig.LdapAttributeUserUniqueIdentifier.Value,
|
|
|
|
|
dbConfig.LdapAttributeUserUsername.Value,
|
|
|
|
|
dbConfig.LdapAttributeUserEmail.Value,
|
|
|
|
|
dbConfig.LdapAttributeUserFirstName.Value,
|
|
|
|
|
dbConfig.LdapAttributeUserLastName.Value,
|
|
|
|
|
dbConfig.LdapAttributeUserProfilePicture.Value,
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Filters must start and finish with ()!
|
2025-04-10 04:41:22 -07:00
|
|
|
searchReq := ldap.NewSearchRequest(
|
|
|
|
|
dbConfig.LdapBase.Value,
|
|
|
|
|
ldap.ScopeWholeSubtree,
|
|
|
|
|
0, 0, 0, false,
|
|
|
|
|
dbConfig.LdapUserSearchFilter.Value,
|
|
|
|
|
searchAttrs,
|
|
|
|
|
[]ldap.Control{},
|
|
|
|
|
)
|
2025-01-19 06:02:07 -06:00
|
|
|
|
|
|
|
|
result, err := client.Search(searchReq)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(fmt.Errorf("failed to query LDAP: %w", err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a mapping for users that exist
|
|
|
|
|
ldapUserIDs := make(map[string]bool)
|
|
|
|
|
|
|
|
|
|
for _, value := range result.Entries {
|
2025-04-10 04:41:22 -07:00
|
|
|
ldapId := value.GetAttributeValue(dbConfig.LdapAttributeUserUniqueIdentifier.Value)
|
2025-03-23 13:30:12 -05:00
|
|
|
|
|
|
|
|
// Skip users without a valid LDAP ID
|
|
|
|
|
if ldapId == "" {
|
2025-04-10 04:41:22 -07:00
|
|
|
log.Printf("Skipping LDAP user without a valid unique identifier (attribute: %s)", dbConfig.LdapAttributeUserUniqueIdentifier.Value)
|
2025-03-23 13:30:12 -05:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-19 06:02:07 -06:00
|
|
|
ldapUserIDs[ldapId] = true
|
|
|
|
|
|
|
|
|
|
// Get the user from the database
|
|
|
|
|
var databaseUser model.User
|
2025-04-06 06:04:08 -07:00
|
|
|
tx.WithContext(ctx).Where("ldap_id = ?", ldapId).First(&databaseUser)
|
2025-01-19 06:02:07 -06:00
|
|
|
|
|
|
|
|
// Check if user is admin by checking if they are in the admin group
|
|
|
|
|
isAdmin := false
|
|
|
|
|
for _, group := range value.GetAttributeValues("memberOf") {
|
2025-04-10 04:41:22 -07:00
|
|
|
if strings.Contains(group, dbConfig.LdapAttributeAdminGroup.Value) {
|
2025-01-19 06:02:07 -06:00
|
|
|
isAdmin = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newUser := dto.UserCreateDto{
|
2025-04-10 04:41:22 -07:00
|
|
|
Username: value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value),
|
|
|
|
|
Email: value.GetAttributeValue(dbConfig.LdapAttributeUserEmail.Value),
|
|
|
|
|
FirstName: value.GetAttributeValue(dbConfig.LdapAttributeUserFirstName.Value),
|
|
|
|
|
LastName: value.GetAttributeValue(dbConfig.LdapAttributeUserLastName.Value),
|
2025-01-19 06:02:07 -06:00
|
|
|
IsAdmin: isAdmin,
|
|
|
|
|
LdapID: ldapId,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if databaseUser.ID == "" {
|
2025-04-06 06:04:08 -07:00
|
|
|
_, err = s.userService.createUserInternal(ctx, newUser, tx)
|
2025-01-19 06:02:07 -06:00
|
|
|
if err != nil {
|
2025-04-06 06:04:08 -07:00
|
|
|
log.Printf("Error syncing user %s: %v", newUser.Username, err)
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-04-06 06:04:08 -07:00
|
|
|
_, err = s.userService.updateUserInternal(ctx, databaseUser.ID, newUser, false, true, tx)
|
2025-01-19 06:02:07 -06:00
|
|
|
if err != nil {
|
2025-04-06 06:04:08 -07:00
|
|
|
log.Printf("Error syncing user %s: %v", newUser.Username, err)
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-19 14:28:45 +01:00
|
|
|
// Save profile picture
|
2025-04-10 04:41:22 -07:00
|
|
|
if pictureString := value.GetAttributeValue(dbConfig.LdapAttributeUserProfilePicture.Value); pictureString != "" {
|
2025-04-06 06:04:08 -07:00
|
|
|
if err := s.saveProfilePicture(ctx, databaseUser.ID, pictureString); err != nil {
|
|
|
|
|
log.Printf("Error saving profile picture for user %s: %v", newUser.Username, err)
|
2025-02-19 14:28:45 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all LDAP users from the database
|
|
|
|
|
var ldapUsersInDb []model.User
|
2025-04-06 06:04:08 -07:00
|
|
|
err = tx.
|
|
|
|
|
WithContext(ctx).
|
|
|
|
|
Find(&ldapUsersInDb, "ldap_id IS NOT NULL").
|
|
|
|
|
Select("ldap_id").
|
|
|
|
|
Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Failed to fetch users from database: %v", err)
|
2025-01-19 06:02:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete users that no longer exist in LDAP
|
|
|
|
|
for _, user := range ldapUsersInDb {
|
|
|
|
|
if _, exists := ldapUserIDs[*user.LdapID]; !exists {
|
2025-04-06 06:04:08 -07:00
|
|
|
if err := s.userService.deleteUserInternal(ctx, user.ID, true, tx); err != nil {
|
2025-01-19 06:02:07 -06:00
|
|
|
log.Printf("Failed to delete user %s with: %v", user.Username, err)
|
|
|
|
|
} else {
|
|
|
|
|
log.Printf("Deleted user %s", user.Username)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-06 06:04:08 -07:00
|
|
|
|
2025-01-19 06:02:07 -06:00
|
|
|
return nil
|
|
|
|
|
}
|
2025-02-19 14:28:45 +01:00
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
func (s *LdapService) saveProfilePicture(parentCtx context.Context, userId string, pictureString string) error {
|
2025-02-19 14:28:45 +01:00
|
|
|
var reader io.Reader
|
|
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
_, err := url.ParseRequestURI(pictureString)
|
|
|
|
|
if err == nil {
|
|
|
|
|
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
|
2025-03-27 17:46:10 +01:00
|
|
|
defer cancel()
|
|
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
var req *http.Request
|
|
|
|
|
req, err = http.NewRequestWithContext(ctx, http.MethodGet, pictureString, nil)
|
2025-03-27 17:46:10 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
var res *http.Response
|
|
|
|
|
res, err = http.DefaultClient.Do(req)
|
2025-02-19 14:28:45 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to download profile picture: %w", err)
|
|
|
|
|
}
|
2025-04-06 06:04:08 -07:00
|
|
|
defer res.Body.Close()
|
2025-02-19 14:28:45 +01:00
|
|
|
|
2025-04-06 06:04:08 -07:00
|
|
|
reader = res.Body
|
2025-02-19 14:28:45 +01:00
|
|
|
} else if decodedPhoto, err := base64.StdEncoding.DecodeString(pictureString); err == nil {
|
|
|
|
|
// If the photo is a base64 encoded string, decode it
|
|
|
|
|
reader = bytes.NewReader(decodedPhoto)
|
|
|
|
|
} else {
|
|
|
|
|
// If the photo is a string, we assume that it's a binary string
|
2025-02-22 14:51:21 +01:00
|
|
|
reader = bytes.NewReader([]byte(pictureString))
|
2025-02-19 14:28:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the profile picture
|
|
|
|
|
if err := s.userService.UpdateProfilePicture(userId, reader); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to update profile picture: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|