mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-03-13 22:12:54 +03:00
Add admin CLI password reset flow
This commit is contained in:
14
README.md
14
README.md
@@ -335,6 +335,20 @@ bun run build
|
|||||||
- Never stored in plaintext
|
- Never stored in plaintext
|
||||||
- Secure cookie-based session management
|
- Secure cookie-based session management
|
||||||
|
|
||||||
|
### Admin Password Recovery (CLI)
|
||||||
|
If email delivery is not configured, an admin with server access can reset a user password from the command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run reset-password -- --email=user@example.com --new-password='new-secure-password'
|
||||||
|
```
|
||||||
|
|
||||||
|
What this does:
|
||||||
|
- Updates the credential password hash for the matching user
|
||||||
|
- Creates a credential account if one does not already exist
|
||||||
|
- Invalidates all active sessions for that user (forces re-login)
|
||||||
|
|
||||||
|
Use this only from trusted server/admin environments.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
Gitea Mirror supports multiple authentication methods. **Email/password authentication is the default and always enabled.**
|
Gitea Mirror supports multiple authentication methods. **Email/password authentication is the default and always enabled.**
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"check-db": "bun scripts/manage-db.ts check",
|
"check-db": "bun scripts/manage-db.ts check",
|
||||||
"fix-db": "bun scripts/manage-db.ts fix",
|
"fix-db": "bun scripts/manage-db.ts fix",
|
||||||
"reset-users": "bun scripts/manage-db.ts reset-users",
|
"reset-users": "bun scripts/manage-db.ts reset-users",
|
||||||
|
"reset-password": "bun scripts/manage-db.ts reset-password",
|
||||||
"db:generate": "bun drizzle-kit generate",
|
"db:generate": "bun drizzle-kit generate",
|
||||||
"db:migrate": "bun drizzle-kit migrate",
|
"db:migrate": "bun drizzle-kit migrate",
|
||||||
"db:push": "bun drizzle-kit push",
|
"db:push": "bun drizzle-kit push",
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { Database } from "bun:sqlite";
|
|||||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||||
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { users, configs, repositories, organizations, mirrorJobs, events } from "../src/lib/db/schema";
|
import { users, configs, repositories, organizations, mirrorJobs, events, accounts, sessions } from "../src/lib/db/schema";
|
||||||
import bcrypt from "bcryptjs";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { eq } from "drizzle-orm";
|
import { hashPassword } from "better-auth/crypto";
|
||||||
|
|
||||||
// Command line arguments
|
// Command line arguments
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
@@ -194,6 +194,92 @@ async function fixDatabase() {
|
|||||||
console.log("✅ Database location fixed");
|
console.log("✅ Database location fixed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset a single user's password (admin recovery flow)
|
||||||
|
*/
|
||||||
|
async function resetPassword() {
|
||||||
|
const emailArg = args.find((arg) => arg.startsWith("--email="));
|
||||||
|
const passwordArg = args.find((arg) => arg.startsWith("--new-password="));
|
||||||
|
const email = emailArg?.split("=")[1]?.trim().toLowerCase();
|
||||||
|
const newPassword = passwordArg?.split("=")[1];
|
||||||
|
|
||||||
|
if (!email || !newPassword) {
|
||||||
|
console.log("❌ Missing required arguments");
|
||||||
|
console.log("Usage:");
|
||||||
|
console.log(" bun run manage-db reset-password --email=user@example.com --new-password='new-secure-password'");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword.length < 8) {
|
||||||
|
console.log("❌ Password must be at least 8 characters");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(dbPath)) {
|
||||||
|
console.log("❌ Database does not exist");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlite = new Database(dbPath);
|
||||||
|
const db = drizzle({ client: sqlite });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await db.query.users.findFirst({
|
||||||
|
where: eq(users.email, email),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
console.log(`❌ No user found for email: ${email}`);
|
||||||
|
sqlite.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await hashPassword(newPassword);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const credentialAccount = await db.query.accounts.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(accounts.userId, user.id),
|
||||||
|
eq(accounts.providerId, "credential"),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (credentialAccount) {
|
||||||
|
await db
|
||||||
|
.update(accounts)
|
||||||
|
.set({
|
||||||
|
password: hashedPassword,
|
||||||
|
updatedAt: now,
|
||||||
|
})
|
||||||
|
.where(eq(accounts.id, credentialAccount.id));
|
||||||
|
} else {
|
||||||
|
await db.insert(accounts).values({
|
||||||
|
id: uuidv4(),
|
||||||
|
accountId: user.id,
|
||||||
|
userId: user.id,
|
||||||
|
providerId: "credential",
|
||||||
|
password: hashedPassword,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedSessions = await db
|
||||||
|
.delete(sessions)
|
||||||
|
.where(eq(sessions.userId, user.id))
|
||||||
|
.returning({ id: sessions.id });
|
||||||
|
|
||||||
|
console.log(`✅ Password reset for ${email}`);
|
||||||
|
console.log(`🔒 Cleared ${deletedSessions.length} active session(s)`);
|
||||||
|
|
||||||
|
sqlite.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error resetting password:", error);
|
||||||
|
sqlite.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto mode - check and initialize if needed
|
* Auto mode - check and initialize if needed
|
||||||
*/
|
*/
|
||||||
@@ -224,6 +310,9 @@ switch (command) {
|
|||||||
case "cleanup":
|
case "cleanup":
|
||||||
await cleanupDatabase();
|
await cleanupDatabase();
|
||||||
break;
|
break;
|
||||||
|
case "reset-password":
|
||||||
|
await resetPassword();
|
||||||
|
break;
|
||||||
case "auto":
|
case "auto":
|
||||||
await autoMode();
|
await autoMode();
|
||||||
break;
|
break;
|
||||||
@@ -233,6 +322,7 @@ switch (command) {
|
|||||||
console.log(" check - Check database status");
|
console.log(" check - Check database status");
|
||||||
console.log(" fix - Fix database location issues");
|
console.log(" fix - Fix database location issues");
|
||||||
console.log(" reset-users - Remove all users and related data");
|
console.log(" reset-users - Remove all users and related data");
|
||||||
|
console.log(" reset-password - Reset one user's password and clear sessions");
|
||||||
console.log(" cleanup - Remove all database files");
|
console.log(" cleanup - Remove all database files");
|
||||||
console.log(" auto - Auto initialize if needed");
|
console.log(" auto - Auto initialize if needed");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user