Commit 1b9856d2d1
1b9856d2d124a1032b6d3d12786a4a6edea5b832
parent: d7fac80c5f
Unsigned
cmc <hello@cleberg.net> · 2026-09-30 02:12 UTC
Sign-in: link to the web token page; say what full scope allows (!136)
- `GitbayInstance.tokenPageURL`: `<origin>/settings#tokens`, unit-tested.
- Sign-in footer: first option is a link to Settings → API tokens on the typed instance (hidden while the instance field doesn't parse), then the SSH hint `gitbay auth token create --name iphone --scope full --ttl 90d`. States that full scope can comment and merge, and on an admin account, administer the instance.
- AccountView, AccountViewModel, README: tokens are minted on the web or over SSH, no longer "SSH-only by design".
Closes #18
Layout: unified · split
README.org
+9 −6
| @@ -26,20 +26,23 @@ not at a keyboard. |
| 26 | 26 | | Explore, feed and profiles | Discovery, activity, contribution graph | |
| 27 | 27 | | Markup | Bodies and comments are written and rendered in the format they were stored in. Org renders natively through [[https://gitbay.org/krz/org-swift][OrgSwiftUI]] — selectable text, Dynamic Type, VoiceOver — rather than in a web view | |
| 28 | 28 | |
| 29 | | Everything the CLI can do reaches the app, except what is SSH-only by |
| 30 | | design: minting an API token, and deleting or transferring a repository, |
| 31 | | both of which want a typed confirmation or carry a credential. |
| 29 | Everything the CLI can do reaches the app, except minting an API token, |
| 30 | which is done on the web or over SSH, and deleting or transferring a |
| 31 | repository, which is SSH-only by design since it wants a typed |
| 32 | confirmation. |
| 32 | 33 | |
| 33 | 34 | * Signing in |
| 34 | 35 | |
| 35 | | As =gitbay= requires, your SSH key is your identity. To login to the mobile app, |
| 36 | | you will need to mint a bearer token: |
| 36 | To sign in to the mobile app you need a bearer token. Create one with full |
| 37 | scope under Settings → API tokens on the instance's website |
| 38 | (=https://<instance>/settings#tokens=), or over SSH: |
| 37 | 39 | |
| 38 | 40 | #+begin_src sh |
| 39 | 41 | gitbay auth token create --name iphone --scope full --ttl 90d |
| 40 | 42 | #+end_src |
| 41 | 43 | |
| 42 | | Alternatively, =--scope read= works too, but a read-only token cannot comment or |
| 44 | Full scope can comment and merge, and on an admin account, administer the |
| 45 | instance. =--scope read= works too, but a read-only token cannot comment or |
| 43 | 46 | merge. |
| 44 | 47 | |
| 45 | 48 | Any gitbay instance works. You just need to enter the host at sign-in. |
gitbay/Account/AccountViewModel.swift
+1 −1
| @@ -2,7 +2,7 @@ import Foundation |
| 2 | 2 | import Observation |
| 3 | 3 | |
| 4 | 4 | /// SSH keys, PGP keys, and email verification — the account rows the web |
| 5 | | /// has. Tokens stay SSH-only by design and get no UI here. |
| 5 | /// has. Tokens are minted on the web or over SSH and get no UI here. |
| 6 | 6 | @Observable |
| 7 | 7 | @MainActor |
| 8 | 8 | final class AccountViewModel { |
gitbay/Networking/GitbayInstance.swift
+8
| @@ -67,6 +67,14 @@ nonisolated struct GitbayInstance: Sendable, Hashable, Codable { |
| 67 | 67 | baseURL.appending(path: "/api/v1/cmd") |
| 68 | 68 | } |
| 69 | 69 | |
| 70 | /// The web Settings → API tokens section. |
| 71 | var tokenPageURL: URL { |
| 72 | var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)! |
| 73 | components.path = "/settings" |
| 74 | components.fragment = "tokens" |
| 75 | return components.url! |
| 76 | } |
| 77 | |
| 70 | 78 | /// Whether a URL is on this instance. Checked before the Authorization |
| 71 | 79 | /// header goes on a request, and again on every redirect. |
| 72 | 80 | func isOwn(_ url: URL) -> Bool { |
gitbay/Views/Account/AccountView.swift
+2 −2
| @@ -1,7 +1,7 @@ |
| 1 | 1 | import SwiftUI |
| 2 | 2 | |
| 3 | | /// SSH keys, PGP keys, and email verification. Tokens are minted over |
| 4 | | /// SSH only, by design — no UI implies otherwise. |
| 3 | /// SSH keys, PGP keys, and email verification. Tokens are minted on the |
| 4 | /// web settings page or over SSH, not here. |
| 5 | 5 | struct AccountView: View { |
| 6 | 6 | |
| 7 | 7 | @State private var model: AccountViewModel |
gitbay/Views/SignInView.swift
+13 −4
| @@ -1,8 +1,8 @@ |
| 1 | 1 | import SwiftUI |
| 2 | 2 | |
| 3 | 3 | /// Paste a token, name the instance, done. There is no browser flow and no |
| 4 | | /// password anywhere in the system: tokens are minted over SSH on a |
| 5 | | /// machine that has it. |
| 4 | /// password in the app: tokens are minted on the instance's web settings |
| 5 | /// page or over SSH. |
| 6 | 6 | struct SignInView: View { |
| 7 | 7 | |
| 8 | 8 | @Environment(SessionStore.self) private var session |
| @@ -42,11 +42,20 @@ struct SignInView: View { |
| 42 | 42 | Text("Token") |
| 43 | 43 | } footer: { |
| 44 | 44 | VStack(alignment: .leading, spacing: 8) { |
| 45 | | Text("Mint one over SSH on a machine that has your key:") |
| 45 | if let tokenPage = (try? GitbayInstance(url: instanceURL))?.tokenPageURL { |
| 46 | Link(destination: tokenPage) { |
| 47 | Text("Create one with full scope under Settings → API tokens") |
| 48 | .multilineTextAlignment(.leading) |
| 49 | .frame(maxWidth: .infinity, alignment: .leading) |
| 50 | } |
| 51 | Text("Or over SSH on a machine that has your key:") |
| 52 | } else { |
| 53 | Text("Create one over SSH on a machine that has your key:") |
| 54 | } |
| 46 | 55 | Text(verbatim: "gitbay auth token create --name iphone --scope full --ttl 90d") |
| 47 | 56 | .font(.gbMono(.caption)) |
| 48 | 57 | .textSelection(.enabled) |
| 49 | | Text("`--scope read` also works, but a read-only token cannot comment or merge.") |
| 58 | Text("Full scope can comment and merge, and on an admin account, administer the instance. A read-only token cannot comment or merge.") |
| 50 | 59 | } |
| 51 | 60 | } |
| 52 | 61 | |
gitbayTests/GitbayClientTests.swift
+7
| @@ -338,6 +338,13 @@ struct GitbayInstanceTests { |
| 338 | 338 | #expect(url.query() == "argv=repo&argv=cat&argv=krz/gitbay&argv=cmd/main.go") |
| 339 | 339 | } |
| 340 | 340 | |
| 341 | @Test func tokenPageURLIsSettingsTokensOnTheInstance() throws { |
| 342 | let instance = try GitbayInstance(url: "gitbay.org/krz/solar?x=1") |
| 343 | #expect(instance.tokenPageURL.absoluteString == "https://gitbay.org/settings#tokens") |
| 344 | let local = try GitbayInstance(url: "http://localhost:3000") |
| 345 | #expect(local.tokenPageURL.absoluteString == "http://localhost:3000/settings#tokens") |
| 346 | } |
| 347 | |
| 341 | 348 | @Test func ownHostCheckCoversSchemeHostAndPort() throws { |
| 342 | 349 | let instance = try GitbayInstance(url: "https://gitbay.org") |
| 343 | 350 | #expect(instance.isOwn(URL(string: "https://gitbay.org/api/v1/read")!)) |