Compare commits

...

6 Commits

Author SHA1 Message Date
d410083cfc wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 18:31:45 +02:00
6045f96a05 fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:55:20 +02:00
c50df0f843 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:54:22 +02:00
c8ebd9f74b wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:29:14 +02:00
b3f441f2cd add basic migrations
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:13:38 +02:00
647f054be3 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 17:11:18 +02:00
10 changed files with 279 additions and 4 deletions

View File

@ -0,0 +1,32 @@
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.core.models import File
LOGGER = get_logger()
class FileSerializer(ModelSerializer):
class Meta:
model = File
fields = (
"pk",
"name",
"content",
"location",
"private",
"url",
)
class FileViewSet(UsedByMixin, ModelViewSet):
queryset = File.objects.all()
serializer_class = FileSerializer
filterset_fields = ("private",)
ordering = ("name",)
search_fields = (
"name",
"location",
)

View File

@ -0,0 +1,44 @@
# Generated by Django 5.1.11 on 2025-06-13 15:12
import uuid
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0048_delete_oldauthenticatedsession_content_type"),
]
operations = [
migrations.CreateModel(
name="File",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("name", models.TextField()),
("content", models.BinaryField()),
("public", models.BooleanField(default=False)),
],
options={
"verbose_name": "Files",
},
),
migrations.RenameField(
model_name="application",
old_name="meta_icon",
new_name="meta_old_icon",
),
migrations.AddField(
model_name="application",
name="meta_icon",
field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.SET_NULL, to="authentik_core.file"
),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 5.1.11 on 2025-06-13 15:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0049_file_rename_meta_icon_application_meta_old_icon"),
]
operations = [
migrations.AddField(
model_name="file",
name="location",
field=models.TextField(null=True),
),
migrations.AlterField(
model_name="file",
name="content",
field=models.BinaryField(null=True),
),
migrations.AddConstraint(
model_name="file",
constraint=models.CheckConstraint(
condition=models.Q(
("content__isnull", False), ("location__isnull", False), _connector="OR"
),
name="one_of_content_location_is_defined",
),
),
]

View File

@ -29,6 +29,7 @@ from authentik.blueprints.models import ManagedModel
from authentik.core.expression.exceptions import PropertyMappingExpressionException
from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.lib.avatars import get_avatar
from authentik.lib.config import CONFIG
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.generators import generate_id
from authentik.lib.merge import MERGE_LIST_UNIQUE
@ -533,12 +534,13 @@ class Application(SerializerModel, PolicyBindingModel):
)
# For template applications, this can be set to /static/authentik/applications/*
meta_icon = models.FileField(
meta_old_icon = models.FileField(
upload_to="application-icons/",
default=None,
null=True,
max_length=500,
)
meta_icon = models.ForeignKey("File", null=True, on_delete=models.SET_NULL)
meta_description = models.TextField(default="", blank=True)
meta_publisher = models.TextField(default="", blank=True)
@ -1100,3 +1102,44 @@ class AuthenticatedSession(SerializerModel):
session=Session.objects.filter(session_key=request.session.session_key).first(),
user=user,
)
class File(SerializerModel):
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField()
content = models.BinaryField(null=True)
location = models.TextField(null=True)
public = models.BooleanField(default=False)
delete_on_delete = models.BooleanField(default=False)
expiry = models.DateTimeField()
class Meta:
verbose_name = _("File")
verbose_name = _("Files")
constraints = (
models.CheckConstraint(
condition=Q(content__isnull=False) | Q(location__isnull=False),
name="one_of_content_location_is_defined",
),
)
def __str__(self) -> str:
return self.name
@property
def serializer(self) -> type[Serializer]:
from authentik.core.api.files import FileSerializer
return FileSerializer
@property
def url(self) -> str:
if self.content:
return (
CONFIG.get("web.path", "/")[:-1]
+ f"/files/{'public' if self.public else 'private'}/{self.pk}"
)
elif self.location.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.location
return self.location

View File

@ -8,6 +8,7 @@ from authentik.core.api.application_entitlements import ApplicationEntitlementVi
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
from authentik.core.api.files import FileViewSet
from authentik.core.api.groups import GroupViewSet
from authentik.core.api.property_mappings import PropertyMappingViewSet
from authentik.core.api.providers import ProviderViewSet
@ -78,6 +79,7 @@ api_urlpatterns = [
TransactionalApplicationView.as_view(),
name="core-transactional-application",
),
("core/files", FileViewSet),
("core/groups", GroupViewSet),
("core/users", UserViewSet),
("core/tokens", TokenViewSet),

