-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.ts
185 lines (159 loc) · 4.88 KB
/
server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import type { AppLoadContext, Session } from "@remix-run/cloudflare";
import { json, redirect } from "@remix-run/cloudflare";
import type { createPagesFunctionHandlerParams } from "@remix-run/cloudflare-pages";
import { createRequestHandler } from "@remix-run/cloudflare-pages";
import * as build from "@remix-run/dev/server-build";
import { envSchema } from "env.server";
import type { SpectacularJWTObtain } from "generated-sources/gocardless";
import { TokenService } from "generated-sources/gocardless";
import { route } from "routes-gen";
import type { GoogleSession } from "~/lib/cookie.server";
import {
createSessionStorage,
goCardlessStorage,
isAccessTokenValid,
isRefreshTokenValid,
setGoCardlessSession,
} from "~/lib/cookie.server";
import { GoogleStrategy } from "~/lib/google-strategy.server";
const ALLOW_UNAUTHENTICATED: `/${string}`[] = [
route("/login"),
route("/logout"),
route("/auth/google/callback"),
] as const;
type PagesContext = { DB: D1Database };
export const onRequest: ReturnType<
typeof createPagesFunctionHandler<PagesContext>
> = async (context) => {
const { DB: db, ...rawEnv } = context.env;
// Don't bother with the parsing, we just want to make sure the DB exists
if (!db) {
return json(
{
message: "Missing DB binding",
},
{
status: 500,
},
);
}
const env = envSchema.safeParse(rawEnv);
if (!env.success) {
return json(
{
message: "Invalid environment",
error: env.error,
},
{
status: 500,
},
);
}
const cookieHeader = context.request.headers.get("Cookie");
const goCardlessSession = await goCardlessStorage.getSession(cookieHeader);
const session = await createSessionStorage(env.data).getSession(cookieHeader);
const user: GoogleSession | null = session.get(
GoogleStrategy.authenticatorOptions.sessionKey,
);
const url = new URL(context.request.url);
if (!user && !ALLOW_UNAUTHENTICATED.some((it) => it === url.pathname)) {
return redirect(route("/login"));
}
const handler = createPagesFunctionHandler({
build,
mode: process.env.NODE_ENV,
getLoadContext: (context): AppLoadContext => {
return {
...context,
env: env.data,
db,
session,
goCardlessSession,
user,
};
},
});
return await handler(context);
};
export function createPagesFunctionHandler<Env = any>({
build,
getLoadContext,
mode,
}: createPagesFunctionHandlerParams<Env>) {
let handleRequest = createRequestHandler<Env>({
build,
getLoadContext,
mode,
});
let handleFetch = async (context: EventContext<Env, any, any>) => {
let response: Response | undefined;
// https://github.com/cloudflare/wrangler2/issues/117
context.request.headers.delete("if-none-match");
try {
response = await context.env.ASSETS.fetch(
context.request.url,
context.request.clone(),
);
response =
response && response.status >= 200 && response.status < 400
? new Response(response.body, response)
: undefined;
} catch {}
if (!response) {
response = await handleRequest(context);
}
const loadContext = await getLoadContext?.(context);
if (loadContext) {
// don't authorize goCardless if user is not logged in
const goCardlessSession = loadContext.user
? await authorizeGoCardless(loadContext)
: loadContext.goCardlessSession;
loadContext.goCardlessSession = goCardlessSession;
response.headers.append(
"Set-Cookie",
await goCardlessStorage.commitSession(loadContext.goCardlessSession),
);
}
return response;
};
return async (context: EventContext<Env, any, any>) => {
try {
return await handleFetch(context);
} catch (error: unknown) {
if (process.env.NODE_ENV === "development" && error instanceof Error) {
console.error(error);
return new Response(error.message || error.toString(), {
status: 500,
});
}
return new Response("Internal Error", {
status: 500,
});
}
};
}
async function authorizeGoCardless(
context: AppLoadContext,
): Promise<Session<SpectacularJWTObtain>> {
const session = context.goCardlessSession;
// Access token not expired? Return session
if (isAccessTokenValid(session)) {
return session;
}
// Refresh token not expired? Refresh token
if (isRefreshTokenValid(session)) {
const res = await TokenService.getANewAccessToken({
refresh: session.data.refresh,
});
return setGoCardlessSession(session, res);
}
// Refresh token expired? Get new tokens
const res = await TokenService.obtainNewAccessRefreshTokenPair({
secret_id: context.env.GO_CARDLESS_SECRET_ID,
secret_key: context.env.GO_CARDLESS_SECRET_KEY,
});
return setGoCardlessSession(session, {
...session.data,
...res,
});
}