@rdlabo/workers-hono-kit 0.6.3 → 0.6.4

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/README.md CHANGED
@@ -5,7 +5,7 @@ Infrastructure toolkit for building APIs on [Hono](https://hono.dev) + [Cloudfla
5
5
  It provides the building blocks a NestJS-style API needs but that don't run on `workerd` (no Node.js AWS SDK, no `firebase-admin`), plus middleware that matches Express / NestJS response semantics byte-for-byte:
6
6
 
7
7
  - **Firebase ID-token verification** on Workers via [`jose`](https://github.com/panva/jose) (RS256 against Google's securetoken JWKS), with optional Identity Toolkit REST for `getUser` / `deleteUser`.
8
- - **AWS Secrets Manager** via SigV4-signed `fetch` ([`aws4fetch`](https://github.com/mhart/aws4fetch)) — no AWS SDK.
8
+ - **AWS Secrets Manager / STS AssumeRole / CloudFront signed URLs** via SigV4-signed `fetch` ([`aws4fetch`](https://github.com/mhart/aws4fetch)) or Web Crypto — no AWS SDK.
9
9
  - **Middleware**: `finalizeResponse` (Express-compatible weak ETag + JSON charset), `validate` (NestJS `ValidationPipe`-shaped 400), and zod number-coercion helpers.
10
10
  - **Standard API errors**: `createHttpErrorHandler` / `notFoundHandler` / `HttpStatus`.
11
11
  - **Deadlock retry** (`ER_LOCK_DEADLOCK` exponential backoff) and an optional **MySQL data layer** (`@rdlabo/workers-hono-kit/db`) for Hyperdrive + Drizzle.
@@ -56,6 +56,7 @@ npm install ai ai-gateway-provider # createAiGatewayProvider
56
56
  | `createSentryValidate(sentry)` | **Deprecated** — use `createValidate({ sentry })`. |
57
57
  | `zNum` / `zNumWithDefault` / `zNumOptional` / `zNumNullable` | Number-coercion zod schemas (mirror class-transformer `@Transform`). |
58
58
  | `getAuthenticationSecret<T>(options, secretId)` / `AwsSecretsOptions` | Fetch a secret from AWS Secrets Manager (SigV4 `fetch`, per-isolate cache). |
59
+ | `getTemporaryCredentials(options)` / `GetTemporaryCredentialsOptions` / `StsCredentials` | STS `AssumeRole` via SigV4 `fetch` (global `sts.amazonaws.com`); returns temporary credentials for browser S3 uploads. |
59
60
  | `getCloudFrontSignedUrl(url, privateKeyPem, keyPairId, dateLessThan)` | CloudFront signed URL (canned policy, RSA-SHA1, URL-safe base64) — Web Crypto reimpl of `@aws-sdk/cloudfront-signer`, byte-identical query order. |
60
61
  | `JoseFirebaseVerifier` / `FirebaseVerifier` / `DecodedIdToken` | Firebase ID-token verification (`verifyIdToken`, `getUser`, `deleteUser`). |
61
62
  | `createRemoteFirebaseVerifier(projectId)` | Convenience factory: production verifier with a cached remote JWKS (verification only). |
@@ -271,6 +272,20 @@ const secret = await getAuthenticationSecret<MySecret>(
271
272
  );
272
273
  ```
273
274
 
275
+ ### STS AssumeRole (browser S3 uploads)
276
+
277
+ ```ts
278
+ import { getTemporaryCredentials } from '@rdlabo/workers-hono-kit';
279
+
280
+ const credentials = await getTemporaryCredentials({
281
+ accessKeyId: env.AWS_ACCESS_KEY_ID,
282
+ secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
283
+ roleArn: 'arn:aws:iam::123456789012:role/s3-put-app-only-role',
284
+ roleSessionName: `session-${userId}-${Date.now()}`,
285
+ });
286
+ // Return credentials to the browser; PutObject uses @aws-sdk/client-s3 with AccessKeyId / …
287
+ ```
288
+
274
289
  ### Deadlock retry & HTTP helpers
275
290
 
276
291
  ```ts
@@ -0,0 +1,67 @@
1
+ /**
2
+ * AWS credentials used to sign an STS `AssumeRole` request.
3
+ *
4
+ * @remarks
5
+ * Same shape as {@link AwsSecretsOptions} minus the required Secrets Manager region; STS defaults to
6
+ * the global endpoint (`us-east-1`) unless {@link GetTemporaryCredentialsOptions.region} is set.
7
+ */
8
+ export interface GetTemporaryCredentialsOptions {
9
+ /** AWS access key ID of the caller principal that may AssumeRole. */
10
+ accessKeyId: string;
11
+ /** AWS secret access key of the caller principal. */
12
+ secretAccessKey: string;
13
+ /** Optional STS session token when the caller already holds temporary credentials. */
14
+ sessionToken?: string;
15
+ /** ARN of the role to assume (e.g. `arn:aws:iam::123:role/s3-put-app-only-role`). */
16
+ roleArn: string;
17
+ /** Session name recorded in CloudTrail (often `session-${userId}-${Date.now()}`). */
18
+ roleSessionName: string;
19
+ /**
20
+ * Credential lifetime in seconds.
21
+ * @defaultValue 900
22
+ */
23
+ durationSeconds?: number;
24
+ /**
25
+ * STS SigV4 signing region.
26
+ * @defaultValue us-east-1
27
+ */
28
+ region?: string;
29
+ /**
30
+ * STS endpoint URL.
31
+ * @defaultValue https://sts.amazonaws.com/
32
+ */
33
+ endpoint?: string;
34
+ }
35
+ /**
36
+ * Temporary credentials returned by STS `AssumeRole`.
37
+ *
38
+ * @remarks
39
+ * Field names match the STS XML response / `@aws-sdk/client-sts` `Credentials` shape so browser
40
+ * apps can pass them straight into `@aws-sdk/client-s3` (`AccessKeyId` → `accessKeyId`, etc.).
41
+ */
42
+ export interface StsCredentials {
43
+ AccessKeyId?: string;
44
+ SecretAccessKey?: string;
45
+ SessionToken?: string;
46
+ Expiration?: Date;
47
+ }
48
+ /**
49
+ * Call STS `AssumeRole` via SigV4-signed `fetch` (aws4fetch) and return temporary credentials.
50
+ *
51
+ * Port of winecode / airlec browser-upload credential issuance — no AWS SDK. The consuming app
52
+ * supplies `roleArn` and `roleSessionName`; the kit only performs the signed STS request and XML parse.
53
+ *
54
+ * @param options - Caller AWS keys plus assume-role parameters.
55
+ * @returns Temporary credentials for browser or edge PutObject / GetObject.
56
+ * @throws Error When the STS response is not OK.
57
+ * @example
58
+ * ```ts
59
+ * const credentials = await getTemporaryCredentials({
60
+ * accessKeyId: env.AWS_ACCESS_KEY_ID,
61
+ * secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
62
+ * roleArn: 'arn:aws:iam::123:role/s3-put-app-only-role',
63
+ * roleSessionName: `session-${userId}-${Date.now()}`,
64
+ * });
65
+ * ```
66
+ */
67
+ export declare function getTemporaryCredentials(options: GetTemporaryCredentialsOptions): Promise<StsCredentials>;
@@ -0,0 +1,66 @@
1
+ import { AwsClient } from 'aws4fetch';
2
+ /** STS SigV4 signing region for the global endpoint (`sts.amazonaws.com`). */
3
+ const DEFAULT_STS_REGION = 'us-east-1';
4
+ /** winecode / airlec default — 15 minutes. */
5
+ const DEFAULT_DURATION_SECONDS = 900;
6
+ const STS_API_VERSION = '2011-06-15';
7
+ /** Global STS endpoint (unchanged from winecode AwsService). */
8
+ const DEFAULT_STS_ENDPOINT = 'https://sts.amazonaws.com/';
9
+ /**
10
+ * Call STS `AssumeRole` via SigV4-signed `fetch` (aws4fetch) and return temporary credentials.
11
+ *
12
+ * Port of winecode / airlec browser-upload credential issuance — no AWS SDK. The consuming app
13
+ * supplies `roleArn` and `roleSessionName`; the kit only performs the signed STS request and XML parse.
14
+ *
15
+ * @param options - Caller AWS keys plus assume-role parameters.
16
+ * @returns Temporary credentials for browser or edge PutObject / GetObject.
17
+ * @throws Error When the STS response is not OK.
18
+ * @example
19
+ * ```ts
20
+ * const credentials = await getTemporaryCredentials({
21
+ * accessKeyId: env.AWS_ACCESS_KEY_ID,
22
+ * secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
23
+ * roleArn: 'arn:aws:iam::123:role/s3-put-app-only-role',
24
+ * roleSessionName: `session-${userId}-${Date.now()}`,
25
+ * });
26
+ * ```
27
+ */
28
+ export async function getTemporaryCredentials(options) {
29
+ const region = options.region ?? DEFAULT_STS_REGION;
30
+ const durationSeconds = options.durationSeconds ?? DEFAULT_DURATION_SECONDS;
31
+ const endpoint = options.endpoint ?? DEFAULT_STS_ENDPOINT;
32
+ const params = new URLSearchParams({
33
+ Action: 'AssumeRole',
34
+ Version: STS_API_VERSION,
35
+ RoleArn: options.roleArn,
36
+ RoleSessionName: options.roleSessionName,
37
+ DurationSeconds: String(durationSeconds),
38
+ });
39
+ const aws = new AwsClient({
40
+ accessKeyId: options.accessKeyId,
41
+ secretAccessKey: options.secretAccessKey,
42
+ sessionToken: options.sessionToken,
43
+ region,
44
+ service: 'sts',
45
+ });
46
+ const response = await aws.fetch(endpoint, {
47
+ method: 'POST',
48
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
49
+ body: params.toString(),
50
+ });
51
+ const xml = await response.text();
52
+ if (!response.ok) {
53
+ throw new Error(`STS AssumeRole failed: ${response.status} ${xml}`);
54
+ }
55
+ const pick = (tag) => {
56
+ const m = new RegExp(`<${tag}>([^<]*)</${tag}>`).exec(xml);
57
+ return m ? m[1] : undefined;
58
+ };
59
+ const expiration = pick('Expiration');
60
+ return {
61
+ AccessKeyId: pick('AccessKeyId'),
62
+ SecretAccessKey: pick('SecretAccessKey'),
63
+ SessionToken: pick('SessionToken'),
64
+ Expiration: expiration ? new Date(expiration) : undefined,
65
+ };
66
+ }
package/dist/index.d.ts CHANGED
@@ -59,6 +59,8 @@ export type { AiGatewayConfig, AiGatewayProvider, AiGatewayBinding, AiGateway, A
59
59
  export { getAuthenticationSecret } from './aws/secrets-manager.js';
60
60
  export type { AwsSecretsOptions } from './aws/secrets-manager.js';
61
61
  export { getCloudFrontSignedUrl } from './aws/cloudfront.js';
62
+ export { getTemporaryCredentials } from './aws/sts.js';
63
+ export type { GetTemporaryCredentialsOptions, StsCredentials } from './aws/sts.js';
62
64
  export type { DecodedIdToken, FirebaseVerifier } from './firebase/firebase-verifier.js';
63
65
  export { JoseFirebaseVerifier, SECURETOKEN_JWK_URL } from './firebase/jose-firebase-verifier.js';
64
66
  export { IdentityToolkit } from './firebase/identity-toolkit.js';
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ export { createAiGatewayProvider } from './ai/gateway.js';
47
47
  // aws
48
48
  export { getAuthenticationSecret } from './aws/secrets-manager.js';
49
49
  export { getCloudFrontSignedUrl } from './aws/cloudfront.js';
50
+ export { getTemporaryCredentials } from './aws/sts.js';
50
51
  export { JoseFirebaseVerifier, SECURETOKEN_JWK_URL } from './firebase/jose-firebase-verifier.js';
51
52
  export { IdentityToolkit } from './firebase/identity-toolkit.js';
52
53
  export { createRemoteFirebaseVerifier, createServiceAccountVerifier } from './firebase/remote-verifier.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rdlabo/workers-hono-kit",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"