Skip to content

Commit ed5694d

Browse files
patelspratikiExalt
authored andcommitted
feat(BRE2-915): API Key as auth method (brevdev#369)
* feat(BRE2-915): api key as auth method * review feedback
1 parent 372f80d commit ed5694d

25 files changed

Lines changed: 1413 additions & 154 deletions

pkg/auth/auth.go

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
104171
func 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.
226328
func 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
415517
func 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

Comments
 (0)