Skip to content

Commit c8e1203

Browse files
committed
Allow credentials for x-admin accessing x-peers
- CORS preflight never includes credentials, so we need to handle OPTIONS before checking authorization. - Pass credentials: include to fetch - Add Access-Control-Allow-Credentials and don't use * in Access-Control-Allow-Headers
1 parent fd48225 commit c8e1203

3 files changed

Lines changed: 58 additions & 20 deletions

File tree

packages/local/XAdmin/ui/src/lib/chain-endpoints.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ class Chain {
9696
public async getPeers(): Promise<z.infer<typeof Peers>> {
9797
const url = siblingUrl(null, "x-peers", "/graphql");
9898
const query = "query { peers { edges { node { id urls endpoint } } } }";
99-
const res: any = await postGraphQLGetJson(url, query);
99+
const res: any = await postGraphQLGetJson(url, query, {
100+
credentials: "include",
101+
});
100102
return Peers.parse(res.data.peers.edges.map((e: any) => e.node));
101103
}
102104

@@ -179,7 +181,7 @@ class Chain {
179181
`Failed to find peer with ID ${id} in existing peers`,
180182
);
181183
const url = siblingUrl(null, "x-peers", "/disconnect");
182-
await postJson(url, { id });
184+
await postJson(url, { id }, { credentials: "include" });
183185
}
184186

185187
public pushArrayBufferBoot(buffer: ArrayBufferLike) {
@@ -221,7 +223,9 @@ class Chain {
221223
url: string;
222224
}): Promise<z.infer<typeof Peer>> {
223225
const url = siblingUrl(null, "x-peers", "/connect");
224-
const result = await postJsonGetJson(url, config);
226+
const result = await postJsonGetJson(url, config, {
227+
credentials: "include",
228+
});
225229
result.urls = [url];
226230
return Peer.parse(result);
227231
}

packages/local/XPeers/src/XPeers.cpp

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ namespace
4848
PSIO_REFLECT(AuthorizationRequest, url, token)
4949
};
5050

51+
std::vector<HttpHeader> allowCorsCredentials(const HttpRequest& req, AccountNumber account)
52+
{
53+
auto result = allowCors(req, account);
54+
result.push_back({"Access-Control-Allow-Credentials", "true"});
55+
for (auto& h : result)
56+
{
57+
if (h.matches("Access-Control-Allow-Headers"))
58+
{
59+
h.value = "Content-Type, Authorization";
60+
break;
61+
}
62+
}
63+
return result;
64+
}
65+
5166
HttpReply error(HttpStatus status, std::string_view msg)
5267
{
5368
return HttpReply{.status = status, .contentType = "text/html", .body{msg.begin(), msg.end()}};
@@ -145,8 +160,8 @@ namespace
145160
std::optional<SocketEndpoint> endpoint;
146161
auto url = splitURL(peer);
147162
HttpRequest request{
148-
.method = "GET",
149-
.target = "/p2p",
163+
.method = "GET",
164+
.target = "/p2p",
150165
};
151166
std::vector<std::string> hosts;
152167
bool secure = false;
@@ -189,7 +204,7 @@ namespace
189204
auto connectionRequests = XPeers{}.open<ConnectionRequestTable>();
190205
check(originalRequest != nullptr, "request is required if requestSocket is provided");
191206
connectionRequests.put(
192-
{socket, *requestSocket, allowCors(*originalRequest, XAdmin::service)});
207+
{socket, *requestSocket, allowCorsCredentials(*originalRequest, XAdmin::service)});
193208
to<XHttp>().autoClose(*requestSocket, false);
194209
}
195210

@@ -589,15 +604,16 @@ auto XPeers::serveSys(const HttpRequest& request, std::optional<std::int32_t> so
589604
}
590605
}
591606

