@preventive/triage 1.0.0-alpha.20 → 1.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preventive/triage",
3
- "version": "1.0.0-alpha.20",
3
+ "version": "1.0.0-alpha.21",
4
4
  "description": "Client & relay server for triaging of automated reports",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -24,8 +24,8 @@ export interface ManagedConfig {
24
24
  // + secret, used by the login flow.
25
25
  githubClientId: string
26
26
  githubClientSecret: string
27
- // Preapproved GitHub identity for the first registration in an empty DB.
28
- // This never changes an existing user's role on later logins.
27
+ // Preapproved GitHub identity promoted on login when it is the only user
28
+ // and its role is No access, including a login after registration.
29
29
  initialAdminGithubId: number | null
30
30
  // Absolute callback registered with GitHub, e.g.
31
31
  // 'https://triage.example.com/api/oauth/github/callback'.
@@ -307,8 +307,8 @@ export interface UserTeam {
307
307
  // Backend-agnostic store surface (SQLite + PostgreSQL implementations).
308
308
  export interface ManagedDb extends ActivityStore, CommentStore, GithubMetadataStore, WorkspaceShareStore, ImportTriageStore {
309
309
  // Upsert the identity; returns the user's opaque id (stable across logins).
310
- // Initial-admin approval comes only from trusted server configuration and
311
- // applies exclusively to the first insertion into an empty user table.
310
+ // Initial-admin approval comes only from trusted login configuration. It
311
+ // promotes a matching No access identity only while it is the sole user.
312
312
  upsertUser(user: ManagedUser, now: number, initialAdminGithubId?: number | null): Promise<string>
313
313
  createSession(session: ManagedSession, now: number): Promise<void>
314
314
  sessionWithUser(id: string, now: number): Promise<{ session: ManagedSession; user: StoredUser } | null>
@@ -447,18 +447,20 @@ const BUNDLE_IN_TEAM_PATH_SQL = REPORT_IN_TEAM_PATH_SQL.replaceAll('r.repo_direc
447
447
  // destructures — keeps openSqliteManagedDb itself small (one place per query).
448
448
  function prepareStatements(db: ManagedSql) {
449
449
  return {
450
- // Only the operator-preapproved identity may bootstrap the empty DB.
451
- // The emptiness check and insert share the store's writer transaction,
452
- // so concurrent first logins cannot both become admins. Re-login never
453
- // overwrites approval or revocation through the ON CONFLICT update.
450
+ // Identity refreshes preserve roles. Login can separately bootstrap the
451
+ // configured sole No access user within this same writer transaction.
454
452
  upsertUserStmt: db.prepare(
455
453
  `INSERT INTO managed_user (id, github_user_id, login, name, avatar_url, role, created_at, updated_at)
456
- VALUES (?, ?, ?, ?, ?, CASE WHEN NOT EXISTS(SELECT 1 FROM managed_user) THEN ? ELSE 'none' END, ?, ?)
454
+ VALUES (?, ?, ?, ?, ?, 'none', ?, ?)
457
455
  ON CONFLICT(github_user_id) DO UPDATE SET
458
456
  login = excluded.login, name = excluded.name,
459
457
  avatar_url = excluded.avatar_url, updated_at = excluded.updated_at`,
460
458
  ),
461
- selectUserIdStmt: db.prepare(`SELECT id FROM managed_user WHERE github_user_id = ?`),
459
+ selectUserIdStmt: db.prepare(`SELECT id, role FROM managed_user WHERE github_user_id = ?`),
460
+ promoteInitialAdminStmt: db.prepare(
461
+ `UPDATE managed_user SET role = 'admin' WHERE id = ? AND role = 'none'
462
+ AND NOT EXISTS(SELECT 1 FROM managed_user WHERE id <> ?)`,
463
+ ),
462
464
  selectGithubIdStmt: db.prepare(`SELECT github_user_id AS githubId FROM managed_user WHERE id = ?`),
463
465
  insertSessionStmt: db.prepare(
464
466
  `INSERT INTO managed_session (id, user_id, csrf_token, created_at, expires_at) VALUES (?, ?, ?, ?, ?)`,
@@ -1261,7 +1263,7 @@ export function createManagedMethods(db: ManagedSql, options: ManagedDbOptions =
1261
1263
  const stmts = prepareStatements(db)
1262
1264
  const triage = triageMethods(stmts, options.triageHistoryLimit ?? 0)
1263
1265
  const {
1264
- upsertUserStmt, selectUserIdStmt, selectGithubIdStmt, insertSessionStmt, selectSessionStmt, selectUsersStmt,
1266
+ upsertUserStmt, selectUserIdStmt, promoteInitialAdminStmt, selectGithubIdStmt, insertSessionStmt, selectSessionStmt, selectUsersStmt,
1265
1267
  touchUserSeenStmt, updateRoleStmt, updateTokensStmt, selectTokensStmt, deleteSessionStmt, deleteExpiredStmt,
1266
1268
  } = stmts
1267
1269
 
@@ -1269,10 +1271,14 @@ export function createManagedMethods(db: ManagedSql, options: ManagedDbOptions =
1269
1271
  async upsertUser(user, now, initialAdminGithubId = null) {
1270
1272
  // New row → a fresh id; ON CONFLICT(github_user_id) keeps an existing
1271
1273
  // user's id (DO UPDATE leaves it untouched), so re-read to return it.
1272
- const initialRole = initialAdminGithubId != null && Number.isSafeInteger(initialAdminGithubId) && initialAdminGithubId > 0
1273
- && user.githubUserId === initialAdminGithubId ? 'admin' : 'none'
1274
- await upsertUserStmt.run(randomUUID(), user.githubUserId, user.login, user.name, user.avatarUrl, initialRole, now, now)
1275
- const row = (await selectUserIdStmt.get(user.githubUserId)) as { id: string }
1274
+ await upsertUserStmt.run(randomUUID(), user.githubUserId, user.login, user.name, user.avatarUrl, now, now)
1275
+ const row = (await selectUserIdStmt.get(user.githubUserId)) as { id: string; role: Role }
1276
+ // Avoid the population check unless this login matches the configured
1277
+ // identity and still needs approval. NOT EXISTS stops at any other user.
1278
+ if (initialAdminGithubId != null && Number.isSafeInteger(initialAdminGithubId) && initialAdminGithubId > 0
1279
+ && user.githubUserId === initialAdminGithubId && row.role === 'none') {
1280
+ await promoteInitialAdminStmt.run(row.id, row.id)
1281
+ }
1276
1282
  return row.id
1277
1283
  },
1278
1284
  async createSession(session, now) {
@@ -2,6 +2,8 @@ import type { StoredUser } from './db.ts'
2
2
 
3
3
  const WHITEHAT = [
4
4
  291301, // ChALkeR
5
+ 259122746, // exo-nikita
6
+ 247161625, // ex0sec
5
7
  ]
6
8
  const whitehatIds = new Set(WHITEHAT)
7
9