@knpkv/confluence-to-markdown 0.2.0 → 0.4.1
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 +60 -0
- package/LICENSE +21 -0
- package/README.md +282 -14
- package/dist/ConfluenceAuth.d.ts +76 -0
- package/dist/ConfluenceAuth.d.ts.map +1 -0
- package/dist/ConfluenceAuth.js +356 -0
- package/dist/ConfluenceAuth.js.map +1 -0
- package/dist/ConfluenceClient.d.ts +26 -2
- package/dist/ConfluenceClient.d.ts.map +1 -1
- package/dist/ConfluenceClient.js +98 -92
- package/dist/ConfluenceClient.js.map +1 -1
- package/dist/ConfluenceConfig.d.ts +4 -24
- package/dist/ConfluenceConfig.d.ts.map +1 -1
- package/dist/ConfluenceConfig.js +45 -7
- package/dist/ConfluenceConfig.js.map +1 -1
- package/dist/ConfluenceError.d.ts +89 -6
- package/dist/ConfluenceError.d.ts.map +1 -1
- package/dist/ConfluenceError.js +88 -5
- package/dist/ConfluenceError.js.map +1 -1
- package/dist/GitError.d.ts +103 -0
- package/dist/GitError.d.ts.map +1 -0
- package/dist/GitError.js +85 -0
- package/dist/GitError.js.map +1 -0
- package/dist/GitService.d.ts +175 -0
- package/dist/GitService.d.ts.map +1 -0
- package/dist/GitService.js +431 -0
- package/dist/GitService.js.map +1 -0
- package/dist/LocalFileSystem.d.ts +29 -4
- package/dist/LocalFileSystem.d.ts.map +1 -1
- package/dist/LocalFileSystem.js +80 -6
- package/dist/LocalFileSystem.js.map +1 -1
- package/dist/MarkdownConverter.d.ts +49 -2
- package/dist/MarkdownConverter.d.ts.map +1 -1
- package/dist/MarkdownConverter.js +73 -111
- package/dist/MarkdownConverter.js.map +1 -1
- package/dist/SchemaConverterError.d.ts +108 -0
- package/dist/SchemaConverterError.d.ts.map +1 -0
- package/dist/SchemaConverterError.js +84 -0
- package/dist/SchemaConverterError.js.map +1 -0
- package/dist/Schemas.d.ts +225 -1
- package/dist/Schemas.d.ts.map +1 -1
- package/dist/Schemas.js +155 -6
- package/dist/Schemas.js.map +1 -1
- package/dist/SyncEngine.d.ts +30 -20
- package/dist/SyncEngine.d.ts.map +1 -1
- package/dist/SyncEngine.js +566 -117
- package/dist/SyncEngine.js.map +1 -1
- package/dist/ast/BlockNode.d.ts +468 -0
- package/dist/ast/BlockNode.d.ts.map +1 -0
- package/dist/ast/BlockNode.js +319 -0
- package/dist/ast/BlockNode.js.map +1 -0
- package/dist/ast/Document.d.ts +244 -0
- package/dist/ast/Document.d.ts.map +1 -0
- package/dist/ast/Document.js +69 -0
- package/dist/ast/Document.js.map +1 -0
- package/dist/ast/InlineNode.d.ts +477 -0
- package/dist/ast/InlineNode.d.ts.map +1 -0
- package/dist/ast/InlineNode.js +263 -0
- package/dist/ast/InlineNode.js.map +1 -0
- package/dist/ast/MacroNode.d.ts +267 -0
- package/dist/ast/MacroNode.d.ts.map +1 -0
- package/dist/ast/MacroNode.js +164 -0
- package/dist/ast/MacroNode.js.map +1 -0
- package/dist/ast/index.d.ts +10 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +14 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/bin.js +33 -149
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth.d.ts +15 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +86 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/clone.d.ts +12 -0
- package/dist/commands/clone.d.ts.map +1 -0
- package/dist/commands/clone.js +93 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/delete.d.ts +13 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +48 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/errorHandler.d.ts +14 -0
- package/dist/commands/errorHandler.d.ts.map +1 -0
- package/dist/commands/errorHandler.js +33 -0
- package/dist/commands/errorHandler.js.map +1 -0
- package/dist/commands/git.d.ts +22 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +72 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/layers.d.ts +31 -0
- package/dist/commands/layers.d.ts.map +1 -0
- package/dist/commands/layers.js +137 -0
- package/dist/commands/layers.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +80 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pageTree.d.ts +18 -0
- package/dist/commands/pageTree.d.ts.map +1 -0
- package/dist/commands/pageTree.js +20 -0
- package/dist/commands/pageTree.js.map +1 -0
- package/dist/commands/shared.d.ts +15 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +27 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/sync.d.ts +15 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +101 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/NodeLayers.d.ts +7 -0
- package/dist/internal/NodeLayers.d.ts.map +1 -0
- package/dist/internal/NodeLayers.js +19 -0
- package/dist/internal/NodeLayers.js.map +1 -0
- package/dist/internal/frontmatter.d.ts +10 -0
- package/dist/internal/frontmatter.d.ts.map +1 -1
- package/dist/internal/frontmatter.js +16 -0
- package/dist/internal/frontmatter.js.map +1 -1
- package/dist/internal/gitCommands.d.ts +78 -0
- package/dist/internal/gitCommands.d.ts.map +1 -0
- package/dist/internal/gitCommands.js +156 -0
- package/dist/internal/gitCommands.js.map +1 -0
- package/dist/internal/hashUtils.d.ts +42 -1
- package/dist/internal/hashUtils.d.ts.map +1 -1
- package/dist/internal/hashUtils.js +38 -2
- package/dist/internal/hashUtils.js.map +1 -1
- package/dist/internal/oauthServer.d.ts +55 -0
- package/dist/internal/oauthServer.d.ts.map +1 -0
- package/dist/internal/oauthServer.js +110 -0
- package/dist/internal/oauthServer.js.map +1 -0
- package/dist/internal/pathUtils.d.ts +21 -4
- package/dist/internal/pathUtils.d.ts.map +1 -1
- package/dist/internal/pathUtils.js +24 -13
- package/dist/internal/pathUtils.js.map +1 -1
- package/dist/internal/tokenStorage.d.ts +75 -0
- package/dist/internal/tokenStorage.d.ts.map +1 -0
- package/dist/internal/tokenStorage.js +149 -0
- package/dist/internal/tokenStorage.js.map +1 -0
- package/dist/internal/userCache.d.ts +42 -0
- package/dist/internal/userCache.d.ts.map +1 -0
- package/dist/internal/userCache.js +51 -0
- package/dist/internal/userCache.js.map +1 -0
- package/dist/parsers/ConfluenceParser.d.ts +26 -0
- package/dist/parsers/ConfluenceParser.d.ts.map +1 -0
- package/dist/parsers/ConfluenceParser.js +792 -0
- package/dist/parsers/ConfluenceParser.js.map +1 -0
- package/dist/parsers/MarkdownParser.d.ts +26 -0
- package/dist/parsers/MarkdownParser.d.ts.map +1 -0
- package/dist/parsers/MarkdownParser.js +873 -0
- package/dist/parsers/MarkdownParser.js.map +1 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +8 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/schemas/ConfluenceSchema.d.ts +21 -0
- package/dist/schemas/ConfluenceSchema.d.ts.map +1 -0
- package/dist/schemas/ConfluenceSchema.js +38 -0
- package/dist/schemas/ConfluenceSchema.js.map +1 -0
- package/dist/schemas/ConversionSchema.d.ts +35 -0
- package/dist/schemas/ConversionSchema.d.ts.map +1 -0
- package/dist/schemas/ConversionSchema.js +208 -0
- package/dist/schemas/ConversionSchema.js.map +1 -0
- package/dist/schemas/MarkdownSchema.d.ts +21 -0
- package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
- package/dist/schemas/MarkdownSchema.js +38 -0
- package/dist/schemas/MarkdownSchema.js.map +1 -0
- package/dist/schemas/hast/HastFromHtml.d.ts +27 -0
- package/dist/schemas/hast/HastFromHtml.d.ts.map +1 -0
- package/dist/schemas/hast/HastFromHtml.js +107 -0
- package/dist/schemas/hast/HastFromHtml.js.map +1 -0
- package/dist/schemas/hast/HastSchema.d.ts +195 -0
- package/dist/schemas/hast/HastSchema.d.ts.map +1 -0
- package/dist/schemas/hast/HastSchema.js +183 -0
- package/dist/schemas/hast/HastSchema.js.map +1 -0
- package/dist/schemas/hast/index.d.ts +9 -0
- package/dist/schemas/hast/index.d.ts.map +1 -0
- package/dist/schemas/hast/index.js +3 -0
- package/dist/schemas/hast/index.js.map +1 -0
- package/dist/schemas/index.d.ts +14 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +16 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts +30 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js +79 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js.map +1 -0
- package/dist/schemas/mdast/MdastSchema.d.ts +385 -0
- package/dist/schemas/mdast/MdastSchema.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastSchema.js +266 -0
- package/dist/schemas/mdast/MdastSchema.js.map +1 -0
- package/dist/schemas/mdast/index.d.ts +10 -0
- package/dist/schemas/mdast/index.d.ts.map +1 -0
- package/dist/schemas/mdast/index.js +4 -0
- package/dist/schemas/mdast/index.js.map +1 -0
- package/dist/schemas/mdast/mdastToString.d.ts +13 -0
- package/dist/schemas/mdast/mdastToString.d.ts.map +1 -0
- package/dist/schemas/mdast/mdastToString.js +85 -0
- package/dist/schemas/mdast/mdastToString.js.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts +43 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.js +634 -0
- package/dist/schemas/nodes/block/BlockSchema.js.map +1 -0
- package/dist/schemas/nodes/block/index.d.ts +7 -0
- package/dist/schemas/nodes/block/index.d.ts.map +1 -0
- package/dist/schemas/nodes/block/index.js +7 -0
- package/dist/schemas/nodes/block/index.js.map +1 -0
- package/dist/schemas/nodes/index.d.ts +9 -0
- package/dist/schemas/nodes/index.d.ts.map +1 -0
- package/dist/schemas/nodes/index.js +12 -0
- package/dist/schemas/nodes/index.js.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts +48 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.js +436 -0
- package/dist/schemas/nodes/inline/InlineSchema.js.map +1 -0
- package/dist/schemas/nodes/inline/index.d.ts +7 -0
- package/dist/schemas/nodes/inline/index.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/index.js +7 -0
- package/dist/schemas/nodes/inline/index.js.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts +27 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.js +162 -0
- package/dist/schemas/nodes/macro/MacroSchema.js.map +1 -0
- package/dist/schemas/nodes/macro/index.d.ts +7 -0
- package/dist/schemas/nodes/macro/index.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/index.js +7 -0
- package/dist/schemas/nodes/macro/index.js.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +24 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js +351 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -0
- package/dist/schemas/preprocessing/index.d.ts +8 -0
- package/dist/schemas/preprocessing/index.d.ts.map +1 -0
- package/dist/schemas/preprocessing/index.js +2 -0
- package/dist/schemas/preprocessing/index.js.map +1 -0
- package/dist/serializers/ConfluenceSerializer.d.ts +30 -0
- package/dist/serializers/ConfluenceSerializer.d.ts.map +1 -0
- package/dist/serializers/ConfluenceSerializer.js +551 -0
- package/dist/serializers/ConfluenceSerializer.js.map +1 -0
- package/dist/serializers/MarkdownSerializer.d.ts +34 -0
- package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
- package/dist/serializers/MarkdownSerializer.js +355 -0
- package/dist/serializers/MarkdownSerializer.js.map +1 -0
- package/dist/serializers/index.d.ts +8 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/serializers/index.js +8 -0
- package/dist/serializers/index.js.map +1 -0
- package/package.json +27 -16
- package/src/ConfluenceAuth.ts +571 -0
- package/src/ConfluenceClient.ts +188 -156
- package/src/ConfluenceConfig.ts +63 -7
- package/src/ConfluenceError.ts +110 -14
- package/src/GitError.ts +92 -0
- package/src/GitService.ts +859 -0
- package/src/LocalFileSystem.ts +179 -9
- package/src/MarkdownConverter.ts +126 -122
- package/src/SchemaConverterError.ts +108 -0
- package/src/Schemas.ts +223 -6
- package/src/SyncEngine.ts +745 -162
- package/src/ast/BlockNode.ts +425 -0
- package/src/ast/Document.ts +90 -0
- package/src/ast/InlineNode.ts +323 -0
- package/src/ast/MacroNode.ts +245 -0
- package/src/ast/index.ts +83 -0
- package/src/bin.ts +50 -249
- package/src/commands/auth.ts +117 -0
- package/src/commands/clone.ts +145 -0
- package/src/commands/delete.ts +57 -0
- package/src/commands/errorHandler.ts +32 -0
- package/src/commands/git.ts +114 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/layers.ts +211 -0
- package/src/commands/new.ts +99 -0
- package/src/commands/pageTree.ts +40 -0
- package/src/commands/shared.ts +35 -0
- package/src/commands/sync.ts +129 -0
- package/src/index.ts +21 -1
- package/src/internal/NodeLayers.ts +21 -0
- package/src/internal/frontmatter.ts +21 -0
- package/src/internal/gitCommands.ts +229 -0
- package/src/internal/hashUtils.ts +65 -3
- package/src/internal/oauthServer.ts +199 -0
- package/src/internal/pathUtils.ts +34 -17
- package/src/internal/tokenStorage.ts +240 -0
- package/src/internal/userCache.ts +90 -0
- package/src/parsers/ConfluenceParser.ts +950 -0
- package/src/parsers/MarkdownParser.ts +1198 -0
- package/src/parsers/index.ts +8 -0
- package/src/schemas/ConfluenceSchema.ts +56 -0
- package/src/schemas/ConversionSchema.ts +318 -0
- package/src/schemas/MarkdownSchema.ts +56 -0
- package/src/schemas/hast/HastFromHtml.ts +153 -0
- package/src/schemas/hast/HastSchema.ts +274 -0
- package/src/schemas/hast/index.ts +35 -0
- package/src/schemas/index.ts +20 -0
- package/src/schemas/mdast/MdastFromMarkdown.ts +118 -0
- package/src/schemas/mdast/MdastSchema.ts +566 -0
- package/src/schemas/mdast/index.ts +59 -0
- package/src/schemas/mdast/mdastToString.ts +102 -0
- package/src/schemas/nodes/block/BlockSchema.ts +773 -0
- package/src/schemas/nodes/block/index.ts +13 -0
- package/src/schemas/nodes/index.ts +20 -0
- package/src/schemas/nodes/inline/InlineSchema.ts +523 -0
- package/src/schemas/nodes/inline/index.ts +14 -0
- package/src/schemas/nodes/macro/MacroSchema.ts +226 -0
- package/src/schemas/nodes/macro/index.ts +6 -0
- package/src/schemas/preprocessing/ConfluencePreprocessor.ts +446 -0
- package/src/schemas/preprocessing/index.ts +8 -0
- package/src/serializers/ConfluenceSerializer.ts +717 -0
- package/src/serializers/MarkdownSerializer.ts +493 -0
- package/src/serializers/index.ts +8 -0
- package/test/GitService.test.ts +209 -0
- package/test/MarkdownConverter.test.ts +37 -3
- package/test/Schemas.test.ts +97 -2
- package/test/ast/BlockNode.test.ts +265 -0
- package/test/ast/Document.test.ts +126 -0
- package/test/ast/InlineNode.test.ts +161 -0
- package/test/fixtures/integration-test.html.fixture +103 -0
- package/test/fixtures/integration-test.md.expected +257 -0
- package/test/integration.test.ts +269 -0
- package/test/oauthServer.test.ts +50 -0
- package/test/parsers/ConfluenceParser.test.ts +283 -0
- package/test/schemas/ConfluencePreprocessor.test.ts +180 -0
- package/test/schemas/ConversionSchema.test.ts +159 -0
- package/test/schemas/HastSchema.test.ts +138 -0
- package/test/schemas/MdastSchema.test.ts +145 -0
- package/test/schemas/nodes/block/BlockSchema.test.ts +173 -0
- package/test/schemas/nodes/inline/InlineSchema.test.ts +198 -0
- package/test/schemas/nodes/macro/MacroSchema.test.ts +142 -0
- package/test/tokenStorage.test.ts +99 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth authentication service for Confluence.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"
|
|
7
|
+
import * as NodePath from "@effect/platform-node/NodePath"
|
|
8
|
+
import * as Command from "@effect/platform/Command"
|
|
9
|
+
import * as CommandExecutor from "@effect/platform/CommandExecutor"
|
|
10
|
+
import * as HttpClient from "@effect/platform/HttpClient"
|
|
11
|
+
import * as HttpClientRequest from "@effect/platform/HttpClientRequest"
|
|
12
|
+
import * as Context from "effect/Context"
|
|
13
|
+
import * as Deferred from "effect/Deferred"
|
|
14
|
+
import * as Effect from "effect/Effect"
|
|
15
|
+
import * as Layer from "effect/Layer"
|
|
16
|
+
import * as Option from "effect/Option"
|
|
17
|
+
import * as Ref from "effect/Ref"
|
|
18
|
+
import * as Schema from "effect/Schema"
|
|
19
|
+
import type { FileSystemError } from "./ConfluenceError.js"
|
|
20
|
+
import { AuthMissingError, OAuthError } from "./ConfluenceError.js"
|
|
21
|
+
import { HttpServerFactoryLive } from "./internal/NodeLayers.js"
|
|
22
|
+
import { startCallbackServer } from "./internal/oauthServer.js"
|
|
23
|
+
import {
|
|
24
|
+
deleteToken,
|
|
25
|
+
HomeDirectoryLive,
|
|
26
|
+
loadOAuthConfig,
|
|
27
|
+
loadToken,
|
|
28
|
+
saveOAuthConfig,
|
|
29
|
+
saveToken
|
|
30
|
+
} from "./internal/tokenStorage.js"
|
|
31
|
+
import type { OAuthConfig, OAuthToken, OAuthUser } from "./Schemas.js"
|
|
32
|
+
|
|
33
|
+
// Layer for token storage operations (FileSystem + Path + HomeDirectory)
|
|
34
|
+
const TokenStorageLive = Layer.mergeAll(
|
|
35
|
+
NodeFileSystem.layer,
|
|
36
|
+
NodePath.layer,
|
|
37
|
+
HomeDirectoryLive
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate a cryptographically secure UUID v4.
|
|
42
|
+
* Uses Web Crypto API (available in all modern runtimes).
|
|
43
|
+
* Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
44
|
+
*/
|
|
45
|
+
const generateUUID = (): Effect.Effect<string> =>
|
|
46
|
+
Effect.sync(() => {
|
|
47
|
+
const bytes = new Uint8Array(16)
|
|
48
|
+
globalThis.crypto.getRandomValues(bytes)
|
|
49
|
+
|
|
50
|
+
// Set version (4) and variant bits per RFC 4122
|
|
51
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x40 // version 4
|
|
52
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80 // variant 10xx
|
|
53
|
+
|
|
54
|
+
const hex = Array.from(bytes)
|
|
55
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
56
|
+
.join("")
|
|
57
|
+
|
|
58
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const OAUTH_SCOPES = [
|
|
62
|
+
// Granular scopes (v2 API)
|
|
63
|
+
"read:page:confluence",
|
|
64
|
+
"write:page:confluence",
|
|
65
|
+
"delete:page:confluence",
|
|
66
|
+
// User identity
|
|
67
|
+
"read:me",
|
|
68
|
+
"offline_access"
|
|
69
|
+
].join(" ")
|
|
70
|
+
|
|
71
|
+
// API endpoints
|
|
72
|
+
const AUTH_URL = "https://auth.atlassian.com/authorize"
|
|
73
|
+
const TOKEN_URL = "https://auth.atlassian.com/oauth/token"
|
|
74
|
+
const REVOKE_URL = "https://auth.atlassian.com/oauth/revoke"
|
|
75
|
+
const RESOURCES_URL = "https://api.atlassian.com/oauth/token/accessible-resources"
|
|
76
|
+
const ME_URL = "https://api.atlassian.com/me"
|
|
77
|
+
|
|
78
|
+
// Response schemas
|
|
79
|
+
const TokenResponseSchema = Schema.Struct({
|
|
80
|
+
access_token: Schema.String,
|
|
81
|
+
refresh_token: Schema.String,
|
|
82
|
+
expires_in: Schema.Number,
|
|
83
|
+
scope: Schema.String,
|
|
84
|
+
token_type: Schema.String
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const AccessibleResourceSchema = Schema.Struct({
|
|
88
|
+
id: Schema.String,
|
|
89
|
+
name: Schema.String,
|
|
90
|
+
url: Schema.String,
|
|
91
|
+
scopes: Schema.Array(Schema.String)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const UserInfoSchema = Schema.Struct({
|
|
95
|
+
account_id: Schema.String,
|
|
96
|
+
name: Schema.String,
|
|
97
|
+
email: Schema.String
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Options for the login method.
|
|
102
|
+
*/
|
|
103
|
+
export interface LoginOptions {
|
|
104
|
+
/** Site URL to select (for accounts with multiple sites) */
|
|
105
|
+
readonly siteUrl?: string
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Information about an accessible Confluence site.
|
|
110
|
+
*/
|
|
111
|
+
export interface AccessibleSite {
|
|
112
|
+
readonly id: string
|
|
113
|
+
readonly name: string
|
|
114
|
+
readonly url: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* ConfluenceAuth service interface.
|
|
119
|
+
*
|
|
120
|
+
* @category Services
|
|
121
|
+
*/
|
|
122
|
+
export interface ConfluenceAuthService {
|
|
123
|
+
/** Configure OAuth client credentials */
|
|
124
|
+
readonly configure: (config: OAuthConfig) => Effect.Effect<void, FileSystemError>
|
|
125
|
+
/** Check if OAuth is configured */
|
|
126
|
+
readonly isConfigured: () => Effect.Effect<boolean, FileSystemError>
|
|
127
|
+
/** Start OAuth login flow. Returns list of sites if multiple are available. */
|
|
128
|
+
readonly login: (
|
|
129
|
+
options?: LoginOptions
|
|
130
|
+
) => Effect.Effect<ReadonlyArray<AccessibleSite> | void, OAuthError | FileSystemError>
|
|
131
|
+
/** Remove stored authentication */
|
|
132
|
+
readonly logout: () => Effect.Effect<void, OAuthError | FileSystemError>
|
|
133
|
+
/** Get access token, refreshing if needed */
|
|
134
|
+
readonly getAccessToken: () => Effect.Effect<string, AuthMissingError | OAuthError | FileSystemError>
|
|
135
|
+
/** Get cloud ID from stored token */
|
|
136
|
+
readonly getCloudId: () => Effect.Effect<string, AuthMissingError | FileSystemError>
|
|
137
|
+
/** Get current user info from stored token */
|
|
138
|
+
readonly getCurrentUser: () => Effect.Effect<OAuthUser | null, FileSystemError>
|
|
139
|
+
/** Check if user is logged in */
|
|
140
|
+
readonly isLoggedIn: () => Effect.Effect<boolean, FileSystemError>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* ConfluenceAuth service tag.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* import { Effect } from "effect"
|
|
149
|
+
* import { ConfluenceAuth } from "@knpkv/confluence-to-markdown/ConfluenceAuth"
|
|
150
|
+
*
|
|
151
|
+
* Effect.gen(function* () {
|
|
152
|
+
* const auth = yield* ConfluenceAuth
|
|
153
|
+
* const isLoggedIn = yield* auth.isLoggedIn()
|
|
154
|
+
* if (!isLoggedIn) {
|
|
155
|
+
* yield* auth.login()
|
|
156
|
+
* }
|
|
157
|
+
* })
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* @category Services
|
|
161
|
+
*/
|
|
162
|
+
export class ConfluenceAuth extends Context.Tag("@knpkv/confluence-to-markdown/ConfluenceAuth")<
|
|
163
|
+
ConfluenceAuth,
|
|
164
|
+
ConfluenceAuthService
|
|
165
|
+
>() {}
|
|
166
|
+
|
|
167
|
+
const buildAuthUrl = (clientId: string, state: string, port: number): string => {
|
|
168
|
+
const params = new URLSearchParams({
|
|
169
|
+
audience: "api.atlassian.com",
|
|
170
|
+
client_id: clientId,
|
|
171
|
+
scope: OAUTH_SCOPES,
|
|
172
|
+
redirect_uri: `http://localhost:${port}/callback`,
|
|
173
|
+
state,
|
|
174
|
+
response_type: "code",
|
|
175
|
+
prompt: "consent"
|
|
176
|
+
})
|
|
177
|
+
return `${AUTH_URL}?${params.toString()}`
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Wrap token storage operations with their required layers
|
|
181
|
+
const loadTokenOp = () => loadToken().pipe(Effect.provide(TokenStorageLive))
|
|
182
|
+
const saveTokenOp = (token: OAuthToken) => saveToken(token).pipe(Effect.provide(TokenStorageLive))
|
|
183
|
+
const deleteTokenOp = () => deleteToken().pipe(Effect.provide(TokenStorageLive))
|
|
184
|
+
const loadOAuthConfigOp = () => loadOAuthConfig().pipe(Effect.provide(TokenStorageLive))
|
|
185
|
+
const saveOAuthConfigOp = (config: OAuthConfig) => saveOAuthConfig(config).pipe(Effect.provide(TokenStorageLive))
|
|
186
|
+
|
|
187
|
+
const make = Effect.gen(function*() {
|
|
188
|
+
const httpClient = yield* HttpClient.HttpClient
|
|
189
|
+
const commandExecutor = yield* CommandExecutor.CommandExecutor
|
|
190
|
+
|
|
191
|
+
// Ref to track ongoing refresh operation to prevent concurrent refreshes
|
|
192
|
+
const refreshLock = yield* Ref.make<Option.Option<Deferred.Deferred<OAuthToken, OAuthError | FileSystemError>>>(
|
|
193
|
+
Option.none()
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
const openBrowserImpl = (url: string): Effect.Effect<void, OAuthError> =>
|
|
197
|
+
Effect.gen(function*() {
|
|
198
|
+
const platform = process.platform
|
|
199
|
+
let command: Command.Command
|
|
200
|
+
|
|
201
|
+
if (platform === "darwin") {
|
|
202
|
+
command = Command.make("open", url)
|
|
203
|
+
} else if (platform === "win32") {
|
|
204
|
+
command = Command.make("cmd", "/c", "start", "", url)
|
|
205
|
+
} else {
|
|
206
|
+
command = Command.make("xdg-open", url)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
yield* Command.exitCode(command).pipe(
|
|
210
|
+
Effect.provide(Layer.succeed(CommandExecutor.CommandExecutor, commandExecutor))
|
|
211
|
+
)
|
|
212
|
+
}).pipe(
|
|
213
|
+
Effect.mapError((cause) => new OAuthError({ step: "authorize", cause }))
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const getConfig = (): Effect.Effect<OAuthConfig, OAuthError | FileSystemError> =>
|
|
217
|
+
Effect.gen(function*() {
|
|
218
|
+
const config = yield* loadOAuthConfigOp()
|
|
219
|
+
if (config === null) {
|
|
220
|
+
return yield* Effect.fail(
|
|
221
|
+
new OAuthError({
|
|
222
|
+
step: "authorize",
|
|
223
|
+
cause: "OAuth not configured. Run 'confluence auth configure' first."
|
|
224
|
+
})
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
return config
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const exchangeCodeForTokens = (
|
|
231
|
+
code: string,
|
|
232
|
+
config: OAuthConfig,
|
|
233
|
+
port: number
|
|
234
|
+
): Effect.Effect<Schema.Schema.Type<typeof TokenResponseSchema>, OAuthError> =>
|
|
235
|
+
Effect.gen(function*() {
|
|
236
|
+
const request = yield* HttpClientRequest.post(TOKEN_URL).pipe(
|
|
237
|
+
HttpClientRequest.setHeader("Content-Type", "application/json"),
|
|
238
|
+
HttpClientRequest.bodyJson({
|
|
239
|
+
grant_type: "authorization_code",
|
|
240
|
+
client_id: config.clientId,
|
|
241
|
+
client_secret: config.clientSecret,
|
|
242
|
+
code,
|
|
243
|
+
redirect_uri: `http://localhost:${port}/callback`
|
|
244
|
+
})
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
const response = yield* httpClient.execute(request)
|
|
248
|
+
const body = yield* response.json
|
|
249
|
+
|
|
250
|
+
return yield* Schema.decodeUnknown(TokenResponseSchema)(body)
|
|
251
|
+
}).pipe(
|
|
252
|
+
Effect.mapError((cause) => new OAuthError({ step: "token", cause }))
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
const getAccessibleResources = (
|
|
256
|
+
accessToken: string
|
|
257
|
+
): Effect.Effect<ReadonlyArray<Schema.Schema.Type<typeof AccessibleResourceSchema>>, OAuthError> =>
|
|
258
|
+
Effect.gen(function*() {
|
|
259
|
+
const request = HttpClientRequest.get(RESOURCES_URL).pipe(
|
|
260
|
+
HttpClientRequest.setHeader("Authorization", `Bearer ${accessToken}`),
|
|
261
|
+
HttpClientRequest.setHeader("Accept", "application/json")
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
const response = yield* httpClient.execute(request)
|
|
265
|
+
const body = yield* response.json
|
|
266
|
+
|
|
267
|
+
return yield* Schema.decodeUnknown(Schema.Array(AccessibleResourceSchema))(body)
|
|
268
|
+
}).pipe(
|
|
269
|
+
Effect.mapError((cause) => new OAuthError({ step: "authorize", cause }))
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
const getUserInfo = (
|
|
273
|
+
accessToken: string
|
|
274
|
+
): Effect.Effect<OAuthUser, OAuthError> =>
|
|
275
|
+
Effect.gen(function*() {
|
|
276
|
+
const request = HttpClientRequest.get(ME_URL).pipe(
|
|
277
|
+
HttpClientRequest.setHeader("Authorization", `Bearer ${accessToken}`),
|
|
278
|
+
HttpClientRequest.setHeader("Accept", "application/json")
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
const response = yield* httpClient.execute(request)
|
|
282
|
+
const body = yield* response.json
|
|
283
|
+
|
|
284
|
+
return yield* Schema.decodeUnknown(UserInfoSchema)(body)
|
|
285
|
+
}).pipe(
|
|
286
|
+
Effect.mapError((cause) => new OAuthError({ step: "authorize", cause }))
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
const refreshToken = (
|
|
290
|
+
token: OAuthToken,
|
|
291
|
+
config: OAuthConfig
|
|
292
|
+
): Effect.Effect<OAuthToken, OAuthError | FileSystemError> =>
|
|
293
|
+
Effect.gen(function*() {
|
|
294
|
+
const request = yield* HttpClientRequest.post(TOKEN_URL).pipe(
|
|
295
|
+
HttpClientRequest.setHeader("Content-Type", "application/json"),
|
|
296
|
+
HttpClientRequest.bodyJson({
|
|
297
|
+
grant_type: "refresh_token",
|
|
298
|
+
client_id: config.clientId,
|
|
299
|
+
client_secret: config.clientSecret,
|
|
300
|
+
refresh_token: token.refresh_token
|
|
301
|
+
}),
|
|
302
|
+
Effect.mapError((cause) => new OAuthError({ step: "refresh", cause }))
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
const response = yield* httpClient.execute(request).pipe(
|
|
306
|
+
Effect.mapError((cause) => new OAuthError({ step: "refresh", cause }))
|
|
307
|
+
)
|
|
308
|
+
const body = yield* response.json.pipe(
|
|
309
|
+
Effect.mapError((cause) => new OAuthError({ step: "refresh", cause }))
|
|
310
|
+
)
|
|
311
|
+
const tokenResponse = yield* Schema.decodeUnknown(TokenResponseSchema)(body).pipe(
|
|
312
|
+
Effect.mapError((cause) => new OAuthError({ step: "refresh", cause }))
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
const updated: OAuthToken = {
|
|
316
|
+
...token,
|
|
317
|
+
access_token: tokenResponse.access_token,
|
|
318
|
+
refresh_token: tokenResponse.refresh_token,
|
|
319
|
+
expires_at: Date.now() + tokenResponse.expires_in * 1000,
|
|
320
|
+
scope: tokenResponse.scope
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
yield* saveTokenOp(updated)
|
|
324
|
+
return updated
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
const revokeToken = (
|
|
328
|
+
token: OAuthToken,
|
|
329
|
+
config: OAuthConfig
|
|
330
|
+
): Effect.Effect<void, OAuthError> =>
|
|
331
|
+
Effect.gen(function*() {
|
|
332
|
+
// Revoke refresh token (this also invalidates access token)
|
|
333
|
+
const request = yield* HttpClientRequest.post(REVOKE_URL).pipe(
|
|
334
|
+
HttpClientRequest.setHeader("Content-Type", "application/json"),
|
|
335
|
+
HttpClientRequest.bodyJson({
|
|
336
|
+
client_id: config.clientId,
|
|
337
|
+
client_secret: config.clientSecret,
|
|
338
|
+
token: token.refresh_token
|
|
339
|
+
}),
|
|
340
|
+
Effect.mapError((cause) => new OAuthError({ step: "revoke", cause }))
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
const response = yield* httpClient.execute(request).pipe(
|
|
344
|
+
Effect.mapError((cause) => new OAuthError({ step: "revoke", cause }))
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
// Validate response status
|
|
348
|
+
if (response.status >= 400) {
|
|
349
|
+
return yield* Effect.fail(
|
|
350
|
+
new OAuthError({
|
|
351
|
+
step: "revoke",
|
|
352
|
+
cause: `Token revocation failed with status ${response.status}`
|
|
353
|
+
})
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
const configure: ConfluenceAuthService["configure"] = (config) => saveOAuthConfigOp(config)
|
|
359
|
+
|
|
360
|
+
const isConfigured: ConfluenceAuthService["isConfigured"] = () =>
|
|
361
|
+
Effect.gen(function*() {
|
|
362
|
+
const config = yield* loadOAuthConfigOp()
|
|
363
|
+
return config !== null
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
const login: ConfluenceAuthService["login"] = (options) =>
|
|
367
|
+
Effect.gen(function*() {
|
|
368
|
+
const config = yield* getConfig()
|
|
369
|
+
const state = yield* generateUUID()
|
|
370
|
+
|
|
371
|
+
const { codePromise, port, shutdown } = yield* startCallbackServer(state).pipe(
|
|
372
|
+
Effect.provide(HttpServerFactoryLive)
|
|
373
|
+
)
|
|
374
|
+
const authUrl = buildAuthUrl(config.clientId, state, port)
|
|
375
|
+
|
|
376
|
+
yield* Effect.log(`Opening browser for Atlassian login (callback on port ${port})...`)
|
|
377
|
+
yield* openBrowserImpl(authUrl)
|
|
378
|
+
yield* Effect.log("Waiting for authorization (press Ctrl+C to cancel)...")
|
|
379
|
+
|
|
380
|
+
const code = yield* codePromise.pipe(
|
|
381
|
+
Effect.timeout("5 minutes"),
|
|
382
|
+
Effect.catchTag("TimeoutException", () =>
|
|
383
|
+
Effect.fail(new OAuthError({ step: "authorize", cause: "Authorization timed out" })))
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
yield* shutdown
|
|
387
|
+
|
|
388
|
+
yield* Effect.log("Exchanging code for tokens...")
|
|
389
|
+
const tokens = yield* exchangeCodeForTokens(code, config, port)
|
|
390
|
+
|
|
391
|
+
yield* Effect.log("Fetching accessible sites...")
|
|
392
|
+
const sites = yield* getAccessibleResources(tokens.access_token)
|
|
393
|
+
|
|
394
|
+
if (sites.length === 0) {
|
|
395
|
+
return yield* Effect.fail(
|
|
396
|
+
new OAuthError({
|
|
397
|
+
step: "authorize",
|
|
398
|
+
cause: "No Confluence sites found for this account"
|
|
399
|
+
})
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let site: Schema.Schema.Type<typeof AccessibleResourceSchema>
|
|
404
|
+
|
|
405
|
+
if (sites.length > 1) {
|
|
406
|
+
// If siteUrl provided, try to match it
|
|
407
|
+
if (options?.siteUrl) {
|
|
408
|
+
const matched = sites.find((s) =>
|
|
409
|
+
s.url === options.siteUrl
|
|
410
|
+
)
|
|
411
|
+
if (!matched) {
|
|
412
|
+
const available = sites.map((s) => ` - ${s.name}: ${s.url}`).join("\n")
|
|
413
|
+
return yield* Effect.fail(
|
|
414
|
+
new OAuthError({
|
|
415
|
+
step: "authorize",
|
|
416
|
+
cause: `Site '${options.siteUrl}' not found. Available sites:\n${available}`
|
|
417
|
+
})
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
site = matched
|
|
421
|
+
} else {
|
|
422
|
+
// Return sites list for user to choose
|
|
423
|
+
yield* Effect.log("Multiple Confluence sites found. Please select one:")
|
|
424
|
+
for (const s of sites) {
|
|
425
|
+
yield* Effect.log(` - ${s.name}: ${s.url}`)
|
|
426
|
+
}
|
|
427
|
+
yield* Effect.log("\nRun 'confluence auth login --site <url>' to select a site")
|
|
428
|
+
return sites.map((s) => ({ id: s.id, name: s.name, url: s.url }))
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
site = sites[0]!
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
yield* Effect.log("Fetching user info...")
|
|
435
|
+
const user = yield* getUserInfo(tokens.access_token)
|
|
436
|
+
|
|
437
|
+
const tokenData: OAuthToken = {
|
|
438
|
+
access_token: tokens.access_token,
|
|
439
|
+
refresh_token: tokens.refresh_token,
|
|
440
|
+
expires_at: Date.now() + tokens.expires_in * 1000,
|
|
441
|
+
scope: tokens.scope,
|
|
442
|
+
cloud_id: site.id,
|
|
443
|
+
site_url: site.url,
|
|
444
|
+
user: {
|
|
445
|
+
account_id: user.account_id,
|
|
446
|
+
name: user.name,
|
|
447
|
+
email: user.email
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
yield* saveTokenOp(tokenData)
|
|
452
|
+
yield* Effect.log(`Logged in as ${user.name} (${user.email})`)
|
|
453
|
+
return undefined
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
const logout: ConfluenceAuthService["logout"] = () =>
|
|
457
|
+
Effect.gen(function*() {
|
|
458
|
+
const token = yield* loadTokenOp()
|
|
459
|
+
if (token === null) {
|
|
460
|
+
yield* Effect.log("Not logged in")
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Try to revoke the token with Atlassian (best effort)
|
|
465
|
+
const config = yield* loadOAuthConfigOp()
|
|
466
|
+
if (config !== null) {
|
|
467
|
+
yield* revokeToken(token, config).pipe(
|
|
468
|
+
Effect.tap(() => Effect.log("Token revoked with Atlassian")),
|
|
469
|
+
Effect.catchAll((error) => Effect.log(`Warning: Failed to revoke token: ${error.message}`))
|
|
470
|
+
)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
yield* deleteTokenOp()
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
const getAccessToken: ConfluenceAuthService["getAccessToken"] = () =>
|
|
477
|
+
Effect.gen(function*() {
|
|
478
|
+
const token = yield* loadTokenOp()
|
|
479
|
+
if (token === null) {
|
|
480
|
+
return yield* Effect.fail(new AuthMissingError())
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const now = Date.now()
|
|
484
|
+
const buffer = 5 * 60 * 1000 // 5 minutes
|
|
485
|
+
|
|
486
|
+
if (token.expires_at - buffer > now) {
|
|
487
|
+
return token.access_token
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Check if refresh is already in progress
|
|
491
|
+
const existing = yield* Ref.get(refreshLock)
|
|
492
|
+
if (Option.isSome(existing)) {
|
|
493
|
+
// Wait for existing refresh to complete
|
|
494
|
+
const refreshed = yield* Deferred.await(existing.value)
|
|
495
|
+
return refreshed.access_token
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Start new refresh operation
|
|
499
|
+
const deferred = yield* Deferred.make<OAuthToken, OAuthError | FileSystemError>()
|
|
500
|
+
yield* Ref.set(refreshLock, Option.some(deferred))
|
|
501
|
+
|
|
502
|
+
const config = yield* getConfig()
|
|
503
|
+
yield* Effect.log("Token expired, refreshing...")
|
|
504
|
+
|
|
505
|
+
const result = yield* refreshToken(token, config).pipe(
|
|
506
|
+
Effect.tap((refreshed) => Deferred.succeed(deferred, refreshed)),
|
|
507
|
+
Effect.tapError((error) => Deferred.fail(deferred, error)),
|
|
508
|
+
Effect.ensuring(Ref.set(refreshLock, Option.none())),
|
|
509
|
+
Effect.catchTag("OAuthError", (error) => {
|
|
510
|
+
// If refresh fails (e.g., refresh token expired), clear tokens and prompt re-login
|
|
511
|
+
if (error.step === "refresh") {
|
|
512
|
+
return Effect.gen(function*() {
|
|
513
|
+
yield* deleteTokenOp()
|
|
514
|
+
return yield* Effect.fail(
|
|
515
|
+
new OAuthError({
|
|
516
|
+
step: "refresh",
|
|
517
|
+
cause: "Refresh token expired. Please run 'confluence auth login' to re-authenticate."
|
|
518
|
+
})
|
|
519
|
+
)
|
|
520
|
+
})
|
|
521
|
+
}
|
|
522
|
+
return Effect.fail(error)
|
|
523
|
+
})
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
return result.access_token
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
const getCloudId: ConfluenceAuthService["getCloudId"] = () =>
|
|
530
|
+
Effect.gen(function*() {
|
|
531
|
+
const token = yield* loadTokenOp()
|
|
532
|
+
if (token === null) {
|
|
533
|
+
return yield* Effect.fail(new AuthMissingError())
|
|
534
|
+
}
|
|
535
|
+
return token.cloud_id
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
const getCurrentUser: ConfluenceAuthService["getCurrentUser"] = () =>
|
|
539
|
+
Effect.gen(function*() {
|
|
540
|
+
const token = yield* loadTokenOp()
|
|
541
|
+
return token?.user ?? null
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
const isLoggedIn: ConfluenceAuthService["isLoggedIn"] = () =>
|
|
545
|
+
Effect.gen(function*() {
|
|
546
|
+
const token = yield* loadTokenOp()
|
|
547
|
+
return token !== null
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
return ConfluenceAuth.of({
|
|
551
|
+
configure,
|
|
552
|
+
isConfigured,
|
|
553
|
+
login,
|
|
554
|
+
logout,
|
|
555
|
+
getAccessToken,
|
|
556
|
+
getCloudId,
|
|
557
|
+
getCurrentUser,
|
|
558
|
+
isLoggedIn
|
|
559
|
+
})
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Layer for ConfluenceAuth service.
|
|
564
|
+
*
|
|
565
|
+
* @category Layers
|
|
566
|
+
*/
|
|
567
|
+
export const layer: Layer.Layer<
|
|
568
|
+
ConfluenceAuth,
|
|
569
|
+
never,
|
|
570
|
+
HttpClient.HttpClient | CommandExecutor.CommandExecutor
|
|
571
|
+
> = Layer.effect(ConfluenceAuth, make)
|