weifuwu 0.5.0 → 0.6.0

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/README.md CHANGED
@@ -20,6 +20,9 @@ Everything follows the same `(req, ctx) => Response` contract. The Router handle
20
20
  - **AI streaming** — `ai(handler)` sub-Router via Vercel AI SDK
21
21
  - **AI workflows** — `workflow(handler)` sub-Router — intent-to-execution pipelines with `tool()` + SSE
22
22
  - **PostgreSQL** — `postgres()` — zod-to-DDL, auto-migration, 6 CRUD methods, `ctx.sql` escape hatch
23
+ - **Redis** — `redis()` — ioredis client, `ctx.redis`, middleware
24
+ - **Queue** — `queue()` — Redis-backed job queue with immediate, delayed, and cron scheduling
25
+ - **Auth** — `user()` — register/login/JWT + OAuth2 Server (authorization code + PKCE + client_credentials)
23
26
  - **Static files** — `serveStatic()` with ETag, 304, MIME, directory index
24
27
  - **Cookie** — `getCookies()`, `setCookie()`, `deleteCookie()` — immutable
25
28
  - **Error handling** — global `onError()`
@@ -258,14 +261,109 @@ await pg.close() // explicit close
258
261
  | `id: z.string().uuid().optional()` | `UUID PRIMARY KEY DEFAULT gen_random_uuid()` |
259
262
  | `id: z.string()` | `TEXT PRIMARY KEY` (you pass the value) |
260
263
 
261
- ## WebSocket
264
+ ## Authentication
265
+
266
+ Built-in user management — password login, JWT, and OAuth2 Server. Zero config beyond PostgreSQL and a secret key.
262
267
 
263
268
  ```ts
269
+ import { serve, Router, postgres, user } from 'weifuwu'
270
+
264
271
  const app = new Router()
265
- .ws('/chat/:room', {
266
- open(ws, ctx) {
267
- ws.send(`Connected to room: ${ctx.params.room}`)
268
- },
272
+ const pg = postgres()
273
+ await pg.migrate()
274
+
275
+ const auth = user({ pg, jwtSecret: process.env.JWT_SECRET! })
276
+
277
+ // POST /auth/register { email, password, name }
278
+ // POST /auth/login { email, password }
279
+ // GET /auth/oauth/authorize?client_id=...&redirect_uri=...&response_type=code
280
+ // POST /auth/oauth/consent
281
+ // POST /auth/oauth/token (grant_type=authorization_code|client_credentials)
282
+ app.use('/auth', auth.router())
283
+
284
+ // Protected routes — verifies JWT, sets ctx.user
285
+ app.get('/me', auth.middleware(), async (req, ctx) => {
286
+ return Response.json(ctx.user)
287
+ // { id, email, name, role }
288
+ })
289
+ ```
290
+
291
+ Password hashing uses `crypto.scryptSync` + `timingSafeEqual` (Node.js built-in, zero deps). JWT tokens use the `jsonwebtoken` package. The users table (`_users` by default) is auto-created on first `migrate()`.
292
+
293
+ ### OAuth2 Server
294
+
295
+ Enable OAuth2 Server to let third-party apps (SPA, mobile, microservices) authenticate users through your app.
296
+
297
+ ```ts
298
+ const auth = user({
299
+ pg,
300
+ jwtSecret: process.env.JWT_SECRET!,
301
+ oauth2: { server: true },
302
+ })
303
+
304
+ await auth.migrate() // creates _users + _oauth2_clients + _oauth2_codes + _oauth2_tokens
305
+
306
+ // Register a client app (programmatic — CLI, admin UI, seed script)
307
+ const client = await auth.registerClient({
308
+ name: 'My SPA',
309
+ redirectUris: ['https://myapp.com/callback'],
310
+ })
311
+ // → { clientId, clientSecret, name, redirectUris }
312
+
313
+ // Use auth middleware to protect routes — OAuth2 JWT tokens work seamlessly
314
+ app.get('/api/data', auth.middleware(), handler)
315
+ ```
316
+
317
+ #### Supported Grant Types
318
+
319
+ | Grant | Use Case | PKCE |
320
+ |-------|----------|------|
321
+ | `authorization_code` (with client_secret) | Server-side apps | Optional |
322
+ | `authorization_code` (with `code_challenge`/`code_verifier`) | SPA / Mobile apps | Required |
323
+ | `client_credentials` | Machine-to-machine | — |
324
+
325
+ #### Flow (Authorization Code + PKCE)
326
+
327
+ ```
328
+ 1. 第三方 App 引导用户:
329
+ GET /oauth/authorize?client_id=xxx&redirect_uri=https://app.com/cb
330
+ &response_type=code&code_challenge=S256&state=yyy
331
+
332
+ 2. 用户未登录 → 302 到 /login?redirect=... → 登录后自动回到授权页
333
+
334
+ 3. 用户确认授权 → POST /oauth/consent { approve: true, client_id, ... }
335
+ 302 redirect_uri?code=xxx&state=yyy
336
+
337
+ 4. 第三方 App POST /oauth/token
338
+ { grant_type: authorization_code, code, client_id, client_secret,
339
+ redirect_uri, code_verifier }
340
+ → { access_token, token_type: "Bearer", expires_in, refresh_token }
341
+
342
+ 5. access_token 是标准 JWT,auth.middleware() 和 auth.verify() 直接可用
343
+ ```
344
+
345
+ #### Client Management
346
+
347
+ ```ts
348
+ const client = await auth.registerClient({ name, redirectUris })
349
+ const found = await auth.getClient(client.clientId)
350
+ await auth.revokeClient(client.clientId)
351
+ ```
352
+
353
+ #### Using OAuth2 Tokens with the Built-in Auth Middleware
354
+
355
+ OAuth2 Server 签发的 `access_token` 与密码登录的 JWT 使用同一 `jwtSecret`,payload 向下兼容(`sub`、`email`、`role`),所以 `auth()` 无需任何修改即可验证 OAuth2 签发的 token:
356
+
357
+ ```ts
358
+ import { auth } from 'weifuwu'
359
+
360
+ // 同一个 auth() 中间件同时支持密码登录 JWT 和 OAuth2 JWT
361
+ app.get('/api', auth({ verify: (token) => auth.verify(token) }), handler)
362
+ ```
363
+
364
+ For `client_credentials` tokens (machine-to-machine), `verify()` returns `null` since no user is associated.
365
+
366
+ ## WebSocket
269
367
  message(ws, ctx, data) {
270
368
  ws.send(`echo: ${data}`)
271
369
  },
