@homurajs/auth 26.10.14 → 26.10.15

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/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
- import { a as RefreshTokenStore, c as RefreshTokenRecord, d as AuthUser, f as OAuth2Endpoints, i as SessionTokens, l as OAuth2Client, n as SessionManager, o as createDrizzleSessionStore, p as TokenResponse, r as SessionManagerConfig, s as ConsumedToken, t as SessionClaims, u as OAuth2ClientConfig } from "./manager-_5q5sluE.js";
1
+ import { _ as TokenResponse, c as SessionManagerConfig, d as RefreshTokenStore, f as createDrizzleSessionStore, g as OAuth2Endpoints, h as AuthUser, i as UserStore, l as SessionTokens, m as RefreshTokenRecord, n as LocalUser, o as SessionClaims, p as ConsumedToken, r as OAuth2LinkStore, s as SessionManager, t as IdentityLink, u as DrizzleDb } from "./types-C-SlEMFJ.js";
2
+ import { a as PasswordStore, c as IdentityResolverOptions, d as OAuth2Client, f as OAuth2ClientConfig, i as PasswordRecord, l as ResolveProfile, n as PasswordProviderOptions, o as IdentityNotLinkedError, r as UsernameTakenError, s as IdentityResolver, t as PasswordProvider, u as createDefaultIdentityResolver } from "./provider-BkUYQ703.js";
3
+ import { a as WebAuthProvider, c as WEBAUTHN_CREDENTIALS_TABLE, d as WebauthnCredentialStore, i as RegistrationResponse, l as createRestWebauthnCredentialStore, n as AuthenticationResponse, o as WebAuthProviderConfig, r as RegistrationOptions, s as RestWebauthnCredentialStoreOptions, t as AuthenticationOptions, u as createDrizzleWebauthnCredentialStore } from "./provider-Bf7d5mkl.js";
2
4
  import { RestDb } from "@homurajs/db";
5
+ import { NodeSQLiteDatabase } from "drizzle-orm/node-sqlite";
6
+ import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
7
  //#region src/session/rest-store.d.ts
4
8
  export declare const REFRESH_TOKENS_TABLE = "refresh_tokens";
