Skip to content

Commit d0b84f6

Browse files
committed
add qr code auth
1 parent 0a2cfba commit d0b84f6

File tree

3 files changed

+131
-34
lines changed

3 files changed

+131
-34
lines changed

Django Files/API/DFAPI.swift

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ struct DFAPI {
3636
case file = "file/"
3737
case raw = "raw/"
3838
case albums = "album/"
39+
case auth_application = "auth/application/"
3940
}
4041

4142
let url: URL
@@ -386,8 +387,34 @@ struct DFAPI {
386387
}
387388
}
388389

390+
struct DFApplicationAuthRequest: Codable {
391+
let signature: String
392+
}
389393

390-
394+
private struct DFApplicationAuthResponse: Codable {
395+
let token: String
396+
}
397+
398+
public func applicationAuth(signature: String) async -> String? {
399+
let request = DFApplicationAuthRequest(signature: signature)
400+
do {
401+
let json = try JSONEncoder().encode(request)
402+
print(json)
403+
print("JSON Data: \(String(data: json, encoding: .utf8) ?? "invalid json")")
404+
let responseBody = try await makeAPIRequest(
405+
body: json,
406+
path: getAPIPath(.auth_application),
407+
parameters: [:],
408+
method: .post,
409+
headerFields: [.contentType: "application/json"]
410+
)
411+
let response = try decoder.decode(DFApplicationAuthResponse.self, from: responseBody)
412+
return response.token
413+
} catch {
414+
print("Application auth request failed \(error)")
415+
return nil
416+
}
417+
}
391418
}
392419

393420
class DjangoFilesUploadDelegate: NSObject, StreamDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate, URLSessionStreamDelegate{
@@ -704,15 +731,15 @@ class DjangoFilesUploadDelegate: NSObject, StreamDelegate, URLSessionDelegate, U
704731
}
705732
}
706733

707-
struct DFAuthMethod: Codable {
708-
let name: String
709-
let url: String
710-
}
734+
struct DFAuthMethod: Codable {
735+
let name: String
736+
let url: String
737+
}
711738

712-
struct DFAuthMethodsResponse: Codable {
713-
let authMethods: [DFAuthMethod]
714-
let siteName: String
715-
}
739+
struct DFAuthMethodsResponse: Codable {
740+
let authMethods: [DFAuthMethod]
741+
let siteName: String
742+
}
716743

717744
class RedirectDelegate: NSObject, URLSessionTaskDelegate {
718745
func urlSession(

Django Files/Django_FilesApp.swift

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@ class SessionManager: ObservableObject {
1818
}
1919
}
2020

21+
func createAndAuthenticateSession(url: URL, signature: String, context: ModelContext) async -> DjangoFilesSession? {
22+
// Create the base URL for the server
23+
let serverURL = "\(url.scheme ?? "https")://\(url.host ?? "")"
24+
25+
// Create a new session
26+
let newSession = DjangoFilesSession(url: serverURL)
27+
28+
// Create API instance
29+
let api = DFAPI(url: URL(string: serverURL)!, token: "")
30+
31+
// Get token using the signature
32+
if let token = await api.applicationAuth(signature: signature) {
33+
newSession.token = token
34+
newSession.auth = true
35+
36+
// Save the session
37+
context.insert(newSession)
38+
try? context.save()
39+
40+
return newSession
41+
}
42+
43+
return nil
44+
}
45+
2146
func loadLastSelectedSession(from sessions: [DjangoFilesSession]) {
2247
// Return if we already have a session loaded
2348
if selectedSession != nil { return }
@@ -78,6 +103,9 @@ struct Django_FilesApp: App {
78103
})
79104
}
80105
}
106+
.onOpenURL { url in
107+
handleDeepLink(url)
108+
}
81109
}
82110
.modelContainer(sharedModelContainer)
83111
#if os(macOS)
@@ -87,6 +115,48 @@ struct Django_FilesApp: App {
87115
#endif
88116
}
89117

