Pinme Auth

glitternetwork/pinme/skills/pinme-auth

作者 glitternetwork7822b0501607786958ecb458f3bd02a061933efa无许可证3.7K 个星标收录于 2026年10月9日更新于 2026年10月9日仓库3周前更新

Use when a PinMe project (Worker TypeScript) needs to integrate user authentication — creating email/password users, verifying id_tokens, querying user info, or listing users via Identity Platform auth proxy APIs.

AI 生成的概览

指导在 PinMe Worker(TypeScript)中调用 PinMe 身份平台认证代理 API。

功能
该技能说明 PinMe Worker(TypeScript)如何调用平台的身份平台认证代理接口:创建邮箱密码用户、校验 id_token、查询单个用户以及分页列出用户。它给出必需的凭证(X-API-Key 请求头和 project_name 查询参数)、通用 TypeScript 类型、请求体、错误码以及各接口的代码示例。此外还介绍使用 public_client_config 配置前端 Firebase Auth,以及典型的注册和 Google 登录流程。
适用场景
当 PinMe Worker 项目需要集成用户认证时使用,例如添加邮箱密码注册、校验前端签发的 id_token、读取用户资料或枚举用户。也适用于将前端 Firebase Auth 客户端与执行服务端令牌校验的 Worker 对接。
运行要求
运行于 PinMe Worker TypeScript 环境,需要 D1 数据库绑定以及环境变量 API_KEY、PROJECT_NAME 和可选的 BASE_URL,并需要访问 PinMe API 的网络。前端集成还需要 Firebase Auth SDK 和 public_client_config 中的配置值。该技能不附带脚本,仅包含说明和代码示例。

PinMe Worker Auth API Integration

Guides how to call PinMe platform's Identity Platform auth proxy APIs in a PinMe Worker (TypeScript).

Environment Variables

typescript
// backend/src/worker.tsexport interface Env {  DB: D1Database;  API_KEY: string;       // 项目 API Key — 用于所有 auth 接口认证  PROJECT_NAME: string;  // 项目名 — 所有 auth 接口必须同时传递  BASE_URL?: string;     // 可选,默认 https://pinme.cloud}

API_KEY 和 PROJECT_NAME 是所有 auth 接口的必填凭证,缺一不可。


认证方式(所有接口通用)

参数传递方式必填说明
X-API-Key请求头是项目 API Key
project_nameQuery 参数是必须与 X-API-Key 对应同一个项目

服务端会先校验这两个字段是否匹配同一个项目,再从项目配置中取出 tenant_id,然后转调 Identity Platform。


通用错误

场景HTTPdata.error
缺少 X-API-Key401X-API-Key header is required
缺少 project_name400project_name is required
API Key 和项目不匹配401Invalid API key or project name
项目未配置认证租户400Auth service not configured for this project

通用 TypeScript 类型

