It mostly works. Issues: Error on signup, and missing icons in signup page and admin page
This commit is contained in:
parent
f2eafaab73
commit
49650a1382
19 changed files with 212 additions and 13 deletions
22
packages/backend/migration/1736372582304-approval.js
Normal file
22
packages/backend/migration/1736372582304-approval.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class ApprovalSignup1697580470000 {
|
||||
name = 'ApprovalSignup1697580470000'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "approvalRequiredForSignup" boolean DEFAULT false NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "approved" boolean DEFAULT false NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "signupReason" character varying(1000) NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "user_pending" ADD "reason" character varying(1000) NULL`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "approvalRequiredForSignup"`);
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "approved"`);
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "signupReason"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_pending" DROP COLUMN "reason"`);
|
||||
}
|
||||
}
|
|
@ -58,9 +58,10 @@ export class SignupService {
|
|||
password?: string | null;
|
||||
passwordHash?: MiUserProfile['password'] | null;
|
||||
host?: string | null;
|
||||
reason?: string | null;
|
||||
ignorePreservedUsernames?: boolean;
|
||||
}) {
|
||||
const { username, password, passwordHash, host } = opts;
|
||||
const { username, password, passwordHash, host, reason } = opts;
|
||||
let hash = passwordHash;
|
||||
|
||||
// Validate username
|
||||
|
@ -93,9 +94,9 @@ export class SignupService {
|
|||
}
|
||||
|
||||
const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
|
||||
const instance = await this.metaService.fetch(true);
|
||||
|
||||
if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
|
||||
const instance = await this.metaService.fetch(true);
|
||||
const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||
if (isPreserved) {
|
||||
throw new Error('USED_USERNAME');
|
||||
|
@ -128,6 +129,10 @@ export class SignupService {
|
|||
try {
|
||||
let account!: MiUser;
|
||||
|
||||
let defaultApproval = false;
|
||||
|
||||
if (!instance.approvalRequiredForSignup) defaultApproval = true;
|
||||
|
||||
// Start transaction
|
||||
await this.db.transaction(async transactionalEntityManager => {
|
||||
const exist = await transactionalEntityManager.findOneBy(MiUser, {
|
||||
|
@ -144,6 +149,8 @@ export class SignupService {
|
|||
host: host ? this.utilityService.normalizeHost(host) : null,
|
||||
token: secret,
|
||||
isRoot: isTheFirstUser,
|
||||
approved: defaultApproval,
|
||||
signupReason: reason,
|
||||
}));
|
||||
|
||||
await transactionalEntityManager.save(new MiUserKeypair({
|
||||
|
|
|
@ -328,8 +328,8 @@ export class ApPersonService implements OnModuleInit {
|
|||
this.logger.error('error occurred while fetching following/followers collection', { error: err });
|
||||
}
|
||||
return 'private';
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
|
||||
|
@ -378,6 +378,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
alsoKnownAs: person.alsoKnownAs,
|
||||
isExplorable: person.discoverable,
|
||||
username: person.preferredUsername,
|
||||
approved: true,
|
||||
usernameLower: person.preferredUsername?.toLowerCase(),
|
||||
host,
|
||||
inbox: person.inbox,
|
||||
|
@ -526,8 +527,8 @@ export class ApPersonService implements OnModuleInit {
|
|||
return undefined;
|
||||
}
|
||||
return 'private';
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
|
||||
|
|
|
@ -68,6 +68,7 @@ export class MetaEntityService {
|
|||
privacyPolicyUrl: instance.privacyPolicyUrl,
|
||||
disableRegistration: instance.disableRegistration,
|
||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||
approvalRequiredForSignup: instance.approvalRequiredForSignup,
|
||||
enableHcaptcha: instance.enableHcaptcha,
|
||||
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
||||
enableMcaptcha: instance.enableMcaptcha,
|
||||
|
@ -140,6 +141,7 @@ export class MetaEntityService {
|
|||
globalTimeline: instance.policies.gtlAvailable,
|
||||
registration: !instance.disableRegistration,
|
||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||
approvalRequiredForSignup: instance.approvalRequiredForSignup,
|
||||
hCaptcha: instance.enableHcaptcha,
|
||||
hcaptcha: instance.enableHcaptcha,
|
||||
mCaptcha: instance.enableMcaptcha,
|
||||
|
|
|
@ -614,6 +614,8 @@ export class UserEntityService implements OnModuleInit {
|
|||
...(opts.includeSecrets ? {
|
||||
email: profile!.email,
|
||||
emailVerified: profile!.emailVerified,
|
||||
approved: user.approved,
|
||||
signupReason: user.signupReason,
|
||||
securityKeysList: profile!.twoFactorEnabled
|
||||
? this.userSecurityKeysRepository.find({
|
||||
where: {
|
||||
|
@ -704,7 +706,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
}
|
||||
}
|
||||
|
||||
return (await Promise.allSettled(_users.map(u => this.pack(u, me, { ...options, userProfile: profilesMap?.get(u.id), userRelations: userRelations, userMemos: userMemos, pinNotes: pinNotes }))))
|
||||
return (await Promise.allSettled(_users.map(u => this.pack(u, me, { ...options, userProfile: profilesMap.get(u.id), userRelations: userRelations, userMemos: userMemos, pinNotes: pinNotes }))))
|
||||
.filter(result => result.status === 'fulfilled')
|
||||
.map(result => (result as PromiseFulfilledResult<Packed<S>>).value);
|
||||
}
|
||||
|
|
|
@ -184,6 +184,11 @@ export class MiMeta {
|
|||
})
|
||||
public emailRequiredForSignup: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public approvalRequiredForSignup: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
|
|
|
@ -261,6 +261,16 @@ export class MiUser {
|
|||
})
|
||||
public token: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public approved: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1000, nullable: true,
|
||||
})
|
||||
public signupReason: string | null;
|
||||
|
||||
constructor(data: Partial<MiUser>) {
|
||||
if (data == null) return;
|
||||
|
||||
|
|
|
@ -38,4 +38,9 @@ export class MiUserPending {
|
|||
length: 128,
|
||||
})
|
||||
public password: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1000,
|
||||
})
|
||||
public reason: string;
|
||||
}
|
||||
|
|
|
@ -75,6 +75,10 @@ export const packedMetaLiteSchema = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
approvalRequiredForSignup: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableHcaptcha: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@ -275,6 +279,10 @@ export const packedMetaDetailedOnlySchema = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
approvalRequiredForSignup: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
localTimeline: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
@ -82,6 +82,7 @@ import * as ep___admin_showUserAccountMoveLogs from './endpoints/admin/show-user
|
|||
import * as ep___admin_showUser from './endpoints/admin/show-user.js';
|
||||
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
||||
import * as ep___admin_approveUser from './endpoints/admin/approve-user.js';
|
||||
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
|
||||
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
|
||||
import * as ep___admin_updateUserName from './endpoints/admin/update-user-name.js';
|
||||
|
@ -477,6 +478,7 @@ const $admin_showUserAccountMoveLogs: Provider = { provide: 'ep:admin/show-user-
|
|||
const $admin_showUser: Provider = { provide: 'ep:admin/show-user', useClass: ep___admin_showUser.default };
|
||||
const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default };
|
||||
const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
|
||||
const $admin_approveUser: Provider = { provide: 'ep:admin/approve-user', useClass: ep___admin_approveUser.default };
|
||||
const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
|
||||
const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
|
||||
const $admin_updateUserName: Provider = { provide: 'ep:admin/update-user-name', useClass: ep___admin_updateUserName.default };
|
||||
|
@ -876,6 +878,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$admin_showUser,
|
||||
$admin_showUsers,
|
||||
$admin_suspendUser,
|
||||
$admin_approveUser,
|
||||
$admin_unsuspendUser,
|
||||
$admin_updateMeta,
|
||||
$admin_updateUserName,
|
||||
|
@ -1269,6 +1272,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$admin_showUser,
|
||||
$admin_showUsers,
|
||||
$admin_suspendUser,
|
||||
$admin_approveUser,
|
||||
$admin_unsuspendUser,
|
||||
$admin_updateMeta,
|
||||
$admin_updateUserName,
|
||||
|
|
|
@ -79,6 +79,8 @@ export class SigninApiService {
|
|||
reply.header('Access-Control-Allow-Origin', this.config.url);
|
||||
reply.header('Access-Control-Allow-Credentials', 'true');
|
||||
|
||||
const instance = await this.metaService.fetch(true);
|
||||
|
||||
const body = request.body;
|
||||
const username = body['username'];
|
||||
const password = body['password'];
|
||||
|
@ -132,13 +134,13 @@ export class SigninApiService {
|
|||
emailVerified: true,
|
||||
user: {
|
||||
host: IsNull(),
|
||||
}
|
||||
},
|
||||
} : {
|
||||
user: {
|
||||
usernameLower: username.toLowerCase(),
|
||||
host: IsNull(),
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
const user = (profile?.user as MiLocalUser) ?? null;
|
||||
|
||||
|
@ -163,6 +165,17 @@ export class SigninApiService {
|
|||
});
|
||||
}
|
||||
|
||||
if (!user.approved && instance.approvalRequiredForSignup) {
|
||||
reply.code(403);
|
||||
return {
|
||||
error: {
|
||||
message: 'The account has not been approved by an admin yet. Try again later.',
|
||||
code: 'NOT_APPROVED',
|
||||
id: '22d05606-fbcf-421a-a2db-b32241faft1b',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(password, profile.password!);
|
||||
|
||||
|
@ -207,6 +220,7 @@ export class SigninApiService {
|
|||
}
|
||||
|
||||
if (same) {
|
||||
if (!instance.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
|
||||
logger.info('Successfully signed in with password.');
|
||||
return this.signinService.signin(request, reply, user);
|
||||
} else {
|
||||
|
@ -234,6 +248,7 @@ export class SigninApiService {
|
|||
});
|
||||
}
|
||||
|
||||
if (!instance.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
|
||||
logger.info('Successfully signed in with password and two-factor token.');
|
||||
return this.signinService.signin(request, reply, user);
|
||||
} else if (body.credential) {
|
||||
|
@ -247,6 +262,7 @@ export class SigninApiService {
|
|||
const authorized = await this.webAuthnService.verifyAuthentication(user.id, body.credential);
|
||||
|
||||
if (authorized) {
|
||||
if (!instance.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
|
||||
logger.info('Successfully signed in with WebAuthn authentication.');
|
||||
return this.signinService.signin(request, reply, user);
|
||||
} else {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { IsNull } from 'typeorm';
|
||||
|
@ -21,8 +22,8 @@ import { bindThis } from '@/decorators.js';
|
|||
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
import instance from './endpoints/charts/instance.js';
|
||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
@Injectable()
|
||||
export class SignupApiService {
|
||||
|
@ -65,6 +66,7 @@ export class SignupApiService {
|
|||
host?: string;
|
||||
invitationCode?: string;
|
||||
emailAddress?: string;
|
||||
reason?: string;
|
||||
'hcaptcha-response'?: string;
|
||||
'g-recaptcha-response'?: string;
|
||||
'turnstile-response'?: string;
|
||||
|
@ -117,6 +119,7 @@ export class SignupApiService {
|
|||
const password = body['password'];
|
||||
const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] ?? null) : null;
|
||||
const invitationCode = body['invitationCode'];
|
||||
const reason = body['reason'];
|
||||
const emailAddress = body['emailAddress'];
|
||||
|
||||
if (instance.emailRequiredForSignup) {
|
||||
|
@ -134,6 +137,13 @@ export class SignupApiService {
|
|||
}
|
||||
}
|
||||
|
||||
if (instance.approvalRequiredForSignup) {
|
||||
if (reason == null || typeof reason !== 'string') {
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let ticket: MiRegistrationTicket | null = null;
|
||||
|
||||
if (instance.disableRegistration) {
|
||||
|
@ -211,6 +221,7 @@ export class SignupApiService {
|
|||
email: emailAddress!,
|
||||
username: username,
|
||||
password: hash,
|
||||
reason: reason,
|
||||
}).then(x => this.userPendingsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
const link = `${this.config.url}/signup-complete/${code}`;
|
||||
|
@ -231,6 +242,17 @@ export class SignupApiService {
|
|||
reply.code(204);
|
||||
return;
|
||||
} else {
|
||||
if (instance.approvalRequiredForSignup) {
|
||||
await this.signupService.signup({
|
||||
username, password, host, reason,
|
||||
});
|
||||
|
||||
if (emailAddress) {
|
||||
this.emailService.sendEmail(emailAddress, 'Approval pending',
|
||||
'Congratulations! Your account is now pending approval. You will get notified when you have been accepted.',
|
||||
'Congratulations! Your account is now pending approval. You will get notified when you have been accepted.');
|
||||
}
|
||||
}
|
||||
try {
|
||||
const { account, secret } = await this.signupService.signup({
|
||||
username, password, host,
|
||||
|
@ -272,6 +294,8 @@ export class SignupApiService {
|
|||
|
||||
const code = body['code'];
|
||||
|
||||
const instance = await this.metaService.fetch(true);
|
||||
|
||||
try {
|
||||
const pendingUser = await this.userPendingsRepository.findOneByOrFail({ code });
|
||||
|
||||
|
@ -283,6 +307,7 @@ export class SignupApiService {
|
|||
const { account, secret } = await this.signupService.signup({
|
||||
username: pendingUser.username,
|
||||
passwordHash: pendingUser.password,
|
||||
reason: pendingUser.reason,
|
||||
});
|
||||
|
||||
this.userPendingsRepository.delete({
|
||||
|
@ -306,6 +331,15 @@ export class SignupApiService {
|
|||
});
|
||||
}
|
||||
|
||||
if (instance.approvalRequiredForSignup) {
|
||||
if (pendingUser.email) {
|
||||
this.emailService.sendEmail(pendingUser.email, 'Approval pending',
|
||||
'Congratulations! Your account is now pending approval. You will get notified when you have been accepted.',
|
||||
'Congratulations! Your account is now pending approval. You will get notified when you have been accepted.');
|
||||
}
|
||||
return { pendingApproval: true };
|
||||
}
|
||||
|
||||
logger.info('Successfully created user.', { userId: account.id });
|
||||
return this.signinService.signin(request, reply, account as MiLocalUser);
|
||||
} catch (err) {
|
||||
|
|
|
@ -82,6 +82,7 @@ import * as ep___admin_showUserAccountMoveLogs from './endpoints/admin/show-user
|
|||
import * as ep___admin_showUser from './endpoints/admin/show-user.js';
|
||||
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
||||
import * as ep___admin_approveUser from './endpoints/admin/approve-user.js';
|
||||
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
|
||||
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
|
||||
import * as ep___admin_updateUserName from './endpoints/admin/update-user-name.js';
|
||||
|
@ -475,6 +476,7 @@ const eps = [
|
|||
['admin/show-user', ep___admin_showUser],
|
||||
['admin/show-users', ep___admin_showUsers],
|
||||
['admin/suspend-user', ep___admin_suspendUser],
|
||||
['admin/approve-user', ep___admin_approveUser],
|
||||
['admin/unsuspend-user', ep___admin_unsuspendUser],
|
||||
['admin/update-meta', ep___admin_updateMeta],
|
||||
['admin/update-user-name', ep___admin_updateUserName],
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { EmailService } from '@/core/EmailService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:approve-user',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private moderationLogService: ModerationLogService,
|
||||
private emailService: EmailService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneBy({ userId: ps.userId });
|
||||
|
||||
await this.usersRepository.update(user.id, {
|
||||
approved: true,
|
||||
});
|
||||
|
||||
if (profile?.email) {
|
||||
this.emailService.sendEmail(profile.email, 'Account Approved',
|
||||
'Your Account has been approved have fun socializing!',
|
||||
'Your Account has been approved have fun socializing!');
|
||||
}
|
||||
|
||||
this.moderationLogService.log(me, 'approve', {
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -33,6 +33,10 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
approvalRequiredForSignup: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableHcaptcha: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@ -552,6 +556,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
privacyPolicyUrl: instance.privacyPolicyUrl,
|
||||
disableRegistration: instance.disableRegistration,
|
||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||
approvalRequiredForSignup: instance.approvalRequiredForSignup,
|
||||
enableHcaptcha: instance.enableHcaptcha,
|
||||
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
||||
enableMcaptcha: instance.enableMcaptcha,
|
||||
|
|
|
@ -180,7 +180,7 @@ export const meta = {
|
|||
memo: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -240,6 +240,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
return {
|
||||
email: profile.email,
|
||||
emailVerified: profile.emailVerified,
|
||||
approved: user.approved,
|
||||
signupReason: user.signupReason,
|
||||
autoAcceptFollowed: profile.autoAcceptFollowed,
|
||||
noCrawle: profile.noCrawle,
|
||||
preventAiLearning: profile.preventAiLearning,
|
||||
|
|
|
@ -35,7 +35,7 @@ export const paramDef = {
|
|||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
offset: { type: 'integer', default: 0 },
|
||||
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt', '+lastActiveDate', '-lastActiveDate'] },
|
||||
state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'suspended'], default: 'all' },
|
||||
state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'suspended', 'approved'], default: 'all' },
|
||||
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' },
|
||||
username: { type: 'string', nullable: true, default: null },
|
||||
hostname: {
|
||||
|
@ -64,6 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
case 'available': query.where('user.isSuspended = FALSE'); break;
|
||||
case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break;
|
||||
case 'suspended': query.where('user.isSuspended = TRUE'); break;
|
||||
case 'approved': query.where('user.approved = FALSE'); break;
|
||||
case 'admin': {
|
||||
const adminIds = await this.roleService.getAdministratorIds();
|
||||
if (adminIds.length === 0) return [];
|
||||
|
|
|
@ -65,6 +65,7 @@ export const paramDef = {
|
|||
cacheRemoteFiles: { type: 'boolean' },
|
||||
cacheRemoteSensitiveFiles: { type: 'boolean' },
|
||||
emailRequiredForSignup: { type: 'boolean' },
|
||||
approvalRequiredForSignup: { type: 'boolean' },
|
||||
enableHcaptcha: { type: 'boolean' },
|
||||
hcaptchaSiteKey: { type: 'string', nullable: true },
|
||||
hcaptchaSecretKey: { type: 'string', nullable: true },
|
||||
|
@ -323,6 +324,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
set.emailRequiredForSignup = ps.emailRequiredForSignup;
|
||||
}
|
||||
|
||||
if (ps.approvalRequiredForSignup !== undefined) {
|
||||
set.approvalRequiredForSignup = ps.approvalRequiredForSignup;
|
||||
}
|
||||
|
||||
if (ps.enableHcaptcha !== undefined) {
|
||||
set.enableHcaptcha = ps.enableHcaptcha;
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ export const followersVisibilities = ['public', 'followers', 'private'] as const
|
|||
export const moderationLogTypes = [
|
||||
'updateServerSettings',
|
||||
'suspend',
|
||||
'approve',
|
||||
'unsuspend',
|
||||
'updateUserName',
|
||||
'updateUserNote',
|
||||
|
@ -111,6 +112,11 @@ export type ModerationLogPayloads = {
|
|||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
approve: {
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
unsuspend: {
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
|
|
Loading…
Reference in a new issue