118+
private func handleDeepLink(_ url: URL) {
119+
// print("Deep link received: \(url)")
120+
guard url.scheme == "djangofiles" else { return }
121+
122+
// Extract the signature from the URL parameters
123+
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
124+
print("Invalid deep link URL")
125+
return
126+
}
127+
// eventually we need a case to handle multiple deep link types
128+
// guard let root_path = components.host else {
129+
// print("Invalid deep link URL: missing root path")
130+
// return
131+
// }
132+
133+
// print("Deep link components: \(components)")
134+
// print("Deep link path: \(components.path)")
135+
// print("Deep link host: \(components.host)")
136+
// print("Deep link query items: \(components.queryItems)")
137+
guard let signature = components.queryItems?.first(where: { $0.name == "signature" })?.value?.removingPercentEncoding,
138+
let serverURL = URL(string: components.queryItems?.first(where: { $0.name == "url" })?.value?.removingPercentEncoding ?? "") else {
139+
print("Unable to parse auth deep link.")
140+
return
141+
}
142+
let finalSignature = signature.replacingOccurrences(of: "+", with: "%20")
143+
144+
// Create and authenticate the session
145+
Task {
146+
if let newSession = await sessionManager.createAndAuthenticateSession(
147+
url: serverURL,
148+
signature: signature,
149+
context: sharedModelContainer.mainContext
150+
) {
151+
// Update the UI on the main thread
152+
await MainActor.run {
153+
sessionManager.selectedSession = newSession
154+
hasExistingSessions = true
155+
}
156+
}
157+
}
158+
}
159+
90160
private func checkForExistingSessions() {
91161
let context = sharedModelContainer.mainContext
92162
let descriptor = FetchDescriptor<DjangoFilesSession>()

Django Files/Views/TabView.swift

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,34 @@ struct TabViewWindow: View {
2929

3030
var body: some View {
3131
TabView(selection: $selectedTab) {
32-
NavigationStack(path: $filesNavigationPath) {
33-
if let server = sessionManager.selectedSession {
34-
FileListView(server: .constant(server), albumID: nil, navigationPath: $filesNavigationPath, albumName: nil)
32+
if let server = sessionManager.selectedSession {
33+
if server.auth {
34+
NavigationStack(path: $filesNavigationPath) {
35+
FileListView(server: .constant(server), albumID: nil, navigationPath: $filesNavigationPath, albumName: nil)
36+
.id(serverChangeRefreshTrigger)
37+
}
38+
.tabItem {
39+
Label("Files", systemImage: "document.fill")
40+
}
41+
.tag(Tab.files)
42+
43+
NavigationStack(path: $albumsNavigationPath) {
44+
AlbumListView(navigationPath: $albumsNavigationPath, server: $sessionManager.selectedSession)
45+
.id(serverChangeRefreshTrigger)
46+
}
47+
.tabItem {
48+
Label("Albums", systemImage: "square.stack")
49+
}
50+
.tag(Tab.albums)
51+
52+
ShortListView(server: $sessionManager.selectedSession)
3553
.id(serverChangeRefreshTrigger)
36-
} else {
37-
Label("No server selected.", systemImage: "exclamationmark.triangle")
54+
.tabItem {
55+
Label("Shorts", systemImage: "link")
56+
}
57+
.tag(Tab.shorts)
3858
}
3959
}
40-
.tabItem {
41-
Label("Files", systemImage: "document.fill")
42-
}
43-
.tag(Tab.files)
44-
45-
NavigationStack(path: $albumsNavigationPath) {
46-
AlbumListView(navigationPath: $albumsNavigationPath, server: $sessionManager.selectedSession)
47-
.id(serverChangeRefreshTrigger)
48-
}
49-
.tabItem {
50-
Label("Albums", systemImage: "square.stack")
51-
}
52-
.tag(Tab.albums)
53-
54-
ShortListView(server: $sessionManager.selectedSession)
55-
.id(serverChangeRefreshTrigger)
56-
.tabItem {
57-
Label("Shorts", systemImage: "link")
58-
}
59-
.tag(Tab.shorts)
6060

6161
ServerSelector(selectedSession: $sessionManager.selectedSession)
6262
.tabItem {

0 commit comments

Comments
 (0)