@codefox-inc/oauth-provider

中文文档
0.4.2发布于 2026/05/17Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 17.3万

OAuth 2.1 / Convex Auth (Beta) 的 OpenID 连接提供者.

中文使用文档
文档目录26 节
  1. 为什么?
  2. 安装
  3. 特征
  4. 支持的赠款类型
  5. 不支持的地物( OAuth 2. 0 遗产)
  6. 关键安全要求
  7. 内设安全控制
  8. 授权流量安全
  9. 支持的范围
  10. 刷新托肯要求
  11. OAuth 托肯检测帮助工具
  12. 设置
  13. 1. 设置环境变量
  14. 2. 登记部分
  15. 3. 配置 HTTP 路由
  16. 用户信息端点
  17. 注册 OAuth 客户端( Admin)
  18. 授权流量
  19. 自动授权处理器
  20. 列表用户授权的应用程序
  21. 撤销授权
  22. 调试验证
  23. 撤销和访问托肯寿命
  24. 在凸起函数中
  25. 测试
  26. 许可证

@ codefox- inc/ oauth- 提供者

OAuth 2.1 / OpenID 连接提供器作为Convex组件被执行.

QQ Beta 软件 - 生产用在你自己的风险.

测试过 凸起轴 财务报告和审定财务报表 @ convex-dev/ better-auth (英语)。 。 。 .

为什么?

大多数MCP客户端(如克洛德代码或ChatGPT)都要求您的应用程序成为OAuth供应商. 如果您想要将您的 Convex 应用程序连接到 MCP 客户端, 您需要执行 OAuth 2.1.

此组件将您的 Convex 应用程序变成一个完全符合 OAuth 2.1 的提供者,所以您可以:

  • 从框中连接 MCP 客户端
  • 让客户端通过动态客户端注册自动注册
  • 让用户控制每个应用程序的权限
  • 专注于您的应用程序, 而非 OAuth 复杂度

安装

bun add @codefox-inc/oauth-provider

特征

  • OAuth 2.1 符合规定 授权和符号终点
  • OpenID 连接发现 用于自动客户端配置
  • 需要PKCE 所有授权代码流量(仅限S256)
  • RFC 8707 资源指标 供观众使用
  • RFC 9068 JWT 访问令牌 (单位:千美元)typ: at+jwt, (中文(简体) ), client_id, (中文(简体) ), scope, (中文(简体) ), jti(中文(简体) )
  • 安全信使存储 使用SHA-256散列作为代币和授权代码
  • JWT 访问符 与 RS256 签名
  • 刷新符号旋转 加强安保
  • 动态客户注册 (选入)
  • 授权管理 用户同意跟踪
  • JWKS 结束点 用于符号验证
OAuth 2.1 遵约情况

其后执行 奥奥特2.1 及相关的OAuth/OIDC规格:

支持的赠款类型

  • ✅ 带有 PKCE 的授权代码 (公共和保密客户)
  • ✅ 刷新托肯 (带有符号旋转)

不支持的地物( OAuth 2. 0 遗产)

  • ❌ 间接赠款 (因安全原因在OAuth 2.1被拆除)
  • ❌ 资源所有人密码证书赠款 (在OAuth 2.1中删除)
  • ❌ PKCE 平原方法 (每个OAuth2.1最佳做法只支持S256)

关键安全要求

  • PKCE 执行: 所有授权代码流量需要使用 S256 方法的 PKCE
  • 重定向 URI 验证: 精确字符串匹配(仅限RFC 8252回转可变端口例外)
  • 资源约束数字 : resource 值与授权授予和刷新令牌绑定
  • 访问托肯观众: 访问令牌 aud 是授权的 resource,或配置的默认受众
  • 授权代码: 单用,10分钟后到期
  • 托肯·哈兴: 所有以 SHA-256 散列存储的令牌
  • 刷新托肯旋转: 每次使用时发布新的刷新令牌,旧令牌作废
安全特性

