Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a6e8a40
feat : WIP authentik integration errors and types are added
manab-pr May 3, 2026
c28e43a
added authentik , fulfilling all the requirements
manab-pr May 7, 2026
35f75df
response struct changes and one api replaced as well
manab-pr May 8, 2026
02a4435
Merge branch 'main' into feature/authentik
manab-pr May 8, 2026
d917e74
using go-client instead of http-client and added more identity as well
manab-pr May 9, 2026
4c6412c
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 9, 2026
7e0d3f2
Merge branch 'main' into feature/authentik
manab-pr May 9, 2026
75f6431
added uid instead of pk for directory ac and members
manab-pr May 9, 2026
3968a73
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 9, 2026
a2bfc86
Both extractHost and extractScheme now use url.Parse and propagate e…
manab-pr May 9, 2026
832fe47
listDirectoryUsers now accept lastRunAt ,SkipDefaultLookback: true ad…
manab-pr May 9, 2026
a49aabc
Merge branch 'main' into feature/authentik
manab-pr May 9, 2026
7dc6b63
Merge branch 'main' into feature/authentik
manab-pr May 10, 2026
ec4db77
Merge branch 'main' into feature/authentik
manab-pr May 11, 2026
cd364b4
added user,group,members test json files as mappingtest suggests and …
manab-pr May 11, 2026
4838e29
Update internal/integrations/definitions/authentik/mappings.go
manab-pr May 11, 2026
19abcd8
Merge branch 'main' into feature/authentik
manab-pr May 11, 2026
2e0678a
fixed linting
manab-pr May 12, 2026
d7b1413
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 12, 2026
e9ed521
Merge branch 'main' into feature/authentik
manab-pr May 12, 2026
3ed015c
fixed mapping , moved compose file to docker directory and added task…
manab-pr May 15, 2026
c2759fd
the IntegrationMappingDirectoryAccountAccountType is written in one line
manab-pr May 15, 2026
c56f7d1
Merge branch 'main' into feature/authentik
manab-pr May 15, 2026
0b56e51
Update internal/integrations/definitions/authentik/operation_director…
manab-pr May 15, 2026
d4589a1
Update internal/integrations/definitions/authentik/operation_director…
manab-pr May 15, 2026
3766015
Apply suggestions from code review
manab-pr May 15, 2026
0b883e9
Update internal/integrations/definitions/authentik/types.go
manab-pr May 15, 2026
e647435
using defualt postgre container now , user need not to provide eenvs …
manab-pr May 16, 2026
3d67413
Merge branch 'main' into feature/authentik
manab-pr May 16, 2026
a407c38
lint issue fixed
manab-pr May 16, 2026
20035a8
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 16, 2026
50a7f1a
test bug removed
manab-pr May 16, 2026
c2b3acb
test failing fix
manab-pr May 16, 2026
3056032
Merge branch 'main' into feature/authentik
manab-pr May 16, 2026
bc5f719
yml modification and mapping modification as well
manab-pr May 16, 2026
461428c
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 16, 2026
292e98f
added missing env
manab-pr May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ require (
github.com/vanng822/css v1.0.1 // indirect
github.com/vanng822/go-premailer v1.33.0 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
goauthentik.io/api/v3 v3.2026020.16 // indirect
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
goauthentik.io/api/v3 v3.2026020.16 h1:sEqcVRXYSJTYaSdU5PzSEdFUWDqCONm5BeL62F5k+58=
goauthentik.io/api/v3 v3.2026020.16/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
gocloud.dev v0.45.0 h1:WknIK8IbRdmynDvara3Q7G6wQhmEiOGwpgJufbM39sY=
gocloud.dev v0.45.0/go.mod h1:0kXKmkCLG6d31N7NyLZWzt7jDSQura9zD/mWgiB6THI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
93 changes: 93 additions & 0 deletions internal/integrations/definitions/authentik/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package authentik

import (
"github.com/theopenlane/core/internal/ent/integrationgenerated"
"github.com/theopenlane/core/internal/integrations/providerkit"
"github.com/theopenlane/core/internal/integrations/registry"
"github.com/theopenlane/core/internal/integrations/types"
)

// Builder returns the Authentik definition builder
func Builder() registry.Builder {
return registry.Builder(func() (types.Definition, error) {
return types.Definition{
DefinitionSpec: types.DefinitionSpec{
ID: definitionID.ID(),
Family: "Authentik",
DisplayName: "Authentik",
Description: "Collect Authentik directory users, groups, and memberships for identity posture and access governance.",
Category: "identity",
DocsURL: "https://docs.theopenlane.io/docs/platform/integrations/authentik/overview",
Tags: []string{"directory"},
Active: false,
Visible: false,
},
UserInput: &types.UserInputRegistration{
Schema: providerkit.SchemaFrom[UserInput](),
},
CredentialRegistrations: []types.CredentialRegistration{
{
Ref: authentikCredential.ID(),
Name: "Authentik Credential",
Description: "API token used to access Authentik instance data.",
Schema: authentikCredentialSchema,
},
},
Connections: []types.ConnectionRegistration{
{
CredentialRef: authentikCredential.ID(),
Name: "Authentik API Token",
Description: "Configure Authentik access using an API token from your instance.",
CredentialRefs: []types.CredentialSlotID{authentikCredential.ID()},
ClientRefs: []types.ClientID{authentikClient.ID()},
ValidationOperation: healthCheckOperation.Name(),
Integration: integration.Registration(),
Disconnect: &types.DisconnectRegistration{
CredentialRef: authentikCredential.ID(),
Description: "Removes the stored API token from Openlane. If the token is no longer needed, revoke it in your Authentik admin panel under Directory > Tokens.",
},
},
},
Clients: []types.ClientRegistration{
{
Ref: authentikClient.ID(),
CredentialRefs: []types.CredentialSlotID{authentikCredential.ID()},
Description: "Authentik API client",
Build: Client{}.Build,
},
},
Operations: []types.OperationRegistration{
{
Name: healthCheckOperation.Name(),
Description: "Call Authentik API to verify token and instance connectivity",
Topic: definitionID.OperationTopic(healthCheckOperation.Name()),
ClientRef: authentikClient.ID(),
Policy: types.ExecutionPolicy{Inline: true},
ConfigSchema: healthCheckSchema,
Handle: HealthCheck{}.Handle(),
},
{
Name: directorySyncOperation.Name(),
Description: "Collect Authentik directory users, groups, and memberships as directory accounts",
Topic: definitionID.OperationTopic(directorySyncOperation.Name()),
ClientRef: authentikClient.ID(),
ConfigSchema: directorySyncSchema,
Policy: types.ExecutionPolicy{Reconcile: true},
Ingest: []types.IngestContract{
Comment thread
manab-pr marked this conversation as resolved.
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryAccount,
},
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryGroup,
},
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryMembership,
},
},
IngestHandle: DirectorySync{}.IngestHandle(),
},
},
Mappings: authentikMappings(),
}, nil
})
}
75 changes: 75 additions & 0 deletions internal/integrations/definitions/authentik/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package authentik

