tangled
alpha
login
or
join now
sparrowtek.com
/
CoreATProtocol
1
fork
atom
this repo has no description
1
fork
atom
overview
issues
pulls
pipelines
first pass at using libraries to implement oauth
radmakr.com
5 months ago
7ad89282
2dfb69af
+94
-1
2 changed files
expand all
collapse all
unified
split
Package.swift
Sources
CoreATProtocol
LoginService.swift
+9
-1
Package.swift
···
17
17
targets: ["CoreATProtocol"]
18
18
),
19
19
],
20
20
+
dependencies: [
21
21
+
.package(url: "https://github.com/ChimeHQ/OAuthenticator", branch: "main"),
22
22
+
.package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0"),
23
23
+
],
20
24
targets: [
21
25
.target(
22
22
-
name: "CoreATProtocol"
26
26
+
name: "CoreATProtocol",
27
27
+
dependencies: [
28
28
+
"OAuthenticator",
29
29
+
.product(name: "JWTKit", package: "jwt-kit"),
30
30
+
],
23
31
),
24
32
.testTarget(
25
33
name: "CoreATProtocolTests",
+85
Sources/CoreATProtocol/LoginService.swift
···
1
1
+
//
2
2
+
// LoginService.swift
3
3
+
// CoreATProtocol
4
4
+
//
5
5
+
// Created by Thomas Rademaker on 10/17/25.
6
6
+
//
7
7
+
8
8
+
import Foundation
9
9
+
import OAuthenticator
10
10
+
import JWTKit
11
11
+
import CryptoKit
12
12
+
13
13
+
@APActor
14
14
+
class LoginService {
15
15
+
private var keys: JWTKeyCollection
16
16
+
private var privateKey: ES256PrivateKey
17
17
+
18
18
+
public init() async {
19
19
+
// Create keys once during initialization
20
20
+
self.privateKey = ES256PrivateKey()
21
21
+
self.keys = JWTKeyCollection()
22
22
+
// Add the key to the collection
23
23
+
await self.keys.add(ecdsa: privateKey)
24
24
+
}
25
25
+
26
26
+
public func login(account: String, clientMetadataEndpoint: String) async throws {
27
27
+
let provider = URLSession.defaultProvider
28
28
+
let host = APEnvironment.current.host ?? ""
29
29
+
let server = if host.hasPrefix("https://") {
30
30
+
String(host.dropFirst(8))
31
31
+
} else if host.hasPrefix("http://") {
32
32
+
String(host.dropFirst(7))
33
33
+
} else { host }
34
34
+
35
35
+
let clientConfig = try await ClientMetadata.load(for: clientMetadataEndpoint, provider: provider)
36
36
+
let serverConfig = try await ServerMetadata.load(for: server, provider: provider)
37
37
+
38
38
+
// Create storage for persisting login state
39
39
+
let loginStorage = LoginStorage {
40
40
+
// Implement retrieving stored login
41
41
+
// Return stored Login if it exists, or nil
42
42
+
return nil
43
43
+
} storeLogin: { login in
44
44
+
// Implement storing the login
45
45
+
// Store the login securely
46
46
+
47
47
+
print("LOGIN: \(login)")
48
48
+
}
49
49
+
50
50
+
let jwtGenerator: DPoPSigner.JWTGenerator = { params in
51
51
+
try await self.generateJWT(params: params)
52
52
+
}
53
53
+
54
54
+
let tokenHandling = Bluesky.tokenHandling(account: account, server: serverConfig, jwtGenerator: jwtGenerator)
55
55
+
let config = Authenticator.Configuration(appCredentials: clientConfig.credentials, loginStorage: loginStorage, tokenHandling: tokenHandling, mode: .automatic)
56
56
+
let authenticator = Authenticator(config: config)
57
57
+
try await authenticator.authenticate()
58
58
+
}
59
59
+
60
60
+
private func generateJWT(params: DPoPSigner.JWTParameters) async throws -> String {
61
61
+
// Create DPoP payload using existing keys
62
62
+
let payload = DPoPPayload(
63
63
+
htm: params.httpMethod,
64
64
+
htu: params.requestEndpoint,
65
65
+
iat: .init(value: .now),
66
66
+
jti: .init(value: UUID().uuidString),
67
67
+
nonce: params.nonce
68
68
+
)
69
69
+
70
70
+
// Sign with existing keys
71
71
+
return try await self.keys.sign(payload)
72
72
+
}
73
73
+
}
74
74
+
75
75
+
private struct DPoPPayload: JWTPayload {
76
76
+
let htm: String
77
77
+
let htu: String
78
78
+
let iat: IssuedAtClaim
79
79
+
let jti: IDClaim
80
80
+
let nonce: String?
81
81
+
82
82
+
func verify(using key: some JWTAlgorithm) throws {
83
83
+
// No additional verification needed
84
84
+
}
85
85
+
}