1
0
mirror of https://github.com/pocket-id/pocket-id.git synced 2026-02-12 17:55:13 +00:00

refactor!: serve the static frontend trough the backend (#520)

Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
Elias Schneider
2025-05-17 00:36:58 +02:00
parent bf710aec56
commit f8a7467ec0
74 changed files with 773 additions and 819 deletions

View File

@@ -84,33 +84,3 @@ export const refreshTokens = [
expired: true
}
];
export const idTokens = [
{
token:
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiSldUIn0.eyJhdWQiOiIzNjU0YTc0Ni0zNWQ0LTQzMjEtYWM2MS0wYmRjZmYyYjQwNTUiLCJlbWFpbCI6InRpbS5jb29rQHRlc3QuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTY5MDAwMDAwMSwiZmFtaWx5X25hbWUiOiJUaW0iLCJnaXZlbl9uYW1lIjoiQ29vayIsImlhdCI6MTY5MDAwMDAwMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdCIsIm5hbWUiOiJUaW0gQ29vayIsIm5vbmNlIjoib1cxQTFPNzhHUTE1RDczT3NIRXg3V1FLajdacXZITFp1XzM3bWRYSXFBUSIsInN1YiI6IjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIiwidHlwZSI6ImlkLXRva2VuIn0.noxQ-sCNHh7f8EaySJT7oF0DlmjYcM-FdMPH45Yuuvt5-bTpLLkggN9aq8RILmkGL9xUVsfZbYkWV5EkGobxfIoXITE98xH54BQwtpOjLL_HZLF4kFXarUyGLGO3zeVJAQzyofVz_1rKfDlZdi5Zmm-91cO5OiOtshfluDqt1h1D-E5h4ShT0eN7apvSvQnD7806-3tfxP0GHE-HuerR1Qbv9p0uWmuhT0CkVIM-K2dKBHdhLtquRqxNp2EuD_T-HA3WJgvkTTWp-JZ6NqvWDMy3M-jB-_Bs9eABERlTSTp7H2XCMGbwRSBZDmSn-97LPwc-NO5JYEkgZOeVr_r6qg',
clientId: oidcClients.nextcloud.id,
expired: true
},
{
token:
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiSldUIn0.eyJhdWQiOiIzNjU0YTc0Ni0zNWQ0LTQzMjEtYWM2MS0wYmRjZmYyYjQwNTUiLCJlbWFpbCI6InRpbS5jb29rQHRlc3QuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MjY5MDAwMDAwMSwiZmFtaWx5X25hbWUiOiJUaW0iLCJnaXZlbl9uYW1lIjoiQ29vayIsImlhdCI6MTY5MDAwMDAwMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdCIsIm5hbWUiOiJUaW0gQ29vayIsIm5vbmNlIjoib1cxQTFPNzhHUTE1RDczT3NIRXg3V1FLajdacXZITFp1XzM3bWRYSXFBUSIsInN1YiI6IjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIiwidHlwZSI6ImlkLXRva2VuIn0.ry7s3xP4-vvvZPzcRCvR1yBl32Pi09ryC6Z-67E1P4xChe8MaMoiQgImS5ZNbZiYzBN4cdkQsExXZK1FP-kMD019k3uNKPq0fIREBwrT9wXPqQJlLSBmN-tVkjLm90-b310SG5p65aajWvMkcPmJleG6y24_zoPFr3ISGI87vV6zdyoqG55pc-GkT7FwiEFIZJGQAzl7u1uOi7sQrda8Y6rF_SCC-f9I4PnHblnaTne8pfXe9jXKJeY1ZKj2Qh9dRPhWCLPHHV1YErUyoMP9oeMVzYpno-pBYVOiT9Ktl6CpG-jqB8smKqDEhZrSejgZ256h34f8jNL1SEhpM-4_cQ',
clientId: oidcClients.nextcloud.id,
expired: false
}
];
export const accessTokens = [
{
token:
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiSldUIn0.eyJhdWQiOlsiMzY1NGE3NDYtMzVkNC00MzIxLWFjNjEtMGJkY2ZmMmI0MDU1Il0sImV4cCI6MTc0Mzk3MjI4MywiaWF0IjoxNzQzOTY4NjgzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Iiwic3ViIjoiZjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIiwidHlwZSI6Im9hdXRoLWFjY2Vzcy10b2tlbiJ9.REFSDFsGso9u7WxpyMmMVvjQMgulbidQNUft-kBRg7nw5LN9pOWhO0Zlr1tZnnrA1LenZRv0BvLIf0qekwGEC4FOPmJ6-As2ggIcoBIXpUR2A4Hhuy0FtqbCUgIkda1Dcx9w1Rmfzi0eHY_-1H_98rDgS5RxqweNA_YP3RsnJqBsc9GYhDarrf1nyCOplshGOEiyisUGoU2TaURI6DTcCiDzVOm_esZqokoZTpKlQw6ZugDDObro0eWYgROo97_3cqPRgRjSYBYRAGCHhZom3bFkjmz3wqpeoGmUNgL022x3-gl7QjurpJMQrKJ7wkFs0bh2uFnnngnh2w6m4j8-5w',
clientId: oidcClients.nextcloud.id,
expired: true
},
{
token:
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiSldUIn0.eyJhdWQiOlsiMzY1NGE3NDYtMzVkNC00MzIxLWFjNjEtMGJkY2ZmMmI0MDU1Il0sImV4cCI6Mjc0Mzk3MjI4MywiaWF0IjoxNzQzOTY4NjgzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Iiwic3ViIjoiZjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIiwidHlwZSI6Im9hdXRoLWFjY2Vzcy10b2tlbiJ9.FaFsHJS_8wbvQvctftNTPyzAe9IhbpJiHIkhg28RrFRFfnBMq0QycmTUh00MJPXkUfd_j5tcCnXybF1efHsq6WbP4AWFG_TJMUyz7a9SYt1lGR8dxo3eys0YAX5eJQ5YoVTKNrivSKrC37Rg3VlcZVWXp6KBAxRWVl3OUlquSC6q7HNKAKg8sbBJiGpUJ37wwanOTE2XhYGvFB2_gxS36tvOuSTV3CVg_7Fctej7gNhKMXBFMJiIFurxZaeNud8620xtv-vJX6ALa1Qu1SkWhhZN2Yx3WuODZNlni3rUps-THoEdqh62jNwItE9wB7C0fGEKuUqVIllaF9I_7i2s3w',
clientId: oidcClients.nextcloud.id,
expired: false
}
];