5
9
  interface RestSessionStoreOptions {
@@ -8,4 +12,44 @@ interface RestSessionStoreOptions {
8
12
  }
9
13
  export declare function createRestSessionStore(rest: RestDb, opts?: RestSessionStoreOptions): RefreshTokenStore;
10
14
  //#endregion
11
- export { type AuthUser, type ConsumedToken, OAuth2Client, type OAuth2ClientConfig, type OAuth2Endpoints, type RefreshTokenRecord, type RefreshTokenStore, type RestSessionStoreOptions, type SessionClaims, SessionManager, type SessionManagerConfig, type SessionTokens, type TokenResponse, createDrizzleSessionStore };
15
+ //#region src/identity/user-store.d.ts
16
+ type DrizzleDb$3 = PostgresJsDatabase | NodeSQLiteDatabase;
17
+ export declare function createDrizzleUserStore(db: DrizzleDb$3, opts?: {
18
+ now?: () => number;
19
+ }): UserStore;
20
+ //#endregion
21
+ //#region src/identity/oauth2-link-store.d.ts
22
+ type DrizzleDb$2 = PostgresJsDatabase | NodeSQLiteDatabase;
23
+ export declare function createDrizzleOAuth2LinkStore(db: DrizzleDb$2, opts?: {
24
+ now?: () => number;
25
+ }): OAuth2LinkStore;
26
+ //#endregion
27
+ //#region src/identity/user-rest-store.d.ts
28
+ interface RestUserStoreOptions {
29
+ table?: string;
30
+ now?: () => number;
31
+ }
32
+ export declare function createRestUserStore(rest: RestDb, opts?: RestUserStoreOptions): UserStore;
33
+ //#endregion
34
+ //#region src/identity/oauth2-link-rest-store.d.ts
35
+ interface RestOAuth2LinkStoreOptions {
36
+ table?: string;
37
+ now?: () => number;
38
+ }
39
+ export declare function createRestOAuth2LinkStore(rest: RestDb, opts?: RestOAuth2LinkStoreOptions): OAuth2LinkStore;
40
+ //#endregion
41
+ //#region src/password/drizzle-store.d.ts
42
+ type DrizzleDb$1 = PostgresJsDatabase | NodeSQLiteDatabase;
43
+ export declare function createDrizzlePasswordStore(db: DrizzleDb$1, opts?: {
44
+ now?: () => number;
45
+ }): PasswordStore;
46
+ //#endregion
47
+ //#region src/password/rest-store.d.ts
48
+ export declare const PASSWORDS_TABLE = "passwords";
49
+ interface RestPasswordStoreOptions {
50
+ table?: string;
51
+ now?: () => number;
52
+ }
53
+ export declare function createRestPasswordStore(rest: RestDb, opts?: RestPasswordStoreOptions): PasswordStore;
54
+ //#endregion
55
+ export { type AuthUser, type AuthenticationOptions, type AuthenticationResponse, type ConsumedToken, type DrizzleDb, type IdentityLink, IdentityNotLinkedError, type IdentityResolver, type IdentityResolverOptions, type LocalUser, OAuth2Client, type OAuth2ClientConfig, type OAuth2Endpoints, type OAuth2LinkStore, PasswordProvider, type PasswordProviderOptions, type PasswordRecord, type PasswordStore, type RefreshTokenRecord, type RefreshTokenStore, type RegistrationOptions, type RegistrationResponse, type ResolveProfile, type RestOAuth2LinkStoreOptions, type RestPasswordStoreOptions, type RestSessionStoreOptions, type RestUserStoreOptions, type RestWebauthnCredentialStoreOptions, type SessionClaims, SessionManager, type SessionManagerConfig, type SessionTokens, type TokenResponse, type UserStore, UsernameTakenError, WEBAUTHN_CREDENTIALS_TABLE, WebAuthProvider, type WebAuthProviderConfig, type WebauthnCredentialStore, createDefaultIdentityResolver, createDrizzleSessionStore, createDrizzleWebauthnCredentialStore, createRestWebauthnCredentialStore };
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { i as createDefaultIdentityResolver, n as UsernameTakenError, r as IdentityNotLinkedError, t as PasswordProvider } from "./provider-DMtwLgGf.js";
2
+ import { i as createDrizzleWebauthnCredentialStore, n as WEBAUTHN_CREDENTIALS_TABLE, r as createRestWebauthnCredentialStore, t as WebAuthProvider } from "./provider-zTWN9myS.js";
1
3
  import { randomToken } from "@homurajs/core";
2
- import { createHash, randomBytes } from "node:crypto";
4
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
3
5
  import { SignJWT, jwtVerify } from "jose";
4
6
  import { sql } from "drizzle-orm";
5
7
  import { isSqlite } from "@homurajs/db";
@@ -191,11 +193,11 @@ var SessionManager = class {
191
193
  };
192
194
  //#endregion
193
195
  //#region src/session/store.ts
194
- async function exec(db, q) {
196
+ async function exec$3(db, q) {
195
197
  if (isSqlite(db)) await db.run(q);
196
198
  else await db.execute(q);
197
199
  }
198
- async function query(db, q) {
200
+ async function query$2(db, q) {
199
201
  if (isSqlite(db)) return await db.all(q);
200
202
  return await db.execute(q) ?? [];
201
203
  }
@@ -207,12 +209,12 @@ function createDrizzleSessionStore(db, opts) {
207
209
  const now = opts?.now ?? (() => Date.now());
208
210
  return {
209
211
  async create(r) {
210
- await exec(db, sql`INSERT INTO refresh_tokens (token_hash, family_id, sid, sub, expires_at, profile)
212
+ await exec$3(db, sql`INSERT INTO refresh_tokens (token_hash, family_id, sid, sub, expires_at, profile)
211
213
  VALUES (${r.tokenHash}, ${r.familyId}, ${r.sid}, ${r.sub}, ${r.expiresAt}, ${r.profile ?? null})`);
212
214
  },
213
215
  async consume(tokenHash) {
214
216
  const ts = now();
215
- const claimed = (await query(db, sql`UPDATE refresh_tokens
217
+ const claimed = (await query$2(db, sql`UPDATE refresh_tokens
216
218
  SET used_at = ${ts}
217
219
  WHERE token_hash = ${tokenHash} AND expires_at > ${ts} AND used_at IS NULL
218
220
  RETURNING family_id, sid, sub, profile`))[0];
@@ -223,7 +225,7 @@ RETURNING family_id, sid, sub, profile`))[0];
223
225
  profile: claimed.profile,
224
226
  revoked: false
225
227
  };
226
- const row = (await query(db, sql`SELECT family_id, used_at FROM refresh_tokens WHERE token_hash = ${tokenHash}`))[0];
228
+ const row = (await query$2(db, sql`SELECT family_id, used_at FROM refresh_tokens WHERE token_hash = ${tokenHash}`))[0];
227
229
  if (!row) return null;
228
230
  if (row.used_at !== null) return {
229
231
  familyId: row.family_id,
@@ -235,10 +237,10 @@ RETURNING family_id, sid, sub, profile`))[0];
235
237
  return null;
236
238
  },
237
239
  async revokeFamily(familyId) {
238
- await exec(db, sql`UPDATE refresh_tokens SET used_at = ${now()} WHERE family_id = ${familyId} AND used_at IS NULL`);
240
+ await exec$3(db, sql`UPDATE refresh_tokens SET used_at = ${now()} WHERE family_id = ${familyId} AND used_at IS NULL`);
239
241
  },
240
242
  async revokeUser(sub) {
241
- await exec(db, sql`UPDATE refresh_tokens SET used_at = ${now()} WHERE sub = ${sub} AND used_at IS NULL`);
243
+ await exec$3(db, sql`UPDATE refresh_tokens SET used_at = ${now()} WHERE sub = ${sub} AND used_at IS NULL`);
242
244
  }
243
245
  };
244
246
  }
@@ -306,4 +308,316 @@ function createRestSessionStore(rest, opts = {}) {
306
308
  };
307
309
  }
308
310
  //#endregion
309
- export { OAuth2Client, REFRESH_TOKENS_TABLE, SessionManager, createDrizzleSessionStore, createRestSessionStore };
311
+ //#region src/identity/user-store.ts
312
+ /**
313
+ * Drizzle 双方言 UserStore —— users 表的 CRUD。
314
+ * 复用 session/store.ts 的 exec/query 模式(isSqlite 分支 + 原生 SQL)。
315
+ *
316
+ * ⚠ 列名必须与 tables.ts 的显式 snake_case 对齐(id/username/nickname/avatar_url/created_at/updated_at)。
317
+ */
318
+ async function exec$2(db, q) {
319
+ if (isSqlite(db)) await db.run(q);
320
+ else await db.execute(q);
321
+ }
322
+ async function query$1(db, q) {
323
+ if (isSqlite(db)) return await db.all(q);
324
+ return await db.execute(q) ?? [];
325
+ }
326
+ function toUser$1(row) {
327
+ return {
328
+ id: row.id,
329
+ username: row.username,
330
+ nickname: row.nickname,
331
+ avatarUrl: row.avatar_url,
332
+ createdAt: Number(row.created_at),
333
+ updatedAt: Number(row.updated_at)
334
+ };
335
+ }
336
+ function createDrizzleUserStore(db, opts) {
337
+ const now = opts?.now ?? (() => Date.now());
338
+ return {
339
+ async findById(id) {
340
+ const rows = await query$1(db, sql`SELECT id, username, nickname, avatar_url, created_at, updated_at FROM users WHERE id = ${id}`);
341
+ return rows[0] ? toUser$1(rows[0]) : null;
342
+ },
343
+ async findByUsername(username) {
344
+ const rows = await query$1(db, sql`SELECT id, username, nickname, avatar_url, created_at, updated_at FROM users WHERE username = ${username}`);
345
+ return rows[0] ? toUser$1(rows[0]) : null;
346
+ },
347
+ async create(input) {
348
+ const id = randomUUID();
349
+ const ts = now();
350
+ await exec$2(db, sql`INSERT INTO users (id, username, nickname, avatar_url, created_at, updated_at)
351
+ VALUES (${id}, ${input.username ?? null}, ${input.nickname ?? null}, ${input.avatarUrl ?? null}, ${ts}, ${ts})`);
352
+ return {
353
+ id,
354
+ username: input.username ?? null,
355
+ nickname: input.nickname ?? null,
356
+ avatarUrl: input.avatarUrl ?? null,
357
+ createdAt: ts,
358
+ updatedAt: ts
359
+ };
360
+ },
361
+ async update(id, patch) {
362
+ const ts = now();
363
+ if (patch.nickname !== void 0 && patch.avatarUrl !== void 0) await exec$2(db, sql`UPDATE users SET nickname = ${patch.nickname}, avatar_url = ${patch.avatarUrl}, updated_at = ${ts} WHERE id = ${id}`);
364
+ else if (patch.nickname !== void 0) await exec$2(db, sql`UPDATE users SET nickname = ${patch.nickname}, updated_at = ${ts} WHERE id = ${id}`);
365
+ else if (patch.avatarUrl !== void 0) await exec$2(db, sql`UPDATE users SET avatar_url = ${patch.avatarUrl}, updated_at = ${ts} WHERE id = ${id}`);
366
+ else await exec$2(db, sql`UPDATE users SET updated_at = ${ts} WHERE id = ${id}`);
367
+ const rows = await query$1(db, sql`SELECT id, username, nickname, avatar_url, created_at, updated_at FROM users WHERE id = ${id}`);
368
+ if (!rows[0]) throw new Error("user 不存在: " + id);
369
+ return toUser$1(rows[0]);
370
+ },
371
+ async delete(id) {
372
+ await exec$2(db, sql`DELETE FROM users WHERE id = ${id}`);
373
+ }
374
+ };
375
+ }
376
+ //#endregion
377
+ //#region src/identity/oauth2-link-store.ts
378
+ /**
379
+ * Drizzle 双方言 OAuth2LinkStore —— oauth2_identity_links 表的 CRUD。
380
+ * identity resolver 在 OAuth2 callback 时用 findByProvider 查已有 link,
381
+ * JIT 模式时 create 新 link 并关联新建的 user。
382
+ *
383
+ * ⚠ 列名必须与 tables.ts 的显式 snake_case 对齐
384
+ * (id/user_id/provider/provider_sub/raw_profile/created_at)。
385
+ */
386
+ async function exec$1(db, q) {
387
+ if (isSqlite(db)) await db.run(q);
388
+ else await db.execute(q);
389
+ }
390
+ async function query(db, q) {
391
+ if (isSqlite(db)) return await db.all(q);
392
+ return await db.execute(q) ?? [];
393
+ }
394
+ function toLink$1(row) {
395
+ return {
396
+ id: row.id,
397
+ userId: row.user_id,
398
+ provider: row.provider,
399
+ providerSub: row.provider_sub,
400
+ rawProfile: row.raw_profile,
401
+ createdAt: Number(row.created_at)
402
+ };
403
+ }
404
+ const SELECT_COLS = sql.raw("id, user_id, provider, provider_sub, raw_profile, created_at");
405
+ function createDrizzleOAuth2LinkStore(db, opts) {
406
+ const now = opts?.now ?? (() => Date.now());
407
+ return {
408
+ async findByProvider(provider, providerSub) {
409
+ const rows = await query(db, sql`SELECT ${SELECT_COLS} FROM oauth2_identity_links
410
+ WHERE provider = ${provider} AND provider_sub = ${providerSub}`);
411
+ return rows[0] ? toLink$1(rows[0]) : null;
412
+ },
413
+ async findByUserId(userId) {
414
+ return (await query(db, sql`SELECT ${SELECT_COLS} FROM oauth2_identity_links WHERE user_id = ${userId}`)).map(toLink$1);
415
+ },
416
+ async create(input) {
417
+ const id = randomUUID();
418
+ const ts = now();
419
+ await exec$1(db, sql`INSERT INTO oauth2_identity_links (id, user_id, provider, provider_sub, raw_profile, created_at)
420
+ VALUES (${id}, ${input.userId}, ${input.provider}, ${input.providerSub}, ${input.rawProfile ?? null}, ${ts})`);
421
+ return {
422
+ id,
423
+ userId: input.userId,
424
+ provider: input.provider,
425
+ providerSub: input.providerSub,
426
+ rawProfile: input.rawProfile ?? null,
427
+ createdAt: ts
428
+ };
429
+ },
430
+ async delete(id) {
431
+ await exec$1(db, sql`DELETE FROM oauth2_identity_links WHERE id = ${id}`);
432
+ }
433
+ };
434
+ }
435
+ function toUser(row) {
436
+ return {
437
+ id: row.id,
438
+ username: row.username,
439
+ nickname: row.nickname,
440
+ avatarUrl: row.avatar_url,
441
+ createdAt: Number(row.created_at),
442
+ updatedAt: Number(row.updated_at)
443
+ };
444
+ }
445
+ function createRestUserStore(rest, opts = {}) {
446
+ const t = rest.table(opts.table ?? "users");
447
+ const now = opts.now ?? (() => Date.now());
448
+ return {
449
+ async findById(id) {
450
+ const row = (await t.select({ id }, { limit: 1 }))[0];
451
+ return row ? toUser(row) : null;
452
+ },
453
+ async findByUsername(username) {
454
+ const row = (await t.select({ username }, { limit: 1 }))[0];
455
+ return row ? toUser(row) : null;
456
+ },
457
+ async create(input) {
458
+ const id = randomUUID();
459
+ const ts = now();
460
+ const row = (await t.insert({
461
+ id,
462
+ username: input.username ?? null,
463
+ nickname: input.nickname ?? null,
464
+ avatar_url: input.avatarUrl ?? null,
465
+ created_at: ts,
466
+ updated_at: ts
467
+ }))[0];
468
+ if (!row) throw new Error("insert failed");
469
+ return toUser(row);
470
+ },
471
+ async update(id, patch) {
472
+ const ts = now();
473
+ const row = (await t.update({
474
+ ...patch.nickname !== void 0 ? { nickname: patch.nickname } : {},
475
+ ...patch.avatarUrl !== void 0 ? { avatar_url: patch.avatarUrl } : {},
476
+ updated_at: ts
477
+ }, { id }))[0];
478
+ if (!row) throw new Error("user 不存在: " + id);
479
+ return toUser(row);
480
+ },
481
+ async delete(id) {
482
+ await t.remove({ id });
483
+ }
484
+ };
485
+ }
486
+ function toLink(row) {
487
+ return {
488
+ id: row.id,
489
+ userId: row.user_id,
490
+ provider: row.provider,
491
+ providerSub: row.provider_sub,
492
+ rawProfile: row.raw_profile,
493
+ createdAt: Number(row.created_at)
494
+ };
495
+ }
496
+ function createRestOAuth2LinkStore(rest, opts = {}) {
497
+ const t = rest.table(opts.table ?? "oauth2_identity_links");
498
+ const now = opts.now ?? (() => Date.now());
499
+ return {
500
+ async findByProvider(provider, providerSub) {
501
+ const row = (await t.select({
502
+ provider,
503
+ provider_sub: providerSub
504
+ }, { limit: 1 }))[0];
505
+ return row ? toLink(row) : null;
506
+ },
507
+ async findByUserId(userId) {
508
+ return (await t.select({ user_id: userId })).map(toLink);
509
+ },
510
+ async create(input) {
511
+ const id = randomUUID();
512
+ const ts = now();
513
+ const row = (await t.insert({
514
+ id,
515
+ user_id: input.userId,
516
+ provider: input.provider,
517
+ provider_sub: input.providerSub,
518
+ raw_profile: input.rawProfile ?? null,
519
+ created_at: ts
520
+ }))[0];
521
+ if (!row) throw new Error("insert failed");
522
+ return toLink(row);
523
+ },
524
+ async delete(id) {
525
+ await t.remove({ id });
526
+ }
527
+ };
528
+ }
529
+ //#endregion
530
+ //#region src/password/drizzle-store.ts
531
+ /**
532
+ * Drizzle 双方言 PasswordStore 实现。
533
+ *
534
+ * ⚠ 列名必须与 tables.ts 的显式 snake_case 对齐
535
+ * (user_id/password_hash/created_at/updated_at)。
536
+ */
537
+ async function exec(db, q) {
538
+ if (isSqlite(db)) await db.run(q);
539
+ else await db.execute(q);
540
+ }
541
+ async function queryOne(db, q) {
542
+ if (isSqlite(db)) return (await db.all(q))[0] ?? null;
543
+ return (await db.execute(q))?.[0] ?? null;
544
+ }
545
+ function toRecord$1(row) {
546
+ return {
547
+ userId: row.user_id,
548
+ passwordHash: row.password_hash,
549
+ createdAt: Number(row.created_at),
550
+ updatedAt: Number(row.updated_at)
551
+ };
552
+ }
553
+ function createDrizzlePasswordStore(db, opts) {
554
+ const now = opts?.now ?? (() => Date.now());
555
+ return {
556
+ async findByUserId(userId) {
557
+ const row = await queryOne(db, sql`SELECT user_id, password_hash, created_at, updated_at FROM passwords WHERE user_id = ${userId}`);
558
+ return row ? toRecord$1(row) : null;
559
+ },
560
+ async create(input) {
561
+ const ts = now();
562
+ await exec(db, sql`INSERT INTO passwords (user_id, password_hash, created_at, updated_at)
563
+ VALUES (${input.userId}, ${input.passwordHash}, ${ts}, ${ts})`);
564
+ return {
565
+ userId: input.userId,
566
+ passwordHash: input.passwordHash,
567
+ createdAt: ts,
568
+ updatedAt: ts
569
+ };
570
+ },
571
+ async updateHash(userId, passwordHash) {
572
+ const ts = now();
573
+ await exec(db, sql`UPDATE passwords SET password_hash = ${passwordHash}, updated_at = ${ts} WHERE user_id = ${userId}`);
574
+ },
575
+ async delete(userId) {
576
+ await exec(db, sql`DELETE FROM passwords WHERE user_id = ${userId}`);
577
+ }
578
+ };
579
+ }
580
+ //#endregion
581
+ //#region src/password/rest-store.ts
582
+ const PASSWORDS_TABLE = "passwords";
583
+ function toRecord(row) {
584
+ return {
585
+ userId: row.user_id,
586
+ passwordHash: row.password_hash,
587
+ createdAt: Number(row.created_at),
588
+ updatedAt: Number(row.updated_at)
589
+ };
590
+ }
591
+ function createRestPasswordStore(rest, opts = {}) {
592
+ const t = rest.table(opts.table ?? "passwords");
593
+ const now = opts.now ?? (() => Date.now());
594
+ return {
595
+ async findByUserId(userId) {
596
+ const row = (await t.select({ user_id: userId }, { limit: 1 }))[0];
597
+ return row ? toRecord(row) : null;
598
+ },
599
+ async create(input) {
600
+ const ts = now();
601
+ const row = (await t.insert({
602
+ user_id: input.userId,
603
+ password_hash: input.passwordHash,
604
+ created_at: ts,
605
+ updated_at: ts
606
+ }))[0];
607
+ if (!row) throw new Error("insert failed");
608
+ return toRecord(row);
609
+ },
610
+ async updateHash(userId, passwordHash) {
611
+ const ts = now();
612
+ await t.update({
613
+ password_hash: passwordHash,
614
+ updated_at: ts
615
+ }, { user_id: userId });
616
+ },
617
+ async delete(userId) {
618
+ await t.remove({ user_id: userId });
619
+ }
620
+ };
621
+ }
622
+ //#endregion
623
+ export { IdentityNotLinkedError, OAuth2Client, PASSWORDS_TABLE, PasswordProvider, REFRESH_TOKENS_TABLE, SessionManager, UsernameTakenError, WEBAUTHN_CREDENTIALS_TABLE, WebAuthProvider, createDefaultIdentityResolver, createDrizzleOAuth2LinkStore, createDrizzlePasswordStore, createDrizzleSessionStore, createDrizzleUserStore, createDrizzleWebauthnCredentialStore, createRestOAuth2LinkStore, createRestPasswordStore, createRestSessionStore, createRestUserStore, createRestWebauthnCredentialStore };
@@ -0,0 +1,164 @@
1
+ import { a as WebauthnCredentialRecord, i as UserStore, n as LocalUser } from "./types-C-SlEMFJ.js";
2
+ import { TtlKv } from "@homurajs/core";
3
+ import { RestDb } from "@homurajs/db";
4
+ import { NodeSQLiteDatabase } from "drizzle-orm/node-sqlite";
5
+ import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
6
+ //#region src/webauthn/credential-store.d.ts
7
+ interface WebauthnCredentialStore {
8
+ /** 按 credentialId 查;不存在返回 null */
9
+ findById(id: string): Promise<WebauthnCredentialRecord | null>;
10
+ /** 按 userId 查所有凭证 */
11
+ findByUserId(userId: string): Promise<WebauthnCredentialRecord[]>;
12
+ /** 创建凭证 */
13
+ create(input: {
14
+ id: string;
15
+ userId: string;
16
+ publicKey: string;
17
+ counter: number;
18
+ deviceType: string;
19
+ backedUp: number;
20
+ transports?: string | null;
21
+ displayName?: string | null;
22
+ }): Promise<WebauthnCredentialRecord>;
23
+ /** 更新 counter(验证成功后调用) */
24
+ updateCounter(id: string, counter: number): Promise<void>;
25
+ /** 删除凭证 */
26
+ delete(id: string): Promise<void>;
27
+ }
28
+ //#endregion
29
+ //#region src/webauthn/credential-drizzle-store.d.ts
30
+ type DrizzleDb = PostgresJsDatabase | NodeSQLiteDatabase;
31
+ declare function createDrizzleWebauthnCredentialStore(db: DrizzleDb, opts?: {
32
+ now?: () => number;
33
+ }): WebauthnCredentialStore;
34
+ //#endregion
35
+ //#region src/webauthn/credential-rest-store.d.ts
36
+ declare const WEBAUTHN_CREDENTIALS_TABLE = "webauthn_credentials";
37
+ interface RestWebauthnCredentialStoreOptions {
38
+ table?: string;
39
+ now?: () => number;
40
+ }
41
+ declare function createRestWebauthnCredentialStore(rest: RestDb, opts?: RestWebauthnCredentialStoreOptions): WebauthnCredentialStore;
42
+ //#endregion
43
+ //#region src/webauthn/provider.d.ts
44
+ /**
45
+ * WebAuthn RP 配置。
46
+ * rpId 通常是域名(如 'app.example.com'),origin 是完整 origin(如 'https://app.example.com')。
47
+ */
48
+ interface WebAuthProviderConfig {
49
+ rpId: string;
50
+ rpName: string;
51
+ /** 允许的 origin(如 ['https://app.example.com']) */
52
+ origin: string | string[];
53
+ credentialStore: WebauthnCredentialStore;
54
+ userStore: UserStore;
55
+ /** challenge 存储(TtlKv),TTL 5min */
56
+ challengeKv: TtlKv;
57
+ /** challenge TTL(默认 5min) */
58
+ challengeTtlMs?: number;
59
+ now?: () => number;
60
+ }
61
+ /** 注册 options(传给客户端 @simplewebauthn/browser 的 startRegistration) */
62
+ interface RegistrationOptions {
63
+ challenge: string;
64
+ rp: {
65
+ name: string;
66
+ id: string;
67
+ };
68
+ user: {
69
+ id: string;
70
+ name: string;
71
+ displayName: string;
72
+ };
73
+ pubKeyCredParams: Array<{
74
+ type: 'public-key';
75
+ alg: number;
76
+ }>;
77
+ timeout: number;
78
+ attestationType: string;
79
+ authenticatorSelection: {
80
+ userVerification: string;
81
+ residentKey: string;
82
+ };
83
+ }
84
+ /** 认证 options(传给客户端 @simplewebauthn/browser 的 startAuthentication) */
85
+ interface AuthenticationOptions {
86
+ challenge: string;
87
+ timeout: number;
88
+ rpId: string;
89
+ allowCredentials: Array<{
90
+ type: 'public-key';
91
+ id: string;
92
+ transports?: string[];
93
+ }>;
94
+ userVerification: string;
95
+ }
96
+ /** 注册验证输入(客户端 attestation 响应) */
97
+ interface RegistrationResponse {
98
+ id: string;
99
+ rawId: string;
100
+ response: {
101
+ clientDataJSON: string;
102
+ attestationObject: string;
103
+ transports?: string[];
104
+ };
105
+ type: string;
106
+ clientExtensionResults?: Record<string, unknown>;
107
+ }
108
+ /** 认证验证输入(客户端 assertion 响应) */
109
+ interface AuthenticationResponse {
110
+ id: string;
111
+ rawId: string;
112
+ response: {
113
+ clientDataJSON: string;
114
+ authenticatorData: string;
115
+ signature: string;
116
+ userHandle?: string | null;
117
+ };
118
+ type: string;
119
+ clientExtensionResults?: Record<string, unknown>;
120
+ }
121
+ declare class WebAuthProvider {
122
+ private readonly config;
123
+ private readonly challengeTtl;
124
+ private readonly nowFn;
125
+ constructor(config: WebAuthProviderConfig);
126
+ /**
127
+ * 生成注册 options(已登录用户发起)。
128
+ * challenge 存入 TtlKv(key = userId:challengeType),TTL 5min。
129
+ */
130
+ generateRegistrationOptions(userId: string): Promise<RegistrationOptions>;
131
+ /**
132
+ * 验证注册响应并存储凭证。
133
+ * challenge 一次性消费(take 语义)。
134
+ */
135
+ verifyRegistration(userId: string, response: RegistrationResponse, displayName?: string): Promise<{
136
+ id: string;
137
+ }>;
138
+ /**
139
+ * 生成认证 options(未登录用户发起)。
140
+ * challenge 存入 TtlKv,返回 { options, challengeKey }——challengeKey 必须由调用方
141
+ * 在 verifyAuthentication 时回传,否则匿名(discoverable credential)流程无法定位 challenge:
142
+ * 匿名场景下 options 阶段还不知道是哪个用户/凭证,key 只能是 `anon:${random}`,
143
+ * 而 verify 阶段是按 response.id 反查凭证的,两条腿对不上同一个 key。
144
+ * 已知 userId 时 key 固定为 `${userId}:auth`,回传与否都能命中(向后兼容)。
145
+ */
146
+ generateAuthenticationOptions(userId?: string): Promise<{
147
+ options: AuthenticationOptions;
148
+ challengeKey: string;
149
+ }>;
150
+ /**
151
+ * 验证认证响应,返回已验证的用户(供 session 创建)。
152
+ * challenge 一次性消费。
153
+ *
154
+ * @param challengeKey generateAuthenticationOptions 返回的 key。匿名流程**必须**回传;
155
+ * 已知 userId 的流程可省略(缺省按 `${cred.userId}:auth` 取)。
156
+ */
157
+ verifyAuthentication(response: AuthenticationResponse, challengeKey?: string): Promise<LocalUser>;
158
+ /** 列出用户的所有凭证(管理用) */
159
+ listCredentials(userId: string): Promise<WebauthnCredentialRecord[]>;
160
+ /** 删除凭证 */
161
+ deleteCredential(userId: string, credentialId: string): Promise<void>;
162
+ }
163
+ //#endregion
164
+ export { WebAuthProvider as a, WEBAUTHN_CREDENTIALS_TABLE as c, WebauthnCredentialStore as d, RegistrationResponse as i, createRestWebauthnCredentialStore as l, AuthenticationResponse as n, WebAuthProviderConfig as o, RegistrationOptions as r, RestWebauthnCredentialStoreOptions as s, AuthenticationOptions as t, createDrizzleWebauthnCredentialStore as u };