mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
feat(api): record which host holds a Steam token for whom
A host that signs a person into Steam ends up holding a refresh token. The control plane needs to know that happened — to show it, and so a host that lost its disk can find out what it is expected to hold — but it must not know the credential, because the token is bound to the address that obtained it and a copy anywhere else is the account-theft signal Steam watches for. So `steam_enrolment` stores the outcome and has no token column, no encrypted token column, and no column that could hold one later. The safeguard is that the credential is never sent here at all; a nullable column would be the first step in undoing it, so a test asserts the column list exactly and fails if one appears. Three machine-authenticated routes go with it: report a completed sign-in, report that Steam refused the token, and list what this host should have. All three take the host from its own credentials, so a box can neither report onto nor read another box's hardware. Their bodies are strict, so a host that sends a token is told it is wrong rather than quietly believed — which also keeps the value out of the request log. The Steam id is deliberately not unique. One account signed in on two hosts is two rows and two tokens, and a unique index there would look like hygiene while refusing somebody their second box. There is no `pending` state: a sign-in challenge lives about two minutes inside one process, and nothing outside it needs to know it exists. Nothing revokes yet, and `last_ok_at` has no writer — a successful logon happens where there is no credential to report it with — so the column exists with the shape it will need and stays null rather than being filled with the nearest event that was easy to observe.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
-- That a host holds a Steam refresh token for a user — and never the token.
|
||||
--
|
||||
-- The auth session begins on the machine that will use the credential, so the
|
||||
-- token is written on that host, encrypted, under that host's own account, and
|
||||
-- it never travels back. What travels back is the outcome, and this table is
|
||||
-- where the outcome is kept. ref(d-0004)
|
||||
--
|
||||
-- **There is no token column and there must never be one**, including a
|
||||
-- nullable "encrypted token" that looks harmless while empty. The protection
|
||||
-- here is not that the column is guarded; it is that the credential is never
|
||||
-- sent to this database at all, and a column able to hold one is the first step
|
||||
-- in undoing that. The same applies to the challenge URL and client id the
|
||||
-- sign-in flow uses: they live for about two minutes inside one process and
|
||||
-- nothing outside it needs them.
|
||||
--
|
||||
-- `steam_id` is not unique, on purpose. One Steam account signed in on two
|
||||
-- hosts is two rows and two tokens, because each token is bound to the address
|
||||
-- that asked for it — that binding is the anti-theft signal, and sharing one
|
||||
-- token between hosts is the thing it fires on. A unique index here would read
|
||||
-- as hygiene and would refuse a person their second box.
|
||||
--
|
||||
-- The key is the pair. An enrolment is a fact about this user on this host and
|
||||
-- there is exactly one such fact, so the row carries no surrogate id. It also
|
||||
-- carries no `time_deleted`: the three states are the lifecycle, and the row
|
||||
-- itself only goes away when the machine or the user does, which the foreign
|
||||
-- keys already do.
|
||||
--
|
||||
-- `last_ok_at` has no writer yet. A successful logon happens inside the
|
||||
-- workload, which holds no control-plane credential, so the report has to come
|
||||
-- back out through the host and nothing carries it today. The column exists
|
||||
-- with the shape it will need and stays null rather than being filled with the
|
||||
-- nearest event that was easy to observe.
|
||||
|
||||
CREATE TYPE "public"."steam_enrolment_state" AS ENUM('enrolled', 'stale', 'revoked');--> statement-breakpoint
|
||||
CREATE TABLE "steam_enrolment" (
|
||||
"machine_id" char(30) NOT NULL,
|
||||
"user_id" char(30) NOT NULL,
|
||||
"steam_id" text NOT NULL,
|
||||
"state" "steam_enrolment_state" NOT NULL,
|
||||
"enrolled_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"last_ok_at" timestamp with time zone,
|
||||
"revoked_at" timestamp with time zone,
|
||||
CONSTRAINT "steam_enrolment_machine_id_user_id_pk" PRIMARY KEY("machine_id","user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "steam_enrolment" ADD CONSTRAINT "steam_enrolment_machine_id_machine_id_fk" FOREIGN KEY ("machine_id") REFERENCES "public"."machine"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "steam_enrolment" ADD CONSTRAINT "steam_enrolment_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
2914
packages/core/migrations/meta/0012_snapshot.json
Normal file
2914
packages/core/migrations/meta/0012_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,13 @@
|
||||
"when": 1788607804606,
|
||||
"tag": "0011_auth_state_in_postgres",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "7",
|
||||
"when": 1788690115352,
|
||||
"tag": "0012_steam_enrolment_without_a_token",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user