webspresso 0.0.98 → 0.0.99

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.
Files changed (56) hide show
  1. package/.agents/ADMIN.md +11 -1
  2. package/.agents/AGENTS.md +1 -1
  3. package/.agents/AUTH.md +169 -0
  4. package/.agents/ORM.md +88 -4
  5. package/.agents/PLUGINS.md +73 -5
  6. package/.agents/QUEUE.md +84 -0
  7. package/.agents/ROUTING.md +127 -7
  8. package/AGENTS.md +1 -0
  9. package/core/auth/index.js +60 -0
  10. package/core/auth/manager.js +134 -0
  11. package/core/auth/middleware.js +88 -0
  12. package/core/auth/oauth.js +517 -0
  13. package/core/auth/qrcode.js +499 -0
  14. package/core/auth/totp.js +504 -0
  15. package/core/orm/eager-loader.js +144 -10
  16. package/core/orm/index.js +8 -0
  17. package/core/orm/model.js +4 -0
  18. package/core/orm/query-builder.js +90 -4
  19. package/core/orm/repository.js +44 -3
  20. package/core/orm/search.js +213 -0
  21. package/core/queue/cron.js +331 -0
  22. package/core/queue/index.js +8 -1
  23. package/core/queue/manager.js +82 -1
  24. package/core/queue/scheduler.js +359 -0
  25. package/core/sse/index.js +19 -0
  26. package/core/sse/stream.js +430 -0
  27. package/index.d.ts +528 -0
  28. package/index.js +44 -0
  29. package/package.json +1 -1
  30. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +323 -31
  31. package/plugins/admin-panel/client/parts/08-record-list.js +5 -0
  32. package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
  33. package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
  34. package/plugins/admin-panel/field-renderers/index.js +2 -0
  35. package/plugins/api-key/crypto.js +101 -0
  36. package/plugins/api-key/index.js +288 -0
  37. package/plugins/api-key/middleware.js +461 -0
  38. package/plugins/api-key/migration-template.js +48 -0
  39. package/plugins/api-key/model.js +50 -0
  40. package/plugins/api-key/scopes.js +125 -0
  41. package/plugins/audit-log/admin-component.js +487 -70
  42. package/plugins/audit-log/api-handlers.js +114 -0
  43. package/plugins/audit-log/index.js +24 -0
  44. package/plugins/file-manager/admin-component.js +20 -4
  45. package/plugins/file-manager/index.js +21 -4
  46. package/plugins/index.js +6 -0
  47. package/plugins/oauth/index.js +338 -0
  48. package/plugins/queue/index.js +4 -0
  49. package/src/api/api-loader.js +1 -0
  50. package/src/discovery/file-route-parser.js +44 -1
  51. package/src/discovery/scan-routes.js +2 -2
  52. package/src/file-router.js +137 -100
  53. package/src/helpers.js +9 -0
  54. package/src/routing/cascading.js +378 -0
  55. package/src/routing/middleware-resolver.js +92 -0
  56. package/src/server.js +6 -0
package/index.d.ts CHANGED
@@ -541,8 +541,47 @@ export function mountPages(
541
541
  registerDynamicFileRoutes: () => void;
542
542
  };
543
543
 
544
+ export interface CascadingLayoutInfo {
545
+ layoutRelPath: string;
546
+ fullPath: string;
547
+ configPath: string | null;
548
+ }
549
+
550
+ export namespace cascading {
551
+ export function getDirectoryChain(relativeFilePath: string): string[];
552
+ export function templateHasExtends(templateBody?: string | null, fullPath?: string): boolean;
553
+ export function findCascadingMiddlewareFiles(baseDir: string, relativeFilePath: string, isDev?: boolean): string[];
554
+ export function loadCascadingMiddlewares(
555
+ baseDir: string,
556
+ relativeFilePath: string,
557
+ middlewareRegistry?: Record<string, unknown>,
558
+ isDev?: boolean
559
+ ): Array<(req: any, res: any, next: (err?: any) => void) => void>;
560
+ export function runMiddleware(mw: Function, req: any, res: any): Promise<void>;
561
+ export function runMiddlewareChain(chain: Function[], req: any, res: any): Promise<void>;
562
+ export function findCascadingLayouts(baseDir: string, relativeFilePath: string, isDev?: boolean): CascadingLayoutInfo[];
563
+ export function executeCascadingLayoutLoads(layouts: CascadingLayoutInfo[], req: any, ctx: any, isDev?: boolean): Promise<void>;
564
+ export function applyCascadingLayouts(
565
+ nunjucksEnv: any,
566
+ layouts: CascadingLayoutInfo[],
567
+ pageHtml: string,
568
+ renderContext: Record<string, unknown>,
569
+ isDev?: boolean
570
+ ): string;
571
+ export function clearCascadingCaches(): void;
572
+ }
573
+
574
+ export function resolveMiddlewares(
575
+ middlewareConfig: unknown[] | null | undefined,
576
+ middlewareRegistry?: Record<string, unknown>
577
+ ): Array<(req: any, res: any, next: (err?: any) => void) => void>;
578
+
544
579
  export function filePathToRoute(filePath: string, pagesDir: string): string;
