-
Notifications
You must be signed in to change notification settings - Fork 11
feat: auth login/logout commands #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
774ff74
to
248f7f5
Compare
Implements OAuth2/OIDC authentication flow with PKCE for secure CLI authentication. Includes provider discovery, token exchange, refresh handling, and comprehensive tests. Uses fixed port 51004 for OAuth callbacks to work with Keycloak redirect URI validation.
- Create HTTP client that handles base URL resolution - Support any HTTPDoer implementation for flexibility - Preserve request context, headers, and body - Add comprehensive test coverage (100%)
- Create API client with typed methods for dataplane operations - Support Get, List, Create, and Delete dataplane operations - Use HTTP constants and path constants for maintainability - Organize dataplane code in separate files following Go conventions - Add comprehensive table-driven tests (82.8% coverage)
- Add auth command group following resource-oriented pattern - Implement auth login with OAuth/OIDC flow support - Implement auth logout to clear stored credentials - Store credentials in Kubernetes secrets for security
248f7f5
to
7f22ea2
Compare
// EnsureValidAuth loads credentials and refreshes if expired | ||
func EnsureValidAuth(ctx context.Context, k8sClient k8s.Client, namespace string) (*Credentials, error) { | ||
// Load credentials from secret | ||
secret, err := k8sClient.SecretGet(ctx, namespace, "abctl-auth") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially these commands were under abctl. I was asked to move them to airbox. Not sure what to do with these kind of secret/configmap names, if the intention is that the commands could be moved back under abctl? Do I change them, or leave them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bgroff do you have a preference? Or thoughts on this?
internal/auth/auth.go
Outdated
client := &http.Client{ | ||
Timeout: 30 * time.Second, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be defined outside of this function? At minimal for testing purposes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah. It should probably be configurable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a fixup so that all commands share a default client with timeout. I think that should be enough for now. Testing coverage should be ample as-is.
internal/auth/auth.go
Outdated
client := &http.Client{ | ||
Timeout: 30 * time.Second, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as previous client
.
internal/auth/auth.go
Outdated
Error string `json:"error"` | ||
ErrorDescription string `json:"error_description"` | ||
} | ||
json.NewDecoder(resp.Body).Decode(&errResp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should do something with the error response from this. Maybe populate the errResp
struct with data indicating that the response couldn't be decoded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a fixup that should return the error with status code for context. 9deb541
internal/auth/client.go
Outdated
if tokenType == "" { | ||
tokenType = "Bearer" // Default token type | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not set this to the default value in the ensureValidToken
call when we update/set the credentials
information?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. I'll add fixup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixup - d2d256d
} | ||
|
||
// Refresh the token | ||
tokens, err := RefreshAccessToken(ctx, c.provider, c.clientID, c.credentials.RefreshToken) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we also need to check again (checked first on line 97) if we have a RefreshToken
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand this correctly.. Refresh tokens can never become empty once set - they only get updated to new non-empty values. The double-check isn't needed since the race condition described cannot actually occur in this codebase.
internal/auth/flow.go
Outdated
return base64.RawURLEncoding.EncodeToString(hash[:]) | ||
} | ||
|
||
const successHTML = `<!DOCTYPE html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For code-cleanliness reasons I wonder if we should move this to a separate file and then embed it into this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixup - 2573a23
internal/cmd/auth/logout.go
Outdated
// Delete the auth secret by type (this will delete all Opaque secrets, but in practice | ||
// this namespace should only contain the abctl-auth secret for auth purposes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we know this for sure? We don't have any checks in place to ensure this is true. If no namespace is provided then we fetch the current namespace, and if that namespace happened to contain an abctl-auth
secret then we going to remove all the secrets.
I might be being a bit alarmist but anytime there is a potential delete more than we created my spidey-sense goes off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a good catch. I was just using the already defined interface, but it in hindsight it was not very safe. I have added fixup c6497a9 which will patch the secret with more safety and prescision.
Fix docs to reflect what IsExpired is actually doing.
Declare a default HTTP client for all airbox commands to use.
Plug in default HTTP client into airbox commands.
Return any decode errors for non-200 responses.
Remove runtime TokenType defaulting in favor of setting defaults during credential creation/update.
Embed success HTML
Patch auth secret rather than delete for saftey.
Thanks for the review @colesnodgrass . I've added fixups. It should be good for a second pass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question regarding the potential for an extra /
in a request path.
// DiscoverProvider fetches OIDC provider configuration from well-known endpoint | ||
func DiscoverProvider(ctx context.Context, issuerURL string) (*Provider, error) { | ||
// Construct well-known URL | ||
wellKnownURL := fmt.Sprintf("%s/.well-known/openid-configuration", issuerURL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to ensure the issuerURL
doesn't end with a /
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏻
Summary
Enhances authentication and configuration commands with better namespace handling and code quality improvements. Builds on the abctl config init foundation to add OAuth/OIDC authentication capabilities.
Note: Per @bgroff request, auth/config commands are initially under the
airbox
command to keep this logic separate during development, with plans to possibly merge into the main project later.What's New
Authentication Package Design
The auth package is designed as a reusable library for multiple service contexts (not just CLI). Some of the design was borrowed from #176:
While some patterns (mutex, channels) may seem complex for CLI usage, they're intentional for when this package is reused by API servers and background services in future PRs.
Authentication Commands
Configuration Improvements
Command Structure
After this PR:
airbox auth login
- Authenticate with Airbyteairbox auth logout
- Clear authenticationairbox config init
- Initialize configuration from existing Airbyte installationAuthentication Flow
Test Coverage