内设安全控制

  • PKCE 执行: 所有授权代码流量都需要PKCE(代码 质疑/代码 验证)
  • 重定向 URI 验证: 严格核对已注册的 URI
  • 范围验证:每个客户只允许注册范围
  • 托肯·哈兴: 访问和刷新令牌作为SHA-256散列存储
  • 客户端秘密搜索: 使用 bcrypt 的客户机密
  • 客户端机密兼容性:新发布的符合bcrypt72字节输入限制的客户机密;现有的更长的秘密对于补丁放出兼容性仍然有效,在实用时应当被旋转
  • 内部变异:关键业务,如 issueAuthorizationCode 无法直接访问
  • 默认禁用 DCR: 必须明确启用动态客户端注册

授权流量安全

这个 /oauth/authorize 端点进行全面的验证 :

  1. 客户身份验证
  2. 根据注册的 URI 重定向 URI 匹配
  3. 对照客户端允许的范围进行范围验证
  4. PKCE 要求(使用 S256 方法的代码-挑战)
  5. 用户认证通过 getUserId 钩子
范围和托肯类型

支持的范围

  • openid: OpenID 连接认证和ID 令牌需要
  • profile: 授权访问用户配置信息(名称、图片)
  • email: 授权访问用户电子邮件地址
  • offline_access: 为长期访问启用刷新令牌发行

刷新托肯要求

