Skip to content

Commit d0608e6

Browse files
fix: move cookie logic to redis
[skip ci]
1 parent 18841b7 commit d0608e6

File tree

3 files changed

+43
-52
lines changed

3 files changed

+43
-52
lines changed

src/typegate/src/services/auth/protocols/oauth2.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,25 +223,26 @@ export class OAuth2Auth extends Protocol {
223223
codeVerifier: state.provider.codeVerifier,
224224
});
225225
const profile = await this.fetchProfile(tokens);
226-
const token = await this.createJWT(request, profile);
227226
const code = randomBytes(32).toString("base64url");
228-
const headers = await setEncryptedSessionCookie(
229-
url.hostname,
230-
this.typegraphName,
231-
{ token, state: state.client, code },
232-
this.cryptoKeys,
233-
);
234227
const redirectUri = new URL(state.client.redirectUri);
235228

229+
const payload = {
230+
profile,
231+
provider: this.authName,
232+
state: state.client,
233+
};
234+
236235
await engine.tg.typegate.redis?.set(
237236
`code:${code}`,
238-
JSON.stringify({ profile, provider: this.authName }),
237+
JSON.stringify(payload),
239238
{ ex: 600 }, // 10 minutes
240239
);
241240

242241
redirectUri.searchParams.append("code", code);
243242
redirectUri.searchParams.append("state", state.client.state);
244243

244+
const headers = new Headers();
245+
245246
headers.set("location", redirectUri.toString());
246247

247248
return new Response(null, {

src/typegate/src/services/auth/routes/token.ts

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import { getLogger } from "../../../log.ts";
55
import { jsonError, jsonOk } from "../../responses.ts";
6-
import { clearCookie, getEncryptedCookie } from "../cookies.ts";
6+
import { clearCookie } from "../cookies.ts";
77
import type { RouteParams } from "./mod.ts";
88
import * as z from "zod";
99
import { sha256 } from "../../../crypto.ts";
@@ -19,12 +19,12 @@ const paramSchema = z.union([
1919
code_verifier: z.string(),
2020
client_id: z.string(),
2121
redirect_uri: z.string(),
22-
// scope: z.string().optional(),
22+
scope: z.string().optional(),
2323
}),
2424
z.object({
2525
grant_type: z.literal("refresh_token"),
2626
refresh_token: z.string(),
27-
// scope: z.string().optional(),
27+
scope: z.string().optional(),
2828
}),
2929
]);
3030

