chain-studio/lib/data/registry_token.dart
flemming-it 891acd2ba2
Some checks failed
Security / Security check (push) Failing after 2s
refactor: rename internal Fai* design system + fai_ helpers to chain
The Studio design system, widgets and helpers carried a Fai* / fai_
prefix (FaiSpace, FaiColors, FaiTheme, FaiLog, 17 fai_*.dart files, the
faiBinary* l10n keys). Studio is the Ch∆In product, so rename them to
Chain* / chain_ — carefully preserving English fail/failure/failed.
Also fix stale references: the 'fai' binary in l10n strings -> 'chain',
FAI_* env vars (FAI_BIN/DATA_DIR/MODULES_DIR/TODAY/BOOTSTRAP_TOKEN) ->
CHAIN_*, fai_platform -> fai_chain, fai_hub -> chain_hub. Vendor
security-hook tooling (FAI_BANNED_TERMS_FILE) + the .fai bundle ext left.
flutter analyze + test: clean (20 passed).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-16 17:53:17 +02:00

67 lines
2.4 KiB
Dart

import 'dart:io';
import 'package:path/path.dart' as p;
/// Operator-managed registry auth token, kept at
/// `~/.chain/registry-token` (mode 0600 on Unix). The hub reads
/// this file at install time when `CHAIN_REGISTRY_TOKEN` is unset
/// — see `download_to_temp` in `crates/chain_hub/src/lib.rs`.
///
/// Studio writes the file directly so a fresh install never
/// requires the operator to fiddle with shell env vars.
class RegistryToken {
static String _faiHome() {
final home =
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
if (home == null || home.isEmpty) {
throw StateError('Cannot resolve home directory (no HOME / USERPROFILE)');
}
return p.join(home, '.fai');
}
/// Absolute path to the token file.
static String get path => p.join(_faiHome(), 'registry-token');
/// True iff the file exists with a non-empty trimmed body.
static Future<bool> isConfigured() async {
final f = File(path);
if (!await f.exists()) return false;
final content = await f.readAsString();
return content.trim().isNotEmpty;
}
/// Length of the trimmed token, or null when the file is
/// missing / empty. Used for status display ("Configured
/// (40 chars)") without ever surfacing the secret.
static Future<int?> charCount() async {
final f = File(path);
if (!await f.exists()) return null;
final content = await f.readAsString();
final trimmed = content.trim();
return trimmed.isEmpty ? null : trimmed.length;
}
/// Persist [token] to disk, creating `~/.chain/` if needed.
/// On Unix the file is chmod-ed to 0600 (owner read/write
/// only). On Windows the default user-ACL is left alone —
/// best-effort, no PowerShell handshake.
static Future<void> write(String token) async {
final f = File(path);
await f.parent.create(recursive: true);
await f.writeAsString(token.trim(), flush: true);
if (Platform.isLinux || Platform.isMacOS) {
try {
await Process.run('chmod', ['600', f.path]);
} catch (_) {
// best-effort; if chmod is unavailable the file still
// exists with default perms (usually 0644, world-readable
// but only on the operator's own machine).
}
}
}
/// Delete the token file. No-op when the file doesn't exist.
static Future<void> delete() async {
final f = File(path);
if (await f.exists()) await f.delete();
}
}