Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for using either public_name or public_identifier #4490

Open
wants to merge 2 commits into
base: staging
Choose a base branch
from

Conversation

@jlbyrne jlbyrne force-pushed the feat/enable-public-name branch from 0604330 to d3fc373 Compare February 5, 2025 05:07
Copy link

github-actions bot commented Feb 5, 2025

[puLL-Merge] - brave-intl/publishers@4490

Description

This PR introduces custom vanity URLs for contribution pages and a system to manage reserved public names. It allows creators to set custom URLs for their contribution pages while maintaining security and uniqueness constraints. The changes also include an admin interface to manage reserved names and a temporary hold period for previously used names.

Security Hotspots

  1. Public URL Validation: The validation of public names (validate_public_url_unique in Channel model) needs careful review to ensure no malicious URLs can be created. Currently implements format restrictions and uniqueness checks.
  2. Admin Controller Access: The new ReservedPublicNamesController should be properly secured with admin authentication (appears to be handled by inheriting from AdminController).
  3. URL Path Traversal: The public_identifier lookup in PublicChannelController now checks two fields which could potentially lead to path traversal if not properly sanitized.
Changes

Changes

New Files:

  • app/controllers/admin/reserved_public_names_controller.rb: Admin interface for managing reserved names
  • app/models/reserved_public_name.rb: Model for storing reserved public names
  • app/views/admin/reserved_public_names/index.html.slim: Admin UI for reserved names
  • nextjs/src/app/[locale]/publishers/contribution_page/PublicUrlConfirmationModal.jsx: Confirmation modal for URL changes

Modified Files:

  1. Channel Model (app/models/channel.rb):

    • Added validation for public names
    • Implemented reserved name system
    • Added whitespace stripping for public names
    • Added uniqueness constraints across public_name and public_identifier
  2. Controllers:

    • Updated PublicChannelController to support vanity URLs
    • Modified ContributionPageController to handle public name updates
  3. Frontend:

    • Added UI for managing custom URLs
    • Added validation messages and error handling
    • Updated translations
  4. Database:

    • Added new reserved_public_names table
    • Added necessary indexes and constraints
sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant Channel
    participant ReservedNames

    User->>Frontend: Enter custom URL
    Frontend->>Frontend: Show confirmation modal
    Frontend->>API: Request URL update
    API->>Channel: Validate URL
    Channel->>ReservedNames: Check if reserved
    ReservedNames-->>Channel: Return status
    Channel-->>API: Return validation result
    API-->>Frontend: Return success/error
    Frontend-->>User: Show result
Loading

private

def set_reserved_public_name
@reserved_public_name = ReservedPublicName.find(params[:id])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reported by reviewdog 🐶
[semgrep] Found an unscoped find(...) with user-controllable input. If the ActiveRecord model being searched against is sensitive, this may lead to Insecure Direct Object Reference (IDOR) behavior and allow users to read arbitrary records. Scope the find to the current user, e.g. current_user.accounts.find(params[:id]).

Source: https://semgrep.dev/r/ruby.rails.security.brakeman.check-unscoped-find.check-unscoped-find


Cc @thypon @kdenhartog

