@soapjs/soap-auth 0.4.0 → 1.0.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.
Files changed (78) hide show
  1. package/README.md +261 -128
  2. package/build/index.d.ts +1 -0
  3. package/build/index.js +1 -0
  4. package/build/recipes/auth-config.recipes.d.ts +40 -0
  5. package/build/recipes/auth-config.recipes.js +135 -0
  6. package/build/recipes/http-context.helpers.d.ts +13 -0
  7. package/build/recipes/http-context.helpers.js +64 -0
  8. package/build/recipes/index.d.ts +3 -0
  9. package/build/recipes/index.js +19 -0
  10. package/build/recipes/oauth2-presets.d.ts +20 -0
  11. package/build/recipes/oauth2-presets.js +74 -0
  12. package/build/soap-auth.js +62 -0
  13. package/build/strategies/jwt/jwt.strategy.js +6 -3
  14. package/build/strategies/jwt/jwt.tools.js +8 -6
  15. package/build/strategies/local/local.strategy.d.ts +2 -2
  16. package/build/strategies/local/local.strategy.js +7 -7
  17. package/build/strategies/oauth2/hybrid.oauth2.strategy.js +1 -1
  18. package/build/strategies/oauth2/oauth2.strategy.js +2 -2
  19. package/build/strategies/oauth2/oauth2.types.d.ts +5 -0
  20. package/build/strategies/oauth2/providers/configurable-hybrid-oauth2.strategy.d.ts +19 -0
  21. package/build/strategies/oauth2/providers/configurable-hybrid-oauth2.strategy.js +85 -0
  22. package/build/strategies/oauth2/providers/configurable-oauth2.strategy.d.ts +11 -0
  23. package/build/strategies/oauth2/providers/configurable-oauth2.strategy.js +46 -0
  24. package/build/strategies/oauth2/providers/index.d.ts +2 -0
  25. package/build/strategies/oauth2/providers/index.js +2 -0
  26. package/build/strategies/oauth2/providers/provider.types.d.ts +3 -0
  27. package/build/types.d.ts +5 -2
  28. package/package.json +91 -13
  29. package/.claude/settings.local.json +0 -20
  30. package/build/__tests__/soap-auth.test.d.ts +0 -1
  31. package/build/__tests__/soap-auth.test.js +0 -136
  32. package/build/services/__tests__/account-lock.service.test.d.ts +0 -1
  33. package/build/services/__tests__/account-lock.service.test.js +0 -55
  34. package/build/services/__tests__/auth-throttle.service.test.d.ts +0 -1
  35. package/build/services/__tests__/auth-throttle.service.test.js +0 -48
  36. package/build/services/__tests__/jwks.service.test.d.ts +0 -1
  37. package/build/services/__tests__/jwks.service.test.js +0 -39
  38. package/build/services/__tests__/mfa.service.test.d.ts +0 -1
  39. package/build/services/__tests__/mfa.service.test.js +0 -66
  40. package/build/services/__tests__/password.service.test.d.ts +0 -1
  41. package/build/services/__tests__/password.service.test.js +0 -73
  42. package/build/services/__tests__/pkce.service.test.d.ts +0 -1
  43. package/build/services/__tests__/pkce.service.test.js +0 -77
  44. package/build/services/__tests__/rate-limit.service.test.d.ts +0 -1
  45. package/build/services/__tests__/rate-limit.service.test.js +0 -37
  46. package/build/services/__tests__/role.service.test.d.ts +0 -1
  47. package/build/services/__tests__/role.service.test.js +0 -31
  48. package/build/services/__tests__/totp.service.test.d.ts +0 -1
  49. package/build/services/__tests__/totp.service.test.js +0 -120
  50. package/build/session/__tests__/file.session-store.test.d.ts +0 -1
  51. package/build/session/__tests__/file.session-store.test.js +0 -117
  52. package/build/session/__tests__/memory.session-store.test.d.ts +0 -1
  53. package/build/session/__tests__/memory.session-store.test.js +0 -77
  54. package/build/session/__tests__/session-handler.test.d.ts +0 -1
  55. package/build/session/__tests__/session-handler.test.js +0 -345
  56. package/build/strategies/__tests__/base-auth.strategy.test.d.ts +0 -15
  57. package/build/strategies/__tests__/base-auth.strategy.test.js +0 -138
  58. package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +0 -15
  59. package/build/strategies/__tests__/credential-auth.strategy.test.js +0 -266
  60. package/build/strategies/__tests__/token-auth.strategy.test.d.ts +0 -29
  61. package/build/strategies/__tests__/token-auth.strategy.test.js +0 -299
  62. package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +0 -1
  63. package/build/strategies/api-key/__tests__/api-key.strategy.test.js +0 -103
  64. package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +0 -1
  65. package/build/strategies/basic/__tests__/basic.strategy.test.js +0 -104
  66. package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +0 -1
  67. package/build/strategies/jwt/__tests__/jwt.strategy.test.js +0 -156
  68. package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +0 -1
  69. package/build/strategies/jwt/__tests__/jwt.tools.test.js +0 -98
  70. package/build/strategies/local/__tests__/local.strategy.test.d.ts +0 -1
  71. package/build/strategies/local/__tests__/local.strategy.test.js +0 -121
  72. package/build/strategies/oauth2/__tests__/oauth2.strategy.test.d.ts +0 -1
  73. package/build/strategies/oauth2/__tests__/oauth2.strategy.test.js +0 -239
  74. package/build/strategies/oauth2/providers/__tests__/social-providers.test.d.ts +0 -1
  75. package/build/strategies/oauth2/providers/__tests__/social-providers.test.js +0 -201
  76. package/build/utils/__tests__/validation.test.d.ts +0 -1
  77. package/build/utils/__tests__/validation.test.js +0 -181
  78. package/jest.config.unit.json +0 -10