545
580
 
581
+ export function isRouteGroupSegment(segment: string): boolean;
582
+
583
+ export function stripRouteGroups(route: string): string;
584
+
546
585
  export function extractMethodFromFilename(filename: string): {
547
586
  method: string;
548
587
  baseName: string;
@@ -864,6 +903,7 @@ export interface ModelOptions<TSchema extends ZodObject<any> = ZodObject<Record<
864
903
  maxIncludes?: number;
865
904
  maxFilterConditions?: number;
866
905
  };
906
+ searchable?: string[];
867
907
  cache?: boolean | 'auto' | 'smart' | { strategy: 'auto' | 'smart' };
868
908
  }
869
909
 
@@ -898,6 +938,7 @@ export interface ModelDefinition<TModel = Record<string, any>> {
898
938
  maxIncludes?: number;
899
939
  maxFilterConditions?: number;
900
940
  };
941
+ searchable?: string[];
901
942
  hooks: Record<string, unknown>;
902
943
  cache?: boolean | 'auto' | 'smart' | { strategy: 'auto' | 'smart' };
903
944
  }
@@ -934,7 +975,10 @@ export function getColumnMeta(schema: ZodTypeAny): unknown;
934
975
 
935
976
  export interface FindOptions {
936
977
  with?: string[];
978
+ include?: string[];
937
979
  select?: string[];
980
+ limit?: number;
981
+ offset?: number;
938
982
  }
939
983
 
