diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/docker-compose.yml b/docker-compose.yml index b44505d..e3ec283 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: postgres: container_name: postgres - image: "postgres:latest" + image: "postgres:17" ports: - "5432:5432" environment: diff --git a/idp/go.mod b/idp/go.mod index c7fde93..fdaeada 100644 --- a/idp/go.mod +++ b/idp/go.mod @@ -28,7 +28,6 @@ require ( cloud.google.com/go/auth v0.23.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.1 // indirect - github.com/andybalholm/brotli v1.2.4 // indirect github.com/boombuler/barcode v1.1.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/idp/go.sum b/idp/go.sum index c846f11..14a43c1 100644 --- a/idp/go.sum +++ b/idp/go.sum @@ -1,11 +1,7 @@ -cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo= -cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0= cloud.google.com/go/auth v0.23.3 h1:UMK+oBtuNGMCR/6i6mmySUItqjOazpJrbmZyhGbGBWo= cloud.google.com/go/auth v0.23.3/go.mod h1:fClbry28fo7XkxhSeT6AQtAVAp6Jy0fW9N99PoPNPFM= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/compute/metadata v0.9.1 h1:CTE1OWBQ0vnF5uHwdFAQJvMQ0Fi/KRcqqKTo9V0F8Ik= cloud.google.com/go/compute/metadata v0.9.1/go.mod h1:NtnlvB6X3t4R6xSWyVX/ZWk493PCxGQlhI/iqxh4M8I= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= @@ -14,10 +10,6 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA5NQ= -github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/andybalholm/brotli v1.2.4 h1:649LN6qF+/7JJYNN3Wdu4Dt5PS6eC/yP/Go3Bb0/AHI= -github.com/andybalholm/brotli v1.2.4/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q= github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -59,17 +51,12 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= -github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= -github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= -github.com/go-faker/faker/v4 v4.6.1 h1:xUyVpAjEtB04l6XFY0V/29oR332rOSPWV4lU8RwDt4k= -github.com/go-faker/faker/v4 v4.6.1/go.mod h1:arSdxNCSt7mOhdk8tEolvHeIJ7eX4OX80wXjKKvkKBY= github.com/go-faker/faker/v4 v4.12.0 h1:yZXxuoQjxN+C2PVgYoDSHGiD9wj6dX1/Ful4p7QQV0k= github.com/go-faker/faker/v4 v4.12.0/go.mod h1:VFIEwWDd16EdYDLF6NJ5gAAzEp7vz5LgKgJ2iZ17Tdg= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -83,12 +70,8 @@ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lY github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.2 h1:LCsMLC9RzmbUMNUPVYD15dmcjwYAJhmX8mPZRW4rAVU= github.com/go-playground/universal-translator v0.18.2/go.mod h1:67VZIMp5lQpDWlnStOct22q1bkdJGJqHghbOtmkawxk= -github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8= -github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= github.com/go-playground/validator/v10 v10.30.5 h1:YyCXvVShZbs2Sm3Mb53eNOlhRXctSOzW5QJAouCTZL4= github.com/go-playground/validator/v10 v10.30.5/go.mod h1:wEqiaov48pXX1kjhc3Da8y0M0Dtg/BK7gurFBLgwFrQ= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -97,16 +80,12 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gofiber/fiber/v3 v3.5.0 h1:dk7TOUH6DXJGtOLsN2XEG+0ZML7cznzHILTVozbNEK8= github.com/gofiber/fiber/v3 v3.5.0/go.mod h1:GOVDTW+gjJvfe0iJyVujbQ1Lnx+JUjFySJRI/9/xX/w= -github.com/gofiber/schema v1.8.3 h1:06ZedxIYjngzc0095PYy7uWnFnbRflWFpikvZH61fDc= -github.com/gofiber/schema v1.8.3/go.mod h1:jWnnZdhcW1mHyV+VnfRxKJDPNcepJsTZ9RIWxrr32Ng= github.com/gofiber/schema v1.8.7 h1:7cSagr2ObiUVFuitCOqjKMqIhV+oVbkanP4Ge/wHsnI= github.com/gofiber/schema v1.8.7/go.mod h1:fBz4T6uOepH3L+dYlz/yzQ0bFHPnfExkp/B3Qrk2VBI= github.com/gofiber/storage/redis/v3 v3.6.0 h1:ryGdJNq5OTcC/VV7vO07obDG5qmjmhwYkkFo6jAaJbg= github.com/gofiber/storage/redis/v3 v3.6.0/go.mod h1:InnkIWanfBw4dX6FjTIHMW3/PRO9azi5lhzKoNVnpUY= github.com/gofiber/storage/testhelpers/redis v0.1.0 h1:lDUwtanDf3f5YwlDwhbqnqCtj9Y/xc8ctxRE6HpQcws= github.com/gofiber/storage/testhelpers/redis v0.1.0/go.mod h1:Y1UccxbGVL04+TF5RuyCsksX+76hu6nJIWjPukBBgJ4= -github.com/gofiber/utils/v2 v2.4.1 h1:E2X9G8O5Mn7b2GDb0JU3IUk42Rw2npuhhepIbuJQ2po= -github.com/gofiber/utils/v2 v2.4.1/go.mod h1:I+RTsgMUdzFuifVc3LOEkfh32wQW9BfRl7l5RYjamW4= github.com/gofiber/utils/v2 v2.5.3 h1:8bOn+DSKeANT8R/eqm+g2FHU/8uuE4Ed2IXOrijS0yw= github.com/gofiber/utils/v2 v2.5.3/go.mod h1:tc9KqdRAQZRD/u0mN2d1ZHROGheSZv+0FcC9cg5OEdw= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -115,18 +94,12 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= -github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/s2a-go v0.1.10 h1:EMp+aOuXN6l8cE/gjF5Bt+vyZxsUuyCWe9chDWR/+uU= github.com/google/s2a-go v0.1.10/go.mod h1:pz4tyvwXvJLLbyrkh6FW1eS2zPUXMaTmyNhYtyP2tNw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.21 h1:OFdQ3tnCX/zaQ0Cedur3D3z7kI6HiLX9g3TiAN4/DFU= -github.com/googleapis/enterprise-certificate-proxy v0.3.21/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w= github.com/googleapis/enterprise-certificate-proxy v0.3.22 h1:NU4XpII6jD+Dxcot94fqjE+AfJoE/lQP9q3faYGzC/c= github.com/googleapis/enterprise-certificate-proxy v0.3.22/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w= -github.com/googleapis/gax-go/v2 v2.24.0 h1:myMaPYyF9MecEmvQqMqomIwn9t/4KCZN9qnwsS76wlg= -github.com/googleapis/gax-go/v2 v2.24.0/go.mod h1:IaTHBDd7NHxSCiu0vEs8pQZu4dGZrWwuSoxCnk16OFM= github.com/googleapis/gax-go/v2 v2.25.0 h1:77gyzCOrTyzPKfzGZJG/V5DO4/Kuamek8W+cYJQ4U8k= github.com/googleapis/gax-go/v2 v2.25.0/go.mod h1:sMKqnMesnKH+3wiRJROcttA+cJoZoGbZl1vDQ8XYtGk= github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= @@ -156,8 +129,6 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= -github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= @@ -204,12 +175,8 @@ github.com/molecule-man/go-brrr v1.1.1 h1:KYwusQhjtV3smALnhmafZf4OBrh7vP4p8O47Dy github.com/molecule-man/go-brrr v1.1.1/go.mod h1:7ybW6/7gA3oKY45jOfVNjSJDtrr6ea4tzbsTkjmQDC4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/openbao/openbao/api/auth/approle/v2 v2.6.0 h1:b24RZeBFDUyQX4liW07KMczDj2R9R/CsZgCKifSyOcs= -github.com/openbao/openbao/api/auth/approle/v2 v2.6.0/go.mod h1:bR50ddAcm6UWz1k13ZOtCOC90vqfCmA2V+lKa7sfoBU= github.com/openbao/openbao/api/auth/approle/v2 v2.7.0 h1:Z3sEM22/Nr5IrKmBXoR+b3okH/A5JjyiGOW74PKRscg= github.com/openbao/openbao/api/auth/approle/v2 v2.7.0/go.mod h1:OoMbWNiRtOdkxEMyzBTDyCzVv2uM3xzE4YMhwMOnux0= -github.com/openbao/openbao/api/v2 v2.6.0 h1:KvfspAaL9bab9hI8jFYkV2cgtSrwWtaG+k9AUTHWU4M= -github.com/openbao/openbao/api/v2 v2.6.0/go.mod h1:H4IWiH+2rgF/TbrsUbsfrMyGoqojkLqxPCRLENSMnSo= github.com/openbao/openbao/api/v2 v2.7.0 h1:3CD1l3tr39nQraCgFGAWA5vYvPFzZoZrt3NL7DMQKAc= github.com/openbao/openbao/api/v2 v2.7.0/go.mod h1:uXbMoyH2pjSvNyTepinUvLde8pOJB82EuhUCfOKnKbo= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -227,9 +194,8 @@ github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/shamaton/msgpack/v3 v3.2.0 h1:1q2Ms+MWmuRju+PuDMSFDB7p7621npeX4zprJN5Zck8= -github.com/shamaton/msgpack/v3 v3.2.0/go.mod h1:sgBYvEiyz8JR1NC3yGRoPVME9xXovpnh3l/plW1nfRo= github.com/shamaton/msgpack/v3 v3.2.3 h1:0r0aZtrXDFOPpwWJTEOpFq+Ih/JmMLm0pqLN5mSilrw= +github.com/shamaton/msgpack/v3 v3.2.3/go.mod h1:sgBYvEiyz8JR1NC3yGRoPVME9xXovpnh3l/plW1nfRo= github.com/shirou/gopsutil/v4 v4.26.8 h1:YQMTF/1J50B5+Y0vlo1eDRf5DoR7Gk69hY+8wjYkQeo= github.com/shirou/gopsutil/v4 v4.26.8/go.mod h1:5O9FjBiXoTDFatIWjZZosqj4pV0DRtLx598xGbBehzM= github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= @@ -251,14 +217,10 @@ github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqo github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.73.0 h1:ocTOORnBWtJ+P8t/6wAjdkchMzdfHmWx2VD/DPbgZ7s= -github.com/valyala/fasthttp v1.73.0/go.mod h1:EtXQDHaR+5P18p8wqDRFpUhxr108Ga9mXvVJXHRrN2k= github.com/valyala/fasthttp v1.74.0 h1:wMS9fnO2QTALozYx5pId2Vi7ZwU/epUkY8i/KPWCHoU= github.com/valyala/fasthttp v1.74.0/go.mod h1:3ARmLamUcw7ElxVtC8PXaGzQ6VEuvnetlkrwIklQBSE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= @@ -277,56 +239,36 @@ go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3 go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= -golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= -golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.297.0 h1:WktxTsnnx0yZNnsR6j0q6hR21RnnK81FHTOPy/ux4OE= -google.golang.org/api v0.297.0/go.mod h1:S4m8x0M6OkQpkOzGk1y9JG2sm4fFQrMh6dxzjCTszhE= google.golang.org/api v0.299.0 h1:b3K+ydSMd0kh6TQI6bJyApRQfqQX2MfSOaVkpM59mJw= google.golang.org/api v0.299.0/go.mod h1:zlR3GVA8b2R5nv5Ij9UWe37StVB3cxDD7DBFi4ZFsHw= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4= google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a h1:3Dnd1cDaZlB68lziofO+bJXpjOy8UfRv8Unt+yH8tQ4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= diff --git a/idp/initial_schema.dbml b/idp/initial_schema.dbml index 067713c..36fff89 100644 --- a/idp/initial_schema.dbml +++ b/idp/initial_schema.dbml @@ -353,6 +353,7 @@ Enum auth_method { Enum response_type { "code" + "id_token" "code id_token" } @@ -379,13 +380,6 @@ Enum account_credentials_type { "mcp" } -Enum transport { - "http" - "https" - "stdio" - "streamable_http" -} - Enum client_subject_type { "public" "pairwise" @@ -407,7 +401,6 @@ Table account_credentials as AC { // Internal for checks domain "varchar(250)" [not null] creation_method creation_method [not null] - transport transport [not null] version integer [not null, default: 1] // Client ID generated internally @@ -793,11 +786,6 @@ Ref: UCK.account_id > A.id [delete: cascade] Enum app_type { "web" // Web apps with server-side logic "native" // Native apps with client-side logic - "spa" // Single-page apps - "backend" // Backend apps - "device" // Device apps - "service" // Service apps - "mcp" // Model Context Protocol apps } Enum app_username_column { @@ -808,6 +796,7 @@ Enum app_username_column { Enum grant_type { "authorization_code" + "implicit" "refresh_token" "client_credentials" "urn:ietf:params:oauth:grant-type:device_code" @@ -852,7 +841,6 @@ Table apps as APP { // Common on all OAuth2 apps domain varchar(250) [not null] - transport transport [not null] allow_user_registration bool [not null] auth_providers "auth_provider[]" [not null] username_column app_username_column [not null] @@ -947,47 +935,6 @@ Ref: APK.account_id > A.id [delete: cascade] Ref: APK.app_id > APP.id [delete: cascade] Ref: APK.credentials_key_id > CK.id [delete: cascade] -Table app_related_apps as ARA { - account_id integer [not null] - app_id integer [not null] - related_app_id integer [not null] - - created_at timestamptz [not null, default: `now()`] - updated_at timestamptz [not null, default: `now()`] - - Indexes { - (app_id, related_app_id) [pk] - (account_id) [name: 'app_related_apps_account_id_idx'] - (app_id) [name: 'app_related_apps_app_id_idx'] - (related_app_id) [name: 'app_related_apps_related_app_id_idx'] - (app_id, related_app_id) [unique, name: 'app_related_apps_app_id_related_app_id_uidx'] - } -} -Ref: ARA.account_id > A.id [delete: cascade] -Ref: ARA.app_id > APP.id [delete: cascade] -Ref: ARA.related_app_id > APP.id [delete: cascade] - -Table app_service_configs as ASCONF { - id serial [pk] - - account_id integer [not null] - app_id integer [not null] - - user_auth_method "auth_method" [not null] - user_grant_types "grant_type[]" [not null] - allowed_domains "varchar(250)[]" [not null] - - created_at timestamptz [not null, default: `now()`] - updated_at timestamptz [not null, default: `now()`] - - Indexes { - (account_id) [name: 'app_service_configs_account_id_idx'] - (app_id) [unique, name: 'app_service_configs_app_id_uidx'] - } -} -Ref: ASCONF.account_id > A.id [delete: cascade] -Ref: ASCONF.app_id > APP.id [delete: cascade] - Table app_designs as AD { id serial [pk] @@ -1065,7 +1012,7 @@ Table app_dynamic_registration_configs as APDRC { initial_access_token_ttl integer [not null, default: 3600] // 1 hour initial_access_token_max_uses int [not null, default: 1] - allowed_grant_types "grant_type[]" [not null, default: '{ "authorization_code", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:jwt-bearer" }'] + allowed_grant_types "grant_type[]" [not null, default: '{ "authorization_code", "implicit", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:jwt-bearer" }'] allowed_response_types "response_type[]" [not null, default: '{ "code", "code id_token" }'] allowed_token_endpoint_auth_methods "auth_method[]" [not null, default: '{ "none", "client_secret_post", "client_secret_basic", "client_secret_jwt", "private_key_jwt" }'] max_redirect_uris int [not null, default: 10] diff --git a/idp/internal/controllers/account_credentials.go b/idp/internal/controllers/account_credentials.go index 2d767f4..c597d28 100644 --- a/idp/internal/controllers/account_credentials.go +++ b/idp/internal/controllers/account_credentials.go @@ -62,7 +62,6 @@ func (c *Controllers) CreateAccountCredentials(ctx fiber.Ctx) error { SoftwareID: body.SoftwareID, SoftwareVersion: body.SoftwareVersion, Algorithm: body.Algorithm, - Transport: body.Transport, }, ) if serviceErr != nil { @@ -181,7 +180,6 @@ func (c *Controllers) UpdateAccountCredentials(ctx fiber.Ctx) error { ClientID: urlParams.ClientID, Name: body.Name, Scopes: body.Scopes, - Transport: body.Transport, Domain: body.Domain, ClientURI: body.ClientURI, RedirectURIs: body.RedirectURIs, diff --git a/idp/internal/controllers/apps.go b/idp/internal/controllers/apps.go index 4b58bab..6373dfc 100644 --- a/idp/internal/controllers/apps.go +++ b/idp/internal/controllers/apps.go @@ -26,13 +26,8 @@ type AppType = string const ( appsLocation string = "apps" - appTypeWeb AppType = "web" - appTypeSPA AppType = "spa" - appTypeNative AppType = "native" - appTypeBackend AppType = "backend" - appTypeDevice AppType = "device" - appTypeService AppType = "service" - appTypeMCP AppType = "mcp" + appTypeWeb AppType = "web" + appTypeNative AppType = "native" ) func (c *Controllers) createWebApp( @@ -74,55 +69,7 @@ func (c *Controllers) createWebApp( DefaultScopes: baseBody.DefaultScopes, RedirectURIs: body.RedirectURIs, ResponseTypes: body.ResponseTypes, - AuthProviders: baseBody.AuthProviders, - Transport: body.Transport, - }) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusCreated) - return ctx.Status(fiber.StatusCreated).JSON(&appDTO) -} - -func (c *Controllers) createSPAApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - baseBody *bodies.CreateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "createSPAOrSpaApp") - - body := new(bodies.CreateAppBodySPA) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - appDTO, serviceErr := c.services.CreateSPANativeApp(ctx.Context(), services.CreateSPANativeAppOptions{ - RequestID: requestID, - AccountPublicID: accountClaims.AccountID, - AccountVersion: accountClaims.AccountVersion, - AppType: database.AppTypeSpa, - CreationMethod: database.CreationMethodManual, - Name: baseBody.Name, - AllowUserRegistration: baseBody.AllowUserRegistration, - Domain: baseBody.Domain, - Transport: body.Transport, - UsernameColumn: baseBody.UsernameColumn, - ResponseTypes: body.ResponseTypes, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - Contacts: baseBody.Contacts, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - RedirectURIs: body.RedirectURIs, - Scopes: baseBody.Scopes, - DefaultScopes: baseBody.DefaultScopes, + GrantTypes: body.GrantTypes, AuthProviders: baseBody.AuthProviders, }) if serviceErr != nil { @@ -149,7 +96,7 @@ func (c *Controllers) createNativeApp( return validateBodyErrorResponse(logger, ctx, err) } - appDTO, serviceErr := c.services.CreateSPANativeApp(ctx.Context(), services.CreateSPANativeAppOptions{ + appDTO, serviceErr := c.services.CreateNativeApp(ctx.Context(), services.CreateNativeAppOptions{ RequestID: requestID, AccountPublicID: accountClaims.AccountID, AccountVersion: accountClaims.AccountVersion, @@ -159,7 +106,6 @@ func (c *Controllers) createNativeApp( AppType: database.AppTypeNative, AllowUserRegistration: baseBody.AllowUserRegistration, Domain: baseBody.Domain, - Transport: body.Transport, ClientURI: baseBody.ClientURI, LogoURI: baseBody.LogoURI, TOSURI: baseBody.TOSURI, @@ -181,197 +127,6 @@ func (c *Controllers) createNativeApp( return ctx.Status(fiber.StatusCreated).JSON(&appDTO) } -func (c *Controllers) createBackendApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - baseBody *bodies.CreateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "createBackendApp") - - body := new(bodies.CreateAppBodyBackend) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - appDTO, serviceErr := c.services.CreateBackendApp(ctx.Context(), services.CreateBackendAppOptions{ - RequestID: requestID, - AccountPublicID: accountClaims.AccountID, - AccountVersion: accountClaims.AccountVersion, - CreationMethod: database.CreationMethodManual, - Name: baseBody.Name, - AllowUserRegistration: baseBody.AllowUserRegistration, - UsernameColumn: baseBody.UsernameColumn, - AuthMethod: body.TokenEndpointAuthMethod, - Algorithm: body.Algorithm, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - Contacts: baseBody.Contacts, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Domain: body.Domain, - Transport: body.Transport, - Scopes: baseBody.Scopes, - DefaultScopes: baseBody.DefaultScopes, - AuthProviders: baseBody.AuthProviders, - }) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusCreated) - return ctx.Status(fiber.StatusCreated).JSON(&appDTO) -} - -func (c *Controllers) createDeviceApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - baseBody *bodies.CreateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "createDeviceOrSpaApp") - - body := new(bodies.CreateAppBodyDevice) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - appDTO, serviceErr := c.services.CreateDeviceApp(ctx.Context(), services.CreateDeviceAppOptions{ - RequestID: requestID, - AccountPublicID: accountClaims.AccountID, - AccountVersion: accountClaims.AccountVersion, - CreationMethod: database.CreationMethodManual, - Name: baseBody.Name, - AllowUserRegistration: baseBody.AllowUserRegistration, - UsernameColumn: baseBody.UsernameColumn, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - Contacts: baseBody.Contacts, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Domain: baseBody.Domain, - BackendDomain: c.backendDomain, - Scopes: baseBody.Scopes, - DefaultScopes: baseBody.DefaultScopes, - AssociatedApps: body.AssociatedApps, - AuthProviders: baseBody.AuthProviders, - Transport: body.Transport, - }) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusCreated) - return ctx.Status(fiber.StatusCreated).JSON(&appDTO) -} - -func (c *Controllers) createServiceApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - baseBody *bodies.CreateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "createServiceApp") - - body := new(bodies.CreateAppBodyService) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - appDTO, serviceErr := c.services.CreateServiceApp(ctx.Context(), services.CreateServiceAppOptions{ - RequestID: requestID, - AccountPublicID: accountClaims.AccountID, - Name: baseBody.Name, - CreationMethod: database.CreationMethodManual, - AccountVersion: accountClaims.AccountVersion, - AllowUserRegistration: baseBody.AllowUserRegistration, - AuthMethod: body.TokenEndpointAuthMethod, - Algorithm: body.Algorithm, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - Contacts: baseBody.Contacts, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Scopes: baseBody.Scopes, - DefaultScopes: baseBody.DefaultScopes, - UsersAuthMethod: body.UsersAuthMethod, - Domain: baseBody.Domain, - Transport: body.Transport, - AllowedDomains: body.AllowedDomains, - AuthProviders: baseBody.AuthProviders, - }) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusCreated) - return ctx.Status(fiber.StatusCreated).JSON(&appDTO) -} - -func (c *Controllers) createMCPApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - baseBody *bodies.CreateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "createMCPApp") - - body := new(bodies.CreateAppBodyMCP) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - appDTO, serviceErr := c.services.CreateMCPApp(ctx.Context(), services.CreateMCPAppOptions{ - RequestID: requestID, - AccountPublicID: accountClaims.AccountID, - AccountVersion: accountClaims.AccountVersion, - CreationMethod: database.CreationMethodManual, - Name: baseBody.Name, - AllowUserRegistration: baseBody.AllowUserRegistration, - UsernameColumn: baseBody.UsernameColumn, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - Contacts: baseBody.Contacts, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Scopes: baseBody.Scopes, - DefaultScopes: baseBody.DefaultScopes, - Transport: body.Transport, - AuthMethod: body.TokenEndpointAuthMethod, - Algorithm: body.Algorithm, - RedirectURIs: body.RedirectURIs, - ResponseTypes: body.ResponseTypes, - Domain: baseBody.Domain, - AuthProviders: baseBody.AuthProviders, - }) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusCreated) - return ctx.Status(fiber.StatusCreated).JSON(&appDTO) -} - func (c *Controllers) CreateApp(ctx fiber.Ctx) error { requestID := getRequestID(ctx) logger := c.buildLogger(requestID, appsLocation, "CreateApp") @@ -393,18 +148,8 @@ func (c *Controllers) CreateApp(ctx fiber.Ctx) error { switch body.Type { case appTypeWeb: return c.createWebApp(ctx, requestID, &accountClaims, body) - case appTypeSPA: - return c.createSPAApp(ctx, requestID, &accountClaims, body) case appTypeNative: return c.createNativeApp(ctx, requestID, &accountClaims, body) - case appTypeBackend: - return c.createBackendApp(ctx, requestID, &accountClaims, body) - case appTypeDevice: - return c.createDeviceApp(ctx, requestID, &accountClaims, body) - case appTypeService: - return c.createServiceApp(ctx, requestID, &accountClaims, body) - case appTypeMCP: - return c.createMCPApp(ctx, requestID, &accountClaims, body) default: logger.WarnContext(ctx.Context(), "Invalid app type", "appType", body.Type) logResponse(logger, ctx, fiber.StatusBadRequest) @@ -551,76 +296,15 @@ func (c *Controllers) updateWebApp( return serviceErrorResponse(logger, ctx, serviceErr) } - completeAppDTO, serviceErr := c.services.UpdateWebSPANativeApp( - ctx.Context(), - appDTO, - services.UpdateWebSPANativeAppOptions{ - RequestID: requestID, - AccountID: accountID, - UsernameColumn: baseBody.UsernameColumn, - Name: baseBody.Name, - Domain: baseBody.Domain, - Transport: body.Transport, - AllowUserRegistration: baseBody.AllowUserRegistration, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Contacts: baseBody.Contacts, - RedirectURIs: body.RedirectURIs, - ResponseTypes: body.ResponseTypes, - AuthProviders: baseBody.AuthProviders, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusOK) - return ctx.Status(fiber.StatusOK).JSON(&completeAppDTO) -} - -func (c *Controllers) updateSPAApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - appDTO *dtos.AppDTO, - baseBody *bodies.UpdateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "updateSPAApp") - - body := new(bodies.UpdateAppBodySPA) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - accountID, serviceErr := c.services.GetAccountIDByPublicIDAndVersion( - ctx.Context(), - services.GetAccountIDByPublicIDAndVersionOptions{ - RequestID: requestID, - PublicID: accountClaims.AccountID, - Version: accountClaims.AccountVersion, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - completeAppDTO, serviceErr := c.services.UpdateWebSPANativeApp( + completeAppDTO, serviceErr := c.services.UpdateWebNativeApp( ctx.Context(), appDTO, - services.UpdateWebSPANativeAppOptions{ + services.UpdateWebNativeAppOptions{ RequestID: requestID, AccountID: accountID, UsernameColumn: baseBody.UsernameColumn, Name: baseBody.Name, Domain: baseBody.Domain, - Transport: body.Transport, AllowUserRegistration: baseBody.AllowUserRegistration, ClientURI: baseBody.ClientURI, LogoURI: baseBody.LogoURI, @@ -671,16 +355,15 @@ func (c *Controllers) updateNativeApp( return serviceErrorResponse(logger, ctx, serviceErr) } - completeAppDTO, serviceErr := c.services.UpdateWebSPANativeApp( + completeAppDTO, serviceErr := c.services.UpdateWebNativeApp( ctx.Context(), appDTO, - services.UpdateWebSPANativeAppOptions{ + services.UpdateWebNativeAppOptions{ RequestID: requestID, AccountID: accountID, UsernameColumn: baseBody.UsernameColumn, Name: baseBody.Name, Domain: baseBody.Domain, - Transport: body.Transport, AllowUserRegistration: baseBody.AllowUserRegistration, ClientURI: baseBody.ClientURI, LogoURI: baseBody.LogoURI, @@ -702,241 +385,6 @@ func (c *Controllers) updateNativeApp( return ctx.Status(fiber.StatusOK).JSON(&completeAppDTO) } -func (c *Controllers) updateServiceApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - appDTO *dtos.AppDTO, - baseBody *bodies.UpdateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "updateServiceApp") - - body := new(bodies.UpdateAppBodyService) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - accountID, serviceErr := c.services.GetAccountIDByPublicIDAndVersion( - ctx.Context(), - services.GetAccountIDByPublicIDAndVersionOptions{ - RequestID: requestID, - PublicID: accountClaims.AccountID, - Version: accountClaims.AccountVersion, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - completeAppDTO, serviceErr := c.services.UpdateServiceApp( - ctx.Context(), - appDTO, - services.UpdateServiceAppOptions{ - RequestID: requestID, - AccountID: accountID, - Name: baseBody.Name, - Domain: baseBody.Domain, - Transport: body.Transport, - AllowUserRegistration: baseBody.AllowUserRegistration, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Contacts: baseBody.Contacts, - AllowedDomains: body.AllowedDomains, - AuthProviders: baseBody.AuthProviders, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusOK) - return ctx.Status(fiber.StatusOK).JSON(&completeAppDTO) -} - -func (c *Controllers) updateBackendApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - appDTO *dtos.AppDTO, - baseBody *bodies.UpdateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "updateBackendApp") - - body := new(bodies.UpdateAppBodyBackend) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - accountID, serviceErr := c.services.GetAccountIDByPublicIDAndVersion( - ctx.Context(), - services.GetAccountIDByPublicIDAndVersionOptions{ - RequestID: requestID, - PublicID: accountClaims.AccountID, - Version: accountClaims.AccountVersion, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - completeAppDTO, serviceErr := c.services.UpdateBackendApp( - ctx.Context(), - appDTO, - services.UpdateBackendAppOptions{ - RequestID: requestID, - AccountID: accountID, - UsernameColumn: baseBody.UsernameColumn, - Name: baseBody.Name, - Domain: body.Domain, - Transport: body.Transport, - AllowUserRegistration: baseBody.AllowUserRegistration, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Contacts: baseBody.Contacts, - AuthProviders: baseBody.AuthProviders, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusOK) - return ctx.Status(fiber.StatusOK).JSON(&completeAppDTO) -} - -func (c *Controllers) updateDeviceApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - appDTO *dtos.AppDTO, - baseBody *bodies.UpdateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "updateDeviceApp") - - body := new(bodies.UpdateAppBodyDevice) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - accountID, serviceErr := c.services.GetAccountIDByPublicIDAndVersion( - ctx.Context(), - services.GetAccountIDByPublicIDAndVersionOptions{ - RequestID: requestID, - PublicID: accountClaims.AccountID, - Version: accountClaims.AccountVersion, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - completeAppDTO, serviceErr := c.services.UpdateDeviceApp( - ctx.Context(), - appDTO, - services.UpdateDeviceAppOptions{ - RequestID: requestID, - AccountID: accountID, - UsernameColumn: baseBody.UsernameColumn, - Name: baseBody.Name, - Domain: baseBody.Domain, - Transport: body.Transport, - AllowUserRegistration: baseBody.AllowUserRegistration, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Contacts: baseBody.Contacts, - BackendDomain: c.backendDomain, - AssociatedApps: body.AssociatedApps, - AuthProviders: baseBody.AuthProviders, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusOK) - return ctx.Status(fiber.StatusOK).JSON(&completeAppDTO) -} - -func (c *Controllers) updateMCPApp( - ctx fiber.Ctx, - requestID string, - accountClaims *tokens.AccountClaims, - appDTO *dtos.AppDTO, - baseBody *bodies.UpdateAppBodyBase, -) error { - logger := c.buildLogger(requestID, appsLocation, "updateMCPApp") - - body := new(bodies.UpdateAppBodyMCP) - if err := ctx.Bind().Body(body); err != nil { - return parseRequestErrorResponse(logger, ctx, err) - } - if err := c.validate.StructCtx(ctx.Context(), body); err != nil { - return validateBodyErrorResponse(logger, ctx, err) - } - - accountID, serviceErr := c.services.GetAccountIDByPublicIDAndVersion( - ctx.Context(), - services.GetAccountIDByPublicIDAndVersionOptions{ - RequestID: requestID, - PublicID: accountClaims.AccountID, - Version: accountClaims.AccountVersion, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - completeAppDTO, serviceErr := c.services.UpdateMCPApp( - ctx.Context(), - appDTO, - services.UpdateMCPAppOptions{ - RequestID: requestID, - AccountID: accountID, - Name: baseBody.Name, - UsernameColumn: baseBody.UsernameColumn, - ClientURI: baseBody.ClientURI, - LogoURI: baseBody.LogoURI, - TOSURI: baseBody.TOSURI, - PolicyURI: baseBody.PolicyURI, - SoftwareID: baseBody.SoftwareID, - SoftwareVersion: baseBody.SoftwareVersion, - Contacts: baseBody.Contacts, - Domain: baseBody.Domain, - RedirectURIs: body.RedirectURIs, - ResponseTypes: body.ResponseTypes, - AllowUserRegistration: baseBody.AllowUserRegistration, - AuthProviders: baseBody.AuthProviders, - }, - ) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusOK) - return ctx.Status(fiber.StatusOK).JSON(&completeAppDTO) -} - func (c *Controllers) UpdateApp(ctx fiber.Ctx) error { requestID := getRequestID(ctx) logger := c.buildLogger(requestID, appsLocation, "UpdateApp") @@ -972,18 +420,8 @@ func (c *Controllers) UpdateApp(ctx fiber.Ctx) error { switch appDTO.AppType { case database.AppTypeWeb: return c.updateWebApp(ctx, requestID, &accountClaims, &appDTO, body) - case database.AppTypeSpa: - return c.updateSPAApp(ctx, requestID, &accountClaims, &appDTO, body) case database.AppTypeNative: return c.updateNativeApp(ctx, requestID, &accountClaims, &appDTO, body) - case database.AppTypeBackend: - return c.updateBackendApp(ctx, requestID, &accountClaims, &appDTO, body) - case database.AppTypeDevice: - return c.updateDeviceApp(ctx, requestID, &accountClaims, &appDTO, body) - case database.AppTypeService: - return c.updateServiceApp(ctx, requestID, &accountClaims, &appDTO, body) - case database.AppTypeMcp: - return c.updateMCPApp(ctx, requestID, &accountClaims, &appDTO, body) default: logger.ErrorContext(ctx.Context(), "Invalid app type", "appType", appDTO.AppType) return serviceErrorResponse(logger, ctx, exceptions.NewInternalServerError()) diff --git a/idp/internal/controllers/bodies/account_credentials.go b/idp/internal/controllers/bodies/account_credentials.go index 6ccd0f5..fe39f97 100644 --- a/idp/internal/controllers/bodies/account_credentials.go +++ b/idp/internal/controllers/bodies/account_credentials.go @@ -10,7 +10,6 @@ type CreateAccountCredentialsBody struct { Type string `json:"type" validate:"required,oneof=native service mcp"` Name string `json:"name" validate:"required,min=1,max=255"` Scopes []string `json:"scopes" validate:"required,unique,dive,oneof=email profile account:admin account:users:read account:users:write account:apps:read account:apps:write account:credentials:read account:credentials:write account:auth_providers:read"` - Transport string `json:"transport,omitempty" validate:"required_if=Type mcp,oneof=http https stdio streamable_http"` TokenEndpointAuthMethod string `json:"token_endpoint_auth_method" validate:"required,oneof=none client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` Domain string `json:"domain,omitempty" validate:"omitempty,fqdn,max=250"` ClientURI string `json:"client_uri" validate:"required,uri"` @@ -26,7 +25,6 @@ type CreateAccountCredentialsBody struct { type UpdateAccountCredentialsBody struct { Name string `json:"name" validate:"required,min=1,max=255"` Scopes []string `json:"scopes" validate:"required,unique,dive,oneof=account:admin account:users:read account:users:write account:apps:read account:apps:write account:credentials:read account:credentials:write account:auth_providers:read"` - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` Domain string `json:"domain,omitempty" validate:"omitempty,fqdn,max=250"` ClientURI string `json:"client_uri" validate:"required,uri"` RedirectURIs []string `json:"redirect_uris,omitempty" validate:"omitempty,unique,dive,uri"` diff --git a/idp/internal/controllers/bodies/app_dynamic_registration_configs.go b/idp/internal/controllers/bodies/app_dynamic_registration_configs.go index 559e5c2..adf6360 100644 --- a/idp/internal/controllers/bodies/app_dynamic_registration_configs.go +++ b/idp/internal/controllers/bodies/app_dynamic_registration_configs.go @@ -7,22 +7,21 @@ package bodies type AppDynamicRegistrationConfigBody struct { - AllowedAppTypes []string `json:"allowed_app_types" validate:"required,unique,min=1,dive,oneof=web spa native backend device service mcp"` + AllowedAppTypes []string `json:"allowed_app_types" validate:"required,unique,min=1,dive,oneof=web native"` DefaultAllowUserRegistration bool `json:"default_allow_user_registration"` DefaultAuthProviders []string `json:"default_auth_providers,omitempty" validate:"omitempty,unique,dive,oneof=local apple facebook github google microsoft"` DefaultUsernameColumn string `json:"default_username_column,omitempty" validate:"omitempty,oneof=email username both"` DefaultAllowedScopes []string `json:"default_allowed_scopes,omitempty" validate:"omitempty,unique,dive,single_scope"` DefaultScopes []string `json:"default_scopes,omitempty" validate:"omitempty,unique,dive,single_scope"` - RequireVerifiedDomainsAppTypes []string `json:"require_verified_domains_app_types,omitempty" validate:"omitempty,unique,dive,oneof=web spa native backend device service mcp"` - RequireSoftwareStatementAppTypes []string `json:"require_software_statement_app_types,omitempty" validate:"omitempty,unique,dive,oneof=web spa native backend device service mcp"` + RequireVerifiedDomainsAppTypes []string `json:"require_verified_domains_app_types,omitempty" validate:"omitempty,unique,dive,oneof=web native"` + RequireSoftwareStatementAppTypes []string `json:"require_software_statement_app_types,omitempty" validate:"omitempty,unique,dive,oneof=web native"` SoftwareStatementVerificationMethods []string `json:"software_statement_verification_methods,omitempty" validate:"omitempty,unique,min=1,max=2,dive,oneof=manual jwks_uri"` - RequireInitialAccessTokenAppTypes []string `json:"require_initial_access_token_app_types,omitempty" validate:"omitempty,unique,dive,oneof=web spa native backend device service mcp"` + RequireInitialAccessTokenAppTypes []string `json:"require_initial_access_token_app_types,omitempty" validate:"omitempty,unique,dive,oneof=web native"` InitialAccessTokenGenerationMethods []string `json:"initial_access_token_generation_methods,omitempty" validate:"omitempty,unique,min=1,max=2,dive,oneof=manual authorization_code"` InitialAccessTokenTtl int32 `json:"initial_access_token_ttl,omitempty" validate:"omitempty,min=1"` InitialAccessTokenMaxUses int32 `json:"initial_access_token_max_uses,omitempty" validate:"omitempty,min=1"` - AllowedGrantTypes []string `json:"allowed_grant_types,omitempty" validate:"omitempty,unique,min=1,dive,oneof=authorization_code refresh_token client_credentials urn:ietf:params:oauth:grant-type:device_code urn:ietf:params:oauth:grant-type:jwt-bearer"` - AllowedResponseTypes []string `json:"allowed_response_types,omitempty" validate:"omitempty,unique,dive,oneof=code 'code id_token'"` - AllowedTokenEndpointAuthMethods []string `json:"allowed_token_endpoint_auth_methods,omitempty" validate:"omitempty,unique,dive,oneof=none client_secret_post client_secret_basic client_secret_jwt private_key_jwt"` + AllowedGrantTypes []string `json:"allowed_grant_types,omitempty" validate:"omitempty,unique,min=1,dive,oneof=authorization_code implicit refresh_token client_credentials urn:ietf:params:oauth:grant-type:device_code urn:ietf:params:oauth:grant-type:jwt-bearer"` + AllowedResponseTypes []string `json:"allowed_response_types,omitempty" validate:"omitempty,unique,dive,oneof=code id_token 'code id_token'"` + AllowedTokenEndpointAuthMethods []string `json:"allowed_token_endpoint_auth_methods,omitempty" validate:"omitempty,unique,dive,oneof=none client_secret_post client_secret_basic client_secret_jwt private_key_jwt"` MaxRedirectUris int32 `json:"max_redirect_uris,omitempty" validate:"omitempty,min=1"` } - diff --git a/idp/internal/controllers/bodies/apps.go b/idp/internal/controllers/bodies/apps.go index ad13063..4167b0c 100644 --- a/idp/internal/controllers/bodies/apps.go +++ b/idp/internal/controllers/bodies/apps.go @@ -7,7 +7,7 @@ package bodies type CreateAppBodyBase struct { - Type string `json:"type" validate:"required,oneof=web spa native backend device service mcp"` + Type string `json:"type" validate:"required,oneof=web native"` Name string `json:"name" validate:"required,min=1,max=255"` Domain string `json:"domain" validate:"omitempty,fqdn,max=250"` ClientURI string `json:"client_uri" validate:"required,url"` @@ -40,87 +40,24 @@ type UpdateAppBodyBase struct { } type CreateAppBodyWeb struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` Algorithm string `json:"algorithm,omitempty" validate:"omitempty,oneof=ES256 EdDSA"` - TokenEndpointAuthMethod string `json:"token_endpoint_auth_method" validate:"required,oneof=client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` + TokenEndpointAuthMethod string `json:"token_endpoint_auth_method" validate:"required,oneof=none client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` + GrantTypes []string `json:"grant_types,omitempty" validate:"omitempty,unique,dive,oneof=authorization_code implicit refresh_token client_credentials urn:ietf:params:oauth:grant-type:jwt-bearer urn:ietf:params:oauth:grant-type:device_code"` ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,unique,dive,oneof=code id_token 'code id_token'"` - RedirectURIs []string `json:"redirect_uris" validate:"required,unique,min=1,dive,url"` + RedirectURIs []string `json:"redirect_uris,omitempty" validate:"omitempty,unique,dive,url"` } type UpdateAppBodyWeb struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,unique,dive,oneof=code id_token 'code id_token'"` - RedirectURIs []string `json:"redirect_uris" validate:"required,unique,min=1,dive,url"` -} - -type CreateAppBodySPA struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` - ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,unique,dive,oneof=code id_token 'code id_token'"` - RedirectURIs []string `json:"redirect_uris" validate:"required,unique,min=1,dive,url"` -} - -type UpdateAppBodySPA struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` - ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,unique,dive,oneof=code id_token 'code id_token'"` - RedirectURIs []string `json:"redirect_uris" validate:"required,unique,min=1,dive,url"` + RedirectURIs []string `json:"redirect_uris,omitempty" validate:"omitempty,unique,dive,url"` } type CreateAppBodyNative struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,unique,dive,oneof=code id_token 'code id_token'"` RedirectURIs []string `json:"redirect_uris" validate:"required,unique,min=1,dive,uri"` } type UpdateAppBodyNative struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,unique,dive,oneof=code id_token 'code id_token'"` RedirectURIs []string `json:"redirect_uris" validate:"required,unique,min=1,dive,uri"` } - -type CreateAppBodyBackend struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` - TokenEndpointAuthMethod string `json:"token_endpoint_auth_method" validate:"required,oneof=client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` - Algorithm string `json:"algorithm,omitempty" validate:"omitempty,oneof=ES256 EdDSA"` - Domain string `json:"domain" validate:"omitempty,fqdn"` -} - -type UpdateAppBodyBackend struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` - Domain string `json:"domain" validate:"omitempty,fqdn"` -} - -type CreateAppBodyDevice struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` - AssociatedApps []string `json:"associated_apps,omitempty" validate:"omitempty,dive,min=22,max=22,alphanum"` -} - -type UpdateAppBodyDevice struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` - AssociatedApps []string `json:"associated_apps,omitempty" validate:"omitempty,dive,min=22,max=22,alphanum"` -} - -type CreateAppBodyService struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` - TokenEndpointAuthMethod string `json:"token_endpoint_auth_method" validate:"required,oneof=client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` - Algorithm string `json:"algorithm,omitempty" validate:"omitempty,oneof=ES256 EdDSA"` - UsersAuthMethod string `json:"users_auth_method" validate:"required,oneof=client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` - AllowedDomains []string `json:"allowed_domains,omitempty" validate:"required_if=UsersAuthMethod private_key_jwt,unique,dive,fqdn"` -} - -type UpdateAppBodyService struct { - Transport string `json:"transport,omitempty" validate:"omitempty,oneof=http https"` - AllowedDomains []string `json:"allowed_domains,omitempty" validate:"omitempty,unique,dive,fqdn,max=250"` -} - -type CreateAppBodyMCP struct { - Transport string `json:"transport" validate:"required,oneof=stdio streamable_http"` - TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty" validate:"required_if=Transport stdio,oneof=client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` - Algorithm string `json:"algorithm,omitempty" validate:"omitempty,oneof=ES256 EdDSA"` - ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,unique,dive,oneof=code 'code id_token'"` - RedirectURIs []string `json:"redirect_uris,omitempty" validate:"omitempty,unique,min=1,dive,uri"` -} - -type UpdateAppBodyMCP struct { - ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,unique,dive,oneof=code 'code id_token'"` - RedirectURIs []string `json:"redirect_uris,omitempty" validate:"omitempty,unique,min=1,dive,uri"` -} diff --git a/idp/internal/controllers/bodies/oauth_dynamic_registration.go b/idp/internal/controllers/bodies/oauth_dynamic_registration.go index f08734b..b096b1e 100644 --- a/idp/internal/controllers/bodies/oauth_dynamic_registration.go +++ b/idp/internal/controllers/bodies/oauth_dynamic_registration.go @@ -14,9 +14,9 @@ type OAuthDynamicClientRegistrationBody struct { ClientSecret string `json:"client_secret,omitempty" validate:"omitempty"` RedirectURIs []string `json:"redirect_uris,omitempty" validate:"omitempty,min=1,dive,uri"` TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty" validate:"omitempty,oneof=none client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` - ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,dive,oneof=code 'code id_token'"` - GrantTypes []string `json:"grant_types,omitempty" validate:"omitempty,min=1,dive,oneof=authorization_code refresh_token client_credentials urn:ietf:params:oauth:grant-type:jwt-bearer"` - ApplicationType string `json:"application_type,omitempty" validate:"omitempty,oneof=native service mcp web spa backend device"` + ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,dive,oneof=code id_token 'code id_token'"` + GrantTypes []string `json:"grant_types,omitempty" validate:"omitempty,min=1,dive,oneof=authorization_code implicit refresh_token client_credentials urn:ietf:params:oauth:grant-type:jwt-bearer"` + ApplicationType string `json:"application_type,omitempty" validate:"omitempty,oneof=web native service mcp"` ClientName string `json:"client_name,omitempty" validate:"omitempty,min=1,max=255"` ClientURI string `json:"client_uri,omitempty" validate:"omitempty,url"` LogoURI string `json:"logo_uri,omitempty" validate:"omitempty,url"` diff --git a/idp/internal/controllers/helpers.go b/idp/internal/controllers/helpers.go index 58ddf8f..0ca8566 100644 --- a/idp/internal/controllers/helpers.go +++ b/idp/internal/controllers/helpers.go @@ -2,7 +2,8 @@ // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/.\n +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + package controllers import ( @@ -134,8 +135,15 @@ func bearerAuthenticationRequired(logger *slog.Logger, ctx fiber.Ctx) error { return ctx.Status(fiber.StatusUnauthorized).Send(nil) } -func oauthErrorResponse(logger *slog.Logger, ctx fiber.Ctx, message string) error { - resErr := exceptions.NewOAuthError(message) +func oauthErrorResponse(logger *slog.Logger, ctx fiber.Ctx, message string, description ...string) error { + var desc string + if len(description) > 0 { + desc = description[0] + } + resErr := exceptions.OAuthErrorResponse{ + Error: message, + ErrorDescription: desc, + } if message == exceptions.OAuthErrorInvalidToken { ctx.Set(fiber.HeaderWWWAuthenticate, `Bearer error="invalid_token"`) } @@ -157,7 +165,10 @@ func oauthErrorResponse(logger *slog.Logger, ctx fiber.Ctx, message string) erro return ctx.Status(fiber.StatusInternalServerError).JSON(&resErr) default: logResponse(logger, ctx, fiber.StatusBadRequest) - resErr = exceptions.NewOAuthError(exceptions.OAuthErrorInvalidRequest) + resErr = exceptions.OAuthErrorResponse{ + Error: exceptions.OAuthErrorInvalidRequest, + ErrorDescription: desc, + } return ctx.Status(fiber.StatusBadRequest).JSON(&resErr) } } @@ -209,30 +220,38 @@ func dynamicRegistrationServiceError( ctx fiber.Ctx, serviceErr *exceptions.ServiceError, ) error { + if serviceErr == nil { + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorServerError, "Unable to process dynamic registration request") + } + desc := serviceErr.Message switch serviceErr.Code { case exceptions.OAuthErrorInvalidClientMetadata: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata, desc) case exceptions.OAuthErrorInvalidRequest: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidRequest) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidRequest, desc) case exceptions.OAuthErrorInvalidClient: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClient) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClient, desc) case exceptions.OAuthErrorInvalidRedirectURI: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidRedirectURI) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidRedirectURI, desc) case exceptions.OAuthErrorInvalidToken: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidToken) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidToken, desc) case exceptions.CodeForbidden: - return ctx.Status(fiber.StatusForbidden).JSON(exceptions.NewOAuthError(exceptions.OAuthErrorAccessDenied)) + return ctx.Status(fiber.StatusForbidden).JSON(exceptions.OAuthErrorResponse{ + Error: exceptions.OAuthErrorAccessDenied, + ErrorDescription: desc, + }) case exceptions.CodeUnauthorized: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidToken) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidToken, desc) case exceptions.OAuthErrorUnauthorizedClient: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorUnauthorizedClient) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorUnauthorizedClient, desc) case exceptions.CodeNotFound, exceptions.CodeValidation: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata, desc) case exceptions.CodeInvalidToken: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidSoftwareStatement) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidSoftwareStatement, desc) case exceptions.CodeUnauthorizedToken: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorUnapprovedSoftwareStatement) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorUnapprovedSoftwareStatement, desc) default: - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorServerError) + logger.ErrorContext(ctx.Context(), "Dynamic registration failed", "serviceError", serviceErr) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorServerError, "Unable to process dynamic registration request") } } diff --git a/idp/internal/controllers/oauth_dynamic_registration_account.go b/idp/internal/controllers/oauth_dynamic_registration_account.go index 0fbd507..7cd5ff8 100644 --- a/idp/internal/controllers/oauth_dynamic_registration_account.go +++ b/idp/internal/controllers/oauth_dynamic_registration_account.go @@ -9,9 +9,10 @@ package controllers import ( "encoding/json" "errors" - "github.com/gofiber/fiber/v3" "strings" + "github.com/gofiber/fiber/v3" + "github.com/tugascript/devlogs/idp/internal/controllers/bodies" "github.com/tugascript/devlogs/idp/internal/exceptions" "github.com/tugascript/devlogs/idp/internal/providers/tokens" @@ -35,7 +36,7 @@ func (c *Controllers) OAuthDynamicRegistration(ctx fiber.Ctx) error { body := new(bodies.OAuthDynamicClientRegistrationBody) if err := ctx.Bind().Body(body); err != nil { - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata, err.Error()) } iatDomain, _ := ctx.Locals("domain").(string) @@ -93,97 +94,6 @@ func (c *Controllers) OAuthDynamicRegistration(ctx fiber.Ctx) error { return ctx.Status(fiber.StatusCreated).JSON(accountCredentialsDTO.Registration) } -func (c *Controllers) OAuthAppDynamicRegistration(ctx fiber.Ctx) error { - requestID := getRequestID(ctx) - logger := c.buildLogger(requestID, oauthDynamicRegistration, "OAuthAppDynamicRegistration") - logRequest(logger, ctx) - ctx.Set(fiber.HeaderCacheControl, "no-store") - ctx.Set(fiber.HeaderPragma, "no-cache") - - _, accountID, serviceErr := getHostAccount(ctx) - if serviceErr != nil { - return serviceErrorResponse(logger, ctx, serviceErr) - } - - body := new(bodies.OAuthDynamicClientRegistrationBody) - if err := ctx.Bind().Body(body); err != nil { - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata) - } - - isAuthenticated, ok := ctx.Locals("isAuthenticated").(bool) - if !ok { - logger.ErrorContext(ctx.Context(), "isAuthenticated should be set in context by middleware") - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorServerError) - } - - account, ok := ctx.Locals("account").(tokens.AccountClaims) - if isAuthenticated && !ok { - logger.ErrorContext(ctx.Context(), "account should be set in context by middleware") - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorServerError) - } - - var iatDomain string - if isAuthenticated { - iatDomain, _ = ctx.Locals("domain").(string) - } - appDTO, serviceErr := c.services.CreateAppCredentialsRegistration( - ctx.Context(), - services.CreateAppCredentialsRegistrationOptions{ - InitialAccessTokenDomain: iatDomain, - RequestID: requestID, - IsAuthenticated: isAuthenticated, - AccountID: accountID, - AccountVersion: account.AccountVersion, - ApplicationType: body.ApplicationType, - RedirectURIs: body.RedirectURIs, - TokenEndpointAuthMethod: body.TokenEndpointAuthMethod, - GrantTypes: body.GrantTypes, - ResponseTypes: body.ResponseTypes, - ClientName: body.ClientName, - ClientURI: body.ClientURI, - LogoURI: body.LogoURI, - TOSURI: body.TOSURI, - PolicyURI: body.PolicyURI, - Contacts: body.Contacts, - SoftwareID: body.SoftwareID, - SoftwareVersion: body.SoftwareVersion, - SoftwareStatement: body.SoftwareStatement, - JWKsURI: body.JWKsURI, - JWKs: body.JWKs, - FrontendDomain: c.frontendDomain, - BackendDomain: c.backendDomain, - RequireAuthTime: body.RequireAuthTime, - DefaultMaxAge: body.DefaultMaxAge, - SubjectType: body.SubjectType, - IDTokenSignedResponseAlg: body.IDTokenSignedResponseAlg, - IDTokenEncryptedResponseAlg: body.IDTokenEncryptedResponseAlg, - IDTokenEncryptedResponseEnc: body.IDTokenEncryptedResponseEnc, - RequestObjectSigningAlg: body.RequestObjectSigningAlg, - RequestObjectEncryptionAlg: body.RequestObjectEncryptionAlg, - RequestObjectEncryptionEnc: body.RequestObjectEncryptionEnc, - DefaultACRValues: body.DefaultACRValues, - Scope: body.Scope, - SectorIdentifierURI: body.SectorIdentifierURI, - InitiateLoginURI: body.InitiateLoginURI, - RequestURIs: body.RequestURIs, - UserInfoSignedResponseAlg: body.UserInfoSignedResponseAlg, - UserInfoEncryptedResponseAlg: body.UserInfoEncryptedResponseAlg, - UserInfoEncryptedResponseEnc: body.UserInfoEncryptedResponseEnc, - TokenEndpointAuthSigningAlg: body.TokenEndpointAuthSigningAlg, - AccessTokenSigningAlg: body.AccessTokenSigningAlg, - }, - ) - if serviceErr != nil { - if !isAuthenticated && serviceErr.Code == exceptions.OAuthErrorInvalidToken { - return bearerAuthenticationRequired(logger, ctx) - } - return dynamicRegistrationServiceError(logger, ctx, serviceErr) - } - - logResponse(logger, ctx, fiber.StatusCreated) - return ctx.Status(fiber.StatusCreated).JSON(appDTO.Registration) -} - func registrationClientIDFromContext(ctx fiber.Ctx) (string, bool) { clientID, ok := ctx.Locals("registrationClientID").(string) return clientID, ok && clientID != "" @@ -290,7 +200,7 @@ func (c *Controllers) OAuthDynamicRegistrationUpdate(ctx fiber.Ctx) error { } body, err := c.bindRegistrationBody(ctx) if err != nil { - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata, err.Error()) } dto, serviceErr := c.services.UpdateRegisteredAccountCredentials(ctx.Context(), services.UpdateRegisteredClientOptions{ @@ -337,7 +247,7 @@ func (c *Controllers) OAuthAppDynamicRegistrationUpdate(ctx fiber.Ctx) error { } body, err := c.bindRegistrationBody(ctx) if err != nil { - return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata) + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata, err.Error()) } dto, serviceErr := c.services.UpdateRegisteredApp(ctx.Context(), services.UpdateRegisteredAppOptions{ diff --git a/idp/internal/controllers/oauth_dynamic_registration_apps.go b/idp/internal/controllers/oauth_dynamic_registration_apps.go index a30e6f8..00ea205 100644 --- a/idp/internal/controllers/oauth_dynamic_registration_apps.go +++ b/idp/internal/controllers/oauth_dynamic_registration_apps.go @@ -12,6 +12,7 @@ import ( "net/url" "github.com/gofiber/fiber/v3" + "github.com/tugascript/devlogs/idp/internal/providers/tokens" "github.com/tugascript/devlogs/idp/internal/controllers/bodies" "github.com/tugascript/devlogs/idp/internal/controllers/params" @@ -768,3 +769,94 @@ func (c *Controllers) AppsOAuthDynamicRegistrationIATToken(ctx fiber.Ctx) error logResponse(logger, ctx, fiber.StatusOK) return ctx.Status(fiber.StatusOK).JSON(authDTO) } + +func (c *Controllers) OAuthAppDynamicRegistration(ctx fiber.Ctx) error { + requestID := getRequestID(ctx) + logger := c.buildLogger(requestID, oauthDynamicRegistration, "OAuthAppDynamicRegistration") + logRequest(logger, ctx) + ctx.Set(fiber.HeaderCacheControl, "no-store") + ctx.Set(fiber.HeaderPragma, "no-cache") + + _, accountID, serviceErr := getHostAccount(ctx) + if serviceErr != nil { + return serviceErrorResponse(logger, ctx, serviceErr) + } + + body := new(bodies.OAuthDynamicClientRegistrationBody) + if err := ctx.Bind().Body(body); err != nil { + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorInvalidClientMetadata, err.Error()) + } + + isAuthenticated, ok := ctx.Locals("isAuthenticated").(bool) + if !ok { + logger.ErrorContext(ctx.Context(), "isAuthenticated should be set in context by middleware") + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorServerError) + } + + account, ok := ctx.Locals("account").(tokens.AccountClaims) + if isAuthenticated && !ok { + logger.ErrorContext(ctx.Context(), "account should be set in context by middleware") + return oauthErrorResponse(logger, ctx, exceptions.OAuthErrorServerError) + } + + var iatDomain string + if isAuthenticated { + iatDomain, _ = ctx.Locals("domain").(string) + } + appDTO, serviceErr := c.services.CreateAppCredentialsRegistration( + ctx.Context(), + services.CreateAppCredentialsRegistrationOptions{ + InitialAccessTokenDomain: iatDomain, + RequestID: requestID, + IsAuthenticated: isAuthenticated, + AccountID: accountID, + AccountVersion: account.AccountVersion, + ApplicationType: body.ApplicationType, + RedirectURIs: body.RedirectURIs, + TokenEndpointAuthMethod: body.TokenEndpointAuthMethod, + GrantTypes: body.GrantTypes, + ResponseTypes: body.ResponseTypes, + ClientName: body.ClientName, + ClientURI: body.ClientURI, + LogoURI: body.LogoURI, + TOSURI: body.TOSURI, + PolicyURI: body.PolicyURI, + Contacts: body.Contacts, + SoftwareID: body.SoftwareID, + SoftwareVersion: body.SoftwareVersion, + SoftwareStatement: body.SoftwareStatement, + JWKsURI: body.JWKsURI, + JWKs: body.JWKs, + FrontendDomain: c.frontendDomain, + BackendDomain: c.backendDomain, + RequireAuthTime: body.RequireAuthTime, + DefaultMaxAge: body.DefaultMaxAge, + SubjectType: body.SubjectType, + IDTokenSignedResponseAlg: body.IDTokenSignedResponseAlg, + IDTokenEncryptedResponseAlg: body.IDTokenEncryptedResponseAlg, + IDTokenEncryptedResponseEnc: body.IDTokenEncryptedResponseEnc, + RequestObjectSigningAlg: body.RequestObjectSigningAlg, + RequestObjectEncryptionAlg: body.RequestObjectEncryptionAlg, + RequestObjectEncryptionEnc: body.RequestObjectEncryptionEnc, + DefaultACRValues: body.DefaultACRValues, + Scope: body.Scope, + SectorIdentifierURI: body.SectorIdentifierURI, + InitiateLoginURI: body.InitiateLoginURI, + RequestURIs: body.RequestURIs, + UserInfoSignedResponseAlg: body.UserInfoSignedResponseAlg, + UserInfoEncryptedResponseAlg: body.UserInfoEncryptedResponseAlg, + UserInfoEncryptedResponseEnc: body.UserInfoEncryptedResponseEnc, + TokenEndpointAuthSigningAlg: body.TokenEndpointAuthSigningAlg, + AccessTokenSigningAlg: body.AccessTokenSigningAlg, + }, + ) + if serviceErr != nil { + if !isAuthenticated && serviceErr.Code == exceptions.OAuthErrorInvalidToken { + return bearerAuthenticationRequired(logger, ctx) + } + return dynamicRegistrationServiceError(logger, ctx, serviceErr) + } + + logResponse(logger, ctx, fiber.StatusCreated) + return ctx.Status(fiber.StatusCreated).JSON(appDTO.Registration) +} diff --git a/idp/internal/controllers/params/apps.go b/idp/internal/controllers/params/apps.go index c8cd5aa..523fd15 100644 --- a/idp/internal/controllers/params/apps.go +++ b/idp/internal/controllers/params/apps.go @@ -11,5 +11,5 @@ type GetAppsQueryParams struct { Offset int `validate:"min=0"` Order string `validate:"oneof=date name"` Name string `validate:"omitempty,max=50,min=1,alphanum"` - Type string `validate:"omitempty,oneof=web spa native backend device service"` + Type string `validate:"omitempty,oneof=web native"` } diff --git a/idp/internal/controllers/registration_errors_test.go b/idp/internal/controllers/registration_errors_test.go index 120834a..6503453 100644 --- a/idp/internal/controllers/registration_errors_test.go +++ b/idp/internal/controllers/registration_errors_test.go @@ -1,17 +1,57 @@ package controllers import ( + "bytes" "encoding/json" "io" "log/slog" "net/http" "net/http/httptest" + "strings" "testing" "github.com/gofiber/fiber/v3" + "github.com/tugascript/devlogs/idp/internal/controllers/bodies" "github.com/tugascript/devlogs/idp/internal/exceptions" + "github.com/tugascript/devlogs/idp/internal/server/validations" ) +func TestRegistrationServerErrorsDoNotExposeInternalDetails(t *testing.T) { + const detail = "database connection failed at internal-db:5432" + for _, code := range []string{exceptions.CodeInternalServerError, exceptions.OAuthErrorServerError, "unexpected_service_error", ""} { + t.Run(code, func(t *testing.T) { + var logs bytes.Buffer + logger := slog.New(slog.NewTextHandler(&logs, nil)) + app := fiber.New() + app.Post("/register", func(ctx fiber.Ctx) error { + var serviceErr *exceptions.ServiceError + if code != "" { + serviceErr = exceptions.NewError(code, detail) + } + return dynamicRegistrationServiceError(logger, ctx, serviceErr) + }) + res, err := app.Test(httptest.NewRequest(http.MethodPost, "/register", nil)) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + var payload exceptions.OAuthErrorResponse + if err := json.NewDecoder(res.Body).Decode(&payload); err != nil { + t.Fatal(err) + } + if res.StatusCode != http.StatusInternalServerError || payload.Error != exceptions.OAuthErrorServerError { + t.Fatalf("status=%d error=%q", res.StatusCode, payload.Error) + } + if payload.ErrorDescription != "Unable to process dynamic registration request" { + t.Fatalf("unsafe error_description=%q", payload.ErrorDescription) + } + if code != "" && !strings.Contains(logs.String(), detail) { + t.Fatalf("internal error detail missing from server logs: %s", logs.String()) + } + }) + } +} + func TestRegistrationErrorResponses(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) c := &Controllers{logger: logger} @@ -80,3 +120,43 @@ func TestRegistrationErrorResponses(t *testing.T) { }) } } + +func TestRegistrationErrorDescription(t *testing.T) { + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + app := fiber.New() + app.Post("/register", func(ctx fiber.Ctx) error { + return dynamicRegistrationServiceError(logger, ctx, exceptions.NewError(exceptions.OAuthErrorInvalidClientMetadata, "sector_identifier_uri must be an HTTPS URL")) + }) + res, err := app.Test(httptest.NewRequest(http.MethodPost, "/register", nil)) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + + var payload exceptions.OAuthErrorResponse + if err := json.NewDecoder(res.Body).Decode(&payload); err != nil { + t.Fatal(err) + } + if payload.Error != exceptions.OAuthErrorInvalidClientMetadata { + t.Fatalf("error=%q, want %q", payload.Error, exceptions.OAuthErrorInvalidClientMetadata) + } + if payload.ErrorDescription != "sector_identifier_uri must be an HTTPS URL" { + t.Fatalf("error_description=%q, want %q", payload.ErrorDescription, "sector_identifier_uri must be an HTTPS URL") + } +} + +func TestOAuthDynamicClientRegistrationBodyApplicationTypeValidation(t *testing.T) { + validate := validations.NewValidator(slog.New(slog.NewTextHandler(io.Discard, nil))) + for _, appType := range []string{"web", "native", "service", "mcp"} { + body := bodies.OAuthDynamicClientRegistrationBody{ApplicationType: appType} + if err := validate.StructPartial(&body, "ApplicationType"); err != nil { + t.Errorf("expected application_type %q to be valid, got: %v", appType, err) + } + } + for _, appType := range []string{"spa", "backend", "device", "unknown"} { + body := bodies.OAuthDynamicClientRegistrationBody{ApplicationType: appType} + if err := validate.StructPartial(&body, "ApplicationType"); err == nil { + t.Errorf("expected application_type %q to be rejected", appType) + } + } +} diff --git a/idp/internal/exceptions/controllers.go b/idp/internal/exceptions/controllers.go index 614646e..401dd14 100644 --- a/idp/internal/exceptions/controllers.go +++ b/idp/internal/exceptions/controllers.go @@ -300,9 +300,14 @@ func NewRequestErrorStatus(code string) int { } type OAuthErrorResponse struct { - Error string `json:"error"` + Error string `json:"error"` + ErrorDescription string `json:"error_description,omitempty"` } func NewOAuthError(message string) OAuthErrorResponse { return OAuthErrorResponse{Error: message} } + +func NewOAuthErrorWithDescription(message string, description string) OAuthErrorResponse { + return OAuthErrorResponse{Error: message, ErrorDescription: description} +} diff --git a/idp/internal/providers/database/account_credentials.sql.go b/idp/internal/providers/database/account_credentials.sql.go index 35c4e42..f72c731 100644 --- a/idp/internal/providers/database/account_credentials.sql.go +++ b/idp/internal/providers/database/account_credentials.sql.go @@ -66,7 +66,6 @@ INSERT INTO "account_credentials" ( "account_public_id", "domain", "creation_method", - "transport", "client_id", "redirect_uris", "token_endpoint_auth_method", @@ -141,9 +140,8 @@ INSERT INTO "account_credentials" ( $36, $37, $38, - $39, - $40 -) RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, transport, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at + $39 +) RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at ` type CreateAccountCredentialsParams struct { @@ -151,7 +149,6 @@ type CreateAccountCredentialsParams struct { AccountPublicID uuid.UUID Domain string CreationMethod CreationMethod - Transport Transport ClientID string RedirectUris []string TokenEndpointAuthMethod AuthMethod @@ -195,7 +192,6 @@ func (q *Queries) CreateAccountCredentials(ctx context.Context, arg CreateAccoun arg.AccountPublicID, arg.Domain, arg.CreationMethod, - arg.Transport, arg.ClientID, arg.RedirectUris, arg.TokenEndpointAuthMethod, @@ -241,7 +237,6 @@ func (q *Queries) CreateAccountCredentials(ctx context.Context, arg CreateAccoun &i.AccountPublicID, &i.Domain, &i.CreationMethod, - &i.Transport, &i.Version, &i.ClientID, &i.RedirectUris, @@ -318,7 +313,7 @@ func (q *Queries) DeleteRegisteredAccountCredentialsGrants(ctx context.Context, } const findAccountCredentialsByAccountPublicIDAndClientID = `-- name: FindAccountCredentialsByAccountPublicIDAndClientID :one -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, transport, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at FROM "account_credentials" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at FROM "account_credentials" WHERE "account_public_id" = $1 AND "client_id" = $2 LIMIT 1 ` @@ -339,7 +334,6 @@ func (q *Queries) FindAccountCredentialsByAccountPublicIDAndClientID(ctx context &i.AccountPublicID, &i.Domain, &i.CreationMethod, - &i.Transport, &i.Version, &i.ClientID, &i.RedirectUris, @@ -384,7 +378,7 @@ func (q *Queries) FindAccountCredentialsByAccountPublicIDAndClientID(ctx context const findAccountCredentialsByClientID = `-- name: FindAccountCredentialsByClientID :one -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, transport, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at FROM "account_credentials" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at FROM "account_credentials" WHERE "client_id" = $1 LIMIT 1 ` @@ -405,7 +399,6 @@ func (q *Queries) FindAccountCredentialsByClientID(ctx context.Context, clientID &i.AccountPublicID, &i.Domain, &i.CreationMethod, - &i.Transport, &i.Version, &i.ClientID, &i.RedirectUris, @@ -449,7 +442,7 @@ func (q *Queries) FindAccountCredentialsByClientID(ctx context.Context, clientID } const findPaginatedAccountCredentialsByAccountPublicID = `-- name: FindPaginatedAccountCredentialsByAccountPublicID :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, transport, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at FROM "account_credentials" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at FROM "account_credentials" WHERE "account_public_id" = $1 ORDER BY "id" DESC OFFSET $2 LIMIT $3 @@ -478,7 +471,6 @@ func (q *Queries) FindPaginatedAccountCredentialsByAccountPublicID(ctx context.C &i.AccountPublicID, &i.Domain, &i.CreationMethod, - &i.Transport, &i.Version, &i.ClientID, &i.RedirectUris, @@ -529,7 +521,7 @@ func (q *Queries) FindPaginatedAccountCredentialsByAccountPublicID(ctx context.C } const lockRegisteredAccountCredentials = `-- name: LockRegisteredAccountCredentials :one -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, transport, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at FROM account_credentials WHERE client_id = $1 AND account_public_id = $2 FOR UPDATE +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at FROM account_credentials WHERE client_id = $1 AND account_public_id = $2 FOR UPDATE ` type LockRegisteredAccountCredentialsParams struct { @@ -548,7 +540,6 @@ func (q *Queries) LockRegisteredAccountCredentials(ctx context.Context, arg Lock &i.AccountPublicID, &i.Domain, &i.CreationMethod, - &i.Transport, &i.Version, &i.ClientID, &i.RedirectUris, @@ -638,11 +629,10 @@ UPDATE "account_credentials" SET "tos_uri" = $9, "software_version" = $10, "contacts" = $11, - "transport" = $12, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 -RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, transport, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at +RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at ` type UpdateAccountCredentialsParams struct { @@ -657,7 +647,6 @@ type UpdateAccountCredentialsParams struct { TosUri pgtype.Text SoftwareVersion pgtype.Text Contacts []string - Transport Transport } func (q *Queries) UpdateAccountCredentials(ctx context.Context, arg UpdateAccountCredentialsParams) (AccountCredential, error) { @@ -673,7 +662,6 @@ func (q *Queries) UpdateAccountCredentials(ctx context.Context, arg UpdateAccoun arg.TosUri, arg.SoftwareVersion, arg.Contacts, - arg.Transport, ) var i AccountCredential err := row.Scan( @@ -684,7 +672,6 @@ func (q *Queries) UpdateAccountCredentials(ctx context.Context, arg UpdateAccoun &i.AccountPublicID, &i.Domain, &i.CreationMethod, - &i.Transport, &i.Version, &i.ClientID, &i.RedirectUris, @@ -730,50 +717,48 @@ func (q *Queries) UpdateAccountCredentials(ctx context.Context, arg UpdateAccoun const updateRegisteredAccountCredentials = `-- name: UpdateRegisteredAccountCredentials :one UPDATE "account_credentials" SET "domain" = $2, - "transport" = $3, - "redirect_uris" = $4, - "token_endpoint_auth_method" = $5, - "grant_types" = $6, - "response_types" = $7, - "client_name" = $8, - "client_uri" = $9, - "logo_uri" = $10, - "scopes" = $11, - "contacts" = $12, - "tos_uri" = $13, - "policy_uri" = $14, - "jwks_uri" = $15, - "jwks" = $16, - "software_id" = $17, - "software_version" = $18, - "sector_identifier_uri" = $19, - "subject_type" = $20, - "id_token_signed_response_alg" = $21, - "id_token_encrypted_response_alg" = $22, - "id_token_encrypted_response_enc" = $23, - "userinfo_signed_response_alg" = $24, - "userinfo_encrypted_response_alg" = $25, - "userinfo_encrypted_response_enc" = $26, - "request_object_signing_alg" = $27, - "request_object_encryption_alg" = $28, - "request_object_encryption_enc" = $29, - "token_endpoint_auth_signing_alg" = $30, - "default_max_age" = $31, - "require_auth_time" = $32, - "default_acr_values" = $33, - "initiate_login_uri" = $34, - "request_uris" = $35, - "access_token_signing_alg" = $36, + "redirect_uris" = $3, + "token_endpoint_auth_method" = $4, + "grant_types" = $5, + "response_types" = $6, + "client_name" = $7, + "client_uri" = $8, + "logo_uri" = $9, + "scopes" = $10, + "contacts" = $11, + "tos_uri" = $12, + "policy_uri" = $13, + "jwks_uri" = $14, + "jwks" = $15, + "software_id" = $16, + "software_version" = $17, + "sector_identifier_uri" = $18, + "subject_type" = $19, + "id_token_signed_response_alg" = $20, + "id_token_encrypted_response_alg" = $21, + "id_token_encrypted_response_enc" = $22, + "userinfo_signed_response_alg" = $23, + "userinfo_encrypted_response_alg" = $24, + "userinfo_encrypted_response_enc" = $25, + "request_object_signing_alg" = $26, + "request_object_encryption_alg" = $27, + "request_object_encryption_enc" = $28, + "token_endpoint_auth_signing_alg" = $29, + "default_max_age" = $30, + "require_auth_time" = $31, + "default_acr_values" = $32, + "initiate_login_uri" = $33, + "request_uris" = $34, + "access_token_signing_alg" = $35, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 -RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, transport, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at +RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, domain, creation_method, version, client_id, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, credentials_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, created_at, updated_at ` type UpdateRegisteredAccountCredentialsParams struct { ID int32 Domain string - Transport Transport RedirectUris []string TokenEndpointAuthMethod AuthMethod GrantTypes []GrantType @@ -813,7 +798,6 @@ func (q *Queries) UpdateRegisteredAccountCredentials(ctx context.Context, arg Up row := q.db.QueryRow(ctx, updateRegisteredAccountCredentials, arg.ID, arg.Domain, - arg.Transport, arg.RedirectUris, arg.TokenEndpointAuthMethod, arg.GrantTypes, @@ -857,7 +841,6 @@ func (q *Queries) UpdateRegisteredAccountCredentials(ctx context.Context, arg Up &i.AccountPublicID, &i.Domain, &i.CreationMethod, - &i.Transport, &i.Version, &i.ClientID, &i.RedirectUris, diff --git a/idp/internal/providers/database/app_related_apps.sql.go b/idp/internal/providers/database/app_related_apps.sql.go deleted file mode 100644 index 5cab0b3..0000000 --- a/idp/internal/providers/database/app_related_apps.sql.go +++ /dev/null @@ -1,140 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.31.1 -// source: app_related_apps.sql - -package database - -import ( - "context" -) - -const createAppRelatedApp = `-- name: CreateAppRelatedApp :exec - -INSERT INTO "app_related_apps" ( - "app_id", - "related_app_id", - "account_id" -) VALUES ( - $1, - $2, - $3 -) -` - -type CreateAppRelatedAppParams struct { - AppID int32 - RelatedAppID int32 - AccountID int32 -} - -// Copyright (c) 2025 Afonso Barracha -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -func (q *Queries) CreateAppRelatedApp(ctx context.Context, arg CreateAppRelatedAppParams) error { - _, err := q.db.Exec(ctx, createAppRelatedApp, arg.AppID, arg.RelatedAppID, arg.AccountID) - return err -} - -const deleteAppRelatedAppsByAppIDAndRelatedAppIDs = `-- name: DeleteAppRelatedAppsByAppIDAndRelatedAppIDs :exec -DELETE FROM "app_related_apps" -WHERE "app_id" = $1 AND "related_app_id" IN ($2) -` - -type DeleteAppRelatedAppsByAppIDAndRelatedAppIDsParams struct { - AppID int32 - RelatedAppIds []int32 -} - -func (q *Queries) DeleteAppRelatedAppsByAppIDAndRelatedAppIDs(ctx context.Context, arg DeleteAppRelatedAppsByAppIDAndRelatedAppIDsParams) error { - _, err := q.db.Exec(ctx, deleteAppRelatedAppsByAppIDAndRelatedAppIDs, arg.AppID, arg.RelatedAppIds) - return err -} - -const findRelatedAppsByAppID = `-- name: FindRelatedAppsByAppID :many -SELECT a.registration_token_jti, a.software_statement, a.id, a.account_id, a.account_public_id, a.client_id, a.version, a.creation_method, a.redirect_uris, a.token_endpoint_auth_method, a.grant_types, a.response_types, a.client_name, a.client_uri, a.logo_uri, a.scopes, a.custom_scopes, a.contacts, a.tos_uri, a.policy_uri, a.jwks_uri, a.jwks, a.software_id, a.software_version, a.domain, a.transport, a.allow_user_registration, a.auth_providers, a.username_column, a.default_scopes, a.default_custom_scopes, a.app_type, a.sector_identifier_uri, a.subject_type, a.id_token_signed_response_alg, a.id_token_encrypted_response_alg, a.id_token_encrypted_response_enc, a.userinfo_signed_response_alg, a.userinfo_encrypted_response_alg, a.userinfo_encrypted_response_enc, a.request_object_signing_alg, a.request_object_encryption_alg, a.request_object_encryption_enc, a.token_endpoint_auth_signing_alg, a.default_max_age, a.require_auth_time, a.default_acr_values, a.initiate_login_uri, a.request_uris, a.access_token_signing_alg, a.session_type, a.access_token_ttl, a.id_token_ttl, a.refresh_token_idle_ttl, a.refresh_token_ttl, a.grant_ttl, a.created_at, a.updated_at FROM "apps" a -INNER JOIN "app_related_apps" ara ON a.id = ara.related_app_id -WHERE ara.app_id = $1 -ORDER BY a.client_name ASC -` - -func (q *Queries) FindRelatedAppsByAppID(ctx context.Context, appID int32) ([]App, error) { - rows, err := q.db.Query(ctx, findRelatedAppsByAppID, appID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []App{} - for rows.Next() { - var i App - if err := rows.Scan( - &i.RegistrationTokenJti, - &i.SoftwareStatement, - &i.ID, - &i.AccountID, - &i.AccountPublicID, - &i.ClientID, - &i.Version, - &i.CreationMethod, - &i.RedirectUris, - &i.TokenEndpointAuthMethod, - &i.GrantTypes, - &i.ResponseTypes, - &i.ClientName, - &i.ClientUri, - &i.LogoUri, - &i.Scopes, - &i.CustomScopes, - &i.Contacts, - &i.TosUri, - &i.PolicyUri, - &i.JwksUri, - &i.Jwks, - &i.SoftwareID, - &i.SoftwareVersion, - &i.Domain, - &i.Transport, - &i.AllowUserRegistration, - &i.AuthProviders, - &i.UsernameColumn, - &i.DefaultScopes, - &i.DefaultCustomScopes, - &i.AppType, - &i.SectorIdentifierUri, - &i.SubjectType, - &i.IDTokenSignedResponseAlg, - &i.IDTokenEncryptedResponseAlg, - &i.IDTokenEncryptedResponseEnc, - &i.UserinfoSignedResponseAlg, - &i.UserinfoEncryptedResponseAlg, - &i.UserinfoEncryptedResponseEnc, - &i.RequestObjectSigningAlg, - &i.RequestObjectEncryptionAlg, - &i.RequestObjectEncryptionEnc, - &i.TokenEndpointAuthSigningAlg, - &i.DefaultMaxAge, - &i.RequireAuthTime, - &i.DefaultAcrValues, - &i.InitiateLoginUri, - &i.RequestUris, - &i.AccessTokenSigningAlg, - &i.SessionType, - &i.AccessTokenTtl, - &i.IDTokenTtl, - &i.RefreshTokenIdleTtl, - &i.RefreshTokenTtl, - &i.GrantTtl, - &i.CreatedAt, - &i.UpdatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} diff --git a/idp/internal/providers/database/app_service_configs.sql.go b/idp/internal/providers/database/app_service_configs.sql.go deleted file mode 100644 index fc2eac2..0000000 --- a/idp/internal/providers/database/app_service_configs.sql.go +++ /dev/null @@ -1,113 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.31.1 -// source: app_service_configs.sql - -package database - -import ( - "context" -) - -const createAppServiceConfig = `-- name: CreateAppServiceConfig :one - -INSERT INTO "app_service_configs" ( - "account_id", - "app_id", - "user_auth_method", - "user_grant_types", - "allowed_domains" -) VALUES ( - $1, - $2, - $3, - $4, - $5 -) RETURNING id, account_id, app_id, user_auth_method, user_grant_types, allowed_domains, created_at, updated_at -` - -type CreateAppServiceConfigParams struct { - AccountID int32 - AppID int32 - UserAuthMethod AuthMethod - UserGrantTypes []GrantType - AllowedDomains []string -} - -// Copyright (c) 2025 Afonso Barracha -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -func (q *Queries) CreateAppServiceConfig(ctx context.Context, arg CreateAppServiceConfigParams) (AppServiceConfig, error) { - row := q.db.QueryRow(ctx, createAppServiceConfig, - arg.AccountID, - arg.AppID, - arg.UserAuthMethod, - arg.UserGrantTypes, - arg.AllowedDomains, - ) - var i AppServiceConfig - err := row.Scan( - &i.ID, - &i.AccountID, - &i.AppID, - &i.UserAuthMethod, - &i.UserGrantTypes, - &i.AllowedDomains, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const findAppServiceConfig = `-- name: FindAppServiceConfig :one -SELECT id, account_id, app_id, user_auth_method, user_grant_types, allowed_domains, created_at, updated_at FROM "app_service_configs" -WHERE "app_id" = $1 LIMIT 1 -` - -func (q *Queries) FindAppServiceConfig(ctx context.Context, appID int32) (AppServiceConfig, error) { - row := q.db.QueryRow(ctx, findAppServiceConfig, appID) - var i AppServiceConfig - err := row.Scan( - &i.ID, - &i.AccountID, - &i.AppID, - &i.UserAuthMethod, - &i.UserGrantTypes, - &i.AllowedDomains, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const updateAppServiceConfig = `-- name: UpdateAppServiceConfig :one -UPDATE "app_service_configs" -SET "allowed_domains" = $3, - "updated_at" = now() -WHERE "account_id" = $1 AND "app_id" = $2 -RETURNING id, account_id, app_id, user_auth_method, user_grant_types, allowed_domains, created_at, updated_at -` - -type UpdateAppServiceConfigParams struct { - AccountID int32 - AppID int32 - AllowedDomains []string -} - -func (q *Queries) UpdateAppServiceConfig(ctx context.Context, arg UpdateAppServiceConfigParams) (AppServiceConfig, error) { - row := q.db.QueryRow(ctx, updateAppServiceConfig, arg.AccountID, arg.AppID, arg.AllowedDomains) - var i AppServiceConfig - err := row.Scan( - &i.ID, - &i.AccountID, - &i.AppID, - &i.UserAuthMethod, - &i.UserGrantTypes, - &i.AllowedDomains, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} diff --git a/idp/internal/providers/database/apps.sql.go b/idp/internal/providers/database/apps.sql.go index d0db776..b1cbf9f 100644 --- a/idp/internal/providers/database/apps.sql.go +++ b/idp/internal/providers/database/apps.sql.go @@ -161,7 +161,6 @@ INSERT INTO "apps" ( "custom_scopes", "default_custom_scopes", "domain", - "transport", "redirect_uris", "response_types", "allow_user_registration", @@ -195,9 +194,8 @@ INSERT INTO "apps" ( $24, $25, $26, - $27, - $28 -) RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at + $27 +) RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at ` type CreateAppParams struct { @@ -222,7 +220,6 @@ type CreateAppParams struct { CustomScopes []string DefaultCustomScopes []string Domain string - Transport Transport RedirectUris []string ResponseTypes []ResponseType AllowUserRegistration bool @@ -259,7 +256,6 @@ func (q *Queries) CreateApp(ctx context.Context, arg CreateAppParams) (App, erro arg.CustomScopes, arg.DefaultCustomScopes, arg.Domain, - arg.Transport, arg.RedirectUris, arg.ResponseTypes, arg.AllowUserRegistration, @@ -294,7 +290,6 @@ func (q *Queries) CreateApp(ctx context.Context, arg CreateAppParams) (App, erro &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -365,7 +360,7 @@ func (q *Queries) DeleteRegisteredAppGrants(ctx context.Context, arg DeleteRegis } const filterAppsByNameAndByAccountPublicIDOrderedByID = `-- name: FilterAppsByNameAndByAccountPublicIDOrderedByID :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "account_public_id" = $1 AND "client_name" ILIKE $2 ORDER BY "id" DESC OFFSET $3 LIMIT $4 @@ -418,7 +413,6 @@ func (q *Queries) FilterAppsByNameAndByAccountPublicIDOrderedByID(ctx context.Co &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -463,7 +457,7 @@ func (q *Queries) FilterAppsByNameAndByAccountPublicIDOrderedByID(ctx context.Co } const filterAppsByNameAndByAccountPublicIDOrderedByName = `-- name: FilterAppsByNameAndByAccountPublicIDOrderedByName :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "account_public_id" = $1 AND "client_name" ILIKE $2 ORDER BY "client_name" ASC OFFSET $3 LIMIT $4 @@ -516,7 +510,6 @@ func (q *Queries) FilterAppsByNameAndByAccountPublicIDOrderedByName(ctx context. &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -561,7 +554,7 @@ func (q *Queries) FilterAppsByNameAndByAccountPublicIDOrderedByName(ctx context. } const filterAppsByNameAndTypeAndByAccountPublicIDOrderedByID = `-- name: FilterAppsByNameAndTypeAndByAccountPublicIDOrderedByID :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "account_public_id" = $1 AND "client_name" ILIKE $2 AND "app_type" = $3 @@ -618,7 +611,6 @@ func (q *Queries) FilterAppsByNameAndTypeAndByAccountPublicIDOrderedByID(ctx con &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -663,7 +655,7 @@ func (q *Queries) FilterAppsByNameAndTypeAndByAccountPublicIDOrderedByID(ctx con } const filterAppsByNameAndTypeAndByAccountPublicIDOrderedByName = `-- name: FilterAppsByNameAndTypeAndByAccountPublicIDOrderedByName :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "account_public_id" = $1 AND "client_name" ILIKE $2 AND "app_type" = $3 @@ -720,7 +712,6 @@ func (q *Queries) FilterAppsByNameAndTypeAndByAccountPublicIDOrderedByName(ctx c &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -765,7 +756,7 @@ func (q *Queries) FilterAppsByNameAndTypeAndByAccountPublicIDOrderedByName(ctx c } const filterAppsByTypeAndByAccountPublicIDOrderedByID = `-- name: FilterAppsByTypeAndByAccountPublicIDOrderedByID :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "account_public_id" = $1 AND "app_type" = $2 ORDER BY "id" DESC OFFSET $3 LIMIT $4 @@ -818,7 +809,6 @@ func (q *Queries) FilterAppsByTypeAndByAccountPublicIDOrderedByID(ctx context.Co &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -863,7 +853,7 @@ func (q *Queries) FilterAppsByTypeAndByAccountPublicIDOrderedByID(ctx context.Co } const filterAppsByTypeAndByAccountPublicIDOrderedByName = `-- name: FilterAppsByTypeAndByAccountPublicIDOrderedByName :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "account_public_id" = $1 AND "app_type" = $2 ORDER BY "client_name" ASC OFFSET $3 LIMIT $4 @@ -916,7 +906,6 @@ func (q *Queries) FilterAppsByTypeAndByAccountPublicIDOrderedByName(ctx context. &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -961,7 +950,7 @@ func (q *Queries) FilterAppsByTypeAndByAccountPublicIDOrderedByName(ctx context. } const findAppByClientID = `-- name: FindAppByClientID :one -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "client_id" = $1 LIMIT 1 ` @@ -994,7 +983,6 @@ func (q *Queries) FindAppByClientID(ctx context.Context, clientID string) (App, &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1032,7 +1020,7 @@ func (q *Queries) FindAppByClientID(ctx context.Context, clientID string) (App, } const findAppByClientIDAndAccountPublicID = `-- name: FindAppByClientIDAndAccountPublicID :one -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "client_id" = $1 AND "account_public_id" = $2 LIMIT 1 ` @@ -1071,7 +1059,6 @@ func (q *Queries) FindAppByClientIDAndAccountPublicID(ctx context.Context, arg F &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1109,7 +1096,7 @@ func (q *Queries) FindAppByClientIDAndAccountPublicID(ctx context.Context, arg F } const findAppByClientIDAndVersion = `-- name: FindAppByClientIDAndVersion :one -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "client_id" = $1 AND "version" = $2 LIMIT 1 ` @@ -1147,7 +1134,6 @@ func (q *Queries) FindAppByClientIDAndVersion(ctx context.Context, arg FindAppBy &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1185,7 +1171,7 @@ func (q *Queries) FindAppByClientIDAndVersion(ctx context.Context, arg FindAppBy } const findAppByID = `-- name: FindAppByID :one -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "id" = $1 LIMIT 1 ` @@ -1218,7 +1204,6 @@ func (q *Queries) FindAppByID(ctx context.Context, id int32) (App, error) { &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1256,7 +1241,7 @@ func (q *Queries) FindAppByID(ctx context.Context, id int32) (App, error) { } const findAppsByClientIDsAndAccountID = `-- name: FindAppsByClientIDsAndAccountID :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "client_id" IN ($3) AND "account_id" = $1 ORDER BY "client_name" ASC LIMIT $2 ` @@ -1302,7 +1287,6 @@ func (q *Queries) FindAppsByClientIDsAndAccountID(ctx context.Context, arg FindA &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1347,7 +1331,7 @@ func (q *Queries) FindAppsByClientIDsAndAccountID(ctx context.Context, arg FindA } const findPaginatedAppsByAccountPublicIDOrderedByID = `-- name: FindPaginatedAppsByAccountPublicIDOrderedByID :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "account_public_id" = $1 ORDER BY "id" DESC OFFSET $2 LIMIT $3 @@ -1394,7 +1378,6 @@ func (q *Queries) FindPaginatedAppsByAccountPublicIDOrderedByID(ctx context.Cont &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1439,7 +1422,7 @@ func (q *Queries) FindPaginatedAppsByAccountPublicIDOrderedByID(ctx context.Cont } const findPaginatedAppsByAccountPublicIDOrderedByName = `-- name: FindPaginatedAppsByAccountPublicIDOrderedByName :many -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM "apps" WHERE "account_public_id" = $1 ORDER BY "client_name" ASC OFFSET $2 LIMIT $3 @@ -1486,7 +1469,6 @@ func (q *Queries) FindPaginatedAppsByAccountPublicIDOrderedByName(ctx context.Co &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1531,7 +1513,7 @@ func (q *Queries) FindPaginatedAppsByAccountPublicIDOrderedByName(ctx context.Co } const lockRegisteredApp = `-- name: LockRegisteredApp :one -SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM apps WHERE client_id = $1 AND account_public_id = $2 FOR UPDATE +SELECT registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at FROM apps WHERE client_id = $1 AND account_public_id = $2 FOR UPDATE ` type LockRegisteredAppParams struct { @@ -1568,7 +1550,6 @@ func (q *Queries) LockRegisteredApp(ctx context.Context, arg LockRegisteredAppPa &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1652,14 +1633,13 @@ SET "client_name" = $2, "software_version" = $9, "contacts" = $10, "domain" = $11, - "transport" = $12, - "redirect_uris" = $13, - "allow_user_registration" = $14, - "response_types" = $15, + "redirect_uris" = $12, + "allow_user_registration" = $13, + "response_types" = $14, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 -RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at +RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at ` type UpdateAppParams struct { @@ -1674,7 +1654,6 @@ type UpdateAppParams struct { SoftwareVersion pgtype.Text Contacts []string Domain string - Transport Transport RedirectUris []string AllowUserRegistration bool ResponseTypes []ResponseType @@ -1693,7 +1672,6 @@ func (q *Queries) UpdateApp(ctx context.Context, arg UpdateAppParams) (App, erro arg.SoftwareVersion, arg.Contacts, arg.Domain, - arg.Transport, arg.RedirectUris, arg.AllowUserRegistration, arg.ResponseTypes, @@ -1725,7 +1703,6 @@ func (q *Queries) UpdateApp(ctx context.Context, arg UpdateAppParams) (App, erro &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1771,7 +1748,7 @@ SET "scopes" = $2, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 -RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at +RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at ` type UpdateAppScopesParams struct { @@ -1817,7 +1794,6 @@ func (q *Queries) UpdateAppScopes(ctx context.Context, arg UpdateAppScopesParams &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -1859,7 +1835,7 @@ UPDATE "apps" SET "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 -RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at +RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at ` func (q *Queries) UpdateAppVersion(ctx context.Context, id int32) (App, error) { @@ -1891,7 +1867,6 @@ func (q *Queries) UpdateAppVersion(ctx context.Context, id int32) (App, error) { &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, diff --git a/idp/internal/providers/database/migrations/20241213231542_create_initial_schema.down.sql b/idp/internal/providers/database/migrations/20241213231542_create_initial_schema.down.sql index d4147e1..054531c 100644 --- a/idp/internal/providers/database/migrations/20241213231542_create_initial_schema.down.sql +++ b/idp/internal/providers/database/migrations/20241213231542_create_initial_schema.down.sql @@ -9,8 +9,6 @@ DROP TABLE IF EXISTS "grants"; DROP TABLE IF EXISTS "app_profiles"; DROP TABLE IF EXISTS "dynamic_registration_configs"; DROP TABLE IF EXISTS "app_designs"; -DROP TABLE IF EXISTS "app_service_configs"; -DROP TABLE IF EXISTS "app_related_apps"; DROP TABLE IF EXISTS "app_keys"; DROP TABLE IF EXISTS "app_secrets"; DROP TABLE IF EXISTS "user_credentials_keys"; @@ -51,7 +49,6 @@ DROP TYPE IF EXISTS "auth_method"; DROP TYPE IF EXISTS "response_type"; DROP TYPE IF EXISTS "account_credentials_scope"; DROP TYPE IF EXISTS "account_credentials_type"; -DROP TYPE IF EXISTS "transport"; DROP TYPE IF EXISTS "creation_method"; DROP TYPE IF EXISTS "auth_provider"; DROP TYPE IF EXISTS "claims"; diff --git a/idp/internal/providers/database/migrations/20241213231542_create_initial_schema.up.sql b/idp/internal/providers/database/migrations/20241213231542_create_initial_schema.up.sql index ced9eee..5f30857 100644 --- a/idp/internal/providers/database/migrations/20241213231542_create_initial_schema.up.sql +++ b/idp/internal/providers/database/migrations/20241213231542_create_initial_schema.up.sql @@ -1,6 +1,6 @@ -- SQL dump generated using DBML (dbml.dbdiagram.io) -- Database: PostgreSQL --- Generated at: 2026-09-22T07:32:32.147Z +-- Generated at: 2026-10-09T19:10:27.473Z CREATE TYPE "kek_usage" AS ENUM ( 'global', @@ -82,6 +82,7 @@ CREATE TYPE "auth_method" AS ENUM ( CREATE TYPE "response_type" AS ENUM ( 'code', + 'id_token', 'code id_token' ); @@ -108,13 +109,6 @@ CREATE TYPE "account_credentials_type" AS ENUM ( 'mcp' ); -CREATE TYPE "transport" AS ENUM ( - 'http', - 'https', - 'stdio', - 'streamable_http' -); - CREATE TYPE "client_subject_type" AS ENUM ( 'public', 'pairwise' @@ -167,12 +161,7 @@ CREATE TYPE "scopes" AS ENUM ( CREATE TYPE "app_type" AS ENUM ( 'web', - 'native', - 'spa', - 'backend', - 'device', - 'service', - 'mcp' + 'native' ); CREATE TYPE "app_username_column" AS ENUM ( @@ -183,6 +172,7 @@ CREATE TYPE "app_username_column" AS ENUM ( CREATE TYPE "grant_type" AS ENUM ( 'authorization_code', + 'implicit', 'refresh_token', 'client_credentials', 'urn:ietf:params:oauth:grant-type:device_code', @@ -372,7 +362,6 @@ CREATE TABLE "account_credentials" ( "account_public_id" uuid NOT NULL, "domain" varchar(250) NOT NULL, "creation_method" creation_method NOT NULL, - "transport" transport NOT NULL, "version" integer NOT NULL DEFAULT 1, "client_id" varchar(22) NOT NULL, "redirect_uris" varchar(2048)[] NOT NULL, @@ -568,7 +557,6 @@ CREATE TABLE "apps" ( "software_id" varchar(512), "software_version" varchar(512), "domain" varchar(250) NOT NULL, - "transport" transport NOT NULL, "allow_user_registration" bool NOT NULL, "auth_providers" auth_provider[] NOT NULL, "username_column" app_username_column NOT NULL, @@ -619,26 +607,6 @@ CREATE TABLE "app_keys" ( PRIMARY KEY ("app_id", "credentials_key_id") ); -CREATE TABLE "app_related_apps" ( - "account_id" integer NOT NULL, - "app_id" integer NOT NULL, - "related_app_id" integer NOT NULL, - "created_at" timestamptz NOT NULL DEFAULT (now()), - "updated_at" timestamptz NOT NULL DEFAULT (now()), - PRIMARY KEY ("app_id", "related_app_id") -); - -CREATE TABLE "app_service_configs" ( - "id" serial PRIMARY KEY, - "account_id" integer NOT NULL, - "app_id" integer NOT NULL, - "user_auth_method" auth_method NOT NULL, - "user_grant_types" grant_type[] NOT NULL, - "allowed_domains" varchar(250)[] NOT NULL, - "created_at" timestamptz NOT NULL DEFAULT (now()), - "updated_at" timestamptz NOT NULL DEFAULT (now()) -); - CREATE TABLE "app_designs" ( "id" serial PRIMARY KEY, "account_id" integer NOT NULL, @@ -680,7 +648,7 @@ CREATE TABLE "app_dynamic_registration_configs" ( "initial_access_token_generation_methods" initial_access_token_generation_method[] NOT NULL, "initial_access_token_ttl" integer NOT NULL DEFAULT 3600, "initial_access_token_max_uses" int NOT NULL DEFAULT 1, - "allowed_grant_types" grant_type[] NOT NULL DEFAULT '{ "authorization_code", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:jwt-bearer" }', + "allowed_grant_types" grant_type[] NOT NULL DEFAULT '{ "authorization_code", "implicit", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:jwt-bearer" }', "allowed_response_types" response_type[] NOT NULL DEFAULT '{ "code", "code id_token" }', "allowed_token_endpoint_auth_methods" auth_method[] NOT NULL DEFAULT '{ "none", "client_secret_post", "client_secret_basic", "client_secret_jwt", "private_key_jwt" }', "max_redirect_uris" int NOT NULL DEFAULT 10, @@ -1070,18 +1038,6 @@ CREATE INDEX "app_keys_account_id_idx" ON "app_keys" ("account_id"); CREATE UNIQUE INDEX "app_keys_app_id_credentials_key_id_uidx" ON "app_keys" ("app_id", "credentials_key_id"); -CREATE INDEX "app_related_apps_account_id_idx" ON "app_related_apps" ("account_id"); - -CREATE INDEX "app_related_apps_app_id_idx" ON "app_related_apps" ("app_id"); - -CREATE INDEX "app_related_apps_related_app_id_idx" ON "app_related_apps" ("related_app_id"); - -CREATE UNIQUE INDEX "app_related_apps_app_id_related_app_id_uidx" ON "app_related_apps" ("app_id", "related_app_id"); - -CREATE INDEX "app_service_configs_account_id_idx" ON "app_service_configs" ("account_id"); - -CREATE UNIQUE INDEX "app_service_configs_app_id_uidx" ON "app_service_configs" ("app_id"); - CREATE INDEX "app_designs_account_id_idx" ON "app_designs" ("account_id"); CREATE UNIQUE INDEX "app_designs_app_id_uidx" ON "app_designs" ("app_id"); @@ -1186,192 +1142,182 @@ CREATE INDEX "allowed_tokens_grant_id_idx" ON "session_tokens" ("grant_id"); CREATE INDEX "allowed_tokens_expires_at_idx" ON "session_tokens" ("expires_at"); -ALTER TABLE "data_encryption_keys" ADD FOREIGN KEY ("kek_kid") REFERENCES "key_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE; - -ALTER TABLE "token_signing_keys" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE; - -ALTER TABLE "account_2fa_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; - -ALTER TABLE "totps" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE; - -ALTER TABLE "totps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; - -ALTER TABLE "credentials_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "data_encryption_keys" ADD FOREIGN KEY ("kek_kid") REFERENCES "key_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "credentials_secrets" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "token_signing_keys" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "credentials_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_2fa_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "credentials_keys" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "totps" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_key_encryption_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "totps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_key_encryption_keys" ADD FOREIGN KEY ("key_encryption_key_id") REFERENCES "key_encryption_keys" ("id") ON DELETE CASCADE; +ALTER TABLE "credentials_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_data_encryption_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "credentials_secrets" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_data_encryption_keys" ADD FOREIGN KEY ("data_encryption_key_id") REFERENCES "data_encryption_keys" ("id") ON DELETE CASCADE; +ALTER TABLE "credentials_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_hmac_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "credentials_keys" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_hmac_secrets" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "account_key_encryption_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_totps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_key_encryption_keys" ADD FOREIGN KEY ("key_encryption_key_id") REFERENCES "key_encryption_keys" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_totps" ADD FOREIGN KEY ("totp_id") REFERENCES "totps" ("id") ON DELETE CASCADE; +ALTER TABLE "account_data_encryption_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_credentials" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_data_encryption_keys" ADD FOREIGN KEY ("data_encryption_key_id") REFERENCES "data_encryption_keys" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_credentials_secrets" ADD FOREIGN KEY ("account_credentials_id") REFERENCES "account_credentials" ("id") ON DELETE CASCADE; +ALTER TABLE "account_hmac_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_credentials_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_hmac_secrets" ADD FOREIGN KEY ("dek_kid") REFERENCES "data_encryption_keys" ("kid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_credentials_secrets" ADD FOREIGN KEY ("credentials_secret_id") REFERENCES "credentials_secrets" ("id") ON DELETE CASCADE; +ALTER TABLE "account_totps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_credentials_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_totps" ADD FOREIGN KEY ("totp_id") REFERENCES "totps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_credentials_keys" ADD FOREIGN KEY ("account_credentials_id") REFERENCES "account_credentials" ("id") ON DELETE CASCADE; +ALTER TABLE "account_credentials" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_credentials_keys" ADD FOREIGN KEY ("credentials_key_id") REFERENCES "credentials_keys" ("id") ON DELETE CASCADE; +ALTER TABLE "account_credentials_secrets" ADD FOREIGN KEY ("account_credentials_id") REFERENCES "account_credentials" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_auth_providers" ADD FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "account_credentials_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "oidc_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_credentials_secrets" ADD FOREIGN KEY ("credentials_secret_id") REFERENCES "credentials_secrets" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_token_signing_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_credentials_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_token_signing_keys" ADD FOREIGN KEY ("token_signing_key_id") REFERENCES "token_signing_keys" ("id") ON DELETE CASCADE; +ALTER TABLE "account_credentials_keys" ADD FOREIGN KEY ("account_credentials_id") REFERENCES "account_credentials" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "users" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_credentials_keys" ADD FOREIGN KEY ("credentials_key_id") REFERENCES "credentials_keys" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_2fa_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_auth_providers" ADD FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_2fa_configs" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "oidc_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_data_encryption_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_token_signing_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_data_encryption_keys" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "account_token_signing_keys" ADD FOREIGN KEY ("token_signing_key_id") REFERENCES "token_signing_keys" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_data_encryption_keys" ADD FOREIGN KEY ("data_encryption_key_id") REFERENCES "data_encryption_keys" ("id") ON DELETE CASCADE; +ALTER TABLE "users" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_totps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_2fa_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_totps" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "user_2fa_configs" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_totps" ADD FOREIGN KEY ("totp_id") REFERENCES "totps" ("id") ON DELETE CASCADE; +ALTER TABLE "user_data_encryption_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_auth_providers" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_data_encryption_keys" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_auth_providers" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "user_data_encryption_keys" ADD FOREIGN KEY ("data_encryption_key_id") REFERENCES "data_encryption_keys" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "user_totps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_totps" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "user_totps" ADD FOREIGN KEY ("totp_id") REFERENCES "totps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials_secrets" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "user_auth_providers" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials_secrets" ADD FOREIGN KEY ("user_credential_id") REFERENCES "user_credentials" ("id") ON DELETE CASCADE; +ALTER TABLE "user_auth_providers" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials_secrets" ADD FOREIGN KEY ("credentials_secret_id") REFERENCES "credentials_secrets" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials_keys" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials_keys" ADD FOREIGN KEY ("user_credential_id") REFERENCES "user_credentials" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials_secrets" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials_keys" ADD FOREIGN KEY ("credentials_key_id") REFERENCES "credentials_keys" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials_secrets" ADD FOREIGN KEY ("user_credential_id") REFERENCES "user_credentials" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_credentials_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials_secrets" ADD FOREIGN KEY ("credentials_secret_id") REFERENCES "credentials_secrets" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "apps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials_keys" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_secrets" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials_keys" ADD FOREIGN KEY ("user_credential_id") REFERENCES "user_credentials" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_secrets" ADD FOREIGN KEY ("credentials_secret_id") REFERENCES "credentials_secrets" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials_keys" ADD FOREIGN KEY ("credentials_key_id") REFERENCES "credentials_keys" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_credentials_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_keys" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "apps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_keys" ADD FOREIGN KEY ("credentials_key_id") REFERENCES "credentials_keys" ("id") ON DELETE CASCADE; +ALTER TABLE "app_secrets" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_related_apps" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "app_secrets" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_related_apps" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "app_secrets" ADD FOREIGN KEY ("credentials_secret_id") REFERENCES "credentials_secrets" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_related_apps" ADD FOREIGN KEY ("related_app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "app_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_service_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "app_keys" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_service_configs" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "app_keys" ADD FOREIGN KEY ("credentials_key_id") REFERENCES "credentials_keys" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_designs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "app_designs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_designs" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "app_designs" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_dynamic_registration_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_dynamic_registration_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_dynamic_registration_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "app_dynamic_registration_configs" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "dynamic_registration_domains" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "dynamic_registration_domains" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "dynamic_registration_domain_codes" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "dynamic_registration_domain_codes" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "dynamic_registration_domain_codes" ADD FOREIGN KEY ("dynamic_registration_domain_id") REFERENCES "dynamic_registration_domains" ("id") ON DELETE CASCADE; +ALTER TABLE "dynamic_registration_domain_codes" ADD FOREIGN KEY ("dynamic_registration_domain_id") REFERENCES "dynamic_registration_domains" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "dynamic_registration_domain_codes" ADD FOREIGN KEY ("hmac_secret_id") REFERENCES "account_hmac_secrets" ("secret_id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "dynamic_registration_domain_codes" ADD FOREIGN KEY ("hmac_secret_id") REFERENCES "account_hmac_secrets" ("secret_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "dynamic_registration_software_statement_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "dynamic_registration_software_statement_keys" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "dynamic_registration_software_statement_keys" ADD FOREIGN KEY ("credentials_key_id") REFERENCES "credentials_keys" ("id") ON DELETE CASCADE; +ALTER TABLE "dynamic_registration_software_statement_keys" ADD FOREIGN KEY ("credentials_key_id") REFERENCES "credentials_keys" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_profiles" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "app_profiles" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_profiles" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "app_profiles" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "app_profiles" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "app_profiles" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "grants" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "grants" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_grants" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_grants" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_grants" ADD FOREIGN KEY ("grant_id") REFERENCES "grants" ("id") ON DELETE CASCADE; +ALTER TABLE "account_grants" ADD FOREIGN KEY ("grant_id") REFERENCES "grants" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_grants" ADD FOREIGN KEY ("account_credentials_id") REFERENCES "account_credentials" ("id") ON DELETE CASCADE; +ALTER TABLE "account_grants" ADD FOREIGN KEY ("account_credentials_id") REFERENCES "account_credentials" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_grants" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "user_grants" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_grants" ADD FOREIGN KEY ("grant_id") REFERENCES "grants" ("id") ON DELETE CASCADE; +ALTER TABLE "user_grants" ADD FOREIGN KEY ("grant_id") REFERENCES "grants" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_grants" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "user_grants" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_grants" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_grants" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "sessions" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "sessions" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "sessions" ADD FOREIGN KEY ("grant_id") REFERENCES "grants" ("id") ON DELETE CASCADE; +ALTER TABLE "sessions" ADD FOREIGN KEY ("grant_id") REFERENCES "grants" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_sessions" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "account_sessions" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_sessions" ADD FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE CASCADE; +ALTER TABLE "account_sessions" ADD FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "account_sessions" ADD FOREIGN KEY ("account_credentials_id") REFERENCES "account_credentials" ("id") ON DELETE CASCADE; +ALTER TABLE "account_sessions" ADD FOREIGN KEY ("account_credentials_id") REFERENCES "account_credentials" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_sessions" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE; +ALTER TABLE "user_sessions" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_sessions" ADD FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE CASCADE; +ALTER TABLE "user_sessions" ADD FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_sessions" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE; +ALTER TABLE "user_sessions" ADD FOREIGN KEY ("app_id") REFERENCES "apps" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "user_sessions" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "user_sessions" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "session_tokens" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE; +ALTER TABLE "session_tokens" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "session_tokens" ADD FOREIGN KEY ("grant_id") REFERENCES "grants" ("id") ON DELETE CASCADE; +ALTER TABLE "session_tokens" ADD FOREIGN KEY ("grant_id") REFERENCES "grants" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; -ALTER TABLE "session_tokens" ADD FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE CASCADE; +ALTER TABLE "session_tokens" ADD FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE; diff --git a/idp/internal/providers/database/models.go b/idp/internal/providers/database/models.go index e59a00f..1d2eea2 100644 --- a/idp/internal/providers/database/models.go +++ b/idp/internal/providers/database/models.go @@ -199,13 +199,8 @@ func (ns NullAppProfileType) Value() (driver.Value, error) { type AppType string const ( - AppTypeWeb AppType = "web" - AppTypeNative AppType = "native" - AppTypeSpa AppType = "spa" - AppTypeBackend AppType = "backend" - AppTypeDevice AppType = "device" - AppTypeService AppType = "service" - AppTypeMcp AppType = "mcp" + AppTypeWeb AppType = "web" + AppTypeNative AppType = "native" ) func (e *AppType) Scan(src interface{}) error { @@ -695,6 +690,7 @@ type GrantType string const ( GrantTypeAuthorizationCode GrantType = "authorization_code" + GrantTypeImplicit GrantType = "implicit" GrantTypeRefreshToken GrantType = "refresh_token" GrantTypeClientCredentials GrantType = "client_credentials" GrantTypeUrnIetfParamsOauthGrantTypeDeviceCode GrantType = "urn:ietf:params:oauth:grant-type:device_code" @@ -824,6 +820,7 @@ type ResponseType string const ( ResponseTypeCode ResponseType = "code" + ResponseTypeIDToken ResponseType = "id_token" ResponseTypeCodeidToken ResponseType = "code id_token" ) @@ -1297,50 +1294,6 @@ func (ns NullTotpUsage) Value() (driver.Value, error) { return string(ns.TotpUsage), nil } -type Transport string - -const ( - TransportHttp Transport = "http" - TransportHttps Transport = "https" - TransportStdio Transport = "stdio" - TransportStreamableHttp Transport = "streamable_http" -) - -func (e *Transport) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = Transport(s) - case string: - *e = Transport(s) - default: - return fmt.Errorf("unsupported scan type for Transport: %T", src) - } - return nil -} - -type NullTransport struct { - Transport Transport - Valid bool // Valid is true if Transport is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullTransport) Scan(value interface{}) error { - if value == nil { - ns.Transport, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.Transport.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullTransport) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.Transport), nil -} - type TwoFactorType string const ( @@ -1427,7 +1380,6 @@ type AccountCredential struct { AccountPublicID uuid.UUID Domain string CreationMethod CreationMethod - Transport Transport Version int32 ClientID string RedirectUris []string @@ -1580,7 +1532,6 @@ type App struct { SoftwareID pgtype.Text SoftwareVersion pgtype.Text Domain string - Transport Transport AllowUserRegistration bool AuthProviders []AuthProvider UsernameColumn AppUsernameColumn @@ -1667,14 +1618,6 @@ type AppProfile struct { CreatedAt time.Time } -type AppRelatedApp struct { - AccountID int32 - AppID int32 - RelatedAppID int32 - CreatedAt time.Time - UpdatedAt time.Time -} - type AppSecret struct { AppID int32 CredentialsSecretID int32 @@ -1682,17 +1625,6 @@ type AppSecret struct { CreatedAt time.Time } -type AppServiceConfig struct { - ID int32 - AccountID int32 - AppID int32 - UserAuthMethod AuthMethod - UserGrantTypes []GrantType - AllowedDomains []string - CreatedAt time.Time - UpdatedAt time.Time -} - type CredentialsKey struct { ID int32 PublicKid string diff --git a/idp/internal/providers/database/queries/account_credentials.sql b/idp/internal/providers/database/queries/account_credentials.sql index 58fbef0..93f1536 100644 --- a/idp/internal/providers/database/queries/account_credentials.sql +++ b/idp/internal/providers/database/queries/account_credentials.sql @@ -25,7 +25,6 @@ INSERT INTO "account_credentials" ( "account_public_id", "domain", "creation_method", - "transport", "client_id", "redirect_uris", "token_endpoint_auth_method", @@ -100,47 +99,45 @@ INSERT INTO "account_credentials" ( $36, $37, $38, - $39, - $40 + $39 ) RETURNING *; -- name: UpdateRegisteredAccountCredentials :one UPDATE "account_credentials" SET "domain" = $2, - "transport" = $3, - "redirect_uris" = $4, - "token_endpoint_auth_method" = $5, - "grant_types" = $6, - "response_types" = $7, - "client_name" = $8, - "client_uri" = $9, - "logo_uri" = $10, - "scopes" = $11, - "contacts" = $12, - "tos_uri" = $13, - "policy_uri" = $14, - "jwks_uri" = $15, - "jwks" = $16, - "software_id" = $17, - "software_version" = $18, - "sector_identifier_uri" = $19, - "subject_type" = $20, - "id_token_signed_response_alg" = $21, - "id_token_encrypted_response_alg" = $22, - "id_token_encrypted_response_enc" = $23, - "userinfo_signed_response_alg" = $24, - "userinfo_encrypted_response_alg" = $25, - "userinfo_encrypted_response_enc" = $26, - "request_object_signing_alg" = $27, - "request_object_encryption_alg" = $28, - "request_object_encryption_enc" = $29, - "token_endpoint_auth_signing_alg" = $30, - "default_max_age" = $31, - "require_auth_time" = $32, - "default_acr_values" = $33, - "initiate_login_uri" = $34, - "request_uris" = $35, - "access_token_signing_alg" = $36, + "redirect_uris" = $3, + "token_endpoint_auth_method" = $4, + "grant_types" = $5, + "response_types" = $6, + "client_name" = $7, + "client_uri" = $8, + "logo_uri" = $9, + "scopes" = $10, + "contacts" = $11, + "tos_uri" = $12, + "policy_uri" = $13, + "jwks_uri" = $14, + "jwks" = $15, + "software_id" = $16, + "software_version" = $17, + "sector_identifier_uri" = $18, + "subject_type" = $19, + "id_token_signed_response_alg" = $20, + "id_token_encrypted_response_alg" = $21, + "id_token_encrypted_response_enc" = $22, + "userinfo_signed_response_alg" = $23, + "userinfo_encrypted_response_alg" = $24, + "userinfo_encrypted_response_enc" = $25, + "request_object_signing_alg" = $26, + "request_object_encryption_alg" = $27, + "request_object_encryption_enc" = $28, + "token_endpoint_auth_signing_alg" = $29, + "default_max_age" = $30, + "require_auth_time" = $31, + "default_acr_values" = $32, + "initiate_login_uri" = $33, + "request_uris" = $34, + "access_token_signing_alg" = $35, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 @@ -158,7 +155,6 @@ UPDATE "account_credentials" SET "tos_uri" = $9, "software_version" = $10, "contacts" = $11, - "transport" = $12, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 diff --git a/idp/internal/providers/database/queries/app_related_apps.sql b/idp/internal/providers/database/queries/app_related_apps.sql deleted file mode 100644 index 4774393..0000000 --- a/idp/internal/providers/database/queries/app_related_apps.sql +++ /dev/null @@ -1,26 +0,0 @@ --- Copyright (c) 2025 Afonso Barracha --- --- This Source Code Form is subject to the terms of the Mozilla Public --- License, v. 2.0. If a copy of the MPL was not distributed with this --- file, You can obtain one at https://mozilla.org/MPL/2.0/. - --- name: CreateAppRelatedApp :exec -INSERT INTO "app_related_apps" ( - "app_id", - "related_app_id", - "account_id" -) VALUES ( - $1, - $2, - $3 -); - --- name: FindRelatedAppsByAppID :many -SELECT a.* FROM "apps" a -INNER JOIN "app_related_apps" ara ON a.id = ara.related_app_id -WHERE ara.app_id = $1 -ORDER BY a.client_name ASC; - --- name: DeleteAppRelatedAppsByAppIDAndRelatedAppIDs :exec -DELETE FROM "app_related_apps" -WHERE "app_id" = $1 AND "related_app_id" IN (sqlc.slice('related_app_ids')); \ No newline at end of file diff --git a/idp/internal/providers/database/queries/app_service_configs.sql b/idp/internal/providers/database/queries/app_service_configs.sql deleted file mode 100644 index 8859c47..0000000 --- a/idp/internal/providers/database/queries/app_service_configs.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Copyright (c) 2025 Afonso Barracha --- --- This Source Code Form is subject to the terms of the Mozilla Public --- License, v. 2.0. If a copy of the MPL was not distributed with this --- file, You can obtain one at https://mozilla.org/MPL/2.0/. - --- name: CreateAppServiceConfig :one -INSERT INTO "app_service_configs" ( - "account_id", - "app_id", - "user_auth_method", - "user_grant_types", - "allowed_domains" -) VALUES ( - $1, - $2, - $3, - $4, - $5 -) RETURNING *; - --- name: FindAppServiceConfig :one -SELECT * FROM "app_service_configs" -WHERE "app_id" = $1 LIMIT 1; - --- name: UpdateAppServiceConfig :one -UPDATE "app_service_configs" -SET "allowed_domains" = $3, - "updated_at" = now() -WHERE "account_id" = $1 AND "app_id" = $2 -RETURNING *; \ No newline at end of file diff --git a/idp/internal/providers/database/queries/apps.sql b/idp/internal/providers/database/queries/apps.sql index d305a4d..dab1092 100644 --- a/idp/internal/providers/database/queries/apps.sql +++ b/idp/internal/providers/database/queries/apps.sql @@ -27,7 +27,6 @@ INSERT INTO "apps" ( "custom_scopes", "default_custom_scopes", "domain", - "transport", "redirect_uris", "response_types", "allow_user_registration", @@ -61,8 +60,7 @@ INSERT INTO "apps" ( $24, $25, $26, - $27, - $28 + $27 ) RETURNING *; @@ -105,10 +103,9 @@ SET "client_name" = $2, "software_version" = $9, "contacts" = $10, "domain" = $11, - "transport" = $12, - "redirect_uris" = $13, - "allow_user_registration" = $14, - "response_types" = $15, + "redirect_uris" = $12, + "allow_user_registration" = $13, + "response_types" = $14, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 diff --git a/idp/internal/providers/database/queries/registered_apps.sql b/idp/internal/providers/database/queries/registered_apps.sql index 44c94c0..70ef4d8 100644 --- a/idp/internal/providers/database/queries/registered_apps.sql +++ b/idp/internal/providers/database/queries/registered_apps.sql @@ -21,7 +21,6 @@ INSERT INTO "apps" ( "custom_scopes", "default_custom_scopes", "domain", - "transport", "redirect_uris", "response_types", "allow_user_registration", @@ -95,8 +94,7 @@ INSERT INTO "apps" ( $44, $45, $46, - $47, - $48 + $47 ) RETURNING *; -- name: UpdateRegisteredApp :one @@ -117,31 +115,30 @@ UPDATE "apps" SET "custom_scopes" = $15, "default_custom_scopes" = $16, "domain" = $17, - "transport" = $18, - "redirect_uris" = $19, - "response_types" = $20, - "allow_user_registration" = $21, - "auth_providers" = $22, - "jwks_uri" = $23, - "jwks" = $24, - "sector_identifier_uri" = $25, - "subject_type" = $26, - "id_token_signed_response_alg" = $27, - "id_token_encrypted_response_alg" = $28, - "id_token_encrypted_response_enc" = $29, - "userinfo_signed_response_alg" = $30, - "userinfo_encrypted_response_alg" = $31, - "userinfo_encrypted_response_enc" = $32, - "request_object_signing_alg" = $33, - "request_object_encryption_alg" = $34, - "request_object_encryption_enc" = $35, - "token_endpoint_auth_signing_alg" = $36, - "default_max_age" = $37, - "require_auth_time" = $38, - "default_acr_values" = $39, - "initiate_login_uri" = $40, - "request_uris" = $41, - "access_token_signing_alg" = $42, + "redirect_uris" = $18, + "response_types" = $19, + "allow_user_registration" = $20, + "auth_providers" = $21, + "jwks_uri" = $22, + "jwks" = $23, + "sector_identifier_uri" = $24, + "subject_type" = $25, + "id_token_signed_response_alg" = $26, + "id_token_encrypted_response_alg" = $27, + "id_token_encrypted_response_enc" = $28, + "userinfo_signed_response_alg" = $29, + "userinfo_encrypted_response_alg" = $30, + "userinfo_encrypted_response_enc" = $31, + "request_object_signing_alg" = $32, + "request_object_encryption_alg" = $33, + "request_object_encryption_enc" = $34, + "token_endpoint_auth_signing_alg" = $35, + "default_max_age" = $36, + "require_auth_time" = $37, + "default_acr_values" = $38, + "initiate_login_uri" = $39, + "request_uris" = $40, + "access_token_signing_alg" = $41, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 diff --git a/idp/internal/providers/database/registered_apps.sql.go b/idp/internal/providers/database/registered_apps.sql.go index 883670b..640283e 100644 --- a/idp/internal/providers/database/registered_apps.sql.go +++ b/idp/internal/providers/database/registered_apps.sql.go @@ -35,7 +35,6 @@ INSERT INTO "apps" ( "custom_scopes", "default_custom_scopes", "domain", - "transport", "redirect_uris", "response_types", "allow_user_registration", @@ -109,9 +108,8 @@ INSERT INTO "apps" ( $44, $45, $46, - $47, - $48 -) RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at + $47 +) RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at ` type CreateRegisteredAppParams struct { @@ -136,7 +134,6 @@ type CreateRegisteredAppParams struct { CustomScopes []string DefaultCustomScopes []string Domain string - Transport Transport RedirectUris []string ResponseTypes []ResponseType AllowUserRegistration bool @@ -188,7 +185,6 @@ func (q *Queries) CreateRegisteredApp(ctx context.Context, arg CreateRegisteredA arg.CustomScopes, arg.DefaultCustomScopes, arg.Domain, - arg.Transport, arg.RedirectUris, arg.ResponseTypes, arg.AllowUserRegistration, @@ -243,7 +239,6 @@ func (q *Queries) CreateRegisteredApp(ctx context.Context, arg CreateRegisteredA &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, @@ -298,35 +293,34 @@ UPDATE "apps" SET "custom_scopes" = $15, "default_custom_scopes" = $16, "domain" = $17, - "transport" = $18, - "redirect_uris" = $19, - "response_types" = $20, - "allow_user_registration" = $21, - "auth_providers" = $22, - "jwks_uri" = $23, - "jwks" = $24, - "sector_identifier_uri" = $25, - "subject_type" = $26, - "id_token_signed_response_alg" = $27, - "id_token_encrypted_response_alg" = $28, - "id_token_encrypted_response_enc" = $29, - "userinfo_signed_response_alg" = $30, - "userinfo_encrypted_response_alg" = $31, - "userinfo_encrypted_response_enc" = $32, - "request_object_signing_alg" = $33, - "request_object_encryption_alg" = $34, - "request_object_encryption_enc" = $35, - "token_endpoint_auth_signing_alg" = $36, - "default_max_age" = $37, - "require_auth_time" = $38, - "default_acr_values" = $39, - "initiate_login_uri" = $40, - "request_uris" = $41, - "access_token_signing_alg" = $42, + "redirect_uris" = $18, + "response_types" = $19, + "allow_user_registration" = $20, + "auth_providers" = $21, + "jwks_uri" = $22, + "jwks" = $23, + "sector_identifier_uri" = $24, + "subject_type" = $25, + "id_token_signed_response_alg" = $26, + "id_token_encrypted_response_alg" = $27, + "id_token_encrypted_response_enc" = $28, + "userinfo_signed_response_alg" = $29, + "userinfo_encrypted_response_alg" = $30, + "userinfo_encrypted_response_enc" = $31, + "request_object_signing_alg" = $32, + "request_object_encryption_alg" = $33, + "request_object_encryption_enc" = $34, + "token_endpoint_auth_signing_alg" = $35, + "default_max_age" = $36, + "require_auth_time" = $37, + "default_acr_values" = $38, + "initiate_login_uri" = $39, + "request_uris" = $40, + "access_token_signing_alg" = $41, "version" = "version" + 1, "updated_at" = now() WHERE "id" = $1 -RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, transport, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at +RETURNING registration_token_jti, software_statement, id, account_id, account_public_id, client_id, version, creation_method, redirect_uris, token_endpoint_auth_method, grant_types, response_types, client_name, client_uri, logo_uri, scopes, custom_scopes, contacts, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, domain, allow_user_registration, auth_providers, username_column, default_scopes, default_custom_scopes, app_type, sector_identifier_uri, subject_type, id_token_signed_response_alg, id_token_encrypted_response_alg, id_token_encrypted_response_enc, userinfo_signed_response_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, request_object_signing_alg, request_object_encryption_alg, request_object_encryption_enc, token_endpoint_auth_signing_alg, default_max_age, require_auth_time, default_acr_values, initiate_login_uri, request_uris, access_token_signing_alg, session_type, access_token_ttl, id_token_ttl, refresh_token_idle_ttl, refresh_token_ttl, grant_ttl, created_at, updated_at ` type UpdateRegisteredAppParams struct { @@ -347,7 +341,6 @@ type UpdateRegisteredAppParams struct { CustomScopes []string DefaultCustomScopes []string Domain string - Transport Transport RedirectUris []string ResponseTypes []ResponseType AllowUserRegistration bool @@ -393,7 +386,6 @@ func (q *Queries) UpdateRegisteredApp(ctx context.Context, arg UpdateRegisteredA arg.CustomScopes, arg.DefaultCustomScopes, arg.Domain, - arg.Transport, arg.RedirectUris, arg.ResponseTypes, arg.AllowUserRegistration, @@ -446,7 +438,6 @@ func (q *Queries) UpdateRegisteredApp(ctx context.Context, arg UpdateRegisteredA &i.SoftwareID, &i.SoftwareVersion, &i.Domain, - &i.Transport, &i.AllowUserRegistration, &i.AuthProviders, &i.UsernameColumn, diff --git a/idp/internal/providers/tokens/dynamic_registration_software_statements.go b/idp/internal/providers/tokens/dynamic_registration_software_statements.go index 8456bf6..457bb72 100644 --- a/idp/internal/providers/tokens/dynamic_registration_software_statements.go +++ b/idp/internal/providers/tokens/dynamic_registration_software_statements.go @@ -23,9 +23,9 @@ type SoftwareStatementClaims struct { RawMetadata map[string]json.RawMessage `json:"-"` RedirectURIs []string `json:"redirect_uris,omitempty" validate:"omitempty,min=1,dive,uri"` TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty" validate:"omitempty,oneof=none client_secret_basic client_secret_post client_secret_jwt private_key_jwt"` - GrantTypes []string `json:"grant_types,omitempty" validate:"omitempty,min=1,dive,oneof=authorization_code refresh_token client_credentials urn:ietf:params:oauth:grant-type:jwt-bearer"` - ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,dive,oneof=none code 'code id_token'"` - ApplicationType string `json:"application_type,omitempty" validate:"omitempty,oneof=native service mcp web spa backend device"` + GrantTypes []string `json:"grant_types,omitempty" validate:"omitempty,min=1,dive,oneof=authorization_code implicit refresh_token client_credentials urn:ietf:params:oauth:grant-type:jwt-bearer"` + ResponseTypes []string `json:"response_types,omitempty" validate:"omitempty,dive,oneof=none code id_token 'code id_token'"` + ApplicationType string `json:"application_type,omitempty" validate:"omitempty,oneof=web native service mcp"` ClientName string `json:"client_name,omitempty" validate:"omitempty,min=1,max=255"` ClientURI string `json:"client_uri,omitempty" validate:"omitempty,url"` LogoURI string `json:"logo_uri,omitempty" validate:"omitempty,url"` @@ -110,3 +110,4 @@ func (t *Tokens) VerifySoftwareStatement( } return claims.SoftwareStatementClaims, claims.RegisteredClaims, nil } + diff --git a/idp/internal/server/server.go b/idp/internal/server/server.go index 6c1471b..544a0ad 100644 --- a/idp/internal/server/server.go +++ b/idp/internal/server/server.go @@ -49,62 +49,66 @@ const ( PgTypeKekUsage PgType = "kek_usage" PgTypeDekUsage PgType = "dek_usage" PgTypeTokenCryptoSuite PgType = "token_crypto_suite" + PgTypeTokenEncryptionAlgorithm PgType = "token_encryption_algorithm" + PgTypeTokenEncryptionEncoding PgType = "token_encryption_encoding" PgTypeTokenKeyUsage PgType = "token_key_usage" PgTypeTokenKeyType PgType = "token_key_type" + PgTypeActivityStatus PgType = "activity_status" PgTypeTwoFactorType PgType = "two_factor_type" PgTypeTOTPUsage PgType = "totp_usage" PgTypeCredentialsUsage PgType = "credentials_usage" - PgTypeSecretStorageMode PgType = "secret_storage_mode" PgTypeAuthMethod PgType = "auth_method" PgTypeResponseType PgType = "response_type" PgTypeAccountCredentialsScope PgType = "account_credentials_scope" PgTypeAccountCredentialsType PgType = "account_credentials_type" - PgTypeTransport PgType = "transport" - PgTypeCreationSource PgType = "creation_source" + PgTypeClientSubjectType PgType = "client_subject_type" + PgTypeCreationMethod PgType = "creation_method" PgTypeAuthProvider PgType = "auth_provider" PgTypeClaims PgType = "claims" PgTypeScopes PgType = "scopes" PgTypeAppType PgType = "app_type" PgTypeAppUsernameColumn PgType = "app_username_column" PgTypeGrantType PgType = "grant_type" + PgTypeSessionType PgType = "session_type" PgTypeInitialAccessTokenGenerationMethod PgType = "initial_access_token_generation_method" PgTypeSoftwareStatementVerificationMethod PgType = "software_statement_verification_method" - PgTypeAppProfileType PgType = "app_profile_type" - PgTypeTokenOwner PgType = "token_owner" PgTypeDynamicRegistrationUsage PgType = "dynamic_registration_usage" PgTypeDomainVerificationMethod PgType = "domain_verification_method" - PgTypeCreationMethod PgType = "creation_method" + PgTypeAppProfileType PgType = "app_profile_type" + PgTypeTokenOwner PgType = "token_owner" ) -var PgTypes = [28]PgType{ +var PgTypes = [30]PgType{ PgTypeKekUsage, PgTypeDekUsage, PgTypeTokenCryptoSuite, + PgTypeTokenEncryptionAlgorithm, + PgTypeTokenEncryptionEncoding, PgTypeTokenKeyUsage, PgTypeTokenKeyType, + PgTypeActivityStatus, PgTypeTwoFactorType, PgTypeTOTPUsage, PgTypeCredentialsUsage, - PgTypeSecretStorageMode, PgTypeAuthMethod, + PgTypeResponseType, PgTypeAccountCredentialsScope, PgTypeAccountCredentialsType, - PgTypeTransport, - PgTypeCreationSource, + PgTypeClientSubjectType, + PgTypeCreationMethod, PgTypeAuthProvider, PgTypeClaims, PgTypeScopes, PgTypeAppType, PgTypeAppUsernameColumn, PgTypeGrantType, - PgTypeResponseType, + PgTypeSessionType, PgTypeInitialAccessTokenGenerationMethod, PgTypeSoftwareStatementVerificationMethod, - PgTypeAppProfileType, - PgTypeTokenOwner, PgTypeDynamicRegistrationUsage, PgTypeDomainVerificationMethod, - PgTypeCreationMethod, + PgTypeAppProfileType, + PgTypeTokenOwner, } func New( diff --git a/idp/internal/services/account_credentials.go b/idp/internal/services/account_credentials.go index e3f8865..71897c9 100644 --- a/idp/internal/services/account_credentials.go +++ b/idp/internal/services/account_credentials.go @@ -29,34 +29,6 @@ const ( accountCredentialsKeysCacheKeyPrefix string = "account_credentials_keys" ) -func mapAccountCredentialsTransport( - transport string, - credentialType database.AccountCredentialsType, -) (database.Transport, *exceptions.ServiceError) { - if credentialType == database.AccountCredentialsTypeMcp { - switch transport { - case transportSTDIO: - return database.TransportStdio, nil - case transportStreamableHTTP: - return database.TransportStreamableHttp, nil - default: - return "", exceptions.NewValidationError("invalid transport: " + transport) - } - } - if credentialType == database.AccountCredentialsTypeService || credentialType == database.AccountCredentialsTypeNative { - switch transport { - case transportHTTP: - return database.TransportHttp, nil - case transportHTTPS: - return database.TransportHttps, nil - default: - return "", exceptions.NewValidationError("invalid transport: " + transport) - } - } - - return "", exceptions.NewValidationError("invalid credentials type: " + string(credentialType)) -} - func mapAccountCredentialsType(credentialsType string) (database.AccountCredentialsType, *exceptions.ServiceError) { acType := database.AccountCredentialsType(credentialsType) switch acType { @@ -74,7 +46,6 @@ func mapAccountCredentialsType(credentialsType string) (database.AccountCredenti func mapAccountCredentialsTokenEndpointAuthMethod( authMethod string, credentialType database.AccountCredentialsType, - transport database.Transport, ) (database.AuthMethod, *exceptions.ServiceError) { switch credentialType { case database.AccountCredentialsTypeNative: @@ -90,18 +61,11 @@ func mapAccountCredentialsTokenEndpointAuthMethod( return mapAuthMethod(authMethod) case database.AccountCredentialsTypeMcp: - if transport == database.TransportStdio { - if authMethod != "" && authMethod != AuthMethodNone { - return "", exceptions.NewValidationError("auth method is not supported for stdio mcp credentials") - } - - return database.AuthMethodNone, nil - } - if transport == database.TransportStreamableHttp { - return mapAuthMethod(authMethod) + if authMethod != "" && authMethod != AuthMethodNone { + return "", exceptions.NewValidationError("only auth method none is supported for mcp credentials") } - return "", exceptions.NewValidationError("invalid transport: " + string(transport)) + return database.AuthMethodNone, nil default: return "", exceptions.NewValidationError("invalid credentials type: " + string(credentialType)) } @@ -124,8 +88,8 @@ func mapAccountCredentialsGrantTypes( return nil, serviceErr } - if mappedGrantType != database.GrantTypeAuthorizationCode && mappedGrantType != database.GrantTypeRefreshToken { - return nil, exceptions.NewValidationError("only authorization_code and refresh_token grant types are supported for mcp credentials") + if mappedGrantType != database.GrantTypeAuthorizationCode && mappedGrantType != database.GrantTypeRefreshToken && mappedGrantType != database.GrantTypeImplicit { + return nil, exceptions.NewValidationError("only authorization_code, implicit, and refresh_token grant types are supported for mcp credentials") } gts = append(gts, mappedGrantType) @@ -164,8 +128,8 @@ func mapAccountCredentialsGrantTypes( return nil, serviceErr } - if mappedGrantType != database.GrantTypeAuthorizationCode && mappedGrantType != database.GrantTypeRefreshToken { - return nil, exceptions.NewValidationError("only authorization_code and refresh_token grant types are supported for native credentials") + if mappedGrantType != database.GrantTypeAuthorizationCode && mappedGrantType != database.GrantTypeRefreshToken && mappedGrantType != database.GrantTypeImplicit { + return nil, exceptions.NewValidationError("only authorization_code, implicit, and refresh_token grant types are supported for native credentials") } gts = append(gts, mappedGrantType) @@ -227,7 +191,6 @@ type CreateAccountCredentialsOptions struct { SoftwareVersion string Contacts []string CreationMethod database.CreationMethod - Transport string Scopes []string AuthMethod string Algorithm string @@ -251,17 +214,7 @@ func (s *Services) CreateAccountCredentials( return dtos.AccountCredentialsDTO{}, serviceErr } - transport, serviceErr := mapAccountCredentialsTransport(opts.Transport, credentialsType) - if serviceErr != nil { - logger.WarnContext(ctx, "Failed to map transport", "serviceError", serviceErr) - return dtos.AccountCredentialsDTO{}, serviceErr - } - - authMethod, serviceErr := mapAccountCredentialsTokenEndpointAuthMethod( - opts.AuthMethod, - credentialsType, - transport, - ) + authMethod, serviceErr := mapAccountCredentialsTokenEndpointAuthMethod(opts.AuthMethod, credentialsType) if serviceErr != nil { logger.WarnContext(ctx, "Failed to map auth method", "serviceError", serviceErr) return dtos.AccountCredentialsDTO{}, serviceErr @@ -283,10 +236,6 @@ func (s *Services) CreateAccountCredentials( return dtos.AccountCredentialsDTO{}, serviceErr } } - if transport == database.TransportStdio { - grantTypes = make([]database.GrantType, 0) - } - scopes, serviceErr := mapAccountCredentialsScopes(opts.Scopes) if serviceErr != nil { logger.WarnContext(ctx, "Failed to map scopes", "serviceError", serviceErr) @@ -351,7 +300,6 @@ func (s *Services) CreateAccountCredentials( SoftwareVersion: mapEmptyString(opts.SoftwareVersion), Contacts: utils.ToEmptySlice(opts.Contacts), CreationMethod: creationMethod, - Transport: transport, IDTokenSignedResponseAlg: database.TokenCryptoSuiteES256, AccessTokenSigningAlg: database.TokenCryptoSuiteES256, }, @@ -398,7 +346,6 @@ func (s *Services) CreateAccountCredentials( SoftwareVersion: mapEmptyString(opts.SoftwareVersion), Contacts: utils.ToEmptySlice(opts.Contacts), CreationMethod: creationMethod, - Transport: transport, IDTokenSignedResponseAlg: database.TokenCryptoSuiteES256, AccessTokenSigningAlg: database.TokenCryptoSuiteES256, }, @@ -640,22 +587,6 @@ func (s *Services) ListAccountCredentialsByAccountPublicID( return accountCredentialsDTOs, count, nil } -func mapAccountCredentialsUpdateTransport( - transport string, - currentTransport database.Transport, - credentialsType database.AccountCredentialsType, -) (database.Transport, *exceptions.ServiceError) { - if credentialsType == database.AccountCredentialsTypeMcp { - if transport != "" { - return "", exceptions.NewValidationError("Transport update is not allowed for MCP credentials") - } - - return currentTransport, nil - } - - return mapAccountCredentialsTransport(transport, credentialsType) -} - type UpdateAccountCredentialsScopesOptions struct { RequestID string AccountPublicID uuid.UUID @@ -671,7 +602,6 @@ type UpdateAccountCredentialsScopesOptions struct { PolicyURI string SoftwareVersion string Contacts []string - Transport string } func (s *Services) UpdateAccountCredentials( @@ -722,15 +652,6 @@ func (s *Services) UpdateAccountCredentials( } } - transport, serviceErr := mapAccountCredentialsUpdateTransport( - opts.Transport, - accountCredentialsDTO.Transport, - accountCredentialsDTO.Type, - ) - if serviceErr != nil { - return dtos.AccountCredentialsDTO{}, serviceErr - } - domain, serviceErr := mapDomain(opts.ClientURI, opts.Domain) if serviceErr != nil { logger.WarnContext(ctx, "Failed to map domain", "serviceError", serviceErr) @@ -751,7 +672,6 @@ func (s *Services) UpdateAccountCredentials( PolicyUri: mapEmptyURL(opts.PolicyURI), SoftwareVersion: mapEmptyString(opts.SoftwareVersion), Contacts: utils.ToEmptySlice(opts.Contacts), - Transport: transport, }) if err != nil { logger.ErrorContext(ctx, "Failed to update account keys scopes", "error", err) diff --git a/idp/internal/services/account_credentials_registration.go b/idp/internal/services/account_credentials_registration.go index 970635c..fbe7016 100644 --- a/idp/internal/services/account_credentials_registration.go +++ b/idp/internal/services/account_credentials_registration.go @@ -46,14 +46,6 @@ var accountCredentialsRegistrationUsages []database.DynamicRegistrationUsage = [ database.DynamicRegistrationUsageAccount, } -func mapAccountCredentialsDRTransport(applicationType database.AccountCredentialsType) database.Transport { - if applicationType == database.AccountCredentialsTypeMcp { - return database.TransportStreamableHttp - } - - return database.TransportHttps -} - type mapAccountCredentialsRegistrationDataToDBParamsOptions struct { applicationType database.AccountCredentialsType accountPublicID uuid.UUID @@ -61,7 +53,6 @@ type mapAccountCredentialsRegistrationDataToDBParamsOptions struct { domain string requestID string tokenEndpointAuthMethod database.AuthMethod - transport database.Transport scopes []database.AccountCredentialsScope data *ApplicationRegistrationData } @@ -177,7 +168,6 @@ func (s *Services) mapAccountCredentialsRegistrationDataToDBParams( AccountPublicID: opts.accountPublicID, Domain: opts.domain, CreationMethod: database.CreationMethodDynamicRegistration, - Transport: opts.transport, ClientID: utils.Base62UUID(), RedirectUris: utils.MapSlice(opts.data.RedirectURIs, func(uri *string) string { return *uri @@ -271,25 +261,6 @@ func (s *Services) CreateAccountCredentialsRegistration(ctx context.Context, opt if opts.InitialAccessTokenDomain == "" { return dtos.AccountCredentialsDTO{}, exceptions.NewError(exceptions.OAuthErrorInvalidToken, "initial access token domain is required") } - return registrationTransaction(s, ctx, opts.RequestID, func(qrs *database.Queries) (dtos.AccountCredentialsDTO, *exceptions.ServiceError) { - return s.createAccountCredentialsRegistration(ctx, qrs, opts) - }) -} - -func (s *Services) createAccountCredentialsRegistration( - ctx context.Context, - qrs *database.Queries, - opts CreateAccountCredentialsRegistrationOptions, -) (dtos.AccountCredentialsDTO, *exceptions.ServiceError) { - logger := s.buildLogger( - opts.RequestID, - accountCredentialsRegistrationLocation, - "CreateAccountCredentialsRegistration", - ).With( - "accountPublicID", opts.AccountPublicID, - ) - logger.InfoContext(ctx, "Creating account credentials registration...") - data := ApplicationRegistrationData{ RedirectURIs: opts.RedirectURIs, TokenEndpointAuthMethod: opts.TokenEndpointAuthMethod, @@ -344,6 +315,19 @@ func (s *Services) createAccountCredentialsRegistration( if preparationErr != nil { return dtos.AccountCredentialsDTO{}, preparationErr } + + return registrationTransaction(s, ctx, opts.RequestID, func(qrs *database.Queries) (dtos.AccountCredentialsDTO, *exceptions.ServiceError) { + return s.createAccountCredentialsRegistration(ctx, qrs, data, opts) + }) +} + +func (s *Services) createAccountCredentialsRegistration( + ctx context.Context, + qrs *database.Queries, + data ApplicationRegistrationData, + opts CreateAccountCredentialsRegistrationOptions, +) (dtos.AccountCredentialsDTO, *exceptions.ServiceError) { + // Use the verified and normalized metadata for all registration decisions. opts.RedirectURIs = data.RedirectURIs opts.TokenEndpointAuthMethod = data.TokenEndpointAuthMethod opts.ResponseTypes = data.ResponseTypes @@ -379,6 +363,15 @@ func (s *Services) createAccountCredentialsRegistration( opts.TokenEndpointAuthSigningAlg = data.TokenEndpointAuthSigningAlg opts.AccessTokenSigningAlg = data.AccessTokenSigningAlg + logger := s.buildLogger( + opts.RequestID, + accountCredentialsRegistrationLocation, + "CreateAccountCredentialsRegistration", + ).With( + "accountPublicID", opts.AccountPublicID, + ) + logger.InfoContext(ctx, "Creating account credentials registration...") + applicationType, serviceErr := mapAccountCredentialsType(opts.ApplicationType) if serviceErr != nil { logger.ErrorContext(ctx, "Failed to map application type", "serviceError", serviceErr) @@ -391,8 +384,10 @@ func (s *Services) createAccountCredentialsRegistration( return dtos.AccountCredentialsDTO{}, serviceErr } - transport := mapAccountCredentialsDRTransport(applicationType) - tokenEndpointAuthMethod, serviceErr := mapAuthMethod(opts.TokenEndpointAuthMethod) + tokenEndpointAuthMethod, serviceErr := mapAccountCredentialsTokenEndpointAuthMethod( + opts.TokenEndpointAuthMethod, + applicationType, + ) if serviceErr != nil { logger.ErrorContext(ctx, "Failed to map token endpoint auth method", "serviceError", serviceErr) return dtos.AccountCredentialsDTO{}, serviceErr @@ -475,7 +470,6 @@ func (s *Services) createAccountCredentialsRegistration( domain: domain, requestID: opts.RequestID, tokenEndpointAuthMethod: tokenEndpointAuthMethod, - transport: transport, scopes: scopes, data: &data, }) @@ -492,7 +486,7 @@ func (s *Services) createAccountCredentialsRegistration( } logger.InfoContext(ctx, "Created account credentials successfully") - return s.finalizeAccountCredentialsRegistration(ctx, opts, &accountCredentials, "", time.Time{}, nil) + return s.finalizeAccountCredentialsRegistration(ctx, qrs, opts, &accountCredentials, "", time.Time{}, nil) } accountCredentials, err := qrs.CreateAccountCredentials(ctx, params) @@ -538,7 +532,7 @@ func (s *Services) createAccountCredentialsRegistration( return dtos.AccountCredentialsDTO{}, serviceErr } - return s.finalizeAccountCredentialsRegistration(ctx, opts, &accountCredentials, "", dbPrms.ExpiresAt, jwk) + return s.finalizeAccountCredentialsRegistration(ctx, qrs, opts, &accountCredentials, "", dbPrms.ExpiresAt, jwk) case database.AuthMethodClientSecretBasic, database.AuthMethodClientSecretPost, database.AuthMethodClientSecretJwt: var ccID int32 var secretID, secret string @@ -570,7 +564,7 @@ func (s *Services) createAccountCredentialsRegistration( return dtos.AccountCredentialsDTO{}, serviceErr } - return s.finalizeAccountCredentialsRegistration(ctx, opts, &accountCredentials, secretID+"."+secret, exp, nil) + return s.finalizeAccountCredentialsRegistration(ctx, qrs, opts, &accountCredentials, secretID+"."+secret, exp, nil) default: logger.ErrorContext(ctx, "Invalid token endpoint auth method", "tokenEndpointAuthMethod", tokenEndpointAuthMethod) serviceErr = exceptions.NewInternalServerError() @@ -580,6 +574,7 @@ func (s *Services) createAccountCredentialsRegistration( func (s *Services) finalizeAccountCredentialsRegistration( ctx context.Context, + queries *database.Queries, opts CreateAccountCredentialsRegistrationOptions, row *database.AccountCredential, secret string, @@ -593,6 +588,7 @@ func (s *Services) finalizeAccountCredentialsRegistration( token, serviceErr := s.registrationResponseToken(ctx, registrationStateOptions{ RequestID: opts.RequestID, AccountPublicID: opts.AccountPublicID, AccountVersion: opts.AccountVersion, ClientID: row.ClientID, BackendDomain: opts.BackendDomain, ID: row.ID, Statement: opts.SoftwareStatement, + Queries: queries, }) if serviceErr != nil { return dtos.AccountCredentialsDTO{}, serviceErr diff --git a/idp/internal/services/app_dynamic_registration.go b/idp/internal/services/app_dynamic_registration.go index 968053d..e710876 100644 --- a/idp/internal/services/app_dynamic_registration.go +++ b/idp/internal/services/app_dynamic_registration.go @@ -36,29 +36,14 @@ var appDynamicRegistrationUsages []database.DynamicRegistrationUsage = []databas database.DynamicRegistrationUsageApp, } -func mapAppDRTransport(appType database.AppType) database.Transport { - if appType == database.AppTypeMcp { - return database.TransportStreamableHttp - } - - return database.TransportHttps -} - func mapAppGrantTypes( appType database.AppType, grantTypes []string, ) ([]database.GrantType, *exceptions.ServiceError) { if len(grantTypes) == 0 { switch appType { - case database.AppTypeWeb, database.AppTypeSpa, database.AppTypeNative, database.AppTypeMcp: + case database.AppTypeWeb, database.AppTypeNative: return authCodeAppGrantTypes, nil - case database.AppTypeBackend, database.AppTypeService: - return []database.GrantType{ - database.GrantTypeClientCredentials, - database.GrantTypeUrnIetfParamsOauthGrantTypeJwtBearer, - }, nil - case database.AppTypeDevice: - return deviceGrantTypes, nil default: return nil, exceptions.NewValidationError("invalid app type") } @@ -76,17 +61,41 @@ func mapAppGrantTypes( return gts, nil } +func validateAppAuthGrantTypes( + appType database.AppType, + authMethod database.AuthMethod, + grantTypes []database.GrantType, +) *exceptions.ServiceError { + serviceGrants := []database.GrantType{ + database.GrantTypeClientCredentials, + database.GrantTypeUrnIetfParamsOauthGrantTypeJwtBearer, + } + if appType == database.AppTypeNative { + for _, grantType := range grantTypes { + if !slices.Contains(authCodeAppGrantTypes, grantType) && grantType != database.GrantTypeImplicit { + return exceptions.NewValidationError("native apps only support authorization_code, refresh_token, and implicit grant types") + } + } + } + if authMethod == database.AuthMethodNone { + for _, grantType := range grantTypes { + if slices.Contains(serviceGrants, grantType) { + return exceptions.NewValidationError("public apps cannot use client_credentials or jwt-bearer grant types") + } + } + } + return nil +} + func mapAppTokenEndpointAuthMethod( authMethod string, appType database.AppType, ) (database.AuthMethod, *exceptions.ServiceError) { if authMethod == "" { switch appType { - case database.AppTypeWeb, database.AppTypeSpa, database.AppTypeNative: + case database.AppTypeWeb: return database.AuthMethodClientSecretPost, nil - case database.AppTypeBackend, database.AppTypeService: - return database.AuthMethodPrivateKeyJwt, nil - case database.AppTypeDevice, database.AppTypeMcp: + case database.AppTypeNative: return database.AuthMethodNone, nil default: return "", exceptions.NewValidationError("invalid app type") @@ -99,17 +108,11 @@ func mapAppTokenEndpointAuthMethod( } switch appType { - case database.AppTypeWeb, database.AppTypeSpa, database.AppTypeNative: - if mappedAuthMethod == database.AuthMethodNone { - return "", exceptions.NewValidationError("auth method none is not supported for web, spa, or native apps") - } - case database.AppTypeBackend, database.AppTypeService: - if mappedAuthMethod == database.AuthMethodNone { - return "", exceptions.NewValidationError("auth method none is not supported for backend or service apps") - } - case database.AppTypeDevice, database.AppTypeMcp: + case database.AppTypeWeb: + // Web apps can be confidential clients or public clients such as SPAs. + case database.AppTypeNative: if mappedAuthMethod != database.AuthMethodNone { - return "", exceptions.NewValidationError("only auth method none is supported for device or mcp apps") + return "", exceptions.NewValidationError("only auth method none is supported for native apps") } } @@ -123,7 +126,6 @@ type mapAppRegistrationDataToDBParamsOptions struct { domain string requestID string tokenEndpointAuthMethod database.AuthMethod - transport database.Transport scopes []database.Scopes customScopes []string defaultScopes []database.Scopes @@ -157,6 +159,9 @@ func (s *Services) mapAppRegistrationDataToDBParams( logger.ErrorContext(ctx, "Failed to map grant types", "serviceError", serviceErr) return database.CreateRegisteredAppParams{}, serviceErr } + if serviceErr := validateAppAuthGrantTypes(opts.appType, opts.tokenEndpointAuthMethod, grantTypes); serviceErr != nil { + return database.CreateRegisteredAppParams{}, serviceErr + } subjectType, serviceErr := mapEmptySubjectType(opts.data.SubjectType) if serviceErr != nil { @@ -238,6 +243,12 @@ func (s *Services) mapAppRegistrationDataToDBParams( if serviceErr != nil { return database.CreateRegisteredAppParams{}, serviceErr } + redirectURIs := utils.MapSlice(opts.data.RedirectURIs, func(uri *string) string { + return *uri + }) + if redirectURIs == nil { + redirectURIs = []string{} + } params := database.CreateRegisteredAppParams{ JwksUri: mapEmptyURL(opts.data.JWKsURI), @@ -278,17 +289,14 @@ func (s *Services) mapAppRegistrationDataToDBParams( Contacts: utils.MapSlice(opts.data.Contacts, func(t *string) string { return utils.Lowered(*t) }), - SoftwareID: mapEmptyString(opts.data.SoftwareID), - SoftwareVersion: mapEmptyString(opts.data.SoftwareVersion), - Scopes: opts.scopes, - DefaultScopes: opts.defaultScopes, - CustomScopes: opts.customScopes, - DefaultCustomScopes: opts.defaultCustomScopes, - Domain: opts.domain, - Transport: opts.transport, - RedirectUris: utils.MapSlice(opts.data.RedirectURIs, func(uri *string) string { - return *uri - }), + SoftwareID: mapEmptyString(opts.data.SoftwareID), + SoftwareVersion: mapEmptyString(opts.data.SoftwareVersion), + Scopes: opts.scopes, + DefaultScopes: opts.defaultScopes, + CustomScopes: opts.customScopes, + DefaultCustomScopes: opts.defaultCustomScopes, + Domain: opts.domain, + RedirectUris: redirectURIs, ResponseTypes: responseTypes, AllowUserRegistration: opts.allowUserRegistration, AuthProviders: opts.authProviders, @@ -347,24 +355,6 @@ func (s *Services) CreateAppCredentialsRegistration(ctx context.Context, opts Cr if opts.IsAuthenticated && opts.InitialAccessTokenDomain == "" { return dtos.AppDTO{}, exceptions.NewError(exceptions.OAuthErrorInvalidToken, "initial access token domain is required") } - return registrationTransaction(s, ctx, opts.RequestID, func(qrs *database.Queries) (dtos.AppDTO, *exceptions.ServiceError) { - return s.createAppCredentialsRegistration(ctx, qrs, opts) - }) -} - -func (s *Services) createAppCredentialsRegistration( - ctx context.Context, - qrs *database.Queries, - opts CreateAppCredentialsRegistrationOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger( - opts.RequestID, - appDynamicRegistrationLocation, - "CreateAppCredentialsRegistration", - ).With( - "accountID", opts.AccountID, - ) - logger.InfoContext(ctx, "Creating app credentials registration...") data := ApplicationRegistrationData{ RedirectURIs: opts.RedirectURIs, @@ -402,9 +392,6 @@ func (s *Services) createAppCredentialsRegistration( TokenEndpointAuthSigningAlg: opts.TokenEndpointAuthSigningAlg, AccessTokenSigningAlg: opts.AccessTokenSigningAlg, } - if opts.IsAuthenticated && opts.InitialAccessTokenDomain == "" { - return dtos.AppDTO{}, exceptions.NewError(exceptions.OAuthErrorInvalidToken, "initial access token domain is required") - } data, preparationErr := s.prepareDynamicRegistration(ctx, prepareDynamicRegistrationOptions{ requestID: opts.RequestID, accountID: opts.AccountID, accountPublicID: uuid.Nil, data: data, softwareStatement: opts.SoftwareStatement, @@ -413,6 +400,26 @@ func (s *Services) createAppCredentialsRegistration( if preparationErr != nil { return dtos.AppDTO{}, preparationErr } + + return registrationTransaction(s, ctx, opts.RequestID, func(qrs *database.Queries) (dtos.AppDTO, *exceptions.ServiceError) { + return s.createAppCredentialsRegistration(ctx, qrs, data, opts) + }) +} + +func (s *Services) createAppCredentialsRegistration( + ctx context.Context, + qrs *database.Queries, + data ApplicationRegistrationData, + opts CreateAppCredentialsRegistrationOptions, +) (dtos.AppDTO, *exceptions.ServiceError) { + logger := s.buildLogger( + opts.RequestID, + appDynamicRegistrationLocation, + "CreateAppCredentialsRegistration", + ).With( + "accountID", opts.AccountID, + ) + logger.InfoContext(ctx, "Creating app credentials registration...") opts.RedirectURIs = data.RedirectURIs opts.TokenEndpointAuthMethod = data.TokenEndpointAuthMethod opts.ResponseTypes = data.ResponseTypes @@ -454,8 +461,10 @@ func (s *Services) createAppCredentialsRegistration( return dtos.AppDTO{}, serviceErr } - transport := mapAppDRTransport(appType) - tokenEndpointAuthMethod, serviceErr := mapAuthMethod(opts.TokenEndpointAuthMethod) + tokenEndpointAuthMethod, serviceErr := mapAppTokenEndpointAuthMethod( + opts.TokenEndpointAuthMethod, + appType, + ) if serviceErr != nil { logger.ErrorContext(ctx, "Failed to map token endpoint auth method", "serviceError", serviceErr) return dtos.AppDTO{}, serviceErr @@ -591,7 +600,6 @@ func (s *Services) createAppCredentialsRegistration( domain: domain, requestID: opts.RequestID, tokenEndpointAuthMethod: tokenEndpointAuthMethod, - transport: transport, scopes: stdScopes, customScopes: customScopes, defaultScopes: defaultStdScopes, @@ -614,7 +622,7 @@ func (s *Services) createAppCredentialsRegistration( } logger.InfoContext(ctx, "Created app successfully") - return s.finalizeRegisteredApp(ctx, opts, accountDTO, &app, "", time.Time{}, nil) + return s.finalizeRegisteredApp(ctx, qrs, opts, accountDTO, &app, "", time.Time{}, nil) } app, err := qrs.CreateRegisteredApp(ctx, params) @@ -624,6 +632,8 @@ func (s *Services) createAppCredentialsRegistration( } switch tokenEndpointAuthMethod { + case database.AuthMethodNone: + return s.finalizeRegisteredApp(ctx, qrs, opts, accountDTO, &app, "", time.Time{}, nil) case database.AuthMethodPrivateKeyJwt: var dbPrms database.CreateCredentialsKeyParams var jwk utils.JWK @@ -658,11 +668,7 @@ func (s *Services) createAppCredentialsRegistration( return dtos.AppDTO{}, serviceErr } - if appType == database.AppTypeBackend || appType == database.AppTypeService { - return s.finalizeRegisteredApp(ctx, opts, accountDTO, &app, "", dbPrms.ExpiresAt, jwk) - } - - return s.finalizeRegisteredApp(ctx, opts, accountDTO, &app, "", dbPrms.ExpiresAt, jwk) + return s.finalizeRegisteredApp(ctx, qrs, opts, accountDTO, &app, "", dbPrms.ExpiresAt, jwk) case database.AuthMethodClientSecretBasic, database.AuthMethodClientSecretPost, database.AuthMethodClientSecretJwt: var ccID int32 var secretID, secret string @@ -692,11 +698,7 @@ func (s *Services) createAppCredentialsRegistration( return dtos.AppDTO{}, serviceErr } - if appType == database.AppTypeBackend || appType == database.AppTypeService { - return s.finalizeRegisteredApp(ctx, opts, accountDTO, &app, secretID+"."+secret, exp, nil) - } - - return s.finalizeRegisteredApp(ctx, opts, accountDTO, &app, secretID+"."+secret, exp, nil) + return s.finalizeRegisteredApp(ctx, qrs, opts, accountDTO, &app, secretID+"."+secret, exp, nil) default: logger.ErrorContext(ctx, "Invalid token endpoint auth method", "tokenEndpointAuthMethod", tokenEndpointAuthMethod) serviceErr = exceptions.NewInternalServerError() @@ -706,6 +708,7 @@ func (s *Services) createAppCredentialsRegistration( func (s *Services) finalizeRegisteredApp( ctx context.Context, + queries *database.Queries, opts CreateAppCredentialsRegistrationOptions, accountDTO dtos.AccountDTO, app *database.App, @@ -717,6 +720,7 @@ func (s *Services) finalizeRegisteredApp( token, serviceErr := s.registrationResponseToken(ctx, registrationStateOptions{ RequestID: opts.RequestID, AccountPublicID: accountDTO.PublicID, AccountVersion: accountDTO.Version(), ClientID: app.ClientID, BackendDomain: opts.BackendDomain, ID: app.ID, Statement: opts.SoftwareStatement, App: true, + Queries: queries, }) if serviceErr != nil { return dtos.AppDTO{}, serviceErr diff --git a/idp/internal/services/app_dynamic_registration_configs.go b/idp/internal/services/app_dynamic_registration_configs.go index d55af9d..a7a4ddf 100644 --- a/idp/internal/services/app_dynamic_registration_configs.go +++ b/idp/internal/services/app_dynamic_registration_configs.go @@ -72,6 +72,8 @@ func mapResponseTypes(responseTypes []string) ([]database.ResponseType, *excepti switch utils.Lowered(responseType) { case ResponseTypeCode: responseTypesDB = append(responseTypesDB, database.ResponseTypeCode) + case ResponseTypeIdToken: + responseTypesDB = append(responseTypesDB, database.ResponseTypeIDToken) case ResponseTypeCodeIdToken: responseTypesDB = append(responseTypesDB, database.ResponseTypeCodeidToken) default: diff --git a/idp/internal/services/apps.go b/idp/internal/services/apps.go index c7fb548..d06d2f3 100644 --- a/idp/internal/services/apps.go +++ b/idp/internal/services/apps.go @@ -8,6 +8,7 @@ package services import ( "context" + "slices" "strings" "time" @@ -25,19 +26,9 @@ const ( responseTypeCode string = "code" responseTypeCodeIDToken string = "code id_token" - - transportSTDIO string = "stdio" - transportStreamableHTTP string = "streamable_http" - transportHTTP string = "http" - transportHTTPS string = "https" ) var authCodeAppGrantTypes = []database.GrantType{database.GrantTypeAuthorizationCode, database.GrantTypeRefreshToken} -var deviceGrantTypes = []database.GrantType{ - database.GrantTypeUrnIetfParamsOauthGrantTypeDeviceCode, - database.GrantTypeRefreshToken, -} - var defaultAllowedScopes = []database.Scopes{database.ScopesOpenid, database.ScopesEmail, database.ScopesProfile} var defaultDefaultScopes = []database.Scopes{database.ScopesOpenid, database.ScopesEmail} @@ -314,18 +305,8 @@ func mapAppTypeToDB(appType string) (database.AppType, *exceptions.ServiceError) switch utils.Lowered(appType) { case "web": return database.AppTypeWeb, nil - case "spa": - return database.AppTypeSpa, nil case "native": return database.AppTypeNative, nil - case "backend": - return database.AppTypeBackend, nil - case "device": - return database.AppTypeDevice, nil - case "service": - return database.AppTypeService, nil - case "mcp": - return database.AppTypeMcp, nil default: return "", exceptions.NewValidationError("Unsupported app type") } @@ -643,7 +624,6 @@ type createAppOptions struct { allowUserRegistration bool clientURI string domain string - transport database.Transport usernameColumn string authMethod database.AuthMethod grantTypes []database.GrantType @@ -665,6 +645,13 @@ func (s *Services) createApp( qrs *database.Queries, opts createAppOptions, ) (database.App, error) { + redirectURIs := utils.MapSlice(opts.redirectURIs, func(t *string) string { + return utils.ProcessURL(*t) + }) + if redirectURIs == nil { + redirectURIs = []string{} + } + logger := s.buildLogger(opts.requestID, appsLocation, "createApp").With( "accountPublicId", opts.accountPublicID, "name", opts.name, @@ -724,12 +711,9 @@ func (s *Services) createApp( CustomScopes: customScopes, DefaultCustomScopes: defaultCustomScopes, Domain: derivedDomain, - Transport: opts.transport, ResponseTypes: opts.responseTypes, AuthProviders: authProviders, - RedirectUris: utils.MapSlice(opts.redirectURIs, func(t *string) string { - return utils.ProcessURL(*t) - }), + RedirectUris: redirectURIs, Contacts: utils.MapSlice(opts.contacts, func(t *string) string { return utils.Lowered(*t) }), @@ -806,7 +790,6 @@ func (s *Services) createSingleApp( CustomScopes: customScopes, DefaultCustomScopes: defaultCustomScopes, Domain: derivedDomain, - Transport: opts.transport, AuthProviders: authProviders, ResponseTypes: opts.responseTypes, RedirectUris: utils.MapSlice(opts.redirectURIs, func(t *string) string { @@ -825,14 +808,6 @@ func (s *Services) createSingleApp( return app, nil } -func mapStandardTransport(transport string) database.Transport { - if transport == transportHTTP { - return database.TransportHttp - } - - return database.TransportHttps -} - type CreateWebAppOptions struct { RequestID string AccountPublicID uuid.UUID @@ -851,9 +826,9 @@ type CreateWebAppOptions struct { Contacts []string SoftwareID string SoftwareVersion string - Transport string RedirectURIs []string ResponseTypes []string + GrantTypes []string Scopes []string DefaultScopes []string AuthProviders []string @@ -876,11 +851,51 @@ func (s *Services) CreateWebApp( return dtos.AppDTO{}, serviceErr } - responseTypes, serviceErr := mapResponseTypesWithDefault(opts.ResponseTypes) + grantTypes, serviceErr := mapAppGrantTypes(database.AppTypeWeb, opts.GrantTypes) if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map response types", "serviceError", serviceErr) + logger.ErrorContext(ctx, "Failed to map grant types", "serviceError", serviceErr) return dtos.AppDTO{}, serviceErr } + if serviceErr := validateAppAuthGrantTypes(database.AppTypeWeb, authMethod, grantTypes); serviceErr != nil { + return dtos.AppDTO{}, serviceErr + } + hasCodeGrant := slices.Contains(grantTypes, database.GrantTypeAuthorizationCode) + hasImplicitGrant := slices.Contains(grantTypes, database.GrantTypeImplicit) + + var responseTypes []database.ResponseType + if hasCodeGrant || hasImplicitGrant { + if len(opts.ResponseTypes) == 0 { + if hasCodeGrant && hasImplicitGrant { + responseTypes = []database.ResponseType{ + database.ResponseTypeCode, + database.ResponseTypeCodeidToken, + } + } else if hasCodeGrant { + responseTypes = []database.ResponseType{database.ResponseTypeCode} + } else { + responseTypes = make([]database.ResponseType, 0) + } + } else { + responseTypes, serviceErr = mapResponseTypesWithDefault(opts.ResponseTypes) + if serviceErr != nil { + logger.ErrorContext(ctx, "Failed to map response types", "serviceError", serviceErr) + return dtos.AppDTO{}, serviceErr + } + } + + if serviceErr := validateAppGrantResponseTypes(grantTypes, responseTypes); serviceErr != nil { + return dtos.AppDTO{}, serviceErr + } + + if len(opts.RedirectURIs) == 0 { + return dtos.AppDTO{}, exceptions.NewValidationError("redirect URIs are required for authorization grants") + } + } else { + if len(opts.ResponseTypes) > 0 { + return dtos.AppDTO{}, exceptions.NewValidationError("response types are not supported without authorization_code or implicit grant") + } + responseTypes = make([]database.ResponseType, 0) + } accountID, serviceErr := s.GetAccountIDByPublicIDAndVersion(ctx, GetAccountIDByPublicIDAndVersionOptions{ RequestID: opts.RequestID, @@ -923,10 +938,9 @@ func (s *Services) CreateWebApp( allowUserRegistration: opts.AllowUserRegistration, clientURI: opts.ClientURI, domain: opts.Domain, - transport: mapStandardTransport(opts.Transport), usernameColumn: opts.UsernameColumn, authMethod: authMethod, - grantTypes: authCodeAppGrantTypes, + grantTypes: grantTypes, logoURI: opts.LogoURI, tosURI: opts.TOSURI, policyURI: opts.PolicyURI, @@ -946,6 +960,9 @@ func (s *Services) CreateWebApp( } switch opts.AuthMethod { + case AuthMethodNone: + logger.InfoContext(ctx, "Created public web app successfully") + return dtos.MapAppToDTO(&app), nil case AuthMethodPrivateKeyJwt: var dbPrms database.CreateCredentialsKeyParams var jwk utils.JWK @@ -1020,7 +1037,7 @@ func (s *Services) CreateWebApp( } } -type CreateSPANativeAppOptions struct { +type CreateNativeAppOptions struct { RequestID string AccountPublicID uuid.UUID AccountVersion int32 @@ -1029,7 +1046,6 @@ type CreateSPANativeAppOptions struct { Name string AllowUserRegistration bool Domain string - Transport string UsernameColumn string ResponseTypes []string ClientURI string @@ -1045,16 +1061,16 @@ type CreateSPANativeAppOptions struct { AuthProviders []string } -func (s *Services) CreateSPANativeApp( +func (s *Services) CreateNativeApp( ctx context.Context, - opts CreateSPANativeAppOptions, + opts CreateNativeAppOptions, ) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "CreateSPANativeApp").With( + logger := s.buildLogger(opts.RequestID, appsLocation, "CreateNativeApp").With( "accountPublicId", opts.AccountPublicID, "accountVersion", opts.AccountVersion, "name", opts.Name, ) - logger.InfoContext(ctx, "Creating SPA or Native app...") + logger.InfoContext(ctx, "Creating native app...") responseTypes, serviceErr := mapResponseTypesWithDefault(opts.ResponseTypes) if serviceErr != nil { @@ -1093,7 +1109,6 @@ func (s *Services) CreateSPANativeApp( allowUserRegistration: opts.AllowUserRegistration, clientURI: opts.ClientURI, domain: opts.Domain, - transport: mapStandardTransport(opts.Transport), usernameColumn: opts.UsernameColumn, authMethod: database.AuthMethodNone, grantTypes: authCodeAppGrantTypes, @@ -1114,1523 +1129,251 @@ func (s *Services) CreateSPANativeApp( return dtos.AppDTO{}, serviceErr } - logger.InfoContext(ctx, "Created SPA app successfully") - return dtos.MapWebNativeSPAMCPAppToDTO(&app), nil + logger.InfoContext(ctx, "Created native app successfully") + return dtos.MapAppToDTO(&app), nil } -type CreateBackendAppOptions struct { - RequestID string - AccountPublicID uuid.UUID - AccountVersion int32 - CreationMethod database.CreationMethod - Name string - AllowUserRegistration bool - UsernameColumn string - AuthMethod string - Algorithm string - ClientURI string - LogoURI string - TOSURI string - PolicyURI string - Contacts []string - SoftwareID string - SoftwareVersion string - Domain string - Transport string - Scopes []string - DefaultScopes []string - AuthProviders []string +func mapUpdateAuthProviders( + authProviders []string, + currentAuthProviders []database.AuthProvider, +) ([]database.AuthProvider, *exceptions.ServiceError) { + if len(authProviders) == 0 { + return currentAuthProviders, nil + } + + validAuthProviders := make([]database.AuthProvider, 0, len(authProviders)) + for _, provider := range authProviders { + dbp := database.AuthProvider(provider) + switch dbp { + case database.AuthProviderLocal, database.AuthProviderApple, database.AuthProviderFacebook, + database.AuthProviderGoogle, database.AuthProviderGithub, database.AuthProviderMicrosoft: + validAuthProviders = append(validAuthProviders, dbp) + default: + return nil, exceptions.NewValidationError("Unsupported auth provider: " + provider) + } + } + + return validAuthProviders, nil +} + +type updateAppOptions struct { + requestID string + usernameColumn string + allowUserRegistration bool + domain string + name string + clientURI string + logoURI string + tosURI string + policyURI string + softwareVersion string + contacts []string + redirectURIs []string + responseTypes []database.ResponseType + authProviders []string } -func (s *Services) CreateBackendApp( +func (s *Services) updateApp( ctx context.Context, - opts CreateBackendAppOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "CreateBackendApp").With( - "accountPublicId", opts.AccountPublicID.String(), - "accountVersion", opts.AccountVersion, - "name", opts.Name, + appDTO *dtos.AppDTO, + qrs *database.Queries, + opts updateAppOptions, +) (database.App, error) { + logger := s.buildLogger(opts.requestID, appsLocation, "updateApp").With( + "appID", appDTO.ID(), + "appClientName", appDTO.ClientName, ) - logger.InfoContext(ctx, "Creating backend app...") + logger.InfoContext(ctx, "Updating base app...") - authMethod, serviceErr := mapAuthMethod(opts.AuthMethod) + usernameColumn, serviceErr := mapUsernameColumn(opts.usernameColumn) if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map auth methods", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr + logger.ErrorContext(ctx, "Failed to map username column", "serviceError", serviceErr) + return database.App{}, serviceErr } - grantTypes, serviceErr := mapServerGrantTypesFromAuthMethod(opts.AuthMethod) + authProviders, serviceErr := mapUpdateAuthProviders(opts.authProviders, appDTO.AuthProviders) if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map grant types", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr + logger.ErrorContext(ctx, "Failed to map auth providers", "serviceError", serviceErr) + return database.App{}, serviceErr } - accountID, serviceErr := s.GetAccountIDByPublicIDAndVersion(ctx, GetAccountIDByPublicIDAndVersionOptions{ - RequestID: opts.RequestID, - PublicID: opts.AccountPublicID, - Version: opts.AccountVersion, - }) + derivedDomain, serviceErr := mapDomain(opts.clientURI, opts.domain) if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to get account ID by public ID and version", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr + logger.ErrorContext(ctx, "Failed to map domain", "serviceError", serviceErr) + return database.App{}, serviceErr } - name := strings.TrimSpace(opts.Name) - if serviceErr := s.checkForDuplicateApps(ctx, checkForDuplicateAppsOptions{ - requestID: opts.RequestID, - accountID: accountID, - name: name, - softwareID: opts.SoftwareID, - }); serviceErr != nil { - logger.ErrorContext(ctx, "Duplicate app found", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr + var softwareVersion pgtype.Text + if opts.softwareVersion != "" { + if err := softwareVersion.Scan(opts.softwareVersion); err != nil { + logger.ErrorContext(ctx, "Failed to scan software version", "error", err) + return database.App{}, err + } } - qrs, txn, err := s.database.BeginTx(ctx) + app, err := qrs.UpdateApp(ctx, database.UpdateAppParams{ + ID: appDTO.ID(), + ClientName: opts.name, + UsernameColumn: usernameColumn, + ClientUri: opts.clientURI, + LogoUri: mapEmptyURL(opts.logoURI), + TosUri: mapEmptyURL(opts.tosURI), + PolicyUri: mapEmptyURL(opts.policyURI), + SoftwareVersion: softwareVersion, + Domain: derivedDomain, + AllowUserRegistration: opts.allowUserRegistration, + ResponseTypes: opts.responseTypes, + AuthProviders: authProviders, + Contacts: utils.MapSlice(opts.contacts, func(t *string) string { + return utils.Lowered(*t) + }), + RedirectUris: utils.MapSlice(opts.redirectURIs, func(t *string) string { + return utils.ProcessURL(*t) + }), + }) if err != nil { - logger.ErrorContext(ctx, "Failed to start transaction", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) + logger.ErrorContext(ctx, "Failed to update app", "error", err) + return database.App{}, err } - defer func() { - logger.DebugContext(ctx, "Finalizing transaction") - s.database.FinalizeTx(ctx, txn, err, serviceErr) - }() - app, err := s.createApp(ctx, qrs, createAppOptions{ - requestID: opts.RequestID, - accountID: accountID, - accountPublicID: opts.AccountPublicID, - creationMethod: opts.CreationMethod, - appType: database.AppTypeBackend, - name: name, - allowUserRegistration: opts.AllowUserRegistration, - clientURI: opts.ClientURI, - domain: opts.Domain, - transport: mapStandardTransport(opts.Transport), - usernameColumn: opts.UsernameColumn, - authMethod: authMethod, - grantTypes: grantTypes, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - contacts: opts.Contacts, - softwareID: opts.SoftwareID, - softwareVersion: opts.SoftwareVersion, - scopes: opts.Scopes, - defaultScopes: opts.DefaultScopes, - redirectURIs: make([]string, 0), - responseTypes: make([]database.ResponseType, 0), - authProviders: opts.AuthProviders, - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to create app", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr + logger.InfoContext(ctx, "Updated base app successfully") + return app, nil +} + +func (s *Services) updateSingleApp( + ctx context.Context, + appDTO *dtos.AppDTO, + opts updateAppOptions, +) (database.App, *exceptions.ServiceError) { + logger := s.buildLogger(opts.requestID, appsLocation, "updateApp").With( + "appID", appDTO.ID(), + "appClientName", appDTO.ClientName, + ) + logger.InfoContext(ctx, "Updating base app...") + + usernameColumn, serviceErr := mapUsernameColumn(opts.usernameColumn) + if serviceErr != nil { + logger.ErrorContext(ctx, "Failed to map username column", "serviceError", serviceErr) + return database.App{}, serviceErr } - switch opts.AuthMethod { - case AuthMethodPrivateKeyJwt: - var dbPrms database.CreateCredentialsKeyParams - var jwk utils.JWK - dbPrms, jwk, serviceErr = s.clientCredentialsKey(ctx, clientCredentialsKeyOptions{ - requestID: opts.RequestID, - accountID: accountID, - accountPublicID: opts.AccountPublicID, - expiresIn: s.accountCCExpDays, - usage: database.CredentialsUsageApp, - cryptoSuite: mapAlgorithmToTokenCryptoSuite(opts.Algorithm), - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to generate client credentials key", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } + authProviders, serviceErr := mapUpdateAuthProviders(opts.authProviders, appDTO.AuthProviders) + if serviceErr != nil { + logger.ErrorContext(ctx, "Failed to map auth providers", "serviceError", serviceErr) + return database.App{}, serviceErr + } - var clientKey database.CredentialsKey - clientKey, err = qrs.CreateCredentialsKey(ctx, dbPrms) - if err != nil { - logger.ErrorContext(ctx, "Failed to create client key", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr + derivedDomain, serviceErr := mapDomain(opts.clientURI, opts.domain) + if serviceErr != nil { + logger.ErrorContext(ctx, "Failed to map domain", "serviceError", serviceErr) + return database.App{}, serviceErr + } + + var softwareVersion pgtype.Text + if opts.softwareVersion != "" { + if err := softwareVersion.Scan(opts.softwareVersion); err != nil { + logger.ErrorContext(ctx, "Failed to scan software version", "error", err) + return database.App{}, exceptions.NewInternalServerError() } + } - if err = qrs.CreateAppKey(ctx, database.CreateAppKeyParams{ - AccountID: accountID, - AppID: app.ID, - CredentialsKeyID: clientKey.ID, - }); err != nil { - logger.ErrorContext(ctx, "Failed to create app key", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Created backend app successfully with private key JWT auth method successfully") - return dtos.MapBackendAppWithJWKToDTO(&app, jwk, clientKey.ExpiresAt), nil - case AuthMethodClientSecretPost, AuthMethodClientSecretBasic, AuthMethodClientSecretJWT: - var ccID int32 - var secretID, secret string - var exp time.Time - ccID, secretID, secret, exp, serviceErr = s.clientCredentialsSecret(ctx, qrs, clientCredentialsSecretOptions{ - requestID: opts.RequestID, - accountID: accountID, - expiresIn: s.appCCExpDays, - usage: database.CredentialsUsageApp, - dekFN: s.BuildGetEncAccountDEKfn(ctx, BuildGetEncAccountDEKOptions{ - RequestID: opts.RequestID, - AccountID: accountID, - }), - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to create client credentials secret", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - if err = qrs.CreateAppSecret(ctx, database.CreateAppSecretParams{ - AppID: app.ID, - CredentialsSecretID: ccID, - AccountID: accountID, - }); err != nil { - logger.ErrorContext(ctx, "Failed to create app secret", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Created backend app successfully with client secret auth method successfully") - return dtos.MapBackendAppWithSecretToDTO(&app, secretID, secret, exp), nil - default: - logger.ErrorContext(ctx, "Unsupported auth method", "authMethod", opts.AuthMethod) - serviceErr = exceptions.NewValidationError("Unsupported auth method") - return dtos.AppDTO{}, serviceErr - } -} - -type CreateDeviceAppOptions struct { - RequestID string - AccountPublicID uuid.UUID - AccountVersion int32 - CreationMethod database.CreationMethod - Name string - AllowUserRegistration bool - Domain string - Transport string - UsernameColumn string - ClientURI string - LogoURI string - TOSURI string - PolicyURI string - Contacts []string - SoftwareID string - SoftwareVersion string - BackendDomain string - AssociatedApps []string - Scopes []string - DefaultScopes []string - AuthProviders []string -} - -func (s *Services) CreateDeviceApp( - ctx context.Context, - opts CreateDeviceAppOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "CreateDeviceApp").With( - "accountPublicId", opts.AccountPublicID.String(), - "accountVersion", opts.AccountVersion, - "name", opts.Name, - ) - logger.InfoContext(ctx, "Creating device app...") - - accountID, serviceErr := s.GetAccountIDByPublicIDAndVersion(ctx, GetAccountIDByPublicIDAndVersionOptions{ - RequestID: opts.RequestID, - PublicID: opts.AccountPublicID, - Version: opts.AccountVersion, - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to get account ID by public ID and version", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - name := strings.TrimSpace(opts.Name) - if serviceErr := s.checkForDuplicateApps(ctx, checkForDuplicateAppsOptions{ - requestID: opts.RequestID, - accountID: accountID, - name: name, - softwareID: opts.SoftwareID, - }); serviceErr != nil { - logger.ErrorContext(ctx, "Duplicate app found", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - if len(opts.AssociatedApps) == 0 { - app, err := s.createSingleApp(ctx, createAppOptions{ - accountID: accountID, - accountPublicID: opts.AccountPublicID, - creationMethod: opts.CreationMethod, - appType: database.AppTypeDevice, - name: name, - clientURI: opts.ClientURI, - allowUserRegistration: opts.AllowUserRegistration, - usernameColumn: opts.UsernameColumn, - authMethod: database.AuthMethodNone, - grantTypes: deviceGrantTypes, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - contacts: utils.MapSlice(opts.Contacts, func(t *string) string { - return utils.Lowered(*t) - }), - softwareID: opts.SoftwareID, - softwareVersion: opts.SoftwareVersion, - scopes: opts.Scopes, - defaultScopes: opts.DefaultScopes, - domain: opts.Domain, - transport: mapStandardTransport(opts.Transport), - redirectURIs: make([]string, 0), - responseTypes: make([]database.ResponseType, 0), - authProviders: opts.AuthProviders, - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to create app", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) - } - - logger.InfoContext(ctx, "Created device app successfully") - return dtos.MapDeviceAppToDTO(&app, make([]database.App, 0), opts.BackendDomain), nil - } - - expectedCount := len(opts.AssociatedApps) - relatedApps, err := s.database.FindAppsByClientIDsAndAccountID(ctx, database.FindAppsByClientIDsAndAccountIDParams{ - AccountID: accountID, - Limit: int32(expectedCount), - ClientIds: opts.AssociatedApps, - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to find related apps", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) - } - - foundCount := len(relatedApps) - if foundCount != expectedCount { - logger.WarnContext(ctx, "Not all related apps found", "expectedCount", expectedCount, "foundCount", foundCount) - return dtos.AppDTO{}, exceptions.NewValidationError("Not all related apps found") - } - - for _, ra := range relatedApps { - if ra.AppType != database.AppTypeWeb && ra.AppType != database.AppTypeSpa { - logger.WarnContext(ctx, "Related app is not a web or spa app", "appID", ra.ID) - return dtos.AppDTO{}, exceptions.NewValidationError("Related app must be a web or SPA app") - } - } - - qrs, txn, err := s.database.BeginTx(ctx) - if err != nil { - logger.ErrorContext(ctx, "Failed to start transaction", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) - } - defer func() { - logger.DebugContext(ctx, "Finalizing transaction") - s.database.FinalizeTx(ctx, txn, err, serviceErr) - }() - - app, err := s.createApp(ctx, qrs, createAppOptions{ - accountID: accountID, - accountPublicID: opts.AccountPublicID, - creationMethod: opts.CreationMethod, - appType: database.AppTypeDevice, - name: name, - clientURI: opts.ClientURI, - allowUserRegistration: opts.AllowUserRegistration, - usernameColumn: opts.UsernameColumn, - authMethod: database.AuthMethodNone, - grantTypes: deviceGrantTypes, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - contacts: utils.MapSlice(opts.Contacts, func(t *string) string { - return utils.Lowered(*t) - }), - softwareID: opts.SoftwareID, - softwareVersion: opts.SoftwareVersion, - scopes: opts.Scopes, - defaultScopes: opts.DefaultScopes, - domain: opts.Domain, - transport: mapStandardTransport(opts.Transport), - redirectURIs: make([]string, 0), - responseTypes: make([]database.ResponseType, 0), - authProviders: opts.AuthProviders, - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to create app", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - for _, ra := range relatedApps { - if err = qrs.CreateAppRelatedApp(ctx, database.CreateAppRelatedAppParams{ - AccountID: accountID, - AppID: app.ID, - RelatedAppID: ra.ID, - }); err != nil { - logger.ErrorContext(ctx, "Failed to create app device config", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - } - - logger.InfoContext(ctx, "Created device app successfully with related app") - return dtos.MapDeviceAppToDTO(&app, relatedApps, opts.BackendDomain), nil -} - -func mapServerGrantTypesFromAuthMethod(authMethod string) ([]database.GrantType, *exceptions.ServiceError) { - switch authMethod { - case AuthMethodClientSecretPost, AuthMethodClientSecretBasic: - return []database.GrantType{database.GrantTypeClientCredentials}, nil - case AuthMethodPrivateKeyJwt, AuthMethodClientSecretJWT: - return []database.GrantType{ - database.GrantTypeClientCredentials, - database.GrantTypeUrnIetfParamsOauthGrantTypeJwtBearer, - }, nil - default: - return nil, exceptions.NewValidationError("Unsupported auth method") - } -} - -type CreateServiceAppOptions struct { - RequestID string - AccountPublicID uuid.UUID - CreationMethod database.CreationMethod - Name string - AuthMethod string - UsernameColumn string - AccountVersion int32 - AllowUserRegistration bool - Algorithm string - ClientURI string - LogoURI string - TOSURI string - PolicyURI string - Contacts []string - SoftwareID string - SoftwareVersion string - UsersAuthMethod string - Domain string - Transport string - AllowedDomains []string - Scopes []string - DefaultScopes []string - AuthProviders []string -} - -func (s *Services) CreateServiceApp( - ctx context.Context, - opts CreateServiceAppOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "CreateServiceApp").With( - "accountPublicId", opts.AccountPublicID, - "accountVersion", opts.AccountVersion, - "name", opts.Name, - "authMethod", opts.AuthMethod, - ) - logger.InfoContext(ctx, "Creating service app...") - - authMethod, serviceErr := mapAuthMethod(opts.AuthMethod) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map auth methods", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - grantTypes, serviceErr := mapServerGrantTypesFromAuthMethod(opts.AuthMethod) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map service grant types", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - userAuthMethod, serviceErr := mapAuthMethod(opts.UsersAuthMethod) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map user auth methods", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - if userAuthMethod == database.AuthMethodPrivateKeyJwt && len(opts.AllowedDomains) == 0 { - logger.ErrorContext(ctx, "Allowed domains must be provided for private key JWT auth method") - return dtos.AppDTO{}, exceptions.NewValidationError("Allowed domains must be provided for private key JWT auth method") - } - - userGrantTypes, serviceErr := mapServerGrantTypesFromAuthMethod(opts.UsersAuthMethod) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map user grant types", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - accountID, serviceErr := s.GetAccountIDByPublicIDAndVersion(ctx, GetAccountIDByPublicIDAndVersionOptions{ - RequestID: opts.RequestID, - PublicID: opts.AccountPublicID, - Version: opts.AccountVersion, - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to get account ID by public ID and version", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - name := strings.TrimSpace(opts.Name) - if serviceErr := s.checkForDuplicateApps(ctx, checkForDuplicateAppsOptions{ - requestID: opts.RequestID, - accountID: accountID, - name: name, - softwareID: opts.SoftwareID, - }); serviceErr != nil { - logger.ErrorContext(ctx, "Duplicate app found", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - qrs, txn, err := s.database.BeginTx(ctx) - if err != nil { - logger.ErrorContext(ctx, "Failed to start transaction", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) - } - defer func() { - logger.DebugContext(ctx, "Finalizing transaction") - s.database.FinalizeTx(ctx, txn, err, serviceErr) - }() - - app, err := s.createApp(ctx, qrs, createAppOptions{ - requestID: opts.RequestID, - accountID: accountID, - accountPublicID: opts.AccountPublicID, - creationMethod: opts.CreationMethod, - appType: database.AppTypeService, - name: name, - allowUserRegistration: opts.AllowUserRegistration, - clientURI: opts.ClientURI, - domain: opts.Domain, - transport: mapStandardTransport(opts.Transport), - usernameColumn: opts.UsernameColumn, - authMethod: authMethod, - grantTypes: grantTypes, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - contacts: opts.Contacts, - softwareID: opts.SoftwareID, - softwareVersion: opts.SoftwareVersion, - scopes: opts.Scopes, - defaultScopes: opts.DefaultScopes, - redirectURIs: make([]string, 0), - responseTypes: make([]database.ResponseType, 0), - authProviders: opts.AuthProviders, - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to create app", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - appService, err := qrs.CreateAppServiceConfig(ctx, database.CreateAppServiceConfigParams{ - AccountID: accountID, - AppID: app.ID, - UserAuthMethod: userAuthMethod, - UserGrantTypes: userGrantTypes, - AllowedDomains: utils.ToEmptySlice(opts.AllowedDomains), - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to create app service config", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - switch opts.AuthMethod { - case AuthMethodPrivateKeyJwt: - var dbPrms database.CreateCredentialsKeyParams - var jwk utils.JWK - dbPrms, jwk, serviceErr = s.clientCredentialsKey(ctx, clientCredentialsKeyOptions{ - requestID: opts.RequestID, - accountID: accountID, - accountPublicID: opts.AccountPublicID, - expiresIn: s.accountCCExpDays, - usage: database.CredentialsUsageApp, - cryptoSuite: mapAlgorithmToTokenCryptoSuite(opts.Algorithm), - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to generate client credentials key", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - var clientKey database.CredentialsKey - clientKey, err = qrs.CreateCredentialsKey(ctx, dbPrms) - if err != nil { - logger.ErrorContext(ctx, "Failed to create client key", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - if err = qrs.CreateAppKey(ctx, database.CreateAppKeyParams{ - AccountID: accountID, - AppID: app.ID, - CredentialsKeyID: clientKey.ID, - }); err != nil { - logger.ErrorContext(ctx, "Failed to create app key", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Created service app successfully with private key JWT auth method successfully") - return dtos.MapServiceAppWithJWKToDTO(&app, &appService, jwk, clientKey.ExpiresAt), nil - case AuthMethodClientSecretPost, AuthMethodClientSecretBasic, AuthMethodClientSecretJWT: - var ccID int32 - var secretID, secret string - var exp time.Time - ccID, secretID, secret, exp, serviceErr = s.clientCredentialsSecret(ctx, qrs, clientCredentialsSecretOptions{ - requestID: opts.RequestID, - accountID: accountID, - expiresIn: s.appCCExpDays, - usage: database.CredentialsUsageApp, - dekFN: s.BuildGetEncAccountDEKfn(ctx, BuildGetEncAccountDEKOptions{ - RequestID: opts.RequestID, - AccountID: accountID, - }), - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to create client credentials secret", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - if err = qrs.CreateAppSecret(ctx, database.CreateAppSecretParams{ - AppID: app.ID, - CredentialsSecretID: ccID, - AccountID: accountID, - }); err != nil { - logger.ErrorContext(ctx, "Failed to create app secret", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Created service app successfully with client secret auth method successfully") - return dtos.MapServiceAppWithSecretToDTO(&app, &appService, secretID, secret, exp), nil - default: - logger.ErrorContext(ctx, "Unsupported auth method", "authMethod", opts.AuthMethod) - serviceErr = exceptions.NewValidationError("Unsupported auth method") - return dtos.AppDTO{}, serviceErr - } -} - -func mapMCPTransport(transport string) (database.Transport, *exceptions.ServiceError) { - switch transport { - case transportSTDIO: - return database.TransportStdio, nil - case transportStreamableHTTP: - return database.TransportStreamableHttp, nil - default: - return "", exceptions.NewValidationError("Unsupported transport: " + transport) - } -} - -func mapMCPAuthMethod(transport database.Transport, authMethod string) (database.AuthMethod, *exceptions.ServiceError) { - if transport == database.TransportStdio { - return database.AuthMethodNone, nil - } - - switch authMethod { - case AuthMethodClientSecretPost, AuthMethodClientSecretBasic: - return database.AuthMethodClientSecretPost, nil - case AuthMethodPrivateKeyJwt: - return database.AuthMethodPrivateKeyJwt, nil - default: - return "", exceptions.NewValidationError("Unsupported auth method: " + authMethod) - } -} - -func mapMCPResponseTypes( - transport database.Transport, - responseTypes []string, -) ([]database.ResponseType, *exceptions.ServiceError) { - if transport == database.TransportStdio { - return make([]database.ResponseType, 0), nil - } - if len(responseTypes) == 0 { - return []database.ResponseType{database.ResponseTypeCode, database.ResponseTypeCodeidToken}, nil - } - - rts := make([]database.ResponseType, 0, len(responseTypes)) - for _, rt := range responseTypes { - switch rt { - case responseTypeCode: - rts = append(rts, database.ResponseTypeCode) - case responseTypeCodeIDToken: - rts = append(rts, database.ResponseTypeCodeidToken) - default: - return nil, exceptions.NewValidationError("Unsupported response type: " + rt) - } - } - - return rts, nil -} - -type CreateMCPAppOptions struct { - RequestID string - AccountPublicID uuid.UUID - AccountVersion int32 - CreationMethod database.CreationMethod - Name string - AllowUserRegistration bool - UsernameColumn string - ClientURI string - LogoURI string - TOSURI string - PolicyURI string - Contacts []string - SoftwareID string - SoftwareVersion string - Scopes []string - DefaultScopes []string - Transport string - AuthMethod string - Algorithm string - RedirectURIs []string - ResponseTypes []string - Domain string - AuthProviders []string -} - -func (s *Services) CreateMCPApp( - ctx context.Context, - opts CreateMCPAppOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "CreateMCPApp").With( - "accountPublicID", opts.AccountPublicID, - "name", opts.Name, - ) - logger.InfoContext(ctx, "Creating MCP app...") - - transport, serviceErr := mapMCPTransport(opts.Transport) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map MCP transport", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - authMethod, serviceErr := mapMCPAuthMethod(transport, opts.AuthMethod) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map MCP auth method", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - responseTypes, serviceErr := mapMCPResponseTypes(transport, opts.ResponseTypes) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map response types", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - accountID, serviceErr := s.GetAccountIDByPublicIDAndVersion(ctx, GetAccountIDByPublicIDAndVersionOptions{ - RequestID: opts.RequestID, - PublicID: opts.AccountPublicID, - Version: opts.AccountVersion, - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to get account ID by public ID and version", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - name := strings.TrimSpace(opts.Name) - if serviceErr := s.checkForDuplicateApps(ctx, checkForDuplicateAppsOptions{ - requestID: opts.RequestID, - accountID: accountID, - name: name, - softwareID: opts.SoftwareID, - }); serviceErr != nil { - logger.ErrorContext(ctx, "Duplicate app found", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - if transport == database.TransportStreamableHttp { - if len(opts.RedirectURIs) == 0 { - logger.ErrorContext(ctx, "Callback URIs must be provided for streamable HTTP transport") - return dtos.AppDTO{}, exceptions.NewValidationError("Callback URIs must be provided for streamable HTTP transport") - } - - app, serviceErr := s.createSingleApp(ctx, createAppOptions{ - requestID: opts.RequestID, - accountID: accountID, - accountPublicID: opts.AccountPublicID, - creationMethod: opts.CreationMethod, - appType: database.AppTypeMcp, - name: name, - allowUserRegistration: opts.AllowUserRegistration, - clientURI: opts.ClientURI, - domain: opts.Domain, - transport: transport, - usernameColumn: opts.UsernameColumn, - authMethod: authMethod, - grantTypes: authCodeAppGrantTypes, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - contacts: opts.Contacts, - softwareID: opts.SoftwareID, - softwareVersion: opts.SoftwareVersion, - scopes: opts.Scopes, - defaultScopes: opts.DefaultScopes, - redirectURIs: opts.RedirectURIs, - responseTypes: responseTypes, - authProviders: opts.AuthProviders, - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to create MCP app", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Created MCP app successfully") - return dtos.MapWebNativeSPAMCPAppToDTO(&app), nil - } - - qrs, txn, err := s.database.BeginTx(ctx) - if err != nil { - logger.ErrorContext(ctx, "Failed to start transaction", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) - } - defer func() { - logger.DebugContext(ctx, "Finalizing transaction") - s.database.FinalizeTx(ctx, txn, err, serviceErr) - }() - - app, err := s.createApp(ctx, qrs, createAppOptions{ - requestID: opts.RequestID, - accountID: accountID, - accountPublicID: opts.AccountPublicID, - creationMethod: opts.CreationMethod, - appType: database.AppTypeMcp, - name: name, - allowUserRegistration: opts.AllowUserRegistration, - clientURI: opts.ClientURI, - domain: opts.Domain, - transport: transport, - usernameColumn: opts.UsernameColumn, - authMethod: authMethod, - grantTypes: authCodeAppGrantTypes, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - contacts: opts.Contacts, - softwareID: opts.SoftwareID, - softwareVersion: opts.SoftwareVersion, - scopes: opts.Scopes, - defaultScopes: opts.DefaultScopes, - redirectURIs: make([]string, 0), - responseTypes: responseTypes, - authProviders: opts.AuthProviders, - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to create app with auth code config", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - switch opts.AuthMethod { - case AuthMethodPrivateKeyJwt: - var dbPrms database.CreateCredentialsKeyParams - var jwk utils.JWK - dbPrms, jwk, serviceErr = s.clientCredentialsKey(ctx, clientCredentialsKeyOptions{ - requestID: opts.RequestID, - accountID: accountID, - accountPublicID: opts.AccountPublicID, - expiresIn: s.accountCCExpDays, - usage: database.CredentialsUsageApp, - cryptoSuite: mapAlgorithmToTokenCryptoSuite(opts.Algorithm), - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to generate client credentials key", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - var clientKey database.CredentialsKey - clientKey, err = qrs.CreateCredentialsKey(ctx, dbPrms) - if err != nil { - logger.ErrorContext(ctx, "Failed to create client key", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - if err = qrs.CreateAppKey(ctx, database.CreateAppKeyParams{ - AccountID: accountID, - AppID: app.ID, - CredentialsKeyID: clientKey.ID, - }); err != nil { - logger.ErrorContext(ctx, "Failed to create app key", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Created service app successfully with private key JWT auth method successfully") - return dtos.MapMCPAppWithJWKToDTO(&app, jwk, clientKey.ExpiresAt), nil - case AuthMethodClientSecretPost, AuthMethodClientSecretBasic, AuthMethodClientSecretJWT: - var ccID int32 - var secretID, secret string - var exp time.Time - ccID, secretID, secret, exp, serviceErr = s.clientCredentialsSecret(ctx, qrs, clientCredentialsSecretOptions{ - requestID: opts.RequestID, - accountID: accountID, - expiresIn: s.appCCExpDays, - usage: database.CredentialsUsageApp, - dekFN: s.BuildGetEncAccountDEKfn(ctx, BuildGetEncAccountDEKOptions{ - RequestID: opts.RequestID, - AccountID: accountID, - }), - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to create client credentials secret", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - if err = qrs.CreateAppSecret(ctx, database.CreateAppSecretParams{ - AppID: app.ID, - CredentialsSecretID: ccID, - AccountID: accountID, - }); err != nil { - logger.ErrorContext(ctx, "Failed to create app secret", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Created service app successfully with client secret auth method successfully") - return dtos.MapMCPAppWithSecretToDTO(&app, secretID, secret, exp), nil - default: - logger.ErrorContext(ctx, "Unsupported auth method", "authMethod", opts.AuthMethod) - serviceErr = exceptions.NewValidationError("Unsupported auth method") - return dtos.AppDTO{}, serviceErr - } -} - -func mapUpdateAuthProviders( - authProviders []string, - currentAuthProviders []database.AuthProvider, -) ([]database.AuthProvider, *exceptions.ServiceError) { - if len(authProviders) == 0 { - return currentAuthProviders, nil - } - - validAuthProviders := make([]database.AuthProvider, 0, len(authProviders)) - for _, provider := range authProviders { - dbp := database.AuthProvider(provider) - switch dbp { - case database.AuthProviderLocal, database.AuthProviderApple, database.AuthProviderFacebook, - database.AuthProviderGoogle, database.AuthProviderGithub, database.AuthProviderMicrosoft: - validAuthProviders = append(validAuthProviders, dbp) - default: - return nil, exceptions.NewValidationError("Unsupported auth provider: " + provider) - } - } - - return validAuthProviders, nil -} - -type updateAppOptions struct { - requestID string - usernameColumn string - transport database.Transport - allowUserRegistration bool - domain string - name string - clientURI string - logoURI string - tosURI string - policyURI string - softwareVersion string - contacts []string - redirectURIs []string - responseTypes []database.ResponseType - authProviders []string -} - -func (s *Services) updateApp( - ctx context.Context, - appDTO *dtos.AppDTO, - qrs *database.Queries, - opts updateAppOptions, -) (database.App, error) { - logger := s.buildLogger(opts.requestID, appsLocation, "updateApp").With( - "appID", appDTO.ID(), - "appClientName", appDTO.ClientName, - ) - logger.InfoContext(ctx, "Updating base app...") - - usernameColumn, serviceErr := mapUsernameColumn(opts.usernameColumn) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map username column", "serviceError", serviceErr) - return database.App{}, serviceErr - } - - authProviders, serviceErr := mapUpdateAuthProviders(opts.authProviders, appDTO.AuthProviders) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map auth providers", "serviceError", serviceErr) - return database.App{}, serviceErr - } - - derivedDomain, serviceErr := mapDomain(opts.clientURI, opts.domain) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map domain", "serviceError", serviceErr) - return database.App{}, serviceErr - } - - var softwareVersion pgtype.Text - if opts.softwareVersion != "" { - if err := softwareVersion.Scan(opts.softwareVersion); err != nil { - logger.ErrorContext(ctx, "Failed to scan software version", "error", err) - return database.App{}, err - } - } - - app, err := qrs.UpdateApp(ctx, database.UpdateAppParams{ - ID: appDTO.ID(), - ClientName: opts.name, - UsernameColumn: usernameColumn, - ClientUri: opts.clientURI, - LogoUri: mapEmptyURL(opts.logoURI), - TosUri: mapEmptyURL(opts.tosURI), - PolicyUri: mapEmptyURL(opts.policyURI), - SoftwareVersion: softwareVersion, - Domain: derivedDomain, - Transport: opts.transport, - AllowUserRegistration: opts.allowUserRegistration, - ResponseTypes: opts.responseTypes, - AuthProviders: authProviders, - Contacts: utils.MapSlice(opts.contacts, func(t *string) string { - return utils.Lowered(*t) - }), - RedirectUris: utils.MapSlice(opts.redirectURIs, func(t *string) string { - return utils.ProcessURL(*t) - }), - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to update app", "error", err) - return database.App{}, err - } - - logger.InfoContext(ctx, "Updated base app successfully") - return app, nil -} - -func (s *Services) updateSingleApp( - ctx context.Context, - appDTO *dtos.AppDTO, - opts updateAppOptions, -) (database.App, *exceptions.ServiceError) { - logger := s.buildLogger(opts.requestID, appsLocation, "updateApp").With( - "appID", appDTO.ID(), - "appClientName", appDTO.ClientName, - ) - logger.InfoContext(ctx, "Updating base app...") - - usernameColumn, serviceErr := mapUsernameColumn(opts.usernameColumn) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map username column", "serviceError", serviceErr) - return database.App{}, serviceErr - } - - authProviders, serviceErr := mapUpdateAuthProviders(opts.authProviders, appDTO.AuthProviders) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map auth providers", "serviceError", serviceErr) - return database.App{}, serviceErr - } - - derivedDomain, serviceErr := mapDomain(opts.clientURI, opts.domain) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map domain", "serviceError", serviceErr) - return database.App{}, serviceErr - } - - var softwareVersion pgtype.Text - if opts.softwareVersion != "" { - if err := softwareVersion.Scan(opts.softwareVersion); err != nil { - logger.ErrorContext(ctx, "Failed to scan software version", "error", err) - return database.App{}, exceptions.NewInternalServerError() - } - } - - app, err := s.database.UpdateApp(ctx, database.UpdateAppParams{ - ID: appDTO.ID(), - ClientName: opts.name, - UsernameColumn: usernameColumn, - ClientUri: opts.clientURI, - LogoUri: mapEmptyURL(opts.logoURI), - TosUri: mapEmptyURL(opts.tosURI), - PolicyUri: mapEmptyURL(opts.policyURI), - SoftwareVersion: softwareVersion, - Domain: derivedDomain, - Transport: opts.transport, - AllowUserRegistration: opts.allowUserRegistration, - ResponseTypes: opts.responseTypes, - AuthProviders: authProviders, - Contacts: utils.MapSlice(opts.contacts, func(t *string) string { - return utils.Lowered(*t) - }), - RedirectUris: utils.MapSlice(opts.redirectURIs, func(t *string) string { - return utils.ProcessURL(*t) - }), - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to update app", "error", err) - return database.App{}, exceptions.FromDBError(err) - } - - logger.InfoContext(ctx, "Updated base app successfully") - return app, nil -} - -func mapStandardTransportUpdate( - currentTransport database.Transport, - transport string, -) database.Transport { - if transport == transportHTTP { - return database.TransportHttp - } - - return currentTransport -} - -func mapResponseTypesUpdate( - responseTypes []string, - currentResponseTypes []database.ResponseType, -) ([]database.ResponseType, *exceptions.ServiceError) { - if len(responseTypes) == 0 { - return currentResponseTypes, nil - } - - var dbResponseTypes []database.ResponseType - for _, rt := range responseTypes { - switch utils.Lowered(rt) { - case ResponseTypeCode: - dbResponseTypes = append(dbResponseTypes, database.ResponseTypeCode) - case ResponseTypeCodeIdToken: - dbResponseTypes = append(dbResponseTypes, database.ResponseTypeCodeidToken) - default: - return nil, exceptions.NewValidationError("invalid response type: " + rt) - } - } - - return dbResponseTypes, nil -} - -type UpdateWebSPANativeAppOptions struct { - RequestID string - AccountID int32 - UsernameColumn string - Name string - Domain string - Transport string - AllowUserRegistration bool - ClientURI string - LogoURI string - TOSURI string - PolicyURI string - SoftwareID string - SoftwareVersion string - Contacts []string - RedirectURIs []string - ResponseTypes []string - AuthProviders []string -} - -func (s *Services) UpdateWebSPANativeApp( - ctx context.Context, - appDTO *dtos.AppDTO, - opts UpdateWebSPANativeAppOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "UpdateWebSPANativeApp").With( - "appID", appDTO.ID(), - "appClientName", appDTO.ClientName, - "appType", appDTO.AppType, - ) - logger.InfoContext(ctx, "Updating web or SPA or native app...") - - responseTypes, serviceErr := mapResponseTypesUpdate(opts.ResponseTypes, appDTO.ResponseTypes) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map response types", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - name := strings.TrimSpace(opts.Name) - if appDTO.ClientName != name { - if serviceErr := s.checkForDuplicateApps(ctx, checkForDuplicateAppsOptions{ - requestID: opts.RequestID, - accountID: opts.AccountID, - name: name, - softwareID: opts.SoftwareID, - }); serviceErr != nil { - logger.ErrorContext(ctx, "Duplicate app found", "serviceError", serviceErr) - } - } - - // Derive domain from client URI when not provided - domain, serviceErr := mapDomain(opts.ClientURI, opts.Domain) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map domain", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - app, serviceErr := s.updateSingleApp(ctx, appDTO, updateAppOptions{ - requestID: opts.RequestID, - usernameColumn: opts.UsernameColumn, - transport: mapStandardTransportUpdate(appDTO.Transport, opts.Transport), - domain: domain, - name: name, - allowUserRegistration: opts.AllowUserRegistration, - clientURI: opts.ClientURI, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - softwareVersion: opts.SoftwareVersion, - contacts: opts.Contacts, - redirectURIs: opts.RedirectURIs, - responseTypes: responseTypes, - authProviders: opts.AuthProviders, - }) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to update app", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Updated web or SPA or native app successfully") - return dtos.MapWebNativeSPAMCPAppToDTO(&app), nil -} - -type UpdateBackendAppOptions struct { - RequestID string - AccountID int32 - UsernameColumn string - Name string - Domain string - Transport string - AllowUserRegistration bool - ClientURI string - LogoURI string - TOSURI string - PolicyURI string - SoftwareID string - SoftwareVersion string - Contacts []string - AuthProviders []string -} - -func (s *Services) UpdateBackendApp( - ctx context.Context, - appDTO *dtos.AppDTO, - opts UpdateBackendAppOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "UpdateBackendApp").With( - "appID", appDTO.ID(), - "appClientName", appDTO.ClientName, - ) - logger.InfoContext(ctx, "Updating backend app...") - - name := strings.TrimSpace(opts.Name) - if appDTO.ClientName != name { - if serviceErr := s.checkForDuplicateApps(ctx, checkForDuplicateAppsOptions{ - requestID: opts.RequestID, - accountID: opts.AccountID, - name: name, - softwareID: opts.SoftwareID, - }); serviceErr != nil { - logger.ErrorContext(ctx, "Duplicate app found", "serviceError", serviceErr) - } + redirectURIs := opts.redirectURIs + if redirectURIs == nil { + redirectURIs = appDTO.RedirectURIs } - - var serviceErr *exceptions.ServiceError - qrs, txn, err := s.database.BeginTx(ctx) - if err != nil { - logger.ErrorContext(ctx, "Failed to start transaction", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) + if redirectURIs == nil { + redirectURIs = []string{} } - defer func() { - logger.DebugContext(ctx, "Finalizing transaction") - s.database.FinalizeTx(ctx, txn, err, serviceErr) - }() - // Ensure we always persist a valid domain and transport - app, err := s.updateApp(ctx, appDTO, qrs, updateAppOptions{ - requestID: opts.RequestID, - usernameColumn: opts.UsernameColumn, - domain: opts.Domain, - transport: mapStandardTransportUpdate(appDTO.Transport, opts.Transport), - allowUserRegistration: opts.AllowUserRegistration, - name: name, - clientURI: opts.ClientURI, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - softwareVersion: opts.SoftwareVersion, - contacts: opts.Contacts, - redirectURIs: make([]string, 0), - responseTypes: make([]database.ResponseType, 0), - authProviders: opts.AuthProviders, + app, err := s.database.UpdateApp(ctx, database.UpdateAppParams{ + ID: appDTO.ID(), + ClientName: opts.name, + UsernameColumn: usernameColumn, + ClientUri: opts.clientURI, + LogoUri: mapEmptyURL(opts.logoURI), + TosUri: mapEmptyURL(opts.tosURI), + PolicyUri: mapEmptyURL(opts.policyURI), + SoftwareVersion: softwareVersion, + Domain: derivedDomain, + AllowUserRegistration: opts.allowUserRegistration, + ResponseTypes: opts.responseTypes, + AuthProviders: authProviders, + Contacts: utils.MapSlice(opts.contacts, func(t *string) string { + return utils.Lowered(*t) + }), + RedirectUris: utils.MapSlice(redirectURIs, func(t *string) string { + return utils.ProcessURL(*t) + }), }) if err != nil { - logger.ErrorContext(ctx, "Failed to update base app", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr + logger.ErrorContext(ctx, "Failed to update app", "error", err) + return database.App{}, exceptions.FromDBError(err) } - // Domain is already included in UpdateApp above; nothing else to update for backend - logger.InfoContext(ctx, "Updated backend app successfully") - return dtos.MapBackendAppToDTO(&app), nil -} - -type UpdateDeviceAppOptions struct { - RequestID string - AccountID int32 - UsernameColumn string - Name string - Domain string - Transport string - AllowUserRegistration bool - ClientURI string - LogoURI string - TOSURI string - PolicyURI string - SoftwareID string - SoftwareVersion string - Contacts []string - BackendDomain string - AssociatedApps []string - AuthProviders []string + logger.InfoContext(ctx, "Updated base app successfully") + return app, nil } -func (s *Services) UpdateDeviceApp( - ctx context.Context, - appDTO *dtos.AppDTO, - opts UpdateDeviceAppOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "UpdateDeviceApp").With( - "appID", appDTO.ID(), - "appClientName", appDTO.ClientName, - ) - logger.InfoContext(ctx, "Updating device app...") - - name := strings.TrimSpace(opts.Name) - if appDTO.ClientName != name { - if serviceErr := s.checkForDuplicateApps(ctx, checkForDuplicateAppsOptions{ - requestID: opts.RequestID, - accountID: opts.AccountID, - name: name, - softwareID: opts.SoftwareID, - }); serviceErr != nil { - logger.ErrorContext(ctx, "Duplicate app found", "serviceError", serviceErr) - } - } - - var serviceErr *exceptions.ServiceError - relatedApps, err := s.database.FindRelatedAppsByAppID(ctx, appDTO.ID()) - if err != nil { - logger.ErrorContext(ctx, "Failed to find related apps", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - toDeleteIDs := make([]int32, 0) - associatedAppSet := utils.SliceToHashSet(opts.AssociatedApps) - for _, ra := range relatedApps { - if !associatedAppSet.Contains(ra.ClientID) { - toDeleteIDs = append(toDeleteIDs, ra.ID) - } +func mapResponseTypesUpdate( + responseTypes []string, + currentResponseTypes []database.ResponseType, +) ([]database.ResponseType, *exceptions.ServiceError) { + if len(responseTypes) == 0 { + return currentResponseTypes, nil } - toAddClientIDs := make([]string, 0) - relatedAppsSet := utils.MapSliceToHashSet(relatedApps, func(ra *database.App) string { - return ra.ClientID - }) - for _, clientID := range opts.AssociatedApps { - if !relatedAppsSet.Contains(clientID) { - toAddClientIDs = append(toAddClientIDs, clientID) + var dbResponseTypes []database.ResponseType + for _, rt := range responseTypes { + switch utils.Lowered(rt) { + case ResponseTypeCode: + dbResponseTypes = append(dbResponseTypes, database.ResponseTypeCode) + case ResponseTypeIdToken: + dbResponseTypes = append(dbResponseTypes, database.ResponseTypeIDToken) + case ResponseTypeCodeIdToken: + dbResponseTypes = append(dbResponseTypes, database.ResponseTypeCodeidToken) + default: + return nil, exceptions.NewValidationError("invalid response type: " + rt) } } - toAddApps := make([]database.App, 0) - if len(toAddClientIDs) > 0 { - toAddApps, err = s.database.FindAppsByClientIDsAndAccountID(ctx, database.FindAppsByClientIDsAndAccountIDParams{ - AccountID: opts.AccountID, - Limit: int32(len(toAddClientIDs)), - ClientIds: toAddClientIDs, - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to find related apps to add", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } + return dbResponseTypes, nil +} - if len(toAddApps) != len(toAddClientIDs) { - logger.WarnContext(ctx, "Not all related apps found for adding", "expectedCount", len(toAddClientIDs), "foundCount", len(toAddApps)) - return dtos.AppDTO{}, exceptions.NewValidationError("Not all related apps found for adding") - } +func validateAppGrantResponseTypes(grantTypes []database.GrantType, responseTypes []database.ResponseType) *exceptions.ServiceError { + hasCodeGrant := slices.Contains(grantTypes, database.GrantTypeAuthorizationCode) + hasImplicitGrant := slices.Contains(grantTypes, database.GrantTypeImplicit) - for _, ra := range toAddApps { - if ra.AppType != database.AppTypeWeb && ra.AppType != database.AppTypeSpa { - logger.WarnContext(ctx, "Related app is not a web or spa app", "appID", ra.ID) - return dtos.AppDTO{}, exceptions.NewValidationError("Related app must be a web or SPA app") - } + if !hasCodeGrant && !hasImplicitGrant { + if len(responseTypes) > 0 { + return exceptions.NewValidationError("response types are not supported without authorization_code or implicit grant") } + return nil } - qrs, txn, err := s.database.BeginTx(ctx) - if err != nil { - logger.ErrorContext(ctx, "Failed to start transaction", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) - } - defer func() { - logger.DebugContext(ctx, "Finalizing transaction") - s.database.FinalizeTx(ctx, txn, err, serviceErr) - }() - - app, err := s.updateApp(ctx, appDTO, qrs, updateAppOptions{ - requestID: opts.RequestID, - usernameColumn: opts.UsernameColumn, - domain: opts.Domain, - transport: mapStandardTransportUpdate(appDTO.Transport, opts.Transport), - allowUserRegistration: opts.AllowUserRegistration, - name: name, - clientURI: opts.ClientURI, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - softwareVersion: opts.SoftwareVersion, - contacts: opts.Contacts, - redirectURIs: make([]string, 0), - responseTypes: make([]database.ResponseType, 0), - authProviders: opts.AuthProviders, + hasCodeResponse := slices.ContainsFunc(responseTypes, func(rt database.ResponseType) bool { + return rt == database.ResponseTypeCode || rt == database.ResponseTypeCodeidToken }) - if err != nil { - logger.ErrorContext(ctx, "Failed to update base app", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - if len(toDeleteIDs) > 0 { - if err = qrs.DeleteAppRelatedAppsByAppIDAndRelatedAppIDs( - ctx, - database.DeleteAppRelatedAppsByAppIDAndRelatedAppIDsParams{ - AppID: app.ID, - RelatedAppIds: toDeleteIDs, - }, - ); err != nil { - logger.ErrorContext(ctx, "Failed to delete related apps", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - } - if len(toAddApps) > 0 { - for _, ra := range toAddApps { - if err = qrs.CreateAppRelatedApp(ctx, database.CreateAppRelatedAppParams{ - AccountID: opts.AccountID, - AppID: app.ID, - RelatedAppID: ra.ID, - }); err != nil { - logger.ErrorContext(ctx, "Failed to create app device config", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - } - } - if len(toDeleteIDs) == 0 && len(toAddClientIDs) == 0 { - logger.InfoContext(ctx, "Updated device app successfully") - return dtos.MapDeviceAppToDTO(&app, relatedApps, opts.BackendDomain), nil - } - - relatedApps, err = qrs.FindRelatedAppsByAppID(ctx, app.ID) - if err != nil { - logger.ErrorContext(ctx, "Failed to find related apps after update", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr - } - - logger.InfoContext(ctx, "Updated device app successfully") - return dtos.MapDeviceAppToDTO(&app, relatedApps, opts.BackendDomain), nil -} - -type UpdateServiceAppOptions struct { - RequestID string - AccountID int32 - Name string - UsernameColumn string - Domain string - Transport string - AllowUserRegistration bool - ClientURI string - LogoURI string - TOSURI string - PolicyURI string - SoftwareID string - SoftwareVersion string - Contacts []string - AllowedDomains []string - AuthProviders []string -} - -func (s *Services) UpdateServiceApp( - ctx context.Context, - appDTO *dtos.AppDTO, - opts UpdateServiceAppOptions, -) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "UpdateServiceApp").With( - "appID", appDTO.ID(), - "appClientName", appDTO.ClientName, - ) - logger.InfoContext(ctx, "Updating service app...") - - name := strings.TrimSpace(opts.Name) - if appDTO.ClientName != name { - if serviceErr := s.checkForDuplicateApps(ctx, checkForDuplicateAppsOptions{ - requestID: opts.RequestID, - accountID: opts.AccountID, - name: name, - softwareID: opts.SoftwareID, - }); serviceErr != nil { - logger.ErrorContext(ctx, "Duplicate app found", "serviceError", serviceErr) - } + if hasCodeResponse && !hasCodeGrant { + return exceptions.NewValidationError("code responses require authorization_code") } - - var serviceErr *exceptions.ServiceError - qrs, txn, err := s.database.BeginTx(ctx) - if err != nil { - logger.ErrorContext(ctx, "Failed to start transaction", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) + if hasCodeGrant && !hasCodeResponse { + return exceptions.NewValidationError("authorization_code requires a code response") } - defer func() { - logger.DebugContext(ctx, "Finalizing transaction") - s.database.FinalizeTx(ctx, txn, err, serviceErr) - }() - app, err := s.updateApp(ctx, appDTO, qrs, updateAppOptions{ - requestID: opts.RequestID, - usernameColumn: opts.UsernameColumn, // Service apps always use email - transport: mapStandardTransportUpdate(appDTO.Transport, opts.Transport), - allowUserRegistration: opts.AllowUserRegistration, - domain: opts.Domain, - name: name, - clientURI: opts.ClientURI, - logoURI: opts.LogoURI, - tosURI: opts.TOSURI, - policyURI: opts.PolicyURI, - softwareVersion: opts.SoftwareVersion, - contacts: opts.Contacts, - redirectURIs: make([]string, 0), - responseTypes: make([]database.ResponseType, 0), - authProviders: opts.AuthProviders, + hasImplicitResponse := slices.ContainsFunc(responseTypes, func(rt database.ResponseType) bool { + return rt == database.ResponseTypeIDToken || rt == database.ResponseTypeCodeidToken }) - if err != nil { - logger.ErrorContext(ctx, "Failed to update base app", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr + if hasImplicitResponse && !hasImplicitGrant { + return exceptions.NewValidationError("id_token responses require implicit") } - - serviceConfig, err := qrs.UpdateAppServiceConfig(ctx, database.UpdateAppServiceConfigParams{ - AccountID: app.AccountID, - AppID: app.ID, - AllowedDomains: utils.ToEmptySlice(opts.AllowedDomains), - }) - if err != nil { - logger.ErrorContext(ctx, "Failed to update app service config", "error", err) - serviceErr = exceptions.FromDBError(err) - return dtos.AppDTO{}, serviceErr + if hasImplicitGrant && !hasImplicitResponse { + return exceptions.NewValidationError("implicit requires an id_token response") } - logger.InfoContext(ctx, "Updated service app successfully") - return dtos.MapServiceAppToDTO(&app, &serviceConfig), nil + return nil } -type UpdateMCPAppOptions struct { +type UpdateWebNativeAppOptions struct { RequestID string AccountID int32 UsernameColumn string @@ -2649,16 +1392,38 @@ type UpdateMCPAppOptions struct { AuthProviders []string } -func (s *Services) UpdateMCPApp( +// TODO: add related apps +func (s *Services) UpdateWebNativeApp( ctx context.Context, appDTO *dtos.AppDTO, - opts UpdateMCPAppOptions, + opts UpdateWebNativeAppOptions, ) (dtos.AppDTO, *exceptions.ServiceError) { - logger := s.buildLogger(opts.RequestID, appsLocation, "UpdateMCPApp").With( + logger := s.buildLogger(opts.RequestID, appsLocation, "UpdateWebNativeApp").With( "appID", appDTO.ID(), "appClientName", appDTO.ClientName, + "appType", appDTO.AppType, ) - logger.InfoContext(ctx, "Updating MCP app...") + logger.InfoContext(ctx, "Updating web or native app...") + + responseTypes, serviceErr := mapResponseTypesUpdate(opts.ResponseTypes, appDTO.ResponseTypes) + if serviceErr != nil { + logger.ErrorContext(ctx, "Failed to map response types", "serviceError", serviceErr) + return dtos.AppDTO{}, serviceErr + } + if serviceErr := validateAppGrantResponseTypes(appDTO.GrantTypes, responseTypes); serviceErr != nil { + return dtos.AppDTO{}, serviceErr + } + redirectURIs := opts.RedirectURIs + if redirectURIs == nil { + redirectURIs = appDTO.RedirectURIs + } + if redirectURIs == nil { + redirectURIs = []string{} + } + if len(redirectURIs) == 0 && (slices.Contains(appDTO.GrantTypes, database.GrantTypeAuthorizationCode) || + slices.Contains(appDTO.GrantTypes, database.GrantTypeImplicit)) { + return dtos.AppDTO{}, exceptions.NewValidationError("redirect URIs are required for authorization grants") + } name := strings.TrimSpace(opts.Name) if appDTO.ClientName != name { @@ -2672,31 +1437,17 @@ func (s *Services) UpdateMCPApp( } } - responseTypes, serviceErr := mapMCPResponseTypes(appDTO.Transport, opts.ResponseTypes) - if serviceErr != nil { - logger.ErrorContext(ctx, "Failed to map response types", "serviceError", serviceErr) - return dtos.AppDTO{}, serviceErr - } - - if appDTO.Transport == database.TransportStreamableHttp { - if len(opts.RedirectURIs) == 0 { - logger.ErrorContext(ctx, "Callback URIs must be provided for streamable HTTP transport") - return dtos.AppDTO{}, exceptions.NewValidationError("Callback URIs must be provided for streamable HTTP transport") - } - } - // Derive domain from client URI when not provided - derivedDomain, serviceErr := mapDomain(opts.ClientURI, opts.Domain) + domain, serviceErr := mapDomain(opts.ClientURI, opts.Domain) if serviceErr != nil { logger.ErrorContext(ctx, "Failed to map domain", "serviceError", serviceErr) return dtos.AppDTO{}, serviceErr } - app, err := s.updateSingleApp(ctx, appDTO, updateAppOptions{ + app, serviceErr := s.updateSingleApp(ctx, appDTO, updateAppOptions{ requestID: opts.RequestID, usernameColumn: opts.UsernameColumn, - transport: appDTO.Transport, - domain: derivedDomain, + domain: domain, name: name, allowUserRegistration: opts.AllowUserRegistration, clientURI: opts.ClientURI, @@ -2705,17 +1456,17 @@ func (s *Services) UpdateMCPApp( policyURI: opts.PolicyURI, softwareVersion: opts.SoftwareVersion, contacts: opts.Contacts, - redirectURIs: utils.ToEmptySlice(opts.RedirectURIs), + redirectURIs: redirectURIs, responseTypes: responseTypes, authProviders: opts.AuthProviders, }) - if err != nil { - logger.ErrorContext(ctx, "Failed to update MCP app", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) + if serviceErr != nil { + logger.ErrorContext(ctx, "Failed to update app", "serviceError", serviceErr) + return dtos.AppDTO{}, serviceErr } - logger.InfoContext(ctx, "Updated MCP app successfully") - return dtos.MapWebNativeSPAMCPAppToDTO(&app), nil + logger.InfoContext(ctx, "Updated web or native app successfully") + return dtos.MapAppToDTO(&app), nil } type GetAppWithRelatedConfigsOptions struct { @@ -2745,27 +1496,9 @@ func (s *Services) GetAppWithRelatedConfigs( } switch app.AppType { - case database.AppTypeWeb, database.AppTypeSpa, database.AppTypeNative: - logger.InfoContext(ctx, "Returning app DTO", "appType", app.AppType) - return dtos.MapWebNativeSPAMCPAppToDTO(&app), nil - case database.AppTypeBackend: - return dtos.MapBackendAppToDTO(&app), nil - case database.AppTypeDevice: - relatedApps, err := s.database.FindRelatedAppsByAppID(ctx, app.ID) - if err != nil { - logger.ErrorContext(ctx, "Failed to find related apps", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) - } - logger.InfoContext(ctx, "Returning app DTO", "appType", app.AppType) - return dtos.MapDeviceAppToDTO(&app, relatedApps, opts.BackendDomain), nil - case database.AppTypeService: - serviceConfig, err := s.database.FindAppServiceConfig(ctx, app.ID) - if err != nil { - logger.ErrorContext(ctx, "Failed to find app service config", "error", err) - return dtos.AppDTO{}, exceptions.FromDBError(err) - } + case database.AppTypeWeb, database.AppTypeNative: logger.InfoContext(ctx, "Returning app DTO", "appType", app.AppType) - return dtos.MapServiceAppToDTO(&app, &serviceConfig), nil + return dtos.MapAppToDTO(&app), nil default: logger.ErrorContext(ctx, "Invalid app type", "appType", app.AppType) return dtos.AppDTO{}, exceptions.NewInternalServerError() @@ -2896,9 +1629,9 @@ func (s *Services) ListAppCredentialsSecretsOrKeys( return nil, 0, serviceErr } switch appDTO.AppType { - case database.AppTypeSpa, database.AppTypeNative, database.AppTypeDevice: + case database.AppTypeNative: return nil, 0, exceptions.NewConflictError("App type does not support secrets or keys") - case database.AppTypeBackend, database.AppTypeService, database.AppTypeWeb: + case database.AppTypeWeb: if appDTO.TokenEndpointAuthMethod == database.AuthMethodPrivateKeyJwt { return s.listAppKeys(ctx, listAppKeysOptions{ requestID: opts.RequestID, @@ -3031,9 +1764,9 @@ func (s *Services) GetAppCredentialsSecretOrKey( } switch appDTO.AppType { - case database.AppTypeSpa, database.AppTypeNative, database.AppTypeDevice: + case database.AppTypeNative: return dtos.ClientCredentialsSecretDTO{}, exceptions.NewConflictError("App type does not support secrets or keys") - case database.AppTypeBackend, database.AppTypeService, database.AppTypeWeb: + case database.AppTypeWeb: if appDTO.TokenEndpointAuthMethod == database.AuthMethodPrivateKeyJwt { return s.getAppKeyByID(ctx, getAppKeyByIDOptions{ requestID: opts.RequestID, @@ -3162,9 +1895,9 @@ func (s *Services) RevokeAppCredentialsSecretOrKey( } switch appDTO.AppType { - case database.AppTypeSpa, database.AppTypeNative, database.AppTypeDevice: + case database.AppTypeNative: return dtos.ClientCredentialsSecretDTO{}, exceptions.NewConflictError("App type does not support secrets or keys") - case database.AppTypeBackend, database.AppTypeService, database.AppTypeWeb: + case database.AppTypeWeb: if appDTO.TokenEndpointAuthMethod == database.AuthMethodPrivateKeyJwt { return s.revokeAppKey(ctx, revokeAppKeyOptions{ requestID: opts.RequestID, @@ -3345,9 +2078,9 @@ func (s *Services) RotateAppCredentialsSecretOrKey( } switch appDTO.AppType { - case database.AppTypeSpa, database.AppTypeNative, database.AppTypeDevice: + case database.AppTypeNative: return dtos.ClientCredentialsSecretDTO{}, exceptions.NewConflictError("App type does not support secrets or keys") - case database.AppTypeBackend, database.AppTypeService, database.AppTypeWeb: + case database.AppTypeWeb: if appDTO.TokenEndpointAuthMethod == database.AuthMethodPrivateKeyJwt { return s.rotateAppKey(ctx, rotateAppKeyOptions{ requestID: opts.RequestID, diff --git a/idp/internal/services/dtos/account_credentials.go b/idp/internal/services/dtos/account_credentials.go index 08392c6..f011535 100644 --- a/idp/internal/services/dtos/account_credentials.go +++ b/idp/internal/services/dtos/account_credentials.go @@ -26,7 +26,6 @@ type AccountCredentialsDTO struct { Domain string `json:"domain"` Scopes []database.AccountCredentialsScope `json:"scopes"` TokenEndpointAuthMethod database.AuthMethod `json:"token_endpoint_auth_method"` - Transport database.Transport `json:"transport"` CreationMethod database.CreationMethod `json:"creation_method"` ClientURI string `json:"client_uri"` RedirectURIs []string `json:"redirect_uris"` @@ -150,7 +149,6 @@ func MapAccountCredentialsToDTO( SoftwareVersion: accountCredential.SoftwareVersion.String, Contacts: contacts, CreationMethod: accountCredential.CreationMethod, - Transport: accountCredential.Transport, TokenEndpointAuthMethod: accountCredential.TokenEndpointAuthMethod, accountId: accountCredential.AccountID, JWKsURI: accountCredential.JwksUri.String, @@ -209,7 +207,6 @@ func MapAccountCredentialsToDTOWithJWK( SoftwareVersion: accountCredential.SoftwareVersion.String, Contacts: contacts, CreationMethod: accountCredential.CreationMethod, - Transport: accountCredential.Transport, TokenEndpointAuthMethod: accountCredential.TokenEndpointAuthMethod, accountId: accountCredential.AccountID, ClientID: accountCredential.ClientID, @@ -275,7 +272,6 @@ func MapAccountCredentialsToDTOWithSecret( SoftwareVersion: accountCredential.SoftwareVersion.String, Contacts: contacts, CreationMethod: accountCredential.CreationMethod, - Transport: accountCredential.Transport, TokenEndpointAuthMethod: accountCredential.TokenEndpointAuthMethod, accountId: accountCredential.AccountID, ClientID: accountCredential.ClientID, diff --git a/idp/internal/services/dtos/app.go b/idp/internal/services/dtos/app.go index 6adf43f..36aac22 100644 --- a/idp/internal/services/dtos/app.go +++ b/idp/internal/services/dtos/app.go @@ -11,31 +11,10 @@ import ( "fmt" "time" - "github.com/tugascript/devlogs/idp/internal/controllers/paths" "github.com/tugascript/devlogs/idp/internal/providers/database" "github.com/tugascript/devlogs/idp/internal/utils" ) -type RelatedAppDTO struct { - AppType database.AppType `json:"app_type"` - ClientName string `json:"client_name"` - ClientID string `json:"client_id"` - Links LinksSelfDTO `json:"links"` -} - -func newRelatedAppDTO( - app *database.App, - backendDomain string, - route string, -) RelatedAppDTO { - return RelatedAppDTO{ - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Links: NewLinksSelfDTO(backendDomain, route), - } -} - type AppDTO struct { Registration *ClientRegistrationDTO `json:"-"` id int32 @@ -46,7 +25,6 @@ type AppDTO struct { ClientName string `json:"client_name"` ClientID string `json:"client_id"` Domain string `json:"domain"` - Transport database.Transport `json:"transport"` CreationMethod database.CreationMethod `json:"creation_method"` ClientURI string `json:"client_uri,omitempty"` @@ -75,11 +53,6 @@ type AppDTO struct { ClientSecretJWK utils.JWK `json:"client_secret_jwk,omitempty"` ClientSecretExp int64 `json:"client_secret_exp,omitempty"` - RelatedApps []RelatedAppDTO `json:"related_apps,omitempty"` - - UsersAuthMethod database.AuthMethod `json:"users_auth_method,omitempty"` - UsersGrantTypes []database.GrantType `json:"users_auth_providers,omitempty"` - AllowedDomains []string `json:"allowed_domains,omitempty"` } func (a *AppDTO) ID() int32 { @@ -135,39 +108,6 @@ func MapAppToDTO(app *database.App) AppDTO { ClientName: app.ClientName, ClientID: app.ClientID, Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.Scopes, app.CustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - RedirectURIs: app.RedirectUris, - ResponseTypes: app.ResponseTypes, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - } -} - -func MapWebNativeSPAMCPAppToDTO(app *database.App) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, CreationMethod: app.CreationMethod, ClientURI: app.ClientUri, LogoURI: app.LogoUri.String, @@ -204,7 +144,6 @@ func MapWebAppWithSecretToDTO( ClientName: app.ClientName, ClientID: app.ClientID, Domain: app.Domain, - Transport: app.Transport, CreationMethod: app.CreationMethod, ClientURI: app.ClientUri, LogoURI: app.LogoUri.String, @@ -239,7 +178,6 @@ func MapWebAppWithJWKToDTO(app *database.App, jwk utils.JWK, exp time.Time) AppD ClientName: app.ClientName, ClientID: app.ClientID, Domain: app.Domain, - Transport: app.Transport, CreationMethod: app.CreationMethod, ClientURI: app.ClientUri, LogoURI: app.LogoUri.String, @@ -264,321 +202,3 @@ func MapWebAppWithJWKToDTO(app *database.App, jwk utils.JWK, exp time.Time) AppD ClientSecretExp: exp.Unix(), } } - -func MapBackendAppWithJWKToDTO(app *database.App, jwk utils.JWK, exp time.Time) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - ClientSecretID: jwk.GetKeyID(), - ClientSecretJWK: jwk, - ClientSecretExp: exp.Unix(), - } -} - -func MapBackendAppWithSecretToDTO(app *database.App, secretID string, secret string, expiresAt time.Time) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - ClientSecretID: secretID, - ClientSecret: fmt.Sprintf("%s.%s", secretID, secret), - ClientSecretExp: expiresAt.Unix(), - } -} - -func MapDeviceAppToDTO(app *database.App, relatedApps []database.App, backendDomain string) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientID: app.ClientID, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.Scopes, app.CustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - RelatedApps: utils.MapSlice(relatedApps, func(ra *database.App) RelatedAppDTO { - return newRelatedAppDTO(ra, backendDomain, paths.AppsBase) - }), - } -} - -func MapServiceAppWithJWKToDTO( - app *database.App, - serviceCfg *database.AppServiceConfig, - jwk utils.JWK, - exp time.Time, -) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.Scopes, app.CustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - ClientSecretID: jwk.GetKeyID(), - ClientSecretJWK: jwk, - ClientSecretExp: exp.Unix(), - UsersAuthMethod: serviceCfg.UserAuthMethod, - UsersGrantTypes: serviceCfg.UserGrantTypes, - AllowedDomains: serviceCfg.AllowedDomains, - } -} - -func MapServiceAppWithSecretToDTO( - app *database.App, - serviceCfg *database.AppServiceConfig, - secretID string, - secret string, - expiresAt time.Time, -) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.Scopes, app.CustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - ClientSecretID: secretID, - ClientSecret: fmt.Sprintf("%s.%s", secretID, secret), - ClientSecretExp: expiresAt.Unix(), - UsersAuthMethod: serviceCfg.UserAuthMethod, - UsersGrantTypes: serviceCfg.UserGrantTypes, - AllowedDomains: serviceCfg.AllowedDomains, - } -} - -func MapBackendAppToDTO(app *database.App) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - } -} - -func MapServiceAppToDTO( - app *database.App, - serviceCfg *database.AppServiceConfig, -) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.Scopes, app.CustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - AllowedDomains: serviceCfg.AllowedDomains, - UsersAuthMethod: serviceCfg.UserAuthMethod, - UsersGrantTypes: serviceCfg.UserGrantTypes, - } -} - -func MapMCPAppWithJWKToDTO(app *database.App, jwk utils.JWK, exp time.Time) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.Scopes, app.CustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - ClientSecretID: jwk.GetKeyID(), - ClientSecretExp: exp.Unix(), - } -} - -func MapMCPAppWithSecretToDTO( - app *database.App, - secretID string, - secret string, - expiresAt time.Time, -) AppDTO { - return AppDTO{ - id: app.ID, - accountID: app.AccountID, - version: app.Version, - AppType: app.AppType, - ClientName: app.ClientName, - ClientID: app.ClientID, - Domain: app.Domain, - Transport: app.Transport, - CreationMethod: app.CreationMethod, - ClientURI: app.ClientUri, - LogoURI: app.LogoUri.String, - TosURI: app.TosUri.String, - PolicyURI: app.PolicyUri.String, - SoftwareID: app.SoftwareID.String, - SoftwareVersion: app.SoftwareVersion.String, - TokenEndpointAuthMethod: app.TokenEndpointAuthMethod, - GrantTypes: app.GrantTypes, - DefaultScopes: mapScopes(app.DefaultScopes, app.DefaultCustomScopes), - Scopes: mapScopes(app.Scopes, app.CustomScopes), - UsernameColumn: app.UsernameColumn, - AuthProviders: app.AuthProviders, - AccessTokenTTL: app.AccessTokenTtl, - IDTokenTTL: app.IDTokenTtl.Int32, - RefreshTokenIdleTTL: app.RefreshTokenIdleTtl.Int32, - RefreshTokenTTL: app.RefreshTokenTtl.Int32, - ClientSecretID: secretID, - ClientSecret: fmt.Sprintf("%s.%s", secretID, secret), - ClientSecretExp: expiresAt.Unix(), - } -} diff --git a/idp/internal/services/dtos/well_known.go b/idp/internal/services/dtos/well_known.go index b504741..6a86eee 100644 --- a/idp/internal/services/dtos/well_known.go +++ b/idp/internal/services/dtos/well_known.go @@ -55,11 +55,12 @@ type WellKnownOIDCConfigurationDTO struct { } var AuthMethodsSupported = []string{"client_secret_basic", "client_secret_post", "private_key_jwt"} -var ResponseTypesSupported = []string{"code", "id_token", "token", "id_token token"} +var ResponseTypesSupported = []string{"code", "id_token", "code id_token"} var SubjectTypesSupported = []string{"public", "pairwise"} var CodeChallengeMethodsSupported = []string{"S256"} var GrantTypesSupported = []string{ "authorization_code", + "implicit", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:device_code", diff --git a/idp/internal/services/dynamic_registration_domains.go b/idp/internal/services/dynamic_registration_domains.go index 1daa030..d2de1e6 100644 --- a/idp/internal/services/dynamic_registration_domains.go +++ b/idp/internal/services/dynamic_registration_domains.go @@ -886,7 +886,7 @@ func (s *Services) checkClientRegistrationDomain( "baseDomain", baseDomain, "iatDomain", opts.iatDomain, ) - return "", exceptions.NewUnauthorizedError() + return "", exceptions.NewError(exceptions.OAuthErrorUnauthorizedClient, "client domain is outside the initial access token domain") } domains := []string{opts.domain} @@ -930,5 +930,5 @@ func (s *Services) checkClientRegistrationDomain( } logger.InfoContext(ctx, "Domain is not whitelisted or verified") - return "", exceptions.NewUnauthorizedError() + return "", exceptions.NewError(exceptions.OAuthErrorUnauthorizedClient, "client domain is not approved for dynamic registration") } diff --git a/idp/internal/services/helpers.go b/idp/internal/services/helpers.go index e069b59..6d706ce 100644 --- a/idp/internal/services/helpers.go +++ b/idp/internal/services/helpers.go @@ -42,6 +42,7 @@ const ( TwoFactorTotp string = "totp" ResponseTypeCode string = "code" + ResponseTypeIdToken string = "id_token" ResponseTypeCodeIdToken string = "code id_token" UsernameColumnEmail string = "email" @@ -49,6 +50,7 @@ const ( UsernameColumnBoth string = "both" GrantTypeAuthorizationCode string = "authorization_code" + GrantTypeImplicit string = "implicit" GrantTypeRefreshToken string = "refresh_token" GrantTypeClientCredentials string = "client_credentials" GrantTypeDeviceCode string = "urn:ietf:params:oauth:grant-type:device_code" @@ -133,6 +135,8 @@ func mapResponseTypesWithDefault(responseTypes []string) ([]database.ResponseTyp switch utils.Lowered(rt) { case ResponseTypeCode: dbResponseTypes = append(dbResponseTypes, database.ResponseTypeCode) + case ResponseTypeIdToken: + dbResponseTypes = append(dbResponseTypes, database.ResponseTypeIDToken) case ResponseTypeCodeIdToken: dbResponseTypes = append(dbResponseTypes, database.ResponseTypeCodeidToken) default: @@ -379,6 +383,8 @@ func mapGrantType(grantType string) (database.GrantType, *exceptions.ServiceErro switch utils.Lowered(grantType) { case GrantTypeAuthorizationCode: return database.GrantTypeAuthorizationCode, nil + case GrantTypeImplicit: + return database.GrantTypeImplicit, nil case GrantTypeRefreshToken: return database.GrantTypeRefreshToken, nil case GrantTypeClientCredentials: @@ -391,3 +397,4 @@ func mapGrantType(grantType string) (database.GrantType, *exceptions.ServiceErro return "", exceptions.NewValidationError("invalid grant type: " + grantType) } } + diff --git a/idp/internal/services/oauth_dynamic_registration_config.go b/idp/internal/services/oauth_dynamic_registration_config.go index 33302bd..1859432 100644 --- a/idp/internal/services/oauth_dynamic_registration_config.go +++ b/idp/internal/services/oauth_dynamic_registration_config.go @@ -427,7 +427,6 @@ func (s *Services) updateRegisteredAccountCredentials( domain: parsedClientURI.Hostname(), requestID: opts.RequestID, tokenEndpointAuthMethod: tokenEndpointAuthMethod, - transport: existing.Transport, scopes: scopes, data: &data, }) @@ -435,7 +434,7 @@ func (s *Services) updateRegisteredAccountCredentials( return nil, serviceErr } updated, err := qrs.UpdateRegisteredAccountCredentials(ctx, database.UpdateRegisteredAccountCredentialsParams{ - ID: existing.ID, Domain: params.Domain, Transport: params.Transport, RedirectUris: params.RedirectUris, + ID: existing.ID, Domain: params.Domain, RedirectUris: params.RedirectUris, TokenEndpointAuthMethod: params.TokenEndpointAuthMethod, GrantTypes: params.GrantTypes, ResponseTypes: params.ResponseTypes, ClientName: params.ClientName, ClientUri: params.ClientUri, LogoUri: params.LogoUri, Scopes: params.Scopes, Contacts: params.Contacts, TosUri: params.TosUri, PolicyUri: params.PolicyUri, JwksUri: params.JwksUri, Jwks: params.Jwks, @@ -461,6 +460,7 @@ func (s *Services) updateRegisteredAccountCredentials( RequestID: opts.RequestID, AccountPublicID: opts.AccountPublicID, AccountVersion: opts.AccountVersion, ClientID: updated.ClientID, BackendDomain: opts.BackendDomain, ID: updated.ID, AccountID: updated.AccountID, Statement: opts.SoftwareStatement, Stored: existing.RegistrationTokenJti, Token: opts.RegistrationToken, App: false, + Queries: qrs, }) if serviceErr != nil { return nil, serviceErr @@ -551,7 +551,7 @@ func (s *Services) updateRegisteredApp( params, serviceErr := s.mapAppRegistrationDataToDBParams(ctx, mapAppRegistrationDataToDBParamsOptions{ appType: existing.AppType, accountPublicID: account.PublicID, accountID: opts.AccountID, domain: parsedClientURI.Hostname(), requestID: opts.RequestID, tokenEndpointAuthMethod: tokenEndpointAuthMethod, - transport: existing.Transport, scopes: stdScopes, customScopes: customScopes, defaultScopes: defaultStdScopes, + scopes: stdScopes, customScopes: customScopes, defaultScopes: defaultStdScopes, defaultCustomScopes: defaultCustomScopes, allowUserRegistration: existing.AllowUserRegistration, usernameColumn: existing.UsernameColumn, authProviders: existing.AuthProviders, data: &data, }) @@ -564,7 +564,7 @@ func (s *Services) updateRegisteredApp( TosUri: params.TosUri, PolicyUri: params.PolicyUri, Contacts: params.Contacts, SoftwareID: params.SoftwareID, SoftwareVersion: params.SoftwareVersion, Scopes: params.Scopes, DefaultScopes: params.DefaultScopes, CustomScopes: params.CustomScopes, DefaultCustomScopes: params.DefaultCustomScopes, Domain: params.Domain, - Transport: params.Transport, RedirectUris: params.RedirectUris, ResponseTypes: params.ResponseTypes, + RedirectUris: params.RedirectUris, ResponseTypes: params.ResponseTypes, AllowUserRegistration: params.AllowUserRegistration, AuthProviders: params.AuthProviders, JwksUri: params.JwksUri, Jwks: params.Jwks, SectorIdentifierUri: params.SectorIdentifierUri, SubjectType: params.SubjectType, IDTokenSignedResponseAlg: params.IDTokenSignedResponseAlg, IDTokenEncryptedResponseAlg: params.IDTokenEncryptedResponseAlg, @@ -585,6 +585,7 @@ func (s *Services) updateRegisteredApp( ClientID: updated.ClientID, BackendDomain: opts.BackendDomain, ID: updated.ID, AccountID: updated.AccountID, Statement: opts.SoftwareStatement, Stored: existing.RegistrationTokenJti, Token: opts.RegistrationToken, App: true, HostUsername: opts.HostUsername, + Queries: qrs, }) if serviceErr != nil { return nil, serviceErr diff --git a/idp/internal/services/registration_management.go b/idp/internal/services/registration_management.go index f9b3823..a5a039e 100644 --- a/idp/internal/services/registration_management.go +++ b/idp/internal/services/registration_management.go @@ -127,6 +127,7 @@ type registrationStateOptions struct { AccountPublicID uuid.UUID AccountID, AccountVersion, ID int32 Stored pgtype.UUID + Queries *database.Queries App bool } @@ -170,10 +171,14 @@ func (s *Services) registrationResponseToken(ctx context.Context, o registration } } var err error + queries := o.Queries + if queries == nil { + queries = s.database.Queries + } if o.App { - err = s.database.SetAppRegistrationState(ctx, database.SetAppRegistrationStateParams{ID: o.ID, RegistrationTokenJti: jti, SoftwareStatement: o.Statement}) + err = queries.SetAppRegistrationState(ctx, database.SetAppRegistrationStateParams{ID: o.ID, RegistrationTokenJti: jti, SoftwareStatement: o.Statement}) } else { - err = s.database.SetAccountCredentialsRegistrationState(ctx, database.SetAccountCredentialsRegistrationStateParams{ID: o.ID, RegistrationTokenJti: jti, SoftwareStatement: o.Statement}) + err = queries.SetAccountCredentialsRegistrationState(ctx, database.SetAccountCredentialsRegistrationStateParams{ID: o.ID, RegistrationTokenJti: jti, SoftwareStatement: o.Statement}) } if err != nil { return "", exceptions.FromDBError(err) diff --git a/idp/internal/services/registration_metadata.go b/idp/internal/services/registration_metadata.go index 5d82751..c856578 100644 --- a/idp/internal/services/registration_metadata.go +++ b/idp/internal/services/registration_metadata.go @@ -10,10 +10,15 @@ import ( "context" "encoding/json" "errors" + "fmt" + "maps" "net" + "net/http" + "net/netip" "net/url" "slices" "strings" + "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" @@ -25,6 +30,175 @@ import ( "github.com/tugascript/devlogs/idp/internal/utils" ) +const registrationMetadataLocation = "registration_metadata" + +type RegistrationMetadataAppType = string + +const ( + AppTypeWeb RegistrationMetadataAppType = "web" + AppTypeNative RegistrationMetadataAppType = "native" +) + +var fetchSectorIdentifierURIs = func(ctx context.Context, uri string, validatedIPs []net.IPAddr) ([]string, error) { + parsed, err := url.Parse(uri) + if err != nil || parsed.Scheme != "https" || parsed.Hostname() == "" { + return nil, fmt.Errorf("invalid sector identifier URI") + } + if len(validatedIPs) == 0 { + return nil, fmt.Errorf("sector identifier host has no validated addresses") + } + for _, addr := range validatedIPs { + if isBlockedSectorIdentifierIP(addr.IP) { + return nil, fmt.Errorf("sector identifier host has a non-public address: %s", addr.IP) + } + } + + port := parsed.Port() + if port == "" { + port = "443" + } + host := parsed.Hostname() + dialer := &net.Dialer{} + transport := http.DefaultTransport.(*http.Transport).Clone() + // Do not use an environment proxy: the connection must go to one of the + // addresses checked above, not to a proxy that resolves the host separately. + transport.Proxy = nil + transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { + dialHost, dialPort, err := net.SplitHostPort(address) + if err != nil || !strings.EqualFold(dialHost, host) || dialPort != port { + return nil, fmt.Errorf("unexpected sector identifier dial target %q", address) + } + + var lastErr error + for _, addr := range validatedIPs { + conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(addr.IP.String(), port)) + if err == nil { + return conn, nil + } + lastErr = err + } + return nil, fmt.Errorf("failed to connect to validated sector identifier addresses: %w", lastErr) + } + defer transport.CloseIdleConnections() + client := &http.Client{ + Transport: transport, + Timeout: 10 * time.Second, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return fmt.Errorf("redirect not allowed") + }, + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + limited := http.MaxBytesReader(nil, resp.Body, 128*1024) + var redirects []string + if err := json.NewDecoder(limited).Decode(&redirects); err != nil { + return nil, err + } + return redirects, nil +} + +var lookupSectorIdentifierIPs = func(ctx context.Context, host string) ([]net.IPAddr, error) { + return net.DefaultResolver.LookupIPAddr(ctx, host) +} + +var blockedSectorIdentifierPrefixes = []netip.Prefix{ + // IPv4 special-use and reserved ranges + netip.MustParsePrefix("0.0.0.0/8"), // Current network (RFC 1122) + netip.MustParsePrefix("10.0.0.0/8"), // Private-use (RFC 1918) + netip.MustParsePrefix("100.64.0.0/10"), // Shared Address Space / CGNAT (RFC 6598) + netip.MustParsePrefix("127.0.0.0/8"), // Loopback (RFC 1122) + netip.MustParsePrefix("169.254.0.0/16"), // Link-local (RFC 3927) + netip.MustParsePrefix("172.16.0.0/12"), // Private-use (RFC 1918) + netip.MustParsePrefix("192.0.0.0/24"), // IETF Protocol Assignments (RFC 6890) + netip.MustParsePrefix("192.0.2.0/24"), // Documentation / TEST-NET-1 (RFC 5737) + netip.MustParsePrefix("192.88.99.0/24"), // 6to4 relay anycast (RFC 7526) + netip.MustParsePrefix("192.168.0.0/16"), // Private-use (RFC 1918) + netip.MustParsePrefix("198.18.0.0/15"), // Benchmarking (RFC 2544) + netip.MustParsePrefix("198.51.100.0/24"), // Documentation / TEST-NET-2 (RFC 5737) + netip.MustParsePrefix("203.0.113.0/24"), // Documentation / TEST-NET-3 (RFC 5737) + netip.MustParsePrefix("224.0.0.0/4"), // Multicast (RFC 5771) + netip.MustParsePrefix("240.0.0.0/4"), // Reserved for future use (RFC 1112) + netip.MustParsePrefix("255.255.255.255/32"), // Limited broadcast (RFC 8190) + + // IPv6 special-use and reserved ranges + netip.MustParsePrefix("::1/128"), // Loopback + netip.MustParsePrefix("::/128"), // Unspecified + netip.MustParsePrefix("::ffff:0:0/96"), // IPv4-mapped + netip.MustParsePrefix("64:ff9b::/96"), // IPv4/IPv6 translation (RFC 6052) + netip.MustParsePrefix("64:ff9b:1::/48"), // Local-use translation (RFC 8215) + netip.MustParsePrefix("100::/64"), // Discard prefix (RFC 6666) + netip.MustParsePrefix("2001:2::/48"), // Benchmarking (RFC 5180) + netip.MustParsePrefix("2001:10::/28"), // ORCHID (RFC 4843) + netip.MustParsePrefix("2001:20::/28"), // ORCHIDv2 (RFC 7343) + netip.MustParsePrefix("2001:db8::/32"), // Documentation (RFC 3849) + netip.MustParsePrefix("2002::/16"), // 6to4 (RFC 7526) + netip.MustParsePrefix("fc00::/7"), // Unique Local (RFC 4193) + netip.MustParsePrefix("fe80::/10"), // Link-Local (RFC 4291) + netip.MustParsePrefix("ff00::/8"), // Multicast (RFC 4291) +} + +func isBlockedSectorIdentifierIP(ip net.IP) bool { + if ip == nil { + return true + } + addr, ok := netip.AddrFromSlice(ip) + if !ok || !addr.IsValid() { + return true + } + addr = addr.Unmap() + if !addr.IsGlobalUnicast() || addr.IsUnspecified() || addr.IsLoopback() || addr.IsPrivate() || addr.IsLinkLocalUnicast() || addr.IsLinkLocalMulticast() || addr.IsMulticast() { + return true + } + for _, prefix := range blockedSectorIdentifierPrefixes { + if prefix.Contains(addr) { + return true + } + } + return false +} + +func validateSectorIdentifierHost(ctx context.Context, host string) ([]net.IPAddr, error) { + if host == "" { + return nil, fmt.Errorf("empty host") + } + + if ip := net.ParseIP(host); ip != nil { + if isBlockedSectorIdentifierIP(ip) { + return nil, fmt.Errorf("host resolves to a non-public address: %s", ip) + } + return []net.IPAddr{{IP: ip}}, nil + } + + ips, err := lookupSectorIdentifierIPs(ctx, host) + if err != nil { + return nil, fmt.Errorf("host resolution failed: %w", err) + } + if len(ips) == 0 { + return nil, fmt.Errorf("host resolved to no addresses") + } + for _, addr := range ips { + if isBlockedSectorIdentifierIP(addr.IP) { + return nil, fmt.Errorf("host resolves to a non-public address: %s", addr.IP) + } + } + return ips, nil +} + type prepareDynamicRegistrationOptions struct { requestID string accountID int32 @@ -58,9 +232,7 @@ func mergeRegistrationMetadata( if body.GrantTypes != nil { fields["grant_types"], _ = json.Marshal(body.GrantTypes) } - for name, value := range statement.RawMetadata { - fields[name] = value - } + maps.Copy(fields, statement.RawMetadata) encoded, err = json.Marshal(fields) if err != nil { return ApplicationRegistrationData{}, err @@ -78,6 +250,20 @@ func (s *Services) prepareDynamicRegistration( ctx context.Context, opts prepareDynamicRegistrationOptions, ) (ApplicationRegistrationData, *exceptions.ServiceError) { + logger := s.buildLogger( + opts.requestID, + registrationMetadataLocation, + "prepareDynamicRegistration", + ).With( + "app", opts.app, + ) + if opts.app { + logger = logger.With("accountID", opts.accountID) + } else { + logger = logger.With("accountPublicID", opts.accountPublicID) + } + logger.InfoContext(ctx, "Preparing dynamic registration metadata...") + data := opts.data var verificationMethods []database.SoftwareStatementVerificationMethod @@ -88,7 +274,8 @@ func (s *Services) prepareDynamicRegistration( ID: opts.accountID, }) if err != nil { - return data, err + logger.ErrorContext(ctx, "Failed to get account by ID", "serviceError", err) + return data, exceptions.NewInternalServerError() } opts.accountPublicID = account.PublicID config, err := s.GetAndCacheAppDynamicRegistrationConfig(ctx, GetAndCacheAppDynamicRegistrationConfigOptions{ @@ -96,7 +283,8 @@ func (s *Services) prepareDynamicRegistration( AccountID: opts.accountID, }) if err != nil { - return data, err + logger.ErrorContext(ctx, "Failed to get and cache app dynamic registration config", "serviceError", err) + return data, exceptions.NewInternalServerError() } verificationMethods = config.SoftwareStatementVerificationMethods if len(config.DefaultAllowedScopes) > 0 { @@ -112,16 +300,19 @@ func (s *Services) prepareDynamicRegistration( AccountPublicID: opts.accountPublicID, }) if err != nil { - return data, err + logger.ErrorContext(ctx, "Failed to get and cache account dynamic registration config", "serviceError", err) + return data, exceptions.NewInternalServerError() } verificationMethods = config.SoftwareStatementVerificationMethods } if opts.softwareStatement != "" { + logger.InfoContext(ctx, "Processing software statement...") // This preview only locates the account's configured verification key/domain. // No metadata from it is applied until signature verification succeeds. var preview jwt.MapClaims if _, _, err := jwt.NewParser().ParseUnverified(opts.softwareStatement, &preview); err != nil { + logger.WarnContext(ctx, "Failed to parse unverified software statement", "error", err) return data, exceptions.NewInvalidTokenError("invalid software statement") } keyURI := data.ClientURI @@ -135,6 +326,7 @@ func (s *Services) prepareDynamicRegistration( domain := registrationDomain(keyURI, data.RedirectURIs) baseDomain, err := publicsuffix.EffectiveTLDPlusOne(domain) if err != nil { + logger.WarnContext(ctx, "Failed to parse software statement base domain", "domain", domain, "error", err) return data, exceptions.NewInvalidTokenError("invalid software statement domain") } claims, standard, err := s.jwt.VerifySoftwareStatement(ctx, tokens.VerifySoftwareStatementOptions{ @@ -152,8 +344,10 @@ func (s *Services) prepareDynamicRegistration( }) if err != nil { if errors.Is(err, errUnapprovedSoftwareStatement) { + logger.WarnContext(ctx, "Software statement is not approved", "error", err) return data, exceptions.NewUnauthorizedTokenError("unapproved software statement") } + logger.WarnContext(ctx, "Failed to verify software statement", "error", err) return data, exceptions.NewInvalidTokenError("invalid software statement") } if serviceErr := s.verifySoftwareStatementSTDClaims(ctx, verifySoftwareStatementSTDClaimsOptions{ @@ -164,6 +358,7 @@ func (s *Services) prepareDynamicRegistration( frontendDomain: opts.frontendDomain, claims: &standard, }); serviceErr != nil { + logger.WarnContext(ctx, "Software statement standard claims verification failed", "serviceError", serviceErr) return data, serviceErr } if serviceErr := s.validateSoftwareStatementClaims(ctx, validateSoftwareStatementClaimsOptions{ @@ -171,12 +366,15 @@ func (s *Services) prepareDynamicRegistration( claims: &claims, allowedScopes: utils.SliceToHashSet(allowedScopes), }); serviceErr != nil { + logger.WarnContext(ctx, "Software statement claims validation failed", "serviceError", serviceErr) return data, exceptions.NewInvalidTokenError("invalid software statement") } data, err = mergeRegistrationMetadata(data, claims) if err != nil { + logger.WarnContext(ctx, "Failed to merge software statement metadata", "error", err) return data, exceptions.NewInvalidTokenError("invalid software statement metadata") } + logger.DebugContext(ctx, "Software statement verified and merged successfully") } if data.ApplicationType == "" { @@ -188,6 +386,9 @@ func (s *Services) prepareDynamicRegistration( data.ApplicationType = "native" } } + if opts.app && data.ApplicationType != AppTypeWeb && data.ApplicationType != AppTypeNative { + return data, exceptions.NewValidationError("application_type must be web or native for apps") + } if data.ClientName == "" { data.ClientName = "Client " + utils.Base62UUID() @@ -196,6 +397,7 @@ func (s *Services) prepareDynamicRegistration( if data.ClientURI == "" { domain := registrationDomain("", data.RedirectURIs) if domain == "" { + logger.WarnContext(ctx, "Failed to determine client domain from redirect URIs") return data, exceptions.NewValidationError("a client domain could not be determined") } data.ClientURI = "https://" + domain @@ -205,15 +407,92 @@ func (s *Services) prepareDynamicRegistration( data.Scope = "profile" } if serviceErr := normalizeRegistrationMetadata(&data); serviceErr != nil { + logger.WarnContext(ctx, "Failed to normalize registration metadata", "serviceError", serviceErr) + return data, serviceErr + } + + if serviceErr := s.validateSectorIdentifier(ctx, opts.requestID, data.SectorIdentifierURI, data.RedirectURIs, data.SubjectType); serviceErr != nil { + logger.WarnContext(ctx, "Sector identifier validation failed", "serviceError", serviceErr) return data, serviceErr } + if err := s.validate.StructCtx(ctx, &data); err != nil { + logger.WarnContext(ctx, "Validation failed for dynamic registration metadata", "error", err) return data, exceptions.NewValidationError("invalid client metadata") } + logger.InfoContext(ctx, "Dynamic registration metadata prepared successfully", + "clientURI", data.ClientURI, + "applicationType", data.ApplicationType, + ) return data, nil } +func (s *Services) validateSectorIdentifier( + ctx context.Context, + requestID string, + sectorIdentifierURI string, + redirectURIs []string, + subjectType string, +) *exceptions.ServiceError { + logger := s.buildLogger(requestID, registrationMetadataLocation, "validateSectorIdentifier") + if sectorIdentifierURI == "" { + if subjectType == SubjectTypePairwise && len(redirectURIs) > 1 { + var firstHost string + for _, r := range redirectURIs { + parsed, err := url.Parse(r) + if err != nil || parsed.Hostname() == "" { + logger.WarnContext(ctx, "Failed to parse redirect URI for pairwise host comparison", "uri", r, "error", err) + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "invalid redirect URI") + } + host := parsed.Hostname() + if firstHost == "" { + firstHost = host + } else if !strings.EqualFold(firstHost, host) { + logger.WarnContext(ctx, "Pairwise subject type redirect URIs have different host components", + "firstHost", firstHost, + "host", host, + ) + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "all redirect_uris must have the same host component for pairwise subject type when sector_identifier_uri is omitted") + } + } + } + return nil + } + + logger.InfoContext(ctx, "Validating sector identifier URI", "sectorIdentifierURI", sectorIdentifierURI) + parsed, err := url.Parse(sectorIdentifierURI) + if err != nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.User != nil || parsed.Fragment != "" { + logger.WarnContext(ctx, "Invalid sector_identifier_uri format", "sectorIdentifierURI", sectorIdentifierURI, "error", err) + return exceptions.NewError(exceptions.OAuthErrorInvalidClientMetadata, "sector_identifier_uri must be an HTTPS URL without userinfo or fragment") + } + validatedIPs, err := validateSectorIdentifierHost(ctx, parsed.Hostname()) + if err != nil { + logger.WarnContext(ctx, "Blocked sector_identifier_uri host", "sectorIdentifierURI", sectorIdentifierURI, "error", err) + return exceptions.NewError(exceptions.OAuthErrorInvalidClientMetadata, "sector_identifier_uri host must resolve to a public address") + } + + sectorRedirects, err := fetchSectorIdentifierURIs(ctx, sectorIdentifierURI, validatedIPs) + if err != nil { + logger.WarnContext(ctx, "Failed to fetch sector_identifier_uri", "sectorIdentifierURI", sectorIdentifierURI, "error", err) + return exceptions.NewError(exceptions.OAuthErrorInvalidClientMetadata, "failed to fetch or parse sector_identifier_uri") + } + + sectorSet := utils.SliceToHashSet(sectorRedirects) + for _, redirectURI := range redirectURIs { + if !sectorSet.Contains(redirectURI) { + logger.WarnContext(ctx, "redirect_uri not found in sector_identifier_uri list", + "redirectURI", redirectURI, + "sectorIdentifierURI", sectorIdentifierURI, + ) + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "redirect_uris must be included in sector_identifier_uri") + } + } + + logger.InfoContext(ctx, "Sector identifier URI validated successfully") + return nil +} + func registrationDomain(clientURI string, redirects []string) string { if parsed, err := url.Parse(clientURI); err == nil && parsed.Hostname() != "" { return parsed.Hostname() @@ -227,12 +506,21 @@ func registrationDomain(clientURI string, redirects []string) string { } func normalizeRegistrationMetadata(data *ApplicationRegistrationData) *exceptions.ServiceError { + if data.ApplicationType == "" { + data.ApplicationType = "web" + } + if data.GrantTypes == nil { data.GrantTypes = []string{"authorization_code"} } + hasCodeGrant := slices.Contains(data.GrantTypes, "authorization_code") if data.ResponseTypes == nil { - data.ResponseTypes = []string{"code"} + if hasCodeGrant { + data.ResponseTypes = []string{"code"} + } else { + data.ResponseTypes = []string{} + } } if data.TokenEndpointAuthMethod == "" { @@ -241,7 +529,6 @@ func normalizeRegistrationMetadata(data *ApplicationRegistrationData) *exception if len(data.GrantTypes) == 0 { return exceptions.NewValidationError("grant_types must not be empty") } - hasCodeGrant := slices.Contains(data.GrantTypes, "authorization_code") hasCodeResponse := slices.ContainsFunc(data.ResponseTypes, func(response string) bool { return slices.Contains(strings.Fields(response), "code") }) @@ -251,28 +538,119 @@ func normalizeRegistrationMetadata(data *ApplicationRegistrationData) *exception if hasCodeGrant && !hasCodeResponse { return exceptions.NewValidationError("authorization_code requires a code response") } - if hasCodeGrant && len(data.RedirectURIs) == 0 { - return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "redirect_uris is required for authorization_code") + + hasImplicitGrant := slices.Contains(data.GrantTypes, "implicit") + hasImplicitResponse := slices.ContainsFunc(data.ResponseTypes, func(response string) bool { + fields := strings.Fields(response) + return slices.Contains(fields, "id_token") + }) + if hasImplicitResponse && !hasImplicitGrant { + return exceptions.NewValidationError("id_token responses require implicit") + } + if hasImplicitGrant && !hasImplicitResponse { + return exceptions.NewValidationError("implicit requires an id_token response") + } + + if len(data.RedirectURIs) == 0 && (hasCodeGrant || hasImplicitGrant) { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "redirect_uris is required") + } + + if len(data.RedirectURIs) > 0 { + if serviceErr := validateRegistrationRedirectURIs(data.ApplicationType, data.RedirectURIs, hasImplicitGrant || hasImplicitResponse); serviceErr != nil { + return serviceErr + } } - if serviceErr := validateRegistrationRedirectURIs(data.RedirectURIs); serviceErr != nil { + if serviceErr := validateRegistrationURIs(data); serviceErr != nil { return serviceErr } return validateRegistrationKeys(data) } -func validateRegistrationRedirectURIs(redirectURIs []string) *exceptions.ServiceError { +func validateRegistrationRedirectURIs(applicationType string, redirectURIs []string, hasImplicit bool) *exceptions.ServiceError { + if len(redirectURIs) == 0 { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "redirect_uris is required") + } + for _, raw := range redirectURIs { uri, err := url.Parse(raw) if err != nil || uri.Scheme == "" || uri.User != nil || strings.Contains(raw, "#") { return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "invalid redirect URI") } - if (uri.Scheme == "https" || uri.Scheme == "http") && uri.Host == "" { - return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "invalid redirect URI") + + scheme := strings.ToLower(uri.Scheme) + + switch applicationType { + case "native": + if scheme == "https" { + if uri.Host == "" { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "invalid redirect URI") + } + } else if scheme == "http" { + if uri.Host == "" { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "invalid redirect URI") + } + if !strings.EqualFold(uri.Hostname(), "localhost") && !net.ParseIP(uri.Hostname()).IsLoopback() { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "HTTP redirect URIs must use localhost or a loopback IP address") + } + } + case "web": + if scheme != "https" && scheme != "http" { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "web clients must use HTTPS or HTTP redirect URIs") + } + if uri.Host == "" { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "invalid redirect URI") + } + if hasImplicit { + if scheme != "https" { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "web clients using implicit grant must use HTTPS redirect URIs") + } + if strings.EqualFold(uri.Hostname(), "localhost") || (net.ParseIP(uri.Hostname()) != nil && net.ParseIP(uri.Hostname()).IsLoopback()) { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "web clients using implicit grant must not use localhost as the hostname") + } + } else { + if scheme == "http" && !strings.EqualFold(uri.Hostname(), "localhost") && !net.ParseIP(uri.Hostname()).IsLoopback() { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "HTTP redirect URIs must use localhost or a loopback IP address") + } + } + default: + if scheme != "https" && scheme != "http" { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "invalid redirect URI") + } + if uri.Host == "" { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "invalid redirect URI") + } + if scheme == "http" && !strings.EqualFold(uri.Hostname(), "localhost") && !net.ParseIP(uri.Hostname()).IsLoopback() { + return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "HTTP redirect URIs must use localhost or a loopback IP address") + } + } + } + + return nil +} + +func validateRegistrationURIs(data *ApplicationRegistrationData) *exceptions.ServiceError { + if data.InitiateLoginURI != "" { + uri, err := url.Parse(data.InitiateLoginURI) + if err != nil || uri.Scheme != "https" || uri.Host == "" || uri.User != nil || uri.Fragment != "" { + return exceptions.NewValidationError("initiate_login_uri must be an HTTPS URL") } - if uri.Scheme == "http" && !strings.EqualFold(uri.Hostname(), "localhost") && !net.ParseIP(uri.Hostname()).IsLoopback() { - return exceptions.NewError(exceptions.OAuthErrorInvalidRedirectURI, "HTTP redirect URIs must use localhost or a loopback IP address") + } + + if len(data.RequestURIs) > 0 { + canVerifySignedRequest := ((data.JWKs != nil && len(data.JWKs.Keys) > 0) || data.JWKsURI != "") && data.RequestObjectSigningAlg != "" + for _, raw := range data.RequestURIs { + uri, err := url.Parse(raw) + if err != nil || uri.Host == "" || uri.User != nil || uri.Fragment != "" { + return exceptions.NewValidationError("invalid request_uri") + } + if uri.Scheme != "https" { + if uri.Scheme == "http" && canVerifySignedRequest { + continue + } + return exceptions.NewValidationError("request_uris must use HTTPS unless request objects are verifiable") + } } } @@ -326,3 +704,4 @@ func mapRegistrationResponseTypes(values []string) ([]database.ResponseType, *ex } return mapResponseTypesWithDefault(values) } + diff --git a/idp/internal/services/registration_metadata_test.go b/idp/internal/services/registration_metadata_test.go index 67bd529..675c38b 100644 --- a/idp/internal/services/registration_metadata_test.go +++ b/idp/internal/services/registration_metadata_test.go @@ -3,18 +3,61 @@ package services import ( "context" "encoding/json" + "errors" "io" "log/slog" + "net" "reflect" "testing" "github.com/golang-jwt/jwt/v5" "github.com/tugascript/devlogs/idp/internal/exceptions" + "github.com/tugascript/devlogs/idp/internal/providers/database" "github.com/tugascript/devlogs/idp/internal/providers/tokens" "github.com/tugascript/devlogs/idp/internal/utils" ) +func TestValidateSectorIdentifierHostRejectsPrivateTargets(t *testing.T) { + for _, tc := range []struct { + name string + host string + want bool + }{ + {name: "public host", host: "example.com", want: false}, + {name: "localhost", host: "localhost", want: true}, + {name: "private ipv4", host: "10.0.0.5", want: true}, + {name: "link local ipv4", host: "169.254.169.254", want: true}, + {name: "shared cgnat ipv4", host: "100.64.0.1", want: true}, + {name: "benchmarking ipv4", host: "198.18.0.1", want: true}, + {name: "documentation ipv4", host: "192.0.2.1", want: true}, + {name: "ipv4 mapped cgnat", host: "::ffff:100.64.0.1", want: true}, + {name: "loopback ipv6", host: "::1", want: true}, + {name: "documentation ipv6", host: "2001:db8::1", want: true}, + {name: "unique local ipv6", host: "fc00::1", want: true}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.host == "example.com" { + orig := lookupSectorIdentifierIPs + lookupSectorIdentifierIPs = func(ctx context.Context, host string) ([]net.IPAddr, error) { + return []net.IPAddr{{IP: net.ParseIP("93.184.216.34")}}, nil + } + defer func() { lookupSectorIdentifierIPs = orig }() + } + ips, err := validateSectorIdentifierHost(context.Background(), tc.host) + if tc.want && err == nil { + t.Fatal("expected blocked host error") + } + if !tc.want && err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !tc.want && len(ips) == 0 { + t.Fatal("expected validated addresses") + } + }) + } +} + func TestRegistrationMetadataDefaultsAndValidation(t *testing.T) { cases := []struct { name string @@ -26,11 +69,19 @@ func TestRegistrationMetadataDefaultsAndValidation(t *testing.T) { {name: "fragment", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/#fragment"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, {name: "empty fragment", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/#"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, {name: "relative redirect", data: ApplicationRegistrationData{RedirectURIs: []string{"/callback"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, - {name: "client credentials without responses", data: ApplicationRegistrationData{GrantTypes: []string{"client_credentials"}, ResponseTypes: []string{}}}, - {name: "inconsistent grant and response", data: ApplicationRegistrationData{GrantTypes: []string{"client_credentials"}}, errorCode: exceptions.CodeValidation}, - {name: "authorization code without code response", data: ApplicationRegistrationData{GrantTypes: []string{"authorization_code"}, ResponseTypes: []string{}}, errorCode: exceptions.CodeValidation}, + {name: "client credentials without redirect or response metadata", data: ApplicationRegistrationData{ApplicationType: "web", GrantTypes: []string{"client_credentials"}}}, + {name: "client credentials without responses", data: ApplicationRegistrationData{GrantTypes: []string{"client_credentials"}, ResponseTypes: []string{}, RedirectURIs: []string{"https://example.com/callback"}}}, + {name: "inconsistent grant and response", data: ApplicationRegistrationData{GrantTypes: []string{"client_credentials"}, ResponseTypes: []string{"code"}, RedirectURIs: []string{"https://example.com/callback"}}, errorCode: exceptions.CodeValidation}, + {name: "authorization code without code response", data: ApplicationRegistrationData{GrantTypes: []string{"authorization_code"}, ResponseTypes: []string{}, RedirectURIs: []string{"https://example.com/callback"}}, errorCode: exceptions.CodeValidation}, {name: "both key sources", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/cb"}, JWKs: &utils.JWKSet{}, JWKsURI: "https://example.com/jwks"}, errorCode: exceptions.CodeValidation}, - {name: "native custom scheme", data: ApplicationRegistrationData{RedirectURIs: []string{"com.example.app:/callback"}}}, + {name: "native custom scheme", data: ApplicationRegistrationData{ApplicationType: "native", RedirectURIs: []string{"com.example.app:/callback"}}}, + {name: "native remote HTTPS", data: ApplicationRegistrationData{ApplicationType: "native", RedirectURIs: []string{"https://example.com/callback"}}}, + {name: "native empty host HTTPS", data: ApplicationRegistrationData{ApplicationType: "native", RedirectURIs: []string{"https:///callback"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, + {name: "native remote HTTP", data: ApplicationRegistrationData{ApplicationType: "native", RedirectURIs: []string{"http://example.com/callback"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, + {name: "native localhost HTTP", data: ApplicationRegistrationData{ApplicationType: "native", RedirectURIs: []string{"http://localhost:8080/callback"}}}, + {name: "native loopback IPv4 HTTP", data: ApplicationRegistrationData{ApplicationType: "native", RedirectURIs: []string{"http://127.0.0.1:8080/callback"}}}, + {name: "native loopback IPv6 HTTP", data: ApplicationRegistrationData{ApplicationType: "native", RedirectURIs: []string{"http://[::1]:8080/callback"}}}, + {name: "web custom scheme", data: ApplicationRegistrationData{ApplicationType: "web", RedirectURIs: []string{"com.example.app:/callback"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, {name: "remote HTTP", data: ApplicationRegistrationData{RedirectURIs: []string{"http://example.com/callback"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, {name: "uppercase remote HTTP", data: ApplicationRegistrationData{RedirectURIs: []string{"HTTP://example.com/callback"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, {name: "localhost lookalike", data: ApplicationRegistrationData{RedirectURIs: []string{"http://localhost.example.com/callback"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, @@ -38,6 +89,18 @@ func TestRegistrationMetadataDefaultsAndValidation(t *testing.T) { {name: "localhost HTTP", data: ApplicationRegistrationData{RedirectURIs: []string{"http://localhost:8080/callback"}}}, {name: "IPv4 loopback HTTP", data: ApplicationRegistrationData{RedirectURIs: []string{"http://127.0.0.1:8080/callback"}}}, {name: "IPv6 loopback HTTP", data: ApplicationRegistrationData{RedirectURIs: []string{"http://[::1]:8080/callback"}}}, + {name: "hybrid code id_token requires implicit", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/callback"}, ResponseTypes: []string{"code id_token"}, GrantTypes: []string{"authorization_code"}}, errorCode: exceptions.CodeValidation}, + {name: "implicit grant requires id_token response", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/callback"}, ResponseTypes: []string{"code"}, GrantTypes: []string{"authorization_code", "implicit"}}, errorCode: exceptions.CodeValidation}, + {name: "hybrid code id_token valid", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/callback"}, ResponseTypes: []string{"code id_token"}, GrantTypes: []string{"authorization_code", "implicit"}}}, + {name: "implicit-only id_token valid", data: ApplicationRegistrationData{ApplicationType: "web", RedirectURIs: []string{"https://example.com/callback"}, ResponseTypes: []string{"id_token"}, GrantTypes: []string{"implicit"}}}, + {name: "web implicit localhost HTTP", data: ApplicationRegistrationData{ApplicationType: "web", RedirectURIs: []string{"http://localhost:8080/callback"}, ResponseTypes: []string{"code id_token"}, GrantTypes: []string{"authorization_code", "implicit"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, + {name: "web implicit localhost HTTPS", data: ApplicationRegistrationData{ApplicationType: "web", RedirectURIs: []string{"https://localhost:8080/callback"}, ResponseTypes: []string{"code id_token"}, GrantTypes: []string{"authorization_code", "implicit"}}, errorCode: exceptions.OAuthErrorInvalidRedirectURI}, + {name: "web implicit remote HTTPS", data: ApplicationRegistrationData{ApplicationType: "web", RedirectURIs: []string{"https://example.com/callback"}, ResponseTypes: []string{"code id_token"}, GrantTypes: []string{"authorization_code", "implicit"}}}, + {name: "initiate login HTTP", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/callback"}, InitiateLoginURI: "http://example.com/login"}, errorCode: exceptions.CodeValidation}, + {name: "initiate login HTTPS", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/callback"}, InitiateLoginURI: "https://example.com/login"}}, + {name: "request uri HTTP without keys", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/callback"}, RequestURIs: []string{"http://example.com/request.jwt"}}, errorCode: exceptions.CodeValidation}, + {name: "request uri HTTPS", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/callback"}, RequestURIs: []string{"https://example.com/request.jwt"}}}, + {name: "request uri HTTP with keys and alg", data: ApplicationRegistrationData{RedirectURIs: []string{"https://example.com/callback"}, RequestURIs: []string{"http://example.com/request.jwt"}, JWKsURI: "https://example.com/jwks", RequestObjectSigningAlg: "ES256"}}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -66,6 +129,160 @@ func TestRegistrationMetadataDefaultsAndValidation(t *testing.T) { } } +func TestMapResponseTypesWithDefaultIncludesStandaloneIDToken(t *testing.T) { + responseTypes, err := mapResponseTypesWithDefault([]string{"id_token"}) + if err != nil { + t.Fatalf("map id_token response type: %v", err) + } + if len(responseTypes) != 1 || responseTypes[0] != database.ResponseTypeIDToken { + t.Fatalf("mapped response types = %v, want [%s]", responseTypes, database.ResponseTypeIDToken) + } +} + +func TestMapResponseTypesUpdateIncludesStandaloneIDToken(t *testing.T) { + responseTypes, err := mapResponseTypesUpdate([]string{"id_token"}, []database.ResponseType{database.ResponseTypeCode}) + if err != nil { + t.Fatalf("map update id_token response type: %v", err) + } + if len(responseTypes) != 1 || responseTypes[0] != database.ResponseTypeIDToken { + t.Fatalf("mapped response types = %v, want [%s]", responseTypes, database.ResponseTypeIDToken) + } +} + +func TestMapAllowedResponseTypesIncludesStandaloneIDToken(t *testing.T) { + responseTypes, err := mapResponseTypes([]string{"id_token"}) + if err != nil { + t.Fatalf("map allowed id_token response type: %v", err) + } + if len(responseTypes) != 1 || responseTypes[0] != database.ResponseTypeIDToken { + t.Fatalf("mapped response types = %v, want [%s]", responseTypes, database.ResponseTypeIDToken) + } +} + +func TestValidateAppGrantResponseTypes(t *testing.T) { + cases := []struct { + name string + grantTypes []database.GrantType + responseTypes []database.ResponseType + wantErr bool + errCode string + }{ + { + name: "implicit only with id_token", + grantTypes: []database.GrantType{database.GrantTypeImplicit}, + responseTypes: []database.ResponseType{database.ResponseTypeIDToken}, + }, + { + name: "implicit only without id_token", + grantTypes: []database.GrantType{database.GrantTypeImplicit}, + responseTypes: []database.ResponseType{database.ResponseTypeCode}, + wantErr: true, + }, + { + name: "implicit only with empty response types", + grantTypes: []database.GrantType{database.GrantTypeImplicit}, + responseTypes: []database.ResponseType{}, + wantErr: true, + }, + { + name: "auth code with code response", + grantTypes: []database.GrantType{database.GrantTypeAuthorizationCode}, + responseTypes: []database.ResponseType{database.ResponseTypeCode}, + }, + { + name: "auth code with hybrid response missing implicit", + grantTypes: []database.GrantType{database.GrantTypeAuthorizationCode}, + responseTypes: []database.ResponseType{database.ResponseTypeCodeidToken}, + wantErr: true, + }, + { + name: "auth code missing code response", + grantTypes: []database.GrantType{database.GrantTypeAuthorizationCode, database.GrantTypeImplicit}, + responseTypes: []database.ResponseType{database.ResponseTypeIDToken}, + wantErr: true, + }, + { + name: "hybrid with code and implicit", + grantTypes: []database.GrantType{database.GrantTypeAuthorizationCode, database.GrantTypeImplicit}, + responseTypes: []database.ResponseType{database.ResponseTypeCodeidToken}, + }, + { + name: "service grant with response types rejected", + grantTypes: []database.GrantType{database.GrantTypeClientCredentials}, + responseTypes: []database.ResponseType{database.ResponseTypeCode}, + wantErr: true, + }, + { + name: "service grant without response types valid", + grantTypes: []database.GrantType{database.GrantTypeClientCredentials}, + responseTypes: []database.ResponseType{}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := validateAppGrantResponseTypes(tc.grantTypes, tc.responseTypes) + if tc.wantErr && err == nil { + t.Fatalf("expected error for case %q, got nil", tc.name) + } + if !tc.wantErr && err != nil { + t.Fatalf("unexpected error for case %q: %v", tc.name, err) + } + }) + } +} + +func TestServiceRegistrationMetadataDoesNotNeedRedirectOrResponseTypes(t *testing.T) { + data := ApplicationRegistrationData{ + ApplicationType: "web", + ClientURI: "https://example.com", + TokenEndpointAuthMethod: "private_key_jwt", + GrantTypes: []string{"client_credentials", "urn:ietf:params:oauth:grant-type:jwt-bearer"}, + } + + if err := normalizeRegistrationMetadata(&data); err != nil { + t.Fatal(err) + } + if len(data.RedirectURIs) != 0 { + t.Fatalf("redirect URIs = %v, want none", data.RedirectURIs) + } + if len(data.ResponseTypes) != 0 { + t.Fatalf("response types = %v, want none", data.ResponseTypes) + } +} + +func TestAppTypesAndServiceGrants(t *testing.T) { + for _, appType := range []string{"web", "native"} { + if _, err := mapAppTypeToDB(appType); err != nil { + t.Errorf("mapAppTypeToDB(%q): %v", appType, err) + } + } + for _, appType := range []string{"spa", "backend", "device", "service", "mcp"} { + if _, err := mapAppTypeToDB(appType); err == nil { + t.Errorf("mapAppTypeToDB(%q) succeeded, want unsupported app type", appType) + } + } + for _, credentialType := range []string{"service", "mcp"} { + if _, err := mapAccountCredentialsType(credentialType); err != nil { + t.Errorf("mapAccountCredentialsType(%q): %v", credentialType, err) + } + } + + serviceGrants := []database.GrantType{ + database.GrantTypeClientCredentials, + database.GrantTypeUrnIetfParamsOauthGrantTypeJwtBearer, + } + if err := validateAppAuthGrantTypes(database.AppTypeWeb, database.AuthMethodPrivateKeyJwt, serviceGrants); err != nil { + t.Fatalf("confidential web service grants: %v", err) + } + if err := validateAppAuthGrantTypes(database.AppTypeWeb, database.AuthMethodNone, serviceGrants); err == nil { + t.Fatal("public web app accepted service grants") + } + if err := validateAppAuthGrantTypes(database.AppTypeNative, database.AuthMethodNone, serviceGrants); err == nil { + t.Fatal("native app accepted service grants") + } +} + func TestSoftwareStatementIssuerClassification(t *testing.T) { s := &Services{logger: slog.New(slog.NewTextHandler(io.Discard, nil))} for _, tc := range []struct{ name, issuer, want string }{ @@ -109,8 +326,8 @@ func TestRegistrationRejectsOtherIATDomains(t *testing.T) { _, err := s.checkClientRegistrationDomain(context.Background(), checkClientRegistrationDomainOptions{ iatDomain: "client.example.com", domain: domain, }) - if err == nil || err.Code != exceptions.CodeUnauthorized { - t.Fatalf("error=%v, want unauthorized", err) + if err == nil || err.Code != exceptions.OAuthErrorUnauthorizedClient { + t.Fatalf("error=%v, want unauthorized_client", err) } }) } @@ -132,3 +349,158 @@ func TestSoftwareStatementMergeUsesPresence(t *testing.T) { t.Fatal("omitted statement fields did not preserve body metadata") } } + +func TestSectorIdentifierValidation(t *testing.T) { + s := &Services{logger: slog.New(slog.NewTextHandler(io.Discard, nil))} + origFetch := fetchSectorIdentifierURIs + origLookup := lookupSectorIdentifierIPs + defer func() { + fetchSectorIdentifierURIs = origFetch + lookupSectorIdentifierIPs = origLookup + }() + + fetchSectorIdentifierURIs = func(ctx context.Context, uri string, validatedIPs []net.IPAddr) ([]string, error) { + if len(validatedIPs) != 1 || !validatedIPs[0].IP.Equal(net.ParseIP("93.184.216.34")) { + t.Errorf("fetch received validated addresses %v, want [93.184.216.34]", validatedIPs) + } + if uri == "https://sector.example.com/redirects.json" { + return []string{ + "https://client.example.com/callback", + "https://client.example.com/callback2", + }, nil + } + if uri == "https://sector.example.com/error.json" { + return nil, errors.New("network failure") + } + return nil, errors.New("not found") + } + lookupSectorIdentifierIPs = func(ctx context.Context, host string) ([]net.IPAddr, error) { + if host == "localhost" { + return []net.IPAddr{{IP: net.ParseIP("127.0.0.1")}}, nil + } + if host == "169.254.169.254" { + return []net.IPAddr{{IP: net.ParseIP(host)}}, nil + } + return []net.IPAddr{{IP: net.ParseIP("93.184.216.34")}}, nil + } + + for _, tc := range []struct { + name string + sectorIdentifierURI string + redirectURIs []string + subjectType string + wantCode string + }{ + { + name: "empty sector URI with pairwise subject and single redirect", + sectorIdentifierURI: "", + redirectURIs: []string{"https://client.example.com/callback"}, + subjectType: "pairwise", + wantCode: "", + }, + { + name: "empty sector URI with pairwise subject and same host redirects", + sectorIdentifierURI: "", + redirectURIs: []string{ + "https://client.example.com/callback", + "https://client.example.com/callback2", + }, + subjectType: "pairwise", + wantCode: "", + }, + { + name: "empty sector URI with pairwise subject and different host redirects", + sectorIdentifierURI: "", + redirectURIs: []string{ + "https://client.example.com/callback", + "https://other.example.net/callback", + }, + subjectType: "pairwise", + wantCode: exceptions.OAuthErrorInvalidRedirectURI, + }, + { + name: "non-HTTPS sector URI", + sectorIdentifierURI: "http://sector.example.com/redirects.json", + redirectURIs: []string{"https://client.example.com/callback"}, + subjectType: "pairwise", + wantCode: exceptions.OAuthErrorInvalidClientMetadata, + }, + { + name: "sector URI with fragment", + sectorIdentifierURI: "https://sector.example.com/redirects.json#fragment", + redirectURIs: []string{"https://client.example.com/callback"}, + subjectType: "pairwise", + wantCode: exceptions.OAuthErrorInvalidClientMetadata, + }, + { + name: "sector URI fetch error", + sectorIdentifierURI: "https://sector.example.com/error.json", + redirectURIs: []string{"https://client.example.com/callback"}, + subjectType: "pairwise", + wantCode: exceptions.OAuthErrorInvalidClientMetadata, + }, + { + name: "sector URI localhost host", + sectorIdentifierURI: "https://localhost/redirects.json", + redirectURIs: []string{"https://client.example.com/callback"}, + subjectType: "pairwise", + wantCode: exceptions.OAuthErrorInvalidClientMetadata, + }, + { + name: "sector URI private IP host", + sectorIdentifierURI: "https://169.254.169.254/redirects.json", + redirectURIs: []string{"https://client.example.com/callback"}, + subjectType: "pairwise", + wantCode: exceptions.OAuthErrorInvalidClientMetadata, + }, + { + name: "sector URI missing redirect URI", + sectorIdentifierURI: "https://sector.example.com/redirects.json", + redirectURIs: []string{ + "https://client.example.com/callback", + "https://not-in-sector.example.com/callback", + }, + subjectType: "pairwise", + wantCode: exceptions.OAuthErrorInvalidRedirectURI, + }, + { + name: "sector URI matching all redirect URIs", + sectorIdentifierURI: "https://sector.example.com/redirects.json", + redirectURIs: []string{ + "https://client.example.com/callback", + "https://client.example.com/callback2", + }, + subjectType: "pairwise", + wantCode: "", + }, + { + name: "sector URI matching with public subject type", + sectorIdentifierURI: "https://sector.example.com/redirects.json", + redirectURIs: []string{ + "https://client.example.com/callback", + }, + subjectType: "public", + wantCode: "", + }, + } { + t.Run(tc.name, func(t *testing.T) { + err := s.validateSectorIdentifier( + context.Background(), + "req-1", + tc.sectorIdentifierURI, + tc.redirectURIs, + tc.subjectType, + ) + if tc.wantCode == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } else { + if err == nil || err.Code != tc.wantCode { + t.Fatalf("got err=%v, want code %s", err, tc.wantCode) + } + } + }) + } +} + diff --git a/idp/tests/account_credentials_test.go b/idp/tests/account_credentials_test.go index 47c21ee..a09812d 100644 --- a/idp/tests/account_credentials_test.go +++ b/idp/tests/account_credentials_test.go @@ -56,7 +56,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "admin-service", Scopes: []string{"account:admin"}, TokenEndpointAuthMethod: "client_secret_jwt", - Transport: "https", ClientURI: "https://admin.example.com", SoftwareID: "admin-service", SoftwareVersion: "1.0.0", @@ -72,7 +71,6 @@ func TestCreateAccountCredentials(t *testing.T) { AssertEmpty(t, resBody.ClientSecretJWK) AssertEqual(t, resBody.TokenEndpointAuthMethod, database.AuthMethodClientSecretJwt) AssertEqual(t, resBody.Type, database.AccountCredentialsTypeService) - AssertEqual(t, resBody.Transport, database.TransportHttps) }, }, { @@ -85,7 +83,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "super-service", Scopes: []string{"account:credentials:read", "account:credentials:write"}, TokenEndpointAuthMethod: "private_key_jwt", - Transport: "https", ClientURI: "https://super.example.com", SoftwareID: "super-service", SoftwareVersion: "2.0.0", @@ -114,7 +111,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "eddsa-service", Scopes: []string{"account:credentials:read", "account:credentials:write"}, TokenEndpointAuthMethod: "private_key_jwt", - Transport: "https", ClientURI: "https://eddsa.example.com", SoftwareID: "eddsa-service", SoftwareVersion: "1.0.0", @@ -143,7 +139,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "app-service", Scopes: []string{"account:apps:read", "account:apps:write"}, TokenEndpointAuthMethod: "client_secret_post", - Transport: "https", ClientURI: "https://app.example.com", SoftwareID: "app-service", SoftwareVersion: "1.0.0", @@ -171,7 +166,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "user-service", Scopes: []string{"account:users:read", "account:users:write"}, TokenEndpointAuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://user.example.com", SoftwareID: "user-service", SoftwareVersion: "1.0.0", @@ -190,7 +184,7 @@ func TestCreateAccountCredentials(t *testing.T) { }, }, { - Name: "Should create MCP credentials with streamable_http transport", + Name: "Should create MCP credentials with no client authentication", ReqFn: func(t *testing.T) (bodies.CreateAccountCredentialsBody, string) { account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderGoogle)) accessToken, _ := GenerateTestAccountAuthTokens(t, &account) @@ -198,8 +192,7 @@ func TestCreateAccountCredentials(t *testing.T) { Type: "mcp", Name: "mcp-client", Scopes: []string{"account:admin"}, - TokenEndpointAuthMethod: "client_secret_basic", - Transport: "streamable_http", + TokenEndpointAuthMethod: "none", ClientURI: "https://mcp.example.com", SoftwareID: "mcp-client", SoftwareVersion: "1.0.0", @@ -209,17 +202,16 @@ func TestCreateAccountCredentials(t *testing.T) { AssertFn: func(t *testing.T, _ bodies.CreateAccountCredentialsBody, res *http.Response) { resBody := AssertTestResponseBody(t, res, dtos.AccountCredentialsDTO{}) AssertNotEmpty(t, resBody.ClientID) - AssertNotEmpty(t, resBody.ClientSecretID) - AssertNotEmpty(t, resBody.ClientSecret) - AssertNotEmpty(t, resBody.ClientSecretExp) + AssertEmpty(t, resBody.ClientSecretID) + AssertEmpty(t, resBody.ClientSecret) + AssertEmpty(t, resBody.ClientSecretExp) AssertEmpty(t, resBody.ClientSecretJWK) - AssertEqual(t, resBody.TokenEndpointAuthMethod, database.AuthMethodClientSecretBasic) + AssertEqual(t, resBody.TokenEndpointAuthMethod, database.AuthMethodNone) AssertEqual(t, resBody.Type, database.AccountCredentialsTypeMcp) - AssertEqual(t, resBody.Transport, database.TransportStreamableHttp) }, }, { - Name: "Should create MCP credentials with stdio transport without client auth", + Name: "Should reject MCP credentials with client authentication", ReqFn: func(t *testing.T) (bodies.CreateAccountCredentialsBody, string) { account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderGoogle)) accessToken, _ := GenerateTestAccountAuthTokens(t, &account) @@ -227,24 +219,16 @@ func TestCreateAccountCredentials(t *testing.T) { Type: "mcp", Name: "mcp-stdio", Scopes: []string{"account:admin"}, - TokenEndpointAuthMethod: "none", - Transport: "stdio", + TokenEndpointAuthMethod: "client_secret_basic", ClientURI: "https://mcp-stdio.example.com", SoftwareID: "mcp-stdio", SoftwareVersion: "1.0.0", }, accessToken }, - ExpStatus: http.StatusCreated, + ExpStatus: http.StatusBadRequest, AssertFn: func(t *testing.T, _ bodies.CreateAccountCredentialsBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AccountCredentialsDTO{}) - AssertNotEmpty(t, resBody.ClientID) - AssertEmpty(t, resBody.ClientSecretID) - AssertEmpty(t, resBody.ClientSecret) - AssertEmpty(t, resBody.ClientSecretExp) - AssertEmpty(t, resBody.ClientSecretJWK) - AssertEqual(t, resBody.TokenEndpointAuthMethod, database.AuthMethodNone) - AssertEqual(t, resBody.Type, database.AccountCredentialsTypeMcp) - AssertEqual(t, resBody.Transport, database.TransportStdio) + resBody := AssertTestResponseBody(t, res, exceptions.ErrorResponse{}) + AssertEqual(t, resBody.Message, "only auth method none is supported for mcp credentials") }, }, { @@ -257,7 +241,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "native-client", Scopes: []string{"account:admin"}, TokenEndpointAuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://native.example.com", SoftwareID: "native-client", SoftwareVersion: "1.0.0", @@ -279,7 +262,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "", Scopes: []string{"invalid:scope", "account:users:readsd"}, TokenEndpointAuthMethod: "invalid_auth_method", - Transport: "invalid_transport", ClientURI: "not-a-uri", SoftwareID: "", SoftwareVersion: "", @@ -306,7 +288,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "existing-name", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://existing.example.com", SoftwareID: "existing-service", SoftwareVersion: "1.0.0", @@ -319,7 +300,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "existing-name", Scopes: []string{"account:admin"}, TokenEndpointAuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://new.example.com", SoftwareID: "new-service", SoftwareVersion: "1.0.0", @@ -339,7 +319,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "unauthorized-service", Scopes: []string{"account:credentials:write", "account:auth_providers:read"}, TokenEndpointAuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://unauthorized.example.com", SoftwareID: "unauthorized-service", SoftwareVersion: "1.0.0", @@ -358,7 +337,6 @@ func TestCreateAccountCredentials(t *testing.T) { Name: "forbidden-service", Scopes: []string{"account:apps:read", "account:apps:write"}, TokenEndpointAuthMethod: "client_secret_post", - Transport: "https", ClientURI: "https://forbidden.example.com", SoftwareID: "forbidden-service", SoftwareVersion: "1.0.0", @@ -394,7 +372,6 @@ func TestUpdateAccountCredentials(t *testing.T) { Name: "update-cred", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://update.example.com", SoftwareID: "update-service", SoftwareVersion: "1.0.0", @@ -414,7 +391,6 @@ func TestUpdateAccountCredentials(t *testing.T) { return bodies.UpdateAccountCredentialsBody{ Name: "updated-service-name", Scopes: []string{"account:users:read"}, - Transport: "https", ClientURI: "https://updated.example.com", SoftwareVersion: "2.0.0", }, accessToken @@ -445,8 +421,7 @@ func TestUpdateAccountCredentials(t *testing.T) { CredentialsType: "mcp", Name: "mcp-update", Scopes: []string{"account:admin"}, - AuthMethod: "client_secret_basic", - Transport: "streamable_http", + AuthMethod: "none", ClientURI: "https://mcp-update.example.com", SoftwareID: "mcp-update", SoftwareVersion: "1.0.0", @@ -468,8 +443,9 @@ func TestUpdateAccountCredentials(t *testing.T) { resBody := AssertTestResponseBody(t, res, dtos.AccountCredentialsDTO{}) AssertEqual(t, resBody.ClientName, "updated-mcp-name") AssertEqual(t, len(resBody.Scopes), 2) - AssertEqual(t, resBody.Scopes[0], "account:users:read") - AssertEqual(t, resBody.Scopes[1], "account:apps:read") + scopes := utils.SliceToHashSet(resBody.Scopes) + AssertEqual(t, scopes.Contains(database.AccountCredentialsScopeAccountUsersRead), true) + AssertEqual(t, scopes.Contains(database.AccountCredentialsScopeAccountAppsRead), true) AssertEqual(t, resBody.SoftwareVersion, "2.0.0") }, PathFn: func() string { @@ -483,7 +459,6 @@ func TestUpdateAccountCredentials(t *testing.T) { return bodies.UpdateAccountCredentialsBody{ Name: "", Scopes: []string{"account:users:read", "invalid:scope"}, - Transport: "invalid_transport", ClientURI: "not-a-uri", SoftwareVersion: "", }, accessToken @@ -512,7 +487,6 @@ func TestUpdateAccountCredentials(t *testing.T) { Name: "existing-name", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://existing.example.com", SoftwareID: "existing-service", SoftwareVersion: "1.0.0", @@ -529,7 +503,6 @@ func TestUpdateAccountCredentials(t *testing.T) { Name: "other-name", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://other.example.com", SoftwareID: "other-service", SoftwareVersion: "1.0.0", @@ -542,7 +515,6 @@ func TestUpdateAccountCredentials(t *testing.T) { return bodies.UpdateAccountCredentialsBody{ Name: "existing-name", Scopes: []string{"account:users:read"}, - Transport: "https", ClientURI: "https://updated.example.com", SoftwareVersion: "2.0.0", }, accessToken @@ -564,7 +536,6 @@ func TestUpdateAccountCredentials(t *testing.T) { return bodies.UpdateAccountCredentialsBody{ Name: "new-name", Scopes: []string{"account:users:read"}, - Transport: "https", ClientURI: "https://new.example.com", SoftwareVersion: "1.0.0", }, accessToken @@ -582,7 +553,6 @@ func TestUpdateAccountCredentials(t *testing.T) { return bodies.UpdateAccountCredentialsBody{ Name: "updated-name", Scopes: []string{"account:users:read"}, - Transport: "https", ClientURI: "https://updated.example.com", SoftwareVersion: "2.0.0", }, "" @@ -607,7 +577,6 @@ func TestUpdateAccountCredentials(t *testing.T) { Name: "forbidden-update", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://forbidden.example.com", SoftwareID: "forbidden-service", SoftwareVersion: "1.0.0", @@ -620,7 +589,6 @@ func TestUpdateAccountCredentials(t *testing.T) { return bodies.UpdateAccountCredentialsBody{ Name: "updated-name", Scopes: []string{"account:users:read"}, - Transport: "https", ClientURI: "https://updated.example.com", SoftwareVersion: "2.0.0", }, accessToken @@ -654,10 +622,9 @@ func TestListAccountCredentials(t *testing.T) { for i := 0; i < n; i++ { credType := "service" authMethod := authMethods[i%len(authMethods)] - transport := "https" if i%2 == 1 { credType = "mcp" - transport = "streamable_http" + authMethod = "none" } name := "cred-" + uuid.NewString() @@ -669,7 +636,6 @@ func TestListAccountCredentials(t *testing.T) { Name: name, Scopes: []string{"account:admin"}, AuthMethod: authMethod, - Transport: transport, ClientURI: "https://" + name + ".example.com", SoftwareID: name + "-service", SoftwareVersion: "1.0.0", @@ -763,7 +729,6 @@ func TestGetSingleAccountCredentials(t *testing.T) { Name: "get-cred", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://get.example.com", SoftwareID: "get-service", SoftwareVersion: "1.0.0", @@ -834,7 +799,6 @@ func TestGetSingleAccountCredentials(t *testing.T) { Name: "forbidden-cred", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://forbidden.example.com", SoftwareID: "forbidden-service", SoftwareVersion: "1.0.0", @@ -878,7 +842,6 @@ func TestDeleteAccountCredentials(t *testing.T) { Name: "delete-cred", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://delete.example.com", SoftwareID: "delete-service", SoftwareVersion: "1.0.0", @@ -944,7 +907,6 @@ func TestDeleteAccountCredentials(t *testing.T) { Name: "forbidden-delete", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://forbidden-delete.example.com", SoftwareID: "forbidden-delete-service", SoftwareVersion: "1.0.0", @@ -988,7 +950,6 @@ func TestListAccountCredentialsSecrets(t *testing.T) { Name: "list-cred", Scopes: []string{"account:admin"}, AuthMethod: authMethods, - Transport: "https", ClientURI: "https://list.example.com", SoftwareID: "list-service", SoftwareVersion: "1.0.0", @@ -1070,7 +1031,6 @@ func TestListAccountCredentialsSecrets(t *testing.T) { Name: "forbidden-list", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://forbidden-list.example.com", SoftwareID: "forbidden-list-service", SoftwareVersion: "1.0.0", @@ -1114,7 +1074,6 @@ func TestCreateAccountCredentialsSecret(t *testing.T) { Name: "create-secret-cred", Scopes: []string{"account:admin"}, AuthMethod: authMethods, - Transport: "https", ClientURI: "https://create-secret.example.com", SoftwareID: "create-secret-service", SoftwareVersion: "1.0.0", @@ -1198,7 +1157,6 @@ func TestCreateAccountCredentialsSecret(t *testing.T) { Name: "forbidden-create-secret", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://forbidden-create-secret.example.com", SoftwareID: "forbidden-create-secret-service", SoftwareVersion: "1.0.0", @@ -1240,7 +1198,6 @@ func TestGetAccountCredentialsSecret(t *testing.T) { Name: "get-secret-cred", Scopes: []string{"account:admin"}, AuthMethod: authMethods, - Transport: "https", ClientURI: "https://get-secret.example.com", SoftwareID: "get-secret-service", SoftwareVersion: "1.0.0", @@ -1334,7 +1291,6 @@ func TestGetAccountCredentialsSecret(t *testing.T) { Name: "forbidden-get-secret", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://forbidden-get-secret.example.com", SoftwareID: "forbidden-get-secret-service", SoftwareVersion: "1.0.0", @@ -1378,7 +1334,6 @@ func TestRevokeAccountCredentialsSecret(t *testing.T) { Name: "revoke-cred", Scopes: []string{"account:admin"}, AuthMethod: authMethods, - Transport: "https", ClientURI: "https://revoke.example.com", SoftwareID: "revoke-service", SoftwareVersion: "1.0.0", @@ -1474,7 +1429,6 @@ func TestRevokeAccountCredentialsSecret(t *testing.T) { Name: "forbidden-revoke", Scopes: []string{"account:admin"}, AuthMethod: "client_secret_basic", - Transport: "https", ClientURI: "https://forbidden-revoke.example.com", SoftwareID: "forbidden-revoke-service", SoftwareVersion: "1.0.0", diff --git a/idp/tests/apps_test.go b/idp/tests/apps_test.go index 533b0bd..5218551 100644 --- a/idp/tests/apps_test.go +++ b/idp/tests/apps_test.go @@ -52,22 +52,15 @@ type createAppBody struct { Name string `json:"name"` ClientURI string `json:"client_uri"` Domain string `json:"domain,omitempty"` - Transport string `json:"transport,omitempty"` // Common optional fields UsernameColumn string `json:"username_column,omitempty"` Algorithm string `json:"algorithm,omitempty"` - // Web/SPA/Native specific fields + // Web/native specific fields TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"` RedirectURIs []string `json:"redirect_uris,omitempty"` - - // Device-specific fields - AssociatedApps []string `json:"associated_apps,omitempty"` - - // Service-specific fields - UsersAuthMethod string `json:"users_auth_method,omitempty"` - AllowedDomains []string `json:"allowed_domains,omitempty"` + GrantTypes []string `json:"grant_types,omitempty"` } func TestCreateApp(t *testing.T) { @@ -105,18 +98,19 @@ func TestCreateApp(t *testing.T) { }, }, { - Name: "Should return 201 CREATED with SPA app data", + Name: "Should return 201 CREATED with public web app data", ReqFn: func(t *testing.T) (createAppBody, string) { account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) return createAppBody{ - Type: "spa", - Name: "Test SPA App", - ClientURI: "https://test-spa-app.example.com", - Domain: "test-spa-app.example.com", - UsernameColumn: "email", - RedirectURIs: []string{"https://test-spa-app.example.com/callback"}, + Type: "web", + Name: "Test Public Web App", + ClientURI: "https://test-public-web-app.example.com", + Domain: "test-public-web-app.example.com", + UsernameColumn: "email", + TokenEndpointAuthMethod: "none", + RedirectURIs: []string{"https://test-public-web-app.example.com/callback"}, }, accessToken }, PathFn: func() string { @@ -127,7 +121,7 @@ func TestCreateApp(t *testing.T) { resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) AssertEqual(t, req.Name, resBody.ClientName) AssertEqual(t, req.ClientURI, resBody.ClientURI) - AssertEqual(t, database.AppTypeSpa, resBody.AppType) + AssertEqual(t, database.AppTypeWeb, resBody.AppType) AssertNotEmpty(t, resBody.ClientID) AssertEmpty(t, resBody.ClientSecretID) AssertEmpty(t, resBody.ClientSecret) @@ -161,79 +155,21 @@ func TestCreateApp(t *testing.T) { AssertEmpty(t, resBody.ClientSecret) }, }, - { - Name: "Should return 201 CREATED with backend app data", - ReqFn: func(t *testing.T) (createAppBody, string) { - account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) - accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) - - return createAppBody{ - Type: "backend", - Name: "Test Backend App", - ClientURI: "https://test-backend-app.example.com", - UsernameColumn: "email", - TokenEndpointAuthMethod: "private_key_jwt", - Algorithm: "EdDSA", - }, accessToken - }, - PathFn: func() string { - return v1Path + paths.AppsBase - }, - ExpStatus: http.StatusCreated, - AssertFn: func(t *testing.T, req createAppBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) - AssertEqual(t, req.Name, resBody.ClientName) - AssertEqual(t, req.ClientURI, resBody.ClientURI) - AssertEqual(t, database.AppTypeBackend, resBody.AppType) - AssertNotEmpty(t, resBody.ClientID) - AssertNotEmpty(t, resBody.ClientSecretID) - AssertNotEmpty(t, resBody.ClientSecretJWK) - }, - }, - { - Name: "Should return 201 CREATED with device app data", - ReqFn: func(t *testing.T) (createAppBody, string) { - account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) - accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) - return createAppBody{ - Type: "device", - Name: "Test Device App", - ClientURI: "https://test-device-app.example.com", - UsernameColumn: "email", - AssociatedApps: []string{}, - }, accessToken - }, - PathFn: func() string { - return v1Path + paths.AppsBase - }, - ExpStatus: http.StatusCreated, - AssertFn: func(t *testing.T, req createAppBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) - AssertEqual(t, req.Name, resBody.ClientName) - AssertEqual(t, req.ClientURI, resBody.ClientURI) - AssertEqual(t, database.AppTypeDevice, resBody.AppType) - AssertNotEmpty(t, resBody.ClientID) - AssertEmpty(t, resBody.ClientSecretID) - AssertEmpty(t, resBody.ClientSecret) - }, - }, { - Name: "Should return 201 CREATED with service app data", + Name: "Should return 201 CREATED with a web service client", ReqFn: func(t *testing.T) (createAppBody, string) { account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) return createAppBody{ - Type: "service", + Type: "web", Name: "Test Service App", ClientURI: "https://test-service-app.example.com", Domain: "test-service-app.example.com", - Transport: "https", TokenEndpointAuthMethod: "private_key_jwt", Algorithm: "ES256", - UsersAuthMethod: "client_secret_basic", - AllowedDomains: []string{}, + GrantTypes: []string{"client_credentials", "urn:ietf:params:oauth:grant-type:jwt-bearer"}, }, accessToken }, PathFn: func() string { @@ -244,69 +180,16 @@ func TestCreateApp(t *testing.T) { resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) AssertEqual(t, req.Name, resBody.ClientName) AssertEqual(t, req.ClientURI, resBody.ClientURI) - AssertEqual(t, database.AppTypeService, resBody.AppType) + AssertEqual(t, database.AppTypeWeb, resBody.AppType) + AssertEqual(t, len(resBody.GrantTypes), 2) + AssertEqual(t, resBody.GrantTypes[0], database.GrantTypeClientCredentials) + AssertEqual(t, resBody.GrantTypes[1], database.GrantTypeUrnIetfParamsOauthGrantTypeJwtBearer) AssertNotEmpty(t, resBody.ClientID) AssertNotEmpty(t, resBody.ClientSecretID) AssertNotEmpty(t, resBody.ClientSecretJWK) }, }, - { - Name: "Should return 201 CREATED with a stdio MCP app data", - ReqFn: func(t *testing.T) (createAppBody, string) { - account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) - accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) - return createAppBody{ - Type: "mcp", - Name: "Test MCP App", - ClientURI: "https://test-mcp-app.example.com", - Domain: "test-custom-app.example.com", - Transport: "stdio", - TokenEndpointAuthMethod: "client_secret_basic", - RedirectURIs: []string{"https://test-mcp-app.example.com/callback"}, - }, accessToken - }, - PathFn: func() string { - return v1Path + paths.AppsBase - }, - ExpStatus: http.StatusCreated, - AssertFn: func(t *testing.T, req createAppBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) - AssertEqual(t, req.Name, resBody.ClientName) - AssertEqual(t, req.ClientURI, resBody.ClientURI) - AssertEqual(t, database.AppTypeMcp, resBody.AppType) - AssertNotEmpty(t, resBody.ClientID) - AssertNotEmpty(t, resBody.ClientSecretID) - }, - }, - { - Name: "Should return 201 CREATED with a streamable_http MCP app data", - ReqFn: func(t *testing.T) (createAppBody, string) { - account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) - accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) - - return createAppBody{ - Type: "mcp", - Name: "Test MCP App", - ClientURI: "https://test-mcp-app.example.com", - Transport: "streamable_http", - TokenEndpointAuthMethod: "client_secret_basic", - RedirectURIs: []string{"https://test-mcp-app.example.com/callback"}, - }, accessToken - }, - PathFn: func() string { - return v1Path + paths.AppsBase - }, - ExpStatus: http.StatusCreated, - AssertFn: func(t *testing.T, req createAppBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) - AssertEqual(t, req.Name, resBody.ClientName) - AssertEqual(t, req.ClientURI, resBody.ClientURI) - AssertEqual(t, database.AppTypeMcp, resBody.AppType) - AssertNotEmpty(t, resBody.ClientID) - AssertEmpty(t, resBody.ClientSecretID) - }, - }, { Name: "Should return 400 BAD REQUEST if validation fails", ReqFn: func(t *testing.T) (createAppBody, string) { @@ -393,28 +276,26 @@ func TestListApps(t *testing.T) { Algorithm: "ES256", ClientURI: "https://test-web-app.example.com", Domain: "test-web-app.example.com", - Transport: "https", RedirectURIs: []string{"https://test-web-app.example.com/callback"}, }) if err != nil { t.Fatal("Failed to create test web app", err) } - _, err = tServs.CreateSPANativeApp(context.Background(), services.CreateSPANativeAppOptions{ + _, err = tServs.CreateNativeApp(context.Background(), services.CreateNativeAppOptions{ RequestID: uuid.New().String(), AccountPublicID: account.PublicID, AccountVersion: account.Version(), - AppType: database.AppTypeSpa, + AppType: database.AppTypeNative, CreationMethod: database.CreationMethodManual, - Name: "Test SPA App", + Name: "Test Native Public App", UsernameColumn: "email", - ClientURI: "https://test-spa-app.example.com", - Domain: "test-spa-app.example.com", - Transport: "https", - RedirectURIs: []string{"https://test-spa-app.example.com/callback"}, + ClientURI: "https://test-native-public-app.example.com", + Domain: "test-native-public-app.example.com", + RedirectURIs: []string{"https://test-native-public-app.example.com/callback"}, }) if err != nil { - t.Fatal("Failed to create test SPA app", err) + t.Fatal("Failed to create test native public app", err) } return nil, accessToken @@ -448,7 +329,6 @@ func TestListApps(t *testing.T) { Algorithm: "ES256", ClientURI: "https://filtered-web-app.example.com", Domain: "filtered-web-app.example.com", - Transport: "https", RedirectURIs: []string{"https://filtered-web-app.example.com/callback"}, }) if err != nil { @@ -487,7 +367,6 @@ func TestListApps(t *testing.T) { Algorithm: "ES256", ClientURI: "https://test-web-app.example.com", Domain: "test-web-app.example.com", - Transport: "https", RedirectURIs: []string{"https://test-web-app.example.com/callback"}, }) if err != nil { @@ -637,7 +516,6 @@ func CreateTestWebApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { Algorithm: "ES256", ClientURI: "https://test-app.example.com", Domain: "test-app.example.com", - Transport: "https", RedirectURIs: []string{"https://test-app.example.com/callback"}, AllowUserRegistration: true, }) @@ -648,35 +526,11 @@ func CreateTestWebApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { return appDTO } -func CreateTestSPAApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { - tServs := GetTestServices(t) - ctx := context.Background() - - appDTO, err := tServs.CreateSPANativeApp(ctx, services.CreateSPANativeAppOptions{ - RequestID: uuid.New().String(), - AccountPublicID: account.PublicID, - AccountVersion: account.Version(), - AppType: database.AppTypeSpa, - CreationMethod: database.CreationMethodManual, - Name: "Test SPA App", - UsernameColumn: "email", - ClientURI: "https://test-spa-app.example.com", - Domain: "test-spa-app.example.com", - Transport: "https", - RedirectURIs: []string{"https://test-spa-app.example.com/callback"}, - }) - if err != nil { - t.Fatal("Failed to create test SPA app", err) - } - - return appDTO -} - func CreateTestNativeApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { tServs := GetTestServices(t) ctx := context.Background() - appDTO, err := tServs.CreateSPANativeApp(ctx, services.CreateSPANativeAppOptions{ + appDTO, err := tServs.CreateNativeApp(ctx, services.CreateNativeAppOptions{ RequestID: uuid.New().String(), AccountPublicID: account.PublicID, AccountVersion: account.Version(), @@ -685,7 +539,6 @@ func CreateTestNativeApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { Name: "Test Native App", ClientURI: "https://test-native-app.example.com", Domain: "test-native-app.example.com", - Transport: "https", RedirectURIs: []string{"com.testnativeapp://callback"}, }) if err != nil { @@ -695,74 +548,6 @@ func CreateTestNativeApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { return appDTO } -func CreateTestBackendApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { - tServs := GetTestServices(t) - ctx := context.Background() - - appDTO, err := tServs.CreateBackendApp(ctx, services.CreateBackendAppOptions{ - RequestID: uuid.New().String(), - AccountPublicID: account.PublicID, - AccountVersion: account.Version(), - CreationMethod: database.CreationMethodManual, - Name: "Test Backend App", - UsernameColumn: "email", - AuthMethod: "private_key_jwt", - Algorithm: "EdDSA", - ClientURI: "https://test-backend-app.example.com", - Domain: "test-backend-app.example.com", - }) - if err != nil { - t.Fatal("Failed to create test backend app", err) - } - - return appDTO -} - -func CreateTestDeviceApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { - tServs := GetTestServices(t) - ctx := context.Background() - - appDTO, err := tServs.CreateDeviceApp(ctx, services.CreateDeviceAppOptions{ - RequestID: uuid.New().String(), - AccountPublicID: account.PublicID, - AccountVersion: account.Version(), - CreationMethod: database.CreationMethodManual, - Name: "Test Device App", - ClientURI: "https://test-device-app.example.com", - UsernameColumn: "email", - AssociatedApps: []string{}, - }) - if err != nil { - t.Fatal("Failed to create test device app", err) - } - - return appDTO -} - -func CreateTestServiceApp(t *testing.T, account *dtos.AccountDTO) dtos.AppDTO { - tServs := GetTestServices(t) - ctx := context.Background() - - appDTO, err := tServs.CreateServiceApp(ctx, services.CreateServiceAppOptions{ - RequestID: uuid.New().String(), - AccountPublicID: account.PublicID, - AccountVersion: account.Version(), - CreationMethod: database.CreationMethodManual, - Name: "Test Service App", - AuthMethod: "private_key_jwt", - Algorithm: "ES256", - ClientURI: "https://test-service-app.example.com", - Domain: "test-service-app.example.com", - UsersAuthMethod: "client_secret_basic", - AllowedDomains: []string{"example.com"}, - }) - if err != nil { - t.Fatal("Failed to create test service app", err) - } - - return appDTO -} - type updateAppBody struct { Name string `json:"name"` ClientURI string `json:"client_uri"` @@ -826,31 +611,7 @@ func TestUpdateApp(t *testing.T) { AssertEqual(t, req.SoftwareVersion, resBody.SoftwareVersion) }, }, - { - Name: "Should return 200 OK updating SPA app data", - ReqFn: func(t *testing.T) (updateAppBody, string) { - account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) - app := CreateTestSPAApp(t, &account) - setAppClientID(app) - accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) - return updateAppBody{ - Name: "Updated SPA App", - ClientURI: "https://updated-spa-app.example.com", - UsernameColumn: "email", - RedirectURIs: []string{"https://updated-spa-app.example.com/callback"}, - }, accessToken - }, - PathFn: func() string { - return v1Path + paths.AppsBase + "/" + appClientID - }, - ExpStatus: http.StatusOK, - AssertFn: func(t *testing.T, req updateAppBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) - AssertEqual(t, req.Name, resBody.ClientName) - AssertEqual(t, req.ClientURI, resBody.ClientURI) - }, - }, { Name: "Should return 200 OK updating native app data", ReqFn: func(t *testing.T) (updateAppBody, string) { @@ -875,79 +636,7 @@ func TestUpdateApp(t *testing.T) { AssertEqual(t, req.ClientURI, resBody.ClientURI) }, }, - { - Name: "Should return 200 OK updating backend app data", - ReqFn: func(t *testing.T) (updateAppBody, string) { - account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) - app := CreateTestBackendApp(t, &account) - setAppClientID(app) - accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) - - return updateAppBody{ - Name: "Updated Backend App", - ClientURI: "https://updated-backend-app.example.com", - UsernameColumn: "email", - }, accessToken - }, - PathFn: func() string { - return v1Path + paths.AppsBase + "/" + appClientID - }, - ExpStatus: http.StatusOK, - AssertFn: func(t *testing.T, req updateAppBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) - AssertEqual(t, req.Name, resBody.ClientName) - AssertEqual(t, req.ClientURI, resBody.ClientURI) - }, - }, - { - Name: "Should return 200 OK updating device app data", - ReqFn: func(t *testing.T) (updateAppBody, string) { - account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) - app := CreateTestDeviceApp(t, &account) - setAppClientID(app) - accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) - return updateAppBody{ - Name: "Updated Device App", - ClientURI: "https://updated-device-app.example.com", - UsernameColumn: "email", - AssociatedApps: []string{}, - }, accessToken - }, - PathFn: func() string { - return v1Path + paths.AppsBase + "/" + appClientID - }, - ExpStatus: http.StatusOK, - AssertFn: func(t *testing.T, req updateAppBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) - AssertEqual(t, req.Name, resBody.ClientName) - AssertEqual(t, req.ClientURI, resBody.ClientURI) - }, - }, - { - Name: "Should return 200 OK updating service app data", - ReqFn: func(t *testing.T) (updateAppBody, string) { - account := CreateTestAccount(t, GenerateFakeAccountData(t, services.AuthProviderLocal)) - app := CreateTestServiceApp(t, &account) - setAppClientID(app) - accessToken := GenerateScopedAccountAccessToken(t, &account, []tokens.AccountScope{tokens.AccountScopeAppsWrite}) - - return updateAppBody{ - Name: "Updated Service App", - ClientURI: "https://updated-service-app.example.com", - AllowedDomains: []string{"example.com"}, - }, accessToken - }, - PathFn: func() string { - return v1Path + paths.AppsBase + "/" + appClientID - }, - ExpStatus: http.StatusOK, - AssertFn: func(t *testing.T, req updateAppBody, res *http.Response) { - resBody := AssertTestResponseBody(t, res, dtos.AppDTO{}) - AssertEqual(t, req.Name, resBody.ClientName) - AssertEqual(t, req.ClientURI, resBody.ClientURI) - }, - }, { Name: "Should return 404 NOT FOUND if app does not exist", ReqFn: func(t *testing.T) (updateAppBody, string) { diff --git a/idp/tests/common_test.go b/idp/tests/common_test.go index a41199e..1a2a498 100644 --- a/idp/tests/common_test.go +++ b/idp/tests/common_test.go @@ -303,7 +303,7 @@ func performTestRequest(t *testing.T, app *fiber.App, delayMs int, method, path, req.Header.Set("Authorization", tokenType+" "+accessToken) } - resp, err := app.Test(req, fiber.TestConfig{Timeout: 2 * time.Second}) + resp, err := app.Test(req, fiber.TestConfig{Timeout: 60 * time.Second}) if err != nil { t.Fatal("Failed to perform request", err) } @@ -337,9 +337,14 @@ func PerformTestRequestWithURLEncodedBody(t *testing.T, app *fiber.App, delayMs } func AssertTestStatusCode(t *testing.T, resp *http.Response, expectedStatusCode int) { + t.Helper() if resp.StatusCode != expectedStatusCode { - t.Logf("Status Code: %d", resp.StatusCode) - t.Fatal("Failed to assert status code") + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Status Code: %d, expected %d; failed to read response body: %v", resp.StatusCode, expectedStatusCode, err) + } + resp.Body = io.NopCloser(bytes.NewReader(body)) + t.Fatalf("Status Code: %d, expected %d; response body: %s", resp.StatusCode, expectedStatusCode, body) } } diff --git a/idp/tests/dynamic_registration_test.go b/idp/tests/dynamic_registration_test.go index c3d12aa..31bc1b8 100644 --- a/idp/tests/dynamic_registration_test.go +++ b/idp/tests/dynamic_registration_test.go @@ -91,8 +91,12 @@ func approveSoftwareStatementKey(t *testing.T, account dtos.AccountDTO, publicJS t.Helper() ctx := context.Background() db := GetTestDatabase(t) + dek, err := db.FindValidGlobalDataEncryptionKey(ctx, time.Now().Add(-2*time.Hour)) + if err != nil { + t.Fatal(err) + } key, err := db.CreateCredentialsKey(ctx, database.CreateCredentialsKeyParams{ - AccountID: account.ID(), PublicKid: kid, PublicKey: publicJSON, + AccountID: account.ID(), PublicKid: kid, PublicKey: publicJSON, DekKid: dek.Kid, CryptoSuite: database.TokenCryptoSuiteEdDSA, Usage: usage, ExpiresAt: time.Now().Add(time.Hour), }) if err != nil { diff --git a/idp/tests/oauth_test.go b/idp/tests/oauth_test.go index 2b7ea0e..4895bba 100644 --- a/idp/tests/oauth_test.go +++ b/idp/tests/oauth_test.go @@ -731,7 +731,6 @@ func TestOAuthToken(t *testing.T) { AuthMethod: "private_key_jwt", Domain: "issuer.example.com", ClientURI: "https://issuer.example.com", - Transport: "https", SoftwareID: "test-software", SoftwareVersion: "1.0.0", Contacts: []string{"test@example.com"}, @@ -814,7 +813,6 @@ func TestOAuthToken(t *testing.T) { AuthMethod: am, Domain: "issuer.example.com", ClientURI: "https://issuer.example.com", - Transport: "https", SoftwareID: "test-software", SoftwareVersion: "1.0.0", Contacts: []string{"test@example.com"}, diff --git a/init-db.sh b/init-db.sh index 9383519..018edf3 100755 --- a/init-db.sh +++ b/init-db.sh @@ -2,7 +2,7 @@ set -e # Define multiple databases -DATABASES="infisical idp idp_test" +DATABASES="idp idp_test" # Loop through and create each database for db in $DATABASES; do diff --git a/project.md b/project.md index 781d161..265ccb8 100644 --- a/project.md +++ b/project.md @@ -19,36 +19,33 @@ - Multiple app types creation - web - native - - spa - - backend - - device - - service - - mcp - Add support for multiple 2FA types +- Make refresh tokens whitelisted not blacklisted +- Add grants to control refresh token ### IDP On-Going - Add OAuth Dynamic Registration for: - accounts - apps -- Make refresh tokens whitelisted not blacklisted -- Add grants to control refresh token ### IDP Todo +- Update serial to big serial for scalability support +- OAuth Client ID Metadata Document - User authentication for each app type: - - web - - native & spa - - backend - - device - - service - - MCP + - web (including public SPA clients and service clients using client_credentials or JWT bearer grants) + - native - Custom External Providers - Account key generation - Add Passkey (WebAuthn) support - Dynamic OIDC configs - Separate signing, encryption, and decryption into a KMS service +### IDP Section For Extra Human Review + +- Dynamic Registration against the OpenID and OAuth 2.0 standards. + ## Mailer ### Mailer Done