14
go.mod
View File

@ -62,6 +62,12 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.5 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@ -77,9 +83,11 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/gorm v1.30.0 // indirect
)

22
go.sum
View File

@ -191,6 +191,14 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@ -205,6 +213,10 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -308,6 +320,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -415,6 +429,8 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -422,6 +438,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -555,6 +573,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -5,6 +5,7 @@ type Config struct {
Storage StorageConfig `yaml:"storage"`
LogLevel string `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL, overwrite"`
ErrorReporting ErrorReportingConfig `yaml:"error_reporting" env:", prefix=AUTHENTIK_ERROR_REPORTING__"`
Postgresql PostgresqlConfig `yaml:"postgresql" env:", prefix=AUTHENTIK_POSTGRESQL__"`
Redis RedisConfig `yaml:"redis" env:", prefix=AUTHENTIK_REDIS__"`
Outposts OutpostConfig `yaml:"outposts" env:", prefix=AUTHENTIK_OUTPOSTS__"`
@ -25,6 +26,16 @@ type Config struct {
AuthentikInsecure bool `env:"AUTHENTIK_INSECURE"`
}
// TODO: SSL
type PostgresqlConfig struct {
Host string `yaml:"host" env:"HOST, overwrite"`
Port string `yaml:"port" env:"PORT, overwrite"`
User string `yaml:"user" env:"USER, overwrite"`
Password string `yaml:"password" env:"PASSWORD, overwrite"`
Name string `yaml:"name" env:"NAME, overwrite"`
DefaultSchema string `yaml:"default_schema" env:"DEFAULT_SCHEMA, overwrite"`
}
type RedisConfig struct {
Host string `yaml:"host" env:"HOST, overwrite"`
Port int `yaml:"port" env:"PORT, overwrite"`

62
internal/web/files.go Normal file
View File

@ -0,0 +1,62 @@
package web
import (
"net/http"
"github.com/go-http-utils/etag"
"github.com/gorilla/mux"
"goauthentik.io/internal/config"
"goauthentik.io/internal/constants"
)
type File struct {
ID string `gorm:"primaryKey"`
Name string
Content []byte
Location string
Public bool
}
func (ws *WebServer) configureFiles() {
// Setup routers
filesRouter := ws.loggingRouter.NewRoute().Subrouter()
filesRouter.Use(ws.filesHeaderMiddleware)
filesRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/files/public/{pk}").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pk := vars["pk"]
var file File
ws.postgresClient.First(&file, "id = ? AND public = true AND content <> NULL", pk)
// TODO: get from DB
rw.Write(file.Content)
})
filesRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/files/private/{pk}").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// TODO: check session
pk := vars["pk"]
var file File
ws.postgresClient.First(&file, "id = ? AND content <> NULL", pk)
rw.Write([]byte(file.Content))
})
}
// TODO: anything else?
func (ws *WebServer) filesHeaderMiddleware(h http.Handler) http.Handler {
etagHandler := etag.Handler(h, false)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "public, no-transform")
w.Header().Set("X-authentik-version", constants.VERSION)
w.Header().Set("Vary", "X-authentik-version, Etag")
etagHandler.ServeHTTP(w, r)
})
}

View File

@ -17,6 +17,8 @@ import (
"github.com/gorilla/securecookie"
"github.com/pires/go-proxyproto"
log "github.com/sirupsen/logrus"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"goauthentik.io/api/v3"
"goauthentik.io/internal/config"
@ -49,6 +51,7 @@ type WebServer struct {
mainRouter *mux.Router
loggingRouter *mux.Router
log *log.Entry
postgresClient *gorm.DB
upstreamClient *http.Client
upstreamURL *url.URL
@ -64,6 +67,21 @@ func NewWebServer() *WebServer {
loggingHandler := mainHandler.NewRoute().Subrouter()
loggingHandler.Use(web.NewLoggingHandler(l, nil))
// TODO: ssl
postgresDsn := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
config.Get().Postgresql.Host,
config.Get().Postgresql.Port,
config.Get().Postgresql.User,
config.Get().Postgresql.Password,
config.Get().Postgresql.Name,
)
db, err := gorm.Open(postgres.Open(postgresDsn), &gorm.Config{})
if err != nil {
panic(err)
}
tmp := os.TempDir()
socketPath := path.Join(tmp, UnixSocketName)
@ -88,6 +106,7 @@ func NewWebServer() *WebServer {
mainRouter: mainHandler,
loggingRouter: loggingHandler,
log: l,
postgresClient: db,
gunicornReady: false,
upstreamClient: upstreamClient,
upstreamURL: u,