mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 13:35:42 +09:00
refactor: simplify account standing and verification levels (#3130)
This commit is contained in:
1 parent
1abde06824
commit
1ec8f31253
488 files changed
+10390
-47117
No files matched your search
@@ -16,7 +16,6 @@ import {BulkJobResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
|
||||
const BULK_TASK_ACLS: Record<AdminBulkTaskType, string> = {
|
||||
[AdminBulkTaskType.UPDATE_USER_FLAGS]: AdminACLs.BULK_UPDATE_USER_FLAGS,
|
||||
[AdminBulkTaskType.UPDATE_SUSPICIOUS_ACTIVITY_FLAGS]: AdminACLs.BULK_UPDATE_SUSPICIOUS_ACTIVITY,
|
||||
[AdminBulkTaskType.UPDATE_GUILD_FEATURES]: AdminACLs.BULK_UPDATE_GUILD_FEATURES,
|
||||
[AdminBulkTaskType.ADD_GUILD_MEMBERS]: AdminACLs.BULK_ADD_GUILD_MEMBERS,
|
||||
[AdminBulkTaskType.SCHEDULE_USER_DELETION]: AdminACLs.BULK_DELETE_USERS,
|
||||
@@ -43,18 +42,6 @@ async function queueBulkJob(
|
||||
},
|
||||
options,
|
||||
);
|
||||
case AdminBulkTaskType.UPDATE_SUSPICIOUS_ACTIVITY_FLAGS:
|
||||
return await workerService.addJob(
|
||||
'bulkUpdateSuspiciousActivityFlags',
|
||||
{
|
||||
user_ids: body.user_ids.map((id) => id.toString()),
|
||||
add_flags: body.add_flags,
|
||||
remove_flags: body.remove_flags,
|
||||
admin_user_id: adminUserId.toString(),
|
||||
audit_log_reason: auditLogReason,
|
||||
},
|
||||
options,
|
||||
);
|
||||
case AdminBulkTaskType.UPDATE_GUILD_FEATURES:
|
||||
return await workerService.addJob(
|
||||
'bulkUpdateGuildFeatures',
|
||||
@@ -112,7 +99,6 @@ export function BulkAdminController(app: HonoApp) {
|
||||
Validator('json', AdminBulkJobCreateRequest),
|
||||
requireAnyAdminACL([
|
||||
AdminACLs.BULK_UPDATE_USER_FLAGS,
|
||||
AdminACLs.BULK_UPDATE_SUSPICIOUS_ACTIVITY,
|
||||
AdminACLs.BULK_UPDATE_GUILD_FEATURES,
|
||||
AdminACLs.BULK_ADD_GUILD_MEMBERS,
|
||||
AdminACLs.BULK_DELETE_USERS,
|
||||
@@ -122,7 +108,7 @@ export function BulkAdminController(app: HonoApp) {
|
||||
operationId: 'create_admin_bulk_job',
|
||||
summary: 'Queue a bulk job',
|
||||
description:
|
||||
'Enqueue one background administrative job. The `task` discriminator selects both the body variant and the ACL evaluated for the request: `update_user_flags` needs bulk:update:user_flags, `update_suspicious_activity_flags` needs bulk:update:suspicious_activity, `update_guild_features` needs bulk:update:guild_features, `add_guild_members` needs bulk:add:guild_members, `schedule_user_deletion` needs bulk:delete:users, and `delete_user_messages` needs bulk:delete:user_messages. Returns a job_id immediately; observe progress at /admin/jobs/:job_id.',
|
||||
'Enqueue one background administrative job. The `task` discriminator selects both the body variant and the ACL evaluated for the request: `update_user_flags` needs bulk:update:user_flags, `update_guild_features` needs bulk:update:guild_features, `add_guild_members` needs bulk:add:guild_members, `schedule_user_deletion` needs bulk:delete:users, and `delete_user_messages` needs bulk:delete:user_messages. Returns a job_id immediately; observe progress at /admin/jobs/:job_id.',
|
||||
responseSchema: BulkJobResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
|
||||
@@ -31,12 +31,9 @@ import {
|
||||
AdminUserFlagsUpdateRequest,
|
||||
AdminUserGuildListQuery,
|
||||
AdminUserListQuery,
|
||||
AdminUserPhoneVerificationRequest,
|
||||
AdminUserPremiumFlagsUpdateRequest,
|
||||
AdminUserRelationshipCategoryQuery,
|
||||
AdminUserRelationshipParam,
|
||||
AdminUserSuspiciousActivityFlagsRequest,
|
||||
AdminUserSuspiciousDisableRequest,
|
||||
AdminUserSystemStatusRequest,
|
||||
AdminUsersMeResponse,
|
||||
AdminUserTraitsRequest,
|
||||
@@ -1129,38 +1126,6 @@ export function UserAdminController(app: HonoApp) {
|
||||
);
|
||||
},
|
||||
);
|
||||
app.put(
|
||||
'/admin/users/:user_id/phone-verification',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
|
||||
requireAdminACL(AdminACLs.USER_UPDATE_PHONE),
|
||||
Validator('param', UserIdParam),
|
||||
Validator('json', AdminUserPhoneVerificationRequest),
|
||||
OpenAPI({
|
||||
operationId: 'update_admin_user_phone_verification',
|
||||
summary: 'Update user phone verification flag',
|
||||
responseSchema: UserMutationResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
description:
|
||||
'Set whether a user is treated as having completed phone verification. This is the only supported path for clearing the irreversible user-facing phone verification flag. Requires USER_UPDATE_PHONE permission.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
const adminUserId = ctx.get('adminUserId');
|
||||
const auditLogReason = ctx.get('auditLogReason');
|
||||
const adminUserAcls = ctx.get('adminUserAcls');
|
||||
const {user_id: userId} = ctx.req.valid('param');
|
||||
return ctx.json(
|
||||
await adminService.userService.securityService.updateHasVerifiedPhone(
|
||||
{user_id: userId, ...ctx.req.valid('json')},
|
||||
adminUserId,
|
||||
auditLogReason,
|
||||
adminUserAcls,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
app.patch(
|
||||
'/admin/users/:user_id/date-of-birth',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
|
||||
@@ -1193,68 +1158,4 @@ export function UserAdminController(app: HonoApp) {
|
||||
);
|
||||
},
|
||||
);
|
||||
app.put(
|
||||
'/admin/users/:user_id/suspicious-activity-flags',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
|
||||
requireAdminACL(AdminACLs.USER_UPDATE_SUSPICIOUS_ACTIVITY),
|
||||
Validator('param', UserIdParam),
|
||||
Validator('json', AdminUserSuspiciousActivityFlagsRequest),
|
||||
OpenAPI({
|
||||
operationId: 'update_admin_user_suspicious_activity_flags',
|
||||
summary: 'Update suspicious activity flags',
|
||||
responseSchema: UserMutationResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
description:
|
||||
'Flag user as suspicious for account abuse, fraud, or policy violations. Enables enforcement actions and rate limiting. Creates audit log entry. Requires USER_UPDATE_SUSPICIOUS_ACTIVITY permission.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
const adminUserId = ctx.get('adminUserId');
|
||||
const auditLogReason = ctx.get('auditLogReason');
|
||||
const adminUserAcls = ctx.get('adminUserAcls');
|
||||
const {user_id: userId} = ctx.req.valid('param');
|
||||
return ctx.json(
|
||||
await adminService.userService.securityService.updateSuspiciousActivityFlags(
|
||||
{user_id: userId, ...ctx.req.valid('json')},
|
||||
adminUserId,
|
||||
auditLogReason,
|
||||
adminUserAcls,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
app.put(
|
||||
'/admin/users/:user_id/suspicious-activity-disablement',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
|
||||
requireAdminACL(AdminACLs.USER_DISABLE_SUSPICIOUS),
|
||||
Validator('param', UserIdParam),
|
||||
Validator('json', AdminUserSuspiciousDisableRequest),
|
||||
OpenAPI({
|
||||
operationId: 'disable_admin_user_suspicious',
|
||||
summary: 'Disable user for suspicious activity',
|
||||
responseSchema: UserMutationResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
description:
|
||||
'Disable user account due to suspicious activity or abuse. Account is locked pending review. User cannot access services. Emails the user unless notify_user is false. Creates audit log entry. Requires USER_DISABLE_SUSPICIOUS permission.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
const adminUserId = ctx.get('adminUserId');
|
||||
const auditLogReason = ctx.get('auditLogReason');
|
||||
const adminUserAcls = ctx.get('adminUserAcls');
|
||||
const {user_id: userId} = ctx.req.valid('param');
|
||||
return ctx.json(
|
||||
await adminService.userService.securityService.disableForSuspiciousActivity(
|
||||
{user_id: userId, ...ctx.req.valid('json')},
|
||||
adminUserId,
|
||||
auditLogReason,
|
||||
adminUserAcls,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
import type {User} from '@app/api/models/User';
|
||||
import {getIpAddressReverse, lookupGeoip} from '@app/api/utils/IpUtils';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {DEFERRED_PHONE_ON_COMMUNITY_JOIN} from '@fluxer/constants/src/UserConstants';
|
||||
import type {UserAdminResponse} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
|
||||
import type {ICacheService} from '@pkgs/cache/src/ICacheService';
|
||||
import {formatGeoipLocation} from '@pkgs/geoip/src/GeoipLookup';
|
||||
@@ -50,7 +49,6 @@ export async function mapUserToAdminResponse(
|
||||
email: canViewEmail ? (user.email ?? null) : null,
|
||||
email_verified: canViewEmail ? user.emailVerified : false,
|
||||
email_bounced: canViewEmail ? user.emailBounced : false,
|
||||
has_verified_phone: user.hasVerifiedPhone,
|
||||
date_of_birth: canViewDob ? user.dateOfBirth : null,
|
||||
locale: user.locale,
|
||||
premium_type: user.premiumType,
|
||||
@@ -58,8 +56,6 @@ export async function mapUserToAdminResponse(
|
||||
premium_until: user.premiumUntil?.toISOString() ?? null,
|
||||
premium_grace_ends_at: user.premiumGraceEndsAt?.toISOString() ?? null,
|
||||
premium_lifetime_sequence: user.premiumLifetimeSequence ?? null,
|
||||
suspicious_activity_flags: user.suspiciousActivityFlags,
|
||||
phone_verification_deferred: ((user.suspiciousActivityFlags ?? 0) & DEFERRED_PHONE_ON_COMMUNITY_JOIN) !== 0,
|
||||
temp_banned_until:
|
||||
user.tempBannedUntil && user.tempBannedUntil.getTime() > Date.now() ? user.tempBannedUntil.toISOString() : null,
|
||||
pending_deletion_at: user.pendingDeletionAt?.toISOString() ?? null,
|
||||
|
||||
@@ -49,14 +49,8 @@ export class AdminUserBanService {
|
||||
} else {
|
||||
tempBannedUntil.setHours(tempBannedUntil.getHours() + data.duration_hours);
|
||||
}
|
||||
const updatedUser = await userRepository.patchUpsert(
|
||||
userId,
|
||||
{
|
||||
temp_banned_until: tempBannedUntil,
|
||||
flags: user.flags | UserFlags.DISABLED,
|
||||
},
|
||||
user.toRow(),
|
||||
);
|
||||
const bannedUser = await userRepository.patchUpsert(userId, {temp_banned_until: tempBannedUntil}, user.toRow());
|
||||
const updatedUser = (await userRepository.updateFlags(userId, (flags) => flags | UserFlags.DISABLED)) ?? bannedUser;
|
||||
await AuthSession.terminateAllUserSessions(this.deps.apiContext, userId);
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser: user, updatedUser: updatedUser});
|
||||
const email = user.email;
|
||||
@@ -146,14 +140,9 @@ export class AdminUserBanService {
|
||||
throw new UnknownUserError();
|
||||
}
|
||||
const wasTempBanned = isTemporarilyBanned(user) && !isAccountClosed(user);
|
||||
const updatedUser = await userRepository.patchUpsert(
|
||||
userId,
|
||||
{
|
||||
temp_banned_until: null,
|
||||
flags: user.flags & ~UserFlags.DISABLED,
|
||||
},
|
||||
user.toRow(),
|
||||
);
|
||||
const unbannedUser = await userRepository.patchUpsert(userId, {temp_banned_until: null}, user.toRow());
|
||||
const updatedUser =
|
||||
(await userRepository.updateFlags(userId, (flags) => flags & ~UserFlags.DISABLED)) ?? unbannedUser;
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser: user, updatedUser: updatedUser});
|
||||
const email = user.email;
|
||||
const notificationSent =
|
||||
|
||||
@@ -4,7 +4,6 @@ import type {ApiContext} from '@app/api/ApiContext';
|
||||
import {mapUserToAdminResponse} from '@app/api/admin/models/UserTypes';
|
||||
import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService';
|
||||
import type {AdminUserUpdatePropagator} from '@app/api/admin/services/AdminUserUpdatePropagator';
|
||||
import {EMAIL_CLEARABLE_SUSPICIOUS_ACTIVITY_FLAGS} from '@app/api/auth/AuthEmail';
|
||||
import {createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import {GuildMemberSearchIndexService} from '@app/api/guild/services/member/GuildMemberSearchIndexService';
|
||||
@@ -188,21 +187,11 @@ export class AdminUserProfileService {
|
||||
if (!user) {
|
||||
throw new UnknownUserError();
|
||||
}
|
||||
const updates: {
|
||||
email_verified: boolean;
|
||||
email_bounced: boolean;
|
||||
suspicious_activity_flags?: number;
|
||||
} = {
|
||||
email_verified: true,
|
||||
email_bounced: false,
|
||||
};
|
||||
if (user.suspiciousActivityFlags !== null && user.suspiciousActivityFlags !== 0) {
|
||||
const newFlags = user.suspiciousActivityFlags & ~EMAIL_CLEARABLE_SUSPICIOUS_ACTIVITY_FLAGS;
|
||||
if (newFlags !== user.suspiciousActivityFlags) {
|
||||
updates.suspicious_activity_flags = newFlags;
|
||||
}
|
||||
}
|
||||
const updatedUser = await userRepository.patchUpsert(userId, updates, user.toRow());
|
||||
const updatedUser = await userRepository.patchUpsert(
|
||||
userId,
|
||||
{email_verified: true, email_bounced: false},
|
||||
user.toRow(),
|
||||
);
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser: user, updatedUser: updatedUser});
|
||||
await auditService.createAuditLog({
|
||||
adminUserId,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import type {ApiContext} from '@app/api/ApiContext';
|
||||
import {mapUserToAdminResponse} from '@app/api/admin/models/UserTypes';
|
||||
import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService';
|
||||
import {trySendAdminNotification} from '@app/api/admin/services/AdminNotification';
|
||||
import type {AdminUserUpdatePropagator} from '@app/api/admin/services/AdminUserUpdatePropagator';
|
||||
import * as AuthEmail from '@app/api/auth/AuthEmail';
|
||||
import * as AuthMfa from '@app/api/auth/AuthMfa';
|
||||
@@ -11,10 +10,10 @@ import * as AuthSession from '@app/api/auth/AuthSession';
|
||||
import * as AuthUtility from '@app/api/auth/AuthUtility';
|
||||
import {visibleWebAuthnCredentials} from '@app/api/auth/services/PasskeyRelyingParty';
|
||||
import {createPasswordResetToken, createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {UserRow} from '@app/api/database/types/UserTypes';
|
||||
import {emitAdminAction} from '@app/api/infrastructure/activity/AccountChangeEvents';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import {getInstanceConfigRepository} from '@app/api/middleware/ServiceSingletons';
|
||||
import {User} from '@app/api/models/User';
|
||||
import {clearNewConversationLimit} from '@app/api/user/NewConversationLimit';
|
||||
import {mapWebAuthnCredentialToResponse} from '@app/api/user/UserMappers';
|
||||
import {resolveAssignedTraits} from '@app/api/user/UserTraits';
|
||||
@@ -22,13 +21,7 @@ import {getIpAddressReverse, getLocationLabelFromIp} from '@app/api/utils/IpUtil
|
||||
import {resolveSessionClientInfo} from '@app/api/utils/SessionClientIdentity';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {
|
||||
ADMIN_PHONE_TOGGLE_CLEARABLE_FLAGS,
|
||||
DEFERRABLE_PHONE_FLAGS,
|
||||
DEFERRED_PHONE_ON_COMMUNITY_JOIN,
|
||||
PHONE_GATE_PROMOTED_FROM_DEFERRAL,
|
||||
UserFlags,
|
||||
} from '@fluxer/constants/src/UserConstants';
|
||||
import {UserFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
import {AccessDeniedError} from '@fluxer/errors/src/domains/core/AccessDeniedError';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
@@ -37,7 +30,6 @@ import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUn
|
||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||
import type {
|
||||
DeleteWebAuthnCredentialRequest,
|
||||
DisableForSuspiciousActivityRequest,
|
||||
DisableMfaRequest,
|
||||
ListWebAuthnCredentialsRequest,
|
||||
ResendVerificationEmailRequest,
|
||||
@@ -45,8 +37,6 @@ import type {
|
||||
SetUserAclsRequest,
|
||||
SetUserTraitsRequest,
|
||||
TerminateSessionsRequest,
|
||||
UpdateHasVerifiedPhoneRequest,
|
||||
UpdateSuspiciousActivityFlagsRequest,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
|
||||
import type {WebAuthnCredentialListResponse} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
|
||||
@@ -132,23 +122,21 @@ export class AdminUserSecurityService {
|
||||
}) {
|
||||
const {users: userRepository, cache: cacheService} = this.deps.apiContext.services;
|
||||
const {auditService, updatePropagator} = this.deps;
|
||||
const user = await userRepository.findUnique(userId);
|
||||
if (!user) {
|
||||
let previousFlags = 0n;
|
||||
const updatedUser = await userRepository.updateFlags(userId, (flags) => {
|
||||
previousFlags = flags;
|
||||
return applyBigIntFlagUpdates(flags, data.addFlags, data.removeFlags);
|
||||
});
|
||||
if (!updatedUser) {
|
||||
throw new UnknownUserError();
|
||||
}
|
||||
const newFlags = applyBigIntFlagUpdates(user.flags, data.addFlags, data.removeFlags);
|
||||
const updatedUser = await userRepository.patchUpsert(
|
||||
userId,
|
||||
{
|
||||
flags: newFlags,
|
||||
},
|
||||
user.toRow(),
|
||||
);
|
||||
const trusted = (newFlags & UserFlags.NOT_SUSPICIOUS) !== 0n && (user.flags & UserFlags.NOT_SUSPICIOUS) === 0n;
|
||||
if (trusted || (user.flags & ~newFlags) !== 0n) {
|
||||
const newFlags = updatedUser.flags;
|
||||
const trusted = (newFlags & UserFlags.LIMIT_EXEMPT) !== 0n && (previousFlags & UserFlags.LIMIT_EXEMPT) === 0n;
|
||||
if (trusted || (previousFlags & ~newFlags) !== 0n) {
|
||||
await clearNewConversationLimit(userId, {cache: cacheService});
|
||||
}
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser: user, updatedUser: updatedUser});
|
||||
const oldUser = new User({...updatedUser.toRow(), flags: previousFlags});
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser, updatedUser});
|
||||
await auditService.createAuditLog({
|
||||
adminUserId,
|
||||
targetType: 'user',
|
||||
@@ -399,150 +387,6 @@ export class AdminUserSecurityService {
|
||||
};
|
||||
}
|
||||
|
||||
async updateHasVerifiedPhone(
|
||||
data: UpdateHasVerifiedPhoneRequest,
|
||||
adminUserId: UserID,
|
||||
auditLogReason: string | null,
|
||||
acls: ReadonlySet<string>,
|
||||
) {
|
||||
const {users: userRepository, cache: cacheService} = this.deps.apiContext.services;
|
||||
const {auditService, updatePropagator} = this.deps;
|
||||
const userId = createUserID(data.user_id);
|
||||
const user = await userRepository.findUnique(userId);
|
||||
if (!user) {
|
||||
throw new UnknownUserError();
|
||||
}
|
||||
const phonePatch: Partial<UserRow> = {has_verified_phone: data.has_verified_phone};
|
||||
if (data.has_verified_phone) {
|
||||
const clearedFlags = (user.suspiciousActivityFlags ?? 0) & ~ADMIN_PHONE_TOGGLE_CLEARABLE_FLAGS;
|
||||
if (clearedFlags !== (user.suspiciousActivityFlags ?? 0)) {
|
||||
phonePatch.suspicious_activity_flags = clearedFlags;
|
||||
}
|
||||
}
|
||||
const updatedUser = await userRepository.patchUpsert(userId, phonePatch, user.toRow());
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser: user, updatedUser});
|
||||
await auditService.createAuditLog({
|
||||
adminUserId,
|
||||
targetType: 'user',
|
||||
targetId: BigInt(userId),
|
||||
action: 'update_has_verified_phone',
|
||||
auditLogReason,
|
||||
metadata: new Map(
|
||||
phonePatch.suspicious_activity_flags === undefined
|
||||
? [['has_verified_phone', String(data.has_verified_phone)]]
|
||||
: [
|
||||
['has_verified_phone', String(data.has_verified_phone)],
|
||||
['suspicious_activity_flags_before', String(user.suspiciousActivityFlags ?? 0)],
|
||||
['suspicious_activity_flags_after', String(phonePatch.suspicious_activity_flags)],
|
||||
],
|
||||
),
|
||||
});
|
||||
await emitAdminAction(adminUserId, userId, 'set_phone_verified');
|
||||
return {
|
||||
user: await mapUserToAdminResponse(updatedUser, cacheService, acls),
|
||||
};
|
||||
}
|
||||
|
||||
async updateSuspiciousActivityFlags(
|
||||
data: UpdateSuspiciousActivityFlagsRequest,
|
||||
adminUserId: UserID,
|
||||
auditLogReason: string | null,
|
||||
acls: ReadonlySet<string>,
|
||||
) {
|
||||
const {users: userRepository, cache: cacheService} = this.deps.apiContext.services;
|
||||
const {auditService, updatePropagator} = this.deps;
|
||||
const userId = createUserID(data.user_id);
|
||||
const user = await userRepository.findUnique(userId);
|
||||
if (!user) {
|
||||
throw new UnknownUserError();
|
||||
}
|
||||
const currentFlags = user.suspiciousActivityFlags ?? 0;
|
||||
const keepsDeferral =
|
||||
(currentFlags & DEFERRED_PHONE_ON_COMMUNITY_JOIN) !== 0 &&
|
||||
(data.flags & DEFERRABLE_PHONE_FLAGS) !== 0 &&
|
||||
(data.flags & DEFERRABLE_PHONE_FLAGS) === (currentFlags & DEFERRABLE_PHONE_FLAGS);
|
||||
const keepsPromotion =
|
||||
(currentFlags & PHONE_GATE_PROMOTED_FROM_DEFERRAL) !== 0 &&
|
||||
(data.flags & DEFERRABLE_PHONE_FLAGS) !== 0 &&
|
||||
(data.flags & DEFERRABLE_PHONE_FLAGS) === (currentFlags & DEFERRABLE_PHONE_FLAGS);
|
||||
const newFlags =
|
||||
(keepsDeferral ? data.flags | DEFERRED_PHONE_ON_COMMUNITY_JOIN : data.flags) |
|
||||
(keepsPromotion ? PHONE_GATE_PROMOTED_FROM_DEFERRAL : 0);
|
||||
const updatedUser = await userRepository.patchUpsert(
|
||||
userId,
|
||||
{
|
||||
suspicious_activity_flags: newFlags,
|
||||
},
|
||||
user.toRow(),
|
||||
);
|
||||
if ((currentFlags & ~newFlags) !== 0) {
|
||||
await clearNewConversationLimit(userId, {cache: cacheService});
|
||||
}
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser: user, updatedUser: updatedUser});
|
||||
await auditService.createAuditLog({
|
||||
adminUserId,
|
||||
targetType: 'user',
|
||||
targetId: BigInt(userId),
|
||||
action: 'update_suspicious_activity_flags',
|
||||
auditLogReason,
|
||||
metadata: new Map([['flags', data.flags.toString()]]),
|
||||
});
|
||||
await emitAdminAction(adminUserId, userId, 'set_suspicious_flags');
|
||||
return {
|
||||
user: await mapUserToAdminResponse(updatedUser, cacheService, acls),
|
||||
};
|
||||
}
|
||||
|
||||
async disableForSuspiciousActivity(
|
||||
data: DisableForSuspiciousActivityRequest,
|
||||
adminUserId: UserID,
|
||||
auditLogReason: string | null,
|
||||
acls: ReadonlySet<string>,
|
||||
) {
|
||||
const {users: userRepository, email: emailService, cache: cacheService} = this.deps.apiContext.services;
|
||||
const {auditService, updatePropagator} = this.deps;
|
||||
const userId = createUserID(data.user_id);
|
||||
const user = await userRepository.findUnique(userId);
|
||||
if (!user) {
|
||||
throw new UnknownUserError();
|
||||
}
|
||||
const updatedUser = await userRepository.patchUpsert(
|
||||
userId,
|
||||
{
|
||||
flags: user.flags | UserFlags.DISABLED_SUSPICIOUS_ACTIVITY,
|
||||
suspicious_activity_flags: data.flags,
|
||||
password_hash: null,
|
||||
},
|
||||
user.toRow(),
|
||||
);
|
||||
await AuthSession.terminateAllUserSessions(this.deps.apiContext, userId);
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser: user, updatedUser: updatedUser});
|
||||
const email = user.email;
|
||||
const notificationSent =
|
||||
data.notify_user && email
|
||||
? await trySendAdminNotification(
|
||||
() => emailService.sendAccountDisabledForSuspiciousActivityEmail(email, user.username, null, user.locale),
|
||||
{action: 'disable_suspicious_activity', targetId: userId.toString()},
|
||||
)
|
||||
: false;
|
||||
await auditService.createAuditLog({
|
||||
adminUserId,
|
||||
targetType: 'user',
|
||||
targetId: BigInt(userId),
|
||||
action: 'disable_suspicious_activity',
|
||||
auditLogReason,
|
||||
metadata: new Map([
|
||||
['flags', data.flags.toString()],
|
||||
['notify_user', data.notify_user ? 'true' : 'false'],
|
||||
['notification_sent', notificationSent ? 'true' : 'false'],
|
||||
]),
|
||||
});
|
||||
await emitAdminAction(adminUserId, userId, 'disable_suspicious');
|
||||
return {
|
||||
user: await mapUserToAdminResponse(updatedUser, cacheService, acls),
|
||||
};
|
||||
}
|
||||
|
||||
async listWebAuthnCredentials(
|
||||
data: ListWebAuthnCredentialsRequest,
|
||||
adminUserId: UserID,
|
||||
|
||||
@@ -48,6 +48,7 @@ export class AdminGuildMembershipService {
|
||||
guildId,
|
||||
sendJoinMessage,
|
||||
skipBanCheck: true,
|
||||
skipAccountLimitCheck: true,
|
||||
joinSourceType: JoinSourceTypes.ADMIN_FORCE_ADD,
|
||||
requestCache,
|
||||
initiatorId: adminUserId,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {createGuild} from '@app/api/channel/tests/ChannelTestUtils';
|
||||
import {getUserActivityBuffer} from '@app/api/middleware/ServiceSingletons';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@app/api/test/TestConstants';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import type {GuildAdminResponse} from '@fluxer/schema/src/domains/admin/AdminGuildSchemas';
|
||||
import type {UserAdminResponse} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
|
||||
import {afterEach, beforeEach, describe, expect, test} from 'vitest';
|
||||
@@ -20,21 +20,6 @@ interface GuildSearchResponse {
|
||||
total: number;
|
||||
}
|
||||
|
||||
async function setContactInfo(
|
||||
harness: ApiTestHarness,
|
||||
userId: string,
|
||||
data: {
|
||||
has_verified_phone?: boolean;
|
||||
email?: string | null;
|
||||
},
|
||||
): Promise<void> {
|
||||
await createBuilderWithoutAuth(harness)
|
||||
.post(`/test/users/${userId}/set-contact-info`)
|
||||
.body(data)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async function setLastActiveIp(harness: ApiTestHarness, token: string, ip: string): Promise<void> {
|
||||
await createBuilder(harness, `${token}`)
|
||||
.get('/users/@me')
|
||||
@@ -197,7 +182,6 @@ describe('Admin Search Field Coverage', () => {
|
||||
const email = `fields-check-${Date.now()}@fieldtest.example`;
|
||||
const username = `fieldcheck_${Date.now()}`;
|
||||
const targetUser = await createTestAccount(harness, {email, username});
|
||||
await setContactInfo(harness, targetUser.userId, {has_verified_phone: true});
|
||||
const result = await createBuilder<UserSearchResponse>(harness, `${admin.token}`)
|
||||
.get(`/admin/users?q=${encodeURIComponent(email)}&limit=10&offset=0`)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
@@ -207,7 +191,6 @@ describe('Admin Search Field Coverage', () => {
|
||||
expect(found!.id).toBe(targetUser.userId);
|
||||
expect(found!.username).toBe(username);
|
||||
expect(found!.email).toBe(email);
|
||||
expect(found!.has_verified_phone).toBe(true);
|
||||
expect(found!).toHaveProperty('discriminator');
|
||||
expect(found!).toHaveProperty('global_name');
|
||||
expect(found!).toHaveProperty('flags');
|
||||
@@ -215,7 +198,6 @@ describe('Admin Search Field Coverage', () => {
|
||||
expect(found!).toHaveProperty('email_bounced');
|
||||
expect(found!).toHaveProperty('premium_type');
|
||||
expect(found!).toHaveProperty('acls');
|
||||
expect(found!).toHaveProperty('suspicious_activity_flags');
|
||||
});
|
||||
});
|
||||
describe('user search isolation across fields', () => {
|
||||
|
||||
@@ -15,7 +15,6 @@ import {HTTP_STATUS} from '@app/api/test/TestConstants';
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {DeletionReasons} from '@fluxer/constants/src/Core';
|
||||
import {SuspiciousActivityFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {afterEach, beforeEach, describe, expect, test} from 'vitest';
|
||||
|
||||
const DELETION_TEMPLATES = [
|
||||
@@ -257,29 +256,6 @@ describe('Admin staff notifications', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('suspicious activity disable', () => {
|
||||
test.each([
|
||||
[undefined, true],
|
||||
[false, false],
|
||||
])('notify_user %s emails %s', async (notifyUser, expected) => {
|
||||
const target = await createTestAccount(harness);
|
||||
await clearTestEmails(harness);
|
||||
await createBuilder(harness, admin.token)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-disablement`)
|
||||
.body({
|
||||
flags: SuspiciousActivityFlags.REQUIRE_VERIFIED_PHONE,
|
||||
...(notifyUser === undefined ? {} : {notify_user: notifyUser}),
|
||||
})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
const types = (await emailsTo(target)).map((email) => email.type);
|
||||
expect(types.includes('account_disabled_suspicious')).toBe(expected);
|
||||
const metadata = await auditMetadata('disable_suspicious_activity', target.userId);
|
||||
expect(metadata?.get('notify_user')).toBe(expected ? 'true' : 'false');
|
||||
expect(metadata?.get('notification_sent')).toBe(expected ? 'true' : 'false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('report resolve', () => {
|
||||
async function fileReport(): Promise<{reporter: TestAccount; reportId: string}> {
|
||||
const reporter = await createTestAccount(harness);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHa
|
||||
import {HTTP_STATUS, TEST_CREDENTIALS} from '@app/api/test/TestConstants';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {SuspiciousActivityFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {UserFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
|
||||
|
||||
interface ChangeLogResponse {
|
||||
@@ -24,7 +24,7 @@ interface ChangeLogResponse {
|
||||
interface UserMutationResponse {
|
||||
user: {
|
||||
id: string;
|
||||
suspicious_activity_flags: number;
|
||||
flags: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,11 +33,10 @@ interface VerifyEmailMutationResponse {
|
||||
id: string;
|
||||
email_verified: boolean;
|
||||
email_bounced: boolean;
|
||||
suspicious_activity_flags: number;
|
||||
};
|
||||
}
|
||||
|
||||
describe('Admin User Change Log and Suspicious Flags', () => {
|
||||
describe('Admin User Change Log and Flags', () => {
|
||||
let harness: ApiTestHarness;
|
||||
beforeAll(async () => {
|
||||
harness = await createApiTestHarness();
|
||||
@@ -130,101 +129,23 @@ describe('Admin User Change Log and Suspicious Flags', () => {
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
describe('PUT /admin/users/{user_id}/suspicious-activity-flags', () => {
|
||||
test('sets suspicious activity flags', async () => {
|
||||
describe('PATCH /admin/users/{user_id}/flags account limitation', () => {
|
||||
test('sets and clears the account limitation', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.WILDCARD]);
|
||||
const target = await createTestAccount(harness);
|
||||
const flags = SuspiciousActivityFlags.REQUIRE_VERIFIED_EMAIL | SuspiciousActivityFlags.REQUIRE_VERIFIED_PHONE;
|
||||
const result = await createBuilder<UserMutationResponse>(harness, `${admin.token}`)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({flags})
|
||||
const limited = await createBuilder<UserMutationResponse>(harness, `${admin.token}`)
|
||||
.patch(`/admin/users/${target.userId}/flags`)
|
||||
.body({add_flags: [UserFlags.ACCOUNT_LIMITED.toString()]})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(result.user.suspicious_activity_flags).toBe(flags);
|
||||
});
|
||||
test('clears all suspicious activity flags by setting to zero', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.WILDCARD]);
|
||||
const target = await createTestAccount(harness);
|
||||
await createBuilder<UserMutationResponse>(harness, `${admin.token}`)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({flags: SuspiciousActivityFlags.REQUIRE_VERIFIED_EMAIL})
|
||||
expect(BigInt(limited.user.flags) & UserFlags.ACCOUNT_LIMITED).toBe(UserFlags.ACCOUNT_LIMITED);
|
||||
const lifted = await createBuilder<UserMutationResponse>(harness, `${admin.token}`)
|
||||
.patch(`/admin/users/${target.userId}/flags`)
|
||||
.body({remove_flags: [UserFlags.ACCOUNT_LIMITED.toString()]})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
const result = await createBuilder<UserMutationResponse>(harness, `${admin.token}`)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({flags: 0})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(result.user.suspicious_activity_flags).toBe(0);
|
||||
});
|
||||
test('rejects invalid user_id format', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.WILDCARD]);
|
||||
await createBuilder(harness, `${admin.token}`)
|
||||
.put('/admin/users/not-a-snowflake/suspicious-activity-flags')
|
||||
.body({flags: 1})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, 'INVALID_FORM_BODY')
|
||||
.execute();
|
||||
});
|
||||
test('rejects missing flags', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.WILDCARD]);
|
||||
const target = await createTestAccount(harness);
|
||||
await createBuilder(harness, `${admin.token}`)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, 'INVALID_FORM_BODY')
|
||||
.execute();
|
||||
});
|
||||
test('rejects negative flags', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.WILDCARD]);
|
||||
const target = await createTestAccount(harness);
|
||||
await createBuilder(harness, `${admin.token}`)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({flags: -1})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, 'INVALID_FORM_BODY')
|
||||
.execute();
|
||||
});
|
||||
test('rejects non-existent user', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.WILDCARD]);
|
||||
await createBuilder(harness, `${admin.token}`)
|
||||
.put('/admin/users/999999999999999999/suspicious-activity-flags')
|
||||
.body({flags: 1})
|
||||
.expect(HTTP_STATUS.NOT_FOUND)
|
||||
.execute();
|
||||
});
|
||||
test('requires USER_UPDATE_SUSPICIOUS_ACTIVITY ACL', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.USER_LOOKUP]);
|
||||
const target = await createTestAccount(harness);
|
||||
await createBuilder(harness, `${admin.token}`)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({flags: 1})
|
||||
.expect(HTTP_STATUS.FORBIDDEN)
|
||||
.execute();
|
||||
});
|
||||
test('updates flags multiple times in sequence', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.WILDCARD]);
|
||||
const target = await createTestAccount(harness);
|
||||
const result1 = await createBuilder<UserMutationResponse>(harness, `${admin.token}`)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({flags: SuspiciousActivityFlags.REQUIRE_VERIFIED_EMAIL})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(result1.user.suspicious_activity_flags).toBe(SuspiciousActivityFlags.REQUIRE_VERIFIED_EMAIL);
|
||||
const combined =
|
||||
SuspiciousActivityFlags.REQUIRE_VERIFIED_EMAIL | SuspiciousActivityFlags.REQUIRE_REVERIFIED_EMAIL;
|
||||
const result2 = await createBuilder<UserMutationResponse>(harness, `${admin.token}`)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({flags: combined})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(result2.user.suspicious_activity_flags).toBe(combined);
|
||||
expect(BigInt(lifted.user.flags) & UserFlags.ACCOUNT_LIMITED).toBe(0n);
|
||||
});
|
||||
});
|
||||
describe('DELETE /admin/users/{user_id}/mfa', () => {
|
||||
@@ -256,7 +177,7 @@ describe('Admin User Change Log and Suspicious Flags', () => {
|
||||
});
|
||||
});
|
||||
describe('PUT /admin/users/{user_id}/email-verification', () => {
|
||||
test('verifying email clears email_bounced and only email-related suspicious flags', async () => {
|
||||
test('verifying email clears email_bounced', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.WILDCARD]);
|
||||
const target = await createTestAccount(harness);
|
||||
@@ -265,7 +186,6 @@ describe('Admin User Change Log and Suspicious Flags', () => {
|
||||
.body({
|
||||
email_bounced: true,
|
||||
email_verified: false,
|
||||
suspicious_activity_flag_names: ['REQUIRE_REVERIFIED_EMAIL', 'REQUIRE_VERIFIED_PHONE'],
|
||||
})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
@@ -275,7 +195,6 @@ describe('Admin User Change Log and Suspicious Flags', () => {
|
||||
.execute();
|
||||
expect(result.user.email_verified).toBe(true);
|
||||
expect(result.user.email_bounced).toBe(false);
|
||||
expect(result.user.suspicious_activity_flags).toBe(SuspiciousActivityFlags.REQUIRE_VERIFIED_PHONE);
|
||||
});
|
||||
});
|
||||
describe('POST /admin/users/{user_id}/verification-email', () => {
|
||||
|
||||
@@ -25,7 +25,7 @@ import type {User} from '@app/api/models/User';
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {DeletionReasons} from '@fluxer/constants/src/Core';
|
||||
import {PremiumFlags, SuspiciousActivityFlags, UserFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {PremiumFlags, UserFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {expect} from 'vitest';
|
||||
|
||||
async function loadUser(account: TestAccount): Promise<User> {
|
||||
@@ -533,34 +533,6 @@ export const UserWriteAdminAuditCases: ReadonlyArray<AdminAuditCoverageCase> = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
route: '/admin/users/:user_id/phone-verification',
|
||||
async prepare(context) {
|
||||
const target = await createTestAccount(context.harness);
|
||||
const flags = SuspiciousActivityFlags.REQUIRE_VERIFIED_EMAIL | SuspiciousActivityFlags.REQUIRE_VERIFIED_PHONE;
|
||||
await adminBuilder(context)
|
||||
.put(`/admin/users/${target.userId}/suspicious-activity-flags`)
|
||||
.body({flags})
|
||||
.execute();
|
||||
return {
|
||||
request: {
|
||||
path: `/admin/users/${target.userId}/phone-verification`,
|
||||
body: {has_verified_phone: true},
|
||||
},
|
||||
expected: {
|
||||
action: 'update_has_verified_phone',
|
||||
targetType: 'user',
|
||||
targetId: target.userId,
|
||||
metadata: {
|
||||
has_verified_phone: 'true',
|
||||
suspicious_activity_flags_before: flags.toString(),
|
||||
suspicious_activity_flags_after: SuspiciousActivityFlags.REQUIRE_VERIFIED_EMAIL.toString(),
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PATCH',
|
||||
route: '/admin/users/:user_id/date-of-birth',
|
||||
@@ -577,38 +549,4 @@ export const UserWriteAdminAuditCases: ReadonlyArray<AdminAuditCoverageCase> = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
route: '/admin/users/:user_id/suspicious-activity-flags',
|
||||
async prepare({harness}) {
|
||||
const target = await createTestAccount(harness);
|
||||
const flags = SuspiciousActivityFlags.REQUIRE_VERIFIED_EMAIL | SuspiciousActivityFlags.REQUIRE_VERIFIED_PHONE;
|
||||
return {
|
||||
request: {path: `/admin/users/${target.userId}/suspicious-activity-flags`, body: {flags}},
|
||||
expected: {
|
||||
action: 'update_suspicious_activity_flags',
|
||||
targetType: 'user',
|
||||
targetId: target.userId,
|
||||
metadata: {flags: flags.toString()},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
route: '/admin/users/:user_id/suspicious-activity-disablement',
|
||||
async prepare({harness}) {
|
||||
const target = await createTestAccount(harness);
|
||||
const flags = SuspiciousActivityFlags.REQUIRE_VERIFIED_PHONE;
|
||||
return {
|
||||
request: {path: `/admin/users/${target.userId}/suspicious-activity-disablement`, body: {flags}},
|
||||
expected: {
|
||||
action: 'disable_suspicious_activity',
|
||||
targetType: 'user',
|
||||
targetId: target.userId,
|
||||
metadata: {flags: flags.toString(), notify_user: 'true', notification_sent: 'true'},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
Reference in new issue
Block a user