typescript
type ApiEnvelope<T> = {  code: number   // 200=成功,其他=失败  msg: string    // "ok" | "fail" | "invalid param"  data: T}
type ApiErrorData = { error?: string }
type UserInfo = {  uid: string  email: string  display_name: string  photo_url?: string  disabled: boolean  email_verified: boolean}

API 1: 创建用户

Endpoint: POST {BASE_URL}/api/v1/auth/create_user?project_name={project_name}

仅用于邮箱密码注册。成功时用户已创建且验证邮件已发出;失败时自动回滚,不会留下僵尸账号。

创建成功后用户默认仍是"未验证"状态,需点击邮件验证链接后,verify_token 才能通过校验。

请求体

json
{ "email": "[email protected]", "password": "Test@12345678", "display_name": "Alice" }
字段类型必填
emailstring是
passwordstring是
display_namestring否

错误

场景HTTPdata.error
缺少 email/password400email and password are required
上游创建失败502Failed to create user
发送验证邮件失败500Failed to send verification email. Please try again.

TypeScript 示例

typescript
async function createAuthUser(  env: Env,  payload: { email: string; password: string; display_name?: string }): Promise<{ user?: UserInfo; error?: string }> {  const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';  const resp = await fetch(    `${baseUrl}/api/v1/auth/create_user?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,    {      method: 'POST',      headers: { 'X-API-Key': env.API_KEY, 'Content-Type': 'application/json' },      body: JSON.stringify(payload),    }  );  const result = await resp.json() as ApiEnvelope<UserInfo | ApiErrorData>;  if (!resp.ok || result.code !== 200) {    return { error: (result.data as ApiErrorData)?.error ?? result.msg };  }  return { user: result.data as UserInfo };}

API 2: 校验 id_token

Endpoint: POST {BASE_URL}/api/v1/auth/verify_token?project_name={project_name}

校验前端登录后拿到的 id_token(邮箱密码或 Google 登录均适用)。

注意: token 合法但邮箱未验证时返回 403,不是 401。

请求体

json
{ "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6..." }

成功响应 data

typescript
type VerifyTokenData = {  uid: string  email?: string  tenant_id: string  claims: Record<string, unknown>}

错误

场景HTTPdata.error
缺少 id_token400id_token is required
token 无效或过期401Invalid or expired token
邮箱未验证403Email not verified. Please check your inbox and verify your email address.

TypeScript 示例

typescript
async function verifyAuthToken(  env: Env,  idToken: string): Promise<{ uid?: string; email?: string; error?: string; emailNotVerified?: boolean }> {  const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';  const resp = await fetch(    `${baseUrl}/api/v1/auth/verify_token?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,    {      method: 'POST',      headers: { 'X-API-Key': env.API_KEY, 'Content-Type': 'application/json' },      body: JSON.stringify({ id_token: idToken }),    }  );  const result = await resp.json() as ApiEnvelope<VerifyTokenData | ApiErrorData>;  if (!resp.ok || result.code !== 200) {    const error = (result.data as ApiErrorData)?.error ?? result.msg;    return { error, emailNotVerified: resp.status === 403 };  }  const data = result.data as VerifyTokenData;  return { uid: data.uid, email: data.email };}

API 3: 查询单个用户

Endpoint: GET {BASE_URL}/api/v1/auth/user?project_name={project_name}&uid={uid}

错误

场景HTTPdata.error
缺少 uid400uid is required
用户不存在404User not found
上游查询失败502Failed to get user

TypeScript 示例

typescript
async function getAuthUser(env: Env, uid: string): Promise<{ user?: UserInfo; error?: string }> {  const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';  const resp = await fetch(    `${baseUrl}/api/v1/auth/user?project_name=${encodeURIComponent(env.PROJECT_NAME)}&uid=${encodeURIComponent(uid)}`,    { method: 'GET', headers: { 'X-API-Key': env.API_KEY } }  );  const result = await resp.json() as ApiEnvelope<UserInfo | ApiErrorData>;  if (!resp.ok || result.code !== 200) {    return { error: (result.data as ApiErrorData)?.error ?? result.msg };  }  return { user: result.data as UserInfo };}

API 4: 列出用户(分页)

Endpoint: GET {BASE_URL}/api/v1/auth/list_users?project_name={project_name}

默认 max_results=100,最大 1000。通过 next_page_token 循环翻页。

Query 参数

参数必填说明
project_name是项目名
page_token否分页游标
max_results否每页数量,1–1000

TypeScript 示例

typescript
async function listAuthUsers(  env: Env,  options: { pageToken?: string; maxResults?: number } = {}): Promise<{ users?: UserInfo[]; nextPageToken?: string; error?: string }> {  const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';  const url = new URL('/api/v1/auth/list_users', baseUrl);  url.searchParams.set('project_name', env.PROJECT_NAME);  if (options.pageToken) url.searchParams.set('page_token', options.pageToken);  if (options.maxResults) url.searchParams.set('max_results', String(options.maxResults));
  const resp = await fetch(url.toString(), { method: 'GET', headers: { 'X-API-Key': env.API_KEY } });  const result = await resp.json() as ApiEnvelope<{ users: UserInfo[]; next_page_token?: string } | ApiErrorData>;  if (!resp.ok || result.code !== 200) {    return { error: (result.data as ApiErrorData)?.error ?? result.msg };  }  const data = result.data as { users: UserInfo[]; next_page_token?: string };  return { users: data.users, nextPageToken: data.next_page_token };}
// 批量遍历所有用户示例async function* iterAllUsers(env: Env) {  let pageToken: string | undefined;  do {    const { users, nextPageToken, error } = await listAuthUsers(env, { pageToken, maxResults: 1000 });    if (error) throw new Error(error);    for (const user of users ?? []) yield user;    pageToken = nextPageToken;  } while (pageToken);}

前端集成(Firebase Auth)

create_worker 响应中包含 public_client_config,前端用它初始化 Firebase Auth SDK。

两种 api_key 区分

字段用途是否可暴露到浏览器
data.api_key项目 API Key,调用本文所有代理接口不能,只给 Worker/服务端
data.public_client_config.auth_api_keyFirebase Web API Key,初始化前端登录 SDK可以

public_client_config 字段说明

字段前端用途
public_client_config.auth_api_keyinitializeApp({ apiKey })
public_client_config.auth_domaininitializeApp({ authDomain })
public_client_config.auth_project_idinitializeApp({ projectId })
public_client_config.tenant_idauth.tenantId = config.tenant_id(必须设置,否则 token 归属错误)

前端 TypeScript 示例

typescript
import { initializeApp } from 'firebase/app'import {  type Auth,  getAuth,  GoogleAuthProvider,  signInWithEmailAndPassword,  signInWithPopup,} from 'firebase/auth'
type PublicClientConfig = {  tenant_id: string  auth_api_key: string  auth_domain: string  auth_project_id: string}
export function createProjectAuth(config: PublicClientConfig): Auth {  const app = initializeApp({    apiKey: config.auth_api_key,    authDomain: config.auth_domain,    projectId: config.auth_project_id,  })  const auth = getAuth(app)  auth.tenantId = config.tenant_id  // 必须设置,确保 token 归属正确租户  return auth}
// 邮箱密码登录,返回 id_tokenexport async function loginWithEmail(auth: Auth, email: string, password: string): Promise<string> {  const credential = await signInWithEmailAndPassword(auth, email, password)  return credential.user.getIdToken()}
// Google 登录,返回 id_tokenexport async function loginWithGoogle(auth: Auth): Promise<string> {  const credential = await signInWithPopup(auth, new GoogleAuthProvider())  return credential.user.getIdToken()}
// 用法示例// pinme create 会自动将 public_client_config 写入 frontend/src/utils/config.tsimport { public_client_config } from '../utils/config'
const auth = createProjectAuth(public_client_config)const idToken = await loginWithGoogle(auth)// 然后把 idToken 发给自己的 Worker,由 Worker 调用 verify_token

前端只负责登录和拿 id_token,不要直接持有项目 api_key。verify_token 必须由 Worker/服务端代调。 frontend/src/utils/config.ts 由 pinme create 自动生成,无需手动创建。


典型调用链路

邮箱密码注册流程:

  1. create_user → 创建用户并发出验证邮件
  2. 用户点击邮件链接完成验证
  3. 前端登录拿到 id_token
  4. verify_token → 校验 token,取得 uid
  5. 需要时再调 getAuthUser 读取完整用户信息

Google 登录流程:

  1. 前端完成 Google Sign-In,拿到 id_token
  2. verify_token → 校验 token(无需调用 create_user)

易错点

错误正确做法
只传 X-API-Key,忘记 project_name每个请求都要同时带 X-API-Key header 和 project_name query
verify_token 返回 403 时当 token 失效处理403 = 邮箱未验证,提示用户检查邮箱;401 才是 token 失效
create_user 成功就认为邮箱已验证创建成功只代表验证邮件已发,用户必须点击后才算验证
list_users 只取第一页有 next_page_token 时需继续请求,直到为空
成功判断只看 resp.ok同时判断 resp.ok && result.code === 200

来源与署名

来源:glitternetwork/pinme位于skills/pinme-auth提交7822b05

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架