@knpkv/atlassian-common 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.
- package/CHANGELOG.md +11 -0
- package/README.md +4 -3
- package/dist/Hash.d.ts +4 -4
- package/dist/Hash.js +6 -6
- package/dist/Hash.js.map +1 -1
- package/dist/auth/OAuthEndpoints.d.ts +6 -4
- package/dist/auth/OAuthEndpoints.d.ts.map +1 -1
- package/dist/auth/OAuthEndpoints.js +12 -7
- package/dist/auth/OAuthEndpoints.js.map +1 -1
- package/dist/auth/OAuthErrors.d.ts.map +1 -1
- package/dist/auth/OAuthErrors.js +4 -3
- package/dist/auth/OAuthErrors.js.map +1 -1
- package/dist/auth/OAuthOperations.d.ts +0 -20
- package/dist/auth/OAuthOperations.d.ts.map +1 -1
- package/dist/auth/OAuthOperations.js +3 -1
- package/dist/auth/OAuthOperations.js.map +1 -1
- package/dist/auth/uuid.d.ts +5 -3
- package/dist/auth/uuid.d.ts.map +1 -1
- package/dist/auth/uuid.js +7 -3
- package/dist/auth/uuid.js.map +1 -1
- package/package.json +2 -2
- package/src/Hash.ts +6 -6
- package/src/auth/OAuthEndpoints.ts +18 -11
- package/src/auth/OAuthErrors.ts +4 -3
- package/src/auth/OAuthOperations.ts +3 -1
- package/src/auth/uuid.ts +9 -3
- package/test/OAuthEndpoints.test.ts +20 -15
- package/test/OAuthOperations.test.ts +5 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,25 +1,30 @@
|
|
|
1
|
+
import * as NodeCrypto from "@effect/platform-node/NodeCrypto"
|
|
1
2
|
import { it as effectIt } from "@effect/vitest"
|
|
2
3
|
import * as Effect from "effect/Effect"
|
|
3
4
|
import { describe, expect, it } from "vitest"
|
|
4
5
|
import { AUTH_URL, buildAuthUrl, computeCodeChallenge, generateCodeVerifier } from "../src/auth/OAuthEndpoints.js"
|
|
5
6
|
|
|
7
|
+
const withCrypto = <A, E, R>(effect: Effect.Effect<A, E, R>) => effect.pipe(Effect.provide(NodeCrypto.layer))
|
|
8
|
+
|
|
6
9
|
describe("OAuthEndpoints", () => {
|
|
7
10
|
// ---- PKCE ----
|
|
8
11
|
|
|
9
12
|
describe("generateCodeVerifier", () => {
|
|
10
13
|
// 32 random bytes base64url-encoded = 43 chars; verifies output shape for RFC 7636 compliance
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
effectIt.effect("returns a base64url string of 43 characters (32 random bytes)", () =>
|
|
15
|
+
Effect.gen(function*() {
|
|
16
|
+
const verifier = yield* generateCodeVerifier()
|
|
17
|
+
expect(verifier).toHaveLength(43)
|
|
18
|
+
expect(verifier).toMatch(/^[A-Za-z0-9_-]+$/)
|
|
19
|
+
}).pipe(withCrypto))
|
|
16
20
|
|
|
17
21
|
// PKCE verifiers must be unique per auth request — reuse enables replay attacks
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
effectIt.effect("produces unique values", () =>
|
|
23
|
+
Effect.gen(function*() {
|
|
24
|
+
const a = yield* generateCodeVerifier()
|
|
25
|
+
const b = yield* generateCodeVerifier()
|
|
26
|
+
expect(a).not.toBe(b)
|
|
27
|
+
}).pipe(withCrypto))
|
|
23
28
|
})
|
|
24
29
|
|
|
25
30
|
describe("computeCodeChallenge", () => {
|
|
@@ -30,23 +35,23 @@ describe("OAuthEndpoints", () => {
|
|
|
30
35
|
const expected = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
|
|
31
36
|
const challenge = yield* computeCodeChallenge(verifier)
|
|
32
37
|
expect(challenge).toBe(expected)
|
|
33
|
-
}))
|
|
38
|
+
}).pipe(withCrypto))
|
|
34
39
|
|
|
35
40
|
// Same verifier must always produce same challenge — server verifies this on token exchange
|
|
36
41
|
effectIt.effect("is deterministic", () =>
|
|
37
42
|
Effect.gen(function*() {
|
|
38
|
-
const v = generateCodeVerifier()
|
|
43
|
+
const v = yield* generateCodeVerifier()
|
|
39
44
|
const a = yield* computeCodeChallenge(v)
|
|
40
45
|
const b = yield* computeCodeChallenge(v)
|
|
41
46
|
expect(a).toBe(b)
|
|
42
|
-
}))
|
|
47
|
+
}).pipe(withCrypto))
|
|
43
48
|
|
|
44
49
|
// base64url must not contain +, /, = — these break URL query params
|
|
45
50
|
effectIt.effect("produces base64url output (no +, /, =)", () =>
|
|
46
51
|
Effect.gen(function*() {
|
|
47
52
|
const challenge = yield* computeCodeChallenge("test-verifier-string")
|
|
48
53
|
expect(challenge).toMatch(/^[A-Za-z0-9_-]+$/)
|
|
49
|
-
}))
|
|
54
|
+
}).pipe(withCrypto))
|
|
50
55
|
})
|
|
51
56
|
|
|
52
57
|
// ---- buildAuthUrl ----
|
|
@@ -103,7 +108,7 @@ describe("OAuthEndpoints", () => {
|
|
|
103
108
|
const parsed = new URL(url)
|
|
104
109
|
expect(parsed.searchParams.get("code_challenge")).toBe(challenge)
|
|
105
110
|
expect(parsed.searchParams.get("code_challenge_method")).toBe("S256")
|
|
106
|
-
})
|
|
111
|
+
}).pipe(withCrypto)
|
|
107
112
|
)
|
|
108
113
|
})
|
|
109
114
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it } from "@effect/vitest"
|
|
2
2
|
import * as Effect from "effect/Effect"
|
|
3
3
|
import * as Layer from "effect/Layer"
|
|
4
|
+
import { TestClock } from "effect/testing"
|
|
4
5
|
import type { HttpClientRequest } from "effect/unstable/http"
|
|
5
6
|
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
|
|
6
7
|
import { expect } from "vitest"
|
|
@@ -58,6 +59,8 @@ const oauthConfig: OAuthConfig = {
|
|
|
58
59
|
clientSecret: "csecret"
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
const TEST_NOW = Date.parse("2026-01-01T00:00:00.000Z")
|
|
63
|
+
|
|
61
64
|
const oauthToken: OAuthToken = {
|
|
62
65
|
access_token: "old-access",
|
|
63
66
|
refresh_token: "old-refresh",
|
|
@@ -221,6 +224,7 @@ describe("OAuthOperations", () => {
|
|
|
221
224
|
// Verifies grant_type=refresh_token is sent and returned token preserves cloud_id/site_url from original
|
|
222
225
|
it.effect("sends refresh_token and returns updated OAuthToken", () =>
|
|
223
226
|
Effect.gen(function*() {
|
|
227
|
+
yield* TestClock.setTime(TEST_NOW)
|
|
224
228
|
const { capturedRequests, mockClient } = createMockHttpClient([
|
|
225
229
|
{ status: 200, body: { ...validTokenBody, access_token: "new-access", refresh_token: "new-refresh" } }
|
|
226
230
|
])
|
|
@@ -232,7 +236,7 @@ describe("OAuthOperations", () => {
|
|
|
232
236
|
// preserves existing fields
|
|
233
237
|
expect(result.cloud_id).toBe("cloud-1")
|
|
234
238
|
expect(result.site_url).toBe("https://site.atlassian.net")
|
|
235
|
-
expect(result.expires_at).
|
|
239
|
+
expect(result.expires_at).toBe(TEST_NOW + validTokenBody.expires_in * 1000)
|
|
236
240
|
|
|
237
241
|
const req = capturedRequests[0]!
|
|
238
242
|
const body = yield* req.body._tag === "Uint8Array"
|