fix(api): a Steam sign-in on any host links the account

A sign-in on a person's own host recorded the enrolment but never
linked the Steam account, so their first launch asked them to link it
again with nothing in the app to do it from. The QR sign-in is the same
proof on their host as on ours.
This commit is contained in:
Wanjohi
2026-10-11 17:33:18 +03:00
parent 84bd6b899a
commit 24ad9f6b78
2 changed files with 11 additions and 22 deletions
+8 -19
View File
@@ -1,7 +1,5 @@
import { Actor } from '@nestri/core/actor';
import { Env } from '@nestri/core/env';
import { ErrorCodes, VisibleError } from '@nestri/core/error';
import { Machine } from '@nestri/core/machine/index';
import { Enrolment } from '@nestri/core/steam/enrolment';
import { Identity } from '@nestri/core/user/identity';
import { Hono } from 'hono';
@@ -31,32 +29,23 @@ import { ErrorResponses, machineOnly, Result, validator } from '../utils';
*/
export namespace EnrolmentApi {
/**
* A sign-in on one of Nestri's own machines is also proof of the Steam
* account, so it links it: the person scanned a QR code on hardware we
* run, the Steam id is the subject of the token Valve issued there, and
* who they are came from the edge. Asking them to sign in to Steam a
* second time, in a browser, would prove nothing new.
*
* **Only the fleet.** On a host somebody brought, the report is that
* person's machine vouching for itself, and a machine can say anything.
* Linking from it would let any host operator attach any Steam account to
* any user enrolled on it.
* A Steam sign-in on a host is also proof of the Steam account, so it
* links it: the person scanned a QR code with the Steam app, the Steam id
* is the subject of the token Valve issued there, and who they are came
* from the edge. Asking them to sign in to Steam a second time, in a
* browser, would prove nothing new.
*
* Never a reason to refuse the report. The sign-in happened and the
* enrolment is recorded either way; a Steam account already linked to
* somebody else stays theirs, and is left for the person to sort out on
* the website.
*/
async function linkIfFleet(machineId: string, userId: string, steamId: string) {
const fleet = Env.get().FLEET_ORGANISATION_ID;
if (!fleet) return;
const machine = await Machine.fromID(machineId);
if (machine?.organisationId !== fleet) return;
async function link(machineId: string, userId: string, steamId: string) {
try {
await Identity.linkSteam({ userId, steamId });
} catch (err) {
if (err instanceof VisibleError) {
console.warn('a fleet sign-in was not linked', { machineId, userId, reason: err.message });
console.warn('a sign-in was not linked', { machineId, userId, reason: err.message });
return;
}
throw err;
@@ -102,7 +91,7 @@ export namespace EnrolmentApi {
userId: body.userId,
steamId: body.steamId
});
await linkIfFleet(Actor.machineID, body.userId, body.steamId);
await link(Actor.machineID, body.userId, body.steamId);
return c.json({ data: enrolment });
}
)
+3 -3
View File
@@ -393,7 +393,7 @@ describe('The enrolment surface refuses a token', () => {
});
});
describe('A sign-in on the fleet links the Steam account', () => {
describe('A sign-in on a host links the Steam account', () => {
/** A fleet host the API knows is the fleet, and a person to enrol on it. */
async function fleetHost(label: string) {
const operator = await Fixtures.owner(`${label}-operator`);
@@ -436,11 +436,11 @@ describe('A sign-in on the fleet links the Steam account', () => {
expect(await linked(steamId(9001))).toHaveLength(1);
});
test('a host somebody brought cannot link anything', async () => {
test('a host somebody brought links it too', async () => {
const host = await registeredHost('own-host-link');
const res = await enrol(host, { userId: host.userId, steamId: steamId(9002) });
expect(res.status).toBe(200);
expect(await linked(steamId(9002))).toHaveLength(0);
expect((await linked(steamId(9002))).map((r) => r.user_id)).toEqual([host.userId]);
});
test('an account already linked elsewhere stays there, and the report still lands', async () => {