Skip to content

Commit 3733dac

Browse files
authored
feat: Add 'Advanced' page to 'Add Server' view & Add basic authentication (#74)
#68
1 parent 15ae6ae commit 3733dac

9 files changed

Lines changed: 132 additions & 21 deletions

File tree

Localization/Localizations/Localizable.xcstrings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,9 @@
13681368
}
13691369
}
13701370
}
1371+
},
1372+
"Basic Authentication" : {
1373+
13711374
},
13721375
"Books" : {
13731376
"extractionState" : "manual",

qBitControl.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
90BCFE8D2DB4262400A54003 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 90BCFE8C2DB4262400A54003 /* Localizable.xcstrings */; };
8787
90DCA7E52B51C393008A9C1B /* ServersHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90DCA7E42B51C393008A9C1B /* ServersHelper.swift */; };
8888
90E1D0542D30438F00B81F12 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90E1D0532D30438C00B81F12 /* Version.swift */; };
89+
90E6DD6F2DFF407000BC1D76 /* ServerAdvancedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90E6DD6E2DFF406A00BC1D76 /* ServerAdvancedView.swift */; };
8990
90E86FFE2C81C89A00F4EA01 /* qBitDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90E86FFD2C81C89A00F4EA01 /* qBitDataClass.swift */; };
9091
90EF6A492909267A001E9E7F /* AuthClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90EF6A482909267A001E9E7F /* AuthClass.swift */; };
9192
90EF6A4D29093142001E9E7F /* qBitRequestClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90EF6A4C29093142001E9E7F /* qBitRequestClass.swift */; };
@@ -194,6 +195,7 @@
194195
90BCFE8C2DB4262400A54003 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
195196
90DCA7E42B51C393008A9C1B /* ServersHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersHelper.swift; sourceTree = "<group>"; };
196197
90E1D0532D30438C00B81F12 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = "<group>"; };
198+
90E6DD6E2DFF406A00BC1D76 /* ServerAdvancedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerAdvancedView.swift; sourceTree = "<group>"; };
197199
90E86FFD2C81C89A00F4EA01 /* qBitDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = qBitDataClass.swift; sourceTree = "<group>"; };
198200
90EF6A482909267A001E9E7F /* AuthClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthClass.swift; sourceTree = "<group>"; };
199201
90EF6A4C29093142001E9E7F /* qBitRequestClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = qBitRequestClass.swift; sourceTree = "<group>"; };
@@ -315,6 +317,7 @@
315317
90726B382CDA22190032993E /* ServersViews */ = {
316318
isa = PBXGroup;
317319
children = (
320+
90E6DD6E2DFF406A00BC1D76 /* ServerAdvancedView.swift */,
318321
90726B352CDA22190032993E /* ServerAddView.swift */,
319322
90726B362CDA22190032993E /* ServerRowView.swift */,
320323
90726B372CDA22190032993E /* ServersView.swift */,
@@ -649,6 +652,7 @@
649652
907C632A2DAEDACA004B1F41 /* SearchStartResult.swift in Sources */,
650653
90726B652CDA22190032993E /* PeersRowView.swift in Sources */,
651654
90726B662CDA22190032993E /* PeersView.swift in Sources */,
655+
90E6DD6F2DFF407000BC1D76 /* ServerAdvancedView.swift in Sources */,
652656
90726B672CDA22190032993E /* TrackerRow.swift in Sources */,
653657
907023322CDA62FD007B2199 /* TorrentListView.swift in Sources */,
654658
90726B682CDA22190032993E /* TrackersView.swift in Sources */,

qBitControl/Classes/AuthClass.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class Auth {
1616
cookies[id] = cookie
1717
}
1818

19-
static func getCookie(url: String, username: String, password: String, isSuccess: @escaping (Bool) -> Void, setCookie: Bool = true) async {
19+
static func getCookie(url: String, username: String, password: String, basicAuth: Server.BasicAuth? = nil, isSuccess: @escaping (Bool) -> Void, setCookie: Bool = true) async {
2020
let urlString = url
2121
guard let url = URL(string: "\(url)/api/v2/auth/login") else { return }
2222

@@ -37,6 +37,12 @@ class Auth {
3737

3838
let sessionConfiguration = URLSessionConfiguration.default
3939
sessionConfiguration.timeoutIntervalForRequest = 10
40+
if let basicAuth = basicAuth {
41+
sessionConfiguration.httpAdditionalHeaders = [
42+
"Authorization": "Basic \(basicAuth.getAuthString())"
43+
]
44+
}
45+
4046
let session = URLSession(configuration: sessionConfiguration)
4147

4248
await session.reset()
@@ -48,6 +54,7 @@ class Auth {
4854
if setCookie {
4955
qBittorrent.setURL(url: urlString)
5056
qBittorrent.setCookie(cookie: cookie)
57+
qBitRequest.setBasicAuth(auth: basicAuth)
5158
}
5259
isSuccess(true)
5360
} else {

qBitControl/Classes/ServersHelper.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class ServersHelper: ObservableObject {
9696

9797
func checkConnection(server: Server, result: @escaping (Bool) -> Void) {
9898
Task {
99-
await Auth.getCookie(url: server.url, username: server.username, password: server.password, isSuccess: {
99+
await Auth.getCookie(url: server.url, username: server.username, password: server.password, basicAuth: server.basicAuth, isSuccess: {
100100
success in
101101
result(success);
102102
}, setCookie: false)
@@ -107,7 +107,7 @@ class ServersHelper: ObservableObject {
107107
connectingServerId = server.id
108108

109109
Task {
110-
await Auth.getCookie(url: server.url, username: server.username, password: server.password, isSuccess: {
110+
await Auth.getCookie(url: server.url, username: server.username, password: server.password, basicAuth: server.basicAuth, isSuccess: {
111111
success in
112112
DispatchQueue.main.async {
113113
if let result = result {

qBitControl/Classes/qBitRequestClass.swift

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ import Foundation
77

88

99
class qBitRequest {
10+
static private var basicAuth: Server.BasicAuth?
11+
12+
static func setBasicAuth(auth: Server.BasicAuth?) {
13+
self.basicAuth = auth
14+
}
15+
16+
private static func getSession() -> URLSession {
17+
let configuration = URLSessionConfiguration.default
18+
19+
if let basicAuth = qBitRequest.basicAuth {
20+
// Set the auth header
21+
configuration.httpAdditionalHeaders = [
22+
"Authorization": "Basic \(basicAuth.getAuthString())"
23+
]
24+
}
25+
26+
// Return a new session with this configuration
27+
return URLSession(configuration: configuration)
28+
}
29+
1030
static func prepareURLRequest(path: String, queryItems: [URLQueryItem]) -> URLRequest {
1131
let cookie = qBittorrent.getCookie()
1232
let url = qBittorrent.getURL()
@@ -51,7 +71,7 @@ class qBitRequest {
5171
}
5272

5373
static func requestTorrentListJSON(request: URLRequest, completionHandler: @escaping ([Torrent]) -> Void) {
54-
URLSession.shared.dataTask(with: request) {
74+
self.getSession().dataTask(with: request) {
5575
data, response, error in
5676
if let data = data {
5777
do {
@@ -65,13 +85,13 @@ class qBitRequest {
6585
}
6686

6787
static func requestUniversal(request: URLRequest) {
68-
URLSession.shared.dataTask(with: request) {
88+
self.getSession().dataTask(with: request) {
6989
data, response, error in
7090
}.resume()
7191
}
7292

7393
static func requestTorrentManagement(request: URLRequest, statusCode: @escaping (Int?) -> Void) {
74-
URLSession.shared.dataTask(with: request) {
94+
self.getSession().dataTask(with: request) {
7595
data, response, error in
7696
if let response = response as? HTTPURLResponse {
7797
statusCode(response.statusCode)
@@ -82,7 +102,7 @@ class qBitRequest {
82102
}
83103

84104
static func requestPreferencesJSON(request: URLRequest, completionHandler: @escaping (qBitPreferences) -> Void) {
85-
URLSession.shared.dataTask(with: request) {
105+
self.getSession().dataTask(with: request) {
86106
data, response, error in
87107
if let data = data {
88108
do {
@@ -96,7 +116,7 @@ class qBitRequest {
96116
}
97117

98118
static func requestSearchStart(request: URLRequest, completionHandler: @escaping (SearchStartResult) -> Void) {
99-
URLSession.shared.dataTask(with: request) {
119+
self.getSession().dataTask(with: request) {
100120
data, response, error in
101121
if let data = data {
102122
do {
@@ -110,7 +130,7 @@ class qBitRequest {
110130
}
111131

112132
static func requestSearchResults(request: URLRequest, completionHandler: @escaping (SearchResponse) -> Void) {
113-
URLSession.shared.dataTask(with: request) {
133+
self.getSession().dataTask(with: request) {
114134
data, response, error in
115135
if let data = data {
116136
do {
@@ -124,7 +144,7 @@ class qBitRequest {
124144
}
125145

126146
static func requestSearchPlugins(request: URLRequest, completionHandler: @escaping ([SearchPlugin]) -> Void) {
127-
URLSession.shared.dataTask(with: request) {
147+
self.getSession().dataTask(with: request) {
128148
data, response, error in
129149
if let data = data {
130150
do {
@@ -138,7 +158,7 @@ class qBitRequest {
138158
}
139159

140160
static func requestGlobalTransferInfo(request: URLRequest, completionHandler: @escaping (GlobalTransferInfo) -> Void) {
141-
URLSession.shared.dataTask(with: request) {
161+
self.getSession().dataTask(with: request) {
142162
data, response, error in
143163
if let data = data {
144164
do {
@@ -152,7 +172,7 @@ class qBitRequest {
152172
}
153173

154174
static func requestMainData(request: URLRequest, completionHandler: @escaping (MainData) -> Void) {
155-
URLSession.shared.dataTask(with: request) {
175+
self.getSession().dataTask(with: request) {
156176
data, response, error in
157177
if let data = data {
158178
do {
@@ -167,7 +187,7 @@ class qBitRequest {
167187

168188

169189
static func requestPeersJSON(request: URLRequest, completionHandler: @escaping (Peers) -> Void) {
170-
URLSession.shared.dataTask(with: request) {
190+
self.getSession().dataTask(with: request) {
171191
data, response, error in
172192
if let data = data {
173193
do {
@@ -181,7 +201,7 @@ class qBitRequest {
181201
}
182202

183203
static func requestTrackersJSON(request: URLRequest, completionHandler: @escaping ([Tracker]) -> Void) {
184-
URLSession.shared.dataTask(with: request) {
204+
self.getSession().dataTask(with: request) {
185205
data, response, error in
186206
if let data = data {
187207
do {
@@ -195,7 +215,7 @@ class qBitRequest {
195215
}
196216

197217
static func requestFilesJSON(request: URLRequest, completionHandler: @escaping ([File]) -> Void) {
198-
URLSession.shared.dataTask(with: request) {
218+
self.getSession().dataTask(with: request) {
199219
data, response, error in
200220
if let data = data {
201221
do {
@@ -209,7 +229,7 @@ class qBitRequest {
209229
}
210230

211231
static func requestCategoriesJSON(request: URLRequest, completionHandler: @escaping ([String: Category]) -> Void) {
212-
URLSession.shared.dataTask(with: request) {
232+
self.getSession().dataTask(with: request) {
213233
data, response, error in
214234
if let data = data {
215235
do {
@@ -223,7 +243,7 @@ class qBitRequest {
223243
}
224244

225245
static func requestVersion(request: URLRequest, completionHandler: @escaping (Version) -> Void) {
226-
URLSession.shared.dataTask(with: request) {
246+
self.getSession().dataTask(with: request) {
227247
data, response, error in
228248
if let data = data, let versionData = String(data: data, encoding: .utf8) {
229249
let versionString = versionData.filter { "0123456789.".contains($0) }
@@ -237,7 +257,7 @@ class qBitRequest {
237257
}
238258

239259
static func requestTagsJSON(request: URLRequest, completionHandler: @escaping ([String]) -> Void) {
240-
URLSession.shared.dataTask(with: request) {
260+
self.getSession().dataTask(with: request) {
241261
data, response, error in
242262
if let data = data {
243263
do {
@@ -251,7 +271,7 @@ class qBitRequest {
251271
}
252272

253273
static func requestRSSFeedJSON(request: URLRequest, completion: @escaping (RSSNode) -> Void) {
254-
URLSession.shared.dataTask(with: request) {
274+
self.getSession().dataTask(with: request) {
255275
data, response, error in
256276

257277
if let data = data {

qBitControl/Models/Server.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,20 @@ struct Server: Codable, Identifiable {
99
let url: String
1010
let username: String
1111
let password: String
12-
}
12+
let basicAuth: BasicAuth?
13+
14+
struct BasicAuth: Codable {
15+
let username: String
16+
let password: String
17+
18+
init(_ username: String, _ password: String) {
19+
self.username = username
20+
self.password = password
21+
}
22+
23+
func getAuthString() -> String {
24+
let authString = "\(username):\(password)".data(using: .utf8)!
25+
return authString.base64EncodedString()
26+
}
27+
}
28+
}

qBitControl/ViewModels/ServersView/ServerAddViewModel.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class ServerAddViewModel: ObservableObject {
1414
@Published var url = ""
1515
@Published var username = ""
1616
@Published var password = ""
17+
@Published var basicAuth: Server.BasicAuth?
1718
@Published var isCheckConnection = true;
1819

1920
@Published var isInvalidAlert = false;
@@ -34,6 +35,7 @@ class ServerAddViewModel: ObservableObject {
3435
url = server.url
3536
username = server.username
3637
password = server.password
38+
basicAuth = server.basicAuth
3739
}
3840
}
3941

@@ -83,7 +85,7 @@ class ServerAddViewModel: ObservableObject {
8385
if(!validateInputs()) { return; }
8486
if(!validateIsConnecting()) { return; }
8587

86-
let server = Server(name: friendlyName, url: url, username: username, password: password)
88+
let server = Server(name: friendlyName, url: url, username: username, password: password, basicAuth: basicAuth)
8789

8890
if(!isCheckConnection) {
8991
if let editServerId = self.editServerId { serversHelper.removeServer(id: editServerId) }

qBitControl/Views/ServersViews/ServerAddView.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ struct ServerAddView: View {
3636
.autocorrectionDisabled()
3737
}
3838

39+
Section {
40+
NavigationLink {
41+
ServerAdvancedView(basicAuth: $viewModel.basicAuth)
42+
} label: {
43+
Text("Advanced")
44+
}
45+
}
46+
3947
Section {
4048
Toggle(isOn: $viewModel.isCheckConnection, label: {
4149
Text("Check Connection")
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import SwiftUI
2+
3+
struct ServerAdvancedView: View {
4+
@Binding var basicAuth: Server.BasicAuth?
5+
6+
@State var isBasicAuthEnabled: Bool = false
7+
@State var username = ""
8+
@State var password = ""
9+
10+
var body: some View {
11+
List {
12+
Section(header: Text("Basic Authentication")) {
13+
self.basicAuthView()
14+
}
15+
}.onAppear { self.restoreValues() }
16+
}
17+
18+
@ViewBuilder
19+
private func basicAuthView() -> some View {
20+
Toggle("Basic Authentication", isOn: self.$isBasicAuthEnabled)
21+
.onChange(of: isBasicAuthEnabled) { _ in self.onChangeHandler() }
22+
23+
if(self.isBasicAuthEnabled) {
24+
TextField("Username", text: self.$username)
25+
.disableAutocorrection(true)
26+
.autocapitalization(.none)
27+
.onChange(of: username) { _ in self.onChangeHandler() }
28+
SecureField("Password", text: self.$password)
29+
.onChange(of: password) { _ in self.onChangeHandler() }
30+
}
31+
}
32+
33+
private func restoreValues() {
34+
if let basicAuth = self.basicAuth {
35+
self.isBasicAuthEnabled = true
36+
self.username = basicAuth.username
37+
self.password = basicAuth.password
38+
}
39+
}
40+
41+
private func onChangeHandler() {
42+
if(self.isBasicAuthEnabled == false) {
43+
self.basicAuth = nil
44+
return
45+
}
46+
47+
if(!username.isEmpty && !password.isEmpty) {
48+
self.basicAuth = Server.BasicAuth(username, password)
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)