[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-94579":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":14,"starSnapshotCount":14,"syncStatus":25,"lastSyncTime":26,"discoverSource":27},94579,"keslr_connect","keslr\u002Fkeslr_connect","keslr","Connect your service to Keslr: sign in with Keslr and read the verification claim that proves a user is a real, vouched-for human — plus run services reachable only by Keslr members.",null,"TypeScript",445,81,219,0,227,55.74,"MIT License",false,"main",true,[],"2026-08-24 04:01:22","# keslr-connect\n\n> **Status: pre-release.** The packages are not on npm yet — install from source (see [Installing](#installing)). The API may change before 1.0.\n\n## What Keslr is\n\nKeslr is an invite-only network whose members are verified humans.\n\nYou can't sign up. There's no registration form to fill in, no email loop to complete, no captcha to squint at. You get in because somebody already on the network vouches for you — they send you a referral, you accept it, and their verification propagates to your account. That chain of vouching is recorded as a trust graph, and it's the whole product.\n\nIf nobody you know is on Keslr, the Keslr team will vouch for you instead. They will also probably call you and make you say difficult words down the phone, or hop on video and ask you to pull a few faces, because it turns out that's still a decent way to tell a person from a program.\n\nOn top of that sits a private network. Members' devices get addresses in the `100.64.0.0\u002F10` range, and services running on it — `nod.app.keslr.com` and friends — are reachable at those addresses and nowhere else. Not behind a login page on the public internet. Actually not on the public internet.\n\nSo Keslr gives an application two things it cannot get anywhere else:\n\n1. **A user who is definitely a person**, attested by a chain of other people.\n2. **A place to run** where the set of parties who can open a TCP connection to you is the set of verified humans.\n\nThe second one has a consequence worth stating plainly, because it changes how you'd design the app: **if your service is only reachable on the Keslr network, you may not need accounts at all.** A device only gets a Keslr address if the member behind it is verified, so anyone who can reach you is already a verified human — you just don't know _which_ one yet. One call to the lookup API turns their address into their identity. No signup form, no password, no session, no \"verify your email\".\n\nUse the OIDC login when you need something a network address can't give you: an explicit consent step, a session that survives a change of device, or profile claims. Otherwise, look them up and get on with it.\n\nThis repository is the code for both.\n\n## What this is\n\nThree TypeScript packages and a working example.\n\n| Package                              | What it does                                                                                                                        |\n| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |\n| [`@keslr\u002Fauth`](packages\u002Fauth)       | OpenID Connect relying party. Sign in with Keslr, and read the claim that says whether they're verified. Zero runtime dependencies. |\n| [`@keslr\u002Fexpress`](packages\u002Fexpress) | Express routes and guards over `@keslr\u002Fauth`, for people who don't want to write the callback handler themselves.                   |\n| [`@keslr\u002Fnetwork`](packages\u002Fnetwork) | Turns the Keslr address a request came from into the member who owns that device.                                                   |\n\nPlus [`examples\u002Fguestbook`](examples\u002Fguestbook), which is a guestbook. Every signature is a real human. It has no moderation queue and it doesn't need one.\n\n## Installing\n\nNot published yet. For now:\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fkeslr\u002Fkeslr_connect.git\ncd keslr_connect\nnpm install\nnpm run build\n```\n\nThen reference the packages from your project with `npm link`, a workspace, or a `file:` dependency. Once they're on npm this becomes the usual thing:\n\n```bash\nnpm install @keslr\u002Fauth @keslr\u002Fexpress   # not yet — see above\n```\n\n## Getting set up\n\nEverything starts at [developers.keslr.com](https:\u002F\u002Fdevelopers.keslr.com):\n\n1. **Sign in** with your Keslr account.\n2. **Apply for developer access.** Applications are reviewed; you'll wait.\n3. Once approved, **create an app**. Keslr allocates it an address on the network and a hostname of the form `your-app.app.keslr.com`, then asks you to verify DNS.\n4. Register an **OIDC client** if you want the login flow, and a **network client** if you want address lookups. These are separate credentials — mixing them up is the most common first-day mistake.\n\n## Signing someone in\n\nWith an OIDC client ID in hand:\n\n```ts\nimport express from 'express';\nimport session from 'express-session';\nimport { keslrAuth, requireAuth, requireVerified } from '@keslr\u002Fexpress';\n\nconst app = express();\n\napp.use(\n  session({\n    secret: process.env.SESSION_SECRET!,\n    resave: false,\n    saveUninitialized: false,\n    cookie: { httpOnly: true, sameSite: 'lax', secure: true },\n  }),\n);\n\napp.use(\n  keslrAuth({\n    issuer: 'https:\u002F\u002Fapi.keslr.com',\n    clientId: process.env.KESLR_CLIENT_ID!,\n    clientSecret: process.env.KESLR_CLIENT_SECRET,\n    redirectUri: 'https:\u002F\u002Fexample.com\u002Fauth\u002Fkeslr\u002Fcallback',\n  }),\n);\n\napp.get('\u002Fprofile', requireAuth(), (req, res) => res.json(req.keslr!.user));\napp.post('\u002Fposts', requireVerified(), createPost);\n```\n\nThat mounts `GET \u002Fauth\u002Fkeslr\u002Flogin`, `GET \u002Fauth\u002Fkeslr\u002Fcallback`, and `POST \u002Fauth\u002Fkeslr\u002Flogout`, and puts the signed-in member on `req.keslr`.\n\nTwo things that will cost you an afternoon if you get them wrong, so they're worth stating plainly:\n\n- **`sameSite: 'lax'`, not `'strict'`.** `'strict'` withholds the session cookie on the redirect _back_ from Keslr, so the callback arrives with no session and every login fails with `login_expired`. The symptom looks nothing like the cause.\n- **The redirect URI must match byte for byte.** `http:\u002F\u002Flocalhost:3000\u002Fcb` and `http:\u002F\u002Flocalhost:3000\u002Fcb\u002F` are different URIs. So are `localhost` and `127.0.0.1`.\n\nNot using Express? `@keslr\u002Fexpress` is about a hundred lines of glue over `@keslr\u002Fauth`; use that directly. See [docs\u002Fauthentication.md](docs\u002Fauthentication.md).\n\n## The claim that matters\n\n```ts\nreq.keslr.user;\n\u002F\u002F {\n\u002F\u002F   id: '019f…',                        ← Keslr's UUID. Use this as your foreign key.\n\u002F\u002F   keslrId: 'KID-A1B2C3D4E5F6',\n\u002F\u002F   username: 'yourname',\n\u002F\u002F   verificationStatus: 'verified',\n\u002F\u002F   verificationMethod: 'referral_approved',\n\u002F\u002F   isVerified: true                     ← this one\n\u002F\u002F }\n```\n\n| Status                 | Meaning                                        |\n| ---------------------- | ---------------------------------------------- |\n| `verified`             | Vouched for through the trust graph. A person. |\n| `pending_verification` | Verification underway, not yet decided         |\n| `unverified`           | Registered, never verified                     |\n| `rejected`             | Verification attempted and refused             |\n\n**Read `isVerified`, not the string.** Writing `verification_status === 'verified'` by hand works right up until you typo it, and every way of typoing it fails _open_ — you let people in rather than keeping them out. It's computed once, in one place, and tested. Unknown statuses normalise to `unverified` for the same reason: if Keslr adds a status this release has never heard of, you deny access rather than accidentally granting it.\n\nMore in [docs\u002Fverification-claims.md](docs\u002Fverification-claims.md).\n\n## Knowing who's calling — without a login\n\nRequests arriving from the Keslr network come from addresses the network assigned to members, so you can resolve them:\n\n```ts\nimport { IdentityLookup, keslrNetworkIdentity } from '@keslr\u002Fnetwork';\n\nconst lookup = new IdentityLookup({\n  baseUrl: 'https:\u002F\u002Fapi.keslr.com',\n  clientId: process.env.KESLR_NETWORK_CLIENT_ID,\n  clientSecret: process.env.KESLR_NETWORK_CLIENT_SECRET,\n});\n\napp.use(keslrNetworkIdentity({ lookup }));\n\napp.get('\u002Fwhoami', (req, res) => {\n  res.json({ member: req.keslrNetwork?.username ?? null });\n});\n```\n\nFor a service reachable only on the Keslr network, that is often the entire user system. `req.keslrNetwork.userId` is the same UUID as the `sub` claim from `@keslr\u002Fauth`, so it works as a foreign key whether or not the member ever logs in — and you can add login later without migrating anything.\n\n**But know what it proves.** It tells you whose _device_ opened the connection, not who is sitting at it. No session, no consent, no scopes, and a shared laptop speaks with its owner's name. That's fine for a forum, a dashboard, or a guestbook. For anything involving money, permissions, or private data, make them actually log in — the two compose:\n\n```ts\napp.post('\u002Ftransfer', requireNetworkIdentity(), requireVerified(), handler);\n```\n\nAnd the thing that matters more than any of this code: **bind to your Keslr address, not `0.0.0.0`.** No middleware can make a publicly-reachable service private. If you listen on every interface, anyone who can route to your host connects, and the fact that they aren't a Keslr member won't stop them — it'll just make `req.keslrNetwork` null after they're already inside. [docs\u002Fhosting-on-keslr.md](docs\u002Fhosting-on-keslr.md) has the details, and a `ss -tlnp` you should run after every deploy.\n\n## Design decisions you might disagree with\n\nSome behaviour isn't configurable. Each of these prevents a specific attack, and making it an option would mostly be a way of letting people turn the protection off by accident:\n\n- **PKCE is always on, S256 only.** `plain` isn't implemented; it protects against nothing an attacker who can see the authorization request can't defeat.\n- **The `alg` header is never trusted.** RS256 is hard-coded. A token claiming `none` or `HS256` is rejected before any signature is computed. This is the family of bugs that has done more damage to JWT deployments than everything else combined.\n- **Token exchanges are never retried.** Authorization codes are single-use, so a retry after a request that actually succeeded turns a network blip into a permanent `invalid_grant`.\n- **`X-Forwarded-For` is ignored** unless you explicitly opt in. It's client-supplied.\n- **State and nonce are compared in constant time.** String comparison short-circuits at the first differing byte, which is a timing oracle.\n\nIf you think one of these is wrong, open an issue with the threat model you have in mind — that's a conversation worth having. \"Make it a flag\" usually isn't, because a flag that fails open is a vulnerability with a config option in front of it.\n\n## Docs\n\n- [Quickstart](docs\u002Fquickstart.md) — nothing to signed-in member, about ten minutes\n- [Authentication in depth](docs\u002Fauthentication.md) — the flow, and what each check defends against\n- [Verification claims](docs\u002Fverification-claims.md) — the statuses and how to use them\n- [Hosting on Keslr](docs\u002Fhosting-on-keslr.md) — binding, addressing, deployment\n- [Troubleshooting](docs\u002Ftroubleshooting.md) — when it doesn't work\n\n## Development\n\n```bash\nnpm install\nnpm test          # 330 tests\nnpm run build\nnpm run lint\n```\n\nTests use real RSA keys and real signatures — nothing is stubbed at the crypto layer, because a suite that mocks signature verification can't tell you whether signature verification works. The Express tests run a real server against a stub OIDC provider over real HTTP.\n\nThere's also `node scripts\u002Fsmoke.mjs`, which checks that the live Keslr provider still looks the way the docs claim. It needs no credentials and runs in CI. It has already earned its keep: the docs had the wrong issuer URL, and it failed on the first run.\n\nContributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Security issues go to [SECURITY.md](SECURITY.md), not the public issue tracker.\n\n## License\n\nMIT © Keslr LLC\n","keslr_connect 是一个 TypeScript 工具库，用于将服务接入 Keslr 验证网络，实现基于人类身份验证的无账户访问控制。其核心功能包括：通过 OIDC 协议完成 Keslr 登录与身份声明解析（验证用户是否为经可信链背书的真实人类），以及提供 Express 中间件和网络层工具，支持服务仅在 Keslr 私有网络（100.64.0.0\u002F10）内被已验证成员设备直接访问。技术特点是零运行时依赖、轻量级 OIDC Relying Party 实现、无需传统账号体系即可完成身份识别与授权。适用于需强身份真实性保障、排斥自动化注册与匿名访问的协作型内部服务、开发者平台或高信任度社区应用。",2,"2026-08-12 02:30:08","CREATED_QUERY"]