@iskra-bun/web-kit 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @iskra-bun/web-kit
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - **Breaking:** public class renames for naming consistency with the `Driver`/`Feature` conventions:
8
+
9
+ - `WebServer` → `WebDriver` (same `{ port, routes }` options).
10
+ - `HealthFeature` → `HealthCheckFeature`.
11
+
12
+ Update imports accordingly: `import { WebDriver } from '@iskra-bun/web-kit'`. See the "Upgrading to 0.2" guide for the full migration. (Pre-1.0, but bumped as a minor to signal the break.)
13
+
14
+ ### Patch Changes
15
+
16
+ - f9654df: `DbDriver` and `DbFeature` now accept an optional schema generic (`DbDriver<TSchema>` / `DbFeature<TSchema>`), so `.db` is a typed Drizzle database instead of `any` — opt-in callers get typed relational queries and autocomplete. The generic defaults preserve existing behavior, so no call site needs changes; consumers that relied on `any` may need to add a type argument or annotation.
17
+ - f9654df: Internal refactor: the email, storage, and auth features now delegate to the new standalone `@iskra-bun/mailer-kit`, `@iskra-bun/storage-kit`, and `@iskra-bun/auth-kit` packages instead of bundling their own copies. The public API (`EmailFeature`, `StorageFeature`, `AuthFeature`, and the types/adapters they re-export) is unchanged. The now-transitive `nodemailer`, `@sendgrid/mail`, and `@aws-sdk/*` direct dependencies were dropped. Note: `MockEmailAdapter` no longer prints a `console.log` line on send (it is now silent).
18
+ - Security fixes for the web server:
19
+
20
+ - The health endpoint no longer leaks internal details by default: `includeDetails` defaults to `false`, failing feature/custom/db checks return only `{ status: 'error' }`, and the raw error is logged server-side only instead of being returned in the response.
21
+ - API keys are no longer used verbatim as cache keys — the cache key is now a SHA-256 hash of the key, so raw secrets are kept out of the cache layer.
22
+ - The CSRF kill-switch (`disableCSRFCheck`) is ignored in production: it is only forwarded when `NODE_ENV !== 'production'`, so CSRF protection cannot be accidentally disabled in a production deployment.
23
+
24
+ - f9654df: Security and correctness fixes:
25
+
26
+ - API key ids are now random and no longer expose a prefix of the secret key.
27
+ - CSRF tokens are HMAC-signed (signed double-submit cookie) and compared in constant time; forged, tampered, and unsigned tokens are rejected.
28
+ - `/health/ready` now runs readiness checks registered via `addReadinessCheck()` and returns 503 when any fails, instead of always reporting ready.
29
+
30
+ - Updated dependencies [f9654df]
31
+ - Updated dependencies
32
+ - Updated dependencies [f9654df]
33
+ - Updated dependencies
34
+ - Updated dependencies
35
+ - Updated dependencies [f9654df]
36
+ - Updated dependencies [f9654df]
37
+ - Updated dependencies [f9654df]
38
+ - Updated dependencies
39
+ - @iskra-bun/auth-kit@0.1.0
40
+ - @iskra-bun/core@0.1.1
41
+ - @iskra-bun/mailer-kit@0.1.0
42
+ - @iskra-bun/storage-kit@0.1.0
43
+
3
44
  ## 0.1.0
4
45
 
5
46
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -4,9 +4,17 @@ import { Driver, App, IskraError, ErrorCode } from '@iskra-bun/core';
4
4
  export { ErrorCode, ErrorCodes } from '@iskra-bun/core';
5
5
  import { z } from 'zod';
6
6
  import { HTTPException } from 'hono/http-exception';