View File

@@ -1,6 +1,7 @@
import test, { expect } from '@playwright/test';
import { accessTokens, idTokens, oidcClients, refreshTokens, users } from './data';
import { oidcClients, refreshTokens, users } from './data';
import { cleanupBackend } from './utils/cleanup.util';
import { generateIdToken, generateOauthAccessToken } from './utils/jwt.util';
import oidcUtil from './utils/oidc.util';
import passkeyUtil from './utils/passkey.util';
@@ -117,7 +118,7 @@ test('End session without id token hint shows confirmation page', async ({ page
test('End session with id token hint redirects to callback URL', async ({ page }) => {
const client = oidcClients.nextcloud;
const idToken = idTokens.filter((token) => token.expired)[0].token;
const idToken = await generateIdToken(users.tim, client.id);
let redirectedCorrectly = false;
await page
.goto(
@@ -193,8 +194,8 @@ test('Using refresh token invalidates it for future use', async ({ request }) =>
test.describe('Introspection endpoint', () => {
const client = oidcClients.nextcloud;
const validAccessToken = accessTokens.filter((token) => !token.expired)[0].token;
test('without client_id and client_secret fails', async ({ request }) => {
const validAccessToken = await generateOauthAccessToken(users.tim, client.id);
const introspectionResponse = await request.post('/api/oidc/introspect', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
@@ -207,7 +208,8 @@ test.describe('Introspection endpoint', () => {
expect(introspectionResponse.status()).toBe(400);
});
test('with client_id and client_secret succeeds', async ({ request }) => {
test('with client_id and client_secret succeeds', async ({ request, baseURL }) => {
const validAccessToken = await generateOauthAccessToken(users.tim, client.id);
const introspectionResponse = await request.post('/api/oidc/introspect', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
@@ -222,7 +224,7 @@ test.describe('Introspection endpoint', () => {
const introspectionBody = await introspectionResponse.json();
expect(introspectionBody.active).toBe(true);
expect(introspectionBody.token_type).toBe('access_token');
expect(introspectionBody.iss).toBe('http://localhost');
expect(introspectionBody.iss).toBe(baseURL);
expect(introspectionBody.sub).toBe(users.tim.id);
expect(introspectionBody.aud).toStrictEqual([oidcClients.nextcloud.id]);
});
@@ -265,7 +267,7 @@ test.describe('Introspection endpoint', () => {
});
test("expired access_token can't be verified", async ({ request }) => {
const expiredAccessToken = accessTokens.filter((token) => token.expired)[0].token;
const expiredAccessToken = await generateOauthAccessToken(users.tim, client.id, true);
const introspectionResponse = await request.post('/api/oidc/introspect', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'

View File

@@ -1,5 +1,6 @@
import axios from 'axios';
import playwrightConfig from '../../playwright.config';
export async function cleanupBackend() {
await axios.post('http://localhost/api/test/reset');
await axios.post(playwrightConfig.use!.baseURL + '/api/test/reset');
}

View File

@@ -0,0 +1,58 @@
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);
}