mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Add interface to block ip-addresses or clients due to abuse (#15484)
* Read IP blocks from database * begin building general blocking solution * add new frontend files * Add UI for managing blockers * correctly reset local data after creating blocker * Tweak wording * Add UI for managing blockers * restructure admin pages * improve test coverage * Improve blocker UI * add blocker to block emails from registration * lint fix * fix * lint fixes * fix import * add new permission for managing blockers * improve permission check * fix managing permissions from admin * improve navbar display for non fullAccess admin * update block error strings * lint fix * add option to errorHandler to skip logging * validate blocker value during input * improve blocker form display * chore(subproj): reconcile habitica-images * fix(scripts): use same Mongo version for dev/test * fix(whitespace): eof * documentation improvements * remove nconf import * remove old test --------- Co-authored-by: Kalista Payne <kalista@habitica.com> Co-authored-by: Kalista Payne <sabrecat@gmail.com>
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<div v-if="hasPermission(user, 'userSupport')">
|
||||
<div
|
||||
v-if="hero && hero.profile"
|
||||
class="row"
|
||||
>
|
||||
<div class="form col-12">
|
||||
<basic-details
|
||||
:user-id="hero._id"
|
||||
:auth="hero.auth"
|
||||
:preferences="hero.preferences"
|
||||
:profile="hero.profile"
|
||||
/>
|
||||
|
||||
<privileges-and-gems
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
:has-unsaved-changes="hasUnsavedChanges([hero.flags, unModifiedHero.flags],
|
||||
[hero.auth, unModifiedHero.auth],
|
||||
[hero.balance, unModifiedHero.balance],
|
||||
[hero.secret, unModifiedHero.secret])"
|
||||
/>
|
||||
|
||||
<subscription-and-perks
|
||||
:hero="hero"
|
||||
:group-plans="groupPlans"
|
||||
:has-unsaved-changes="hasUnsavedChanges([hero.purchased.plan,
|
||||
unModifiedHero.purchased.plan])"
|
||||
/>
|
||||
|
||||
<cron-and-auth
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<user-profile
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
:has-unsaved-changes="hasUnsavedChanges([hero.profile, unModifiedHero.profile])"
|
||||
/>
|
||||
|
||||
<party-and-quest
|
||||
v-if="adminHasPrivForParty"
|
||||
:user-id="hero._id"
|
||||
:username="hero.auth.local.username"
|
||||
:user-has-party="hasParty"
|
||||
:party-not-exist-error="partyNotExistError"
|
||||
:user-party-data="hero.party"
|
||||
:group-party-data="party"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<avatar-and-drops
|
||||
:items="hero.items"
|
||||
:preferences="hero.preferences"
|
||||
/>
|
||||
|
||||
<stats
|
||||
:hero="hero"
|
||||
:has-unsaved-changes="hasUnsavedChanges([hero.stats, unModifiedHero.stats])"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<items-owned
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<customizations-owned
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<achievements
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<transactions
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<user-history
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<contributor-details
|
||||
:hero="hero"
|
||||
:has-unsaved-changes="hasUnsavedChanges(
|
||||
[hero.contributor, unModifiedHero.contributor],
|
||||
[hero.permissions, unModifiedHero.permissions],
|
||||
[hero.secret, unModifiedHero.secret],
|
||||
)"
|
||||
:reset-counter="resetCounter"
|
||||
@clear-data="clearData"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .accordion-group .accordion-group {
|
||||
margin-left: 1em;
|
||||
}
|
||||
::v-deep h3 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
::v-deep h4 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
::v-deep .expand-toggle::after {
|
||||
margin-left: 5px;
|
||||
}
|
||||
::v-deep .subsection-start {
|
||||
margin-top: 1em;
|
||||
}
|
||||
::v-deep .form-inline {
|
||||
margin-bottom: 1em;
|
||||
input, span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
::v-deep .errorMessage {
|
||||
font-weight: bold;
|
||||
}
|
||||
::v-deep .markdownPreview {
|
||||
margin-left: 3em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import isEqualWith from 'lodash/isEqualWith';
|
||||
import BasicDetails from './basicDetails';
|
||||
import ItemsOwned from './itemsOwned';
|
||||
import CronAndAuth from './cronAndAuth';
|
||||
import UserProfile from './userProfile';
|
||||
import PartyAndQuest from './partyAndQuest';
|
||||
import AvatarAndDrops from './avatarAndDrops';
|
||||
import PrivilegesAndGems from './privilegesAndGems';
|
||||
import ContributorDetails from './contributorDetails';
|
||||
import Transactions from './transactions';
|
||||
import SubscriptionAndPerks from './subscriptionAndPerks';
|
||||
import CustomizationsOwned from './customizationsOwned.vue';
|
||||
import Achievements from './achievements.vue';
|
||||
import UserHistory from './userHistory.vue';
|
||||
import Stats from './stats.vue';
|
||||
|
||||
import { userStateMixin } from '../../../../mixins/userState';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BasicDetails,
|
||||
ItemsOwned,
|
||||
CustomizationsOwned,
|
||||
CronAndAuth,
|
||||
PartyAndQuest,
|
||||
AvatarAndDrops,
|
||||
PrivilegesAndGems,
|
||||
ContributorDetails,
|
||||
Transactions,
|
||||
UserHistory,
|
||||
Stats,
|
||||
SubscriptionAndPerks,
|
||||
UserProfile,
|
||||
Achievements,
|
||||
},
|
||||
mixins: [userStateMixin],
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.userIdentifier = to.params.userIdentifier;
|
||||
next();
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
userIdentifier: '',
|
||||
resetCounter: 0,
|
||||
unModifiedHero: {},
|
||||
hero: {},
|
||||
party: {},
|
||||
groupPlans: [],
|
||||
hasParty: false,
|
||||
partyNotExistError: false,
|
||||
adminHasPrivForParty: true,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
userIdentifier () {
|
||||
// close modal if the page is opened in an existing tab from the modal
|
||||
this.$root.$emit('bv::hide::modal', 'profile');
|
||||
|
||||
this.loadHero(this.userIdentifier);
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.userIdentifier = this.$route.params.userIdentifier;
|
||||
},
|
||||
methods: {
|
||||
clearData () {
|
||||
this.unModifiedHero = {};
|
||||
this.hero = {};
|
||||
},
|
||||
|
||||
async loadHero (userIdentifier) {
|
||||
const id = userIdentifier.replace(/@/, ''); // allow "@name" to be entered
|
||||
this.$emit('changeUserIdentifier', id); // change user identifier in Admin Panel's form
|
||||
|
||||
this.hero = await this.$store.dispatch('hall:getHero', { uuid: id });
|
||||
this.unModifiedHero = JSON.parse(JSON.stringify(this.hero));
|
||||
|
||||
if (!this.hero.flags) {
|
||||
this.hero.flags = {
|
||||
chatRevoked: false,
|
||||
chatShadowMuted: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.hero.permissions) {
|
||||
this.hero.permissions = {};
|
||||
}
|
||||
|
||||
this.hasParty = false;
|
||||
this.partyNotExistError = false;
|
||||
this.adminHasPrivForParty = true;
|
||||
if (this.hero.party && this.hero.party._id) {
|
||||
try {
|
||||
this.party = await this.$store.dispatch('hall:getHeroParty', { groupId: this.hero.party._id });
|
||||
this.hasParty = true;
|
||||
} catch (e) {
|
||||
if (e.message.includes('status code 401')) {
|
||||
// @TODO is there a better way to recognise NotAuthorized error?
|
||||
this.adminHasPrivForParty = false;
|
||||
} else {
|
||||
// the API's error message isn't worth reporting ("Request failed with status code 404")
|
||||
this.partyNotExistError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hero.purchased.plan.planId === 'group_plan_auto') {
|
||||
try {
|
||||
this.groupPlans = await this.$store.dispatch('hall:getHeroGroupPlans', { heroId: this.hero._id });
|
||||
} catch (e) {
|
||||
this.groupPlans = [];
|
||||
}
|
||||
}
|
||||
|
||||
this.resetCounter += 1; // tell child components to reinstantiate from scratch
|
||||
},
|
||||
hasUnsavedChanges (...comparisons) {
|
||||
for (const index in comparisons) {
|
||||
if (index && comparisons[index]) {
|
||||
const objs = comparisons[index];
|
||||
const obj1 = objs[0];
|
||||
const obj2 = objs[1];
|
||||
if (!isEqualWith(obj1, obj2, (x, y) => {
|
||||
if (typeof x === 'object' && typeof y === 'object') {
|
||||
return undefined;
|
||||
}
|
||||
if (x === false && y === undefined) {
|
||||
// Special case for checkboxes
|
||||
return true;
|
||||
}
|
||||
return x == y; // eslint-disable-line eqeqeq
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user