mirror of https://github.com/wg-easy/wg-easy
committed by
GitHub
10 changed files with 293 additions and 95 deletions
@ -0,0 +1,71 @@ |
|||
import { defineCommand } from 'citty'; |
|||
import { consola } from 'consola'; |
|||
import { eq } from 'drizzle-orm'; |
|||
|
|||
import { db, schema } from '../db'; |
|||
import { hashPassword } from '../../server/utils/password'; |
|||
|
|||
export default defineCommand({ |
|||
meta: { |
|||
name: 'db:admin:reset', |
|||
description: 'Reset the admin user password and TOTP settings', |
|||
}, |
|||
args: { |
|||
password: { |
|||
type: 'string', |
|||
description: 'New password for the admin user', |
|||
required: false, |
|||
}, |
|||
}, |
|||
async run(ctx) { |
|||
let password = ctx.args.password || undefined; |
|||
if (!password) { |
|||
password = await consola.prompt('Please enter a new password:', { |
|||
type: 'text', |
|||
}); |
|||
} |
|||
if (!password) { |
|||
consola.error('Password is required'); |
|||
return; |
|||
} |
|||
if (password.length < 12) { |
|||
consola.error('Password must be at least 12 characters long'); |
|||
return; |
|||
} |
|||
consola.info('Setting new password for admin user...'); |
|||
const hash = await hashPassword(password); |
|||
|
|||
const user = await db.transaction(async (tx) => { |
|||
const user = await tx |
|||
.select() |
|||
.from(schema.user) |
|||
.where(eq(schema.user.id, 1)) |
|||
.get(); |
|||
|
|||
if (!user) { |
|||
consola.error('Admin user not found'); |
|||
return; |
|||
} |
|||
|
|||
await tx |
|||
.update(schema.user) |
|||
.set({ |
|||
password: hash, |
|||
totpVerified: false, |
|||
totpKey: null, |
|||
}) |
|||
.where(eq(schema.user.id, 1)); |
|||
|
|||
return user; |
|||
}); |
|||
|
|||
if (!user) { |
|||
consola.error('Failed to update admin user'); |
|||
return; |
|||
} |
|||
|
|||
consola.success( |
|||
`Successfully updated admin user ${user.id} (${user.username})` |
|||
); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,29 @@ |
|||
import { defineCommand } from 'citty'; |
|||
import { consola } from 'consola'; |
|||
|
|||
import { db } from '../db'; |
|||
|
|||
export default defineCommand({ |
|||
meta: { |
|||
name: 'clients:list', |
|||
description: 'List all clients', |
|||
}, |
|||
async run() { |
|||
consola.info('Listing all clients...'); |
|||
const clients = await db.query.client.findMany({ |
|||
columns: { |
|||
id: true, |
|||
name: true, |
|||
publicKey: true, |
|||
enabled: true, |
|||
}, |
|||
}); |
|||
|
|||
if (clients.length === 0) { |
|||
consola.info('No clients found'); |
|||
return; |
|||
} |
|||
|
|||
console.table(clients); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,71 @@ |
|||
import { defineCommand } from 'citty'; |
|||
import { consola } from 'consola'; |
|||
import { eq } from 'drizzle-orm'; |
|||
|
|||
import { wg } from '../../server/utils/wgHelper'; |
|||
import { encodeQRCodeTerm } from '../../server/utils/qr'; |
|||
import { db, schema } from '../db'; |
|||
|
|||
export default defineCommand({ |
|||
meta: { |
|||
name: 'clients:qr', |
|||
description: 'Generate QR code for a client', |
|||
}, |
|||
args: { |
|||
id: { |
|||
required: true, |
|||
type: 'positional', |
|||
}, |
|||
ipv6: { |
|||
required: false, |
|||
type: 'boolean', |
|||
default: true, |
|||
}, |
|||
}, |
|||
async run(ctx) { |
|||
const clientId = Number(ctx.args.id); |
|||
const enableIpv6 = ctx.args.ipv6; |
|||
|
|||
if (Number.isNaN(clientId)) { |
|||
consola.error('Invalid client ID'); |
|||
return; |
|||
} |
|||
|
|||
consola.info('Generating QR code for client...'); |
|||
|
|||
const wgInterface = await db.query.wgInterface.findFirst({ |
|||
where: eq(schema.wgInterface.name, 'wg0'), |
|||
}); |
|||
if (!wgInterface) { |
|||
consola.error('WireGuard interface not found'); |
|||
return; |
|||
} |
|||
|
|||
const userConfig = await db.query.userConfig.findFirst({ |
|||
where: eq(schema.userConfig.id, 'wg0'), |
|||
}); |
|||
if (!userConfig) { |
|||
consola.error('User config not found'); |
|||
return; |
|||
} |
|||
|
|||
const client = await db.query.client.findFirst({ |
|||
where: eq(schema.client.id, clientId), |
|||
}); |
|||
if (!client) { |
|||
consola.error(`Client with ID ${clientId} not found`); |
|||
return; |
|||
} |
|||
|
|||
const clientConfig = wg.generateClientConfig( |
|||
wgInterface, |
|||
userConfig, |
|||
client, |
|||
{ |
|||
enableIpv6, |
|||
} |
|||
); |
|||
|
|||
consola.log(encodeQRCodeTerm(clientConfig)); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,10 @@ |
|||
import { createClient } from '@libsql/client'; |
|||
import { drizzle } from 'drizzle-orm/libsql'; |
|||
|
|||
import * as schema from '../server/database/schema'; |
|||
|
|||
//const client = createClient({ url: 'file:../data/wg-easy.db' });
|
|||
const client = createClient({ url: 'file:/etc/wireguard/wg-easy.db' }); |
|||
export const db = drizzle({ client, schema }); |
|||
|
|||
export { schema }; |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"lib": ["ESNext"], |
|||
"module": "esnext", |
|||
"target": "es2024", |
|||
"esModuleInterop": true, |
|||
"strict": true, |
|||
"skipLibCheck": true, |
|||
"moduleResolution": "bundler" |
|||
}, |
|||
"include": ["./**/*.ts"] |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// ! Auto Imports are not supported in this file
|
|||
|
|||
import type { ErrorCorrection } from 'qr'; |
|||
import { encodeQR } from 'qr'; |
|||
|
|||
export function encodeQRCode(config: string): string { |
|||
return tryECCModes((ecc) => { |
|||
return encodeQR(config, 'svg', { |
|||
ecc, |
|||
scale: 2, |
|||
encoding: 'byte', |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
export function encodeQRCodeTerm(config: string): string { |
|||
return tryECCModes((ecc) => { |
|||
return encodeQR(config, 'term', { |
|||
ecc, |
|||
encoding: 'byte', |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function tryECCModes<T>(callback: (ecc: ErrorCorrection) => T): T { |
|||
// defined manually, as qr's ECMode is in wrong order
|
|||
const ECMode = ['high', 'quartile', 'medium', 'low'] as const; |
|||
for (const ecc of ECMode) { |
|||
try { |
|||
return callback(ecc); |
|||
} catch (err) { |
|||
if (!(err instanceof Error && err.message === 'Capacity overflow')) { |
|||
throw err; |
|||
} |
|||
// retry with lower ecc
|
|||
} |
|||
} |
|||
throw new Error( |
|||
'Failed to generate QR code: Capacity overflow at all ECC levels' |
|||
); |
|||
} |
|||
Loading…
Reference in new issue