tangled
alpha
login
or
join now
vielle.dev
/
atcities.dev
8
fork
atom
[WIP] A (somewhat barebones) atproto app for creating custom sites without hosting!
8
fork
atom
overview
issues
pulls
pipelines
server: resolve handle && did to pds
vielle.dev
5 months ago
9f5e0e5d
99b6270a
+134
-7
4 changed files
expand all
collapse all
unified
split
deno.json
deno.lock
main.ts
user.ts
+4
-1
deno.json
···
1
1
{
2
2
"tasks": {
3
3
-
"dev": "PORT=8000 deno run --watch --allow-net --allow-env=HOSTNAME,PORT main.ts"
3
3
+
"dev": "PORT=8000 deno run --watch --allow-net --allow-env=HOSTNAME,PORT,NODE_ENV main.ts"
4
4
+
},
5
5
+
"imports": {
6
6
+
"@atcute/identity-resolver": "npm:@atcute/identity-resolver@^1.1.3"
4
7
},
5
8
"unstable": ["raw-imports"]
6
9
}
+47
deno.lock
···
1
1
+
{
2
2
+
"version": "5",
3
3
+
"specifiers": {
4
4
+
"npm:@atcute/identity-resolver@^1.1.3": "1.1.3_@atcute+identity@1.1.0"
5
5
+
},
6
6
+
"npm": {
7
7
+
"@atcute/identity-resolver@1.1.3_@atcute+identity@1.1.0": {
8
8
+
"integrity": "sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA==",
9
9
+
"dependencies": [
10
10
+
"@atcute/identity",
11
11
+
"@atcute/lexicons",
12
12
+
"@atcute/util-fetch",
13
13
+
"@badrap/valita"
14
14
+
]
15
15
+
},
16
16
+
"@atcute/identity@1.1.0": {
17
17
+
"integrity": "sha512-6vRvRqJatDB+JUQsb+UswYmtBGQnSZcqC3a2y6H5DB/v5KcIh+6nFFtc17G0+3W9rxdk7k9M4KkgkdKf/YDNoQ==",
18
18
+
"dependencies": [
19
19
+
"@atcute/lexicons",
20
20
+
"@badrap/valita"
21
21
+
]
22
22
+
},
23
23
+
"@atcute/lexicons@1.1.1": {
24
24
+
"integrity": "sha512-k6qy5p3j9fJJ6ekaMPfEfp3ni4TW/XNuH9ZmsuwC0fi0tOjp+Fa8ZQakHwnqOzFt/cVBfGcmYE/lKNAbeTjgUg==",
25
25
+
"dependencies": [
26
26
+
"esm-env"
27
27
+
]
28
28
+
},
29
29
+
"@atcute/util-fetch@1.0.1": {
30
30
+
"integrity": "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==",
31
31
+
"dependencies": [
32
32
+
"@badrap/valita"
33
33
+
]
34
34
+
},
35
35
+
"@badrap/valita@0.4.6": {
36
36
+
"integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="
37
37
+
},
38
38
+
"esm-env@1.2.2": {
39
39
+
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="
40
40
+
}
41
41
+
},
42
42
+
"workspace": {
43
43
+
"dependencies": [
44
44
+
"npm:@atcute/identity-resolver@^1.1.3"
45
45
+
]
46
46
+
}
47
47
+
}
+4
-2
main.ts
···
6
6
7
7
const SUBDOMAIN_REGEX = new RegExp(`.+(?=\\.${ROOT_DOMAIN}$)`, "gm");
8
8
9
9
-
Deno.serve({ port: PORT, hostname: ROOT_DOMAIN }, (req) => {
9
9
+
Deno.serve({ port: PORT, hostname: ROOT_DOMAIN }, async (req) => {
10
10
const reqUrl = new URL(req.url);
11
11
const subdomain = reqUrl.hostname.match(SUBDOMAIN_REGEX)?.at(0)?.split(".");
12
12
···
36
36
// ex: vielle.dev.ROOT_DOMAIN
37
37
// cannot contain hyphen in top level domain
38
38
if (!subdomain.at(-1)?.startsWith("did-") && subdomain.length > 1) {
39
39
-
return user(req, { handle: subdomain.join(".") });
39
39
+
return await user(req, {
40
40
+
handle: subdomain.join(".") as `${string}.${string}`,
41
41
+
});
40
42
}
41
43
42
44
return new Response(
+79
-4
user.ts
···
1
1
-
export default function (
1
1
+
import {
2
2
+
CompositeHandleResolver,
3
3
+
DohJsonHandleResolver,
4
4
+
WellKnownHandleResolver,
5
5
+
CompositeDidDocumentResolver,
6
6
+
PlcDidDocumentResolver,
7
7
+
WebDidDocumentResolver,
8
8
+
} from "@atcute/identity-resolver";
9
9
+
10
10
+
const handleResolver = new CompositeHandleResolver({
11
11
+
strategy: "race",
12
12
+
methods: {
13
13
+
dns: new DohJsonHandleResolver({
14
14
+
dohUrl: "https://mozilla.cloudflare-dns.com/dns-query",
15
15
+
}),
16
16
+
http: new WellKnownHandleResolver(),
17
17
+
},
18
18
+
});
19
19
+
20
20
+
const docResolver = new CompositeDidDocumentResolver({
21
21
+
methods: {
22
22
+
plc: new PlcDidDocumentResolver(),
23
23
+
web: new WebDidDocumentResolver(),
24
24
+
},
25
25
+
});
26
26
+
27
27
+
export default async function (
2
28
_req: Request,
3
3
-
user: { handle: string } | { did: `did:plc:${string}` | `did:web:${string}` }
4
4
-
): Response {
5
5
-
return new Response(`${"handle" in user ? user.handle : user.did}`);
29
29
+
user:
30
30
+
| { handle: `${string}.${string}` }
31
31
+
| { did: `did:plc:${string}` | `did:web:${string}` }
32
32
+
): Promise<Response> {
33
33
+
// if handle: resolve did
34
34
+
let did: `did:${"plc" | "web"}:${string}`;
35
35
+
if ("handle" in user) {
36
36
+
try {
37
37
+
// cast bc i know it will be `string.string`
38
38
+
did = await handleResolver.resolve(user.handle);
39
39
+
} catch {
40
40
+
return new Response("Failed to resolve handle", {
41
41
+
status: 500,
42
42
+
});
43
43
+
}
44
44
+
} else did = user.did;
45
45
+
46
46
+
// resolve did doc
47
47
+
const doc = await docResolver.resolve(did);
48
48
+
49
49
+
// handle must be in did document
50
50
+
if ("handle" in user && !doc.alsoKnownAs?.includes(`at://${user.handle}`)) {
51
51
+
return new Response(
52
52
+
`Provided handle (at://${user.handle}) was not found in the did document for ${did}. Found handles:\n${doc.alsoKnownAs?.map((x) => "- " + x).join("\n") ?? "None"}`,
53
53
+
{
54
54
+
status: 500,
55
55
+
}
56
56
+
);
57
57
+
}
58
58
+
59
59
+
const pds = doc.service?.filter(
60
60
+
(x) =>
61
61
+
x.id.endsWith("#atproto_pds") &&
62
62
+
x.type === "AtprotoPersonalDataServer" &&
63
63
+
typeof x.serviceEndpoint === "string"
64
64
+
// cast as we type checked it above but it didnt acc apply?
65
65
+
)[0].serviceEndpoint as string | undefined;
66
66
+
if (!pds)
67
67
+
return new Response(
68
68
+
`Could not find a valid pds for ${"handle" in user ? user.handle : user.did}`,
69
69
+
{
70
70
+
status: 500,
71
71
+
}
72
72
+
);
73
73
+
74
74
+
pds;
75
75
+
76
76
+
return new Response(`Resolved: ${"handle" in user ? user.handle : user.did}
77
77
+
78
78
+
handle: ${"handle" in user ? user.handle : (doc.alsoKnownAs?.filter((x) => x.startsWith("at://"))[0] ?? "N/A")}
79
79
+
did: ${did}
80
80
+
pds: ${pds}`);
6
81
}