···11+import { OWNER_DID, SERVICE_DID } from "@/lib/env";
22+import { issueNewLatticeToken } from "@/lib/sessions";
33+import { HttpGeneralErrorType } from "@/lib/types/http/errors";
44+import { handshakeDataSchema } from "@/lib/types/http/handlers";
55+import { systemsGmstnDevelopmentChannelRecordSchema } from "@/lib/types/lexicon/systems.gmstn.development.channel";
66+import type { RouteHandler } from "@/lib/types/routes";
77+import { stringToAtUri } from "@/lib/utils/atproto";
88+import {
99+ getConstellationBacklink,
1010+ getPdsRecordFromBacklink,
1111+} from "@/lib/utils/constellation";
1212+import {
1313+ newErrorResponse,
1414+ newSuccessResponse,
1515+} from "@/lib/utils/http/responses";
1616+import { verifyServiceJwt } from "@/lib/utils/verifyJwt";
1717+import { z } from "zod";
1818+1919+export const handshakeHandler: RouteHandler = async (req) => {
2020+ const {
2121+ success: handshakeParseSuccess,
2222+ error: handshakeParseError,
2323+ data: handshakeData,
2424+ } = handshakeDataSchema.safeParse(req.body);
2525+ if (!handshakeParseSuccess) {
2626+ return newErrorResponse(400, {
2727+ message: HttpGeneralErrorType.TYPE_ERROR,
2828+ details: z.treeifyError(handshakeParseError),
2929+ });
3030+ }
3131+3232+ const { interServiceJwt, channelAtUris: channelAtUriStrings } =
3333+ handshakeData;
3434+ const allowedChannels = channelAtUriStrings.map((channel) => {
3535+ const res = stringToAtUri(channel);
3636+ if (!res.ok) return;
3737+ return res.data;
3838+ });
3939+4040+ const verifyJwtResult = await verifyServiceJwt(interServiceJwt);
4141+ if (!verifyJwtResult.ok) {
4242+ const { error } = verifyJwtResult;
4343+ return newErrorResponse(
4444+ 401,
4545+ {
4646+ message:
4747+ "JWT authentication failed. Did you submit the right inter-service JWT to the right endpoint with the right signatures?",
4848+ details: error,
4949+ },
5050+ {
5151+ headers: {
5252+ "WWW-Authenticate":
5353+ 'Bearer error="invalid_token", error_description="JWT signature verification failed"',
5454+ },
5555+ },
5656+ );
5757+ }
5858+5959+ // TODO:
6060+ // if(PRIVATE_SHARD) doAllowCheck()
6161+ // see the sequence diagram for the proper flow.
6262+ // not implemented for now because we support public first
6363+6464+ const constellationResponse = await getConstellationBacklink({
6565+ subject: `at://${OWNER_DID}/systems.gmstn.development.shard/${SERVICE_DID.slice(8)}`,
6666+ source: {
6767+ nsid: "systems.gmstn.development.channel",
6868+ fieldName: "storeAt.uri",
6969+ },
7070+ });
7171+ if (!constellationResponse.ok) {
7272+ const { error } = constellationResponse;
7373+ if ("fetchStatus" in error)
7474+ return newErrorResponse(error.fetchStatus, {
7575+ message:
7676+ "Could not fetch backlinks from constellation. Likely something went wrong on our side.",
7777+ details: error.message,
7878+ });
7979+ else
8080+ return newErrorResponse(400, {
8181+ message: HttpGeneralErrorType.TYPE_ERROR,
8282+ details: z.treeifyError(error),
8383+ });
8484+ }
8585+8686+ const pdsRecordFetchPromises = constellationResponse.data.records.map(
8787+ async (backlink) => {
8888+ const recordResult = await getPdsRecordFromBacklink(backlink);
8989+ if (!recordResult.ok) {
9090+ console.error(
9191+ `something went wrong fetching the record from the given backlink ${JSON.stringify(backlink)}`,
9292+ );
9393+ throw new Error(
9494+ JSON.stringify({ error: recordResult.error, backlink }),
9595+ );
9696+ }
9797+ return recordResult.data;
9898+ },
9999+ );
100100+101101+ let pdsChannelRecords;
102102+ try {
103103+ pdsChannelRecords = await Promise.all(pdsRecordFetchPromises);
104104+ } catch (err) {
105105+ return newErrorResponse(500, {
106106+ message:
107107+ "Something went wrong when fetching backlink channel records. Check the Shard logs if possible.",
108108+ details: err,
109109+ });
110110+ }
111111+112112+ const {
113113+ success: channelRecordsParseSuccess,
114114+ error: channelRecordsParseError,
115115+ data: channelRecordsParsed,
116116+ } = z
117117+ .array(systemsGmstnDevelopmentChannelRecordSchema)
118118+ .safeParse(pdsChannelRecords);
119119+ if (!channelRecordsParseSuccess) {
120120+ return newErrorResponse(500, {
121121+ message:
122122+ "One of the backlinks returned by Constellation did not resolve to a proper lexicon Channel record.",
123123+ details: z.treeifyError(channelRecordsParseError),
124124+ });
125125+ }
126126+127127+ // TODO:
128128+ // for private shards, ensure that the channels described by constellation backlinks are made
129129+ // by authorised parties (check owner pds for workspace management permissions)
130130+ // do another fetch to owner's pds first to grab the records, then cross-reference with the
131131+ // did of the backlink. if there are any channels described by unauthorised parties, simply drop them.
132132+133133+ let mismatchOrIncorrect = false;
134134+ const requestingLatticeDid = verifyJwtResult.value.issuer;
135135+136136+ channelRecordsParsed.forEach((channel) => {
137137+ if (mismatchOrIncorrect) return;
138138+139139+ const { storeAt: storeAtRecord, routeThrough: routeThroughRecord } =
140140+ channel;
141141+ const storeAtRecordParseResult = stringToAtUri(storeAtRecord.uri);
142142+ if (!storeAtRecordParseResult.ok) {
143143+ mismatchOrIncorrect = true;
144144+ return;
145145+ }
146146+ const storeAtUri = storeAtRecordParseResult.data;
147147+148148+ // FIXME: this assumes that the current shard's SERVICE_DID is a did:web.
149149+ // we should resolve the full record or add something that can tell us where to find this shard.
150150+ // likely, we should simply resolve the described shard record, which we can technically do faaaaar earlier on in the request
151151+ // or even store it in memory upon first boot of a shard.
152152+ // also incorrectly assumes that the storeAt rkey is a domain when it can in fact be anything.
153153+ // we should probably just resolve this properly first but for now, i cba.
154154+ if (storeAtUri.rKey !== SERVICE_DID.slice(8)) {
155155+ mismatchOrIncorrect = true;
156156+ return;
157157+ }
158158+159159+ const routeThroughRecordParseResult = stringToAtUri(
160160+ routeThroughRecord.uri,
161161+ );
162162+ if (!routeThroughRecordParseResult.ok) {
163163+ mismatchOrIncorrect = true;
164164+ return;
165165+ }
166166+ const routeThroughUri = routeThroughRecordParseResult.data;
167167+168168+ // FIXME: this also assumes that the requesting lattice's DID is a did:web
169169+ // see above for the rest of the issues.
170170+ if (routeThroughUri.rKey === requestingLatticeDid.slice(8)) {
171171+ mismatchOrIncorrect = true;
172172+ return;
173173+ }
174174+ });
175175+176176+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
177177+ if (mismatchOrIncorrect)
178178+ return newErrorResponse(400, {
179179+ message:
180180+ "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.",
181181+ });
182182+183183+ // yipee, it's a valid request :3
184184+185185+ const sessionInfo = issueNewLatticeToken({
186186+ allowedChannels,
187187+ clientDid: verifyJwtResult.value.issuer,
188188+ });
189189+190190+ return newSuccessResponse({ sessionInfo });
191191+};