Explain the expiring-token refusal for keys and emails !154
merged
merged by cmc on 2026-09-30 06:24 UTC
· krz/gitbay-ios:expiring-token-refusal into main
3 files changed, +23 −3
Layout: unified · split
gitbay/Networking/GitbayError.swift
+4
| @@ -32,6 +32,10 @@ nonisolated enum GitbayError: LocalizedError, Sendable { |
| 32 | 32 | /// them loses the explanation. |
| 33 | 33 | var userFacingMessage: String { |
| 34 | 34 | switch self { |
| 35 | // Since krz/gitbay#257 an expiring token cannot create credentials |
| 36 | // (SSH keys, email addresses), and the sign-in hint mints one. |
| 37 | case .denied(let message) where message.contains("creates a credential"): |
| 38 | "This needs a token without an expiry. Add it on the web under Settings, or over SSH." |
| 35 | 39 | case .denied(let message), .notFound(let message): |
| 36 | 40 | message |
| 37 | 41 | case .unauthorized: |
gitbayTests/GitbayClientTests.swift
+16
| @@ -388,3 +388,19 @@ struct ETagStoreTests { |
| 388 | 388 | #expect(store.count == 0) |
| 389 | 389 | } |
| 390 | 390 | } |
| 391 | |
| 392 | struct ExpiringCredentialRefusalTests { |
| 393 | |
| 394 | @Test func theRefusalSaysWhatToDoInstead() { |
| 395 | let error = GitbayError.denied( |
| 396 | "keys add creates a credential, and the one this request came with expires; use a token or key without an expiry") |
| 397 | #expect(error.userFacingMessage.contains("token without an expiry")) |
| 398 | #expect(error.userFacingMessage.contains("Settings")) |
| 399 | #expect(!error.userFacingMessage.contains("keys add")) |
| 400 | } |
| 401 | |
| 402 | @Test func otherDenialsPassThrough() { |
| 403 | #expect(GitbayError.denied("you are not a maintainer of krz/gitbay").userFacingMessage |
| 404 | == "you are not a maintainer of krz/gitbay") |
| 405 | } |
| 406 | } |
gitbayUITests/LiveSmokeUITests.swift
+3 −3
| @@ -1006,10 +1006,10 @@ extension LiveSmokeUITests { |
| 1006 | 1006 | app.descendants(matching: .any).matching(identifier: "key-paste-submit") |
| 1007 | 1007 | .firstMatch.tap() |
| 1008 | 1008 | // An expiring token (the suite's are) is refused before the key is |
| 1009 | | // parsed; either refusal is the server's text reaching the sheet. |
| 1009 | // parsed; either refusal reaching the sheet passes. |
| 1010 | 1010 | XCTAssertTrue(app.staticTexts |
| 1011 | 1011 | .containing(NSPredicate(format: |
| 1012 | | "label CONTAINS 'not a valid public key' OR label CONTAINS 'creates a credential'")) |
| 1012 | "label CONTAINS 'not a valid public key' OR label CONTAINS 'token without an expiry'")) |
| 1013 | 1013 | .firstMatch.waitForExistence(timeout: 15), "invalid-key refusal not surfaced") |
| 1014 | 1014 | app.buttons["Cancel"].firstMatch.tap() |
| 1015 | 1015 | |
| @@ -1069,7 +1069,7 @@ extension LiveSmokeUITests { |
| 1069 | 1069 | verify.tap() |
| 1070 | 1070 | XCTAssertTrue(app.staticTexts |
| 1071 | 1071 | .containing(NSPredicate(format: |
| 1072 | | "label CONTAINS 'invalid, expired' OR label CONTAINS 'creates a credential'")) |
| 1072 | "label CONTAINS 'invalid, expired' OR label CONTAINS 'token without an expiry'")) |
| 1073 | 1073 | .firstMatch.waitForExistence(timeout: 15), "bad-code refusal not surfaced") |
| 1074 | 1074 | } |
| 1075 | 1075 | } |