@o3co/auth-provider-foundation 0.4.0 → 0.5.0

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.ja.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @o3co/auth-provider-foundation
2
2
 
3
- auth.provider 向けの組み込みリポジトリ実装パッケージ。HTTP ベースのユーザー認証と、Redis を使った認可コードのストレージを提供する。
3
+ auth.provider 向けの本番用 HTTP ユーザー認証アダプターパッケージ。`"http"` アダプター型を `UserRepository` ファクトリーに登録し、`authenticate` / `authenticateByToken` を上流 HTTP サービスへ委譲する。
4
+
5
+ このパッケージのスコープは v0.5.0 モジュールシステムにおける **本番用の非データベース / 外部サービスアダプター** であり、v0.5.0 時点では `HttpUserRepository` のみを提供する。以前同梱されていた Redis `CodeRepository` アダプターは Phase 10 で [`@o3co/auth-provider-redis`](../redis/README.ja.md) に移管された。
4
6
 
5
7
  ## インストール
6
8
 
@@ -8,27 +10,27 @@ auth.provider 向けの組み込みリポジトリ実装パッケージ。HTTP
8
10
  npm install @o3co/auth-provider-foundation
9
11
  # peer dependency(必須):
10
12
  npm install @o3co/auth-provider-core
11
- # peer dependency(任意 — RedisCodeRepository を使う場合のみ必要):
12
- npm install redis
13
13
  ```
14
14
 
15
15
  ## パブリック API
16
16
 
17
17
  ### `registerBuiltinAdapters`
18
18
 
19
- 組み込みアダプターのファクトリーを、指定されたファクトリーインスタンスに登録する。
20
-
21
- - `"http"` を `userFactory` に登録 → `HttpUserRepository` を生成
22
- - `"redis"` を `codeFactory` に登録 → `RedisCodeRepository` を生成
19
+ `"http"` アダプター型を、指定された `userFactory` に登録する。
23
20
 
24
21
  ```typescript
25
22
  function registerBuiltinAdapters(factories: {
26
23
  userFactory: AdapterFactory<UserRepository>;
27
- codeFactory: AdapterFactory<CodeRepository>;
28
- pathResolver?: PathResolver; // 任意 — "redis" モジュールのパス解決に使用
29
24
  }): void;
30
25
  ```
31
26
 
27
+ Redis を使った認可コードストレージが必要な場合は、`@o3co/auth-provider-redis` の builder を直接登録する:
28
+
29
+ ```typescript
30
+ import { redisCodeRepositoryBuilder } from "@o3co/auth-provider-redis";
31
+ codeFactory.register("redis", redisCodeRepositoryBuilder);
32
+ ```
33
+
32
34
  ### `HttpUserRepository`
33
35
 
34
36
  上流の HTTP サービスに認証処理を委譲する `UserRepository` 実装。
@@ -52,45 +54,15 @@ class HttpUserRepository implements UserRepository {
52
54
  - HTTP 401 または 403 の場合は `null` を返す。
53
55
  - その他の非 OK ステータスの場合はエラーをスローする。
54
56
 
55
- ### `RedisCodeRepository`
56
-
57
- Redis に認可コードを保存する `CodeRepository` 実装。キーは `oauth:code:<base64url-code>` 形式で保存され、TTL を設定できる。
58
-
59
- ```typescript
60
- class RedisCodeRepository implements CodeRepository {
61
- constructor(redis: RedisClient, defaultExpiresIn?: number); // defaultExpiresIn のデフォルトは 600 秒
62
-
63
- // ファクトリーメソッド — config から Redis クライアントを生成して接続する
64
- static create(
65
- config: Record<string, unknown>,
66
- pathResolver?: PathResolver,
67
- ): Promise<RedisCodeRepository>;
68
- // config のキー:
69
- // endpointUri (string, 必須) — Redis 接続 URI
70
- // password (string, 任意) — Redis パスワード
71
- // defaultExpiresIn (number, 任意) — TTL(秒)、デフォルト 600
72
-
73
- initialize(): Promise<void>; // Redis クライアントを接続する
74
- createCode(params: {
75
- code_challenge?: string;
76
- code_challenge_method?: string;
77
- expiresIn?: number; // このコードだけ defaultExpiresIn を上書きする
78
- }): Promise<Code>;
79
- getByCode(code: string): Promise<Code | null>;
80
- consumeByCode(code: string): Promise<Code | null>; // アトミックな GET + DEL
81
- removeByCode(code: string): Promise<void>;
82
- }
83
- ```
84
-
85
57
  ## 使い方
86
58
 
87
59
  ```typescript