刷新符号是 仅印发 当 offline_access 在最初授权期间,请求并授予范围。 对于 OpenID 连接请求, offline_access 需求 prompt=consent (或包含 consent:

  • ✅ 与 offline_access:客户端同时收到访问令牌和刷新令牌
  • ❌ 无 offline_access:客户端仅接收访问令牌(无刷新令牌)

刷新托肯赠款流程 :

  • 使用 refresh_token 获取新访问符的赠款类型
  • 原始授权必须包括: offline_access 范围
  • 刷新令牌在每次使用时自动旋转(旧令牌无效)
  • 新的刷新令牌保持与原值相同的范围
  • 重用旋转的刷新令牌将活动刷新的家族及其授权记录取消

这遵循了OAuth 2.1和OpenID Connect的规格,确保了长寿的刷新令牌只有在得到用户明确同意的情况下才发布.

资源指标和有约束力受众

这个提供者支持 RFC 8707 resource mCP和其他资源服务器流量的指标.

  • resource 可在授权请求中选择.
  • 如果存在,它必须是绝对的无碎片URI.
  • 授权代码存储批准 resource。 。 。 .
  • 令牌请求可能重复同样内容 resource,但不能添加未经批准的新的.
  • 刷新令牌在旋转时保留相同的资源/听众绑定 .
  • 访问符使用授权 resource 作为 aud; 否则使用 applicationID 默认 convex 观众.

对于自定义同意的 UI , 保存收到的 resource 参数并传递给 issueAuthorizationCode。 。 。 .

OAuth 托肯检测帮助工具

提供区分 OAuth 令牌和会话令牌的帮助函数 :

import { isOAuthToken, getOAuthClientId } from "@codefox-inc/oauth-provider";

const identity = await ctx.auth.getUserIdentity();

if (isOAuthToken(identity)) {
    // Handle OAuth token (MCP client, third-party apps, etc.)
    const clientId = getOAuthClientId(identity);
    console.log("OAuth client:", clientId);
} else {
    // Handle Convex Auth session (first-party user)
}

设置

1. 设置环境变量

这个组件与任何认证系统相配合. 选择匹配您的堆栈的设置 :

备选案文A:带有 Convex Auth

如果你在用的话 凸起轴,您已经配置了所需的环境变量(JWT_PRIVATE_KEY, (中文(简体) ), JWKS, (中文(简体) ), SITE_URL) (中文(简体) ).

备选案文B:使用更好的认证

如果你在用的话 @ convex-dev/ better-auth (英语),您可以共享相同的密钥 :

bunx convex env set OAUTH_PRIVATE_KEY "$(cat private.pem)"  # Or use JWT_PRIVATE_KEY
bunx convex env set OAUTH_JWKS '{"keys":[...]}'             # Or use JWKS
bunx convex env set SITE_URL "https://your-app.example.com"

重要内容: 当使用更好的 Auth 时, 设置 applicationID: "oauth-provider" 在 OAuth 配置中区分 OAuth 符号和 Better Auth 会话符号.

选项 C: 手工设置

手动生成 RSA 密钥 :

# Generate private key
openssl genrsa -out private.pem 2048

# Generate JWKS (use https://mkjwk.org or this script)
node -e "
const jose = require('jose');
const fs = require('fs');
const privateKey = fs.readFileSync('private.pem', 'utf8');
(async () => {
  const key = await jose.importPKCS8(privateKey, 'RS256');
  const jwk = await jose.exportJWK(key);
  console.log(JSON.stringify({ keys: [{ ...jwk, use: 'sig', alg: 'RS256', kid: 'default-key' }] }));
})();
"

设置环境变量 :

bunx convex env set JWT_PRIVATE_KEY "-----BEGIN RSA PRIVATE KEY-----\n..."
bunx convex env set JWKS '{"keys":[...]}'
bunx convex env set SITE_URL "https://your-app.example.com"

2. 登记部分

// convex/convex.config.ts
import { defineApp } from "convex/server";
import oauthProvider from "@codefox-inc/oauth-provider/convex.config";

const app = defineApp();
app.use(oauthProvider, { name: "oauthProvider" });

export default app;

3. 配置 HTTP 路由

// convex/http.ts
import { httpAction } from "./_generated/server";
import { httpRouter } from "convex/server";
import { OAuthProvider, registerOAuthRoutes } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";
import { api } from "./_generated/api";

const http = httpRouter();

const oauthProvider = new OAuthProvider(components.oauthProvider, {
    privateKey: process.env.JWT_PRIVATE_KEY!,
    jwks: process.env.JWKS!,
    siteUrl: process.env.SITE_URL!,

    // REQUIRED: Authenticate user for authorization endpoint
    getUserId: async (ctx, request) => {
        const identity = await ctx.auth.getUserIdentity();
        return identity?.subject ?? null;
    },
});

// Register all OAuth routes automatically
registerOAuthRoutes(http, httpAction, oauthProvider, {
    siteUrl: process.env.SITE_URL!,
    // OPTIONAL: Override the prefix used for route registration.
    // By default, this uses oauthProvider's config prefix.
    // prefix: "/oauth",
    getUserProfile: async (ctx, userId) => {
        // Return user profile for /oauth/userinfo endpoint
        const user = await ctx.runQuery(api.users.get, { userId });
        return user ? {
            sub: userId,
            name: user.name,
            email: user.email,
            picture: user.pictureUrl
        } : null;
    },
});

export default http;

备选案文B:使用更好的认证

// convex/http.ts
import { httpAction } from "./_generated/server";
import { httpRouter } from "convex/server";
import { OAuthProvider, registerOAuthRoutes } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";
import { api } from "./_generated/api";

const http = httpRouter();

const oauthProvider = new OAuthProvider(components.oauthProvider, {
    privateKey: process.env.OAUTH_PRIVATE_KEY ?? process.env.JWT_PRIVATE_KEY!,
    jwks: process.env.OAUTH_JWKS ?? process.env.JWKS!,
    siteUrl: process.env.SITE_URL!,

    // IMPORTANT: Set applicationID to distinguish from Better Auth tokens
    applicationID: "oauth-provider",

    getUserId: async (ctx, request) => {
        const identity = await ctx.auth.getUserIdentity();
        return identity?.subject ?? null;
    },
});

// Register Better Auth routes first (if using @convex-dev/better-auth)
// authComponent.registerRoutes(http, createAuth, { cors: true });

// Then register OAuth routes
registerOAuthRoutes(http, httpAction, oauthProvider, {
    siteUrl: process.env.SITE_URL!,
    getUserProfile: async (ctx, userId) => {
        const user = await ctx.runQuery(api.users.get, { userId });
        return user ? {
            sub: userId,
            name: user.name,
            email: user.email,
            picture: user.pictureUrl
        } : null;
    },
});

export default http;
备选案文C:人工路由登记
// convex/http.ts
import { httpAction } from "./_generated/server";
import { httpRouter } from "convex/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

const http = httpRouter();

const oauthProvider = new OAuthProvider(components.oauthProvider, {
    privateKey: process.env.JWT_PRIVATE_KEY!,
    jwks: process.env.JWKS!,
    siteUrl: process.env.SITE_URL!,

    // REQUIRED: Authenticate user for authorization endpoint
    getUserId: async (ctx, request) => {
        const identity = await ctx.auth.getUserIdentity();
        return identity?.subject ?? null;
    },
});

// OpenID Connect Discovery
http.route({
    path: "/oauth/.well-known/openid-configuration",
    method: "GET",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.openIdConfiguration(ctx, req)
    ),
});

