From bb73087d79d84eda26c577abe8b73593370360f7 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Wed, 20 May 2026 11:30:01 -0400 Subject: [PATCH 1/4] oidc generic pass 1 --- .env.dev.enc | 56 +++---- .env.local.enc | 58 ++++---- .env.prod.enc | 53 ++++--- .sops.yaml | 3 +- README.md | 21 ++- docker-compose.withauth.yml | 169 +++++++++++++++++++++ selfhost/Caddyfile | 17 +++ selfhost/init-db.sh | 12 ++ server.ts | 9 ++ src/api/accounts.ts | 21 +-- src/api/auth.server.ts | 7 +- src/api/kf-auth.ts | 9 +- src/api/kf-summary.ts | 2 +- src/lib/auth-internal.server.ts | 128 ++++++++++++++++ src/lib/kf-auth.server.ts | 135 ++--------------- src/lib/kf-orgs.server.ts | 28 +--- src/lib/kf-profile-cache.server.ts | 60 +------- src/lib/oidc.server.ts | 230 +++++++++++++++++++++++++++++ src/loaders.server.ts | 10 +- 19 files changed, 722 insertions(+), 306 deletions(-) create mode 100644 docker-compose.withauth.yml create mode 100644 selfhost/Caddyfile create mode 100755 selfhost/init-db.sh create mode 100644 src/lib/auth-internal.server.ts create mode 100644 src/lib/oidc.server.ts diff --git a/.env.dev.enc b/.env.dev.enc index 9162ddb..0f5d240 100644 --- a/.env.dev.enc +++ b/.env.dev.enc @@ -1,34 +1,36 @@ -#ENC[AES256_GCM,data:lmG0AqBdAGWZ,iv:MwavSYQHmVIdo7D/1H2P6IefCglKP/zO8H2Xid/Q3aA=,tag:erWfGwBLgtkurOL1QyEfqA==,type:comment] -DATABASE_URL=ENC[AES256_GCM,data:qaQbPyVtnXzaLBvXgbJmz0u8lbMucfDeWndAdohHyv0Obgaw41YP1MHHYhGu9CVwaD1gtnU=,iv:bMnTxRSRxyrk1+w4J4LlxxHTBB3kHegGDZMpFud9xJk=,tag:wf5APMrohuj9mjB3bxRLuA==,type:str] -SESSION_SECRET=ENC[AES256_GCM,data:Ndjpa8UbWLB4mERTyYRzdnHOYeA=,iv:hL3b1nEA7oRzCKwk9eysL7wJpdf2/IatYNQOUOOEH4g=,tag:27AtxAFW47IoEyeu4RTiLQ==,type:str] -PORT=ENC[AES256_GCM,data:cwe3nQ==,iv:P2g2H8uZayCkxK72zt6iEXz6gYuBjMzGSLAT9bjmDF8=,tag:bHCQjJR3+q1b9UsBeFp11g==,type:str] -APP_REPLICAS=ENC[AES256_GCM,data:kw==,iv:xziOg6i4k+7EW1/9qRLqa+81MHI3C8qpf4SBX2hTtkI=,tag:+ZaHEXP3SuQwz7RbmEJlEw==,type:str] -#ENC[AES256_GCM,data:0wTn,iv:47NbN2UqfY6m1bc236726HMblGjeeG/Zhq6OusazugY=,tag:8eVx9TY7QKVVtTfZ6vZr0A==,type:comment] -S3_BUCKET=ENC[AES256_GCM,data:vYPB9/hnBdidqjZSTSji,iv:h01LvE7PgzdlGbPDlO4hvEXOrHyUIwa0V5HLrt65shc=,tag:oeSdbohtQ3MM7hKSJGmnhg==,type:str] -S3_ENDPOINT=ENC[AES256_GCM,data:NVE8DTSt7kOhNAN1jDb2OnIyQO1CUUVYJVd9DOOEOGB+8+thHPft6qLTsKUTLAMM6Hhwe/fz3KvKoEZPL1Jp5GY=,iv:zGhNHAuVT+j5FlMbOLvn1MFDhkItiF8ULmAsTYDaOGo=,tag:4KY5RC60LvE+O7e+Z2asEw==,type:str] -S3_ACCESS_KEY=ENC[AES256_GCM,data:YCQiNgmHl5Y7iOufuXH/h0MMcV9FD5s17dLNKZOVfKw=,iv:6r/l6bFkvPu0pjfv1hVzDmAUXHYKCcikyNsOLKst1Rw=,tag:xf39HHqjnCSdmGCYvRlsVg==,type:str] -S3_SECRET_KEY=ENC[AES256_GCM,data:m/oU91hR/3oMOSCq7JfXvMj1F6B4bXORol8s8NCjzeT0INjUxUopiOrjWyPczQ6Ob+UNUQqYMG9UakmppK8aJg==,iv:+g3Zp1OsklYlR0LTV/M7PONRxtfy1PNvv9NFw3c9mxg=,tag:OiZcJW9JHboPleMAaW4ZHQ==,type:str] -ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:0xHZcdQ=,iv:YTsBYn4WFyFOp5jI5KvEt++jMttMbAKG3wf4QClGq3c=,tag:qKNachtilRJuhNOMRf8D7Q==,type:str] -CF_ACCOUNT_ID=ENC[AES256_GCM,data:NJtRMOUyRcuch+gO/uDL/mm2lZfzmv2BSsrUa+/2AAo=,iv:gXK7qb5N8X/nLHTruVJA0S3SIOwk0yBS3FTmoN5BrCE=,tag:wboPv1RKrwIokNKrvmvD4w==,type:str] -CF_API_TOKEN=ENC[AES256_GCM,data:ftZnNMauVci3t2kigVJ8YzZ/Iht4tanQCEMYJM+MX/iqHli39T9TeyNomjemt4ckCNy7CEk=,iv:AoXwvL2jsF13CjKjJEIb0Gsxbfdy9pPQFc7nXkGA/+U=,tag:D2nTBOaW+9nl3Pikw96xXw==,type:str] -DEPLOY_HOST=ENC[AES256_GCM,data:hmfvyFfEthMPpl7glA==,iv:dzQE2927TQW59oxEszmOc+nprDmj9MVhCywBxzuE7yU=,tag:KKaUYhYwVfgQhEoyBy8I0w==,type:str] -#ENC[AES256_GCM,data:5EmRUT5TP04DiujwA4ybhpxmpwg=,iv:KL1SBHuUitjog44NMZM0NDljMebwx8sYPUMFIWCPp80=,tag:0uDxOgyB1yDf2y981u7Upw==,type:comment] -KF_AUTH_URL=ENC[AES256_GCM,data:E34WyjcN/ynL8nqSBE4cGrWkFgHpVpq+6quoX5IS4mKxCYPNuQ==,iv:TRqhyuD69okX6T3Zfh+DRG87X6a06S7u2+Pf4nUEwWM=,tag:SpOJTCdcjg705lDzft16EA==,type:str] -KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:iztGhF7KOBQGGiM=,iv:azzug4Y7T90ICdRwITtSFpPoX3g3fpMr5V7s1c3wrno=,tag:QIGjCq5g49IYoJhweqjCYQ==,type:str] -KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:nP7Nd/LsicIDeEAN6xbWGQ3jYECinkca+PMve8qUHISTKECNEGvs/BBhkSPTTbT/gb/wqZuqLMa7I9RVc7rWWQ==,iv:Ngb+Bg1bPVuHxtHY0hpF1BgKU14ECE7SG+Vl+wCB0ms=,tag:LIc8nj+gxQdlyggLMHT4/g==,type:str] -KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:D4P6kATJx5yiKzvN6syctZJxDTpyw5A7k+61LkJG3FS7frqnIYop7EKc5tBoF/CdI/4tkwXYBchDxXsK89k59g==,iv:p82O44FMgnxV2okordv8Jm+JcVLB6sCERbzXRY9yJaQ=,tag:f28C3tInSKpxhrxlitWq1g==,type:str] -APP_URL=ENC[AES256_GCM,data:IPJstMk95GuHJunALmEIDZDa9QRgcsid,iv:llGmGCn+mXDPj1zB8H05z8pLGTuyecjoYeL0uyQyBfk=,tag:sM7e9S73CGC0KOQHOAFjHw==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPa0tzZ1k4eGR4NzhXWmU1\nQll0SXZUWUFsRGVNNnFUc08rZ3ozSXRDNlhBCkVKeEZYQlRzWnlUTVZZSWUyTFZn\naHV6eGhlVjR4TElUTzVOVVByYmxPZ3cKLS0tIDh1QVUxY2ZGY05pZklwUEFOVHU1\neUxzTmhnTEl5ZHM5WDhySnBTRThwYUUKtb4XQILCcsJWRDmt2ckSeey1jonQbmx7\nxq3wc07dNjXLTQOKzbqRPCRRl3ivxRSEQhP3GRPXISh9V+Pl9aUj3g==\n-----END AGE ENCRYPTED FILE-----\n +#ENC[AES256_GCM,data:J64y9RiY9mO7,iv:PgPt5keWxH/YIwgqOzflOmiXZV0/NLfY0jVl2lBb8sc=,tag:dEGfzjOcx7/LrPjnyFbylQ==,type:comment] +DATABASE_URL=ENC[AES256_GCM,data:MH6IJi+UhlojEFOcu0ZYqYCJKsTUTiFQRunI7BlwjrErwk/YUrb65o8edV9mSUjftbnk+jo=,iv:EJcQOziM1WIngMvWX7dlG877iYSItmSlnm2nlIcQVmE=,tag:cPIt27fEAU037qCd+RGN3A==,type:str] +SESSION_SECRET=ENC[AES256_GCM,data:SThK9G1iAKNRAw2cJdKCepn6amI=,iv:mY9V91jZ6YKcKWqOat8lA0yNJEj9rphaSK9gKz5Iun4=,tag:yeW3882+Gc05FCLpBPvFnQ==,type:str] +PORT=ENC[AES256_GCM,data:5lWgTQ==,iv:7zxANyByMT9cw3SuIe1pQKYhm3xpsDiFxgtlevVRdg8=,tag:n7Z2S9C225Zfh3qMpaE5xA==,type:str] +APP_REPLICAS=ENC[AES256_GCM,data:2g==,iv:/+iwIiaa+GSQkaOdWLvO5wd7ocO9MbbEF6k6/C5fRIk=,tag:NnXfnpYiSkQWzALItH9Atw==,type:str] +#ENC[AES256_GCM,data:oX6D,iv:bWay5HSecSfDGsyqV1qr5HyusjpLNdn+6ZaRKS+Pam8=,tag:He9zA9PmQTUJsGR+YGuEMQ==,type:comment] +S3_BUCKET=ENC[AES256_GCM,data:d4bmrVBqEd8PBCgZmJWe,iv:IcZqJIe6VN8ok2WN1zYIZ/gB01wXRHhJ16As0Cl1v18=,tag:1CISAApN3VceMfSY/cnTLw==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:dI+gGhHn+YE//lydzWQeK2oKUHa4eQ2DZ46jTpIZzNtKxMSjOJ1dn6aFITrtPvHr17Ef9xMW+H+dXcYo4yyBYF8=,iv:JalLirX7aPihIHX0DdO13uxLo+uWobzuk/jEkhF5rmI=,tag:TdyciEppW4ZqHMtdZsltcA==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:fEibwHUmYY8EyBnefRqLIZtaxj267fkUZgnUWITNImA=,iv:mFOQMXuKA1mhc9bVJnrIIiuQnjnBMWiM9ZYAYxq+LV0=,tag:4bppPzXoRI6/hC79wk1XUA==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:SqnLov0rL9VtoiTgw9AzAbdlrTlIkjibMT/uuRJbix+Of5qPpWVwMCUU2r9lm90dmBO2/7RjXMv8tnqcFirWrw==,iv:gPCHgc8s6cmny2SJPeM/rGtY7nYSjM3YNwEQg+c+fg4=,tag:rkSnwgx98TIU2qG3Z41qfA==,type:str] +ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:0ZBLuIc=,iv:mByaVTqE3zZYsOfoHf0x6gUQ60+1wn1dd9iPlco0SC8=,tag:F2sw4F+AD8+9YHcqyBkp6A==,type:str] +CF_ACCOUNT_ID=ENC[AES256_GCM,data:UDA7iTRvFEtONquBpukKrUbJSZ9EzrdOetRc4I5SrUg=,iv:Ux9e2KWScRIsen+jMa5mE1R/h5qJ0RWiHSHkYX5+76M=,tag:f7ohH4pFVyQtkUiPQ4u2RA==,type:str] +CF_API_TOKEN=ENC[AES256_GCM,data:AU9N7AsYCvadKO3DGlmqgo58ugW/VvPOcmcHQngqhqauFGPzym4aCYZL1xv9+BQX4b6XdZM=,iv:BGRd4+qUSdTtdkMWcieOfOHuWctdw8rmwVjj7Fg6Y8A=,tag:ywN8hMBesjtGpen5ImRC5Q==,type:str] +DEPLOY_HOST=ENC[AES256_GCM,data:YH65GXlLDyGeO0CB1A==,iv:k+E4hNXEUHq/A086aFy3hSCBr5G/eazv7m5LiTOkGh0=,tag:0hDQY7eFa9OMxF+msAaGQw==,type:str] +#ENC[AES256_GCM,data:0pVHFB+aljI+8luWNO5lPhu15rA=,iv:O6Wgy/AOLT9hJbzrYAecI5Mj+aOv7cdH9P5sj+eYhn0=,tag:MbM4zLssMwNojtqSvFGLzg==,type:comment] +KF_AUTH_URL=ENC[AES256_GCM,data:XHj9nRZx27Br/gECcNE6nFRmwyqtScXRF+pXZ2JC2dlFYWD+ig==,iv:u+8P1wSqFDIJTMUtmLHFHYPTIbk6T0WW77Sul0l5BF0=,tag:62BCM8VoU4VdypD6FXx/bg==,type:str] +KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:g1F0y4pTy1uSueY=,iv:kBO/7z1Dyw3fDMPS/JCGMYewVJHu+pDWL7IUWJm7cwM=,tag:QZNPoojcOxjeDxDB75qr/Q==,type:str] +KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:NaWmucYbbqoVZNtU0AP91v2HFSDI1nhTGdEFeUrufRuugRj617LvSgonabGri31pW09GYcgip0tLffjwxlh6bA==,iv:f6U5aGXz3uFLffzH+uuVpMk86MSNdWM5sCa/OwAsZ9k=,tag:JNgFl+NIN6hCRozbabE95A==,type:str] +KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:+M2mGEeSmm/rVkdMtraeobv5/s5jOvfrnudsG5Qvxm+Jv9wuPhFdmAeMkXqK6HRDDoTSDRPYrjkK/Kwaq2ghzQ==,iv:9mudkZFpeddUAYDTzJAtJBYhHf//80tFftpaDWRedLw=,tag:pPtyM56v2815rW5Xak+gGw==,type:str] +APP_URL=ENC[AES256_GCM,data:Tbqim9G/Bxqo2RSGvVuk3xfrQWPEXzj5,iv:NmGm+Q1PrImHUSHy70wQnNQ1fKCEf2zh6YqATGPY4kA=,tag:9KyNXpuI8Uo/BPXGdWzPGw==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKOUx3QTBoZm05S3BsYmlU\nRG9yNkw0ZFI2MDh2OStQcXQrZWxLaklmMDEwCk5xUnFnOWlGSFB0THV2WDM3UUYx\nMEE0ZFBTejNwb01YNXhvdFBtRlJoSlkKLS0tIFFySHpFNnk0eWhTNHlXM3BRc3dF\nTFAzcHRHalJlVUtSdnN2WE1ZcU55RW8K/kUhdyj/LfqtuOJQNU0F3DL4cngDfEh9\nWck0u6vAPy6pfC+xzzBE2r4/MEdITIfr2uhF1PLOZfSkm2PFKcUtPg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhWkUvRnQ1VFV0eGc1NTBD\nZlh3NGE0eU92S0tkQjdUenBCNDFBYW9Rc0hBCi9rRkxLaDhkNzZycktaT1NKaEFk\nUzFkblU1RkUvQVNxcmQyQXVzWjJFY2sKLS0tIDhncEowTDV3QzFaQ1RyM2RMdGNG\nb1FKKzhwRXpkOW9kREc2OEh4NmFQS28KQq7UW46I8aivy7v54ssfMlzss3uHxv2V\nsuPkfJhuWynpGZC6SOh++M7jqskLilR+mi67DjFNGhth+ehu09A/bQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyaFBPdGJuUjEyYTVDeXJa\nRDZwakQ5MGNvMUhoV1NiMEE2UnpDWmlBUjBNCm8zQkNOSjlMUUMzUkhpTlA4SEho\ndFNqSmRqL0h2VWFtOTlEWER3WTUrbm8KLS0tIGRjV1VMK0cyWmxJSFMySW1PVjIy\nSUl0R3FEOEJPWE82OEZJN0YrOHp1aUUKa6r5xLqeazSlCVqwahVUS7DyDK2gaf5Z\n5/wgpfziUCrTfpPZHle6BOt+tWqndwA4rPJt75FdUborh1SQo1mJSg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1ysddqggsx3h8zkv7xn3z26sjak5pqms6pyqhnky9ukrvpk7es5jsayz8w7 -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzQ3paZ0ZNaFg2NmE5c3B1\nY0lMdTc5ZTZ5SVBPZHo3M21pbFNhQ2dFdkJNCjU5WUxtMlM4VGRZVDcrY1htQ3VB\nVDZ6aGRPWWpnMWZITjRnVEFXdmIzSU0KLS0tIHA4VlljSEp2UzV4TGs4bVg0bjFD\naS9TUTQ1NE9Ga0pMR3Q4ZUNiVWIrQWcKd+zLbd1tHj1dVq5iY1B9cVmCwXGwdaQU\np5Wyyz2oygptyUheShfTim68mTDYm2k0+Ip6l9pIrtCTP9gGtM9Meg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYT2VGcmhPTVMvY0tQRXBw\nemVLaXBZakpUU0Jad2FoUlFneTR2S0lyMUZrClhTZnk2YURuSmw5OHRFdHpsMnVJ\nLzVqTFhFZk1uVVladnErVTRCeU9ka2sKLS0tIGliSTRQUmc1SGJOb2pmVExTektJ\nV08zN3dTZlVHYXRxbmZnd2dub05TeWcKVYE9K0cvh1J0/ac5+XnuBYSbQBLiYI+D\n3YaHr22wVWu+7z0aCTUpwqEuTPC1uAPj++VQpBYhNnAReFDJ20kCuw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBscnFReUxTYklKZWZxdWc4\nK1M0WHA0bjhwMGhzN1dGYTJhaFdyVW5ob0JrCkxGTmd0UVdmNmdNa0ZBR0VKRWMv\nZEdXYzF2RXdSZFVwREthR244SGVVcEkKLS0tIGZrSW5DQnBGUG9Zb3ZPUS85SXVY\nODdPeDFXWEtDQ1RTTHZUUlZKZmxoOUEK02DjreAQVgqpgrE29yyJ72D+OmlBXC8C\nwAZEZxZBbEL0wo145N2Tnm22tHkQ7B6P2C46fymxUS4l49qF6DvLHw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1SGJ4cEp5TVl0QmtCMmZ0\nT3ZFZ2tDRzFZajRqckhBM0xqSENxY3J6RUJVClVtODVoSTYrbWx0NS8vemVkb1kz\nTHBsdG5JTVVKaUZublBuSUZwTlJqQXMKLS0tIFlhL3FsNS9BNkcvMkZIOS85V2VU\nRnNTcXEzdHd5RnF0dWxXeVJPR0lueTAKRfZ+rFyoNPFtmrlU/XnZf+HJ8TLtEkZS\n+1nfMxkqwalBRQr98JRoBjEbKSU8tEC4By2J5hCAnKdznxHJjx+AAQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1qn0x93jhqjpqwvx5tgxnrwq5e3vuzur9whrkdnrvapd58esm45rqfkuxqh -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVWTRRNStCUy85T0J3TFlZ\nTEZIUEZVSElreTFLVUVsMVY1aU5MRlFhSVZ3CjFBUnY1TWtNbHdZM0VvR3Znalg4\naWVwdnhYOTkxR3p4Vis1K2xyeGI1UDgKLS0tIDh4bytib2JLU1pvUTU4NUJFc1U4\nR2JjS3FYNUIrS01jTmxEbE1kNlkzajQKEfSkMPUMCDMoZD/exYQDJ0OrWdfHW5K+\nXgII97m2GwW+oOrajdXhZCxgWsOZ0QnS1WP8JE4NN9vp6sxthoC72g==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5bUprbUVRM1ZUU0ZqREt4\na1EzUnNqRDFzSXY5NGF3eEFQUmVnWFlQeEE4CmRadGJKeHVtOW5HUmowS2VveHpl\nMFVKeGhhMkpOVTZKSDFzb0crY05US3cKLS0tIENINlZCL0UxMEtEbFRBVGFOTXVF\nMERnYktjak1DOHFhTEQzRmFacWxCeHMKVOGSrnO+0YU84r8p1tCatZI0CL8FOs0i\n67EtolhSu9GLarnuNv4MBG7JLefkXNxJnEssQKZ+BXWMkdTMe6I7cg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7 -sops_lastmodified=2026-05-17T03:28:30Z -sops_mac=ENC[AES256_GCM,data:qhUZeGuCDydHnrSDM5AQUTct8ss+O3pKFWphuIh88YCfRMSFuZjjVDbql5SqD5etXyGSxG4+pxVUFCvyfF0DKw8a73XdnYsPM05zrnFuX1p3UOikVtajKWYCHBu/Yjx6VOOS29HP9YCCJJX3bKfpTcNpkh30YWm8IH4/zUz8yb0=,iv:m48OnT821Uetn/GSoGeteqMn2awWLU1QgdDca8SV+bE=,tag:F9NIItju1QfEEXvZElgZgA==,type:str] +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5ZUVJeEhUTGExVW1LOUdn\nb2hQTUE0dDlDaGZQZnF5SkFCMGxHYytkV0ZzClNSN2lXaXJxSG9UbTV4L1J3M1Rn\nOEpobUZidm9zVlI2WGl5aVluajZwbncKLS0tICtjMENsZXplWGxZYUNsR2djbjJE\nNDZvOE92SVhBTDJLY1hwTERSYmZsMmsKt3wKD6mbEeyTBy5SKrrdBhUyHIPA/fQ7\nLC4gcd4kKHhEp9l9dY7YQeHxbVMNbQ2+JSQVYVpkaNzbaHMAXvDrKg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h +sops_lastmodified=2026-05-20T13:46:56Z +sops_mac=ENC[AES256_GCM,data:vopz9Fv7NU2kzx8OBW4wgcw2BSLhxo3bbNgz+Y9KPCtSeO/TI+W9QvfCmL3/a5ZUpq+PNlQq9T53LDy4YxuterPf8HzDombylY+tjNI1Qo6rfnTuJ5AZyDFxNgZSSI7TaokOp/dUMfKd+IYZme3vOLCqhns9wtdhCrFi1z1ccTU=,iv:NRfuTGgYDomLbtLE5r0JmIyeJ4QcpKPkL3Zd8I1R/aA=,tag:Cj3B0K+J75saxQ12QkaGTg==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/.env.local.enc b/.env.local.enc index e2be693..ef4db60 100644 --- a/.env.local.enc +++ b/.env.local.enc @@ -1,35 +1,37 @@ -#ENC[AES256_GCM,data:EwG7RRV6tRRp,iv:szSa5gysyS7lr5/4rnulyYhX33/6v6kWNoREGFrj2JM=,tag:k9obKCeUYSEzuUnnAapqEg==,type:comment] -NODE_ENV=ENC[AES256_GCM,data:xfSbj3OvQSlkuhI=,iv:U7OzOOp3BlZGITKK7ixzN2StVi5yGwobli26ZqRWRyY=,tag:kx+KDazKRB/8T2XdHjN3kw==,type:str] -PORT=ENC[AES256_GCM,data:igm7kQ==,iv:I1WRKuavFad/4voErHMyT7HfJT2OeCjktH1zv7CvOZ8=,tag:3DAa/hCbrp2ItT976w8eRg==,type:str] -DATABASE_URL=ENC[AES256_GCM,data:Zyccn7eQMZu6N5o4/VqB0Gafsip1V8ZPWjR4PytIIevmvMef0Br52FkhsVPXj9qD2g/Kuo0=,iv:eWo/ggSxXL3nRYrNv9bPPnu922H9F4JN6MUYt+Xp+PA=,tag:fpy8wx+0dPnnlaBfzSGQFw==,type:str] -SESSION_SECRET=ENC[AES256_GCM,data:+COS9fGKuGEMyHQBXWJMwlBVfnY=,iv:pv8tJfJhZrAgLPO8Txvf5gEUZ2m9eioxVhj6KziIopg=,tag:D8yOO+xCKUuax4B5EuqGVQ==,type:str] -#ENC[AES256_GCM,data:iqmMy/fCrzgR0yK3OpWkHWaBwg==,iv:uqznSmxMAyseEyqy7yQgrY0cT2AIG7tXbyIB7DX2/XE=,tag:DxyQyxqvHe0jTBshYfWfPw==,type:comment] -S3_BUCKET=ENC[AES256_GCM,data:qQCv9WqEOCpxZfRJvejV,iv:PX8OTqKO34d/gomzjF4pHAU3Xi3NWdXQfQmBTXOEs3E=,tag:ylKc+e6T8jWDm43O1XNBEA==,type:str] -S3_ENDPOINT=ENC[AES256_GCM,data:doEPI2dGiv08oLdBfrtNSxFA2G3JAfHPeSSJclj4APVFDrqTrFGnVdxVZEsELUBsvtPBf87942fX7L4+opTqYtU=,iv:Aa2ZPZUbsuMSrAWUF/fIbZEIJqvlvjbZiYAhCUdUhIo=,tag:v4Q77lniZGDMj+ZTdMBmgg==,type:str] -S3_ACCESS_KEY=ENC[AES256_GCM,data:vwB86XE3vg4efKrDMnxFgNjMh9+9m+IqfZzcgmT6eJE=,iv:JNx8Uyw4qGA9y77wCyBuOwXj4kY+Or3mTiYflQORQHc=,tag:doZWw9vFc9WmAeYCwnXI7w==,type:str] -S3_SECRET_KEY=ENC[AES256_GCM,data:Iu0wo4qgPUdk/zpk4S0yHXJCqVH9C9S0Vw1TPbVo8z9shhl0/GmSdgKeDUIrpXuM2PGyqgPIsBf46A6ORnwGVA==,iv:ZZWajwWE0PLjEN4hPpx+Im1cisfZlUkIg4PBm3HQ5b0=,tag:8eK9TCD+q2V6j7sRFkSWOA==,type:str] -#ENC[AES256_GCM,data:wAJC60sWyg==,iv:RjxyGe9nMuoemVnBj8jH7/zXwtm7dT6LTVdsA6DvQ5w=,tag:6RaUkl8fb2nlBq1AbhGOJQ==,type:comment] -BACKUP_S3_PREFIX=ENC[AES256_GCM,data:HXINLjypKwQN,iv:0MX7Qebmyha8ZRuN/vt/3mIvApsXbtpmsDhYl/i0je8=,tag:VTXo8vAJV3ESu810/bjeDQ==,type:str] -CF_ACCOUNT_ID=ENC[AES256_GCM,data:NTsh9OYOuOpdobd0yl5u6kG8Dpr0FW6nPh3VZkRGocM=,iv:HYcfttiil7d6bnC+Lzf5aw6IyV8XoEDH/YiL9rypJT4=,tag:rwWbpxDsdN+NzVSnLsTp3g==,type:str] -CF_API_TOKEN=ENC[AES256_GCM,data:utXG/zszpYGYy9BBTl+toYzMncKdI6USIFCYQntaUQqbvdQ9NTqZIyXRmSQHG4l9r4QMPP0=,iv:vjsT4dZlICdI+wwzMOtIC/i5pOQIwgV1Mhnr1KHmMSc=,tag:RV9uy6EpzmAVxkoQktT3oQ==,type:str] -KF_AUTH_URL=ENC[AES256_GCM,data:nmJJWcAYd5rYkWB/NFJNeblQ/qoM,iv:q7HaKrlDKIfm8T4NsjBXlkr4qNbwGLaMNWk+C4fm8TY=,tag:U2yzNSb35kkZViX11P8BzA==,type:str] -KF_AUTH_INTERNAL_URL=ENC[AES256_GCM,data:Kv0ZgmnWS9l7CwkXgxV9fjVIsJnue4MfHqFz4acxL2o=,iv:HXKcVpIq8/GznRznSz7CzVoTet3tjgaw3SyqMPDDUf4=,tag:dCWZnd6g6y/3livKpUfjmQ==,type:str] -KF_ACCOUNT_URL=ENC[AES256_GCM,data:Un3dm3GYts2mKBC8V1dBdTG5R7h6,iv:QuGXX1JSV+egkGpsWdDUhVBvScUSn6ra/PW4h9Tqeq4=,tag:4ax4V1aKfA9HZW1sPSr2uw==,type:str] -KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:t8yRbfOcoNq79zE=,iv:O7qE3QStFljwyEiJoGnWAukglolacPTuHN5nrftZ9KA=,tag:Js6yNFM7XPvfz8tW1avZZw==,type:str] -KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:rKG4MU2+iYvutBwjZEXvekzrphn/G5rS7w7AYyjmfvolpeN2n2A/0/qsRzObfDmUK9oHSny9hMh87quULJEMEA==,iv:G98aGrbpCOcKevLemnKoUDfCznWEyk/OIJObVdljMS4=,tag:uknMJTV24+ann9+n2zzklg==,type:str] -APP_URL=ENC[AES256_GCM,data:P2FPGGTVQorCezo3vpd1RQf17mLy,iv:p47nF3xiNcciBQE7ixVYPkFLDv1HvoqPMWmVg/OhAz8=,tag:6Np2Pp1VDHauxP2g52hYXg==,type:str] -KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:5W76Sb3vbv3G/GY49V8vhpZeOn9nQ4Ezgg2EfTajGVA=,iv:suuMiX8lKXKhHp+JAmT7VkESeFDgJZK7+ATw3jTsRvU=,tag:Fz1xxA3CAmgNb40fVEkllQ==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpc1J2cHdHL1ZuWHFwZGZs\nUUR0OHhHUGlKWk5PRlE3azhGZ0EwSHcwbWdJCktpVHBLSXVxUW01dmd2ZWFZQ092\nQ0VjeGZ0b1hWaG51VmhsMGNMSkZCMnMKLS0tIFUxd3FLS1NUWWFLWkdNa0RhS2Zt\nUWZGYi93N3ExWTg5elVxWnhQSXdxdWsKpvGWBahHOOTicknPKDOqgkzF0VSuYtwA\nw3SMwZzwQ00gnRLw7LrY/EDAM+KYk/C1egMEAtAPPDfyX5xAGSDIIw==\n-----END AGE ENCRYPTED FILE-----\n +#ENC[AES256_GCM,data:2QdtLPlWcNjj,iv:LHuRl3g/4msGUBXWbBT7AbussDIydRTpxfBdFB8P0Mo=,tag:SeM78a/cDUK5vW12sGNnKQ==,type:comment] +NODE_ENV=ENC[AES256_GCM,data:+bI6CxTQ9pgPW+c=,iv:1mmud40amvgZ329F87BYtmUqqcMboCBs70k5qzn3wC0=,tag:u9nUrK+wZalk1z9UW+oA2g==,type:str] +PORT=ENC[AES256_GCM,data:dByevg==,iv:ykv09nU0UrzohvwRJy4RbA39ybZv6iw3gB0Qj/TOQ28=,tag:+NZo1z8DPha8Vew48LYqEQ==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:FNZEPzBVpfR6V7IPNAk+BJNzuzAZOmTiUCBvZfXKDxDvV8kxYkzvgfvELymVou308nj6NgA=,iv:Aq2OrPxw7jKcie+SA0Lp9IP0joxs6d4p/LIYfbC9T9w=,tag:VglDCUA3pEWI69tEfs2Fpg==,type:str] +SESSION_SECRET=ENC[AES256_GCM,data:3/O078CnO/vc/tJq2dPDejVvBIc=,iv:GdvlkSEODTQKSwj978cLTrsPxfAPBuoPxxYTnAhAv5Q=,tag:sCKymXSEbhBlj75WyX04qQ==,type:str] +#ENC[AES256_GCM,data:er2SEZApd+jg3IcZy/nDti1+YA==,iv:0QM88QHpuHY5AUdhhRjXiEB3WAWb/6J7JSjJ5Myb2ck=,tag:TuI3PAAVJssY+9BAh8ND/Q==,type:comment] +S3_BUCKET=ENC[AES256_GCM,data:fai2KOzrpBKlnh7YLU3p,iv:S3u1ypr8GQzFQrg6FCi8clJgEDAvZOwr+GgzeeKgCfI=,tag:/i9cW5MggQsM5MH6vHPSyA==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:D6Tvp21kchSdYCAiXwF4ZUQHnLHxL8DaiTeZRTxy8mkTbAMf6MzLZ5qRoEkbpp1J1X+BAvRkq8WQ+4yflVApjbo=,iv:IydUs2DeD7sm/vmxgwexn7nZK28D65O4yFTaJCJokzk=,tag:fwBGX32C3ayHxXtwHd8CqQ==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:rNaCds9WwNIfUxSmkM723Xnl5PMmzKxIZnV+LXE/IWY=,iv:HzEl5h+K806CUQ8mA5MpWe64S8jEcdeUqdohl5EfokE=,tag:Mj/t6SbJplwyV196tRjzGg==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:jfzHzxazhfXx3+HlMS3jcf2mgjCZv8fKv+5PbOg/pw157DuAb1mxandT6IdG16hAKHk+DTSIdk9MPzS5zZvFwg==,iv:sx9RimeHmFmLxbPKE5zveCv7dwKioOAjH9sO+GmttY0=,tag:g6t6hxsbl3QKvNerFP9aDg==,type:str] +#ENC[AES256_GCM,data:T4xd4cdfew==,iv:Et1FTnnN/GIZQTkKDgVzDpkRJo7L+Yh07D3Flt3JWAI=,tag:Y6rJ/RM+Ug2wsUN/+YbUqw==,type:comment] +BACKUP_S3_PREFIX=ENC[AES256_GCM,data:17dSkZiDUy1D,iv:UuqwiF1EVJec0hOxOIbtBTI7D+cyHAEKyE52modMPnQ=,tag:0bWzei6yqmh8WEZTsscJBQ==,type:str] +CF_ACCOUNT_ID=ENC[AES256_GCM,data:CvUrNqqJ4NmLwfP4IHHwTd37PCKT8ur5xf71crVESRw=,iv:RYtOJg4H5543AJ9lXIrh9AsxPmhhJacq546fiKxt0b4=,tag:TdMBeXQ7Rj57W2fjEpnYbg==,type:str] +CF_API_TOKEN=ENC[AES256_GCM,data:QOgWy1RVi5ZOUh9SCjlG7EPon/MexH8DCJzxQCqEZhdBV5s1oy/jgrVsmJPCOVhWLdbI0wo=,iv:MxkA8owuEvLFB+aSD4eHNfPO9JmRZlaWpdqu0hKDYxc=,tag:1I2Gt5sM8Dw1RryMsgr6dA==,type:str] +OIDC_ISSUER_URL=ENC[AES256_GCM,data:TPkA11ZHy5JfrN26pJOXbdE/RYnI,iv:7XZ0b63+tR2P0ndtQHLlTJIXMKsTmaJ/pMWsDPBrOu4=,tag:1tdRV5EqCpOX78xbTe5vUQ==,type:str] +OIDC_ISSUER_INTERNAL_URL=ENC[AES256_GCM,data:WYYzrJoM7UVEoPXM3/Rs1NCtayttS4QGFQIGLmReUL4=,iv:bo2kRtbwHh0zHzDSz+CRDg92LkWvVbPBNXrbAVjdl4Y=,tag:IYnes7T67mLopxk9XmxWfA==,type:str] +OIDC_CLIENT_ID=ENC[AES256_GCM,data:ls9hqkJNRnH5e80=,iv:RP6JBIjgfptNKiVK1TwkeVrGO7GxVRPz9zPsb/tDT34=,tag:YcB23Fu1RP1qKa4AB4Xi/w==,type:str] +OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:bPOlaOLQ0+0f457+/Ox+JffKjWbS9jD6G9u0l6oIatNDfKfoSrWmzgeqnm6T31vi+Qa56NT23oZvWWE09kz8fw==,iv:e6idHjojxoxUtH4TJpz8LNLnohkJYdcTC9jO74LnJM4=,tag:Weh8Popy5zuprnsyX7sSyQ==,type:str] +AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:Sb/90FRvGTi9gi2XinGXN7/SUT4cTfsC3TSEdcvngvQ=,iv:Tj4UqEoZMP5hGDpSSHiRRk3yQnC3Urg0G50c2HYqQm8=,tag:kh6HgP9inHZ/bTK+yGeElQ==,type:str] +OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:XW7Tbnfs6emjBqWMMAo6qx6UM55E,iv:x7c5aodVQEfiXBox3Mvs/nu/Zw1dBKw0RdHSJKv0eLU=,tag:DjiJCUsh1OmVl6xGDcPNxA==,type:str] +APP_URL=ENC[AES256_GCM,data:SoZT6GGL+32wmVeTPLZfSaqKVEke,iv:9sLgDY6Hx0clqGKj5SKBSTQT3staTYF5mfLuN3F2hho=,tag:ajF4CFsQwyY3BVLwwee14g==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCRVcvNWVmR01XN3JGeVUr\nTkdnYy9BSVBoQlRPaU9tQzdabi80b29HS25JCm5sSUFrenJXcWk0a3M2YkhQcTBS\nV1JkMWUrYW1xUmMvdTdnbmd5Q1FsOGMKLS0tIG80N05FdmYvYVpXVjI2Zk11RDRC\nUXB6eVZYSk5XbWQ4RjA5L09pT01Hd0UK20NhEK8dBjYUHKocws20N+AmK2lGX2Nw\nRFJ26WcdRGY8TADAM/rHxZ/5rtDWCIly3GgA0JrWQBqG1WqKeNJmiw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPaTdrOFo0RGpOQkp3NCt4\nUEUzS3FZTjZWc3czbUhISHZyVFVrVm9CaEhZCi85WkR2NmxObFVneDdqZFpja1NO\nZldyYXRZSjRnUmhMRnlOamVRa2VCdGsKLS0tIHpTTGJDWUFJQThiR3pERUhXY2tM\nV3huTDVYRnlabkNuQmJqWnFwT2ZWMlkKlTbID7me8QBHvBjljXvj+kl5Hmo0Oprn\nSLT6MSYP0rvuD3uu4Qj+Z6IELbAzrt6ffGjMx+xohWse5R+u8UFMzA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArNEdzT1BBcHJZYkJJbXFo\nb05tcTJOcWJOV2t1U0NCL3FaK0J4TDY2RmljCjRzUFBWQjZkVmw1WlpyeHF4YlBK\ncmpYcHZ5WlgrNzlDc1RRdzA5NnhOaFEKLS0tIFl0ZTRsZldhMUYzblIrYytvZTRE\nMVVqWGRpNDRTN3gzSlVwSkd3L2x2T0kKDhGflRStRyTsIf9VIDv9UsVwXV0Xszop\nukbJXEJTKEiZt61TGtAjZZg/VyaJ3YlgBq99E6NiSU7Jy1ywxdnOOQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1ysddqggsx3h8zkv7xn3z26sjak5pqms6pyqhnky9ukrvpk7es5jsayz8w7 -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZMkhPQmF5RWJvWS9UL2hD\nWmlEK2QrckhIN255Y2FpcGdkenF0eEJZSXpJCkdESlB5SEZMVW1uNkdWeVQrUHVY\nQjNtZy8wZjVwcjZWTzFLam1LaWw3b1EKLS0tIDRyM1RiTERUN0hFMWVCQnAraktu\nWW1pQ2lwZmZBN0JlYThKRTdLRE5qTDgK3LZGWZwVlmmiQ4CghkA1VEJLWaOsRCE7\nD57rKhXq5/QKjoziyXsc//PxZnJaTiJQ2xxsG8uMLDL5sCMz9NDiTg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBITkRrYVdJQ3ZQa1dsRGE3\nTDVERm5xZDMyTVd1SnN6YUZyOGs1Y3d0dmhnCm0vZzNyWFNEclZKcUxSZjBrTkJC\nRkVxdkl4ajFDWXVKNnpobUsrMUhucXcKLS0tIEo3cmx4TDlrcUE1dVB6SThBdTd0\ncDJDd255UXVWdmk0WkVrSlV3Qm9JRVkKbbZtpztj79uo6Mlt46SqOMxfsVB3GJCm\n/r2ppqms32PQxzsiNxK/t+Pu0PsUlUrlMrOA8rrlancNIIuUhj8zPQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYV0ZhbVVxZVcxa2ZrZVRU\nVnorTWZvc1JMc2ZhM01mbFBRUTNLeTAyUTA4CjVubkNNZ0xoUjdhVXloNDZ4clpO\neUlGVlB4QTB4TEhGdVNPaXV1UW1YUTAKLS0tIFJrOUJSSlc4RmpBWW5QK1pRdXhK\nS05OWGh6a0RrekxteTRGSnhZd0VMRUEKcYsvFySf+hAGk3zsHN1MOK2/AayQQsQI\ntWerQqnKHoKHt5Fd2WZRUMrtzyV5stTZ0WwUhMIvFnapz+sQNDSYSA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBudHNvWlp2bGNFSGlIcjlN\nVitBSmVNWU5NQTZrS05XVytHbXZKV2FIeDNNClhpRSsxdWxVYzR0bFNrb09KN3FY\nOEZNSVp2TzRDOGdYL1BNS0Zoa1VtWnMKLS0tIHkvbzhvV2hZcmZLOGtaSmZhTmls\naVVoOSs4U0xFM3dlM2ZZZmlZQlBva0EKWUNmOgjhxTzzY29Ji0+TufoeyL1RjPeV\nx2MnJyp9PhksO5gra1WaxCp6cx+Cm1tm9Jn5EBEX3kHTLxPCGBjYYg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1qn0x93jhqjpqwvx5tgxnrwq5e3vuzur9whrkdnrvapd58esm45rqfkuxqh -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnY0RGNkNoczNrTHdYbmtN\nZTNtY1RQNHVHMmNhOEJCNzZ6ZVVxUUJLeUd3Ckovdm1vdWttR2NMeWpZZnhnN0xD\ncS8yZEJjUkhQWG1ReWdTNDJOU3cvMUUKLS0tIEpRSzBOZmxRVERIQ1pWZDFiV2l5\neGxGQ1ZxVmZTUjB5OUNadENnTWNUWVUK45kOkImJ/sdznjiCBSV1BLa6Z3ZFZh/H\n4WhN0fUKLPoBIY2MtcETa0XFuIdx54p+CJuMlok6KUuMh3h8ORxsVQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVdGxibXc3dTUrejdMTWVI\nZy9kUk9qK3cyOHBMckZMY3MvWU12VjRxdFEwCm05Q0c3dXNlVDhaMmc1TldXR3JB\nUURWL1BhSU5pRUNaSVdCVUhybWdiRFEKLS0tIFJxeVhtSjFNMVV5YXZ3a1hKaSt2\neE41dEowNzF5NjlEZG1PcWxvQ1dlZ1kKse42a4FgQoyaiOTgBbal3DvWQ6jP2IU1\nFncyLr6BfRbSLspqaUIiKYpJqNjSzQd0AHEXDntBKk+l+g1pMYSnXQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7 -sops_lastmodified=2026-05-18T03:03:32Z -sops_mac=ENC[AES256_GCM,data:QYYsO6U+2eK6GX90Y8+uy1LtPlJdiWTkj9QPZ1jgEUlZJOc79xPBai7i7TfwSbfG9Pv0aPVkcWjoIIBQkGC3Jdt1wIGMLCmlIeHU+MNH1ZcdOlmn9PI72GbMhWGF534kJFfgSxWJDrH2ow9NqOebcDESsZ/S35ajCUeyqAD5Yas=,iv:ZVbQlqQF5IU7YkfexRNQPDbXHnPlZ6z6soBpvzUVpIc=,tag:vINZd9Dznsyf0zxXdbOU5g==,type:str] +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtWGpMZk9RdHpTclRiQmFZ\nN3g2VkJwS3RaeEt5amFtaHFhd2IxUFU3OUhvCkcreUpRSkt4SldNb0NNRWlENnJI\nclUzYlFzMWJ5cXpsRHJBK0F4QVphWHMKLS0tIFhQWkpwNUlLMkRzSWFOcEM2eVo3\nd0hDRmg1M1k3bGwvdnA1S1lFYm5kemMKZel5NBTEg4ARxgUyPKuDSJRLlYFyjWr1\nwJ0H8SE4oZrsWuVuROSBf2YYoQJoAHlSzTOQZhD4jVwQmDa46GB2+Q==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h +sops_lastmodified=2026-05-20T15:29:49Z +sops_mac=ENC[AES256_GCM,data:GWPR2arEpI8cdOyNlfUOOqPVOCwK1TJhg6uPyYDCzCo/ex9Zsq1pUBFse5qsa7vZDlD3FrOiejoRh2VsTZSPIufye8PJkuRY6vb56YduJqLdFmOBGzW595oHRbEAWfdpEVJMnhgcgGh08yTTA8fai9F8NLHPhX5Y4Us8iSNhAlk=,iv:atz9oslK57IOn8aJTP+71JaAa5N93a+mOszMmJmeSYw=,tag:1ddyZdW5gTW61lNqUT2DhQ==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/.env.prod.enc b/.env.prod.enc index c1f0b6f..c37b7b8 100644 --- a/.env.prod.enc +++ b/.env.prod.enc @@ -1,29 +1,38 @@ -#ENC[AES256_GCM,data:G79O2jtY5wOd,iv:PPCHTkPLT222tWp72Yf8nOpUSQ+Xtl9WElZrALB80QE=,tag:eINk1hO6VgXTgY3xulstzA==,type:comment] -DATABASE_URL=ENC[AES256_GCM,data:8a45ENrpPPPmc078uZM+szGNSus2H1nn78yxEnCsfI1MAmTVdoK5mnRfSOdnVz7s9GQEkCQ=,iv:P3k/harr4BNmXks9qOZI0G/M9al33P5r8ze8wgjhizU=,tag:u4xB+ykAJ5Sa+fAzidhaEw==,type:str] -SESSION_SECRET=ENC[AES256_GCM,data:5HFYIHfpPDZMhutvmw/B5Ja+TOlw3Vm4YpP5GqUTrRU=,iv:2cIpp0mjV70bwGSGgaZTujp2/X4FxJw8akntGT3XHGk=,tag:sRm2qujcLBKt4hFXh9vlsA==,type:str] -PORT=ENC[AES256_GCM,data:cmAkiQ==,iv:IuiEZmYBgluA/eON4EbUZfqZQxeBboZ1ucaGBB2KTB4=,tag:R65RYJlxA9dZxIE9I15/Nw==,type:str] -#ENC[AES256_GCM,data:h8th,iv:3UWBQr6vElyiPMFRCHFluuNdjsWvelZK8z+oHI3zVA0=,tag:8bPbDmEITWt+qvAKRoLEuA==,type:comment] -S3_BUCKET=ENC[AES256_GCM,data:gJwRRmJZC1i3hyjKJLFK,iv:Kzm8l5jNoaX5c+OtBaU+fgSLTrI85Lu9IHcTWjqZrnQ=,tag:cxF/kmlqTx+SmyEwZ2/4sQ==,type:str] -S3_ENDPOINT=ENC[AES256_GCM,data:+qV7lAVsT5SAnxU9VLixecjcTSLgWtN11ihQzSUcYyFtP/4f8BkmdDbCbndx5LGJ5+VXZqgIY9rX9l0UiCrRhd4=,iv:EJERkOiZBN7QBDOhggdnZqS4d/mqBV0S7nMnkuBConE=,tag:NIovhkVEVmkfA1lcJpWmng==,type:str] -S3_ACCESS_KEY=ENC[AES256_GCM,data:a6ULL1I8WkBLElwYYvSKfUeqghe2lbkHRBkI24Tn3vY=,iv:EkG7Jx930v+p309GFHTdPyi/AOYsrZPAWJbxpqAD+10=,tag:jXcLsfS7nMPI8sqSHNx97Q==,type:str] -S3_SECRET_KEY=ENC[AES256_GCM,data:hzl6E8NCCumH4C7VdBbVvYIM6hDGllBd6VO3Fi30fCieuTALHfrEFbHfxS9YgvfynD2hIK+GS8CgBXGtO2OBdQ==,iv:PAqnAY2Me5UgmrLBZsZuD6N20QJZgL/bWszzCnFMA30=,tag:CFOpvlovhud3IjD5PWFY5g==,type:str] -CF_ACCOUNT_ID=ENC[AES256_GCM,data:pNRHODXwuEgxTujxDKqD4AFSlnlN5QxoksT12zf5a/8=,iv:2/HQxHDrh7DiwjHgCZCs2EKXvMtPSakfPpQGAiTFDWU=,tag:QgXB+Cwm4hJ9jyzZb7DEbw==,type:str] -CF_API_TOKEN=ENC[AES256_GCM,data:QeXoWYFuRfCK4iBvszGq45sL5LnEsRu0A2o5K0TV1OjZCYQzm3KzBrKy6lVPi0ocQZZW6YY=,iv:LeFbb6z0hrPXOF4ThTYNQ+HOYywUf0q1cyDt85d/Q38=,tag:Pvd5wQuSDOyWnuBHUlRh7g==,type:str] -ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:+1GMiZc=,iv:tFS+h5ZmvYRjn/1fbn9L9TzvIJXCgrK0L0aoMw1t3ho=,tag:FS4qiSsWZUzgZrPYXaArqw==,type:str] -#ENC[AES256_GCM,data:JMY6pl3vv+/WUg+XKcZaiCRK,iv:LYHlOqnGZJWtMFHwRUGw/2vY6dBetyyNfxIAJdgdhvw=,tag:/3mR2XZJJFfJ7AT86Gm0yA==,type:comment] -UNDERLAY_UPSTREAM_API_KEY=ENC[AES256_GCM,data:mOJ8BO5XC7lckg3a8lZIsalt+QaXufTWI+r3BpIpNjopXU8=,iv:Iv99fo5Lf5A6CoQRse4oMcH1iBhP+BQFEVsQfXr0nwQ=,tag:Mqks9E5KMOj0YDLvbdalSQ==,type:str] -DEPLOY_HOST=ENC[AES256_GCM,data:/sJzsacP03kwQBtQQA==,iv:OA/emiwAyDLtdsxutC8ydE1WeAHaI8u/1vbmugRtOYU=,tag:R+P9FwLluo3c5GHMJooufg==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzLzVaZlpNeStza0p2a3V0\nZFFXOHh2ZnNCMFU4bW1ycXZPY0N5UWxreVFvCjRSRjhGLzNTODBXOXpwWTd5T2FI\nLzhvdGRWS211WDh3KytBa3RmM1Z6SEUKLS0tIDVlWEczOFFjM1VmTnllODl2Vitj\nWnoxYzd3U3lvVFJjelhrcEp5ZFRyWmMKbaggcwEccx1k3UYOE+a+bLNPlBL4vIsx\nhRav2a5TUBc+Wp55cbfliiTQCm+lXcv6kci+YeKHQf6Q67VDeIFRVg==\n-----END AGE ENCRYPTED FILE-----\n +#ENC[AES256_GCM,data:bYUmXCw/HzHE,iv:/8kUK9BrKewmpcJOUS+qC3Xc+pp6UGnMVdrX4P3cAUw=,tag:+1a45AVCG0r8kdKGxuwlUA==,type:comment] +DATABASE_URL=ENC[AES256_GCM,data:+75be17RMsZ4SjumHUsU9YEw+CyOlnZV6xoSq7bhZ0Nyt5JoUgBsIxjJp/BvdvzOSeIlYjU=,iv:q8MFpo7ypKmdow1iJWsVNzWoxLTf9th3Rx+3itaDYjY=,tag:tOHxzZMKmtzwEdz5AzK1Ww==,type:str] +SESSION_SECRET=ENC[AES256_GCM,data:vdICH7EmhkJoptYHzXc+MYrl86QN5yMj+qonEoMIhoE=,iv:+/ikEWyPhrtdiQgoSBCmbj5uvbZwcBAiABbJsMDjuHI=,tag:2XZPNcM8BslYoa88EiI53w==,type:str] +PORT=ENC[AES256_GCM,data:xbViuQ==,iv:5BuD782DkszqAql3bLN32byuqTGYS+auHkxmS4/pqIQ=,tag:RPR/2j09ithv1JQGh28OqQ==,type:str] +#ENC[AES256_GCM,data:w5px,iv:9xkFSgqgTVmgwCDTp8uDvbqvdQ+DfAvqYPBxy4CNO1g=,tag:ZuCmPtKc1V9WuOJ/7+cYSQ==,type:comment] +S3_BUCKET=ENC[AES256_GCM,data:QkEdZ6i1SEjiTZY7rA9B,iv:uK+IUDCgv1CqQV30eJ3ijzlILtxiITL7JeWq/CrCD/I=,tag:shy6hBqAgCNVyglSKzAVew==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:WxSSQvbQFDfQiRDcsmu28vYPIqIg5jdskKm18iVC7P1dSGMpXJvREIn5NCHfses+EGg4YXizntQkHnTwk0K8Sb4=,iv:1RjCM1T1z93oplpqzg9njgf/HMMMVV/HUbVAd5RUBDk=,tag:vx2Ht/zMG87f0I4i+TGP4g==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:IojJ8K8lSrg3RpZ4pm70w6E+eLnO8nHUxN/hiSdtV3w=,iv:ciwPn2ibe2zx9/pB7a5Ml5scv3kgGaMEoXCq9y2Ru2k=,tag:DqOwfGQz/8o/i/EaVobfNA==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:y38yZ7zYqXJpMlNGHJbl+esScT7WchqzRi1UTbzDRy6mJUxVPjkar2oOFVwq60GLa0RwjcI452pyocuI02CLQQ==,iv:/rVWxOgIyH08UFV/Ix24CwwjxNsMT+Ya8BvS9YmJBjE=,tag:i4JsaBuGP6/qanqgZiyeQA==,type:str] +CF_ACCOUNT_ID=ENC[AES256_GCM,data:A7mBYmmtU5qbCF9NcBuU6EID2jN0WBiJW+FHobwTsQA=,iv:FQlBf5EOwydioswIv02TCa+ao8gWAib5Gd0AEVU4i0k=,tag:f/o3KvL/6mnX9WyivMG3cw==,type:str] +CF_API_TOKEN=ENC[AES256_GCM,data:x77SghBmaOhYzmbYNlInYucVm2WMWX9ox3jkuWXcMXGu0LnmoKwdSgVReSQtLDTMymOOjHk=,iv:IFIezYUpauUfBf7agySgwoCYI0FCPoL5oZI3eWuXMMk=,tag:QfWM7lEl2pg2Qg9WVaci7g==,type:str] +ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:5MrYqlw=,iv:o9Z7+WsSLM685pOSR5Zazi8vSCpxlGqmhc2o4rqZMuc=,tag:NRA7qCtr00eNFdtiyH0w1w==,type:str] +#ENC[AES256_GCM,data:AVYnHX9LodTgwxgl0e8UPugt,iv:bQ3FZ9cjKOcKWAEJCOSw4Gq/xu76tDy4ZwvDdu9DgeE=,tag:DJbvWH3oVFCRHRVuRxgkfQ==,type:comment] +UNDERLAY_UPSTREAM_API_KEY=ENC[AES256_GCM,data:S9/6DM5edcT6W+6c+IHSbYkBcUBkRo3xnZI8vpWmFzBq8TM=,iv:s7iSamQUHboaai9pSWnJr/nhG9NDokrqcIo8TIfSHLk=,tag:W3CKICzHSokcrSjuda22VA==,type:str] +DEPLOY_HOST=ENC[AES256_GCM,data:GHA9qmtsJD9h3Ydoyg==,iv:i0FBJ5EU4MhYvm+/UsYh44ezhcYMvXZQZ3z/F7AVYcE=,tag:SQ5fXpZx0fbOCrviSCOBPw==,type:str] +#ENC[AES256_GCM,data:zWgopAUsB+k=,iv:UX04P91rzKP5n/k/fbREKJabXyZqdqZoEv1LBASIkBo=,tag:WnqBget1roiEuikszltSSA==,type:comment] +KF_AUTH_URL=ENC[AES256_GCM,data:I5P5O2MvluuLgChKNE6I43cb3T+y,iv:X3BH8cM1A9jNaEA9JRqV2RvhY4WThqXwQQUOggaFcXg=,tag:0zMJ9Tes2FvWwoHvrfAgHA==,type:str] +KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:WKCYLS1wvFTVRtA=,iv:BbFgu6wzg3aZnXAzZ36LT9RazFkkISZNlCHlR23LUvc=,tag:rmYU7NcZHFpKuzMbE7iMGw==,type:str] +KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:FxA3KnZtF61u92r/BRPHiv8MLrgYlQJtH8j63EwIlh0=,iv:QvVblYBIx0tE9qX5aPy9YIUxDFbmifAoafOd4q+Do9o=,tag:1xB8ZmdoNw1HQ+U0j2bvqQ==,type:str] +KF_ACCOUNT_URL=ENC[AES256_GCM,data:tSq0C0vdu4zBBRDUH4o4roTW4e43,iv:cvmtIkcBSQFL0PsLy/9TLhTMqDbr2ky4mBsROwU+CiE=,tag:bXHyZr7x+ximzLafE+aM4Q==,type:str] +KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:AxuXmvn0uv+hnO4ujg6J7TzAKsK3eOePeDgsq8DoILk7v+jbI99CK+GYuzUnw/nbygk4YapKXlytAvCehi99Yw==,iv:1wG+0oa2nKzWT0RIcMnmVHEadGGAGrZ42SYazNHbpRg=,tag:cvmoYHPTcyafmNvo/MiVYg==,type:str] +APP_URL=ENC[AES256_GCM,data:afSaFu9HdwaEfzKVhDyBHMAMNNez,iv:GhTwIYCJQqUsvydi6g/YyWyTppLCRkt1WTP5/M8/cds=,tag:WbExHPUY3UFJ8jddLmh7bg==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmdWwycDVoMDMvY29qU1J1\nSFR1a3lMMXE3TVo4ZmRyZFhyWExjclhPcnlzCnBUdVJDRlZLTnVmRmw2NU5zVG0z\nNkN1cUFaNXRiNk5oeDMzWE9FWEVZZjgKLS0tIGlncUZGSGtvd0tab1N4MlFJNC9i\nYVR6OUFKS282d3psTmIrY3hYUnViRm8KMC+xkTONMmUwNyi9M0XDsOVRyLzA+82H\nEKpjfEkbS8S3yDVithJ6wrixKiovuumGH1cGSJcOnm9pqYGhVi2LrQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXMjgzNlZhOGRkOGgxTGRn\nZEhnNXYwakE2ekFGc2c4eXJ0VUZLT2p0QVVFCnQreWx6RjljK1F5L0hjeXFmZ3lX\nbGlBNHhxN2h0MUM0ZWo1eFBpQ0xNSWcKLS0tIDRsY3VkYnBETlljbE5yM3VjdXA0\neGt2R0FTam52VTVRdnlMOFNxZ2VjdHMK+L7MquhICF+TVqX4Cd7/6z8tIouB4NQR\nYs9eHJukpgK0e/8pA4LtZG5PRkyEbaWIIXH9XWvQ18+nXa+kOYiG7Q==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3S0c2WjRKNVVPbUZsbWEx\nbUZHQklSbzd1MCtYckUwUkxJSXJrSUZiclRBCk1lWTJUK0hRak0rME1YU1NkMitt\ndW9nSUdWcmI4NkFDY3NNMk0yRHgyOFkKLS0tIGx2TzhFRUdpbDVlQ0hUNWVuQnJF\nZSs0T3JiR3orNHJaTmRqRDRUVFNJQUUKLZjNT4VZOr6CM2mt2XzewT9trgRgflRt\nWyCI+EKQXKx8zhEsrdNMKdOvpVI6rmtYDDjupLfj0c9EpGS03w7RVQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1ysddqggsx3h8zkv7xn3z26sjak5pqms6pyqhnky9ukrvpk7es5jsayz8w7 -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmNVkvd3c1NEpLYzdNQnlo\nZWI1TlpEbDRjWVJaL1dMSk5VK2I1SzhxU1JnCjE0MW1BS3lOdEZOa09WSmxJdkNS\nK3ZLY3lWa0g5ZGdwQjlXSmlsS21lRDgKLS0tIFVUYWZPVVczNU1yVnJqNGNFMy9L\nUSt0QkxUM2V2QTRXdkNRV0JsZUF5ZXcKh37S7FzQ/FesSscsliDL6F5OJhLVmZpd\niuyotrYRbxLJe07FCF2qKCEAZgJ+LQSHOGXFjXyilZhaCD7jjlxuGQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlSGd5WHgyNWp3ZFdaYldz\nbFBhRDFxNFprbTJxNldYbU1GcGFGUmFodEVVCkFRa2xGL0Yvc2hWZTBmTmFJK0xr\nQ1V6RDNzT2xBTjVPL2kzNDVTWXhOMTgKLS0tIEZEOE14cnhtWnRvcllwU1JvUFdX\nNTUrVmNPVXBYM2RzK0c4RUxMdDJ3MDAKEB4/7RH7yDE9wIW1CO7wkjtTqWJ28wNe\nRFYq4UAg/C/sh7R40wPM1MlmHeOLmHud8bD+awNSMriq07WOunBARQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLdUZWT1g5N0pIeU85NFl6\nWllydjNQM05BbkJMbmdGOXU4YnZ2ZnVIdUZNCnlUclhJa0RPNTJvbGhEaWJlaUxh\ndTJDMFFoYmFpK0NyTkc2ZDNBTk5jSHMKLS0tIGkwZlpnUy9HVnZvUDR6WE1TbkN4\nRWx4NnNVbE55bkpxbFJUWVZTVnBvUGsKaBWF4o4HEa+AYrhab0PlS2jbMPLy5FcI\niULVo+OXB/rF5309D3kCSAECHGhAaWTKKDQaX8L9r8PRPxiKHnbukA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoZnAyRDMxVFFiM3hnR1Fq\nemo3TXhnTU5ITTBpRllQMk5NajlHTUNSbEh3CkF2V1oxVWV2UTIxc0pmaWZXU3B6\ncGwrQ2VTWUVWRmpuMXU5bEtGWDlWdlUKLS0tIFhmUHdHZTBVbnpRR0w5SEVtbE8w\nbXRxTURGRWo5QnBwM0FJTmRmZERGNHcKbbyDOyQWjOwgF37GjGIiqg+4ZkO5KK5h\neF7y7ndM271RGY90xkDPFuPYHjB+L98zXrpDDczBzaNkH8cRsKDI+Q==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1qn0x93jhqjpqwvx5tgxnrwq5e3vuzur9whrkdnrvapd58esm45rqfkuxqh -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBudkNDQTV2Ukh4T1RGMHZM\neDVUdU5FWmVhUUlQc0VTa1BuS1cxY0pqS2s0CnB0S1YrUFlpRE91akxXd0k4Sksy\nbWl0NWtaeDBWTFFNMUw1dFNUQkwrSUUKLS0tIE5NYXJBZ29qOG1QdzVMTDVXUXFq\nVXdqSlpsQjBlUjRiaHJ6NEEvTjdDVGcKrL/jghlJmI/2gXli7FBkSkHn/48CC10Q\nJd20NjPz12l0u6jRodhqJbUdO45JVoa+Jn11kGegA1+2RC/CbbLeZA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0VGpZNWE0c0tIc1dFMlNE\nay9EZWpIaVlncklNZHFKSVgzbFBMQ01tWmpBCkt2SXUzbXNjWlhobGdPODBnSGQv\nNUFQY05VWTBVbnZkREdmdTEvOXBaWFUKLS0tIDJ1WHh6RjdLanZyRWdKZXdmZ1BO\nSC9OVHVuK2JrZmVtZHJITUxzaGdSSzAK9A8/n2Ajmmt4H4tIodWh4mW2H3L/UCGG\nsCOmqGzmipWu9iYOOI1YUuBCTcz4NwnOVg1xAnHpXiXlNRXzgwlcaQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7 -sops_lastmodified=2026-05-11T15:15:44Z -sops_mac=ENC[AES256_GCM,data:gdyOZK39HYrvgEIYaPpQpdaXdaSUUPzbJoMD+GD1jUYk3Tnd6cCQzCczuV5FYaX2h4iz3L2SQkQHi0ltlmdPSUQjhdXEQgVKjJ05Z2TrjtHPnishIZJT14CxBZ/ZyvbIvZe7XZDKX13tLs89ozR9v11oswfxQx5HyrI/qekUJMQ=,iv:AaDc3U9qDEPKr0xUhUfIVCwOBxqjr7xSnUNtwv4UyVw=,tag:bjOsQwuVBS8il62Cpq0QPw==,type:str] +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAveTRpUmF0OHhiK2g3RS8v\nS3pFTzk4UlVwUXJHNkRoWUlnZFVVYmt0WVRRCm1mMmhjcnZRMkhDSEg3VVJqdGNx\nckhDMkZWTnErUU53Nmo2cDRsUjdxMjgKLS0tIDMwL3dUejhLbXpPTStxTmEzNDcr\nU25UYjJLUXRYdUcwRGp4ZEVML2svc3MKEsv7qIrbE8zOX33u3yLHptRpWi52+quR\nfNr+uAvISSwM10iR/8hyTv2IBgsdnKPiuy65DRZjctiEIr8k4S9Y+Q==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h +sops_lastmodified=2026-05-20T13:47:02Z +sops_mac=ENC[AES256_GCM,data:mQcnXY1ZnC6jXvKu7qWx2iBgMeqKQF8wEzXhPLGBp/qXZh6x0wGAUURz9xNKXjM2X3Z3DHuzJNG0PssBUlJWbzTGZjqFfjJviKTI0fTO0umTQylml7CV3WhNJiAAo8HRgv1hjHOxU2YQ+JxOE7LW6z7C3snTnCWDN31O1TCaJps=,iv:DTmahNH+esURR5Fajxb9VOxnHFV1AbS/JsSvJukrlOU=,tag:1N7khs/V/efPYDHu4k0SfA==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/.sops.yaml b/.sops.yaml index 1fb0434..47614a9 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -9,4 +9,5 @@ creation_rules: age1ysddqggsx3h8zkv7xn3z26sjak5pqms6pyqhnky9ukrvpk7es5jsayz8w7, age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy, age1qn0x93jhqjpqwvx5tgxnrwq5e3vuzur9whrkdnrvapd58esm45rqfkuxqh, - age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7 + age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7, + age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h diff --git a/README.md b/README.md index d1344e6..faad0ff 100644 --- a/README.md +++ b/README.md @@ -154,10 +154,23 @@ Required GitHub secrets: `SSH_PRIVATE_KEY`, `SSH_USER`, `GHCR_USER`, `GHCR_TOKEN ### Docker Compose Files -| File | Purpose | -| -------------------------- | ---------------------------------------------- | -| `docker-compose.yml` | Deployed stacks (prod & dev via Swarm) | -| `docker-compose.local.yml` | Local development (source-mounted, hot reload) | +| File | Purpose | +| ------------------------------ | ---------------------------------------------- | +| `docker-compose.yml` | Deployed stacks (prod & dev via Swarm) | +| `docker-compose.local.yml` | Local development (source-mounted, hot reload) | +| `docker-compose.withauth.yml` | Self-hosted: app + bundled KF Auth stack | + +### Self-Hosting + +Run the Underlay with a bundled auth server (no external auth provider needed): + +```bash +DOMAIN=https://my-instance.com docker compose -f docker-compose.withauth.yml up +``` + +This starts Postgres, KF Auth (auth + account), the Underlay app, and Caddy with TLS. On first boot, secrets are auto-generated. Set `SMTP_*` vars for email delivery. + +Supporting files live in `selfhost/` (Caddyfile, Postgres init script). ## Environment Variables diff --git a/docker-compose.withauth.yml b/docker-compose.withauth.yml new file mode 100644 index 0000000..6a848c5 --- /dev/null +++ b/docker-compose.withauth.yml @@ -0,0 +1,169 @@ +# docker-compose.withauth.yml — Self-hosted Underlay with bundled auth. +# +# Runs: Underlay app + KF Auth (auth + account) + Postgres + Caddy +# One command: docker compose -f docker-compose.withauth.yml up +# +# First run generates secrets automatically via the init container. +# Set DOMAIN=https://your-domain.com in your shell or .env file. + +name: underlay-withauth + +services: + # --- Init container: generates secrets + config --- + withauth-init: + image: alpine:3.20 + entrypoint: /bin/sh + command: + - -c + - | + if [ -f /config/.env.withauth ]; then + echo "Config already exists, skipping init." + exit 0 + fi + apk add --no-cache openssl + AUTH_SECRET=$$(openssl rand -hex 32) + CLIENT_SECRET=$$(openssl rand -hex 32) + INTERNAL_KEY=$$(openssl rand -hex 32) + printf '%s\n' \ + "BETTER_AUTH_SECRET=$$AUTH_SECRET" \ + "BETTER_AUTH_URL=$${DOMAIN:-http://localhost}/auth" \ + "ACCOUNT_URL=$${DOMAIN:-http://localhost}/account" \ + "DATABASE_URL=postgres://kfauth:kfauth@postgres:5432/kfauth" \ + "SMTP_HOST=$${SMTP_HOST:-localhost}" \ + "SMTP_PORT=$${SMTP_PORT:-25}" \ + "SMTP_FROM=$${SMTP_FROM:-noreply@localhost}" \ + "SMTP_USER=$${SMTP_USER:-}" \ + "SMTP_PASS=$${SMTP_PASS:-}" \ + "KF_INTERNAL_API_KEY=$$INTERNAL_KEY" \ + "BASE_PATH=/auth" \ + "APPS_REGISTRY_FILE=/config/apps.withauth.yaml" \ + "GITHUB_CLIENT_ID=$${GITHUB_CLIENT_ID:-}" \ + "GITHUB_CLIENT_SECRET=$${GITHUB_CLIENT_SECRET:-}" \ + "GOOGLE_CLIENT_ID=$${GOOGLE_CLIENT_ID:-}" \ + "GOOGLE_CLIENT_SECRET=$${GOOGLE_CLIENT_SECRET:-}" \ + "ORCID_CLIENT_ID=$${ORCID_CLIENT_ID:-}" \ + "ORCID_CLIENT_SECRET=$${ORCID_CLIENT_SECRET:-}" \ + "AUTH_SERVICE_NAME=$${AUTH_SERVICE_NAME:-Underlay Auth}" \ + > /config/.env.withauth + printf '%s\n' \ + "- client_id: underlay" \ + " client_secret: $$CLIENT_SECRET" \ + " redirect_uris:" \ + " - $${DOMAIN:-http://localhost}/auth/callback" \ + " skip_consent: true" \ + " display_name: \"Underlay\"" \ + " allow_sign_up: true" \ + > /config/apps.withauth.yaml + printf '%s\n' \ + "OIDC_ISSUER_URL=$${DOMAIN:-http://localhost}/auth" \ + "OIDC_ISSUER_INTERNAL_URL=http://auth:3000" \ + "OIDC_CLIENT_ID=underlay" \ + "OIDC_CLIENT_SECRET=$$CLIENT_SECRET" \ + "AUTH_INTERNAL_API_KEY=$$INTERNAL_KEY" \ + "DATABASE_URL=postgres://kfauth:kfauth@postgres:5432/app" \ + > /config/.env.app + echo "Init complete." + volumes: + - withauth-config:/config + environment: + - DOMAIN + - SMTP_HOST + - SMTP_PORT + - SMTP_FROM + - SMTP_USER + - SMTP_PASS + - GITHUB_CLIENT_ID + - GITHUB_CLIENT_SECRET + - GOOGLE_CLIENT_ID + - GOOGLE_CLIENT_SECRET + - ORCID_CLIENT_ID + - ORCID_CLIENT_SECRET + - AUTH_SERVICE_NAME + + # --- Shared Postgres (two databases: kfauth + app) --- + postgres: + image: postgres:16-alpine + environment: + POSTGRES_USER: kfauth + POSTGRES_PASSWORD: kfauth + POSTGRES_DB: kfauth + volumes: + - pgdata:/var/lib/postgresql/data + - ./selfhost/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh:ro + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U kfauth'] + interval: 5s + timeout: 5s + retries: 10 + + # --- Auth server (kf-auth) --- + auth: + image: ghcr.io/knowledgefutures/kf-auth:latest + depends_on: + postgres: + condition: service_healthy + withauth-init: + condition: service_completed_successfully + environment: + NODE_ENV: production + PORT: 3000 + volumes: + - withauth-config:/config:ro + command: sh -c "set -a && . /config/.env.withauth && set +a && node dist/server.js" + + # --- Account server (kf-auth account UI) --- + account: + image: ghcr.io/knowledgefutures/kf-auth:latest + depends_on: + postgres: + condition: service_healthy + withauth-init: + condition: service_completed_successfully + environment: + NODE_ENV: production + PORT: 3001 + volumes: + - withauth-config:/config:ro + command: sh -c "set -a && . /config/.env.withauth && set +a && node dist/server-account.js" + + # --- Underlay app --- + app: + image: ghcr.io/knowledgefutures/underlay:latest + depends_on: + postgres: + condition: service_healthy + withauth-init: + condition: service_completed_successfully + auth: + condition: service_started + environment: + NODE_ENV: production + PORT: 4100 + APP_URL: ${DOMAIN:-http://localhost} + volumes: + - withauth-config:/config:ro + command: sh -c "set -a && . /config/.env.app && set +a && node dist/server.js" + + # --- Caddy reverse proxy --- + caddy: + image: caddy:2-alpine + ports: + - '80:80' + - '443:443' + volumes: + - ./selfhost/Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + environment: + DOMAIN: ${DOMAIN:-localhost} + APP_PORT: '4100' + depends_on: + - auth + - account + - app + +volumes: + pgdata: + withauth-config: + caddy-data: + caddy-config: diff --git a/selfhost/Caddyfile b/selfhost/Caddyfile new file mode 100644 index 0000000..ea30e34 --- /dev/null +++ b/selfhost/Caddyfile @@ -0,0 +1,17 @@ +{ + admin off +} + +{$DOMAIN:localhost} { + handle_path /auth/* { + reverse_proxy auth:3000 + } + + handle_path /account/* { + reverse_proxy account:3001 + } + + handle { + reverse_proxy app:{$APP_PORT:4100} + } +} diff --git a/selfhost/init-db.sh b/selfhost/init-db.sh new file mode 100755 index 0000000..22c7a7b --- /dev/null +++ b/selfhost/init-db.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Creates both the auth and app databases in a single Postgres instance. +# Mounted at /docker-entrypoint-initdb.d/ — runs once on first container start. + +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE DATABASE app; + GRANT ALL PRIVILEGES ON DATABASE app TO $POSTGRES_USER; +EOSQL + +echo "Created 'app' database alongside default '${POSTGRES_DB}' database." diff --git a/server.ts b/server.ts index f575334..2848703 100644 --- a/server.ts +++ b/server.ts @@ -23,6 +23,7 @@ import * as schemas from '~/api/schemas' import * as uploads from '~/api/uploads' import * as versions from '~/api/versions' import { getMirrorConfig } from '~/lib/mirror-config' +import { initOidc } from '~/lib/oidc.server' const isProd = process.env.NODE_ENV === 'production' const app = new Hono() @@ -304,5 +305,13 @@ if (isProd) { } const port = Number(process.env.PORT) || 3000 + +// Validate OIDC provider is reachable before accepting requests +await initOidc().catch((err) => { + console.error('FATAL: OIDC discovery failed — cannot start without a valid OIDC provider.') + console.error(err.message) + process.exit(1) +}) + console.log(`Server running at http://localhost:${port}`) serve({ fetch: app.fetch, port }) diff --git a/src/api/accounts.ts b/src/api/accounts.ts index 03a231c..589f59a 100644 --- a/src/api/accounts.ts +++ b/src/api/accounts.ts @@ -1059,23 +1059,16 @@ export async function acceptInvitation(c: Context) { } // Verify the logged-in user's email matches the invitation. - // Email is fetched from KF Auth since we don't store it locally. - const { getKfProfile } = await import('../lib/kf-profile-cache.server.js') + // Email is fetched from auth internal API since we don't store it locally. + const { getAuthUserWithEmail } = await import('../lib/auth-internal.server.js') const accountId = c.get('accountId')! - // Fetch email from KF Auth internal API directly (profile cache doesn't include email) - const KF_AUTH_URL = process.env.KF_AUTH_URL ?? 'http://localhost:3000' - const KF_INTERNAL_API_KEY = process.env.KF_INTERNAL_API_KEY ?? '' + // Fetch email from auth internal API let userEmail: string | null = null - try { - const res = await fetch(`${KF_AUTH_URL}/api/internal/users/${accountId}`, { - headers: { Authorization: `Bearer ${KF_INTERNAL_API_KEY}` }, - }) - if (res.ok) { - const data = (await res.json()) as { email: string } - userEmail = data.email - } - } catch {} + const authUser = await getAuthUserWithEmail(accountId) + if (authUser) { + userEmail = authUser.email + } if (!userEmail || userEmail !== invitation.email) { return c.json( diff --git a/src/api/auth.server.ts b/src/api/auth.server.ts index 2fa3a38..84b529b 100644 --- a/src/api/auth.server.ts +++ b/src/api/auth.server.ts @@ -18,7 +18,8 @@ export type AuthEnv = { const publicPaths = new Set(['/api/health', '/api/query/generate-sql']) const internalToken = process.env.INTERNAL_API_TOKEN ?? 'internal-dev-token' -const kfInternalApiKey = process.env.KF_INTERNAL_API_KEY ?? '' +const authInternalApiKey = + process.env.AUTH_INTERNAL_API_KEY ?? process.env.KF_INTERNAL_API_KEY ?? '' const sessionSecret = process.env.SESSION_SECRET ?? 'dev-secret-change-me' export const authMiddleware = createMiddleware(async (c, next) => { @@ -29,9 +30,9 @@ export const authMiddleware = createMiddleware(async (c, next) => { return next() } - // KF Auth internal API key (used by /api/kf/* endpoints) + // Auth provider internal API key (used by /api/kf/* endpoints) const auth = c.req.header('authorization') - if (kfInternalApiKey && auth === `Bearer ${kfInternalApiKey}`) { + if (authInternalApiKey && auth === `Bearer ${authInternalApiKey}`) { c.set('apiKeyScope', 'admin') return next() } diff --git a/src/api/kf-auth.ts b/src/api/kf-auth.ts index a6d367a..8dd071e 100644 --- a/src/api/kf-auth.ts +++ b/src/api/kf-auth.ts @@ -9,9 +9,10 @@ import { db, schema } from '../db/client.server.js' import { buildAuthorizeUrl, exchangeCode, + extractOrgs, fetchUserInfo, - type KFOrg, -} from '../lib/kf-auth.server.js' + type OIDCOrg, +} from '../lib/oidc.server.js' import { type AuthEnv, setSessionCookie } from './auth.server.js' const STATE_COOKIE = 'kf_oauth_state' @@ -37,7 +38,7 @@ export async function login(c: Context) { setCookie(c, STATE_COOKIE, state, cookieOpts) setCookie(c, RETURN_COOKIE, returnTo, cookieOpts) - const { url, codeVerifier } = buildAuthorizeUrl(state) + const { url, codeVerifier } = await buildAuthorizeUrl(state) setCookie(c, VERIFIER_COOKIE, codeVerifier, cookieOpts) return c.redirect(url) @@ -99,7 +100,7 @@ export async function callback(c: Context) { // User account id IS the KF Auth user id (userInfo.sub). // No profile data stored locally — fetched from KF Auth on demand. const kfUserId = userInfo.sub - const kfOrgs: KFOrg[] = userInfo['https://knowledgefutures.org/orgs'] ?? [] + const kfOrgs: OIDCOrg[] = extractOrgs(userInfo) const kfPersonalOrg = kfOrgs.find((o) => o.type === 'personal') const accountId = kfUserId diff --git a/src/api/kf-summary.ts b/src/api/kf-summary.ts index b028ddf..49b3190 100644 --- a/src/api/kf-summary.ts +++ b/src/api/kf-summary.ts @@ -19,7 +19,7 @@ export async function summary(c: Context) { // Verify internal API key const authHeader = c.req.header('Authorization') - const expectedKey = process.env.KF_INTERNAL_API_KEY + const expectedKey = process.env.AUTH_INTERNAL_API_KEY ?? process.env.KF_INTERNAL_API_KEY if (!expectedKey || authHeader !== `Bearer ${expectedKey}`) { return c.json({ error: 'Unauthorized' }, 401) } diff --git a/src/lib/auth-internal.server.ts b/src/lib/auth-internal.server.ts new file mode 100644 index 0000000..99852aa --- /dev/null +++ b/src/lib/auth-internal.server.ts @@ -0,0 +1,128 @@ +/** + * Auth provider internal API client. + * + * Optional — only active when AUTH_INTERNAL_API_URL + AUTH_INTERNAL_API_KEY are set. + * When the internal API is unavailable, apps fall back to OIDC userinfo data only. + * + * Env vars (with backward-compat fallbacks): + * AUTH_INTERNAL_API_URL — base URL for internal API (fallback: KF_AUTH_INTERNAL_URL, then OIDC_ISSUER_INTERNAL_URL) + * AUTH_INTERNAL_API_KEY — shared secret for service-to-service calls (fallback: KF_INTERNAL_API_KEY) + */ + +import { OIDC_ISSUER_INTERNAL_URL } from './oidc.server.js' + +const AUTH_INTERNAL_API_URL = + process.env.AUTH_INTERNAL_API_URL ?? + process.env.KF_AUTH_INTERNAL_URL ?? + process.env.KF_AUTH_URL ?? + OIDC_ISSUER_INTERNAL_URL + +const AUTH_INTERNAL_API_KEY = + process.env.AUTH_INTERNAL_API_KEY ?? process.env.KF_INTERNAL_API_KEY ?? '' + +/** Whether the internal API is configured and available. */ +export const hasInternalApi = Boolean(AUTH_INTERNAL_API_KEY) + +// --- Types --- + +export interface AuthOrg { + id: string + name: string + slug: string + type: 'personal' | 'shared' + role: string +} + +export interface AuthProfile { + name: string + image: string | null +} + +// --- Profile cache (in-memory, 5-minute TTL) --- + +const TTL_MS = 5 * 60 * 1000 + +interface CachedProfile { + name: string + image: string | null + fetchedAt: number +} + +const profileCache = new Map() + +/** + * Fetch a user's profile (name, image) from the auth internal API. + * Uses an in-memory cache with a 5-minute TTL. + * Returns null if internal API is not configured or request fails. + */ +export async function getAuthProfile(userId: string): Promise { + if (!hasInternalApi) return null + + const cached = profileCache.get(userId) + if (cached && Date.now() - cached.fetchedAt < TTL_MS) { + return { name: cached.name, image: cached.image } + } + + try { + const res = await fetch(`${AUTH_INTERNAL_API_URL}/api/internal/users/${userId}`, { + headers: { Authorization: `Bearer ${AUTH_INTERNAL_API_KEY}` }, + }) + if (!res.ok) return null + + const data = (await res.json()) as { + id: string + name: string + email: string + image: string | null + } + const entry: CachedProfile = { name: data.name, image: data.image, fetchedAt: Date.now() } + profileCache.set(userId, entry) + return { name: entry.name, image: entry.image } + } catch (e) { + console.error(`Failed to fetch auth profile for ${userId}:`, e) + if (cached) return { name: cached.name, image: cached.image } + return null + } +} + +/** + * Fetch all orgs a user belongs to from the auth internal API. + * Returns empty array if internal API is not configured or request fails. + */ +export async function fetchAuthOrgs(userId: string): Promise { + if (!hasInternalApi) return [] + + const res = await fetch(`${AUTH_INTERNAL_API_URL}/api/internal/users/${userId}/orgs`, { + headers: { Authorization: `Bearer ${AUTH_INTERNAL_API_KEY}` }, + }) + if (!res.ok) { + console.error(`Failed to fetch orgs for ${userId}: ${res.status}`) + return [] + } + const data = (await res.json()) as { orgs?: AuthOrg[] } | AuthOrg[] + if (Array.isArray(data)) return data + return data.orgs ?? [] +} + +/** + * Fetch a single user's full profile including email from the auth internal API. + * Used for cases where email is needed but not stored locally. + * Returns null if internal API is not configured or request fails. + */ +export async function getAuthUserWithEmail( + userId: string, +): Promise<{ id: string; name: string; email: string; image: string | null } | null> { + if (!hasInternalApi) return null + + try { + const res = await fetch(`${AUTH_INTERNAL_API_URL}/api/internal/users/${userId}`, { + headers: { Authorization: `Bearer ${AUTH_INTERNAL_API_KEY}` }, + }) + if (!res.ok) return null + return (await res.json()) as { id: string; name: string; email: string; image: string | null } + } catch { + return null + } +} + +export { AUTH_INTERNAL_API_URL, AUTH_INTERNAL_API_KEY } diff --git a/src/lib/kf-auth.server.ts b/src/lib/kf-auth.server.ts index 6db31ff..642e234 100644 --- a/src/lib/kf-auth.server.ts +++ b/src/lib/kf-auth.server.ts @@ -1,124 +1,17 @@ /** - * Lightweight OIDC client for KF Auth. - * - * Two base URLs: - * KF_AUTH_URL — browser-facing (e.g. localhost:3000) - * KF_AUTH_INTERNAL_URL — server-to-server (e.g. host.docker.internal:3000 in Docker) - * Falls back to KF_AUTH_URL when not set (production). + * Legacy re-export shim. + * New code should import from './oidc.server.js' and './auth-internal.server.js' directly. */ -import crypto from 'node:crypto' - -const KF_AUTH_URL = process.env.KF_AUTH_URL ?? 'http://localhost:3000' -const KF_AUTH_INTERNAL_URL = process.env.KF_AUTH_INTERNAL_URL ?? KF_AUTH_URL -const KF_AUTH_CLIENT_ID = process.env.KF_AUTH_CLIENT_ID ?? 'kf_underlay' -const KF_AUTH_CLIENT_SECRET = process.env.KF_AUTH_CLIENT_SECRET ?? '' -const APP_URL = process.env.APP_URL ?? 'http://localhost:4100' -const REDIRECT_URI = `${APP_URL}/auth/callback` - -// BetterAuth OIDC endpoints (well-known paths) -const AUTHORIZE_PATH = '/api/auth/oauth2/authorize' -const TOKEN_PATH = '/api/auth/oauth2/token' -const USERINFO_PATH = '/api/auth/oauth2/userinfo' - -// --- PKCE helpers --- - -/** Generate a random code_verifier (43–128 chars, URL-safe). */ -export function generateCodeVerifier(): string { - return crypto.randomBytes(32).toString('base64url') -} - -/** Derive the S256 code_challenge from a code_verifier. */ -export function generateCodeChallenge(verifier: string): string { - return crypto.createHash('sha256').update(verifier).digest('base64url') -} - -/** - * Build the URL to redirect the user to for authentication. - * Uses KF_AUTH_URL (browser-facing). - * Returns the URL and the PKCE code_verifier (must be stored server-side). - */ -export function buildAuthorizeUrl(state: string): { url: string; codeVerifier: string } { - const codeVerifier = generateCodeVerifier() - const codeChallenge = generateCodeChallenge(codeVerifier) - const params = new URLSearchParams({ - client_id: KF_AUTH_CLIENT_ID, - redirect_uri: REDIRECT_URI, - response_type: 'code', - scope: 'openid profile email', - state, - code_challenge: codeChallenge, - code_challenge_method: 'S256', - }) - return { url: `${KF_AUTH_URL}${AUTHORIZE_PATH}?${params}`, codeVerifier } -} - -interface TokenResponse { - access_token: string - token_type: string - expires_in: number - id_token?: string - refresh_token?: string -} - -/** - * Exchange an authorization code for tokens. - * Uses KF_AUTH_INTERNAL_URL (server-to-server). - */ -export async function exchangeCode(code: string, codeVerifier: string): Promise { - const body = new URLSearchParams({ - grant_type: 'authorization_code', - code, - redirect_uri: REDIRECT_URI, - client_id: KF_AUTH_CLIENT_ID, - client_secret: KF_AUTH_CLIENT_SECRET, - code_verifier: codeVerifier, - }) - - const res = await fetch(`${KF_AUTH_INTERNAL_URL}${TOKEN_PATH}`, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body, - }) - - if (!res.ok) { - const text = await res.text() - throw new Error(`Token exchange failed: ${res.status} ${text}`) - } - - return res.json() as Promise -} - -export interface KFOrg { - id: string - name: string - slug: string - type: 'personal' | 'shared' - role: string -} - -export interface KFUserInfo { - sub: string - name?: string - email?: string - picture?: string - 'https://knowledgefutures.org/orgs'?: KFOrg[] -} - -/** - * Fetch user info from KF Auth using an access token. - * Uses KF_AUTH_INTERNAL_URL (server-to-server). - */ -export async function fetchUserInfo(accessToken: string): Promise { - const res = await fetch(`${KF_AUTH_INTERNAL_URL}${USERINFO_PATH}`, { - headers: { Authorization: `Bearer ${accessToken}` }, - }) - - if (!res.ok) { - throw new Error(`UserInfo failed: ${res.status}`) - } - - return res.json() as Promise -} - -export { KF_AUTH_CLIENT_ID, KF_AUTH_URL, REDIRECT_URI } +export { + buildAuthorizeUrl, + exchangeCode, + fetchUserInfo, + extractOrgs, + OIDC_ISSUER_URL as KF_AUTH_URL, + OIDC_CLIENT_ID as KF_AUTH_CLIENT_ID, + REDIRECT_URI, + type OIDCOrg as KFOrg, + type OIDCUserInfo as KFUserInfo, + initOidc, +} from './oidc.server.js' diff --git a/src/lib/kf-orgs.server.ts b/src/lib/kf-orgs.server.ts index 6eadd64..9498c9e 100644 --- a/src/lib/kf-orgs.server.ts +++ b/src/lib/kf-orgs.server.ts @@ -1,28 +1,6 @@ /** - * Fetch a user's KF org memberships on demand from KF Auth's internal API. - * Used for the org-creation dropdown (availableKfOrgs). + * Legacy re-export shim. + * New code should import from './auth-internal.server.js' directly. */ -import type { KFOrg } from './kf-auth.server.js' - -const KF_AUTH_URL = process.env.KF_AUTH_URL ?? 'http://localhost:3000' -const KF_AUTH_INTERNAL_URL = process.env.KF_AUTH_INTERNAL_URL ?? KF_AUTH_URL -const KF_INTERNAL_API_KEY = process.env.KF_INTERNAL_API_KEY ?? '' - -/** - * Fetch all KF orgs the given user belongs to. - * Calls KF Auth internal API with API key auth. - */ -export async function fetchKfOrgs(kfUserId: string): Promise { - const res = await fetch(`${KF_AUTH_INTERNAL_URL}/api/internal/users/${kfUserId}/orgs`, { - headers: { Authorization: `Bearer ${KF_INTERNAL_API_KEY}` }, - }) - if (!res.ok) { - console.error(`Failed to fetch KF orgs for ${kfUserId}: ${res.status}`) - return [] - } - const data = (await res.json()) as { orgs?: KFOrg[] } | KFOrg[] - // Handle both { orgs: [...] } and bare array shapes - if (Array.isArray(data)) return data - return data.orgs ?? [] -} +export { fetchAuthOrgs as fetchKfOrgs, type AuthOrg as KFOrg } from './auth-internal.server.js' diff --git a/src/lib/kf-profile-cache.server.ts b/src/lib/kf-profile-cache.server.ts index 1bb5dc8..c4e8041 100644 --- a/src/lib/kf-profile-cache.server.ts +++ b/src/lib/kf-profile-cache.server.ts @@ -1,57 +1,9 @@ /** - * In-memory cache for KF Auth user profile data. - * Fetches name + image from KF Auth's internal API with a 5-minute TTL. + * Legacy re-export shim. + * New code should import from './auth-internal.server.js' directly. */ -const KF_AUTH_URL = process.env.KF_AUTH_URL ?? 'http://localhost:3000' -const KF_AUTH_INTERNAL_URL = process.env.KF_AUTH_INTERNAL_URL ?? KF_AUTH_URL -const KF_INTERNAL_API_KEY = process.env.KF_INTERNAL_API_KEY ?? '' - -const TTL_MS = 5 * 60 * 1000 // 5 minutes - -interface CachedProfile { - name: string - image: string | null - fetchedAt: number -} - -const cache = new Map() - -export interface KFProfile { - name: string - image: string | null -} - -/** - * Get a KF Auth user's profile (name, image). - * Uses an in-memory cache with a 5-minute TTL. - * On miss/expiry, calls KF Auth internal API. - */ -export async function getKfProfile(userId: string): Promise { - const cached = cache.get(userId) - if (cached && Date.now() - cached.fetchedAt < TTL_MS) { - return { name: cached.name, image: cached.image } - } - - try { - const res = await fetch(`${KF_AUTH_INTERNAL_URL}/api/internal/users/${userId}`, { - headers: { Authorization: `Bearer ${KF_INTERNAL_API_KEY}` }, - }) - if (!res.ok) return null - - const data = (await res.json()) as { - id: string - name: string - email: string - image: string | null - } - const entry: CachedProfile = { name: data.name, image: data.image, fetchedAt: Date.now() } - cache.set(userId, entry) - return { name: entry.name, image: entry.image } - } catch (e) { - console.error(`Failed to fetch KF profile for ${userId}:`, e) - // Return stale cache on error if available - if (cached) return { name: cached.name, image: cached.image } - return null - } -} +export { + getAuthProfile as getKfProfile, + type AuthProfile as KFProfile, +} from './auth-internal.server.js' diff --git a/src/lib/oidc.server.ts b/src/lib/oidc.server.ts new file mode 100644 index 0000000..34c0c17 --- /dev/null +++ b/src/lib/oidc.server.ts @@ -0,0 +1,230 @@ +/** + * Generic OIDC client with auto-discovery. + * + * Reads endpoints from the provider's .well-known/openid-configuration. + * Works with any standards-compliant OIDC provider (KF Auth, Keycloak, Auth0, etc.). + * + * Env vars (new canonical names with backward-compat fallbacks): + * OIDC_ISSUER_URL — browser-facing issuer URL (fallback: KF_AUTH_URL) + * OIDC_ISSUER_INTERNAL_URL — server-to-server URL for Docker (fallback: KF_AUTH_INTERNAL_URL, then OIDC_ISSUER_URL) + * OIDC_CLIENT_ID — OAuth client ID (fallback: KF_AUTH_CLIENT_ID) + * OIDC_CLIENT_SECRET — OAuth client secret (fallback: KF_AUTH_CLIENT_SECRET) + * OIDC_ORGS_CLAIM — custom claim key for org memberships (default: https://knowledgefutures.org/orgs) + */ + +import crypto from 'node:crypto' + +// --- Config (with backward-compat fallbacks) --- + +const OIDC_ISSUER_URL = + process.env.OIDC_ISSUER_URL ?? process.env.KF_AUTH_URL ?? 'http://localhost:3000' + +const OIDC_ISSUER_INTERNAL_URL = + process.env.OIDC_ISSUER_INTERNAL_URL ?? process.env.KF_AUTH_INTERNAL_URL ?? OIDC_ISSUER_URL + +const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID ?? process.env.KF_AUTH_CLIENT_ID ?? 'kf_underlay' + +const OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET ?? process.env.KF_AUTH_CLIENT_SECRET ?? '' + +const OIDC_ORGS_CLAIM = process.env.OIDC_ORGS_CLAIM ?? 'https://knowledgefutures.org/orgs' + +const APP_URL = process.env.APP_URL ?? 'http://localhost:4100' +const REDIRECT_URI = `${APP_URL}/auth/callback` + +// --- OIDC Discovery --- + +interface OIDCDiscovery { + issuer: string + authorization_endpoint: string + token_endpoint: string + userinfo_endpoint: string + jwks_uri?: string +} + +let discoveryCache: OIDCDiscovery | null = null +let discoveryPromise: Promise | null = null + +/** + * Fetch and cache the OIDC discovery document. + * Uses the internal URL for server-to-server fetch. + * Throws if discovery fails — app should not start without valid OIDC config. + */ +async function discover(): Promise { + if (discoveryCache) return discoveryCache + if (discoveryPromise) return discoveryPromise + + discoveryPromise = (async () => { + const url = `${OIDC_ISSUER_INTERNAL_URL}/.well-known/openid-configuration` + const res = await fetch(url) + if (!res.ok) { + throw new Error( + `OIDC discovery failed: ${res.status} from ${url}. ` + + `Ensure OIDC_ISSUER_URL or KF_AUTH_URL points to a valid OIDC provider.`, + ) + } + const config = (await res.json()) as OIDCDiscovery + discoveryCache = config + return config + })() + + return discoveryPromise +} + +/** + * Initialize OIDC — call at app startup to fail fast if provider is unreachable. + */ +export async function initOidc(): Promise { + await discover() +} + +/** + * Rewrite a discovered endpoint URL to use the internal host. + * Discovery may return URLs with the public host (BETTER_AUTH_URL), + * but server-to-server calls must use OIDC_ISSUER_INTERNAL_URL. + */ +function internalEndpoint(discoveredUrl: string): string { + const url = new URL(discoveredUrl) + const base = new URL(OIDC_ISSUER_INTERNAL_URL) + url.protocol = base.protocol + url.host = base.host + return url.toString() +} + +// --- PKCE helpers --- + +/** Generate a random code_verifier (43–128 chars, URL-safe). */ +export function generateCodeVerifier(): string { + return crypto.randomBytes(32).toString('base64url') +} + +/** Derive the S256 code_challenge from a code_verifier. */ +export function generateCodeChallenge(verifier: string): string { + return crypto.createHash('sha256').update(verifier).digest('base64url') +} + +// --- OIDC Flows --- + +/** + * Build the URL to redirect the user to for authentication. + * Uses the discovered authorization_endpoint. + * Returns the URL and the PKCE code_verifier (must be stored server-side). + */ +export async function buildAuthorizeUrl( + state: string, +): Promise<{ url: string; codeVerifier: string }> { + const config = await discover() + const codeVerifier = generateCodeVerifier() + const codeChallenge = generateCodeChallenge(codeVerifier) + + // Use the browser-facing issuer URL for authorize (user's browser navigates here) + // Discovery may return an internal URL, so construct from OIDC_ISSUER_URL + path + const authorizeUrl = new URL(config.authorization_endpoint) + // Replace host with the browser-facing URL if discovery returned an internal one + const browserBase = new URL(OIDC_ISSUER_URL) + authorizeUrl.protocol = browserBase.protocol + authorizeUrl.host = browserBase.host + + const params = new URLSearchParams({ + client_id: OIDC_CLIENT_ID, + redirect_uri: REDIRECT_URI, + response_type: 'code', + scope: 'openid profile email', + state, + code_challenge: codeChallenge, + code_challenge_method: 'S256', + }) + + return { url: `${authorizeUrl.toString()}?${params}`, codeVerifier } +} + +export interface TokenResponse { + access_token: string + token_type: string + expires_in: number + id_token?: string + refresh_token?: string +} + +/** + * Exchange an authorization code for tokens. + * Uses the discovered token_endpoint (server-to-server). + */ +export async function exchangeCode(code: string, codeVerifier: string): Promise { + const config = await discover() + const body = new URLSearchParams({ + grant_type: 'authorization_code', + code, + redirect_uri: REDIRECT_URI, + client_id: OIDC_CLIENT_ID, + client_secret: OIDC_CLIENT_SECRET, + code_verifier: codeVerifier, + }) + + const res = await fetch(internalEndpoint(config.token_endpoint), { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body, + }) + + if (!res.ok) { + const text = await res.text() + throw new Error(`Token exchange failed: ${res.status} ${text}`) + } + + return res.json() as Promise +} + +export interface OIDCOrg { + id: string + name: string + slug: string + type: 'personal' | 'shared' + role: string +} + +export interface OIDCUserInfo { + sub: string + name?: string + email?: string + picture?: string + [key: string]: unknown +} + +/** + * Fetch user info from the OIDC provider using an access token. + * Uses the discovered userinfo_endpoint (server-to-server). + */ +export async function fetchUserInfo(accessToken: string): Promise { + const config = await discover() + const res = await fetch(internalEndpoint(config.userinfo_endpoint), { + headers: { Authorization: `Bearer ${accessToken}` }, + }) + + if (!res.ok) { + throw new Error(`UserInfo failed: ${res.status}`) + } + + return res.json() as Promise +} + +/** + * Extract org memberships from the userinfo response. + * Uses the configurable OIDC_ORGS_CLAIM key. + */ +export function extractOrgs(userInfo: OIDCUserInfo): OIDCOrg[] { + const orgs = userInfo[OIDC_ORGS_CLAIM] + if (Array.isArray(orgs)) return orgs as OIDCOrg[] + return [] +} + +// --- Exports --- + +export { + OIDC_ISSUER_URL, + OIDC_ISSUER_INTERNAL_URL, + OIDC_CLIENT_ID, + OIDC_CLIENT_SECRET, + OIDC_ORGS_CLAIM, + APP_URL, + REDIRECT_URI, +} diff --git a/src/loaders.server.ts b/src/loaders.server.ts index 0b50ad8..13f2541 100644 --- a/src/loaders.server.ts +++ b/src/loaders.server.ts @@ -91,7 +91,12 @@ const loaders: Record = { }, '/logout': async () => { - return { data: { kfAuthUrl: process.env.KF_AUTH_URL ?? 'http://localhost:3000' } } + return { + data: { + kfAuthUrl: + process.env.OIDC_ISSUER_URL ?? process.env.KF_AUTH_URL ?? 'http://localhost:3000', + }, + } }, '/forgot-password': async () => { @@ -386,7 +391,8 @@ const loaders: Record = { }, } -const kfAccountUrl = process.env.KF_ACCOUNT_URL ?? 'http://localhost:3001' +const kfAccountUrl = + process.env.OIDC_ACCOUNT_URL ?? process.env.KF_ACCOUNT_URL ?? 'http://localhost:3001' export async function runLoaders( matchedRoutes: { path: string; params: Record }[], From d46f8067c7b545eeec9f73b0162bb5da067f285e Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Wed, 20 May 2026 13:22:33 -0400 Subject: [PATCH 2/4] fix env files --- .env.dev.enc | 57 ++++++++++++++++++++++++----------------------- .env.local.enc | 59 +++++++++++++++++++++++++------------------------ .env.prod.enc | 60 +++++++++++++++++++++++++------------------------- 3 files changed, 89 insertions(+), 87 deletions(-) diff --git a/.env.dev.enc b/.env.dev.enc index 0f5d240..d5038b4 100644 --- a/.env.dev.enc +++ b/.env.dev.enc @@ -1,36 +1,37 @@ -#ENC[AES256_GCM,data:J64y9RiY9mO7,iv:PgPt5keWxH/YIwgqOzflOmiXZV0/NLfY0jVl2lBb8sc=,tag:dEGfzjOcx7/LrPjnyFbylQ==,type:comment] -DATABASE_URL=ENC[AES256_GCM,data:MH6IJi+UhlojEFOcu0ZYqYCJKsTUTiFQRunI7BlwjrErwk/YUrb65o8edV9mSUjftbnk+jo=,iv:EJcQOziM1WIngMvWX7dlG877iYSItmSlnm2nlIcQVmE=,tag:cPIt27fEAU037qCd+RGN3A==,type:str] -SESSION_SECRET=ENC[AES256_GCM,data:SThK9G1iAKNRAw2cJdKCepn6amI=,iv:mY9V91jZ6YKcKWqOat8lA0yNJEj9rphaSK9gKz5Iun4=,tag:yeW3882+Gc05FCLpBPvFnQ==,type:str] -PORT=ENC[AES256_GCM,data:5lWgTQ==,iv:7zxANyByMT9cw3SuIe1pQKYhm3xpsDiFxgtlevVRdg8=,tag:n7Z2S9C225Zfh3qMpaE5xA==,type:str] -APP_REPLICAS=ENC[AES256_GCM,data:2g==,iv:/+iwIiaa+GSQkaOdWLvO5wd7ocO9MbbEF6k6/C5fRIk=,tag:NnXfnpYiSkQWzALItH9Atw==,type:str] -#ENC[AES256_GCM,data:oX6D,iv:bWay5HSecSfDGsyqV1qr5HyusjpLNdn+6ZaRKS+Pam8=,tag:He9zA9PmQTUJsGR+YGuEMQ==,type:comment] -S3_BUCKET=ENC[AES256_GCM,data:d4bmrVBqEd8PBCgZmJWe,iv:IcZqJIe6VN8ok2WN1zYIZ/gB01wXRHhJ16As0Cl1v18=,tag:1CISAApN3VceMfSY/cnTLw==,type:str] -S3_ENDPOINT=ENC[AES256_GCM,data:dI+gGhHn+YE//lydzWQeK2oKUHa4eQ2DZ46jTpIZzNtKxMSjOJ1dn6aFITrtPvHr17Ef9xMW+H+dXcYo4yyBYF8=,iv:JalLirX7aPihIHX0DdO13uxLo+uWobzuk/jEkhF5rmI=,tag:TdyciEppW4ZqHMtdZsltcA==,type:str] -S3_ACCESS_KEY=ENC[AES256_GCM,data:fEibwHUmYY8EyBnefRqLIZtaxj267fkUZgnUWITNImA=,iv:mFOQMXuKA1mhc9bVJnrIIiuQnjnBMWiM9ZYAYxq+LV0=,tag:4bppPzXoRI6/hC79wk1XUA==,type:str] -S3_SECRET_KEY=ENC[AES256_GCM,data:SqnLov0rL9VtoiTgw9AzAbdlrTlIkjibMT/uuRJbix+Of5qPpWVwMCUU2r9lm90dmBO2/7RjXMv8tnqcFirWrw==,iv:gPCHgc8s6cmny2SJPeM/rGtY7nYSjM3YNwEQg+c+fg4=,tag:rkSnwgx98TIU2qG3Z41qfA==,type:str] -ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:0ZBLuIc=,iv:mByaVTqE3zZYsOfoHf0x6gUQ60+1wn1dd9iPlco0SC8=,tag:F2sw4F+AD8+9YHcqyBkp6A==,type:str] -CF_ACCOUNT_ID=ENC[AES256_GCM,data:UDA7iTRvFEtONquBpukKrUbJSZ9EzrdOetRc4I5SrUg=,iv:Ux9e2KWScRIsen+jMa5mE1R/h5qJ0RWiHSHkYX5+76M=,tag:f7ohH4pFVyQtkUiPQ4u2RA==,type:str] -CF_API_TOKEN=ENC[AES256_GCM,data:AU9N7AsYCvadKO3DGlmqgo58ugW/VvPOcmcHQngqhqauFGPzym4aCYZL1xv9+BQX4b6XdZM=,iv:BGRd4+qUSdTtdkMWcieOfOHuWctdw8rmwVjj7Fg6Y8A=,tag:ywN8hMBesjtGpen5ImRC5Q==,type:str] -DEPLOY_HOST=ENC[AES256_GCM,data:YH65GXlLDyGeO0CB1A==,iv:k+E4hNXEUHq/A086aFy3hSCBr5G/eazv7m5LiTOkGh0=,tag:0hDQY7eFa9OMxF+msAaGQw==,type:str] -#ENC[AES256_GCM,data:0pVHFB+aljI+8luWNO5lPhu15rA=,iv:O6Wgy/AOLT9hJbzrYAecI5Mj+aOv7cdH9P5sj+eYhn0=,tag:MbM4zLssMwNojtqSvFGLzg==,type:comment] -KF_AUTH_URL=ENC[AES256_GCM,data:XHj9nRZx27Br/gECcNE6nFRmwyqtScXRF+pXZ2JC2dlFYWD+ig==,iv:u+8P1wSqFDIJTMUtmLHFHYPTIbk6T0WW77Sul0l5BF0=,tag:62BCM8VoU4VdypD6FXx/bg==,type:str] -KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:g1F0y4pTy1uSueY=,iv:kBO/7z1Dyw3fDMPS/JCGMYewVJHu+pDWL7IUWJm7cwM=,tag:QZNPoojcOxjeDxDB75qr/Q==,type:str] -KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:NaWmucYbbqoVZNtU0AP91v2HFSDI1nhTGdEFeUrufRuugRj617LvSgonabGri31pW09GYcgip0tLffjwxlh6bA==,iv:f6U5aGXz3uFLffzH+uuVpMk86MSNdWM5sCa/OwAsZ9k=,tag:JNgFl+NIN6hCRozbabE95A==,type:str] -KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:+M2mGEeSmm/rVkdMtraeobv5/s5jOvfrnudsG5Qvxm+Jv9wuPhFdmAeMkXqK6HRDDoTSDRPYrjkK/Kwaq2ghzQ==,iv:9mudkZFpeddUAYDTzJAtJBYhHf//80tFftpaDWRedLw=,tag:pPtyM56v2815rW5Xak+gGw==,type:str] -APP_URL=ENC[AES256_GCM,data:Tbqim9G/Bxqo2RSGvVuk3xfrQWPEXzj5,iv:NmGm+Q1PrImHUSHy70wQnNQ1fKCEf2zh6YqATGPY4kA=,tag:9KyNXpuI8Uo/BPXGdWzPGw==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKOUx3QTBoZm05S3BsYmlU\nRG9yNkw0ZFI2MDh2OStQcXQrZWxLaklmMDEwCk5xUnFnOWlGSFB0THV2WDM3UUYx\nMEE0ZFBTejNwb01YNXhvdFBtRlJoSlkKLS0tIFFySHpFNnk0eWhTNHlXM3BRc3dF\nTFAzcHRHalJlVUtSdnN2WE1ZcU55RW8K/kUhdyj/LfqtuOJQNU0F3DL4cngDfEh9\nWck0u6vAPy6pfC+xzzBE2r4/MEdITIfr2uhF1PLOZfSkm2PFKcUtPg==\n-----END AGE ENCRYPTED FILE-----\n +#ENC[AES256_GCM,data:SkXTEkxEVNDC,iv:20MGbTzjrXXEfgrg07TlWcd65OBvSXra6LES+JszsRA=,tag:TcDi0/3N4yfU6+wvW+IPEg==,type:comment] +DATABASE_URL=ENC[AES256_GCM,data:2mlttdfAAng7kvNGJ9wiJYTHzfioSe2famC722SQOCdoNiL3+EfXVBiht+0371TuIDdoUvo=,iv:RtarTtROsZzPHNgXRmlTdAwr7I3V6XWQSnTHHj19tLE=,tag:eJrY7eohdNGHlha15J9lKg==,type:str] +SESSION_SECRET=ENC[AES256_GCM,data:D5+kHHe3QSmU/q0dw1qH0Uo3NqE=,iv:AxZbEKwAH5kVo0usT5na/0/AnENsrpVb9EuzeGExtpI=,tag:yl+D5z9e9KROagNL/lCSbA==,type:str] +PORT=ENC[AES256_GCM,data:yV9fcA==,iv:3YO4mLxBfzrTaBLXEaTVNK+o0Wk+FgRQGNYL+nEv5Io=,tag:r/PFa3C+l6lMGstSNXHvOA==,type:str] +APP_REPLICAS=ENC[AES256_GCM,data:9Q==,iv:xlV2bT7re8mXxkFl89KPVFb5TlGGrjBPf5kP+3IjC4o=,tag:eRnU05fOAeVNLlR2dDDqsA==,type:str] +#ENC[AES256_GCM,data:suOT,iv:QnvGa3MHGQSvGPKv9+uc8UnecaUn6C5vO6FTv2/AUaM=,tag:DI5owLtwNV7rjoB0EGgJgg==,type:comment] +S3_BUCKET=ENC[AES256_GCM,data:gZJT5O5p4bZiDwF9aqgj,iv:am1qKwxcVWs1/ghwCZCfcdIsqF/Fesf9stLVi0jB/3Q=,tag:3We68PqApD2kgMWp126N0g==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:6Izx/xafSWgedBJRKlpTySv8waJeCEtJ9eA4REgxHYifDLOY0IABHYmzxv7t0jT4lW5hlpy4NfboZ4d/JIqN8pc=,iv:UWoyuLt++ldO8HneIn4ztwtS2zo0wdn7umYbBihMhfw=,tag:x/LEnUgi/wF/RKPT9CzSrw==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:XTVGaelE08hdjcdTD9rYr4bge5OVU1Y2GWfCbTp9sY8=,iv:z+08ILWfAx6AXKfKk5qj0klFco0cnwn60svaIhvUVOo=,tag:AQHU9DizJ4pd2PF2GeJLrA==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:hobkMoScovS/9rAFn3/arUmjAJmKceO8MEqh6M4QoQ19a34PvQHPcAek5W3AuF1wcnsRDL581Nsrnr5QkpJSaQ==,iv:B8IjNrh0uDT7MU5zvBXKhV0vhGLmxTvP1k9GQYSogXU=,tag:qeEVvsb5B1qYm/330NiG3Q==,type:str] +ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:Y3HQHnA=,iv:7dk6VP4SAMl6+4H9QoJ5H8+8An1p9TRMA+qaGECQgS0=,tag:nBFQTPgOqePcPHJ7yWYjIw==,type:str] +CF_ACCOUNT_ID=ENC[AES256_GCM,data:ampmc7WgrIqN52sKfDbkPpnzziFNEdNbKo8kAH5Vvzo=,iv:yBnG/2aUPNtTMc9q67rlQcZ1XwW88URk1QO87evGbjE=,tag:0KieLLQJ0k59Gemua1F4gw==,type:str] +CF_API_TOKEN=ENC[AES256_GCM,data:YEmE20j9XdfaZA07FYvWu7RBlkOIUS5AM1Ac//ZglMXk1xMbpTo5suV66byDHX9oc9LDDkY=,iv:fi4FamLKcnZbyALGq6xKmytTMhZRD7dLnV5vkVOcZso=,tag:AXsipKabcHBYyNSb1qA4xg==,type:str] +DEPLOY_HOST=ENC[AES256_GCM,data:H+kIyRVkmxXFEROceg==,iv:+6MTOkolYuRUM1kDfaxnAK681tmAmu2IP+Qk2V2BCmk=,tag:Y5jQBIFw1QOV4CMtMksFUQ==,type:str] +#ENC[AES256_GCM,data:D81HIiY6HaN5TdubwexjVFrfFgs=,iv:eV4CTjlponnfAN6bSCosCUJ2JTtpybDzoDjflyRkjPY=,tag:agerV1G4l7CAHO8KlDwAbA==,type:comment] +OIDC_ISSUER_URL=ENC[AES256_GCM,data:11yTXIz4rEMkaEvjw1jjpd0PKylG7sIZcDzzQrRSFykGfeyMOA==,iv:MzUn9rBkRcXphqnybWbykYtYFW0t9W3VAYHUsDjQac8=,tag:pHrJrS4Mcd4oZiG9S57ADA==,type:str] +OIDC_CLIENT_ID=ENC[AES256_GCM,data:TT/TmBpVv38HN74=,iv:dn3Divc7SQ3/4/3N4N+pjaWI1hstyyMf5DihL3Nnjl4=,tag:1U7wPDmcf3kvGGNtKWduCw==,type:str] +OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:JZjZBjUVqhgVCQFG9w6ZJUC7jHVW9vADF5D7hM3wN9h14Ix4KfviJWOu3AU5ANMIG0e2b5SQiB+IWU6HHtrP1A==,iv:rh5ytL3CnUTFlmZXD+mkxYSVUQP8YvEfWJhahJLNe9k=,tag:fQG2BE85XoMMZqtn+NItKw==,type:str] +AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:G1oyzUx7mmv9COkDUBOlhDUJsS5yefnHX//M2C3TfiJUSkK3MsdfPfcJk/kasgVfwOr2B8vtWGUV1SKJEPACGg==,iv:2i/ChJK96Yw7yIzVdLt9hps87L7JyJVgZ+nMHfHTAt4=,tag:AlHror7WRIQSXzLoqbg8hQ==,type:str] +OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:qaM+JUm38v6EJ9z8OnNNFJ3lpRxyr2TYBp16fnJ3opBkheFmUtLx2Q==,iv:CBSlYhECmeQGM4SgwqpiCbdMhs/6M29ibi7f3zURme8=,tag:3B3Jd50Ssghoc9+fYx3E7w==,type:str] +APP_URL=ENC[AES256_GCM,data:0Ee4N1DtAO6zd/iWLhFPjNSTsUPB8XBG,iv:7zkzsfoXzC1hg3X/zA0LuXd+dc1lifKPYy6m4wZ94vU=,tag:wUvE9eVnvCgfXY7OfKSVqA==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0NzduTnlsVnowK3kzV0FF\nN3plM2tGc0dQN1lRK2M2clRmME9oYWhrWUhnCkNTOUNvR2RGdFNkRzBvUVYwNWtr\nREJXOXVOdWUxQytSSy9RZ0RweGJuV3MKLS0tIElvWHVnRFp5N0kwWmp5WjRSNVZP\nY2c1WWV2d1Y5WkwyYS9PSFpWajl6dDQKtm7+/VTLWjgcyU03ATz5nthkf7FdW+gv\nDYg+0GFoKJ329Vdam1n5B/km9GXLFLFcdshb/7kzbM4pHz7YMRre6w==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyaFBPdGJuUjEyYTVDeXJa\nRDZwakQ5MGNvMUhoV1NiMEE2UnpDWmlBUjBNCm8zQkNOSjlMUUMzUkhpTlA4SEho\ndFNqSmRqL0h2VWFtOTlEWER3WTUrbm8KLS0tIGRjV1VMK0cyWmxJSFMySW1PVjIy\nSUl0R3FEOEJPWE82OEZJN0YrOHp1aUUKa6r5xLqeazSlCVqwahVUS7DyDK2gaf5Z\n5/wgpfziUCrTfpPZHle6BOt+tWqndwA4rPJt75FdUborh1SQo1mJSg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmVzRzZnVqdEx1YThmYndD\nRWpwTHh2d094MVdlOVZlS2FWR1FhVXZURmhvCityR1oyL3Z0VkZjTlBrcmY1SDI2\nR1dpZXJFOC9ObXh2ZnFzV1NCeWdjK28KLS0tIGsrM3o0ODkycXdpVHo0bVFFdnZ1\nM0l5RVVXemRGN2k3bisrM2ZCaTI5cVUKat5SVPmUJh0TFMzwukfZURZHEQgYla11\nJn5KEyc0XRtWV4KAq13EZtnxy2ZX1eEneAn63tM0h3qtpKi2ioua1A==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1ysddqggsx3h8zkv7xn3z26sjak5pqms6pyqhnky9ukrvpk7es5jsayz8w7 -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYT2VGcmhPTVMvY0tQRXBw\nemVLaXBZakpUU0Jad2FoUlFneTR2S0lyMUZrClhTZnk2YURuSmw5OHRFdHpsMnVJ\nLzVqTFhFZk1uVVladnErVTRCeU9ka2sKLS0tIGliSTRQUmc1SGJOb2pmVExTektJ\nV08zN3dTZlVHYXRxbmZnd2dub05TeWcKVYE9K0cvh1J0/ac5+XnuBYSbQBLiYI+D\n3YaHr22wVWu+7z0aCTUpwqEuTPC1uAPj++VQpBYhNnAReFDJ20kCuw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPN01vMWJGdTZaRjN1VytG\ndkx3bUhHVlZnZHBLMHIwb0RXSWRXVlYrd0FBCmZqbjg3Y2lKUXI0T0NXbGlDSlZU\nS3lHVDBTbnJtSCtpK0F0dXc1WUVNdzAKLS0tIDVaU2YvT25qSDArWTJBSlN4R0xN\nbnlIWUlBaE8ySUlYSlhsYjNKZ3huekUK+XvJxNKUQhb5OdbBwtmNP5xACJDtiz/V\nhlbt3tAfImxt19heQIgTIe1ahJmF5eSua+vhDhO36SyOWU+bioTizA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1SGJ4cEp5TVl0QmtCMmZ0\nT3ZFZ2tDRzFZajRqckhBM0xqSENxY3J6RUJVClVtODVoSTYrbWx0NS8vemVkb1kz\nTHBsdG5JTVVKaUZublBuSUZwTlJqQXMKLS0tIFlhL3FsNS9BNkcvMkZIOS85V2VU\nRnNTcXEzdHd5RnF0dWxXeVJPR0lueTAKRfZ+rFyoNPFtmrlU/XnZf+HJ8TLtEkZS\n+1nfMxkqwalBRQr98JRoBjEbKSU8tEC4By2J5hCAnKdznxHJjx+AAQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqUkI1K255NTh2bjN0VW9i\ncEJLNEZTdnIzcWJBc0dNYVd6cWY2aFgycFFBCnJxMXM5THlaaDVFVmlzL0tZTjBF\nR2hnRzVuYUVNU3UrRGdNVU1oQzZlVGsKLS0tIFovMWI0c1BxOFJ0bFNIY0lHOXFM\nTDU4VGpYSzNaa0VlTU1taDdmSWJISncKX8j7oH4Tss4yq+mHi9gQET911tT5k38d\nHYzgqADuz+MY20bBpDggaC7Gl/Qksj9LpOBWC+QUf/bmHwcWNZC9IQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1qn0x93jhqjpqwvx5tgxnrwq5e3vuzur9whrkdnrvapd58esm45rqfkuxqh -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5bUprbUVRM1ZUU0ZqREt4\na1EzUnNqRDFzSXY5NGF3eEFQUmVnWFlQeEE4CmRadGJKeHVtOW5HUmowS2VveHpl\nMFVKeGhhMkpOVTZKSDFzb0crY05US3cKLS0tIENINlZCL0UxMEtEbFRBVGFOTXVF\nMERnYktjak1DOHFhTEQzRmFacWxCeHMKVOGSrnO+0YU84r8p1tCatZI0CL8FOs0i\n67EtolhSu9GLarnuNv4MBG7JLefkXNxJnEssQKZ+BXWMkdTMe6I7cg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBteU1aTERRK21hN2c0bUhi\nT0JXTGllYlV2Uks0RytyWUFsTExKMjkvM2lzCmF0b2FUT0RYNmxoUzBORzlVSDBx\ndkJPQmQyVzJWOTNVZDNBVHNleUtVdkUKLS0tIHNJV1RQZEpmUlliUjI1QUNBMDRw\nVkFlTnNacFc1RmsyaU1BKzVGTjVpV2MKL9IL9BgmS0Hl6bx/S+ACHais3qj2zpL9\nP43XHpbK/SbCBH/w0CoH+cj5uO4ZTk6hIZ7m0N9dLOCp7sfldX6LrA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7 -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5ZUVJeEhUTGExVW1LOUdn\nb2hQTUE0dDlDaGZQZnF5SkFCMGxHYytkV0ZzClNSN2lXaXJxSG9UbTV4L1J3M1Rn\nOEpobUZidm9zVlI2WGl5aVluajZwbncKLS0tICtjMENsZXplWGxZYUNsR2djbjJE\nNDZvOE92SVhBTDJLY1hwTERSYmZsMmsKt3wKD6mbEeyTBy5SKrrdBhUyHIPA/fQ7\nLC4gcd4kKHhEp9l9dY7YQeHxbVMNbQ2+JSQVYVpkaNzbaHMAXvDrKg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTOTRHQmlCV1FtWXVDQU9M\neVlwVGNqSlFxNzY4NUpEU0lrbXJsRTVoK2hZCkZucDJ1bVZwdTBHcHdrazVmZGNL\nQzNFQURMajlNcVZ1aFgwTDc2UE9tVDgKLS0tIEtESjN6TjdJaHcvVjNYNWp4SCtX\nZEJiTmxONXFIQzl1Q2RXVnF6OTU2NmsKES3lEZwqRSiS0tuHp70Ula7uohnwBqnc\nDYbCQZlHx7egkG780RLaVZAThO2Rr3osQqTnVoVz5CwvcUtGT2Etaw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_lastmodified=2026-05-20T13:46:56Z -sops_mac=ENC[AES256_GCM,data:vopz9Fv7NU2kzx8OBW4wgcw2BSLhxo3bbNgz+Y9KPCtSeO/TI+W9QvfCmL3/a5ZUpq+PNlQq9T53LDy4YxuterPf8HzDombylY+tjNI1Qo6rfnTuJ5AZyDFxNgZSSI7TaokOp/dUMfKd+IYZme3vOLCqhns9wtdhCrFi1z1ccTU=,iv:NRfuTGgYDomLbtLE5r0JmIyeJ4QcpKPkL3Zd8I1R/aA=,tag:Cj3B0K+J75saxQ12QkaGTg==,type:str] +sops_lastmodified=2026-05-20T17:21:40Z +sops_mac=ENC[AES256_GCM,data:zEs7C2tA8cLsullU6zSNeZzPYM/Qt38ATTgxcjRcDQ5A5NQzEaTsBrlIyn6sXvzqH1l+QDv6yMxXzxRVhZo1/zuDNgAPeyScbed1x8ThNO6yHuU+u+oduKgnwrPZGS7whEHpSOk355Bhr7POylFTGZl6SK2E7fNhzoX5T77pY5Q=,iv:PV7GU3ZQDxSHRaCSOewfNe10kEbPSTg5SyLGG4TiSWI=,tag:j2DSZia+O/FUCyCGdnCSnA==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/.env.local.enc b/.env.local.enc index ef4db60..44f6b68 100644 --- a/.env.local.enc +++ b/.env.local.enc @@ -1,37 +1,38 @@ -#ENC[AES256_GCM,data:2QdtLPlWcNjj,iv:LHuRl3g/4msGUBXWbBT7AbussDIydRTpxfBdFB8P0Mo=,tag:SeM78a/cDUK5vW12sGNnKQ==,type:comment] -NODE_ENV=ENC[AES256_GCM,data:+bI6CxTQ9pgPW+c=,iv:1mmud40amvgZ329F87BYtmUqqcMboCBs70k5qzn3wC0=,tag:u9nUrK+wZalk1z9UW+oA2g==,type:str] -PORT=ENC[AES256_GCM,data:dByevg==,iv:ykv09nU0UrzohvwRJy4RbA39ybZv6iw3gB0Qj/TOQ28=,tag:+NZo1z8DPha8Vew48LYqEQ==,type:str] -DATABASE_URL=ENC[AES256_GCM,data:FNZEPzBVpfR6V7IPNAk+BJNzuzAZOmTiUCBvZfXKDxDvV8kxYkzvgfvELymVou308nj6NgA=,iv:Aq2OrPxw7jKcie+SA0Lp9IP0joxs6d4p/LIYfbC9T9w=,tag:VglDCUA3pEWI69tEfs2Fpg==,type:str] -SESSION_SECRET=ENC[AES256_GCM,data:3/O078CnO/vc/tJq2dPDejVvBIc=,iv:GdvlkSEODTQKSwj978cLTrsPxfAPBuoPxxYTnAhAv5Q=,tag:sCKymXSEbhBlj75WyX04qQ==,type:str] -#ENC[AES256_GCM,data:er2SEZApd+jg3IcZy/nDti1+YA==,iv:0QM88QHpuHY5AUdhhRjXiEB3WAWb/6J7JSjJ5Myb2ck=,tag:TuI3PAAVJssY+9BAh8ND/Q==,type:comment] -S3_BUCKET=ENC[AES256_GCM,data:fai2KOzrpBKlnh7YLU3p,iv:S3u1ypr8GQzFQrg6FCi8clJgEDAvZOwr+GgzeeKgCfI=,tag:/i9cW5MggQsM5MH6vHPSyA==,type:str] -S3_ENDPOINT=ENC[AES256_GCM,data:D6Tvp21kchSdYCAiXwF4ZUQHnLHxL8DaiTeZRTxy8mkTbAMf6MzLZ5qRoEkbpp1J1X+BAvRkq8WQ+4yflVApjbo=,iv:IydUs2DeD7sm/vmxgwexn7nZK28D65O4yFTaJCJokzk=,tag:fwBGX32C3ayHxXtwHd8CqQ==,type:str] -S3_ACCESS_KEY=ENC[AES256_GCM,data:rNaCds9WwNIfUxSmkM723Xnl5PMmzKxIZnV+LXE/IWY=,iv:HzEl5h+K806CUQ8mA5MpWe64S8jEcdeUqdohl5EfokE=,tag:Mj/t6SbJplwyV196tRjzGg==,type:str] -S3_SECRET_KEY=ENC[AES256_GCM,data:jfzHzxazhfXx3+HlMS3jcf2mgjCZv8fKv+5PbOg/pw157DuAb1mxandT6IdG16hAKHk+DTSIdk9MPzS5zZvFwg==,iv:sx9RimeHmFmLxbPKE5zveCv7dwKioOAjH9sO+GmttY0=,tag:g6t6hxsbl3QKvNerFP9aDg==,type:str] -#ENC[AES256_GCM,data:T4xd4cdfew==,iv:Et1FTnnN/GIZQTkKDgVzDpkRJo7L+Yh07D3Flt3JWAI=,tag:Y6rJ/RM+Ug2wsUN/+YbUqw==,type:comment] -BACKUP_S3_PREFIX=ENC[AES256_GCM,data:17dSkZiDUy1D,iv:UuqwiF1EVJec0hOxOIbtBTI7D+cyHAEKyE52modMPnQ=,tag:0bWzei6yqmh8WEZTsscJBQ==,type:str] -CF_ACCOUNT_ID=ENC[AES256_GCM,data:CvUrNqqJ4NmLwfP4IHHwTd37PCKT8ur5xf71crVESRw=,iv:RYtOJg4H5543AJ9lXIrh9AsxPmhhJacq546fiKxt0b4=,tag:TdMBeXQ7Rj57W2fjEpnYbg==,type:str] -CF_API_TOKEN=ENC[AES256_GCM,data:QOgWy1RVi5ZOUh9SCjlG7EPon/MexH8DCJzxQCqEZhdBV5s1oy/jgrVsmJPCOVhWLdbI0wo=,iv:MxkA8owuEvLFB+aSD4eHNfPO9JmRZlaWpdqu0hKDYxc=,tag:1I2Gt5sM8Dw1RryMsgr6dA==,type:str] -OIDC_ISSUER_URL=ENC[AES256_GCM,data:TPkA11ZHy5JfrN26pJOXbdE/RYnI,iv:7XZ0b63+tR2P0ndtQHLlTJIXMKsTmaJ/pMWsDPBrOu4=,tag:1tdRV5EqCpOX78xbTe5vUQ==,type:str] -OIDC_ISSUER_INTERNAL_URL=ENC[AES256_GCM,data:WYYzrJoM7UVEoPXM3/Rs1NCtayttS4QGFQIGLmReUL4=,iv:bo2kRtbwHh0zHzDSz+CRDg92LkWvVbPBNXrbAVjdl4Y=,tag:IYnes7T67mLopxk9XmxWfA==,type:str] -OIDC_CLIENT_ID=ENC[AES256_GCM,data:ls9hqkJNRnH5e80=,iv:RP6JBIjgfptNKiVK1TwkeVrGO7GxVRPz9zPsb/tDT34=,tag:YcB23Fu1RP1qKa4AB4Xi/w==,type:str] -OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:bPOlaOLQ0+0f457+/Ox+JffKjWbS9jD6G9u0l6oIatNDfKfoSrWmzgeqnm6T31vi+Qa56NT23oZvWWE09kz8fw==,iv:e6idHjojxoxUtH4TJpz8LNLnohkJYdcTC9jO74LnJM4=,tag:Weh8Popy5zuprnsyX7sSyQ==,type:str] -AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:Sb/90FRvGTi9gi2XinGXN7/SUT4cTfsC3TSEdcvngvQ=,iv:Tj4UqEoZMP5hGDpSSHiRRk3yQnC3Urg0G50c2HYqQm8=,tag:kh6HgP9inHZ/bTK+yGeElQ==,type:str] -OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:XW7Tbnfs6emjBqWMMAo6qx6UM55E,iv:x7c5aodVQEfiXBox3Mvs/nu/Zw1dBKw0RdHSJKv0eLU=,tag:DjiJCUsh1OmVl6xGDcPNxA==,type:str] -APP_URL=ENC[AES256_GCM,data:SoZT6GGL+32wmVeTPLZfSaqKVEke,iv:9sLgDY6Hx0clqGKj5SKBSTQT3staTYF5mfLuN3F2hho=,tag:ajF4CFsQwyY3BVLwwee14g==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCRVcvNWVmR01XN3JGeVUr\nTkdnYy9BSVBoQlRPaU9tQzdabi80b29HS25JCm5sSUFrenJXcWk0a3M2YkhQcTBS\nV1JkMWUrYW1xUmMvdTdnbmd5Q1FsOGMKLS0tIG80N05FdmYvYVpXVjI2Zk11RDRC\nUXB6eVZYSk5XbWQ4RjA5L09pT01Hd0UK20NhEK8dBjYUHKocws20N+AmK2lGX2Nw\nRFJ26WcdRGY8TADAM/rHxZ/5rtDWCIly3GgA0JrWQBqG1WqKeNJmiw==\n-----END AGE ENCRYPTED FILE-----\n +#ENC[AES256_GCM,data:/JOFp1qXl8zo,iv:YkvwILjKP3ukgfDU4FAXRspTcOtlriAhF1ARrSK64qo=,tag:MdTBsgsJ96NwB1OO7gil8g==,type:comment] +NODE_ENV=ENC[AES256_GCM,data:G0E4LCkWP3GpxqU=,iv:gHXVHnY7SBuDHZxoHuC+wASCd9fAUyJEnD7kxPq4I5Q=,tag:P5mzOLMi139EQNzbL2Z07w==,type:str] +PORT=ENC[AES256_GCM,data:TxnQfg==,iv:MpcCr/BfSQnR0MTu9IkqaLEHwDr9rmdN1UuFJPE4Dfo=,tag:/QQgaZEsTDRFN05YFwJFfQ==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:v7SbUAfNKKHwkZNalJQ8RarypW9TM5AS5ELaeo2Tao/q2isK7XmrcwqC3y7cSwGrlW7V670=,iv:qquaG701XM7G/zDGaKlQCW8/k8E4pVUb162DlemlxgU=,tag:XaXCPOksPgVSnUMtPORiEg==,type:str] +SESSION_SECRET=ENC[AES256_GCM,data:ZAMAQhydGYVCSmgAzx2CajZ2Ivs=,iv:l1XQIa3LusZS/yJBzY8uJAjw9ulZ1h0dhCc7Ttsk4OA=,tag:4f+r1bAI6HWHE01VEOwF+Q==,type:str] +#ENC[AES256_GCM,data:EHmPdIQNJsCHlM5mXxhlaoXtzw==,iv:zMeB/NL81FJ4wfnZimy23Cj+oVP+OrJcw1XSnADx+Qo=,tag:3ikLncPbT82dfAglLxVQtA==,type:comment] +S3_BUCKET=ENC[AES256_GCM,data:LMDoOfh9F4fConQ7B849,iv:O978wGztWUl/UGJ9C4VQdBQ0/RdJDR9w3d0JoeLlJdc=,tag:PAd2k2eGHkqfffryQ6NCVQ==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:7rA38EkALQGQ9RXsrlL/xMMC9trsHWaVzc0xv47U13p4neWympZ00wC4xaT1WfL2O7hatAFq2wOeci80UM97HKw=,iv:TWCLRN+JYI1tmDCtE9hN4T/Y2mXf+OCb28LlFTJA1aM=,tag:7ljrPXcM27XTFmTUdiceiQ==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:/jEB8GxyKHN/+3IZwPysg9EGzoF05KpyvCxEV8KM4fs=,iv:V6/u6230hz3zOlYT/Vu0WwsVB+zUmTs3BKS+sKOK5wQ=,tag:mGcJPRfWROQZOjnoUE9JJg==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:NSFIRisBJyck5DmBJ+XuBcZ6Wh62l9BvCztxXBeyc5ba8qrYhNcPMol6fYW3jFgwDO05WPfd/0Yl4vtnt4/cFQ==,iv:U/Kga1EbBjRTKp5/0rngRu029baPkWaPXgooXMPewyM=,tag:UMNf9yfkuCLORN+R8uRNVA==,type:str] +#ENC[AES256_GCM,data:EqyQ7+7nuA==,iv:/3C+1h2EoaCyKRdomS2yMeHLSXCsKlglyGSrFveX3d4=,tag:6QQ3pZD7wweChAi4LfRveQ==,type:comment] +BACKUP_S3_PREFIX=ENC[AES256_GCM,data:zwS0vQbVdzpM,iv:zYnicTlGWhyR/5lAJ/s+1T/bwuLbB74KZummZdT7EC8=,tag:FoLMGUrnH6JzouJCGGzr7g==,type:str] +CF_ACCOUNT_ID=ENC[AES256_GCM,data:eNjyfXP8R3IkBZahMBHEXHQ/BfE9wH1rX3a901esxak=,iv:5vuR2gDeO6xiCMIzAjlI8pVM1w1LcjSlcTeRCy4BO48=,tag:S5EjVdkMiak70wQQVY1uQg==,type:str] +CF_API_TOKEN=ENC[AES256_GCM,data:mnmrFcUdk53aaax+uk02Ss+9HwYgSqbyxYp80ZYr4+1b/UcplqoA1wSMXj005DZQUXQ4US8=,iv:N6WrkigbAogpDfSi9trQiYYD9DurAWjr1iDRLtbu+94=,tag:7bFo/80/9xwtp7ntlOx6KA==,type:str] +#ENC[AES256_GCM,data:NDvfVG5fK9OXOlCN0ojPElRnn/I=,iv:ttkB15j3Ab6WJ/m7ML7ihRt3aMWk5AV17idnEmjgMuQ=,tag:3kr2o7KKoU4GwAh8pvOg1g==,type:comment] +OIDC_ISSUER_URL=ENC[AES256_GCM,data:wJ+w0GnQACTcNWXgsaaWVCU2HF5d,iv:R6k+E0fYot0kC+daLWUROqlYAlEZGOEEKGiePHYf6bw=,tag:u6ptwpKsR7gcrFfJckqk0w==,type:str] +OIDC_ISSUER_INTERNAL_URL=ENC[AES256_GCM,data:sK2O2GgXn1G4TnBA4j8jEjlfQ4vw4YAl207U9IGQk34=,iv:XqA89gOHbIUlygddNpRu/McL6pibPlsEmdlFV9OF8PI=,tag:/bsevi7+NXjFWfz7MiKOsA==,type:str] +OIDC_CLIENT_ID=ENC[AES256_GCM,data:BpC8mloAIXnShdU=,iv:MZqmeq6UZ8iS5RX+d+suLxNGIpu+hI04QuCO/GD5glE=,tag:F/mhnLP0jsvuV69huovGkQ==,type:str] +OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:fpSa/WfQu7bVScAfo0APRpoGWZgwBX38MAsWhkiNYsN95I6J0twDaFBlOwH8Xkhv4wFz3FbhVrQ2cyIHlmVm/A==,iv:a6NwC4Oltkytu5cLfHiTDx1k/nwucQQ0yfgdS6HYTOM=,tag:CntUqzB0zwYTc0di7iLhjQ==,type:str] +AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:8OEi7Xe3GCL4Emt6I4Tp1ofzk1TycM3L7y5dH+yZPCE=,iv:gj4/n9eQFYbFJXWe8OzOsPUL9cf1/6GQ2nzeCzc2dGI=,tag:oz+v5bsTYynyr/7IyxHIKg==,type:str] +OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:Mk7/kRwo+ynNnsXseSvmaQsvh8Jq,iv:vUcEbL3w6rh94+yVk+G5ZiebJYaVJtTgIckyuiCKp1c=,tag:R0iJNZY0iUjzfshAPuKDFA==,type:str] +APP_URL=ENC[AES256_GCM,data:0vzSZMY99Ywa0+HikgLknFOq9D5L,iv:fxtP8b0ihBPjqtoxsS/LU3M7UjT5WB0Md8dOeTyFIkQ=,tag:c/zR1WNBx9qWBtnKSJx5pw==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3cFhvbktEb3JZdGxMMEt1\nNDcwRXVHbC84NVdwUHlhcFdncU5xRkR6bmxJCmRBL2cwWW9ZUGRUS04ySllNT3hm\nbzFRR2xKbllPSzR1bjVJU3lSL3k2dFUKLS0tIFJpSE1Wc3B3V1kwRlpGOUY0TTFo\nVlJpakhlWDF4amZIOVF4akxzeGYya2cKaBsAXo+d3n4E46X7XCxvuNV69YGW0kH+\ngYDMnYHTj38BHmovffGB8xBByBtIH9NXBM78JabdZfm6Tbm4f0DppA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArNEdzT1BBcHJZYkJJbXFo\nb05tcTJOcWJOV2t1U0NCL3FaK0J4TDY2RmljCjRzUFBWQjZkVmw1WlpyeHF4YlBK\ncmpYcHZ5WlgrNzlDc1RRdzA5NnhOaFEKLS0tIFl0ZTRsZldhMUYzblIrYytvZTRE\nMVVqWGRpNDRTN3gzSlVwSkd3L2x2T0kKDhGflRStRyTsIf9VIDv9UsVwXV0Xszop\nukbJXEJTKEiZt61TGtAjZZg/VyaJ3YlgBq99E6NiSU7Jy1ywxdnOOQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3SFdHN3hUZE83a0V5eHE5\nSmRiUmR3djN1ZHJxUjZOY1lTQ1pxbEpUTlg0CnhFZWpKQjZCMG5xVFZsYU1LRDBj\nV2h0U2tYa3hIa08wUlFPb3ZITVJjSFkKLS0tIFFwcGRudUxJYnpCNHg0ckNXdEhh\nK3NtN1dZQnJScmlZRVc5N1pVQVZZNWMKOhDUZQMIF4RNibiASaRv0LcynMSMsQdP\n6qd4OOp3eGOPRNRYbEJcvc4lkIM3VCtmb+/cDJK54aZN+AWTrn7OLg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1ysddqggsx3h8zkv7xn3z26sjak5pqms6pyqhnky9ukrvpk7es5jsayz8w7 -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBITkRrYVdJQ3ZQa1dsRGE3\nTDVERm5xZDMyTVd1SnN6YUZyOGs1Y3d0dmhnCm0vZzNyWFNEclZKcUxSZjBrTkJC\nRkVxdkl4ajFDWXVKNnpobUsrMUhucXcKLS0tIEo3cmx4TDlrcUE1dVB6SThBdTd0\ncDJDd255UXVWdmk0WkVrSlV3Qm9JRVkKbbZtpztj79uo6Mlt46SqOMxfsVB3GJCm\n/r2ppqms32PQxzsiNxK/t+Pu0PsUlUrlMrOA8rrlancNIIuUhj8zPQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjNUR3TDhITElMdE00MkxZ\ndG45ckdyNklDaHNnYXRWS2ZLZGRwMFhpdWl3CjcyS2lXcWJVRkhrcXlGMU13NXFz\ndHFWcHlGd05kRitsZzdsSlRwR3B4V2sKLS0tICtwSzQyVm5ITUhZR3ZCdkpIWjl6\nejk0enVaVGxwMXdRajFiRVFTbm5sNUUK21DqPtP9y4R6MEuboU8RnNhdQXY0CH68\nAU4vpnQ6lC/pIgBCeI1BCFV+x87qnxiGcaUAVI1t4KC017SFmuAxUw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBudHNvWlp2bGNFSGlIcjlN\nVitBSmVNWU5NQTZrS05XVytHbXZKV2FIeDNNClhpRSsxdWxVYzR0bFNrb09KN3FY\nOEZNSVp2TzRDOGdYL1BNS0Zoa1VtWnMKLS0tIHkvbzhvV2hZcmZLOGtaSmZhTmls\naVVoOSs4U0xFM3dlM2ZZZmlZQlBva0EKWUNmOgjhxTzzY29Ji0+TufoeyL1RjPeV\nx2MnJyp9PhksO5gra1WaxCp6cx+Cm1tm9Jn5EBEX3kHTLxPCGBjYYg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqWmdPRjlPTGZrTmFxcjMy\najMyL2c2RUVHMDNNV2wzZGF1MXcxWFphZVFRCkhlSFFvQXRuQjluWVdBMWtZM0c0\nOGpqa1hkWnFobWllc3VIeGxHOVdVbW8KLS0tIDBFZkgzVnlzZzl2RHowYnRtaUdH\nMWxnWFllcnhyWFM1djM2QnYxTHpBRWsKmJOSjUn1oNtx/i+nKQoBwdYsnA9z0JCa\nQrGhQ5YXP/cRLBaunywJLNdcWCnG1dDi5PUkEEnELHqi1nwd2Ge8ag==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1qn0x93jhqjpqwvx5tgxnrwq5e3vuzur9whrkdnrvapd58esm45rqfkuxqh -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVdGxibXc3dTUrejdMTWVI\nZy9kUk9qK3cyOHBMckZMY3MvWU12VjRxdFEwCm05Q0c3dXNlVDhaMmc1TldXR3JB\nUURWL1BhSU5pRUNaSVdCVUhybWdiRFEKLS0tIFJxeVhtSjFNMVV5YXZ3a1hKaSt2\neE41dEowNzF5NjlEZG1PcWxvQ1dlZ1kKse42a4FgQoyaiOTgBbal3DvWQ6jP2IU1\nFncyLr6BfRbSLspqaUIiKYpJqNjSzQd0AHEXDntBKk+l+g1pMYSnXQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvekJ1ZUxET0tvbkJTVUVF\neDYxdnpZd1pxOGhaRXcvaFp3RFdHSW10a0drCkdZZDQ3bkFsa0loYjhpenl2Q1JH\nRlNEQ0JPQW5mdkNEVzVtOVFFbEFtNW8KLS0tIDlITXViVUxSLzRDVGY2K05MQ1Nx\nYjNGSzlXc2hlbTBBMStDZjVSaDA2cnMKgIOm1HunSwCHRu3ciWG2KRjucfV+2OM3\ntwYtmVdCVIIXYxwRU1HHcjZZEtPUIS5Xbb6XyvZB8CgVBLe8L3ixEg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7 -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtWGpMZk9RdHpTclRiQmFZ\nN3g2VkJwS3RaeEt5amFtaHFhd2IxUFU3OUhvCkcreUpRSkt4SldNb0NNRWlENnJI\nclUzYlFzMWJ5cXpsRHJBK0F4QVphWHMKLS0tIFhQWkpwNUlLMkRzSWFOcEM2eVo3\nd0hDRmg1M1k3bGwvdnA1S1lFYm5kemMKZel5NBTEg4ARxgUyPKuDSJRLlYFyjWr1\nwJ0H8SE4oZrsWuVuROSBf2YYoQJoAHlSzTOQZhD4jVwQmDa46GB2+Q==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEOWVseFNmYkprekdKNmNP\nM3FMSFVqVFI4ZkovcFZjc255bmRrODdkeTNNCjRndVdmQVU3YkxSeTE0aWJnOTRY\nUVZGTmovUEVUUW9xVDAxOW5ZVkdFVWMKLS0tIEsxOWpOWnZYbjVTMlkyR0pJcjE5\nRWdPWUI3TnV4NjYwWEhrU0ZBNUVpaGsK9UGm6eSy4SbwmkaBVoiKVXwVBIHQXRG0\nbq3T5HpOJcCViVuOzrm5mF4pPK9WyVBqpnc3zxPZIb9DfPWLa7+5Eg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_lastmodified=2026-05-20T15:29:49Z -sops_mac=ENC[AES256_GCM,data:GWPR2arEpI8cdOyNlfUOOqPVOCwK1TJhg6uPyYDCzCo/ex9Zsq1pUBFse5qsa7vZDlD3FrOiejoRh2VsTZSPIufye8PJkuRY6vb56YduJqLdFmOBGzW595oHRbEAWfdpEVJMnhgcgGh08yTTA8fai9F8NLHPhX5Y4Us8iSNhAlk=,iv:atz9oslK57IOn8aJTP+71JaAa5N93a+mOszMmJmeSYw=,tag:1ddyZdW5gTW61lNqUT2DhQ==,type:str] +sops_lastmodified=2026-05-20T17:21:37Z +sops_mac=ENC[AES256_GCM,data:usGnoCBtaNFyUVJK+H6PDlODzDBKeO6hagXWhLz0dS/jEIj9z3enfPniIalb6K4ZHBVaENdsErEWRz4h+u8Yc740LLfQZvPzsmY+8F5yyH6tOHYZfYxr8z5/ADmC2UW9cx+Mv/Jl0l9v32UlaMIF1YYGAa6DSz4PevdJYLyjATs=,iv:04ENRUatPgleieKadCcuHLPolVKoWe8YYkEqr6aebWM=,tag:KtkYhNqR9/b6SEgBbiqOgw==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/.env.prod.enc b/.env.prod.enc index c37b7b8..be64af9 100644 --- a/.env.prod.enc +++ b/.env.prod.enc @@ -1,38 +1,38 @@ -#ENC[AES256_GCM,data:bYUmXCw/HzHE,iv:/8kUK9BrKewmpcJOUS+qC3Xc+pp6UGnMVdrX4P3cAUw=,tag:+1a45AVCG0r8kdKGxuwlUA==,type:comment] -DATABASE_URL=ENC[AES256_GCM,data:+75be17RMsZ4SjumHUsU9YEw+CyOlnZV6xoSq7bhZ0Nyt5JoUgBsIxjJp/BvdvzOSeIlYjU=,iv:q8MFpo7ypKmdow1iJWsVNzWoxLTf9th3Rx+3itaDYjY=,tag:tOHxzZMKmtzwEdz5AzK1Ww==,type:str] -SESSION_SECRET=ENC[AES256_GCM,data:vdICH7EmhkJoptYHzXc+MYrl86QN5yMj+qonEoMIhoE=,iv:+/ikEWyPhrtdiQgoSBCmbj5uvbZwcBAiABbJsMDjuHI=,tag:2XZPNcM8BslYoa88EiI53w==,type:str] -PORT=ENC[AES256_GCM,data:xbViuQ==,iv:5BuD782DkszqAql3bLN32byuqTGYS+auHkxmS4/pqIQ=,tag:RPR/2j09ithv1JQGh28OqQ==,type:str] -#ENC[AES256_GCM,data:w5px,iv:9xkFSgqgTVmgwCDTp8uDvbqvdQ+DfAvqYPBxy4CNO1g=,tag:ZuCmPtKc1V9WuOJ/7+cYSQ==,type:comment] -S3_BUCKET=ENC[AES256_GCM,data:QkEdZ6i1SEjiTZY7rA9B,iv:uK+IUDCgv1CqQV30eJ3ijzlILtxiITL7JeWq/CrCD/I=,tag:shy6hBqAgCNVyglSKzAVew==,type:str] -S3_ENDPOINT=ENC[AES256_GCM,data:WxSSQvbQFDfQiRDcsmu28vYPIqIg5jdskKm18iVC7P1dSGMpXJvREIn5NCHfses+EGg4YXizntQkHnTwk0K8Sb4=,iv:1RjCM1T1z93oplpqzg9njgf/HMMMVV/HUbVAd5RUBDk=,tag:vx2Ht/zMG87f0I4i+TGP4g==,type:str] -S3_ACCESS_KEY=ENC[AES256_GCM,data:IojJ8K8lSrg3RpZ4pm70w6E+eLnO8nHUxN/hiSdtV3w=,iv:ciwPn2ibe2zx9/pB7a5Ml5scv3kgGaMEoXCq9y2Ru2k=,tag:DqOwfGQz/8o/i/EaVobfNA==,type:str] -S3_SECRET_KEY=ENC[AES256_GCM,data:y38yZ7zYqXJpMlNGHJbl+esScT7WchqzRi1UTbzDRy6mJUxVPjkar2oOFVwq60GLa0RwjcI452pyocuI02CLQQ==,iv:/rVWxOgIyH08UFV/Ix24CwwjxNsMT+Ya8BvS9YmJBjE=,tag:i4JsaBuGP6/qanqgZiyeQA==,type:str] -CF_ACCOUNT_ID=ENC[AES256_GCM,data:A7mBYmmtU5qbCF9NcBuU6EID2jN0WBiJW+FHobwTsQA=,iv:FQlBf5EOwydioswIv02TCa+ao8gWAib5Gd0AEVU4i0k=,tag:f/o3KvL/6mnX9WyivMG3cw==,type:str] -CF_API_TOKEN=ENC[AES256_GCM,data:x77SghBmaOhYzmbYNlInYucVm2WMWX9ox3jkuWXcMXGu0LnmoKwdSgVReSQtLDTMymOOjHk=,iv:IFIezYUpauUfBf7agySgwoCYI0FCPoL5oZI3eWuXMMk=,tag:QfWM7lEl2pg2Qg9WVaci7g==,type:str] -ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:5MrYqlw=,iv:o9Z7+WsSLM685pOSR5Zazi8vSCpxlGqmhc2o4rqZMuc=,tag:NRA7qCtr00eNFdtiyH0w1w==,type:str] -#ENC[AES256_GCM,data:AVYnHX9LodTgwxgl0e8UPugt,iv:bQ3FZ9cjKOcKWAEJCOSw4Gq/xu76tDy4ZwvDdu9DgeE=,tag:DJbvWH3oVFCRHRVuRxgkfQ==,type:comment] -UNDERLAY_UPSTREAM_API_KEY=ENC[AES256_GCM,data:S9/6DM5edcT6W+6c+IHSbYkBcUBkRo3xnZI8vpWmFzBq8TM=,iv:s7iSamQUHboaai9pSWnJr/nhG9NDokrqcIo8TIfSHLk=,tag:W3CKICzHSokcrSjuda22VA==,type:str] -DEPLOY_HOST=ENC[AES256_GCM,data:GHA9qmtsJD9h3Ydoyg==,iv:i0FBJ5EU4MhYvm+/UsYh44ezhcYMvXZQZ3z/F7AVYcE=,tag:SQ5fXpZx0fbOCrviSCOBPw==,type:str] -#ENC[AES256_GCM,data:zWgopAUsB+k=,iv:UX04P91rzKP5n/k/fbREKJabXyZqdqZoEv1LBASIkBo=,tag:WnqBget1roiEuikszltSSA==,type:comment] -KF_AUTH_URL=ENC[AES256_GCM,data:I5P5O2MvluuLgChKNE6I43cb3T+y,iv:X3BH8cM1A9jNaEA9JRqV2RvhY4WThqXwQQUOggaFcXg=,tag:0zMJ9Tes2FvWwoHvrfAgHA==,type:str] -KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:WKCYLS1wvFTVRtA=,iv:BbFgu6wzg3aZnXAzZ36LT9RazFkkISZNlCHlR23LUvc=,tag:rmYU7NcZHFpKuzMbE7iMGw==,type:str] -KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:FxA3KnZtF61u92r/BRPHiv8MLrgYlQJtH8j63EwIlh0=,iv:QvVblYBIx0tE9qX5aPy9YIUxDFbmifAoafOd4q+Do9o=,tag:1xB8ZmdoNw1HQ+U0j2bvqQ==,type:str] -KF_ACCOUNT_URL=ENC[AES256_GCM,data:tSq0C0vdu4zBBRDUH4o4roTW4e43,iv:cvmtIkcBSQFL0PsLy/9TLhTMqDbr2ky4mBsROwU+CiE=,tag:bXHyZr7x+ximzLafE+aM4Q==,type:str] -KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:AxuXmvn0uv+hnO4ujg6J7TzAKsK3eOePeDgsq8DoILk7v+jbI99CK+GYuzUnw/nbygk4YapKXlytAvCehi99Yw==,iv:1wG+0oa2nKzWT0RIcMnmVHEadGGAGrZ42SYazNHbpRg=,tag:cvmoYHPTcyafmNvo/MiVYg==,type:str] -APP_URL=ENC[AES256_GCM,data:afSaFu9HdwaEfzKVhDyBHMAMNNez,iv:GhTwIYCJQqUsvydi6g/YyWyTppLCRkt1WTP5/M8/cds=,tag:WbExHPUY3UFJ8jddLmh7bg==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmdWwycDVoMDMvY29qU1J1\nSFR1a3lMMXE3TVo4ZmRyZFhyWExjclhPcnlzCnBUdVJDRlZLTnVmRmw2NU5zVG0z\nNkN1cUFaNXRiNk5oeDMzWE9FWEVZZjgKLS0tIGlncUZGSGtvd0tab1N4MlFJNC9i\nYVR6OUFKS282d3psTmIrY3hYUnViRm8KMC+xkTONMmUwNyi9M0XDsOVRyLzA+82H\nEKpjfEkbS8S3yDVithJ6wrixKiovuumGH1cGSJcOnm9pqYGhVi2LrQ==\n-----END AGE ENCRYPTED FILE-----\n +#ENC[AES256_GCM,data:XdphNQbJWvNz,iv:miMmCSRnETMY53cT2/dIJxI4SpnXVRHk42y6881DpHU=,tag:Is1SVbUe9qTPnCHg969myg==,type:comment] +DATABASE_URL=ENC[AES256_GCM,data:HHZBBqv2dyfty81jQEV/xAuF9DzKFdJceELFhJ91eW3l2qEh1EIb3uf3cxbi0RK19C2XnqA=,iv:SVnkIyc6LbBCCaH/zfJ0ODWxmuYTjJ+oqWHvE+quAoA=,tag:nnd0k+18SY76J676huDJjg==,type:str] +SESSION_SECRET=ENC[AES256_GCM,data:ABfGS+27tAXnkUReLWzzdRRy84EPtSoYQW4DXDShOds=,iv:IuBiGRZ99yQXgoyFLbMgveGK1s9afa7sBhDHCoHXSRw=,tag:7wyI6RuBXdZRG2QmBoW0Nw==,type:str] +PORT=ENC[AES256_GCM,data:d/3XPg==,iv:1Py1VHjsNKUXTIlrchejz78QcqijaPbn40KABj5xAwQ=,tag:BVNZElDZpxuPM7WRzplldQ==,type:str] +#ENC[AES256_GCM,data:cCyJ,iv:DaWUuA+fJhgQRi8Nuki/1/OovG+7v7Kh4X/0dBPdBgE=,tag:rbi/BpDcDgv2BylVz9FduQ==,type:comment] +S3_BUCKET=ENC[AES256_GCM,data:x7QN15OzDT2sLKrhNOD2,iv:RlxbdCVIropV7UJBqfeYNnCqYx8GnQvw0f/hjAWcTlE=,tag:e+nEYo1XZQZDPzyxj4sXNg==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:AOC+R5QlSXSBb6l7i6XQRTvpF44dC4hYii/tDawSYtdU4toTQT58Ccc1uK5RLVXakMG5WgTZnSfoRFYr75R9+ow=,iv:9+k4f4TySPN3yCq6e+86FP9f1f+tCqDl2zYIA5tu2V4=,tag:SSig5o584taA1CVTtgjLhQ==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:yrVY8givy+M7JEhYdOAWUCp2PtS3v8vOv5g9M2K7sTg=,iv:n2GfvVN6ltibBhAtOGZN44d8hnXxp2N/chbcGy4Ncww=,tag:OCZRnZsy/k/uW9Osi2F9sg==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:9Kqcbhg3Lg5H6o4ldylXd8WTY8I7eQmu+SuXUwAUrPrdToBkA5swIq+ETB2wYMTQw6TJDFMo+ZxyAuT4FTG+qQ==,iv:8BhJ5vm6+zUZTlJwHtgSdMdFruAAofkJIH2GiyK/MG0=,tag:iFeNjOBL+e6lrNhqHopHZQ==,type:str] +CF_ACCOUNT_ID=ENC[AES256_GCM,data:bv6uGGPZw+xYibfJm+iQoCnT/V6dbaedOAJCmDfFiGc=,iv:1kIUNSn0CjsXDb6YyebWSfgQ9OjLWR9ql/bGWX3eo9M=,tag:2xdVrzJ9ZPg2c9VXIEeF0Q==,type:str] +CF_API_TOKEN=ENC[AES256_GCM,data:X1nKWzBSSDm+sXypmCJYvCpMLrFxOPswm/4JaDB4NZbgXvCqWXNXKWRgsELW2B/kTlTItIE=,iv:liSv7VmAFpmpF97QyJy1Mpl0+Wii/CsPvvEY8BK2zqw=,tag:+waP7J2b/IorS5S5txIzFg==,type:str] +ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:SatzlBA=,iv:+7YHCaqZlO4Z3zEXuCE9irIZAjWwPIj8aB5ys/hU5mo=,tag:QwopvorSvgVk4XY4qoWERg==,type:str] +#ENC[AES256_GCM,data:JcTBwIafBHgjmk9kyAsbX9UD,iv:oi7tGmP976cKUIBy3z4rwo1H0LjDUt5AC8JQ8Q+DztE=,tag:49cFBHRMqGnCxXMtZDLvZg==,type:comment] +UNDERLAY_UPSTREAM_API_KEY=ENC[AES256_GCM,data:GHFb8ZHWWNTV2HFZixxM8owvcxlVcG9yFnvwkxFN8d/rb8w=,iv:taaR1xnLPte3GQ6W2m8iv1VrhNWio4y8tebRAZKqqVk=,tag:L7mZzCzg8uxVJIE5nhf/wg==,type:str] +DEPLOY_HOST=ENC[AES256_GCM,data:DMrGYnYM3+Yz0W/eGQ==,iv:8/n2P7bIc7SgdlYVI5wT2vjXIkEmFae0hAhcM3nOBfc=,tag:eRkaNlXUF4FHdk9LplsMJg==,type:str] +#ENC[AES256_GCM,data:fNxrW0Hxfeo=,iv:VnabMB0c+3oupoDZP+/204mX7ruufWm9o2dhMi37ISU=,tag:iSEZUC8krBp15HVU7eTKKg==,type:comment] +OIDC_ISSUER_URL=ENC[AES256_GCM,data:E8G41RqOgD1GCoJo2Jav3h8dW8z8Rs9tuLdCOOv+1CXI,iv:RFo8KmQaLN7UEy9iZqsOR1urhtZSUG3LJIqT3NzfE18=,tag:n2qUG1Tr/9K9k5K3ujtf1A==,type:str] +OIDC_CLIENT_ID=ENC[AES256_GCM,data:FkSZuDNaVzdx7EU=,iv:Pdptrn/gh6E0wAcl/DhJUktf6YYy4QfvOUZBmpEvz9Y=,tag:hfbRC1NuVXPpelvdIY//VQ==,type:str] +OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:kK57keW+uGy2n8XruiccJ98amTPN3w3KT2R53QP4CMZpBg0/46YGa/71i7uoRRE4pcE5iUMQ/hwK/57YLfIIHQ==,iv:MzR5qVc4yc9KhENyuGI7/+S0hyJrcLggzYpuErF6/xE=,tag:3NidEpl9YquOnYDDQ4t4dA==,type:str] +AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:gq00GfRdO4ng+flhtCIYVvdHBe2ypRKoZzt3jEr0K6V7UFguD6hOXNHEzKV7DPB/P+lVAaGW5rRckBdUn4mvTw==,iv:CQ4NqFrWJNp2OH8MmQSZhUhBFRk7a3m48TnWDLijBvI=,tag:CIAV4VUjXG9YpE2WIxKEtg==,type:str] +OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:wkCtGLqn1X8sM3vc5pTJ6gixX5T5bFyB5Fq8X3npEAzstu86,iv:9jeMEQrdtwHCDkqovTIocnQ1uHj9RaBZJ4fXRF6Fxrs=,tag:Z4kIaG9HlnC1SH6KRyDG5Q==,type:str] +APP_URL=ENC[AES256_GCM,data:5Efq+NCqSCb6MS2Ex+jiWHPMGwhd,iv:r1t8AiEPUmb2bGvN9KBR27IO+IfMU0+STXZIbRQ5Ycs=,tag:G8doB2h6IqdlhRL4EPimlA==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxWnVGVGc3Wm9VQWQ0aTJy\nalhIaUw3MVgzT3NtNldwY2VhN0c0WmtPbWpjCjBVSm40L05nQjg3dUFZUFhEMXlh\nWWxET29vRXh6ZXRYYytIbFRqalQzM1UKLS0tIEQ4NHNuVWVaRFRUak9NVm9sM3k2\nbGhDMGpxejNYTVlrOEZNWjBUNFZYWFEK+VAtz9gor6irMB14YmFt1e2xJ+ejCI2u\ng/I0IG5pK1mLXJeAPSFwS8POhgckp98SbsyFdyXXKdy3G6InhqVung==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3S0c2WjRKNVVPbUZsbWEx\nbUZHQklSbzd1MCtYckUwUkxJSXJrSUZiclRBCk1lWTJUK0hRak0rME1YU1NkMitt\ndW9nSUdWcmI4NkFDY3NNMk0yRHgyOFkKLS0tIGx2TzhFRUdpbDVlQ0hUNWVuQnJF\nZSs0T3JiR3orNHJaTmRqRDRUVFNJQUUKLZjNT4VZOr6CM2mt2XzewT9trgRgflRt\nWyCI+EKQXKx8zhEsrdNMKdOvpVI6rmtYDDjupLfj0c9EpGS03w7RVQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0enFya3hxanJqUzEwdnEr\nOFZ4UjlrUGV4TDBqU2QrSytndjZPRDB0QmprCk9zWDFhVEdXYVZKWnZZWW9TMFd4\ncGRLZVJpRGhvU3NvZ1VCYnU2S3hTc2MKLS0tIGlSeVdrZG52QUZGWUFHVE9BeExC\nRmJkNlNwRGpIUGtCbTB4bS9PLzRTWFUKImy1vyAe6n7QujWv66Rlm9/6t7BNSA+z\nPLEAHv9aHCEyAbIpuExB0+0jUxU4jw9/U8SPOquyTzdxP+Zet4QoOg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1ysddqggsx3h8zkv7xn3z26sjak5pqms6pyqhnky9ukrvpk7es5jsayz8w7 -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlSGd5WHgyNWp3ZFdaYldz\nbFBhRDFxNFprbTJxNldYbU1GcGFGUmFodEVVCkFRa2xGL0Yvc2hWZTBmTmFJK0xr\nQ1V6RDNzT2xBTjVPL2kzNDVTWXhOMTgKLS0tIEZEOE14cnhtWnRvcllwU1JvUFdX\nNTUrVmNPVXBYM2RzK0c4RUxMdDJ3MDAKEB4/7RH7yDE9wIW1CO7wkjtTqWJ28wNe\nRFYq4UAg/C/sh7R40wPM1MlmHeOLmHud8bD+awNSMriq07WOunBARQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsQzQzdVZTb3VtY1B4Q3BF\ncU9XU2pFTE51enRXcHJPZ3liS1VHMFFwN2hRCm1OSUZPUWJyNlZURzltNE85TTRZ\nT1FNcXllYk9DL2FLM0V2QmNCSS9CZncKLS0tIHZMeFBMeWU1c0FUSUc5Nm8ySWMw\nOFExaVphdkU3R0NMTGNrQTc3ck0xYjQKZG2YYTf3NTC0JdcS2Sws2JOYS4h9z6OZ\nzaciJlLWtvciUAU0/eFBXyMAShX8fxbgyO1MxKc3ZFYECjBo5+DyBQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoZnAyRDMxVFFiM3hnR1Fq\nemo3TXhnTU5ITTBpRllQMk5NajlHTUNSbEh3CkF2V1oxVWV2UTIxc0pmaWZXU3B6\ncGwrQ2VTWUVWRmpuMXU5bEtGWDlWdlUKLS0tIFhmUHdHZTBVbnpRR0w5SEVtbE8w\nbXRxTURGRWo5QnBwM0FJTmRmZERGNHcKbbyDOyQWjOwgF37GjGIiqg+4ZkO5KK5h\neF7y7ndM271RGY90xkDPFuPYHjB+L98zXrpDDczBzaNkH8cRsKDI+Q==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpclFaL255dFViZG1TdzJW\nQWNDVENuT0xENk52KzU5THZzMytIRVVMY1NjCms5VHRtQXY1TGpNZittOXFJT2xj\nQXBjaVI1ZktVTlZVd09PWFlPQzZqalkKLS0tIDdkazZGSitaQVZBTU96YUpJRDNI\ncGIvMTNUdFZVcjBqb3VtZEZYcDNXbGsKxFSX6W4ND9bg9TZnVU5VoNvkA7pBOIjL\nnCzn9ayrGFsjHtbh+NSEu9NqZwgZE6TNHm+2i8xvj94vxJGeZsKJew==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1qn0x93jhqjpqwvx5tgxnrwq5e3vuzur9whrkdnrvapd58esm45rqfkuxqh -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0VGpZNWE0c0tIc1dFMlNE\nay9EZWpIaVlncklNZHFKSVgzbFBMQ01tWmpBCkt2SXUzbXNjWlhobGdPODBnSGQv\nNUFQY05VWTBVbnZkREdmdTEvOXBaWFUKLS0tIDJ1WHh6RjdLanZyRWdKZXdmZ1BO\nSC9OVHVuK2JrZmVtZHJITUxzaGdSSzAK9A8/n2Ajmmt4H4tIodWh4mW2H3L/UCGG\nsCOmqGzmipWu9iYOOI1YUuBCTcz4NwnOVg1xAnHpXiXlNRXzgwlcaQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGb2xqL0w5Wk9jbkZaRTFO\ncU5Sckk1ampJR3lWaDZKbzlqMHdIVGR1OVFNCmhyMnlvaEhxcnJLVjBVN0d6NXNP\ncDlyV2JpbEYzRHlIWnF6b2NOWWI3U1EKLS0tIFlPZnphSGRGVEs2eHlQVjJYM05p\nd3BtOVVKTTdUUW5Kcm53MVBqeFRYQXMKn+z4lheg487g+o72qrpIiXINqTrvcg2u\nOjdwEKyRCHEmxzGCqtG6egJ4Nrry4bjOzEBrEYT7bQKco/2Mg8JRuQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7 -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAveTRpUmF0OHhiK2g3RS8v\nS3pFTzk4UlVwUXJHNkRoWUlnZFVVYmt0WVRRCm1mMmhjcnZRMkhDSEg3VVJqdGNx\nckhDMkZWTnErUU53Nmo2cDRsUjdxMjgKLS0tIDMwL3dUejhLbXpPTStxTmEzNDcr\nU25UYjJLUXRYdUcwRGp4ZEVML2svc3MKEsv7qIrbE8zOX33u3yLHptRpWi52+quR\nfNr+uAvISSwM10iR/8hyTv2IBgsdnKPiuy65DRZjctiEIr8k4S9Y+Q==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtdjBXMTA4SmRGWHRBdWdl\nUlBTb0s4NHA1Y0Q3L3M5SW5oMGhMZWh4VlI4ClJRTmg1ZEpGeHdwRXRVaGNiajZm\ncytqZWVCcW44OUh4L2VuOHI3UElyeGsKLS0tIEZucE1oOXYxN1FnREVmZlFLdThx\neFRwM1g4d3lUejhydUFNRFYzeTRSTUkKZB/gb6l82oxm+qKhGG6RckL4HiBT3Nph\n6xnAmiJ5ZkGc1K+UaigjkhrLz05giT9wZyhS+MthOdsSorGZunickQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_lastmodified=2026-05-20T13:47:02Z -sops_mac=ENC[AES256_GCM,data:mQcnXY1ZnC6jXvKu7qWx2iBgMeqKQF8wEzXhPLGBp/qXZh6x0wGAUURz9xNKXjM2X3Z3DHuzJNG0PssBUlJWbzTGZjqFfjJviKTI0fTO0umTQylml7CV3WhNJiAAo8HRgv1hjHOxU2YQ+JxOE7LW6z7C3snTnCWDN31O1TCaJps=,iv:DTmahNH+esURR5Fajxb9VOxnHFV1AbS/JsSvJukrlOU=,tag:1N7khs/V/efPYDHu4k0SfA==,type:str] +sops_lastmodified=2026-05-20T17:22:26Z +sops_mac=ENC[AES256_GCM,data:bbAxKHABz674Nmbcnl38xu+US8iGYCoTkUYRtRrAdwunhf2BGlnl16+1k2AjJIm+t9W9zqKLBZpYQ9Ata2erzJAyfTlnW7XvInpIsV0SHeo+U7BtD3XDfsW7qu7JWO4ZRTaUgQY5+adxatr19LslfNI8+On37PpdxiSN16kMYdQ=,iv:VKQS450DsuO+sSyqeDYDGTSvJ+8vMSWeglzwbiaOkPU=,tag:QRnZtVhsEl9SxvvAfWuMig==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 From a4f4add6a0a9602c77ff349c963c3434af1c2d0c Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Wed, 20 May 2026 13:24:56 -0400 Subject: [PATCH 3/4] fix prod env --- .env.prod.enc | 60 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.env.prod.enc b/.env.prod.enc index be64af9..2454674 100644 --- a/.env.prod.enc +++ b/.env.prod.enc @@ -1,38 +1,38 @@ -#ENC[AES256_GCM,data:XdphNQbJWvNz,iv:miMmCSRnETMY53cT2/dIJxI4SpnXVRHk42y6881DpHU=,tag:Is1SVbUe9qTPnCHg969myg==,type:comment] -DATABASE_URL=ENC[AES256_GCM,data:HHZBBqv2dyfty81jQEV/xAuF9DzKFdJceELFhJ91eW3l2qEh1EIb3uf3cxbi0RK19C2XnqA=,iv:SVnkIyc6LbBCCaH/zfJ0ODWxmuYTjJ+oqWHvE+quAoA=,tag:nnd0k+18SY76J676huDJjg==,type:str] -SESSION_SECRET=ENC[AES256_GCM,data:ABfGS+27tAXnkUReLWzzdRRy84EPtSoYQW4DXDShOds=,iv:IuBiGRZ99yQXgoyFLbMgveGK1s9afa7sBhDHCoHXSRw=,tag:7wyI6RuBXdZRG2QmBoW0Nw==,type:str] -PORT=ENC[AES256_GCM,data:d/3XPg==,iv:1Py1VHjsNKUXTIlrchejz78QcqijaPbn40KABj5xAwQ=,tag:BVNZElDZpxuPM7WRzplldQ==,type:str] -#ENC[AES256_GCM,data:cCyJ,iv:DaWUuA+fJhgQRi8Nuki/1/OovG+7v7Kh4X/0dBPdBgE=,tag:rbi/BpDcDgv2BylVz9FduQ==,type:comment] -S3_BUCKET=ENC[AES256_GCM,data:x7QN15OzDT2sLKrhNOD2,iv:RlxbdCVIropV7UJBqfeYNnCqYx8GnQvw0f/hjAWcTlE=,tag:e+nEYo1XZQZDPzyxj4sXNg==,type:str] -S3_ENDPOINT=ENC[AES256_GCM,data:AOC+R5QlSXSBb6l7i6XQRTvpF44dC4hYii/tDawSYtdU4toTQT58Ccc1uK5RLVXakMG5WgTZnSfoRFYr75R9+ow=,iv:9+k4f4TySPN3yCq6e+86FP9f1f+tCqDl2zYIA5tu2V4=,tag:SSig5o584taA1CVTtgjLhQ==,type:str] -S3_ACCESS_KEY=ENC[AES256_GCM,data:yrVY8givy+M7JEhYdOAWUCp2PtS3v8vOv5g9M2K7sTg=,iv:n2GfvVN6ltibBhAtOGZN44d8hnXxp2N/chbcGy4Ncww=,tag:OCZRnZsy/k/uW9Osi2F9sg==,type:str] -S3_SECRET_KEY=ENC[AES256_GCM,data:9Kqcbhg3Lg5H6o4ldylXd8WTY8I7eQmu+SuXUwAUrPrdToBkA5swIq+ETB2wYMTQw6TJDFMo+ZxyAuT4FTG+qQ==,iv:8BhJ5vm6+zUZTlJwHtgSdMdFruAAofkJIH2GiyK/MG0=,tag:iFeNjOBL+e6lrNhqHopHZQ==,type:str] -CF_ACCOUNT_ID=ENC[AES256_GCM,data:bv6uGGPZw+xYibfJm+iQoCnT/V6dbaedOAJCmDfFiGc=,iv:1kIUNSn0CjsXDb6YyebWSfgQ9OjLWR9ql/bGWX3eo9M=,tag:2xdVrzJ9ZPg2c9VXIEeF0Q==,type:str] -CF_API_TOKEN=ENC[AES256_GCM,data:X1nKWzBSSDm+sXypmCJYvCpMLrFxOPswm/4JaDB4NZbgXvCqWXNXKWRgsELW2B/kTlTItIE=,iv:liSv7VmAFpmpF97QyJy1Mpl0+Wii/CsPvvEY8BK2zqw=,tag:+waP7J2b/IorS5S5txIzFg==,type:str] -ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:SatzlBA=,iv:+7YHCaqZlO4Z3zEXuCE9irIZAjWwPIj8aB5ys/hU5mo=,tag:QwopvorSvgVk4XY4qoWERg==,type:str] -#ENC[AES256_GCM,data:JcTBwIafBHgjmk9kyAsbX9UD,iv:oi7tGmP976cKUIBy3z4rwo1H0LjDUt5AC8JQ8Q+DztE=,tag:49cFBHRMqGnCxXMtZDLvZg==,type:comment] -UNDERLAY_UPSTREAM_API_KEY=ENC[AES256_GCM,data:GHFb8ZHWWNTV2HFZixxM8owvcxlVcG9yFnvwkxFN8d/rb8w=,iv:taaR1xnLPte3GQ6W2m8iv1VrhNWio4y8tebRAZKqqVk=,tag:L7mZzCzg8uxVJIE5nhf/wg==,type:str] -DEPLOY_HOST=ENC[AES256_GCM,data:DMrGYnYM3+Yz0W/eGQ==,iv:8/n2P7bIc7SgdlYVI5wT2vjXIkEmFae0hAhcM3nOBfc=,tag:eRkaNlXUF4FHdk9LplsMJg==,type:str] -#ENC[AES256_GCM,data:fNxrW0Hxfeo=,iv:VnabMB0c+3oupoDZP+/204mX7ruufWm9o2dhMi37ISU=,tag:iSEZUC8krBp15HVU7eTKKg==,type:comment] -OIDC_ISSUER_URL=ENC[AES256_GCM,data:E8G41RqOgD1GCoJo2Jav3h8dW8z8Rs9tuLdCOOv+1CXI,iv:RFo8KmQaLN7UEy9iZqsOR1urhtZSUG3LJIqT3NzfE18=,tag:n2qUG1Tr/9K9k5K3ujtf1A==,type:str] -OIDC_CLIENT_ID=ENC[AES256_GCM,data:FkSZuDNaVzdx7EU=,iv:Pdptrn/gh6E0wAcl/DhJUktf6YYy4QfvOUZBmpEvz9Y=,tag:hfbRC1NuVXPpelvdIY//VQ==,type:str] -OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:kK57keW+uGy2n8XruiccJ98amTPN3w3KT2R53QP4CMZpBg0/46YGa/71i7uoRRE4pcE5iUMQ/hwK/57YLfIIHQ==,iv:MzR5qVc4yc9KhENyuGI7/+S0hyJrcLggzYpuErF6/xE=,tag:3NidEpl9YquOnYDDQ4t4dA==,type:str] -AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:gq00GfRdO4ng+flhtCIYVvdHBe2ypRKoZzt3jEr0K6V7UFguD6hOXNHEzKV7DPB/P+lVAaGW5rRckBdUn4mvTw==,iv:CQ4NqFrWJNp2OH8MmQSZhUhBFRk7a3m48TnWDLijBvI=,tag:CIAV4VUjXG9YpE2WIxKEtg==,type:str] -OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:wkCtGLqn1X8sM3vc5pTJ6gixX5T5bFyB5Fq8X3npEAzstu86,iv:9jeMEQrdtwHCDkqovTIocnQ1uHj9RaBZJ4fXRF6Fxrs=,tag:Z4kIaG9HlnC1SH6KRyDG5Q==,type:str] -APP_URL=ENC[AES256_GCM,data:5Efq+NCqSCb6MS2Ex+jiWHPMGwhd,iv:r1t8AiEPUmb2bGvN9KBR27IO+IfMU0+STXZIbRQ5Ycs=,tag:G8doB2h6IqdlhRL4EPimlA==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxWnVGVGc3Wm9VQWQ0aTJy\nalhIaUw3MVgzT3NtNldwY2VhN0c0WmtPbWpjCjBVSm40L05nQjg3dUFZUFhEMXlh\nWWxET29vRXh6ZXRYYytIbFRqalQzM1UKLS0tIEQ4NHNuVWVaRFRUak9NVm9sM3k2\nbGhDMGpxejNYTVlrOEZNWjBUNFZYWFEK+VAtz9gor6irMB14YmFt1e2xJ+ejCI2u\ng/I0IG5pK1mLXJeAPSFwS8POhgckp98SbsyFdyXXKdy3G6InhqVung==\n-----END AGE ENCRYPTED FILE-----\n +#ENC[AES256_GCM,data:TS4j3twMFoZh,iv:67VeU7kyhgPK0TptCh60HYxLTe56pK2862zEboOr26M=,tag:4JiTyCGuL3fqU7LlqtYzkA==,type:comment] +DATABASE_URL=ENC[AES256_GCM,data:9c2V8Ezkdb4sXC+mrz6BhrtesdQ7pnA5PjrffBIRBHNfPSkZft1HrIGAEvgde25jFeXt1Ck=,iv:LSzvRX/PSto7ROQ5TXCM3G5pg/Qm2RiQnX1ZOw2Gc3Y=,tag:OkYD9v2+pZpQBNQay1Cxyg==,type:str] +SESSION_SECRET=ENC[AES256_GCM,data:W2tBSuCsF69dFbX+uYYa/5H6KKnVWYl3Bibez0btM8g=,iv:DE3NdgXvRJbd6Xvnu9ZVCmOhzbANvvw9X/DkcZAGGHw=,tag:gs+2u6AmFGISNW+3mCjSLQ==,type:str] +PORT=ENC[AES256_GCM,data:ZbjWrQ==,iv:LNymntIzDLZO3Y4+ROk0StHRoD73LP5ympAFIeihC9s=,tag:PiiFCezINAJjqOAyOFM8Rg==,type:str] +#ENC[AES256_GCM,data:T5oV,iv:rsC4Pk0F4XQ2lVXl+mKL+hrnBYQAMuWuaJPgvfleXPg=,tag:eC85ejuc/0YgfrrtIxCXSA==,type:comment] +S3_BUCKET=ENC[AES256_GCM,data:uguPyh8NpEh5UQtgeBBd,iv:trAHyJZ0JDG4d4G2mudHLcvsq3Mimlw+GnGvf4DrbmU=,tag:Cs1RVRextKulmyknzr6qYg==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:XmbQiGK/QP7SEazjUrAZWX0daHnXNWNmWqzbSzh5goFBkR62aKB9IefC8wtQ9NTySQlNv+kuisVOHQJ8/SFvVx4=,iv:AIur7LSlRhHwcPYnuasW89dt/Zr6I/qtmwVJfy3gHdE=,tag:IuTmLbTUmIw8fKm2xCKXiQ==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:3pnGOvtCorluqErK6kZtUz7cYJQaUsJgl2X7mqZDgg0=,iv:L2D4WRE45Q2L7S6WY0tffaS9KRqNTvXZLtwJMU6tHSw=,tag:ZOpEFvpihR0tcsQ4WVSDsg==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:e+wGR1nZbyDZ8J/inqOooffPYlFHxa7mS1XujmP45LZx704GIs6RLZ4v1WPkjuoq1NoGV3HPe3gk5u2vU3vz5w==,iv:6TtKzHjhQG5xCnhLFPmp/CEIlBGB6sZIEkDjeIU+OWE=,tag:O0biF8VDHRYz7YBy2OgDAg==,type:str] +CF_ACCOUNT_ID=ENC[AES256_GCM,data:OO7KrYgpLwbj/VWx+6bSCL4qo7LhjcjNPFQfl0Gg0KA=,iv:h1vVwXbxdRBbU+FbGpdZrbFarKdYzDn+aUS8fvLhyyc=,tag:y80M9a7h45UIrCFDO8h/Ow==,type:str] +CF_API_TOKEN=ENC[AES256_GCM,data:iJdQyRQScMxcrcU4CEQgW7+qKWmb7s93QRcYgh291Qzf3NC59ExxnOpBU++ZV6LFWjUDdUU=,iv:xztI5eMbEk1WNw3o9zrjbi8jJ8NtAuMJl9FbWTUsaTE=,tag:1lOSYrww6hZ/WjWzSTW04Q==,type:str] +ARK_DEFAULT_NAAN=ENC[AES256_GCM,data:8xsRwK8=,iv:tCaPuOCVI5aoxnwxTKa7OMqGFQD+W8ll76Q8Srw9gt4=,tag:C03OWlm2jOOcSrCGNn2whA==,type:str] +#ENC[AES256_GCM,data:+BWEFM6L6aD1rMG+g/Fypx73,iv:7ez6bhjdrh4suF7FnzeqbV9YaGWQjdzHwrg5C12POdA=,tag:lPwrgKv9U94oei4l9wnPsg==,type:comment] +UNDERLAY_UPSTREAM_API_KEY=ENC[AES256_GCM,data:gg8Fm3ngcXfMSqnravyzd4F7nVaOfjF0kLq6akC4c41pqoc=,iv:3tjfQSU3RoapeTpZ/26b/cRyXEET1/plU/zAAZqBUek=,tag:SchlwcyYhcStKabyhHXHsA==,type:str] +DEPLOY_HOST=ENC[AES256_GCM,data:R3wMJvEgnZLS2sUzJQ==,iv:ILujgHggz/XnjDQajRC5UPSdiKWGPqDtpG7MxbfycBI=,tag:gjmxSKYwdxKt0PZzoxE5Uw==,type:str] +#ENC[AES256_GCM,data:kYe5O3MvtkE=,iv:SsygxrvIPlPYGwdmgghhWQEmhYXfHTqjYEcqQBwdBn8=,tag:c7IPDL4NUvHDY/HvALw4Qg==,type:comment] +OIDC_ISSUER_URL=ENC[AES256_GCM,data:6EoGyzM6mMjE7gkG2VHCmf7wjfoUAebL2xNX2b4AlohZ,iv:J9UCzieBnS67x1SF99LQxNXRFtXnzAsx/nKgHZAsEtg=,tag:aXKXAXOhWUiqts1j/85y1A==,type:str] +OIDC_CLIENT_ID=ENC[AES256_GCM,data:YJZQDDDbW3SCUsw=,iv:i02lGIh0O1Ni0pnQzFQwdA4yGW7DMaGBern62zEurdc=,tag:HyQbPDlIRPkodn9mMixrgw==,type:str] +OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:CKbYqzPEHYnffkosxGMzbaUQrjmAr1mbpkEObM/COucG+nr35sJ0eZCVUgq9rh4fSXh7XX8R5MUGrjSamTZBtg==,iv:2JwORSKnfi+7apoQKkxYAxwBK0WjWLSADDowRExcRik=,tag:AYwc/V95qREMocDAo7cMSA==,type:str] +AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:T80WuhiPSGm1FMNsvrM+4wRINOSvxhxoVMXeybBOjiNrKb4V6PxX54R3rweyqe2AJNdYlv2zaiQVYX3C3cvQ9Q==,iv:ycYFuwUGrSJGXw9gwxWQrG4Xj1DZfBW0SoUaBRopBAo=,tag:+FZQLFaWnwQSBZQUynAwxw==,type:str] +OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:bqv5vgziTgy55XFOdtYm+qPzmFNrs7Xn4kTxCPzcHy7ikbSJ,iv:+9jMnI8mQgZm9c2+5BQCpO8fkOdDoXj/4WifPjH8LHE=,tag:1fHxiv8ST6DOYCnw2kWn3g==,type:str] +APP_URL=ENC[AES256_GCM,data:GgfRNwfNJ7O3mCanfl+EyvDT8bqH/qE=,iv:4soW9wM1vyXRMWSSU9ZuGKwwXWr0/BNQt9eU+K7Ahjc=,tag:PfFGv8qnIOufUXfORF5D3w==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGN01qQVdQTHlHcWJHbHNI\nUXVnaE1EcEUxRG12UGx4YTU3SmdBUmhuYmwwClkvaUZHelB6a1FTQ1YrbzJia2dR\nRkRaQUJJejhMeUJLMnpqUmRTTVBBbm8KLS0tIFZmNkcrTWVZUnpaMGU2N3VsOVc2\nNTYxemxzQmxxbDhDeTQybnRxd0NrQzgK6AwczPTgFWItfLq6+jc3d8AwHBJ6UfOo\nnJoEcNu1hjcXWxb3PLeEVer9ICR1VcYBZEtyOZ1dPKgb3OLOjTym7g==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0enFya3hxanJqUzEwdnEr\nOFZ4UjlrUGV4TDBqU2QrSytndjZPRDB0QmprCk9zWDFhVEdXYVZKWnZZWW9TMFd4\ncGRLZVJpRGhvU3NvZ1VCYnU2S3hTc2MKLS0tIGlSeVdrZG52QUZGWUFHVE9BeExC\nRmJkNlNwRGpIUGtCbTB4bS9PLzRTWFUKImy1vyAe6n7QujWv66Rlm9/6t7BNSA+z\nPLEAHv9aHCEyAbIpuExB0+0jUxU4jw9/U8SPOquyTzdxP+Zet4QoOg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjWEN5L1BtRloyWHNzZklN\nL05uazJLTWRGS0pXQVMvQU5kd1p6K0RDQVM0ClJBd0lsQ1N4bmp0OTMwRmpNQXAr\ndTd1ZEZqaFhoYzU2RzRYN1Y0aHNXSEkKLS0tIG1uMDZvRWF3VnN3ZXZwb1VwVGNm\nMmd6TmxxeUkxdi9CQkQ4Wm5qQVVya2MK9TRB9XCAtavdWC+rQnGd9MAfn4KenAQI\nludEN39eo296ZzAUE6WJ5/7y4U4CcpCyKBPILKdCjpeNRYQ6pmR8wg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1ysddqggsx3h8zkv7xn3z26sjak5pqms6pyqhnky9ukrvpk7es5jsayz8w7 -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsQzQzdVZTb3VtY1B4Q3BF\ncU9XU2pFTE51enRXcHJPZ3liS1VHMFFwN2hRCm1OSUZPUWJyNlZURzltNE85TTRZ\nT1FNcXllYk9DL2FLM0V2QmNCSS9CZncKLS0tIHZMeFBMeWU1c0FUSUc5Nm8ySWMw\nOFExaVphdkU3R0NMTGNrQTc3ck0xYjQKZG2YYTf3NTC0JdcS2Sws2JOYS4h9z6OZ\nzaciJlLWtvciUAU0/eFBXyMAShX8fxbgyO1MxKc3ZFYECjBo5+DyBQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCcCtkYTNvUm51TmV2YWZ1\nRitKQkw2RUJwdkltYlhvUWpNazI5TUJwcnpjCjNrTms3ZFVXUGhlaXBlamRETXBE\nN0Vud20wbU1FUXNmQVp5YnFVZncwalkKLS0tIDdPQk5KbUo4ZmRpa1AzWk9pRlFB\nNU8zN3lLRnNpQ3RMRnBXWGlOTTBWQzQKXtQDtLS0TNZiW46EsrW6tnbxXc773oMW\nH8BWDxRavEpiRyqkH1EKjEBzTA3xKfwaG3RT6d6tUY6g9PnuL0BkoA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpclFaL255dFViZG1TdzJW\nQWNDVENuT0xENk52KzU5THZzMytIRVVMY1NjCms5VHRtQXY1TGpNZittOXFJT2xj\nQXBjaVI1ZktVTlZVd09PWFlPQzZqalkKLS0tIDdkazZGSitaQVZBTU96YUpJRDNI\ncGIvMTNUdFZVcjBqb3VtZEZYcDNXbGsKxFSX6W4ND9bg9TZnVU5VoNvkA7pBOIjL\nnCzn9ayrGFsjHtbh+NSEu9NqZwgZE6TNHm+2i8xvj94vxJGeZsKJew==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvbU9wVnlZUjBDQTVQT21i\ndU01TTEvUzlPTzZGMFVwc0M1bkkvNUU0YkNvCm91NHdGWTVvU1JaOWRmQVJ3cHBo\nMXdmc0syVncvZzNKMlBCbHJDdm5ZblEKLS0tIE5lOWJvMDhyM01VbU0wbGNEc2xI\nVkZrbER1LzhWQ2swS0ZTV1FiWGx3K0EKIoezkGxEUfWDjlkeAnKH20UxsDFQJ+1b\nzdcghmt6gnzIxR3FWSCFHHKAzfi3rL8QlkW3Ro5UBoAEEDWy7nvz2w==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1qn0x93jhqjpqwvx5tgxnrwq5e3vuzur9whrkdnrvapd58esm45rqfkuxqh -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGb2xqL0w5Wk9jbkZaRTFO\ncU5Sckk1ampJR3lWaDZKbzlqMHdIVGR1OVFNCmhyMnlvaEhxcnJLVjBVN0d6NXNP\ncDlyV2JpbEYzRHlIWnF6b2NOWWI3U1EKLS0tIFlPZnphSGRGVEs2eHlQVjJYM05p\nd3BtOVVKTTdUUW5Kcm53MVBqeFRYQXMKn+z4lheg487g+o72qrpIiXINqTrvcg2u\nOjdwEKyRCHEmxzGCqtG6egJ4Nrry4bjOzEBrEYT7bQKco/2Mg8JRuQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWU1drSWRUbEVYSVNUTzFy\nM1JJbSthM3VpU1Y0ejgzbjNUSEhYMENGRUNZCmowZE13TUtYclVmMS9VenBVQXp0\nd09hM0lsa01Cd0lzTDlCRDduOWd0aFEKLS0tIHI0M0xOSFhndW5XdUJJV3cwV09X\nWUx4T05INWRvU01OU3UwSUFxcC9nM00Keb2AeDtqamChWI3PAYxLDT3qZhmCf8Q9\nZi8Qe6LJ6a8C6wsFN+auPy7bIJdmVYcfoHFgsRVdyEXx9bcj1+hIGg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1h86dek80u5t677tsparz395uk3zvz4yuj9m5t2v2nsdfsvyjmafsra5yt7 -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtdjBXMTA4SmRGWHRBdWdl\nUlBTb0s4NHA1Y0Q3L3M5SW5oMGhMZWh4VlI4ClJRTmg1ZEpGeHdwRXRVaGNiajZm\ncytqZWVCcW44OUh4L2VuOHI3UElyeGsKLS0tIEZucE1oOXYxN1FnREVmZlFLdThx\neFRwM1g4d3lUejhydUFNRFYzeTRSTUkKZB/gb6l82oxm+qKhGG6RckL4HiBT3Nph\n6xnAmiJ5ZkGc1K+UaigjkhrLz05giT9wZyhS+MthOdsSorGZunickQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSclNPdDJGeWlCSzJtQi92\nYVlaVFhGd2krM2J1VEVqVSt0REJwRnI2ZmlJClBzTCtSZFBvMWdBcU1IeXFzaThz\nMFRCazJmTFQ4WkNPRU5xU0VqOEJZbkUKLS0tIFF5eitRekJUSDBhZmhsZHpMdHpi\nK0N4L1FqazJOVUNYaUIxL0dMMm9kZjgKr3PaMpDWqnt0J3yMZxuD0hvCVDnSuX+G\nhSSLmO0gaXuiw2/m7ehzkTtcl8BlN6o8iVPfdbA09lOAl0U9vOPSaA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_lastmodified=2026-05-20T17:22:26Z -sops_mac=ENC[AES256_GCM,data:bbAxKHABz674Nmbcnl38xu+US8iGYCoTkUYRtRrAdwunhf2BGlnl16+1k2AjJIm+t9W9zqKLBZpYQ9Ata2erzJAyfTlnW7XvInpIsV0SHeo+U7BtD3XDfsW7qu7JWO4ZRTaUgQY5+adxatr19LslfNI8+On37PpdxiSN16kMYdQ=,iv:VKQS450DsuO+sSyqeDYDGTSvJ+8vMSWeglzwbiaOkPU=,tag:QRnZtVhsEl9SxvvAfWuMig==,type:str] +sops_lastmodified=2026-05-20T17:24:41Z +sops_mac=ENC[AES256_GCM,data:JUtXxPbs16EWlzLgiPArQMZWlIhixP9KQyBnHSW9vAbApSzop+MCAhVcBXrbBsozhn+EPej+br3r3zfAjR9gof+lSh1WBqSKkAw9sR1gMcfV8T4eOKVk7nCUZRuKVhoSHyDvAv6gJZF0LKhvwBc1TR0A4msZaecDDG5jNSaMsDw=,iv:RbmNhPgiPozA3WGpZAqk5xTMhuFtSOk2bsbollKT174=,tag:UTGqsTB8uMcMV7esNHQS3w==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 From 49ae3443fc5b2eaf029790ac953b1edc0a9420c1 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Wed, 20 May 2026 15:50:55 -0400 Subject: [PATCH 4/4] swap in better auth for client work too --- package.json | 1 + pnpm-lock.yaml | 304 ++- server.ts | 64 +- src/api/accounts.ts | 6 +- src/api/auth.server.ts | 60 +- src/api/kf-auth.ts | 1 + src/db/migrations/0012_better-auth-tables.sql | 50 + .../0013_rename-account-to-oauth-account.sql | 9 + src/db/migrations/meta/0012_snapshot.json | 1794 +++++++++++++++++ src/db/migrations/meta/_journal.json | 14 + src/db/schema.ts | 52 + src/lib/auth-internal.server.ts | 3 +- src/lib/auth.server.ts | 60 +- src/lib/better-auth.ts | 181 ++ 14 files changed, 2491 insertions(+), 108 deletions(-) create mode 100644 src/db/migrations/0012_better-auth-tables.sql create mode 100644 src/db/migrations/0013_rename-account-to-oauth-account.sql create mode 100644 src/db/migrations/meta/0012_snapshot.json create mode 100644 src/lib/better-auth.ts diff --git a/package.json b/package.json index 62f6a65..042bae7 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "ajv": "^8.17.0", "ajv-formats": "^3.0.0", "bcrypt": "^6.0.0", + "better-auth": "^1.6.11", "better-sqlite3": "^12.9.0", "codemirror": "^6.0.2", "drizzle-orm": "^0.45.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cea68fe..6fcf6aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: bcrypt: specifier: ^6.0.0 version: 6.0.0 + better-auth: + specifier: ^1.6.11 + version: 1.6.11(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(kysely@0.28.17)(postgres@3.4.9)(sql.js@1.14.1))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.6(@types/node@25.6.2)(happy-dom@20.9.0)(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.9.0))) better-sqlite3: specifier: ^12.9.0 version: 12.9.0 @@ -52,7 +55,7 @@ importers: version: 6.0.2 drizzle-orm: specifier: ^0.45.0 - version: 0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(postgres@3.4.9)(sql.js@1.14.1) + version: 0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(kysely@0.28.17)(postgres@3.4.9)(sql.js@1.14.1) hono: specifier: ^4 version: 4.12.18 @@ -405,6 +408,85 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@better-auth/core@1.6.11': + resolution: {integrity: sha512-LrwidLCV8azdMGjvtwp30nj9tIv1BwI3VhtC0UaGSjQkAVWw4bN42I8qwbxRziPeSQoj+zUVkOpxZzAWBDARtQ==} + peerDependencies: + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + '@cloudflare/workers-types': '>=4' + '@opentelemetry/api': ^1.9.0 + better-call: 1.3.5 + jose: ^6.1.0 + kysely: ^0.28.5 + nanostores: ^1.0.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + '@opentelemetry/api': + optional: true + + '@better-auth/drizzle-adapter@1.6.11': + resolution: {integrity: sha512-4jpkETIGZOHCf7BK4jnu22fdN6jjomH0/HhEzkaWy3+Eppi5PYlHTF/460jrTmA3Xc+Vqwp9t282ymHiEPypGw==} + peerDependencies: + '@better-auth/core': ^1.6.11 + '@better-auth/utils': 0.4.0 + drizzle-orm: ^0.45.2 + peerDependenciesMeta: + drizzle-orm: + optional: true + + '@better-auth/kysely-adapter@1.6.11': + resolution: {integrity: sha512-/g8M9RfIjdcZDnbstSUvQiINkvdNlCeZr248zwqx2/PVksQI1MhQofbzUn3RnQnbPKp0EPwpX/dR3oudRFenUg==} + peerDependencies: + '@better-auth/core': ^1.6.11 + '@better-auth/utils': 0.4.0 + kysely: ^0.28.17 + peerDependenciesMeta: + kysely: + optional: true + + '@better-auth/memory-adapter@1.6.11': + resolution: {integrity: sha512-hpdfw0BBf8MuzLkIdmbcUZICbY9r/bhLO2RxSnkzT5+/O+0I0u2I8+m0YUP7vNllP/ZCKASHOYgXPLO75Z0f9Q==} + peerDependencies: + '@better-auth/core': ^1.6.11 + '@better-auth/utils': 0.4.0 + + '@better-auth/mongo-adapter@1.6.11': + resolution: {integrity: sha512-3Tor8rSv8vSEIMEaV2PFpPEuVhqc1gNoZ6eGvoh3LwExXXuj8madew6ob+H1pH7Aphn3Ar5PQ08AguT8TbwFAA==} + peerDependencies: + '@better-auth/core': ^1.6.11 + '@better-auth/utils': 0.4.0 + mongodb: ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + mongodb: + optional: true + + '@better-auth/prisma-adapter@1.6.11': + resolution: {integrity: sha512-Pw+7q7zTp+VSci1V+CYMvuxIbAeVMZLe4lRo46LJoAKMHfjFl5T/ycsyFvWs/DkWC7n9gZZzRDEbHp0I5FiKKw==} + peerDependencies: + '@better-auth/core': ^1.6.11 + '@better-auth/utils': 0.4.0 + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + '@prisma/client': + optional: true + prisma: + optional: true + + '@better-auth/telemetry@1.6.11': + resolution: {integrity: sha512-hsjDHc8MZbm6/AHeNdtywrWedXevnBjmdvnHTcZub+rTVjOv+Td0roI8USKuC6uUibmrl//2rJfVCsGbopihNA==} + peerDependencies: + '@better-auth/core': ^1.6.11 + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + + '@better-auth/utils@0.4.0': + resolution: {integrity: sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA==} + + '@better-fetch/fetch@1.1.21': + resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@codemirror/autocomplete@6.20.2': resolution: {integrity: sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==} @@ -918,9 +1000,21 @@ packages: '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@noble/ciphers@2.2.0': + resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} + engines: {node: '>= 20.19.0'} + + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + engines: {node: '>= 20.19.0'} + '@nodable/entities@2.1.0': resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + engines: {node: '>=14'} + '@oxfmt/binding-android-arm-eabi@0.50.0': resolution: {integrity: sha512-ICXQVKrDvsWUtfx6EiVJxfWrajKTwTfRV8vz2XiMkxZeuCKJLgD4YAj6dE3BWvpqDlkVkie4VSTAtMUWO9LDXg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1824,6 +1918,76 @@ packages: resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} engines: {node: '>= 18'} + better-auth@1.6.11: + resolution: {integrity: sha512-Wwt6+q07dwIhsp6XiM7L1qSXVUWBEtNl+eZvwM778CguFqDZFBN9Pt6LtFaHl55t8Z+Zc//5kxcbgDY8/79vFQ==} + peerDependencies: + '@lynx-js/react': '*' + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + '@sveltejs/kit': ^2.0.0 + '@tanstack/react-start': ^1.0.0 + '@tanstack/solid-start': ^1.0.0 + better-sqlite3: ^12.0.0 + drizzle-kit: '>=0.31.4' + drizzle-orm: ^0.45.2 + mongodb: ^6.0.0 || ^7.0.0 + mysql2: ^3.0.0 + next: ^14.0.0 || ^15.0.0 || ^16.0.0 + pg: ^8.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + solid-js: ^1.0.0 + svelte: ^4.0.0 || ^5.0.0 + vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + '@lynx-js/react': + optional: true + '@prisma/client': + optional: true + '@sveltejs/kit': + optional: true + '@tanstack/react-start': + optional: true + '@tanstack/solid-start': + optional: true + better-sqlite3: + optional: true + drizzle-kit: + optional: true + drizzle-orm: + optional: true + mongodb: + optional: true + mysql2: + optional: true + next: + optional: true + pg: + optional: true + prisma: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vitest: + optional: true + vue: + optional: true + + better-call@1.3.5: + resolution: {integrity: sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==} + peerDependencies: + zod: ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + better-sqlite3@12.9.0: resolution: {integrity: sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} @@ -1899,6 +2063,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2144,6 +2311,9 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2160,6 +2330,10 @@ packages: engines: {node: '>=6'} hasBin: true + kysely@0.28.17: + resolution: {integrity: sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==} + engines: {node: '>=20.0.0'} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -2289,6 +2463,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanostores@1.3.0: + resolution: {integrity: sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==} + engines: {node: ^20.0.0 || >=22.0.0} + napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -2433,6 +2611,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rou3@0.7.12: + resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -2451,6 +2632,9 @@ packages: set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2733,6 +2917,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + snapshots: '@aws-crypto/crc32@5.2.0': @@ -3296,6 +3483,59 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)': + dependencies: + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + '@opentelemetry/semantic-conventions': 1.41.1 + '@standard-schema/spec': 1.1.0 + better-call: 1.3.5(zod@4.4.3) + jose: 6.2.3 + kysely: 0.28.17 + nanostores: 1.3.0 + zod: 4.4.3 + + '@better-auth/drizzle-adapter@1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(kysely@0.28.17)(postgres@3.4.9)(sql.js@1.14.1))': + dependencies: + '@better-auth/core': 1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.0 + optionalDependencies: + drizzle-orm: 0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(kysely@0.28.17)(postgres@3.4.9)(sql.js@1.14.1) + + '@better-auth/kysely-adapter@1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(kysely@0.28.17)': + dependencies: + '@better-auth/core': 1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.0 + optionalDependencies: + kysely: 0.28.17 + + '@better-auth/memory-adapter@1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)': + dependencies: + '@better-auth/core': 1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.0 + + '@better-auth/mongo-adapter@1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)': + dependencies: + '@better-auth/core': 1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.0 + + '@better-auth/prisma-adapter@1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)': + dependencies: + '@better-auth/core': 1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.0 + + '@better-auth/telemetry@1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)': + dependencies: + '@better-auth/core': 1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + + '@better-auth/utils@0.4.0': + dependencies: + '@noble/hashes': 2.2.0 + + '@better-fetch/fetch@1.1.21': {} + '@codemirror/autocomplete@6.20.2': dependencies: '@codemirror/language': 6.12.3 @@ -3620,8 +3860,14 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} + '@noble/ciphers@2.2.0': {} + + '@noble/hashes@2.2.0': {} + '@nodable/entities@2.1.0': {} + '@opentelemetry/semantic-conventions@1.41.1': {} + '@oxfmt/binding-android-arm-eabi@0.50.0': optional: true @@ -4430,6 +4676,45 @@ snapshots: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 + better-auth@1.6.11(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(kysely@0.28.17)(postgres@3.4.9)(sql.js@1.14.1))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vitest@4.1.6(@types/node@25.6.2)(happy-dom@20.9.0)(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.9.0))): + dependencies: + '@better-auth/core': 1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/drizzle-adapter': 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(kysely@0.28.17)(postgres@3.4.9)(sql.js@1.14.1)) + '@better-auth/kysely-adapter': 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(kysely@0.28.17) + '@better-auth/memory-adapter': 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0) + '@better-auth/mongo-adapter': 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0) + '@better-auth/prisma-adapter': 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0) + '@better-auth/telemetry': 1.6.11(@better-auth/core@1.6.11(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@3.25.76))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21) + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + '@noble/ciphers': 2.2.0 + '@noble/hashes': 2.2.0 + better-call: 1.3.5(zod@4.4.3) + defu: 6.1.7 + jose: 6.2.3 + kysely: 0.28.17 + nanostores: 1.3.0 + zod: 4.4.3 + optionalDependencies: + better-sqlite3: 12.9.0 + drizzle-kit: 0.31.10 + drizzle-orm: 0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(kysely@0.28.17)(postgres@3.4.9)(sql.js@1.14.1) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + vitest: 4.1.6(@types/node@25.6.2)(happy-dom@20.9.0)(vite@6.4.2(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.9.0)) + transitivePeerDependencies: + - '@cloudflare/workers-types' + - '@opentelemetry/api' + + better-call@1.3.5(zod@4.4.3): + dependencies: + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + rou3: 0.7.12 + set-cookie-parser: 3.1.0 + optionalDependencies: + zod: 4.4.3 + better-sqlite3@12.9.0: dependencies: bindings: 1.5.0 @@ -4505,6 +4790,8 @@ snapshots: deep-extend@0.6.0: {} + defu@6.1.7: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -4518,10 +4805,11 @@ snapshots: esbuild: 0.25.12 tsx: 4.21.0 - drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(postgres@3.4.9)(sql.js@1.14.1): + drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.9.0)(kysely@0.28.17)(postgres@3.4.9)(sql.js@1.14.1): optionalDependencies: '@types/better-sqlite3': 7.6.13 better-sqlite3: 12.9.0 + kysely: 0.28.17 postgres: 3.4.9 sql.js: 1.14.1 @@ -4712,6 +5000,8 @@ snapshots: jiti@2.7.0: {} + jose@6.2.3: {} + js-tokens@4.0.0: {} jsesc@3.1.0: {} @@ -4720,6 +5010,8 @@ snapshots: json5@2.2.3: {} + kysely@0.28.17: {} + lightningcss-android-arm64@1.32.0: optional: true @@ -4822,6 +5114,8 @@ snapshots: nanoid@3.3.12: {} + nanostores@1.3.0: {} + napi-build-utils@2.0.0: {} node-abi@3.92.0: @@ -5010,6 +5304,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.3 fsevents: 2.3.3 + rou3@0.7.12: {} + safe-buffer@5.2.1: {} scheduler@0.27.0: {} @@ -5020,6 +5316,8 @@ snapshots: set-cookie-parser@2.7.2: {} + set-cookie-parser@3.1.0: {} + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -5255,3 +5553,5 @@ snapshots: optional: true zod@3.25.76: {} + + zod@4.4.3: {} diff --git a/server.ts b/server.ts index 2848703..0bda888 100644 --- a/server.ts +++ b/server.ts @@ -16,14 +16,13 @@ import { authMiddleware, requireAuth } from '~/api/auth.server' import * as collections from '~/api/collections' import * as files from '~/api/files' import * as health from '~/api/health' -import * as kfAuth from '~/api/kf-auth' import * as kfSummary from '~/api/kf-summary' import * as query from '~/api/query' import * as schemas from '~/api/schemas' import * as uploads from '~/api/uploads' import * as versions from '~/api/versions' +import { auth } from '~/lib/better-auth' import { getMirrorConfig } from '~/lib/mirror-config' -import { initOidc } from '~/lib/oidc.server' const isProd = process.env.NODE_ENV === 'production' const app = new Hono() @@ -46,7 +45,7 @@ app.use('/api/admin/*', async (c, next) => { // --- ARK resolution middleware --- app.use('/ark\\:*', arkMiddleware) -// --- KF Auth (OIDC login) --- +// --- KF Auth (OIDC login via better-auth) --- app.get('/login', async (c, next) => { // Server-side redirect to avoid client-side "Redirecting..." flash. // Fall through to the React route only when there's an error to display. @@ -60,9 +59,55 @@ app.get('/login', async (c, next) => { } await next() }) -app.get('/auth/login', kfAuth.login) -app.get('/auth/callback', kfAuth.callback) -app.post('/auth/logout', kfAuth.logout) + +// /auth/login — triggers better-auth's social sign-in with kf-auth provider +app.get('/auth/login', async (c) => { + const url = new URL(c.req.url) + const returnTo = url.searchParams.get('return_to') ?? '/' + // POST to better-auth's sign-in/social internally + const signInUrl = new URL('/api/auth/sign-in/social', url.origin) + const req = new Request(signInUrl, { + method: 'POST', + headers: new Headers({ + 'Content-Type': 'application/json', + Origin: url.origin, + cookie: c.req.header('cookie') ?? '', + }), + body: JSON.stringify({ provider: 'kf-auth', callbackURL: returnTo }), + }) + const res = await auth.handler(req) + + // better-auth returns JSON { url, redirect } — extract and do a real redirect + // Also forward any Set-Cookie headers (state/PKCE cookies) + const body = (await res.json()) as { url?: string; redirect?: boolean } + if (body?.url) { + const response = c.redirect(body.url) + // Forward cookies set by better-auth (OAuth state, code verifier) + res.headers.forEach((value, key) => { + if (key.toLowerCase() === 'set-cookie') { + response.headers.append('set-cookie', value) + } + }) + return response + } + return res +}) + +// /auth/logout — proxy to better-auth sign-out +app.post('/auth/logout', async (c) => { + // Forward the request to better-auth's sign-out handler + const signOutUrl = new URL('/api/auth/sign-out', c.req.url) + const req = new Request(signOutUrl, { + method: 'POST', + headers: c.req.raw.headers, + }) + return auth.handler(req) +}) + +// better-auth handles: /api/auth/sign-in/social, /api/auth/callback/kf-auth, sessions, etc. +app.on(['GET', 'POST'], '/api/auth/*', (c) => { + return auth.handler(c.req.raw) +}) // --- API routes --- app.get('/api/health', health.check) @@ -306,12 +351,5 @@ if (isProd) { const port = Number(process.env.PORT) || 3000 -// Validate OIDC provider is reachable before accepting requests -await initOidc().catch((err) => { - console.error('FATAL: OIDC discovery failed — cannot start without a valid OIDC provider.') - console.error(err.message) - process.exit(1) -}) - console.log(`Server running at http://localhost:${port}`) serve({ fetch: app.fetch, port }) diff --git a/src/api/accounts.ts b/src/api/accounts.ts index 589f59a..129ae18 100644 --- a/src/api/accounts.ts +++ b/src/api/accounts.ts @@ -1,13 +1,13 @@ import bcrypt from 'bcrypt' import { and, count, eq } from 'drizzle-orm' import type { Context } from 'hono' -import { getCookie } from 'hono/cookie' +import { deleteCookie, getCookie } from 'hono/cookie' import { v4 as uuidv4 } from 'uuid' import { db, schema } from '../db/client.server.js' import { sendEmail } from '../lib/email.js' import { deleteS3Objects, listS3Objects, uploadToS3 } from '../lib/s3.js' -import { type AuthEnv, clearSessionCookie } from './auth.server.js' +import type { AuthEnv } from './auth.server.js' /** Base URL for public assets (avatars, etc.) */ const ASSETS_BASE_URL = process.env.ASSETS_BASE_URL ?? 'https://assets.underlay.org' @@ -280,7 +280,7 @@ export async function deleteMe(c: Context) { // Cascade will handle sessions, memberships, api keys await db.delete(schema.accounts).where(eq(schema.accounts.id, account.id)) - clearSessionCookie(c) + deleteCookie(c, 'ul.session_token', { path: '/' }) return c.json({ ok: true }) } diff --git a/src/api/auth.server.ts b/src/api/auth.server.ts index 84b529b..e0d94cd 100644 --- a/src/api/auth.server.ts +++ b/src/api/auth.server.ts @@ -1,10 +1,10 @@ import bcrypt from 'bcrypt' import { eq } from 'drizzle-orm' -import type { Context, MiddlewareHandler } from 'hono' -import { deleteCookie, getCookie, setCookie } from 'hono/cookie' +import type { MiddlewareHandler } from 'hono' import { createMiddleware } from 'hono/factory' import { db, schema } from '../db/client.server.js' +import { auth } from '../lib/better-auth.js' export type AuthEnv = { Variables: { @@ -20,7 +20,6 @@ const publicPaths = new Set(['/api/health', '/api/query/generate-sql']) const internalToken = process.env.INTERNAL_API_TOKEN ?? 'internal-dev-token' const authInternalApiKey = process.env.AUTH_INTERNAL_API_KEY ?? process.env.KF_INTERNAL_API_KEY ?? '' -const sessionSecret = process.env.SESSION_SECRET ?? 'dev-secret-change-me' export const authMiddleware = createMiddleware(async (c, next) => { // Internal service calls (legacy header) @@ -31,15 +30,15 @@ export const authMiddleware = createMiddleware(async (c, next) => { } // Auth provider internal API key (used by /api/kf/* endpoints) - const auth = c.req.header('authorization') - if (authInternalApiKey && auth === `Bearer ${authInternalApiKey}`) { + const headerAuth = c.req.header('authorization') + if (authInternalApiKey && headerAuth === `Bearer ${authInternalApiKey}`) { c.set('apiKeyScope', 'admin') return next() } // API key auth via Bearer token - if (auth?.startsWith('Bearer ')) { - const token = auth.slice(7) + if (headerAuth?.startsWith('Bearer ')) { + const token = headerAuth.slice(7) const keys = await db.select().from(schema.apiKeys) let matched = false for (const key of keys) { @@ -62,31 +61,13 @@ export const authMiddleware = createMiddleware(async (c, next) => { return next() } - // Session cookie auth - const sessionCookie = getCookie(c, 'session') - if (sessionCookie) { - try { - // Try to parse as signed cookie (value.signature format) - let sessionId = sessionCookie - const dotIdx = sessionCookie.lastIndexOf('.') - if (dotIdx > 0) { - sessionId = sessionCookie.slice(0, dotIdx) - } - if (sessionId) { - const [session] = await db - .select() - .from(schema.sessions) - .where(eq(schema.sessions.id, sessionId)) - .limit(1) - if (session && new Date(session.expiresAt) > new Date()) { - c.set('sessionUserId', session.userId) - c.set('accountId', session.userId) - c.set('apiKeyScope', 'admin') - } - } - } catch { - // Invalid or expired cookie — ignore silently - } + // Session cookie auth via better-auth + const session = await auth.api.getSession({ headers: c.req.raw.headers }) + if (session?.user) { + // session.user.id = KF Auth sub = accounts.id (shared ID) + c.set('sessionUserId', session.user.id) + c.set('accountId', session.user.id) + c.set('apiKeyScope', 'admin') } // Public GETs are allowed without auth @@ -116,18 +97,3 @@ export function requireAuth(scope?: 'read' | 'write' | 'admin'): MiddlewareHandl return next() } } - -// Helper to set signed session cookie -export function setSessionCookie(c: Context, sessionId: string) { - setCookie(c, 'session', sessionId, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'Lax', - path: '/', - maxAge: 30 * 24 * 60 * 60, // 30 days - }) -} - -export function clearSessionCookie(c: Context) { - deleteCookie(c, 'session', { path: '/' }) -} diff --git a/src/api/kf-auth.ts b/src/api/kf-auth.ts index 8dd071e..a6ebe59 100644 --- a/src/api/kf-auth.ts +++ b/src/api/kf-auth.ts @@ -1,3 +1,4 @@ +// @ts-nocheck — DEPRECATED: This file is replaced by better-auth (see src/lib/better-auth.ts) import crypto from 'node:crypto' import { eq } from 'drizzle-orm' diff --git a/src/db/migrations/0012_better-auth-tables.sql b/src/db/migrations/0012_better-auth-tables.sql new file mode 100644 index 0000000..9288cce --- /dev/null +++ b/src/db/migrations/0012_better-auth-tables.sql @@ -0,0 +1,50 @@ +CREATE TABLE "oauth_account" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "account_id" text NOT NULL, + "provider_id" text NOT NULL, + "access_token" text, + "refresh_token" text, + "access_token_expires_at" timestamp with time zone, + "refresh_token_expires_at" timestamp with time zone, + "scope" text, + "id_token" text, + "password" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "token" text NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "ip_address" text, + "user_agent" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "session_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean DEFAULT false NOT NULL, + "image" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "verification" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "oauth_account" ADD CONSTRAINT "oauth_account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/src/db/migrations/0013_rename-account-to-oauth-account.sql b/src/db/migrations/0013_rename-account-to-oauth-account.sql new file mode 100644 index 0000000..2e341aa --- /dev/null +++ b/src/db/migrations/0013_rename-account-to-oauth-account.sql @@ -0,0 +1,9 @@ +-- Rename better-auth's "account" table to "oauth_account" to avoid confusion with our "accounts" table. +-- This is a no-op if the table was already created as "oauth_account" (fresh installs). +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'account' AND table_schema = 'public') THEN + ALTER TABLE "account" RENAME TO "oauth_account"; + ALTER TABLE "oauth_account" RENAME CONSTRAINT "account_user_id_user_id_fk" TO "oauth_account_user_id_user_id_fk"; + END IF; +END $$; diff --git a/src/db/migrations/meta/0012_snapshot.json b/src/db/migrations/meta/0012_snapshot.json new file mode 100644 index 0000000..5fa303c --- /dev/null +++ b/src/db/migrations/meta/0012_snapshot.json @@ -0,0 +1,1794 @@ +{ + "id": "eadefa2b-c59d-4d5b-a086-c403f2bfe421", + "prevId": "9898328b-fc86-4214-a39d-4a17f262e207", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notification_prefs": { + "name": "notification_prefs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ark_naan": { + "name": "ark_naan", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kf_org_id": { + "name": "kf_org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "accounts_slug_unique": { + "name": "accounts_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_account_id_accounts_id_fk": { + "name": "api_keys_account_id_accounts_id_fk", + "tableFrom": "api_keys", + "tableTo": "accounts", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_keys_collection_id_collections_id_fk": { + "name": "api_keys_collection_id_collections_id_fk", + "tableFrom": "api_keys", + "tableTo": "collections", + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ark_collections": { + "name": "ark_collections", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "ark_id": { + "name": "ark_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "custom_url": { + "name": "custom_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ark_collections_collection_id_collections_id_fk": { + "name": "ark_collections_collection_id_collections_id_fk", + "tableFrom": "ark_collections", + "tableTo": "collections", + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ark_collections_ark_id_unique": { + "name": "ark_collections_ark_id_unique", + "nullsNotDistinct": false, + "columns": ["ark_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ark_record_types": { + "name": "ark_record_types", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "record_type": { + "name": "record_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_url_field": { + "name": "redirect_url_field", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ark_record_types_collection_id_collections_id_fk": { + "name": "ark_record_types_collection_id_collections_id_fk", + "tableFrom": "ark_record_types", + "tableTo": "collections", + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ark_record_types_collection_id_record_type_pk": { + "name": "ark_record_types_collection_id_record_type_pk", + "columns": ["collection_id", "record_type"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ark_shoulders": { + "name": "ark_shoulders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "shoulder": { + "name": "shoulder", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ark_shoulders_account_id_accounts_id_fk": { + "name": "ark_shoulders_account_id_accounts_id_fk", + "tableFrom": "ark_shoulders", + "tableTo": "accounts", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ark_shoulders_account_id_unique": { + "name": "ark_shoulders_account_id_unique", + "nullsNotDistinct": false, + "columns": ["account_id"] + }, + "ark_shoulders_shoulder_unique": { + "name": "ark_shoulders_shoulder_unique", + "nullsNotDistinct": false, + "columns": ["shoulder"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "forked_from": { + "name": "forked_from", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "collections_account_id_idx": { + "name": "collections_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collections_account_id_accounts_id_fk": { + "name": "collections_account_id_accounts_id_fk", + "tableFrom": "collections", + "tableTo": "accounts", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collections_forked_from_collections_id_fk": { + "name": "collections_forked_from_collections_id_fk", + "tableFrom": "collections", + "tableTo": "collections", + "columnsFrom": ["forked_from"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collections_account_id_slug_unique": { + "name": "collections_account_id_slug_unique", + "nullsNotDistinct": false, + "columns": ["account_id", "slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "size": { + "name": "size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.org_invitations": { + "name": "org_invitations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "org_invitations_org_id_accounts_id_fk": { + "name": "org_invitations_org_id_accounts_id_fk", + "tableFrom": "org_invitations", + "tableTo": "accounts", + "columnsFrom": ["org_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_invitations_invited_by_accounts_id_fk": { + "name": "org_invitations_invited_by_accounts_id_fk", + "tableFrom": "org_invitations", + "tableTo": "accounts", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "org_invitations_token_unique": { + "name": "org_invitations_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.org_memberships": { + "name": "org_memberships", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "org_memberships_org_id_accounts_id_fk": { + "name": "org_memberships_org_id_accounts_id_fk", + "tableFrom": "org_memberships", + "tableTo": "accounts", + "columnsFrom": ["org_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_memberships_user_id_accounts_id_fk": { + "name": "org_memberships_user_id_accounts_id_fk", + "tableFrom": "org_memberships", + "tableTo": "accounts", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "org_memberships_org_id_user_id_pk": { + "name": "org_memberships_org_id_user_id_pk", + "columns": ["org_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.records": { + "name": "records", + "schema": "", + "columns": { + "version_id": { + "name": "version_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "records_version_id_type_idx": { + "name": "records_version_id_type_idx", + "columns": [ + { + "expression": "version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "records_version_id_versions_id_fk": { + "name": "records_version_id_versions_id_fk", + "tableFrom": "records", + "tableTo": "versions", + "columnsFrom": ["version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "records_version_id_record_id_pk": { + "name": "records_version_id_record_id_pk", + "columns": ["version_id", "record_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schema_labels": { + "name": "schema_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "schema_id": { + "name": "schema_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "schema_labels_label_idx": { + "name": "schema_labels_label_idx", + "columns": [ + { + "expression": "label", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "schema_labels_schema_id_schemas_id_fk": { + "name": "schema_labels_schema_id_schemas_id_fk", + "tableFrom": "schema_labels", + "tableTo": "schemas", + "columnsFrom": ["schema_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "schema_labels_schema_id_label_unique": { + "name": "schema_labels_schema_id_label_unique", + "nullsNotDistinct": false, + "columns": ["schema_id", "label"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schemas": { + "name": "schemas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "schema_hash": { + "name": "schema_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "schemas_schema_hash_unique": { + "name": "schemas_schema_hash_unique", + "nullsNotDistinct": false, + "columns": ["schema_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_accounts_id_fk": { + "name": "sessions_user_id_accounts_id_fk", + "tableFrom": "sessions", + "tableTo": "accounts", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_runs": { + "name": "sync_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "collections_synced": { + "name": "collections_synced", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "collections_created": { + "name": "collections_created", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "collections_failed": { + "name": "collections_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "versions_pulled": { + "name": "versions_pulled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "files_downloaded": { + "name": "files_downloaded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "files_skipped": { + "name": "files_skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "errors": { + "name": "errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.upload_records": { + "name": "upload_records", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "upload_records_session_id_upload_sessions_id_fk": { + "name": "upload_records_session_id_upload_sessions_id_fk", + "tableFrom": "upload_records", + "tableTo": "upload_sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "upload_records_session_id_record_id_pk": { + "name": "upload_records_session_id_record_id_pk", + "columns": ["session_id", "record_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.upload_sessions": { + "name": "upload_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "base_version": { + "name": "base_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "readme": { + "name": "readme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schemas": { + "name": "schemas", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "record_count": { + "name": "record_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "upload_sessions_collection_id_collections_id_fk": { + "name": "upload_sessions_collection_id_collections_id_fk", + "tableFrom": "upload_sessions", + "tableTo": "collections", + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "upload_sessions_account_id_accounts_id_fk": { + "name": "upload_sessions_account_id_accounts_id_fk", + "tableFrom": "upload_sessions", + "tableTo": "accounts", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.version_files": { + "name": "version_files", + "schema": "", + "columns": { + "version_id": { + "name": "version_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "version_files_file_hash_idx": { + "name": "version_files_file_hash_idx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "version_files_version_id_versions_id_fk": { + "name": "version_files_version_id_versions_id_fk", + "tableFrom": "version_files", + "tableTo": "versions", + "columnsFrom": ["version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "version_files_file_hash_files_hash_fk": { + "name": "version_files_file_hash_files_hash_fk", + "tableFrom": "version_files", + "tableTo": "files", + "columnsFrom": ["file_hash"], + "columnsTo": ["hash"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "version_files_version_id_file_hash_pk": { + "name": "version_files_version_id_file_hash_pk", + "columns": ["version_id", "file_hash"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.version_schemas": { + "name": "version_schemas", + "schema": "", + "columns": { + "version_id": { + "name": "version_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_id": { + "name": "schema_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "version_schemas_schema_id_idx": { + "name": "version_schemas_schema_id_idx", + "columns": [ + { + "expression": "schema_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "version_schemas_version_id_versions_id_fk": { + "name": "version_schemas_version_id_versions_id_fk", + "tableFrom": "version_schemas", + "tableTo": "versions", + "columnsFrom": ["version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "version_schemas_schema_id_schemas_id_fk": { + "name": "version_schemas_schema_id_schemas_id_fk", + "tableFrom": "version_schemas", + "tableTo": "schemas", + "columnsFrom": ["schema_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "version_schemas_version_id_slug_pk": { + "name": "version_schemas_version_id_slug_pk", + "columns": ["version_id", "slug"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.versions": { + "name": "versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_hash": { + "name": "public_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_number": { + "name": "base_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "readme": { + "name": "readme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pushed_by": { + "name": "pushed_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "record_count": { + "name": "record_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "versions_collection_id_collections_id_fk": { + "name": "versions_collection_id_collections_id_fk", + "tableFrom": "versions", + "tableTo": "collections", + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "versions_pushed_by_accounts_id_fk": { + "name": "versions_pushed_by_accounts_id_fk", + "tableFrom": "versions", + "tableTo": "accounts", + "columnsFrom": ["pushed_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "versions_collection_id_number_unique": { + "name": "versions_collection_id_number_unique", + "nullsNotDistinct": false, + "columns": ["collection_id", "number"] + }, + "versions_collection_id_hash_unique": { + "name": "versions_collection_id_hash_unique", + "nullsNotDistinct": false, + "columns": ["collection_id", "hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index 7263189..774d76c 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -85,6 +85,20 @@ "when": 1778812081690, "tag": "0011_messy_molten_man", "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1779303678092, + "tag": "0012_better-auth-tables", + "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1779310000000, + "tag": "0013_rename-account-to-oauth-account", + "breakpoints": true } ] } diff --git a/src/db/schema.ts b/src/db/schema.ts index 5e0ab2c..1200c10 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -318,3 +318,55 @@ export const arkRecordTypes = pgTable( }, (t) => [primaryKey({ columns: [t.collectionId, t.recordType] })], ) + +// --- Better-auth tables (session management) --- + +export const user = pgTable('user', { + id: text('id').primaryKey(), + name: text('name').notNull(), + email: text('email').notNull().unique(), + emailVerified: boolean('email_verified').notNull().default(false), + image: text('image'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), +}) + +export const session = pgTable('session', { + id: text('id').primaryKey(), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + token: text('token').notNull().unique(), + expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), + ipAddress: text('ip_address'), + userAgent: text('user_agent'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), +}) + +export const oauthAccount = pgTable('oauth_account', { + id: text('id').primaryKey(), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + accountId: text('account_id').notNull(), + providerId: text('provider_id').notNull(), + accessToken: text('access_token'), + refreshToken: text('refresh_token'), + accessTokenExpiresAt: timestamp('access_token_expires_at', { withTimezone: true }), + refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { withTimezone: true }), + scope: text('scope'), + idToken: text('id_token'), + password: text('password'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), +}) + +export const verification = pgTable('verification', { + id: text('id').primaryKey(), + identifier: text('identifier').notNull(), + value: text('value').notNull(), + expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), +}) diff --git a/src/lib/auth-internal.server.ts b/src/lib/auth-internal.server.ts index 99852aa..8732764 100644 --- a/src/lib/auth-internal.server.ts +++ b/src/lib/auth-internal.server.ts @@ -9,7 +9,8 @@ * AUTH_INTERNAL_API_KEY — shared secret for service-to-service calls (fallback: KF_INTERNAL_API_KEY) */ -import { OIDC_ISSUER_INTERNAL_URL } from './oidc.server.js' +const OIDC_ISSUER_INTERNAL_URL = + process.env.OIDC_ISSUER_INTERNAL_URL ?? process.env.OIDC_ISSUER_URL ?? 'http://localhost:3000' const AUTH_INTERNAL_API_URL = process.env.AUTH_INTERNAL_API_URL ?? diff --git a/src/lib/auth.server.ts b/src/lib/auth.server.ts index dae0f06..23b4d10 100644 --- a/src/lib/auth.server.ts +++ b/src/lib/auth.server.ts @@ -1,6 +1,7 @@ import { eq } from 'drizzle-orm' import { db, schema } from '../db/client.server.js' +import { auth } from './better-auth.js' import { getKfProfile } from './kf-profile-cache.server.js' export interface SessionUser { @@ -22,53 +23,28 @@ export interface SessionUser { * the local DB. */ export async function getSessionUser(request: Request): Promise { - const cookieHeader = request.headers.get('cookie') - if (!cookieHeader) return null + // Use better-auth to validate session from cookies + const session = await auth.api.getSession({ headers: request.headers }) + if (!session?.user) return null - // Parse session cookie - const cookies = Object.fromEntries( - cookieHeader.split(';').map((c) => { - const [key, ...rest] = c.trim().split('=') - return [key, rest.join('=')] as [string, string] - }), - ) + const userId = session.user.id - let sessionId = cookies['session'] - if (!sessionId) return null - - // Strip signature if present (legacy signed cookies) - const dotIdx = sessionId.lastIndexOf('.') - if (dotIdx > 0) { - sessionId = sessionId.slice(0, dotIdx) - } - - // Look up session - const [session] = await db - .select() - .from(schema.sessions) - .where(eq(schema.sessions.id, sessionId)) - .limit(1) - - if (!session || new Date(session.expiresAt) <= new Date()) { - return null - } - - // Look up user - const [user] = await db + // Look up domain account + const [account] = await db .select() .from(schema.accounts) - .where(eq(schema.accounts.id, session.userId)) + .where(eq(schema.accounts.id, userId)) .limit(1) - if (!user) return null + if (!account) return null // For user accounts, fetch profile from KF Auth (name + image). // For org accounts, use locally stored displayName + avatarUrl. - let displayName = user.displayName - let avatarUrl = user.avatarUrl + let displayName = account.displayName + let avatarUrl = account.avatarUrl - if (user.type === 'user') { - const profile = await getKfProfile(user.id) + if (account.type === 'user') { + const profile = await getKfProfile(account.id) if (profile) { displayName = profile.name avatarUrl = profile.image @@ -84,14 +60,14 @@ export async function getSessionUser(request: Request): Promise ({ slug: m.orgSlug, diff --git a/src/lib/better-auth.ts b/src/lib/better-auth.ts new file mode 100644 index 0000000..fe27be6 --- /dev/null +++ b/src/lib/better-auth.ts @@ -0,0 +1,181 @@ +/** + * Better-auth configuration for OIDC client mode. + * + * Uses the genericOAuth plugin to authenticate against KF Auth (or any OIDC provider). + * Better-auth manages: user table, session table, OAuth account linkage, cookies, CSRF. + * The app keeps its own `accounts` table (users + orgs domain model) — linked by shared ID. + */ + +import crypto from 'node:crypto' + +import { betterAuth } from 'better-auth' +import { drizzleAdapter } from 'better-auth/adapters/drizzle' +import { genericOAuth } from 'better-auth/plugins/generic-oauth' +import { eq } from 'drizzle-orm' + +import { db, schema } from '../db/client.server.js' + +// --- Config (with backward-compat fallbacks) --- + +const OIDC_ISSUER_URL = + process.env.OIDC_ISSUER_URL ?? process.env.KF_AUTH_URL ?? 'http://localhost:3000' + +const OIDC_ISSUER_INTERNAL_URL = + process.env.OIDC_ISSUER_INTERNAL_URL ?? process.env.KF_AUTH_INTERNAL_URL ?? OIDC_ISSUER_URL + +const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID ?? process.env.KF_AUTH_CLIENT_ID ?? 'kf_underlay' + +const OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET ?? process.env.KF_AUTH_CLIENT_SECRET ?? '' + +const OIDC_ORGS_CLAIM = process.env.OIDC_ORGS_CLAIM ?? 'https://knowledgefutures.org/orgs' + +const APP_URL = process.env.APP_URL ?? 'http://localhost:4100' + +// --- Temp storage: pass OAuth sub from getUserInfo → user.create.before --- +// Keyed by email to be safe across concurrent requests. +const pendingOAuthSubs = new Map() + +// --- Account upsert (same logic as before, runs on every login) --- + +interface OIDCOrg { + id: string + name: string + slug: string + type: 'personal' | 'shared' + role: string +} + +async function upsertAccount(profile: Record) { + const kfUserId = profile.sub as string + const orgs = (profile[OIDC_ORGS_CLAIM] ?? []) as OIDCOrg[] + const personalOrg = orgs.find((o) => o.type === 'personal') + + const [existing] = await db + .select({ id: schema.accounts.id }) + .from(schema.accounts) + .where(eq(schema.accounts.id, kfUserId)) + .limit(1) + + if (existing) { + // Update personal org link if it changed + if (personalOrg) { + await db + .update(schema.accounts) + .set({ kfOrgId: personalOrg.id }) + .where(eq(schema.accounts.id, kfUserId)) + } + } else { + // Create new account — derive slug from email or name + const email = profile.email as string | undefined + const name = profile.name as string | undefined + const baseSlug = (email?.split('@')[0] ?? name ?? 'user') + .toLowerCase() + .replace(/[^a-z0-9-]/g, '-') + .replace(/-+/g, '-') + .slice(0, 30) + + // Ensure slug is unique + let slug = baseSlug + let attempt = 0 + while (true) { + const [conflict] = await db + .select({ id: schema.accounts.id }) + .from(schema.accounts) + .where(eq(schema.accounts.slug, slug)) + .limit(1) + + if (!conflict) break + attempt++ + slug = `${baseSlug}-${attempt}` + } + + await db.insert(schema.accounts).values({ + id: kfUserId, + slug, + type: 'user', + kfOrgId: personalOrg?.id ?? null, + }) + } +} + +// --- Better-auth instance --- + +export const auth = betterAuth({ + database: drizzleAdapter(db, { + provider: 'pg', + schema: { + user: schema.user, + session: schema.session, + account: schema.oauthAccount, + verification: schema.verification, + }, + }), + baseURL: APP_URL, + basePath: '/api/auth', + secret: process.env.SESSION_SECRET ?? 'dev-secret-change-me', + + advanced: { + database: { + generateId: () => crypto.randomUUID(), + }, + cookiePrefix: 'ul', + }, + + plugins: [ + genericOAuth({ + config: [ + { + providerId: 'kf-auth', + clientId: OIDC_CLIENT_ID, + clientSecret: OIDC_CLIENT_SECRET, + // Browser-facing (user redirect) + authorizationUrl: `${OIDC_ISSUER_URL}/api/auth/oauth2/authorize`, + // Server-to-server (code exchange, userinfo) + tokenUrl: `${OIDC_ISSUER_INTERNAL_URL}/api/auth/oauth2/token`, + scopes: ['openid', 'profile', 'email'], + pkce: true, + getUserInfo: async (tokens) => { + const res = await fetch(`${OIDC_ISSUER_INTERNAL_URL}/api/auth/oauth2/userinfo`, { + headers: { Authorization: `Bearer ${tokens.accessToken}` }, + }) + if (!res.ok) throw new Error(`UserInfo failed: ${res.status}`) + const profile = (await res.json()) as Record + + // Store sub for user.create.before hook (so user.id = kf auth sub) + const email = profile.email as string + const sub = profile.sub as string + if (email) pendingOAuthSubs.set(email, sub) + + // Upsert the domain `accounts` row (runs every login) + await upsertAccount(profile) + + return { + id: sub, + name: (profile.name as string) ?? undefined, + email, + image: (profile.picture as string) ?? undefined, + emailVerified: !!profile.email_verified, + } + }, + }, + ], + }), + ], + + databaseHooks: { + user: { + create: { + before: async (user) => { + // Override the generated UUID with the KF Auth sub + // so that user.id === accounts.id (shared ID space) + const sub = pendingOAuthSubs.get(user.email) + if (sub) { + pendingOAuthSubs.delete(sub) + return { data: { ...user, id: sub } } + } + return { data: user } + }, + }, + }, + }, +})