Initial commit

This commit is contained in:
2025-09-11 17:30:40 +03:00
commit c635a33654
5 changed files with 230 additions and 0 deletions

11
config-paths-needed.json Normal file
View File

@@ -0,0 +1,11 @@
[
[ "selfprivacy", "domain" ],
[ "selfprivacy", "modules", "auth", "enable" ],
[ "selfprivacy", "modules", "hedgedoc" ],
[ "selfprivacy", "passthru", "auth", "mkOAuth2ClientSecretFP" ],
[ "selfprivacy", "passthru", "auth", "oauth2-discovery-url" ],
[ "selfprivacy", "passthru", "auth", "oauth2-provider-name" ],
[ "selfprivacy", "sso", "enable" ],
[ "selfprivacy", "useBinds" ],
[ "services", "kanidm", "serverSettings", "origin" ]
]

41
flake.nix Normal file
View File

@@ -0,0 +1,41 @@
{
# TODO: check whether there is no TODOs
# TODO: check whether there is no hedgegdoc mentions
description = "Mastodon module";
outputs = { ... }:
{
nixosModules.default = import ./module.nix;
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
meta =
{ lib, ... }:
{
spModuleSchemaVersion = 1;
id = "mastodon";
name = "Mastodon";
description = "Mastodon is a free non-profit decentralized social media, a part of Fediverse network, offering a web client and a mobile app.";
svgIcon = builtins.readFile ./icon.svg;
isMovable = true;
isRequired = false;
backupDescription = ".";
systemdServices = [
"mastodon.service"
];
folders = [
"/var/lib/mastodon"
];
postgreDatabases = [
"mastodon"
];
license = [
lib.licenses.agpl3Only # TODO: plus or only
];
homepage = "https://joinmastodon.org";
sourcePage = "https://github.com/mastodon/mastodon";
supportLevel = "normal";
sso = {
userGroup = "sp.mastodon.users";
};
};
};
}

View File

