init
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getOriginFromRequest } from "@/lib/request-origin";
|
||||
import { requireAdminAccess } from "@/lib/session";
|
||||
import { buildNodeHy2Config } from "@/lib/subscription";
|
||||
import { getNodeBySlug } from "@/lib/store";
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
context: RouteContext<"/api/admin/nodes/[slug]/config">,
|
||||
) {
|
||||
await requireAdminAccess();
|
||||
|
||||
const { slug } = await context.params;
|
||||
const node = await getNodeBySlug(slug);
|
||||
|
||||
if (!node) {
|
||||
return new NextResponse("node not found", { status: 404 });
|
||||
}
|
||||
|
||||
return new NextResponse(buildNodeHy2Config(node, getOriginFromRequest(request)), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/x-yaml; charset=utf-8",
|
||||
"Content-Disposition": `attachment; filename="${slug}.config.yaml"`,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { getUserByUsername, writeAuthAudit } from "@/lib/store";
|
||||
import { parseHy2Userpass, verifyNodeToken, evaluateUserForAuth } from "@/lib/hy2";
|
||||
import { verifyPassword } from "@/lib/password";
|
||||
|
||||
type AuthRequest = {
|
||||
addr?: string;
|
||||
auth?: string;
|
||||
tx?: number;
|
||||
};
|
||||
|
||||
function deny(id = "") {
|
||||
return Response.json({ ok: false, id });
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
context: RouteContext<"/api/hy2/nodes/[slug]/auth">,
|
||||
) {
|
||||
const { slug } = await context.params;
|
||||
const requestUrl = new URL(request.url);
|
||||
const token =
|
||||
requestUrl.searchParams.get("token") ||
|
||||
request.headers.get("authorization")?.replace(/^Bearer\s+/i, "") ||
|
||||
request.headers.get("x-node-token");
|
||||
|
||||
const nodeResult = await verifyNodeToken(slug, token);
|
||||
if (!nodeResult.ok) {
|
||||
await writeAuthAudit({
|
||||
nodeId: nodeResult.node?.id ?? null,
|
||||
ok: false,
|
||||
reason: nodeResult.reason,
|
||||
});
|
||||
return deny();
|
||||
}
|
||||
|
||||
const node = nodeResult.node;
|
||||
if (!node) {
|
||||
return deny();
|
||||
}
|
||||
|
||||
let payload: AuthRequest;
|
||||
|
||||
try {
|
||||
payload = (await request.json()) as AuthRequest;
|
||||
} catch {
|
||||
await writeAuthAudit({
|
||||
nodeId: node.id,
|
||||
addr: null,
|
||||
requestedTx: null,
|
||||
ok: false,
|
||||
reason: "bad_json",
|
||||
});
|
||||
return deny();
|
||||
}
|
||||
|
||||
if (!payload.auth) {
|
||||
await writeAuthAudit({
|
||||
nodeId: node.id,
|
||||
addr: payload.addr ?? null,
|
||||
requestedTx: payload.tx ?? null,
|
||||
ok: false,
|
||||
reason: "missing_auth",
|
||||
});
|
||||
return deny();
|
||||
}
|
||||
|
||||
const parsed = parseHy2Userpass(payload.auth);
|
||||
if (!parsed) {
|
||||
await writeAuthAudit({
|
||||
nodeId: node.id,
|
||||
addr: payload.addr ?? null,
|
||||
requestedTx: payload.tx ?? null,
|
||||
ok: false,
|
||||
reason: "bad_auth_format",
|
||||
});
|
||||
return deny();
|
||||
}
|
||||
|
||||
const user = await getUserByUsername(parsed.username);
|
||||
if (!user) {
|
||||
await writeAuthAudit({
|
||||
nodeId: node.id,
|
||||
username: parsed.username,
|
||||
addr: payload.addr ?? null,
|
||||
requestedTx: payload.tx ?? null,
|
||||
ok: false,
|
||||
reason: "user_not_found",
|
||||
});
|
||||
return deny();
|
||||
}
|
||||
|
||||
const passwordOk = await verifyPassword(parsed.password, user.password_hash);
|
||||
if (!passwordOk) {
|
||||
await writeAuthAudit({
|
||||
nodeId: node.id,
|
||||
userId: user.id,
|
||||
authId: user.auth_id,
|
||||
username: user.username,
|
||||
addr: payload.addr ?? null,
|
||||
requestedTx: payload.tx ?? null,
|
||||
ok: false,
|
||||
reason: "password_invalid",
|
||||
});
|
||||
return deny();
|
||||
}
|
||||
|
||||
const access = evaluateUserForAuth(user);
|
||||
if (!access.ok) {
|
||||
await writeAuthAudit({
|
||||
nodeId: node.id,
|
||||
userId: user.id,
|
||||
authId: user.auth_id,
|
||||
username: user.username,
|
||||
addr: payload.addr ?? null,
|
||||
requestedTx: payload.tx ?? null,
|
||||
ok: false,
|
||||
reason: access.reason,
|
||||
});
|
||||
return deny();
|
||||
}
|
||||
|
||||
await writeAuthAudit({
|
||||
nodeId: node.id,
|
||||
userId: user.id,
|
||||
authId: user.auth_id,
|
||||
username: user.username,
|
||||
addr: payload.addr ?? null,
|
||||
requestedTx: payload.tx ?? null,
|
||||
ok: true,
|
||||
reason: "ok",
|
||||
});
|
||||
|
||||
return Response.json({ ok: true, id: user.auth_id });
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { buildSubscriptionConfig, buildSubscriptionHeaders } from "@/lib/subscription";
|
||||
import {
|
||||
getSubscriptionSettings,
|
||||
getUserBySubscriptionToken,
|
||||
listSubscriptionNodes,
|
||||
} from "@/lib/store";
|
||||
|
||||
export async function GET(
|
||||
_request: Request,
|
||||
context: RouteContext<"/api/subscription/[token]/clash">,
|
||||
) {
|
||||
const { token } = await context.params;
|
||||
const user = await getUserBySubscriptionToken(token);
|
||||
|
||||
if (!user || !user.enabled) {
|
||||
return new Response("subscription not found", { status: 404 });
|
||||
}
|
||||
|
||||
const [nodes, settings] = await Promise.all([
|
||||
listSubscriptionNodes(),
|
||||
getSubscriptionSettings(),
|
||||
]);
|
||||
|
||||
let body: string;
|
||||
|
||||
try {
|
||||
body = buildSubscriptionConfig({ user, nodes, settings });
|
||||
} catch {
|
||||
return new Response("subscription secret unavailable", { status: 409 });
|
||||
}
|
||||
|
||||
const profileName = user.subscription_label || settings.profileName || user.username;
|
||||
|
||||
return new Response(body, {
|
||||
status: 200,
|
||||
headers: buildSubscriptionHeaders(user, profileName),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user