-
Notifications
You must be signed in to change notification settings - Fork 18
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
feat: auth providers #1085
feat: auth providers #1085
Conversation
b52009b
to
1192c32
Compare
049c10a
to
6d0ab66
Compare
9c7b10d
to
64f7594
Compare
64f7594
to
0b4d773
Compare
value: parameters[optionalParameterKey] ?? "", | ||
})); | ||
|
||
export function ProviderForm({ |
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.
nit: Wondering we could break the model provider / auth provider logic into a parent component and pass that in as props to ProviderForm, ProviderConfigure, ProviderDeconfigure.
Thinking something like:
// called in auth_providers.tsx specific context
<ProviderForm
onConfigure={configureAuthProvider}
loading={configureAuthProvider.isLoading || isLoading}
tooltips={AuthProviderRequiredTooltips}
/>
// called in model_providers.tsx specific context
<ProviderForm
onConfigure={configureModelProvider}
loading={configureModelProvider.isLoading || isLoading}
tooltips={ModelProviderRequiredTooltips}
/>
Then when you have to call them, you're not worried about handling the other provider type cases (or if something else uses this in the future).
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 it's alright, I'd like for this to be in a follow-up.
const handleSubmit = async (event: React.FormEvent) => { | ||
event.preventDefault(); | ||
try { | ||
const result = await fetch(ApiRoutes.bootstrap.login().url, { | ||
method: "POST", | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}); | ||
|
||
if (result.status === 401) { | ||
setError("Invalid token"); | ||
return; | ||
} else if (result.status !== 200) { | ||
setError("Failed to login: " + result.statusText); | ||
return; | ||
} | ||
|
||
setError(""); | ||
window.location.href = "/admin/auth-providers"; | ||
} catch (e) { | ||
setError("Failed to login: " + e); | ||
} | ||
}; |
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.
Let's Create a BootstrapService
for all of these methods
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.
Sounds good. If it's okay, I'd like for that to be a follow-up refinement. For this PR, the UI just needs to work. Non-optimal things about the code can be fixed afterward.
); | ||
} | ||
|
||
function warningMessage(t: string | undefined): string | undefined { |
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.
nit:
function warningMessage(t: string | undefined): string | undefined { | |
function warningMessage(t?: string) { |
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.
Fixed
provider.id !== CommonModelProviderIds.AZURE_OPENAI && | ||
provider.id !== CommonAuthProviderIds.GOOGLE && | ||
provider.id !== CommonAuthProviderIds.GITHUB, |
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.
nit:
provider.id !== CommonModelProviderIds.AZURE_OPENAI && | |
provider.id !== CommonAuthProviderIds.GOOGLE && | |
provider.id !== CommonAuthProviderIds.GITHUB, | |
![CommonModelProviderIds.AZURE_OPENAI, CommonAuthProviderIds.GOOGLE, CommonAuthProviderIds.GITHUB].includes(provider.id) |
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.
Fixed
export type ProviderStatus = { | ||
configured: boolean; | ||
modelsBackPopulated?: boolean; | ||
icon?: string; | ||
requiredConfigurationParameters?: string[]; | ||
optionalConfigurationParameters?: string[]; | ||
missingConfigurationParameters?: string[]; | ||
}; |
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.
These should follow a more True-to-life pattern:
type ProviderBase = {...}
type ModelProviderBase = ProviderBase & {...}
type ModelProvider = EntityMeta & ProviderBase & { type: 'modelprovider' }
type AuthProviderBase = ProviderBase & {...}
type AuthProvider = EntityMeta & AuthProviderBase & { type: 'authprovider' }
type Provider = ModelProvider | AuthProvider
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'd like for this to be a follow-up improvement.
case error instanceof AxiosError && | ||
[401, 403].includes(error.response?.status ?? 0): |
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.
@g-linville can you explain why you needed to add this? I'd like to make sure our first layer of error handling can catch this and convert it to the proper error 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.
I don't 100% remember but I think it had to do with the error we get back when we hit /api/me
with my new changes to the backend.
@ryanhopperlowe you need to download and build my branch of the tools repo from this PR: obot-platform/tools#320 And then set the |
17e202c
to
78654e2
Compare
Sorry for force pushing again. There were more conflicts that I needed to fix. |
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.
Some nits and suggestions, nothing that should hold this up.
// Allow for updating credentials. The only way to update a credential is to delete the existing one and recreate it. | ||
if err := ap.gptscript.DeleteCredential(req.Context(), string(ref.UID), ref.Name); err != nil && !strings.HasSuffix(err.Error(), "credential not found") { | ||
return fmt.Errorf("failed to update credential: %w", err) | ||
} | ||
|
||
for key, val := range envVars { | ||
if val == "" { | ||
delete(envVars, key) | ||
} | ||
} | ||
|
||
if err := ap.gptscript.CreateCredential(req.Context(), gptscript.Credential{ | ||
Context: string(ref.UID), | ||
ToolName: ref.Name, | ||
Type: gptscript.CredentialTypeTool, | ||
Env: envVars, | ||
}); err != nil { | ||
return fmt.Errorf("failed to create credential for auth provider %q: %w", ref.Name, err) | ||
} |
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 the env hasn't changed this will still delete and recreate the credential and stop the provider even the env hasn't changed, right? Would it be better to check for changes to the credential first?
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.
All I did here was copy the logic that we used for model providers. I think @thedadams may have been the one who implemented that, and would know the answer to this.
@g-linville @ryanhopperlowe this has recently changed due to #1193 merging. Now, instead of setting
|
@njhale @ryanhopperlowe for testing with this branch, the old env var should be used to set the registry, since I have not (yet) brought in those changes from main to this branch. |
pkg/api/handlers/authprovider.go
Outdated
var credEnvVars map[string]string | ||
if ref.Status.Tool != nil { | ||
if envVars := ref.Status.Tool.Metadata["envVars"]; envVars != "" { | ||
fmt.Printf("revealing creds for auth provider %q\n", ref.Name) |
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.
Nit: this is debug log? if not should we use our standard log library to print?
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.
Thanks for finding these! I forgot to remove them before. Pushed the fix
pkg/api/handlers/authprovider.go
Outdated
return types.NewErrBadRequest("%q is not an auth provider", ref.Name) | ||
} | ||
|
||
fmt.Printf("revealing creds for auth provider %q\n", ref.Name) |
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.
Debug log
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.
Fixed
pkg/bootstrap/bootstrap.go
Outdated
token = fmt.Sprintf("%x", bytes) | ||
|
||
// We deliberately only print the token if it was not provided by the user. | ||
fmt.Printf("Bootstrap token: %s\n", token) |
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 print it so that user can grab it from the log? if so I would suggest the log to be more descriptive.
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.
Fixed
538836b
to
f5b843a
Compare
Rebased again to fix conflicts. |
|
||
type AuthProviderManifest struct { | ||
Name string `json:"name"` | ||
Namespace string `json:"namespace"` |
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 shouldn't be part of the manifest.
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.
Are you sure? Name
is part of the ModelProviderManifest. We need a namespace for auth providers, since I was told I can't assume that they will always be in default
.
pkg/api/router/router.go
Outdated
@@ -36,6 +37,7 @@ func Router(services *services.Services) (http.Handler, error) { | |||
sendgridWebhookHandler := sendgrid.NewInboundWebhookHandler(services.StorageClient, services.EmailServerName, services.SendgridWebhookUsername, services.SendgridWebhookPassword) | |||
|
|||
// Version | |||
|
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.
nit: remove line
pkg/api/server/server.go
Outdated
// If this is not a request coming from browser or the proxy is not enabled, then return 403. | ||
if !isOAuthPath && (s.proxyServer == nil || req.Method != http.MethodGet || slices.Contains(user.GetGroups(), authz.AuthenticatedGroup) || !strings.Contains(strings.ToLower(req.UserAgent()), "mozilla")) { | ||
if strings.HasPrefix(req.URL.Path, "/api/") { | ||
if !s.authorizer.Authorize(req, user) { |
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 if-statement should not be nested.
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 we bring it outside of if strings.HasPrefix(req.URL.Path, "/api/")
, then we end up denying all unauthenticated requests to the user UI, which basically breaks everything. It needs to be nested in here.
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 that is the case, then that should be fixed outside of this. This definitely should not be nested.
return nil | ||
} | ||
|
||
func (b *Bootstrap) Logout(req api.Context) error { |
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.
Can we delete the bootstrap
user from the database in this case? If it doesn't make sense to do here, can we do it at some point?
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'm okay with doing this, but I'm not sure that it's really necessary?
kclient "sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
type Dispatcher struct { | ||
invoker *invoke.Invoker | ||
gptscript *gptscript.GPTScript | ||
client kclient.Client | ||
lock *sync.RWMutex | ||
urls map[string]*url.URL | ||
modelLock *sync.RWMutex |
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.
nit: the dispatcher can be separated from the "transformer" and the dispatcher made generic with the implementation used for both model and auth providers.
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'd like for this to be a later change if it is needed.
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 agree. Which is why it is a nit.
pkg/proxy/proxy.go
Outdated
return | ||
} | ||
|
||
http.Error(w, "no auth providers configured", http.StatusInternalServerError) |
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 a bad request?
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
pkg/proxy/proxy.go
Outdated
URL: req.URL.String(), | ||
Header: make(map[string][]string), | ||
} | ||
for k, v := range req.Header { |
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.
sr.Header = req.Header
pkg/services/config.go
Outdated
EmailServerName string `usage:"The name of the email server to display for email receivers"` | ||
Docker bool `usage:"Enable Docker support" default:"false" env:"OBOT_DOCKER"` | ||
EnvKeys []string `usage:"The environment keys to pass through to the GPTScript server" env:"OBOT_ENV_KEYS"` | ||
KnowledgeSetIngestionLimit int `usage:"The maximum number of files to ingest into a knowledge set" default:"3000" env:"OBOT_KNOWLEDGESET_INGESTION_LIMIT" name:"knowledge-set-ingestion-limit"` | ||
NoReplyEmailAddress string `usage:"The email to use for no-reply emails from obot"` | ||
DisableAuthentication bool `usage:"Disable authentication" default:"false" env:"OBOT_DISABLE_AUTHENTICATION"` |
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 one were to use this as a CLI flag, using --disable-authentication
seems that it would actually enable authentication.
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 point. I'll invert this
pkg/services/config.go
Outdated
EmailServerName string `usage:"The name of the email server to display for email receivers"` | ||
Docker bool `usage:"Enable Docker support" default:"false" env:"OBOT_DOCKER"` | ||
EnvKeys []string `usage:"The environment keys to pass through to the GPTScript server" env:"OBOT_ENV_KEYS"` | ||
KnowledgeSetIngestionLimit int `usage:"The maximum number of files to ingest into a knowledge set" default:"3000" env:"OBOT_KNOWLEDGESET_INGESTION_LIMIT" name:"knowledge-set-ingestion-limit"` | ||
NoReplyEmailAddress string `usage:"The email to use for no-reply emails from obot"` |
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.
Is this related to your changes?
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.
Nope. Might have been an accident when trying to resolve conflicts. Maybe this got added and removed at some point? I can take it away.
pkg/services/config.go
Outdated
@@ -23,6 +23,7 @@ import ( | |||
"github.com/obot-platform/obot/pkg/api/authn" | |||
"github.com/obot-platform/obot/pkg/api/authz" | |||
"github.com/obot-platform/obot/pkg/api/server" | |||
bootstrap2 "github.com/obot-platform/obot/pkg/bootstrap" |
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.
nit: I don't think this import needs to be renamed.
Signed-off-by: Grant Linville <[email protected]>
Signed-off-by: Grant Linville <[email protected]>
Signed-off-by: Grant Linville <[email protected]>
Signed-off-by: Grant Linville <[email protected]>
Signed-off-by: Donnie Adams <[email protected]>
704e9b8
to
c36a669
Compare
Signed-off-by: Donnie Adams <[email protected]>
This PR is a substantial refactoring of our auth providers so that they are now brought in as external tools rather than being built into the gateway. We also now allow multiple auth providers to be configured at the same time.
There is a known bug in the frontend where you have to refresh the page after configuring the auth provider in order to see the change. It's taken me long enough to get this PR ready, so I figured it would be best to fix that in a follow up (or have Ryan or Ivy figure it out for me, lol).Fixed. Thanks, Ivy!I will also do another follow-up PR to support deconfiguring auth providers. Currently, this is unimplemented in the backend.