diff --git a/package.json b/package.json index 7c76ab77..d85a09df 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,5 @@ "docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080", "scripts:version": "bash scripts/version.sh" }, - "packageManager": "pnpm@10.6.2" + "packageManager": "pnpm@10.6.3" } diff --git a/src/package.json b/src/package.json index c8ab0cc8..2adb40e6 100644 --- a/src/package.json +++ b/src/package.json @@ -61,5 +61,5 @@ "typescript": "^5.8.2", "vue-tsc": "^2.2.8" }, - "packageManager": "pnpm@10.6.2" + "packageManager": "pnpm@10.6.3" } diff --git a/src/server/api/admin/ip-info.get.ts b/src/server/api/admin/ip-info.get.ts new file mode 100644 index 00000000..1ea6d745 --- /dev/null +++ b/src/server/api/admin/ip-info.get.ts @@ -0,0 +1,4 @@ +export default definePermissionEventHandler('admin', 'any', async () => { + const result = await cachedGetIpInformation(); + return result; +}); diff --git a/src/server/utils/cache.ts b/src/server/utils/cache.ts new file mode 100644 index 00000000..96ea8c2d --- /dev/null +++ b/src/server/utils/cache.ts @@ -0,0 +1,29 @@ +type Opts = { + /** + * Expiry time in milliseconds + */ + expiry: number; +}; + +/** + * Cache function for 1 hour + */ +export function cacheFunction(fn: () => T, { expiry }: Opts): () => T { + let cache: { value: T; expiry: number } | null = null; + + return (): T => { + const now = Date.now(); + + if (cache && cache.expiry > now) { + return cache.value; + } + + const result = fn(); + cache = { + value: result, + expiry: now + expiry, + }; + + return result; + }; +} diff --git a/src/server/utils/ip.ts b/src/server/utils/ip.ts index 86219e0c..977148ec 100644 --- a/src/server/utils/ip.ts +++ b/src/server/utils/ip.ts @@ -1,4 +1,5 @@ import { Resolver } from 'node:dns/promises'; +import { networkInterfaces } from 'node:os'; import { stringifyIp } from 'ip-bigint'; import type { parseCidr } from 'cidr-tools'; @@ -40,7 +41,7 @@ const dnsServers = { ip: 'myip.opendns.com', }; -export async function getPublicInformation() { +async function getPublicInformation() { const ipv4 = await getPublicIpv4(); const ipv6 = await getPublicIpv6(); @@ -83,3 +84,89 @@ async function getReverseDns(ip: string) { return []; } } + +function getPrivateInformation() { + const interfaces = networkInterfaces(); + + const interfaceNames = Object.keys(interfaces); + + const obj: Record = {}; + + for (const name of interfaceNames) { + if (name === 'wg0') { + continue; + } + + const iface = interfaces[name]; + if (!iface) continue; + + for (const { family, internal, address } of iface) { + if (internal) { + continue; + } + if (!(name in obj)) { + obj[name] = { + ipv4: [], + ipv6: [], + }; + } + if (family === 'IPv4') { + obj[name].ipv4.push(address); + } else if (family === 'IPv6') { + obj[name].ipv6.push(address); + } + } + } + + return obj; +} + +async function getIpInformation() { + const results = []; + + const publicInfo = await getPublicInformation(); + if (publicInfo.ipv4) { + results.push({ + value: publicInfo.ipv4, + label: 'IPv4 - Public', + }); + } + if (publicInfo.ipv6) { + results.push({ + value: `[${publicInfo.ipv6}]`, + label: 'IPv6 - Public', + }); + } + for (const hostname of publicInfo.hostnames) { + results.push({ + value: hostname, + label: 'Hostname - Public', + }); + } + + const privateInfo = getPrivateInformation(); + for (const [name, { ipv4, ipv6 }] of Object.entries(privateInfo)) { + for (const ip of ipv4) { + results.push({ + value: ip, + label: `IPv4 - ${name}`, + }); + } + for (const ip of ipv6) { + results.push({ + value: `[${ip}]`, + label: `IPv6 - ${name}`, + }); + } + } + + return results; +} + +/** + * Fetch IP Information + * @cache Response is cached for 15 min + */ +export const cachedGetIpInformation = cacheFunction(getIpInformation, { + expiry: 15 * 60 * 1000, +}); diff --git a/src/server/utils/release.ts b/src/server/utils/release.ts index 29fe762c..f5dfdc46 100644 --- a/src/server/utils/release.ts +++ b/src/server/utils/release.ts @@ -3,29 +3,6 @@ type GithubRelease = { body: string; }; -/** - * Cache function for 1 hour - */ -function cacheFunction(fn: () => T): () => T { - let cache: { value: T; expiry: number } | null = null; - - return (): T => { - const now = Date.now(); - - if (cache && cache.expiry > now) { - return cache.value; - } - - const result = fn(); - cache = { - value: result, - expiry: now + 3600000, - }; - - return result; - }; -} - async function fetchLatestRelease() { try { const response = await $fetch( @@ -53,4 +30,6 @@ async function fetchLatestRelease() { * Fetch latest release from GitHub * @cache Response is cached for 1 hour */ -export const cachedFetchLatestRelease = cacheFunction(fetchLatestRelease); +export const cachedFetchLatestRelease = cacheFunction(fetchLatestRelease, { + expiry: 60 * 60 * 1000, +}); diff --git a/src/server/utils/session.ts b/src/server/utils/session.ts index 9602ded5..1a144cea 100644 --- a/src/server/utils/session.ts +++ b/src/server/utils/session.ts @@ -91,6 +91,11 @@ export async function getCurrentUser(event: H3Event) { }); } user = foundUser; + } else { + throw createError({ + statusCode: 401, + statusMessage: 'Session failed. No Authorization', + }); } if (!user) {