summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api')
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts4
-rw-r--r--packages/backend/src/server/api/SigninApiService.ts20
-rw-r--r--packages/backend/src/server/api/SignupApiService.ts32
-rw-r--r--packages/backend/src/server/api/endpoints.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/approve-user.ts61
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/admin/show-user.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/meta.ts5
9 files changed, 136 insertions, 0 deletions
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 9aa75b0af8..079040b85f 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -62,6 +62,7 @@ import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderatio
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_deleteAccount from './endpoints/admin/delete-account.js';
@@ -415,6 +416,7 @@ const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation
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_deleteAccount: Provider = { provide: 'ep:admin/delete-account', useClass: ep___admin_deleteAccount.default };
@@ -772,6 +774,7 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
$admin_showUser,
$admin_showUsers,
$admin_suspendUser,
+ $admin_approveUser,
$admin_unsuspendUser,
$admin_updateMeta,
$admin_deleteAccount,
@@ -1123,6 +1126,7 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
$admin_showUser,
$admin_showUsers,
$admin_suspendUser,
+ $admin_approveUser,
$admin_unsuspendUser,
$admin_updateMeta,
$admin_deleteAccount,
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index 2d4de605ea..fd247df22a 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -21,6 +21,7 @@ import { IdService } from '@/core/IdService.js';
import { bindThis } from '@/decorators.js';
import { WebAuthnService } from '@/core/WebAuthnService.js';
import { UserAuthService } from '@/core/UserAuthService.js';
+import { MetaService } from '@/core/MetaService.js';
import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js';
import type { AuthenticationResponseJSON } from '@simplewebauthn/typescript-types';
@@ -46,6 +47,7 @@ export class SigninApiService {
private signinService: SigninService,
private userAuthService: UserAuthService,
private webAuthnService: WebAuthnService,
+ private metaService: MetaService,
) {
}
@@ -64,6 +66,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'];
@@ -123,6 +127,17 @@ export class SigninApiService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+ 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 argon2.verify(profile.password!, password) || bcrypt.compareSync(password, profile.password!);
@@ -147,6 +162,8 @@ export class SigninApiService {
password: newHash
});
}
+ if (!instance.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
+
return this.signinService.signin(request, reply, user);
} else {
return await fail(403, {
@@ -176,6 +193,8 @@ export class SigninApiService {
});
}
+ if (!instance.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
+
return this.signinService.signin(request, reply, user);
} else if (body.credential) {
if (!same && !profile.usePasswordLessLogin) {
@@ -187,6 +206,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 });
return this.signinService.signin(request, reply, user);
} else {
return await fail(403, {
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 02e5cd4fdf..53f770e172 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -22,6 +22,7 @@ import { bindThis } from '@/decorators.js';
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
import { SigninService } from './SigninService.js';
import type { FastifyRequest, FastifyReply } from 'fastify';
+import instance from './endpoints/charts/instance.js';
@Injectable()
export class SignupApiService {
@@ -63,6 +64,7 @@ export class SignupApiService {
host?: string;
invitationCode?: string;
emailAddress?: string;
+ reason?: string;
'hcaptcha-response'?: string;
'g-recaptcha-response'?: string;
'turnstile-response'?: string;
@@ -100,6 +102,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) {
@@ -115,6 +118,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) {
@@ -170,6 +180,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}`;
@@ -187,6 +198,19 @@ 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.');
+ }
+
+ reply.code(204);
+ return;
} else {
try {
const { account, secret } = await this.signupService.signup({
@@ -222,12 +246,15 @@ export class SignupApiService {
const code = body['code'];
+ const instance = await this.metaService.fetch(true);
+
try {
const pendingUser = await this.userPendingsRepository.findOneByOrFail({ code });
const { account, secret } = await this.signupService.signup({
username: pendingUser.username,
passwordHash: pendingUser.password,
+ reason: pendingUser.reason,
});
this.userPendingsRepository.delete({
@@ -250,6 +277,11 @@ export class SignupApiService {
pendingUserId: null,
});
}
+
+ if (instance.approvalRequiredForSignup) {
+ reply.code(204);
+ return;
+ }
return this.signinService.signin(request, reply, account as MiLocalUser);
} catch (err) {
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index d7f5611f55..e978093364 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -62,6 +62,7 @@ import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderatio
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_deleteAccount from './endpoints/admin/delete-account.js';
@@ -413,6 +414,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/delete-account', ep___admin_deleteAccount],
diff --git a/packages/backend/src/server/api/endpoints/admin/approve-user.ts b/packages/backend/src/server/api/endpoints/admin/approve-user.ts
new file mode 100644
index 0000000000..0ea656ddaf
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/approve-user.ts
@@ -0,0 +1,61 @@
+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,
+} 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,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 88725ddbbf..763c4ea807 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -32,6 +32,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ approvalRequiredForSignup: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
enableHcaptcha: {
type: 'boolean',
optional: false, nullable: false,
@@ -353,6 +357,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,
enableRecaptcha: instance.enableRecaptcha,
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index f550c4fd28..5ad90f48b4 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -73,6 +73,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,
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 40cc50fe7c..be14037a76 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -59,6 +59,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 },
@@ -249,6 +250,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;
}
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index d37919b474..dbd72763bf 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -100,6 +100,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ approvalRequiredForSignup: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
enableHcaptcha: {
type: 'boolean',
optional: false, nullable: false,
@@ -308,6 +312,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,
enableRecaptcha: instance.enableRecaptcha,