// JWKS endpoint
http.route({
    path: "/oauth/.well-known/jwks.json",
    method: "GET",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.jwks(ctx, req)
    ),
});

// Authorization endpoint (validates and issues auth codes)
http.route({
    path: "/oauth/authorize",
    method: "GET",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.authorize(ctx, req)
    ),
});

// Token endpoint
http.route({
    path: "/oauth/token",
    method: "POST",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.token(ctx, req)
    ),
});

// UserInfo endpoint
http.route({
    path: "/oauth/userinfo",
    method: "GET",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.userInfo(ctx, req, async (userId) => {
            const user = await ctx.runQuery(api.users.get, { userId });
            return user ? { sub: userId, name: user.name, email: user.email } : null;
        })
    ),
});

// Dynamic Client Registration (optional)
http.route({
    path: "/oauth/register",
    method: "POST",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.register(ctx, req)
    ),
});

export default http;

用户信息端点

要求数 openid 范围。 根据范围提出的回返申请:

  • openid: 总是返回 sub
  • profile页:1 name, (中文(简体) ), picture
  • email页:1 email (和 email_verified (如果有)
客户注册

注册 OAuth 客户端( Admin)

// convex/oauthAdmin.ts
import { mutation } from "./_generated/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

export const registerOAuthClient = mutation({
    handler: async (ctx, args: {
        name: string;
        redirectUris: string[];
        scopes: string[];
        type: "confidential" | "public";
    }) => {
        // Check admin permissions
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error("Unauthorized");

        const oauthProvider = new OAuthProvider(components.oauthProvider, {
            privateKey: process.env.JWT_PRIVATE_KEY!,
            jwks: process.env.JWKS!,
            siteUrl: process.env.SITE_URL!,
        });

        const result = await oauthProvider.registerClient(ctx, {
            name: args.name,
            redirectUris: args.redirectUris,
            scopes: args.scopes,
            type: args.type,
        });

        // IMPORTANT: Save clientSecret securely - it's only returned once!
        return result;
    },
});

授权流量

自动授权处理器

这个 /oauth/authorize 端点自动处理完整的授权流 :

GET /oauth/authorize?
  response_type=code
  &client_id=CLIENT_ID
  &redirect_uri=REDIRECT_URI
  &scope=openid+profile+email
  &resource=https://api.example.com/mcp
  &state=STATE
  &code_challenge=CHALLENGE
  &code_challenge_method=S256
  &nonce=NONCE

处理者:

  1. 验证客户端身份
  2. 对照已注册的 URI 检查方向调整 uri
  3. 验证请求的范围
  4. 需要 PKCE( 代码- 挑战)
  5. 验证和装订 resource 提供时
  6. 通过 getUserId
  7. 发布授权代码
  8. 用代码重定向回客户端
自定义授权流程( 高级)

如果您需要自定义同意, 您可以直接使用 SDK 方法 :

// convex/oauth.ts
import { mutation } from "./_generated/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

export const approveAuthorization = mutation({
    handler: async (ctx, args: {
        clientId: string;
        scopes: string[];
        redirectUri: string;
        codeChallenge: string;
        codeChallengeMethod: string;
        nonce?: string;
        resource?: string;
    }) => {
        // Verify user is authenticated
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error("Not authenticated");

        const oauthProvider = new OAuthProvider(components.oauthProvider, {
            privateKey: process.env.JWT_PRIVATE_KEY!,
            jwks: process.env.JWKS!,
            siteUrl: process.env.SITE_URL!,
        });

        // Issue authorization code (automatically creates authorization record)
        const authCode = await oauthProvider.issueAuthorizationCode(ctx, {
            userId: identity.subject,
            clientId: args.clientId,
            scopes: args.scopes,
            redirectUri: args.redirectUri,
            codeChallenge: args.codeChallenge,
            codeChallengeMethod: args.codeChallengeMethod,
            nonce: args.nonce,
            resource: args.resource,
        });

        return authCode;
    },
});

当授权请求包含 resource把它显示在同意书上 并且通过它没有改变。 如果象征性请求要求 resource 未存储在授权代码中, 符号端点返回 invalid_target。 。 。 .

授权管理

列表用户授权的应用程序

import { query } from "./_generated/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

export const listAuthorizedApps = query({
    handler: async (ctx) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) return [];

        const oauthProvider = new OAuthProvider(components.oauthProvider, {
            privateKey: process.env.JWT_PRIVATE_KEY!,
            jwks: process.env.JWKS!,
            siteUrl: process.env.SITE_URL!,
        });

        return await oauthProvider.listUserAuthorizations(ctx, identity.subject);
    },
});

