diff options
author | Hailey <me@haileyok.com> | 2025-02-24 13:59:57 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-24 13:59:57 -0800 |
commit | 61a14043e51475b64c5c505dd10d81a0165bb3f2 (patch) | |
tree | 5b6deda9cface0b2d4a04a61f59c2bc7e9b588eb /bskylink | |
parent | 3fc4c32ac62403269340c40ad529bfa67cd0a35a (diff) | |
download | voidsky-61a14043e51475b64c5c505dd10d81a0165bb3f2.tar.zst |
add to blink (#7788)
Diffstat (limited to 'bskylink')
-rw-r--r-- | bskylink/src/routes/createShortLink.ts (renamed from bskylink/src/routes/create.ts) | 0 | ||||
-rw-r--r-- | bskylink/src/routes/index.ts | 8 | ||||
-rw-r--r-- | bskylink/src/routes/redirect.ts | 55 | ||||
-rw-r--r-- | bskylink/src/routes/shortLink.ts | 54 |
4 files changed, 83 insertions, 34 deletions
diff --git a/bskylink/src/routes/create.ts b/bskylink/src/routes/createShortLink.ts index db7c3f809..db7c3f809 100644 --- a/bskylink/src/routes/create.ts +++ b/bskylink/src/routes/createShortLink.ts diff --git a/bskylink/src/routes/index.ts b/bskylink/src/routes/index.ts index f60b99bcb..cfdaf3eaa 100644 --- a/bskylink/src/routes/index.ts +++ b/bskylink/src/routes/index.ts @@ -1,9 +1,10 @@ import {Express} from 'express' import {AppContext} from '../context.js' -import {default as create} from './create.js' +import {default as createShortLink} from './createShortLink.js' import {default as health} from './health.js' import {default as redirect} from './redirect.js' +import {default as shortLink} from './shortLink.js' import {default as siteAssociation} from './siteAssociation.js' export * from './util.js' @@ -11,7 +12,8 @@ export * from './util.js' export default function (ctx: AppContext, app: Express) { app = health(ctx, app) // GET /_health app = siteAssociation(ctx, app) // GET /.well-known/apple-app-site-association - app = create(ctx, app) // POST /link - app = redirect(ctx, app) // GET /:linkId (should go last due to permissive matching) + app = redirect(ctx, app) // GET /redirect + app = createShortLink(ctx, app) // POST /link + app = shortLink(ctx, app) // GET /:linkId (should go last due to permissive matching) return app } diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts index 276aae1ca..4e7052af7 100644 --- a/bskylink/src/routes/redirect.ts +++ b/bskylink/src/routes/redirect.ts @@ -6,47 +6,40 @@ import {Express} from 'express' import {AppContext} from '../context.js' import {handler} from './util.js' +const INTERNAL_IP_REGEX = new RegExp( + '(^127.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$)|(^10.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$)|(^172.1[6-9]{1}[0-9]{0,1}.[0-9]{1,3}.[0-9]{1,3}$)|(^172.2[0-9]{1}[0-9]{0,1}.[0-9]{1,3}.[0-9]{1,3}$)|(^172.3[0-1]{1}[0-9]{0,1}.[0-9]{1,3}.[0-9]{1,3}$)|(^192.168.[0-9]{1,3}.[0-9]{1,3}$)|^localhost', + 'i', +) + export default function (ctx: AppContext, app: Express) { return app.get( - '/:linkId', + '/redirect', handler(async (req, res) => { - const linkId = req.params.linkId - const contentType = req.accepts(['html', 'json']) + let link = req.query.u assert( - typeof linkId === 'string', - 'express guarantees id parameter is a string', + typeof link === 'string', + 'express guarantees link query parameter is a string', ) - const found = await ctx.db.db - .selectFrom('link') - .selectAll() - .where('id', '=', linkId) - .executeTakeFirst() - if (!found) { - // potentially broken or mistyped link + link = decodeURIComponent(link) + + let url: URL | undefined + try { + url = new URL(link) + } catch {} + + if ( + !url || + (url.protocol !== 'http:' && url.protocol !== 'https:') || // is a http(s) url + (ctx.cfg.service.hostnames.includes(url.hostname.toLowerCase()) && + url.pathname === '/redirect') || // is a redirect loop + INTERNAL_IP_REGEX.test(url.hostname) // isn't directing to an internal location + ) { res.setHeader('Cache-Control', 'no-store') - if (contentType === 'json') { - return res - .status(404) - .json({ - error: 'NotFound', - message: 'Link not found', - }) - .end() - } - // send the user to the app res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`) return res.status(302).end() } - // build url from original url in order to preserve query params - const url = new URL( - req.originalUrl, - `https://${ctx.cfg.service.appHostname}`, - ) - url.pathname = found.path + res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`) - if (contentType === 'json') { - return res.json({url: url.href}).end() - } res.setHeader('Location', url.href) return res.status(301).end() }), diff --git a/bskylink/src/routes/shortLink.ts b/bskylink/src/routes/shortLink.ts new file mode 100644 index 000000000..276aae1ca --- /dev/null +++ b/bskylink/src/routes/shortLink.ts @@ -0,0 +1,54 @@ +import assert from 'node:assert' + +import {DAY, SECOND} from '@atproto/common' +import {Express} from 'express' + +import {AppContext} from '../context.js' +import {handler} from './util.js' + +export default function (ctx: AppContext, app: Express) { + return app.get( + '/:linkId', + handler(async (req, res) => { + const linkId = req.params.linkId + const contentType = req.accepts(['html', 'json']) + assert( + typeof linkId === 'string', + 'express guarantees id parameter is a string', + ) + const found = await ctx.db.db + .selectFrom('link') + .selectAll() + .where('id', '=', linkId) + .executeTakeFirst() + if (!found) { + // potentially broken or mistyped link + res.setHeader('Cache-Control', 'no-store') + if (contentType === 'json') { + return res + .status(404) + .json({ + error: 'NotFound', + message: 'Link not found', + }) + .end() + } + // send the user to the app + res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`) + return res.status(302).end() + } + // build url from original url in order to preserve query params + const url = new URL( + req.originalUrl, + `https://${ctx.cfg.service.appHostname}`, + ) + url.pathname = found.path + res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`) + if (contentType === 'json') { + return res.json({url: url.href}).end() + } + res.setHeader('Location', url.href) + return res.status(301).end() + }), + ) +} |