@user-center/next-better-auth 0.0.0-stage → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +52 -2
- package/dist/integration-contracts/src/compatibility.d.ts +9 -0
- package/dist/integration-contracts/src/compatibility.js +3 -0
- package/dist/integration-contracts/src/config.d.ts +15 -0
- package/dist/integration-contracts/src/config.js +17 -0
- package/dist/integration-contracts/src/environment.d.ts +25 -0
- package/dist/integration-contracts/src/environment.js +108 -0
- package/dist/integration-contracts/src/metadata.d.ts +4 -0
- package/dist/integration-contracts/src/metadata.js +71 -0
- package/dist/integration-contracts/src/public.d.ts +51 -0
- package/dist/integration-contracts/src/public.js +134 -0
- package/dist/next-better-auth/src/client.d.ts +10 -0
- package/dist/next-better-auth/src/client.js +36 -0
- package/dist/next-better-auth/src/config.d.ts +2 -0
- package/dist/next-better-auth/src/config.js +1 -0
- package/dist/next-better-auth/src/public.d.ts +1 -0
- package/dist/next-better-auth/src/public.js +1 -0
- package/dist/next-better-auth/src/server.d.ts +142 -0
- package/dist/next-better-auth/src/server.js +298 -0
- package/dist/reference/LICENSE +21 -0
- package/dist/reference/README.md +10 -0
- package/dist/reference/example-next/LICENSE +21 -0
- package/dist/reference/example-next/README.md +27 -0
- package/dist/reference/example-next/environment.example.txt +8 -0
- package/dist/reference/example-next/gitignore.txt +5 -0
- package/dist/reference/example-next/integration.json +10 -0
- package/dist/reference/example-next/next.config.ts +3 -0
- package/dist/reference/example-next/package.json +37 -0
- package/dist/reference/example-next/pnpm-workspace.yaml +4 -0
- package/dist/reference/example-next/profiles.example.json +34 -0
- package/dist/reference/example-next/scripts/schema.mts +22 -0
- package/dist/reference/example-next/src/app/actions.tsx +15 -0
- package/dist/reference/example-next/src/app/api/auth/[...all]/route.ts +4 -0
- package/dist/reference/example-next/src/app/api/me/route.ts +9 -0
- package/dist/reference/example-next/src/app/auth-error/page.tsx +2 -0
- package/dist/reference/example-next/src/app/dashboard/page.tsx +2 -0
- package/dist/reference/example-next/src/app/layout.tsx +2 -0
- package/dist/reference/example-next/src/app/login/page.tsx +6 -0
- package/dist/reference/example-next/src/app/page.tsx +2 -0
- package/dist/reference/example-next/src/app/projects/[id]/page.tsx +5 -0
- package/dist/reference/example-next/src/lib/auth.ts +17 -0
- package/dist/reference/example-next/src/lib/config.ts +45 -0
- package/dist/reference/example-next/src/lib/protected.tsx +13 -0
- package/dist/reference/example-next/tsconfig.json +37 -0
- package/dist/reference/guide/agent-prompt.md +68 -0
- package/dist/reference/guide/doctor.md +21 -0
- package/dist/reference/guide/environments.md +18 -0
- package/dist/reference/guide/index.md +188 -0
- package/dist/reference/guide/support.md +12 -0
- package/package.json +61 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getMigrations } from "better-auth/db/migration";
|
|
2
|
+
const {loadSampleEnvironment, getSampleServerConfiguration} = await import("../src/lib/config.js");
|
|
3
|
+
try {
|
|
4
|
+
loadSampleEnvironment();
|
|
5
|
+
const {getIntegration} = await import("../src/lib/auth.js");
|
|
6
|
+
const auth = await getIntegration().getAuth();
|
|
7
|
+
const plan = await getMigrations(auth.options, {throwOnUnsafe: true});
|
|
8
|
+
if (!process.argv.includes("--apply")) {
|
|
9
|
+
console.log(await plan.compileMigrations());
|
|
10
|
+
console.log('CREATE UNIQUE INDEX IF NOT EXISTS sdk_account_identity_unique ON account ("providerId", "accountId");');
|
|
11
|
+
} else {
|
|
12
|
+
await plan.runMigrations();
|
|
13
|
+
const {Pool} = await import("pg"); const pool = new Pool({connectionString: getSampleServerConfiguration().databaseUrl});
|
|
14
|
+
try {
|
|
15
|
+
await pool.query('CREATE UNIQUE INDEX IF NOT EXISTS sdk_account_identity_unique ON account ("providerId", "accountId")');
|
|
16
|
+
const result = await pool.query("SELECT indexdef FROM pg_indexes WHERE schemaname = current_schema() AND indexname = 'sdk_account_identity_unique'");
|
|
17
|
+
if (!result.rows[0]?.indexdef.includes('UNIQUE')) throw new Error();
|
|
18
|
+
console.log("HOST_SCHEMA_APPLIED_AND_IDENTITY_INDEX_PRESENT");
|
|
19
|
+
} finally { await pool.end(); }
|
|
20
|
+
}
|
|
21
|
+
process.exit(0);
|
|
22
|
+
} catch { console.error("HOST_SCHEMA_PREPARATION_FAILED"); process.exit(1); }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { createUserCenterClient } from "@user-center/next-better-auth/client";
|
|
4
|
+
import type { PublicConfig } from "@user-center/next-better-auth/public";
|
|
5
|
+
export function AuthActions({config, returnTo, logout = false}: {config: PublicConfig; returnTo?: string; logout?: boolean}) {
|
|
6
|
+
const [message, setMessage] = useState(""); const [pending, setPending] = useState(false);
|
|
7
|
+
async function run() {
|
|
8
|
+
setPending(true); setMessage(""); const client = createUserCenterClient(config);
|
|
9
|
+
try {
|
|
10
|
+
if (logout) { await client.signOut(); window.location.assign("/"); }
|
|
11
|
+
else { const result = await client.startLogin(returnTo === undefined ? {} : {returnTo}); window.location.assign(result.url); }
|
|
12
|
+
} catch { setMessage(logout ? "退出结果尚未确认,请检查当前会话后再操作。" : "无法开始登录,请检查配置或重新从此页面发起。"); setPending(false); }
|
|
13
|
+
}
|
|
14
|
+
return <><button type="button" disabled={pending} onClick={run}>{pending ? "处理中…" : logout ? "退出此平台" : "前往用户中心登录"}</button><p role="status">{message}</p></>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { getIntegration } from "../../../../lib/auth";
|
|
2
|
+
export const runtime = "nodejs";
|
|
3
|
+
export async function GET(request: Request) { return getIntegration().handlers.GET(request); }
|
|
4
|
+
export async function POST(request: Request) { return getIntegration().handlers.POST(request); }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getIntegration } from "../../../lib/auth";
|
|
2
|
+
export const runtime = "nodejs";
|
|
3
|
+
export async function GET(request: Request) {
|
|
4
|
+
try {
|
|
5
|
+
const principal = await getIntegration().getPrincipal(request.headers);
|
|
6
|
+
if (principal.status !== "authenticated") return Response.json({status: principal.status}, {status: principal.status === "anonymous" ? 401 : 403, headers: {"Cache-Control": "no-store"}});
|
|
7
|
+
return Response.json({businessUserId: principal.user.id}, {headers: {"Cache-Control": "no-store"}});
|
|
8
|
+
} catch { return Response.json({code: "AUTHENTICATION_UNAVAILABLE"}, {status: 503, headers: {"Cache-Control": "no-store"}}); }
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { getSampleConfiguration } from "../../lib/config";
|
|
2
|
+
import { AuthActions } from "../actions";
|
|
3
|
+
export default async function Login({searchParams}: {searchParams: Promise<{returnTo?: string}>}) {
|
|
4
|
+
const query = await searchParams;
|
|
5
|
+
return <><h1>登录此平台</h1><AuthActions config={getSampleConfiguration().publicConfig} returnTo={query.returnTo}/></>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ProtectedPage } from "../../../lib/protected";
|
|
2
|
+
export default async function Project({params, searchParams}: {params: Promise<{id: string}>; searchParams: Promise<{tab?: string}>}) {
|
|
3
|
+
const {id} = await params; const {tab} = await searchParams;
|
|
4
|
+
return <ProtectedPage returnTo={"/projects/" + encodeURIComponent(id) + (tab ? "?tab=" + encodeURIComponent(tab) : "")}/>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
import { createUserCenterAuth } from "@user-center/next-better-auth/server";
|
|
3
|
+
import { Pool } from "pg";
|
|
4
|
+
import { getSampleServerConfiguration } from "./config";
|
|
5
|
+
|
|
6
|
+
const state = globalThis as typeof globalThis & { sdkSamplePool?: Pool; sdkSampleAuth?: ReturnType<typeof makeAuth> };
|
|
7
|
+
function makeAuth() {
|
|
8
|
+
const {manifest, environment, clientSecret, sessionSecret, databaseUrl} = getSampleServerConfiguration();
|
|
9
|
+
const database = state.sdkSamplePool ??= new Pool({connectionString: databaseUrl, max: 5, connectionTimeoutMillis: 5000, query_timeout: 5000, statement_timeout: 5000});
|
|
10
|
+
database.on("error", () => { /* Host monitoring may record a finite storage failure code. */ });
|
|
11
|
+
return createUserCenterAuth({manifest, environment, database, clientSecret, sessionSecret, authUserPolicy: "allow-new",
|
|
12
|
+
// This new platform explicitly uses its own authentication user ID as business ID.
|
|
13
|
+
// An established platform should instead read an explicit identity mapping here.
|
|
14
|
+
async resolveBusinessUser(identity) { return {status: "ready", user: {id: identity.authUserId}}; },
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function getIntegration() { return state.sdkSampleAuth ??= makeAuth(); }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
import { readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { assertPostgresBinding, describeIntegration, resolveEnvironmentConfig } from "@user-center/next-better-auth/config";
|
|
5
|
+
import legacyManifest from "../../integration.json";
|
|
6
|
+
import * as nextEnvironment from "@next/env";
|
|
7
|
+
const {loadEnvConfig} = ("default" in nextEnvironment ? nextEnvironment.default : nextEnvironment) as typeof nextEnvironment;
|
|
8
|
+
|
|
9
|
+
function resolveConfiguration() {
|
|
10
|
+
let selected: ReturnType<typeof resolveEnvironmentConfig> | undefined;
|
|
11
|
+
if (process.env.USER_CENTER_CONFIG_FILE) {
|
|
12
|
+
const file = resolve(process.cwd(), process.env.USER_CENTER_CONFIG_FILE);
|
|
13
|
+
if (statSync(file).size > 65536) throw new Error("ENVIRONMENT_MISMATCH");
|
|
14
|
+
let input: unknown;
|
|
15
|
+
try { input = JSON.parse(readFileSync(file, "utf8")); } catch { throw new Error("ENVIRONMENT_MISMATCH"); }
|
|
16
|
+
selected = resolveEnvironmentConfig(input, {
|
|
17
|
+
runtimeEnvironment: process.env.USER_CENTER_RUNTIME_ENVIRONMENT ?? "",
|
|
18
|
+
...(process.env.USER_CENTER_TARGET_ENVIRONMENT === undefined ? {} : {targetEnvironment: process.env.USER_CENTER_TARGET_ENVIRONMENT}),
|
|
19
|
+
});
|
|
20
|
+
} else if (process.env.USER_CENTER_RUNTIME_ENVIRONMENT !== undefined || process.env.USER_CENTER_TARGET_ENVIRONMENT !== undefined) {
|
|
21
|
+
// Selecting an environment may never silently fall back to the legacy manifest.
|
|
22
|
+
throw new Error("ENVIRONMENT_MISMATCH");
|
|
23
|
+
}
|
|
24
|
+
const manifest = selected?.manifest ?? legacyManifest;
|
|
25
|
+
const publicConfig = describeIntegration(manifest);
|
|
26
|
+
if (selected && !selected.database) throw new Error("ENVIRONMENT_MISMATCH");
|
|
27
|
+
return {manifest, publicConfig, databaseBinding: selected?.database, ...(selected ? {environment: selected.environment} : {})};
|
|
28
|
+
}
|
|
29
|
+
const state = globalThis as typeof globalThis & {sdkSampleConfiguration?: ReturnType<typeof resolveConfiguration>};
|
|
30
|
+
// Capture one configuration for the server and login page. Restart after changes.
|
|
31
|
+
export function getSampleConfiguration() { return state.sdkSampleConfiguration ??= resolveConfiguration(); }
|
|
32
|
+
export function loadSampleEnvironment() {
|
|
33
|
+
loadEnvConfig(process.cwd(), (process.env.NODE_ENV ?? "development") === "development");
|
|
34
|
+
return getSampleConfiguration();
|
|
35
|
+
}
|
|
36
|
+
export function getSampleServerConfiguration() {
|
|
37
|
+
const selected = getSampleConfiguration();
|
|
38
|
+
const {manifest} = selected;
|
|
39
|
+
const required = (key: string) => { const value = process.env[key]; if (!value?.trim()) throw new Error("SAMPLE_CONFIGURATION_REQUIRED"); return value; };
|
|
40
|
+
const clientSecret = required(manifest.clientSecretEnv), sessionSecret = required(manifest.sessionSecretEnv);
|
|
41
|
+
const databaseUrl = required(selected.databaseBinding?.urlEnv ?? "DATABASE_URL");
|
|
42
|
+
if (selected.databaseBinding) assertPostgresBinding(databaseUrl, selected.databaseBinding);
|
|
43
|
+
if (clientSecret === databaseUrl || sessionSecret === databaseUrl) throw new Error("ENVIRONMENT_MISMATCH");
|
|
44
|
+
return {...selected, clientSecret, sessionSecret, databaseUrl};
|
|
45
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { headers } from "next/headers";
|
|
2
|
+
import { redirect } from "next/navigation";
|
|
3
|
+
import { getIntegration } from "./auth";
|
|
4
|
+
import { AuthActions } from "../app/actions";
|
|
5
|
+
export async function ProtectedPage({returnTo}: {returnTo: string}) {
|
|
6
|
+
const requestHeaders = await headers();
|
|
7
|
+
let state;
|
|
8
|
+
try { const integration = getIntegration(); state = {principal: await integration.getPrincipal(requestHeaders), config: integration.publicConfig}; } catch { return <p role="alert">认证或业务身份暂不可用,请稍后重新访问。</p>; }
|
|
9
|
+
const {principal, config} = state;
|
|
10
|
+
if (principal.status === "anonymous") redirect("/login?returnTo=" + encodeURIComponent(returnTo));
|
|
11
|
+
if (principal.status === "mapping-required") return <p role="alert">你已登录,业务身份尚未关联,暂不能访问此页面。</p>;
|
|
12
|
+
return <><h1>受保护页面</h1><p>业务用户:{principal.user.id}</p><p>目标:{returnTo}</p><a href="/api/me">读取受保护 API</a><AuthActions config={config} logout/></>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": [
|
|
5
|
+
"dom",
|
|
6
|
+
"dom.iterable",
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"module": "esnext",
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
"plugins": [
|
|
19
|
+
{
|
|
20
|
+
"name": "next"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"allowJs": true,
|
|
24
|
+
"incremental": true
|
|
25
|
+
},
|
|
26
|
+
"include": [
|
|
27
|
+
"next-env.d.ts",
|
|
28
|
+
"**/*.ts",
|
|
29
|
+
"**/*.tsx",
|
|
30
|
+
"**/*.mts",
|
|
31
|
+
".next/types/**/*.ts",
|
|
32
|
+
".next/dev/types/**/*.ts"
|
|
33
|
+
],
|
|
34
|
+
"exclude": [
|
|
35
|
+
"node_modules"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 给目标工程 Agent 的接入 Prompt 0.1.10
|
|
2
|
+
|
|
3
|
+
将本文与同目录完整指南、环境说明和包内参考样例一起提供。只填写非敏感路径或变量名;秘密留在目标工程忽略配置,不发聊天。
|
|
4
|
+
|
|
5
|
+
## 任务与输入
|
|
6
|
+
|
|
7
|
+
在目标工程接入已核验的 SDK/CLI 0.1.10,完成可审阅的配置、认证路由、页面/API 保护、业务映射和本地回归。保留目标工程规则、未提交改动、用户 ID、业务数据与已有权限。SDK 不改工程、不安装宿主依赖、不执行迁移;工程 Agent 在操作者明确范围内完成接线,不依赖中心私有源码或数据库。
|
|
8
|
+
|
|
9
|
+
| 输入 | 待填值 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 目标工程与只读交付根目录 | `<明确路径>` |
|
|
12
|
+
| 接入配置模式 | `<推荐 profiles,或保留旧 Manifest v1>` |
|
|
13
|
+
| 平台 runtime | `<development / staging / production>` |
|
|
14
|
+
| 目标中心 target | `<默认同 runtime,仅 development 可跨环境>` |
|
|
15
|
+
| profiles 文件或单 Manifest 路径 | `<非敏感 JSON>` |
|
|
16
|
+
| 平台 HTTPS origin 与 authBasePath | `<实际地址,默认 /api/auth>` |
|
|
17
|
+
| 各目标 issuer 与专用 Client ID | `<已登记值,缺少时列待准备>` |
|
|
18
|
+
| 秘密配置位置 | `<只写路径或变量名,不写值>` |
|
|
19
|
+
| 适配器、存储及迁移范围 | `<明确开发资源与操作范围>` |
|
|
20
|
+
| 认证用户策略 | `<allow-new 或 existing-only>` |
|
|
21
|
+
| 业务用户映射、返回路径及允许 query | `<宿主已确定规则>` |
|
|
22
|
+
| 公共 CA 与中心环境声明状态 | `<证书信任及目标中心是否已部署声明>` |
|
|
23
|
+
|
|
24
|
+
缺少资源时继续不依赖它们的工作,并记录具体待办。本文不自行扩大生产 Client 创建、数据库迁移、部署或真实发信范围,操作者本次明确指令优先。
|
|
25
|
+
|
|
26
|
+
## 1. 核对交付与工程
|
|
27
|
+
|
|
28
|
+
读取目标 AGENTS.md、真实依赖和锁文件、认证路由、适配器及业务用户模型;Next 实现前读取目标安装版本的 node_modules/next/dist/docs。当前候选面向 Node、Next App Router、ESM/TypeScript、Better Auth 1.7.12 和 confidential Web,不声明 Public Client、Edge 或任意插件组合支持。
|
|
29
|
+
|
|
30
|
+
读取随包 SDK 指南、环境说明、支持范围及独立样例,先核对实际拿到的版本与分发来源。从官方 registry 安装固定版本并更新宿主锁文件:`pnpm add --save-exact better-auth@1.7.12 server-only@0.0.1 @user-center/next-better-auth@0.1.10`,CLI 使用 `pnpm add -D --save-exact @user-center/cli@0.1.10`。离线交付可按[交付摘要](index.md)核对原 tgz、release-manifest.json 和 SHA256SUMS,改用可信归档路径。独立 Node 服务端脚本保留 --conditions=react-server。只使用公开导出,不导入私有 dist、中心源码或 workspace 源码链接,不覆盖已有版本产物。
|
|
31
|
+
|
|
32
|
+
## 2. 选择环境与登记回调
|
|
33
|
+
|
|
34
|
+
profiles 由宿主服务端读取,通过 resolveEnvironmentConfig(profiles, {runtimeEnvironment, targetEnvironment?}) 选取。默认 target 与 runtime 相同;staging/production 跨环境应拒绝。本地改连远端时,平台 origin、数据库和会话密钥仍为 development,Client 从 development.clients[target] 取得。
|
|
35
|
+
|
|
36
|
+
在目标中心登记本地回调专用 Client,本地应用在表单选择“本地开发”。表单环境不切换中心,不复用远端生产平台的 Client、origin 或回调。登记须满足中心原有创建资格及邮箱要求。
|
|
37
|
+
|
|
38
|
+
用 describeRegistration({issuer,platformOrigin,authBasePath?}) 或 describeIntegration(selected.manifest) 得到完整 callbackUrl。SDK Provider ID 由 issuer 派生,不能写回原 Generic OAuth 的固定 user-center。不同中心的同名 subject 不自动关联,不能凭 email 合并旧账号。
|
|
39
|
+
|
|
40
|
+
profiles 使用样例的 USER_CENTER_CONFIG_FILE、USER_CENTER_RUNTIME_ENVIRONMENT 和可选 USER_CENTER_TARGET_ENVIRONMENT,是样例宿主的加载约定,SDK 库不会自行读取这些变量或文件。staging 通常 NODE_ENV=production;本地独立加载器未设置 NODE_ENV 时按 development 读取文件,`.env.staging` 不会自动加载。改配置后重启。
|
|
41
|
+
|
|
42
|
+
## 3. 创建实例与准备存储
|
|
43
|
+
|
|
44
|
+
manifest 与 environment 一起传给 createUserCenterAuth,保留严格检查。Client Secret 和 sessionSecret 分别从所选变量读取,不回退另一环境或旧平铺配置。sessionSecret 至少 32 字符,独立于 Client Secret;不放进 NEXT_PUBLIC、浏览器 props、日志或命令参数。
|
|
45
|
+
|
|
46
|
+
target 中心必须已部署且在 Discovery 返回相符的 user_center_environment。缺失或不符时保留 ENVIRONMENT_MISMATCH,先核对中心部署与选择器,不去掉 environment 或降低 TLS 校验来继续。
|
|
47
|
+
|
|
48
|
+
database 适配器来自 runtime 的平台存储,不能用中心数据库。helper 返回的 database 是可选 PG 绑定描述,不是适配器;PG 宿主在建连接前用 assertPostgresBinding(url, selected.database) 校验,其他适配器明确验证其实际归属。SDK 无法证明不透明适配器连接了哪个库。
|
|
49
|
+
|
|
50
|
+
形成宿主自己的迁移计划,核对 User 主键、Session token 唯一、合法外键及 Account(providerId,accountId) 全局原子唯一约束。样例 SQL 仅适用于干净 pg 样例,不直接用于旧数据。迁移与服务端使用同一 runtime 配置;改变 target 不更换平台库,也不授予操作远端库的资格。
|
|
51
|
+
|
|
52
|
+
显式选择 authUserPolicy,并提供只读 resolveBusinessUser(identity,{requestId,signal})。业务账号创建、认领和权限由宿主负责;尊重 5 秒期限,不在回调里隐式合并用户或依赖相同邮箱认领。
|
|
53
|
+
|
|
54
|
+
## 4. 路由、页面与退出
|
|
55
|
+
|
|
56
|
+
只挂载 SDK handlers.GET/POST,不公开 getAuth() 的原始 handler。浏览器只接收 publicConfig,使用客户端 facade;登录 DTO 仅可选 returnTo,不透传 provider 或 callbackURL 等原生参数。原业务目标必须符合允许路径及 query,不能成为任意外站跳转。
|
|
57
|
+
|
|
58
|
+
页面和 API 统一使用 getPrincipal 检查认证、业务映射及宿主权限,区分匿名、映射未完成和依赖故障。退出收到 SIGN_OUT_UNCONFIRMED 时不显示成功,不自动重试;平台退出不宣称撤销中心或其他平台会话。协议、state、nonce、PKCE、签名与换码由官方库和 SDK 处理,不重放或自动重试授权码。
|
|
59
|
+
|
|
60
|
+
## 5. 诊断与验收
|
|
61
|
+
|
|
62
|
+
doctor 只接收已选中的单份 Manifest v1。profiles 使用者可由宿主显式导出 selected.manifest 为非敏感 JSON;不要把 profiles 文件直接传给 --config,不添加不存在的 runtime/target CLI 参数。默认只读本地输入,联网范围只包含明确目标的公开 Discovery/JWKS,不验证 Client、真实登录或存储,也不替代 SDK environment 严格检查。
|
|
63
|
+
|
|
64
|
+
完成目标工程适用的类型、lint、构建与回归。覆盖同环境默认、本地显式远端、非本地跨环境拒绝、缺 profile/凭据、错数据库路由和错中心环境声明;确保服务端与页面使用同一快照。保留旧单 Manifest 时明确其没有新增 profiles 防混校验。
|
|
65
|
+
|
|
66
|
+
本版本地结果以操作者实际交付记录为证,不相加重叠批次或套用其他版本结果。真实注册/邮件、必要 consent、回调换码验签、稳定身份、业务关联、原目标恢复、页面/API、刷新与退出,以及实际存储约束和并发,仍需目标环境独立留证。
|
|
67
|
+
|
|
68
|
+
交付可审阅变更与记录,写清实际版本及摘要、runtime/target、公开登记信息、迁移范围、测试结果、doctor 状态和未运行项。缺真实条件时标待配置或待验,不宣称生产接入、正式兼容组合或 MVP 已通过。blog 接入已由用户确认完成,本任务不重复实施,也不据此推定 blog 使用 0.1.10 或正式兼容认证通过,不自动提交、推送或部署。
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 只读 doctor 0.1.10
|
|
2
|
+
|
|
3
|
+
pnpm exec user-center doctor --config integration.json --project . --env-file .env.local --json
|
|
4
|
+
pnpm exec user-center doctor --config integration.json --project . --network
|
|
5
|
+
|
|
6
|
+
--config 是必需的单 Manifest v1;--project 默认当前目录。--env-file 必须显式指定,只解析静态环境文本,不插值、不执行命令。进程变量优先,空进程值也会遮盖文件值。profiles 须先由宿主选择并导出单 Manifest,详见[环境说明](environments.md)。
|
|
7
|
+
|
|
8
|
+
doctor 只读显式 JSON、环境文件和已安装 Node/Next/React/Better Auth 的有限元数据。不加载宿主 JS/TS、不连接数据库、不安装依赖、不登录、不执行迁移、不修改文件。输入秘密只检查存在和长度,值不打印;缺少真实注册、存储和认证证据不能标 PASS。
|
|
9
|
+
|
|
10
|
+
只有 --network 才会 GET 固定中心的 Discovery 和通过检查的同源 JWKS。每项最多 5 秒、1 MiB,零重试、零重定向,不发 Cookie/Authorization,不访问登录、token、回调或管理接口。不关闭 TLS 校验来绕过不可达或不可信结果。
|
|
11
|
+
|
|
12
|
+
| 状态 | 含义 |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| PASS | 此项在明确范围有依据通过 |
|
|
15
|
+
| FAIL | 有依据确认失败 |
|
|
16
|
+
| BLOCKED | 网络或环境条件不足,或组合尚未完成真实验收 |
|
|
17
|
+
| NOT_CHECKED | 不在本次能力或显式运行范围 |
|
|
18
|
+
|
|
19
|
+
人读输出先给 PASS、FAIL、BLOCKED、NOT_CHECKED 的数量汇总。JSON schemaVersion=1,包含 toolVersion、overall 与逐项检查,不把不同类别合并。退出码 0 是本次范围通过,1 是有 FAIL,2 是无 FAIL 但有 BLOCKED,3 是参数或输入配置错误。
|
|
20
|
+
|
|
21
|
+
未配置样例返回 FAIL/exit1 是预期诊断,不等于已完成接入。[支持范围](support.md)中的正式组合仍为空;registration、storage 和真实 runtime 集成始终 NOT_CHECKED。生产验收见[完整指南](index.md)。
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# 平台 runtime 与中心 target
|
|
2
|
+
|
|
3
|
+
完整示例在 [profiles JSON](../example-next/profiles.example.json),接线见[完整指南](index.md)。旧单份 Manifest v1 仍可使用。
|
|
4
|
+
|
|
5
|
+
- centers 定义 development、staging、production 中心 issuer。
|
|
6
|
+
- runtimes 定义该平台自己的 HTTPS origin、authBasePath、Client/会话凭据变量及可选 PostgreSQL 绑定。
|
|
7
|
+
- 默认 target 与 runtime 一致。只有 development 可以显式跨到 staging 或 production,staging/production 跨环境应拒绝。
|
|
8
|
+
- 切换 target 仅改变中心和该目标的本地回调专用 Client,不改变平台 origin、平台数据库或会话密钥。
|
|
9
|
+
|
|
10
|
+
宿主服务端读取 JSON,通过 resolveEnvironmentConfig(profiles,{runtimeEnvironment,targetEnvironment?}) 选择,再把 manifest 和 environment 一起传给 createUserCenterAuth。SDK 不自动读取文件、.env 或 NODE_ENV,也不会自动挑选另一个环境的秘密。
|
|
11
|
+
|
|
12
|
+
本包样例由自己的加载器读取 USER_CENTER_CONFIG_FILE、USER_CENTER_RUNTIME_ENVIRONMENT、可选 USER_CENTER_TARGET_ENVIRONMENT。你可以把 profiles 示例复制为 integration.environments.json,在自己的忽略环境文件中填写这些选择器以及所引用的秘密变量。修改配置后重启。其他工程可以使用自己的加载器,不应把这些名字当成 SDK 自动行为。
|
|
13
|
+
|
|
14
|
+
严格模式检查目标 Discovery 的 user_center_environment,缺失或不匹配时保留 ENVIRONMENT_MISMATCH。中心还没有部署声明时先处理目标部署,不移除 environment、不降低 TLS、不改用其他环境凭据。
|
|
15
|
+
|
|
16
|
+
PostgreSQL 宿主在创建连接前可用 assertPostgresBinding(url,selected.database) 校验完整路由身份。绑定描述不是数据库适配器;其他适配器仍由宿主核对实际归属。迁移与服务端应使用同一 runtime 快照,迁移角色须符合明确的 migrationDatabase 绑定。SDK 无法识别不透明适配器实际连到哪里。
|
|
17
|
+
|
|
18
|
+
profiles 使用者先由宿主导出 selected.manifest 为非敏感 JSON,再交给[doctor](doctor.md)。doctor 没有 runtime/target 选择参数,不能把整个 profiles JSON 传给 --config。
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# 完整接入指南 0.1.10
|
|
2
|
+
|
|
3
|
+
本指南与 [Agent Prompt](agent-prompt.md)、[可复制样例](../example-next/README.md)均在安装包内。SDK 为宿主创建受控 Better Auth 实例,宿主拥有框架、适配器、数据库、业务用户与权限。SDK 和 doctor 不改工程、不安装依赖、不执行迁移。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
已实际发布并确认来源的版本可以固定安装:
|
|
8
|
+
|
|
9
|
+
pnpm add --save-exact better-auth@1.7.12 server-only@0.0.1 @user-center/next-better-auth@0.1.10
|
|
10
|
+
pnpm add -D --save-exact @user-center/cli@0.1.10
|
|
11
|
+
|
|
12
|
+
候选交付时,将两个包名替换为操作者提供且已核验的 tgz 绝对路径,保持 Better Auth peer 精确版本。不要从 dry-run 或本文推断 registry 已有该版本。保留锁文件,升级须核对运行时及存储,不覆盖旧产物。
|
|
13
|
+
|
|
14
|
+
目前支持目标是 Node、Next App Router、ESM/TypeScript、Better Auth 1.7.12 与 confidential Web,正式运行组合仍待验,详见[支持范围](support.md)。宿主直接导入 server-only 时应直接声明它;独立 Node 服务端脚本使用 --conditions=react-server。安装不替换宿主 Next、React、ORM 或构建配置。
|
|
15
|
+
|
|
16
|
+
## 准备与登记
|
|
17
|
+
|
|
18
|
+
准备有创建应用资格的中心开发者、专用 confidential Web Client、可信 HTTPS 平台 origin、独立平台存储和至少 32 字符的独立随机 sessionSecret。普通平台用户无需创建应用资格。客户端 Secret 与会话密钥必须独立,并由宿主服务端读取,不放入浏览器、NEXT_PUBLIC、日志或聊天。
|
|
19
|
+
|
|
20
|
+
先从 @user-center/next-better-auth/config 调用 describeRegistration({issuer,platformOrigin,authBasePath?}),将返回的 callbackUrl 和 origin 原样登记,scope 使用 openid profile email。issuer 包含实际 base path,例如 https://auth.example.test/api/auth。派生 Provider ID 使用规范化 issuer 的完整 SHA256,不能改成固定 user-center 或把业务成功页当协议回调。
|
|
21
|
+
|
|
22
|
+
可信 .test 域名可用于 development Client;localhost/回环 IP 不属于首期平台 origin 范围。远端中心登记本地平台时仍选本地开发并填写本地 HTTPS 回调,不能复用远端平台的 Client。注册和邮箱验证不自动登录,创建资格不替代普通登录身份。
|
|
23
|
+
|
|
24
|
+
## Manifest 与服务端配置
|
|
25
|
+
|
|
26
|
+
有两种配置模式:推荐用 profiles 明确选择平台 runtime 与中心 target;已有单份 Manifest v1 可继续使用。两种模式都由宿主服务端读取文件与秘密,SDK 不自动选择 `.env` 或执行配置脚本。
|
|
27
|
+
|
|
28
|
+
### 原单份 Manifest v1
|
|
29
|
+
|
|
30
|
+
保留原模式时,你可以手写一个非敏感 `integration.json`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"schemaVersion": 1,
|
|
35
|
+
"issuer": "https://auth.example.com/api/auth",
|
|
36
|
+
"platformOrigin": "https://platform.example.test",
|
|
37
|
+
"clientId": "your-registered-client-id",
|
|
38
|
+
"clientSecretEnv": "USER_CENTER_CLIENT_SECRET",
|
|
39
|
+
"sessionSecretEnv": "PLATFORM_SESSION_SECRET",
|
|
40
|
+
"returnTo": {
|
|
41
|
+
"fallback": "/dashboard",
|
|
42
|
+
"allowedPaths": ["/dashboard"],
|
|
43
|
+
"allowedPathPrefixes": ["/projects"],
|
|
44
|
+
"allowedQueryKeys": ["tab"]
|
|
45
|
+
},
|
|
46
|
+
"errorPath": "/auth-error"
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
未知字段拒绝。`authBasePath` 默认 `/api/auth`,如需修改,你的挂载路径与中心登记也应一致。Issuer 算法 v1 只接受 HTTPS、空或根路径、以及由 ASCII 字母数字与 `._~-` 组成的路径段。百分号编码、点段、连续斜线、非根尾斜线和非 ASCII 路径均拒绝。仅配置输入归一主机名、默认 443 端口和根路径;Discovery issuer 与 ID Token iss 按原文字面比较,不采用这套归一规则。升级不能悄悄改变既有命名空间。
|
|
51
|
+
|
|
52
|
+
你可以把 Client Secret、至少 32 字符的独立随机 sessionSecret 和数据库连接串放到宿主忽略的环境文件或服务端密钥系统。环境加载由宿主负责,SDK 不读取文件。不要放进 NEXT_PUBLIC 变量、RSC props、日志或命令参数。两个环境变量名称必须不同,Client Secret 与 sessionSecret 不可共用。
|
|
53
|
+
|
|
54
|
+
下面的服务端模块由你主动编写。数据库来自你的适配器,并非中心数据库:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import 'server-only';
|
|
58
|
+
import { createUserCenterAuth } from '@user-center/next-better-auth/server';
|
|
59
|
+
import manifest from './integration.json';
|
|
60
|
+
import { database, identityMappings } from './your-server-services';
|
|
61
|
+
|
|
62
|
+
export const integration = createUserCenterAuth({
|
|
63
|
+
manifest,
|
|
64
|
+
database,
|
|
65
|
+
clientSecret: process.env.USER_CENTER_CLIENT_SECRET!,
|
|
66
|
+
sessionSecret: process.env.PLATFORM_SESSION_SECRET!,
|
|
67
|
+
authUserPolicy: 'existing-only',
|
|
68
|
+
async resolveBusinessUser(identity, context) {
|
|
69
|
+
const user = await identityMappings.find(
|
|
70
|
+
{ issuer: identity.issuer, subject: identity.subject },
|
|
71
|
+
{ signal: context.signal },
|
|
72
|
+
);
|
|
73
|
+
return user ? { status: 'ready', user } : { status: 'unmapped' };
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`authUserPolicy` 必须明确选 `allow-new` 或 `existing-only`,后者拒绝创建新的本地认证用户,也不能通过登录请求的 requestSignUp 绕过。业务 User 的创建、原用户认领及角色属于你的工程,应通过显式操作与事务完成;解析回调只读取结果。同邮箱不构成认领依据。业务结果最低需要非空字符串 `id`。干净新平台可以显式选择用本平台 authUserId 作为业务 ID,独立样例采用这个简化策略;已有业务平台不应直接替换原 User ID。
|
|
79
|
+
|
|
80
|
+
SDK 从当前平台会话调用官方 `getSession` 与 `listUserAccounts`,只认可当前用户下该 Provider 的唯一账号,以 accountId 作为中心 subject。身份键是 `(issuer, subject)`,authUserId 是本地认证 ID,business user.id 是你的业务 ID。profile 是本地当前资料,不能作为本次中心邮箱验证证明。
|
|
81
|
+
|
|
82
|
+
解析回调只收到 identity 和 `{requestId, signal}`。requestId 每次由 SDK 生成,不采信来路请求头。5 秒到期后中止 signal、丢弃迟到结果并拒绝业务访问;这不保证底层查询已经停止。回调不能读取原请求、Cookie、Authorization 或 OAuth 凭据。
|
|
83
|
+
|
|
84
|
+
### 三环境 profiles
|
|
85
|
+
|
|
86
|
+
复制并填写[样例配置](../example-next/profiles.example.json),宿主服务端调用:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { resolveEnvironmentConfig } from '@user-center/next-better-auth/config';
|
|
90
|
+
|
|
91
|
+
const selected = resolveEnvironmentConfig(profiles, {
|
|
92
|
+
runtimeEnvironment: process.env.USER_CENTER_RUNTIME_ENVIRONMENT!,
|
|
93
|
+
...(process.env.USER_CENTER_TARGET_ENVIRONMENT === undefined ? {} : {
|
|
94
|
+
targetEnvironment: process.env.USER_CENTER_TARGET_ENVIRONMENT,
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
// 其余参数沿用上面的宿主适配器、凭据读取及业务回调。
|
|
98
|
+
const integration = createUserCenterAuth({ ...selected, database, clientSecret, sessionSecret,
|
|
99
|
+
authUserPolicy: 'existing-only', resolveBusinessUser });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`profiles` 由宿主从明确的 JSON 文件读取;`clientSecret`、`sessionSecret` 必须分别从 selected.manifest 声明的变量取值,不能沿用另一环境的值。`manifest` 与 `environment` 一起传入实例,丢掉 environment 就不会启用中心环境声明校验。公共 Manifest v1 本身没有新增 environment 字段。
|
|
103
|
+
|
|
104
|
+
runtime 决定平台 origin、数据库和会话密钥,target 决定 issuer,Client 来自 `(runtime,target)`。默认同环境,只有 development 可跨到 staging/production;本地跨环境仍登记本地回调专用 Client。PG 宿主可在创建连接前用 `assertPostgresBinding(url, selected.database)` 校验所选存储;不透明适配器的真实归属由宿主保证。
|
|
105
|
+
|
|
106
|
+
严格模式检查 Discovery 的 `user_center_environment`,旧中心缺字段时返回 `ENVIRONMENT_MISMATCH`,应先部署中心新代码,不自动降级。独立样例的配置读取、只向浏览器传 publicConfig 及重启要求,见[完整三环境步骤](environments.md)。
|
|
107
|
+
|
|
108
|
+
## 认证存储与迁移
|
|
109
|
+
|
|
110
|
+
你可以按自己适配器的 Better Auth 标准流程生成、审查并执行迁移。SDK 没有自有表,也不会在构建或导入时迁移。你需要核对 User 主键、Session token 唯一、合法外键,以及 Account 的 `(providerId, accountId)` 全局唯一约束,不能仅在单一用户账号列表中查重。并发首次关联冲突应失败,不能依赖先查后写规避冲突。
|
|
111
|
+
|
|
112
|
+
直接 pg 的干净样例包含宿主自己的 `scripts/schema.mts`:`pnpm schema:plan` 只输出计划,`pnpm schema:apply` 才执行宿主迁移并验证唯一索引。样例默认使用 Better Auth 表名,附加 SQL 为:
|
|
113
|
+
|
|
114
|
+
```sql
|
|
115
|
+
CREATE UNIQUE INDEX IF NOT EXISTS sdk_account_identity_unique
|
|
116
|
+
ON account ("providerId", "accountId");
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
profiles 模式下,样例 schema 命令和服务端选择相同 runtime 的数据库,不回退读取旧 DATABASE_URL。独立加载器未设置 NODE_ENV 时按 development 读取文件;执行 staging/production 的独立迁移时显式设置 NODE_ENV=production 及对应 runtime,先审查绑定和计划,不把 target 改为远端理解为允许迁移远端平台库。
|
|
120
|
+
|
|
121
|
+
这是干净样例的迁移流程,不能直接用于已有库。你的生产迁移应先核对现有表、重复数据和外键,按工程自己的流程实施。未实际执行迁移及存储验证前,不能记录该适配器已兼容。
|
|
122
|
+
|
|
123
|
+
## 路由、登录与退出
|
|
124
|
+
|
|
125
|
+
你可以在自己的 `app/api/auth/[...all]/route.ts` 导出受控处理器:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { integration } from './your-server-auth';
|
|
129
|
+
export const runtime = 'nodejs';
|
|
130
|
+
export const GET = integration.handlers.GET;
|
|
131
|
+
export const POST = integration.handlers.POST;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
仅公开四个表面:POST `/sign-in/social`、GET `/callback/{providerId}`、GET `/get-session`、POST `/sign-out`。其他路径为 404,已知路径错误方法为 405。你不应导出 `getAuth()` 返回实例的原始 handler,否则会绕过 SDK 的公开边界。`getAuth()` 只供显式服务端维护;不能序列化或传入浏览器。
|
|
135
|
+
|
|
136
|
+
你可以将 `describeIntegration(manifest)` 或 `integration.publicConfig` 传给 Client Component,不包含 Secret 或适配器:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { createUserCenterClient } from '@user-center/next-better-auth/client';
|
|
140
|
+
const client = createUserCenterClient(publicConfig);
|
|
141
|
+
const { url } = await client.startLogin({ returnTo: '/projects/example?tab=activity' });
|
|
142
|
+
window.location.assign(url);
|
|
143
|
+
// 退出成功后由你的页面导航。
|
|
144
|
+
await client.signOut();
|
|
145
|
+
window.location.assign('/');
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
SDK 不自动导航。公开登录请求只接受可选字符串 returnTo,不能传 provider、callbackURL、newUserCallbackURL、errorCallbackURL、requestSignUp 或其他库参数。SDK 在服务端重新构造可信请求,普通与新用户成功后使用同一目标。原生 Better Auth signIn.social 的完整请求体不是本 SDK 的公开合同。
|
|
149
|
+
|
|
150
|
+
returnTo 是你的业务页面,不是协议回调或 OAuth state。客户端和服务端都校验。它必须属于显式路径集合或按段匹配的 prefix;query 只允许配置键,拒绝重复键和认证凭据类键。外站、协议相对 URL、编码分隔符、点段、fragment 和认证 API 路径均拒绝。未提供时用 fallback,提供非法值时不会默默回退。错误页只收到有限 code,不复制库错误描述或授权 URL。
|
|
151
|
+
|
|
152
|
+
平台 Cookie 使用独立前缀、HTTPS、HttpOnly、host-only,禁 Cookie 会话缓存和自动刷新。默认 session 有效期 86400 秒,可显式设为 300 至 86400 的整数。平台退出撤销本地会话,并不承诺退出中心或其他平台。退出结果不明时,你可以先 `client.getSession()` 检查状态,再由用户决定下一步,SDK 不自动重复退出。
|
|
153
|
+
|
|
154
|
+
## 受保护页面与 API
|
|
155
|
+
|
|
156
|
+
你可以在 RSC 使用 `await headers()` 读取当前请求,不能在渲染中写 Cookie:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const principal = await integration.getPrincipal(await headers());
|
|
160
|
+
if (principal.status === 'anonymous') { /* 跳转到你的登录页 */ }
|
|
161
|
+
if (principal.status === 'mapping-required') { /* 拒绝业务访问,展示关联入口 */ }
|
|
162
|
+
if (principal.status === 'authenticated') { /* 再核对你的业务权限 */ }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
API 同样调用 `getPrincipal(request.headers)`,匿名返回 401,映射未完成或无业务权限返回 403,依赖故障返回 503。你可以参考独立样例的 `/api/me` 和 `src/lib/protected.tsx`。隐藏按钮不能替代 API 权限检查。`client.getSession()` 仅返回安全本地用户资料与 expiresAt,没有 session bearer,也不包含业务授权。
|
|
166
|
+
|
|
167
|
+
## 失败恢复与可观测性
|
|
168
|
+
|
|
169
|
+
`INVALID_CONFIG` 表示 Manifest 或实例选项不合法;`INVALID_RETURN_TO`、`INVALID_LOGIN_REQUEST` 表示目标或登录 DTO 不合法。`LOGIN_UNAVAILABLE` 表示授权未能确认,`SIGN_OUT_UNCONFIRMED` 表示退出未能确认。`CENTER_UNAVAILABLE`、`UNTRUSTED_METADATA` 表示依赖或可信元数据不足,`IDENTITY_CONFLICT` 表示账号关联不唯一或不属于当前会话,`BUSINESS_RESOLUTION_FAILED` 表示解析故障或超时。官方库拒绝同邮箱的新身份关联时,SDK 输出 `ACCOUNT_CONFLICT`;其他回调错误统一收敛为 `LOGIN_UNAVAILABLE`,不复制邮箱或内部错误。
|
|
170
|
+
|
|
171
|
+
`ENVIRONMENT_MISMATCH` 表示严格环境选择或绑定不符合合同,例如 staging 指向 production、缺所选组合、复用凭据引用,或中心环境声明缺失/不符。先核对 profiles、selector 与中心部署,不能通过去掉 environment、关闭 TLS 或换用其他环境凭据让检查通过。
|
|
172
|
+
|
|
173
|
+
认证协议、state、nonce、PKCE、签名、audience 和有效期由 Better Auth 与其 JOSE 验证器处理。SDK 固定协议端点、精确 Issuer 和 JWKS 地址,并核对 ID Token 与 UserInfo subject、真实 email_verified。公开插件扩展保留官方 init 与 retry hook,固定 JWKS resolver,防止库重复 Discovery 改变验签密钥来源。SDK 自有请求限时、限体积且不跟随重定向。Better Auth 初始 Discovery 不能通过公开 API完全取消;SDK 最长等待 10 秒,后台操作可能继续,同一未完成初始化不会被重复创建。新的显式读取可以恢复,不会自动重试已经开始的换码。
|
|
174
|
+
|
|
175
|
+
你可以配置 onEvent 接收有限阶段、code、时间、SDK 生成的 requestId 和版本。回调故障不改变认证结果。事件不包含身份、邮箱、query、授权码、token、Secret 或原请求;若你增加宿主日志,也应维持这个边界。事件不是密码学验证的独立证据。
|
|
176
|
+
|
|
177
|
+
中心注册与邮件验证不自动登录,恢复沿用既有中心流程。当前生产的授权请求、flow 和邮件期限需要在实际验收时核对。收件晚于原授权期限时,可以先完成验证,再从平台发起全新的授权;不能延长期限、重放旧 state/code 或自动重试换码。
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## doctor 与真实验收
|
|
181
|
+
|
|
182
|
+
按[doctor 说明](doctor.md)先运行静态检查;profiles 先由宿主选出单 Manifest。只读输出不证明 Client Secret 有效、存储完成或真实认证成功。
|
|
183
|
+
|
|
184
|
+
从受保护的 /projects/example?tab=activity 发起:注册并收到实际邮件,完成邮箱验证后主动登录及必要授权,完成派生回调、换码和身份验证,关联宿主用户并建立独立平台会话,恢复原目标;页面与 API 读到同一身份,刷新保持会话,本地退出后拒绝业务访问。过期的原请求按标准流程重新开始,不延长期限、重放授权码或自动重试换码。
|
|
185
|
+
|
|
186
|
+
宿主还需验证 User/Session/Account 的真实约束、全局 Account(providerId,accountId) 唯一和并发冲突处理,以及映射未完成、依赖故障、非法返回目标、回调重放等失败分支。相同邮箱不能自动认领旧账号。记录实际 SDK 版本、两个环境、适配器、步骤与脱敏结果,不保存原始认证 URL、code、state、token 或真实邮箱地址。
|
|
187
|
+
|
|
188
|
+
blog 按用户确认已完成接入,不据此推定其 SDK 版本或正式兼容认证。生产链路、真实邮件、样例存储和正式组合仍待验;本地构建和静态 PASS 不能关闭这些项目。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 试用支持范围 0.1.10
|
|
2
|
+
|
|
3
|
+
SDK 需要 Node >=22,CLI 需要 Node >=22.21.1。目标是 Next App Router、Node runtime、ESM/TypeScript、Better Auth 1.7.12 和 confidential Web。宿主保留自己的 Next/React、适配器、数据库和构建设置。Edge、Pages Router、CJS、SPA、原生端、Public Client 和任意插件组合不属于本期目标。
|
|
4
|
+
|
|
5
|
+
| 候选 | 目标版本 | 真实验收 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| M1 | Node 24.19.0、pnpm 12.9.1、Next 16.4.3、React 19.3.0、Better Auth 1.7.12、宿主 pg | 本地受控验证可另留记录;真实中心、邮件、宿主存储及并发仍待验 |
|
|
8
|
+
| M2 | Node 22.21.1、pnpm 10.28.1、Next 16.3.3、React 19.2.4、Better Auth 1.7.12、blog Prisma | blog 接入由用户确认完成;其实际 SDK/runtime 未在本轮核定,正式组合仍待留证 |
|
|
9
|
+
|
|
10
|
+
没有正式通过的运行组合,doctor 的已验证集合仍为空。peer 可安装不证明正式兼容,Node 导入、类型、构建、静态诊断和本地浏览器 fixture 也不替代真实认证、邮件、读写、唯一约束与并发证据。
|
|
11
|
+
|
|
12
|
+
公开 npm、MIT、next 或 latest 只是分发或许可信息,不是稳定性承诺。首次试用固定精确版本和锁文件,按[指南](index.md)完成目标环境验收。升级 issuer 派生算法或存储时须有宿主迁移方案;回退包版本不自动恢复用户/数据变化。
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@user-center/next-better-auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.10",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"README.md",
|
|
8
|
+
"LICENSE"
|
|
9
|
+
],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/next-better-auth/src/public.d.ts",
|
|
16
|
+
"import": "./dist/next-better-auth/src/public.js"
|
|
17
|
+
},
|
|
18
|
+
"./public": {
|
|
19
|
+
"types": "./dist/next-better-auth/src/public.d.ts",
|
|
20
|
+
"import": "./dist/next-better-auth/src/public.js"
|
|
21
|
+
},
|
|
22
|
+
"./config": {
|
|
23
|
+
"types": "./dist/next-better-auth/src/config.d.ts",
|
|
24
|
+
"import": "./dist/next-better-auth/src/config.js"
|
|
25
|
+
},
|
|
26
|
+
"./server": {
|
|
27
|
+
"types": "./dist/next-better-auth/src/server.d.ts",
|
|
28
|
+
"import": "./dist/next-better-auth/src/server.js"
|
|
29
|
+
},
|
|
30
|
+
"./client": {
|
|
31
|
+
"types": "./dist/next-better-auth/src/client.d.ts",
|
|
32
|
+
"import": "./dist/next-better-auth/src/client.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"server-only": "0.0.1",
|
|
37
|
+
"jose": "6.2.13"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"better-auth": "1.7.12"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"better-auth": "1.7.12",
|
|
44
|
+
"typescript": "^5",
|
|
45
|
+
"@types/node": "^24"
|
|
46
|
+
},
|
|
47
|
+
"description": "Controlled User Center authentication integration for Next App Router and Better Auth.",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/ZXFTech/user-center.git",
|
|
52
|
+
"directory": "packages/next-better-auth"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://www.npmjs.com/package/@user-center/next-better-auth/v/0.1.10",
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"registry": "https://registry.npmjs.org/",
|
|
57
|
+
"access": "public",
|
|
58
|
+
"tag": "next"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "node ../../scripts/build-integration.mjs next-better-auth",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
63
|
+
}
|
|
6
64
|
}
|