mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
feat: imgur proxying for blocked countries (#7104)
This commit is contained in:
@@ -32,6 +32,7 @@ import { LabrinthCollectionsModule } from './labrinth/collections'
|
||||
import { LabrinthContentV3Module } from './labrinth/content/v3'
|
||||
import { LabrinthExternalProjectsInternalModule } from './labrinth/external-projects/internal'
|
||||
import { LabrinthFriendsV3Module } from './labrinth/friends/v3'
|
||||
import { LabrinthGeoIpModule } from './labrinth/geoip'
|
||||
import { LabrinthGlobalsInternalModule } from './labrinth/globals/internal'
|
||||
import { LabrinthImagesV3Module } from './labrinth/images/v3'
|
||||
import { LabrinthLimitsV3Module } from './labrinth/limits/v3'
|
||||
@@ -111,6 +112,7 @@ export const MODULE_REGISTRY = {
|
||||
labrinth_content_v3: LabrinthContentV3Module,
|
||||
labrinth_external_projects_internal: LabrinthExternalProjectsInternalModule,
|
||||
labrinth_friends_v3: LabrinthFriendsV3Module,
|
||||
labrinth_geoip: LabrinthGeoIpModule,
|
||||
labrinth_globals_internal: LabrinthGlobalsInternalModule,
|
||||
labrinth_images_v3: LabrinthImagesV3Module,
|
||||
labrinth_moderation_internal: LabrinthModerationInternalModule,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { AbstractModule } from '../../core/abstract-module'
|
||||
|
||||
export class LabrinthGeoIpModule extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'labrinth_geoip'
|
||||
}
|
||||
|
||||
public async getCountry(): Promise<string | undefined> {
|
||||
const trace = await this.client.request<string>('/trace', {
|
||||
api: 'https://api.modrinth.com',
|
||||
version: 'cdn-cgi',
|
||||
method: 'GET',
|
||||
skipAuth: true,
|
||||
})
|
||||
const country = trace
|
||||
.split('\n')
|
||||
.find((line) => line.startsWith('loc='))
|
||||
?.slice(4)
|
||||
.toUpperCase()
|
||||
|
||||
return country?.match(/^[A-Z]{2}$/) ? country : undefined
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export * from './collections'
|
||||
export * from './content/v3'
|
||||
export * from './external-projects/internal'
|
||||
export * from './friends/v3'
|
||||
export * from './geoip'
|
||||
export * from './globals/internal'
|
||||
export * from './images/v3'
|
||||
export * from './limits/v3'
|
||||
|
||||
@@ -18,4 +18,5 @@ export * from './project-page-new'
|
||||
export * from './server-context'
|
||||
export * from './server-settings-modal'
|
||||
export * from './tags'
|
||||
export * from './user-country'
|
||||
export * from './web-notifications'
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { setMarkdownUserCountryResolver } from '@modrinth/utils'
|
||||
import type { Ref } from 'vue'
|
||||
import { hasInjectionContext } from 'vue'
|
||||
|
||||
import { createContext } from './create-context'
|
||||
|
||||
const [injectUserCountryContext, provideUserCountryContext] = createContext<Ref<string>>(
|
||||
'root',
|
||||
'userCountry',
|
||||
)
|
||||
|
||||
let clientCountry: Ref<string> | null = null
|
||||
|
||||
export const injectUserCountry = injectUserCountryContext
|
||||
export const useUserCountry = injectUserCountryContext
|
||||
|
||||
export function provideUserCountry(country: Ref<string>) {
|
||||
if (typeof window !== 'undefined') {
|
||||
clientCountry = country
|
||||
}
|
||||
|
||||
return provideUserCountryContext(country)
|
||||
}
|
||||
|
||||
setMarkdownUserCountryResolver(() => {
|
||||
const injectedCountry = hasInjectionContext() ? injectUserCountryContext(null) : null
|
||||
return injectedCountry?.value ?? clientCountry?.value
|
||||
})
|
||||
@@ -4,6 +4,39 @@ import xss from 'xss'
|
||||
// @ts-expect-error xss types don't reflect CJS default export shape
|
||||
const { escapeAttrValue, FilterXSS, safeAttrValue, whiteList } = xss
|
||||
|
||||
// Imgur is blocked in UK and Indonesia
|
||||
const imgurProxyCountries = new Set(['GB', 'ID'])
|
||||
|
||||
type MarkdownUserCountryResolver = () => string | null | undefined
|
||||
|
||||
let resolveMarkdownUserCountry: MarkdownUserCountryResolver = () => undefined
|
||||
|
||||
export const setMarkdownUserCountryResolver = (resolver: MarkdownUserCountryResolver) => {
|
||||
resolveMarkdownUserCountry = resolver
|
||||
}
|
||||
|
||||
const shouldProxyImgur = () => {
|
||||
try {
|
||||
const country = resolveMarkdownUserCountry()
|
||||
return country ? imgurProxyCountries.has(country.toUpperCase()) : false
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const getImgurProxyUrl = (value: string) => {
|
||||
try {
|
||||
const url = new URL(value.replaceAll('&', '&'))
|
||||
if (url.hostname !== 'imgur.com' && !url.hostname.endsWith('.imgur.com')) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `https://external-content.duckduckgo.com/iu/?u=${encodeURIComponent(url.toString())}.png`
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const configuredXss = new FilterXSS({
|
||||
whiteList: {
|
||||
...whiteList,
|
||||
@@ -84,6 +117,18 @@ export const configuredXss = new FilterXSS({
|
||||
}
|
||||
},
|
||||
safeAttrValue(tag, name, value, cssFilter) {
|
||||
if (
|
||||
shouldProxyImgur() &&
|
||||
((tag === 'a' && name === 'href') ||
|
||||
((tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') &&
|
||||
(name === 'src' || name === 'srcset' || name === 'poster')))
|
||||
) {
|
||||
const proxiedUrl = getImgurProxyUrl(value)
|
||||
if (proxiedUrl) {
|
||||
return safeAttrValue(tag, name, proxiedUrl, cssFilter)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') &&
|
||||
(name === 'src' || name === 'srcset' || name === 'poster') &&
|
||||
|
||||
Reference in New Issue
Block a user