[GH-ISSUE #164] How to reset password #79

Closed
opened 2026-02-27 15:55:00 +03:00 by kerem · 4 comments
Owner

Originally created by @Bluscream on GitHub (Jan 12, 2026).
Original GitHub issue: https://github.com/RayLabsHQ/gitea-mirror/issues/164

When logging in and forgotten the password i see Don't have an account? Contact your administrator. but i am the admin. i dont have access the docker internal fs tho.

Application Version Network Container IP Container Port LAN IP:Port Volume Mappings (App to Host) Autostart Uptime
giteastarted up-to-date bridge Tailscale 172.17.0.16 22:TCP3000:TCP 192.168.2.12:2222192.168.2.12:3001 /data/git/repositories/mnt/user/projects//data/mnt/user/appdata/gitea//opt/unraid/tailscale/usr/local/share/docker/tailscale_container_hook Off Uptime: 2 hoursCreated: 1 hour ago
gitea-mirrorstartedCompose Stack: gitea_mirror Compose gitea_mirror_default 172.23.0.2 4321:TCP 192.168.2.12:3002 /app/datagitea_mirror_gitea-mirror-data Compose Uptime: About an hour (healthy)Created: 1 hour ago

I accidentally used bogus login while installing because it was complaining about wrong origin (the server has 3 exposed ips .10,.11,.12 and i tried to use .12 but it only allowed from .10)

Originally created by @Bluscream on GitHub (Jan 12, 2026). Original GitHub issue: https://github.com/RayLabsHQ/gitea-mirror/issues/164 When logging in and forgotten the password i see `Don't have an account? Contact your administrator.` but i am the admin. i dont have access the docker internal fs tho. Application | Version | Network | Container IP | Container Port | LAN IP:Port | Volume Mappings (App to Host) | Autostart | Uptime -- | -- | -- | -- | -- | -- | -- | -- | -- giteastarted | up-to-date | bridge Tailscale | 172.17.0.16 | 22:TCP3000:TCP | 192.168.2.12:2222192.168.2.12:3001 | /data/git/repositories/mnt/user/projects//data/mnt/user/appdata/gitea//opt/unraid/tailscale/usr/local/share/docker/tailscale_container_hook | Off | Uptime: 2 hoursCreated: 1 hour ago gitea-mirrorstartedCompose Stack: gitea_mirror | Compose | gitea_mirror_default | 172.23.0.2 | 4321:TCP | 192.168.2.12:3002 | /app/datagitea_mirror_gitea-mirror-data | Compose | Uptime: About an hour (healthy)Created: 1 hour ago I accidentally used bogus login while installing because it was complaining about wrong origin (the server has 3 exposed ips .10,.11,.12 and i tried to use .12 but it only allowed from .10)
kerem closed this issue 2026-02-27 15:55:00 +03:00
Author
Owner

@Bluscream commented on GitHub (Jan 13, 2026):

#!/usr/bin/env node
/**
    sudo apt install sqlite3 nodejs npm
    winget install SQLite NodeJs
    npm install sqlite3
    node script.js --hash/-h <password> # Generate hash for password
    node script.js [secret] [user] [email] [pass] [db] # Create admin user
*/
const sqlite3 = require('sqlite3').verbose();
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const BETTER_AUTH_CONFIG = { N: 16384, r: 16, p: 1, dkLen: 64 };
const hex = { encode: (buffer) => Buffer.from(buffer).toString('hex') };
async function testHashing() {
    const knownPassword = "123123123";
    const knownSalt = "2c3261f3350781361e46bc04844d7299";
    const knownHash = "8f402fda934a0f5cd29bca3cb6e870870da82dd96d57d49bdeb8938b88f3c4b02187aa973401fffa33ff2e16f94a550a229e8a32c1392d521335eb0bcf35830c";
    const expectedFullHash = `${knownSalt}:${knownHash}`;
    console.log("🧪 Testing Better Auth 1.4.5 hashing implementation...");
    try {
        const testResult = await generateKey(knownPassword, knownSalt);
        const computedHash = testResult.toString('hex');
        const computedFullHash = `${knownSalt}:${computedHash}`;
        if (computedFullHash === expectedFullHash) {
            console.log("✅ Hash verification PASSED - Implementation is correct!");
            return true;
        } else {
            console.log("❌ Hash verification FAILED - Implementation is incorrect!");
            console.log(`   Expected: ${expectedFullHash.substring(0, 32)}...`);
            console.log(`   Got:      ${computedFullHash.substring(0, 32)}...`);
            return false;
        }
    } catch (error) {
        console.log(`❌ Hash verification ERROR: ${error.message}`);
        return false;
    }
}
async function generateKey(password, salt) {
    return new Promise((resolve, reject) => {
        const maxmem = 128 * BETTER_AUTH_CONFIG.N * BETTER_AUTH_CONFIG.r * 2;
        crypto.scrypt(password.normalize("NFKC"), salt, BETTER_AUTH_CONFIG.dkLen, {
            N: BETTER_AUTH_CONFIG.N,
            r: BETTER_AUTH_CONFIG.r,
            p: BETTER_AUTH_CONFIG.p,
            maxmem: maxmem
        }, (err, derivedKey) => {
            if (err) reject(err);
            else resolve(derivedKey);
        });
    });
}
async function hashPassword(password) { // Hash password using exact Better Auth 1.4.5 implementation
    const salt = hex.encode(crypto.randomBytes(16));
    try {
        const hashBytes = await generateKey(password, salt);
        return `${salt}:${hashBytes.toString('hex')}`;
    } catch (error) {
        console.log(`Warning: Could not use exact Better Auth parameters (${error.message}), falling back to compatible params`);
        return new Promise((resolve, reject) => {
            crypto.scrypt(password.normalize("NFKC"), salt, 64, {
                N: 8192, r: 8, p: 1
            }, (err, derivedKey) => {
                if (err) reject(err);
                else resolve(`${salt}:${derivedKey.toString('hex')}`);
            });
        });
    }
}
function generateNanoid(length = 32) {
    const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let result = '';
    for (let i = 0; i < length; i++) {
        result += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
    }
    return result;
}
async function addOrUpdateUser(dbPath, username, email, password, secret) {
    return new Promise((resolve, reject) => {
        if (!fs.existsSync(dbPath)) {
            reject(new Error(`Database file ${dbPath} not found`));
            return;
        }
        const backupPath = dbPath.replace('.db', '_patched.db');
        try {
            fs.copyFileSync(dbPath, backupPath);
            console.log(`Created backup: ${backupPath}`);
        } catch (error) {
            reject(new Error(`Error creating backup: ${error.message}`));
            return;
        }
        const db = new sqlite3.Database(backupPath);
        db.serialize(() => {
            const userId = generateNanoid(32);
            const currentTime = Math.floor(Date.now() / 1000);
            hashPassword(password).then(hashedPassword => {
                db.get(
                    "SELECT id, email, username FROM users WHERE email = ? OR username = ?",
                    [email, username],
                    (err, existingUser) => {
                        if (err) {
                            db.close();
                            reject(new Error(`Database error: ${err.message}`));
                            return;
                        }
                        if (existingUser) {
                            console.log(`Updating existing user: ${existingUser.username} (${existingUser.email})`);
                            db.run(`
                                UPDATE users 
                                SET username = ?, email = ?, name = ?, updated_at = ?
                                WHERE id = ?
                            `, [username, email, `${username} (Admin)`, currentTime, existingUser.id]);
                            db.run(`
                                UPDATE accounts 
                                SET password = ?, updated_at = ?
                                WHERE user_id = ? AND provider_id = 'credential'
                            `, [hashedPassword, currentTime, existingUser.id]);
                            db.get(
                                "SELECT COUNT(*) as count FROM accounts WHERE user_id = ? AND provider_id = 'credential'",
                                [existingUser.id],
                                (err, row) => {
                                    if (err) {
                                        db.close();
                                        reject(new Error(`Database error: ${err.message}`));
                                        return;
                                    }
                                    if (row.count === 0) {
                                        const accountId = generateNanoid(32);
                                        db.run(`
                                            INSERT INTO accounts (id, account_id, user_id, provider_id, password, created_at, updated_at)
                                            VALUES (?, ?, ?, ?, ?, ?, ?)
                                        `, [accountId, existingUser.id, existingUser.id, 'credential', hashedPassword, currentTime, currentTime]);
                                    }
                                    createAdminConfig(db, existingUser.id, username, currentTime, resolve, reject);
                                }
                            );
                        } else {
                            console.log(`Creating new admin user: ${username} (${email})`);
                            db.run(`
                                INSERT INTO users (id, username, name, email, email_verified, created_at, updated_at)
                                VALUES (?, ?, ?, ?, ?, ?, ?)
                            `, [userId, username, `${username} (Admin)`, email, 1, currentTime, currentTime]);
                            const accountId = generateNanoid(32);
                            db.run(`
                                INSERT INTO accounts (id, account_id, user_id, provider_id, password, created_at, updated_at)
                                VALUES (?, ?, ?, ?, ?, ?, ?)
                            `, [accountId, userId, userId, 'credential', hashedPassword, currentTime, currentTime]);
                            createAdminConfig(db, userId, username, currentTime, password, resolve, reject);
                        }
                    }
                );
            }).catch(reject);
        });
    });
}
function createAdminConfig(db, userId, username, currentTime, password, resolve, reject) {
    const configId = generateNanoid(32);
    const githubConfig = '{"owner":"admin","type":"personal","token":"admin-token","includeStarred":true,"includeForks":true,"includeArchived":true,"includePrivate":true,"includePublic":true,"includeOrganizations":["*"],"mirrorStrategy":"preserve","createOrg":true,"addTopics":true}';
    const giteaConfig = '{"url":"http://localhost:3000","token":"admin-token","defaultOwner":"admin","organization":"admin","mirrorInterval":"1h","lfs":true,"wiki":true,"visibility":"public","createOrg":true,"addTopics":true,"preserveVisibility":true,"preserveOrgStructure":true,"forkStrategy":"full-copy","issueConcurrency":10,"pullRequestConcurrency":10,"mirrorReleases":true,"mirrorMetadata":true,"mirrorIssues":true,"mirrorPullRequests":true,"mirrorLabels":true,"mirrorMilestones":true}';
    const scheduleConfig = '{"enabled":true,"interval":"0 */6 * * *","concurrent":true,"batchSize":50,"retryAttempts":5,"autoRetry":true,"autoImport":true,"autoMirror":true}';
    const cleanupConfig = '{"enabled":true,"deleteFromGitea":true,"deleteIfNotInGitHub":true,"orphanedRepoAction":"delete","batchSize":20}';
    db.get(
        "SELECT id FROM configs WHERE user_id = ? AND name = ?",
        [userId, "Admin Configuration"],
        (err, existingConfig) => {
            if (err) {
                db.close();
                reject(new Error(`Database error: ${err.message}`));
                return;
            }
            if (existingConfig) {
                db.run(`
                    UPDATE configs 
                    SET github_config = ?, gitea_config = ?, schedule_config = ?, 
                        cleanup_config = ?, is_active = 1, updated_at = ?
                    WHERE id = ?
                `, [githubConfig, giteaConfig, scheduleConfig, cleanupConfig, currentTime, existingConfig.id]);
                console.log(`Updated admin configuration for user ${username}`);
            } else {
                db.run(`
                    INSERT INTO configs (id, user_id, name, is_active, github_config, gitea_config, 
                                       include, exclude, schedule_config, cleanup_config, created_at, updated_at)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                `, [configId, userId, "Admin Configuration", 1, githubConfig, giteaConfig, 
                    '["*"]', '[]', scheduleConfig, cleanupConfig, currentTime, currentTime]);
                console.log(`Created admin configuration for user ${username}`);
            }
            console.log(`Successfully created/updated admin user '${username}'`);
            console.log(`User ID: ${userId}`);
            console.log(`Email: ${username.toLowerCase()}@test.com`);
            console.log(`Password: ${password} (hashed with exact Better Auth 1.4.5 scrypt)`);
            console.log(`Admin Configuration: Created/Updated`);
            db.close((err) => {
                if (err) {
                    reject(new Error(`Error closing database: ${err.message}`));
                } else {
                    resolve(true);
                }
            });
        }
    );
}
async function main() {
    const args = process.argv.slice(2);
    const parsedArgs = {
        hashMode: false,
        password: null,
        secret: null,
        username: null,
        email: null,
        database: null
    };
    for (let i = 0; i < args.length; i++) {
        const arg = args[i];
        if (arg === '--hash' || arg === '-h') {
            parsedArgs.hashMode = true;
            parsedArgs.password = args[i + 1];
            i++; // Skip next argument as it's the password
        } else if (arg.startsWith('--')) {
            console.error(`Unknown option: ${arg}`);
            console.error('Usage: node script.js [--hash <password>] [secret] [username] [email] [password] [database]');
            process.exit(1);
        } else if (!parsedArgs.secret) {
            parsedArgs.secret = arg;
        } else if (!parsedArgs.username) {
            parsedArgs.username = arg;
        } else if (!parsedArgs.email) {
            parsedArgs.email = arg;
        } else if (!parsedArgs.password && !parsedArgs.hashMode) {
            parsedArgs.password = arg;
        } else if (!parsedArgs.database) {
            parsedArgs.database = arg;
        }
    }
    if (!parsedArgs.secret) parsedArgs.secret = "60710b49-f133-43cf-ae5b-c89ba0ed84ca";
    if (!parsedArgs.username) parsedArgs.username = "test";
    if (!parsedArgs.email) parsedArgs.email = "test@test.test";
    if (!parsedArgs.password && !parsedArgs.hashMode) parsedArgs.password = "test";
    if (!parsedArgs.database) parsedArgs.database = "gitea-mirror.db";
    const hashTestPassed = await testHashing();
    if (!hashTestPassed) {
        console.error("\n❌ CRITICAL: Hash verification failed! Script cannot continue.");
        console.error("The Better Auth 1.4.5 implementation is not working correctly.");
        process.exit(1);
    }
    console.log();
    if (parsedArgs.hashMode) {
        if (!parsedArgs.password) {
            console.error('Usage: node script.js --hash/-h <password>');
            process.exit(1);
        }
        console.log("=== Better Auth 1.4.5 Password Hash Generator ===");
        console.log(`Password: ${parsedArgs.password}`);
        console.log();
        try {
            const hash = await hashPassword(parsedArgs.password);
            console.log(`Hash: ${hash}`);
            console.log();
            console.log("Usage:");
            console.log("1. Copy the hash above");
            console.log("2. Update the 'password' field in the 'accounts' table");
            console.log("3. Set format: salt:hash");
            console.log("4. Use the original password to login");
        } catch (error) {
            console.error(`Error generating hash: ${error.message}`);
            process.exit(1);
        }
        return;
    }
    console.log("=== Gitea Mirror Database Admin User Patcher (Node.js) ===");
    console.log("Using EXACT Better Auth 1.4.5 password hashing with verified parameters");
    console.log();
    console.log(`Secret: ${parsedArgs.secret}`);
    console.log(`Database: ${parsedArgs.database}`);
    console.log(`Target admin user: ${parsedArgs.username} (${parsedArgs.email})`);
    console.log();
    try {
        const success = await addOrUpdateUser(parsedArgs.database, parsedArgs.username, parsedArgs.email, parsedArgs.password, parsedArgs.secret);
        if (success) {
            console.log("\n=== Operation completed successfully ===");
            console.log(`Patched database saved as: ${parsedArgs.database.replace('.db', '_patched.db')}`);
            console.log(`Admin user '${parsedArgs.username}' now has full permissions`);
            console.log("\n=== Login Credentials ===");
            console.log(`  Email: ${parsedArgs.email}`);
            console.log(`  Password: ${parsedArgs.password}`);
        }
    } catch (error) {
        console.error(`\n=== Operation failed ===`);
        console.error(error.message);
        process.exit(1);
    }
}
if (require.main === module) {
    main().catch(console.error);
}
module.exports = {
    hashPassword,
    addOrUpdateUser,
    generateNanoid
};
<!-- gh-comment-id:3741962111 --> @Bluscream commented on GitHub (Jan 13, 2026): ```js #!/usr/bin/env node /** sudo apt install sqlite3 nodejs npm winget install SQLite NodeJs npm install sqlite3 node script.js --hash/-h <password> # Generate hash for password node script.js [secret] [user] [email] [pass] [db] # Create admin user */ const sqlite3 = require('sqlite3').verbose(); const crypto = require('crypto'); const path = require('path'); const fs = require('fs'); const BETTER_AUTH_CONFIG = { N: 16384, r: 16, p: 1, dkLen: 64 }; const hex = { encode: (buffer) => Buffer.from(buffer).toString('hex') }; async function testHashing() { const knownPassword = "123123123"; const knownSalt = "2c3261f3350781361e46bc04844d7299"; const knownHash = "8f402fda934a0f5cd29bca3cb6e870870da82dd96d57d49bdeb8938b88f3c4b02187aa973401fffa33ff2e16f94a550a229e8a32c1392d521335eb0bcf35830c"; const expectedFullHash = `${knownSalt}:${knownHash}`; console.log("🧪 Testing Better Auth 1.4.5 hashing implementation..."); try { const testResult = await generateKey(knownPassword, knownSalt); const computedHash = testResult.toString('hex'); const computedFullHash = `${knownSalt}:${computedHash}`; if (computedFullHash === expectedFullHash) { console.log("✅ Hash verification PASSED - Implementation is correct!"); return true; } else { console.log("❌ Hash verification FAILED - Implementation is incorrect!"); console.log(` Expected: ${expectedFullHash.substring(0, 32)}...`); console.log(` Got: ${computedFullHash.substring(0, 32)}...`); return false; } } catch (error) { console.log(`❌ Hash verification ERROR: ${error.message}`); return false; } } async function generateKey(password, salt) { return new Promise((resolve, reject) => { const maxmem = 128 * BETTER_AUTH_CONFIG.N * BETTER_AUTH_CONFIG.r * 2; crypto.scrypt(password.normalize("NFKC"), salt, BETTER_AUTH_CONFIG.dkLen, { N: BETTER_AUTH_CONFIG.N, r: BETTER_AUTH_CONFIG.r, p: BETTER_AUTH_CONFIG.p, maxmem: maxmem }, (err, derivedKey) => { if (err) reject(err); else resolve(derivedKey); }); }); } async function hashPassword(password) { // Hash password using exact Better Auth 1.4.5 implementation const salt = hex.encode(crypto.randomBytes(16)); try { const hashBytes = await generateKey(password, salt); return `${salt}:${hashBytes.toString('hex')}`; } catch (error) { console.log(`Warning: Could not use exact Better Auth parameters (${error.message}), falling back to compatible params`); return new Promise((resolve, reject) => { crypto.scrypt(password.normalize("NFKC"), salt, 64, { N: 8192, r: 8, p: 1 }, (err, derivedKey) => { if (err) reject(err); else resolve(`${salt}:${derivedKey.toString('hex')}`); }); }); } } function generateNanoid(length = 32) { const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += alphabet.charAt(Math.floor(Math.random() * alphabet.length)); } return result; } async function addOrUpdateUser(dbPath, username, email, password, secret) { return new Promise((resolve, reject) => { if (!fs.existsSync(dbPath)) { reject(new Error(`Database file ${dbPath} not found`)); return; } const backupPath = dbPath.replace('.db', '_patched.db'); try { fs.copyFileSync(dbPath, backupPath); console.log(`Created backup: ${backupPath}`); } catch (error) { reject(new Error(`Error creating backup: ${error.message}`)); return; } const db = new sqlite3.Database(backupPath); db.serialize(() => { const userId = generateNanoid(32); const currentTime = Math.floor(Date.now() / 1000); hashPassword(password).then(hashedPassword => { db.get( "SELECT id, email, username FROM users WHERE email = ? OR username = ?", [email, username], (err, existingUser) => { if (err) { db.close(); reject(new Error(`Database error: ${err.message}`)); return; } if (existingUser) { console.log(`Updating existing user: ${existingUser.username} (${existingUser.email})`); db.run(` UPDATE users SET username = ?, email = ?, name = ?, updated_at = ? WHERE id = ? `, [username, email, `${username} (Admin)`, currentTime, existingUser.id]); db.run(` UPDATE accounts SET password = ?, updated_at = ? WHERE user_id = ? AND provider_id = 'credential' `, [hashedPassword, currentTime, existingUser.id]); db.get( "SELECT COUNT(*) as count FROM accounts WHERE user_id = ? AND provider_id = 'credential'", [existingUser.id], (err, row) => { if (err) { db.close(); reject(new Error(`Database error: ${err.message}`)); return; } if (row.count === 0) { const accountId = generateNanoid(32); db.run(` INSERT INTO accounts (id, account_id, user_id, provider_id, password, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, [accountId, existingUser.id, existingUser.id, 'credential', hashedPassword, currentTime, currentTime]); } createAdminConfig(db, existingUser.id, username, currentTime, resolve, reject); } ); } else { console.log(`Creating new admin user: ${username} (${email})`); db.run(` INSERT INTO users (id, username, name, email, email_verified, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, [userId, username, `${username} (Admin)`, email, 1, currentTime, currentTime]); const accountId = generateNanoid(32); db.run(` INSERT INTO accounts (id, account_id, user_id, provider_id, password, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, [accountId, userId, userId, 'credential', hashedPassword, currentTime, currentTime]); createAdminConfig(db, userId, username, currentTime, password, resolve, reject); } } ); }).catch(reject); }); }); } function createAdminConfig(db, userId, username, currentTime, password, resolve, reject) { const configId = generateNanoid(32); const githubConfig = '{"owner":"admin","type":"personal","token":"admin-token","includeStarred":true,"includeForks":true,"includeArchived":true,"includePrivate":true,"includePublic":true,"includeOrganizations":["*"],"mirrorStrategy":"preserve","createOrg":true,"addTopics":true}'; const giteaConfig = '{"url":"http://localhost:3000","token":"admin-token","defaultOwner":"admin","organization":"admin","mirrorInterval":"1h","lfs":true,"wiki":true,"visibility":"public","createOrg":true,"addTopics":true,"preserveVisibility":true,"preserveOrgStructure":true,"forkStrategy":"full-copy","issueConcurrency":10,"pullRequestConcurrency":10,"mirrorReleases":true,"mirrorMetadata":true,"mirrorIssues":true,"mirrorPullRequests":true,"mirrorLabels":true,"mirrorMilestones":true}'; const scheduleConfig = '{"enabled":true,"interval":"0 */6 * * *","concurrent":true,"batchSize":50,"retryAttempts":5,"autoRetry":true,"autoImport":true,"autoMirror":true}'; const cleanupConfig = '{"enabled":true,"deleteFromGitea":true,"deleteIfNotInGitHub":true,"orphanedRepoAction":"delete","batchSize":20}'; db.get( "SELECT id FROM configs WHERE user_id = ? AND name = ?", [userId, "Admin Configuration"], (err, existingConfig) => { if (err) { db.close(); reject(new Error(`Database error: ${err.message}`)); return; } if (existingConfig) { db.run(` UPDATE configs SET github_config = ?, gitea_config = ?, schedule_config = ?, cleanup_config = ?, is_active = 1, updated_at = ? WHERE id = ? `, [githubConfig, giteaConfig, scheduleConfig, cleanupConfig, currentTime, existingConfig.id]); console.log(`Updated admin configuration for user ${username}`); } else { db.run(` INSERT INTO configs (id, user_id, name, is_active, github_config, gitea_config, include, exclude, schedule_config, cleanup_config, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [configId, userId, "Admin Configuration", 1, githubConfig, giteaConfig, '["*"]', '[]', scheduleConfig, cleanupConfig, currentTime, currentTime]); console.log(`Created admin configuration for user ${username}`); } console.log(`Successfully created/updated admin user '${username}'`); console.log(`User ID: ${userId}`); console.log(`Email: ${username.toLowerCase()}@test.com`); console.log(`Password: ${password} (hashed with exact Better Auth 1.4.5 scrypt)`); console.log(`Admin Configuration: Created/Updated`); db.close((err) => { if (err) { reject(new Error(`Error closing database: ${err.message}`)); } else { resolve(true); } }); } ); } async function main() { const args = process.argv.slice(2); const parsedArgs = { hashMode: false, password: null, secret: null, username: null, email: null, database: null }; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '--hash' || arg === '-h') { parsedArgs.hashMode = true; parsedArgs.password = args[i + 1]; i++; // Skip next argument as it's the password } else if (arg.startsWith('--')) { console.error(`Unknown option: ${arg}`); console.error('Usage: node script.js [--hash <password>] [secret] [username] [email] [password] [database]'); process.exit(1); } else if (!parsedArgs.secret) { parsedArgs.secret = arg; } else if (!parsedArgs.username) { parsedArgs.username = arg; } else if (!parsedArgs.email) { parsedArgs.email = arg; } else if (!parsedArgs.password && !parsedArgs.hashMode) { parsedArgs.password = arg; } else if (!parsedArgs.database) { parsedArgs.database = arg; } } if (!parsedArgs.secret) parsedArgs.secret = "60710b49-f133-43cf-ae5b-c89ba0ed84ca"; if (!parsedArgs.username) parsedArgs.username = "test"; if (!parsedArgs.email) parsedArgs.email = "test@test.test"; if (!parsedArgs.password && !parsedArgs.hashMode) parsedArgs.password = "test"; if (!parsedArgs.database) parsedArgs.database = "gitea-mirror.db"; const hashTestPassed = await testHashing(); if (!hashTestPassed) { console.error("\n❌ CRITICAL: Hash verification failed! Script cannot continue."); console.error("The Better Auth 1.4.5 implementation is not working correctly."); process.exit(1); } console.log(); if (parsedArgs.hashMode) { if (!parsedArgs.password) { console.error('Usage: node script.js --hash/-h <password>'); process.exit(1); } console.log("=== Better Auth 1.4.5 Password Hash Generator ==="); console.log(`Password: ${parsedArgs.password}`); console.log(); try { const hash = await hashPassword(parsedArgs.password); console.log(`Hash: ${hash}`); console.log(); console.log("Usage:"); console.log("1. Copy the hash above"); console.log("2. Update the 'password' field in the 'accounts' table"); console.log("3. Set format: salt:hash"); console.log("4. Use the original password to login"); } catch (error) { console.error(`Error generating hash: ${error.message}`); process.exit(1); } return; } console.log("=== Gitea Mirror Database Admin User Patcher (Node.js) ==="); console.log("Using EXACT Better Auth 1.4.5 password hashing with verified parameters"); console.log(); console.log(`Secret: ${parsedArgs.secret}`); console.log(`Database: ${parsedArgs.database}`); console.log(`Target admin user: ${parsedArgs.username} (${parsedArgs.email})`); console.log(); try { const success = await addOrUpdateUser(parsedArgs.database, parsedArgs.username, parsedArgs.email, parsedArgs.password, parsedArgs.secret); if (success) { console.log("\n=== Operation completed successfully ==="); console.log(`Patched database saved as: ${parsedArgs.database.replace('.db', '_patched.db')}`); console.log(`Admin user '${parsedArgs.username}' now has full permissions`); console.log("\n=== Login Credentials ==="); console.log(` Email: ${parsedArgs.email}`); console.log(` Password: ${parsedArgs.password}`); } } catch (error) { console.error(`\n=== Operation failed ===`); console.error(error.message); process.exit(1); } } if (require.main === module) { main().catch(console.error); } module.exports = { hashPassword, addOrUpdateUser, generateNanoid }; ```
Author
Owner

@arunavo4 commented on GitHub (Jan 13, 2026):

Hi @Bluscream I realised I never added a proper reset password rather I just used to delete the user and recreate it again. casue its a backup anyway. But I will add a reset script. The one you dropped here has a few concerns.

Also, for that wrong origin you will need to add the ip where the container is running. Once I have some time in a week or so I will address this issue properly tho I have documented it the issue stems from the way better auth is configured.

<!-- gh-comment-id:3742288936 --> @arunavo4 commented on GitHub (Jan 13, 2026): Hi @Bluscream I realised I never added a proper reset password rather I just used to delete the user and recreate it again. casue its a backup anyway. But I will add a reset script. The one you dropped here has a few concerns. Also, for that wrong origin you will need to add the ip where the container is running. Once I have some time in a week or so I will address this issue properly tho I have documented it the issue stems from the way better auth is configured.
Author
Owner

@arunavo4 commented on GitHub (Feb 24, 2026):

Follow-up implemented in PR #178: added a built-in CLI admin password reset flow.

Command:

  • bun run reset-password -- --email=user@example.com --new-password='new-secure-password'

Behavior:

  • updates password hash in Better Auth credential account
  • creates credential account if missing
  • clears active sessions for that user (forces re-login)

Also documented in README under Admin Password Recovery (CLI).

<!-- gh-comment-id:3948876672 --> @arunavo4 commented on GitHub (Feb 24, 2026): Follow-up implemented in PR #178: added a built-in CLI admin password reset flow. Command: - `bun run reset-password -- --email=user@example.com --new-password='new-secure-password'` Behavior: - updates password hash in Better Auth credential account - creates credential account if missing - clears active sessions for that user (forces re-login) Also documented in README under **Admin Password Recovery (CLI)**.
Author
Owner

@arunavo4 commented on GitHub (Feb 24, 2026):

Resolved by #178 (built-in CLI password reset command and documentation).

<!-- gh-comment-id:3948887833 --> @arunavo4 commented on GitHub (Feb 24, 2026): Resolved by #178 (built-in CLI password reset command and documentation).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/gitea-mirror#79
No description provided.