npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@scorehub/auth-sdk

v1.5.1

Published

Auth-Service SDK for Node.js applications

Readme

@scorehub/auth-sdk 使用文档

安装

npm install @scorehub/auth-sdk

初始化

import { AuthClient } from '@scorehub/auth-sdk';

const authClient = new AuthClient({
  serviceUrl: 'https://api-test.scorehub.cn/auth/api/v1', // Auth-Service 地址
  clientId: 'your-client-id',                             // OAuth Client ID
  clientSecret: 'your-client-secret',                     // OAuth Client Secret
  publicKey: fs.readFileSync('./keys/public.pem'),         // RS256 公钥 (可选,用于本地验证)
  issuer: 'auth-service',                                 // JWT issuer,默认 'auth-service'
});

publicKey 可选。提供时启用本地 JWT 验证(零网络延迟);不提供时需调用 introspectToken 远程验证。


获取公钥

服务提供标准 JWKS 端点,可在运行时动态获取公钥。

JWKS 地址:

GET https://api-test.scorehub.cn/auth/api/v1/.well-known/jwks.json

动态获取并初始化(推荐)

import { AuthClient } from '@scorehub/auth-sdk';

async function createAuthClient() {
  const res = await fetch('https://api-test.scorehub.cn/auth/api/v1/.well-known/jwks.json');
  const { keys } = await res.json();
  const x5c = keys[0].x5c[0];

  // 还原为 PEM 格式
  const publicKey = [
    '-----BEGIN PUBLIC KEY-----',
    ...x5c.match(/.{1,64}/g),
    '-----END PUBLIC KEY-----',
  ].join('\n');

  return new AuthClient({
    serviceUrl: 'https://api-test.scorehub.cn/auth/api/v1',
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    publicKey,
  });
}

const authClient = await createAuthClient();

提前下载保存为文件

curl -s https://api-test.scorehub.cn/auth/api/v1/.well-known/jwks.json \
  | node -e "
    const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
    const x5c = d.keys[0].x5c[0];
    const pem = '-----BEGIN PUBLIC KEY-----\n' + x5c.match(/.{1,64}/g).join('\n') + '\n-----END PUBLIC KEY-----';
    process.stdout.write(pem);
  " > public.pem

然后直接读取文件初始化:

import fs from 'fs';

const authClient = new AuthClient({
  serviceUrl: 'https://api-test.scorehub.cn/auth/api/v1',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  publicKey: fs.readFileSync('./public.pem'),
});

Token 验证

本地验证(推荐,需配置 publicKey)

try {
  const payload = await authClient.verifyAccessToken(token);
  console.log(payload.sub);      // 用户 ID
  console.log(payload.username); // 用户名
  console.log(payload.roles);    // 角色列表
  console.log(payload.scopes);   // 授权范围
} catch (err) {
  // TokenExpiredError / JsonWebTokenError
}

远程内省(无 publicKey 时使用)

const result = await authClient.introspectToken(token);
if (result.active) {
  console.log(result.sub, result.username);
} else {
  // token 已失效
}

TokenPayload 结构

| 字段 | 类型 | 说明 | |------|------|------| | sub | string | 用户/Client ID | | type | 'user' | 'client' | Token 类型 | | username | string? | 用户名 | | roles | string[]? | 角色列表 | | clientId | string? | OAuth Client ID | | scopes | string[] | 授权范围 | | jti | string | JWT ID | | iss | string | 签发者 | | iat / exp | number | 签发/过期时间戳 |


用户认证

检查手机号是否已注册

const { exists } = await authClient.checkUserExists('13800138000');
if (exists) {
  // 直接走登录流程
} else {
  // 引导注册
}

公开接口,无需鉴权。适合在注册/登录前预判手机号状态。

账号密码登录

const result = await authClient.loginByPassword('username', 'password');
console.log(result.accessToken);
console.log(result.refreshToken);
console.log(result.isNewUser); // false
console.log(result.user.id, result.user.roles);

短信验证码登录(含自动注册)

// 先发送验证码
await authClient.sendSmsCode('13800138000');

// 验证码登录(手机号不存在时自动注册)
const result = await authClient.loginBySms('13800138000', '123456');
console.log(result.isNewUser); // true=本次自动注册,false=历史已存在

用户注册

// 先发送验证码
await authClient.sendSmsCode('13800138000');

const result = await authClient.register({
  username: 'testuser',
  password: 'Password123',
  mobile: '13800138000',
  code: '123456',
  realname: '张三',  // 可选
});
console.log(result.isNewUser); // true

LoginResult 结构

| 字段 | 类型 | 说明 | |------|------|------| | accessToken | string | Access Token | | refreshToken | string | Refresh Token | | expiresIn | number | Access Token 有效期(秒) | | tokenType | string | Token 类型,通常为 Bearer | | isNewUser | boolean | true 表示本次请求中创建了新用户 | | user | object | 当前登录用户信息 |

重置密码

await authClient.sendSmsCode('13800138000');
await authClient.resetPassword('13800138000', '123456', 'NewPassword123');

修改密码(已登录)

await authClient.changePassword(
  accessToken,
  'OldPassword123',
  'NewPassword123',
);

