@@ -10,22 +10,52 @@ import (
1010
1111 "github.com/stackitcloud/stackit-cli/internal/pkg/config"
1212 pkgErrors "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
13+ "github.com/stackitcloud/stackit-cli/internal/pkg/print"
1314
1415 "github.com/zalando/go-keyring"
1516)
1617
18+ // Package-level printer for debug logging in storage operations
19+ var storagePrinter = print .NewPrinter ()
20+
21+ // SetStoragePrinter sets the printer used for storage debug logging
22+ // This should be called with the main command's printer to ensure consistent verbosity
23+ func SetStoragePrinter (p * print.Printer ) {
24+ if p != nil {
25+ storagePrinter = p
26+ }
27+ }
28+
1729// Name of an auth-related field
1830type authFieldKey string
1931
2032// Possible values of authentication flows
2133type AuthFlow string
2234
35+ // StorageContext represents the context in which credentials are stored
36+ // CLI context is for the CLI's own authentication
37+ // API context is for Terraform Provider and SDK authentication
38+ type StorageContext string
39+
2340const (
24- keyringService = "stackit-cli"
25- textFileName = "cli-auth-storage.txt"
41+ StorageContextCLI StorageContext = "cli"
42+ StorageContextAPI StorageContext = "api"
43+ )
44+
45+ const (
46+ keyringServiceCLI = "stackit-cli"
47+ keyringServiceAPI = "stackit-cli-api"
48+ textFileNameCLI = "cli-auth-storage.txt"
49+ textFileNameAPI = "cli-api-auth-storage.txt"
2650 envAccessTokenName = "STACKIT_ACCESS_TOKEN"
2751)
2852
53+ // Legacy constants for backward compatibility
54+ const (
55+ keyringService = keyringServiceCLI
56+ textFileName = textFileNameCLI
57+ )
58+
2959const (
3060 SESSION_EXPIRES_AT_UNIX authFieldKey = "session_expires_at_unix"
3161 ACCESS_TOKEN authFieldKey = "access_token"
@@ -70,10 +100,40 @@ var loginAuthFieldKeys = []authFieldKey{
70100 USER_EMAIL ,
71101}
72102
103+ // getKeyringServiceName returns the keyring service name for the given context and profile
104+ func getKeyringServiceName (context StorageContext , profile string ) string {
105+ var baseService string
106+ switch context {
107+ case StorageContextAPI :
108+ baseService = keyringServiceAPI
109+ default :
110+ baseService = keyringServiceCLI
111+ }
112+
113+ if profile != config .DefaultProfileName {
114+ return filepath .Join (baseService , profile )
115+ }
116+ return baseService
117+ }
118+
119+ // getTextFileName returns the text file name for the given context
120+ func getTextFileName (context StorageContext ) string {
121+ switch context {
122+ case StorageContextAPI :
123+ return textFileNameAPI
124+ default :
125+ return textFileNameCLI
126+ }
127+ }
128+
73129func SetAuthFlow (value AuthFlow ) error {
74130 return SetAuthField (authFlowType , string (value ))
75131}
76132
133+ func SetAuthFlowWithContext (context StorageContext , value AuthFlow ) error {
134+ return SetAuthFieldWithContext (context , authFlowType , string (value ))
135+ }
136+
77137// Sets the values in the auth storage according to the given map
78138func SetAuthFieldMap (keyMap map [authFieldKey ]string ) error {
79139 for key , value := range keyMap {
@@ -85,19 +145,39 @@ func SetAuthFieldMap(keyMap map[authFieldKey]string) error {
85145 return nil
86146}
87147
148+ // SetAuthFieldMapWithContext sets the values in the auth storage according to the given map for a specific context
149+ func SetAuthFieldMapWithContext (context StorageContext , keyMap map [authFieldKey ]string ) error {
150+ for key , value := range keyMap {
151+ err := SetAuthFieldWithContext (context , key , value )
152+ if err != nil {
153+ return fmt .Errorf ("set auth field \" %s\" : %w" , key , err )
154+ }
155+ }
156+ return nil
157+ }
158+
88159func SetAuthField (key authFieldKey , value string ) error {
160+ return SetAuthFieldWithContext (StorageContextCLI , key , value )
161+ }
162+
163+ // SetAuthFieldWithContext sets an auth field for a specific storage context
164+ func SetAuthFieldWithContext (context StorageContext , key authFieldKey , value string ) error {
89165 activeProfile , err := config .GetProfile ()
90166 if err != nil {
91167 return fmt .Errorf ("get profile: %w" , err )
92168 }
93169
94- return setAuthFieldWithProfile ( activeProfile , key , value )
170+ return setAuthFieldWithProfileAndContext ( context , activeProfile , key , value )
95171}
96172
97173func setAuthFieldWithProfile (profile string , key authFieldKey , value string ) error {
98- err := setAuthFieldInKeyring (profile , key , value )
174+ return setAuthFieldWithProfileAndContext (StorageContextCLI , profile , key , value )
175+ }
176+
177+ func setAuthFieldWithProfileAndContext (context StorageContext , profile string , key authFieldKey , value string ) error {
178+ err := setAuthFieldInKeyringWithContext (context , profile , key , value )
99179 if err != nil {
100- errFallback := setAuthFieldInEncodedTextFile ( profile , key , value )
180+ errFallback := setAuthFieldInEncodedTextFileWithContext ( context , profile , key , value )
101181 if errFallback != nil {
102182 return fmt .Errorf ("write to keyring failed (%w), try writing to encoded text file: %w" , err , errFallback )
103183 }
@@ -106,27 +186,37 @@ func setAuthFieldWithProfile(profile string, key authFieldKey, value string) err
106186}
107187
108188func setAuthFieldInKeyring (activeProfile string , key authFieldKey , value string ) error {
109- if activeProfile != config .DefaultProfileName {
110- activeProfileKeyring := filepath .Join (keyringService , activeProfile )
111- return keyring .Set (activeProfileKeyring , string (key ), value )
112- }
113- return keyring .Set (keyringService , string (key ), value )
189+ return setAuthFieldInKeyringWithContext (StorageContextCLI , activeProfile , key , value )
190+ }
191+
192+ func setAuthFieldInKeyringWithContext (context StorageContext , activeProfile string , key authFieldKey , value string ) error {
193+ keyringServiceName := getKeyringServiceName (context , activeProfile )
194+ return keyring .Set (keyringServiceName , string (key ), value )
114195}
115196
116197func DeleteAuthField (key authFieldKey ) error {
198+ return DeleteAuthFieldWithContext (StorageContextCLI , key )
199+ }
200+
201+ // DeleteAuthFieldWithContext deletes an auth field for a specific storage context
202+ func DeleteAuthFieldWithContext (context StorageContext , key authFieldKey ) error {
117203 activeProfile , err := config .GetProfile ()
118204 if err != nil {
119205 return fmt .Errorf ("get profile: %w" , err )
120206 }
121- return deleteAuthFieldWithProfile ( activeProfile , key )
207+ return deleteAuthFieldWithProfileAndContext ( context , activeProfile , key )
122208}
123209
124210func deleteAuthFieldWithProfile (profile string , key authFieldKey ) error {
125- err := deleteAuthFieldInKeyring (profile , key )
211+ return deleteAuthFieldWithProfileAndContext (StorageContextCLI , profile , key )
212+ }
213+
214+ func deleteAuthFieldWithProfileAndContext (context StorageContext , profile string , key authFieldKey ) error {
215+ err := deleteAuthFieldInKeyringWithContext (context , profile , key )
126216 if err != nil {
127217 // if the key is not found, we can ignore the error
128218 if ! errors .Is (err , keyring .ErrNotFound ) {
129- errFallback := deleteAuthFieldInEncodedTextFile ( profile , key )
219+ errFallback := deleteAuthFieldInEncodedTextFileWithContext ( context , profile , key )
130220 if errFallback != nil {
131221 return fmt .Errorf ("delete from keyring failed (%w), try deleting from encoded text file: %w" , err , errFallback )
132222 }
@@ -136,13 +226,18 @@ func deleteAuthFieldWithProfile(profile string, key authFieldKey) error {
136226}
137227
138228func deleteAuthFieldInEncodedTextFile (activeProfile string , key authFieldKey ) error {
139- err := createEncodedTextFile (activeProfile )
229+ return deleteAuthFieldInEncodedTextFileWithContext (StorageContextCLI , activeProfile , key )
230+ }
231+
232+ func deleteAuthFieldInEncodedTextFileWithContext (context StorageContext , activeProfile string , key authFieldKey ) error {
233+ err := createEncodedTextFileWithContext (context , activeProfile )
140234 if err != nil {
141235 return err
142236 }
143237
144238 textFileDir := config .GetProfileFolderPath (activeProfile )
145- textFilePath := filepath .Join (textFileDir , textFileName )
239+ fileName := getTextFileName (context )
240+ textFilePath := filepath .Join (textFileDir , fileName )
146241
147242 contentEncoded , err := os .ReadFile (textFilePath )
148243 if err != nil {
@@ -173,21 +268,27 @@ func deleteAuthFieldInEncodedTextFile(activeProfile string, key authFieldKey) er
173268}
174269
175270func deleteAuthFieldInKeyring (activeProfile string , key authFieldKey ) error {
176- keyringServiceLocal := keyringService
177- if activeProfile != config .DefaultProfileName {
178- keyringServiceLocal = filepath .Join (keyringService , activeProfile )
179- }
271+ return deleteAuthFieldInKeyringWithContext (StorageContextCLI , activeProfile , key )
272+ }
180273
181- return keyring .Delete (keyringServiceLocal , string (key ))
274+ func deleteAuthFieldInKeyringWithContext (context StorageContext , activeProfile string , key authFieldKey ) error {
275+ keyringServiceName := getKeyringServiceName (context , activeProfile )
276+ return keyring .Delete (keyringServiceName , string (key ))
182277}
183278
184279func setAuthFieldInEncodedTextFile (activeProfile string , key authFieldKey , value string ) error {
185- err := createEncodedTextFile (activeProfile )
280+ return setAuthFieldInEncodedTextFileWithContext (StorageContextCLI , activeProfile , key , value )
281+ }
282+
283+ func setAuthFieldInEncodedTextFileWithContext (context StorageContext , activeProfile string , key authFieldKey , value string ) error {
284+ textFileDir := config .GetProfileFolderPath (activeProfile )
285+ fileName := getTextFileName (context )
286+ textFilePath := filepath .Join (textFileDir , fileName )
287+
288+ err := createEncodedTextFileWithContext (context , activeProfile )
186289 if err != nil {
187290 return err
188291 }
189- textFileDir := config .GetProfileFolderPath (activeProfile )
190- textFilePath := filepath .Join (textFileDir , textFileName )
191292
192293 contentEncoded , err := os .ReadFile (textFilePath )
193294 if err != nil {
@@ -219,8 +320,13 @@ func setAuthFieldInEncodedTextFile(activeProfile string, key authFieldKey, value
219320
220321// Populates the values in the given map according to the auth storage
221322func GetAuthFieldMap (keyMap map [authFieldKey ]string ) error {
323+ return GetAuthFieldMapWithContext (StorageContextCLI , keyMap )
324+ }
325+
326+ // GetAuthFieldMapWithContext populates the values in the given map according to the auth storage for a specific context
327+ func GetAuthFieldMapWithContext (context StorageContext , keyMap map [authFieldKey ]string ) error {
222328 for key := range keyMap {
223- value , err := GetAuthField ( key )
329+ value , err := GetAuthFieldWithContext ( context , key )
224330 if err != nil {
225331 return fmt .Errorf ("get auth field \" %s\" : %w" , key , err )
226332 }
@@ -230,23 +336,36 @@ func GetAuthFieldMap(keyMap map[authFieldKey]string) error {
230336}
231337
232338func GetAuthFlow () (AuthFlow , error ) {
233- value , err := GetAuthField (authFlowType )
339+ return GetAuthFlowWithContext (StorageContextCLI )
340+ }
341+
342+ func GetAuthFlowWithContext (context StorageContext ) (AuthFlow , error ) {
343+ value , err := GetAuthFieldWithContext (context , authFlowType )
234344 return AuthFlow (value ), err
235345}
236346
237347func GetAuthField (key authFieldKey ) (string , error ) {
348+ return GetAuthFieldWithContext (StorageContextCLI , key )
349+ }
350+
351+ // GetAuthFieldWithContext retrieves an auth field for a specific storage context
352+ func GetAuthFieldWithContext (context StorageContext , key authFieldKey ) (string , error ) {
238353 activeProfile , err := config .GetProfile ()
239354 if err != nil {
240355 return "" , fmt .Errorf ("get profile: %w" , err )
241356 }
242- return getAuthFieldWithProfile ( activeProfile , key )
357+ return getAuthFieldWithProfileAndContext ( context , activeProfile , key )
243358}
244359
245360func getAuthFieldWithProfile (profile string , key authFieldKey ) (string , error ) {
246- value , err := getAuthFieldFromKeyring (profile , key )
361+ return getAuthFieldWithProfileAndContext (StorageContextCLI , profile , key )
362+ }
363+
364+ func getAuthFieldWithProfileAndContext (context StorageContext , profile string , key authFieldKey ) (string , error ) {
365+ value , err := getAuthFieldFromKeyringWithContext (context , profile , key )
247366 if err != nil {
248367 var errFallback error
249- value , errFallback = getAuthFieldFromEncodedTextFile ( profile , key )
368+ value , errFallback = getAuthFieldFromEncodedTextFileWithContext ( context , profile , key )
250369 if errFallback != nil {
251370 return "" , fmt .Errorf ("read from keyring: %w, read from encoded file as fallback: %w" , err , errFallback )
252371 }
@@ -255,21 +374,27 @@ func getAuthFieldWithProfile(profile string, key authFieldKey) (string, error) {
255374}
256375
257376func getAuthFieldFromKeyring (activeProfile string , key authFieldKey ) (string , error ) {
258- if activeProfile != config .DefaultProfileName {
259- activeProfileKeyring := filepath .Join (keyringService , activeProfile )
260- return keyring .Get (activeProfileKeyring , string (key ))
261- }
262- return keyring .Get (keyringService , string (key ))
377+ return getAuthFieldFromKeyringWithContext (StorageContextCLI , activeProfile , key )
378+ }
379+
380+ func getAuthFieldFromKeyringWithContext (context StorageContext , activeProfile string , key authFieldKey ) (string , error ) {
381+ keyringServiceName := getKeyringServiceName (context , activeProfile )
382+ return keyring .Get (keyringServiceName , string (key ))
263383}
264384
265385func getAuthFieldFromEncodedTextFile (activeProfile string , key authFieldKey ) (string , error ) {
266- err := createEncodedTextFile (activeProfile )
386+ return getAuthFieldFromEncodedTextFileWithContext (StorageContextCLI , activeProfile , key )
387+ }
388+
389+ func getAuthFieldFromEncodedTextFileWithContext (context StorageContext , activeProfile string , key authFieldKey ) (string , error ) {
390+ err := createEncodedTextFileWithContext (context , activeProfile )
267391 if err != nil {
268392 return "" , err
269393 }
270394
271395 textFileDir := config .GetProfileFolderPath (activeProfile )
272- textFilePath := filepath .Join (textFileDir , textFileName )
396+ fileName := getTextFileName (context )
397+ textFilePath := filepath .Join (textFileDir , fileName )
273398
274399 contentEncoded , err := os .ReadFile (textFilePath )
275400 if err != nil {
@@ -295,8 +420,13 @@ func getAuthFieldFromEncodedTextFile(activeProfile string, key authFieldKey) (st
295420// If it doesn't, creates it with the content "{}" encoded.
296421// If it does, does nothing (and returns nil).
297422func createEncodedTextFile (activeProfile string ) error {
423+ return createEncodedTextFileWithContext (StorageContextCLI , activeProfile )
424+ }
425+
426+ func createEncodedTextFileWithContext (context StorageContext , activeProfile string ) error {
298427 textFileDir := config .GetProfileFolderPath (activeProfile )
299- textFilePath := filepath .Join (textFileDir , textFileName )
428+ fileName := getTextFileName (context )
429+ textFilePath := filepath .Join (textFileDir , fileName )
300430
301431 err := os .MkdirAll (textFileDir , 0o750 )
302432 if err != nil {
@@ -364,23 +494,33 @@ func GetAuthEmail() (string, error) {
364494}
365495
366496func LoginUser (email , accessToken , refreshToken , sessionExpiresAtUnix string ) error {
497+ return LoginUserWithContext (StorageContextCLI , email , accessToken , refreshToken , sessionExpiresAtUnix )
498+ }
499+
500+ // LoginUserWithContext stores user login credentials for a specific storage context
501+ func LoginUserWithContext (context StorageContext , email , accessToken , refreshToken , sessionExpiresAtUnix string ) error {
367502 authFields := map [authFieldKey ]string {
368503 SESSION_EXPIRES_AT_UNIX : sessionExpiresAtUnix ,
369504 ACCESS_TOKEN : accessToken ,
370505 REFRESH_TOKEN : refreshToken ,
371506 USER_EMAIL : email ,
372507 }
373508
374- err := SetAuthFieldMap ( authFields )
509+ err := SetAuthFieldMapWithContext ( context , authFields )
375510 if err != nil {
376511 return fmt .Errorf ("set auth fields: %w" , err )
377512 }
378513 return nil
379514}
380515
381516func LogoutUser () error {
517+ return LogoutUserWithContext (StorageContextCLI )
518+ }
519+
520+ // LogoutUserWithContext removes user authentication for a specific storage context
521+ func LogoutUserWithContext (context StorageContext ) error {
382522 for _ , key := range loginAuthFieldKeys {
383- err := DeleteAuthField ( key )
523+ err := DeleteAuthFieldWithContext ( context , key )
384524 if err != nil {
385525 return fmt .Errorf ("delete auth field \" %s\" : %w" , key , err )
386526 }
0 commit comments