fix(api): premium sanitisation recursion issue (#25)
This commit is contained in:
parent
c4be1d424c
commit
11ec2e63b3
@ -43,7 +43,6 @@ import {
|
||||
} from '~/Constants';
|
||||
import {mapChannelToResponse, mapMessageToResponse} from '~/channel/ChannelModel';
|
||||
import type {IChannelRepository} from '~/channel/IChannelRepository';
|
||||
import type {UserRow} from '~/database/types/UserTypes';
|
||||
import {RateLimitError, UnauthorizedError, UnknownGuildError} from '~/Errors';
|
||||
import {mapFavoriteMemeToResponse} from '~/favorite_meme/FavoriteMemeModel';
|
||||
import type {IFavoriteMemeRepository} from '~/favorite_meme/IFavoriteMemeRepository';
|
||||
@ -88,7 +87,7 @@ import type {RpcRequest, RpcResponse, RpcResponseGuildData, RpcResponseSessionDa
|
||||
import type {IUserRepository} from '~/user/IUserRepository';
|
||||
import {CustomStatusValidator} from '~/user/services/CustomStatusValidator';
|
||||
import {getCachedUserPartialResponse} from '~/user/UserCacheHelpers';
|
||||
import {mapExpiredPremiumFields, shouldStripExpiredPremium} from '~/user/UserHelpers';
|
||||
import {createPremiumClearPatch, shouldStripExpiredPremium} from '~/user/UserHelpers';
|
||||
import {
|
||||
mapRelationshipToResponse,
|
||||
mapUserGuildSettingsToResponse,
|
||||
@ -574,10 +573,7 @@ export class RpcService {
|
||||
|
||||
if (needsPremiumStrip) {
|
||||
try {
|
||||
const strippedUser = await this.userRepository.patchUpsert(
|
||||
user.id,
|
||||
mapExpiredPremiumFields(() => null) as Partial<UserRow>,
|
||||
);
|
||||
const strippedUser = await this.userRepository.patchUpsert(user.id, createPremiumClearPatch());
|
||||
if (strippedUser) {
|
||||
user = strippedUser;
|
||||
userData.user = strippedUser;
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
import {Config} from '~/Config';
|
||||
import {UserFlags} from '~/Constants';
|
||||
import type {UserRow} from '~/database/types/UserTypes';
|
||||
|
||||
interface PremiumCheckable {
|
||||
premiumType: number | null;
|
||||
@ -81,3 +82,7 @@ export function mapExpiredPremiumFields<T>(mapper: (field: PremiumClearField) =>
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createPremiumClearPatch(): Partial<UserRow> {
|
||||
return mapExpiredPremiumFields(() => null) as Partial<UserRow>;
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ import {
|
||||
z,
|
||||
} from '~/Schema';
|
||||
import {getCachedUserPartialResponse, mapUserToPartialResponseWithCache} from '~/user/UserCacheHelpers';
|
||||
import {createPremiumClearPatch, shouldStripExpiredPremium} from '~/user/UserHelpers';
|
||||
import {
|
||||
mapGuildMemberToProfileResponse,
|
||||
mapUserGuildSettingsToResponse,
|
||||
@ -406,7 +407,32 @@ export const UserAccountController = (app: HonoApp) => {
|
||||
requestCache: ctx.get('requestCache'),
|
||||
});
|
||||
|
||||
const userProfile = mapUserToProfileResponse(profile.user);
|
||||
let profileUser = profile.user;
|
||||
let premiumType = profile.premiumType;
|
||||
let premiumSince = profile.premiumSince;
|
||||
let premiumLifetimeSequence = profile.premiumLifetimeSequence;
|
||||
|
||||
if (shouldStripExpiredPremium(profileUser)) {
|
||||
try {
|
||||
const sanitizedUser = await ctx
|
||||
.get('userRepository')
|
||||
.patchUpsert(profileUser.id, createPremiumClearPatch(), profileUser.toRow());
|
||||
if (sanitizedUser) {
|
||||
profileUser = sanitizedUser;
|
||||
profile.user = sanitizedUser;
|
||||
premiumType = undefined;
|
||||
premiumSince = undefined;
|
||||
premiumLifetimeSequence = undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.warn(
|
||||
{userId: profileUser.id.toString(), error},
|
||||
'Failed to sanitize expired premium fields before returning profile',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const userProfile = mapUserToProfileResponse(profileUser);
|
||||
const guildMemberProfile = mapGuildMemberToProfileResponse(profile.guildMemberDomain ?? null);
|
||||
|
||||
const mutualFriends = profile.mutualFriends
|
||||
@ -423,16 +449,16 @@ export const UserAccountController = (app: HonoApp) => {
|
||||
|
||||
return ctx.json({
|
||||
user: await mapUserToPartialResponseWithCache({
|
||||
user: profile.user,
|
||||
user: profileUser,
|
||||
userCacheService: ctx.get('userCacheService'),
|
||||
requestCache: ctx.get('requestCache'),
|
||||
}),
|
||||
user_profile: userProfile,
|
||||
guild_member: profile.guildMember ?? undefined,
|
||||
guild_member_profile: guildMemberProfile ?? undefined,
|
||||
premium_type: profile.premiumType,
|
||||
premium_since: profile.premiumSince?.toISOString(),
|
||||
premium_lifetime_sequence: profile.premiumLifetimeSequence,
|
||||
premium_type: premiumType,
|
||||
premium_since: premiumSince?.toISOString(),
|
||||
premium_lifetime_sequence: premiumLifetimeSequence,
|
||||
mutual_friends: mutualFriends,
|
||||
mutual_guilds: profile.mutualGuilds,
|
||||
});
|
||||
|
||||
@ -20,10 +20,8 @@
|
||||
import {createUserID, type UserID} from '~/BrandedTypes';
|
||||
import {buildPatchFromData, Db, executeVersionedUpdate, fetchMany, fetchOne} from '~/database/Cassandra';
|
||||
import {EMPTY_USER_ROW, USER_COLUMNS, type UserRow} from '~/database/CassandraTypes';
|
||||
import {Logger} from '~/Logger';
|
||||
import {User} from '~/Models';
|
||||
import {Users} from '~/Tables';
|
||||
import {shouldStripExpiredPremium} from '~/user/UserHelpers';
|
||||
|
||||
const FETCH_USERS_BY_IDS_CQL = Users.selectCql({
|
||||
where: Users.where.in('user_id', 'user_ids'),
|
||||
@ -75,21 +73,7 @@ export class UserDataRepository {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = new User(userRow);
|
||||
|
||||
if (shouldStripExpiredPremium(user)) {
|
||||
try {
|
||||
await this.readRepairExpiredPremium(user);
|
||||
const repairedRow = await fetchOne<UserRow>(FETCH_USER_BY_ID_CQL, {
|
||||
user_id: userId,
|
||||
});
|
||||
return repairedRow ? new User(repairedRow) : null;
|
||||
} catch (error) {
|
||||
Logger.warn({userId: user.id.toString(), error}, 'Failed to repair expired premium fields while reading user');
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
return new User(userRow);
|
||||
}
|
||||
|
||||
async findUniqueAssert(userId: UserID): Promise<User> {
|
||||
@ -168,18 +152,4 @@ export class UserDataRepository {
|
||||
|
||||
await this.patchUser(userId, patch);
|
||||
}
|
||||
|
||||
private async readRepairExpiredPremium(user: User): Promise<void> {
|
||||
await this.patchUser(user.id, createPremiumClearPatch());
|
||||
}
|
||||
}
|
||||
|
||||
function createPremiumClearPatch(): UserPatch {
|
||||
return {
|
||||
premium_type: Db.clear<number | null>(),
|
||||
premium_since: Db.clear<Date | null>(),
|
||||
premium_until: Db.clear<Date | null>(),
|
||||
premium_will_cancel: Db.clear<boolean | null>(),
|
||||
premium_billing_cycle: Db.clear<string | null>(),
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user