@koralabs/kora-labs-common 6.6.4 → 6.7.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/aws/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { decryptKmsCiphertext, hydrateKmsEnvironment, loadAfterHydratingKmsEnvironment } from './kmsEnvironment';
2
2
  export type { KmsClientLike } from './kmsEnvironment';
3
+ export { signRs256, verifyRs256, isLocalJwtSigner } from './jwtSigner';
package/aws/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadAfterHydratingKmsEnvironment = exports.hydrateKmsEnvironment = exports.decryptKmsCiphertext = void 0;
3
+ exports.isLocalJwtSigner = exports.verifyRs256 = exports.signRs256 = exports.loadAfterHydratingKmsEnvironment = exports.hydrateKmsEnvironment = exports.decryptKmsCiphertext = void 0;
4
4
  var kmsEnvironment_1 = require("./kmsEnvironment");
5
5
  Object.defineProperty(exports, "decryptKmsCiphertext", { enumerable: true, get: function () { return kmsEnvironment_1.decryptKmsCiphertext; } });
6
6
  Object.defineProperty(exports, "hydrateKmsEnvironment", { enumerable: true, get: function () { return kmsEnvironment_1.hydrateKmsEnvironment; } });
7
7
  Object.defineProperty(exports, "loadAfterHydratingKmsEnvironment", { enumerable: true, get: function () { return kmsEnvironment_1.loadAfterHydratingKmsEnvironment; } });
