@pattern-stack/codegen 0.6.4 → 0.6.5

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 (63) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -0
  3. package/dist/runtime/subsystems/auth/auth-oauth-state.schema.d.ts +81 -0
  4. package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js +12 -0
  5. package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js.map +1 -0
  6. package/dist/runtime/subsystems/auth/auth.module.d.ts +39 -24
  7. package/dist/runtime/subsystems/auth/auth.module.js +246 -13
  8. package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
  9. package/dist/runtime/subsystems/auth/auth.tokens.d.ts +15 -2
  10. package/dist/runtime/subsystems/auth/auth.tokens.js +9 -1
  11. package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
  12. package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.d.ts +23 -0
  13. package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js +68 -0
  14. package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js.map +1 -0
  15. package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.d.ts +21 -0
  16. package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.js +51 -0
  17. package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.js.map +1 -0
  18. package/dist/runtime/subsystems/auth/controllers/auth.controller.d.ts +31 -0
  19. package/dist/runtime/subsystems/auth/controllers/auth.controller.js +137 -0
  20. package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -0
  21. package/dist/runtime/subsystems/auth/index.d.ts +13 -4
  22. package/dist/runtime/subsystems/auth/index.js +253 -14
  23. package/dist/runtime/subsystems/auth/index.js.map +1 -1
  24. package/dist/runtime/subsystems/auth/protocols/integration-store.d.ts +36 -1
  25. package/dist/runtime/subsystems/auth/protocols/oauth-state-store.d.ts +33 -7
  26. package/dist/runtime/subsystems/auth/protocols/oauth-state-store.js +12 -0
  27. package/dist/runtime/subsystems/auth/protocols/oauth-state-store.js.map +1 -1
  28. package/dist/runtime/subsystems/auth/protocols/provider-strategy.d.ts +47 -0
  29. package/dist/runtime/subsystems/auth/protocols/provider-strategy.js +1 -0
  30. package/dist/runtime/subsystems/auth/protocols/provider-strategy.js.map +1 -0
  31. package/dist/runtime/subsystems/auth/protocols/user-context.d.ts +24 -0
  32. package/dist/runtime/subsystems/auth/protocols/user-context.js +1 -0
  33. package/dist/runtime/subsystems/auth/protocols/user-context.js.map +1 -0
  34. package/dist/runtime/subsystems/index.d.ts +9 -4
  35. package/dist/runtime/subsystems/index.js +247 -14
  36. package/dist/runtime/subsystems/index.js.map +1 -1
  37. package/dist/src/cli/index.js +574 -142
  38. package/dist/src/cli/index.js.map +1 -1
  39. package/package.json +1 -1
  40. package/runtime/subsystems/auth/auth-oauth-state.schema.ts +30 -0
  41. package/runtime/subsystems/auth/auth.module.ts +89 -32
  42. package/runtime/subsystems/auth/auth.tokens.ts +14 -1
  43. package/runtime/subsystems/auth/backends/state-store.drizzle-backend.ts +83 -0
  44. package/runtime/subsystems/auth/backends/state-store.memory-backend.ts +76 -0
  45. package/runtime/subsystems/auth/controllers/auth.controller.ts +155 -0
  46. package/runtime/subsystems/auth/index.ts +43 -4
  47. package/runtime/subsystems/auth/protocols/integration-store.ts +37 -0
  48. package/runtime/subsystems/auth/protocols/oauth-state-store.ts +38 -6
  49. package/runtime/subsystems/auth/protocols/provider-strategy.ts +41 -0
  50. package/runtime/subsystems/auth/protocols/user-context.ts +22 -0
  51. package/runtime/subsystems/index.ts +17 -2
  52. package/templates/subsystem/auth/app-module-hook.ejs.t +21 -0
  53. package/templates/subsystem/auth/auth-oauth-state.schema.ejs.t +35 -0
  54. package/templates/subsystem/auth/env-config.ejs.t +20 -0
  55. package/templates/subsystem/auth/prompt.js +46 -0
  56. package/templates/subsystem/auth-config/codegen-config-auth-block.ejs.t +20 -0
  57. package/templates/subsystem/auth-config/prompt.js +20 -0
  58. package/templates/subsystem/auth-integrations/app-module-hook.ejs.t +16 -0
  59. package/templates/subsystem/auth-integrations/prompt.js +23 -0
  60. package/dist/runtime/subsystems/auth/backends/oauth-state-store/in-memory.d.ts +0 -24
  61. package/dist/runtime/subsystems/auth/backends/oauth-state-store/in-memory.js +0 -24
  62. package/dist/runtime/subsystems/auth/backends/oauth-state-store/in-memory.js.map +0 -1
  63. package/runtime/subsystems/auth/backends/oauth-state-store/in-memory.ts +0 -42
