server: refactor ffVisibility checks into function

This commit is contained in:
Johann150 2023-05-20 00:22:38 +02:00
parent eafacbba99
commit ed9f2f4900
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
5 changed files with 46 additions and 68 deletions

View file

@ -230,6 +230,34 @@ export const UserRepository = db.getRepository(User).extend({
return `${config.url}/identicon/${userId}`;
},
/**
* Determines whether the followers/following of user `user` are visibile to user `me`.
*/
async areFollowersVisibleTo(user: User, me: { id: User['id'] } | null | undefined): Promise<boolean> {
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
switch (profile.ffVisibility) {
case 'public':
return true;
case 'followers':
if (me == null) {
return false;
} else if (me.id === user.id) {
return true;
} else {
return await Followings.count({
where: {
followerId: me.id,
followeeId: user.id,
},
take: 1,
}).then(n => n > 0);
}
case 'private':
return me?.id === user.id;
}
}
async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
src: User['id'] | User,
me?: { id: User['id'] } | null | undefined,
@ -270,15 +298,13 @@ export const UserRepository = db.getRepository(User).extend({
.getMany() : [];
const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null;
const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
(profile.ffVisibility === 'followers') && relation?.isFollowing ? user.followingCount :
null;
const ffVisible = await this.areFollowersVisibleTo(user, me);
const followersCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followersCount :
(profile.ffVisibility === 'followers') && relation?.isFollowing ? user.followersCount :
null;
const followingCount = opts.detail ? null :
ffVisible ? user.followingCount : null;
const followersCount = opts.detail ? null :
ffVisible ? user.followersCount : null;
const packed = {
id: user.id,

View file

@ -6,7 +6,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
import { Users, Followings, UserProfiles } from '@/models/index.js';
import { Users, Followings } from '@/models/index.js';
import { Following } from '@/models/entities/following.js';
import { setResponseType } from '../activitypub.js';
@ -31,19 +31,12 @@ export default async (ctx: Router.RouterContext) => {
return;
}
//#region Check ff visibility
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.ffVisibility === 'private') {
ctx.status = 403;
ctx.set('Cache-Control', 'public, max-age=30');
return;
} else if (profile.ffVisibility === 'followers') {
const ffVisible = await Users.areFollowersVisibleTo(user, null);
if (!ffVisible) {
ctx.status = 403;
ctx.set('Cache-Control', 'public, max-age=30');
return;
}
//#endregion
const limit = 10;
const partOf = `${config.url}/users/${userId}/followers`;

View file

@ -6,7 +6,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
import { Users, Followings, UserProfiles } from '@/models/index.js';
import { Users, Followings } from '@/models/index.js';
import { Following } from '@/models/entities/following.js';
import { setResponseType } from '../activitypub.js';
@ -31,19 +31,12 @@ export default async (ctx: Router.RouterContext) => {
return;
}
//#region Check ff visibility
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.ffVisibility === 'private') {
ctx.status = 403;
ctx.set('Cache-Control', 'public, max-age=30');
return;
} else if (profile.ffVisibility === 'followers') {
const ffVisible = await Users.areFollowersVisibleTo(user, null);
if (!ffVisible) {
ctx.status = 403;
ctx.set('Cache-Control', 'public, max-age=30');
return;
}
//#endregion
const limit = 10;
const partOf = `${config.url}/users/${userId}/following`;

View file

@ -1,5 +1,5 @@
import { IsNull } from 'typeorm';
import { Users, Followings, UserProfiles } from '@/models/index.js';
import { Users, Followings } from '@/models/index.js';
import { toPunyNullable } from '@/misc/convert-host.js';
import define from '@/server/api/define.js';
import { ApiError } from '@/server/api/error.js';
@ -61,25 +61,8 @@ export default define(meta, paramDef, async (ps, me) => {
if (user == null) throw new ApiError('NO_SUCH_USER');
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.ffVisibility === 'private') {
if (me == null || (me.id !== user.id)) {
throw new ApiError('ACCESS_DENIED');
}
} else if (profile.ffVisibility === 'followers') {
if (me == null) {
throw new ApiError('ACCESS_DENIED');
} else if (me.id !== user.id) {
const following = await Followings.countBy({
followeeId: user.id,
followerId: me.id,
});
if (!following) {
throw new ApiError('ACCESS_DENIED');
}
}
}
const ffVisible = await Users.areFollowersVisibleTo(user, me);
if (!ffVisible) throw new ApiError('ACCESS_DENIED');
const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
.andWhere('following.followeeId = :userId', { userId: user.id })

View file

@ -1,5 +1,5 @@
import { IsNull } from 'typeorm';
import { Users, Followings, UserProfiles } from '@/models/index.js';
import { Users, Followings } from '@/models/index.js';
import { toPunyNullable } from '@/misc/convert-host.js';
import define from '@/server/api/define.js';
import { ApiError } from '@/server/api/error.js';
@ -61,25 +61,8 @@ export default define(meta, paramDef, async (ps, me) => {
if (user == null) throw new ApiError('NO_SUCH_USER');
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.ffVisibility === 'private') {
if (me == null || (me.id !== user.id)) {
throw new ApiError('ACCESS_DENIED');
}
} else if (profile.ffVisibility === 'followers') {
if (me == null) {
throw new ApiError('ACCESS_DENIED');
} else if (me.id !== user.id) {
const following = await Followings.countBy({
followeeId: user.id,
followerId: me.id,
});
if (!following) {
throw new ApiError('ACCESS_DENIED');
}
}
}
const ffVisible = await Users.areFollowersVisibleTo(user, me);
if (!ffVisible) throw new ApiError('ACCESS_DENIED');
const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
.andWhere('following.followerId = :userId', { userId: user.id })