1
0
mirror of https://github.com/pocket-id/pocket-id.git synced 2026-02-04 16:49:42 +00:00

refactor: move e2e tests to root of repository

This commit is contained in:
Elias Schneider
2025-05-22 22:02:44 +02:00
parent 5fa15f6098
commit 966a566ade
33 changed files with 1476 additions and 1153 deletions

12
tests/utils/auth.util.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { Page } from '@playwright/test';
import passkeyUtil from './passkey.util';
async function authenticate(page: Page) {
await page.goto('/login');
await (await passkeyUtil.init(page)).addPasskey();
await page.getByRole('button', { name: 'Authenticate' }).click();
}
export default { authenticate };

View File

@@ -0,0 +1,16 @@
import playwrightConfig from "../playwright.config";
export async function cleanupBackend() {
const response = await fetch(
playwrightConfig.use!.baseURL + "/api/test/reset",
{
method: "POST",
}
);
if (!response.ok) {
throw new Error(
`Failed to reset backend: ${response.status} ${response.statusText}`
);
}
}

64
tests/utils/jwt.util.ts Normal file
View File

@@ -0,0 +1,64 @@
import * as jose from "jose";
import playwrightConfig from "../playwright.config";
const PRIVATE_KEY_STRING = `{"alg":"RS256","d":"mvMDWSdPPvcum0c0iEHE2gbqtV2NKMmLwrl9E6K7g8lTV95SePLnW_bwyMPV7EGp7PQk3l17I5XRhFjze7GqTnFIOgKzMianPs7jv2ELtBMGK0xOPATgu1iGb70xZ6vcvuEfRyY3dJ0zr4jpUdVuXwKmx9rK4IdZn2dFCKfvSuspqIpz11RhF1ALrqDLkxGVv7ZwNh0_VhJZU9hcjG5l6xc7rQEKpPRkZp0IdjkGS8Z0FskoVaiRIWAbZuiVFB9WCW8k1czC4HQTPLpII01bUQx2ludbm0UlXRgVU9ptUUbU7GAImQqTOW8LfPGklEvcgzlIlR_oqw4P9yBxLi-yMQ","dp":"pvNCSnnhbo8Igw9psPR-DicxFnkXlu_ix4gpy6efTrxA-z1VDFDioJ814vKQNioYDzpyAP1gfMPhRkvG_q0hRZsJah3Sb9dfA-WkhSWY7lURQP4yIBTMU0PF_rEATuS7lRciYk1SOx5fqXZd3m_LP0vpBC4Ujlq6NAq6CIjCnms","dq":"TtUVGCCkPNgfOLmkYXu7dxxUCV5kB01-xAEK2OY0n0pG8vfDophH4_D_ZC7nvJ8J9uDhs_3JStexq1lIvaWtG99RNTChIEDzpdn6GH9yaVcb_eB4uJjrNm64FhF8PGCCwxA-xMCZMaARKwhMB2_IOMkxUbWboL3gnhJ2rDO_QO0","e":"AQAB","kid":"8uHDw3M6rf8","kty":"RSA","n":"yaeEL0VKoPBXIAaWXsUgmu05lAvEIIdJn0FX9lHh4JE5UY9B83C5sCNdhs9iSWzpeP11EVjWp8i3Yv2CF7c7u50BXnVBGtxpZpFC-585UXacoJ0chUmarL9GRFJcM1nPHBTFu68aRrn1rIKNHUkNaaxFo0NFGl_4EDDTO8HwawTjwkPoQlRzeByhlvGPVvwgB3Fn93B8QJ_cZhXKxJvjjrC_8Pk76heC_ntEMru71Ix77BoC3j2TuyiN7m9RNBW8BU5q6lKoIdvIeZfTFLzi37iufyfvMrJTixp9zhNB1NxlLCeOZl2MXegtiGqd2H3cbAyqoOiv9ihUWTfXj7SxJw","p":"_Yylc9e07CKdqNRD2EosMC2mrhrEa9j5oY_l00Qyy4-jmCA59Q9viyqvveRo0U7cRvFA5BWgWN6GGLh1DG3X-QBqVr0dnk3uzbobb55RYUXyPLuBZI2q6w2oasbiDwPdY7KpkVv_H-bpITQlyDvO8hhucA6rUV7F6KTQVz8M3Ms","q":"y5p3hch-7jJ21TkAhp_Vk1fLCAuD4tbErwQs2of9ja8sB4iJOs5Wn6HD3P7Mc8Plye7qaLHvzc8I5g0tPKWvC0DPd_FLPXiWwMVAzee3NUX_oGeJNOQp11y1w_KqdO9qZqHSEPZ3NcFL_SZMFgggxhM1uzRiPzsVN0lnD_6prZU","qi":"2Grt6uXHm61ji3xSdkBWNtUnj19vS1-7rFJp5SoYztVQVThf_W52BAiXKBdYZDRVoItC_VS2NvAOjeJjhYO_xQ_q3hK7MdtuXfEPpLnyXKkmWo3lrJ26wbeF6l05LexCkI7ShsOuSt-dsyaTJTszuKDIA6YOfWvfo3aVZmlWRaI","use":"sig"}`;
type User = {
id: string;
email: string;
firstname: string;
lastname: string;
};
const privateKey = JSON.parse(PRIVATE_KEY_STRING);
const privateKeyImported = await jose.importJWK(privateKey, "RS256");
export async function generateIdToken(
user: User,
clientId: string,
expired = false
) {
const now = Math.floor(Date.now() / 1000);
const expiration = expired ? now + 1 : now + 1000000000; // Either expired or valid for a long time
const payload = {
aud: clientId,
email: user.email,
email_verified: true,
exp: expiration,
family_name: user.lastname,
given_name: user.firstname,
iat: now,
iss: playwrightConfig.use!.baseURL,
name: `${user.firstname} ${user.lastname}`,
nonce: "oW1A1O78GQ15D73OsHEx7WQKj7ZqvHLZu_37mdXIqAQ",
sub: user.id,
type: "id-token",
};
return await new jose.SignJWT(payload)
.setProtectedHeader({ alg: "RS256", kid: privateKey.kid, typ: "JWT" })
.sign(privateKeyImported);
}
export async function generateOauthAccessToken(
user: User,
clientId: string,
expired = false
) {
const now = Math.floor(Date.now() / 1000);
const expiration = expired ? now - 1000 : now + 1000000000; // Either expired or valid for a long time
const payload = {
aud: [clientId],
exp: expiration,
iat: now,
iss: playwrightConfig.use!.baseURL,
sub: user.id,
type: "oauth-access-token",
};
return await new jose.SignJWT(payload)
.setProtectedHeader({ alg: "RS256", kid: privateKey.kid, typ: "JWT" })
.sign(privateKeyImported);
}

