228 lines
6.9 KiB
TypeScript
228 lines
6.9 KiB
TypeScript
|
import { expect } from "@playwright/test";
|
||
|
import { randomBytes } from "crypto";
|
||
|
|
||
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||
|
import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler";
|
||
|
|
||
|
import { test } from "./lib/fixtures";
|
||
|
|
||
|
test.afterEach(async ({ users }) => {
|
||
|
await users.deleteAll();
|
||
|
});
|
||
|
|
||
|
let client: {
|
||
|
clientId: string;
|
||
|
redirectUri: string;
|
||
|
orginalSecret: string;
|
||
|
name: string;
|
||
|
clientSecret: string;
|
||
|
logo: string | null;
|
||
|
};
|
||
|
|
||
|
test.describe("OAuth Provider", () => {
|
||
|
test.beforeAll(async () => {
|
||
|
client = await createTestCLient();
|
||
|
});
|
||
|
test("should create valid access toke & refresh token for user", async ({ page, users }) => {
|
||
|
const user = await users.create({ username: "test user", name: "test user" });
|
||
|
await user.apiLogin();
|
||
|
|
||
|
await page.goto(
|
||
|
`auth/oauth2/authorize?client_id=${client.clientId}&redirect_uri=${client.redirectUri}&response_type=code&scope=READ_PROFILE&state=1234`
|
||
|
);
|
||
|
|
||
|
await page.waitForLoadState("networkidle");
|
||
|
await page.getByTestId("allow-button").click();
|
||
|
|
||
|
await page.waitForFunction(() => {
|
||
|
return window.location.href.startsWith("https://example.com");
|
||
|
});
|
||
|
|
||
|
const url = new URL(page.url());
|
||
|
|
||
|
// authorization code that is returned to client with redirect uri
|
||
|
const code = url.searchParams.get("code");
|
||
|
|
||
|
// request token with authorization code
|
||
|
const tokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/token`, {
|
||
|
body: JSON.stringify({
|
||
|
code,
|
||
|
client_id: client.clientId,
|
||
|
client_secret: client.orginalSecret,
|
||
|
grant_type: "authorization_code",
|
||
|
redirect_uri: client.redirectUri,
|
||
|
}),
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
});
|
||
|
|
||
|
const tokenData = await tokenResponse.json();
|
||
|
|
||
|
// test if token is valid
|
||
|
const meResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/me`, {
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
"Content-Type": "application/json",
|
||
|
Authorization: "Bearer " + tokenData.access_token,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
const meData = await meResponse.json();
|
||
|
|
||
|
// check if user access token is valid
|
||
|
expect(meData.username.startsWith("test user")).toBe(true);
|
||
|
|
||
|
// request new token with refresh token
|
||
|
const refreshTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/refreshToken`, {
|
||
|
body: JSON.stringify({
|
||
|
refresh_token: tokenData.refresh_token,
|
||
|
client_id: client.clientId,
|
||
|
client_secret: client.orginalSecret,
|
||
|
grant_type: "refresh_token",
|
||
|
}),
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
});
|
||
|
|
||
|
const refreshTokenData = await refreshTokenResponse.json();
|
||
|
|
||
|
expect(refreshTokenData.access_token).not.toBe(tokenData.access_token);
|
||
|
|
||
|
const validTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/me`, {
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
"Content-Type": "application/json",
|
||
|
Authorization: "Bearer " + tokenData.access_token,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
expect(meData.username.startsWith("test user")).toBe(true);
|
||
|
});
|
||
|
|
||
|
test("should create valid access toke & refresh token for team", async ({ page, users }) => {
|
||
|
const user = await users.create({ username: "test user", name: "test user" }, { hasTeam: true });
|
||
|
await user.apiLogin();
|
||
|
|
||
|
await page.goto(
|
||
|
`auth/oauth2/authorize?client_id=${client.clientId}&redirect_uri=${client.redirectUri}&response_type=code&scope=READ_PROFILE&state=1234`
|
||
|
);
|
||
|
|
||
|
await page.waitForLoadState("networkidle");
|
||
|
|
||
|
await page.locator("#account-select").click();
|
||
|
|
||
|
await page.locator("#react-select-2-option-1").click();
|
||
|
|
||
|
await page.getByTestId("allow-button").click();
|
||
|
|
||
|
await page.waitForFunction(() => {
|
||
|
return window.location.href.startsWith("https://example.com");
|
||
|
});
|
||
|
|
||
|
const url = new URL(page.url());
|
||
|
|
||
|
// authorization code that is returned to client with redirect uri
|
||
|
const code = url.searchParams.get("code");
|
||
|
|
||
|
// request token with authorization code
|
||
|
const tokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/token`, {
|
||
|
body: JSON.stringify({
|
||
|
code,
|
||
|
client_id: client.clientId,
|
||
|
client_secret: client.orginalSecret,
|
||
|
grant_type: "authorization_code",
|
||
|
redirect_uri: client.redirectUri,
|
||
|
}),
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
});
|
||
|
|
||
|
const tokenData = await tokenResponse.json();
|
||
|
|
||
|
// test if token is valid
|
||
|
const meResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/me`, {
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
"Content-Type": "application/json",
|
||
|
Authorization: "Bearer " + tokenData.access_token,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
const meData = await meResponse.json();
|
||
|
|
||
|
// check if team access token is valid
|
||
|
expect(meData.username.endsWith("Team Team")).toBe(true);
|
||
|
|
||
|
// request new token with refresh token
|
||
|
const refreshTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/refreshToken`, {
|
||
|
body: JSON.stringify({
|
||
|
refresh_token: tokenData.refresh_token,
|
||
|
client_id: client.clientId,
|
||
|
client_secret: client.orginalSecret,
|
||
|
grant_type: "refresh_token",
|
||
|
}),
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
});
|
||
|
|
||
|
const refreshTokenData = await refreshTokenResponse.json();
|
||
|
|
||
|
expect(refreshTokenData.access_token).not.toBe(tokenData.access_token);
|
||
|
|
||
|
const validTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/me`, {
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
"Content-Type": "application/json",
|
||
|
Authorization: "Bearer " + tokenData.access_token,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
expect(meData.username.endsWith("Team Team")).toBe(true);
|
||
|
});
|
||
|
|
||
|
test("redirect not logged-in users to login page and after forward to authorization page", async ({
|
||
|
page,
|
||
|
users,
|
||
|
}) => {
|
||
|
const user = await users.create({ username: "test-user", name: "test user" });
|
||
|
|
||
|
await page.goto(
|
||
|
`auth/oauth2/authorize?client_id=${client.clientId}&redirect_uri=${client.redirectUri}&response_type=code&scope=READ_PROFILE&state=1234`
|
||
|
);
|
||
|
|
||
|
// check if user is redirected to login page
|
||
|
await expect(page.getByRole("heading", { name: "Welcome back" })).toBeVisible();
|
||
|
await page.locator("#email").fill(user.email);
|
||
|
await page.locator("#password").fill(user.username || "");
|
||
|
await page.locator('[type="submit"]').click();
|
||
|
|
||
|
await page.waitForSelector("#account-select");
|
||
|
|
||
|
await expect(page.getByText("test user")).toBeVisible();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
const createTestCLient = async () => {
|
||
|
const [hashedSecret, secret] = generateSecret();
|
||
|
const clientId = randomBytes(32).toString("hex");
|
||
|
|
||
|
const client = await prisma.oAuthClient.create({
|
||
|
data: {
|
||
|
name: "Test Client",
|
||
|
clientId,
|
||
|
clientSecret: hashedSecret,
|
||
|
redirectUri: "https://example.com",
|
||
|
},
|
||
|
});
|
||
|
|
||
|
return { ...client, orginalSecret: secret };
|
||
|
};
|