@@ -654,6 +752,18 @@ const app = new Router()
654
752
 
655
753
  Returns `{ stop, port, hostname, ready }`.
656
754
 
755
+ ### `user(options)`
756
+
757
+ | Option | Default | Description |
758
+ |--------|---------|-------------|
759
+ | `pg` | — | PostgreSQL client from `postgres()` |
760
+ | `jwtSecret` | — | Secret key for JWT signing |
761
+ | `table` | `'_users'` | Users table name |
762
+ | `expiresIn` | `'24h'` | JWT expiration |
763
+ | `oauth2.server` | `false` | Enable OAuth2 Server |
764
+
765
+ Returns `UserModule` — `{ router, middleware, migrate, register, login, verify, registerClient, getClient, revokeClient, close }`.
766
+
657
767
  ### `tsx(options)`
658
768
 
659
769
  | Option | Default | Description |
@@ -690,6 +800,9 @@ Returns `Promise<Router>`.
690
800
  | Import | Description |
691
801
  |--------|-------------|
692
802
  | `postgres(options?)` | PostgreSQL connection + auto-migration + 6 CRUD methods |
803
+ | `redis(options?)` | Redis client (ioredis) — injects `ctx.redis` |
804
+ | `queue(options?)` | Redis-backed job queue — immediate, delayed, cron scheduling |
805
+ | `user(options)` | Built-in authentication (password + OAuth2 Server + JWT, middleware) |
693
806
  | `graphql(handler)` | GraphQL endpoint (GET/POST + GraphiQL) |
694
807
  | `ai(handler)` | AI streaming endpoint (POST) |
695
808
  | `workflow(handler)` | Workflow engine (POST + SSE) |
package/dist/index.d.ts CHANGED
@@ -27,3 +27,9 @@ export { tool, createWorkflowEngine, createSSEManager, generateWorkflow, workflo
27
27
  export type { Tool, Workflow, WorkflowEngine, WorkflowState, SSEEvent, WorkflowOptions, WorkflowHandler } from './workflow/index.ts';
28
28
  export { postgres } from './postgres/index.ts';
29
29
  export type { PostgresOptions, PostgresClient, TableProxy, ListOptions, TableBuilder } from './postgres/types.ts';
30
+ export { user } from './user/index.ts';
31
+ export type { UserOptions, UserData, UserModule, OAuth2Client } from './user/types.ts';
32
+ export { redis } from './redis/index.ts';
33
+ export type { RedisOptions, RedisClient } from './redis/types.ts';
34
+ export { queue } from './queue/index.ts';
35
+ export type { QueueOptions, QueueJob, Queue } from './queue/types.ts';