22
tests/utils/oidc.util.ts Normal file
View File

@@ -0,0 +1,22 @@
import type { Page } from '@playwright/test';
async function getUserCode(page: Page, clientId: string, clientSecret: string) {
const response = await page.request
.post('/api/oidc/device/authorize', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
client_id: clientId,
client_secret: clientSecret,
scope: 'openid profile email'
}
})
.then((r) => r.json());
return response.user_code;
}
export default {
getUserCode
};

View File

@@ -0,0 +1,70 @@
import type { CDPSession, Page } from '@playwright/test';
// The existing passkeys are already stored in the database
const passkeys = {
tim: {
credentialId: 'test-credential-tim',
userHandle: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e',
privateKey:
'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3rNKkGApsEA1TpGiphKh6axTq3Vh6wBghLLea/YkIp+hRANCAATBw6jkpXXr0pHrtAQetxiR5cTcILG/YGDCdKrhVhNDHIu12YrF6B7Frwl3AUqEpdrYEwj3Fo3XkGgvrBIJEUmG'
},
craig: {
credentialId: 'test-credential-craig',
userHandle: '1cd19686-f9a6-43f4-a41f-14a0bf5b4036',
privateKey:
'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgL1UaeWG1KYpN+HcxQvXEJysiQjT9Fn7Zif3i5cY+s+yhRANCAASPioDQ+tnODwKjULbufJRvOunwTCOvt46UYjYt+vOZsvmc+FlEB0neERqqscxKckGF8yq1AYrANiloshAUAouH'
},
timNew: {
credentialId: 'new-test-credential-tim',
userHandle: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e',
privateKey:
'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgFl2lIlRyc2G7O9D8WWrw2N8D7NTlhgWcKFY7jYxrfcmhRANCAASmvbCFrXshUvW7avTIysV9UymbhmUwGb7AonUMQPgqK2Jur7PWp9V0AIe5YMuXYH1oxsqY5CoAbdY2YsPmhYoX'
}
};
async function init(page: Page) {
const client = await page.context().newCDPSession(page);
await client.send('WebAuthn.enable');
const authenticatorId = await addVirtualAuthenticator(client);
return {
addPasskey: async (passkey?: keyof typeof passkeys) => {
await addPasskey(authenticatorId, client, passkey);
}
};
}
async function addVirtualAuthenticator(client: CDPSession): Promise<string> {
const result = await client.send('WebAuthn.addVirtualAuthenticator', {
// config authenticator
options: {
protocol: 'ctap2',
transport: 'internal',
hasResidentKey: true,
hasUserVerification: true,
isUserVerified: true
}
});
return result.authenticatorId;
}
async function addPasskey(
authenticatorId: string,
client: CDPSession,
passkeyName: keyof typeof passkeys = 'tim'
): Promise<void> {
const passkey = passkeys[passkeyName];
await client.send('WebAuthn.addCredential', {
authenticatorId,
credential: {
credentialId: btoa(passkey.credentialId),
isResidentCredential: true,
rpId: 'localhost',
privateKey: passkey.privateKey,
userHandle: btoa(passkey.userHandle),
signCount: Math.round((new Date().getTime() - 1704444610871) / 1000 / 2)
}
});
}
export default { init };