Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/graphql/src/instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ describe("Mutation.createInstance", () => {
assert.equal(members.length, 1);
assert.equal(members[0]?.accountId, accountId);
assert.equal(members[0]?.instanceId, instances[0]?.id);
assert.ok(members[0]?.accepted instanceof Date);
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/graphql/src/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ builder.mutationFields((t) => ({
await tx.insert(schema.instanceMembers).values({
instanceId: instance.id,
accountId: account.id,
accepted: new Date(),
});
const instances = await tx.$count(
schema.instanceMembers,
Expand Down
38 changes: 26 additions & 12 deletions packages/web/src/RelayEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,47 +14,61 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import { getCookie } from "@solidjs/start/http";
import {
Environment,
type FetchFunction,
type GraphQLResponse,
Network,
RecordSource,
Store,
} from "relay-runtime";
import { getRequestEvent } from "solid-js/web";

import { readSessionCookie } from "./routes/session.ts";
const SESSION_COOKIE = "session";
const ACCESS_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;

// oxlint-disable no-async-await
const fetchFn: FetchFunction = async (params, variables) => {
const event = getRequestEvent();
const accessToken = readSessionCookie(event?.request);
const fetchGraphQL = async (
query: string,
variables: Parameters<FetchFunction>[1],
): Promise<GraphQLResponse> => {
"use server";

const accessToken = getCookie(SESSION_COOKIE);
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (accessToken !== undefined) {
if (accessToken !== undefined && ACCESS_TOKEN_PATTERN.test(accessToken)) {
headers.Authorization = `Bearer ${accessToken}`;
}

const url = new URL(
"/graphql",
event?.request.url ?? globalThis.location.href,
);
const url = new URL("/graphql", import.meta.env.VITE_BACKEND_URL);

const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify({
query: params.text,
query,
variables,
}),
credentials: "include",
});

if (!response.ok) {
throw new Error(`GraphQL request failed (${response.status})`);
}

// oxlint-disable return-await no-unsafe-return
return await response.json();
};

const fetchFn: FetchFunction = async (params, variables) => {
if (params.text == undefined || params.text == "") {
throw new Error(`Relay operation ${params.name} has no query text.`);
}
const graphQLResponse = await fetchGraphQL(params.text, variables);
return graphQLResponse;
};

export function createRelayEnvironment() {
return new Environment({
network: Network.create(fetchFn),
Expand Down
57 changes: 21 additions & 36 deletions packages/web/src/routes/confirm/[token].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { commitMutation, graphql } from "relay-runtime";
import { Show, createSignal, onMount } from "solid-js";

import { createRelayEnvironment } from "~/RelayEnvironment";
import { setSessionCookie } from "~/session";

import type { CompleteLoginChallenge } from "./__generated__/CompleteLoginChallenge.graphql.ts";

Expand All @@ -31,34 +32,27 @@ const completeLoginChallengeMutation = graphql`
}
`;

type CompleteLogInResult =
| {
message: string;
status: "error";
}
| {
accessToken: string;
expires: string;
message: string;
status: "success";
};
interface CompleteLoginResult {
message: string;
status: "error" | "success";
}

const completeLoginChallengeAction = action(
async ({ token, code }: { token: string; code: string }) => {
"use server";

const environment = createRelayEnvironment();

const result = await new Promise<CompleteLogInResult>((resolve) => {
const result = await new Promise<CompleteLoginResult>((resolve) => {
commitMutation<CompleteLoginChallenge>(environment, {
mutation: completeLoginChallengeMutation,
variables: { token, code },
onCompleted: (response, errors) => {
const errorMessage = errors?.map((e) => e.message).join("\n");
const graphQLErrors = errors ?? [];

if (errorMessage !== undefined) {
if (graphQLErrors.length > 0) {
resolve({
message: errorMessage,
message: graphQLErrors.map((error) => error.message).join("\n"),
status: "error",
});
return;
Expand All @@ -76,9 +70,17 @@ const completeLoginChallengeAction = action(
return;
}

try {
setSessionCookie(session.accessToken, session.expires);
} catch {
resolve({
message: "Unable to save a session.",
status: "error",
});
return;
}

resolve({
accessToken: session.accessToken,
expires: session.expires,
message: "Signing in…",
status: "success",
});
Expand All @@ -101,7 +103,7 @@ export default function ConfirmPage() {
const params = useParams<{ token: string }>();
const [searchParams] = useSearchParams<{ code?: string }>();
const completeLoginChallenge = useAction(completeLoginChallengeAction);
const [result, setResult] = createSignal<CompleteLogInResult>();
const [result, setResult] = createSignal<CompleteLoginResult>();

onMount(() => {
async function complete() {
Expand All @@ -116,23 +118,6 @@ export default function ConfirmPage() {
return;
}

const response = await fetch("/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
accessToken: completeResult.accessToken,
expires: completeResult.expires,
}),
});

if (!response.ok) {
setResult({
message: "Unable to save a session.",
status: "error",
});
return;
}

globalThis.location.assign("/");
} catch (error) {
setResult({
Expand All @@ -149,7 +134,7 @@ export default function ConfirmPage() {
});

return (
<Show when={result()}>
<Show when={result()} fallback={<output>Signing in…</output>}>
{(value) => <output class={value().status}>{value().message}</output>}
</Show>
);
Expand Down
111 changes: 0 additions & 111 deletions packages/web/src/routes/session.ts

This file was deleted.

Loading