@@ -0,0 +1,27 @@
diff --git a/lib/web/auth/oauth2/index.js b/lib/web/auth/oauth2/index.js
index b0ffa5e8a..45229358e 100644
--- a/lib/web/auth/oauth2/index.js
+++ b/lib/web/auth/oauth2/index.js
@@ -6,6 +6,7 @@ const { Strategy, InternalOAuthError } = require('passport-oauth2')
const config = require('../../../config')
const logger = require('../../../logger')
const { passportGeneralCallback } = require('../utils')
+const fs = require('fs')
const oauth2Auth = module.exports = Router()
@@ -130,11 +131,13 @@ OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) {
})
}
+const OAuth2clientSecretText = fs.readFileSync(config.oauth2.clientSecret, 'utf8').split("\n")[0]
+
passport.use(new OAuth2CustomStrategy({
authorizationURL: config.oauth2.authorizationURL,
tokenURL: config.oauth2.tokenURL,
clientID: config.oauth2.clientID,
- clientSecret: config.oauth2.clientSecret,
+ clientSecret: OAuth2clientSecretText,
callbackURL: config.serverURL + '/auth/oauth2/callback',
userProfileURL: config.oauth2.userProfileURL,
scope: config.oauth2.scope,

20
icon.svg Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1772 1772" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<path d="M1553.66,961.083L1628.91,885.825L1553.66,810.579L1610.27,720.483L1520.17,663.867L1555.32,563.421L1454.88,528.279L1466.79,422.533L1361.05,410.621L1349.13,304.871L1243.39,316.783L1208.24,216.338L1107.81,251.483L1051.18,161.383L961.073,218L885.831,142.754L810.589,218.004L720.485,161.392L663.873,251.488L563.431,216.338L528.289,316.779L422.539,304.863L410.627,410.604L304.885,422.517L316.802,528.258L216.348,563.408L251.493,663.846L161.393,720.458L218.014,810.575L142.756,885.825L218.006,961.083L161.393,1051.19L251.493,1107.8L216.348,1208.24L316.798,1243.39L304.885,1349.12L410.627,1361.04L422.539,1466.78L528.289,1454.87L563.431,1555.3L663.868,1520.17L720.485,1610.27L810.581,1553.65L885.831,1628.2L961.081,1553.67L1051.18,1610.28L1107.79,1520.17L1208.24,1555.32L1243.38,1454.88L1349.13,1466.79L1361.04,1361.04L1466.79,1349.13L1454.87,1243.39L1555.32,1208.24L1520.17,1107.79L1610.27,1051.18L1553.66,961.083Z" style="fill:rgb(181,31,8);fill-rule:nonzero;"/>
<path d="M1401.3,1004.78C1401.3,859.343 1283.4,741.439 1137.96,741.439C1065.72,741.439 1000.28,770.551 952.708,817.664L952.675,817.63L885.579,884.73L830.717,829.868C782.45,774.801 711.646,739.989 632.675,739.989C487.233,739.989 369.333,857.893 369.333,1003.33C369.333,1079.33 401.563,1147.76 453.054,1195.82L885.833,1628.37L1309.05,1204.88C1361.93,1155.11 1401.3,1084.88 1401.3,1004.78" style="fill:rgb(252,202,140);fill-rule:nonzero;"/>
<path d="M885.579,884.73L830.717,829.868C782.45,774.801 711.646,739.989 632.675,739.989C487.233,739.989 369.333,857.893 369.333,1003.33C369.333,1079.33 401.563,1147.76 453.054,1195.82L885.833,1628.37" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
</g>
<path d="M885.833,1628.37L885.579,884.73" style="fill:none;"/>
<g>
<path d="M961.011,1553.59C941.732,1534.24 915.094,1522.28 885.686,1522.28C856.269,1522.28 829.64,1534.24 810.357,1553.59C810.665,1594.93 844.265,1628.35 885.682,1628.35C927.098,1628.35 960.702,1594.92 961.011,1553.59" style="fill:rgb(1,0,7);fill-rule:nonzero;"/>
<path d="M797.707,1098.22C797.707,1130.02 771.94,1155.79 740.136,1155.79C708.349,1155.79 682.578,1130.02 682.578,1098.22C682.578,1066.42 708.349,1040.65 740.136,1040.65C771.94,1040.65 797.707,1066.42 797.707,1098.22" style="fill:rgb(1,0,7);fill-rule:nonzero;"/>
<path d="M777.962,1089.59C777.962,1098.41 770.816,1105.53 762.012,1105.53C753.204,1105.53 746.054,1098.41 746.054,1089.59C746.054,1080.78 753.204,1073.63 762.012,1073.63C770.816,1073.63 777.962,1080.78 777.962,1089.59" style="fill:rgb(255,255,250);fill-rule:nonzero;"/>
<path d="M1089.65,1098.22C1089.65,1130.02 1063.88,1155.79 1032.08,1155.79C1000.29,1155.79 974.513,1130.02 974.513,1098.22C974.513,1066.42 1000.29,1040.65 1032.08,1040.65C1063.88,1040.65 1089.65,1066.42 1089.65,1098.22" style="fill:rgb(1,0,7);fill-rule:nonzero;"/>
<path d="M1069.9,1089.59C1069.9,1098.41 1062.75,1105.53 1053.95,1105.53C1045.14,1105.53 1037.99,1098.41 1037.99,1089.59C1037.99,1080.78 1045.14,1073.63 1053.95,1073.63C1062.75,1073.63 1069.9,1080.78 1069.9,1089.59" style="fill:rgb(255,255,250);fill-rule:nonzero;"/>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(200,-420,420,200,660,1340)"><stop offset="0" style="stop-color:rgb(252,202,140);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(220,160,85);stop-opacity:1"/></linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

131
module.nix Normal file
View File

@@ -0,0 +1,131 @@
{
config,
lib,
pkgs,
...
}:
let
sp = config.selfprivacy;
cfg = sp.modules.mastodon;
oauthClientID = "mastodon";
auth-passthru = config.selfprivacy.passthru.auth;
oauthDiscoveryURL = config.services.kanidm.serverSettings.origin;
issuer = lib.strings.removeSuffix "/.well-known/openid-configuration" oauthDiscoveryURL;
# SelfPrivacy uses SP Module ID to identify the group!
usersGroup = "sp.mastodon.users";
oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP oauthClientID;
oauthRedirectURL = "https://${cfg.subdomain}.${sp.domain}/auth/auth/openid_connect/callback";
in
{
options.selfprivacy.modules.mastodon = {
enable =
(lib.mkOption {
default = false;
type = lib.types.bool;
description = "Enable Mastodon";
})
// {
meta = {
type = "enable";
};
};
location =
(lib.mkOption {
type = lib.types.str;
description = "Mastodon location";
})
// {
meta = {
type = "location";
};
};
subdomain =
(lib.mkOption {
default = "mastodon";
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
description = "Subdomain";
})
// {
meta = {
widget = "subdomain";
type = "string";
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
weight = 0;
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = sp.sso.enable;
message = "Mastodon cannot be enabled when SSO is disabled.";
}
];
fileSystems = lib.mkIf sp.useBinds {
"/var/lib/mastodon" = {
device = "/volumes/${cfg.location}/mastodon";
options = [ "bind" ];
};
};
# services.postgresql = {
# ensureDatabases = [ "mastodon" ];
# ensureUsers = [
# {
# name = "mastodon";
# ensureDBOwnership = true;
# }
# ];
# };
services.mastodon = {
enable = true;
localDomain = "${cfg.subdomain}.${sp.domain}";
enableUnixSocket = false;
configureNginx = true;
database.createLocally = true;
};
systemd = {
services.mastodon = {
unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/mastodon";
serviceConfig = {
loadCredentials = ["client-secret:${oauthClientSecretFP}"];
ExecStart = ''
export CLIENT_SECRET=$(cat $CREDENTIALS_DIRECTORY/client-secret)
${config.services.mastodon.package}/bin/puma -C config/puma.rb`
'';
};
environment = {
OIDC_ENABLED = true;
OIDC_DISPLAY_NAME= "Kanidm";
OIDC_ISSUER = issuer;
OIDC_DISCOVERY = true;
OIDC_SCOPE = "openid,profile";
OIDC_UID_FIELD = "sub";
OIDC_CLIENT_ID = oauthClientID;
OIDC_REDIRECT_URI = oauthRedirectURL;
};
};
slices.mastodon = {
description = "Mastodon service slice";
};
};
selfprivacy.auth.clients.${oauthClientID} = {
inherit usersGroup;
subdomain = cfg.subdomain;
originLanding = "https://${cfg.subdomain}.${sp.domain}/";
originUrl = oauthRedirectURL;
clientSystemdUnits = [ "mastodon.service" ];
enablePkce = true;
linuxUserOfClient = "mastodon";
linuxGroupOfClient = "mastodon";
};
};
}