feat: auth provider REST API and public provider discovery

This commit is contained in:
2026-08-03 10:51:35 +01:00
parent f3b9f6f286
commit dde47de145
6 changed files with 263 additions and 106 deletions
+45
View File
@@ -65,6 +65,12 @@ func HandleLocalLogin(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// The login page hides the form, but the page is a courtesy and the API is
// the boundary.
if !services.IsLocalLoginEnabled(instanceID) {
c.JSON(http.StatusForbidden, gin.H{"error": "password sign-in is disabled for this instance"})
return
}
u, err := services.GetUserInInstanceByEmail(instanceID, body.Email)
if err != nil || !services.VerifyPassword(u, body.Password) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
@@ -82,6 +88,45 @@ func HandleLocalLogin(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// HandleListPublicProviders is unauthenticated: it is what the login page reads
// to decide what to draw. It carries no issuer, no client ID and no secret —
// only what a button needs, because anyone who can reach the login page can
// read this.
func HandleListPublicProviders(c *gin.Context) {
type publicProvider struct {
ID string `json:"id"`
Name string `json:"name"`
Preset string `json:"preset"`
}
out := []publicProvider{}
instanceID, err := resolveLoginInstance(c)
if err != nil {
// An unresolvable instance is not an error the login page can act on:
// it still has to render a password form. Answer the safe shape.
c.JSON(http.StatusOK, gin.H{"local_enabled": true, "providers": out})
return
}
// A lapsed licence stops SSO, so a button that cannot work is not offered.
if services.GetLicenseState(instanceID).Feature("oidc") {
providers, err := services.ListEnabledAuthProviders(instanceID)
if err == nil {
for _, p := range providers {
out = append(out, publicProvider{ID: p.ProviderID, Name: p.Name, Preset: p.Preset})
}
}
}
localEnabled := services.IsLocalLoginEnabled(instanceID)
// Belt and braces against a hand-edited database: a login page with neither
// a form nor a button is unrecoverable without database access.
if !localEnabled && len(out) == 0 {
localEnabled = true
}
c.JSON(http.StatusOK, gin.H{"local_enabled": localEnabled, "providers": out})
}
func HandleBootstrapStatus(c *gin.Context) {
var (
n int64