940
984
  export interface PaginatedResult<T = Record<string, any>> {
@@ -945,6 +989,16 @@ export interface PaginatedResult<T = Record<string, any>> {
945
989
  totalPages: number;
946
990
  }
947
991
 
992
+ export interface SearchOptions {
993
+ mode?: 'auto' | 'fts' | 'like';
994
+ language?: string;
995
+ matchAllWords?: boolean;
996
+ exact?: boolean;
997
+ rank?: boolean;
998
+ ftsTable?: string;
999
+ boolean?: 'and' | 'or';
1000
+ }
1001
+
948
1002
  export interface QueryBuilder<T = Record<string, any>> {
949
1003
  where(column: keyof T | string, value: unknown): this;
950
1004
  where(column: keyof T | string, operator: string, value: unknown): this;
@@ -954,14 +1008,19 @@ export interface QueryBuilder<T = Record<string, any>> {
954
1008
  whereBetween(column: keyof T | string, range: [unknown, unknown]): this;
955
1009
  select(...columns: (keyof T | string)[]): this;
956
1010
  orderBy(column: keyof T | string, direction?: 'asc' | 'desc'): this;
1011
+ orderByRaw(sql: string, bindings?: unknown[]): this;
1012
+ search(term: string, columns?: string | string[], options?: SearchOptions): this;
1013
+ orSearch(term: string, columns?: string | string[], options?: SearchOptions): this;
957
1014
  limit(n: number): this;
958
1015
  offset(n: number): this;
959
1016
  with(...relations: string[]): this;
1017
+ include(...relations: string[]): this;
960
1018
  withTrashed(): this;
961
1019
  onlyTrashed(): this;
962
1020
  first(): Promise<T | null>;
963
1021
  list(): Promise<T[]>;
964
1022
  get(): Promise<T[]>;
1023
+ find(): Promise<T[]>;
965
1024
  count(): Promise<number>;
966
1025
  paginate(page?: number, perPage?: number): Promise<PaginatedResult<T>>;
967
1026
  exists(): Promise<boolean>;
@@ -976,6 +1035,7 @@ export interface Repository<T = Record<string, any>> {
976
1035
  findById(id: string | number, options?: FindOptions): Promise<T | null>;
977
1036
  findOne(conditions: Partial<T> | Record<string, unknown>, options?: FindOptions): Promise<T | null>;
978
1037
  findAll(options?: FindOptions): Promise<T[]>;
1038
+ find(conditions?: Partial<T> | Record<string, unknown>, options?: FindOptions): Promise<T[]>;
979
1039
  create(data: Partial<T> | Record<string, unknown>): Promise<T>;
980
1040
  createMany(data: (Partial<T> | Record<string, unknown>)[]): Promise<T[]>;
981
1041
  update(id: string | number, data: Partial<T> | Record<string, unknown>): Promise<T | null>;
@@ -988,6 +1048,8 @@ export interface Repository<T = Record<string, any>> {
988
1048
  count(conditions?: Partial<T> | Record<string, unknown>): Promise<number>;
989
1049
  exists(conditions: Partial<T> | Record<string, unknown>): Promise<boolean>;
990
1050
  with(...relations: string[]): QueryBuilder<T>;
1051
+ include(...relations: string[]): QueryBuilder<T>;
1052
+ search(term: string, columns?: string | string[], options?: SearchOptions): QueryBuilder<T>;
991
1053
  model: ModelDefinition<T>;
992
1054
  }
993
1055
 
@@ -1208,6 +1270,24 @@ export function validateQueryComplexity(
1208
1270
  includes: string[];
1209
1271
  };
1210
1272
 
1273
+ export function buildSearchClause(params: {
1274
+ knex: any;
1275
+ model: any;
1276
+ term: string;
1277
+ columns?: string | string[];
1278
+ options?: SearchOptions;
1279
+ }): {
1280
+ raw: true;
1281
+ sql: string;
1282
+ bindings: any[];
1283
+ boolean: 'and' | 'or';
1284
+ rankClause: { sql: string; bindings: any[] } | null;
1285
+ } | null;
1286
+
1287
+ export function tokenizeSearchTerm(term: string): string[];
1288
+
1289
+ export function detectDialect(knex: any): 'sqlite' | 'postgres' | 'mysql' | 'generic';
1290
+
1211
1291
  export interface SchemaExplorerPluginOptions {
1212
1292
  /** Raw JSON API endpoint path (default: '/_schema') */
1213
1293
  path?: string;
@@ -1374,6 +1454,7 @@ export interface AuditLogPluginOptions {
1374
1454
  tableName?: string;
1375
1455
  includeDefaultPage?: boolean;
1376
1456
  apiPrefix?: string;
1457
+ registerRecordAction?: boolean;
1377
1458
  retentionDays?: number;
1378
1459
  cleanupIntervalMs?: number;
1379
1460
  autoCleanup?: boolean;
@@ -1391,6 +1472,8 @@ export function auditLogPlugin(options?: AuditLogPluginOptions): WebspressoPlugi
1391
1472
  stopCleanup(): void;
1392
1473
  getRetentionConfig(): AuditLogRetentionConfig;
1393
1474
  queryLogs(filters?: Record<string, unknown>): Promise<any[]>;
1475
+ queryTimeline(model: string, recordId: string | number): Promise<any[]>;
1476
+ getRecordTimeline(model: string, recordId: string | number): Promise<any[]>;
1394
1477
  getMigrationTemplate(tableName?: string): string;
1395
1478
  };
1396
1479
  dispose?(): void;
@@ -1632,6 +1715,86 @@ export interface BasicAuthPluginOptions {
1632
1715
 
1633
1716
  export function basicAuthPlugin(options?: BasicAuthPluginOptions): WebspressoPlugin;
1634
1717
 
1718
+ export interface ApiKeyRecord {
1719
+ id?: number | string;
1720
+ name: string;
1721
+ key_hash: string;
1722
+ key_hint: string;
1723
+ prefix?: string;
1724
+ user_id?: number | string | null;
1725
+ scopes?: string[] | string | null;
1726
+ rate_limit?: number | null;
1727
+ metadata?: any;
1728
+ expires_at?: Date | string | null;
1729
+ last_used_at?: Date | string | null;
1730
+ revoked_at?: Date | string | null;
1731
+ created_at?: Date | string;
1732
+ updated_at?: Date | string;
1733
+ user?: any;
1734
+ [key: string]: any;
1735
+ }
1736
+
1737
+ export interface ApiKeyPluginOptions {
1738
+ /** Database instance or Knex instance. */
1739
+ db?: any;
1740
+ /** Knex instance. */
1741
+ knex?: any;
1742
+ /** Table name for API keys. Default 'api_keys'. */
1743
+ tableName?: string;
1744
+ /** Default key prefix. Default 'wsp_live_'. */
1745
+ prefix?: string;
1746
+ /** Random byte length for key generation. Default 24. */
1747
+ byteLength?: number;
1748
+ /** Protect all matching routes globally. Default false. */
1749
+ global?: boolean;
1750
+ /** Route prefixes to protect when global: true. Default true (all routes). */
1751
+ routes?: string | string[] | boolean;
1752
+ /** Path prefixes to bypass authentication. */
1753
+ skipPaths?: string[];
1754
+ /** Custom predicate function to skip authentication. */
1755
+ skip?: (req: Request) => boolean;
1756
+ /** Static in-memory keys for testing, serverless, or microservices. */
1757
+ keys?: Array<{ key?: string; key_hash?: string; name?: string; scopes?: string[] | string; [key: string]: any }> | Record<string, any>;
1758
+ /** Automatically update last_used_at timestamp. Default true. */
1759
+ trackUsage?: boolean;
1760
+ /** Throttle interval in milliseconds for last_used_at updates. Default 60000. */
1761
+ updateLastUsedThrottleMs?: number;
1762
+ /** Allow query parameter extraction (?apiKey=...). Default false. */
1763
+ allowQueryParam?: boolean;
1764
+ /** Query parameter name. Default 'apiKey'. */
1765
+ queryParamName?: string;
1766
+ /** Custom key extraction function. */
1767
+ extractKey?: (req: Request) => string | null;
1768
+ /** Custom key lookup by SHA-256 hash. */
1769
+ findKeyByHash?: (hash: string, req?: Request) => Promise<ApiKeyRecord | null> | ApiKeyRecord | null;
1770
+ /** Custom verification hook. */
1771
+ verify?: (rawKey: string, req?: Request) => Promise<ApiKeyRecord | null> | ApiKeyRecord | null;
1772
+ /** Custom user loader for key. */
1773
+ getUser?: (record: ApiKeyRecord, req?: Request) => Promise<any> | any;
1774
+ /** Callback hook on successful authentication. */
1775
+ onAuthenticated?: (req: Request, record: ApiKeyRecord) => void | Promise<void>;
1776
+ /** Custom 401 Unauthorized response handler, string, or object. */
1777
+ unauthorizedResponse?: ((req: Request, res: Response, info?: any) => unknown) | string | Record<string, unknown>;
1778
+ /** Custom 403 Forbidden response handler, string, or object. */
1779
+ forbiddenResponse?: ((req: Request, res: Response, info?: any) => unknown) | string | Record<string, unknown>;
1780
+ }
1781
+
1782
+ export interface ApiKeyRouteOptions {
1783
+ /** Required scope(s) for this route. Supports wildcards ('*', 'reports:*'). */
1784
+ scopes?: string | string[];
1785
+ /** Whether all required scopes must be present (true) or any scope (false). Default true. */
1786
+ requireAllScopes?: boolean;
1787
+ /** If true, allows requests without API key to pass through without error. Default false. */
1788
+ optional?: boolean;
1789
+ /** Custom 401 Unauthorized response. */
1790
+ unauthorizedResponse?: ((req: Request, res: Response, info?: any) => unknown) | string | Record<string, unknown>;
1791
+ /** Custom 403 Forbidden response. */
1792
+ forbiddenResponse?: ((req: Request, res: Response, info?: any) => unknown) | string | Record<string, unknown>;
1793
+ }
1794
+
1795
+ export function apiKeyPlugin(options?: ApiKeyPluginOptions): WebspressoPlugin;
1796
+ export function createApiKeyMiddleware(options?: ApiKeyPluginOptions & ApiKeyRouteOptions): RequestHandler;
1797
+
1635
1798
  export interface ContentPluginOptions {
1636
1799
  db: import('./index').Database;
1637
1800
  adminPath?: string;
@@ -2117,6 +2280,75 @@ export interface QueueRetentionConfig {
2117
2280
  runOnStart?: boolean;
2118
2281
  }
2119
2282
 
2283
+ export interface ScheduleOptions {
2284
+ id?: string;
2285
+ overlap?: boolean;
2286
+ maxRuns?: number;
2287
+ utc?: boolean;
2288
+ runImmediately?: boolean;
2289
+ delay?: number | Date;
2290
+ attempts?: number;
2291
+ priority?: number;
2292
+ timeout?: number;
2293
+ backoff?: number | { type?: 'exponential' | 'fixed'; delay?: number };
2294
+ [key: string]: unknown;
2295
+ }
2296
+
2297
+ export interface ScheduleInfo {
2298
+ id: string;
2299
+ name: string;
2300
+ cronExpression: string;
2301
+ cron?: CronExpression;
2302
+ data: unknown;
2303
+ options: ScheduleOptions;
2304
+ runsCount: number;
2305
+ lastRunAt: Date | null;
2306
+ nextRunAt: Date | null;
2307
+ isRunning: boolean;
2308
+ activeJobId: string | number | null;
2309
+ registeredAt: Date;
2310
+ }
2311
+
2312
+ export class CronExpression {
2313
+ rawExpression: string;
2314
+ resolvedExpression: string;
2315
+ utc: boolean;
2316
+ minutes: Set<number>;
2317
+ hours: Set<number>;
2318
+ doms: Set<number>;
2319
+ months: Set<number>;
2320
+ dows: Set<number>;
2321
+ constructor(expression: string, options?: { utc?: boolean });
2322
+ match(date?: Date): boolean;
2323
+ getNextDate(fromDate?: Date): Date | null;
2324
+ getNextDates(count?: number, fromDate?: Date): Date[];
2325
+ static isValid(expression: string): boolean;
2326
+ static parse(expression: string, options?: { utc?: boolean }): CronExpression;
2327
+ }
2328
+
2329
+ export function parseCron(expression: string, options?: { utc?: boolean }): CronExpression;
2330
+ export function isValidCron(expression: string): boolean;
2331
+ export function resolveCronAlias(str: string): string;
2332
+ export const CRON_ALIASES: Record<string, string>;
2333
+
2334
+ export class TaskScheduler {
2335
+ queue: QueueManager;
2336
+ maxIntervalMs: number;
2337
+ isRunning: boolean;
2338
+ schedules: Map<string, any>;
2339
+ constructor(queue: QueueManager, options?: { autoStart?: boolean; maxIntervalMs?: number });
2340
+ schedule(cronExpression: string | CronExpression, jobName: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo;
2341
+ cron(cronExpression: string | CronExpression, jobName: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo;
2342
+ unschedule(idOrJobName: string): boolean;
2343
+ getSchedules(): ScheduleInfo[];
2344
+ getSchedule(idOrJobName: string): ScheduleInfo | null;
2345
+ trigger(idOrJobName: string): Promise<Job | null>;
2346
+ start(): this;
2347
+ stop(): this;
2348
+ clear(): void;
2349
+ destroy(): void;
2350
+ }
2351
+
2120
2352
  export class QueueManager extends EventEmitter {
2121
2353
  adapter: QueueAdapter;
2122
2354
  concurrency: number;
@@ -2125,6 +2357,7 @@ export class QueueManager extends EventEmitter {
2125
2357
  isPaused: boolean;
2126
2358
  isDraining: boolean;
2127
2359
  retentionConfig: QueueRetentionConfig;
2360
+ scheduler: TaskScheduler;
2128
2361
  constructor(options?: {
2129
2362
  adapter?: QueueAdapter;
2130
2363
  concurrency?: number;
@@ -2141,6 +2374,11 @@ export class QueueManager extends EventEmitter {
2141
2374
  define(name: string, handler: (job: Job, ctx: { queue: QueueManager }) => Promise<any> | any, options?: DispatchOptions): this;
2142
2375
  has(name: string): boolean;
2143
2376
  dispatch(name: string, data?: unknown, options?: DispatchOptions): Promise<Job>;
2377
+ schedule(cronExpression: string | CronExpression, name: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo;
2378
+ cron(cronExpression: string | CronExpression, name: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo;
2379
+ unschedule(idOrJobName: string): boolean;
2380
+ getSchedules(): ScheduleInfo[];
2381
+ getSchedule(idOrJobName: string): ScheduleInfo | null;
2144
2382
  start(): this;
2145
2383
  pause(): this;
2146
2384
  resume(): this;
@@ -2202,6 +2440,10 @@ export function queuePlugin(options?: {
2202
2440
  olderThan?: Date | string | number;
2203
2441
  limit?: number;
2204
2442
  }): Promise<number>;
2443
+ schedule(cronExpression: string | CronExpression, name: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo | undefined;
2444
+ cron(cronExpression: string | CronExpression, name: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo | undefined;
2445
+ unschedule(idOrJobName: string): boolean | undefined;
2446
+ getSchedules(): ScheduleInfo[];
2205
2447
  };
2206
2448
  };
2207
2449
 
@@ -2234,6 +2476,54 @@ export const ssr: {
2234
2476
  renderStream: typeof renderStream;
2235
2477
  };
2236
2478
 
2479
+ // --- Server-Sent Events (SSE) Streaming ---
2480
+
2481
+ export interface SseOptions {
2482
+ status?: number;
2483
+ headers?: Record<string, string>;
2484
+ heartbeat?: number | boolean;
2485
+ heartbeatComment?: string;
2486
+ retry?: number;
2487
+ map?: (chunk: any) => any;
2488
+ }
2489
+
2490
+ export interface SseEventObject {
2491
+ data?: any;
2492
+ event?: string;
2493
+ id?: string | number;
2494
+ retry?: number;
2495
+ }
2496
+
2497
+ export class ServerSentEventsStream extends EventEmitter {
2498
+ req: import('http').IncomingMessage;
2499
+ res: import('http').ServerResponse;
2500
+ options: SseOptions;
2501
+ isOpen: boolean;
2502
+ isClosed: boolean;
2503
+
2504
+ constructor(req: import('http').IncomingMessage, res: import('http').ServerResponse, options?: SseOptions);
2505
+ send(payloadOrData: any | SseEventObject, event?: string, id?: string | number): Promise<boolean>;
2506
+ comment(text: string): boolean;
2507
+ heartbeat(comment?: string): boolean;
2508
+ retry(ms: number): boolean;
2509
+ pipe(source: AsyncIterable<any> | Iterable<any> | import('stream').Readable, mapFn?: (chunk: any) => any): Promise<void>;
2510
+ stream(generatorFn: (stream: ServerSentEventsStream) => any): Promise<void>;
2511
+ close(): void;
2512
+ }
2513
+
2514
+ export function createSseStream(
2515
+ req: import('http').IncomingMessage,
2516
+ res: import('http').ServerResponse,
2517
+ sourceOrOptions?: SseOptions | AsyncIterable<any> | Iterable<any> | import('stream').Readable | ((stream: ServerSentEventsStream) => any),
2518
+ maybeOptions?: SseOptions
2519
+ ): ServerSentEventsStream | Promise<void>;
2520
+
2521
+ export const sseStream: {
2522
+ ServerSentEventsStream: typeof ServerSentEventsStream;
2523
+ createSseStream: typeof createSseStream;
2524
+ formatSseFrame: (data?: any, event?: string, id?: string | number, retry?: number) => string;
2525
+ };
2526
+
2237
2527
  // --- Model Context Protocol (MCP) Plugin ---
2238
2528
 
2239
2529
  export interface McpPluginOptions {
@@ -2629,6 +2919,244 @@ export interface GenerateTypesResult {
2629
2919
 
2630
2920
  export function generateTypes(options?: GenerateTypesOptions): GenerateTypesResult;
2631
2921
 
2922
+ // --- OAuth 2.0 & Social Login Plugin ---
2923
+
2924
+ export class OAuthError extends Error {
2925
+ code: string;
2926
+ details?: any;
2927
+ constructor(message: string, code?: string, details?: any);
2928
+ }
2929
+
2930
+ export interface OAuthUserProfile {
2931
+ provider: string;
2932
+ id: string;
2933
+ email?: string | null;
2934
+ emailVerified?: boolean;
2935
+ name?: string | null;
2936
+ firstName?: string | null;
2937
+ lastName?: string | null;
2938
+ username?: string | null;
2939
+ avatarUrl?: string | null;
2940
+ bio?: string | null;
2941
+ location?: string | null;
2942
+ locale?: string | null;
2943
+ raw: Record<string, any>;
2944
+ [key: string]: any;
2945
+ }
2946
+
2947
+ export interface OAuthProviderConfig {
2948
+ name?: string;
2949
+ clientId: string;
2950
+ clientSecret: string;
2951
+ redirectUri?: string;
2952
+ authorizationUrl?: string;
2953
+ tokenUrl?: string;
2954
+ userInfoUrl?: string;
2955
+ scopes?: string[] | string;
2956
+ defaultScopes?: string[];
2957
+ params?: Record<string, any>;
2958
+ normalizeProfile?: (raw: any, extra?: any) => OAuthUserProfile;
2959
+ fetchExtra?: (tokens: any) => Promise<any>;
2960
+ }
2961
+
2962
+ export interface OAuthClientConfig {
2963
+ secret?: string;
2964
+ stateTtlMs?: number;
2965
+ providers?: Record<string, OAuthProviderConfig>;
2966
+ }
2967
+
2968
+ export class OAuthClient {
2969
+ secret: string;
2970
+ stateTtlMs: number;
2971
+ providers: Record<string, OAuthProviderConfig>;
2972
+ constructor(config?: OAuthClientConfig);
2973
+ registerProvider(name: string, options?: OAuthProviderConfig): this;
2974
+ getProvider(name: string): OAuthProviderConfig;
2975
+ getProviderNames(): string[];
2976
+ getAuthorizationUrl(providerName: string, options?: {
2977
+ redirectUri?: string;
2978
+ scopes?: string[] | string;
2979
+ stateData?: Record<string, any>;
2980
+ pkce?: boolean;
2981
+ extraParams?: Record<string, any>;
2982
+ }): { url: string; state: string; codeVerifier?: string };
2983
+ exchangeCode(providerName: string, params: {
2984
+ code: string;
2985
+ redirectUri?: string;
2986
+ codeVerifier?: string;
2987
+ }): Promise<Record<string, any>>;
2988
+ fetchUserProfile(providerName: string, tokens: Record<string, any>): Promise<OAuthUserProfile>;
2989
+ handleCallback(providerName: string, params: {
2990
+ code: string;
2991
+ state: string;
2992
+ redirectUri?: string;
2993
+ }): Promise<{ provider: string; user: OAuthUserProfile; tokens: Record<string, any>; stateData: Record<string, any> }>;
2994
+ }
2995
+
2996
+ export function createOAuthClient(config?: OAuthClientConfig): OAuthClient;
2997
+ export function generatePKCE(): { codeVerifier: string; codeChallenge: string; codeChallengeMethod: 'S256' };
2998
+ export function createSignedState(payload: Record<string, any>, secret: string): string;
2999
+ export function verifySignedState(stateString: string, secret: string, maxAgeMs?: number): Record<string, any>;
3000
+
3001
+ export interface OAuthPluginOptions {
3002
+ client?: OAuthClient;
3003
+ providers?: Record<string, OAuthProviderConfig>;
3004
+ secret?: string;
3005
+ pathPrefix?: string;
3006
+ routes?: boolean;
3007
+ pkce?: boolean;
3008
+ stateTtlMs?: number;
3009
+ defaultReturnTo?: string;
3010
+ failureRedirect?: string;
3011
+ findOrCreateUser?: (params: {
3012
+ provider: string;
3013
+ profile: OAuthUserProfile;
3014
+ tokens: Record<string, any>;
3015
+ stateData: Record<string, any>;
3016
+ req: any;
3017
+ res: any;
3018
+ db?: any;
3019
+ }) => Promise<any>;
3020
+ onSuccess?: (params: {
3021
+ provider: string;
3022
+ user: any;
3023
+ profile: OAuthUserProfile;
3024
+ tokens: Record<string, any>;
3025
+ stateData: Record<string, any>;
3026
+ req: any;
3027
+ res: any;
3028
+ client: OAuthClient;
3029
+ }) => Promise<void | any>;
3030
+ onError?: (params: {
3031
+ error: Error | OAuthError;
3032
+ provider: string;
3033
+ req: any;
3034
+ res: any;
3035
+ client: OAuthClient;
3036
+ }) => Promise<void | any>;
3037
+ db?: any;
3038
+ }
3039
+
3040
+ export function oauthPlugin(options?: OAuthPluginOptions): WebspressoPlugin;
3041
+
3042
+ // ==========================================
3043
+ // 2FA / TOTP (RFC 6238, RFC 4226, RFC 4648)
3044
+ // ==========================================
3045
+
3046
+ export interface Base32EncodeOptions {
3047
+ padding?: boolean;
3048
+ }
3049
+
3050
+ export interface TotpSecretOptions extends Base32EncodeOptions {
3051
+ length?: number;
3052
+ }
3053
+
3054
+ export interface HotpOptions {
3055
+ digits?: number;
3056
+ algorithm?: 'sha1' | 'sha256' | 'sha512' | string;
3057
+ encoding?: 'base32' | 'ascii' | 'utf8' | 'hex';
3058
+ }
3059
+
3060
+ export interface TotpOptions extends HotpOptions {
3061
+ time?: number | Date;
3062
+ step?: number;
3063
+ epoch?: number;
3064
+ }
3065
+
3066
+ export interface TotpVerifyOptions extends TotpOptions {
3067
+ window?: number;
3068
+ detailed?: boolean;
3069
+ }
3070
+
3071
+ export interface TotpUriOptions {
3072
+ secret: string;
3073
+ account?: string;
3074
+ issuer?: string;
3075
+ label?: string;
3076
+ algorithm?: string;
3077
+ digits?: number;
3078
+ period?: number;
3079
+ }
3080
+
3081
+ export interface QrSvgOptions {
3082
+ size?: number;
3083
+ margin?: number;
3084
+ darkColor?: string;
3085
+ lightColor?: string;
3086
+ version?: number;
3087
+ ecc?: 'L' | 'M' | 'Q' | 'H';
3088
+ mask?: number;
3089
+ }
3090
+
3091
+ export interface RecoveryCodeOptions {
3092
+ count?: number;
3093
+ length?: number;
3094
+ hyphen?: boolean;
3095
+ }
3096
+
3097
+ export interface TotpSetupOptions extends TotpSecretOptions {
3098
+ user?: any;
3099
+ account?: string;
3100
+ issuer?: string;
3101
+ recoveryCodeCount?: number;
3102
+ qrOptions?: QrSvgOptions;
3103
+ algorithm?: string;
3104
+ digits?: number;
3105
+ period?: number;
3106
+ }
3107
+
3108
+ export interface TotpSetupResult {
3109
+ secret: string;
3110
+ uri: string;
3111
+ qrSvg: string;
3112
+ qrDataUri: string;
3113
+ recoveryCodes: string[];
3114
+ recoveryCodesHashed: string[];
3115
+ }
3116
+
3117
+ export function base32Encode(buffer: Buffer | string, options?: Base32EncodeOptions): string;
3118
+ export function base32Decode(input: string): Buffer;
3119
+ export function generateTotpSecret(options?: TotpSecretOptions): string;
3120
+ export function generateHotp(secret: string | Buffer, counter: number | bigint, options?: HotpOptions): string;
3121
+ export function verifyHotp(token: string, secret: string | Buffer, counter: number | bigint, options?: HotpOptions): boolean;
3122
+ export function generateTotp(secret: string | Buffer, options?: TotpOptions): string;
3123
+ export function verifyTotp(token: string, secret: string | Buffer, options?: TotpVerifyOptions): boolean | { valid: boolean; delta: number | null };
3124
+ export function generateTotpUri(options: TotpUriOptions): string;
3125
+ export function generateTotpQrSvg(uriOrOptions: string | TotpUriOptions, svgOptions?: QrSvgOptions): string;
3126
+ export function generateTotpQrDataUri(uriOrOptions: string | TotpUriOptions, svgOptions?: QrSvgOptions): string;
3127
+ export function generateRecoveryCodes(options?: RecoveryCodeOptions): string[];
3128
+ export function hashRecoveryCode(code: string): string;
3129
+ export function verifyRecoveryCode(code: string, hashedCodes: string[]): { valid: boolean; matchedIndex: number; remainingHashedCodes: string[] };
3130
+ export function createTotpSetup(options?: TotpSetupOptions): TotpSetupResult;
3131
+
3132
+ export function createQrMatrix(text: string, options?: QrSvgOptions): { matrix: boolean[][]; size: number; version: number };
3133
+ export function generateQrSvg(text: string, options?: QrSvgOptions): string;
3134
+ export function generateQrDataUri(text: string, options?: QrSvgOptions): string;
3135
+
3136
+ export namespace totp {
3137
+ export {
3138
+ base32Encode,
3139
+ base32Decode,
3140
+ generateTotpSecret,
3141
+ generateHotp,
3142
+ verifyHotp,
3143
+ generateTotp,
3144
+ verifyTotp,
3145
+ generateTotpUri,
3146
+ generateTotpQrSvg,
3147
+ generateTotpQrDataUri,
3148
+ generateRecoveryCodes,
3149
+ hashRecoveryCode,
3150
+ verifyRecoveryCode,
3151
+ createTotpSetup,
3152
+ createQrMatrix,
3153
+ generateQrSvg,
3154
+ generateQrDataUri,
3155
+ };
3156
+ }
3157
+
3158
+
3159
+
2632
3160
 
2633
3161
 
2634
3162