@develit-io/backend-sdk 6.0.0 → 7.0.1

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.mts CHANGED
@@ -1,8 +1,7 @@
1
1
  import * as drizzle_orm from 'drizzle-orm';
2
- import { ExtractTablesWithRelations, DBQueryConfig, BuildQueryResult } from 'drizzle-orm';
2
+ import { ExtractTablesWithRelations, DBQueryConfig, BuildQueryResult, AnyColumn } from 'drizzle-orm';
3
3
  import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
4
4
  import { AnySQLiteTable } from 'drizzle-orm/sqlite-core';
5
- import { MiddlewareHandler } from 'hono/types';
6
5
  import { z as z$1 } from 'zod';
7
6
  import * as z from 'zod/v4/core';
8
7
  import { ContentfulStatusCode, SuccessStatusCode } from 'hono/utils/http-status';
@@ -11,12 +10,14 @@ import { Queue } from '@cloudflare/workers-types';
11
10
  import { BatchItem } from 'drizzle-orm/batch';
12
11
  import { DrizzleD1Database } from 'drizzle-orm/d1';
13
12
 
14
- declare const uuidv4: () => `${string}-${string}-${string}-${string}-${string}`;
13
+ declare const ENVIRONMENT: string[];
15
14
 
16
15
  type Environment = string | 'localhost' | 'dev' | 'test' | 'staging' | 'production';
17
16
 
18
17
  type Project = 'creditio' | 'dbu-mdm' | 'dbu-txs' | 'cryptobyte-website' | 'lrf-website' | 'moneio' | 'paycorp-fiat-processor';
19
18
 
