package authentik import ( "context" "fmt" "net/url" "strings" ) type sourceRef struct { PK string `json:"pk"` Slug string `json:"slug"` Name string `json:"name"` } type brandRef struct { PK string `json:"pk"` BrandUUID string `json:"brand_uuid"` Domain string `json:"domain"` FlowEnrollment *string `json:"flow_enrollment"` } type flowInstance struct { PK string `json:"pk"` Slug string `json:"slug"` Name string `json:"name"` } func (c *Client) FindOAuthSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) { q := url.Values{} q.Set("slug", slug) var out listResponse[sourceRef] if err := c.getJSON(ctx, "/api/v3/sources/oauth/?"+q.Encode(), &out); err != nil { return nil, false, err } if len(out.Results) == 0 { return nil, false, nil } return &out.Results[0], true, nil } func (c *Client) FindSAMLSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) { q := url.Values{} q.Set("slug", slug) var out listResponse[sourceRef] if err := c.getJSON(ctx, "/api/v3/sources/saml/?"+q.Encode(), &out); err != nil { return nil, false, err } if len(out.Results) == 0 { return nil, false, nil } return &out.Results[0], true, nil } func (c *Client) FindLDAPSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) { q := url.Values{} q.Set("slug", slug) var out listResponse[sourceRef] if err := c.getJSON(ctx, "/api/v3/sources/ldap/?"+q.Encode(), &out); err != nil { return nil, false, err } if len(out.Results) == 0 { return nil, false, nil } return &out.Results[0], true, nil } type OAuthSourceRequest struct { Name string Slug string Enabled bool ProviderType string ClientID string ClientSecret string AuthorizationURL string AccessTokenURL string ProfileURL string Scopes string AuthenticationFlow string EnrollmentFlow string } func (c *Client) CreateOAuthSource(ctx context.Context, req OAuthSourceRequest) (string, error) { body := map[string]any{ "name": req.Name, "slug": req.Slug, "enabled": req.Enabled, "provider_type": req.ProviderType, "consumer_key": req.ClientID, "consumer_secret": req.ClientSecret, "authorization_url": req.AuthorizationURL, "access_token_url": req.AccessTokenURL, "profile_url": req.ProfileURL, "authentication_flow": req.AuthenticationFlow, "enrollment_flow": req.EnrollmentFlow, "user_matching_mode": "email_link", "policy_engine_mode": "any", "request_token_url": "", "additional_scopes": req.Scopes, } var created sourceRef if err := c.postJSON(ctx, "/api/v3/sources/oauth/", body, &created); err != nil { return "", err } return created.PK, nil } func (c *Client) UpdateOAuthSource(ctx context.Context, pk string, req OAuthSourceRequest) error { body := map[string]any{ "name": req.Name, "slug": req.Slug, "enabled": req.Enabled, "provider_type": req.ProviderType, "consumer_key": req.ClientID, "authorization_url": req.AuthorizationURL, "access_token_url": req.AccessTokenURL, "profile_url": req.ProfileURL, "authentication_flow": req.AuthenticationFlow, "enrollment_flow": req.EnrollmentFlow, "additional_scopes": req.Scopes, } if strings.TrimSpace(req.ClientSecret) != "" { body["consumer_secret"] = req.ClientSecret } return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/oauth/%s/", pk), body) } func (c *Client) DeleteOAuthSource(ctx context.Context, pk string) error { return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/oauth/%s/", pk)) } type SAMLSourceRequest struct { Name string Slug string Enabled bool MetadataURL string MetadataXML string EntityID string SSOURL string SLOURL string SigningCert string AuthenticationFlow string EnrollmentFlow string } func (c *Client) CreateSAMLSource(ctx context.Context, req SAMLSourceRequest) (string, error) { body := map[string]any{ "name": req.Name, "slug": req.Slug, "enabled": req.Enabled, "metadata_url": req.MetadataURL, "sso_url": req.SSOURL, "slo_url": req.SLOURL, "issuer": req.EntityID, "authentication_flow": req.AuthenticationFlow, "enrollment_flow": req.EnrollmentFlow, "user_matching_mode": "email_link", "policy_engine_mode": "any", } if strings.TrimSpace(req.MetadataXML) != "" { body["metadata"] = req.MetadataXML } if strings.TrimSpace(req.SigningCert) != "" { body["verification_kp"] = req.SigningCert } var created sourceRef if err := c.postJSON(ctx, "/api/v3/sources/saml/", body, &created); err != nil { return "", err } return created.PK, nil } func (c *Client) UpdateSAMLSource(ctx context.Context, pk string, req SAMLSourceRequest) error { body := map[string]any{ "name": req.Name, "slug": req.Slug, "enabled": req.Enabled, "metadata_url": req.MetadataURL, "sso_url": req.SSOURL, "slo_url": req.SLOURL, "issuer": req.EntityID, "authentication_flow": req.AuthenticationFlow, "enrollment_flow": req.EnrollmentFlow, } if strings.TrimSpace(req.MetadataXML) != "" { body["metadata"] = req.MetadataXML } if strings.TrimSpace(req.SigningCert) != "" { body["verification_kp"] = req.SigningCert } return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/saml/%s/", pk), body) } func (c *Client) DeleteSAMLSource(ctx context.Context, pk string) error { return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/saml/%s/", pk)) } type LDAPSourceRequest struct { Name string Slug string Enabled bool ServerURI string BindDN string BindPassword string BaseDN string UserFilter string StartTLS bool SyncUsers bool AuthenticationFlow string EnrollmentFlow string } func (c *Client) CreateLDAPSource(ctx context.Context, req LDAPSourceRequest) (string, error) { body := map[string]any{ "name": req.Name, "slug": req.Slug, "enabled": req.Enabled, "server_uri": req.ServerURI, "bind_cn": req.BindDN, "bind_password": req.BindPassword, "base_dn": req.BaseDN, "search_mode": "direct", "start_tls": req.StartTLS, "sync_users": req.SyncUsers, "authentication_flow": req.AuthenticationFlow, "enrollment_flow": req.EnrollmentFlow, "user_matching_mode": "email_link", "policy_engine_mode": "any", } if filter := strings.TrimSpace(req.UserFilter); filter != "" { body["search_filter"] = filter } var created sourceRef if err := c.postJSON(ctx, "/api/v3/sources/ldap/", body, &created); err != nil { return "", err } return created.PK, nil } func (c *Client) UpdateLDAPSource(ctx context.Context, pk string, req LDAPSourceRequest) error { body := map[string]any{ "name": req.Name, "slug": req.Slug, "enabled": req.Enabled, "server_uri": req.ServerURI, "bind_cn": req.BindDN, "base_dn": req.BaseDN, "start_tls": req.StartTLS, "sync_users": req.SyncUsers, "authentication_flow": req.AuthenticationFlow, "enrollment_flow": req.EnrollmentFlow, } if filter := strings.TrimSpace(req.UserFilter); filter != "" { body["search_filter"] = filter } if strings.TrimSpace(req.BindPassword) != "" { body["bind_password"] = req.BindPassword } return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/ldap/%s/", pk), body) } func (c *Client) DeleteLDAPSource(ctx context.Context, pk string) error { return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/ldap/%s/", pk)) } func (c *Client) deleteJSON(ctx context.Context, path string) error { return c.doJSON(ctx, "DELETE", path, nil, nil) } func (c *Client) FindDefaultBrand(ctx context.Context) (*brandRef, error) { var out listResponse[brandRef] if err := c.getJSON(ctx, "/api/v3/core/brands/", &out); err != nil { return nil, err } for _, b := range out.Results { if b.Domain == "authentik-default" || strings.Contains(strings.ToLower(b.Domain), "default") { return &b, nil } } if len(out.Results) > 0 { return &out.Results[0], nil } return nil, fmt.Errorf("no brand found") } func (c *Client) SetBrandEnrollmentFlow(ctx context.Context, brandPK string, flowPK *string) error { body := map[string]any{} if flowPK == nil || strings.TrimSpace(*flowPK) == "" { body["flow_enrollment"] = nil } else { body["flow_enrollment"] = *flowPK } return c.patchJSON(ctx, fmt.Sprintf("/api/v3/core/brands/%s/", brandPK), body) } func (c *Client) FindFlowInstanceBySlug(ctx context.Context, slug string) (*flowInstance, bool, error) { q := url.Values{} q.Set("slug", slug) var out listResponse[flowInstance] if err := c.getJSON(ctx, "/api/v3/flows/instances/?"+q.Encode(), &out); err != nil { return nil, false, err } if len(out.Results) == 0 { return nil, false, nil } return &out.Results[0], true, nil } func (c *Client) ResolveSourceFlows(ctx context.Context) (authFlow, enrollmentFlow string, err error) { authFlow, err = c.FindFlowBySlug(ctx, "default-source-authentication") if err != nil { authFlow, err = c.FindFlowBySlug(ctx, "default-authentication-flow") } if err != nil { return "", "", err } enrollmentFlow, err = c.FindFlowBySlug(ctx, "default-source-enrollment") if err != nil { enrollmentFlow, _ = c.FindFlowBySlug(ctx, "default-enrollment-flow") } return authFlow, enrollmentFlow, nil }