mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-06 11:52:13 +00:00
104 lines
3.8 KiB
TypeScript
104 lines
3.8 KiB
TypeScript
/*
|
|
* Copyright (C) 2024-present Puter Technologies Inc.
|
|
*
|
|
* This file is part of Puter.
|
|
*
|
|
* Puter is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* Wiring test for `guiOriginOnly`.
|
|
*
|
|
* `originGate.test.ts` covers what the gate decides. This covers that the
|
|
* routes which hand a session credential back to the caller actually opt into
|
|
* it — a correct gate wired to nothing protects nothing, and the failure is
|
|
* invisible (the route keeps working, just for everybody).
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { AuthController } from './AuthController.js';
|
|
import {
|
|
ROUTES_METADATA_KEY,
|
|
type CollectedRoute,
|
|
} from '../../core/http/types.js';
|
|
|
|
// The decorators register on the prototype via `addInitializer`, which only
|
|
// runs once an instance exists. We never start this one — constructing it is
|
|
// enough to populate the route metadata.
|
|
const collectRoutes = (): CollectedRoute[] => {
|
|
const proto = AuthController.prototype as unknown as Record<
|
|
string,
|
|
CollectedRoute[] | undefined
|
|
>;
|
|
if (!proto[ROUTES_METADATA_KEY]) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
new (AuthController as any)({}, {}, {}, {}, {});
|
|
}
|
|
return proto[ROUTES_METADATA_KEY] ?? [];
|
|
};
|
|
|
|
/**
|
|
* Every route that answers with a usable session credential. Adding one here
|
|
* without `guiOriginOnly` is the mistake this test exists to catch: reflected
|
|
* CORS would let any page read the credential out of the response.
|
|
*/
|
|
const CREDENTIAL_ROUTES: Array<[method: string, path: string]> = [
|
|
['post', '/login'],
|
|
['post', '/login/otp'],
|
|
['post', '/login/recovery-code'],
|
|
['post', '/signup'],
|
|
['get', '/session/sync-cookie'],
|
|
];
|
|
|
|
describe('guiOriginOnly wiring on AuthController', () => {
|
|
const routes = collectRoutes();
|
|
|
|
it('registers the routes under test at all (guards against a rename)', () => {
|
|
for (const [method, path] of CREDENTIAL_ROUTES) {
|
|
const found = routes.find(
|
|
(r) => r.method === method && r.path === path,
|
|
);
|
|
expect(found, `${method.toUpperCase()} ${path} not registered`)
|
|
.toBeDefined();
|
|
}
|
|
});
|
|
|
|
it.each(CREDENTIAL_ROUTES)(
|
|
'gates %s %s to the GUI origin',
|
|
(method, path) => {
|
|
const route = routes.find(
|
|
(r) => r.method === method && r.path === path,
|
|
);
|
|
expect(route?.options.guiOriginOnly).toBe(true);
|
|
},
|
|
);
|
|
|
|
// The popup relay is the third-party sign-in path: `puter.auth.signIn()`
|
|
// polls `/login/wait` from whatever origin the app is served from, and
|
|
// `/login/set` is posted by the popup. Gating either to our own origin
|
|
// would break every third-party app, and neither needs it — the token
|
|
// they move is app-scoped, not a session.
|
|
it.each([
|
|
['post', '/login/wait'],
|
|
['post', '/login/set'],
|
|
])('leaves %s %s open cross-origin', (method, path) => {
|
|
const route = routes.find(
|
|
(r) => r.method === method && r.path === path,
|
|
);
|
|
expect(route, `${method.toUpperCase()} ${path} not registered`)
|
|
.toBeDefined();
|
|
expect(route?.options.guiOriginOnly).toBeUndefined();
|
|
});
|
|
});
|