-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: finish implementing link verification.
- Loading branch information
Showing
12 changed files
with
200 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,81 +1,67 @@ | ||
import { parseHTML } from 'linkedom'; | ||
import { GitHubLinkVerificationStrategy } from './strategy/GitHubLinkVerificationStrategy'; | ||
|
||
import type { WebLink } from '$lib/leaf/profile'; | ||
import type { LinkVerificationStrategyFactory } from './strategy/LinkVerificationStrategy'; | ||
import { env } from '$env/dynamic/public'; | ||
import { DefaultLinkVerificationStrategy } from './strategy/DefaultLinkVerificationStrategy'; | ||
|
||
export const VERIFIABLE_ORIGIN_STRATEGY: Record<string, LinkVerificationStrategyFactory> = { | ||
'https://github.com': (dom) => new GitHubLinkVerificationStrategy(dom) | ||
// Put custom link verifiers for specific domains here once we have them. | ||
// 'https://github.com': GitHubLinkVerificationStrategy | ||
}; | ||
|
||
export const VERIFIABLE_ORIGINS: string[] = Object.keys(VERIFIABLE_ORIGIN_STRATEGY); | ||
|
||
export function verifiableOriginFilter(webLink: WebLink): boolean { | ||
return ( | ||
VERIFIABLE_ORIGINS.findIndex((url) => new URL(webLink.url).origin === new URL(url).origin) !== | ||
-1 | ||
); | ||
} | ||
|
||
export type LinkArray = { label?: string; url: string }[]; | ||
|
||
export class LinkVerifier { | ||
private webLinks: LinkArray; | ||
private webLinks: string[]; | ||
private userName: string; | ||
|
||
constructor(links: LinkArray, userName: string) { | ||
this.webLinks = links.filter(verifiableOriginFilter); | ||
this.userName = userName; | ||
constructor(links: string[], username: string) { | ||
this.webLinks = links; | ||
this.userName = username; | ||
} | ||
|
||
private static async fetchHtml(webLink: WebLink): Promise<Window> { | ||
const res = await fetch(webLink.url); | ||
private static async fetchHtml(webLink: string): Promise<Window> { | ||
const res = await fetch(webLink); | ||
|
||
if (res.status === 200) { | ||
const resText = await res.text(); | ||
return parseHTML(resText); | ||
} | ||
|
||
throw new Error( | ||
`Failed to fetch "${webLink.url}", expected a 200 HTTP Response, got "${res.status}" instead.` | ||
`Failed to fetch "${webLink}", expected a 200 HTTP Response, got "${res.status}" instead.` | ||
); | ||
} | ||
|
||
get links(): WebLink[] { | ||
get links(): string[] { | ||
return [...this.webLinks]; | ||
} | ||
|
||
async verify(): Promise<WebLink[]> { | ||
const verifiedLinks: WebLink[] = []; | ||
async verify(): Promise<string[]> { | ||
const verifiedLinks: string[] = []; | ||
|
||
for (const webLink of this.webLinks) { | ||
const origin = new URL(webLink.url).origin; | ||
const linkVerificationStrategyFactory = VERIFIABLE_ORIGIN_STRATEGY[ | ||
origin | ||
] as LinkVerificationStrategyFactory | null; | ||
|
||
if (typeof linkVerificationStrategyFactory === 'function') { | ||
const dom = await LinkVerifier.fetchHtml(webLink); | ||
const strategy = linkVerificationStrategyFactory(dom); | ||
const isVerified = await strategy.verify(this.userProfileLink()); | ||
|
||
if (isVerified) { | ||
verifiedLinks.push(webLink); | ||
} | ||
|
||
const origin = new URL(webLink).origin; | ||
const linkVerificationStrategyFactory = | ||
(VERIFIABLE_ORIGIN_STRATEGY[origin] as LinkVerificationStrategyFactory) || | ||
DefaultLinkVerificationStrategy; | ||
|
||
let dom; | ||
try { | ||
dom = await LinkVerifier.fetchHtml(webLink); | ||
} catch (_) { | ||
// If it can't be fetched, it just isn't verified. | ||
continue; | ||
} | ||
const strategy = new linkVerificationStrategyFactory(dom); | ||
const isVerified = await strategy.verify(this.userName); | ||
|
||
if (isVerified) { | ||
verifiedLinks.push(webLink); | ||
} | ||
|
||
// This should not happen, but if we got here somehow its likely we got a false positive | ||
// in the origins map. | ||
throw new Error(`The WebLink with URL "${webLink.url}" is not supported by any strategy.`); | ||
continue; | ||
} | ||
|
||
return verifiedLinks; | ||
} | ||
|
||
private userProfileLink(): string { | ||
return `${env.PUBLIC_URL}/${this.userName}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/lib/link_verifier/strategy/DefaultLinkVerificationStrategy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { env } from '$env/dynamic/public'; | ||
import { usernames } from '$lib/usernames/client'; | ||
import { LinkVerificationStrategy } from './LinkVerificationStrategy'; | ||
|
||
export class DefaultLinkVerificationStrategy extends LinkVerificationStrategy { | ||
constructor(dom: Window) { | ||
super('DefaultLinkVerificationStrategy', dom); | ||
} | ||
|
||
async verify(userDomain: string): Promise<boolean> { | ||
const document = this.dom.document; | ||
const nodes = Array.from(document.querySelectorAll('a[rel~="me"]')); | ||
for (const node of nodes) { | ||
const href = node.getAttribute('href'); | ||
try { | ||
if (href) { | ||
const url = new URL(href); | ||
if ( | ||
url.host == userDomain || | ||
url.href == env.PUBLIC_URL + '/' + userDomain || | ||
url.href == env.PUBLIC_URL + '/' + usernames.shortNameOrDomain(userDomain) | ||
) | ||
return true; | ||
} | ||
} catch (_) {} // Just in case URL is invalid | ||
} | ||
|
||
return false; | ||
} | ||
} |
25 changes: 0 additions & 25 deletions
25
src/lib/link_verifier/strategy/GitHubLinkVerificationStrategy.ts
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.