Sign-in: link to the web token page; say what full scope allows !136
merged
merged by cmc on 2026-09-30 02:12 UTC
· krz/gitbay-ios:sign-in-token-page into main
Conversation
Commits 1
Files changed 8
8 files changed, +46 −27
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 // / S S H k e y s , P G P k e y s , a n d e m a i l v e r i f i c a t i o n — t h e a c c o u n t r o w s t h e w e b
5 // / h a s . T o k e n s s t a y S S H - o n l y b y d e s i g n a n d g e t n o U I h e r e .
5 // / h a s . T o k e n s a r e m i n t e d o n t h e w e b o r o v e r S S H a n d g e t n o U I h e r e .
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 // / T h e w e b S e t t i n g s → A P I t o k e n s s e c t i o n .
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 // / W h e t h e r a U R L i s o n t h i s i n s t a n c e . C h e c k e d b e f o r e t h e A u t h o r i z a t i o n
71 79 // / h e a d e r g o e s o n a r e q u e s t , a n d a g a i n o n e v e r y r e d i r e c t .
72 80 func isOwn ( _ url : URL ) -> Bool {
gitbay/Repos/WebhookViewModels.swift
+5 −6
@@ -28,18 +28,17 @@ final class WebhookListViewModel {
28 28 }
29 29 }
30 30
31 // / ` w e b h o o k a d d < r e p o > < u r l > [ - - s e c r e t - ] [ - - e v e n t s < e > ] ` — a n e m p t y
31 // / ` w e b h o o k a d d < r e p o > < u r l > [ - - s e c r e t < s > ] [ - - e v e n t s < e > ] ` — a n e m p t y
32 32 // / s e c r e t o r e v e n t l i s t i s l e f t o u t , s o t h e s e r v e r a p p l i e s i t s d e f a u l t .
33 // / T h e s e c r e t t r a v e l s o n s t d i n ; t h e s e r v e r r e f u s e s i t i n a r g v .
34 33 func add ( url : String , secret : String , events : String ) async -> Bool {
35 34 let url = url . trimmingCharacters ( in : . whitespaces )
36 35 guard ! url . isEmpty else { return false }
37 36 var argv = [ " webhook " , " add " , repoPath , url ]
38 37 let secret = secret . trimmingCharacters ( in : . whitespaces )
39 if ! secret . isEmpty { argv += [ " --secret " , " - " ] }
38 if ! secret . isEmpty { argv += [ " --secret " , secret ] }
40 39 let events = events . trimmingCharacters ( in : . whitespaces )
41 40 if ! events . isEmpty { argv += [ " --events " , events ] }
42 await perform ( argv , stdin : secret . isEmpty ? nil : secret + " \n " )
41 await perform ( argv )
43 42 return actionError = = nil
44 43 }
45 44
@@ -47,12 +46,12 @@ final class WebhookListViewModel {
47 46 await perform ( [ " webhook " , " remove " , repoPath , String ( id ) ] )
48 47 }
49 48
50 private func perform ( _ argv : [ String ] , stdin : String ? = nil ) async {
49 private func perform ( _ argv : [ String ] ) async {
51 50 working = true
52 51 actionError = nil
53 52 defer { working = false }
54 53 do {
55 try await client . run ( argv , stdin : stdin )
54 try await client . run ( argv )
56 55 await load ( )
57 56 } catch let error as GitbayError {
58 57 actionError = error . userFacingMessage
gitbay/Views/Account/AccountView.swift
+2 −2
@@ -1,7 +1,7 @@
1 1 import SwiftUI
2 2
3 // / S S H k e y s , P G P k e y s , a n d e m a i l v e r i f i c a t i o n . T o k e n s a r e m i n t e d o v e r
4 // / S S H o n l y , b y d e s i g n — n o U I i m p l i e s o t h e r w i s e .
3 // / S S H k e y s , P G P k e y s , a n d e m a i l v e r i f i c a t i o n . T o k e n s a r e m i n t e d o n t h e
4 // / w e b s e t t i n g s p a g e o r o v e r S S H , n o t h e r e .
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 // / P a s t e a t o k e n , n a m e t h e i n s t a n c e , d o n e . T h e r e i s n o b r o w s e r f l o w a n d n o
4 // / p a s s w o r d a n y w h e r e i n t h e s y s t e m : t o k e n s a r e m i n t e d o v e r S S H o n a
5 // / m a c h i n e t h a t h a s i t .
4 // / p a s s w o r d i n t h e a p p : t o k e n s a r e m i n t e d o n t h e i n s t a n c e ' s w e b s e t t i n g s
5 // / p a g e o r o v e r S S H .
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 " ) ! ) )
gitbayTests/WebhookTests.swift
+1 −8
@@ -17,11 +17,6 @@ private func argvOf(_ seen: StubProtocol.Seen) throws -> [String] {
17 17 return try # require ( body [ " argv " ] as ? [ String ] )
18 18 }
19 19
20 private func stdinOf ( _ seen : StubProtocol . Seen ) throws -> String ? {
21 let body = try # require ( try JSONSerialization . jsonObject ( with : seen . body ) as ? [ String : Any ] )
22 return body [ " stdin " ] as ? String
23 }
24
25 20 private let okJSON = # " { " protocol_version " :1, " data " :{}, " exit_code " :0} " #
26 21
27 22 private let hooksJSON = " " "
@@ -86,11 +81,9 @@ struct WebhookListViewModelTests {
86 81 # expect ( first && second )
87 82 let writes = try stub . seen . filter { $0 . method = = " POST " } . map ( argvOf )
88 83 # expect ( writes = = [
89 [ " webhook " , " add " , " krz/gitbay " , " https://a.example/h " , " --secret " , " - " , " --events " , " push " ] ,
84 [ " webhook " , " add " , " krz/gitbay " , " https://a.example/h " , " --secret " , " s3 " , " --events " , " push " ] ,
90 85 [ " webhook " , " add " , " krz/gitbay " , " https://b.example/h " ] ,
91 86 ] )
92 let posts = stub . seen . filter { $0 . method = = " POST " }
93 # expect ( try posts . map ( stdinOf ) = = [ " s3 \n " , nil ] )
94 87 }
95 88
96 89 @ Test func removeSendsTheIdThenReloads ( ) async throws {
Checks
No checks reported
Revisions
1. 12cfcf2845 2026-09-30 02:00 UTC
Source and target
krz/gitbay-ios:sign-in-token-page into main
merged at 12cfcf2845 · base d7fac80c5f · branch deleted