该方法用于已登录用户使用旧密码修改为新密码,需要传入当前用户的 Access Token。

校验短信验证码(一次性)

await authClient.verifySmsCode('13800138000', '123456');
// 成功则继续,失败抛出异常

Token 管理

刷新 Token

const tokenPair = await authClient.refreshToken(refreshToken);
// { access_token, refresh_token, token_type, expires_in }

吊销 Token

await authClient.revokeToken(accessToken);

服务间调用

获取 Service Token(自动缓存)

// client_credentials 方式,内置缓存,剩余有效期 < 60s 时自动刷新
const serviceToken = await authClient.getServiceToken(['read:users']);

fetch('http://other-service/api/data', {
  headers: { Authorization: `Bearer ${serviceToken}` },
});

根据 ID 查询用户信息

const user = await authClient.getUserById('user-uuid');
// { sub, username, mobile, email, realname, nickname, avatar, roles }

管理端操作

使用 service token 鉴权,无需短信验证码,适合后台管理系统调用。

管理端创建用户

const user = await authClient.adminCreateUser({
  username: 'newuser',
  password: 'Password123',
  mobile: '13900139000',
  realname: '李四',  // 可选
});
// { id, username, mobile }

管理端重置用户密码

await authClient.adminResetPassword('user-uuid', 'NewPassword123');

设置用户状态

// 将用户状态设为 DISABLED(禁用)
await authClient.setUserStatus('user-uuid', 'DISABLED');

// 将用户状态恢复为 ACTIVE(启用)
await authClient.setUserStatus('user-uuid', 'ACTIVE');

// 将用户状态设为 DELETED(软删除)
await authClient.setUserStatus('user-uuid', 'DELETED');

状态枚举:ACTIVE(正常)、DISABLED(禁用)、DELETED(已删除)。 使用 service token 鉴权,适合后台管理系统调用。

删除用户(软删除)

await authClient.deleteUser('user-uuid');

软删除:将用户状态设为 DELETED,保留数据用于审计,不物理删除。等效于 setUserStatus(userId, 'DELETED')

管理端修改用户信息

// 修改用户名
await authClient.adminUpdateUser('user-uuid', { username: 'new_username' });

// 修改手机号
await authClient.adminUpdateUser('user-uuid', { mobile: '13900139001' });

// 修改真实姓名
await authClient.adminUpdateUser('user-uuid', { realname: '张三' });

// 同时修改多个字段
await authClient.adminUpdateUser('user-uuid', {
  username: 'new_username',
  mobile: '13900139001',
  realname: '张三',
});

使用 service token 鉴权,无需短信验证码,适合后台管理系统调用。 usernamemobile 唯一,冲突时抛出 409 错误(err.authCode 对应业务错误码)。


框架中间件

Koa

import Koa from 'koa';
import { AuthClient, koaAuthMiddleware } from '@scorehub/auth-sdk';

const app = new Koa();
const authClient = new AuthClient({ /* ... */ });

app.use(koaAuthMiddleware(authClient, {
  exclude: ['/health', '/api/v1/auth/login'],  // 不需认证的路径
}));

// 路由中获取用户信息
app.use(async (ctx) => {
  const user = ctx.state.user;    // TokenPayload
  const admin = ctx.state.admin;  // 兼容旧代码: admin._id / admin.username
});

Express

import express from 'express';
import { AuthClient, expressAuthMiddleware } from '@scorehub/auth-sdk';

const app = express();
const authClient = new AuthClient({ /* ... */ });

app.use(expressAuthMiddleware(authClient, {
  exclude: ['/health'],
}));

// 路由中获取用户信息
app.get('/profile', (req, res) => {
  const user = (req as any).user; // TokenPayload
  res.json(user);
});

NestJS

// app.module.ts
import { Module } from '@nestjs/common';
import { AuthClient, AUTH_CLIENT } from '@scorehub/auth-sdk';

@Module({
  providers: [
    {
      provide: AUTH_CLIENT,
      useFactory: () => new AuthClient({
        serviceUrl: process.env.AUTH_SERVICE_URL,
        clientId: process.env.AUTH_CLIENT_ID,
        clientSecret: process.env.AUTH_CLIENT_SECRET,
        publicKey: fs.readFileSync('./keys/public.pem'),
      }),
    },
  ],
  exports: [AUTH_CLIENT],
})
export class AppModule {}

// controller
import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '@scorehub/auth-sdk';

@UseGuards(AuthGuard)
@Controller('users')
export class UsersController {
  @Get('me')
  getProfile(@Request() req) {
    return req.user; // TokenPayload
  }
}

Token 提取规则

中间件按以下优先级提取 Token:

| 优先级 | 来源 | |--------|------| | 1 | Authorization: Bearer <token> | | 2 | x-access-token Header | | 3 | access-token Header(仅 Koa)| | 4 | access_token Header(仅 Koa)| | 5 | ?token= Query 参数 |


错误处理

所有方法在失败时抛出带 authCode 属性的 Error:

try {
  await authClient.loginByPassword(username, password);
} catch (err: any) {
  console.log(err.message);   // 错误描述
  console.log(err.authCode);  // 业务错误码,如 1001 (Unauthorized)
}