19
+ declare const uuidv4: () => `${string}-${string}-${string}-${string}-${string}`;
20
+
20
21
  declare const base: {
21
22
  id: drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"id", [string, ...string[]], number | undefined>>>;
22
23
  createdAt: drizzle_orm.HasDefault<drizzle_orm_sqlite_core.SQLiteTimestampBuilderInitial<"created_at">>;
@@ -65,20 +66,6 @@ declare const composeWranglerBase: ({ project, name, }: {
65
66
  keep_vars: boolean;
66
67
  };
67
68
 
68
- declare const idempotency: <T extends {
69
- IDEMPOTENCY_KV: KVNamespace;
70
- }>() => MiddlewareHandler;
71
-
72
- declare const jwt: <TAuthService, TJwtPayload>() => MiddlewareHandler;
73
-
74
- declare const ip: <TOrganizationService, TJwtPayload>() => MiddlewareHandler;
75
-
76
- declare const logger: () => MiddlewareHandler;
77
-
78
- declare const signature: <TOrganizationService, TJwtPayload>() => MiddlewareHandler;
79
-
80
- declare const ENVIRONMENT: string[];
81
-
82
69
  interface AuditLogPayload<T> {
83
70
  action: T;
84
71
  actorId: string;
@@ -678,6 +665,16 @@ declare const getDrizzleD1Config: () => {
678
665
  dialect: "sqlite";
679
666
  };
680
667
 
668
+ type Operator = 'like' | 'ilike';
669
+ type Wrap = 'both' | 'prefix' | 'suffix' | 'none';
670
+ type BuildSearchOptions = {
671
+ operator?: Operator;
672
+ wrap?: Wrap;
673
+ };
674
+ declare const buildSearchConditions: (search: string | undefined | null, columns: ReadonlyArray<AnyColumn>, opts?: BuildSearchOptions) => drizzle_orm.SQL<unknown> | undefined;
675
+ declare const buildMultiFilterConditions: <T>(column: drizzle_orm.Column, value: T | T[] | undefined) => drizzle_orm.SQL<unknown> | undefined;
676
+ declare const buildRangeFilterConditions: <T>(column: drizzle_orm.Column, minValue: T | undefined, maxValue: T | undefined) => drizzle_orm.SQL<unknown> | undefined;
677
+
681
678
  interface Command<TAuditAction = string> {
682
679
  handler: (db: DrizzleD1Database<Record<string, unknown>>) => CommandItem<TAuditAction>;
683
680
  }
@@ -720,6 +717,10 @@ interface UseFetchOptions extends RequestInit {
720
717
  }
721
718
  declare function useFetch<T = unknown>(url: string, { parseAs, ...options }?: UseFetchOptions): Promise<Result<T>>;
722
719
 
720
+ declare const createSignatureKeyPair: () => Promise<{
721
+ publicKey: string;
722
+ privateKey: string;
723
+ }>;
723
724
  declare const signPayload: ({ payload, privateKey, }: {
724
725
  payload: string;
725
726
  privateKey: string;
@@ -820,5 +821,5 @@ interface WithRetryCounterOptions {
820
821
  type AsyncMethod<TArgs extends unknown[] = unknown[], TResult = unknown> = (...args: TArgs) => Promise<TResult>;
821
822
  declare function cloudflareQueue<TArgs extends unknown[] = unknown[], TResult = unknown>(options: WithRetryCounterOptions): (target: unknown, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<AsyncMethod<TArgs, TResult>>) => void;
822
823
 
823
- export { DatabaseTransaction, ENVIRONMENT, RPCResponse, USER_ROLES, action, bankAccount, bankAccountMetadataSchema, base, bicSchema, calculateExponentialBackoff, cloudflareQueue, composeWranglerBase, createAuditLogWriter, createInternalError, defineCommand, derivePortFromId, develitWorker, durableObjectNamespaceIdFromName, first, firstOrError, getD1Credentials, getD1DatabaseIdFromWrangler, getDrizzleD1Config, getSecret, handleAction, handleActionResponse, ibanSchema, idempotency, ip, isInternalError, jwt, logger, paginationQuerySchema, paginationSchema, service, signPayload, signature, useFetch, useResult, useResultSync, uuidv4, verifyPayloadSignature };
824
- export type { ActionExecution, ActionHandlerOptions, AuditLogWriter, AuthUser, BankAccountMetadata, BaseEvent, Command, CommandLogPayload, DevelitWorkerMethods, Environment, GatewayResponse, IRPCResponse, IdempotencyVariables, IncludeRelation, InferResultType, InternalError, InternalErrorResponseStatus, Project, UserRole, UserVariables, ValidatedInput };
824
+ export { DatabaseTransaction, ENVIRONMENT, RPCResponse, USER_ROLES, action, bankAccount, bankAccountMetadataSchema, base, bicSchema, buildMultiFilterConditions, buildRangeFilterConditions, buildSearchConditions, calculateExponentialBackoff, cloudflareQueue, composeWranglerBase, createAuditLogWriter, createInternalError, createSignatureKeyPair, defineCommand, derivePortFromId, develitWorker, durableObjectNamespaceIdFromName, first, firstOrError, getD1Credentials, getD1DatabaseIdFromWrangler, getDrizzleD1Config, getSecret, handleAction, handleActionResponse, ibanSchema, isInternalError, paginationQuerySchema, paginationSchema, service, signPayload, useFetch, useResult, useResultSync, uuidv4, verifyPayloadSignature };
825
+ export type { ActionExecution, ActionHandlerOptions, AuditLogWriter, AuthUser, BankAccountMetadata, BaseEvent, BuildSearchOptions, Command, CommandLogPayload, DevelitWorkerMethods, Environment, GatewayResponse, IRPCResponse, IdempotencyVariables, IncludeRelation, InferResultType, InternalError, InternalErrorResponseStatus, Project, UserRole, UserVariables, ValidatedInput };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import * as drizzle_orm from 'drizzle-orm';
2
- import { ExtractTablesWithRelations, DBQueryConfig, BuildQueryResult } from 'drizzle-orm';
2
+ import { ExtractTablesWithRelations, DBQueryConfig, BuildQueryResult, AnyColumn } from 'drizzle-orm';
3
3
  import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
4
4
  import { AnySQLiteTable } from 'drizzle-orm/sqlite-core';
5
- import { MiddlewareHandler } from 'hono/types';
6
5
  import { z as z$1 } from 'zod';
7
6
  import * as z from 'zod/v4/core';
8
7
  import { ContentfulStatusCode, SuccessStatusCode } from 'hono/utils/http-status';
@@ -11,12 +10,14 @@ import { Queue } from '@cloudflare/workers-types';
11
10
  import { BatchItem } from 'drizzle-orm/batch';
12
11
  import { DrizzleD1Database } from 'drizzle-orm/d1';
13
12
 
14
- declare const uuidv4: () => `${string}-${string}-${string}-${string}-${string}`;
13
+ declare const ENVIRONMENT: string[];
15
14
 
16
15
  type Environment = string | 'localhost' | 'dev' | 'test' | 'staging' | 'production';
17
16
 
18
17
  type Project = 'creditio' | 'dbu-mdm' | 'dbu-txs' | 'cryptobyte-website' | 'lrf-website' | 'moneio' | 'paycorp-fiat-processor';
19
18
 
19
+ declare const uuidv4: () => `${string}-${string}-${string}-${string}-${string}`;
20
+
20
21
  declare const base: {
21
22
  id: drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"id", [string, ...string[]], number | undefined>>>;
22
23
  createdAt: drizzle_orm.HasDefault<drizzle_orm_sqlite_core.SQLiteTimestampBuilderInitial<"created_at">>;
@@ -65,20 +66,6 @@ declare const composeWranglerBase: ({ project, name, }: {
65
66
  keep_vars: boolean;
66
67
  };
67
68
 
68
- declare const idempotency: <T extends {
69
- IDEMPOTENCY_KV: KVNamespace;
70
- }>() => MiddlewareHandler;
71
-
72
- declare const jwt: <TAuthService, TJwtPayload>() => MiddlewareHandler;
73
-
74
- declare const ip: <TOrganizationService, TJwtPayload>() => MiddlewareHandler;
75
-
76
- declare const logger: () => MiddlewareHandler;
77
-
78
- declare const signature: <TOrganizationService, TJwtPayload>() => MiddlewareHandler;
79
-
80
- declare const ENVIRONMENT: string[];
81
-
82
69
  interface AuditLogPayload<T> {
83
70
  action: T;
84
71
  actorId: string;
@@ -678,6 +665,16 @@ declare const getDrizzleD1Config: () => {
678
665
  dialect: "sqlite";
679
666
  };
680
667
 
668
+ type Operator = 'like' | 'ilike';
669
+ type Wrap = 'both' | 'prefix' | 'suffix' | 'none';
670
+ type BuildSearchOptions = {
671
+ operator?: Operator;
672
+ wrap?: Wrap;
673
+ };
674
+ declare const buildSearchConditions: (search: string | undefined | null, columns: ReadonlyArray<AnyColumn>, opts?: BuildSearchOptions) => drizzle_orm.SQL<unknown> | undefined;
675
+ declare const buildMultiFilterConditions: <T>(column: drizzle_orm.Column, value: T | T[] | undefined) => drizzle_orm.SQL<unknown> | undefined;
676
+ declare const buildRangeFilterConditions: <T>(column: drizzle_orm.Column, minValue: T | undefined, maxValue: T | undefined) => drizzle_orm.SQL<unknown> | undefined;
677
+
681
678
  interface Command<TAuditAction = string> {
682
679
  handler: (db: DrizzleD1Database<Record<string, unknown>>) => CommandItem<TAuditAction>;
683
680
  }
@@ -720,6 +717,10 @@ interface UseFetchOptions extends RequestInit {
720
717
  }
721
718
  declare function useFetch<T = unknown>(url: string, { parseAs, ...options }?: UseFetchOptions): Promise<Result<T>>;
722
719
 
720
+ declare const createSignatureKeyPair: () => Promise<{
721
+ publicKey: string;
722
+ privateKey: string;
723
+ }>;
723
724
  declare const signPayload: ({ payload, privateKey, }: {
724
725
  payload: string;
725
726
  privateKey: string;
@@ -820,5 +821,5 @@ interface WithRetryCounterOptions {
820
821
  type AsyncMethod<TArgs extends unknown[] = unknown[], TResult = unknown> = (...args: TArgs) => Promise<TResult>;
821
822
  declare function cloudflareQueue<TArgs extends unknown[] = unknown[], TResult = unknown>(options: WithRetryCounterOptions): (target: unknown, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<AsyncMethod<TArgs, TResult>>) => void;
822
823
 
823
- export { DatabaseTransaction, ENVIRONMENT, RPCResponse, USER_ROLES, action, bankAccount, bankAccountMetadataSchema, base, bicSchema, calculateExponentialBackoff, cloudflareQueue, composeWranglerBase, createAuditLogWriter, createInternalError, defineCommand, derivePortFromId, develitWorker, durableObjectNamespaceIdFromName, first, firstOrError, getD1Credentials, getD1DatabaseIdFromWrangler, getDrizzleD1Config, getSecret, handleAction, handleActionResponse, ibanSchema, idempotency, ip, isInternalError, jwt, logger, paginationQuerySchema, paginationSchema, service, signPayload, signature, useFetch, useResult, useResultSync, uuidv4, verifyPayloadSignature };
824
- export type { ActionExecution, ActionHandlerOptions, AuditLogWriter, AuthUser, BankAccountMetadata, BaseEvent, Command, CommandLogPayload, DevelitWorkerMethods, Environment, GatewayResponse, IRPCResponse, IdempotencyVariables, IncludeRelation, InferResultType, InternalError, InternalErrorResponseStatus, Project, UserRole, UserVariables, ValidatedInput };
824
+ export { DatabaseTransaction, ENVIRONMENT, RPCResponse, USER_ROLES, action, bankAccount, bankAccountMetadataSchema, base, bicSchema, buildMultiFilterConditions, buildRangeFilterConditions, buildSearchConditions, calculateExponentialBackoff, cloudflareQueue, composeWranglerBase, createAuditLogWriter, createInternalError, createSignatureKeyPair, defineCommand, derivePortFromId, develitWorker, durableObjectNamespaceIdFromName, first, firstOrError, getD1Credentials, getD1DatabaseIdFromWrangler, getDrizzleD1Config, getSecret, handleAction, handleActionResponse, ibanSchema, isInternalError, paginationQuerySchema, paginationSchema, service, signPayload, useFetch, useResult, useResultSync, uuidv4, verifyPayloadSignature };
825
+ export type { ActionExecution, ActionHandlerOptions, AuditLogWriter, AuthUser, BankAccountMetadata, BaseEvent, BuildSearchOptions, Command, CommandLogPayload, DevelitWorkerMethods, Environment, GatewayResponse, IRPCResponse, IdempotencyVariables, IncludeRelation, InferResultType, InternalError, InternalErrorResponseStatus, Project, UserRole, UserVariables, ValidatedInput };
package/dist/index.mjs CHANGED
@@ -1,18 +1,22 @@
1
- import { sql } from 'drizzle-orm';
1
+ import { sql, or, inArray, eq, gte, lte, and } from 'drizzle-orm';
2
2
  import { integer, text } from 'drizzle-orm/sqlite-core';
3
3
  import { COUNTRY_CODES_2, CURRENCY_CODES, BANK_CODES } from '@develit-io/general-codes';
4
- import { createMiddleware } from 'hono/factory';
5
- import { HTTPException } from 'hono/http-exception';
6
- import { createError } from 'h3';
7
4
  import { z as z$1 } from 'zod';
5
+ import * as z from 'zod/v4/core';
6
+ import { createError } from 'h3';
8
7
  import 'cloudflare';
9
8
  import fs from 'node:fs';
10
9
  import crypto$1 from 'node:crypto';
11
10
  import path from 'node:path';
12
11
  import { parse } from 'comment-json';
13
- import * as z from 'zod/v4/core';
12
+ export { c as createSignatureKeyPair, s as signPayload, v as verifyPayloadSignature } from './shared/backend-sdk.CgM_2r0b.mjs';
14
13
  import superjson from 'superjson';
15
14
 
15
+ const ENVIRONMENT = ["dev", "test", "staging", "production"];
16
+
17
+ const COMPATIBILITY_DATE = "2025-06-04";
18
+ const COMPATIBILITY_FLAGS = ["nodejs_compat"];
19
+
16
20
  const uuidv4 = () => crypto.randomUUID();
17
21
 
18
22
  const base = {
@@ -48,11 +52,6 @@ const bankAccount = {
48
52
 
49
53
  const USER_ROLES = ["OWNER", "ADMIN", "MEMBER"];
50
54
 
51
- const ENVIRONMENT = ["dev", "test", "staging", "production"];
52
-
53
- const COMPATIBILITY_DATE = "2025-06-04";
54
- const COMPATIBILITY_FLAGS = ["nodejs_compat"];
55
-
56
55
  const composeWranglerBase = ({
57
56
  project,
58
57
  name
@@ -76,34 +75,119 @@ const composeWranglerBase = ({
76
75
  };
77
76
  };
78
77
 
79
- const idempotency = () => {
80
- return createMiddleware(async (context, next) => {
81
- const idempotencyKeyHeader = context.req.header("X-Idempotency-Key");
82
- if (!idempotencyKeyHeader) {
83
- throw new HTTPException(401, {
84
- message: `The 'X-Idempotency-Key' header must exist and must have a value.`
85
- });
86
- }
87
- const existingIdempotencyRecord = await context.env.IDEMPOTENCY_KV.get(idempotencyKeyHeader);
88
- if (existingIdempotencyRecord) {
89
- throw new HTTPException(409, {
90
- message: "The identical request has already been processed. The idempotency key is not unique."
91
- });
92
- }
93
- await context.env.IDEMPOTENCY_KV.put(
94
- idempotencyKeyHeader,
95
- idempotencyKeyHeader,
96
- {
97
- expirationTtl: 60 * 60 * 24 * 3
98
- // 3 days
78
+ const ibanSchema = z$1.string().min(1).regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/, {
79
+ message: "Invalid IBAN format"
80
+ });
81
+ const bicSchema = z$1.string().min(1).regex(/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/, {
82
+ message: "Invalid SWIFT/BIC format"
83
+ });
84
+ const bankAccountMetadataSchema = z$1.object({
85
+ id: z$1.string().optional(),
86
+ number: z$1.string().optional(),
87
+ bankCode: z$1.enum(BANK_CODES).optional(),
88
+ holderName: z$1.string().optional(),
89
+ iban: ibanSchema.optional(),
90
+ address: z$1.string().optional(),
91
+ swiftBic: bicSchema.optional(),
92
+ bicCor: bicSchema.optional(),
93
+ currency: z$1.enum(CURRENCY_CODES).optional(),
94
+ country: z$1.enum(COUNTRY_CODES_2).optional(),
95
+ routingNumber: z$1.string().optional(),
96
+ // US
97
+ sortCode: z$1.string().optional(),
98
+ // UK
99
+ clabe: z$1.string().optional(),
100
+ // Mexico
101
+ bsb: z$1.string().optional(),
102
+ // Australia
103
+ brBankNumber: z$1.string().optional()
104
+ // Brazil
105
+ });
106
+
107
+ const paginationQuerySchema = new z.$ZodObject({
108
+ type: "object",
109
+ shape: {
110
+ page: new z.$ZodDefault({
111
+ type: "default",
112
+ innerType: new z.$ZodNumber({
113
+ type: "number",
114
+ coerce: true
115
+ }),
116
+ defaultValue: () => 1
117
+ }),
118
+ limit: new z.$ZodDefault({
119
+ type: "default",
120
+ innerType: new z.$ZodNumber({
121
+ type: "number",
122
+ coerce: true
123
+ }),
124
+ defaultValue: () => 20
125
+ }),
126
+ sort: new z.$ZodDefault({
127
+ type: "default",
128
+ innerType: new z.$ZodObject({
129
+ type: "object",
130
+ shape: {
131
+ column: new z.$ZodDefault({
132
+ type: "default",
133
+ innerType: new z.$ZodEnum({
134
+ type: "enum",
135
+ entries: {
136
+ createdAt: "createdAt",
137
+ updatedAt: "updatedAt",
138
+ deletedAt: "deletedAt"
139
+ }
140
+ }),
141
+ defaultValue: () => "updatedAt"
142
+ }),
143
+ direction: new z.$ZodDefault({
144
+ type: "default",
145
+ innerType: new z.$ZodEnum({
146
+ type: "enum",
147
+ entries: {
148
+ asc: "asc",
149
+ desc: "desc"
150
+ }
151
+ }),
152
+ defaultValue: () => "desc"
153
+ })
154
+ }
155
+ }),
156
+ defaultValue: () => ({ column: "updatedAt", direction: "desc" })
157
+ })
158
+ }
159
+ });
160
+ const paginationSchema = new z.$ZodObject({
161
+ type: "object",
162
+ shape: {
163
+ page: new z.$ZodNumber({
164
+ type: "number"
165
+ }),
166
+ limit: new z.$ZodNumber({
167
+ type: "number"
168
+ }),
169
+ sort: new z.$ZodObject({
170
+ type: "object",
171
+ shape: {
172
+ column: new z.$ZodEnum({
173
+ type: "enum",
174
+ entries: {
175
+ createdAt: "createdAt",
176
+ updatedAt: "updatedAt",
177
+ deletedAt: "deletedAt"
178
+ }
179
+ }),
180
+ direction: new z.$ZodEnum({
181
+ type: "enum",
182
+ entries: {
183
+ asc: "asc",
184
+ desc: "desc"
185
+ }
186
+ })
99
187
  }
100
- );
101
- context.set("idempotency", {
102
- key: idempotencyKeyHeader
103
- });
104
- await next();
105
- });
106
- };
188
+ })
189
+ }
190
+ });
107
191
 
108
192
  const handleActionResponse = ({
109
193
  error,
@@ -263,16 +347,6 @@ function createAuditLogWriter(table) {
263
347
  };
264
348
  }
265
349
 
266
- const validateBearerScheme = (header) => {
267
- return header.startsWith("Bearer ") && header.length > 7 && !header.slice(7).includes(" ");
268
- };
269
- const extractBearerToken = (header) => {
270
- return header.slice(7).trim();
271
- };
272
- const validateBearerToken = (bearerToken) => {
273
- return z$1.jwt().safeParse(bearerToken).success;
274
- };
275
-
276
350
  function first(rows) {
277
351
  return rows.length > 0 ? rows[0] : void 0;
278
352
  }
@@ -386,6 +460,34 @@ const getDrizzleD1Config = () => ({
386
460
  ...getD1Credentials()
387
461
  });
388
462
 
463
+ const buildSearchConditions = (search, columns, opts = {}) => {
464
+ const { wrap = "both" } = opts;
465
+ if (!search || search.trim() === "" || columns.length === 0) return void 0;
466
+ const searchTerm = search.toLowerCase();
467
+ const pattern = wrap === "both" ? `%${searchTerm}%` : wrap === "prefix" ? `%${searchTerm}` : wrap === "suffix" ? `${searchTerm}%` : searchTerm;
468
+ const parts = columns.map((col) => sql`LOWER(${col} || '') LIKE ${pattern}`);
469
+ return parts.length === 1 ? parts[0] : or(...parts);
470
+ };
471
+ const buildMultiFilterConditions = (column, value) => {
472
+ if (value === void 0) return void 0;
473
+ if (Array.isArray(value)) {
474
+ return value.length > 0 ? inArray(column, value) : void 0;
475
+ }
476
+ return eq(column, value);
477
+ };
478
+ const buildRangeFilterConditions = (column, minValue, maxValue) => {
479
+ const conditions = [];
480
+ if (minValue !== void 0) {
481
+ conditions.push(gte(column, minValue));
482
+ }
483
+ if (maxValue !== void 0) {
484
+ conditions.push(lte(column, maxValue));
485
+ }
486
+ if (conditions.length === 0) return void 0;
487
+ if (conditions.length === 1) return conditions[0];
488
+ return and(...conditions);
489
+ };
490
+
389
491
  class DatabaseTransaction {
390
492
  constructor(db, serviceName, auditLogWriter) {
391
493
  this.db = db;
@@ -481,90 +583,6 @@ async function useFetch(url, { parseAs = "json", ...options } = {}) {
481
583
  return [body, null];
482
584
  }
483
585
 
484
- const logRequest = (log) => {
485
- console.log("REQUEST | An incoming request has been recorded.", log);
486
- };
487
- const logResponse = (log) => {
488
- console.log(`RESPONSE | An outgoing response has been recorded.`, log);
489
- };
490
-
491
- const signPayload = async ({
492
- payload,
493
- privateKey
494
- }) => {
495
- const binaryPrivateKey = Uint8Array.from(
496
- atob(privateKey),
497
- (c) => c.charCodeAt(0)
498
- );
499
- const importedPrivateKey = await crypto.subtle.importKey(
500
- "pkcs8",
501
- binaryPrivateKey,
502
- {
503
- name: "RSASSA-PKCS1-v1_5",
504
- hash: "SHA-256"
505
- },
506
- false,
507
- ["sign"]
508
- );
509
- const encodedPayload = new TextEncoder().encode(payload);
510
- const signature = await crypto.subtle.sign(
511
- {
512
- name: "RSASSA-PKCS1-v1_5"
513
- },
514
- importedPrivateKey,
515
- encodedPayload
516
- );
517
- const base64Signature = btoa(
518
- String.fromCharCode(...new Uint8Array(signature))
519
- );
520
- return base64Signature;
521
- };
522
- const algParams = {
523
- RSA: {
524
- name: "RSASSA-PKCS1-v1_5",
525
- hash: { name: "SHA-256" }
526
- },
527
- EC: {
528
- name: "ECDSA",
529
- namedCurve: "P-256"
530
- },
531
- HMAC: {
532
- name: "HMAC",
533
- hash: { name: "SHA-256" }
534
- }
535
- };
536
- const verifyPayloadSignature = async ({
537
- signature,
538
- data,
539
- publicKey,
540
- algorithm = "RSA"
541
- }) => {
542
- const binaryPublicKey = Uint8Array.from(
543
- atob(publicKey),
544
- (c) => c.charCodeAt(0)
545
- );
546
- const format = algorithm === "HMAC" ? "raw" : "spki";
547
- const importedPublicKey = await crypto.subtle.importKey(
548
- format,
549
- binaryPublicKey,
550
- algParams[algorithm],
551
- false,
552
- ["verify"]
553
- );
554
- const encodedPayload = new TextEncoder().encode(data);
555
- const decodedSignature = Uint8Array.from(
556
- atob(signature),
557
- (c) => c.charCodeAt(0)
558
- );
559
- const isValid = await crypto.subtle.verify(
560
- algParams[algorithm],
561
- importedPublicKey,
562
- decodedSignature,
563
- encodedPayload
564
- );
565
- return isValid;
566
- };
567
-
568
586
  const calculateExponentialBackoff = (attempts, baseDelaySeconds) => {
569
587
  return baseDelaySeconds ** attempts;
570
588
  };
@@ -581,291 +599,6 @@ const getSecret = async (secretName, env) => {
581
599
  return await secret.get();
582
600
  };
583
601
 
584
- const jwt = () => {
585
- return createMiddleware(async (context, next) => {
586
- const authorizationHeader = context.req.header("Authorization");
587
- if (!authorizationHeader) {
588
- throw new HTTPException(401, {
589
- message: `The 'Authorization' header must exist and must have a value.`
590
- });
591
- }
592
- if (!validateBearerScheme(authorizationHeader)) {
593
- throw new HTTPException(401, {
594
- message: `The 'Authorization' header value must use the Bearer scheme.`
595
- });
596
- }
597
- const bearerToken = extractBearerToken(authorizationHeader);
598
- if (!validateBearerToken(bearerToken)) {
599
- throw new HTTPException(401, {
600
- message: `The Bearer token in the 'Authorization' header value must be a JWT.`
601
- });
602
- }
603
- const authService = context.env.AUTH_SERVICE;
604
- const { data, error } = await authService.verifyAccessToken({
605
- accessToken: bearerToken
606
- });
607
- if (!data || error) {
608
- throw new HTTPException(401, {
609
- message: "The JWT must contain valid user information."
610
- });
611
- }
612
- const rawUserMetaDataString = data.payload.user.rawUserMetaData;
613
- const rawUserMetaData = rawUserMetaDataString ? JSON.parse(rawUserMetaDataString) : null;
614
- const organizationId = rawUserMetaData?.organizationId ?? null;
615
- if (!organizationId) {
616
- throw new HTTPException(422, {
617
- message: "User data integrity check failed."
618
- });
619
- }
620
- context.set("user", {
621
- email: data.payload.user.email,
622
- role: data.payload.user.role,
623
- organizationId
624
- });
625
- context.set("jwt", data.payload);
626
- await next();
627
- });
628
- };
629
-
630
- const ip = () => {
631
- return createMiddleware(async (context, next) => {
632
- if (!["localhost", "dev"].includes(context.env.ENVIRONMENT)) {
633
- const requestIp = context.req.header("cf-connecting-ip") || context.req.header("x-forwarded-for");
634
- if (!requestIp) {
635
- throw new HTTPException(401, {
636
- message: "Failed to retrieve request IP address."
637
- });
638
- }
639
- const user = context.get("user");
640
- if (!user.organizationId) {
641
- throw new HTTPException(401, {
642
- message: "Failed to retrieve request organization ID."
643
- });
644
- }
645
- const organizationService = context.env.ORGANIZATION_SERVICE;
646
- const { data: organization, error } = await organizationService.getOrganization({
647
- organizationId: user.organizationId
648
- });
649
- if (!organization || error) {
650
- throw new HTTPException(404, {
651
- message: "Failed to retrieve organization."
652
- });
653
- }
654
- if (organization.ipAuthorization) {
655
- if (!organization.authorizedIps.map((ip2) => ip2.ip).includes(requestIp)) {
656
- throw new HTTPException(404, {
657
- message: "Forbidden"
658
- });
659
- }
660
- }
661
- }
662
- await next();
663
- });
664
- };
665
-
666
- const composeRequestLog = async (request) => {
667
- return {
668
- method: request.method,
669
- path: request.path,
670
- query: request.query(),
671
- headers: request.header(),
672
- body: await request.json().catch(() => null)
673
- };
674
- };
675
- const composeResponseLog = async (response, method, path) => {
676
- return {
677
- method,
678
- path,
679
- status: response.status,
680
- statusText: response.statusText,
681
- headers: Object.fromEntries(response.headers.entries()),
682
- body: await response.json().catch(() => null)
683
- };
684
- };
685
-
686
- const logger = () => {
687
- return createMiddleware(async (context, next) => {
688
- const requestLog = await composeRequestLog(context.req);
689
- logRequest(requestLog);
690
- await next();
691
- const response = context.res.clone();
692
- const responseLog = await composeResponseLog(
693
- response,
694
- context.req.method,
695
- context.req.url
696
- );
697
- logResponse(responseLog);
698
- });
699
- };
700
-
701
- const signature = () => {
702
- return createMiddleware(async (context, next) => {
703
- if (!["localhost", "dev"].includes(context.env.ENVIRONMENT)) {
704
- const signatureHeader = context.req.header("X-Signature");
705
- if (!signatureHeader) {
706
- throw new HTTPException(401, {
707
- message: `The 'X-Signature' header must exist and must have a value.`
708
- });
709
- }
710
- const signatureKeyHeader = context.req.header("X-Signature-Key");
711
- if (!signatureKeyHeader) {
712
- throw new HTTPException(401, {
713
- message: `The 'X-Signature-Key' header must exist and must have a value.`
714
- });
715
- }
716
- const payload = JSON.stringify(await context.req.json().catch(() => null));
717
- const user = context.get("user");
718
- if (!user.organizationId) {
719
- throw new HTTPException(401, {
720
- message: "Failed to retrieve request organization ID."
721
- });
722
- }
723
- const organizationService = context.env.ORGANIZATION_SERVICE;
724
- const { data: organization, error } = await organizationService.getOrganization({
725
- organizationId: user.organizationId
726
- });
727
- if (!organization || error) {
728
- throw new HTTPException(404, {
729
- message: "Failed to retrieve organization."
730
- });
731
- }
732
- const signatureKey = organization.signatureKeys.filter(
733
- (signatureKey2) => signatureKey2.name === signatureKeyHeader
734
- )[0];
735
- if (!signatureKey) {
736
- throw new HTTPException(404, {
737
- message: "Signature key not found."
738
- });
739
- }
740
- const isVerified = await verifyPayloadSignature({
741
- signature: signatureHeader,
742
- publicKey: signatureKey.publicKey,
743
- data: payload
744
- });
745
- if (!isVerified) {
746
- throw new HTTPException(401, {
747
- message: "Invalid signature key or signature."
748
- });
749
- }
750
- }
751
- await next();
752
- });
753
- };
754
-
755
- const ibanSchema = z$1.string().min(1).regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/, {
756
- message: "Invalid IBAN format"
757
- });
758
- const bicSchema = z$1.string().min(1).regex(/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/, {
759
- message: "Invalid SWIFT/BIC format"
760
- });
761
- const bankAccountMetadataSchema = z$1.object({
762
- id: z$1.string().optional(),
763
- number: z$1.string().optional(),
764
- bankCode: z$1.enum(BANK_CODES).optional(),
765
- holderName: z$1.string().optional(),
766
- iban: ibanSchema.optional(),
767
- address: z$1.string().optional(),
768
- swiftBic: bicSchema.optional(),
769
- bicCor: bicSchema.optional(),
770
- currency: z$1.enum(CURRENCY_CODES).optional(),
771
- country: z$1.enum(COUNTRY_CODES_2).optional(),
772
- routingNumber: z$1.string().optional(),
773
- // US
774
- sortCode: z$1.string().optional(),
775
- // UK
776
- clabe: z$1.string().optional(),
777
- // Mexico
778
- bsb: z$1.string().optional(),
779
- // Australia
780
- brBankNumber: z$1.string().optional()
781
- // Brazil
782
- });
783
-
784
- const paginationQuerySchema = new z.$ZodObject({
785
- type: "object",
786
- shape: {
787
- page: new z.$ZodDefault({
788
- type: "default",
789
- innerType: new z.$ZodNumber({
790
- type: "number",
791
- coerce: true
792
- }),
793
- defaultValue: () => 1
794
- }),
795
- limit: new z.$ZodDefault({
796
- type: "default",
797
- innerType: new z.$ZodNumber({
798
- type: "number",
799
- coerce: true
800
- }),
801
- defaultValue: () => 20
802
- }),
803
- sort: new z.$ZodDefault({
804
- type: "default",
805
- innerType: new z.$ZodObject({
806
- type: "object",
807
- shape: {
808
- column: new z.$ZodDefault({
809
- type: "default",
810
- innerType: new z.$ZodEnum({
811
- type: "enum",
812
- entries: {
813
- createdAt: "createdAt",
814
- updatedAt: "updatedAt",
815
- deletedAt: "deletedAt"
816
- }
817
- }),
818
- defaultValue: () => "updatedAt"
819
- }),
820
- direction: new z.$ZodDefault({
821
- type: "default",
822
- innerType: new z.$ZodEnum({
823
- type: "enum",
824
- entries: {
825
- asc: "asc",
826
- desc: "desc"
827
- }
828
- }),
829
- defaultValue: () => "desc"
830
- })
831
- }
832
- }),
833
- defaultValue: () => ({ column: "updatedAt", direction: "desc" })
834
- })
835
- }
836
- });
837
- const paginationSchema = new z.$ZodObject({
838
- type: "object",
839
- shape: {
840
- page: new z.$ZodNumber({
841
- type: "number"
842
- }),
843
- limit: new z.$ZodNumber({
844
- type: "number"
845
- }),
846
- sort: new z.$ZodObject({
847
- type: "object",
848
- shape: {
849
- column: new z.$ZodEnum({
850
- type: "enum",
851
- entries: {
852
- createdAt: "createdAt",
853
- updatedAt: "updatedAt",
854
- deletedAt: "deletedAt"
855
- }
856
- }),
857
- direction: new z.$ZodEnum({
858
- type: "enum",
859
- entries: {
860
- asc: "asc",
861
- desc: "desc"
862
- }
863
- })
864
- }
865
- })
866
- }
867
- });
868
-
869
602
  const service = (serviceName) => {
870
603
  return function(constructor) {
871
604
  return class extends constructor {
@@ -1006,4 +739,4 @@ function develitWorker(Worker) {
1006
739
  return DevelitWorker;
1007
740
  }
1008
741
 
1009
- export { DatabaseTransaction, ENVIRONMENT, RPCResponse, USER_ROLES, action, bankAccount, bankAccountMetadataSchema, base, bicSchema, calculateExponentialBackoff, cloudflareQueue, composeWranglerBase, createAuditLogWriter, createInternalError, defineCommand, derivePortFromId, develitWorker, durableObjectNamespaceIdFromName, first, firstOrError, getD1Credentials, getD1DatabaseIdFromWrangler, getDrizzleD1Config, getSecret, handleAction, handleActionResponse, ibanSchema, idempotency, ip, isInternalError, jwt, logger, paginationQuerySchema, paginationSchema, service, signPayload, signature, useFetch, useResult, useResultSync, uuidv4, verifyPayloadSignature };
742
+ export { DatabaseTransaction, ENVIRONMENT, RPCResponse, USER_ROLES, action, bankAccount, bankAccountMetadataSchema, base, bicSchema, buildMultiFilterConditions, buildRangeFilterConditions, buildSearchConditions, calculateExponentialBackoff, cloudflareQueue, composeWranglerBase, createAuditLogWriter, createInternalError, defineCommand, derivePortFromId, develitWorker, durableObjectNamespaceIdFromName, first, firstOrError, getD1Credentials, getD1DatabaseIdFromWrangler, getDrizzleD1Config, getSecret, handleAction, handleActionResponse, ibanSchema, isInternalError, paginationQuerySchema, paginationSchema, service, useFetch, useResult, useResultSync, uuidv4 };
@@ -0,0 +1,15 @@
1
+ import { MiddlewareHandler } from 'hono/types';
2
+
3
+ declare const idempotency: <T extends {
4
+ IDEMPOTENCY_KV: KVNamespace;
5
+ }>() => MiddlewareHandler;
6
+
7
+ declare const jwt: <TAuthService, TJwtPayload>() => MiddlewareHandler;
8
+
9
+ declare const ip: <TOrganizationService, TJwtPayload>() => MiddlewareHandler;
10
+
11
+ declare const logger: () => MiddlewareHandler;
12
+
13
+ declare const signature: <TOrganizationService, TJwtPayload>() => MiddlewareHandler;
14
+
15
+ export { idempotency, ip, jwt, logger, signature };
@@ -0,0 +1,15 @@
1
+ import { MiddlewareHandler } from 'hono/types';
2
+
3
+ declare const idempotency: <T extends {
4
+ IDEMPOTENCY_KV: KVNamespace;
5
+ }>() => MiddlewareHandler;
6
+
7
+ declare const jwt: <TAuthService, TJwtPayload>() => MiddlewareHandler;
8
+
9
+ declare const ip: <TOrganizationService, TJwtPayload>() => MiddlewareHandler;
10
+
11
+ declare const logger: () => MiddlewareHandler;
12
+
13
+ declare const signature: <TOrganizationService, TJwtPayload>() => MiddlewareHandler;
14
+
15
+ export { idempotency, ip, jwt, logger, signature };
@@ -0,0 +1,230 @@
1
+ import { createMiddleware } from 'hono/factory';
2
+ import { HTTPException } from 'hono/http-exception';
3
+ import 'h3';
4
+ import { z } from 'zod';
5
+ import 'cloudflare';
6
+ import 'node:fs';
7
+ import 'node:crypto';
8
+ import 'node:path';
9
+ import 'comment-json';
10
+ import 'drizzle-orm';
11
+ import { v as verifyPayloadSignature } from './shared/backend-sdk.CgM_2r0b.mjs';
12
+
13
+ const validateBearerScheme = (header) => {
14
+ return header.startsWith("Bearer ") && header.length > 7 && !header.slice(7).includes(" ");
15
+ };
16
+ const extractBearerToken = (header) => {
17
+ return header.slice(7).trim();
18
+ };
19
+ const validateBearerToken = (bearerToken) => {
20
+ return z.jwt().safeParse(bearerToken).success;
21
+ };
22
+
23
+ const logRequest = (log) => {
24
+ console.log("REQUEST | An incoming request has been recorded.", log);
25
+ };
26
+ const logResponse = (log) => {
27
+ console.log(`RESPONSE | An outgoing response has been recorded.`, log);
28
+ };
29
+
30
+ const idempotency = () => {
31
+ return createMiddleware(async (context, next) => {
32
+ const idempotencyKeyHeader = context.req.header("X-Idempotency-Key");
33
+ if (!idempotencyKeyHeader) {
34
+ throw new HTTPException(401, {
35
+ message: `The 'X-Idempotency-Key' header must exist and must have a value.`
36
+ });
37
+ }
38
+ const existingIdempotencyRecord = await context.env.IDEMPOTENCY_KV.get(idempotencyKeyHeader);
39
+ if (existingIdempotencyRecord) {
40
+ throw new HTTPException(409, {
41
+ message: "The identical request has already been processed. The idempotency key is not unique."
42
+ });
43
+ }
44
+ await context.env.IDEMPOTENCY_KV.put(
45
+ idempotencyKeyHeader,
46
+ idempotencyKeyHeader,
47
+ {
48
+ expirationTtl: 60 * 60 * 24 * 3
49
+ // 3 days
50
+ }
51
+ );
52
+ context.set("idempotency", {
53
+ key: idempotencyKeyHeader
54
+ });
55
+ await next();
56
+ });
57
+ };
58
+
59
+ const jwt = () => {
60
+ return createMiddleware(async (context, next) => {
61
+ const authorizationHeader = context.req.header("Authorization");
62
+ if (!authorizationHeader) {
63
+ throw new HTTPException(401, {
64
+ message: `The 'Authorization' header must exist and must have a value.`
65
+ });
66
+ }
67
+ if (!validateBearerScheme(authorizationHeader)) {
68
+ throw new HTTPException(401, {
69
+ message: `The 'Authorization' header value must use the Bearer scheme.`
70
+ });
71
+ }
72
+ const bearerToken = extractBearerToken(authorizationHeader);
73
+ if (!validateBearerToken(bearerToken)) {
74
+ throw new HTTPException(401, {
75
+ message: `The Bearer token in the 'Authorization' header value must be a JWT.`
76
+ });
77
+ }
78
+ const authService = context.env.AUTH_SERVICE;
79
+ const { data, error } = await authService.verifyAccessToken({
80
+ accessToken: bearerToken
81
+ });
82
+ if (!data || error) {
83
+ throw new HTTPException(401, {
84
+ message: "The JWT must contain valid user information."
85
+ });
86
+ }
87
+ const rawUserMetaDataString = data.payload.user.rawUserMetaData;
88
+ const rawUserMetaData = rawUserMetaDataString ? JSON.parse(rawUserMetaDataString) : null;
89
+ const organizationId = rawUserMetaData?.organizationId ?? null;
90
+ if (!organizationId) {
91
+ throw new HTTPException(422, {
92
+ message: "User data integrity check failed."
93
+ });
94
+ }
95
+ context.set("user", {
96
+ email: data.payload.user.email,
97
+ role: data.payload.user.role,
98
+ organizationId
99
+ });
100
+ context.set("jwt", data.payload);
101
+ await next();
102
+ });
103
+ };
104
+
105
+ const ip = () => {
106
+ return createMiddleware(async (context, next) => {
107
+ if (!["localhost", "dev"].includes(context.env.ENVIRONMENT)) {
108
+ const requestIp = context.req.header("cf-connecting-ip") || context.req.header("x-forwarded-for");
109
+ if (!requestIp) {
110
+ throw new HTTPException(401, {
111
+ message: "Failed to retrieve request IP address."
112
+ });
113
+ }
114
+ const user = context.get("user");
115
+ if (!user.organizationId) {
116
+ throw new HTTPException(401, {
117
+ message: "Failed to retrieve request organization ID."
118
+ });
119
+ }
120
+ const organizationService = context.env.ORGANIZATION_SERVICE;
121
+ const { data: organization, error } = await organizationService.getOrganization({
122
+ organizationId: user.organizationId
123
+ });
124
+ if (!organization || error) {
125
+ throw new HTTPException(404, {
126
+ message: "Failed to retrieve organization."
127
+ });
128
+ }
129
+ if (organization.ipAuthorization) {
130
+ if (!organization.authorizedIps.map((ip2) => ip2.ip).includes(requestIp)) {
131
+ throw new HTTPException(404, {
132
+ message: "Forbidden"
133
+ });
134
+ }
135
+ }
136
+ }
137
+ await next();
138
+ });
139
+ };
140
+
141
+ const composeRequestLog = async (request) => {
142
+ return {
143
+ method: request.method,
144
+ path: request.path,
145
+ query: request.query(),
146
+ headers: request.header(),
147
+ body: await request.json().catch(() => null)
148
+ };
149
+ };
150
+ const composeResponseLog = async (response, method, path) => {
151
+ return {
152
+ method,
153
+ path,
154
+ status: response.status,
155
+ statusText: response.statusText,
156
+ headers: Object.fromEntries(response.headers.entries()),
157
+ body: await response.json().catch(() => null)
158
+ };
159
+ };
160
+
161
+ const logger = () => {
162
+ return createMiddleware(async (context, next) => {
163
+ const requestLog = await composeRequestLog(context.req);
164
+ logRequest(requestLog);
165
+ await next();
166
+ const response = context.res.clone();
167
+ const responseLog = await composeResponseLog(
168
+ response,
169
+ context.req.method,
170
+ context.req.url
171
+ );
172
+ logResponse(responseLog);
173
+ });
174
+ };
175
+
176
+ const signature = () => {
177
+ return createMiddleware(async (context, next) => {
178
+ if (!["localhost", "dev"].includes(context.env.ENVIRONMENT)) {
179
+ const signatureHeader = context.req.header("X-Signature");
180
+ if (!signatureHeader) {
181
+ throw new HTTPException(401, {
182
+ message: `The 'X-Signature' header must exist and must have a value.`
183
+ });
184
+ }
185
+ const signatureKeyHeader = context.req.header("X-Signature-Key");
186
+ if (!signatureKeyHeader) {
187
+ throw new HTTPException(401, {
188
+ message: `The 'X-Signature-Key' header must exist and must have a value.`
189
+ });
190
+ }
191
+ const payload = JSON.stringify(await context.req.json().catch(() => null));
192
+ const user = context.get("user");
193
+ if (!user.organizationId) {
194
+ throw new HTTPException(401, {
195
+ message: "Failed to retrieve request organization ID."
196
+ });
197
+ }
198
+ const organizationService = context.env.ORGANIZATION_SERVICE;
199
+ const { data: organization, error } = await organizationService.getOrganization({
200
+ organizationId: user.organizationId
201
+ });
202
+ if (!organization || error) {
203
+ throw new HTTPException(404, {
204
+ message: "Failed to retrieve organization."
205
+ });
206
+ }
207
+ const signatureKey = organization.signatureKeys.filter(
208
+ (signatureKey2) => signatureKey2.name === signatureKeyHeader
209
+ )[0];
210
+ if (!signatureKey) {
211
+ throw new HTTPException(404, {
212
+ message: "Signature key not found."
213
+ });
214
+ }
215
+ const isVerified = await verifyPayloadSignature({
216
+ signature: signatureHeader,
217
+ publicKey: signatureKey.publicKey,
218
+ data: payload
219
+ });
220
+ if (!isVerified) {
221
+ throw new HTTPException(401, {
222
+ message: "Invalid signature key or signature."
223
+ });
224
+ }
225
+ }
226
+ await next();
227
+ });
228
+ };
229
+
230
+ export { idempotency, ip, jwt, logger, signature };
@@ -0,0 +1,98 @@
1
+ import { subtle } from 'node:crypto';
2
+
3
+ const createSignatureKeyPair = async () => {
4
+ const { publicKey, privateKey } = await subtle.generateKey(
5
+ {
6
+ name: "RSASSA-PKCS1-v1_5",
7
+ modulusLength: 4096,
8
+ publicExponent: new Uint8Array([1, 0, 1]),
9
+ hash: "SHA-256"
10
+ },
11
+ true,
12
+ ["sign", "verify"]
13
+ );
14
+ const exportedPublicKey = await subtle.exportKey("spki", publicKey);
15
+ const exportedPrivateKey = await subtle.exportKey("pkcs8", privateKey);
16
+ return {
17
+ publicKey: Buffer.from(exportedPublicKey).toString("base64"),
18
+ privateKey: Buffer.from(exportedPrivateKey).toString("base64")
19
+ };
20
+ };
21
+ const signPayload = async ({
22
+ payload,
23
+ privateKey
24
+ }) => {
25
+ const binaryPrivateKey = Uint8Array.from(
26
+ atob(privateKey),
27
+ (c) => c.charCodeAt(0)
28
+ );
29
+ const importedPrivateKey = await crypto.subtle.importKey(
30
+ "pkcs8",
31
+ binaryPrivateKey,
32
+ {
33
+ name: "RSASSA-PKCS1-v1_5",
34
+ hash: "SHA-256"
35
+ },
36
+ false,
37
+ ["sign"]
38
+ );
39
+ const encodedPayload = new TextEncoder().encode(payload);
40
+ const signature = await crypto.subtle.sign(
41
+ {
42
+ name: "RSASSA-PKCS1-v1_5"
43
+ },
44
+ importedPrivateKey,
45
+ encodedPayload
46
+ );
47
+ const base64Signature = btoa(
48
+ String.fromCharCode(...new Uint8Array(signature))
49
+ );
50
+ return base64Signature;
51
+ };
52
+ const algParams = {
53
+ RSA: {
54
+ name: "RSASSA-PKCS1-v1_5",
55
+ hash: { name: "SHA-256" }
56
+ },
57
+ EC: {
58
+ name: "ECDSA",
59
+ namedCurve: "P-256"
60
+ },
61
+ HMAC: {
62
+ name: "HMAC",
63
+ hash: { name: "SHA-256" }
64
+ }
65
+ };
66
+ const verifyPayloadSignature = async ({
67
+ signature,
68
+ data,
69
+ publicKey,
70
+ algorithm = "RSA"
71
+ }) => {
72
+ const binaryPublicKey = Uint8Array.from(
73
+ atob(publicKey),
74
+ (c) => c.charCodeAt(0)
75
+ );
76
+ const format = algorithm === "HMAC" ? "raw" : "spki";
77
+ const importedPublicKey = await crypto.subtle.importKey(
78
+ format,
79
+ binaryPublicKey,
80
+ algParams[algorithm],
81
+ false,
82
+ ["verify"]
83
+ );
84
+ const encodedPayload = new TextEncoder().encode(data);
85
+ const decodedSignature = Uint8Array.from(
86
+ atob(signature),
87
+ (c) => c.charCodeAt(0)
88
+ );
89
+ const isValid = await crypto.subtle.verify(
90
+ algParams[algorithm],
91
+ importedPublicKey,
92
+ decodedSignature,
93
+ encodedPayload
94
+ );
95
+ return isValid;
96
+ };
97
+
98
+ export { createSignatureKeyPair as c, signPayload as s, verifyPayloadSignature as v };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@develit-io/backend-sdk",
3
- "version": "6.0.0",
3
+ "version": "7.0.1",
4
4
  "description": "Develit Backend SDK",
5
5
  "author": "Develit.io",
6
6
  "license": "ISC",
@@ -22,6 +22,11 @@
22
22
  "import": "./dist/index.mjs",
23
23
  "default": "./dist/index.mjs"
24
24
  },
25
+ "./middlewares": {
26
+ "types": "./dist/middlewares.d.ts",
27
+ "import": "./dist/middlewares.mjs",
28
+ "default": "./dist/middlewares.mjs"
29
+ },
25
30
  "./package.json": "./package.json"
26
31
  },
27
32
  "files": [