@@ -1,239 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const oauth2_strategy_1 = require("../oauth2.strategy");
4
- const oauth2_errors_1 = require("../oauth2.errors");
5
- const errors_1 = require("../../../errors");
6
- const mockFetch = jest.fn();
7
- global.fetch = mockFetch;
8
- function mockFetchOk(body) {
9
- mockFetch.mockResolvedValueOnce({
10
- ok: true,
11
- status: 200,
12
- statusText: "OK",
13
- json: async () => body,
14
- });
15
- }
16
- function mockFetchFail(status = 400) {
17
- mockFetch.mockResolvedValueOnce({
18
- ok: false,
19
- status,
20
- statusText: "Bad Request",
21
- json: async () => ({}),
22
- });
23
- }
24
- class TestOAuth2Strategy extends oauth2_strategy_1.OAuth2Strategy {
25
- name = "test-oauth2";
26
- async extractAccessToken(ctx) {
27
- return ctx.tokens.access;
28
- }
29
- async extractRefreshToken(ctx) {
30
- return ctx.tokens.refresh;
31
- }
32
- async storeAccessToken(token, ctx) {
33
- ctx.tokens.access = token;
34
- }
35
- async storeRefreshToken(token, ctx) {
36
- ctx.tokens.refresh = token;
37
- }
38
- embedAccessToken(token, ctx) {
39
- ctx.tokens.access = token;
40
- }
41
- embedRefreshToken(token, ctx) {
42
- ctx.tokens.refresh = token;
43
- }
44
- extractAuthorizationCode(ctx) {
45
- return ctx.query.code ?? null;
46
- }
47
- redirectUser(ctx, url) {
48
- ctx.redirectTo = url;
49
- }
50
- }
51
- const baseConfig = {
52
- clientId: "client-id",
53
- clientSecret: "client-secret",
54
- redirectUri: "https://example.com/callback",
55
- grantType: "authorization_code",
56
- scope: ["openid", "email"],
57
- endpoints: {
58
- authorizationUrl: "https://provider.example/auth",
59
- tokenUrl: "https://provider.example/token",
60
- userInfoUrl: "https://provider.example/userinfo",
61
- },
62
- routes: {
63
- login: { path: "/login", method: "GET" },
64
- callback: { path: "/callback", method: "GET" },
65
- },
66
- user: {
67
- fetchUser: jest.fn(),
68
- validateUser: jest.fn(async (p) => ({ id: p.sub ?? p.id, email: p.email })),
69
- },
70
- };
71
- function makeCtx(overrides = {}) {
72
- return { tokens: {}, query: {}, ...overrides };
73
- }
74
- describe("OAuth2Strategy — state / CSRF", () => {
75
- afterEach(() => jest.clearAllMocks());
76
- it("validateState passes when stored === returned state", async () => {
77
- let stored = "csrf-abc";
78
- const config = {
79
- ...baseConfig,
80
- state: {
81
- persistence: {
82
- store: jest.fn(async (s) => { stored = s; }),
83
- read: jest.fn(async () => stored),
84
- remove: jest.fn(async () => { stored = null; }),
85
- },
86
- },
87
- };
88
- const strategy = new TestOAuth2Strategy(config);
89
- const ctx = makeCtx({ query: { code: "auth-code", state: "csrf-abc" } });
90
- await expect(strategy.validateState(ctx)).resolves.toBeUndefined();
91
- expect(config.state.persistence.remove).toHaveBeenCalled();
92
- });
93
- it("validateState throws InvalidStateError on mismatch", async () => {
94
- const config = {
95
- ...baseConfig,
96
- state: {
97
- persistence: {
98
- read: jest.fn(async () => "stored-state"),
99
- remove: jest.fn(),
100
- },
101
- },
102
- };
103
- const strategy = new TestOAuth2Strategy(config);
104
- const ctx = makeCtx({ query: { state: "wrong-state" } });
105
- await expect(strategy.validateState(ctx)).rejects.toThrow(oauth2_errors_1.InvalidStateError);
106
- });
107
- it("validateState is a no-op when config.state is absent", async () => {
108
- const strategy = new TestOAuth2Strategy({ ...baseConfig, state: undefined });
109
- await expect(strategy.validateState(makeCtx())).resolves.toBeUndefined();
110
- });
111
- it("startAuthorizationFlow generates state, stores it, and redirects", async () => {
112
- const stored = [];
113
- const config = {
114
- ...baseConfig,
115
- state: {
116
- generateState: jest.fn(async () => "gen-state"),
117
- persistence: {
118
- store: jest.fn(async (s) => stored.push(s)),
119
- },
120
- },
121
- };
122
- const strategy = new TestOAuth2Strategy(config);
123
- const ctx = makeCtx();
124
- await strategy.startAuthorizationFlow(ctx);
125
- expect(stored).toContain("gen-state");
126
- expect(ctx.redirectTo).toContain("https://provider.example/auth");
127
- expect(ctx.redirectTo).toContain("state=gen-state");
128
- });
129
- });
130
- describe("OAuth2Strategy — buildAuthorizationUrl", () => {
131
- afterEach(() => jest.clearAllMocks());
132
- it("includes client_id, redirect_uri, response_type, scope", async () => {
133
- const strategy = new TestOAuth2Strategy(baseConfig);
134
- const url = await strategy.buildAuthorizationUrl(makeCtx());
135
- expect(url).toContain("client_id=client-id");
136
- expect(url).toContain("redirect_uri=");
137
- expect(url).toContain("response_type=code");
138
- expect(url).toContain("scope=openid+email");
139
- });
140
- it("embeds state when provided", async () => {
141
- const strategy = new TestOAuth2Strategy(baseConfig);
142
- const url = await strategy.buildAuthorizationUrl(makeCtx(), "my-state");
143
- expect(url).toContain("state=my-state");
144
- });
145
- it("embeds nonce when provided", async () => {
146
- const strategy = new TestOAuth2Strategy(baseConfig);
147
- const url = await strategy.buildAuthorizationUrl(makeCtx(), undefined, "my-nonce");
148
- expect(url).toContain("nonce=my-nonce");
149
- });
150
- });
151
- describe("OAuth2Strategy — verifyAuthorizationCode", () => {
152
- afterEach(() => jest.clearAllMocks());
153
- it("does nothing when code is present", async () => {
154
- const strategy = new TestOAuth2Strategy(baseConfig);
155
- const ctx = makeCtx();
156
- await expect(strategy.verifyAuthorizationCode(ctx, "valid-code")).resolves.toBeUndefined();
157
- });
158
- it("redirects and throws MissingAuthorizationCodeError when code is absent", async () => {
159
- const strategy = new TestOAuth2Strategy(baseConfig);
160
- const ctx = makeCtx();
161
- await expect(strategy.verifyAuthorizationCode(ctx, null)).rejects.toThrow(errors_1.MissingAuthorizationCodeError);
162
- expect(ctx.redirectTo).toBeDefined();
163
- });
164
- });
165
- describe("OAuth2Strategy — exchangeCodeForToken", () => {
166
- afterEach(() => jest.clearAllMocks());
167
- it("sends correct POST body and returns tokens", async () => {
168
- mockFetchOk({ access_token: "at-123", refresh_token: "rt-456" });
169
- const strategy = new TestOAuth2Strategy(baseConfig);
170
- const result = await strategy.exchangeCodeForToken(makeCtx(), "auth-code");
171
- expect(result.accessToken).toBe("at-123");
172
- expect(result.refreshToken).toBe("rt-456");
173
- expect(mockFetch).toHaveBeenCalledWith("https://provider.example/token", expect.objectContaining({ method: "POST" }));
174
- });
175
- it("throws when token endpoint returns non-ok status", async () => {
176
- mockFetchFail(401);
177
- const strategy = new TestOAuth2Strategy(baseConfig);
178
- await expect(strategy.exchangeCodeForToken(makeCtx(), "bad-code")).rejects.toThrow();
179
- });
180
- });
181
- describe("OAuth2Strategy — isTokenExpired", () => {
182
- afterEach(() => jest.clearAllMocks());
183
- it("returns false for non-JWT tokens", () => {
184
- const strategy = new TestOAuth2Strategy(baseConfig);
185
- expect(strategy.isTokenExpired("opaque-token")).toBe(false);
186
- });
187
- it("returns true for an expired JWT", () => {
188
- const strategy = new TestOAuth2Strategy(baseConfig);
189
- const pastExp = Math.floor(Date.now() / 1000) - 3600;
190
- const payload = Buffer.from(JSON.stringify({ exp: pastExp })).toString("base64");
191
- const jwt = `header.${payload}.sig`;
192
- expect(strategy.isTokenExpired(jwt)).toBe(true);
193
- });
194
- it("returns false for a non-expired JWT", () => {
195
- const strategy = new TestOAuth2Strategy(baseConfig);
196
- const futureExp = Math.floor(Date.now() / 1000) + 3600;
197
- const payload = Buffer.from(JSON.stringify({ exp: futureExp })).toString("base64");
198
- const jwt = `header.${payload}.sig`;
199
- expect(strategy.isTokenExpired(jwt)).toBe(false);
200
- });
201
- it("returns false when JWT payload has no exp", () => {
202
- const strategy = new TestOAuth2Strategy(baseConfig);
203
- const payload = Buffer.from(JSON.stringify({ sub: "user" })).toString("base64");
204
- const jwt = `header.${payload}.sig`;
205
- expect(strategy.isTokenExpired(jwt)).toBe(false);
206
- });
207
- });
208
- describe("OAuth2Strategy — authenticate", () => {
209
- afterEach(() => jest.clearAllMocks());
210
- it("exchanges code and returns AuthResult when no token in context", async () => {
211
- let stored = "state-xyz";
212
- const config = {
213
- ...baseConfig,
214
- state: {
215
- persistence: {
216
- read: jest.fn(async () => stored),
217
- remove: jest.fn(async () => { stored = null; }),
218
- },
219
- },
220
- };
221
- const strategy = new TestOAuth2Strategy(config);
222
- mockFetchOk({ access_token: "at-new", refresh_token: "rt-new" });
223
- mockFetchOk({ sub: "u1", email: "user@example.com" });
224
- const ctx = makeCtx({ query: { code: "auth-code", state: "state-xyz" } });
225
- const result = await strategy.authenticate(ctx);
226
- expect(result?.user).toMatchObject({ id: "u1", email: "user@example.com" });
227
- expect(result?.tokens?.accessToken).toBe("at-new");
228
- });
229
- it("fetches user when valid access token already in context", async () => {
230
- const strategy = new TestOAuth2Strategy(baseConfig);
231
- const futureExp = Math.floor(Date.now() / 1000) + 3600;
232
- const payload = Buffer.from(JSON.stringify({ exp: futureExp })).toString("base64");
233
- const validJwt = `h.${payload}.s`;
234
- mockFetchOk({ sub: "u2", email: "other@example.com" });
235
- const ctx = makeCtx({ tokens: { access: validJwt } });
236
- const result = await strategy.authenticate(ctx);
237
- expect(result?.user).toMatchObject({ id: "u2" });
238
- });
239
- });
@@ -1,201 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const google_strategy_1 = require("../google.strategy");
4
- const github_strategy_1 = require("../github.strategy");
5
- const facebook_strategy_1 = require("../facebook.strategy");
6
- const mockFetch = jest.fn();
7
- global.fetch = mockFetch;
8
- const baseConfig = {
9
- clientId: "client-id",
10
- clientSecret: "client-secret",
11
- redirectUri: "https://example.com/callback",
12
- };
13
- function makeMockCtx() {
14
- return {
15
- req: {
16
- method: "GET",
17
- path: "/callback",
18
- headers: { authorization: "Bearer test-token" },
19
- query: { code: "auth-code", state: "csrf-state" },
20
- cookies: {},
21
- },
22
- res: {
23
- status: jest.fn().mockReturnThis(),
24
- json: jest.fn().mockReturnThis(),
25
- setHeader: jest.fn().mockReturnThis(),
26
- cookie: jest.fn().mockReturnThis(),
27
- redirect: jest.fn(),
28
- },
29
- next: jest.fn(),
30
- };
31
- }
32
- function mockFetchOk(body) {
33
- mockFetch.mockResolvedValueOnce({
34
- ok: true,
35
- json: async () => body,
36
- });
37
- }
38
- describe("GoogleStrategy", () => {
39
- afterEach(() => jest.clearAllMocks());
40
- it("has name 'google'", () => {
41
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
42
- expect(strategy.name).toBe("google");
43
- });
44
- it("maps Google profile to AuthUser", async () => {
45
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
46
- const googleProfile = {
47
- sub: "google-sub-123",
48
- email: "user@gmail.com",
49
- name: "Test User",
50
- picture: "https://example.com/pic.jpg",
51
- email_verified: true,
52
- };
53
- mockFetchOk(googleProfile);
54
- const user = await strategy.fetchUser("test-access-token");
55
- expect(user).toMatchObject({
56
- id: "google-sub-123",
57
- email: "user@gmail.com",
58
- username: "Test User",
59
- picture: "https://example.com/pic.jpg",
60
- emailVerified: true,
61
- });
62
- });
63
- it("calls validateUser when provided", async () => {
64
- const validateUser = jest.fn().mockResolvedValue({ id: "custom-id", email: "x@x.com" });
65
- const strategy = new google_strategy_1.GoogleStrategy({ ...baseConfig, user: { fetchUser: jest.fn(), validateUser } });
66
- mockFetchOk({ sub: "sub123", email: "raw@gmail.com" });
67
- const user = await strategy.fetchUser("token");
68
- expect(validateUser).toHaveBeenCalledWith(expect.objectContaining({ sub: "sub123" }));
69
- expect(user).toMatchObject({ id: "custom-id" });
70
- });
71
- it("returns null when fetch fails", async () => {
72
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
73
- mockFetch.mockResolvedValueOnce({ ok: false, status: 401 });
74
- const user = await strategy.fetchUser("bad-token");
75
- expect(user).toBeNull();
76
- });
77
- it("uses openid+email+profile scope by default", () => {
78
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
79
- expect(strategy.config.scope).toContain("openid");
80
- });
81
- it("respects custom scope", () => {
82
- const strategy = new google_strategy_1.GoogleStrategy({ ...baseConfig, scope: ["openid"] });
83
- expect(strategy.config.scope).toEqual(["openid"]);
84
- });
85
- });
86
- describe("GitHubStrategy", () => {
87
- afterEach(() => jest.clearAllMocks());
88
- it("has name 'github'", () => {
89
- expect(new github_strategy_1.GitHubStrategy(baseConfig).name).toBe("github");
90
- });
91
- it("maps GitHub profile to AuthUser", async () => {
92
- const strategy = new github_strategy_1.GitHubStrategy(baseConfig);
93
- mockFetchOk({
94
- id: 12345,
95
- login: "octocat",
96
- email: "octocat@github.com",
97
- name: "The Octocat",
98
- avatar_url: "https://github.com/avatar.png",
99
- });
100
- const user = await strategy.fetchUser("token");
101
- expect(user).toMatchObject({
102
- id: 12345,
103
- email: "octocat@github.com",
104
- username: "octocat",
105
- name: "The Octocat",
106
- avatarUrl: "https://github.com/avatar.png",
107
- });
108
- });
109
- it("returns null when fetch fails", async () => {
110
- const strategy = new github_strategy_1.GitHubStrategy(baseConfig);
111
- mockFetch.mockResolvedValueOnce({ ok: false, status: 403 });
112
- expect(await strategy.fetchUser("bad")).toBeNull();
113
- });
114
- });
115
- describe("FacebookStrategy", () => {
116
- afterEach(() => jest.clearAllMocks());
117
- it("has name 'facebook'", () => {
118
- expect(new facebook_strategy_1.FacebookStrategy(baseConfig).name).toBe("facebook");
119
- });
120
- it("maps Facebook profile to AuthUser", async () => {
121
- const strategy = new facebook_strategy_1.FacebookStrategy(baseConfig);
122
- mockFetchOk({
123
- id: "fb-id-456",
124
- name: "FB User",
125
- email: "fb@facebook.com",
126
- picture: { data: { url: "https://fb.com/pic.jpg" } },
127
- });
128
- const user = await strategy.fetchUser("token");
129
- expect(user).toMatchObject({
130
- id: "fb-id-456",
131
- email: "fb@facebook.com",
132
- username: "FB User",
133
- picture: "https://fb.com/pic.jpg",
134
- });
135
- });
136
- it("returns null on Facebook API error", async () => {
137
- const strategy = new facebook_strategy_1.FacebookStrategy(baseConfig);
138
- mockFetchOk({ error: { message: "Invalid token", code: 190 } });
139
- expect(await strategy.fetchUser("token")).toBeNull();
140
- });
141
- it("returns null when fetch fails", async () => {
142
- const strategy = new facebook_strategy_1.FacebookStrategy(baseConfig);
143
- mockFetch.mockResolvedValueOnce({ ok: false, status: 401 });
144
- expect(await strategy.fetchUser("bad")).toBeNull();
145
- });
146
- });
147
- describe("HttpOAuth2Strategy — HTTP plumbing", () => {
148
- afterEach(() => jest.clearAllMocks());
149
- it("extracts access token from Authorization header", async () => {
150
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
151
- const ctx = makeMockCtx();
152
- const token = await strategy.extractAccessToken(ctx);
153
- expect(token).toBe("test-token");
154
- });
155
- it("extracts access token from cookie when no header", async () => {
156
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
157
- const ctx = makeMockCtx();
158
- ctx.req.headers = {};
159
- ctx.req.cookies = { access_token: "cookie-token" };
160
- const token = await strategy.extractAccessToken(ctx);
161
- expect(token).toBe("cookie-token");
162
- });
163
- it("extracts refresh token from cookie", async () => {
164
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
165
- const ctx = makeMockCtx();
166
- ctx.req.cookies = { refresh_token: "rt-abc" };
167
- const rt = await strategy.extractRefreshToken(ctx);
168
- expect(rt).toBe("rt-abc");
169
- });
170
- it("extracts authorization code from query", () => {
171
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
172
- const ctx = makeMockCtx();
173
- expect(strategy.extractAuthorizationCode(ctx)).toBe("auth-code");
174
- });
175
- it("embeds access token as Authorization header", () => {
176
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
177
- const ctx = makeMockCtx();
178
- strategy.embedAccessToken("new-token", ctx);
179
- expect(ctx.res.setHeader).toHaveBeenCalledWith("Authorization", "Bearer new-token");
180
- });
181
- it("embeds refresh token as httpOnly cookie", () => {
182
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
183
- const ctx = makeMockCtx();
184
- strategy.embedRefreshToken("rt-new", ctx);
185
- expect(ctx.res.cookie).toHaveBeenCalledWith("refresh_token", "rt-new", expect.objectContaining({ httpOnly: true, secure: true }));
186
- });
187
- it("redirects via res.redirect when available", () => {
188
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
189
- const ctx = makeMockCtx();
190
- strategy.redirectUser(ctx, "https://accounts.google.com/auth");
191
- expect(ctx.res.redirect).toHaveBeenCalledWith("https://accounts.google.com/auth");
192
- });
193
- it("falls back to Location header when res.redirect is missing", () => {
194
- const strategy = new google_strategy_1.GoogleStrategy(baseConfig);
195
- const ctx = makeMockCtx();
196
- delete ctx.res.redirect;
197
- strategy.redirectUser(ctx, "https://accounts.google.com/auth");
198
- expect(ctx.res.setHeader).toHaveBeenCalledWith("Location", "https://accounts.google.com/auth");
199
- expect(ctx.res.status).toHaveBeenCalledWith(302);
200
- });
201
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,181 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const validation_1 = require("../validation");
4
- describe("ValidationUtils", () => {
5
- describe("required", () => {
6
- it("should not throw for valid values", () => {
7
- expect(() => validation_1.ValidationUtils.required("test", "field")).not.toThrow();
8
- expect(() => validation_1.ValidationUtils.required(0, "field")).not.toThrow();
9
- expect(() => validation_1.ValidationUtils.required(false, "field")).not.toThrow();
10
- expect(() => validation_1.ValidationUtils.required([], "field")).not.toThrow();
11
- expect(() => validation_1.ValidationUtils.required({}, "field")).not.toThrow();
12
- });
13
- it("should throw ValidationError for null or undefined", () => {
14
- expect(() => validation_1.ValidationUtils.required(null, "field")).toThrow(validation_1.ValidationError);
15
- expect(() => validation_1.ValidationUtils.required(undefined, "field")).toThrow(validation_1.ValidationError);
16
- });
17
- it("should include field name in error message", () => {
18
- expect(() => validation_1.ValidationUtils.required(null, "testField")).toThrow("testField is required");
19
- });
20
- });
21
- describe("nonEmptyString", () => {
22
- it("should return trimmed string for valid input", () => {
23
- expect(validation_1.ValidationUtils.nonEmptyString(" test ", "field")).toBe("test");
24
- });
25
- it("should throw for non-string input", () => {
26
- expect(() => validation_1.ValidationUtils.nonEmptyString(123, "field")).toThrow(validation_1.ValidationError);
27
- });
28
- it("should throw for empty string", () => {
29
- expect(() => validation_1.ValidationUtils.nonEmptyString("", "field")).toThrow(validation_1.ValidationError);
30
- expect(() => validation_1.ValidationUtils.nonEmptyString(" ", "field")).toThrow(validation_1.ValidationError);
31
- });
32
- it("should throw for null or undefined", () => {
33
- expect(() => validation_1.ValidationUtils.nonEmptyString(null, "field")).toThrow(validation_1.ValidationError);
34
- expect(() => validation_1.ValidationUtils.nonEmptyString(undefined, "field")).toThrow(validation_1.ValidationError);
35
- });
36
- });
37
- describe("email", () => {
38
- it("should return email for valid input", () => {
39
- expect(validation_1.ValidationUtils.email("test@example.com", "field")).toBe("test@example.com");
40
- });
41
- it("should throw for invalid email format", () => {
42
- expect(() => validation_1.ValidationUtils.email("invalid-email", "field")).toThrow(validation_1.ValidationError);
43
- expect(() => validation_1.ValidationUtils.email("@example.com", "field")).toThrow(validation_1.ValidationError);
44
- expect(() => validation_1.ValidationUtils.email("test@", "field")).toThrow(validation_1.ValidationError);
45
- });
46
- });
47
- describe("password", () => {
48
- it("should return password for valid input", () => {
49
- expect(validation_1.ValidationUtils.password("password123", "field")).toBe("password123");
50
- });
51
- it("should throw for short password", () => {
52
- expect(() => validation_1.ValidationUtils.password("short", "field", 8)).toThrow(validation_1.ValidationError);
53
- });
54
- it("should use default minimum length", () => {
55
- expect(() => validation_1.ValidationUtils.password("short", "field")).toThrow(validation_1.ValidationError);
56
- });
57
- });
58
- describe("positiveNumber", () => {
59
- it("should return number for valid input", () => {
60
- expect(validation_1.ValidationUtils.positiveNumber(5, "field")).toBe(5);
61
- expect(validation_1.ValidationUtils.positiveNumber("10", "field")).toBe(10);
62
- });
63
- it("should throw for non-positive numbers", () => {
64
- expect(() => validation_1.ValidationUtils.positiveNumber(0, "field")).toThrow(validation_1.ValidationError);
65
- expect(() => validation_1.ValidationUtils.positiveNumber(-1, "field")).toThrow(validation_1.ValidationError);
66
- expect(() => validation_1.ValidationUtils.positiveNumber("abc", "field")).toThrow(validation_1.ValidationError);
67
- });
68
- });
69
- describe("range", () => {
70
- it("should return number within range", () => {
71
- expect(validation_1.ValidationUtils.range(5, "field", 1, 10)).toBe(5);
72
- });
73
- it("should throw for numbers outside range", () => {
74
- expect(() => validation_1.ValidationUtils.range(0, "field", 1, 10)).toThrow(validation_1.ValidationError);
75
- expect(() => validation_1.ValidationUtils.range(11, "field", 1, 10)).toThrow(validation_1.ValidationError);
76
- });
77
- });
78
- describe("oneOf", () => {
79
- it("should return value if it's in allowed values", () => {
80
- expect(validation_1.ValidationUtils.oneOf("test", "field", ["test", "other"])).toBe("test");
81
- });
82
- it("should throw if value is not in allowed values", () => {
83
- expect(() => validation_1.ValidationUtils.oneOf("invalid", "field", ["test", "other"])).toThrow(validation_1.ValidationError);
84
- });
85
- });
86
- describe("url", () => {
87
- it("should return URL for valid input", () => {
88
- expect(validation_1.ValidationUtils.url("https://example.com", "field")).toBe("https://example.com");
89
- });
90
- it("should throw for invalid URL", () => {
91
- expect(() => validation_1.ValidationUtils.url("not-a-url", "field")).toThrow(validation_1.ValidationError);
92
- });
93
- });
94
- describe("jwtToken", () => {
95
- it("should return token for valid JWT format", () => {
96
- const token = "header.payload.signature";
97
- expect(validation_1.ValidationUtils.jwtToken(token, "field")).toBe(token);
98
- });
99
- it("should throw for invalid JWT format", () => {
100
- expect(() => validation_1.ValidationUtils.jwtToken("invalid", "field")).toThrow(validation_1.ValidationError);
101
- expect(() => validation_1.ValidationUtils.jwtToken("header.payload", "field")).toThrow(validation_1.ValidationError);
102
- });
103
- });
104
- describe("uuid", () => {
105
- it("should return UUID for valid input", () => {
106
- const uuid = "123e4567-e89b-12d3-a456-426614174000";
107
- expect(validation_1.ValidationUtils.uuid(uuid, "field")).toBe(uuid);
108
- });
109
- it("should throw for invalid UUID", () => {
110
- expect(() => validation_1.ValidationUtils.uuid("not-a-uuid", "field")).toThrow(validation_1.ValidationError);
111
- });
112
- });
113
- describe("object", () => {
114
- it("should return object for valid input", () => {
115
- const obj = { test: "value" };
116
- expect(validation_1.ValidationUtils.object(obj, "field")).toBe(obj);
117
- });
118
- it("should throw for non-object input", () => {
119
- expect(() => validation_1.ValidationUtils.object("string", "field")).toThrow(validation_1.ValidationError);
120
- expect(() => validation_1.ValidationUtils.object([], "field")).toThrow(validation_1.ValidationError);
121
- });
122
- });
123
- describe("array", () => {
124
- it("should return array for valid input", () => {
125
- const arr = [1, 2, 3];
126
- expect(validation_1.ValidationUtils.array(arr, "field")).toBe(arr);
127
- });
128
- it("should throw for non-array input", () => {
129
- expect(() => validation_1.ValidationUtils.array("string", "field")).toThrow(validation_1.ValidationError);
130
- expect(() => validation_1.ValidationUtils.array({}, "field")).toThrow(validation_1.ValidationError);
131
- });
132
- });
133
- describe("function", () => {
134
- it("should return function for valid input", () => {
135
- const fn = () => { };
136
- expect(validation_1.ValidationUtils.function(fn, "field")).toBe(fn);
137
- });
138
- it("should throw for non-function input", () => {
139
- expect(() => validation_1.ValidationUtils.function("string", "field")).toThrow(validation_1.ValidationError);
140
- });
141
- });
142
- describe("validateConfig", () => {
143
- it("should not throw for valid config", () => {
144
- const config = { field1: "value1", field2: "value2" };
145
- expect(() => validation_1.ValidationUtils.validateConfig(config, ["field1", "field2"])).not.toThrow();
146
- });
147
- it("should throw for missing required fields", () => {
148
- const config = { field1: "value1" };
149
- expect(() => validation_1.ValidationUtils.validateConfig(config, ["field1", "field2"])).toThrow(validation_1.ValidationError);
150
- });
151
- });
152
- describe("pattern", () => {
153
- it("should return string for matching pattern", () => {
154
- const regex = /^[a-z]+$/;
155
- expect(validation_1.ValidationUtils.pattern("test", "field", regex)).toBe("test");
156
- });
157
- it("should throw for non-matching pattern", () => {
158
- const regex = /^[a-z]+$/;
159
- expect(() => validation_1.ValidationUtils.pattern("TEST", "field", regex)).toThrow(validation_1.ValidationError);
160
- });
161
- });
162
- describe("date", () => {
163
- it("should return Date for valid input", () => {
164
- const date = validation_1.ValidationUtils.date("2023-01-01", "field");
165
- expect(date).toBeInstanceOf(Date);
166
- });
167
- it("should throw for invalid date", () => {
168
- expect(() => validation_1.ValidationUtils.date("invalid-date", "field")).toThrow(validation_1.ValidationError);
169
- });
170
- });
171
- describe("boolean", () => {
172
- it("should return boolean for valid input", () => {
173
- expect(validation_1.ValidationUtils.boolean(true, "field")).toBe(true);
174
- expect(validation_1.ValidationUtils.boolean(false, "field")).toBe(false);
175
- });
176
- it("should throw for non-boolean input", () => {
177
- expect(() => validation_1.ValidationUtils.boolean("true", "field")).toThrow(validation_1.ValidationError);
178
- expect(() => validation_1.ValidationUtils.boolean(1, "field")).toThrow(validation_1.ValidationError);
179
- });
180
- });
181
- });
@@ -1,10 +0,0 @@
1
- {
2
- "preset": "ts-jest",
3
- "testEnvironment": "node",
4
- "clearMocks": true,
5
- "collectCoverage": true,
6
- "coverageDirectory": "coverage",
7
- "coverageProvider": "v8",
8
- "testMatch": ["**/__tests__/**/*.test.ts"],
9
- "testTimeout": 10000
10
- }