···
1
+
import { OWNER_DID, SERVICE_DID } from "@/lib/env";
2
+
import { generateSessionId, generateSessionInfo } from "@/lib/sessions";
3
+
import { systemsGmstnDevelopmentChannelRecordSchema } from "@/lib/types/gmstn";
4
+
import { HttpGeneralErrorType } from "@/lib/types/http/errors";
5
+
import { handshakeDataSchema } from "@/lib/types/http/handlers";
6
+
import type { RouteHandler } from "@/lib/types/routes";
7
+
import { stringToAtUri } from "@/lib/utils/atproto";
9
+
getConstellationBacklink,
10
+
getPdsRecordFromBacklink,
11
+
} from "@/lib/utils/constellation";
15
+
} from "@/lib/utils/http/responses";
16
+
import { verifyServiceJwt } from "@/lib/utils/verifyJwt";
17
+
import { z } from "zod";
19
+
export const handshakeHandler: RouteHandler = async (req) => {
21
+
success: handshakeParseSuccess,
22
+
error: handshakeParseError,
23
+
data: handshakeData,
24
+
} = handshakeDataSchema.safeParse(req.body);
25
+
if (!handshakeParseSuccess) {
26
+
return newErrorResponse(400, {
27
+
message: HttpGeneralErrorType.TYPE_ERROR,
28
+
details: z.treeifyError(handshakeParseError),
32
+
const { interServiceJwt } = handshakeData;
34
+
const verifyJwtResult = await verifyServiceJwt(interServiceJwt);
35
+
if (!verifyJwtResult.ok) {
36
+
const { error } = verifyJwtResult;
37
+
return newErrorResponse(
41
+
"JWT authentication failed. Did you submit the right inter-service JWT to the right endpoint with the right signatures?",
47
+
'Bearer error="invalid_token", error_description="JWT signature verification failed"',
54
+
// if(PRIVATE_SHARD) doAllowCheck()
55
+
// see the sequence diagram for the proper flow.
56
+
// not implemented for now because we support public first
58
+
const constellationResponse = await getConstellationBacklink({
61
+
nsid: "systems.gmstn.development.shard",
62
+
fieldName: "storeAt.uri",
65
+
if (!constellationResponse.ok) {
66
+
const { error } = constellationResponse;
67
+
if ("fetchStatus" in error)
68
+
return newErrorResponse(error.fetchStatus, {
70
+
"Could not fetch backlinks from constellation. Likely something went wrong on our side.",
71
+
details: error.message,
74
+
return newErrorResponse(400, {
75
+
message: HttpGeneralErrorType.TYPE_ERROR,
76
+
details: z.treeifyError(error),
80
+
const pdsRecordFetchPromises = constellationResponse.data.records.map(
81
+
async (backlink) => {
82
+
const recordResult = await getPdsRecordFromBacklink(backlink);
83
+
if (!recordResult.ok) {
85
+
`something went wrong fetching the record from the given backlink ${JSON.stringify(backlink)}`,
88
+
JSON.stringify({ error: recordResult.error, backlink }),
91
+
return recordResult.data;
95
+
let pdsChannelRecords;
97
+
pdsChannelRecords = await Promise.all(pdsRecordFetchPromises);
99
+
return newErrorResponse(500, {
101
+
"Something went wrong when fetching backlink channel records. Check the Shard logs if possible.",
107
+
success: channelRecordsParseSuccess,
108
+
error: channelRecordsParseError,
109
+
data: channelRecordsParsed,
111
+
.array(systemsGmstnDevelopmentChannelRecordSchema)
112
+
.safeParse(pdsChannelRecords);
113
+
if (!channelRecordsParseSuccess) {
114
+
return newErrorResponse(500, {
116
+
"One of the backlinks returned by Constellation did not resolve to a proper lexicon Channel record.",
117
+
details: z.treeifyError(channelRecordsParseError),
122
+
// for private shards, ensure that the channels described by constellation backlinks are made
123
+
// by authorised parties (check owner pds for workspace management permissions)
124
+
// do another fetch to owner's pds first to grab the records, then cross-reference with the
125
+
// did of the backlink. if there are any channels described by unauthorised parties, simply drop them.
127
+
let mismatchOrIncorrect = false;
128
+
const requestingLatticeDid = verifyJwtResult.value.issuer;
130
+
channelRecordsParsed.forEach((channel) => {
131
+
if (mismatchOrIncorrect) return;
133
+
const { storeAt: storeAtRecord, routeThrough: routeThroughRecord } =
135
+
// @ts-expect-error atcute's schema shape gives an unknown when it should give string. will probably swap to a different library eventually.
136
+
const storeAtRecordParseResult = stringToAtUri(storeAtRecord.uri);
137
+
if (!storeAtRecordParseResult.ok) {
138
+
mismatchOrIncorrect = true;
141
+
const storeAtUri = storeAtRecordParseResult.data;
143
+
// FIXME: this assumes that the current shard's SERVICE_DID is a did:web.
144
+
// we should resolve the full record or add something that can tell us where to find this shard.
145
+
// likely, we should simply resolve the described shard record, which we can technically do faaaaar earlier on in the request
146
+
// or even store it in memory upon first boot of a shard.
147
+
// also incorrectly assumes that the storeAt rkey is a domain when it can in fact be anything.
148
+
// we should probably just resolve this properly first but for now, i cba.
149
+
if (storeAtUri.rKey !== SERVICE_DID.slice(8)) {
150
+
mismatchOrIncorrect = true;
154
+
const routeThroughRecordParseResult = stringToAtUri(
155
+
// @ts-expect-error atcute's schema shape gives an unknown when it should give string. will probably swap to a different library eventually.
156
+
routeThroughRecord.uri,
158
+
if (!routeThroughRecordParseResult.ok) {
159
+
mismatchOrIncorrect = true;
162
+
const routeThroughUri = routeThroughRecordParseResult.data;
164
+
// FIXME: this also assumes that the requesting lattice's DID is a did:web
165
+
// see above for the rest of the issues.
166
+
if (routeThroughUri.rKey === requestingLatticeDid.slice(8)) {
167
+
mismatchOrIncorrect = true;
172
+
// we are mutating this above @typescript-eslint/no-unnecessary-condition
173
+
// eslint-disable-next-line
174
+
if (mismatchOrIncorrect)
175
+
return newErrorResponse(400, {
177
+
"Channels provided during the handshake had a mismatch between the channel values. Ensure that you are only submitting exactly the channels you have access to.",
180
+
// yipee, it's a valid request :3
182
+
const sessionId = generateSessionId();
183
+
const sessionInfo = generateSessionInfo(sessionId);
185
+
return newSuccessResponse({ sessionInfo });