From b91b0e8796ce2d2f2de61afb8376e2936b5dd014 Mon Sep 17 00:00:00 2001 From: StarFleetCPTN Date: Mon, 24 Mar 2025 14:51:38 -0700 Subject: [PATCH] feat: Implement authentication provider management components - Added new templates for managing authentication providers, including forms for creating and editing providers. - Implemented backend logic to handle retrieval, creation, and deletion of authentication providers. - Introduced new database migrations to support the storage of authentication provider data. - Enhanced the user interface to display available authentication providers and their statuses. - Added routes and handlers for managing authentication provider actions in the web application. --- components/auth_provider_form.templ | 655 +++++++++++ components/auth_providers.templ | 380 +++++++ components/auth_providers_buttons.templ | 52 + components/layout.templ | 21 +- components/login.templ | 12 +- components/settings.templ | 1005 +++++++++++------ internal/db/auth_provider.go | 154 +++ internal/db/auth_provider_db.go | 156 +++ internal/db/db.go | 28 + .../db/migrations/011_add_auth_providers.go | 134 +++ internal/db/migrations/migrations.go | 1 + internal/web/handlers/auth_handlers.go | 529 +++++++++ .../web/handlers/auth_provider_handlers.go | 428 +++++++ internal/web/handlers/routes.go | 30 + static/img/authelia.svg | 57 + static/img/authentik.svg | 23 + static/img/keycloak.svg | 81 ++ static/img/oauth2.svg | 106 ++ static/img/oidc.svg | 11 + static/img/pocket-id.svg | 12 + static/img/saml.svg | 9 + 21 files changed, 3524 insertions(+), 360 deletions(-) create mode 100644 components/auth_provider_form.templ create mode 100644 components/auth_providers.templ create mode 100644 components/auth_providers_buttons.templ create mode 100644 internal/db/auth_provider.go create mode 100644 internal/db/auth_provider_db.go create mode 100644 internal/db/migrations/011_add_auth_providers.go create mode 100644 internal/web/handlers/auth_provider_handlers.go create mode 100644 static/img/authelia.svg create mode 100644 static/img/authentik.svg create mode 100644 static/img/keycloak.svg create mode 100644 static/img/oauth2.svg create mode 100644 static/img/oidc.svg create mode 100644 static/img/pocket-id.svg create mode 100644 static/img/saml.svg diff --git a/components/auth_provider_form.templ b/components/auth_provider_form.templ new file mode 100644 index 0000000..eca1ef7 --- /dev/null +++ b/components/auth_provider_form.templ @@ -0,0 +1,655 @@ +package components + +import ( + "fmt" + "encoding/json" + "context" + "github.com/starfleetcptn/gomft/internal/db" +) + +templ AuthProviderForm(ctx context.Context, provider *db.AuthProvider, isNew bool) { + @LayoutWithContext(getPageTitle(isNew), ctx) { +
+
+
+
+ if isNew { +

+ + New Authentication Provider +

+

Configure a new external authentication source

+ } else { +

+ + Edit Authentication Provider +

+

Update an existing external authentication source

+ } +
+
+ + Back to Providers + +
+
+ +
+ if isNew { +
+ @formContent(provider, isNew) +
+ } else { +
+ + @formContent(provider, isNew) +
+ } +
+ + +
+
+
+ +
+
+

Authentication providers allow users to sign in using external identity providers.

+
+
+ +
+
+ +
+
+

Make sure to enter the correct callback URL in your external provider's configuration.

+
+
+ +
+
+ +
+
+

Configure attribute mappings to match the fields in your identity provider's user data.

+
+
+
+
+
+ + + } +} + +func getPageTitle(isNew bool) string { + if isNew { + return "New Authentication Provider" + } + return "Edit Authentication Provider" +} + +templ formContent(provider *db.AuthProvider, isNew bool) { +
+ +
+ + +

A descriptive name for this authentication provider

+
+ + +
+ + +

The type of external authentication service

+
+ + +
+
+ if provider == nil || provider.Enabled { + + } else { + + } + +
+

Whether this authentication provider is active and available for login

+
+ + +
+ + +

Additional information about this authentication provider

+
+
+ + +
+

Provider Icon

+ +
+
+ + +

URL to the provider's icon image (SVG recommended)

+
+ +
+ +
+
+ if provider != nil && provider.IconURL != "" { + + } else { + + } +
+
+

Preview of the icon that will be displayed on the login button.

+

If no URL is provided, a default icon will be used based on the provider type.

+
+
+
+
+
+ +
+

Connection Settings

+ +
+ +
+ + +

The base URL of the authentication provider

+
+ + +
+ + +

The client identifier assigned by the authentication provider

+
+ + +
+ + if isNew { + + } else { + + } +

The client secret for authentication with the provider

+
+ + +
+ + +

The callback URL that will handle the authentication response

+
+ + +
+ + +

Space-separated list of scopes to request from the provider

+
+
+
+ +
+

Authentik Settings

+ +
+ +
+ + +

Authentik tenant ID (optional, defaults to 'default')

+
+
+
+ +
+

OpenID Connect Settings

+ +
+ +
+ + +

URL to the OIDC discovery document

+
+
+
+ +
+

SAML Settings

+ +
+ +
+ + +

URL to the SAML metadata XML

+
+
+
+ +
+

User Attribute Mapping

+ +
+ +
+ + +

The attribute to use as the username

+
+ + +
+ + +

The attribute to use as the email address

+
+ + +
+ + +

The attribute to use as the display name

+
+ + +
+ + +

The attribute that contains user groups

+
+
+
+ +
+ + Cancel + + if !isNew { + + } + +
+} + +func getValue(provider *db.AuthProvider, field string) string { + if provider == nil { + return "" + } + + switch field { + case "name": + return provider.Name + case "provider_url": + return provider.ProviderURL + case "client_id": + return provider.ClientID + case "redirect_url": + return provider.RedirectURL + case "scopes": + return provider.Scopes + case "icon_url": + return provider.IconURL + default: + return "" + } +} + +func getRedirectURL(provider *db.AuthProvider) string { + if provider == nil || provider.RedirectURL == "" { + return fmt.Sprintf("https://%s/auth/callback", "your-app-domain.com") + } + return provider.RedirectURL +} + +func getConfigValue(provider *db.AuthProvider, key string) string { + if provider == nil || provider.Config == "" { + return "" + } + + var config map[string]interface{} + if err := json.Unmarshal([]byte(provider.Config), &config); err != nil { + return "" + } + + if value, ok := config[key]; ok { + if strValue, ok := value.(string); ok { + return strValue + } + } + + return "" +} + +func getAttributeValue(provider *db.AuthProvider, key string) string { + if provider == nil || provider.AttributeMapping == "" { + return "" + } + + var mapping map[string]string + if err := json.Unmarshal([]byte(provider.AttributeMapping), &mapping); err != nil { + return "" + } + + if value, ok := mapping[key]; ok { + return value + } + + return "" +} \ No newline at end of file diff --git a/components/auth_providers.templ b/components/auth_providers.templ new file mode 100644 index 0000000..1fa052b --- /dev/null +++ b/components/auth_providers.templ @@ -0,0 +1,380 @@ +package components + +import ( + "context" + "fmt" + "time" + "github.com/starfleetcptn/gomft/internal/db" +) + +// formatTime formats a time.Time value as a human-readable string +func formatTime(t time.Time) string { + return t.Format("Jan 02, 2006 15:04") +} + +templ AuthProviders(ctx context.Context, providers []db.AuthProvider) { + @LayoutWithContext("Authentication Providers", ctx) { + +
+ +
+
+
+
+