592-
if (auto reply = to<XAdmin>().checkAuth(request, socket))
593-
return reply;
594-
595607
if (request.method == "OPTIONS")
596608
{
597-
HttpReply result{.headers = allowCors(request, XAdmin::service)};
609+
HttpReply result{.headers = allowCorsCredentials(request, XAdmin::service)};
598610
result.headers.push_back({"Allow", "GET, POST, PUT, OPTIONS, DELETE"});
599611
return result;
600612
}
613+
614+
if (auto reply = to<XAdmin>().checkAuth(request, socket))
615+
return reply;
616+
601617
if (target == "/connect")
602618
{
603619
if (request.contentType != "application/json")
@@ -642,15 +658,15 @@ auto XPeers::serveSys(const HttpRequest& request, std::optional<std::int32_t> so
642658
}
643659
if (row)
644660
{
645-
return HttpReply{.headers = allowCors(request, XAdmin::service)};
661+
return HttpReply{.headers = allowCorsCredentials(request, XAdmin::service)};
646662
}
647663
else
648664
{
649665
std::string_view msg{"Socket not found"};
650666
return HttpReply{.status = HttpStatus::notFound,
651667
.contentType = "text/html",
652668
.body{msg.begin(), msg.end()},
653-
.headers = allowCors(request, XAdmin::service)};
669+
.headers = allowCorsCredentials(request, XAdmin::service)};
654670
}
655671
}
656672
else if (target == "/users")
@@ -675,7 +691,7 @@ auto XPeers::serveSys(const HttpRequest& request, std::optional<std::int32_t> so
675691
table.erase(body.account);
676692
}
677693
}
678-
return HttpReply{.headers = allowCors(request, XAdmin::service)};
694+
return HttpReply{.headers = allowCorsCredentials(request, XAdmin::service)};
679695
}
680696
else if (target == "/authorization")
681697
{
@@ -699,7 +715,7 @@ auto XPeers::serveSys(const HttpRequest& request, std::optional<std::int32_t> so
699715
table.erase(body.url);
700716
}
701717
}
702-
return HttpReply{.headers = allowCors(request, XAdmin::service)};
718+
return HttpReply{.headers = allowCorsCredentials(request, XAdmin::service)};
703719
}
704720
else if (target == "/peers")
705721
{

packages/user/CommonApi/common/packages/common-lib/src/rpc.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import hashJs from "hash.js";
2+
13
import {
24
privateStringToKeyPair,
35
publicKeyPairToDER,
46
signatureToBin,
57
} from "./key-conversions";
6-
import hashJs from "hash.js";
78

89
export class RPCError extends Error {
910
trace: any;
@@ -105,26 +106,38 @@ export async function postText(url: string, text: string) {
105106
);
106107
}
107108

108-
export async function postGraphQL(url: string, graphql: string) {
109+
export async function postGraphQL(
110+
url: string,
111+
graphql: string,
112+
options?: RequestInit,
113+
) {
114+
if (!options) {
115+
options = {};
116+
}
109117
return throwIfError(
110118
await fetch(url, {
111119
method: "POST",
112120
headers: {
113121
"Content-Type": "application/graphql",
114122
},
115123
body: graphql,
124+
...options,
116125
}),
117126
);
118127
}
119128

120-
export async function postJson(url: string, json: any) {
129+
export async function postJson(url: string, json: any, options?: RequestInit) {
130+
if (!options) {
131+
options = {};
132+
}
121133
return throwIfError(
122134
await fetch(url, {
123135
method: "POST",
124136
headers: {
125137
"Content-Type": "application/json",
126138
},
127139
body: JSON.stringify(json),
140+
...options,
128141
}),
129142
);
130143
}
@@ -149,13 +162,18 @@ export async function postTextGetJson(url: string, text: string) {
149162
export async function postGraphQLGetJson<GqlResponse>(
150163
url: string,
151164
graphQL: string,
165+
options?: RequestInit,
152166
): Promise<GqlResponse> {
153-
const res = await postGraphQL(url, graphQL);
167+
const res = await postGraphQL(url, graphQL, options);
154168
return res.json();
155169
}
156170

157-
export async function postJsonGetJson(url: string, json: any) {
158-
const res = await postJson(url, json);
171+
export async function postJsonGetJson(
172+
url: string,
173+
json: any,
174+
options?: RequestInit,
175+
) {
176+
const res = await postJson(url, json, options);
159177
return res.json();
160178
}
161179

0 commit comments

Comments
 (0)