mirror of
https://github.com/hanzoai/iam-sdk.git
synced 2026-10-30 14:54:01 +00:00
Single source of truth for OIDC paths, one name per thing, no
backwards compatibility. Every Hanzo/Lux/Zoo/Bootnode JS app
authenticates against Hanzo IAM exactly one way.
Paths (src/paths.ts):
- OIDC_PATHS is the ONLY path set; every module references it, no
path string anywhere else. Canonical /v1/iam/oauth/{authorize,
token,userinfo,logout} + /v1/iam/.well-known/jwks + discovery.
- IAM_PATHS for non-OIDC IAM endpoints (auth methods, onboarding).
- Kills all legacy bare /oauth/* paths in browser/passport/betterauth.
better-auth (src/betterauth.ts):
- iamProvider now returns a REAL genericOAuth config entry
{ providerId:"hanzo", authorizationUrl, tokenUrl, userInfoUrl,
clientId, clientSecret, scopes, pkce:true, authentication:"basic" }.
No discoveryUrl. Usage: genericOAuth({ config:[iamProvider({...})] }).
Security:
- pkce.ts: RFC 7636 verifier = base64url(32 random bytes) = 256-bit
entropy; generateState likewise. Was a weak base36 slice.
- auth.ts validateToken: aud must equal clientId or fail
iam_audience_invalid. Removed the silent audience-less retry; gate
with allowMissingAudience. Branch on jose error codes, not strings.
New server export (src/server.ts):
- getServerSession(req,config) -> { userId, owner, email, claims } | null;
withSession() route guard; reads bearer or hanzo_iam_access_token
cookie; duck-types Web Request and Node IncomingMessage.
Views (src/views.tsx):
- Replaced the declare-only (empty-at-runtime) module with real React
components: Login/SocialButton/WalletButton/EmailPasswordForm/OTPStep
+ OnboardingFlow and 5 step primitives + useAuthMethods. Shipped via
tsup entry + "./views" export.
Cleanup / no-compat:
- Deleted billing.ts tombstone (threw on import) + "./billing" export.
- Collapsed dual naming: removed all Hanzo* aliases and duplicate Iam*
type aliases; IamContext.displayName = "IamContext"; one name per thing.
- nextauth checks default to ["state","pkce"]; wellKnown via OIDC_PATHS.
- package.json exports: + ./paths ./server ./views, - ./billing.
- Repo url -> hanzoai/iam-sdk. Bump 0.10.0 -> 0.11.0. Drop package-lock
(pnpm is the one way); tsx devDependency for node --test.
Tests (44, node --test --import tsx): lock every endpoint to /v1/iam/*,
validateToken happy/expired/bad-aud(fails)/missing-aud(fails)/
allowMissingAudience/tampered, PKCE >=256-bit, exact iamProvider shape,
nextauth pkce default, passport /v1/iam URLs, server header extraction,
views-are-real, dual ESM+CJS resolution of every subpath.
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { iamProvider } from "./betterauth.js";
|
|
|
|
test("iamProvider returns the exact better-auth genericOAuth shape", () => {
|
|
const cfg = iamProvider({
|
|
serverUrl: "https://iam.hanzo.ai",
|
|
clientId: "my-app",
|
|
clientSecret: "shh",
|
|
});
|
|
|
|
assert.deepEqual(cfg, {
|
|
providerId: "hanzo",
|
|
authorizationUrl: "https://iam.hanzo.ai/v1/iam/oauth/authorize",
|
|
tokenUrl: "https://iam.hanzo.ai/v1/iam/oauth/token",
|
|
userInfoUrl: "https://iam.hanzo.ai/v1/iam/oauth/userinfo",
|
|
clientId: "my-app",
|
|
clientSecret: "shh",
|
|
scopes: ["openid", "profile", "email"],
|
|
pkce: true,
|
|
authentication: "basic",
|
|
});
|
|
});
|
|
|
|
test("iamProvider has no discoveryUrl (paths are pinned, no magic discovery)", () => {
|
|
const cfg = iamProvider({ serverUrl: "https://lux.id", clientId: "x" }) as unknown as Record<string, unknown>;
|
|
assert.ok(!("discoveryUrl" in cfg));
|
|
assert.equal(cfg.authorizationUrl, "https://lux.id/v1/iam/oauth/authorize");
|
|
});
|
|
|
|
test("iamProvider works for any brand origin", () => {
|
|
const cfg = iamProvider({ serverUrl: "https://id.bootno.de/", clientId: "bootnode-app" });
|
|
assert.equal(cfg.tokenUrl, "https://id.bootno.de/v1/iam/oauth/token");
|
|
assert.equal(cfg.userInfoUrl, "https://id.bootno.de/v1/iam/oauth/userinfo");
|
|
assert.equal(cfg.providerId, "hanzo");
|
|
});
|