8
+ var jwtSigner_1 = require("./jwtSigner");
9
+ Object.defineProperty(exports, "signRs256", { enumerable: true, get: function () { return jwtSigner_1.signRs256; } });
10
+ Object.defineProperty(exports, "verifyRs256", { enumerable: true, get: function () { return jwtSigner_1.verifyRs256; } });
11
+ Object.defineProperty(exports, "isLocalJwtSigner", { enumerable: true, get: function () { return jwtSigner_1.isLocalJwtSigner; } });
@@ -0,0 +1,8 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ /** True when a local RSA key is configured (i.e. signing/verifying needs no AWS). */
4
+ export declare const isLocalJwtSigner: () => boolean;
5
+ /** Sign `message` with RS256 — local private key if present, else AWS KMS. */
6
+ export declare const signRs256: (message: Buffer) => Promise<Buffer>;
7
+ /** Verify an RS256 `signature` over `message` — local public key if present, else AWS KMS. */
8
+ export declare const verifyRs256: (message: Buffer, signature: Buffer) => Promise<boolean>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyRs256 = exports.signRs256 = exports.isLocalJwtSigner = void 0;
4
+ const client_kms_1 = require("@aws-sdk/client-kms");
5
+ const crypto_1 = require("crypto");
6
+ // RS256 (RSASSA_PKCS1_V1_5_SHA_256) sign/verify primitive shared by the JWT-issuing/
7
+ // verifying services (auth.handle.me grant cookie, handle.me/minting session tokens).
8
+ //
9
+ // It PREFERS a LOCAL RSA key — for the self-hosted deployment with no AWS — and FALLS
10
+ // BACK to AWS KMS (JWT_KEY_ID) when no local key is configured, so existing KMS-backed
11
+ // deployments keep working byte-for-byte and adoption can be gradual.
12
+ //
13
+ // JWT_PRIVATE_KEY RSA private key (PEM, or base64-encoded PEM) — used to SIGN. The
14
+ // public half is derived from it for verification, so a single shared
15
+ // secret (the private key) is enough on every node.
16
+ // JWT_PUBLIC_KEY (optional) RSA public key (PEM/base64) for verify-only nodes that
17
+ // should not hold the private key.
18
+ // JWT_KEY_ID AWS KMS key id/alias (legacy fallback when no local key is set).
19
+ //
20
+ // These callers sign/verify over different bytes (auth signs the payload only; the
21
+ // session-token path signs `header.payload`), so this stays a low-level bytes-in/bytes-out
22
+ // primitive — each service keeps building its own token exactly as before.
23
+ const SIGNING_ALGORITHM = 'RSASSA_PKCS1_V1_5_SHA_256';
24
+ let _kms;
25
+ const kms = () => (_kms !== null && _kms !== void 0 ? _kms : (_kms = new client_kms_1.KMSClient({})));
26
+ // Accept a raw PEM or a base64-encoded PEM (single-line, env-friendly).
27
+ const pemFromEnv = (value) => {
28
+ if (!value)
29
+ return undefined;
30
+ if (value.includes('-----BEGIN'))
31
+ return value;
32
+ try {
33
+ const decoded = Buffer.from(value, 'base64').toString('utf-8');
34
+ return decoded.includes('-----BEGIN') ? decoded : undefined;
35
+ }
36
+ catch (_a) {
37
+ return undefined;
38
+ }
39
+ };
40
+ const localPrivateKey = () => pemFromEnv(process.env.JWT_PRIVATE_KEY);
41
+ const localPublicKey = () => {
42
+ const pub = pemFromEnv(process.env.JWT_PUBLIC_KEY);
43
+ if (pub)
44
+ return pub;
45
+ const priv = localPrivateKey();
46
+ if (!priv)
47
+ return undefined;
48
+ return (0, crypto_1.createPublicKey)(priv).export({ type: 'spki', format: 'pem' }).toString();
49
+ };
50
+ /** True when a local RSA key is configured (i.e. signing/verifying needs no AWS). */
51
+ const isLocalJwtSigner = () => !!localPrivateKey() || !!pemFromEnv(process.env.JWT_PUBLIC_KEY);
52
+ exports.isLocalJwtSigner = isLocalJwtSigner;
53
+ /** Sign `message` with RS256 — local private key if present, else AWS KMS. */
54
+ const signRs256 = async (message) => {
55
+ const priv = localPrivateKey();
56
+ if (priv) {
57
+ const signer = (0, crypto_1.createSign)('RSA-SHA256');
58
+ signer.update(message);
59
+ signer.end();
60
+ return signer.sign(priv);
61
+ }
62
+ const keyId = process.env.JWT_KEY_ID;
63
+ if (!keyId)
64
+ throw new Error('JWT_KEY_ID not configured');
65
+ const response = await kms().send(new client_kms_1.SignCommand({
66
+ KeyId: keyId,
67
+ Message: new Uint8Array(message),
68
+ MessageType: 'RAW',
69
+ SigningAlgorithm: SIGNING_ALGORITHM
70
+ }));
71
+ if (!response.Signature)
72
+ throw new Error('KMS sign returned no signature');
73
+ return Buffer.from(response.Signature);
74
+ };
75
+ exports.signRs256 = signRs256;
76
+ /** Verify an RS256 `signature` over `message` — local public key if present, else AWS KMS. */
77
+ const verifyRs256 = async (message, signature) => {
78
+ const pub = localPublicKey();
79
+ if (pub) {
80
+ const verifier = (0, crypto_1.createVerify)('RSA-SHA256');
81
+ verifier.update(message);
82
+ verifier.end();
83
+ return verifier.verify(pub, signature);
84
+ }
85
+ const keyId = process.env.JWT_KEY_ID;
86
+ if (!keyId)
87
+ throw new Error('JWT_KEY_ID not configured');
88
+ const response = await kms().send(new client_kms_1.VerifyCommand({
89
+ KeyId: keyId,
90
+ Message: new Uint8Array(message),
91
+ MessageType: 'RAW',
92
+ Signature: new Uint8Array(signature),
93
+ SigningAlgorithm: SIGNING_ALGORITHM
94
+ }));
95
+ return !!response.SignatureValid;
96
+ };
97
+ exports.verifyRs256 = verifyRs256;
package/fn/index.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ interface AlbEvent {
4
+ requestContext: {
5
+ elb: {
6
+ targetGroupArn: string;
7
+ };
8
+ };
9
+ httpMethod: string;
10
+ path: string;
11
+ queryStringParameters: Record<string, string> | null;
12
+ multiValueQueryStringParameters?: Record<string, string[]> | null;
13
+ headers: Record<string, string>;
14
+ body: string | null;
15
+ isBase64Encoded: boolean;
16
+ }
17
+ interface AlbResult {
18
+ statusCode?: number;
19
+ statusDescription?: string;
20
+ headers?: Record<string, string | number | boolean>;
21
+ multiValueHeaders?: Record<string, Array<string | number | boolean>>;
22
+ body?: string;
23
+ isBase64Encoded?: boolean;
24
+ }
25
+ export type AlbHandler = (event: AlbEvent, context: Record<string, unknown>) => Promise<AlbResult> | AlbResult;
26
+ export interface FnHttpGateway {
27
+ requestURL: string;
28
+ method: string;
29
+ headers: Record<string, string | string[]>;
30
+ statusCode: number;
31
+ setResponseHeader(key: string, ...values: string[]): void;
32
+ addResponseHeader(key: string, ...values: string[]): void;
33
+ }
34
+ export interface FnContext {
35
+ httpGateway?: FnHttpGateway;
36
+ }
37
+ /**
38
+ * Drive an app's ALB (serverless-express) handler from an Fn HTTP invocation.
39
+ * @param albHandler the app's existing @vendia/serverless-express handler
40
+ * @param ctx the Fn FDK context (must be HTTP-triggered → ctx.httpGateway present)
41
+ * @param input the request body (Buffer, with fdk inputMode 'buffer')
42
+ * @returns the response body (Buffer or string); status + headers are set on ctx.httpGateway
43
+ */
44
+ export declare const invokeExpressViaAlb: (albHandler: AlbHandler, ctx: FnContext, input?: Buffer | Uint8Array | string) => Promise<Buffer | string>;
45
+ export {};
package/fn/index.js ADDED
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ // Fn <-> Express bridge for the self-hosted runtime (MIGRATION-PLAN §4).
3
+ //
4
+ // Each app already exposes an @vendia/serverless-express ALB handler (e.g.
5
+ // lambdas/api.app.ts `handler(event, ctx)`), which is how it ran on Lambda/ALB. Rather
6
+ // than invent a new req/res bridge, `invokeExpressViaAlb` adapts an Fn HTTP-trigger
7
+ // invocation INTO an ALB event, calls that existing handler, and maps the ALB response
8
+ // back to Fn. So the Fn function reuses the exact Express request handling the app already
9
+ // has, and apps need only a tiny func.js entrypoint:
10
+ //
11
+ // const fdk = require('@fnproject/fdk');
12
+ // const { handler } = require('./dist/lambdas/api.app'); // the app's ALB handler
13
+ // const { invokeExpressViaAlb } = require('@koralabs/kora-labs-common');
14
+ // fdk.handle((input, ctx) => invokeExpressViaAlb(handler, ctx, input), { inputMode: 'buffer' });
15
+ //
16
+ // Typed loosely (no @types/aws-lambda / @fnproject/fdk dependency here) — the shapes below
17
+ // match the ALB event/result and the Fn HTTPGatewayContext.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.invokeExpressViaAlb = void 0;
20
+ const flattenHeaders = (h) => {
21
+ const out = {};
22
+ for (const [k, v] of Object.entries(h || {})) {
23
+ out[k.toLowerCase()] = Array.isArray(v) ? v.join(', ') : String(v);
24
+ }
25
+ return out;
26
+ };
27
+ /**
28
+ * Drive an app's ALB (serverless-express) handler from an Fn HTTP invocation.
29
+ * @param albHandler the app's existing @vendia/serverless-express handler
30
+ * @param ctx the Fn FDK context (must be HTTP-triggered → ctx.httpGateway present)
31
+ * @param input the request body (Buffer, with fdk inputMode 'buffer')
32
+ * @returns the response body (Buffer or string); status + headers are set on ctx.httpGateway
33
+ */
34
+ const invokeExpressViaAlb = async (albHandler, ctx, input) => {
35
+ var _a;
36
+ const h = ctx.httpGateway;
37
+ if (!h)
38
+ throw new Error('invokeExpressViaAlb: ctx.httpGateway missing (the function must be HTTP-triggered)');
39
+ // requestURL may be a bare path (`/handles?x=1`) or absolute; URL() needs a base.
40
+ const url = new URL(h.requestURL, 'http://kora.local');
41
+ const qs = {};
42
+ const mqs = {};
43
+ for (const key of new Set(url.searchParams.keys())) {
44
+ const all = url.searchParams.getAll(key);
45
+ qs[key] = all[all.length - 1];
46
+ mqs[key] = all;
47
+ }
48
+ const bodyBuf = input == null
49
+ ? Buffer.alloc(0)
50
+ : Buffer.isBuffer(input) ? input : Buffer.from(input);
51
+ const event = {
52
+ requestContext: { elb: { targetGroupArn: 'arn:kora:fn' } },
53
+ httpMethod: h.method,
54
+ path: url.pathname,
55
+ queryStringParameters: Object.keys(qs).length ? qs : null,
56
+ multiValueQueryStringParameters: Object.keys(mqs).length ? mqs : null,
57
+ headers: flattenHeaders(h.headers),
58
+ body: bodyBuf.length ? bodyBuf.toString('base64') : null,
59
+ isBase64Encoded: true
60
+ };
61
+ const res = (await albHandler(event, {})) || {};
62
+ h.statusCode = (_a = res.statusCode) !== null && _a !== void 0 ? _a : 200;
63
+ if (res.headers) {
64
+ for (const [k, v] of Object.entries(res.headers))
65
+ h.setResponseHeader(k, String(v));
66
+ }
67
+ if (res.multiValueHeaders) {
68
+ for (const [k, arr] of Object.entries(res.multiValueHeaders)) {
69
+ for (const v of arr)
70
+ h.addResponseHeader(k, String(v));
71
+ }
72
+ }
73
+ if (res.body == null)
74
+ return '';
75
+ return res.isBase64Encoded ? Buffer.from(res.body, 'base64') : res.body;
76
+ };
77
+ exports.invokeExpressViaAlb = invokeExpressViaAlb;
package/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './constants';
2
2
  export * from './aws';
3
3
  export { ComputeEnvironment, Environment } from './environment';
4
4
  export * from './errors';
5
+ export * from './fn';
5
6
  export * from './handles';
6
7
  export * from './http';
7
8
  export { LogCategory, Logger } from './logger';
package/index.js CHANGED
@@ -21,6 +21,7 @@ var environment_1 = require("./environment");
21
21
  Object.defineProperty(exports, "ComputeEnvironment", { enumerable: true, get: function () { return environment_1.ComputeEnvironment; } });
22
22
  Object.defineProperty(exports, "Environment", { enumerable: true, get: function () { return environment_1.Environment; } });
23
23
  __exportStar(require("./errors"), exports);
24
+ __exportStar(require("./fn"), exports);
24
25
  __exportStar(require("./handles"), exports);
25
26
  __exportStar(require("./http"), exports);
26
27
  var logger_1 = require("./logger");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koralabs/kora-labs-common",
3
- "version": "6.6.4",
3
+ "version": "6.7.1",
4
4
  "description": "Kora Labs Common Utilities",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",