Feat: Suggest IP or Hostname (#1739)
* get ip and hostnames * use heroicons * add host field * get private info * unstyled prototype * styled select * add to setup * fix types
This commit is contained in:
4
src/server/api/admin/ip-info.get.ts
Normal file
4
src/server/api/admin/ip-info.get.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePermissionEventHandler('admin', 'any', async () => {
|
||||
const result = await cachedGetIpInformation();
|
||||
return result;
|
||||
});
|
||||
4
src/server/api/setup/4.get.ts
Normal file
4
src/server/api/setup/4.get.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default defineSetupEventHandler(4, async () => {
|
||||
const result = await cachedGetIpInformation();
|
||||
return result;
|
||||
});
|
||||
29
src/server/utils/cache.ts
Normal file
29
src/server/utils/cache.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
type Opts = {
|
||||
/**
|
||||
* Expiry time in milliseconds
|
||||
*/
|
||||
expiry: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache function for 1 hour
|
||||
*/
|
||||
export function cacheFunction<T>(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;
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { parseCidr } from 'cidr-tools';
|
||||
import { Resolver } from 'node:dns/promises';
|
||||
import { networkInterfaces } from 'node:os';
|
||||
import { stringifyIp } from 'ip-bigint';
|
||||
import type { parseCidr } from 'cidr-tools';
|
||||
|
||||
import type { ClientNextIpType } from '#db/repositories/client/types';
|
||||
|
||||
@@ -31,3 +33,140 @@ export function nextIP(
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
// use opendns to get public ip
|
||||
const dnsServers = {
|
||||
ip4: ['208.67.222.222'],
|
||||
ip6: ['2620:119:35::35'],
|
||||
ip: 'myip.opendns.com',
|
||||
};
|
||||
|
||||
async function getPublicInformation() {
|
||||
const ipv4 = await getPublicIpv4();
|
||||
const ipv6 = await getPublicIpv6();
|
||||
|
||||
const ptr4 = ipv4 ? await getReverseDns(ipv4) : [];
|
||||
const ptr6 = ipv6 ? await getReverseDns(ipv6) : [];
|
||||
const hostnames = [...new Set([...ptr4, ...ptr6])];
|
||||
|
||||
return { ipv4, ipv6, hostnames };
|
||||
}
|
||||
|
||||
async function getPublicIpv4() {
|
||||
try {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers(dnsServers.ip4);
|
||||
const ipv4 = await resolver.resolve4(dnsServers.ip);
|
||||
return ipv4[0];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getPublicIpv6() {
|
||||
try {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers(dnsServers.ip6);
|
||||
const ipv6 = await resolver.resolve6(dnsServers.ip);
|
||||
return ipv6[0];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getReverseDns(ip: string) {
|
||||
try {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers([...dnsServers.ip4, ...dnsServers.ip6]);
|
||||
const ptr = await resolver.reverse(ip);
|
||||
return ptr;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getPrivateInformation() {
|
||||
const interfaces = networkInterfaces();
|
||||
|
||||
const interfaceNames = Object.keys(interfaces);
|
||||
|
||||
const obj: Record<string, { ipv4: string[]; ipv6: string[] }> = {};
|
||||
|
||||
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 (!obj[name]) {
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -3,29 +3,6 @@ type GithubRelease = {
|
||||
body: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache function for 1 hour
|
||||
*/
|
||||
function cacheFunction<T>(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<GithubRelease>(
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -91,6 +91,11 @@ export async function getCurrentUser(event: H3Event) {
|
||||
});
|
||||
}
|
||||
user = foundUser;
|
||||
} else {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Session failed. No Authorization',
|
||||
});
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
|
||||
Reference in New Issue
Block a user