packages/server/src/utils/verification.ts 3.2 K raw
1
/**
2
 * Verification utilities for standard.site records.
3
 *
4
 * Publications are verified via /.well-known/site.standard.publication
5
 * Documents are verified via <link rel="site.standard.document"> in HTML
6
 */
7
8
/**
9
 * Verifies a publication by checking /.well-known/site.standard.publication
10
 * @param pubUrl The publication's base URL (e.g., "https://example.com")
11
 * @param siteUri The expected AT-URI of the publication (e.g., "at://did:plc:abc/site.standard.publication/rkey")
12
 * @returns true if the .well-known endpoint returns the matching AT-URI
13
 */
14
export async function verifyPublication(
15
  pubUrl: string,
16
  siteUri: string
17
): Promise<boolean> {
18
  try {
19
    const baseUrl = pubUrl.startsWith("http") ? pubUrl : `https://${pubUrl}`;
20
    const wellKnownUrl = `${baseUrl.replace(/\/$/, "")}/.well-known/site.standard.publication`;
21
22
    const response = await fetch(wellKnownUrl, {
23
      headers: { Accept: "text/plain" },
24
    });
25
26
    if (!response.ok) return false;
27
28
    const body = await response.text();
29
    return body.trim() === siteUri.trim();
30
  } catch {
31
    return false;
32
  }
33
}
34
35
/**
36
 * Verifies a document by checking for a matching <link rel="site.standard.document"> tag
37
 * @param viewUrl The document's canonical URL (e.g., "https://example.com/blog/post")
38
 * @param documentUri The expected AT-URI of the document (e.g., "at://did:plc:abc/site.standard.document/rkey")
39
 * @returns true if the HTML contains a matching link tag
40
 */
41
export async function verifyDocument(
42
  viewUrl: string,
43
  documentUri: string
44
): Promise<boolean> {
45
  try {
46
    const response = await fetch(viewUrl, {
47
      headers: { Accept: "text/html" },
48
    });
49
50
    if (!response.ok) return false;
51
52
    const html = await response.text();
53
54
    // Look for <link rel="site.standard.document" href="at://...">
55
    // Using regex to avoid heavy HTML parser dependency
56
    const linkPattern =
57
      /<link[^>]+rel=["']site\.standard\.document["'][^>]+href=["']([^"']+)["'][^>]*>/i;
58
    const altPattern =
59
      /<link[^>]+href=["']([^"']+)["'][^>]+rel=["']site\.standard\.document["'][^>]*>/i;
60
61
    const match = html.match(linkPattern) || html.match(altPattern);
62
    if (!match) return false;
63
64
    return match[1].trim() === documentUri.trim();
65
  } catch {
66
    return false;
67
  }
68
}
69
70
/**
71
 * Combined verification for a document record.
72
 * Checks publication verification first (if applicable), then document verification.
73
 *
74
 * @param pubUrl The publication's base URL
75
 * @param siteUri The AT-URI of the publication (from document's site field)
76
 * @param viewUrl The document's canonical URL
77
 * @param documentUri The AT-URI of the document
78
 * @returns true if either publication or document verification passes
79
 */
80
export async function verifyDocumentRecord(
81
  pubUrl: string | null,
82
  siteUri: string | null,
83
  viewUrl: string | null,
84
  documentUri: string
85
): Promise<boolean> {
86
  // Try publication verification first (if we have a publication AT-URI)
87
  if (pubUrl && siteUri && siteUri.startsWith("at://")) {
88
    const pubVerified = await verifyPublication(pubUrl, siteUri);
89
    if (pubVerified) return true;
90
  }
91
92
  // Fall back to document verification (if we have a view URL)
93
  if (viewUrl) {
94
    const docVerified = await verifyDocument(viewUrl, documentUri);
95
    if (docVerified) return true;
96
  }
97
98
  return false;
99
}