@@ -64,3 +64,40 @@ export interface IntegrationTokenUpdate {
64
64
  export interface IIntegrationTokenWriter {
65
65
  persistRefresh(update: IntegrationTokenUpdate): Promise<void>;
66
66
  }
67
+
68
+ /**
69
+ * Grant-sink port — persists a freshly-minted OAuth2 grant from the
70
+ * authorize-code callback (i.e. the user just connected a new provider, or
71
+ * re-connected an existing one).
72
+ *
73
+ * `AuthController.callback` invokes this after `ProviderStrategy.exchangeCodeForTokens`.
74
+ * The subsystem itself never imports a concrete `IntegrationsService` — the
75
+ * consumer's `auth-integrations` starter (or any equivalent) adapts this
76
+ * port. Keeps the auth subsystem standalone: a non-codegen consumer can
77
+ * satisfy the port against its own integrations storage.
78
+ *
79
+ * Semantics:
80
+ * - Upserts on `(userId, provider)`. Repeated grants for the same pair
81
+ * replace the prior tokens (re-connect flow).
82
+ * - Implementations are responsible for encrypting tokens at rest.
83
+ * - `expiresAt` / `refreshToken` / `scope` / `externalAccountId` /
84
+ * `providerMetadata` are optional because not every provider supplies
85
+ * them (e.g. some providers omit `expires_in`; not every flow returns
86
+ * a refresh token on first grant).
87
+ */
88
+ export interface IntegrationGrantInput {
89
+ userId: string;
90
+ /** Provider slug — must match the strategy's `provider`. */
91
+ provider: string;
92
+ accessToken: string;
93
+ refreshToken?: string;
94
+ expiresAt?: Date;
95
+ scope?: string[];
96
+ externalAccountId?: string;
97
+ /** Provider-specific bag (SFDC `instance_url`, Google `sub`, …). */
98
+ providerMetadata?: Record<string, unknown>;
99
+ }
100
+
101
+ export interface IIntegrationGrantSink {
102
+ createOrUpdateFromOAuthGrant(input: IntegrationGrantInput): Promise<void>;
103
+ }
@@ -2,15 +2,47 @@
2
2
  * Auth subsystem — `IOAuthStateStore` port.
3
3
  *
4
4
  * CSRF protection for the OAuth2 authorize-code callback. Generic across
5
- * providers. Concrete backends live under `../backends/oauth-state-store/`.
5
+ * providers. The store mints opaque state tokens at /connect time and
6
+ * single-use consumes them at /callback time, returning the original
7
+ * record (userId + optional post-callback redirect path).
8
+ *
9
+ * Concrete backends live under `../backends/`:
10
+ * - `state-store.memory-backend.ts` — in-process Map (tests/dev).
11
+ * - `state-store.drizzle-backend.ts` — Postgres (prod).
12
+ *
13
+ * Semantics:
14
+ * - `generate(record)` → returns an opaque state token; record is stored
15
+ * under that token until consumed or until TTL expires.
16
+ * - `consume(state)` → atomically deletes the entry and returns the
17
+ * record. Throws on missing, expired, or replayed state. Never returns
18
+ * null — a missing/expired state is a CSRF signal.
6
19
  */
7
- export interface OAuthStateEntry {
20
+ export interface OAuthStateRecord {
8
21
  userId: string;
9
- createdAt: Date;
22
+ /** Optional post-callback redirect path (relative URL). */
23
+ redirect?: string;
10
24
  }
11
25
 
12
26
  export interface IOAuthStateStore {
13
- put(state: string, entry: OAuthStateEntry): Promise<void>;
14
- /** Single-use consume: returns entry if present + valid, deletes it. */
15
- consume(state: string): Promise<OAuthStateEntry | null>;
27
+ /** Mint an opaque state token bound to `record`. Single-use. */
28
+ generate(record: OAuthStateRecord): Promise<string>;
29
+ /**
30
+ * Atomically consume `state`, returning the bound record. Throws on
31
+ * missing / expired / replayed state.
32
+ */
33
+ consume(state: string): Promise<OAuthStateRecord>;
34
+ }
35
+
36
+ /**
37
+ * Thrown by `IOAuthStateStore.consume` when the state token is unknown,
38
+ * expired, or has already been consumed (replay attempt).
39
+ */
40
+ export class OAuthStateError extends Error {
41
+ constructor(
42
+ message: string,
43
+ public readonly reason: 'missing' | 'expired',
44
+ ) {
45
+ super(message);
46
+ this.name = 'OAuthStateError';
47
+ }
16
48
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Auth subsystem — `ProviderStrategy` contract.
3
+ *
4
+ * Extension of `OAuth2RefreshStrategy` (which already covers the refresh
5
+ * path) that adds the two methods needed by the connect/callback dance:
6
+ *
7
+ * - `buildAuthorizeUrl({ state, redirectUri })` → consent-page URL.
8
+ * - `exchangeCodeForTokens({ code, redirectUri })` → tokens after consent.
9
+ *
10
+ * Concrete per-provider strategies (HubSpot, SFDC, Google, Gong, Fathom, …)
11
+ * stay consumer-side per ADR-031 ("every app has different combinations").
12
+ * They typically subclass `OAuth2RefreshStrategy` for the refresh path and
13
+ * implement these two methods structurally — that satisfies
14
+ * `ProviderStrategy` because TS lets interfaces extend classes by type.
15
+ *
16
+ * AuthController never imports a concrete strategy — it injects the
17
+ * `STRATEGY_REGISTRY` (a `ReadonlyMap<provider-slug, ProviderStrategy>`)
18
+ * and dispatches by slug.
19
+ */
20
+ import type { OAuth2RefreshStrategy } from '../runtime/oauth2-refresh.strategy';
21
+
22
+ export interface ExchangedTokens {
23
+ accessToken: string;
24
+ refreshToken?: string;
25
+ expiresAt?: Date;
26
+ scope?: string[];
27
+ externalAccountId?: string;
28
+ /** Provider-specific bag (SFDC `instance_url`, Google `sub`, …). */
29
+ providerMetadata?: Record<string, unknown>;
30
+ }
31
+
32
+ export interface ProviderStrategy extends OAuth2RefreshStrategy {
33
+ buildAuthorizeUrl(args: { state: string; redirectUri: string }): string;
34
+ exchangeCodeForTokens(args: {
35
+ code: string;
36
+ redirectUri: string;
37
+ }): Promise<ExchangedTokens>;
38
+ }
39
+
40
+ /** The DI value type behind the `STRATEGY_REGISTRY` token. */
41
+ export type ProviderStrategyRegistry = ReadonlyMap<string, ProviderStrategy>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Auth subsystem — `IUserContext` port.
3
+ *
4
+ * Resolves "who is the current user" from a request. The shape is
5
+ * universal; the implementation is always app-specific:
6
+ *
7
+ * - WorkOS session: `req.session.user.id`
8
+ * - JWT bearer: `decode(req.headers.authorization).sub`
9
+ * - Test fixture: hardcoded UUID
10
+ *
11
+ * The auth subsystem cannot ship a default — every app does auth differently —
12
+ * but the port is universal, so the contract ships here. Consumers bind a
13
+ * concrete implementation under the `AUTH_USER_CONTEXT` token in their app
14
+ * module.
15
+ *
16
+ * `req` is typed as `unknown` deliberately: this protocol must not pull a
17
+ * dependency on `express` / `fastify` / NestJS request types. The concrete
18
+ * adapter narrows it (e.g. via a `Request` import).
19
+ */
20
+ export interface IUserContext {
21
+ getCurrentUserId(req: unknown): Promise<string>;
22
+ }
@@ -56,14 +56,22 @@ export {
56
56
  OAUTH_STATE_STORE,
57
57
  AUTH_INTEGRATION_READER,
58
58
  AUTH_INTEGRATION_TOKEN_WRITER,
59
+ AUTH_INTEGRATION_GRANT_SINK,
60
+ AUTH_USER_CONTEXT,
61
+ STRATEGY_REGISTRY,
62
+ AUTH_OPTIONS,
59
63
  AuthModule,
64
+ AuthController,
60
65
  OAuth2RefreshStrategy,
61
66
  withAuthRetry,
62
67
  IntegrationBrokenError,
63
68
  SessionExpiredError,
64
69
  isSessionExpiredError,
70
+ OAuthStateError,
65
71
  EnvEncryptionKey,
66
- InMemoryOAuthStateStore,
72
+ MemoryOAuthStateStore,
73
+ DrizzleOAuthStateStore,
74
+ authOAuthState,
67
75
  } from './auth';
68
76
  export type {
69
77
  IAuthStrategy,
@@ -71,10 +79,17 @@ export type {
71
79
  IOAuthStateStore,
72
80
  IIntegrationReader,
73
81
  IIntegrationTokenWriter,
82
+ IIntegrationGrantSink,
83
+ IntegrationGrantInput,
84
+ IUserContext,
85
+ ProviderStrategy,
86
+ ProviderStrategyRegistry,
87
+ ExchangedTokens,
74
88
  AuthCredentials,
75
89
  AuthResolveOptions,
76
90
  DecryptedIntegration,
77
- OAuthStateEntry,
91
+ OAuthStateRecord,
78
92
  IntegrationTokenUpdate,
79
93
  ParsedRefreshResponse,
94
+ AuthOAuthState,
80
95
  } from './auth';
@@ -0,0 +1,21 @@
1
+ ---
2
+ to: "<%= appModulePath %>"
3
+ inject: true
4
+ append: true
5
+ skip_if: "AuthModule"
6
+ ---
7
+
8
+ // TODO: Register AuthModule (auth subsystem)
9
+ // Add to AppModule.imports:
10
+ //
11
+ // import { AuthModule } from '@shared/subsystems/auth';
12
+ // // ...
13
+ // AuthModule.forRoot({
14
+ // encryptionKey: 'env',
15
+ // oauthStateStore: 'drizzle',
16
+ // enableController: true,
17
+ // redirectUriBase: process.env.AUTH_REDIRECT_URI_BASE ?? '<%= redirectUriBase %>',
18
+ // }),
19
+ //
20
+ // Requires TOKEN_ENCRYPTION_KEY in your environment (see .env.config).
21
+ // Provide an IUserContext adapter (your app's session/JWT scheme).
@@ -0,0 +1,35 @@
1
+ ---
2
+ to: "<%= schemaPath %>"
3
+ force: true
4
+ ---
5
+ // Schema barrel append tracked in #284 — same gap as events/jobs/sync today.
6
+ /**
7
+ * Drizzle schema for the `auth_oauth_state` table — backs the
8
+ * `DrizzleOAuthStateStore` (`state-store.drizzle-backend.ts`).
9
+ *
10
+ * One row per outstanding /connect → /callback dance. Single-use; rows are
11
+ * deleted on consume. A periodic sweep (or a `WHERE expires_at < now()`
12
+ * filter on read) clears abandoned rows.
13
+ *
14
+ * Columns:
15
+ * - `state` — opaque random token, primary key.
16
+ * - `user_id` — text (matches the consumer-defined user-id shape;
17
+ * the auth subsystem doesn't constrain this to UUID
18
+ * because some apps key users by external id).
19
+ * - `redirect` — optional post-callback redirect path.
20
+ * - `expires_at` — TTL boundary; entries past this are treated as absent.
21
+ *
22
+ * Convention: schema files live at the root of the subsystem dir
23
+ * (mirrors `cache.schema.ts`, `sync-audit.schema.ts`, `domain-events.schema.ts`).
24
+ */
25
+ import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';
26
+ import type { InferSelectModel } from 'drizzle-orm';
27
+
28
+ export const authOAuthState = pgTable('auth_oauth_state', {
29
+ state: text('state').primaryKey(),
30
+ userId: text('user_id').notNull(),
31
+ redirect: text('redirect'),
32
+ expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
33
+ });
34
+
35
+ export type AuthOAuthState = InferSelectModel<typeof authOAuthState>;
@@ -0,0 +1,20 @@
1
+ ---
2
+ to: "<%= envConfigPath %>"
3
+ inject: true
4
+ append: true
5
+ skip_if: "TOKEN_ENCRYPTION_KEY"
6
+ ---
7
+
8
+ # OAuth integration token encryption key — 32 bytes base64 (AES-256-GCM).
9
+ # DO NOT REUSE this dev value in prod. To regenerate locally:
10
+ # node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
11
+ # Re-running `codegen subsystem install auth` does NOT rotate this key —
12
+ # `skip_if: "TOKEN_ENCRYPTION_KEY"` blocks the inject intentionally, because
13
+ # silently rotating would invalidate every encrypted token already in the
14
+ # consumer's `integrations` table. Rotation is a separate, auditable op.
15
+ # In production: store the key in `secrets/secrets.yaml` (or your secret
16
+ # manager) and have your deploy pipeline materialise it into the env at
17
+ # boot — this `.env.config` line should be a placeholder, not the source.
18
+ TOKEN_ENCRYPTION_KEY=<%= tokenEncryptionKey %>
19
+ # Public base URL where OAuth providers redirect back. Override in staging/prod.
20
+ AUTH_REDIRECT_URI_BASE=<%= redirectUriBase %>
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Hygen prompt.js — #287 auth subsystem scaffold.
3
+ *
4
+ * Locals are resolved by the CLI (src/cli/shared/auth-scaffold-locals.ts)
5
+ * and forwarded as CLI args. This prompt.js just coerces and forwards;
6
+ * no interactive prompts.
7
+ *
8
+ * Invoked via:
9
+ * bunx hygen subsystem auth \
10
+ * --appName <string> \
11
+ * --configPath <abs> \
12
+ * --schemaPath <abs> \
13
+ * --appModulePath <abs> \
14
+ * --envConfigPath <abs> \
15
+ * --redirectUriBase <url> \
16
+ * --tokenEncryptionKey <b64-32-bytes>
17
+ *
18
+ * The three templates this folder steers:
19
+ * - `auth-oauth-state.schema.ejs.t` — emits the `auth_oauth_state`
20
+ * drizzle schema (sole emitter; copyRuntime skips the runtime source).
21
+ * - `app-module-hook.ejs.t` — appends a TODO comment block to
22
+ * app.module.ts directing the human to register
23
+ * `AuthModule.forRoot({ ... })`. Same convention as observability.
24
+ * - `env-config.ejs.t` — appends `TOKEN_ENCRYPTION_KEY=<b64>` and
25
+ * `AUTH_REDIRECT_URI_BASE=<url>` to `.env.config`. Idempotent via
26
+ * `skip_if: "TOKEN_ENCRYPTION_KEY"` — re-running install does NOT
27
+ * regenerate the key (rotation is a separate operation).
28
+ *
29
+ * Auth has NO `multi_tenant` knob (see auth-scaffold-locals.ts docstring).
30
+ */
31
+
32
+ export default {
33
+ prompt: async ({ args }) => {
34
+ return {
35
+ appName: args.appName ?? "",
36
+ configPath: args.configPath ?? "codegen.config.yaml",
37
+ schemaPath:
38
+ args.schemaPath ??
39
+ "src/shared/subsystems/auth/auth-oauth-state.schema.ts",
40
+ appModulePath: args.appModulePath ?? "src/app.module.ts",
41
+ envConfigPath: args.envConfigPath ?? ".env.config",
42
+ redirectUriBase: args.redirectUriBase ?? "http://localhost:3000",
43
+ tokenEncryptionKey: args.tokenEncryptionKey ?? "",
44
+ };
45
+ },
46
+ };
@@ -0,0 +1,20 @@
1
+ ---
2
+ to: "<%= configPath %>"
3
+ inject: true
4
+ append: true
5
+ skip_if: "auth:"
6
+ ---
7
+
8
+ auth:
9
+ # ── Encryption key (TOKEN_ENCRYPTION_KEY from .env.config) ──
10
+ encryption_key: env
11
+
12
+ # ── OAuth state store (drizzle for prod, memory for tests) ──
13
+ oauth_state_store: drizzle
14
+
15
+ # ── Public base URL — providers redirect back here ──
16
+ # Override in staging/prod via AUTH_REDIRECT_URI_BASE env var.
17
+ redirect_uri_base: http://localhost:3000
18
+
19
+ # ── Mount AuthController under /auth/:provider ──
20
+ enable_controller: true
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Hygen prompt.js — #287 auth config-block scaffold.
3
+ *
4
+ * Split from `templates/subsystem/auth/` so the CLI can invoke the
5
+ * config-block inject step independently — `subsystem install auth --force`
6
+ * preserves an existing `auth:` block by skipping this action; pass
7
+ * `--force-config` to opt into regeneration. Mirrors `events-config` /
8
+ * `sync-config` / `observability-config` exactly.
9
+ *
10
+ * Invoked via:
11
+ * bunx hygen subsystem auth-config --configPath <abs>
12
+ */
13
+
14
+ export default {
15
+ prompt: async ({ args }) => {
16
+ return {
17
+ configPath: args.configPath ?? "codegen.config.yaml",
18
+ };
19
+ },
20
+ };
@@ -0,0 +1,16 @@
1
+ ---
2
+ to: "<%= appModulePath %>"
3
+ inject: true
4
+ append: true
5
+ skip_if: "IntegrationsAuthModule"
6
+ ---
7
+
8
+ // TODO: Register IntegrationsAuthModule (vendored from auth-integrations starter)
9
+ // Add to AppModule.imports AFTER AuthModule:
10
+ //
11
+ // import { IntegrationsAuthModule } from '@shared/integrations/integrations-auth.module';
12
+ // // ...
13
+ // IntegrationsAuthModule,
14
+ //
15
+ // Requires AuthModule.forRoot(...) registered first (provides ENCRYPTION_KEY).
16
+ // Run `cdp entity new integration` to scaffold the codegen layer the adapters import.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Hygen prompt.js — #287 auth-integrations starter scaffold.
3
+ *
4
+ * Locals are resolved by the CLI
5
+ * (src/cli/shared/auth-integrations-scaffold-locals.ts) and forwarded as
6
+ * CLI args. The vendor copies (runtime/integrations/** + integration.yaml)
7
+ * happen in subsystem.ts (`runAuthIntegrationsScaffold`); this template
8
+ * folder only injects the TODO comment block into app.module.ts.
9
+ *
10
+ * Invoked via:
11
+ * bunx hygen subsystem auth-integrations \
12
+ * --appName <string> \
13
+ * --appModulePath <abs>
14
+ */
15
+
16
+ export default {
17
+ prompt: async ({ args }) => {
18
+ return {
19
+ appName: args.appName ?? "",
20
+ appModulePath: args.appModulePath ?? "src/app.module.ts",
21
+ };
22
+ },
23
+ };
@@ -1,24 +0,0 @@
1
- import { IOAuthStateStore, OAuthStateEntry } from '../../protocols/oauth-state-store.js';
2
-
3
- /**
4
- * In-memory OAuth state store.
5
- *
6
- * Single-process dev store. Production deployments need a Redis-backed impl
7
- * (follow-up) so state survives restarts + is shared across workers.
8
- */
9
-
10
- interface InMemoryOAuthStateStoreOptions {
11
- /** TTL in ms. Entries older than this are treated as absent. Default 10min. */
12
- ttlMs?: number;
13
- now?: () => number;
14
- }
15
- declare class InMemoryOAuthStateStore implements IOAuthStateStore {
16
- private readonly store;
17
- private readonly ttlMs;
18
- private readonly now;
19
- constructor(opts?: InMemoryOAuthStateStoreOptions);
20
- put(state: string, entry: OAuthStateEntry): Promise<void>;
21
- consume(state: string): Promise<OAuthStateEntry | null>;
22
- }
23
-
24
- export { InMemoryOAuthStateStore, type InMemoryOAuthStateStoreOptions };
@@ -1,24 +0,0 @@
1
- // runtime/subsystems/auth/backends/oauth-state-store/in-memory.ts
2
- var InMemoryOAuthStateStore = class {
3
- store = /* @__PURE__ */ new Map();
4
- ttlMs;
5
- now;
6
- constructor(opts = {}) {
7
- this.ttlMs = opts.ttlMs ?? 10 * 60 * 1e3;
8
- this.now = opts.now ?? (() => Date.now());
9
- }
10
- async put(state, entry) {
11
- this.store.set(state, { entry, expiresAt: this.now() + this.ttlMs });
12
- }
13
- async consume(state) {
14
- const slot = this.store.get(state);
15
- if (!slot) return null;
16
- this.store.delete(state);
17
- if (slot.expiresAt <= this.now()) return null;
18
- return slot.entry;
19
- }
20
- };
21
- export {
22
- InMemoryOAuthStateStore
23
- };
24
- //# sourceMappingURL=in-memory.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../runtime/subsystems/auth/backends/oauth-state-store/in-memory.ts"],"sourcesContent":["/**\n * In-memory OAuth state store.\n *\n * Single-process dev store. Production deployments need a Redis-backed impl\n * (follow-up) so state survives restarts + is shared across workers.\n */\nimport type {\n IOAuthStateStore,\n OAuthStateEntry,\n} from '../../protocols/oauth-state-store';\n\nexport interface InMemoryOAuthStateStoreOptions {\n /** TTL in ms. Entries older than this are treated as absent. Default 10min. */\n ttlMs?: number;\n now?: () => number;\n}\n\nexport class InMemoryOAuthStateStore implements IOAuthStateStore {\n private readonly store = new Map<\n string,\n { entry: OAuthStateEntry; expiresAt: number }\n >();\n private readonly ttlMs: number;\n private readonly now: () => number;\n\n constructor(opts: InMemoryOAuthStateStoreOptions = {}) {\n this.ttlMs = opts.ttlMs ?? 10 * 60 * 1000;\n this.now = opts.now ?? (() => Date.now());\n }\n\n async put(state: string, entry: OAuthStateEntry): Promise<void> {\n this.store.set(state, { entry, expiresAt: this.now() + this.ttlMs });\n }\n\n async consume(state: string): Promise<OAuthStateEntry | null> {\n const slot = this.store.get(state);\n if (!slot) return null;\n this.store.delete(state);\n if (slot.expiresAt <= this.now()) return null;\n return slot.entry;\n }\n}\n"],"mappings":";AAiBO,IAAM,0BAAN,MAA0D;AAAA,EAC9C,QAAQ,oBAAI,IAG3B;AAAA,EACe;AAAA,EACA;AAAA,EAEjB,YAAY,OAAuC,CAAC,GAAG;AACrD,SAAK,QAAQ,KAAK,SAAS,KAAK,KAAK;AACrC,SAAK,MAAM,KAAK,QAAQ,MAAM,KAAK,IAAI;AAAA,EACzC;AAAA,EAEA,MAAM,IAAI,OAAe,OAAuC;AAC9D,SAAK,MAAM,IAAI,OAAO,EAAE,OAAO,WAAW,KAAK,IAAI,IAAI,KAAK,MAAM,CAAC;AAAA,EACrE;AAAA,EAEA,MAAM,QAAQ,OAAgD;AAC5D,UAAM,OAAO,KAAK,MAAM,IAAI,KAAK;AACjC,QAAI,CAAC,KAAM,QAAO;AAClB,SAAK,MAAM,OAAO,KAAK;AACvB,QAAI,KAAK,aAAa,KAAK,IAAI,EAAG,QAAO;AACzC,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}
@@ -1,42 +0,0 @@
1
- /**
2
- * In-memory OAuth state store.
3
- *
4
- * Single-process dev store. Production deployments need a Redis-backed impl
5
- * (follow-up) so state survives restarts + is shared across workers.
6
- */
7
- import type {
8
- IOAuthStateStore,
9
- OAuthStateEntry,
10
- } from '../../protocols/oauth-state-store';
11
-
12
- export interface InMemoryOAuthStateStoreOptions {
13
- /** TTL in ms. Entries older than this are treated as absent. Default 10min. */
14
- ttlMs?: number;
15
- now?: () => number;
16
- }
17
-
18
- export class InMemoryOAuthStateStore implements IOAuthStateStore {
19
- private readonly store = new Map<
20
- string,
21
- { entry: OAuthStateEntry; expiresAt: number }
22
- >();
23
- private readonly ttlMs: number;
24
- private readonly now: () => number;
25
-
26
- constructor(opts: InMemoryOAuthStateStoreOptions = {}) {
27
- this.ttlMs = opts.ttlMs ?? 10 * 60 * 1000;
28
- this.now = opts.now ?? (() => Date.now());
29
- }
30
-
31
- async put(state: string, entry: OAuthStateEntry): Promise<void> {
32
- this.store.set(state, { entry, expiresAt: this.now() + this.ttlMs });
33
- }
34
-
35
- async consume(state: string): Promise<OAuthStateEntry | null> {
36
- const slot = this.store.get(state);
37
- if (!slot) return null;
38
- this.store.delete(state);
39
- if (slot.expiresAt <= this.now()) return null;
40
- return slot.entry;
41
- }
42
- }