Compare commits
7 Commits
6969252a9c
...
trunk
Author | SHA1 | Date | |
---|---|---|---|
57f4ff5187
|
|||
7d59e09968
|
|||
4537511dab
|
|||
74051f3afc
|
|||
c3831e9efe
|
|||
860dbe6032
|
|||
aa86f5de48
|
37
README.md
Normal file
37
README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# frontpage (name pending)
|
||||
|
||||
The front page of your self-hosted server.
|
||||
|
||||
This app fits the use case of having multiple applications with access gated by
|
||||
an OIDC provider, and showing a user what applications they have access to.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
frontpage -c CONFIG.TOML
|
||||
```
|
||||
|
||||
where a minimal config file looks like:
|
||||
|
||||
```toml
|
||||
[oidc]
|
||||
client_id = "some_id"
|
||||
client_secret = "some_secret"
|
||||
issuer = "https://auth.example.com/oauth"
|
||||
scopes = [ "groups" ]
|
||||
```
|
||||
|
||||
Applications are defined using the `apps` keys:
|
||||
|
||||
```toml
|
||||
[apps.login]
|
||||
name = "Login portal"
|
||||
url = "https://auth.example.com"
|
||||
description = "Update your user details"
|
||||
groups = [ "users" ]
|
||||
```
|
||||
|
||||
In this example, only users whose OIDC groups claim includes `users` will be
|
||||
allowed to see a link to the login portal. Protection of the link, should a user
|
||||
gain access to it otherwise, is expected to be done externall (e.g., via an
|
||||
ingress controller).
|
67
flake.lock
generated
67
flake.lock
generated
@ -1,12 +1,15 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -16,12 +19,15 @@
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -32,26 +38,27 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1662166109,
|
||||
"narHash": "sha256-cmNWUeVDfSJC9y8nmX2O/7kuOXJU1ZVFJMYP87qrm/Y=",
|
||||
"lastModified": 1685533922,
|
||||
"narHash": "sha256-y4FCQpYafMQ42l1V+NUrMel9RtFtZo59PzdzflKR/lo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cb5a1a003dde9c16a1ae4b28cbe7bf0fab15da32",
|
||||
"rev": "3a70dd92993182f8e514700ccf5b1ae9fc8a3b8d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1662166109,
|
||||
"narHash": "sha256-cmNWUeVDfSJC9y8nmX2O/7kuOXJU1ZVFJMYP87qrm/Y=",
|
||||
"lastModified": 1685580370,
|
||||
"narHash": "sha256-zTPVdZwLVQl/y0QTZEtYs9iNvZW6H9h+/MZsKdUinu8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cb5a1a003dde9c16a1ae4b28cbe7bf0fab15da32",
|
||||
"rev": "fabe2064486b607c2516296ce6108549de0649c4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -66,11 +73,11 @@
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1662044036,
|
||||
"narHash": "sha256-+5YZPznhy1gEKPdWiZj7UcLoRaLbfvUDr8OzOY+75jM=",
|
||||
"lastModified": 1685418143,
|
||||
"narHash": "sha256-q2ORekI8au0pGMtOLQI8WMCJBxjzWgYRHpiEOVSBq3w=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "efe5b281b51c22495c488480d23d7bb1426bf3ba",
|
||||
"rev": "f11cc14e28078c701072f2d1fb34a6495c9376b1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -85,6 +92,36 @@
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
35
flake.nix
35
flake.nix
@ -2,32 +2,53 @@
|
||||
description = "Application packaged using poetry2nix";
|
||||
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
|
||||
inputs.poetry2nix.url = "github:nix-community/poetry2nix";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, poetry2nix }: {
|
||||
overlay = nixpkgs.lib.composeManyExtensions [
|
||||
poetry2nix.overlay
|
||||
(final: prev: {
|
||||
frontpage = prev.poetry2nix.mkPoetryApplication {
|
||||
python = prev.python39;
|
||||
projectDir = ./.;
|
||||
};
|
||||
frontpage = prev.poetry2nix.mkPoetryApplication
|
||||
{
|
||||
python = prev.python39;
|
||||
projectDir = ./.;
|
||||
overrides =
|
||||
prev.poetry2nix.overrides.withDefaults (final_: prev_:
|
||||
nixpkgs.lib.listToAttrs
|
||||
(builtins.map
|
||||
(name: {
|
||||
inherit name;
|
||||
value = prev_."${name}".overridePythonAttrs (old: {
|
||||
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ final_.setuptools ];
|
||||
});
|
||||
})
|
||||
[ "beaker" "flask-pyoidc-oda" "oic" ]));
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
nixosModules = rec {
|
||||
default = frontpage;
|
||||
frontpage = import ./nixos;
|
||||
};
|
||||
} // (flake-utils.lib.eachDefaultSystem (system:
|
||||
let pkgs = import nixpkgs { inherit system; overlays = [ self.overlay ]; }; in
|
||||
{
|
||||
apps = {
|
||||
default = pkgs.frontpage;
|
||||
frontpage = pkgs.frontpage;
|
||||
};
|
||||
|
||||
defaultApp = pkgs.frontpage;
|
||||
|
||||
devShell = (pkgs.poetry2nix.mkPoetryEnv {
|
||||
python = pkgs.python39;
|
||||
projectDir = ./.;
|
||||
extraPackages = ps: [ ps.python-lsp-server ];
|
||||
}).env;
|
||||
|
||||
packages = {
|
||||
default = pkgs.frontpage;
|
||||
frontpage = pkgs.frontpage;
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ class CoreConfig:
|
||||
|
||||
debug: bool = False
|
||||
port: int = 5000
|
||||
name: str = "Front page"
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -43,8 +44,8 @@ class AppConfig:
|
||||
class Config:
|
||||
"""Top-level configuration."""
|
||||
|
||||
core: CoreConfig
|
||||
oidc: OidcConfig
|
||||
core: CoreConfig = field(default_factory=CoreConfig)
|
||||
oidc: OidcConfig = field(default_factory=OidcConfig)
|
||||
apps: Dict[str, AppConfig] = field(default_factory=dict)
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@ from flask import Blueprint, current_app, render_template
|
||||
from flask_pyoidc import OIDCAuthentication
|
||||
from flask_pyoidc.user_session import UserSession
|
||||
|
||||
from frontpage.config import AppConfig, current_config
|
||||
from frontpage.config import AppConfig, Config, current_config
|
||||
|
||||
|
||||
def _allowed(items_from: Iterable[Any], items_in: Iterable[Any]) -> bool:
|
||||
@ -29,12 +29,16 @@ def register(auth: OIDCAuthentication, auth_provider: str) -> Blueprint:
|
||||
Renders the home route.
|
||||
"""
|
||||
user_session = UserSession(flask.session)
|
||||
groups: List[str] = user_session.userinfo["groups"]
|
||||
groups: List[str] = user_session.userinfo.get("groups") or []
|
||||
|
||||
apps: AppConfig = current_config().apps
|
||||
config: Config = current_config()
|
||||
name = config.core.name
|
||||
apps = config.apps
|
||||
allowed_apps = {
|
||||
ident: a for ident, a in apps.items() if _allowed(a.groups, groups)
|
||||
}
|
||||
return render_template("home.html", apps=allowed_apps, groups=groups)
|
||||
return render_template(
|
||||
"home.html", brand_name=name, apps=allowed_apps, groups=groups
|
||||
)
|
||||
|
||||
return routes
|
||||
|
@ -8,7 +8,7 @@
|
||||
<body>
|
||||
<nav class="nav">
|
||||
<div class="nav-left">
|
||||
<a class="brand" href="/">Front page</a>
|
||||
<a class="brand" href="/">{{ brand_name }}</a>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="container">
|
||||
|
76
nixos/default.nix
Normal file
76
nixos/default.nix
Normal file
@ -0,0 +1,76 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.frontpage;
|
||||
|
||||
toml = pkgs.formats.toml { };
|
||||
fullSettings = recursiveUpdate cfg.settings {
|
||||
core.port = cfg.port;
|
||||
oidc.client_secret = "@SECRET@";
|
||||
};
|
||||
settingsFile = toml.generate "config.toml" fullSettings;
|
||||
in
|
||||
{
|
||||
options.services.frontpage = {
|
||||
enable = mkEnableOption "frontpage";
|
||||
|
||||
package = mkPackageOption pkgs "frontpage" { };
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "frontpage";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "frontpage";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 32195;
|
||||
};
|
||||
|
||||
oidcSecretFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to a file containing the OIDC secret for the application.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf anything;
|
||||
default = { };
|
||||
description = ''
|
||||
Settings attribute set as described by the documentation.
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.frontpage = {
|
||||
description = "Web front page";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
preStart = ''
|
||||
sed \
|
||||
"s=@SECRET@=$(<${cfg.oidcSecretFile})=" \
|
||||
${settingsFile} \
|
||||
> /run/frontpage/config.toml
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "2s";
|
||||
ExecStart = "${cfg.package}/bin/frontpage -c /run/frontpage/config.toml";
|
||||
RuntimeDirectory = [ "frontpage" ];
|
||||
User = cfg.user;
|
||||
};
|
||||
};
|
||||
|
||||
users.users."${cfg.user}" = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
|
||||
users.groups."${cfg.group}" = { };
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user