From 3a63a14c4d71eefc3a6a1e26eca13a68539b4493 Mon Sep 17 00:00:00 2001 From: Nikolai Papin Date: Sat, 2 Aug 2025 23:37:16 +0300 Subject: [PATCH] refactor: implemented privacy checks in the GetProfileByUsername method; refactor: reworked sql request for privacy-checking profile getter --- backend/internal/database/query.sql.go | 82 ++++++++++++++------------ backend/internal/services/profile.go | 20 +++++-- sqlc/query.sql | 48 ++++++++------- 3 files changed, 85 insertions(+), 65 deletions(-) diff --git a/backend/internal/database/query.sql.go b/backend/internal/database/query.sql.go index a85cf12..5d8c06a 100644 --- a/backend/internal/database/query.sql.go +++ b/backend/internal/database/query.sql.go @@ -345,59 +345,63 @@ func (q *Queries) GetProfileByUsername(ctx context.Context, username string) (Pr return i, err } -const getProfileByUsernameRestricted = `-- name: GetProfileByUsernameRestricted :one +const getProfileByUsernameWithPrivacy = `-- name: GetProfileByUsernameWithPrivacy :one SELECT - users.username, - profiles.name, - CASE - WHEN profile_settings.hide_birthday OR profile_settings.hide_profile_details THEN NULL - ELSE profiles.birthday - END AS birthday, - CASE - WHEN profile_settings.hide_profile_details THEN NULL - ELSE profiles.bio - END AS bio, - CASE - WHEN profile_settings.hide_profile_details THEN NULL - ELSE profiles.avatar_url - END AS avatar_url, - profiles.color, - profiles.color_grad, - profile_settings.hide_profile_details -FROM profiles -JOIN users ON users.id = profiles.user_id -JOIN profile_settings ON profiles.id = profile_settings.profile_id -WHERE users.username = $1 AND ($2 IS FALSE OR profile_settings.hide_for_unauthenticated IS FALSE) + u.username, + p.name, + p.bio, + p.avatar_url, + CASE WHEN ps.hide_birthday THEN NULL ELSE p.birthday END AS birthday, + p.color, + p.color_grad, + NOT ($1::text = '' AND ps.hide_for_unauthenticated) AS access_allowed +FROM + users AS u +JOIN profiles AS p ON u.id = p.user_id +JOIN profile_settings AS ps ON p.id = ps.profile_id +WHERE + u.username = $2::text + AND ( + $2::text = $1::text + OR + u.deleted IS FALSE + AND u.verified IS TRUE + AND NOT EXISTS ( + SELECT 1 + FROM banned_users + WHERE user_id = u.id + ) + ) ` -type GetProfileByUsernameRestrictedParams struct { - Username string - Column2 *bool +type GetProfileByUsernameWithPrivacyParams struct { + Requester string + SearchedUsername string } -type GetProfileByUsernameRestrictedRow struct { - Username string - Name string - Birthday pgtype.Timestamp - Bio *string - AvatarUrl *string - Color string - ColorGrad string - HideProfileDetails bool +type GetProfileByUsernameWithPrivacyRow struct { + Username string + Name string + Bio string + AvatarUrl string + Birthday pgtype.Timestamp + Color string + ColorGrad string + AccessAllowed *bool } -func (q *Queries) GetProfileByUsernameRestricted(ctx context.Context, arg GetProfileByUsernameRestrictedParams) (GetProfileByUsernameRestrictedRow, error) { - row := q.db.QueryRow(ctx, getProfileByUsernameRestricted, arg.Username, arg.Column2) - var i GetProfileByUsernameRestrictedRow +func (q *Queries) GetProfileByUsernameWithPrivacy(ctx context.Context, arg GetProfileByUsernameWithPrivacyParams) (GetProfileByUsernameWithPrivacyRow, error) { + row := q.db.QueryRow(ctx, getProfileByUsernameWithPrivacy, arg.Requester, arg.SearchedUsername) + var i GetProfileByUsernameWithPrivacyRow err := row.Scan( &i.Username, &i.Name, - &i.Birthday, &i.Bio, &i.AvatarUrl, + &i.Birthday, &i.Color, &i.ColorGrad, - &i.HideProfileDetails, + &i.AccessAllowed, ) return i, err } diff --git a/backend/internal/services/profile.go b/backend/internal/services/profile.go index c6698ab..c074915 100644 --- a/backend/internal/services/profile.go +++ b/backend/internal/services/profile.go @@ -70,7 +70,6 @@ func (p *profileServiceImpl) GetMyProfile(cinfo dto.ClientInfo) (*dto.ProfileDto return profileDto, nil } -// TODO: Profile privacy settings checks func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username string) (*dto.ProfileDto, error) { helper, db, err := database.NewDbHelperTransaction(p.dbctx); if err != nil { p.log.Error( @@ -80,7 +79,10 @@ func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username } defer helper.Rollback() - profile, err := db.TXQueries.GetProfileByUsername(db.CTX, username); if err != nil { + profileRow, err := db.TXQueries.GetProfileByUsernameWithPrivacy(db.CTX, database.GetProfileByUsernameWithPrivacyParams{ + Requester: cinfo.Username, + SearchedUsername: username, + }); if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, errs.ErrNotFound } @@ -92,8 +94,18 @@ func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username return nil, errs.ErrServerError } - profileDto := &dto.ProfileDto{} - mapspecial.MapProfileDto(profile, profileDto) + if !*profileRow.AccessAllowed { + return nil, errs.ErrForbidden + } + + profileDto := &dto.ProfileDto{ + Name: profileRow.Name, + Bio: profileRow.Bio, + AvatarUrl: &profileRow.AvatarUrl, + Birthday: profileRow.Birthday.Time.UnixMilli(), + Color: profileRow.Color, + ColorGrad: profileRow.ColorGrad, + } return profileDto, nil } diff --git a/sqlc/query.sql b/sqlc/query.sql index 4aefdf9..55c6b75 100644 --- a/sqlc/query.sql +++ b/sqlc/query.sql @@ -296,29 +296,33 @@ SELECT profiles.* FROM profiles JOIN users ON users.id = profiles.user_id WHERE users.username = $1; -;-- name: GetProfileByUsernameRestricted :one +;-- name: GetProfileByUsernameWithPrivacy :one SELECT - users.username, - profiles.name, - CASE - WHEN profile_settings.hide_birthday OR profile_settings.hide_profile_details THEN NULL - ELSE profiles.birthday - END AS birthday, - CASE - WHEN profile_settings.hide_profile_details THEN NULL - ELSE profiles.bio - END AS bio, - CASE - WHEN profile_settings.hide_profile_details THEN NULL - ELSE profiles.avatar_url - END AS avatar_url, - profiles.color, - profiles.color_grad, - profile_settings.hide_profile_details -FROM profiles -JOIN users ON users.id = profiles.user_id -JOIN profile_settings ON profiles.id = profile_settings.profile_id -WHERE users.username = $1 AND ($2 IS FALSE OR profile_settings.hide_for_unauthenticated IS FALSE); + u.username, + p.name, + p.bio, + p.avatar_url, + CASE WHEN ps.hide_birthday THEN NULL ELSE p.birthday END AS birthday, + p.color, + p.color_grad, + NOT (@requester::text = '' AND ps.hide_for_unauthenticated) AS access_allowed +FROM + users AS u +JOIN profiles AS p ON u.id = p.user_id +JOIN profile_settings AS ps ON p.id = ps.profile_id +WHERE + u.username = @searched_username::text + AND ( + @searched_username::text = @requester::text + OR + u.deleted IS FALSE + AND u.verified IS TRUE + AND NOT EXISTS ( + SELECT 1 + FROM banned_users + WHERE user_id = u.id + ) + ); ;-- name: GetProfilesRestricted :many SELECT