import (
"context"
"net/http"
"strings"
"time"

authentikSDK "goauthentik.io/api/v3"

"github.com/theopenlane/core/internal/integrations/types"
)

const (
// authentikRequestTimeout is the per-request timeout for Authentik API calls
authentikRequestTimeout = 30 * time.Second
)

// Client builds Authentik API clients for one installation
type Client struct{}

// Build constructs the Authentik API client for one installation
func (Client) Build(_ context.Context, req types.ClientBuildRequest) (any, error) {
cred, err := resolveCredential(req.Credentials)
if err != nil {
return nil, err
}

if cred.Token == "" {
return nil, ErrAPITokenMissing
}

if cred.BaseURL == "" {
return nil, ErrBaseURLMissing
}

cfg := authentikSDK.NewConfiguration()
cfg.Host = extractHost(cred.BaseURL)
cfg.Scheme = extractScheme(cred.BaseURL)
cfg.HTTPClient = &http.Client{Timeout: authentikRequestTimeout}
cfg.AddDefaultHeader("Authorization", "Bearer "+cred.Token)

return authentikSDK.NewAPIClient(cfg), nil
}

// resolveCredential extracts the CredentialSchema from the provided credential bindings
func resolveCredential(bindings types.CredentialBindings) (CredentialSchema, error) {
cred, ok, err := authentikCredential.Resolve(bindings)
if err != nil {
return CredentialSchema{}, ErrCredentialDecode
}

if !ok {
return CredentialSchema{}, ErrCredentialDecode
}

return cred, nil
}

// extractHost extracts the host from a base URL
func extractHost(baseURL string) string {
host := strings.TrimPrefix(baseURL, "https://")
host = strings.TrimPrefix(host, "http://")

return strings.TrimRight(host, "/")
}

// extractScheme extracts the scheme from a base URL
func extractScheme(baseURL string) string {
if strings.HasPrefix(baseURL, "https://") {
return "https"
}

return "http"
}
Comment thread
manab-pr marked this conversation as resolved.
Outdated
4 changes: 4 additions & 0 deletions internal/integrations/definitions/authentik/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package authentik provides an integration definition for Authentik,
// an open-source identity provider. It supports directory sync of users,
// groups, and memberships via the Authentik REST API using API token authentication.
package authentik
30 changes: 30 additions & 0 deletions internal/integrations/definitions/authentik/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package authentik

