Skip to content

Commit

Permalink
feat: Add support for mCaptcha (#12905)
Browse files Browse the repository at this point in the history
* feat: Add support for mCaptcha

* fix: Fix docker compose configuration

* chore(frontend/docs): update changelog & fix eslint errors

* `@mcaptcha/vanilla-glue`をダイナミックインポートするように

* chore: Add missing prefix to CHANGELOG

* refactor(backend): 適当につけた変数の名前を変更
  • Loading branch information
chocolate-pie committed Jan 6, 2024
1 parent b55a6a8 commit 072f67d
Show file tree
Hide file tree
Showing 24 changed files with 336 additions and 62 deletions.
1 change: 1 addition & 0 deletions .config/docker_example.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
POSTGRES_PASSWORD=example-misskey-pass
POSTGRES_USER=example-misskey-user
POSTGRES_DB=misskey
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

## 202x.x.x (Unreleased)

### General
- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加

### Client
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
Expand Down
31 changes: 31 additions & 0 deletions docker-compose_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ services:
links:
- db
- redis
# - mcaptcha
# - meilisearch
depends_on:
db:
Expand Down Expand Up @@ -48,6 +49,36 @@ services:
interval: 5s
retries: 20

# mcaptcha:
# restart: always
# image: mcaptcha/mcaptcha:latest
# networks:
# internal_network:
# external_network:
# aliases:
# - localhost
# ports:
# - 7493:7493
# env_file:
# - .config/docker.env
# environment:
# PORT: 7493
# MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
# depends_on:
# db:
# condition: service_healthy
# mcaptcha_redis:
# condition: service_healthy
#
# mcaptcha_redis:
# image: mcaptcha/cache:latest
# networks:
# - internal_network
# healthcheck:
# test: "redis-cli ping"
# interval: 5s
# retries: 20

# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4
Expand Down
5 changes: 5 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,11 @@ export interface Locale {
"enableHcaptcha": string;
"hcaptchaSiteKey": string;
"hcaptchaSecretKey": string;
"mcaptcha": string;
"enableMcaptcha": string;
"mcaptchaSiteKey": string;
"mcaptchaSecretKey": string;
"mcaptchaInstanceUrl": string;
"recaptcha": string;
"enableRecaptcha": string;
"recaptchaSiteKey": string;
Expand Down
5 changes: 5 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,11 @@ hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptchaを有効にする"
hcaptchaSiteKey: "サイトキー"
hcaptchaSecretKey: "シークレットキー"
mcaptcha: "mCaptcha"
enableMcaptcha: "mCaptchaを有効にする"
mcaptchaSiteKey: "サイトキー"
mcaptchaSecretKey: "シークレットキー"
mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL"
recaptcha: "reCAPTCHA"
enableRecaptcha: "reCAPTCHAを有効にする"
recaptchaSiteKey: "サイトキー"
Expand Down
22 changes: 22 additions & 0 deletions packages/backend/migration/1704373210054-support-mcaptcha.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class SupportMcaptcha1704373210054 {
name = 'SupportMcaptcha1704373210054'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableMcaptcha" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSitekey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSecretKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaInstanceUrl" character varying(1024)`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaInstanceUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSecretKey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSitekey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableMcaptcha"`);
}
}
31 changes: 31 additions & 0 deletions packages/backend/src/core/CaptchaService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,37 @@ export class CaptchaService {
}
}

// https://codeberg.org/Gusted/mCaptcha/src/branch/main/mcaptcha.go
@bindThis
public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise<void> {
if (response == null) {
throw new Error('mcaptcha-failed: no response provided');
}

const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost);
const result = await this.httpRequestService.send(endpointUrl.toString(), {
method: 'POST',
body: JSON.stringify({
key: siteKey,
secret: secret,
token: response,
}),
headers: {
'Content-Type': 'application/json',
},
});

if (result.status !== 200) {
throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK');
}

const resp = (await result.json()) as { valid: boolean };

if (!resp.valid) {
throw new Error('mcaptcha-request-failed');
}
}

@bindThis
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
Expand Down
25 changes: 24 additions & 1 deletion packages/backend/src/models/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,29 @@ export class MiMeta {
})
public hcaptchaSecretKey: string | null;

@Column('boolean', {
default: false,
})
public enableMcaptcha: boolean;

@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaSitekey: string | null;

@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaSecretKey: string | null;

@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaInstanceUrl: string | null;

@Column('boolean', {
default: false,
})
Expand Down Expand Up @@ -467,7 +490,7 @@ export class MiMeta {
nullable: true,
})
public truemailInstance: string | null;

@Column('varchar', {
length: 1024,
nullable: true,
Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/server/api/SignupApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class SignupApiService {
'hcaptcha-response'?: string;
'g-recaptcha-response'?: string;
'turnstile-response'?: string;
'm-captcha-response'?: string;
}
}>,
reply: FastifyReply,
Expand All @@ -82,6 +83,12 @@ export class SignupApiService {
});
}

if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) {
await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
throw new FastifyReplyError(400, err);
});
}

if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
throw new FastifyReplyError(400, err);
Expand Down
20 changes: 20 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
enableMcaptcha: {
type: 'boolean',
optional: false, nullable: false,
},
mcaptchaSiteKey: {
type: 'string',
optional: false, nullable: true,
},
mcaptchaInstanceUrl: {
type: 'string',
optional: false, nullable: true,
},
enableRecaptcha: {
type: 'boolean',
optional: false, nullable: false,
Expand Down Expand Up @@ -163,6 +175,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
mcaptchaSecretKey: {
type: 'string',
optional: false, nullable: true,
},
recaptchaSecretKey: {
type: 'string',
optional: false, nullable: true,
Expand Down Expand Up @@ -468,6 +484,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableMcaptcha: instance.enableMcaptcha,
mcaptchaSiteKey: instance.mcaptchaSitekey,
mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
enableRecaptcha: instance.enableRecaptcha,
recaptchaSiteKey: instance.recaptchaSiteKey,
enableTurnstile: instance.enableTurnstile,
Expand Down Expand Up @@ -498,6 +517,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
sensitiveWords: instance.sensitiveWords,
preservedUsernames: instance.preservedUsernames,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
mcaptchaSecretKey: instance.mcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
turnstileSecretKey: instance.turnstileSecretKey,
sensitiveMediaDetection: instance.sensitiveMediaDetection,
Expand Down
22 changes: 21 additions & 1 deletion packages/backend/src/server/api/endpoints/admin/update-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export const paramDef = {
enableHcaptcha: { type: 'boolean' },
hcaptchaSiteKey: { type: 'string', nullable: true },
hcaptchaSecretKey: { type: 'string', nullable: true },
enableMcaptcha: { type: 'boolean' },
mcaptchaSiteKey: { type: 'string', nullable: true },
mcaptchaInstanceUrl: { type: 'string', nullable: true },
mcaptchaSecretKey: { type: 'string', nullable: true },
enableRecaptcha: { type: 'boolean' },
recaptchaSiteKey: { type: 'string', nullable: true },
recaptchaSecretKey: { type: 'string', nullable: true },
Expand Down Expand Up @@ -269,6 +273,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
}

if (ps.enableMcaptcha !== undefined) {
set.enableMcaptcha = ps.enableMcaptcha;
}

if (ps.mcaptchaSiteKey !== undefined) {
set.mcaptchaSitekey = ps.mcaptchaSiteKey;
}

if (ps.mcaptchaInstanceUrl !== undefined) {
set.mcaptchaInstanceUrl = ps.mcaptchaInstanceUrl;
}

if (ps.mcaptchaSecretKey !== undefined) {
set.mcaptchaSecretKey = ps.mcaptchaSecretKey;
}

if (ps.enableRecaptcha !== undefined) {
set.enableRecaptcha = ps.enableRecaptcha;
}
Expand Down Expand Up @@ -472,7 +492,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.verifymailAuthKey = ps.verifymailAuthKey;
}
}

if (ps.enableTruemailApi !== undefined) {
set.enableTruemailApi = ps.enableTruemailApi;
}
Expand Down
15 changes: 15 additions & 0 deletions packages/backend/src/server/api/endpoints/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
enableMcaptcha: {
type: 'boolean',
optional: false, nullable: false,
},
mcaptchaSiteKey: {
type: 'string',
optional: false, nullable: true,
},
mcaptchaInstanceUrl: {
type: 'string',
optional: false, nullable: true,
},
enableRecaptcha: {
type: 'boolean',
optional: false, nullable: false,
Expand Down Expand Up @@ -351,6 +363,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableMcaptcha: instance.enableMcaptcha,
mcaptchaSiteKey: instance.mcaptchaSitekey,
mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
enableRecaptcha: instance.enableRecaptcha,
recaptchaSiteKey: instance.recaptchaSiteKey,
enableTurnstile: instance.enableTurnstile,
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"dependencies": {
"@discordapp/twemoji": "15.0.2",
"@github/webauthn-json": "2.1.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@misskey-dev/browser-image-resizer": "2.2.1-misskey.10",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "5.0.5",
Expand Down

0 comments on commit 072f67d

Please sign in to comment.