mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-29 06:02:25 +03:00
Installing on a host should be a command a person pastes, not a user session copied onto a machine. A token is issued for a team, spent by its first use, expires after an hour and is stored only as a digest, because it travels in that command and so lands in shell history. Redeeming is one conditional update inside the registration transaction: concurrent redemptions of one token register exactly one machine, and a registration that fails leaves the token unspent.
41 lines
1.5 KiB
TypeScript
41 lines
1.5 KiB
TypeScript
import { index, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
|
|
import { id, timestamps, ulid, utc } from '../db/types.js';
|
|
import { TeamTable } from '../team/team.sql.js';
|
|
import { UserTable } from '../user/user.sql.js';
|
|
import { MachineTable } from './machine.sql.js';
|
|
|
|
/**
|
|
* A one-time credential that registers exactly one machine to one team.
|
|
*
|
|
* It exists so that installing on a host is a command a person pastes, rather
|
|
* than a user session copied onto a machine. It travels in that command, so it
|
|
* lands in shell history: that is why it is single-use, expires in minutes,
|
|
* and is stored only as a digest.
|
|
*/
|
|
export const InstallTokenTable = pgTable(
|
|
'install_token',
|
|
{
|
|
...id,
|
|
...timestamps,
|
|
teamId: ulid('team_id')
|
|
.notNull()
|
|
.references(() => TeamTable.id, { onDelete: 'cascade' }),
|
|
// Who asked for it. They become the machine's owner, as they would have
|
|
// by registering it with their own session.
|
|
createdByUserId: ulid('created_by_user_id')
|
|
.notNull()
|
|
.references(() => UserTable.id, { onDelete: 'cascade' }),
|
|
tokenHash: text('token_hash').notNull(),
|
|
expiresAt: utc('expires_at').notNull(),
|
|
redeemedAt: utc('redeemed_at'),
|
|
// The machine it made. Null until redeemed; kept afterwards so a support
|
|
// conversation can say which command produced which host.
|
|
machineId: ulid('machine_id').references(() => MachineTable.id, { onDelete: 'set null' })
|
|
},
|
|
(t) => [
|
|
uniqueIndex('install_token_hash_unique').on(t.tokenHash),
|
|
index('install_token_team_idx').on(t.teamId)
|
|
]
|
|
);
|