mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
fix(api): a misshapen id is bad input, not a server fault
Ids are stored in a fixed-width column, so an overlong one is refused by Postgres rather than simply matching nothing. That refusal is not a foreign-key violation, so it fell through to the global error boundary and reached the caller as a 500 — telling a host to retry something that can never succeed. Measured: a 44-character user id returned 500, where an absent but well-formed one correctly returned 404. `Identifier.schema` is the natural place for the check and had no callers yet, so it now asserts the exact width an id has as well as its prefix — including the separator, without which `usrsomething` reads as a user id. The enrolment schema uses it for both foreign keys, so the refusal happens where the input arrives and names the field. Also index `steam_enrolment.user_id`. The primary key begins with the machine, which answers what one host holds and nothing else, so neither of the two things that read by user alone can use it: the cascade behind deleting a user, and asking which hosts hold a token for one person. The table's migration has not been released, so this is folded into it rather than following it with a correction.
This commit is contained in:
@@ -149,12 +149,33 @@ describe('POST /machine/enrolment', () => {
|
||||
|
||||
test('a user nobody has heard of is refused rather than crashing', async () => {
|
||||
const host = await registeredHost('enrol-ghost');
|
||||
const res = await enrol(host, { userId: 'usr_nosuchuseratall', steamId: steamId(6) });
|
||||
// Well-formed and simply absent, which is the case the foreign key
|
||||
// catches. A malformed one never reaches the database at all.
|
||||
const res = await enrol(host, {
|
||||
userId: Identifier.ascending('user'),
|
||||
steamId: steamId(6)
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
const body = (await res.json()) as any;
|
||||
expect(body.type).toBe('not_found');
|
||||
});
|
||||
|
||||
test('a userId of the wrong shape is bad input, not a server fault', async () => {
|
||||
// Ids live in a fixed-width column, so an overlong one is refused by
|
||||
// the database rather than merely not found — and that refusal used to
|
||||
// reach the host as a 500, which tells it to retry something that can
|
||||
// never succeed. The width is checked where the input arrives.
|
||||
const host = await registeredHost('enrol-misshapen');
|
||||
const malformed = [`usr_${'a'.repeat(40)}`, 'usr_short', `mch_${'a'.repeat(26)}`, 'nonsense'];
|
||||
for (const userId of malformed) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const res = await enrol(host, { userId, steamId: steamId(15) });
|
||||
expect(res.status).toBe(400);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
expect(((await res.json()) as any).type).toBe('validation');
|
||||
}
|
||||
});
|
||||
|
||||
test('a Steam id has to look like one', async () => {
|
||||
const host = await registeredHost('enrol-badsteam');
|
||||
const res = await enrol(host, { userId: host.userId, steamId: 'not-a-steam-id' });
|
||||
@@ -165,7 +186,7 @@ describe('POST /machine/enrolment', () => {
|
||||
const res = await app.request('/machine/enrolment', {
|
||||
method: 'POST',
|
||||
headers: { 'x-nestri-admin-token': TEST_ADMIN_SECRET, 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ userId: 'usr_x', steamId: steamId(7) })
|
||||
body: JSON.stringify({ userId: Identifier.ascending('user'), steamId: steamId(7) })
|
||||
});
|
||||
expect(res.status).toBe(403);
|
||||
expect(((await res.json()) as any).message).toContain('Machine credentials');
|
||||
@@ -220,7 +241,7 @@ describe('POST /machine/enrolment/stale', () => {
|
||||
const res = await app.request('/machine/enrolment/stale', {
|
||||
method: 'POST',
|
||||
headers: { 'x-nestri-admin-token': TEST_ADMIN_SECRET, 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ userId: 'usr_x' })
|
||||
body: JSON.stringify({ userId: Identifier.ascending('user') })
|
||||
});
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user