-
Notifications
You must be signed in to change notification settings - Fork 12.5k
Expand file tree
/
Copy pathoauth-refresh-tokens.e2e.ts
More file actions
175 lines (149 loc) · 5.12 KB
/
oauth-refresh-tokens.e2e.ts
File metadata and controls
175 lines (149 loc) · 5.12 KB
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
import { expect } from "@playwright/test";
import { randomBytes } from "node:crypto";
import process from "node:process";
import { test } from "../lib/fixtures";
import type { PrismaClient } from "@calcom/prisma";
import jwt from "jsonwebtoken";
test.describe("OAuth - refresh tokens", () => {
test.afterEach(async ({ prisma, users }, testInfo) => {
const testPrefix = `e2e-oauth-refresh-status-${testInfo.testId}-`;
await prisma.oAuthClient.deleteMany({
where: {
name: {
startsWith: testPrefix,
},
},
});
await users.deleteAll();
});
async function createOAuthClient({
prisma,
name,
status,
clientType,
}: {
prisma: PrismaClient;
name: string;
status: "PENDING" | "APPROVED" | "REJECTED";
clientType: "PUBLIC" | "CONFIDENTIAL";
}) {
const clientId = randomBytes(32).toString("hex");
const client = await prisma.oAuthClient.create({
data: {
clientId,
name,
redirectUri: "https://example.com",
clientSecret: null,
clientType,
status,
},
});
return client;
}
function signRefreshToken(clientId: string, userId: number): string {
const secretKey = process.env.CALENDSO_ENCRYPTION_KEY;
if (!secretKey) {
throw new Error("CALENDSO_ENCRYPTION_KEY is not set");
}
return jwt.sign(
{
userId,
teamId: null,
scope: [],
token_type: "Refresh Token",
clientId,
},
secretKey,
{ expiresIn: 30 * 24 * 60 * 60 }
);
}
test("legacy refresh token without secret is accepted", async ({ page, users, prisma }, testInfo) => {
const user = await users.create({ username: "oauth-refresh-legacy" });
await user.apiLogin();
const testPrefix = `e2e-oauth-refresh-status-${testInfo.testId}-`;
const client = await createOAuthClient({
prisma,
name: `${testPrefix}approved-${Date.now()}`,
status: "APPROVED",
clientType: "PUBLIC",
});
// Legacy token has no secret field — should be accepted once
const legacyToken = signRefreshToken(client.clientId, user.id);
const response = await page.request.post("/api/auth/oauth/refreshToken", {
form: {
grant_type: "refresh_token",
client_id: client.clientId,
refresh_token: legacyToken,
},
});
expect(response.status()).toBe(200);
const json = await response.json();
expect(json.access_token).toBeDefined();
expect(json.refresh_token).toBeDefined();
});
test("reusing a rotated refresh token is rejected", async ({ page, users, prisma }, testInfo) => {
const user = await users.create({ username: "oauth-refresh-rotation" });
await user.apiLogin();
const testPrefix = `e2e-oauth-refresh-status-${testInfo.testId}-`;
const client = await createOAuthClient({
prisma,
name: `${testPrefix}approved-${Date.now()}`,
status: "APPROVED",
clientType: "PUBLIC",
});
// Use a legacy token to get a new-format token with a secret
const legacyToken = signRefreshToken(client.clientId, user.id);
const firstResponse = await page.request.post("/api/auth/oauth/refreshToken", {
form: {
grant_type: "refresh_token",
client_id: client.clientId,
refresh_token: legacyToken,
},
});
expect(firstResponse.status()).toBe(200);
const newRefreshToken = (await firstResponse.json()).refresh_token;
// Use the new token once (rotates it)
const secondResponse = await page.request.post("/api/auth/oauth/refreshToken", {
form: {
grant_type: "refresh_token",
client_id: client.clientId,
refresh_token: newRefreshToken,
},
});
expect(secondResponse.status()).toBe(200);
// Reuse the same token — should be rejected
const reuseResponse = await page.request.post("/api/auth/oauth/refreshToken", {
form: {
grant_type: "refresh_token",
client_id: client.clientId,
refresh_token: newRefreshToken,
},
});
expect(reuseResponse.status()).toBe(400);
const reuseJson = await reuseResponse.json();
expect(reuseJson.error).toBe("invalid_grant");
expect(reuseJson.error_description).toBe("refresh_token_revoked");
});
test("token refresh fails if client is not approved", async ({ page, users, prisma }, testInfo) => {
const user = await users.create({ username: "oauth-refresh-status-check" });
await user.apiLogin();
const testPrefix = `e2e-oauth-refresh-status-${testInfo.testId}-`;
const client = await createOAuthClient({
prisma,
name: `${testPrefix}pending-${Date.now()}`,
status: "PENDING",
clientType: "PUBLIC",
});
const refreshToken = signRefreshToken(client.clientId, user.id);
const refreshResponse = await page.request.post("/api/auth/oauth/refreshToken", {
form: {
grant_type: "refresh_token",
client_id: client.clientId,
refresh_token: refreshToken,
},
});
expect(refreshResponse.status()).toBe(401);
const refreshJson = await refreshResponse.json();
expect(refreshJson.error).toBe("unauthorized_client");
});
});