撤销授权

import { mutation } from "./_generated/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

export const revokeApp = mutation({
    handler: async (ctx, args: { clientId: string }) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error("Not authenticated");

        const oauthProvider = new OAuthProvider(components.oauthProvider, {
            privateKey: process.env.JWT_PRIVATE_KEY!,
            jwks: process.env.JWKS!,
            siteUrl: process.env.SITE_URL!,
        });

        // Deletes authorization and all associated tokens
        await oauthProvider.revokeAuthorization(ctx, identity.subject, args.clientId);
    },
});
配置选项( OAuthConfig)
interface OAuthConfig {
    // REQUIRED: RSA private key in PEM format
    privateKey: string;

    // REQUIRED: JWKS for token verification (public keys only)
    jwks: string;

    // REQUIRED: Your application URL
    siteUrl: string;

    // OPTIONAL: Convex deployment URL (if different from siteUrl)
    convexSiteUrl?: string;

    // OPTIONAL: OAuth endpoint prefix (default: "/oauth")
    // Normalized to a leading slash, trailing slash removed; "/" means root.
    // Must match the route prefix you register in http.ts.
    prefix?: string;

    // OPTIONAL: Comma-separated list of allowed CORS origins
    allowedOrigins?: string;

    // OPTIONAL: Allowed scopes for dynamic client registration
    allowedScopes?: string[];

    // OPTIONAL: JWT audience claim (default: "convex")
    // Set to "oauth-provider" when using Better Auth to distinguish tokens
    applicationID?: string;

    // REQUIRED: Function to get authenticated user ID
    // Must return a Convex users table Id (string)
    // Returns null if user is not authenticated
    getUserId?: (ctx: ActionCtx, request: Request) => Promise<string | null> | string | null;

    // OPTIONAL: Enable dynamic client registration (default: false)
    allowDynamicClientRegistration?: boolean;
}

调试验证

撤销和访问托肯寿命

访问符是JWT,可以无国籍地与JWKS验证. 仅凭无国籍核查,无法观察授权撤销、授权码重放检测或刷新式家庭撤销,直到访问令牌到期.