88
- import { createDefaultFactories } from "@o3co/auth-provider-core";
60
+ import { createRepositoryFactories } from "@o3co/auth-provider-core";
89
61
  import { registerBuiltinAdapters } from "@o3co/auth-provider-foundation";
90
62
 
91
- const { userFactory, codeFactory } = createDefaultFactories();
63
+ const { userFactory } = createRepositoryFactories();
92
64
 
93
- registerBuiltinAdapters({ userFactory, codeFactory });
65
+ registerBuiltinAdapters({ userFactory });
94
66
 
95
67
  // ファクトリー経由で HTTP ユーザーリポジトリを生成
96
68
  const userRepo = await userFactory.create({
@@ -99,16 +71,10 @@ const userRepo = await userFactory.create({
99
71
  authenticateByTokenUrl: "https://users.example.com/authenticate-by-token",
100
72
  timeout: 5000,
101
73
  });
102
-
103
- // ファクトリー経由で Redis コードリポジトリを生成
104
- const codeRepo = await codeFactory.create({
105
- type: "redis",
106
- endpointUri: "redis://localhost:6379",
107
- defaultExpiresIn: 300,
108
- });
109
74
  ```
110
75
 
111
76
  ## 関連
112
77
 
113
- - [`@o3co/auth-provider-core`](../core/README.md) — コアインターフェース(`UserRepository`、`CodeRepository`、`AdapterFactory`、`createAdapterFactory`、`BuilderContext`、`PathResolver`)
78
+ - [`@o3co/auth-provider-core`](../core/README.ja.md) — コアインターフェース(`UserRepository`、`CodeRepository`、`AdapterFactory`、`createAdapterFactory`、`BuilderContext`、`PathResolver`)
79
+ - [`@o3co/auth-provider-redis`](../redis/README.ja.md) — Redis バックエンドのアダプター群(challenges、replay-seen-set、refresh-token-family、user-sessions、federation-tokens、**code-repository**、rate-limiter)
114
80
  - [auth.provider](../../README.md) — リポジトリ全体のドキュメント
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @o3co/auth-provider-foundation
2
2
 
3
- Built-in repository implementations for auth.provider. Provides HTTP-based user authentication and Redis-backed authorization code storage.
3
+ Production HTTP user-authentication adapter for auth.provider. Registers the `"http"` adapter type into a `UserRepository` factory, which delegates `authenticate` / `authenticateByToken` to an upstream HTTP service.
4
+
5
+ This package's scope is **production non-database / external-service adapters** for the v0.5.0 module system. As of v0.5.0 it ships exactly one such adapter (`HttpUserRepository`); the previous Redis `CodeRepository` adapter was relocated to [`@o3co/auth-provider-redis`](../redis/README.md) in Phase 10.
4
6
 
5
7
  ## Install
6
8
 
@@ -8,27 +10,27 @@ Built-in repository implementations for auth.provider. Provides HTTP-based user
8
10
  npm install @o3co/auth-provider-foundation
9
11
  # Peer dependency (required):
10
12
  npm install @o3co/auth-provider-core
11
- # Peer dependency (optional — required only when using RedisCodeRepository):
12
- npm install redis
13
13
  ```
14
14
 
15
15
  ## Public API
16
16
 
17
17
  ### `registerBuiltinAdapters`
18
18
 
19
- Registers the built-in adapter factories into the provided factory instances.
20
-
21
- - Registers `"http"` into `userFactory` → creates `HttpUserRepository`
22
- - Registers `"redis"` into `codeFactory` → creates `RedisCodeRepository`
19
+ Registers the `"http"` adapter type into the provided `userFactory`.
23
20
 
24
21
  ```typescript
25
22
  function registerBuiltinAdapters(factories: {
26
23
  userFactory: AdapterFactory<UserRepository>;
27
- codeFactory: AdapterFactory<CodeRepository>;
28
- pathResolver?: PathResolver; // optional — used to resolve the "redis" module path
29
24
  }): void;
30
25
  ```
31
26
 
27
+ For Redis-backed code storage, register the builder from `@o3co/auth-provider-redis` directly:
28
+
29
+ ```typescript
30
+ import { redisCodeRepositoryBuilder } from "@o3co/auth-provider-redis";
31
+ codeFactory.register("redis", redisCodeRepositoryBuilder);
32
+ ```
33
+
32
34
  ### `HttpUserRepository`
33
35
 
34
36
  A `UserRepository` implementation that delegates authentication to an upstream HTTP service.
@@ -52,45 +54,15 @@ class HttpUserRepository implements UserRepository {
52
54
  - Returns `null` on HTTP 401 or 403.
53
55
  - Throws an error on any other non-OK HTTP status.
54
56
 
55
- ### `RedisCodeRepository`
56
-
57
- A `CodeRepository` implementation that stores authorization codes in Redis. Keys are stored as `oauth:code:<base64url-code>` with a configurable TTL.
58
-
59
- ```typescript
60
- class RedisCodeRepository implements CodeRepository {
61
- constructor(redis: RedisClient, defaultExpiresIn?: number); // defaultExpiresIn defaults to 600 seconds
62
-
63
- // Factory method — creates and connects a Redis client from config
64
- static create(
65
- config: Record<string, unknown>,
66
- pathResolver?: PathResolver,
67
- ): Promise<RedisCodeRepository>;
68
- // config keys:
69
- // endpointUri (string, required) — Redis connection URI
70
- // password (string, optional) — Redis password
71
- // defaultExpiresIn (number, optional) — TTL in seconds, default 600
72
-
73
- initialize(): Promise<void>; // connects the Redis client
74
- createCode(params: {
75
- code_challenge?: string;
76
- code_challenge_method?: string;
77
- expiresIn?: number; // overrides defaultExpiresIn for this code
78
- }): Promise<Code>;
79
- getByCode(code: string): Promise<Code | null>;
80
- consumeByCode(code: string): Promise<Code | null>; // atomic GET + DEL
81
- removeByCode(code: string): Promise<void>;
82
- }
83
- ```
84
-
85
57
  ## Usage Example
86
58
 
87
59
  ```typescript
88
- import { createDefaultFactories } from "@o3co/auth-provider-core";
60
+ import { createRepositoryFactories } from "@o3co/auth-provider-core";
89
61
  import { registerBuiltinAdapters } from "@o3co/auth-provider-foundation";
90
62
 
91
- const { userFactory, codeFactory } = createDefaultFactories();
63
+ const { userFactory } = createRepositoryFactories();
92
64
 
93
- registerBuiltinAdapters({ userFactory, codeFactory });
65
+ registerBuiltinAdapters({ userFactory });
94
66
 
95
67
  // Create an HTTP user repository via the factory
96
68
  const userRepo = await userFactory.create({
@@ -99,16 +71,10 @@ const userRepo = await userFactory.create({
99
71
  authenticateByTokenUrl: "https://users.example.com/authenticate-by-token",
100
72
  timeout: 5000,
101
73
  });
102
-
103
- // Create a Redis code repository via the factory
104
- const codeRepo = await codeFactory.create({
105
- type: "redis",
106
- endpointUri: "redis://localhost:6379",
107
- defaultExpiresIn: 300,
108
- });
109
74
  ```
110
75
 
111
76
  ## See Also
112
77
 
113
78
  - [`@o3co/auth-provider-core`](../core/README.md) — Core interfaces (`UserRepository`, `CodeRepository`, `AdapterFactory`, `createAdapterFactory`, `BuilderContext`, `PathResolver`)
79
+ - [`@o3co/auth-provider-redis`](../redis/README.md) — Redis-backed adapters (challenges, replay-seen-set, refresh-token-family, user-sessions, federation-tokens, **code-repository**, rate-limiter)
114
80
  - [auth.provider](../../README.md) — Top-level repository documentation
package/dist/index.d.mts CHANGED
@@ -1,9 +1,6 @@
1
- import type { AdapterFactory, CodeRepository, PathResolver, UserRepository } from "@o3co/auth-provider-core";
1
+ import type { AdapterFactory, UserRepository } from "@o3co/auth-provider-core";
2
2
  export declare const registerBuiltinAdapters: (factories: {
3
3
  userFactory: AdapterFactory<UserRepository>;
4
- codeFactory: AdapterFactory<CodeRepository>;
5
- pathResolver?: PathResolver;
6
4
  }) => void;
7
5
  export { HttpUserRepository } from "./repositories/HttpUserRepository.mjs";
8
- export { RedisCodeRepository } from "./repositories/RedisCodeRepository.mjs";
9
6
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACX,cAAc,EACd,cAAc,EACd,YAAY,EACZ,cAAc,EACd,MAAM,0BAA0B,CAAC;AAIlC,eAAO,MAAM,uBAAuB,GAAI,WAAW;IAClD,WAAW,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC;IAC5C,WAAW,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC;IAC5C,YAAY,CAAC,EAAE,YAAY,CAAC;CAC5B,KAAG,IAiCH,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/E,eAAO,MAAM,uBAAuB,GAAI,WAAW;IAClD,WAAW,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC;CAC5C,KAAG,IAyBH,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC"}
package/dist/index.mjs CHANGED
@@ -14,9 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { HttpUserRepository } from "./repositories/HttpUserRepository.mjs";
17
- import { RedisCodeRepository } from "./repositories/RedisCodeRepository.mjs";
18
17
  export const registerBuiltinAdapters = (factories) => {
19
- const { pathResolver } = factories;
20
18
  factories.userFactory.register("http", (config) => {
21
19
  if (typeof config.authenticateUrl !== "string") {
22
20
  throw new Error('HttpUserRepository requires "authenticateUrl" in config');
@@ -41,12 +39,5 @@ export const registerBuiltinAdapters = (factories) => {
41
39
  })(),
42
40
  });
43
41
  });
44
- factories.codeFactory.register("redis", (config) => {
45
- if (typeof config.endpointUri !== "string") {
46
- throw new Error('RedisCodeRepository requires "endpointUri" in config');
47
- }
48
- return RedisCodeRepository.create(config, pathResolver);
49
- });
50
42
  };
51
43
  export { HttpUserRepository } from "./repositories/HttpUserRepository.mjs";
52
- export { RedisCodeRepository } from "./repositories/RedisCodeRepository.mjs";
@@ -39,8 +39,7 @@ afterAll(() => server.close());
39
39
  describe("registerBuiltinAdapters", () => {
40
40
  it("registers 'http' type in userFactory", async () => {
41
41
  const userFactory = createAdapterFactory("UserRepository");
42
- const codeFactory = createAdapterFactory("CodeRepository");
43
- registerBuiltinAdapters({ userFactory, codeFactory });
42
+ registerBuiltinAdapters({ userFactory });
44
43
  try {
45
44
  await userFactory.create({ type: "unknown" });
46
45
  throw new Error("should have thrown");
@@ -53,26 +52,9 @@ describe("registerBuiltinAdapters", () => {
53
52
  expect(e.registered).toContain("http");
54
53
  }
55
54
  });
56
- it("registers 'redis' type in codeFactory", async () => {
57
- const userFactory = createAdapterFactory("UserRepository");
58
- const codeFactory = createAdapterFactory("CodeRepository");
59
- registerBuiltinAdapters({ userFactory, codeFactory });
60
- try {
61
- await codeFactory.create({ type: "unknown" });
62
- throw new Error("should have thrown");
63
- }
64
- catch (err) {
65
- expect(err).toBeInstanceOf(AdapterFactoryError);
66
- const e = err;
67
- expect(e.reason).toBe("unknown");
68
- expect(e.kind).toBe("CodeRepository");
69
- expect(e.registered).toContain("redis");
70
- }
71
- });
72
55
  it("http builder creates a working HttpUserRepository", async () => {
73
56
  const userFactory = createAdapterFactory("UserRepository");
74
- const codeFactory = createAdapterFactory("CodeRepository");
75
- registerBuiltinAdapters({ userFactory, codeFactory });
57
+ registerBuiltinAdapters({ userFactory });
76
58
  const repo = await userFactory.create({
77
59
  type: "http",
78
60
  authenticateUrl: `${BASE_URL}/auth`,
@@ -89,8 +71,7 @@ describe("registerBuiltinAdapters", () => {
89
71
  });
90
72
  it("http builder coerces string timeout to number (env-override path)", async () => {
91
73
  const userFactory = createAdapterFactory("UserRepository");
92
- const codeFactory = createAdapterFactory("CodeRepository");
93
- registerBuiltinAdapters({ userFactory, codeFactory });
74
+ registerBuiltinAdapters({ userFactory });
94
75
  const repo = await userFactory.create({
95
76
  type: "http",
96
77
  authenticateUrl: `${BASE_URL}/auth`,
@@ -105,14 +86,7 @@ describe("registerBuiltinAdapters", () => {
105
86
  });
106
87
  it("http builder throws when authenticateUrl is missing", async () => {
107
88
  const userFactory = createAdapterFactory("UserRepository");
108
- const codeFactory = createAdapterFactory("CodeRepository");
109
- registerBuiltinAdapters({ userFactory, codeFactory });
89
+ registerBuiltinAdapters({ userFactory });
110
90
  await expect(userFactory.create({ type: "http", timeout: 5000 })).rejects.toThrow(/authenticateUrl/);
111
91
  });
112
- it("redis builder throws when endpointUri is missing", async () => {
113
- const userFactory = createAdapterFactory("UserRepository");
114
- const codeFactory = createAdapterFactory("CodeRepository");
115
- registerBuiltinAdapters({ userFactory, codeFactory });
116
- await expect(codeFactory.create({ type: "redis" })).rejects.toThrow(/endpointUri/);
117
- });
118
92
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@o3co/auth-provider-foundation",
3
- "description": "Production adapters for auth.provider",
4
- "version": "0.4.0",
3
+ "description": "Production HTTP user-authentication adapter for auth.provider",
4
+ "version": "0.5.0",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "main": "./dist/index.mjs",
@@ -29,25 +29,20 @@
29
29
  }
30
30
  },
31
31
  "peerDependencies": {
32
- "@o3co/auth-provider-core": "^0.0.0",
33
- "redis": "^5.10.0"
34
- },
35
- "peerDependenciesMeta": {
36
- "redis": {
37
- "optional": true
38
- }
32
+ "@o3co/auth-provider-core": "^0.0.0"
39
33
  },
40
34
  "devDependencies": {
41
35
  "@types/node": "^25.1.0",
42
36
  "msw": "^2.12.14",
43
- "redis": "^5.10.0",
44
37
  "pino": "^10.3.1",
45
38
  "vitest": "^4.1.2",
46
- "@o3co/auth-provider-core": "0.4.0"
39
+ "@vitest/coverage-v8": "^4.1.2",
40
+ "@o3co/auth-provider-core": "0.5.0"
47
41
  },
48
42
  "scripts": {
49
43
  "prebuild": "rimraf dist",
50
44
  "build": "tsc",
51
- "test": "vitest run"
45
+ "test": "vitest run",
46
+ "test:coverage": "vitest run --coverage"
52
47
  }
53
48
  }
@@ -1,28 +0,0 @@
1
- import type { Code, CodeRepository, PathResolver } from "@o3co/auth-provider-core";
2
- interface RedisClient {
3
- connect(): Promise<void>;
4
- get(key: string): Promise<string | null>;
5
- set(key: string, value: string, options?: {
6
- EX?: number;
7
- }): Promise<unknown>;
8
- getDel(key: string): Promise<string | null>;
9
- del(key: string): Promise<number>;
10
- }
11
- export declare class RedisCodeRepository implements CodeRepository {
12
- private redis;
13
- private defaultExpiresIn;
14
- constructor(redis: RedisClient, defaultExpiresIn?: number);
15
- static create(config: Record<string, unknown>, pathResolver?: PathResolver): Promise<RedisCodeRepository>;
16
- initialize(): Promise<void>;
17
- createCode({ code_challenge, code_challenge_method, expiresIn, }: {
18
- code_challenge?: string;
19
- code_challenge_method?: string;
20
- expiresIn?: number;
21
- }): Promise<Code>;
22
- getByCode(code: string): Promise<Code | null>;
23
- consumeByCode(code: string): Promise<Code | null>;
24
- removeByCode(code: string): Promise<void>;
25
- private parseCodeValue;
26
- }
27
- export {};
28
- //# sourceMappingURL=RedisCodeRepository.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RedisCodeRepository.d.mts","sourceRoot":"","sources":["../../src/repositories/RedisCodeRepository.mts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAMnF,UAAU,WAAW;IACpB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,qBAAa,mBAAoB,YAAW,cAAc;IACzD,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,gBAAgB,CAAS;gBAErB,KAAK,EAAE,WAAW,EAAE,gBAAgB,SAAM;WAKzC,MAAM,CAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,YAAY,CAAC,EAAE,YAAY,GACzB,OAAO,CAAC,mBAAmB,CAAC;IA2BzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,UAAU,CAAC,EAChB,cAAc,EACd,qBAAqB,EACrB,SAAiC,GACjC,EAAE;QACF,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYX,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAK7C,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAKjD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,OAAO,CAAC,cAAc;CAUtB"}
@@ -1,79 +0,0 @@
1
- /*
2
- * Copyright 2026 1o1 Co. Ltd.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import crypto from "node:crypto";
17
- const KEY_PREFIX = "oauth:code:";
18
- export class RedisCodeRepository {
19
- redis;
20
- defaultExpiresIn;
21
- constructor(redis, defaultExpiresIn = 600) {
22
- this.redis = redis;
23
- this.defaultExpiresIn = defaultExpiresIn;
24
- }
25
- static async create(config, pathResolver) {
26
- if (typeof config.endpointUri !== "string") {
27
- throw new Error('RedisCodeRepository requires "endpointUri" in config');
28
- }
29
- const { createClient } = pathResolver
30
- ? (await import(pathResolver("redis")))
31
- : await import("redis");
32
- const redis = createClient({
33
- url: config.endpointUri,
34
- password: typeof config.password === "string" ? config.password : undefined,
35
- socket: {
36
- reconnectStrategy: (retries) => {
37
- const jitter = Math.floor(Math.random() * 200);
38
- const delay = Math.min(2 ** retries * 50, 2000);
39
- return delay + jitter;
40
- },
41
- },
42
- });
43
- const defaultExpiresIn = typeof config.defaultExpiresIn === "number" ? config.defaultExpiresIn : undefined;
44
- const repo = new RedisCodeRepository(redis, defaultExpiresIn);
45
- await repo.initialize();
46
- return repo;
47
- }
48
- async initialize() {
49
- await this.redis.connect();
50
- }
51
- async createCode({ code_challenge, code_challenge_method, expiresIn = this.defaultExpiresIn, }) {
52
- const code = crypto.randomBytes(32).toString("base64url");
53
- await this.redis.set(KEY_PREFIX + code, JSON.stringify({ code_challenge, code_challenge_method }), { EX: expiresIn });
54
- return { code, code_challenge, code_challenge_method, expiresIn };
55
- }
56
- async getByCode(code) {
57
- const value = await this.redis.get(KEY_PREFIX + code);
58
- return this.parseCodeValue(code, value);
59
- }
60
- async consumeByCode(code) {
61
- const value = await this.redis.getDel(KEY_PREFIX + code);
62
- return this.parseCodeValue(code, value);
63
- }
64
- async removeByCode(code) {
65
- await this.redis.del(KEY_PREFIX + code);
66
- }
67
- parseCodeValue(code, value) {
68
- if (!value)
69
- return null;
70
- try {
71
- return { ...JSON.parse(value), code };
72
- }
73
- catch (err) {
74
- const codeHash = crypto.createHash("sha256").update(code).digest("hex").slice(0, 16);
75
- console.error(`RedisCodeRepository: corrupted data for code (hash=${codeHash})`, err);
76
- return null;
77
- }
78
- }
79
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=RedisCodeRepository.test.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RedisCodeRepository.test.d.mts","sourceRoot":"","sources":["../../../src/repositories/__tests__/RedisCodeRepository.test.mts"],"names":[],"mappings":""}
@@ -1,141 +0,0 @@
1
- /*
2
- * Copyright 2026 1o1 Co. Ltd.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import { beforeEach, describe, expect, it, vi } from "vitest";
17
- const KEY_PREFIX = "oauth:code:";
18
- // In-memory store simulating Redis
19
- const store = new Map();
20
- import { RedisCodeRepository } from "../RedisCodeRepository.mjs";
21
- const createMockRedis = () => ({
22
- connect: vi.fn().mockResolvedValue(undefined),
23
- set: vi.fn().mockImplementation((key, value) => {
24
- store.set(key, value);
25
- return Promise.resolve("OK");
26
- }),
27
- get: vi.fn().mockImplementation((key) => {
28
- return Promise.resolve(store.get(key) ?? null);
29
- }),
30
- getDel: vi.fn().mockImplementation((key) => {
31
- const value = store.get(key) ?? null;
32
- store.delete(key);
33
- return Promise.resolve(value);
34
- }),
35
- del: vi.fn().mockImplementation((key) => {
36
- store.delete(key);
37
- return Promise.resolve(1);
38
- }),
39
- });
40
- describe("RedisCodeRepository", () => {
41
- let repo;
42
- beforeEach(async () => {
43
- store.clear();
44
- const redis = createMockRedis();
45
- repo = new RedisCodeRepository(redis);
46
- await repo.initialize();
47
- });
48
- describe("createCode", () => {
49
- it("generates a code string and stores it", async () => {
50
- const result = await repo.createCode({});
51
- expect(typeof result.code).toBe("string");
52
- expect(result.code.length).toBeGreaterThan(0);
53
- // Verify it was stored with namespace prefix
54
- expect(store.has(`${KEY_PREFIX}${result.code}`)).toBe(true);
55
- });
56
- it("returns code with code_challenge and code_challenge_method", async () => {
57
- const result = await repo.createCode({
58
- code_challenge: "abc123",
59
- code_challenge_method: "S256",
60
- });
61
- expect(result.code_challenge).toBe("abc123");
62
- expect(result.code_challenge_method).toBe("S256");
63
- });
64
- it("stores code_challenge and code_challenge_method in Redis", async () => {
65
- const result = await repo.createCode({
66
- code_challenge: "challenge-value",
67
- code_challenge_method: "S256",
68
- });
69
- const stored = store.get(`${KEY_PREFIX}${result.code}`);
70
- expect(stored).toBeDefined();
71
- const parsed = JSON.parse(stored);
72
- expect(parsed.code_challenge).toBe("challenge-value");
73
- expect(parsed.code_challenge_method).toBe("S256");
74
- });
75
- it("uses default expiresIn when not provided", async () => {
76
- const result = await repo.createCode({});
77
- expect(result.expiresIn).toBe(600);
78
- });
79
- it("uses provided expiresIn", async () => {
80
- const result = await repo.createCode({ expiresIn: 300 });
81
- expect(result.expiresIn).toBe(300);
82
- });
83
- });
84
- describe("getByCode", () => {
85
- it("returns stored code data", async () => {
86
- const created = await repo.createCode({
87
- code_challenge: "test-challenge",
88
- code_challenge_method: "S256",
89
- });
90
- const found = await repo.getByCode(created.code);
91
- expect(found).not.toBeNull();
92
- expect(found?.code).toBe(created.code);
93
- expect(found?.code_challenge).toBe("test-challenge");
94
- expect(found?.code_challenge_method).toBe("S256");
95
- });
96
- it("returns null for unknown code", async () => {
97
- const result = await repo.getByCode("nonexistent-code");
98
- expect(result).toBeNull();
99
- });
100
- it("returns null for corrupted data", async () => {
101
- store.set(`${KEY_PREFIX}corrupted`, "not-valid-json{{{");
102
- const result = await repo.getByCode("corrupted");
103
- expect(result).toBeNull();
104
- });
105
- });
106
- describe("consumeByCode", () => {
107
- it("returns code data and removes it atomically", async () => {
108
- const created = await repo.createCode({
109
- code_challenge: "consume-test",
110
- code_challenge_method: "S256",
111
- });
112
- const consumed = await repo.consumeByCode(created.code);
113
- expect(consumed).not.toBeNull();
114
- expect(consumed?.code).toBe(created.code);
115
- expect(consumed?.code_challenge).toBe("consume-test");
116
- // Should be gone from store
117
- expect(store.has(`${KEY_PREFIX}${created.code}`)).toBe(false);
118
- });
119
- it("returns null for unknown code", async () => {
120
- expect(await repo.consumeByCode("nonexistent")).toBeNull();
121
- });
122
- it("second consume returns null (replay prevention)", async () => {
123
- const created = await repo.createCode({});
124
- const first = await repo.consumeByCode(created.code);
125
- expect(first).not.toBeNull();
126
- const second = await repo.consumeByCode(created.code);
127
- expect(second).toBeNull();
128
- });
129
- });
130
- describe("removeByCode", () => {
131
- it("removes a stored code", async () => {
132
- const created = await repo.createCode({ code_challenge: "c" });
133
- expect(store.has(`${KEY_PREFIX}${created.code}`)).toBe(true);
134
- await repo.removeByCode(created.code);
135
- expect(store.has(`${KEY_PREFIX}${created.code}`)).toBe(false);
136
- });
137
- it("does not throw for unknown code", async () => {
138
- await expect(repo.removeByCode("nonexistent")).resolves.toBeUndefined();
139
- });
140
- });
141
- });