+ + Authentication Providers +

+

Manage external authentication sources like Authentik, OIDC, SAML, etc.

+
+
+ + + New Provider + +
+
+ +
+ if len(providers) > 0 { +
+
+ + + + + + + + + + + + + + for _, provider := range providers { + + + + + + + + + + } + +
NameIconTypeStatusProvider URLLast UsedActions
+ { provider.Name } + +
+ if provider.IconURL != "" { + { + } else { + if provider.Type == db.ProviderTypeAuthentik { + Authentik + } else if provider.Type == db.ProviderTypeOIDC { + OIDC + } else if provider.Type == db.ProviderTypeSAML { + SAML + } else if provider.Type == db.ProviderTypeOAuth2 { + OAuth2 + } else { + + } + } +
+
+ { string(provider.Type) } + + if provider.Enabled { + + Active + + } else { + + Disabled + + } + + { provider.ProviderURL } + + if provider.LastUsed.Valid { + { formatTime(provider.LastUsed.Time) } + } else { + Never + } + +
+ + + Edit + + + +
+
+
+
+ } else { +
+
+ +
+

No Authentication Providers

+

+ You haven't set up any external authentication providers yet. +

+ + + Add First Provider + +
+ } +
+ + +
+
+
+ +
+
+

Authentication providers allow users to sign in using external identity providers.

+
+
+ +
+
+ +
+
+

Make sure to configure callback URLs in your provider's settings.

+
+
+ +
+
+ +
+
+

Test your connections to ensure proper communication with external authentication systems.

+
+
+
+
+
+ + + + + + } +} \ No newline at end of file diff --git a/components/auth_providers_buttons.templ b/components/auth_providers_buttons.templ new file mode 100644 index 0000000..33aa3ef --- /dev/null +++ b/components/auth_providers_buttons.templ @@ -0,0 +1,52 @@ +package components + +import ( + "fmt" + "github.com/starfleetcptn/gomft/internal/db" +) + +// getProviderIcon returns the appropriate icon for a provider +func getProviderIcon(provider db.AuthProvider) templ.Component { + // If provider has a custom icon URL, use it + if provider.IconURL != "" { + return templ.Raw(fmt.Sprintf(`%s icon`, provider.IconURL, provider.Name)) + } + + // Otherwise fall back to default icons based on type + switch provider.Type { + case db.ProviderTypeAuthentik: + return templ.Raw(`Authentik`) + case db.ProviderTypeOIDC: + return templ.Raw(`OIDC`) + case db.ProviderTypeSAML: + return templ.Raw(`SAML`) + case db.ProviderTypeOAuth2: + return templ.Raw(`OAuth2`) + default: + return templ.Raw(``) + } +} + +templ AuthProviderButtons(providers []db.AuthProvider) { + if len(providers) == 0 { +
+ No external authentication providers available +
+ } else { +
+ for _, provider := range providers { + if provider.Enabled { + + + @getProviderIcon(provider) + + { provider.Name } + + } + } +
+ } +} \ No newline at end of file diff --git a/components/layout.templ b/components/layout.templ index f090a35..3c4f411 100644 --- a/components/layout.templ +++ b/components/layout.templ @@ -228,10 +228,25 @@ templ LayoutWithContext(title string, ctx context.Context) { Database Tools - + // Settings Dropdown + + } diff --git a/components/login.templ b/components/login.templ index 23f52c3..e63ce9d 100644 --- a/components/login.templ +++ b/components/login.templ @@ -111,10 +111,20 @@ templ Login(ctx context.Context, errorMessage string) {
-

+

Contact an administrator to create an account

+ + +
+

Or sign in with:

+
+
+
+
+
+
diff --git a/components/settings.templ b/components/settings.templ index 5b2d119..2592d2c 100644 --- a/components/settings.templ +++ b/components/settings.templ @@ -1,8 +1,8 @@ package components import ( - "fmt" "context" + "fmt" ) type NotificationService struct { @@ -28,133 +28,131 @@ type SettingsData struct { templ Settings(ctx context.Context, data SettingsData) { @LayoutWithContext("Application Settings", ctx) { -
+

- - Application Settings -

-

- Configure global settings for your GoMFT instance. -

-
- - - if data.SuccessMessage != "" { - - } - - - if data.ErrorMessage != "" { -