mirror of
https://codeberg.org/secana/ForgejoKit.git
synced 2026-06-16 05:13:53 -07:00
Compare commits
2 commits
485c147e7f
...
7675312f5c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7675312f5c | ||
|
|
1ad2ee2607 |
4 changed files with 82 additions and 68 deletions
|
|
@ -17,7 +17,7 @@ Add ForgejoKit as a dependency in your `Package.swift`:
|
|||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://codeberg.org/secana/ForgejoKit.git", from: "0.8.0"),
|
||||
.package(url: "https://codeberg.org/secana/ForgejoKit.git", from: "0.8.1"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
|
|
|
|||
73
Sources/ForgejoKit/Services/AuthenticationError.swift
Normal file
73
Sources/ForgejoKit/Services/AuthenticationError.swift
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import Foundation
|
||||
|
||||
public enum AuthenticationError: LocalizedError, Sendable, Equatable {
|
||||
case invalidURL
|
||||
case invalidCredentials
|
||||
case otpRequired
|
||||
case basicAuthBlockedBySecurityKey
|
||||
case serverNotFound
|
||||
case invalidResponse
|
||||
case certificateError
|
||||
case unknownError(statusCode: Int)
|
||||
|
||||
static func from(statusCode: Int, body: String = "") -> AuthenticationError {
|
||||
switch statusCode {
|
||||
case 401 where body.localizedCaseInsensitiveContains("security key"):
|
||||
.basicAuthBlockedBySecurityKey
|
||||
case 401:
|
||||
body.localizedCaseInsensitiveContains("OTP") ? .otpRequired : .invalidCredentials
|
||||
case 404:
|
||||
.serverNotFound
|
||||
default:
|
||||
.unknownError(statusCode: statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
public var httpStatusCode: Int? {
|
||||
switch self {
|
||||
case .invalidCredentials, .otpRequired, .basicAuthBlockedBySecurityKey:
|
||||
401
|
||||
case .serverNotFound:
|
||||
404
|
||||
case let .unknownError(statusCode):
|
||||
statusCode
|
||||
case .invalidURL, .invalidResponse, .certificateError:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
public var httpErrorCategory: HTTPErrorCategory? {
|
||||
guard let httpStatusCode else {
|
||||
return nil
|
||||
}
|
||||
return HTTPErrorCategory(statusCode: httpStatusCode)
|
||||
}
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL:
|
||||
"Invalid server URL"
|
||||
case .invalidCredentials:
|
||||
"Invalid username or password"
|
||||
case .otpRequired:
|
||||
"Two-factor authentication code required"
|
||||
case .basicAuthBlockedBySecurityKey:
|
||||
"An enrolled security key blocks Basic auth. Create a personal access token and use 'Login with Token'."
|
||||
case .serverNotFound:
|
||||
"Server not found. Please check the URL."
|
||||
case .invalidResponse:
|
||||
"Invalid response from server"
|
||||
case .certificateError:
|
||||
"SSL certificate error. Try enabling 'Accept Self-Signed Certificates' if your server uses one."
|
||||
case let .unknownError(statusCode):
|
||||
switch HTTPErrorCategory(statusCode: statusCode) {
|
||||
case .permissionDenied:
|
||||
"Permission denied (Status: \(statusCode))"
|
||||
case .server:
|
||||
"Server error occurred (Status: \(statusCode))"
|
||||
case .authentication, .notFound, .other:
|
||||
"Unknown error occurred (Status: \(statusCode))"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -460,73 +460,6 @@ public enum HTTPErrorCategory: Equatable, Sendable {
|
|||
}
|
||||
}
|
||||
|
||||
public enum AuthenticationError: LocalizedError, Sendable, Equatable {
|
||||
case invalidURL
|
||||
case invalidCredentials
|
||||
case otpRequired
|
||||
case serverNotFound
|
||||
case invalidResponse
|
||||
case certificateError
|
||||
case unknownError(statusCode: Int)
|
||||
|
||||
static func from(statusCode: Int, body: String = "") -> AuthenticationError {
|
||||
switch statusCode {
|
||||
case 401:
|
||||
body.localizedCaseInsensitiveContains("OTP") ? .otpRequired : .invalidCredentials
|
||||
case 404:
|
||||
.serverNotFound
|
||||
default:
|
||||
.unknownError(statusCode: statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
public var httpStatusCode: Int? {
|
||||
switch self {
|
||||
case .invalidCredentials, .otpRequired:
|
||||
401
|
||||
case .serverNotFound:
|
||||
404
|
||||
case let .unknownError(statusCode):
|
||||
statusCode
|
||||
case .invalidURL, .invalidResponse, .certificateError:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
public var httpErrorCategory: HTTPErrorCategory? {
|
||||
guard let httpStatusCode else {
|
||||
return nil
|
||||
}
|
||||
return HTTPErrorCategory(statusCode: httpStatusCode)
|
||||
}
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL:
|
||||
"Invalid server URL"
|
||||
case .invalidCredentials:
|
||||
"Invalid username or password"
|
||||
case .otpRequired:
|
||||
"Two-factor authentication code required"
|
||||
case .serverNotFound:
|
||||
"Server not found. Please check the URL."
|
||||
case .invalidResponse:
|
||||
"Invalid response from server"
|
||||
case .certificateError:
|
||||
"SSL certificate error. Try enabling 'Accept Self-Signed Certificates' if your server uses one."
|
||||
case let .unknownError(statusCode):
|
||||
switch HTTPErrorCategory(statusCode: statusCode) {
|
||||
case .permissionDenied:
|
||||
"Permission denied (Status: \(statusCode))"
|
||||
case .server:
|
||||
"Server error occurred (Status: \(statusCode))"
|
||||
case .authentication, .notFound, .other:
|
||||
"Unknown error occurred (Status: \(statusCode))"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ServiceError: LocalizedError, Sendable, Equatable {
|
||||
case noActiveInstance
|
||||
case invalidURL
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@ struct ErrorMappingTests {
|
|||
#expect(AuthenticationError.from(statusCode: 418) == .unknownError(statusCode: 418))
|
||||
}
|
||||
|
||||
@Test func mapsSecurityKeyBlockedBasicAuth() {
|
||||
// Forgejo's exact 401 body when a passkey/security key is enrolled on the account.
|
||||
let body = "Basic authorization is not allowed while having security keys enrolled"
|
||||
#expect(AuthenticationError.from(statusCode: 401, body: body) == .basicAuthBlockedBySecurityKey)
|
||||
#expect(AuthenticationError.basicAuthBlockedBySecurityKey.httpStatusCode == 401)
|
||||
#expect(AuthenticationError.basicAuthBlockedBySecurityKey.httpErrorCategory == .authentication)
|
||||
}
|
||||
|
||||
@Test func exposesAuthenticationStatusAndCategory() {
|
||||
#expect(AuthenticationError.invalidCredentials.httpStatusCode == 401)
|
||||
#expect(AuthenticationError.invalidCredentials.httpErrorCategory == .authentication)
|
||||
|
|
|
|||
Loading…
Reference in a new issue