@@ -101,6 +101,73 @@ type Auth struct {
101101 shouldLogin func () (bool , error )
102102}
103103
104+ const BrevAPIKeyPrefix = "bak-"
105+
106+ const MissingAPIKeyOrgIDMessage = "api key auth requires an org id; run brev login --api-key <api-key> --org-id <org-id>"
107+
108+ type APIKeyAuthStore interface {
109+ GetAuthTokens () (* entity.AuthTokens , error )
110+ }
111+
112+ type CurrentUserAuthStore interface {
113+ APIKeyAuthStore
114+ GetCurrentUser () (* entity.User , error )
115+ }
116+
117+ type CLIAuth struct {
118+ apiKey bool
119+ user * entity.User
120+ }
121+
122+ func (a CLIAuth ) IsAPIKey () bool {
123+ return a .apiKey
124+ }
125+
126+ func (a CLIAuth ) User () * entity.User {
127+ return a .user
128+ }
129+
130+ func ResolveCLIAuth (store CurrentUserAuthStore ) (CLIAuth , error ) {
131+ if IsAPIKeyAuthStore (store ) {
132+ return CLIAuth {apiKey : true }, nil
133+ }
134+ user , err := store .GetCurrentUser ()
135+ if err != nil {
136+ return CLIAuth {}, breverrors .WrapAndTrace (err )
137+ }
138+ return CLIAuth {user : user }, nil
139+ }
140+
141+ func IsBrevAPIKey (token string ) bool {
142+ return strings .HasPrefix (strings .TrimSpace (token ), BrevAPIKeyPrefix )
143+ }
144+
145+ func IsAPIKeyAuthStore (authTokensProvider APIKeyAuthStore ) bool {
146+ tokens , err := authTokensProvider .GetAuthTokens ()
147+ if err != nil {
148+ return false
149+ }
150+ if tokens == nil {
151+ return false
152+ }
153+ return IsBrevAPIKey (tokens .APIKey )
154+ }
155+
156+ func GetAPIKeyOrgID (authTokensProvider APIKeyAuthStore ) (string , error ) {
157+ tokens , err := authTokensProvider .GetAuthTokens ()
158+ if err != nil {
159+ return "" , breverrors .WrapAndTrace (err )
160+ }
161+ if tokens == nil {
162+ return "" , breverrors .NewValidationError (MissingAPIKeyOrgIDMessage )
163+ }
164+ orgID := strings .TrimSpace (tokens .APIKeyOrgID )
165+ if orgID == "" {
166+ return "" , breverrors .NewValidationError (MissingAPIKeyOrgIDMessage )
167+ }
168+ return orgID , nil
169+ }
170+
104171func NewAuth (authStore AuthStore , oauth OAuth ) * Auth {
105172 return & Auth {
106173 authStore : authStore ,
@@ -146,6 +213,11 @@ func (t Auth) GetFreshAccessTokenOrNil() (string, error) {
146213 return "" , nil
147214 }
148215
216+ apiKey := strings .TrimSpace (tokens .APIKey )
217+ if apiKey != "" {
218+ return apiKey , nil
219+ }
220+
149221 // should always at least have access token?
150222 if tokens .AccessToken == "" {
151223 breverrors .GetDefaultErrorReporter ().ReportMessage ("access token is an empty string but shouldn't be" )
@@ -222,6 +294,36 @@ func (t Auth) LoginWithToken(token string) error {
222294 return nil
223295}
224296
297+ func (t Auth ) LoginWithAPIKey (apiKey string , orgID string ) error {
298+ apiKey = strings .TrimSpace (apiKey )
299+ if apiKey == "" {
300+ return breverrors .NewValidationError ("api key is empty" )
301+ }
302+ if ! IsBrevAPIKey (apiKey ) {
303+ return breverrors .NewValidationError (fmt .Sprintf ("api key must start with %s" , BrevAPIKeyPrefix ))
304+ }
305+ orgID = strings .TrimSpace (orgID )
306+ if orgID == "" {
307+ return breverrors .NewValidationError (MissingAPIKeyOrgIDMessage )
308+ }
309+
310+ tokens , err := t .getSavedTokensOrNil ()
311+ if err != nil {
312+ return breverrors .WrapAndTrace (err )
313+ }
314+ if tokens == nil {
315+ tokens = & entity.AuthTokens {}
316+ }
317+ tokens .APIKey = apiKey
318+ tokens .APIKeyOrgID = orgID
319+
320+ err = t .authStore .SaveAuthTokens (* tokens )
321+ if err != nil {
322+ return breverrors .WrapAndTrace (err )
323+ }
324+ return nil
325+ }
326+
225327// showLoginURL displays the login link and CLI alternative for manual navigation.
226328func showLoginURL (url string ) {
227329 urlType := color .New (color .FgCyan , color .Bold ).SprintFunc ()
@@ -313,7 +415,7 @@ func (t Auth) getSavedTokensOrNil() (*entity.AuthTokens, error) {
313415 }
314416 return nil , breverrors .WrapAndTrace (err )
315417 }
316- if tokens != nil && tokens .AccessToken == "" && tokens .RefreshToken == "" {
418+ if tokens != nil && tokens .AccessToken == "" && tokens .RefreshToken == "" && tokens . APIKey == "" {
317419 return nil , nil
318420 }
319421 return tokens , nil
@@ -415,7 +517,7 @@ func AuthProviderFlagToCredentialProvider(authProviderFlag string) entity.Creden
415517func StandardLogin (authProvider string , email string , tokens * entity.AuthTokens ) OAuth {
416518 // Set KAS as the default authenticator
417519 shouldPromptEmail := false
418- if email == "" && tokens != nil && tokens .AccessToken != "" {
520+ if email == "" && tokens != nil && tokens .AccessToken != "" && tokens . APIKey == "" {
419521 email = GetEmailFromToken (tokens .AccessToken )
420522 shouldPromptEmail = true
421523 }
@@ -445,7 +547,7 @@ func StandardLogin(authProvider string, email string, tokens *entity.AuthTokens)
445547 kasAuthenticator ,
446548 })
447549
448- if tokens != nil && tokens .AccessToken != "" {
550+ if tokens != nil && tokens .AccessToken != "" && tokens . APIKey == "" {
449551 authenticatorFromToken , errr := authRetriever .GetByToken (tokens .AccessToken )
450552 if errr != nil {
451553 fmt .Printf ("%v\n " , errr )
0 commit comments