@@ -3,7 +3,6 @@ package main
33import (
44 "crypto/tls"
55 "errors"
6- "flag"
76 "fmt"
87 "net/http"
98 "net/url"
@@ -18,35 +17,36 @@ import (
1817 retry "github.com/appleboy/go-httpretry"
1918 "github.com/google/uuid"
2019 "github.com/joho/godotenv"
20+ "github.com/spf13/cobra"
2121)
2222
2323// version is set at build time via -ldflags "-X main.version=...".
2424var version string
2525
2626var (
27- serverURL string
28- clientID string
29- clientSecret string
30- redirectURI string
31- callbackPort int
32- scope string
33- tokenFile string
34- tokenStoreMode string
35- forceDevice bool
36- configInitialized bool
37- retryClient * retry.Client
38- tokenStore credstore.Store [credstore.Token ]
27+ serverURL string
28+ clientID string
29+ clientSecret string
30+ redirectURI string
31+ callbackPort int
32+ scope string
33+ tokenFile string
34+ tokenStoreMode string
35+ forceDevice bool
36+ storeConfigInitialized bool
37+ configInitialized bool
38+ retryClient * retry.Client
39+ tokenStore credstore.Store [credstore.Token ]
3940
40- flagServerURL * string
41- flagClientID * string
42- flagClientSecret * string
43- flagRedirectURI * string
44- flagCallbackPort * int
45- flagScope * string
46- flagTokenFile * string
47- flagTokenStore * string
48- flagDevice * bool
49- flagVersion * bool
41+ flagServerURL string
42+ flagClientID string
43+ flagClientSecret string
44+ flagRedirectURI string
45+ flagCallbackPort int
46+ flagScope string
47+ flagTokenFile string
48+ flagTokenStore string
49+ flagDevice bool
5050)
5151
5252const (
@@ -58,47 +58,56 @@ const (
5858 defaultKeyringService = "authgate-cli"
5959)
6060
61- func init ( ) {
61+ func registerFlags ( cmd * cobra. Command ) {
6262 _ = godotenv .Load ()
63+ cmd .PersistentFlags ().
64+ StringVar (& flagServerURL , "server-url" , "" , "OAuth server URL (default: http://localhost:8080 or SERVER_URL env)" )
65+ cmd .PersistentFlags ().
66+ StringVar (& flagClientID , "client-id" , "" , "OAuth client ID (required, or set CLIENT_ID env)" )
67+ cmd .PersistentFlags ().
68+ StringVar (& flagClientSecret , "client-secret" , "" , "OAuth client secret (confidential clients only; omit for public/PKCE clients)" )
69+ cmd .PersistentFlags ().
70+ StringVar (& flagRedirectURI , "redirect-uri" , "" , "Redirect URI registered with the OAuth server (default: http://localhost:PORT/callback)" )
71+ cmd .PersistentFlags ().
72+ IntVar (& flagCallbackPort , "port" , 0 , "Local callback port for browser flow (default: 8888 or CALLBACK_PORT env)" )
73+ cmd .PersistentFlags ().
74+ StringVar (& flagScope , "scope" , "" , "Space-separated OAuth scopes (default: \" read write\" )" )
75+ cmd .PersistentFlags ().
76+ StringVar (& flagTokenFile , "token-file" , "" , "Token storage file (default: .authgate-tokens.json or TOKEN_FILE env)" )
77+ cmd .PersistentFlags ().
78+ StringVar (& flagTokenStore , "token-store" , "" , "Token storage backend: auto, file, keyring (default: auto or TOKEN_STORE env)" )
79+ cmd .PersistentFlags ().
80+ BoolVar (& flagDevice , "device" , false , "Force Device Code Flow (skip browser detection)" )
81+ }
82+
83+ // initStoreConfig initialises only the token store and client ID — the minimum
84+ // needed for commands like `token get` that read local credentials without
85+ // making any network calls.
86+ func initStoreConfig () {
87+ if storeConfigInitialized {
88+ return
89+ }
90+ storeConfigInitialized = true
91+
92+ clientID = getConfig (flagClientID , "CLIENT_ID" , "" )
93+ tokenFile = getConfig (flagTokenFile , "TOKEN_FILE" , ".authgate-tokens.json" )
94+ tokenStoreMode = getConfig (flagTokenStore , "TOKEN_STORE" , "auto" )
95+
96+ if clientID == "" {
97+ fmt .Fprintln (os .Stderr , "Error: CLIENT_ID not set. Please provide it via:" )
98+ fmt .Fprintln (os .Stderr , " 1. Command-line flag: --client-id=<your-client-id>" )
99+ fmt .Fprintln (os .Stderr , " 2. Environment variable: CLIENT_ID=<your-client-id>" )
100+ fmt .Fprintln (os .Stderr , " 3. .env file: CLIENT_ID=<your-client-id>" )
101+ fmt .Fprintln (os .Stderr , "\n You can find the client_id in the server startup logs." )
102+ os .Exit (1 )
103+ }
63104
64- flagServerURL = flag .String (
65- "server-url" ,
66- "" ,
67- "OAuth server URL (default: http://localhost:8080 or SERVER_URL env)" ,
68- )
69- flagClientID = flag .String ("client-id" , "" , "OAuth client ID (required, or set CLIENT_ID env)" )
70- flagClientSecret = flag .String (
71- "client-secret" ,
72- "" ,
73- "OAuth client secret (confidential clients only; omit for public/PKCE clients)" ,
74- )
75- flagRedirectURI = flag .String (
76- "redirect-uri" ,
77- "" ,
78- "Redirect URI registered with the OAuth server (default: http://localhost:PORT/callback)" ,
79- )
80- flagCallbackPort = flag .Int (
81- "port" ,
82- 0 ,
83- "Local callback port for browser flow (default: 8888 or CALLBACK_PORT env)" ,
84- )
85- flagScope = flag .String ("scope" , "" , "Space-separated OAuth scopes (default: \" read write\" )" )
86- flagTokenFile = flag .String (
87- "token-file" ,
88- "" ,
89- "Token storage file (default: .authgate-tokens.json or TOKEN_FILE env)" ,
90- )
91- flagTokenStore = flag .String (
92- "token-store" ,
93- "" ,
94- "Token storage backend: auto, file, keyring (default: auto or TOKEN_STORE env)" ,
95- )
96- flagDevice = flag .Bool (
97- "device" ,
98- false ,
99- "Force Device Code Flow (skip browser detection)" ,
100- )
101- flagVersion = flag .Bool ("version" , false , "Print version and exit" )
105+ var storeErr error
106+ tokenStore , storeErr = newTokenStore (tokenStoreMode , tokenFile , defaultKeyringService )
107+ if storeErr != nil {
108+ fmt .Fprintln (os .Stderr , storeErr )
109+ os .Exit (1 )
110+ }
102111}
103112
104113func initConfig () {
@@ -107,27 +116,20 @@ func initConfig() {
107116 }
108117 configInitialized = true
109118
110- flag .Parse ()
111-
112- // --version prints build version and exits immediately.
113- if * flagVersion {
114- fmt .Println (getVersion ())
115- os .Exit (0 )
116- }
119+ // initStoreConfig sets clientID, tokenFile, tokenStoreMode, and tokenStore.
120+ initStoreConfig ()
117121
118122 // --device forces Device Code Flow unconditionally.
119- forceDevice = * flagDevice
123+ forceDevice = flagDevice
120124
121- serverURL = getConfig (* flagServerURL , "SERVER_URL" , "http://localhost:8080" )
122- clientID = getConfig (* flagClientID , "CLIENT_ID" , "" )
123- clientSecret = getConfig (* flagClientSecret , "CLIENT_SECRET" , "" )
124- scope = getConfig (* flagScope , "SCOPE" , "read write" )
125- tokenFile = getConfig (* flagTokenFile , "TOKEN_FILE" , ".authgate-tokens.json" )
125+ serverURL = getConfig (flagServerURL , "SERVER_URL" , "http://localhost:8080" )
126+ clientSecret = getConfig (flagClientSecret , "CLIENT_SECRET" , "" )
127+ scope = getConfig (flagScope , "SCOPE" , "read write" )
126128
127129 // Resolve callback port (int flag needs special handling).
128130 portStr := ""
129- if * flagCallbackPort != 0 {
130- portStr = strconv .Itoa (* flagCallbackPort )
131+ if flagCallbackPort != 0 {
132+ portStr = strconv .Itoa (flagCallbackPort )
131133 }
132134 portStr = getConfig (portStr , "CALLBACK_PORT" , "8888" )
133135 if _ , err := fmt .Sscanf (portStr , "%d" , & callbackPort ); err != nil || callbackPort <= 0 {
@@ -136,7 +138,7 @@ func initConfig() {
136138
137139 // Resolve redirect URI (depends on port, so compute after port is known).
138140 defaultRedirectURI := fmt .Sprintf ("http://localhost:%d/callback" , callbackPort )
139- redirectURI = getConfig (* flagRedirectURI , "REDIRECT_URI" , defaultRedirectURI )
141+ redirectURI = getConfig (flagRedirectURI , "REDIRECT_URI" , defaultRedirectURI )
140142
141143 if err := validateServerURL (serverURL ); err != nil {
142144 fmt .Fprintf (os .Stderr , "Error: Invalid SERVER_URL: %v\n " , err )
@@ -155,15 +157,6 @@ func initConfig() {
155157 fmt .Fprintln (os .Stderr )
156158 }
157159
158- if clientID == "" {
159- fmt .Println ("Error: CLIENT_ID not set. Please provide it via:" )
160- fmt .Println (" 1. Command-line flag: -client-id=<your-client-id>" )
161- fmt .Println (" 2. Environment variable: CLIENT_ID=<your-client-id>" )
162- fmt .Println (" 3. .env file: CLIENT_ID=<your-client-id>" )
163- fmt .Println ("\n You can find the client_id in the server startup logs." )
164- os .Exit (1 )
165- }
166-
167160 if _ , err := uuid .Parse (clientID ); err != nil {
168161 fmt .Fprintf (
169162 os .Stderr ,
@@ -188,14 +181,6 @@ func initConfig() {
188181 panic (fmt .Sprintf ("failed to create retry client: %v" , err ))
189182 }
190183
191- // Initialize token store based on mode
192- tokenStoreMode = getConfig (* flagTokenStore , "TOKEN_STORE" , "auto" )
193- var storeErr error
194- tokenStore , storeErr = newTokenStore (tokenStoreMode , tokenFile , defaultKeyringService )
195- if storeErr != nil {
196- fmt .Fprintln (os .Stderr , storeErr )
197- os .Exit (1 )
198- }
199184 if tokenStoreMode == "auto" {
200185 if ss , ok := tokenStore .(* credstore.SecureStore [credstore.Token ]); ok && ! ss .UseKeyring () {
201186 fmt .Fprintln (
0 commit comments