@@ -34,6 +34,7 @@ export async function token(params: RouteParams) {
3434
const url = new URL(request.url);
3535
const resHeaders = clearCookie(url.hostname, engine.name, headers);
3636
const origin = request.headers.get("origin") ?? "";
37+
const redis = engine.tg.typegate.redis;
3738

3839
if (!paramSchema.safeParse(body).success) {
3940
return jsonError({
@@ -43,17 +44,24 @@ export async function token(params: RouteParams) {
4344
});
4445
}
4546

46-
if (!engine.tg.typegate.redis) {
47+
if (!redis) {
4748
throw new Error("no redis connection");
4849
}
4950

5051
try {
5152
if (body.grant_type === "authorization_code") {
52-
const { token, state, code } = await getEncryptedCookie(
53-
request.headers,
54-
engine.name,
55-
engine.tg.typegate.cryptoKeys,
56-
);
53+
const rawData = await redis.get(`code:${body.code}`);
54+
55+
if (!rawData) {
56+
return jsonError({
57+
status: 400,
58+
message: "invalid code",
59+
headers: resHeaders,
60+
});
61+
}
62+
63+
const { state, profile, provider } = JSON.parse(rawData);
64+
const auth = engine.tg.auths.get(provider) as OAuth2Auth;
5765
const expectedChallenge = await sha256(body.code_verifier);
5866

5967
if (!state.redirectUri.startsWith(origin)) {
@@ -66,7 +74,6 @@ export async function token(params: RouteParams) {
6674

6775
if (
6876
state.codeChallenge !== expectedChallenge ||
69-
code !== body.code ||
7077
body.client_id !== state.id ||
7178
body.redirect_uri !== state.redirectUri
7279
) {
@@ -77,33 +84,25 @@ export async function token(params: RouteParams) {
7784
});
7885
}
7986

80-
const cachedData = await engine.tg.typegate.redis?.get(
81-
`code:${body.code}`,
82-
);
83-
84-
if (!cachedData) {
85-
return jsonError({
86-
status: 401,
87-
message: "code expired",
88-
headers: resHeaders,
89-
});
87+
if (!auth) {
88+
throw new Error(`provider not found: ${provider}`);
9089
}
9190

92-
await engine.tg.typegate.redis.set(
91+
const token = await auth.createJWT(request, profile);
92+
93+
await redis.set(
9394
`refresh:${token.refresh_token}`,
94-
cachedData,
95+
JSON.stringify({ profile, provider }),
9596
{ ex: engine.tg.typegate.config.base.jwt_max_duration_sec },
9697
);
97-
await engine.tg.typegate.redis.del(`code:${body.code}`);
98+
await redis.del(`code:${body.code}`);
9899
resHeaders.set("content-type", "application/json");
99100

100101
return jsonOk({ data: { ...token }, headers: resHeaders });
101102
}
102103

103104
if (body.grant_type === "refresh_token") {
104-
const rawData = await engine.tg.typegate.redis.get(
105-
`refresh:${body.refresh_token}`,
106-
);
105+
const rawData = await redis.get(`refresh:${body.refresh_token}`);
107106

108107
if (!rawData) {
109108
return jsonError({
@@ -113,24 +112,22 @@ export async function token(params: RouteParams) {
113112
});
114113
}
115114

116-
const cachedData = JSON.parse(rawData) as {
115+
const { provider, profile } = JSON.parse(rawData) as {
117116
profile: Record<string, unknown>;
118117
provider: string;
119118
};
120-
const auth = engine.tg.auths.get(cachedData.provider) as OAuth2Auth;
119+
const auth = engine.tg.auths.get(provider) as OAuth2Auth;
121120

122121
if (!auth) {
123-
throw new Error(`provider not found: ${cachedData.provider}`);
122+
throw new Error(`provider not found: ${provider}`);
124123
}
125124

126-
const newTokens = await auth.createJWT(request, cachedData.profile);
125+
const newTokens = await auth.createJWT(request, profile);
127126

128-
await engine.tg.typegate.redis.set(
129-
`refresh:${newTokens.refresh_token}`,
130-
rawData,
131-
{ ex: engine.tg.typegate.config.base.jwt_max_duration_sec },
132-
);
133-
await engine.tg.typegate.redis.del(`refresh:${body.refresh_token}`);
127+
await redis.set(`refresh:${newTokens.refresh_token}`, rawData, {
128+
ex: engine.tg.typegate.config.base.jwt_max_duration_sec,
129+
});
130+
await redis.del(`refresh:${body.refresh_token}`);
134131

135132
return jsonOk({
136133
headers: resHeaders,

tests/auth/auth_test.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ Meta.test(
200200
});
201201

202202
const decrypted = await crypto.decrypt(nextCookies);
203-
const { provider, client } = JSON.parse(decrypted);
203+
const { provider } = JSON.parse(decrypted);
204204
const headers = new Headers();
205205
headers.set("cookie", `test_auth=${nextCookies}`);
206206
const req = new Request(
@@ -212,14 +212,7 @@ Meta.test(
212212

213213
assertEquals(res.status, 302);
214214
assertEquals(currentUrl.origin, appRedirectUri);
215-
assertEquals(currentUrl.searchParams.get("state"), client.state);
216215

217-
const cook = getCookie(res.headers);
218-
const { token } = JSON.parse(await crypto.decrypt(cook!));
219-
const claims = (await crypto.verifyJWT(token.access_token)) as JWTClaims;
220-
assertEquals(claims.profile?.id, id);
221-
222-
nextCookies = cook!;
223216
nextCode = currentUrl.searchParams.get("code")!;
224217
});
225218

0 commit comments

Comments
 (0)