7
- import { Auth as Auth$1 } from 'better-auth';
7
+ import * as _iskra_bun_storage_kit from '@iskra-bun/storage-kit';
8
+ import { BaseStorageAdapter, StorageConfig } from '@iskra-bun/storage-kit';
9
+ export { BaseStorageAdapter, LocalStorageAdapter, StorageConfig, StorageFile } from '@iskra-bun/storage-kit';
10
+ import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
11
+ import { MySql2Database } from 'drizzle-orm/mysql2';
12
+ import { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
13
+ import { User, createBetterAuth, Auth } from '@iskra-bun/auth-kit';
8
14
  import * as hono_utils_types from 'hono/utils/types';
9
15
  import { ErrorObject } from 'ajv';
16
+ import { EmailAdapter, EmailConfig } from '@iskra-bun/mailer-kit';
17
+ export { EmailAdapter, EmailConfig, EmailMessage, MockEmailAdapter, TemplateData } from '@iskra-bun/mailer-kit';
10
18
  export { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
11
19
 
12
20
  /**
@@ -140,6 +148,9 @@ interface HealthCheckConfig {
140
148
  details?: any;
141
149
  }>;
142
150
  };
151
+ readinessChecks?: {
152
+ [name: string]: () => Promise<boolean>;
153
+ };
143
154
  }
144
155
  interface RequestIdConfig {
145
156
  headerName?: string;
@@ -336,6 +347,7 @@ declare class WebDriver implements Driver {
336
347
  private runningServer;
337
348
  constructor(options?: WebServerOptions);
338
349
  init(app: App): void;
350
+ private setupSecurityHeaders;
339
351
  private setupOpenApi;
340
352
  private setupRoutes;
341
353
  start(): void;
@@ -363,7 +375,9 @@ declare class HealthCheckFeature implements Feature {
363
375
  name: string;
364
376
  private kernel?;
365
377
  private config;
378
+ private readinessChecks;
366
379
  constructor(config?: HealthCheckConfig);
380
+ addReadinessCheck(name: string, check: () => Promise<boolean>): void;
367
381
  initialize(kernel: Kernel): Promise<void>;
368
382
  private handleHealthCheck;
369
383
  private checkFeatureHealth;
@@ -397,78 +411,6 @@ declare class RequestIdFeature implements Feature {
397
411
  private defaultGenerator;
398
412
  }
399
413
 
400
- interface StorageConfig {
401
- adapter: "local" | "minio" | "s3";
402
- basePath?: string;
403
- connection?: {
404
- endpoint?: string;
405
- accessKey?: string;
406
- secretKey?: string;
407
- bucket?: string;
408
- region?: string;
409
- useSSL?: boolean;
410
- };
411
- }
412
- interface StorageFile {
413
- name: string;
414
- path: string;
415
- size: number;
416
- mimeType?: string;
417
- lastModified?: Date;
418
- url?: string;
419
- }
420
- interface PutOptions {
421
- contentType?: string;
422
- metadata?: Record<string, string>;
423
- public?: boolean;
424
- }
425
- interface StorageAdapter {
426
- connect(): Promise<void>;
427
- disconnect(): Promise<void>;
428
- put(path: string, data: Uint8Array | Buffer | ReadableStream, options?: PutOptions): Promise<StorageFile>;
429
- get(path: string): Promise<Uint8Array | null>;
430
- delete(path: string): Promise<void>;
431
- exists(path: string): Promise<boolean>;
432
- list(prefix?: string): Promise<StorageFile[]>;
433
- url(path: string, expiresIn?: number): Promise<string>;
434
- copy(from: string, to: string): Promise<void>;
435
- move(from: string, to: string): Promise<void>;
436
- isDirectory(path: string): Promise<boolean>;
437
- }
438
- declare abstract class BaseStorageAdapter implements StorageAdapter {
439
- protected connected: boolean;
440
- abstract connect(): Promise<void>;
441
- abstract disconnect(): Promise<void>;
442
- abstract put(path: string, data: Uint8Array | Buffer | ReadableStream, options?: PutOptions): Promise<StorageFile>;
443
- abstract get(path: string): Promise<Uint8Array | null>;
444
- abstract delete(path: string): Promise<void>;
445
- abstract exists(path: string): Promise<boolean>;
446
- abstract list(prefix?: string): Promise<StorageFile[]>;
447
- abstract url(path: string, expiresIn?: number): Promise<string>;
448
- abstract isDirectory(path: string): Promise<boolean>;
449
- isConnected(): boolean;
450
- protected ensureConnected(): void;
451
- copy(from: string, to: string): Promise<void>;
452
- move(from: string, to: string): Promise<void>;
453
- protected generateFileName(originalName: string): string;
454
- protected sanitizePath(path: string): string;
455
- protected getMimeType(filename: string): string;
456
- }
457
-
458
- declare class LocalStorageAdapter extends BaseStorageAdapter {
459
- private basePath;
460
- constructor(config: StorageConfig);
461
- connect(): Promise<void>;
462
- disconnect(): Promise<void>;
463
- put(filePath: string, data: Uint8Array | Buffer, options?: PutOptions): Promise<StorageFile>;
464
- get(filePath: string): Promise<Uint8Array | null>;
465
- delete(filePath: string): Promise<void>;
466
- exists(filePath: string): Promise<boolean>;
467
- isDirectory(filePath: string): Promise<boolean>;
468
- list(prefix?: string): Promise<StorageFile[]>;
469
- url(filePath: string, _expiresIn?: number): Promise<string>;
470
- }
471
-
472
414
  declare module "hono" {
473
415
  interface ContextVariableMap {
474
416
  storage: BaseStorageAdapter;
@@ -484,16 +426,24 @@ declare class StorageFeature implements Feature {
484
426
  getAdapter(): BaseStorageAdapter | undefined;
485
427
  }
486
428
 
429
+ /**
430
+ * The Drizzle database handle a {@link DbFeature} exposes, parameterized by the
431
+ * caller's schema. A union of the supported dialect databases — all share the
432
+ * same `TSchema extends Record<string, unknown> = Record<string, never>`
433
+ * parameter, so passing a schema types `db.query.*` for opt-in callers while the
434
+ * default `Record<string, never>` reproduces the historical untyped behavior.
435
+ */
436
+ type WebKitDrizzleDb<TSchema extends Record<string, unknown> = Record<string, never>> = PostgresJsDatabase<TSchema> | MySql2Database<TSchema> | BunSQLiteDatabase<TSchema>;
487
437
  declare module "hono" {
488
438
  interface ContextVariableMap {
489
- db: any;
439
+ db: WebKitDrizzleDb;
490
440
  }
491
441
  }
492
- declare class DbFeature implements Feature {
442
+ declare class DbFeature<TSchema extends Record<string, unknown> = Record<string, never>> implements Feature {
493
443
  private config;
494
444
  name: string;
495
445
  private client;
496
- db: any;
446
+ db: WebKitDrizzleDb<TSchema>;
497
447
  readonly adapter: string;
498
448
  constructor(config: DbConfig);
499
449
  initialize(kernel: Kernel): Promise<void>;
@@ -541,55 +491,6 @@ declare class SessionFeature implements Feature {
541
491
  shutdown(): Promise<void>;
542
492
  }
543
493
 
544
- interface BetterAuthConfigOptions {
545
- db: any;
546
- adapterType: "postgres" | "mysql" | "sqlite";
547
- secret: string;
548
- baseURL?: string;
549
- basePath?: string;
550
- trustedOrigins?: string[];
551
- enableEmailPassword?: boolean;
552
- disableCSRFCheck?: boolean;
553
- socialProviders?: Record<string, any>;
554
- oidcConfig?: {
555
- clientId: string;
556
- clientSecret: string;
557
- issuer: string;
558
- providerId?: string;
559
- authorizationEndpoint?: string;
560
- tokenEndpoint?: string;
561
- userinfoEndpoint?: string;
562
- jwksEndpoint?: string;
563
- discoveryEndpoint?: string;
564
- scopes?: string[];
565
- pkce?: boolean;
566
- mapping?: {
567
- id?: string;
568
- email?: string;
569
- emailVerified?: string;
570
- name?: string;
571
- image?: string;
572
- extraFields?: Record<string, string>;
573
- };
574
- };
575
- }
576
- declare function createBetterAuth(options: BetterAuthConfigOptions): Auth$1;
577
- type Auth = ReturnType<typeof createBetterAuth>;
578
-
579
- /**
580
- * Auth feature types and interfaces
581
- */
582
- interface User {
583
- id: string;
584
- email: string;
585
- emailVerified: boolean;
586
- name?: string | null;
587
- image?: string | null;
588
- createdAt: Date;
589
- updatedAt: Date;
590
- [key: string]: any;
591
- }
592
-
593
494
  declare module "hono" {
594
495
  interface ContextVariableMap {
595
496
  user: User | null;
@@ -603,8 +504,13 @@ declare class AuthFeature implements Feature {
603
504
  private config;
604
505
  private kernel?;
605
506
  private authMode;
606
- constructor(config: AuthConfig);
507
+ private createAuth;
508
+ constructor(config: AuthConfig, createAuth?: typeof createBetterAuth);
607
509
  initialize(kernel: Kernel): Promise<void>;
510
+ private authRateLimitHits;
511
+ private readonly authRateLimitWindowMs;
512
+ private readonly authRateLimitMax;
513
+ private authRateLimitMiddleware;
608
514
  routes(app: Hono): void;
609
515
  getAuth(): Auth | undefined;
610
516
  }
@@ -644,17 +550,19 @@ declare class PermissionsFeature implements Feature {
644
550
  declare function requirePermission(permission: string): (c: Context, next: Next) => Promise<void>;
645
551
  declare function requireRole(role: string): (c: Context, next: Next) => Promise<void>;
646
552
 
553
+ type ResolvedApiKeyConfig = Required<Omit<ApiKeyConfig, "vaultService" | "customExtractor" | "onError" | "onValidated">> & Pick<ApiKeyConfig, "vaultService" | "customExtractor" | "onError" | "onValidated">;
647
554
  declare class ApiKeyStore {
648
555
  private config;
649
556
  private kernel;
650
557
  private staticKeysMap;
651
558
  private cache?;
652
- constructor(config: Required<ApiKeyConfig>, kernel: Kernel);
559
+ constructor(config: ResolvedApiKeyConfig, kernel: Kernel);
653
560
  private getCache;
654
561
  private initializeStaticKeys;
655
562
  private generateId;
656
563
  private compareKeys;
657
564
  private isExpired;
565
+ private cacheKeyFor;
658
566
  validate(key: string): Promise<ApiKeyValidationResult>;
659
567
  hasScopes(key: ApiKeyMetadata, requiredScopes: string[]): boolean;
660
568
  }
@@ -787,10 +695,11 @@ declare class UploadHelper {
787
695
  private basePath;
788
696
  constructor(storage: BaseStorageAdapter, projectName: string);
789
697
  getBasePath(): string;
698
+ private safeBasename;
790
699
  private buildPath;
791
700
  upload(filename: string, data: Uint8Array | string, subfolder?: string, options?: UploadOptions): Promise<UploadResult>;
792
701
  uploadFromRequest(request: Request, fieldName: string, subfolder?: string, options?: UploadOptions): Promise<UploadResult>;
793
- list(subfolder?: string): Promise<StorageFile[]>;
702
+ list(subfolder?: string): Promise<_iskra_bun_storage_kit.StorageFile[]>;
794
703
  get(filename: string, subfolder?: string): Promise<Uint8Array<ArrayBufferLike> | null>;
795
704
  delete(filename: string, subfolder?: string): Promise<void>;
796
705
  getUrl(filename: string, subfolder?: string, expiresIn?: number): Promise<string>;
@@ -829,68 +738,6 @@ declare class OtelTracingFeature implements Feature {
829
738
  initialize(kernel: Kernel): Promise<void>;
830
739
  }
831
740
 
832
- interface EmailConfig {
833
- provider: "smtp" | "sendgrid" | "mock" | "mailgun" | "ses";
834
- smtp?: {
835
- host: string;
836
- port: number;
837
- username: string;
838
- password: string;
839
- secure?: boolean;
840
- };
841
- apiKey?: string;
842
- apiSecret?: string;
843
- region?: string;
844
- domain?: string;
845
- baseUrl?: string;
846
- from?: {
847
- name?: string;
848
- email: string;
849
- };
850
- templateDir?: string;
851
- }
852
- interface EmailMessage {
853
- to: string | string[];
854
- from?: {
855
- name?: string;
856
- email: string;
857
- };
858
- subject: string;
859
- text?: string;
860
- html?: string;
861
- cc?: string | string[];
862
- bcc?: string | string[];
863
- replyTo?: string;
864
- attachments?: Array<{
865
- filename: string;
866
- content: Uint8Array | string;
867
- contentType?: string;
868
- }>;
869
- headers?: Record<string, string>;
870
- }
871
- interface TemplateData {
872
- [key: string]: unknown;
873
- }
874
- interface EmailAdapter {
875
- send(message: EmailMessage): Promise<{
876
- messageId: string;
877
- success: boolean;
878
- }>;
879
- sendTemplate(templateName: string, to: string | string[], data: TemplateData): Promise<{
880
- messageId: string;
881
- success: boolean;
882
- }>;
883
- }
884
- declare class MockEmailAdapter implements EmailAdapter {
885
- send(message: EmailMessage): Promise<{
886
- messageId: string;
887
- success: boolean;
888
- }>;
889
- sendTemplate(name: string, to: string | string[], data: TemplateData): Promise<{
890
- messageId: string;
891
- success: boolean;
892
- }>;
893
- }
894
741
  declare module "hono" {
895
742
  interface ContextVariableMap {
896
743
  email: EmailAdapter;
@@ -902,7 +749,6 @@ declare class EmailFeature implements Feature {
902
749
  private adapter?;
903
750
  constructor(config: EmailConfig);
904
751
  initialize(kernel: Kernel): Promise<void>;
905
- private createAdapter;
906
752
  getAdapter(): EmailAdapter;
907
753
  }
908
754
 
@@ -963,4 +809,4 @@ declare class ConflictError extends HttpError {
963
809
  });
964
810
  }
965
811
 
966
- export { type ApiKeyConfig, ApiKeyFeature, type ApiKeyMetadata, ApiKeyStore, type ApiKeyValidationResult, type ApiResponse, type AuthConfig, AuthError, AuthFeature, BaseStorageAdapter, type CacheAdapter, type CacheConfig, CacheFeature, ConflictError, type CorsConfig, CorsFeature, type CsrfConfig, CsrfFeature, type DbConfig, DbFeature, type EmailAdapter, type EmailConfig, EmailFeature, type EmailMessage, type ErrorHandlerConfig, ErrorHandlerFeature, type ErrorResponse, type Feature, ForbiddenError, type FormattedValidationErrors, type HealthCheckConfig, HealthCheckFeature, HttpError, JsonSchemaValidationFeature, type JsonValidationOptions, type JsonValidationSchema, Kernel, type KernelConfig, LocalStorageAdapter, type LoggerConfig, LoggerFeature, MockEmailAdapter, NotFoundError, type OpenAPIConfig, OpenAPIFeature, type OtelTracingConfig, OtelTracingFeature, type PermissionsConfig, PermissionsFeature, type RateLimitConfig, RateLimitFeature, type RequestIdConfig, RequestIdFeature, type Role, type RouteOptions, type SecurityHeadersConfig, type SessionConfig, SessionFeature, type Sink, type StorageConfig, StorageFeature, type StorageFile, type SuccessResponse, type TemplateData, type UploadConfig, UploadFeature, ValidationError, ValidationFeature, type ValidationOptions, type ValidationSchema, type WebContext, WebDriver, WebPlugin, type WebPluginConfig, type WebServerOptions, createHttpError, createJsonSchemaValidationMiddleware, createRouter, createValidationMiddleware, errorResponse, formatAjvErrors, requireApiKey, requireAuth, requireCsrf, requirePermission, requireRole, requireScope, successResponse };
812
+ export { type ApiKeyConfig, ApiKeyFeature, type ApiKeyMetadata, ApiKeyStore, type ApiKeyValidationResult, type ApiResponse, type AuthConfig, AuthError, AuthFeature, type CacheAdapter, type CacheConfig, CacheFeature, ConflictError, type CorsConfig, CorsFeature, type CsrfConfig, CsrfFeature, type DbConfig, DbFeature, EmailFeature, type ErrorHandlerConfig, ErrorHandlerFeature, type ErrorResponse, type Feature, ForbiddenError, type FormattedValidationErrors, type HealthCheckConfig, HealthCheckFeature, HttpError, JsonSchemaValidationFeature, type JsonValidationOptions, type JsonValidationSchema, Kernel, type KernelConfig, type LoggerConfig, LoggerFeature, NotFoundError, type OpenAPIConfig, OpenAPIFeature, type OtelTracingConfig, OtelTracingFeature, type PermissionsConfig, PermissionsFeature, type RateLimitConfig, RateLimitFeature, type RequestIdConfig, RequestIdFeature, type Role, type RouteOptions, type SecurityHeadersConfig, type SessionConfig, SessionFeature, type Sink, StorageFeature, type SuccessResponse, type UploadConfig, UploadFeature, ValidationError, ValidationFeature, type ValidationOptions, type ValidationSchema, type WebContext, WebDriver, type WebKitDrizzleDb, WebPlugin, type WebPluginConfig, type WebServerOptions, createHttpError, createJsonSchemaValidationMiddleware, createRouter, createValidationMiddleware, errorResponse, formatAjvErrors, requireApiKey, requireAuth, requireCsrf, requirePermission, requireRole, requireScope, successResponse };