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

feat: disallow login to defederated, banned, or malicious domains #2165

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/cloudflare_pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Deploy to Cloudflare Pages

on:
# Uncomment *ONLY* this to enable CD for every new release
# push:
# tags:
# - 'v*'
# Uncomment *ONLY* this to enable CD for every push to the `main` branch
# push:
# branch: release
workflow_dispatch: {}

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- run: corepack enable

- name: Set node
uses: actions/setup-node@v3
with:
node-version: 18
cache: pnpm

- name: Install build dependencies
run: npm install -g pnpm && pnpm i --frozen-lockfile

- name: Build Elk
run: pnpm run build
env:
HOST: 0.0.0.0
NODE_ENV: production
NUXT_DEPLOY_URL: ${{ vars.NUXT_DEPLOY_URL }}
NUXT_PUBLIC_DEFAULT_SERVER: ${{ vars.NUXT_PUBLIC_DEFAULT_SERVER }}
NUXT_STORAGE_DRIVER: kv-binding
VITE_DEV_PWA: true
NITRO_PRESET: ${{ vars.NITRO_PRESET }}

- name: Publish
uses: cloudflare/pages-action@1
with:
directory: .output/public
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ secrets.CLOUDFLARE_PAGES_PROJECT_NAME }}
apiToken: ${{ secrets.CLOUDFLARE_PAGES_API_TOKEN }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
22 changes: 17 additions & 5 deletions components/user/UserSignIn.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ onMounted(async () => {
onClickOutside(input, () => {
autocompleteShow = false
})

const disableSubmitButton = $computed(() => {
return ((server.value.search(/^[a-z0-9]([a-z0-9_\-]+\.?[a-z0-9_\-]+)*\.[a-z]{2,}/ig) === -1) || !isReachableDomain(server.value))
})
</script>

<template>
Expand Down Expand Up @@ -161,14 +165,22 @@ onClickOutside(input, () => {
</div>
</div>
<div text-secondary text-sm flex>
<div i-ri:lightbulb-line me-1 />
<div v-if="!isReachableDomain(server)" text-primary i-ri:alert-line me-1 />
<div v-else i-ri:lightbulb-line me-1 />
<span>
<i18n-t keypath="user.tip_no_account">
<NuxtLink href="https://joinmastodon.org/servers" target="_blank" external class="text-primary" hover="underline">{{ $t('user.tip_register_account') }}</NuxtLink>
</i18n-t>
<template v-if="!isReachableDomain(server)">
<i18n-t keypath="user.tip_unreachable_host">
<span role="alert" p-0 m-0 text-xs text-primary>{{ server ?? '' }}</span>
</i18n-t>
</template>
<template v-else>
<i18n-t keypath="user.tip_no_account">
<NuxtLink href="https://joinmastodon.org/servers" target="_blank" external class="text-primary" hover="underline">{{ $t('user.tip_register_account') }}</NuxtLink>
</i18n-t>
</template>
</span>
</div>
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!server || busy">
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!!disableSubmitButton || !server || busy">
<span v-if="busy" aria-hidden="true" block animate animate-spin preserve-3d class="rtl-flip">
<span block i-ri:loader-2-fill aria-hidden="true" />
</span>
Expand Down
211 changes: 211 additions & 0 deletions composables/filter-domains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/**
* Instances with more than 50 moderation actions against them, per:
* @source https://gist.github.com/yetzt/32c754fbe87009f8c161f40541147c70
*
* @usage ```
* const breakfasts = ["bacon", "eggs", "oatmeal", "toast", "cereal"];
* const order = "Let me get some sw.Bacon and eggs, please";
*
* order.match(new RegExp(`\\b(${breakfasts.join("|")})\\b`, "ig"));
* // Returns ['eggs']
*
* ```
*/
const tdDomains: string[] = [
'poa.st',
'freespeechextremist.com',
'pawoo.net',
'gab.com',
'baraag.net',
'kiwifarms.cc',
'shitposter.club',
'bae.st',
'noagendasocial.com',
'gameliberty.club',
'spinster.xyz',
'gab.ai',
'nicecrew.digital',
'pieville.net',
'yggdrasil.social',
'sinblr.com',
'iddqd.social',
'detroitriotcity.com',
'freecumextremist.com',
'freeatlantis.com',
'glindr.org',
'brighteon.social',
'beefyboys.win',
'chudbuds.lol',
'gleasonator.com',
'neckbeard.xyz',
'rdrama.cc',
'freezepeach.xyz',
'cawfee.club',
'cum.salon',
'ryona.agency',
'varishangout.net',
'nobodyhasthe.biz',
'smuglo.li',
'truthsocial.com',
'solagg.com',
'shortstackran.ch',
'paypig.org',
'posting.lolicon.rocks',
'gitmo.life',
'rakket.app',
'mstdn.foxfam.club',
'develop.gab.com',
'anime.website',
'kiwifarms.is',
'humblr.social',
'10minutepleroma.com',
'honkwerx.tech',
'newjack.city',
'eientei.org',
'leafposter.club',
'weedis.life',
'd-fens.systems',
'eveningzoo.club',
'kiwifarms.net',
'bitcoinhackers.org',
'truthsocial.co.in',
'switter.at',
'freefedifollowers.ga',
'liberdon.com',
'hunk.city',
'glowers.club',
'qoto.org',
'seal.cafe',
'sealion.club',
'sleepy.cafe',
'firedragonstudios.com',
'gabfed.com',
'gearlandia.haus',
'jaeger.website',
'toot.love',
'nazi.social',
'hentai.baby',
'pooper.social',
'pleroma.rareome.ga',
'socnet.supes.com',
'sneed.social',
'search.fedi.app',
'preteengirls.biz',
'skippers-bin.com',
'crypto-group-buy.com',
'ligma.pro',
'masochi.st',
'unsafe.space',
'youjo.love',
'social.quodverum.com',
'pedo.school',
'nnia.space',
'tastingtraffic.net',
'raplst.town',
'fedichive.tk',
'bsd.moe',
'fr13nd5.com',
'catgirl.life',
'degenerates.fail',
'milker.cafe',
'feminism.lgbt',
'wagesofsinisdeath.com',
'sneak.berlin',
'exited.eu',
'husk.site',
'beefyboys.club',
'ns.auction',
'midnightride.rs',
'gs.smuglo.li',
'archivefedifor.fun',
'a.sc',
'wintermute.fr.to',
'rage.lol',
'justicewarrior.social',
'mugicha.club',
'outpoa.st',
'freak.university',
'frennet.link',
'rainbowdash.net',
'albin.social',
'lets.saynoto.lgbt',
'neenster.org',
'cliterati.club',
'freespeech.group',
'coom.club',
'comfyboy.club',
'clubcyberia.co',
'eatthebugs.social',
'gab.protohype.net',
'fedi.app',
'tkammer.de',
'melalandia.tk',
'rapefeminists.network',
'weeaboo.space',
'lain.sh',
'blob.cat',
'wolfgirl.bar',
'bajax.us',
'springbo.cc',
'wurm.host',
'shigusegubu.club',
'zztails.gay',
'soykaf.com',
]

/**
* Domains that don't meet the threshold for automatic exclusion, but are known to be havens
* for hateful conduct or illegal content. These are applied as open-ended RegEx to prevent
* ban evasion
*
* @source https://joinfediverse.wiki/FediBlock
*
* @usage ```
* const breakfasts = ["bacon", "eggs", "oatmeal", "toast", "cereal"];
* const order = "Let me get some sw.Bacon and eggs, please";
*
* order.match(new RegExp(`(${breakfasts.join("|")})\\b`, "ig"));
* // Returns ['Bacon', 'eggs']
*
* ```
*/
const fbDomains: string[] = [
'101010.pl',
'kitsunemimi.club',
'urspringer.de',
'mastodon.cf',
'valkyrie.world',
'wiki-tube.de',
'bitcoinhackers.org',
'voring.me',
'lgbtfree.zone',
'workers.dev',
]

const otherDomains: string[] = [
'101010',
'kiwi',
'loli',
'lolli',
'troll',
'mastodong',
'shitpos',
'extremis',
'antivaxxer',
'genitalia',
'fuck',
'birdsite',
'twitter',
'porn',
'higgers',
]

export function isReachableDomain(label: string): boolean {
return (
(label.match(new RegExp(`(${fbDomains.join('|')})\\b`, 'ig')) === null)
&& (label.match(new RegExp(`(${tdDomains.join('|')})\\b`, 'ig')) === null)
&& (label.match(new RegExp(`(${otherDomains.join('|')})`, 'ig')) === null)
&& (label.match(/[^\040-\176 \t\r\n]/gi) === null)
&& (label.match(/<[^>]+>/ig) === null)
)
}
9 changes: 9 additions & 0 deletions composables/sign-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ export function useSignIn(input?: Ref<HTMLInputElement | undefined>) {
if (busy.value)
return

if (!isReachableDomain(server.value)) {
await openErrorDialog({
title: t('common.error'),
messages: [t('error.sign_in_error')],
close: t('action.close'),
})
return
}

busy.value = true
error.value = false
displayError.value = false
Expand Down
61 changes: 0 additions & 61 deletions docs/content/2.deployment/1.netlify.md

This file was deleted.

Loading