~cytrogen/masto-fe

7e54a30f06740028bd67cb0aeb42c77f4db85c3a — Claire 2 years ago 67055b0 + 4534498
Merge commit '4534498a8e43f59980ee56e9938efab8580c78c8' into glitch-soc/merge-upstream
M app/javascript/mastodon/components/account.jsx => app/javascript/mastodon/components/account.jsx +3 -3
@@ 8,15 8,15 @@ import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';

import { counterRenderer } from 'mastodon/components/common_counter';
import { EmptyAccount } from 'mastodon/components/empty_account';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { VerifiedBadge } from 'mastodon/components/verified_badge';

import { me } from '../initial_state';

import { Avatar } from './avatar';
import Button from './button';
import { FollowersCounter } from './counters';
import { DisplayName } from './display_name';
import { IconButton } from './icon_button';
import { RelativeTimestamp } from './relative_timestamp';


@@ 160,7 160,7 @@ class Account extends ImmutablePureComponent {
              <DisplayName account={account} />
              {!minimal && (
                <div className='account__details'>
                  <ShortNumber value={account.get('followers_count')} renderer={counterRenderer('followers')} /> {verification} {muteTimeRemaining}
                  <ShortNumber value={account.get('followers_count')} renderer={FollowersCounter} /> {verification} {muteTimeRemaining}
                </div>
              )}
            </div>

M app/javascript/mastodon/components/animated_number.tsx => app/javascript/mastodon/components/animated_number.tsx +1 -1
@@ 4,7 4,7 @@ import { TransitionMotion, spring } from 'react-motion';

import { reduceMotion } from '../initial_state';

import ShortNumber from './short_number';
import { ShortNumber } from './short_number';

const obfuscatedCount = (count: number) => {
  if (count < 0) {

M app/javascript/mastodon/components/autosuggest_hashtag.tsx => app/javascript/mastodon/components/autosuggest_hashtag.tsx +1 -1
@@ 1,6 1,6 @@
import { FormattedMessage } from 'react-intl';

import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';

interface Props {
  tag: {

R app/javascript/mastodon/components/common_counter.jsx => app/javascript/mastodon/components/counters.tsx +42 -57
@@ 1,60 1,45 @@
// @ts-check
import React from 'react';

import { FormattedMessage } from 'react-intl';

/**
 * Returns custom renderer for one of the common counter types
 * @param {"statuses" | "following" | "followers"} counterType
 * Type of the counter
 * @param {boolean} isBold Whether display number must be displayed in bold
 * @returns {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
 * Renderer function
 * @throws If counterType is not covered by this function
 */
export function counterRenderer(counterType, isBold = true) {
  /**
   * @type {(displayNumber: JSX.Element) => JSX.Element}
   */
  const renderCounter = isBold
    ? (displayNumber) => <strong>{displayNumber}</strong>
    : (displayNumber) => displayNumber;
export const StatusesCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.statuses_counter'
    defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

export const FollowingCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.following_counter'
    defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

  switch (counterType) {
  case 'statuses': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.statuses_counter'
        defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  case 'following': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.following_counter'
        defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  case 'followers': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.followers_counter'
        defaultMessage='{count, plural, one {{counter} Follower} other {{counter} Followers}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  default: throw Error(`Incorrect counter name: ${counterType}. Ensure it accepted by commonCounter function`);
  }
}
export const FollowersCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.followers_counter'
    defaultMessage='{count, plural, one {{counter} Follower} other {{counter} Followers}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

D app/javascript/mastodon/components/dismissable_banner.jsx => app/javascript/mastodon/components/dismissable_banner.jsx +0 -55
@@ 1,55 0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import { injectIntl, defineMessages } from 'react-intl';

import { bannerSettings } from 'mastodon/settings';

import { IconButton } from './icon_button';

const messages = defineMessages({
  dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
});

class DismissableBanner extends PureComponent {

  static propTypes = {
    id: PropTypes.string.isRequired,
    children: PropTypes.node,
    intl: PropTypes.object.isRequired,
  };

  state = {
    visible: !bannerSettings.get(this.props.id),
  };

  handleDismiss = () => {
    const { id } = this.props;
    this.setState({ visible: false }, () => bannerSettings.set(id, true));
  };

  render () {
    const { visible } = this.state;

    if (!visible) {
      return null;
    }

    const { children, intl } = this.props;

    return (
      <div className='dismissable-banner'>
        <div className='dismissable-banner__message'>
          {children}
        </div>

        <div className='dismissable-banner__action'>
          <IconButton icon='times' title={intl.formatMessage(messages.dismiss)} onClick={this.handleDismiss} />
        </div>
      </div>
    );
  }

}

export default injectIntl(DismissableBanner);

A app/javascript/mastodon/components/dismissable_banner.tsx => app/javascript/mastodon/components/dismissable_banner.tsx +47 -0
@@ 0,0 1,47 @@
import type { PropsWithChildren } from 'react';
import { useCallback, useState } from 'react';

import { defineMessages, useIntl } from 'react-intl';

import { bannerSettings } from 'mastodon/settings';

import { IconButton } from './icon_button';

const messages = defineMessages({
  dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
});

interface Props {
  id: string;
}

export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
  id,
  children,
}) => {
  const [visible, setVisible] = useState(!bannerSettings.get(id));
  const intl = useIntl();

  const handleDismiss = useCallback(() => {
    setVisible(false);
    bannerSettings.set(id, true);
  }, [id]);

  if (!visible) {
    return null;
  }

  return (
    <div className='dismissable-banner'>
      <div className='dismissable-banner__message'>{children}</div>

      <div className='dismissable-banner__action'>
        <IconButton
          icon='times'
          title={intl.formatMessage(messages.dismiss)}
          onClick={handleDismiss}
        />
      </div>
    </div>
  );
};

M app/javascript/mastodon/components/hashtag.jsx => app/javascript/mastodon/components/hashtag.jsx +1 -1
@@ 11,7 11,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';

import { Sparklines, SparklinesCurve } from 'react-sparklines';

import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';

class SilentErrorBoundary extends Component {

M app/javascript/mastodon/components/server_banner.jsx => app/javascript/mastodon/components/server_banner.jsx +1 -1
@@ 9,7 9,7 @@ import { connect } from 'react-redux';

import { fetchServer } from 'mastodon/actions/server';
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
import Account from 'mastodon/containers/account_container';
import { domain } from 'mastodon/initial_state';

R app/javascript/mastodon/components/short_number.jsx => app/javascript/mastodon/components/short_number.tsx +59 -84
@@ 1,69 1,49 @@
import PropTypes from 'prop-types';
import { memo } from 'react';

import { FormattedMessage, FormattedNumber } from 'react-intl';

import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../utils/numbers';

// @ts-check
type ShortNumberRenderer = (
  displayNumber: JSX.Element,
  pluralReady: number
) => JSX.Element;

/**
 * @callback ShortNumberRenderer
 * @param {JSX.Element} displayNumber Number to display
 * @param {number} pluralReady Number used for pluralization
 * @returns {JSX.Element} Final render of number
 */

/**
 * @typedef {object} ShortNumberProps
 * @property {number} value Number to display in short variant
 * @property {ShortNumberRenderer} [renderer]
 * Custom renderer for numbers, provided as a prop. If another renderer
 * passed as a child of this component, this prop won't be used.
 * @property {ShortNumberRenderer} [children]
 * Custom renderer for numbers, provided as a child. If another renderer
 * passed as a prop of this component, this one will be used instead.
 */
interface ShortNumberProps {
  value: number;
  renderer?: ShortNumberRenderer;
  children?: ShortNumberRenderer;
}

/**
 * Component that renders short big number to a shorter version
 * @param {ShortNumberProps} param0 Props for the component
 * @returns {JSX.Element} Rendered number
 */
function ShortNumber({ value, renderer, children }) {
export const ShortNumberRenderer: React.FC<ShortNumberProps> = ({
  value,
  renderer,
  children,
}) => {
  const shortNumber = toShortNumber(value);
  const [, division] = shortNumber;

  if (children != null && renderer != null) {
    console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.');
  if (children && renderer) {
    console.warn(
      'Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.'
    );
  }

  const customRenderer = children != null ? children : renderer;
  const customRenderer = children || renderer || null;

  const displayNumber = <ShortNumberCounter value={shortNumber} />;

  return customRenderer != null
    ? customRenderer(displayNumber, pluralReady(value, division))
    : displayNumber;
}

ShortNumber.propTypes = {
  value: PropTypes.number.isRequired,
  renderer: PropTypes.func,
  children: PropTypes.func,
  return (
    customRenderer?.(displayNumber, pluralReady(value, division)) ||
    displayNumber
  );
};
export const ShortNumber = memo(ShortNumberRenderer);

/**
 * @typedef {object} ShortNumberCounterProps
 * @property {import('../utils/number').ShortNumber} value Short number
 */

/**
 * Renders short number into corresponding localizable react fragment
 * @param {ShortNumberCounterProps} param0 Props for the component
 * @returns {JSX.Element} FormattedMessage ready to be embedded in code
 */
function ShortNumberCounter({ value }) {
interface ShortNumberCounterProps {
  value: number[];
}
const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
  const [rawNumber, unit, maxFractionDigits = 0] = value;

  const count = (


@@ 73,43 53,38 @@ function ShortNumberCounter({ value }) {
    />
  );

  let values = { count, rawNumber };
  const values = { count, rawNumber };

  switch (unit) {
  case DECIMAL_UNITS.THOUSAND: {
    return (
      <FormattedMessage
        id='units.short.thousand'
        defaultMessage='{count}K'
        values={values}
      />
    );
    case DECIMAL_UNITS.THOUSAND: {
      return (
        <FormattedMessage
          id='units.short.thousand'
          defaultMessage='{count}K'
          values={values}
        />
      );
    }
    case DECIMAL_UNITS.MILLION: {
      return (
        <FormattedMessage
          id='units.short.million'
          defaultMessage='{count}M'
          values={values}
        />
      );
    }
    case DECIMAL_UNITS.BILLION: {
      return (
        <FormattedMessage
          id='units.short.billion'
          defaultMessage='{count}B'
          values={values}
        />
      );
    }
    // Not sure if we should go farther - @Sasha-Sorokin
    default:
      return count;
  }
  case DECIMAL_UNITS.MILLION: {
    return (
      <FormattedMessage
        id='units.short.million'
        defaultMessage='{count}M'
        values={values}
      />
    );
  }
  case DECIMAL_UNITS.BILLION: {
    return (
      <FormattedMessage
        id='units.short.billion'
        defaultMessage='{count}B'
        values={values}
      />
    );
  }
  // Not sure if we should go farther - @Sasha-Sorokin
  default: return count;
  }
}

ShortNumberCounter.propTypes = {
  value: PropTypes.arrayOf(PropTypes.number),
};

export default memo(ShortNumber);

M app/javascript/mastodon/components/status_content.jsx => app/javascript/mastodon/components/status_content.jsx +1 -1
@@ 44,7 44,7 @@ class TranslateButton extends PureComponent {
    }

    return (
      <button className='status__content__read-more-button' onClick={onClick}>
      <button className='status__content__translate-button' onClick={onClick}>
        <FormattedMessage id='status.translate' defaultMessage='Translate' />
      </button>
    );

M app/javascript/mastodon/features/account/components/header.jsx => app/javascript/mastodon/features/account/components/header.jsx +5 -5
@@ 11,10 11,10 @@ import ImmutablePureComponent from 'react-immutable-pure-component';

import { Avatar } from 'mastodon/components/avatar';
import Button from 'mastodon/components/button';
import { counterRenderer } from 'mastodon/components/common_counter';
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
import { Icon }  from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { autoPlayGif, me, domain } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';


@@ 451,21 451,21 @@ class Header extends ImmutablePureComponent {
                <NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
                  <ShortNumber
                    value={account.get('statuses_count')}
                    renderer={counterRenderer('statuses')}
                    renderer={StatusesCounter}
                  />
                </NavLink>

                <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
                  <ShortNumber
                    value={account.get('following_count')}
                    renderer={counterRenderer('following')}
                    renderer={FollowingCounter}
                  />
                </NavLink>

                <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
                  <ShortNumber
                    value={account.get('followers_count')}
                    renderer={counterRenderer('followers')}
                    renderer={FollowersCounter}
                  />
                </NavLink>
              </div>

M app/javascript/mastodon/features/community_timeline/index.jsx => app/javascript/mastodon/features/community_timeline/index.jsx +1 -1
@@ 7,7 7,7 @@ import { Helmet } from 'react-helmet';

import { connect } from 'react-redux';

import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { domain } from 'mastodon/initial_state';

import { addColumn, removeColumn, moveColumn } from '../../actions/columns';

M app/javascript/mastodon/features/directory/components/account_card.jsx => app/javascript/mastodon/features/directory/components/account_card.jsx +1 -1
@@ 19,7 19,7 @@ import { openModal } from 'mastodon/actions/modal';
import { Avatar } from 'mastodon/components/avatar';
import Button from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
import { makeGetAccount } from 'mastodon/selectors';


M app/javascript/mastodon/features/explore/components/story.jsx => app/javascript/mastodon/features/explore/components/story.jsx +1 -1
@@ 5,7 5,7 @@ import classNames from 'classnames';

import { Blurhash } from 'mastodon/components/blurhash';
import { accountsCountRenderer } from 'mastodon/components/hashtag';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';

export default class Story extends PureComponent {

M app/javascript/mastodon/features/explore/links.jsx => app/javascript/mastodon/features/explore/links.jsx +1 -1
@@ 7,7 7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';

import { fetchTrendingLinks } from 'mastodon/actions/trends';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';

import Story from './components/story';

M app/javascript/mastodon/features/explore/statuses.jsx => app/javascript/mastodon/features/explore/statuses.jsx +1 -1
@@ 9,7 9,7 @@ import { connect } from 'react-redux';
import { debounce } from 'lodash';

import { fetchTrendingStatuses, expandTrendingStatuses } from 'mastodon/actions/trends';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import StatusList from 'mastodon/components/status_list';
import { getStatusList } from 'mastodon/selectors';


M app/javascript/mastodon/features/explore/tags.jsx => app/javascript/mastodon/features/explore/tags.jsx +1 -1
@@ 7,7 7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';

import { fetchTrendingHashtags } from 'mastodon/actions/trends';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';


M app/javascript/mastodon/features/firehose/index.jsx => app/javascript/mastodon/features/firehose/index.jsx +1 -1
@@ 10,7 10,7 @@ import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import initialState, { domain } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store';


M app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx => app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx +1 -1
@@ 5,7 5,7 @@ import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';

import background from 'mastodon/../images/friends-cropped.png';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';


export const ExplorePrompt = () => (

M app/javascript/mastodon/features/public_timeline/index.jsx => app/javascript/mastodon/features/public_timeline/index.jsx +1 -1
@@ 7,7 7,7 @@ import { Helmet } from 'react-helmet';

import { connect } from 'react-redux';

import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { domain } from 'mastodon/initial_state';

import { addColumn, removeColumn, moveColumn } from '../../actions/columns';

M app/javascript/styles/contrast/diff.scss => app/javascript/styles/contrast/diff.scss +2 -1
@@ 15,7 15,8 @@
.status__content a,
.link-footer a,
.reply-indicator__content a,
.status__content__read-more-button {
.status__content__read-more-button,
.status__content__translate-button {
  text-decoration: underline;

  &:hover,

M app/javascript/styles/mastodon/components.scss => app/javascript/styles/mastodon/components.scss +2 -1
@@ 981,7 981,8 @@ body > [data-popper-placement] {
  max-height: 22px * 15; // 15 lines is roughly above 500 characters
}

.status__content__read-more-button {
.status__content__read-more-button,
.status__content__translate-button {
  display: block;
  font-size: 15px;
  line-height: 22px;

M config/brakeman.ignore => config/brakeman.ignore +28 -64
@@ 18,6 18,9 @@
      },
      "user_input": "id",
      "confidence": "Weak",
      "cwe_id": [
        89
      ],
      "note": ""
    },
    {


@@ 38,26 41,9 @@
      },
      "user_input": "ids.join(\",\")",
      "confidence": "Weak",
      "note": ""
    },
    {
      "warning_type": "Redirect",
      "warning_code": 18,
      "fingerprint": "5fad11cd67f905fab9b1d5739d01384a1748ebe78c5af5ac31518201925265a7",
      "check_name": "Redirect",
      "message": "Possible unprotected redirect",
      "file": "app/controllers/remote_interaction_controller.rb",
      "line": 24,
      "link": "https://brakemanscanner.org/docs/warning_types/redirect/",
      "code": "redirect_to(RemoteFollow.new(resource_params).interact_address_for(Status.find(params[:id])))",
      "render_path": null,
      "location": {
        "type": "method",
        "class": "RemoteInteractionController",
        "method": "create"
      },
      "user_input": "RemoteFollow.new(resource_params).interact_address_for(Status.find(params[:id]))",
      "confidence": "High",
      "cwe_id": [
        89
      ],
      "note": ""
    },
    {


@@ 88,6 74,9 @@
      },
      "user_input": "(Unresolved Model).new.strike",
      "confidence": "Weak",
      "cwe_id": [
        79
      ],
      "note": ""
    },
    {


@@ 108,26 97,9 @@
      },
      "user_input": "SecureRandom.hex(16)",
      "confidence": "Medium",
      "note": ""
    },
    {
      "warning_type": "Mass Assignment",
      "warning_code": 105,
      "fingerprint": "7631e93d0099506e7c3e5c91ba8d88523b00a41a0834ae30031a5a4e8bb3020a",
      "check_name": "PermitAttributes",
      "message": "Potentially dangerous key allowed for mass assignment",
      "file": "app/controllers/api/v2/search_controller.rb",
      "line": 28,
      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
      "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id)",
      "render_path": null,
      "location": {
        "type": "method",
        "class": "Api::V2::SearchController",
        "method": "search_params"
      },
      "user_input": ":account_id",
      "confidence": "High",
      "cwe_id": [
        89
      ],
      "note": ""
    },
    {


@@ 137,7 109,7 @@
      "check_name": "PermitAttributes",
      "message": "Potentially dangerous key allowed for mass assignment",
      "file": "app/controllers/api/v1/admin/reports_controller.rb",
      "line": 90,
      "line": 88,
      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
      "code": "params.permit(:resolved, :account_id, :target_account_id)",
      "render_path": null,


@@ 148,6 120,9 @@
      },
      "user_input": ":account_id",
      "confidence": "High",
      "cwe_id": [
        915
      ],
      "note": ""
    },
    {


@@ 157,7 132,7 @@
      "check_name": "PermitAttributes",
      "message": "Potentially dangerous key allowed for mass assignment",
      "file": "app/controllers/api/v1/notifications_controller.rb",
      "line": 81,
      "line": 77,
      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
      "code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))",
      "render_path": null,


@@ 168,26 143,9 @@
      },
      "user_input": ":account_id",
      "confidence": "High",
      "note": ""
    },
    {
      "warning_type": "Redirect",
      "warning_code": 18,
      "fingerprint": "ba568ac09683f98740f663f3d850c31785900215992e8c090497d359a2563d50",
      "check_name": "Redirect",
      "message": "Possible unprotected redirect",
      "file": "app/controllers/remote_follow_controller.rb",
      "line": 21,
      "link": "https://brakemanscanner.org/docs/warning_types/redirect/",
      "code": "redirect_to(RemoteFollow.new(resource_params).subscribe_address_for(@account))",
      "render_path": null,
      "location": {
        "type": "method",
        "class": "RemoteFollowController",
        "method": "create"
      },
      "user_input": "RemoteFollow.new(resource_params).subscribe_address_for(@account)",
      "confidence": "High",
      "cwe_id": [
        915
      ],
      "note": ""
    },
    {


@@ 218,6 176,9 @@
      },
      "user_input": "(Unresolved Model).new.url",
      "confidence": "Weak",
      "cwe_id": [
        79
      ],
      "note": ""
    },
    {


@@ 238,9 199,12 @@
      },
      "user_input": ":account_id",
      "confidence": "High",
      "cwe_id": [
        915
      ],
      "note": ""
    }
  ],
  "updated": "2022-03-22 07:48:32 +0100",
  "brakeman_version": "5.2.1"
  "updated": "2023-07-05 14:34:42 -0400",
  "brakeman_version": "5.4.1"
}