@@ -95,6 +95,9 @@ export default function PublicChannelPage({publicIdentifier, previewMode}) {
<div className={`${styles['privacy-disclaimer']}`}>
{t('publicChannelPage.privacyDisclaimer')}
</div>
<div className={`${styles['privacy-disclaimer']}`}>
{t('publicChannelPage.report_sus_urls')}<a target="_blank" rel="noopener noreferrer" href="https://support.brave.com/hc/en-us/requests/new">https://support.brave.com/hc/en-us/requests/new</a>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reported by reviewdog 🐶
[semgrep] Translation key 'publicChannelPage.report_sus_urls' should match format 'MODULE.FEATURE.*'

Source: https://semgrep.dev/r/typescript.react.portability.i18next.i18next-key-format.i18next-key-format


Cc @thypon @kdenhartog


return (
<div>
<p className={styles['privacy-text']}>{t('contribution_pages.confirmation_text')}</p>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reported by reviewdog 🐶
[semgrep] Translation key 'contribution_pages.confirmation_text' should match format 'MODULE.FEATURE.*'

Source: https://semgrep.dev/r/typescript.react.portability.i18next.i18next-key-format.i18next-key-format


Cc @thypon @kdenhartog

style={{ margin: '10px 0px', width: '320px' }}
kind='outline'
>
{t('shared.cancel')}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reported by reviewdog 🐶
[semgrep] Translation key 'shared.cancel' should match format 'MODULE.FEATURE.*'

Source: https://semgrep.dev/r/typescript.react.portability.i18next.i18next-key-format.i18next-key-format


Cc @thypon @kdenhartog

style={{ margin: '10px 0px', width: '320px' }}
kind='filled'
>
{t('shared.continue')}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reported by reviewdog 🐶
[semgrep] Translation key 'shared.continue' should match format 'MODULE.FEATURE.*'

Source: https://semgrep.dev/r/typescript.react.portability.i18next.i18next-key-format.i18next-key-format


Cc @thypon @kdenhartog

<span slot='errors'>{publicNameError}</span>
</Input>
<div className='pb-3 color-tertiary'>
{t('contribution_pages.public_url_note')}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reported by reviewdog 🐶
[semgrep] Translation key 'contribution_pages.public_url_note' should match format 'MODULE.FEATURE.*'

Source: https://semgrep.dev/r/typescript.react.portability.i18next.i18next-key-format.i18next-key-format


Cc @thypon @kdenhartog

rel="noreferrer"
href="https://support.brave.com/hc/en-us/articles/33646848629901-Creators-Custom-URLs-for-Contribution-Pages"
>
{t('contribution_pages.public_url_link')}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reported by reviewdog 🐶
[semgrep] Translation key 'contribution_pages.public_url_link' should match format 'MODULE.FEATURE.*'

Source: https://semgrep.dev/r/typescript.react.portability.i18next.i18next-key-format.i18next-key-format


Cc @thypon @kdenhartog

>
{t('contribution_pages.public_url_link')}
</Link>
{t('contribution_pages.public_url_note_2')}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reported by reviewdog 🐶
[semgrep] Translation key 'contribution_pages.public_url_note_2' should match format 'MODULE.FEATURE.*'

Source: https://semgrep.dev/r/typescript.react.portability.i18next.i18next-key-format.i18next-key-format


Cc @thypon @kdenhartog

Copy link

github-actions bot commented Feb 5, 2025

The following commits were not verified:
d3fc373 (unsigned)
6c0bdc2 (unsigned)

@Miyayes
Copy link
Collaborator

Miyayes commented Feb 5, 2025

@jlbyrne Text:

Choose a custom URL for your contribution page. See our support article for more information about custom URLs.

Modal:

Are you sure you would like to change the URL for your contribution page? Note:

  1. You can only change your URL once every 12 months.
  2. If you change your URL, your old URL will become unavailable for awhile before it can be used again.

On the contribution page itself:

  1. Make the first two paragraphs into 1 paragraph. (Remove the newline.) Remember to put a period in between the two sentences.

  2. Does this page have an inappropriate URL? Report it to Brave. ("Report it to Brave" is hyperlinked to the support form.)

@Miyayes
Copy link
Collaborator

Miyayes commented Feb 5, 2025

Can we remove the space between .../c/ and VANITYURLHERE?

for VANITYURLHERE, you can bold the text in the field.

@jlbyrne Question: Given the cooldown on changing your URL, we should gray out the field and put different text if the user is still in cooldown period. Are we able to show exactly how many days are remaining? If so, then we should gray out the field and say:

Your shareable URL can be changed in x days.

@jlbyrne
Copy link
Contributor Author

jlbyrne commented Feb 5, 2025

Can we remove the space between .../c/ and VANITYURLHERE?

for VANITYURLHERE, you can bold the text in the field.

@jlbyrne Question: Given the cooldown on changing your URL, we should gray out the field and put different text if the user is still in cooldown period. Are we able to show exactly how many days are remaining? If so, then we should gray out the field and say:

Your shareable URL can be changed in x days.

So the cooldown period is for use of the custom url itself, across all users. A user can still change their url to something else.

@Miyayes
Copy link
Collaborator

Miyayes commented Feb 6, 2025

So the cooldown period is for use of the custom url itself, across all users. A user can still change their url to something else.

If a user has no cooldown on changing their URL, then can't they just run an attack and basically lock out every single possible URL? For example, every 1 second, I change my custom URL to something else, locking out all the custom URLs I touch for 1 month. @jlbyrne @kdenhartog

@jlbyrne
Copy link
Contributor Author

jlbyrne commented Feb 6, 2025

If a user has no cooldown on changing their URL, then can't they just run an attack and basically lock out every single possible URL? For example, every 1 second, I change my custom URL to something else, locking out all the custom URLs I touch for 1 month. @jlbyrne @kdenhartog

That's correct. I could add in an additional cooldown per user if necessary?

@Miyayes
Copy link
Collaborator

Miyayes commented Feb 10, 2025

If a user has no cooldown on changing their URL, then can't they just run an attack and basically lock out every single possible URL? For example, every 1 second, I change my custom URL to something else, locking out all the custom URLs I touch for 1 month. @jlbyrne @kdenhartog

That's correct. I could add in an additional cooldown per user if necessary?

Yes, I think that is necessary. How about we make it 60 days? cc: @kdenhartog

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants