拥有自己的节点管理!

我又来摸鱼了,继续从 Cloudflare接入LinuxDo有什么用?

有什么用呢?你可以得到一个节点分享工具

管理员(自己部署)


普通用户

可以设置几级可见

https://proxy.xlike.us.kg/dashboard

22 个赞
// work.js
const AUTHORIZATION_ENDPOINT = "https://connect.linux.do/oauth2/authorize";
const TOKEN_ENDPOINT = "https://connect.linux.do/oauth2/token";
const USER_ENDPOINT = "https://connect.linux.do/api/user";

let cachedKey = null;

// ========== 加解密工具 ==========
async function getKey(AUTH_PASS_WORD) {
  if (cachedKey) return cachedKey;
  const enc = new TextEncoder();
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    enc.encode(AUTH_PASS_WORD),
    { name: "PBKDF2" },
    false,
    ["deriveKey"]
  );
  const key = await crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt: enc.encode("static_salt"),
      iterations: 100000,
      hash: "SHA-256",
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt", "decrypt"]
  );
  cachedKey = key;
  return key;
}

async function encrypt(text, AUTH_PASS_WORD) {
  const enc = new TextEncoder();
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const key = await getKey(AUTH_PASS_WORD);
  const ciphertext = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    enc.encode(text)
  );
  const buffer = new Uint8Array(iv.byteLength + ciphertext.byteLength);
  buffer.set(iv, 0);
  buffer.set(new Uint8Array(ciphertext), iv.byteLength);
  let binary = "";
  buffer.forEach((b) => (binary += String.fromCharCode(b)));
  return btoa(binary);
}

async function decrypt(data, AUTH_PASS_WORD) {
  try {
    const buffer = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
    const iv = buffer.slice(0, 12);
    const ciphertext = buffer.slice(12);
    const key = await getKey(AUTH_PASS_WORD);
    const decrypted = await crypto.subtle.decrypt(
      { name: "AES-GCM", iv },
      key,
      ciphertext
    );
    const dec = new TextDecoder();
    return dec.decode(decrypted);
  } catch (e) {
    return null;
  }
}

// ========== Cookie相关 ==========
function parseCookies(cookieHeader) {
  if (!cookieHeader) return {};
  let cookies = {};
  cookieHeader.split(";").forEach((cookie) => {
    const [name, ...rest] = cookie.trim().split("=");
    const value = rest.join("=");
    cookies[name] = value;
  });
  return cookies;
}

function createCookie(name, value, options = {}) {
  let cookie = `${name}=${value}`;
  if (options.Path) cookie += `; Path=${options.Path}`;
  if (options.HttpOnly) cookie += `; HttpOnly`;
  if (options.SameSite) cookie += `; SameSite=${options.SameSite}`;
  // if (options.Secure) cookie += '; Secure'; // HTTPS可加
  return cookie;
}

// ========== 其他工具:随机字符串、KV分页、时间格式化 ==========
function cryptoRandomString() {
  return Array.from(crypto.getRandomValues(new Uint8Array(16)))
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
}

// 获取所有 key
async function getAllKeys(envKV) {
  let allKeys = [];
  let cursor = null;
  do {
    const list = await envKV.list({ limit: 1000, cursor });
    allKeys = allKeys.concat(list.keys);
    cursor = list.cursor;
    if (list.list_complete) break;
  } while (true);
  return allKeys;
}

