1
0
mirror of https://git.boykissers.com/pawkey/pawkey-sk.git synced 2025-12-20 04:04:16 +00:00

feat: require a special registration word in registration reason (#2)

A crude way to prune out people who don't read the rules. The hint is in
the bottom of the rules generally.
This commit is contained in:
Leafus
2025-05-19 16:33:28 +00:00
committed by Bluey Heeler
parent 06ff35a89f
commit df322ebadc
9 changed files with 185 additions and 80 deletions

View File

@@ -1982,6 +1982,7 @@ _signup:
almostThere: "Almost there"
emailAddressInfo: "Please enter your email address. It will not be made public."
emailSent: "A confirmation email has been sent to your email address ({email}). Please click the included link to complete account creation."
registrationWordRequired: "Please include the required registration word in your reason"
_accountDelete:
accountDelete: "Delete account"
mayTakeTime: "As account deletion is a resource-heavy process, it may take some time to complete depending on how much content you have created and how many files you have uploaded."

View File

@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddRegWordFieldsToMeta1743894419864 implements MigrationInterface {
name = 'AddRegWordFieldsToMeta1743894419864'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "meta" ADD "regWordRequired" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "registrationWord" character varying(1024)`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "registrationWord"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "regWordRequired"`);
}
}

View File

@@ -86,6 +86,8 @@ export class MetaEntityService {
disableRegistration: instance.disableRegistration,
emailRequiredForSignup: instance.emailRequiredForSignup,
approvalRequiredForSignup: instance.approvalRequiredForSignup,
regWordRequired: instance.regWordRequired,
registrationWord: instance.registrationWord,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableMcaptcha: instance.enableMcaptcha,

View File

@@ -139,9 +139,8 @@ export class MiMeta {
})
public logoImageUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
@Column('boolean', {
default: false,
})
public iconUrl: string | null;
@@ -204,6 +203,18 @@ export class MiMeta {
@Column('boolean', {
default: false,
})
public regWordRequired: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public registrationWord: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public enableHcaptcha: boolean;
@Column('varchar', {

View File

@@ -313,6 +313,14 @@ export const packedMetaLiteSchema = {
type: 'string',
optional: false, nullable: true,
},
regWordRequired: {
type: 'boolean',
optional: false, nullable: false,
},
registrationWord: {
type: 'string',
optional: false, nullable: true,
},
privacyPolicyUrl: {
type: 'string',
optional: false, nullable: true,

View File

@@ -39,6 +39,14 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
registrationWord: {
type: 'string',
optional: false, nullable: true,
},
regWordRequired: {
type: 'boolean',
optional: false, nullable: false,
},
enableHcaptcha: {
type: 'boolean',
optional: false, nullable: false,
@@ -657,6 +665,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
disableRegistration: instance.disableRegistration,
emailRequiredForSignup: instance.emailRequiredForSignup,
approvalRequiredForSignup: instance.approvalRequiredForSignup,
registrationWord: instance.registrationWord,
regWordRequired: instance.regWordRequired,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableMcaptcha: instance.enableMcaptcha,

View File

@@ -74,6 +74,8 @@ export const paramDef = {
cacheRemoteSensitiveFiles: { type: 'boolean' },
emailRequiredForSignup: { type: 'boolean' },
approvalRequiredForSignup: { type: 'boolean' },
regWordRequired: { type: 'boolean', nullable: false },
registrationWord: { type: 'string', nullable: true },
enableHcaptcha: { type: 'boolean' },
hcaptchaSiteKey: { type: 'string', nullable: true },
hcaptchaSecretKey: { type: 'string', nullable: true },
@@ -357,6 +359,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.approvalRequiredForSignup = ps.approvalRequiredForSignup;
}
if (ps.registrationWord !== undefined) {
set.registrationWord = ps.registrationWord;
}
if (ps.regWordRequired !== undefined) {
set.regWordRequired = ps.regWordRequired;
}
if (ps.enableHcaptcha !== undefined) {
set.enableHcaptcha = ps.enableHcaptcha;
}

View File

@@ -65,6 +65,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInput v-if="instance.approvalRequiredForSignup" v-model="reason" type="text" :spellcheck="false" required data-cy-signup-reason>
<template #label>Reason <div v-tooltip:dialog="i18n.ts._signup.reasonInfo" class="_button _help"><i class="ph-question ph-bold ph-lg"></i></div></template>
<template #prefix><i class="ph-chalkboard-teacher ph-bold ph-lg"></i></template>
<template #caption>
<span v-if="instance.regWordRequired && !reason.includes(instance.registrationWord)" style="color: var(--MI_THEME-error)">
<i class="ti ti-alert-triangle ti-fw"></i> Registration word is required
</span>
</template>
</MkInput>
<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
@@ -149,7 +154,8 @@ const shouldDisableSubmitting = computed((): boolean => {
instance.enableTestcaptcha && !testcaptchaResponse.value ||
instance.emailRequiredForSignup && emailState.value !== 'ok' ||
usernameState.value !== 'ok' ||
passwordRetypeState.value !== 'match';
passwordRetypeState.value !== 'match' ||
(instance.regWordRequired && !reason.value.includes(instance.registrationWord));
});
function getPasswordStrength(source: string): number {

View File

@@ -4,91 +4,117 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
<div class="_gaps_m">
<MkFolder v-if="meta.federation !== 'none'">
<template #label>{{ i18n.ts.authorizedFetchSection }}</template>
<template #suffix>{{ meta.allowUnsignedFetch !== 'always' ? i18n.ts.enabled : i18n.ts.disabled }}</template>
<template v-if="authFetchForm.modified.value" #footer>
<MkFormFooter :form="authFetchForm"/>
</template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
<div class="_gaps_m">
<MkFolder v-if="meta.federation !== 'none'">
<template #label>{{ i18n.ts.authorizedFetchSection }}</template>
<template #suffix>{{ meta.allowUnsignedFetch !== 'always' ? i18n.ts.enabled : i18n.ts.disabled
}}</template>
<template v-if="authFetchForm.modified.value" #footer>
<MkFormFooter :form="authFetchForm" />
</template>
<MkRadios v-model="authFetchForm.state.allowUnsignedFetch">
<template #label>{{ i18n.ts.authorizedFetchLabel }}</template>
<template #caption>{{ i18n.ts.authorizedFetchDescription }}</template>
<option value="never">{{ i18n.ts._authorizedFetchValue.never }} - {{ i18n.ts._authorizedFetchValueDescription.never }}</option>
<option value="always">{{ i18n.ts._authorizedFetchValue.always }} - {{ i18n.ts._authorizedFetchValueDescription.always }}</option>
<option value="essential">{{ i18n.ts._authorizedFetchValue.essential }} - {{ i18n.ts._authorizedFetchValueDescription.essential }}</option>
</MkRadios>
</MkFolder>
<MkRadios v-model="authFetchForm.state.allowUnsignedFetch">
<template #label>{{ i18n.ts.authorizedFetchLabel }}</template>
<template #caption>{{ i18n.ts.authorizedFetchDescription }}</template>
<option value="never">{{ i18n.ts._authorizedFetchValue.never }} - {{
i18n.ts._authorizedFetchValueDescription.never
}}</option>
<option value="always">{{ i18n.ts._authorizedFetchValue.always }} - {{
i18n.ts._authorizedFetchValueDescription.always }}</option>
<option value="essential">{{ i18n.ts._authorizedFetchValue.essential }} - {{
i18n.ts._authorizedFetchValueDescription.essential }}</option>
</MkRadios>
</MkFolder>
<XBotProtection/>
<XBotProtection />
<MkFolder>
<template #label>Active Email Validation</template>
<template v-if="emailValidationForm.savedState.enableActiveEmailValidation" #suffix>Enabled</template>
<template v-else #suffix>Disabled</template>
<template v-if="emailValidationForm.modified.value" #footer>
<MkFormFooter :form="emailValidationForm"/>
</template>
<MkFolder>
<template #label>Active Email Validation</template>
<template v-if="emailValidationForm.savedState.enableActiveEmailValidation"
#suffix>Enabled</template>
<template v-else #suffix>Disabled</template>
<template v-if="emailValidationForm.modified.value" #footer>
<MkFormFooter :form="emailValidationForm" />
</template>
<div class="_gaps_m">
<span>{{ i18n.ts.activeEmailValidationDescription }}</span>
<MkSwitch v-model="emailValidationForm.state.enableActiveEmailValidation">
<template #label>Enable</template>
</MkSwitch>
<MkSwitch v-model="emailValidationForm.state.enableVerifymailApi">
<template #label>Use Verifymail.io API</template>
</MkSwitch>
<MkInput v-model="emailValidationForm.state.verifymailAuthKey">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>Verifymail.io API Auth Key</template>
</MkInput>
<MkSwitch v-model="emailValidationForm.state.enableTruemailApi">
<template #label>Use TrueMail API</template>
</MkSwitch>
<MkInput v-model="emailValidationForm.state.truemailInstance">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>TrueMail API Instance</template>
</MkInput>
<MkInput v-model="emailValidationForm.state.truemailAuthKey">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>TrueMail API Auth Key</template>
</MkInput>
</div>
</MkFolder>
<div class="_gaps_m">
<span>{{ i18n.ts.activeEmailValidationDescription }}</span>
<MkSwitch v-model="emailValidationForm.state.enableActiveEmailValidation">
<template #label>Enable</template>
</MkSwitch>
<MkSwitch v-model="emailValidationForm.state.enableVerifymailApi">
<template #label>Use Verifymail.io API</template>
</MkSwitch>
<MkInput v-model="emailValidationForm.state.verifymailAuthKey">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>Verifymail.io API Auth Key</template>
</MkInput>
<MkSwitch v-model="emailValidationForm.state.enableTruemailApi">
<template #label>Use TrueMail API</template>
</MkSwitch>
<MkInput v-model="emailValidationForm.state.truemailInstance">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>TrueMail API Instance</template>
</MkInput>
<MkInput v-model="emailValidationForm.state.truemailAuthKey">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>TrueMail API Auth Key</template>
</MkInput>
</div>
</MkFolder>
<MkFolder>
<template #label>Banned Email Domains</template>
<template v-if="bannedEmailDomainsForm.modified.value" #footer>
<MkFormFooter :form="bannedEmailDomainsForm"/>
</template>
<MkFolder>
<template #label>Banned Email Domains</template>
<template v-if="bannedEmailDomainsForm.modified.value" #footer>
<MkFormFooter :form="bannedEmailDomainsForm" />
</template>
<div class="_gaps_m">
<MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains">
<template #label>Banned Email Domains List</template>
</MkTextarea>
</div>
</MkFolder>
<div class="_gaps_m">
<MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains">
<template #label>Banned Email Domains List</template>
</MkTextarea>
</div>
</MkFolder>
<MkFolder>
<template #label>Log IP address</template>
<template v-if="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template>
<template v-else #suffix>Disabled</template>
<template v-if="ipLoggingForm.modified.value" #footer>
<MkFormFooter :form="ipLoggingForm"/>
</template>
<MkFolder>
<template #label>Log IP address</template>
<template v-if="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template>
<template v-else #suffix>Disabled</template>
<template v-if="ipLoggingForm.modified.value" #footer>
<MkFormFooter :form="ipLoggingForm" />
</template>
<div class="_gaps_m">
<MkSwitch v-model="ipLoggingForm.state.enableIpLogging">
<template #label>Enable</template>
</MkSwitch>
</div>
</MkFolder>
<div class="_gaps_m">
<MkSwitch v-model="ipLoggingForm.state.enableIpLogging">
<template #label>Enable</template>
</MkSwitch>
</div>
</MkFolder>
<MkFolder>
<template #label>Registration Word</template>
<template v-if="regWordForm.savedState.regWordRequired" #suffix>Enabled</template>
<template v-else #suffix>Disabled</template>
<template v-if="regWordForm.modified.value" #footer>
<MkFormFooter :form="regWordForm" />
</template>
<div class="_gaps_m">
<MkSwitch v-model="regWordForm.state.regWordRequired">
<template #label>Enable</template>
<template #caption>Require user to input a special registration word</template>
</MkSwitch>
<MkInput v-model="regWordForm.state.registrationWord">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>Registration Word</template>
</MkInput>
</div>
</MkFolder>
</div>
</div>
</div>
</PageWithHeader>
</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -128,6 +154,17 @@ const ipLoggingForm = useForm({
fetchInstance(true);
});
const regWordForm = useForm({
regWordRequired: meta.regWordRequired,
registrationWord: meta.registrationWord,
}, async (state) => {
await os.apiWithDialog('admin/update-meta', {
regWordRequired: state.regWordRequired,
registrationWord: state.registrationWord,
});
fetchInstance(true);
});
const emailValidationForm = useForm({
enableActiveEmailValidation: meta.enableActiveEmailValidation,
enableVerifymailApi: meta.enableVerifymailApi,