import "errors"

var (
// ErrAPITokenMissing indicates the API token is missing from the credential
ErrAPITokenMissing = errors.New("authentik: api token missing")
// ErrBaseURLMissing indicates the Authentik base URL is missing from the credential
ErrBaseURLMissing = errors.New("authentik: base url missing")
// ErrClientType indicates the provided client is not the expected authentik client type
ErrClientType = errors.New("authentik: unexpected client type")
// ErrClientConfigInvalid indicates the Authentik client configuration is invalid
ErrClientConfigInvalid = errors.New("authentik: client config invalid")
// ErrCredentialEncode indicates the credential could not be serialized
ErrCredentialEncode = errors.New("authentik: credential encode failed")
// ErrCredentialDecode indicates the credential could not be deserialized
ErrCredentialDecode = errors.New("authentik: credential decode failed")
// ErrHealthCheckFailed indicates the health check request failed
ErrHealthCheckFailed = errors.New("authentik: health check failed")
// ErrDirectoryUsersFetchFailed indicates the users listing failed
ErrDirectoryUsersFetchFailed = errors.New("authentik: directory users fetch failed")
// ErrDirectoryGroupsFetchFailed indicates the groups listing failed
ErrDirectoryGroupsFetchFailed = errors.New("authentik: directory groups fetch failed")
// ErrDirectoryGroupMembersFetchFailed indicates the group members listing failed
ErrDirectoryGroupMembersFetchFailed = errors.New("authentik: directory group members fetch failed")
// ErrPayloadEncode indicates a provider payload could not be serialized
ErrPayloadEncode = errors.New("authentik: payload encode failed")
// ErrResultEncode indicates an operation result could not be serialized
ErrResultEncode = errors.New("authentik: result encode failed")
)
38 changes: 38 additions & 0 deletions internal/integrations/definitions/authentik/installation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package authentik

import (
"context"

"github.com/theopenlane/core/internal/integrations/types"
authentikSDK "goauthentik.io/api/v3"
)

// resolveInstallationMetadata derives Authentik instance metadata from the persisted credential
func resolveInstallationMetadata(ctx context.Context, req types.InstallationRequest) (InstallationMetadata, bool, error) {
cred, err := resolveCredential(req.Credentials)
if err != nil {
return InstallationMetadata{}, false, ErrCredentialDecode
}

if cred.Token == "" || cred.BaseURL == "" {
return InstallationMetadata{}, false, nil
}

client, err := Client{}.Build(ctx, types.ClientBuildRequest{Credentials: req.Credentials})
if err != nil {
return InstallationMetadata{}, false, err
}
Comment thread
manab-pr marked this conversation as resolved.

apiClient := client.(*authentikSDK.APIClient)

info, _, err := apiClient.AdminApi.AdminSystemRetrieve(ctx).Execute()
if err != nil {
return InstallationMetadata{}, false, ErrHealthCheckFailed
}
Comment thread
manab-pr marked this conversation as resolved.

return InstallationMetadata{
Brand: info.GetBrand(),
Host: info.GetHttpHost(),
BaseURL: cred.BaseURL,
}, true, nil
}
66 changes: 66 additions & 0 deletions internal/integrations/definitions/authentik/mappings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package authentik

import (
"github.com/theopenlane/core/internal/ent/integrationgenerated"
"github.com/theopenlane/core/internal/integrations/providerkit"
"github.com/theopenlane/core/internal/integrations/types"
)