function formatDateTime(dateString) {
  if (!dateString) return "";
  const d = new Date(dateString);
  const yyyy = d.getFullYear();
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  const hh = String(d.getHours()).padStart(2, "0");
  const min = String(d.getMinutes()).padStart(2, "0");
  const ss = String(d.getSeconds()).padStart(2, "0");
  return `${yyyy}-${mm}-${dd} ${hh}:${min}:${ss}`;
}

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const pathname = url.pathname;
    const searchParams = url.searchParams;
    const method = request.method;

    // 解密 Cookie
    async function getUserInfo(request) {
      const cookieHeader = request.headers.get("Cookie");
      const cookies = parseCookies(cookieHeader);
      if (!cookies.auth) return null;
      const decrypted = await decrypt(cookies.auth, env.AUTH_PASS_WORD);
      if (!decrypted) return null;
      try {
        const userInfo = JSON.parse(decrypted);
        return userInfo;
      } catch (e) {
        return null;
      }
    }

    // ========== 路由 ==========
    // 1) 首页 '/'
    if (pathname === "/") {
      return new Response(
        `<!DOCTYPE html>
        <html>
        <head>
          <meta charset="UTF-8">
          <title>LinuxDo-ProxyShare</title>
          <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
        </head>
        <body class="bg-gray-100 flex items-center justify-center h-screen">
          <div class="text-center">
            <h1 class="text-2xl mb-5 font-bold">LinuxDo-ProxyShare</h1>
            <button id="loginButton" class="px-6 py-3 bg-green-500 text-white rounded hover:bg-green-600">一键登录</button>
          </div>
          <script>
            document.getElementById('loginButton').addEventListener('click', () => {
              fetch('/oauth2/initiate', { method: 'GET' })
                .then(res => res.json())
                .then(data => {
                  window.location.href = data.url;
                })
                .catch(() => alert('Error initiating OAuth2'));
            });
          </script>
        </body>
        </html>`,
        { headers: { "Content-Type": "text/html" } }
      );
    }

    // 2) OAuth2 初始化 '/oauth2/initiate'
    else if (pathname === "/oauth2/initiate") {
      const state = cryptoRandomString();
      await env.user.put(`state:${state}`, "valid", { expirationTtl: 300 });
      const authorizationUrl = `${AUTHORIZATION_ENDPOINT}?client_id=${encodeURIComponent(
        env.CLIENT_ID
      )}&response_type=code&redirect_uri=${encodeURIComponent(
        env.REDIRECT_URI + "/oauth2/callback"
      )}&scope=read,write&state=${state}`;
      return new Response(JSON.stringify({ url: authorizationUrl }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      });
    }

    // 3) OAuth2 回调 '/oauth2/callback'
    else if (pathname === "/oauth2/callback") {
      const code = searchParams.get("code");
      const state = searchParams.get("state");
      const stateValue = await env.user.get(`state:${state}`);
      if (!code || !state || !stateValue) {
        return new Response("Invalid or missing code/state", {
          status: 400,
          headers: { "Content-Type": "text/plain" },
        });
      }
      await env.user.delete(`state:${state}`);

      try {
        // 交换 code -> token
        const tokenResponse = await fetch(TOKEN_ENDPOINT, {
          method: "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            Authorization:
              "Basic " + btoa(`${env.CLIENT_ID}:${env.CLIENT_SECRET}`),
          },
          body: new URLSearchParams({
            grant_type: "authorization_code",
            code: code,
            redirect_uri: `${env.REDIRECT_URI}/oauth2/callback`,
          }),
        });
        if (!tokenResponse.ok) throw new Error("Failed to exchange code");
        const tokenData = await tokenResponse.json();
        if (!tokenData.access_token) throw new Error("No access_token");

        // 获取用户信息
        const userResponse = await fetch(USER_ENDPOINT, {
          headers: { Authorization: `Bearer ${tokenData.access_token}` },
        });
        if (!userResponse.ok) throw new Error("Failed to fetch user info");
        const userData = await userResponse.json();

        // 存储完整
        await env.user.put(String(userData.id), JSON.stringify(userData));

        // 只在 Cookie 中存部分
        const userInfo = {
          id: userData.id,
          username: userData.username,
          trust_level: userData.trust_level || 0, 
          avatar_url: userData.avatar_url,
        };
        const encrypted = await encrypt(JSON.stringify(userInfo), env.AUTH_PASS_WORD);
        const cookie = createCookie("auth", encrypted, {
          Path: "/",
          HttpOnly: true,
          SameSite: "Strict",
        });


            // const baseURL = "https://proxy.xlike.us.kg/dashboard";
      
        url.pathname = "/dashboard";
        const redirectUrl = url.href; 
        return new Response("", {
          status: 302,
          headers: {
            "Location": redirectUrl,
            "Set-Cookie": cookie,
          },
        });


      } catch (err) {
        return new Response(err.message, {
          status: 500,
          headers: { "Content-Type": "text/plain" },
        });
      }
    }

    // 4) 仪表板 '/dashboard'
    else if (pathname === "/dashboard") {
      const userInfo = await getUserInfo(request);
      if (!userInfo) {
        // 未登录 -> 重定向 '/'
        url.pathname = "/";
        return new Response("", {
          status: 302,
          headers: { "Location": url.href },
        });
      }
      const isAdmin = userInfo.username === env.AUTH_NAME;

      // 获取全部
      const allKeys = await getAllKeys(env.proxy);
      let data = [];
      for (const kv of allKeys) {
        const item = await env.proxy.get(kv.name, { type: "json" });
        if (item) {
          // 判断可见性
          // 如果 item.visible_level 不存在,默认为0
          const requiredLevel = item.visible_level ?? 0;
          if (userInfo.trust_level >= requiredLevel) {
            data.push(item);
          }
        }
      }

      // 时间降序
      data.sort((a, b) => {
        let ad = new Date(a.created_at).getTime();
        let bd = new Date(b.created_at).getTime();
        return bd - ad; // b在前
      });

      // 分页:每页5条
      const page = parseInt(searchParams.get("page") || "1", 10) || 1;
      const pageSize = 5;
      const total = data.length;
      const totalPages = Math.ceil(total / pageSize);
      const startIndex = (page - 1) * pageSize;
      const endIndex = startIndex + pageSize;
      const pageData = data.slice(startIndex, endIndex);

      // 格式化时间
      pageData.forEach(item => {
        item.created_at_formatted = formatDateTime(item.created_at);
      });

      // 构造分页链接
      function pageLink(p) {
        const newUrl = new URL(request.url);
        newUrl.searchParams.set("page", p);
        return newUrl.pathname + newUrl.search; 
      }

      // 构造分页HTML
      let paginationHtml = `
      <div class="flex justify-center items-center space-x-4 mt-4">
        ${page > 1 ? `<a href="${pageLink(page - 1)}" class="px-3 py-1 bg-gray-200 hover:bg-gray-300 rounded">上一页</a>` : ""}
        <span>第 ${page} 页 / 共 ${totalPages} 页</span>
        ${page < totalPages ? `<a href="${pageLink(page + 1)}" class="px-3 py-1 bg-gray-200 hover:bg-gray-300 rounded">下一页</a>` : ""}
      </div>
      `;

      // 返回HTML
      return new Response(
        `<!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <title>节点订阅管理</title>
          <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
        </head>
        <body class="bg-gray-100 min-h-screen">
          
          <!-- 顶部导航 -->
          <nav class="bg-blue-600 p-4 text-white">
            <div class="max-w-7xl mx-auto flex justify-between">
              <div class="text-xl font-bold">
                欢迎, ${userInfo.username} (${isAdmin ? "管理员" : "普通用户"}) 信任等级: ${userInfo.trust_level}
              </div>
              <a href="/" class="hover:underline">退出</a>
            </div>
          </nav>

          <div class="max-w-7xl mx-auto p-4">
            <div class="flex justify-between items-center mt-6 mb-4">
              <h2 class="text-2xl font-bold">节点订阅管理</h2>
              <!-- 无论是否管理员,都可以新增 -->
              <button onclick="showAddModal()" class="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700">新增</button>
            </div>

            <!-- 为了防止页面太长,加一个固定高度+滚动条 -->
            <div class="max-h-[500px] overflow-y-auto bg-white rounded shadow">
              <table class="min-w-full divide-y divide-gray-200">
                <thead class="bg-gray-50 sticky top-0">
                  <tr>
                    <th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase">序号</th>
                    <th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase">节点说明</th>
                    <th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase">可见级别</th>
                    <th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase">昵称/头像</th>
                    <th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase">创建时间</th>
                    <th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase">订阅链接</th>
                    ${
                      isAdmin
                        ? `<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase">操作</th>`
                        : ``
                    }
                  </tr>
                </thead>
                <tbody class="divide-y divide-gray-200">
                  ${pageData
                    .map((item, index) => {
                      return `
                        <tr>
                          <td class="px-6 py-4 text-center">${startIndex + index + 1}</td>
                          <td class="px-6 py-4 text-center">${item.title || "-"}</td>
                          <td class="px-6 py-4 text-center">${item.visible_level ?? 0}</td>
                          <td class="px-6 py-4 text-center">
                            <div class="flex flex-col items-center">
                              <img src="${item.avatar_url ||
                                "https://via.placeholder.com/50"}" class="w-10 h-10 rounded-full mb-1" />
                              <span>${item.username || "未知"}</span>
                            </div>
                          </td>
                          <td class="px-6 py-4 text-center">${item.created_at_formatted || "-"}</td>
                          <td class="px-6 py-4 text-center">
                            <a href="#" class="text-blue-600 hover:underline" onclick="copyToClipboard('${item.subscription || ""}'); return false;">复制订阅</a>
                          </td>
                          ${
                            isAdmin
                              ? `
                              <td class="px-6 py-4 text-center">
                                <button onclick="showEditModal('${item.id}')" class="bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600">编辑</button>
                                <button onclick="deleteItem('${item.id}')" class="bg-red-500 text-white px-2 py-1 rounded hover:bg-red-600 ml-2">删除</button>
                              </td>`
                              : ``
                          }
                        </tr>
                      `;
                    })
                    .join("")}
                </tbody>
              </table>
            </div>
            ${paginationHtml}
          </div>

          <!-- 新增 Modal -->
          <div id="addModalOverlay" class="fixed inset-0 bg-black bg-opacity-50 hidden justify-center items-center z-10" onclick="hideAddModal()"></div>
          <div id="addModal" class="fixed bg-white p-6 rounded shadow hidden z-20" 
               style="width: 320px; transform: translate(-50%, -50%); left: 50%; top: 50%;" 
               onclick="event.stopPropagation()">
            <h3 class="text-lg font-bold mb-2">新增节点订阅</h3>
            <div class="mb-2">
              <label class="block text-sm mb-1">节点说明</label>
              <input type="text" id="addTitle" class="border border-gray-300 rounded px-2 py-1 w-full" />
            </div>
            <div class="mb-2">
              <label class="block text-sm mb-1">节点订阅链接</label>
              <input type="text" id="addLink" class="border border-gray-300 rounded px-2 py-1 w-full" />
            </div>
            <div class="mb-2">
              <label class="block text-sm mb-1">可见级别(0~3)</label>
              <select id="addVisible" class="border border-gray-300 rounded px-2 py-1 w-full">
                <option value="0">0 (所有人可见)</option>
                <option value="1">1 (trust_level >=1 可见)</option>
                <option value="2">2 (trust_level >=2 可见)</option>
                <option value="3">3 (trust_level >=3 可见)</option>
              </select>
            </div>
            <button onclick="addNewItem()" class="bg-green-600 text-white px-4 py-1 rounded hover:bg-green-700">提交</button>
            <button onclick="hideAddModal()" class="ml-2 text-gray-500 hover:text-gray-700">取消</button>
          </div>

          <!-- 编辑 Modal(只有管理员可发PUT) -->
          <div id="editModalOverlay" class="fixed inset-0 bg-black bg-opacity-50 hidden justify-center items-center z-10" onclick="hideEditModal()"></div>
          <div id="editModal" class="fixed bg-white p-6 rounded shadow hidden z-20"
               style="width: 320px; transform: translate(-50%, -50%); left: 50%; top: 50%;"
               onclick="event.stopPropagation()">
            <h3 class="text-lg font-bold mb-2">编辑节点订阅</h3>
            <input type="hidden" id="editItemId" />
            <div class="mb-2">
              <label class="block text-sm mb-1">标题</label>
              <input type="text" id="editTitle" class="border border-gray-300 rounded px-2 py-1 w-full" />
            </div>
            <div class="mb-2">
              <label class="block text-sm mb-1">节点订阅链接</label>
              <input type="text" id="editLink" class="border border-gray-300 rounded px-2 py-1 w-full" />
            </div>
            <div class="mb-2">
              <label class="block text-sm mb-1">可见级别(0~3)</label>
              <select id="editVisible" class="border border-gray-300 rounded px-2 py-1 w-full">
                <option value="0">0</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
              </select>
            </div>
            <button onclick="editItem()" class="bg-blue-600 text-white px-4 py-1 rounded hover:bg-blue-700">保存</button>
            <button onclick="hideEditModal()" class="ml-2 text-gray-500 hover:text-gray-700">取消</button>
          </div>

          <script>
            function showAddModal() {
              document.getElementById('addModalOverlay').classList.remove('hidden');
              document.getElementById('addModalOverlay').classList.add('flex');
              document.getElementById('addModal').classList.remove('hidden');
            }
            function hideAddModal() {
              document.getElementById('addModalOverlay').classList.add('hidden');
              document.getElementById('addModalOverlay').classList.remove('flex');
              document.getElementById('addModal').classList.add('hidden');
            }

            function showEditModal(id) {
              document.getElementById('editModalOverlay').classList.remove('hidden');
              document.getElementById('editModalOverlay').classList.add('flex');
              document.getElementById('editModal').classList.remove('hidden');
              fetch('/proxy?id=' + id)
                .then(res => res.json())
                .then(item => {
                  document.getElementById('editItemId').value = item.id;
                  document.getElementById('editTitle').value = item.title || '';
                  document.getElementById('editLink').value = item.subscription || '';
                  const vis = item.visible_level ?? 0;
                  document.getElementById('editVisible').value = vis;
                })
                .catch(() => alert('获取信息失败'));
            }
            function hideEditModal() {
              document.getElementById('editModalOverlay').classList.add('hidden');
              document.getElementById('editModalOverlay').classList.remove('flex');
              document.getElementById('editModal').classList.add('hidden');
            }

            // 新增
            async function addNewItem() {
              const title = document.getElementById('addTitle').value.trim();
              const link = document.getElementById('addLink').value.trim();
              const vis = parseInt(document.getElementById('addVisible').value, 10);
              if(!title || !link) {
                alert('标题和链接不能为空');
                return;
              }
              const time = new Date().toISOString();
              const body = { title, subscription: link, visible_level: vis, created_at: time };
              const res = await fetch('/proxy', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(body)
              });
              if (res.ok) {
                alert('新增成功');
                location.reload(); 
              } else {
                alert('新增失败');
              }
            }

            // 编辑
            async function editItem() {
              const id = document.getElementById('editItemId').value;
              const title = document.getElementById('editTitle').value.trim();
              const link = document.getElementById('editLink').value.trim();
              const vis = parseInt(document.getElementById('editVisible').value, 10);
              if (!id) return alert('无效的ID');
              const res = await fetch('/proxy', {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ id, title, subscription: link, visible_level: vis })
              });
              if (res.ok) {
                alert('修改成功');
                location.reload();
              } else {
                alert('修改失败');
              }
            }

            // 删除
            async function deleteItem(id) {
              if (!confirm('确定删除该订阅吗?')) return;
              const res = await fetch('/proxy', {
                method: 'DELETE',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ id })
              });
              if (res.ok) {
                alert('删除成功');
                location.reload();
              } else {
                alert('删除失败');
              }
            }

            // 复制
            function copyToClipboard(text) {
              navigator.clipboard.writeText(text).then(() => {
                alert('已复制到剪贴板');
              }, () => {
                alert('复制失败');
              });
            }
          </script>
        </body>
        </html>`,
        {
          headers: {
            "Content-Type": "text/html",
            // 禁止缓存
            "Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate",
            'Expires': '0',
          },
        }
      );
    }

    // 5) 代理 API '/proxy'
    else if (pathname.startsWith("/proxy")) {
      const userInfo = await getUserInfo(request);
      if (!userInfo) {
        return new Response("Unauthorized", { status: 401 });
      }
      const isAdmin = userInfo.username === env.AUTH_NAME;

      if (method === "GET") {
        // GET /proxy?id=xxx 单条
        const id = searchParams.get("id");
        if (id) {
          const item = await env.proxy.get(id, { type: "json" });
          if (!item) {
            return new Response("Not Found", { status: 404 });
          }
          return new Response(JSON.stringify(item), {
            headers: { "Content-Type": "application/json" },
          });
        } else {
          // 否则全部
          const all = await getAllKeys(env.proxy);
          let arr = [];
          for (const kv of all) {
            const item = await env.proxy.get(kv.name, { type: "json" });
            if (item) arr.push(item);
          }
          return new Response(JSON.stringify(arr), {
            headers: { "Content-Type": "application/json" },
          });
        }
      }
      else if (method === "POST") {
        // 普通用户也能新增
        const body = await request.json();
        const kvId = cryptoRandomString();
        // 拿完整信息
        const fullUserDataStr = await env.user.get(String(userInfo.id));
        let fullUserData = {};
        try {
          fullUserData = JSON.parse(fullUserDataStr || "{}");
        } catch(e){}
        // 组合
        const newSub = {
          id: kvId,
          username: fullUserData.username || "未知",
          avatar_url: fullUserData.avatar_url || "",
          ...body,
          created_at: body.created_at || new Date().toISOString(),
        };
        await env.proxy.put(kvId, JSON.stringify(newSub));
        return new Response(JSON.stringify(newSub), {
          headers: { "Content-Type": "application/json" },
        });
      }
      else if (method === "PUT") {
        // 只有管理员能编辑
        if (!isAdmin) {
          return new Response("Forbidden", { status: 403 });
        }
        const body = await request.json();
        const existing = await env.proxy.get(body.id, { type: "json" });
        if (!existing) {
          return new Response("Not Found", { status: 404 });
        }
        // 合并
        const updated = { ...existing, ...body, id: body.id };
        await env.proxy.put(body.id, JSON.stringify(updated));
        return new Response(JSON.stringify(updated), {
          headers: { "Content-Type": "application/json" },
        });
      }
      else if (method === "DELETE") {
        // 只有管理员能删除
        if (!isAdmin) {
          return new Response("Forbidden", { status: 403 });
        }
        const body = await request.json();
        if (!body.id) {
          return new Response("Missing id", { status: 400 });
        }
        await env.proxy.delete(body.id);
        return new Response("Deleted");
      }
      else {
        return new Response("Method Not Allowed", { status: 405 });
      }
    }

    // 6) 其他路径
    else {
      return new Response("Not Found", { status: 404 });
    }
  },
};

4 个赞

没人点个赞吗?

1 个赞

:100: 不懂,感觉很厉害

大佬666

原来可以这样实现登录,之前还纠结要怎么写

晚点,出一个详细的部署教程

1 个赞

马可一下:sunglasses:

这个感觉不错 :+1:

详细教程 → https://linux.do/t/topic/323067/3

感谢分享 :tieba_087:

此话题已在最后回复的 30 天后被自动关闭。不再允许新回复。