Skip to content

Commit

Permalink
Improve ad security, add CMP changes (#2399)
Browse files Browse the repository at this point in the history
  • Loading branch information
Geometrically committed Sep 14, 2024
1 parent 3d619e6 commit 95cd485
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 169 deletions.
17 changes: 11 additions & 6 deletions apps/app-frontend/src/components/ui/PromotionWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { get as getCreds } from '@/helpers/mr_auth.js'
import { handleError } from '@/store/notifications.js'
import { get_user } from '@/helpers/cache.js'
import { ChevronRightIcon } from '@modrinth/assets'
import { init_ads_window } from '@/helpers/ads.js'
import { init_ads_window, open_ads_link, record_ads_click } from '@/helpers/ads.js'
import { listen } from '@tauri-apps/api/event'
const showAd = ref(true)
Expand Down Expand Up @@ -73,6 +73,11 @@ function updateAdPosition(overrideShown = false) {
}
}
async function openPlusLink() {
await record_ads_click()
await open_ads_link('https://modrinth.com/plus', 'https://modrinth.com')
}
const unlisten = await listen('ads-scroll', (event) => {
if (adsWrapper.value) {
adsWrapper.value.parentNode.scrollTop += event.payload.scroll
Expand Down Expand Up @@ -105,17 +110,17 @@ onUnmounted(() => {
class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised cursor-pointer"
>
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
<p class="m-0 text-2xl font-bold text-contrast">90% of ad revenue goes to creators</p>
<a
href="https://modrinth.com/plus"
class="mt-auto items-center gap-1 text-purple hover:underline"
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
<button
class="mt-auto items-center gap-1 text-purple hover:underline bg-transparent border-none text-left cursor-pointer outline-none"
@click="openPlusLink"
>
<span>
Support creators and Modrinth ad-free with
<span class="font-bold">Modrinth+</span>
</span>
<ChevronRightIcon class="relative top-[3px] h-5 w-5" />
</a>
</button>
</div>
</div>
</template>
8 changes: 8 additions & 0 deletions apps/app-frontend/src/helpers/ads.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ export async function show_ads_window() {
export async function hide_ads_window(reset) {
return await invoke('plugin:ads|hide_ads_window', { reset })
}

export async function record_ads_click() {
return await invoke('plugin:ads|record_ads_click')
}

export async function open_ads_link(path, origin) {
return await invoke('plugin:ads|open_link', { path, origin })
}
2 changes: 2 additions & 0 deletions apps/app/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ fn main() {
"hide_ads_window",
"scroll_ads_window",
"show_ads_window",
"record_ads_click",
"open_link",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
Expand Down
3 changes: 1 addition & 2 deletions apps/app/capabilities/ads.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"ads-window"
],
"permissions": [
"shell:allow-open",
"ads:default"
]
}
}
2 changes: 1 addition & 1 deletion apps/app/gen/schemas/acl-manifests.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/app/gen/schemas/capabilities.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["shell:allow-open","ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
28 changes: 28 additions & 0 deletions apps/app/gen/schemas/desktop-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,20 @@
"ads:allow-init-ads-window"
]
},
{
"description": "ads:allow-open-link -> Enables the open_link command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:allow-open-link"
]
},
{
"description": "ads:allow-record-ads-click -> Enables the record_ads_click command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:allow-record-ads-click"
]
},
{
"description": "ads:allow-scroll-ads-window -> Enables the scroll_ads_window command without any pre-configured scope.",
"type": "string",
Expand Down Expand Up @@ -348,6 +362,20 @@
"ads:deny-init-ads-window"
]
},
{
"description": "ads:deny-open-link -> Denies the open_link command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:deny-open-link"
]
},
{
"description": "ads:deny-record-ads-click -> Denies the record_ads_click command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:deny-record-ads-click"
]
},
{
"description": "ads:deny-scroll-ads-window -> Denies the scroll_ads_window command without any pre-configured scope.",
"type": "string",
Expand Down
28 changes: 28 additions & 0 deletions apps/app/gen/schemas/macOS-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,20 @@
"ads:allow-init-ads-window"
]
},
{
"description": "ads:allow-open-link -> Enables the open_link command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:allow-open-link"
]
},
{
"description": "ads:allow-record-ads-click -> Enables the record_ads_click command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:allow-record-ads-click"
]
},
{
"description": "ads:allow-scroll-ads-window -> Enables the scroll_ads_window command without any pre-configured scope.",
"type": "string",
Expand Down Expand Up @@ -348,6 +362,20 @@
"ads:deny-init-ads-window"
]
},
{
"description": "ads:deny-open-link -> Denies the open_link command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:deny-open-link"
]
},
{
"description": "ads:deny-record-ads-click -> Denies the record_ads_click command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:deny-record-ads-click"
]
},
{
"description": "ads:deny-scroll-ads-window -> Denies the scroll_ads_window command without any pre-configured scope.",
"type": "string",
Expand Down
13 changes: 8 additions & 5 deletions apps/app/src/api/ads-init.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
if (!window.modrinthClickListener) {
window.modrinthClickListener = true
document.addEventListener('click', function (e) {
document.addEventListener(
'click',
function (e) {
window.top.postMessage({ modrinthAdClick: true }, 'https://modrinth.com')

let target = e.target
while (target != null) {
if (target.matches('a')) {
Expand All @@ -12,8 +14,9 @@ if (!window.modrinthClickListener) {
}
target = target.parentElement
}
})
}
},
true,
)

window.open = (url, target, features) => {
window.top.postMessage({ modrinthOpenUrl: url }, 'https://modrinth.com')
Expand Down
61 changes: 59 additions & 2 deletions apps/app/src/api/ads.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
use serde::Serialize;
use std::collections::HashSet;
use std::time::{Duration, Instant};
use tauri::plugin::TauriPlugin;
use tauri::{Emitter, LogicalPosition, LogicalSize, Manager, Runtime};
use tauri::{
Emitter, Listener, LogicalPosition, LogicalSize, Manager, Runtime,
};
use tauri_plugin_shell::{open, ShellExt};
use tokio::sync::RwLock;

pub struct AdsState {
pub shown: bool,
pub size: Option<LogicalSize<f32>>,
pub position: Option<LogicalPosition<f32>>,
pub last_click: Option<Instant>,
pub malicious_origins: HashSet<String>,
}

pub fn init<R: Runtime>() -> TauriPlugin<R> {
Expand All @@ -16,6 +23,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
shown: true,
size: None,
position: None,
last_click: None,
malicious_origins: HashSet::new(),
}));

// We refresh the ads window every 5 minutes for performance
Expand Down Expand Up @@ -43,6 +52,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
hide_ads_window,
scroll_ads_window,
show_ads_window,
record_ads_click,
open_link,
])
.build()
}
Expand Down Expand Up @@ -75,7 +86,7 @@ pub async fn init_ads_window<R: Runtime>(
let _ = webview.set_size(LogicalSize::new(width, height));
}
} else if let Some(window) = app.get_window("main") {
let _ = window.add_child(
let window = window.add_child(
tauri::webview::WebviewBuilder::new(
"ads-window",
WebviewUrl::External(
Expand All @@ -93,6 +104,12 @@ pub async fn init_ads_window<R: Runtime>(
},
LogicalSize::new(width, height),
);

if let Ok(window) = window {
window.listen_any("click", |event| {
println!("click: {:?}", event);
});
}
}

Ok(())
Expand Down Expand Up @@ -159,3 +176,43 @@ pub async fn scroll_ads_window<R: Runtime>(

Ok(())
}

#[tauri::command]
pub async fn record_ads_click<R: Runtime>(
app: tauri::AppHandle<R>,
) -> crate::api::Result<()> {
let state = app.state::<RwLock<AdsState>>();

let mut state = state.write().await;
state.last_click = Some(Instant::now());

Ok(())
}

#[tauri::command]
pub async fn open_link<R: Runtime>(
app: tauri::AppHandle<R>,
path: String,
origin: String,
) -> crate::api::Result<()> {
let state = app.state::<RwLock<AdsState>>();
let mut state = state.write().await;

if url::Url::parse(&path).is_ok()
&& !state.malicious_origins.contains(&origin)
{
if let Some(last_click) = state.last_click {
if last_click.elapsed() < Duration::from_millis(100) {
let _ = app.shell().open(&path, None);
state.last_click = None;

return Ok(());
}
}
}

tracing::info!("Malicious click: {path} origin {origin}");
state.malicious_origins.insert(origin);

Ok(())
}
2 changes: 1 addition & 1 deletion apps/frontend/src/components/ui/AdPlaceholder.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised">
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
<p class="m-0 text-2xl font-bold text-contrast">90% of ad revenue goes to creators</p>
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
<nuxt-link to="/plus" class="mt-auto items-center gap-1 text-purple hover:underline">
<span>
Support creators and Modrinth ad-free with
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/src/pages/dashboard/revenue/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
You have
<strong>{{ $formatMoney(userBalance.available) }}</strong>
available to withdraw. <strong>{{ $formatMoney(userBalance.pending) }}</strong> of your
balance is <a class="text-link" href="#">pending</a>.
balance is <nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
</p>
</div>
<p v-else>
You have made
<strong>{{ $formatMoney(userBalance.available) }}</strong
>, which is under the minimum of ${{ minWithdraw }} to withdraw.
<strong>{{ $formatMoney(userBalance.pending) }}</strong> of your balance is
<a class="text-link" href="#">pending</a>.
<nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
</p>
<div class="input-group mt-4">
<nuxt-link
Expand Down
Loading

0 comments on commit 95cd485

Please sign in to comment.