// mapExprDirectoryAccount is the CEL mapping expression for Authentik user payloads mapped to DirectoryAccount
var mapExprDirectoryAccount = providerkit.CelMapExpr([]providerkit.CelMapEntry{
Comment thread
manab-pr marked this conversation as resolved.
{Key: integrationgenerated.IntegrationMappingDirectoryAccountExternalID, Expr: `'pk' in payload ? string(payload.pk) : ""`},
{Key: integrationgenerated.IntegrationMappingDirectoryAccountCanonicalEmail, Expr: `'email' in payload && payload.email != null && payload.email != "" ? payload.email : ""`},
Comment thread
manab-pr marked this conversation as resolved.
Outdated
{Key: integrationgenerated.IntegrationMappingDirectoryAccountDisplayName, Expr: `'name' in payload && payload.name != null && payload.name != "" ? payload.name : ('username' in payload ? payload.username : "")`},
{Key: integrationgenerated.IntegrationMappingDirectoryAccountStatus, Expr: `dyn('is_active' in payload ? (payload.is_active ? "ACTIVE" : "INACTIVE") : "INACTIVE")`},
{Key: integrationgenerated.IntegrationMappingDirectoryAccountAccountType, Expr: `'type' in payload && payload.type != null ? payload.type : ""`},
{Key: integrationgenerated.IntegrationMappingDirectoryAccountAddedAt, Expr: `'date_joined' in payload && payload.date_joined != null ? payload.date_joined : null`},
{Key: integrationgenerated.IntegrationMappingDirectoryAccountLastSeenAt, Expr: `'last_login' in payload && payload.last_login != null ? payload.last_login : null`},
{Key: integrationgenerated.IntegrationMappingDirectoryAccountObservedAt, Expr: `'last_updated' in payload && payload.last_updated != null ? payload.last_updated : null`},
Comment thread
manab-pr marked this conversation as resolved.
Outdated
{Key: integrationgenerated.IntegrationMappingDirectoryAccountMetadata, Expr: `'attributes' in payload ? payload.attributes : {}`},
{Key: integrationgenerated.IntegrationMappingDirectoryAccountProfile, Expr: "payload"},
})

// mapExprDirectoryGroup is the CEL mapping expression for Authentik group payloads mapped to DirectoryGroup
var mapExprDirectoryGroup = providerkit.CelMapExpr([]providerkit.CelMapEntry{
Comment thread
golanglemonade marked this conversation as resolved.
{Key: integrationgenerated.IntegrationMappingDirectoryGroupExternalID, Expr: `'pk' in payload ? payload.pk : ""`},
{Key: integrationgenerated.IntegrationMappingDirectoryGroupDisplayName, Expr: `'name' in payload && payload.name != null ? payload.name : ""`},
{Key: integrationgenerated.IntegrationMappingDirectoryGroupClassification, Expr: `dyn('is_superuser' in payload && payload.is_superuser ? "ADMIN" : "TEAM")`},
{Key: integrationgenerated.IntegrationMappingDirectoryGroupStatus, Expr: `dyn("ACTIVE")`},
{Key: integrationgenerated.IntegrationMappingDirectoryGroupMetadata, Expr: `'attributes' in payload ? payload.attributes : {}`},
{Key: integrationgenerated.IntegrationMappingDirectoryGroupProfile, Expr: "payload"},
})

// mapExprDirectoryMembership is the CEL mapping expression for Authentik membership payloads mapped to DirectoryMembership
var mapExprDirectoryMembership = providerkit.CelMapExpr([]providerkit.CelMapEntry{
{Key: integrationgenerated.IntegrationMappingDirectoryMembershipDirectoryAccountID, Expr: `'pk' in payload ? string(payload.pk) : ""`},
{Key: integrationgenerated.IntegrationMappingDirectoryMembershipDirectoryGroupID, Expr: `resource != "" ? resource : ""`},
{Key: integrationgenerated.IntegrationMappingDirectoryMembershipRole, Expr: `dyn("MEMBER")`},
{Key: integrationgenerated.IntegrationMappingDirectoryMembershipMetadata, Expr: "payload"},
})

// authentikMappings returns the built-in Authentik ingest mappings
func authentikMappings() []types.MappingRegistration {
return []types.MappingRegistration{
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryAccount,
Spec: types.MappingOverride{
FilterExpr: "true",
MapExpr: mapExprDirectoryAccount,
},
},
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryGroup,
Spec: types.MappingOverride{
FilterExpr: "true",
MapExpr: mapExprDirectoryGroup,
},
},
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryMembership,
Spec: types.MappingOverride{
FilterExpr: "true",
MapExpr: mapExprDirectoryMembership,
},
},
}
}
26 changes: 26 additions & 0 deletions internal/integrations/definitions/authentik/mappings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package authentik

import (
Comment thread
manab-pr marked this conversation as resolved.
"testing"

"gotest.tools/v3/assert"

"github.com/theopenlane/core/internal/integrations/providerkit"
)

func TestMappingExpressionsValid(t *testing.T) {
for _, m := range authentikMappings() {
name := m.Schema
if m.Variant != "" {
name += "/" + m.Variant
}

t.Run(name+"/filter", func(t *testing.T) {
assert.NilError(t, providerkit.ValidateExpr(m.Spec.FilterExpr))
})

t.Run(name+"/map", func(t *testing.T) {
assert.NilError(t, providerkit.ValidateExpr(m.Spec.MapExpr))
})
}
}
Loading