refactor: use uhtml, cleanup express logic

This commit is contained in:
Hailey
2025-06-24 14:54:56 -07:00
parent a8d62c402d
commit c81ad8efe4
6 changed files with 340 additions and 232 deletions
+21
View File
@@ -0,0 +1,21 @@
import {type Hole, html} from 'uhtml'
export function linkRedirectContents(link: string): Hole {
return html`
<html>
<head>
<meta http-equiv="refresh" content="0; URL='${link}'" />
<meta
http-equiv="Cache-Control"
content="no-store, no-cache, must-revalidate, max-age=0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<style>
:root {
color-scheme: light dark;
}
</style>
</head>
</html>
`
}
+29
View File
@@ -0,0 +1,29 @@
import {type Hole, html} from 'uhtml'
export function linkWarningContents(opts: {
type: 'warn' | 'block'
link: string
}): Hole {
return html`
<div class="warning-icon">⚠️</div>
<h1>
${opts.type === 'warn' ? 'Potentially Dangerous Link' : 'Blocked Link'}
</h1>
<p class="warning-text">
${opts.type === 'warn'
? 'This link may be malicious. You should proceed at your own risk.'
: 'This link has been identified as malicious and has blocked for your safety.'}
</p>
<div class="blocked-site">
<p class="site-url">${opts.link}</p>
</div>
<div class="button-group">
${opts.type === 'warn'
? html`<a class="button secondary" href="${opts.link}"
>Continue Anyway</a
>`
: null}
<a class="button primary" href="https://bsky.app">Return to Bluesky</a>
</div>
`
}
+133
View File
@@ -0,0 +1,133 @@
import {type Hole, html} from 'uhtml'
export function linkWarningLayout(
title: string,
containerContents: Hole,
): Hole {
return html`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Cache-Control"
content="no-store, no-cache, must-revalidate, max-age=0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${title}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Arial, sans-serif;
background-color: #ffffff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 400px;
text-align: center;
}
.warning-icon {
font-size: 48px;
margin-bottom: 16px;
}
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
color: #000000;
}
.warning-text {
font-size: 15px;
color: #536471;
line-height: 1.4;
margin-bottom: 24px;
padding: 0 20px;
}
.blocked-site {
background-color: #f7f9fa;
border-radius: 12px;
padding: 16px;
margin-bottom: 24px;
text-align: left;
word-break: break-all;
}
.site-name {
font-size: 16px;
font-weight: 500;
color: #000000;
margin-bottom: 4px;
word-break: break-word;
display: block;
text-align: center;
}
.site-url {
font-size: 14px;
color: #536471;
word-break: break-all;
display: block;
text-align: center;
}
.button {
border: none;
border-radius: 24px;
padding: 12px 32px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
max-width: 280px;
transition: background-color 0.2s;
}
.primary {
background-color: #1d9bf0;
color: white;
}
.secondary {
}
.back-button:hover {
background-color: #1a8cd8;
}
.back-button:active {
background-color: #1681c4;
}
@media (max-width: 480px) {
.warning-text {
padding: 0 10px;
}
.blocked-site {
padding: 8px;
}
}
</style>
</head>
<body>
<div class="container">${containerContents}</div>
</body>
</html>
`
}
+53 -232
View File
@@ -2,10 +2,13 @@ import assert from 'node:assert'
import {ToolsOzoneSafelinkDefs} from '@atproto/api'
import {DAY, SECOND} from '@atproto/common'
import escapeHTML from 'escape-html'
import {type Express} from 'express'
import {type Hole} from 'uhtml'
import {type AppContext} from '../context.js'
import {linkRedirectContents} from '../html/linkRedirectContents.js'
import {linkWarningContents} from '../html/linkWarningContents.js'
import {linkWarningLayout} from '../html/linkWarningLayout.js'
import {redirectLogger} from '../logger.js'
import {handler} from './util.js'
@@ -41,247 +44,65 @@ export default function (ctx: AppContext, app: Express) {
return res.status(302).end()
}
// Default to a max age header
res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`)
res.type('html')
res.status(200)
res.type('html')
let hole: Hole | undefined
if (ctx.cfg.service.safelinkEnabled) {
const rulePresent: ToolsOzoneSafelinkDefs.Event | undefined =
ctx.cfg.eventCache.smartGet(link)
// begin link safety checks
if (
rulePresent &&
rulePresent.eventType === ToolsOzoneSafelinkDefs.REMOVERULE
) {
redirectLogger.info(
`No rule or Remove rule matched for ${rulePresent.url}`,
)
const escaped = escapeHTML(url.href)
const html = safe_redirect(escaped)
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(html),
})
res.end(html) // Critical - must call end()
return
}
if (
rulePresent &&
rulePresent.action === ToolsOzoneSafelinkDefs.WHITELIST
) {
redirectLogger.info(`Whitelist rule matched for ${rulePresent.url}`)
const escaped = escapeHTML(url.href)
const html = safe_redirect(escaped)
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(html),
})
res.end(html) // Critical - must call end()
return
}
if (
rulePresent &&
rulePresent.action === ToolsOzoneSafelinkDefs.BLOCK
) {
redirectLogger.info(`Block rule matched for ${rulePresent.url}`)
res.setHeader('Cache-Control', 'no-store')
const html = warnRedirect(
'Blocked Link',
'This link has been identified as malicious, it has been blocked to protect your account and data',
'Go Back To BlueSky',
'DANGER',
escapeHTML(url.toString()),
`https://${ctx.cfg.service.appHostname}`,
)
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(html),
})
res.end(html) // Critical - must call end()
return
}
if (rulePresent && rulePresent.action === ToolsOzoneSafelinkDefs.WARN) {
redirectLogger.info(`Warn rule matched for ${rulePresent.url}`)
res.setHeader('Cache-Control', 'no-store')
const html = warnRedirect(
'Warning: Malicious Link',
'This link has been identified as malicious, continue at your own risk',
'continue at your own risk',
'DANGER',
escapeHTML(url.toString()),
`https://${ctx.cfg.service.appHostname}`,
)
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(html),
})
res.end(html) // Critical - must call end()
return
if (rulePresent) {
switch (rulePresent.action) {
case ToolsOzoneSafelinkDefs.BLOCK:
hole = linkWarningLayout(
'Blocked Link Warning',
linkWarningContents({
type: 'block',
link: url.href,
}),
)
res.setHeader('Cache-Control', 'no-store')
redirectLogger.info(`Block rule matched for ${rulePresent.url}`)
break
case ToolsOzoneSafelinkDefs.WARN:
hole = linkWarningLayout(
'Malicious Link Warning',
linkWarningContents({
type: 'warn',
link: url.href,
}),
)
res.setHeader('Cache-Control', 'no-store')
redirectLogger.info(`Warn rule matched for ${rulePresent.url}`)
break
case ToolsOzoneSafelinkDefs.WHITELIST:
redirectLogger.info(
`Whitelist rule matched for ${rulePresent.url}`,
)
break
case ToolsOzoneSafelinkDefs.REMOVERULE:
redirectLogger.info(`Remove rule matched for ${rulePresent.url}`)
break
default:
redirectLogger.warn(
`${rulePresent.action} rule (an unknown rule) matched for ${rulePresent.url}`,
)
}
} else {
redirectLogger.info(`No rule present for ${rulePresent.url}`)
}
}
const escaped = escapeHTML(url.href)
const html = safe_redirect(escaped)
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(html),
})
res.end(html) // Critical - must call end()
return
// If there is no hole defined yet, we will create a redirect hole
if (!hole) {
hole = linkRedirectContents(url.href)
}
return res.end(String(hole))
}),
)
}
const safe_redirect = (escaped: string) =>
`<html><head>
<meta http-equiv="refresh" content="0; URL='${escaped}'" />
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<style>:root { color-scheme: light dark; }</style>
</head></html>`
const warnRedirect = (
mainText: string,
warningText: string,
buttonText: string,
reason: string,
siteUrl: string,
returnUrl = 'https://bsky.app',
) => {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${mainText}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
background-color: #ffffff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 400px;
text-align: center;
}
.warning-icon {
font-size: 48px;
margin-bottom: 16px;
}
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
color: #000000;
}
.warning-text {
font-size: 15px;
color: #536471;
line-height: 1.4;
margin-bottom: 24px;
padding: 0 20px;
}
.blocked-site {
background-color: #f7f9fa;
border-radius: 12px;
padding: 16px;
margin-bottom: 24px;
text-align: left;
word-break: break-all;
}
.site-name {
font-size: 16px;
font-weight: 500;
color: #000000;
margin-bottom: 4px;
word-break: break-word;
display: block;
text-align: center;
}
.site-url {
font-size: 14px;
color: #536471;
word-break: break-all;
display: block;
text-align: center;
}
.back-button {
background-color: #1d9bf0;
color: white;
border: none;
border-radius: 24px;
padding: 12px 32px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
max-width: 280px;
transition: background-color 0.2s;
}
.back-button:hover {
background-color: #1a8cd8;
}
.back-button:active {
background-color: #1681c4;
}
@media (max-width: 480px) {
.warning-text {
padding: 0 10px;
}
.blocked-site {
padding: 8px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="warning-icon">⚠️</div>
<h1>${mainText}</h1>
<p class="warning-text">${escapeHTML(warningText)}</p>
<div class="blocked-site">
<span class="site-name">${escapeHTML(reason)}</span>
<span class="site-url">${escapeHTML(siteUrl)}</span>
</div>
<button class="back-button" id="redirect-button">${escapeHTML(
buttonText,
)}</button>
</div>
<script>
document.getElementById('redirect-button').addEventListener('click', function() {
window.location.href = ${JSON.stringify(returnUrl)};
});
</script>
</body>
</html>`
}