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.
2626| Explore, feed and profiles | Discovery, activity, contribution graph |
2727| 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 |
2828
29Everything the CLI can do reaches the app, except what is SSH-only by
30design: minting an API token, and deleting or transferring a repository,
31both of which want a typed confirmation or carry a credential.
29Everything the CLI can do reaches the app, except minting an API token,
30which is done on the web or over SSH, and deleting or transferring a
31repository, which is SSH-only by design since it wants a typed
32confirmation.
3233
3334* Signing in
3435
35As =gitbay= requires, your SSH key is your identity. To login to the mobile app,
36you will need to mint a bearer token:
36To sign in to the mobile app you need a bearer token. Create one with full
37scope under Settings → API tokens on the instance's website
38(=https://<instance>/settings#tokens=), or over SSH:
3739
3840#+begin_src sh
3941gitbay auth token create --name iphone --scope full --ttl 90d
4042#+end_src
4143
42Alternatively, =--scope read= works too, but a read-only token cannot comment or
44Full scope can comment and merge, and on an admin account, administer the
45instance. =--scope read= works too, but a read-only token cannot comment or
4346merge.
4447
4548Any 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
22import Observation
33
44/// 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.
66@Observable
77@MainActor
88final class AccountViewModel {
gitbay/Networking/GitbayInstance.swift +8
@@ -67,6 +67,14 @@ nonisolated struct GitbayInstance: Sendable, Hashable, Codable {
6767 baseURL.appending(path: "/api/v1/cmd")
6868 }
6969
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
7078 /// Whether a URL is on this instance. Checked before the Authorization
7179 /// header goes on a request, and again on every redirect.
7280 func isOwn(_ url: URL) -> Bool {
gitbay/Views/Account/AccountView.swift +2 −2
@@ -1,7 +1,7 @@
11import SwiftUI
22
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.
55struct AccountView: View {
66
77 @State private var model: AccountViewModel
gitbay/Views/SignInView.swift +13 −4
@@ -1,8 +1,8 @@
11import SwiftUI
22
33/// 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.
66struct SignInView: View {
77
88 @Environment(SessionStore.self) private var session
@@ -42,11 +42,20 @@ struct SignInView: View {
4242 Text("Token")
4343 } footer: {
4444 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 }
4655 Text(verbatim: "gitbay auth token create --name iphone --scope full --ttl 90d")
4756 .font(.gbMono(.caption))
4857 .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.")
5059 }
5160 }
5261
gitbayTests/GitbayClientTests.swift +7
@@ -338,6 +338,13 @@ struct GitbayInstanceTests {
338338 #expect(url.query() == "argv=repo&argv=cat&argv=krz/gitbay&argv=cmd/main.go")
339339 }
340340
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
341348 @Test func ownHostCheckCoversSchemeHostAndPort() throws {
342349 let instance = try GitbayInstance(url: "https://gitbay.org")
343350 #expect(instance.isOwn(URL(string: "https://gitbay.org/api/v1/read")!))