Skip to content

Commit 9b9b205

Browse files
authored
chore: kickoff release
2 parents 36e5e92 + 949d160 commit 9b9b205

File tree

12 files changed

+104
-168
lines changed

12 files changed

+104
-168
lines changed

AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequestAuth.swift

+7-27
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import Foundation
1010

1111
public enum AppSyncRealTimeRequestAuth {
12+
private static let jsonEncoder = JSONEncoder()
13+
private static let jsonDecoder = JSONDecoder()
14+
1215
case authToken(AuthToken)
1316
case apiKey(ApiKey)
1417
case iam(IAM)
@@ -31,33 +34,10 @@ public enum AppSyncRealTimeRequestAuth {
3134
let amzDate: String
3235
}
3336

34-
public struct URLQuery {
35-
let header: AppSyncRealTimeRequestAuth
36-
let payload: String
37-
38-
init(header: AppSyncRealTimeRequestAuth, payload: String = "{}") {
39-
self.header = header
40-
self.payload = payload
41-
}
42-
43-
func withBaseURL(_ url: URL, encoder: JSONEncoder? = nil) -> URL {
44-
let jsonEncoder: JSONEncoder = encoder ?? JSONEncoder()
45-
guard let headerJsonData = try? jsonEncoder.encode(header) else {
46-
return url
47-
}
48-
49-
guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
50-
else {
51-
return url
52-
}
53-
54-
urlComponents.queryItems = [
55-
URLQueryItem(name: "header", value: headerJsonData.base64EncodedString()),
56-
URLQueryItem(name: "payload", value: try? payload.base64EncodedString())
57-
]
58-
59-
return urlComponents.url ?? url
60-
}
37+
var authHeaders: [String: String] {
38+
(try? Self.jsonEncoder.encode(self)).flatMap {
39+
try? Self.jsonDecoder.decode([String: String].self, from: $0)
40+
} ?? [:]
6141
}
6242
}
6343

AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/APIKeyAuthInterceptor.swift

+5-4
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ class APIKeyAuthInterceptor {
2121
}
2222

2323
extension APIKeyAuthInterceptor: WebSocketInterceptor {
24-
func interceptConnection(url: URL) async -> URL {
24+
25+
func interceptConnection(request: URLRequest) async -> URLRequest {
26+
guard let url = request.url else { return request }
27+
2528
let authHeader = getAuthHeader(apiKey, AppSyncRealTimeClientFactory.appSyncApiEndpoint(url).host!)
26-
return AppSyncRealTimeRequestAuth.URLQuery(
27-
header: .apiKey(authHeader)
28-
).withBaseURL(url)
29+
return request.injectAppSyncAuthToRequestHeader(auth: .apiKey(authHeader))
2930
}
3031
}
3132

AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/AuthTokenInterceptor.swift

+6-5
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,16 @@ extension AuthTokenInterceptor: AppSyncRequestInterceptor {
5757
}
5858

5959
extension AuthTokenInterceptor: WebSocketInterceptor {
60-
func interceptConnection(url: URL) async -> URL {
60+
func interceptConnection(request: URLRequest) async -> URLRequest {
61+
guard let url = request.url else { return request }
6162
let authToken = await getAuthToken()
6263

63-
return AppSyncRealTimeRequestAuth.URLQuery(
64-
header: .authToken(.init(
64+
return request.injectAppSyncAuthToRequestHeader(
65+
auth: .authToken(.init(
6566
host: AppSyncRealTimeClientFactory.appSyncApiEndpoint(url).host!,
6667
authToken: authToken
67-
))
68-
).withBaseURL(url)
68+
)
69+
))
6970
}
7071
}
7172

AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift

+5-6
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,14 @@ class IAMAuthInterceptor {
8888
}
8989

9090
extension IAMAuthInterceptor: WebSocketInterceptor {
91-
func interceptConnection(url: URL) async -> URL {
91+
92+
func interceptConnection(request: URLRequest) async -> URLRequest {
93+
guard let url = request.url else { return request }
9294
let connectUrl = AppSyncRealTimeClientFactory.appSyncApiEndpoint(url).appendingPathComponent("connect")
9395
guard let authHeader = await getAuthHeader(connectUrl, with: "{}") else {
94-
return connectUrl
96+
return request
9597
}
96-
97-
return AppSyncRealTimeRequestAuth.URLQuery(
98-
header: .iam(authHeader)
99-
).withBaseURL(url)
98+
return request.injectAppSyncAuthToRequestHeader(auth: .iam(authHeader))
10099
}
101100
}
102101

AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift

+7-5
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ actor AppSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol {
127127
extension AppSyncRealTimeClientFactory {
128128

129129
/**
130-
Converting appsync api url to realtime api url
131-
1. api.example.com/graphql -> api.example.com/graphql/realtime
132-
2. abc.appsync-api.us-east-1.amazonaws.com/graphql -> abc.appsync-realtime-api.us-east-1.amazonaws.com/graphql
130+
Converting appsync api url to realtime api url, realtime endpoint has scheme 'wss'
131+
1. api.example.com/graphql -> wss://api.example.com/graphql/realtime
132+
2. abc.appsync-api.us-east-1.amazonaws.com/graphql -> wss://abc.appsync-realtime-api.us-east-1.amazonaws.com/graphql
133133
*/
134134
static func appSyncRealTimeEndpoint(_ url: URL) -> URL {
135135
guard let host = url.host else {
@@ -145,6 +145,7 @@ extension AppSyncRealTimeClientFactory {
145145
}
146146

147147
urlComponents.host = host.replacingOccurrences(of: "appsync-api", with: "appsync-realtime-api")
148+
urlComponents.scheme = "wss"
148149
guard let realTimeUrl = urlComponents.url else {
149150
return url
150151
}
@@ -153,9 +154,9 @@ extension AppSyncRealTimeClientFactory {
153154
}
154155

155156
/**
156-
Converting appsync realtime api url to api url
157+
Converting appsync realtime api url to api url, api endpoint has scheme 'https'
157158
1. api.example.com/graphql/realtime -> api.example.com/graphql
158-
2. abc.appsync-realtime-api.us-east-1.amazonaws.com/graphql -> abc.appsync-api.us-east-1.amazonaws.com/graphql
159+
2. abc.appsync-realtime-api.us-east-1.amazonaws.com/graphql -> https://abc.appsync-api.us-east-1.amazonaws.com/graphql
159160
*/
160161
static func appSyncApiEndpoint(_ url: URL) -> URL {
161162
guard let host = url.host else {
@@ -174,6 +175,7 @@ extension AppSyncRealTimeClientFactory {
174175
}
175176

176177
urlComponents.host = host.replacingOccurrences(of: "appsync-realtime-api", with: "appsync-api")
178+
urlComponents.scheme = "https"
177179
guard let apiUrl = urlComponents.url else {
178180
return url
179181
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
9+
import Foundation
10+
11+
extension URLRequest {
12+
func injectAppSyncAuthToRequestHeader(auth: AppSyncRealTimeRequestAuth) -> URLRequest {
13+
var requstCopy = self
14+
auth.authHeaders.forEach { requstCopy.setValue($0.value, forHTTPHeaderField: $0.key) }
15+
return requstCopy
16+
}
17+
}

AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeRequestAuthTests.swift

-55
Original file line numberDiff line numberDiff line change
@@ -147,61 +147,6 @@ class AppSyncRealTimeRequestAuthTests: XCTestCase {
147147
""".shrink())
148148
}
149149

150-
func testAppSyncRealTimeRequestAuth_URLQueryWithCognitoAuthHeader() {
151-
let expectedURL = """
152-
https://example.com?\
153-
header=eyJBdXRob3JpemF0aW9uIjoiNDk4NTljN2MtNzQwNS00ZDU4LWFmZjctNTJiZ\
154-
TRiNDczNTU3IiwiaG9zdCI6ImV4YW1wbGUuY29tIn0%3D\
155-
&payload=e30%3D
156-
"""
157-
let encodedURL = AppSyncRealTimeRequestAuth.URLQuery(
158-
header: .authToken(.init(
159-
host: "example.com",
160-
authToken: "49859c7c-7405-4d58-aff7-52be4b473557"
161-
))
162-
).withBaseURL(URL(string: "https://example.com")!, encoder: jsonEncoder)
163-
XCTAssertEqual(encodedURL.absoluteString, expectedURL)
164-
}
165-
166-
func testAppSyncRealTimeRequestAuth_URLQueryWithApiKeyAuthHeader() {
167-
let expectedURL = """
168-
https://example.com?\
169-
header=eyJob3N0IjoiZXhhbXBsZS5jb20iLCJ4LWFtei1kYXRlIjoiOWUwZTJkZjktMmVlNy00NjU5L\
170-
TgzNjItMWM4ODFlMTE4YzlmIiwieC1hcGkta2V5IjoiNjVlMmZhY2EtOGUxZS00ZDM3LThkYzctNjQ0N\
171-
2Q5Njk4MjQ3In0%3D\
172-
&payload=e30%3D
173-
"""
174-
let encodedURL = AppSyncRealTimeRequestAuth.URLQuery(
175-
header: .apiKey(.init(
176-
host: "example.com",
177-
apiKey: "65e2faca-8e1e-4d37-8dc7-6447d9698247",
178-
amzDate: "9e0e2df9-2ee7-4659-8362-1c881e118c9f"
179-
))
180-
).withBaseURL(URL(string: "https://example.com")!, encoder: jsonEncoder)
181-
XCTAssertEqual(encodedURL.absoluteString, expectedURL)
182-
}
183-
184-
func testAppSyncRealTimeRequestAuth_URLQueryWithIAMAuthHeader() {
185-
186-
let expectedURL = """
187-
https://example.com?\
188-
header=eyJhY2NlcHQiOiJhcHBsaWNhdGlvblwvanNvbiwgdGV4dFwvamF2YXNjcmlwdCIsIkF1dGhvcml6YXR\
189-
pb24iOiJjOWRhZDg5Ny05MGQxLTRhNGMtYTVjOS0yYjM2YTI0NzczNWYiLCJjb250ZW50LWVuY29kaW5nIjoiY\
190-
W16LTEuMCIsImNvbnRlbnQtdHlwZSI6ImFwcGxpY2F0aW9uXC9qc29uOyBjaGFyc2V0PVVURi04IiwiaG9zdCI\
191-
6ImV4YW1wbGUuY29tIiwieC1hbXotZGF0ZSI6IjllMGUyZGY5LTJlZTctNDY1OS04MzYyLTFjODgxZTExOGM5Z\
192-
iIsIlgtQW16LVNlY3VyaXR5LVRva2VuIjoiZTdlNjI2OWUtZmRhMS00ZGUwLThiZGItYmFhN2I2ZGQwYTBkIn0%3D\
193-
&payload=e30%3D
194-
"""
195-
let encodedURL = AppSyncRealTimeRequestAuth.URLQuery(
196-
header: .iam(.init(
197-
host: "example.com",
198-
authToken: "c9dad897-90d1-4a4c-a5c9-2b36a247735f",
199-
securityToken: "e7e6269e-fda1-4de0-8bdb-baa7b6dd0a0d",
200-
amzDate: "9e0e2df9-2ee7-4659-8362-1c881e118c9f"))
201-
).withBaseURL(URL(string: "https://example.com")!, encoder: jsonEncoder)
202-
XCTAssertEqual(encodedURL.absoluteString, expectedURL)
203-
}
204-
205150
private func toJson(_ value: Encodable) -> String? {
206151
return try? String(data: jsonEncoder.encode(value), encoding: .utf8)
207152
}

AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/SubscriptionInterceptor/APIKeyAuthInterceptorTests.swift

+5-12
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,13 @@ import Amplify
1212

1313
class APIKeyAuthInterceptorTests: XCTestCase {
1414

15-
func testInterceptConnection_addApiKeySignatureInURLQuery() async {
15+
func testInterceptConnection_addApiKeyInRequestHeader() async {
1616
let apiKey = UUID().uuidString
1717
let interceptor = APIKeyAuthInterceptor(apiKey: apiKey)
18-
let resultUrl = await interceptor.interceptConnection(url: URL(string: "https://example.com")!)
19-
guard let components = URLComponents(url: resultUrl, resolvingAgainstBaseURL: false) else {
20-
XCTFail("Failed to decode decorated URL")
21-
return
22-
}
23-
24-
let header = components.queryItems?.first { $0.name == "header" }
25-
XCTAssertNotNil(header?.value)
26-
let headerData = try! header?.value!.base64DecodedString().data(using: .utf8)
27-
let decodedHeader = try! JSONDecoder().decode(JSONValue.self, from: headerData!)
28-
XCTAssertEqual(decodedHeader["x-api-key"]?.stringValue, apiKey)
18+
let resultUrlRequest = await interceptor.interceptConnection(request: URLRequest(url: URL(string: "https://example.com")!))
19+
20+
let header = resultUrlRequest.value(forHTTPHeaderField: "x-api-key")
21+
XCTAssertEqual(header, apiKey)
2922
}
3023

3124
func testInterceptRequest_appendAuthInfoInPayload() async {

AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/SubscriptionInterceptor/CognitoAuthInterceptorTests.swift

+8-40
Original file line numberDiff line numberDiff line change
@@ -13,56 +13,24 @@ import Amplify
1313

1414
class CognitoAuthInterceptorTests: XCTestCase {
1515

16-
func testInterceptConnection_withAuthTokenProvider_appendCorrectAuthHeaderToQuery() async {
16+
func testInterceptConnection_withAuthTokenProvider_appendCorrectAuthHeader() async {
1717
let authTokenProvider = MockAuthTokenProvider()
1818
let interceptor = AuthTokenInterceptor(authTokenProvider: authTokenProvider)
1919

20-
let decoratedURL = await interceptor.interceptConnection(url: URL(string: "https://example.com")!)
21-
guard let components = URLComponents(url: decoratedURL, resolvingAgainstBaseURL: false) else {
22-
XCTFail("Failed to get url components from decorated URL")
23-
return
24-
}
20+
let decoratedURLRequest = await interceptor.interceptConnection(request: URLRequest(url:URL(string: "https://example.com")!))
2521

26-
guard let queryHeaderString =
27-
try? components.queryItems?.first(where: { $0.name == "header" })?.value?.base64DecodedString()
28-
else {
29-
XCTFail("Failed to extract header field from query string")
30-
return
31-
}
32-
33-
guard let queryHeader = try? JSONDecoder().decode(JSONValue.self, from: queryHeaderString.data(using: .utf8)!)
34-
else {
35-
XCTFail("Failed to decode query header to json object")
36-
return
37-
}
38-
XCTAssertEqual(authTokenProvider.authToken, queryHeader.Authorization?.stringValue)
39-
XCTAssertEqual("example.com", queryHeader.host?.stringValue)
22+
XCTAssertEqual(authTokenProvider.authToken, decoratedURLRequest.value(forHTTPHeaderField: "Authorization"))
23+
XCTAssertEqual("example.com", decoratedURLRequest.value(forHTTPHeaderField: "host"))
4024
}
4125

42-
func testInterceptConnection_withAuthTokenProviderFailed_appendEmptyAuthHeaderToQuery() async {
26+
func testInterceptConnection_withAuthTokenProviderFailed_appendEmptyAuthHeader() async {
4327
let authTokenProvider = MockAuthTokenProviderFailed()
4428
let interceptor = AuthTokenInterceptor(authTokenProvider: authTokenProvider)
4529

46-
let decoratedURL = await interceptor.interceptConnection(url: URL(string: "https://example.com")!)
47-
guard let components = URLComponents(url: decoratedURL, resolvingAgainstBaseURL: false) else {
48-
XCTFail("Failed to get url components from decorated URL")
49-
return
50-
}
30+
let decoratedURLRequest = await interceptor.interceptConnection(request: URLRequest(url:URL(string: "https://example.com")!))
5131

52-
guard let queryHeaderString =
53-
try? components.queryItems?.first(where: { $0.name == "header" })?.value?.base64DecodedString()
54-
else {
55-
XCTFail("Failed to extract header field from query string")
56-
return
57-
}
58-
59-
guard let queryHeader = try? JSONDecoder().decode(JSONValue.self, from: queryHeaderString.data(using: .utf8)!)
60-
else {
61-
XCTFail("Failed to decode query header to json object")
62-
return
63-
}
64-
XCTAssertEqual("", queryHeader.Authorization?.stringValue)
65-
XCTAssertEqual("example.com", queryHeader.host?.stringValue)
32+
XCTAssertEqual("", decoratedURLRequest.value(forHTTPHeaderField: "Authorization"))
33+
XCTAssertEqual("example.com", decoratedURLRequest.value(forHTTPHeaderField: "host"))
6634
}
6735

6836
func testInterceptRequest_withAuthTokenProvider_appendCorrectAuthInfoToPayload() async {

AmplifyPlugins/API/Tests/AWSAPIPluginTests/SubscriptionFactory/AppSyncRealTimeClientFactoryTests.swift

+27-3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ class AppSyncRealTimeClientFactoryTests: XCTestCase {
1515
let appSyncEndpoint = URL(string: "https://abc.appsync-api.amazonaws.com/graphql")!
1616
XCTAssertEqual(
1717
AppSyncRealTimeClientFactory.appSyncRealTimeEndpoint(appSyncEndpoint),
18-
URL(string: "https://abc.appsync-realtime-api.amazonaws.com/graphql")
18+
URL(string: "wss://abc.appsync-realtime-api.amazonaws.com/graphql")
1919
)
2020
}
2121

2222
func testAppSyncRealTimeEndpoint_withAWSAppSyncRealTimeDomain_returnTheSameDomain() {
23-
let appSyncEndpoint = URL(string: "https://abc.appsync-realtime-api.amazonaws.com/graphql")!
23+
let appSyncEndpoint = URL(string: "wss://abc.appsync-realtime-api.amazonaws.com/graphql")!
2424
XCTAssertEqual(
2525
AppSyncRealTimeClientFactory.appSyncRealTimeEndpoint(appSyncEndpoint),
26-
URL(string: "https://abc.appsync-realtime-api.amazonaws.com/graphql")
26+
URL(string: "wss://abc.appsync-realtime-api.amazonaws.com/graphql")
2727
)
2828
}
2929

@@ -34,4 +34,28 @@ class AppSyncRealTimeClientFactoryTests: XCTestCase {
3434
URL(string: "https://test.example.com/graphql/realtime")
3535
)
3636
}
37+
38+
func testAppSyncApiEndpoint_withAWSAppSyncRealTimeDomain_returnCorrectApiDomain() {
39+
let appSyncEndpoint = URL(string: "wss://abc.appsync-realtime-api.amazonaws.com/graphql")!
40+
XCTAssertEqual(
41+
AppSyncRealTimeClientFactory.appSyncApiEndpoint(appSyncEndpoint),
42+
URL(string: "https://abc.appsync-api.amazonaws.com/graphql")
43+
)
44+
}
45+
46+
func testAppSyncApiEndpoint_withAWSAppSyncApiDomain_returnTheSameDomain() {
47+
let appSyncEndpoint = URL(string: "https://abc.appsync-api.amazonaws.com/graphql")!
48+
XCTAssertEqual(
49+
AppSyncRealTimeClientFactory.appSyncApiEndpoint(appSyncEndpoint),
50+
URL(string: "https://abc.appsync-api.amazonaws.com/graphql")
51+
)
52+
}
53+
54+
func testAppSyncApiEndpoint_withCustomDomain_returnCorrectRealtimePath() {
55+
let appSyncEndpoint = URL(string: "https://test.example.com/graphql")!
56+
XCTAssertEqual(
57+
AppSyncRealTimeClientFactory.appSyncApiEndpoint(appSyncEndpoint),
58+
URL(string: "https://test.example.com/graphql")
59+
)
60+
}
3761
}

0 commit comments

Comments
 (0)