diff --git a/.gitignore b/.gitignore index 0d566cc..916145e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,10 @@ pnpm-debug.log* # Test coverage coverage/ .nyc_output/ + +# Playwright CLI artifacts + ad-hoc screenshots (local verification only) +.playwright-cli/ +gate2-*.png + +# Operator-only credential seed (run out-of-band; never tracked) +apps/api/scripts/seed-credential.mjs diff --git a/apps/api/scripts/seed-credential.mjs b/apps/api/scripts/seed-credential.mjs deleted file mode 100644 index a276c64..0000000 --- a/apps/api/scripts/seed-credential.mjs +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Seed (or update) a member's encrypted Fastmail app-password credential. - * - * There is no onboarding UI — member_credentials is seeded out-of-band by the - * operator. This script encrypts the app password with the same AES-256-GCM - * helper the app uses (APP_PASSWORD_ENCRYPTION_KEY) and upserts one row. - * - * Run INSIDE the api container (where APP_PASSWORD_ENCRYPTION_KEY + DB_* are set): - * - * docker compose cp apps/api/scripts/seed-credential.mjs api:/app/apps/api/scripts/seed-credential.mjs - * docker compose exec \ - * -e FASTMAIL_APP_PASSWORD='xxxx-xxxx-xxxx-xxxx' \ - * -e FASTMAIL_EMAIL='me@lucasberger.ca' \ - * -e SEED_USER_ID=2 \ - * api node scripts/seed-credential.mjs - * - * The app password never leaves the operator's shell — it is passed as an env - * var to `docker compose exec` and encrypted at rest immediately. - */ -import { encryptPassword } from '../dist/broker/crypto.js' -import mysql from 'mysql2/promise' - -const email = process.env.FASTMAIL_EMAIL || 'me@lucasberger.ca' -const password = process.env.FASTMAIL_APP_PASSWORD -const userId = Number.parseInt(process.env.SEED_USER_ID || '', 10) - -if (!password) { - console.error('ERROR: set FASTMAIL_APP_PASSWORD') - process.exit(1) -} -if (!Number.isInteger(userId) || userId <= 0) { - console.error('ERROR: set SEED_USER_ID to the target users.id (integer)') - process.exit(1) -} - -// Encrypt with the app's key (read from APP_PASSWORD_ENCRYPTION_KEY at call time). -const encrypted = encryptPassword(password) - -const conn = await mysql.createConnection({ - host: process.env.DB_HOST || 'mariadb', - port: Number.parseInt(process.env.DB_PORT || '3306', 10), - user: process.env.DB_USER || 'familysync', - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME || 'familysync', -}) - -try { - // Confirm the user exists before linking a credential to it. - const [users] = await conn.execute('SELECT id FROM users WHERE id = ?', [userId]) - if (users.length === 0) { - console.error(`ERROR: no users row with id=${userId}. Log in first to create it.`) - process.exit(1) - } - - const [existing] = await conn.execute( - 'SELECT id FROM member_credentials WHERE user_id = ?', - [userId], - ) - if (existing.length > 0) { - await conn.execute( - 'UPDATE member_credentials SET encrypted_password = ?, fastmail_email = ? WHERE user_id = ?', - [encrypted, email, userId], - ) - console.log(`Updated member_credentials for user_id=${userId} (${email})`) - } else { - await conn.execute( - 'INSERT INTO member_credentials (user_id, encrypted_password, fastmail_email) VALUES (?, ?, ?)', - [userId, encrypted, email], - ) - console.log(`Inserted member_credentials for user_id=${userId} (${email})`) - } -} finally { - await conn.end() -}