Compare commits
7 commits
f2501365c4
...
23442bb6ea
| Author | SHA1 | Date | |
|---|---|---|---|
| 23442bb6ea | |||
| 47e3d0b55f | |||
| eec0ae6ced | |||
| a27985e10d | |||
| 6cc1748c51 | |||
| 541d2fc43d | |||
| d611a1ff05 |
12 changed files with 214 additions and 190 deletions
|
|
@ -58,6 +58,9 @@ nix-shell -p sops --run 'sops hosts/<hostname>/secrets.yaml'
|
||||||
### Deployment
|
### Deployment
|
||||||
The Git runner handles deployment automatically when changes merge to main. A successful `./scripts/test-configuration <host>` dry-build is the gate before pushing.
|
The Git runner handles deployment automatically when changes merge to main. A successful `./scripts/test-configuration <host>` dry-build is the gate before pushing.
|
||||||
|
|
||||||
|
### Host Access
|
||||||
|
When connecting to a host via SSH or any network tool, always use the fully-qualified `<hostname>.cloonar.com` form (e.g. `web-arm.cloonar.com`, `mail.cloonar.com`). Bare hostnames do not resolve.
|
||||||
|
|
||||||
## Custom Packages
|
## Custom Packages
|
||||||
|
|
||||||
When creating a new package in `utils/pkgs/`, always include an `update.sh` script to automate version updates. See `utils/pkgs/claude-code/update.sh` for the pattern:
|
When creating a new package in `utils/pkgs/`, always include an `update.sh` script to automate version updates. See `utils/pkgs/claude-code/update.sh` for the pattern:
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
/home/dominik/projects/cloonar/ai-image-alt
|
/home/dominik/projects/cloonar/ai-image-alt
|
||||||
/home/dominik/projects/cloonar/bookmap
|
/home/dominik/projects/cloonar/bookmap
|
||||||
/home/dominik/projects/cloonar/iso-bot
|
/home/dominik/projects/cloonar/iso-bot
|
||||||
|
/home/dominik/projects/macher.solutions/mengenkauf-backend
|
||||||
|
|
||||||
/home/dominik/projects/home-automation/lego-hetzner-bridge
|
/home/dominik/projects/home-automation/lego-hetzner-bridge
|
||||||
/home/dominik/projects/home-automation/ghetto-nixos
|
/home/dominik/projects/home-automation/ghetto-nixos
|
||||||
|
|
|
||||||
|
|
@ -614,6 +614,11 @@ in
|
||||||
home.activation.projects = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
home.activation.projects = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
PATH="${pkgs.git}/bin:${pkgs.openssh}/bin:$PATH"
|
PATH="${pkgs.git}/bin:${pkgs.openssh}/bin:$PATH"
|
||||||
set +eu
|
set +eu
|
||||||
|
|
||||||
|
mkdir -p ${persistHome}/projects/infrastructure
|
||||||
|
mkdir -p ${persistHome}/projects/cloonar
|
||||||
|
mkdir -p ${persistHome}/projects/macher.solutions
|
||||||
|
|
||||||
ssh-keygen -R git.cloonar.com
|
ssh-keygen -R git.cloonar.com
|
||||||
ssh-keyscan git.cloonar.com >> ~/.ssh/known_hosts
|
ssh-keyscan git.cloonar.com >> ~/.ssh/known_hosts
|
||||||
git clone forgejo@git.cloonar.com/infrastructure/ci-templates.git ${persistHome}/projects/infrastructure/ci-templates 2>/dev/null
|
git clone forgejo@git.cloonar.com/infrastructure/ci-templates.git ${persistHome}/projects/infrastructure/ci-templates 2>/dev/null
|
||||||
|
|
@ -638,6 +643,7 @@ in
|
||||||
git clone forgejo@git.cloonar.com:Cloonar/ai-image-alt.git ${persistHome}/projects/cloonar/ai-image-alt 2>/dev/null
|
git clone forgejo@git.cloonar.com:Cloonar/ai-image-alt.git ${persistHome}/projects/cloonar/ai-image-alt 2>/dev/null
|
||||||
git clone forgejo@git.cloonar.com:Cloonar/bookmap.git ${persistHome}/projects/cloonar/bookmap 2>/dev/null
|
git clone forgejo@git.cloonar.com:Cloonar/bookmap.git ${persistHome}/projects/cloonar/bookmap 2>/dev/null
|
||||||
git clone forgejo@git.cloonar.com/openclawd/iso-bot.git ${persistHome}/projects/cloonar/iso-bot 2>/dev/null
|
git clone forgejo@git.cloonar.com/openclawd/iso-bot.git ${persistHome}/projects/cloonar/iso-bot 2>/dev/null
|
||||||
|
git clone forgejo@git.cloonar.com/macher.solutions/mengenkauf-backend.git ${persistHome}/projects/macher.solutions/mengenkauf-backend
|
||||||
|
|
||||||
|
|
||||||
git clone forgejo@git.cloonar.com:dominik.polakovics/typo3-basic.git ${persistHome}/cloonar/typo3-basic 2>/dev/null
|
git clone forgejo@git.cloonar.com:dominik.polakovics/typo3-basic.git ${persistHome}/cloonar/typo3-basic 2>/dev/null
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
./modules/bitwarden
|
./modules/bitwarden
|
||||||
./modules/authelia.nix
|
./modules/authelia.nix
|
||||||
./modules/collabora.nix
|
./modules/collabora.nix
|
||||||
./modules/ocis.nix
|
|
||||||
./modules/nextcloud
|
./modules/nextcloud
|
||||||
./modules/rustdesk.nix
|
./modules/rustdesk.nix
|
||||||
./modules/postgresql.nix
|
./modules/postgresql.nix
|
||||||
|
|
@ -55,11 +54,6 @@
|
||||||
"openssl-1.1.1w"
|
"openssl-1.1.1w"
|
||||||
];
|
];
|
||||||
|
|
||||||
# oCIS (ownCloud Infinite Scale) has an unfree license
|
|
||||||
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
|
||||||
"ocis_5-bin"
|
|
||||||
];
|
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
vim
|
vim
|
||||||
davfs2
|
davfs2
|
||||||
|
|
|
||||||
|
|
@ -169,14 +169,6 @@ in {
|
||||||
oidc = {
|
oidc = {
|
||||||
## The other portions of the mandatory OpenID Connect 1.0 configuration go here.
|
## The other portions of the mandatory OpenID Connect 1.0 configuration go here.
|
||||||
## See: https://www.authelia.com/c/oidc
|
## See: https://www.authelia.com/c/oidc
|
||||||
lifespans = {
|
|
||||||
custom = {
|
|
||||||
ocis = {
|
|
||||||
access_token = "2 days";
|
|
||||||
refresh_token = "3 days";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
cors = {
|
cors = {
|
||||||
endpoints = [
|
endpoints = [
|
||||||
"authorization"
|
"authorization"
|
||||||
|
|
@ -297,79 +289,6 @@ in {
|
||||||
];
|
];
|
||||||
userinfo_signing_algorithm = "none";
|
userinfo_signing_algorithm = "none";
|
||||||
}
|
}
|
||||||
# oCIS (ownCloud Infinite Scale) - web client (public, PKCE)
|
|
||||||
{
|
|
||||||
id = "ocis";
|
|
||||||
description = "ownCloud Infinite Scale";
|
|
||||||
lifespan = "ocis";
|
|
||||||
public = true;
|
|
||||||
authorization_policy = "internal";
|
|
||||||
require_pkce = true;
|
|
||||||
pkce_challenge_method = "S256";
|
|
||||||
redirect_uris = [
|
|
||||||
"https://files.cloonar.com/"
|
|
||||||
"https://files.cloonar.com/oidc-callback.html"
|
|
||||||
"https://files.cloonar.com/oidc-silent-redirect.html"
|
|
||||||
"https://files.cloonar.com/apps/openidconnect/redirect"
|
|
||||||
];
|
|
||||||
scopes = [ "openid" "offline_access" "groups" "profile" "email" ];
|
|
||||||
response_types = [ "code" ];
|
|
||||||
grant_types = [ "authorization_code" "refresh_token" ];
|
|
||||||
access_token_signed_response_alg = "none";
|
|
||||||
userinfo_signing_algorithm = "none";
|
|
||||||
token_endpoint_auth_method = "none";
|
|
||||||
}
|
|
||||||
# oCIS Desktop - static credentials hardcoded in the oCIS desktop app
|
|
||||||
{
|
|
||||||
id = "xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69";
|
|
||||||
description = "ownCloud Infinite Scale (Desktop)";
|
|
||||||
secret = "$pbkdf2-sha512$310000$NR4tztBecptj1ZiITK/Ktw$GkFNBfq1B3T1lDTKMci1aO8iulQFNlEtfydLwTrNTKIfrQFjM7EiOBaHGOBC7ohPaNfYCRAYYzcP2fDQf5XRGQ";
|
|
||||||
public = false;
|
|
||||||
authorization_policy = "internal";
|
|
||||||
require_pkce = true;
|
|
||||||
pkce_challenge_method = "S256";
|
|
||||||
redirect_uris = [ "http://127.0.0.1" "http://localhost" ];
|
|
||||||
scopes = [ "openid" "offline_access" "groups" "profile" "email" ];
|
|
||||||
response_types = [ "code" ];
|
|
||||||
grant_types = [ "authorization_code" "refresh_token" ];
|
|
||||||
access_token_signed_response_alg = "none";
|
|
||||||
userinfo_signing_algorithm = "none";
|
|
||||||
token_endpoint_auth_method = "client_secret_basic";
|
|
||||||
}
|
|
||||||
# oCIS Android - static credentials hardcoded in the oCIS Android app
|
|
||||||
{
|
|
||||||
id = "e4rAsNUSIUs0lF4nbv9FmCeUkTlV9GdgTLDH1b5uie7syb90SzEVrbN7HIpmWJeD";
|
|
||||||
description = "ownCloud Infinite Scale (Android)";
|
|
||||||
secret = "$pbkdf2-sha512$310000$NjEumkph77Gql.CH0Oq3zg$I9ubOZ3VRCXPbHpW1U4bQmvLgP5DdiFeGgple2nIjtUJsFgkdiV/hcCt1h6adr1uvJSJAtHDRnMhYf3Zp2BpcQ";
|
|
||||||
public = false;
|
|
||||||
authorization_policy = "internal";
|
|
||||||
require_pkce = true;
|
|
||||||
pkce_challenge_method = "S256";
|
|
||||||
redirect_uris = [ "oc://android.owncloud.com" ];
|
|
||||||
scopes = [ "openid" "offline_access" "groups" "profile" "email" ];
|
|
||||||
response_types = [ "code" ];
|
|
||||||
grant_types = [ "authorization_code" "refresh_token" ];
|
|
||||||
access_token_signed_response_alg = "none";
|
|
||||||
userinfo_signing_algorithm = "none";
|
|
||||||
token_endpoint_auth_method = "client_secret_basic";
|
|
||||||
}
|
|
||||||
# oCIS iOS - static credentials hardcoded in the oCIS iOS app
|
|
||||||
{
|
|
||||||
id = "mxd5OQDk6es5LzOzRvidJNfXLUZS2oN3oUFeXPP8LpPrhx3UroJFduGEYIBOxkY1";
|
|
||||||
description = "ownCloud Infinite Scale (iOS)";
|
|
||||||
secret = "$pbkdf2-sha512$310000$.nIk0IUua7n8VAUoR85yyA$6UhT/gi7spH/0PRqTa6clz7QMRSmP/FZ0BDIumJupM4V2Ai6MgGKdzlEaNTc2IDqpGL3NxF626g4zAHFRgD7Zg";
|
|
||||||
public = false;
|
|
||||||
authorization_policy = "internal";
|
|
||||||
require_pkce = true;
|
|
||||||
pkce_challenge_method = "S256";
|
|
||||||
redirect_uris = [ "oc://ios.owncloud.com" "oc.ios://ios.owncloud.com" ];
|
|
||||||
scopes = [ "openid" "offline_access" "groups" "profile" "email" ];
|
|
||||||
response_types = [ "code" ];
|
|
||||||
grant_types = [ "authorization_code" "refresh_token" ];
|
|
||||||
access_token_signed_response_alg = "none";
|
|
||||||
userinfo_signing_algorithm = "none";
|
|
||||||
token_endpoint_auth_method = "client_secret_basic";
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,14 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
hostName = "nextcloud.cloonar.com";
|
hostName = "nextcloud.cloonar.com";
|
||||||
https = true;
|
https = true;
|
||||||
package = pkgs.nextcloud32;
|
package = pkgs.nextcloud33;
|
||||||
# Instead of using pkgs.nextcloud27Packages.apps,
|
# Instead of using pkgs.nextcloud27Packages.apps,
|
||||||
# we'll reference the package version specified above
|
# we'll reference the package version specified above
|
||||||
extraApps = {
|
extraApps = {
|
||||||
inherit (config.services.nextcloud.package.packages.apps) calendar contacts deck groupfolders mail richdocuments tasks;
|
inherit (config.services.nextcloud.package.packages.apps) calendar contacts deck groupfolders mail richdocuments tasks;
|
||||||
oidc_login = pkgs.fetchNextcloudApp rec {
|
oidc_login = pkgs.fetchNextcloudApp rec {
|
||||||
url = "https://github.com/pulsejet/nextcloud-oidc-login/releases/download/v3.2.5/oidc_login.tar.gz";
|
url = "https://github.com/pulsejet/nextcloud-oidc-login/releases/download/v3.3.1/oidc_login.tar.gz";
|
||||||
sha256 = "sha256-Qtqcw1OspTHg0QRIgDMxNru6ZGL8y5XhJ5gdgqn6/Wc=";
|
sha256 = "sha256-KBa8A7aC0uS6FQoOSa7nIkaaYe+A2KeAtzfqoKw0Gn4=";
|
||||||
license = "gpl3";
|
license = "gpl3";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
sops.secrets.ocis-admin-password = {
|
|
||||||
owner = "ocis";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Upstream services.ocis module adds ReadOnlyPaths = [ configDir ] to the
|
|
||||||
# systemd unit, which makes systemd fail the namespace setup if the path
|
|
||||||
# does not exist, and it never runs `ocis init` to populate ocis.yaml with
|
|
||||||
# the service's internal secrets. Run init in a separate oneshot so the
|
|
||||||
# sandbox restrictions of ocis.service don't block writes to configDir.
|
|
||||||
systemd.services.ocis-init = {
|
|
||||||
description = "Initialize oCIS config (one-shot)";
|
|
||||||
before = [ "ocis.service" ];
|
|
||||||
requiredBy = [ "ocis.service" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
User = "ocis";
|
|
||||||
Group = "ocis";
|
|
||||||
StateDirectory = "ocis";
|
|
||||||
LoadCredential = "admin-password:${config.sops.secrets.ocis-admin-password.path}";
|
|
||||||
};
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
install -d -m 0700 /var/lib/ocis/config
|
|
||||||
if [ ! -f /var/lib/ocis/config/ocis.yaml ]; then
|
|
||||||
${lib.getExe pkgs.ocis_5-bin} init \
|
|
||||||
--config-path /var/lib/ocis/config \
|
|
||||||
--admin-password "$(cat "$CREDENTIALS_DIRECTORY/admin-password")" \
|
|
||||||
--insecure true
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
services.ocis = {
|
|
||||||
enable = true;
|
|
||||||
url = "https://files.cloonar.com";
|
|
||||||
address = "127.0.0.1";
|
|
||||||
port = 9200;
|
|
||||||
stateDir = "/var/lib/ocis";
|
|
||||||
configDir = "/var/lib/ocis/config";
|
|
||||||
environment = {
|
|
||||||
# Proxy - SSL terminated at nginx
|
|
||||||
PROXY_TLS = "false";
|
|
||||||
OCIS_INSECURE = "false";
|
|
||||||
|
|
||||||
# OIDC - Authelia
|
|
||||||
PROXY_OIDC_ISSUER = "https://auth.cloonar.com";
|
|
||||||
PROXY_OIDC_REWRITE_WELLKNOWN = "true";
|
|
||||||
PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD = "none";
|
|
||||||
PROXY_OIDC_SKIP_USER_INFO = "false";
|
|
||||||
WEB_OIDC_CLIENT_ID = "ocis";
|
|
||||||
|
|
||||||
# Auto-provision user accounts from OIDC claims
|
|
||||||
PROXY_AUTOPROVISION_ACCOUNTS = "true";
|
|
||||||
PROXY_AUTOPROVISION_CLAIM_USERNAME = "preferred_username";
|
|
||||||
PROXY_AUTOPROVISION_CLAIM_EMAIL = "email";
|
|
||||||
PROXY_AUTOPROVISION_CLAIM_DISPLAYNAME = "name";
|
|
||||||
PROXY_AUTOPROVISION_CLAIM_GROUPS = "groups";
|
|
||||||
|
|
||||||
# Disable demo users
|
|
||||||
IDM_CREATE_DEMO_USERS = "false";
|
|
||||||
|
|
||||||
# Move internal services off their defaults where Prometheus exporters
|
|
||||||
# already bind on this host:
|
|
||||||
# - node-exporter owns 9100 (oCIS web default)
|
|
||||||
# - blackbox-exporter owns 9115 (oCIS webdav default)
|
|
||||||
WEB_HTTP_ADDR = "127.0.0.1:19100";
|
|
||||||
WEBDAV_HTTP_ADDR = "127.0.0.1:19115";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Nginx reverse proxy
|
|
||||||
services.nginx.virtualHosts."files.cloonar.com" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
acmeRoot = null;
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://127.0.0.1:9200";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
extraConfig = ''
|
|
||||||
client_max_body_size 10G;
|
|
||||||
proxy_read_timeout 600s;
|
|
||||||
proxy_send_timeout 600s;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -74,6 +74,27 @@ in
|
||||||
ExecStart = "${envGenerateScript} ${config.sops.secrets.supabase-env.path}";
|
ExecStart = "${envGenerateScript} ${config.sops.secrets.supabase-env.path}";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
# Seed the edge-runtime's bootstrap `main` function. The container's
|
||||||
|
# entrypoint requires `/home/deno/functions/main/index.ts` to exist;
|
||||||
|
# without it edge-runtime fails with "could not find an appropriate
|
||||||
|
# entrypoint". Re-seed on every activation so updates to the bootstrap
|
||||||
|
# are picked up, while leaving user-authored functions untouched.
|
||||||
|
supabase-functions-seed = {
|
||||||
|
description = "Seed Supabase edge-functions main bootstrap";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
install -d -m 0755 /var/lib/supabase/functions/main
|
||||||
|
install -m 0644 ${./functions/main/index.ts} /var/lib/supabase/functions/main/index.ts
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
podman-supabase-functions = {
|
||||||
|
after = [ "supabase-functions-seed.service" ];
|
||||||
|
requires = [ "supabase-functions-seed.service" ];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
144
hosts/web-arm/modules/supabase/functions/main/index.ts
Normal file
144
hosts/web-arm/modules/supabase/functions/main/index.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
|
||||||
|
|
||||||
|
console.log('main function started')
|
||||||
|
|
||||||
|
const JWT_SECRET = Deno.env.get('JWT_SECRET')
|
||||||
|
const SUPABASE_URL = Deno.env.get('SUPABASE_URL')
|
||||||
|
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
|
||||||
|
|
||||||
|
// Create JWKS for ES256/RS256 tokens (newer tokens)
|
||||||
|
let SUPABASE_JWT_KEYS: ReturnType<typeof jose.createRemoteJWKSet> | null = null
|
||||||
|
if (SUPABASE_URL) {
|
||||||
|
try {
|
||||||
|
SUPABASE_JWT_KEYS = jose.createRemoteJWKSet(
|
||||||
|
new URL('/auth/v1/.well-known/jwks.json', SUPABASE_URL)
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to fetch JWKS from SUPABASE_URL:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAuthToken(req: Request) {
|
||||||
|
const authHeader = req.headers.get('authorization')
|
||||||
|
if (!authHeader) {
|
||||||
|
throw new Error('Missing authorization header')
|
||||||
|
}
|
||||||
|
const [bearer, token] = authHeader.split(' ')
|
||||||
|
if (bearer !== 'Bearer') {
|
||||||
|
throw new Error(`Auth header is not 'Bearer {token}'`)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isValidLegacyJWT(jwt: string): Promise<boolean> {
|
||||||
|
if (!JWT_SECRET) {
|
||||||
|
console.error('JWT_SECRET not available for HS256 token verification')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const secretKey = encoder.encode(JWT_SECRET)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await jose.jwtVerify(jwt, secretKey);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Symmetric Legacy JWT verification error', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isValidJWT(jwt: string): Promise<boolean> {
|
||||||
|
if (!SUPABASE_JWT_KEYS) {
|
||||||
|
console.error('JWKS not available for ES256/RS256 token verification')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await jose.jwtVerify(jwt, SUPABASE_JWT_KEYS)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Asymmetric JWT verification error', e);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isValidHybridJWT(jwt: string): Promise<boolean> {
|
||||||
|
const { alg: jwtAlgorithm } = jose.decodeProtectedHeader(jwt)
|
||||||
|
|
||||||
|
if (jwtAlgorithm === 'HS256') {
|
||||||
|
console.log(`Legacy token type detected, attempting ${jwtAlgorithm} verification.`)
|
||||||
|
|
||||||
|
return await isValidLegacyJWT(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jwtAlgorithm === 'ES256' || jwtAlgorithm === 'RS256') {
|
||||||
|
return await isValidJWT(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.serve(async (req: Request) => {
|
||||||
|
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
|
||||||
|
try {
|
||||||
|
const token = getAuthToken(req)
|
||||||
|
const isValidJWT = await isValidHybridJWT(token);
|
||||||
|
|
||||||
|
if (!isValidJWT) {
|
||||||
|
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return new Response(JSON.stringify({ msg: e.toString() }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(req.url)
|
||||||
|
const { pathname } = url
|
||||||
|
const path_parts = pathname.split('/')
|
||||||
|
const service_name = path_parts[1]
|
||||||
|
|
||||||
|
if (!service_name || service_name === '') {
|
||||||
|
const error = { msg: 'missing function name in request' }
|
||||||
|
return new Response(JSON.stringify(error), {
|
||||||
|
status: 400,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const servicePath = `/home/deno/functions/${service_name}`
|
||||||
|
console.error(`serving the request with ${servicePath}`)
|
||||||
|
|
||||||
|
const memoryLimitMb = 150
|
||||||
|
const workerTimeoutMs = 1 * 60 * 1000
|
||||||
|
const noModuleCache = false
|
||||||
|
const importMapPath = null
|
||||||
|
const envVarsObj = Deno.env.toObject()
|
||||||
|
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
|
||||||
|
|
||||||
|
try {
|
||||||
|
const worker = await EdgeRuntime.userWorkers.create({
|
||||||
|
servicePath,
|
||||||
|
memoryLimitMb,
|
||||||
|
workerTimeoutMs,
|
||||||
|
noModuleCache,
|
||||||
|
importMapPath,
|
||||||
|
envVars,
|
||||||
|
})
|
||||||
|
return await worker.fetch(req)
|
||||||
|
} catch (e) {
|
||||||
|
const error = { msg: e.toString() }
|
||||||
|
return new Response(JSON.stringify(error), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -1,4 +1,19 @@
|
||||||
{ pkgs, lib, config, ... }:
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
# Universal Links / Associated Domains for the iOS workout app
|
||||||
|
appleAppSiteAssociation = {
|
||||||
|
applinks = {
|
||||||
|
details = [
|
||||||
|
{
|
||||||
|
appIDs = [ "XWJ4DC7TBH.io.fueltide.workout" ];
|
||||||
|
components = [
|
||||||
|
{ "/" = "/auth/*"; }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
# SOPS secret for fueltide.io DNS credentials (separate Hetzner API token)
|
# SOPS secret for fueltide.io DNS credentials (separate Hetzner API token)
|
||||||
sops.secrets.fueltide-lego-credentials = { };
|
sops.secrets.fueltide-lego-credentials = { };
|
||||||
|
|
@ -17,6 +32,10 @@
|
||||||
credentialsFile = config.sops.secrets.fueltide-lego-credentials.path;
|
credentialsFile = config.sops.secrets.fueltide-lego-credentials.path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
security.acme.certs."link.fueltide.io" = {
|
||||||
|
credentialsFile = config.sops.secrets.fueltide-lego-credentials.path;
|
||||||
|
};
|
||||||
|
|
||||||
security.acme.certs."stage.fueltide.io" = {
|
security.acme.certs."stage.fueltide.io" = {
|
||||||
credentialsFile = config.sops.secrets.fueltide-lego-credentials.path;
|
credentialsFile = config.sops.secrets.fueltide-lego-credentials.path;
|
||||||
};
|
};
|
||||||
|
|
@ -25,6 +44,16 @@
|
||||||
credentialsFile = config.sops.secrets.fueltide-lego-credentials.path;
|
credentialsFile = config.sops.secrets.fueltide-lego-credentials.path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."link.fueltide.io" = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
|
||||||
|
locations."= /.well-known/apple-app-site-association".extraConfig = ''
|
||||||
|
default_type application/json;
|
||||||
|
return 200 '${builtins.toJSON appleAppSiteAssociation}';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
services.webstack.instances."stage.fueltide.io" = {
|
services.webstack.instances."stage.fueltide.io" = {
|
||||||
enablePhp = false;
|
enablePhp = false;
|
||||||
enableDefaultLocations = false;
|
enableDefaultLocations = false;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{ lib, pkgs, runCommand, claude-code }:
|
{ lib, pkgs, runCommand, claude-code }:
|
||||||
|
|
||||||
let
|
let
|
||||||
version = "2.1.75";
|
version = "2.1.111";
|
||||||
|
|
||||||
src = pkgs.fetchzip {
|
src = pkgs.fetchzip {
|
||||||
url = "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${version}.tgz";
|
url = "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${version}.tgz";
|
||||||
hash = "sha256-EgwxqiCl7c8PoRYyHDvcgvK8txDd0XJeZD1vybZyp4E=";
|
hash = "sha256-K3qhZXVJ2DIKv7YL9f/CHkuUYnK0lkIR1wjEa+xeSCk=";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Create a modified source with our package-lock.json
|
# Create a modified source with our package-lock.json
|
||||||
|
|
@ -22,7 +22,7 @@ in
|
||||||
|
|
||||||
npmDeps = pkgs.fetchNpmDeps {
|
npmDeps = pkgs.fetchNpmDeps {
|
||||||
src = srcWithLock;
|
src = srcWithLock;
|
||||||
hash = "sha256-DIyV2ZyrEI+iCOb4VcKbdd6NWyFqRUpH/rKv/HvCcG8=";
|
hash = "sha256-EFh1nVImvToqY+scUcodg50emRpF6Rzi+AJNPi59AVY=";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Remove the old postPatch since srcWithLock already includes package-lock.json
|
# Remove the old postPatch since srcWithLock already includes package-lock.json
|
||||||
|
|
|
||||||
8
utils/pkgs/claude-code/package-lock.json
generated
8
utils/pkgs/claude-code/package-lock.json
generated
|
|
@ -5,13 +5,13 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-code": "^2.1.75"
|
"@anthropic-ai/claude-code": "^2.1.111"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@anthropic-ai/claude-code": {
|
"node_modules/@anthropic-ai/claude-code": {
|
||||||
"version": "2.1.75",
|
"version": "2.1.111",
|
||||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.75.tgz",
|
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.111.tgz",
|
||||||
"integrity": "sha512-K5xwpR53WfZcKXhQedgoxcRbi6arH7S9YrhVXifA3klZ/5L4zcv+wENzITISygm1MwbxX5w2c2N1Qd/oUSY42w==",
|
"integrity": "sha512-zNZcINqvtMpDM4lZqnOZcrou57k9rUBfCZziH47nMG9FNks7I6azN7+SMU3zhwqBwYrvx6o4i7Ecu7mDCi0AmA==",
|
||||||
"license": "SEE LICENSE IN README.md",
|
"license": "SEE LICENSE IN README.md",
|
||||||
"bin": {
|
"bin": {
|
||||||
"claude": "cli.js"
|
"claude": "cli.js"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue