File tree 4 files changed +87
-8
lines changed
4 files changed +87
-8
lines changed Original file line number Diff line number Diff line change @@ -28,14 +28,14 @@ func extractParams(from url: URL) -> [String: String] {
28
28
private func extractParams( from fragment: String ) -> [ URLQueryItem ] {
29
29
let components =
30
30
fragment
31
- . split ( separator: " & " )
32
- . map { $0. split ( separator: " = " ) }
31
+ . split ( separator: " & " )
32
+ . map { $0. split ( separator: " = " ) }
33
33
34
34
return
35
35
components
36
- . compactMap {
37
- $0. count == 2
38
- ? URLQueryItem ( name: String ( $0 [ 0 ] ) , value: String ( $0 [ 1 ] ) )
39
- : nil
40
- }
36
+ . compactMap {
37
+ $0. count == 2
38
+ ? URLQueryItem ( name: String ( $0 [ 0 ] ) , value: String ( $0 [ 1 ] ) )
39
+ : nil
40
+ }
41
41
}
Original file line number Diff line number Diff line change
1
+ import Foundation
2
+ import HTTPTypes
3
+ import IssueReporting
4
+
5
+ let base64UrlRegex = try ! NSRegularExpression (
6
+ pattern: " ^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$) " , options: . caseInsensitive)
7
+
8
+ /// Checks that the value somewhat looks like a JWT, does not do any additional parsing or verification.
9
+ func isJWT( _ value: String ) -> Bool {
10
+ var token = value
11
+
12
+ if token. hasPrefix ( " Bearer " ) {
13
+ token = String ( token. dropFirst ( " Bearer " . count) )
14
+ }
15
+
16
+ token = token. trimmingCharacters ( in: . whitespacesAndNewlines)
17
+
18
+ guard !token. isEmpty else {
19
+ return false
20
+ }
21
+
22
+ let parts = token. split ( separator: " . " )
23
+
24
+ guard parts. count == 3 else {
25
+ return false
26
+ }
27
+
28
+ for part in parts {
29
+ if part. count < 4 || !isBase64Url( String ( part) ) {
30
+ return false
31
+ }
32
+ }
33
+
34
+ return true
35
+ }
36
+
37
+ func isBase64Url( _ value: String ) -> Bool {
38
+ let range = NSRange ( location: 0 , length: value. utf16. count)
39
+ return base64UrlRegex. firstMatch ( in: value, options: [ ] , range: range) != nil
40
+ }
41
+
42
+ func checkAuthorizationHeader(
43
+ _ headers: HTTPFields ,
44
+ fileID: StaticString = #fileID,
45
+ filePath: StaticString = #filePath,
46
+ line: UInt = #line,
47
+ column: UInt = #column
48
+ ) {
49
+ guard let authorization = headers [ . authorization] else { return }
50
+
51
+ if !isJWT( authorization) {
52
+ reportIssue (
53
+ " Authorization header does not contain a JWT " ,
54
+ fileID: fileID,
55
+ filePath: filePath,
56
+ line: line,
57
+ column: column
58
+ )
59
+ }
60
+ }
Original file line number Diff line number Diff line change @@ -170,6 +170,8 @@ public final class SupabaseClient: Sendable {
170
170
] )
171
171
. merging ( with: HTTPFields ( options. global. headers) )
172
172
173
+ checkAuthorizationHeader ( _headers)
174
+
173
175
// default storage key uses the supabase project ref as a namespace
174
176
let defaultStorageKey = " sb- \( supabaseURL. host!. split ( separator: " . " ) [ 0 ] ) -auth-token "
175
177
@@ -351,7 +353,7 @@ public final class SupabaseClient: Sendable {
351
353
let token = try ? await _getAccessToken ( )
352
354
353
355
var request = request
354
- if let token {
356
+ if let token, isJWT ( token ) , request . value ( forHTTPHeaderField : " Authorization " ) == nil {
355
357
request. setValue ( " Bearer \( token) " , forHTTPHeaderField: " Authorization " )
356
358
}
357
359
return request
Original file line number Diff line number Diff line change
1
+ @testable import Supabase
2
+ import XCTest
3
+
4
+ final class HeleperTests : XCTestCase {
5
+ func testIsJWT( ) {
6
+ XCTAssertTrue ( isJWT ( " eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c " ) )
7
+ XCTAssertTrue ( isJWT ( " Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c " ) )
8
+ XCTAssertFalse ( isJWT ( " invalid.token.format " ) )
9
+ XCTAssertFalse ( isJWT ( " part1.part2.part3.part4 " ) )
10
+ XCTAssertFalse ( isJWT ( " part1.part2 " ) )
11
+ XCTAssertFalse ( isJWT ( " .. " ) )
12
+ XCTAssertFalse ( isJWT ( " a.a.a " ) )
13
+ XCTAssertFalse ( isJWT ( " eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.*&@!.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c " ) )
14
+ XCTAssertFalse ( isJWT ( " " ) )
15
+ XCTAssertFalse ( isJWT ( " Bearer " ) )
16
+ }
17
+ }
You can’t perform that action at this time.
0 commit comments