Convex 资源服务器, 有线 createAuthorizationChecker() 输入 createAuthHelper() 因此不记名请求检查当前授权记录:

import { createAuthHelper, OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

const oauthProvider = new OAuthProvider(components.oauthProvider, {
    privateKey: process.env.JWT_PRIVATE_KEY!,
    jwks: process.env.JWKS!,
    siteUrl: process.env.SITE_URL!,
});

export const authHelper = createAuthHelper({
    providers: ["anonymous"],
    checkAuthorization: oauthProvider.createAuthorizationChecker(),
});

如果您在 Convex 外验证访问符 verifyAccessToken() 只有被吊销的访问令牌有效,直到其 exp 时间。 如果需要立即吊销,则使用简短的访问存储寿命并使用资源服务器授权检查.

更新此组件后, 重新运行 Convex 代码生成( 例如) convex dev --once 或您的仓库的代码脚本。 生成的组件参考和计划必须与软件包版本相匹配; prompt=none 静态授权还依赖于最近生成的 getAuthorization 查询参考.

在凸起函数中

import { query } from "./_generated/server";

export const protectedQuery = query({
    handler: async (ctx) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error("Not authenticated");

        // Token is already verified by Convex Auth
        // Use identity.subject for user ID
        return { userId: identity.subject };
    },
});
外部调试验证
import { verifyAccessToken } from "@codefox-inc/oauth-provider";

const payload = await verifyAccessToken(
    token,
    {
        jwks: process.env.JWKS!,
        siteUrl: process.env.SITE_URL!,
        // If using Better Auth, specify the applicationID
        // applicationID: "oauth-provider",
    },
    issuerUrl
);

console.log("User ID:", payload.sub);
console.log("Scopes:", payload.scp);
console.log("Client ID:", payload.cid);
区分 OAuth 从会话托肯

当使用多个认证系统(例如:Better Auth + OAuth Profer)时,您可以通过检查发件人来区分符:

import { isOAuthToken, getOAuthClientId } from "@codefox-inc/oauth-provider";

// Option 1: Using helper functions
const identity = await ctx.auth.getUserIdentity();
if (isOAuthToken(identity)) {
    const clientId = getOAuthClientId(identity);
    // Handle OAuth token (MCP clients, third-party apps)
} else {
    // Handle session token (first-party users)
}

// Option 2: Check issuer directly
if (identity?.issuer?.includes("/oauth")) {
    // This is an OAuth token
}

测试

bun run test

许可证

阿帕奇 - 2.0

版本与发布记录

@codefox-inc/oauth-provider 共发布 11 个版本,首次发布于 2026/01/23;过去 12 个月发布了 11 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.2latest2026/05/175 个月前
0.4.12026/05/056 个月前
0.4.02026/05/056 个月前
0.3.22026/03/257 个月前
0.3.12026/03/207 个月前
0.3.02026/02/019 个月前

关于 @codefox-inc/oauth-provider 的常见问题

@codefox-inc/oauth-provider 是什么?

OAuth 2.1 / Convex Auth (Beta) 的 OpenID 连接提供者.

如何安装 @codefox-inc/oauth-provider?

在项目目录运行 npm install @codefox-inc/oauth-provider,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:convex(^1.31.6)、react(^18.2.0 || ^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@codefox-inc/oauth-provider 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@codefox-inc/oauth-provider 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@codefox-inc/oauth-provider 可以商用吗?

@codefox-inc/oauth-provider 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@codefox-inc/oauth-provider 的下载量有多少?

根据 npm 官方下载统计,@codefox-inc/oauth-provider 在 2026/10/10 – 2026/11/08 这 30 天内被下载 172,999 次,日均约 5,767 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@codefox-inc/oauth-provider 还在维护吗?

最新版本 0.4.2 发布于 2026/05/17(5 个月前);至今共发布 11 个版本,其中过去 12 个月发布了 11 个;首次发布于 2026/01/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。