diff --git a/assets/templates/app.gohtml b/assets/templates/app.gohtml index bbfc42672..2eb104263 100644 --- a/assets/templates/app.gohtml +++ b/assets/templates/app.gohtml @@ -5,7 +5,8 @@
- {{if .config.LegalInfo}} + {{if .error }}{{ .error }} + {{else if .config.LegalInfo}} {{if .config.LegalUrl}}{{ .config.LegalInfo }} {{else}}{{ .config.LegalInfo }}{{end}} {{else}} diff --git a/assets/templates/auth.gohtml b/assets/templates/auth.gohtml new file mode 100644 index 000000000..95f2af4eb --- /dev/null +++ b/assets/templates/auth.gohtml @@ -0,0 +1,35 @@ + + + + + + + {{if and .config.SiteCaption .config.Sponsor }}{{ .config.SiteCaption }}{{else}}{{ .config.Name }}{{end}} + {{template "favicons.gohtml" .}} + + + + + +{{template "app.gohtml" .}} + + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2ab3bc139..fe1d7cce5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1876,9 +1876,9 @@ } }, "node_modules/@csstools/cascade-layer-name-parser": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.11.tgz", - "integrity": "sha512-yhsonEAhaWRQvHFYhSzOUobH2Ev++fMci+ppFRagw0qVSPlcPV4FnNmlwpM/b2BM10ZeMRkVV4So6YRswD0O0w==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.12.tgz", + "integrity": "sha512-iNCCOnaoycAfcIot3v/orjkTol+j8+Z5xgpqxUpZSdqeaxCADQZtldHhlvzDipmi7OoWdcJUO6DRZcnkMSBEIg==", "funding": [ { "type": "github", @@ -1894,14 +1894,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2" } }, "node_modules/@csstools/color-helpers": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.2.0.tgz", - "integrity": "sha512-hJJrSBzbfGxUsaR6X4Bzd/FLx0F1ulKnR5ljY9AiXCtsR+H+zSWQDFWlKES1BRaVZTDHLpIIHS9K2o0h+JLlrg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.2.1.tgz", + "integrity": "sha512-CEypeeykO9AN7JWkr1OEOQb0HRzZlPWGwV0Ya6DuVgFdDi6g3ma/cPZ5ZPZM4AWQikDpq/0llnGGlIL+j8afzw==", "funding": [ { "type": "github", @@ -1918,9 +1918,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.2.2.tgz", - "integrity": "sha512-0owrl7AruDRKAxoSIW8XzJdz7GnuW3AOj4rYLfmXsoKIX2ZZzttzGXoiC8n8V08X7wIBlEWWVB4C8fAN18+I6Q==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.2.3.tgz", + "integrity": "sha512-rlOh81K3CvtY969Od5b1h29YT6MpCHejMCURKrRrXFeCpz67HGaBNvBmWT5S7S+CKn+V7KJ+qxSmK8jNd/aZWA==", "funding": [ { "type": "github", @@ -1936,14 +1936,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2" } }, "node_modules/@csstools/css-color-parser": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-2.0.2.tgz", - "integrity": "sha512-Agx2YmxTcZ7TfB7KNZQ+iekaxbWSdblvtA35aTwE3KfuYyjOlCg3P4KGGdQF/cjm1pHWVSBo5duF/BRfZ8s07A==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-2.0.3.tgz", + "integrity": "sha512-Qqhb5I/gEh1wI4brf6Kmy0Xn4J1IqO8OTDKWGRsBYtL4bGkHcV9i0XI2Mmo/UYFtSRoXW/RmKTcMh6sCI433Cw==", "funding": [ { "type": "github", @@ -1956,21 +1956,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^4.2.0", - "@csstools/css-calc": "^1.2.2" + "@csstools/color-helpers": "^4.2.1", + "@csstools/css-calc": "^1.2.3" }, "engines": { "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz", - "integrity": "sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.0.tgz", + "integrity": "sha512-qvBMcOU/uWFCH/VO0MYe0AMs0BGMWAt6FTryMbFIKYtZtVnqTZtT8ktv5o718llkaGZWomJezJZjq3vJDHeJNQ==", "funding": [ { "type": "github", @@ -1986,13 +1986,13 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-tokenizer": "^2.3.2" } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.3.1.tgz", - "integrity": "sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.3.2.tgz", + "integrity": "sha512-0xYOf4pQpAaE6Sm2Q0x3p25oRukzWQ/O8hWVvhIt9Iv98/uu053u2CGm/g3kJ+P0vOYTAYzoU8Evq2pg9ZPXtw==", "funding": [ { "type": "github", @@ -2009,9 +2009,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.11.tgz", - "integrity": "sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA==", + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.12.tgz", + "integrity": "sha512-t1/CdyVJzOQUiGUcIBXRzTAkWTFPxiPnoKwowKW2z9Uj78c2bBWI/X94BeVfUwVq1xtCjD7dnO8kS6WONgp8Jw==", "funding": [ { "type": "github", @@ -2027,8 +2027,8 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2" } }, "node_modules/@csstools/postcss-cascade-layers": { @@ -2058,9 +2058,9 @@ } }, "node_modules/@csstools/postcss-color-function": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.16.tgz", - "integrity": "sha512-KtmXfckANSKsLBoTQCzggvKft1cmmmDKYjFO4yVlB23nWUgGInVBTE9T5JLmH29NNdTWSEPLWPUxoQ6XiIEn2Q==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.17.tgz", + "integrity": "sha512-hi6g5KHMvxpxf01LCVu5xnNxX5h2Vkn9aKRmspn2esWjWtshuTXVOavTjwvogA+Eycm9Rn21QTYNU+qbKw6IeQ==", "funding": [ { "type": "github", @@ -2073,9 +2073,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -2087,9 +2087,9 @@ } }, "node_modules/@csstools/postcss-color-mix-function": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.16.tgz", - "integrity": "sha512-BJnD1M5Pdypl1cJuwGuzVC52PqgzaObsDLu34jgf+QU7daVFqz432PvpqvXTmfTSNt4OckOT1QIzWexEFlDNXw==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.17.tgz", + "integrity": "sha512-Y65GHGCY1R+9+/5KrJjN7gAF1NZydng4AGknMggeUJIyo2ckLb4vBrlDmpIcHDdjQtV5631j1hxvalVTbpoiFw==", "funding": [ { "type": "github", @@ -2102,9 +2102,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -2116,9 +2116,9 @@ } }, "node_modules/@csstools/postcss-exponential-functions": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.7.tgz", - "integrity": "sha512-9usBPQX74OhiF/VuaVrp44UAPzqbKNyoaxEa6tbEXiFp+OAm3yB/TLRKyPUWg5tvvHGCduGJVdJJB3w8c8NBtA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.8.tgz", + "integrity": "sha512-/4WHpu4MrCCsUWRaDreyBcdF+5xnudk1JJLg6aWREeMaSpr3vsD0eywmOXct3xUm28TCqKS//S86IlcDJJdzoQ==", "funding": [ { "type": "github", @@ -2131,9 +2131,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-calc": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-calc": "^1.2.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2169,9 +2169,9 @@ } }, "node_modules/@csstools/postcss-gamut-mapping": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.9.tgz", - "integrity": "sha512-JmOeiBJj1RJriAkr+aLBaiYUpEqdNOIo3ERQ5a4uNzy18upzrQ6tz7m2Vt1GQpJ62zQj7rC5PjAhCoZCoyE31g==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.10.tgz", + "integrity": "sha512-iPz4/cO8YiNjAYdtAiKGBdKZdFlAvDtUr2AgvAMxCa83e9MwTIKmsJZC3Frw7VYmkfknmdElEZr1FJU+PmB2PA==", "funding": [ { "type": "github", @@ -2184,9 +2184,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2196,9 +2196,9 @@ } }, "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.17.tgz", - "integrity": "sha512-qSNIqzLPKd2SadfWwHZv42lDRyYlLaM+Vx5rRIsnYCZbQxzFfe1XAwssrcCsHgba5bA6bi5oDoFCx0W+PRCpfw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.18.tgz", + "integrity": "sha512-rZH7RnNYY911I/n8+DRrcri89GffptdyuFDGGj/UbxDISFirdR1uI/wcur9KYR/uFHXqrnJjrfi1cisfB7bL+g==", "funding": [ { "type": "github", @@ -2211,9 +2211,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -2225,9 +2225,9 @@ } }, "node_modules/@csstools/postcss-hwb-function": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.15.tgz", - "integrity": "sha512-l34fRiZ7o5+pULv7OplXniBTU4TuKYNNOv0abuvUanddWGSy3+YHlMKUSgcVFo0d1DorxPAhJSTCrugl+4OmMQ==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.16.tgz", + "integrity": "sha512-nlC4D5xB7pomgR4kDZ1lqbVqrs6gxPqsM2OE5CkCn0EqCMxtqqtadtbK2dcFwzyujv3DL4wYNo+fgF4rJgLPZA==", "funding": [ { "type": "github", @@ -2240,9 +2240,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -2329,9 +2329,9 @@ } }, "node_modules/@csstools/postcss-light-dark-function": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-1.0.5.tgz", - "integrity": "sha512-kKM9dtEaVmSTb3scL2pgef62KyWv6SK19JiAnCCuiDhlRE6PADKzaPPBXmP3qj4IEgIH+cQhdEosB0eroU6Fnw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-1.0.6.tgz", + "integrity": "sha512-bu+cxKpcTrMDMkVCv7QURwKNPZEuXA3J0Udvz3HfmQHt4+OIvvfvDpTgejFXdOliCU4zK9/QdqebPcYneygZtg==", "funding": [ { "type": "github", @@ -2344,8 +2344,8 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -2448,9 +2448,9 @@ } }, "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.9.tgz", - "integrity": "sha512-iBBJuExgHwedFH9AqNOHWzZFgYnt17zhu1qWjmSihu1P5pw0lIG9q5t3uIgJJFDNmYoOGfBKan66z9u1QH8yBQ==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.10.tgz", + "integrity": "sha512-nGP0KanI/jXrUMpaIBz6mdy/vNs3d/cjbNYuoEc7lCdNkntmxZvwxC2zIKI8QzGWaYsh9jahozMVceZ0jNyjgg==", "funding": [ { "type": "github", @@ -2463,7 +2463,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/utilities": "^1.0.0" }, "engines": { @@ -2474,9 +2474,9 @@ } }, "node_modules/@csstools/postcss-media-minmax": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.6.tgz", - "integrity": "sha512-bc0frf2Lod53j6wEHVsaVElfvCf6uhc96v99M/wUfer4MmNYfO3YLx1kFuB8xXvb0AXiWx4fohCJqemHV3bfRg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.7.tgz", + "integrity": "sha512-AjLG+vJvhrN2geUjYNvzncW1TJ+vC4QrVPGrLPxOSJ2QXC94krQErSW4aXMj0b13zhvVWeqf2NHIOVQknqV9cg==", "funding": [ { "type": "github", @@ -2489,10 +2489,10 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-calc": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", - "@csstools/media-query-list-parser": "^2.1.11" + "@csstools/css-calc": "^1.2.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", + "@csstools/media-query-list-parser": "^2.1.12" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2502,9 +2502,9 @@ } }, "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.9.tgz", - "integrity": "sha512-PR0s3tFSxPoKoPLoKuiZuYhwQC5bQxq/gFfywX2u/kh8rMzesARPZYKxE71I3jHWi6KDHGZl9Xb5xcFPwtvLiQ==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.10.tgz", + "integrity": "sha512-DXae3i7OYJTejxcoUuf/AOIpy+6FWfGGKo/I3WefZI538l3k+ErU6V2xQOx/UmUXT2FDIdE1Ucl9JkZib2rEsA==", "funding": [ { "type": "github", @@ -2517,9 +2517,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", - "@csstools/media-query-list-parser": "^2.1.11" + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", + "@csstools/media-query-list-parser": "^2.1.12" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2580,9 +2580,9 @@ } }, "node_modules/@csstools/postcss-oklab-function": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.16.tgz", - "integrity": "sha512-zm8nND+EraZrmbO4mgcT8FrJrAQUfWNfMmbV5uTCpWtAcO5ycX3E3bO8T1TjczKYRxC5QMM/91n9YExYCF4Mvw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.17.tgz", + "integrity": "sha512-kIng3Xmw6NKUvD/eEoHGwbyDFXDsuzsVGtNo3ndgZYYqy+DLiD+3drxwRKiViE5LUieLB1ERczXpLVmpSw61eg==", "funding": [ { "type": "github", @@ -2595,9 +2595,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -2634,9 +2634,9 @@ } }, "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.16.tgz", - "integrity": "sha512-TSM8fVqJkT8JZDranZPnkpxjU/Q1sNR192lXMND+EcKOUjYa6uYpGSfHgjnWjCRiBSciettS+sL7y9wmnas7qQ==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.17.tgz", + "integrity": "sha512-EVckAtG8bocItZflXLJ50Su+gwg/4Jhkz1BztyNsT0/svwS6QMAeLjyUA75OsgtejNWQHvBMWna4xc9LCqdjrQ==", "funding": [ { "type": "github", @@ -2649,9 +2649,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -2688,9 +2688,9 @@ } }, "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.8.tgz", - "integrity": "sha512-X76+thsvsmH/SkqVbN+vjeFKe1ABGLRx8/Wl68QTb/zvJWdzgx5S/nbszZP5O3nTRc5eI8NxIOrQUiy30fR+0g==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.9.tgz", + "integrity": "sha512-uAw1J8hiZ0mM1DLaziI7CP5oagSwDnS5kufuROGIJFzESYfTqNVS3b7FgDZto9AxXdkwI+Sn48+cvG8PwzGMog==", "funding": [ { "type": "github", @@ -2703,9 +2703,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-calc": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-calc": "^1.2.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2715,9 +2715,9 @@ } }, "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.6.tgz", - "integrity": "sha512-Q8HEu4AEiwNVZBD6+DpQ8M9SajpMow4+WtmndWIAv8qxDtDYL4JK1xXWkhOGk28PrcJawOvkrEZ8Ri59UN1TJw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.7.tgz", + "integrity": "sha512-+cptcsM5r45jntU6VjotnkC9GteFR7BQBfZ5oW7inLCxj7AfLGAzMbZ60hKTP13AULVZBdxky0P8um0IBfLHVA==", "funding": [ { "type": "github", @@ -2730,7 +2730,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/color-helpers": "^4.2.0", + "@csstools/color-helpers": "^4.2.1", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -2741,9 +2741,9 @@ } }, "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.8.tgz", - "integrity": "sha512-zEzyGriPqoIYFgHJqWNy8bmoxjM4+ONyTap1ZzQK/Lll/VsCYvx0IckB33W/u89uLSVeeB8xC7uTrkoQ7ogKyQ==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.9.tgz", + "integrity": "sha512-rCAtKX3EsH91ZIHoxFzAAcMQeQCS+PsjzHl6fvsGXz/SV3lqzSmO7MWgFXyPktC2zjZXgOObAJ/2QkhMqVpgNg==", "funding": [ { "type": "github", @@ -2756,9 +2756,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-calc": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-calc": "^1.2.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2880,9 +2880,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", - "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -3619,42 +3619,42 @@ "license": "ISC" }, "node_modules/@vue/compiler-core": { - "version": "3.4.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.30.tgz", - "integrity": "sha512-ZL8y4Xxdh8O6PSwfdZ1IpQ24PjTAieOz3jXb/MDTfDtANcKBMxg1KLm6OX2jofsaQGYfIVzd3BAG22i56/cF1w==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", "license": "MIT", "optional": true, "dependencies": { "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.30", + "@vue/shared": "3.4.31", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.30.tgz", - "integrity": "sha512-+16Sd8lYr5j/owCbr9dowcNfrHd+pz+w2/b5Lt26Oz/kB90C9yNbxQ3bYOvt7rI2bxk0nqda39hVcwDFw85c2Q==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", "license": "MIT", "optional": true, "dependencies": { - "@vue/compiler-core": "3.4.30", - "@vue/shared": "3.4.30" + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.30.tgz", - "integrity": "sha512-8vElKklHn/UY8+FgUFlQrYAPbtiSB2zcgeRKW7HkpSRn/JjMRmZvuOtwDx036D1aqKNSTtXkWRfqx53Qb+HmMg==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", "license": "MIT", "optional": true, "dependencies": { "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.30", - "@vue/compiler-dom": "3.4.30", - "@vue/compiler-ssr": "3.4.30", - "@vue/shared": "3.4.30", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.38", @@ -3662,14 +3662,14 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.30.tgz", - "integrity": "sha512-ZJ56YZGXJDd6jky4mmM0rNaNP6kIbQu9LTKZDhcpddGe/3QIalB1WHHmZ6iZfFNyj5mSypTa4+qDJa5VIuxMSg==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", "license": "MIT", "optional": true, "dependencies": { - "@vue/compiler-dom": "3.4.30", - "@vue/shared": "3.4.30" + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" } }, "node_modules/@vue/component-compiler-utils": { @@ -3747,9 +3747,9 @@ "license": "ISC" }, "node_modules/@vue/shared": { - "version": "3.4.30", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.30.tgz", - "integrity": "sha512-CLg+f8RQCHQnKvuHY9adMsMaQOcqclh6Z5V9TaoMgy0ut0tz848joZ7/CYFFyF/yZ5i2yaw7Fn498C+CNZVHIg==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.31.tgz", + "integrity": "sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==", "license": "MIT", "optional": true }, @@ -5017,9 +5017,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001638", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz", - "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", + "version": "1.0.30001639", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001639.tgz", + "integrity": "sha512-eFHflNTBIlFwP2AIKaYuBQN/apnUoKNhBdza8ZnW/h2di4LCZ4xFqYlxUxo+LQ76KFI1PGcC1QDxMbxTZpSCAg==", "funding": [ { "type": "opencollective", @@ -6439,9 +6439,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.812", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.812.tgz", - "integrity": "sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==", + "version": "1.4.815", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.815.tgz", + "integrity": "sha512-OvpTT2ItpOXJL7IGcYakRjHCt8L5GrrN/wHCQsRB4PQa1X9fe+X9oen245mIId7s14xvArCGSTIq644yPUKKLg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -6953,9 +6953,9 @@ } }, "node_modules/eslint-plugin-es-x": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.7.0.tgz", - "integrity": "sha512-aP3qj8BwiEDPttxQkZdI221DLKq9sI/qHolE2YSQL1/9+xk7dTV+tB1Fz8/IaCA+lnLA1bDEnvaS2LKs0k2Uig==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", "funding": [ "https://github.com/sponsors/ota-meshi", "https://opencollective.com/eslint" @@ -6964,7 +6964,7 @@ "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0", + "@eslint-community/regexpp": "^4.11.0", "eslint-compat-utils": "^0.5.1" }, "engines": { @@ -7166,9 +7166,9 @@ } }, "node_modules/eslint-plugin-promise": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.2.0.tgz", - "integrity": "sha512-QmAqwizauvnKOlifxyDj2ObfULpHQawlg/zQdgEixur9vl0CvZGv/LCJV2rtj3210QCoeGBzVMfMXqGAOr/4fA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.4.0.tgz", + "integrity": "sha512-/KWWRaD3fGkVCZsdR0RU53PSthFmoHVhZl+y9+6DqeDLSikLdlUVpVEAmI6iCRR5QyOjBYBqHZV/bdv4DJ4Gtw==", "license": "ISC", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -11486,9 +11486,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", + "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", "license": "ISC", "engines": { "node": "14 || >=16.14" @@ -11656,9 +11656,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "funding": [ { "type": "opencollective", @@ -11676,7 +11676,7 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "source-map-js": "^1.2.0" }, "engines": { @@ -11740,9 +11740,9 @@ } }, "node_modules/postcss-color-functional-notation": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.11.tgz", - "integrity": "sha512-gJ+hAtAsgBF4w7eh28Pg7EA60lx7vE5xO/B/yZawaI6FYHky+5avA9YSe73nJHnAMEVFpCMeJc6Wts5g+niksg==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.12.tgz", + "integrity": "sha512-LGLWl6EDofJwDHMElYvt4YU9AeH+oijzOfeKhE0ebuu0aBSDeEg7CfFXMi0iiXWV1VKxn3MLGOtcBNnOiQS9Yg==", "funding": [ { "type": "github", @@ -11755,9 +11755,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -11855,9 +11855,9 @@ } }, "node_modules/postcss-custom-media": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.6.tgz", - "integrity": "sha512-BjihQoIO4Wjqv9fQNExSJIim8UAmkhLxuJnhJsLTRFSba1y1MhxkJK5awsM//6JJ+/Tu5QUxf624RQAvKHv6SA==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.7.tgz", + "integrity": "sha512-o2k5nnvRZhF36pr1fGFM7a1EMTcNdKNO70Tp1g2lfpYgiwIctR7ic4acBCDHBMYRcQ8mFlaBB1QsEywqrSIaFQ==", "funding": [ { "type": "github", @@ -11870,10 +11870,10 @@ ], "license": "MIT", "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.11", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", - "@csstools/media-query-list-parser": "^2.1.11" + "@csstools/cascade-layer-name-parser": "^1.0.12", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", + "@csstools/media-query-list-parser": "^2.1.12" }, "engines": { "node": "^14 || ^16 || >=18" @@ -11883,9 +11883,9 @@ } }, "node_modules/postcss-custom-properties": { - "version": "13.3.10", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.10.tgz", - "integrity": "sha512-ejaalIpl7p0k0L5ngIZ86AZGmp3m1KdeOCbSQTK4gQcB1ncaoPTHorw206+tsZRIhIDYvh5ZButEje6740YDXw==", + "version": "13.3.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.11.tgz", + "integrity": "sha512-CAIgz03I/GMhVbAKIi3u3P8j5JY2KHl0TlePcfUX3OUy8t0ynnWvyJaS1D92pEAw1LjmeKWi7+aIU0s53iYdOQ==", "funding": [ { "type": "github", @@ -11898,9 +11898,9 @@ ], "license": "MIT", "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.11", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/cascade-layer-name-parser": "^1.0.12", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, @@ -11912,9 +11912,9 @@ } }, "node_modules/postcss-custom-selectors": { - "version": "7.1.10", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.10.tgz", - "integrity": "sha512-bV/6+IExyT2J4kMzX6c+ZMlN1xDfjcC4ePr1ywKezcTgwgUn11qQN3jdzFBpo8Dk1K7vO/OYOwMb5AtJP4JZcg==", + "version": "7.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.11.tgz", + "integrity": "sha512-IoGprXOueDJL5t3ZuWR+QzPpmrQCFNhvoICsg0vDSehGwWNG0YV/Z4A+zouGRonC7NJThoV+A8A74IEMqMQUQw==", "funding": [ { "type": "github", @@ -11927,10 +11927,10 @@ ], "license": "MIT", "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.11", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", - "postcss-selector-parser": "^6.0.13" + "@csstools/cascade-layer-name-parser": "^1.0.12", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", + "postcss-selector-parser": "^6.1.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -12167,9 +12167,9 @@ } }, "node_modules/postcss-lab-function": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.16.tgz", - "integrity": "sha512-QWv0VxfjgIl8jBR/wuQcm/o31jn4P/LwzYuVKzNQoO5t7HPcU0d3RfWUiDrHN3frmSv+YYZppr3P81tKFTDyqg==", + "version": "6.0.17", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.17.tgz", + "integrity": "sha512-QzjC6/3J6XKZzHGuUKhWNvlDMfWo+08dQOfQj4vWQdpZFdOxCh9QCR4w4XbV68EkdzywJie1mcm81jwFyV0+kg==", "funding": [ { "type": "github", @@ -12182,9 +12182,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^2.0.2", - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", + "@csstools/css-color-parser": "^2.0.3", + "@csstools/css-parser-algorithms": "^2.7.0", + "@csstools/css-tokenizer": "^2.3.2", "@csstools/postcss-progressive-custom-properties": "^3.2.0", "@csstools/utilities": "^1.0.0" }, @@ -12679,9 +12679,9 @@ } }, "node_modules/postcss-preset-env": { - "version": "9.5.14", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.5.14.tgz", - "integrity": "sha512-gTMi+3kENN/mN+K59aR+vEOjlkujTmmXJcM9rnAqGh9Y/euQ/ypdp9rd8mO1eoIjAD8vNS15+xbkBxoi+65BqQ==", + "version": "9.5.15", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.5.15.tgz", + "integrity": "sha512-z/2akOVQChOGAdzaUR4pQrDOM3xGZc5/k4THHWyREbWAfngaJATA2SkEQMkiyV5Y/EoSwE0nt0IiaIs6CMmxfQ==", "funding": [ { "type": "github", @@ -12695,48 +12695,48 @@ "license": "MIT-0", "dependencies": { "@csstools/postcss-cascade-layers": "^4.0.6", - "@csstools/postcss-color-function": "^3.0.16", - "@csstools/postcss-color-mix-function": "^2.0.16", - "@csstools/postcss-exponential-functions": "^1.0.7", + "@csstools/postcss-color-function": "^3.0.17", + "@csstools/postcss-color-mix-function": "^2.0.17", + "@csstools/postcss-exponential-functions": "^1.0.8", "@csstools/postcss-font-format-keywords": "^3.0.2", - "@csstools/postcss-gamut-mapping": "^1.0.9", - "@csstools/postcss-gradients-interpolation-method": "^4.0.17", - "@csstools/postcss-hwb-function": "^3.0.15", + "@csstools/postcss-gamut-mapping": "^1.0.10", + "@csstools/postcss-gradients-interpolation-method": "^4.0.18", + "@csstools/postcss-hwb-function": "^3.0.16", "@csstools/postcss-ic-unit": "^3.0.6", "@csstools/postcss-initial": "^1.0.1", "@csstools/postcss-is-pseudo-class": "^4.0.8", - "@csstools/postcss-light-dark-function": "^1.0.5", + "@csstools/postcss-light-dark-function": "^1.0.6", "@csstools/postcss-logical-float-and-clear": "^2.0.1", "@csstools/postcss-logical-overflow": "^1.0.1", "@csstools/postcss-logical-overscroll-behavior": "^1.0.1", "@csstools/postcss-logical-resize": "^2.0.1", - "@csstools/postcss-logical-viewport-units": "^2.0.9", - "@csstools/postcss-media-minmax": "^1.1.6", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.9", + "@csstools/postcss-logical-viewport-units": "^2.0.10", + "@csstools/postcss-media-minmax": "^1.1.7", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.10", "@csstools/postcss-nested-calc": "^3.0.2", "@csstools/postcss-normalize-display-values": "^3.0.2", - "@csstools/postcss-oklab-function": "^3.0.16", + "@csstools/postcss-oklab-function": "^3.0.17", "@csstools/postcss-progressive-custom-properties": "^3.2.0", - "@csstools/postcss-relative-color-syntax": "^2.0.16", + "@csstools/postcss-relative-color-syntax": "^2.0.17", "@csstools/postcss-scope-pseudo-class": "^3.0.1", - "@csstools/postcss-stepped-value-functions": "^3.0.8", - "@csstools/postcss-text-decoration-shorthand": "^3.0.6", - "@csstools/postcss-trigonometric-functions": "^3.0.8", + "@csstools/postcss-stepped-value-functions": "^3.0.9", + "@csstools/postcss-text-decoration-shorthand": "^3.0.7", + "@csstools/postcss-trigonometric-functions": "^3.0.9", "@csstools/postcss-unset-value": "^3.0.1", "autoprefixer": "^10.4.19", - "browserslist": "^4.22.3", + "browserslist": "^4.23.1", "css-blank-pseudo": "^6.0.2", "css-has-pseudo": "^6.0.5", "css-prefers-color-scheme": "^9.0.1", "cssdb": "^8.0.0", "postcss-attribute-case-insensitive": "^6.0.3", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^6.0.11", + "postcss-color-functional-notation": "^6.0.12", "postcss-color-hex-alpha": "^9.0.4", "postcss-color-rebeccapurple": "^9.0.3", - "postcss-custom-media": "^10.0.6", - "postcss-custom-properties": "^13.3.10", - "postcss-custom-selectors": "^7.1.10", + "postcss-custom-media": "^10.0.7", + "postcss-custom-properties": "^13.3.11", + "postcss-custom-selectors": "^7.1.11", "postcss-dir-pseudo-class": "^8.0.1", "postcss-double-position-gradients": "^5.0.6", "postcss-focus-visible": "^9.0.1", @@ -12744,7 +12744,7 @@ "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^5.0.1", "postcss-image-set-function": "^6.0.3", - "postcss-lab-function": "^6.0.16", + "postcss-lab-function": "^6.0.17", "postcss-logical": "^7.0.1", "postcss-nesting": "^12.1.5", "postcss-opacity-percentage": "^2.0.0", @@ -14447,9 +14447,9 @@ } }, "node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -16607,9 +16607,9 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "license": "MIT", "engines": { "node": ">=12.20" diff --git a/frontend/src/dialog/account/apps.vue b/frontend/src/dialog/account/apps.vue index afa68a84b..50fa5b829 100644 --- a/frontend/src/dialog/account/apps.vue +++ b/frontend/src/dialog/account/apps.vue @@ -425,7 +425,7 @@ export default { return; } - if (this.confirmAction === "") { + if (this.confirmAction === "" && this.$session.provider !== "oidc") { this.confirmAction = "onGenerate"; return; } diff --git a/frontend/src/model/user.js b/frontend/src/model/user.js index a185f9662..b9c963a60 100644 --- a/frontend/src/model/user.js +++ b/frontend/src/model/user.js @@ -209,7 +209,7 @@ export class User extends RestModel { } isRemote() { - return this.AuthProvider && this.AuthProvider === "ldap"; + return this.AuthProvider && (this.AuthProvider === "ldap" || this.AuthProvider === "oidc"); } authInfo() { diff --git a/frontend/src/options/auth.js b/frontend/src/options/auth.js index b666f2aa6..b31ad017f 100644 --- a/frontend/src/options/auth.js +++ b/frontend/src/options/auth.js @@ -80,3 +80,21 @@ export const ScopeOptions = () => { */ ]; }; + +// GrantTypes maps grant types to their display name. +export const GrantTypes = () => { + return { + "": "Default", + cli: "CLI", + implicit: "Implicit", + session: $gettext("Session"), + password: $gettext("Password"), + client_credentials: "Client Credentials", + share_token: "Share Token", + refresh_token: "Refresh Token", + authorization_code: "Authorization Code", + "urn:ietf:params:oauth:grant-type:jwt-bearer": "JWT Bearer Assertion", + "urn:ietf:params:oauth:grant-type:saml2-bearer": "SAML2 Bearer Assertion", + "urn:ietf:params:oauth:grant-type:token-exchange": "Token Exchange", + }; +}; diff --git a/frontend/src/page/auth/login.vue b/frontend/src/page/auth/login.vue index d26a9df27..a2071ed45 100644 --- a/frontend/src/page/auth/login.vue +++ b/frontend/src/page/auth/login.vue @@ -173,6 +173,11 @@ export default { }, created() { this.$scrollbar.hide(this.$isMobile); + const authError = window.localStorage.getItem("authError"); + if (authError) { + this.$notify.error(authError); + window.localStorage.removeItem("authError"); + } }, destroyed() { this.$scrollbar.show(); diff --git a/go.mod b/go.mod index 58c329125..38d2fd1ad 100644 --- a/go.mod +++ b/go.mod @@ -98,7 +98,6 @@ require ( github.com/cloudwego/iasm v0.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/davidbyttow/govips v0.0.0-20201026223743-b1b72c7305d9 // indirect github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect github.com/gin-contrib/sse v0.1.0 // indirect diff --git a/go.sum b/go.sum index 352ed0842..357c485fd 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,6 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidbyttow/govips v0.0.0-20201026223743-b1b72c7305d9 h1:nXNjG6pGALu++DSxtrdO79AZhtU+jZnyMlq3FTkFH74= -github.com/davidbyttow/govips v0.0.0-20201026223743-b1b72c7305d9/go.mod h1:HLCb4NOCQTqIYx+Mu01rZChNoP/vDwQNVFaKWUTzuxk= github.com/davidbyttow/govips/v2 v2.14.0 h1:il3pX0XMZ5nlwipkFJHRZ3vGzcdXWApARalJxNpRHJU= github.com/davidbyttow/govips/v2 v2.14.0/go.mod h1:eglyvgm65eImDiJJk4wpj9LSz4pWivPzWgDqkxWJn5k= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= @@ -471,7 +469,6 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= @@ -520,7 +517,6 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -641,7 +637,6 @@ google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6h google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -653,7 +648,6 @@ gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/api/albums.go b/internal/api/albums.go index be351483e..94dbb88e1 100644 --- a/internal/api/albums.go +++ b/internal/api/albums.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/albums_search.go b/internal/api/albums_search.go index 3aa21bdf2..1aed1ac24 100644 --- a/internal/api/albums_search.go +++ b/internal/api/albums_search.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/api_auth.go b/internal/api/api_auth.go index dab6ec7d3..470337eea 100644 --- a/internal/api/api_auth.go +++ b/internal/api/api_auth.go @@ -3,7 +3,7 @@ package api import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/pkg/authn" diff --git a/internal/api/api_auth_test.go b/internal/api/api_auth_test.go index 04362f497..ac70e77dc 100644 --- a/internal/api/api_auth_test.go +++ b/internal/api/api_auth_test.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/session" "github.com/photoprism/photoprism/pkg/header" ) diff --git a/internal/api/batch.go b/internal/api/batch.go index 56192d362..6a9d78463 100644 --- a/internal/api/batch.go +++ b/internal/api/batch.go @@ -10,7 +10,7 @@ import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/config_options.go b/internal/api/config_options.go index b7317e0d6..d1418f26c 100644 --- a/internal/api/config_options.go +++ b/internal/api/config_options.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" "gopkg.in/yaml.v2" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/mutex" diff --git a/internal/api/config_settings.go b/internal/api/config_settings.go index 6d3a5bc55..7c19a2d08 100644 --- a/internal/api/config_settings.go +++ b/internal/api/config_settings.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/customize" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/connect.go b/internal/api/connect.go index 10dc23329..c69bf45a0 100644 --- a/internal/api/connect.go +++ b/internal/api/connect.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/mutex" diff --git a/internal/api/errors.go b/internal/api/errors.go index a6525d870..4761556be 100644 --- a/internal/api/errors.go +++ b/internal/api/errors.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/pkg/i18n" diff --git a/internal/api/faces.go b/internal/api/faces.go index f53f381d9..da0fc6fd8 100644 --- a/internal/api/faces.go +++ b/internal/api/faces.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/faces_search.go b/internal/api/faces_search.go index 44908bc01..38a8efdb9 100644 --- a/internal/api/faces_search.go +++ b/internal/api/faces_search.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/pkg/txt" diff --git a/internal/api/feedback.go b/internal/api/feedback.go index 70e70dc3c..1555cb8ee 100644 --- a/internal/api/feedback.go +++ b/internal/api/feedback.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/pkg/i18n" diff --git a/internal/api/file_delete.go b/internal/api/file_delete.go index d8a168268..f8d98adb3 100644 --- a/internal/api/file_delete.go +++ b/internal/api/file_delete.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/photoprism" diff --git a/internal/api/file_orientation.go b/internal/api/file_orientation.go index 3943de0b1..a8aaed57c 100644 --- a/internal/api/file_orientation.go +++ b/internal/api/file_orientation.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/photoprism" diff --git a/internal/api/files.go b/internal/api/files.go index 158bb7d0b..8e4b931f8 100644 --- a/internal/api/files.go +++ b/internal/api/files.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/pkg/clean" ) diff --git a/internal/api/folders_search.go b/internal/api/folders_search.go index baa9fc7a6..210e11894 100644 --- a/internal/api/folders_search.go +++ b/internal/api/folders_search.go @@ -9,7 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/import.go b/internal/api/import.go index d07d6afd3..8382b5dd8 100644 --- a/internal/api/import.go +++ b/internal/api/import.go @@ -11,7 +11,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/index.go b/internal/api/index.go index 1b2a4b98e..c6c9b60b0 100644 --- a/internal/api/index.go +++ b/internal/api/index.go @@ -8,7 +8,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/labels.go b/internal/api/labels.go index 2ddfec147..c8f3a1450 100644 --- a/internal/api/labels.go +++ b/internal/api/labels.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/labels_search.go b/internal/api/labels_search.go index 5abda4962..16cd00ce3 100644 --- a/internal/api/labels_search.go +++ b/internal/api/labels_search.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/pkg/txt" diff --git a/internal/api/links.go b/internal/api/links.go index 171c56596..c5794fa7f 100644 --- a/internal/api/links.go +++ b/internal/api/links.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/query" diff --git a/internal/api/markers.go b/internal/api/markers.go index e81c13f7c..dca56a0de 100644 --- a/internal/api/markers.go +++ b/internal/api/markers.go @@ -9,7 +9,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/crop" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" diff --git a/internal/api/metrics.go b/internal/api/metrics.go index 50f887961..9f13547a8 100644 --- a/internal/api/metrics.go +++ b/internal/api/metrics.go @@ -11,7 +11,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/common/expfmt" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/get" ) diff --git a/internal/api/moments_time.go b/internal/api/moments_time.go index 774989e63..fa305217f 100644 --- a/internal/api/moments_time.go +++ b/internal/api/moments_time.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/pkg/txt" diff --git a/internal/api/oauth_revoke.go b/internal/api/oauth_revoke.go index f2d622ff0..97e6d65d4 100644 --- a/internal/api/oauth_revoke.go +++ b/internal/api/oauth_revoke.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/oidc_login.go b/internal/api/oidc_login.go index d2553d491..0fd1c488f 100644 --- a/internal/api/oidc_login.go +++ b/internal/api/oidc_login.go @@ -7,6 +7,7 @@ import ( "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/get" + "github.com/photoprism/photoprism/internal/server/limiter" "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/header" "github.com/photoprism/photoprism/pkg/i18n" @@ -28,7 +29,7 @@ func OIDCLogin(router *gin.RouterGroup) { // Get client IP address for logs and rate limiting checks. clientIp := ClientIP(c) - actor := "unknown client" + actor := "unknown user" action := "login" // Get global config. @@ -45,6 +46,16 @@ func OIDCLogin(router *gin.RouterGroup) { return } + // Check request rate limit. + var r *limiter.Request + r = limiter.Login.Request(clientIp) + + // Abort if failure rate limit is exceeded. + if r.Reject() || limiter.Auth.Reject(clientIp) { + limiter.AbortJSON(c) + return + } + // Get OIDC provider. provider := get.OIDC() @@ -54,6 +65,9 @@ func OIDCLogin(router *gin.RouterGroup) { return } + // Return the reserved request rate limit token. + r.Success() + // Handle OIDC login request. provider.AuthCodeUrlHandler(c) }) diff --git a/internal/api/oidc_redirect.go b/internal/api/oidc_redirect.go index 0a2ea7275..453e3a607 100644 --- a/internal/api/oidc_redirect.go +++ b/internal/api/oidc_redirect.go @@ -5,11 +5,16 @@ import ( "github.com/gin-gonic/gin" + "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/get" + "github.com/photoprism/photoprism/internal/server/limiter" "github.com/photoprism/photoprism/pkg/authn" + "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/header" "github.com/photoprism/photoprism/pkg/i18n" + "github.com/photoprism/photoprism/pkg/txt" + "github.com/photoprism/photoprism/pkg/unix" ) // OIDCRedirect creates a new access token for authenticated users and then redirects the browser back to the app. @@ -17,9 +22,12 @@ import ( // GET /api/v1/oidc/redirect func OIDCRedirect(router *gin.RouterGroup) { router.GET("/oidc/redirect", func(c *gin.Context) { + // Get global config. + conf := get.Config() + // Prevent CDNs from caching this endpoint. if header.IsCdn(c.Request) { - AbortNotFound(c) + c.Redirect(http.StatusTemporaryRedirect, conf.LoginUri()) return } @@ -28,20 +36,34 @@ func OIDCRedirect(router *gin.RouterGroup) { // Get client IP address for logs and rate limiting checks. clientIp := ClientIP(c) - actor := "unknown client" + actor := "unknown user" action := "redirect" - // Get global config. - conf := get.Config() - // Abort in public mode and if OIDC is disabled. if get.Config().Public() { event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrDisabledInPublicMode.Error()}) - Abort(c, http.StatusForbidden, i18n.ErrForbidden) + c.Redirect(http.StatusTemporaryRedirect, conf.LoginUri()) return } else if !conf.OIDCEnabled() { event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrAuthenticationDisabled.Error()}) - Abort(c, http.StatusMethodNotAllowed, i18n.ErrUnsupported) + c.Redirect(http.StatusTemporaryRedirect, conf.LoginUri()) + return + } + + // Check request rate limit. + var r *limiter.Request + r = limiter.Login.Request(clientIp) + + // Abort if failure rate limit is exceeded. + if r.Reject() || limiter.Auth.Reject(clientIp) { + c.HTML(http.StatusTooManyRequests, "auth.gohtml", CreateSessionError(http.StatusTooManyRequests, i18n.Error(i18n.ErrForbidden))) + return + } + + // Check if the required request parameters are present. + if c.Query("state") == "" || c.Query("code") == "" { + event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrAuthCodeRequired.Error()}) + c.Redirect(http.StatusTemporaryRedirect, conf.LoginUri()) return } @@ -50,32 +72,156 @@ func OIDCRedirect(router *gin.RouterGroup) { if provider == nil { event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrAuthenticationDisabled.Error()}) - Abort(c, http.StatusInternalServerError, i18n.ErrConnectionFailed) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) return } - _, claimErr := provider.CodeExchangeUserInfo(c) + userInfo, tokens, claimErr := provider.CodeExchangeUserInfo(c) if claimErr != nil { event.AuditErr([]string{clientIp, "oidc", actor, action, claimErr.Error()}) - Abort(c, http.StatusForbidden, i18n.ErrForbidden) return } - // TODO 1: Create user account if it does not exist yet. - /* - user := &entity.User{ - DisplayName: userInfo.GetName(), - UserName: oidc.UsernameFromUserInfo(userInfo), - UserEmail: userInfo.GetEmail(), - AuthID: userInfo.GetSubject(), - AuthProvider: authn.ProviderOIDC.String(), - } */ + // Step 1: Create user account if it does not exist yet. + var user *entity.User + var err error - // TODO 2: Create and return user session. + // Find existing user record and update it, if necessary. + if oidcUser := entity.OidcUser(userInfo, conf.OIDCUsername()); oidcUser.UserName == "" || authn.ProviderOIDC.NotEqual(oidcUser.AuthProvider) { + event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrInvalidUsername.Error()}) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) + return + } else if user = entity.FindUser(oidcUser); user != nil { + // Check if username and subject UID match. + if user.Username() == "" || oidcUser.UserName == "" || user.Username() != oidcUser.UserName { + event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrInvalidUsername.Error()}) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) + return + } else if user.AuthID == "" || oidcUser.AuthID == "" || user.AuthID != oidcUser.AuthID { + event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrInvalidAuthID.Error()}) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) + return + } - // TODO 3: Render HTML template to set the access token in localStorage. + actor = user.Username() - c.JSON(http.StatusMethodNotAllowed, gin.H{"status": StatusFailed}) + // Update user profile information. + user.SetDisplayName(userInfo.GetName(), entity.SrcOIDC) + user.SetGivenName(userInfo.GetGivenName()) + user.SetFamilyName(userInfo.GetFamilyName()) + + // Update user account. + if err = user.Save(); err != nil { + event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrAccountUpdateFailed.Error(), err.Error()}) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) + return + } + } else if conf.OIDCRegister() { + // Create new user record. + user = &oidcUser + actor = user.Username() + + // Set profile information. + user.SetDisplayName(userInfo.GetName(), entity.SrcOIDC) + user.SetGivenName(userInfo.GetGivenName()) + user.SetFamilyName(userInfo.GetFamilyName()) + user.Details().NickName = clean.Name(userInfo.GetNickname()) + user.Details().ProfileURL = clean.Uri(userInfo.GetProfile()) + user.Details().SiteURL = clean.Uri(userInfo.GetWebsite()) + user.Details().UserGender = clean.Name(string(userInfo.GetGender())) + + // Set UI locale. + if locale, _, _ := userInfo.GetLocale().Raw(); len(locale.String()) == 2 { + user.Settings().UILanguage = locale.String() + } + + // Set UI timezone. + user.Settings().UITimeZone = userInfo.GetZoneinfo() + + // Set address information, if available. + if addr := userInfo.GetAddress(); addr != nil { + user.Details().UserLocation = clean.Name(addr.GetLocality()) + user.Details().UserCountry = clean.TypeLowerUnderscore(addr.GetCountry()) + } + + // Set birthday, if available. + if birthDate := txt.ParseTime(userInfo.GetBirthdate(), userInfo.GetZoneinfo()); !birthDate.IsZero() { + user.BornAt = &birthDate + user.Details().BirthDay = birthDate.Day() + user.Details().BirthMonth = int(birthDate.Month()) + user.Details().BirthYear = birthDate.Year() + } + + // Flag as verified? + if userInfo.IsEmailVerified() { + user.UserEmail = clean.Email(userInfo.GetEmail()) + user.VerifiedAt = entity.TimeStamp() + } + + // Set role and permissions. + user.SetRole(conf.OIDCRole().String()) + user.CanLogin = true + user.WebDAV = conf.OIDCWebDAV() + + // Create user account. + if err = user.Create(); err != nil { + event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrAccountCreateFailed.Error(), err.Error()}) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) + return + } + } else { + event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrRegistrationDisabled.Error()}) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) + return + } + + // Login allowed? + if !user.CanLogIn() { + event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrAccountDisabled.Error()}) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) + return + } + + // Step 2: Create user session. + sess := get.Session().New(c) + sess.SetProvider(authn.ProviderOIDC) + sess.SetMethod(authn.MethodDefault) + sess.SetUser(user) + sess.SetGrantType(authn.GrantAuthorizationCode) + + // Set identity provider tokens. + sess.IdToken = tokens.IDToken + sess.AccessToken = tokens.AccessToken + sess.RefreshToken = tokens.RefreshToken + + // Set session expiration and timeout. + sess.SetExpiresIn(unix.Day) + sess.SetTimeout(-1) + + // Save session after successful authentication. + if sess, err = get.Session().Save(sess); err != nil { + event.AuditErr([]string{clientIp, "oidc", actor, action, "%s"}, err) + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrInvalidCredentials))) + return + } else if sess == nil { + c.HTML(http.StatusUnauthorized, "auth.gohtml", CreateSessionError(http.StatusUnauthorized, i18n.Error(i18n.ErrUnexpected))) + return + } + + // Return the reserved request rate limit token after successful authentication. + r.Success() + + // Response includes user data, session data, and client config values. + response := CreateSessionResponse(sess.AuthToken(), sess, conf.ClientSession(sess)) + + // Log success. + event.AuditInfo([]string{clientIp, "oidc", actor, action, authn.Succeeded}) + + // Update login timestamp. + user.UpdateLoginTime() + + // Step 3: Render HTML template to set the access token in localStorage. + c.HTML(http.StatusOK, "auth.gohtml", response) }) } diff --git a/internal/api/photo_label.go b/internal/api/photo_label.go index 3d6ad4c51..bf5d17a30 100644 --- a/internal/api/photo_label.go +++ b/internal/api/photo_label.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/classify" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" diff --git a/internal/api/photo_unstack.go b/internal/api/photo_unstack.go index e52e35511..c5378b89d 100644 --- a/internal/api/photo_unstack.go +++ b/internal/api/photo_unstack.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/photos.go b/internal/api/photos.go index af86ee9a3..9f2f2007c 100644 --- a/internal/api/photos.go +++ b/internal/api/photos.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/photos_search.go b/internal/api/photos_search.go index db6db7c74..f3bca4cfb 100644 --- a/internal/api/photos_search.go +++ b/internal/api/photos_search.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/photos_search_geo.go b/internal/api/photos_search_geo.go index 28824e671..c31f01123 100644 --- a/internal/api/photos_search_geo.go +++ b/internal/api/photos_search_geo.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/reactions.go b/internal/api/reactions.go index 2f26a10cf..a1d5a8e59 100644 --- a/internal/api/reactions.go +++ b/internal/api/reactions.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/pkg/clean" diff --git a/internal/api/server.go b/internal/api/server.go index d16d964ce..05c489b44 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/get" ) diff --git a/internal/api/services.go b/internal/api/services.go index 0654606ef..f304e6606 100644 --- a/internal/api/services.go +++ b/internal/api/services.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/services_search.go b/internal/api/services_search.go index 5869d8da8..69baebe5c 100644 --- a/internal/api/services_search.go +++ b/internal/api/services_search.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/services_upload.go b/internal/api/services_upload.go index 7a8fc1ec2..b4aebd5fc 100644 --- a/internal/api/services_upload.go +++ b/internal/api/services_upload.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/session_delete.go b/internal/api/session_delete.go index 7cf5e06e1..5f75ec6bc 100644 --- a/internal/api/session_delete.go +++ b/internal/api/session_delete.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/session_get.go b/internal/api/session_get.go index b3d91a0b5..959a9c606 100644 --- a/internal/api/session_get.go +++ b/internal/api/session_get.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/header" diff --git a/internal/api/session_response.go b/internal/api/session_response.go index 760324095..c61a1d3c6 100644 --- a/internal/api/session_response.go +++ b/internal/api/session_response.go @@ -5,6 +5,7 @@ import ( "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/internal/get" ) // CreateSessionResponse returns the authentication response data for POST requests @@ -13,6 +14,16 @@ func CreateSessionResponse(authToken string, sess *entity.Session, conf config.C return GetSessionResponse(authToken, sess, conf) } +// CreateSessionError returns an authentication error response. +func CreateSessionError(code int, err error) gin.H { + return gin.H{ + "status": StatusFailed, + "code": code, + "error": err.Error(), + "config": get.Config().ClientPublic(), + } +} + // GetSessionResponse returns the authentication response data for GET requests // based on the session and configuration. func GetSessionResponse(authToken string, sess *entity.Session, conf config.ClientConfig) gin.H { diff --git a/internal/api/subjects.go b/internal/api/subjects.go index 27b46d1c7..007242d64 100644 --- a/internal/api/subjects.go +++ b/internal/api/subjects.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/subjects_search.go b/internal/api/subjects_search.go index 93d923241..858772fe3 100644 --- a/internal/api/subjects_search.go +++ b/internal/api/subjects_search.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/pkg/txt" diff --git a/internal/api/users_avatar.go b/internal/api/users_avatar.go index e70546b1b..1665fd221 100644 --- a/internal/api/users_avatar.go +++ b/internal/api/users_avatar.go @@ -7,7 +7,7 @@ import ( "github.com/gabriel-vasile/mimetype" "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/users_passcode.go b/internal/api/users_passcode.go index 90ee7094e..8c931b30c 100644 --- a/internal/api/users_passcode.go +++ b/internal/api/users_passcode.go @@ -6,7 +6,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/users_password.go b/internal/api/users_password.go index 9bab6f5a9..228cb31e4 100644 --- a/internal/api/users_password.go +++ b/internal/api/users_password.go @@ -6,7 +6,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/api/users_sessions.go b/internal/api/users_sessions.go index ae273f878..154e00ede 100644 --- a/internal/api/users_sessions.go +++ b/internal/api/users_sessions.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/users_update.go b/internal/api/users_update.go index 70193c52f..5d11274e3 100644 --- a/internal/api/users_update.go +++ b/internal/api/users_update.go @@ -6,7 +6,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/users_upload.go b/internal/api/users_upload.go index 302f2567e..b91b47d20 100644 --- a/internal/api/users_upload.go +++ b/internal/api/users_upload.go @@ -11,7 +11,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" diff --git a/internal/api/websocket.go b/internal/api/websocket.go index 3d4c1a5df..675801e66 100644 --- a/internal/api/websocket.go +++ b/internal/api/websocket.go @@ -7,7 +7,7 @@ import ( "github.com/gorilla/websocket" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" ) diff --git a/internal/api/websocket_writer.go b/internal/api/websocket_writer.go index 869e6b487..74c3e063b 100644 --- a/internal/api/websocket_writer.go +++ b/internal/api/websocket_writer.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" ) diff --git a/internal/api/zip.go b/internal/api/zip.go index 9f8ab354c..2542f0d79 100644 --- a/internal/api/zip.go +++ b/internal/api/zip.go @@ -12,7 +12,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/photoprism" diff --git a/internal/acl/acl.go b/internal/auth/acl/acl.go similarity index 100% rename from internal/acl/acl.go rename to internal/auth/acl/acl.go diff --git a/internal/acl/acl_test.go b/internal/auth/acl/acl_test.go similarity index 100% rename from internal/acl/acl_test.go rename to internal/auth/acl/acl_test.go diff --git a/internal/acl/const.go b/internal/auth/acl/const.go similarity index 100% rename from internal/acl/const.go rename to internal/auth/acl/const.go diff --git a/internal/acl/events.go b/internal/auth/acl/events.go similarity index 100% rename from internal/acl/events.go rename to internal/auth/acl/events.go diff --git a/internal/acl/grant.go b/internal/auth/acl/grant.go similarity index 100% rename from internal/acl/grant.go rename to internal/auth/acl/grant.go diff --git a/internal/acl/grant_test.go b/internal/auth/acl/grant_test.go similarity index 100% rename from internal/acl/grant_test.go rename to internal/auth/acl/grant_test.go diff --git a/internal/acl/grants.go b/internal/auth/acl/grants.go similarity index 100% rename from internal/acl/grants.go rename to internal/auth/acl/grants.go diff --git a/internal/acl/grants_test.go b/internal/auth/acl/grants_test.go similarity index 100% rename from internal/acl/grants_test.go rename to internal/auth/acl/grants_test.go diff --git a/internal/acl/permission.go b/internal/auth/acl/permission.go similarity index 100% rename from internal/acl/permission.go rename to internal/auth/acl/permission.go diff --git a/internal/acl/permission_test.go b/internal/auth/acl/permission_test.go similarity index 100% rename from internal/acl/permission_test.go rename to internal/auth/acl/permission_test.go diff --git a/internal/acl/permissions.go b/internal/auth/acl/permissions.go similarity index 100% rename from internal/acl/permissions.go rename to internal/auth/acl/permissions.go diff --git a/internal/acl/resource.go b/internal/auth/acl/resource.go similarity index 100% rename from internal/acl/resource.go rename to internal/auth/acl/resource.go diff --git a/internal/acl/resource_names.go b/internal/auth/acl/resource_names.go similarity index 100% rename from internal/acl/resource_names.go rename to internal/auth/acl/resource_names.go diff --git a/internal/acl/resource_test.go b/internal/auth/acl/resource_test.go similarity index 100% rename from internal/acl/resource_test.go rename to internal/auth/acl/resource_test.go diff --git a/internal/acl/role.go b/internal/auth/acl/role.go similarity index 100% rename from internal/acl/role.go rename to internal/auth/acl/role.go diff --git a/internal/acl/role_test.go b/internal/auth/acl/role_test.go similarity index 100% rename from internal/acl/role_test.go rename to internal/auth/acl/role_test.go diff --git a/internal/acl/roles.go b/internal/auth/acl/roles.go similarity index 100% rename from internal/acl/roles.go rename to internal/auth/acl/roles.go diff --git a/internal/acl/rules.go b/internal/auth/acl/rules.go similarity index 100% rename from internal/acl/rules.go rename to internal/auth/acl/rules.go diff --git a/internal/acl/scopes.go b/internal/auth/acl/scopes.go similarity index 100% rename from internal/acl/scopes.go rename to internal/auth/acl/scopes.go diff --git a/internal/acl/scopes_test.go b/internal/auth/acl/scopes_test.go similarity index 100% rename from internal/acl/scopes_test.go rename to internal/auth/acl/scopes_test.go diff --git a/internal/oidc/client.go b/internal/auth/oidc/client.go similarity index 82% rename from internal/oidc/client.go rename to internal/auth/oidc/client.go index fa229401f..f183d03a7 100644 --- a/internal/oidc/client.go +++ b/internal/auth/oidc/client.go @@ -43,7 +43,7 @@ func NewClient(iss *url.URL, clientId, clientSecret, customScopes, siteUrl strin return nil, err } - u.Path = path.Join(u.Path, config.OIDCRedirectUri) + u.Path = path.Join(u.Path, config.OidcRedirectUri) log.Debugf("oidc: redirect uri %s", u.String()) var hashKey, encryptKey []byte @@ -113,12 +113,10 @@ func (c *Client) AuthCodeUrlHandler(ctx *gin.Context) { handle(ctx.Writer, ctx.Request) } -func (c *Client) CodeExchangeUserInfo(ctx *gin.Context) (oidc.UserInfo, error) { - var userinfo oidc.UserInfo - - userinfoClosure := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens, state string, rp rp.RelyingParty, info oidc.UserInfo) { - log.Debugf("oidc: UserInfo: %s %s %s %s %s", info.GetEmail(), info.GetSubject(), info.GetNickname(), info.GetName(), info.GetPreferredUsername()) - userinfo = info +func (c *Client) CodeExchangeUserInfo(ctx *gin.Context) (userInfo oidc.UserInfo, tokens *oidc.Tokens, err error) { + userinfoClosure := func(w http.ResponseWriter, r *http.Request, t *oidc.Tokens, state string, rp rp.RelyingParty, i oidc.UserInfo) { + userInfo = i + tokens = t } //you could also just take the access_token and id_token without calling the userinfo endpoint: @@ -133,14 +131,14 @@ func (c *Client) CodeExchangeUserInfo(ctx *gin.Context) (oidc.UserInfo, error) { //handle := rp.CodeExchangeHandler(tokeninfoClosure, c) handle(ctx.Writer, ctx.Request) - log.Debugf("oidc: current request state: %v", ctx.Writer.Status()) + // log.Debugf("oidc: current request state: %v", ctx.Writer.Status()) if sc := ctx.Writer.Status(); sc != 0 && sc != http.StatusOK { - err := ctx.Writer.Header().Get("oidc_error") - if err == "" { - return nil, errors.New("oidc: couldn't exchange auth code and thus not retrieve external user info (unknown error)") + if oidcErr := ctx.Writer.Header().Get("oidc_error"); oidcErr == "" { + return userInfo, tokens, errors.New("tailed to exchange the authentication code and retrieve the user information") + } else { + return userInfo, tokens, errors.New(oidcErr) } - return nil, errors.New(ctx.Writer.Header().Get("oidc_error")) } - return userinfo, nil + return userInfo, tokens, nil } diff --git a/internal/oidc/helper.go b/internal/auth/oidc/helper.go similarity index 100% rename from internal/oidc/helper.go rename to internal/auth/oidc/helper.go diff --git a/internal/oidc/helper_test.go b/internal/auth/oidc/helper_test.go similarity index 100% rename from internal/oidc/helper_test.go rename to internal/auth/oidc/helper_test.go diff --git a/internal/oidc/http_client.go b/internal/auth/oidc/http_client.go similarity index 100% rename from internal/oidc/http_client.go rename to internal/auth/oidc/http_client.go diff --git a/internal/oidc/http_client_test.go b/internal/auth/oidc/http_client_test.go similarity index 100% rename from internal/oidc/http_client_test.go rename to internal/auth/oidc/http_client_test.go diff --git a/internal/oidc/oidc.go b/internal/auth/oidc/oidc.go similarity index 100% rename from internal/oidc/oidc.go rename to internal/auth/oidc/oidc.go diff --git a/internal/oidc/register.go b/internal/auth/oidc/register.go similarity index 100% rename from internal/oidc/register.go rename to internal/auth/oidc/register.go diff --git a/internal/commands/clients.go b/internal/commands/clients.go index b94de9de9..b158f1b19 100644 --- a/internal/commands/clients.go +++ b/internal/commands/clients.go @@ -3,7 +3,7 @@ package commands import ( "github.com/urfave/cli" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/unix" ) diff --git a/internal/commands/users.go b/internal/commands/users.go index d02176392..a84306ba3 100644 --- a/internal/commands/users.go +++ b/internal/commands/users.go @@ -3,7 +3,7 @@ package commands import ( "github.com/urfave/cli" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" ) // Usage hints for the user management subcommands. diff --git a/internal/config/cli_flag_test.go b/internal/config/cli_flag_test.go index a3207b4f8..65d77278a 100644 --- a/internal/config/cli_flag_test.go +++ b/internal/config/cli_flag_test.go @@ -90,7 +90,7 @@ func TestCliFlag_Default(t *testing.T) { } func TestCliFlag_EnvVar(t *testing.T) { - hasdefault := CliFlag{ + hasDefault := CliFlag{ Flag: cli.StringFlag{ Name: "flag-with-default", Usage: "`STRING`", @@ -100,7 +100,7 @@ func TestCliFlag_EnvVar(t *testing.T) { Tags: []string{"foo", "bar"}, } - assert.Equal(t, "PHOTOPRISM_DEFAULT", hasdefault.EnvVar()) + assert.Equal(t, "PHOTOPRISM_DEFAULT", hasDefault.EnvVar()) } func TestCliFlag_CommandFlag(t *testing.T) { diff --git a/internal/config/cli_flags_report.go b/internal/config/cli_flags_report.go index 4fa4ae204..eec1bbb38 100644 --- a/internal/config/cli_flags_report.go +++ b/internal/config/cli_flags_report.go @@ -1,5 +1,7 @@ package config +import "strings" + // Report returns global config values as a table for reporting. func (f CliFlags) Report() (rows [][]string, cols []string) { cols = []string{"Environment", "CLI Flag", "Default", "Description"} @@ -11,7 +13,7 @@ func (f CliFlags) Report() (rows [][]string, cols []string) { continue } - rows = append(rows, []string{flag.EnvVar(), flag.CommandFlag(), flag.Default(), flag.Usage()}) + rows = append(rows, []string{strings.ReplaceAll(flag.EnvVar(), ",", ", "), flag.CommandFlag(), flag.Default(), flag.Usage()}) } return rows, cols diff --git a/internal/config/client_config.go b/internal/config/client_config.go index fd1e8488a..54d58cd36 100644 --- a/internal/config/client_config.go +++ b/internal/config/client_config.go @@ -4,7 +4,7 @@ import ( "strings" "time" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/customize" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/query" diff --git a/internal/config/client_config_test.go b/internal/config/client_config_test.go index 942531d81..a6553b5f9 100644 --- a/internal/config/client_config_test.go +++ b/internal/config/client_config_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/customize" "github.com/photoprism/photoprism/internal/entity" ) diff --git a/internal/config/config_oidc.go b/internal/config/config_oidc.go index e130f176b..8f34a4a40 100644 --- a/internal/config/config_oidc.go +++ b/internal/config/config_oidc.go @@ -5,14 +5,17 @@ import ( "net/url" "strings" "unicode/utf8" + + "github.com/photoprism/photoprism/internal/auth/acl" + "github.com/photoprism/photoprism/pkg/authn" + "github.com/photoprism/photoprism/pkg/clean" ) const ( - OIDCDefaultScopes = "openid email profile" - OIDCDefaultProviderName = "OpenID" - OIDCDefaultProviderIcon = "img/oidc.svg" - OIDCLoginUri = ApiUri + "/oidc/login" - OIDCRedirectUri = ApiUri + "/oidc/redirect" + OidcDefaultProviderName = "OpenID" + OidcDefaultProviderIcon = "img/oidc.svg" + OidcLoginUri = ApiUri + "/oidc/login" + OidcRedirectUri = ApiUri + "/oidc/redirect" ) // OIDCEnabled checks if sign-on via OpenID Connect (OIDC) is fully configured and enabled. @@ -45,11 +48,6 @@ func (c *Config) OIDCUri() *url.URL { } } -// OIDCInsecure checks if OIDC issuer SSL/TLS certificate verification should be skipped. -func (c *Config) OIDCInsecure() bool { - return c.options.OIDCInsecure -} - // OIDCClient returns the Client ID for single sign-on via OIDC. func (c *Config) OIDCClient() string { return c.options.OIDCClient @@ -63,7 +61,7 @@ func (c *Config) OIDCSecret() string { // OIDCScopes returns the user information scopes for single sign-on via OIDC. func (c *Config) OIDCScopes() string { if c.options.OIDCScopes == "" { - return OIDCDefaultScopes + return authn.OidcScopes } return c.options.OIDCScopes @@ -72,7 +70,7 @@ func (c *Config) OIDCScopes() string { // OIDCProvider returns the OIDC provider name. func (c *Config) OIDCProvider() string { if c.options.OIDCProvider == "" { - return OIDCDefaultProviderName + return OidcDefaultProviderName } return c.options.OIDCProvider @@ -81,20 +79,49 @@ func (c *Config) OIDCProvider() string { // OIDCIcon returns the OIDC provider icon URI. func (c *Config) OIDCIcon() string { if c.options.OIDCIcon == "" { - return c.StaticAssetUri(OIDCDefaultProviderIcon) + return c.StaticAssetUri(OidcDefaultProviderIcon) } return c.options.OIDCIcon } +// OIDCRedirect checks if unauthenticated users should automatically be redirected to the OIDC login page. +func (c *Config) OIDCRedirect() bool { + return c.options.OIDCRedirect +} + // OIDCRegister checks if new accounts may be created via OIDC. func (c *Config) OIDCRegister() bool { return c.options.OIDCRegister } -// OIDCRedirect checks if unauthenticated users should automatically be redirected to the OIDC login page. -func (c *Config) OIDCRedirect() bool { - return c.options.OIDCRedirect +// OIDCUsername returns the claim to use as username when signing up via OIDC. +func (c *Config) OIDCUsername() string { + if c.options.OIDCUsername == authn.ClaimEmail { + return authn.ClaimEmail + } + + return authn.ClaimUsername +} + +// OIDCRole returns the default user role when signing up via OIDC. +func (c *Config) OIDCRole() acl.Role { + if c.options.OIDCRole == "" { + return acl.RoleGuest + } + + role := acl.UserRoles[clean.Role(c.options.OIDCRole)] + + if role != acl.RoleNone { + return role + } + + return acl.RoleNone +} + +// OIDCWebDAV checks if newly registered accounts should be allowed to use WebDAV if their role allows. +func (c *Config) OIDCWebDAV() bool { + return c.options.OIDCWebDAV } // DisableOIDC checks if single sign-on via OpenID Connect (OIDC) should be disabled. @@ -104,12 +131,12 @@ func (c *Config) DisableOIDC() bool { // OIDCLoginUri returns the OIDC login API endpoint URI. func (c *Config) OIDCLoginUri() string { - return c.BaseUri(OIDCLoginUri) + return c.BaseUri(OidcLoginUri) } // OIDCRedirectUri returns the OIDC redirect API endpoint URI. func (c *Config) OIDCRedirectUri() string { - return c.BaseUri(OIDCRedirectUri) + return c.BaseUri(OidcRedirectUri) } // OIDCReport returns the OpenID Connect config values as a table for reporting. @@ -118,14 +145,16 @@ func (c *Config) OIDCReport() (rows [][]string, cols []string) { rows = [][]string{ {"oidc-uri", c.OIDCUri().String()}, - {"oidc-insecure", fmt.Sprintf("%t", c.OIDCInsecure())}, {"oidc-client", c.OIDCClient()}, {"oidc-secret", strings.Repeat("*", utf8.RuneCountInString(c.OIDCSecret()))}, {"oidc-scopes", c.OIDCScopes()}, {"oidc-provider", c.OIDCProvider()}, {"oidc-icon", c.OIDCIcon()}, - {"oidc-register", fmt.Sprintf("%t", c.OIDCRegister())}, {"oidc-redirect", fmt.Sprintf("%t", c.OIDCRedirect())}, + {"oidc-register", fmt.Sprintf("%t", c.OIDCRegister())}, + {"oidc-username", c.OIDCUsername()}, + {"oidc-role", c.OIDCRole().String()}, + {"oidc-webdav", fmt.Sprintf("%t", c.OIDCWebDAV())}, {"disable-oidc", fmt.Sprintf("%t", c.DisableOIDC())}, } diff --git a/internal/config/config_oidc_test.go b/internal/config/config_oidc_test.go index 381fbc01a..681587d4d 100644 --- a/internal/config/config_oidc_test.go +++ b/internal/config/config_oidc_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/internal/auth/acl" + "github.com/photoprism/photoprism/pkg/authn" ) func TestConfig_OIDCEnabled(t *testing.T) { @@ -59,12 +62,6 @@ func TestConfig_OIDCUri(t *testing.T) { assert.Equal(t, "", c.OIDCUri().String()) } -func TestConfig_OIDCInsecure(t *testing.T) { - c := NewConfig(CliTestContext()) - - assert.False(t, c.OIDCInsecure()) -} - func TestConfig_OIDCClient(t *testing.T) { c := NewConfig(CliTestContext()) @@ -80,11 +77,11 @@ func TestConfig_OIDCSecret(t *testing.T) { func TestConfig_OIDCScopes(t *testing.T) { c := NewConfig(CliTestContext()) - assert.Equal(t, OIDCDefaultScopes, c.OIDCScopes()) + assert.Equal(t, authn.OidcScopes, c.OIDCScopes()) c.options.OIDCScopes = "" - assert.Equal(t, OIDCDefaultScopes, c.OIDCScopes()) + assert.Equal(t, authn.OidcScopes, c.OIDCScopes()) } func TestConfig_OIDCProvider(t *testing.T) { @@ -107,16 +104,42 @@ func TestConfig_OIDCIcon(t *testing.T) { assert.Equal(t, "./test.svg", c.OIDCIcon()) } +func TestConfig_OIDCRedirect(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.False(t, c.OIDCRedirect()) +} + +func TestConfig_OIDCUsername(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.Equal(t, authn.ClaimUsername, c.OIDCUsername()) + + c.options.OIDCUsername = "email" + + assert.Equal(t, authn.ClaimEmail, c.OIDCUsername()) + + c.options.OIDCUsername = "" + + assert.Equal(t, authn.ClaimUsername, c.OIDCUsername()) +} + func TestConfig_OIDCRegister(t *testing.T) { c := NewConfig(CliTestContext()) assert.False(t, c.OIDCRegister()) } -func TestConfig_OIDCRedirect(t *testing.T) { +func TestConfig_OIDCRole(t *testing.T) { c := NewConfig(CliTestContext()) - assert.False(t, c.OIDCRedirect()) + assert.Equal(t, acl.RoleGuest, c.OIDCRole()) +} + +func TestConfig_OIDCWebDAV(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.False(t, c.OIDCWebDAV()) } func TestConfig_DisableOIDC(t *testing.T) { diff --git a/internal/config/flags.go b/internal/config/flags.go index 71c64aa9f..c0f286add 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -12,6 +12,7 @@ import ( "github.com/photoprism/photoprism/internal/ffmpeg" "github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/ttl" + "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/header" "github.com/photoprism/photoprism/pkg/i18n" "github.com/photoprism/photoprism/pkg/txt" @@ -36,7 +37,7 @@ var Flags = CliFlags{ Name: "admin-user, login", Usage: "`USERNAME` of the superadmin account that is created on first startup", Value: "admin", - EnvVar: EnvVar("ADMIN_USER"), + EnvVar: EnvVar("ADMIN_USER") + "," + EnvVar("ADMIN_USERNAME"), }}, { Flag: cli.StringFlag{ Name: "admin-password, pw", @@ -49,11 +50,6 @@ var Flags = CliFlags{ Value: "", EnvVar: EnvVar("OIDC_URI"), }}, { - Flag: cli.BoolFlag{ - Name: "oidc-insecure", - Usage: "skip identity provider SSL/TLS certificate verification", - EnvVar: EnvVar("OIDC_INSECURE"), - }}, { Flag: cli.StringFlag{ Name: "oidc-client", Usage: "client `ID` for single sign-on via OpenID Connect", @@ -70,7 +66,7 @@ var Flags = CliFlags{ Name: "oidc-scopes", Hidden: true, Usage: "user information `SCOPES` for single sign-on via OpenID Connect", - Value: OIDCDefaultScopes, + Value: authn.OidcScopes, EnvVar: EnvVar("OIDC_SCOPES"), }}, { Flag: cli.StringFlag{ @@ -85,15 +81,26 @@ var Flags = CliFlags{ Value: "", EnvVar: EnvVar("OIDC_ICON"), }}, { + Flag: cli.BoolFlag{ + Name: "oidc-redirect", + Usage: "automatically redirect unauthenticated users to the configured identity provider", + EnvVar: EnvVar("OIDC_REDIRECT"), + }}, { Flag: cli.BoolFlag{ Name: "oidc-register", Usage: "allow new users to create an account when they sign in with OpenID Connect", EnvVar: EnvVar("OIDC_REGISTER"), }}, { + Flag: cli.StringFlag{ + Name: "oidc-username", + Usage: "username `CLAIM` for OpenID Connect users (preferred_username, email)", + Value: authn.ClaimUsername, + EnvVar: EnvVar("OIDC_USERNAME"), + }}, { Flag: cli.BoolFlag{ - Name: "oidc-redirect", - Usage: "automatically redirect unauthenticated users to the configured identity provider", - EnvVar: EnvVar("OIDC_REDIRECT"), + Name: "oidc-webdav", + Usage: "enable WebDAV for new OpenID Connect users if their role allows it", + EnvVar: EnvVar("OIDC_WEBDAV"), }}, { Flag: cli.BoolFlag{ Name: "disable-oidc", @@ -274,7 +281,7 @@ var Flags = CliFlags{ Name: "index-workers, workers", Usage: "maximum `NUMBER` of indexing workers, default depends on the number of physical cores", Value: cpuid.CPU.PhysicalCores / 2, - EnvVar: EnvVar("INDEX_WORKERS") + ", " + EnvVar("WORKERS"), + EnvVar: EnvVar("INDEX_WORKERS") + "," + EnvVar("WORKERS"), }}, { Flag: cli.StringFlag{ Name: "index-schedule", @@ -753,7 +760,7 @@ var Flags = CliFlags{ Name: "sips-exclude", Usage: "file `EXTENSIONS` not to be used with Sips *macOS only*", Value: "avif, avifs, thm", - EnvVar: EnvVar("SIPS_EXCLUDE") + ", " + EnvVar("SIPS_BLACKLIST"), + EnvVar: EnvVar("SIPS_EXCLUDE") + "," + EnvVar("SIPS_BLACKLIST"), }}, { Flag: cli.StringFlag{ Name: "darktable-bin", @@ -765,7 +772,7 @@ var Flags = CliFlags{ Name: "darktable-exclude", Usage: "file `EXTENSIONS` not to be used with Darktable", Value: "thm", - EnvVar: EnvVar("DARKTABLE_EXCLUDE") + ", " + EnvVar("DARKTABLE_BLACKLIST"), + EnvVar: EnvVar("DARKTABLE_EXCLUDE") + "," + EnvVar("DARKTABLE_BLACKLIST"), }}, { Flag: cli.StringFlag{ Name: "darktable-cache-path", @@ -789,7 +796,7 @@ var Flags = CliFlags{ Name: "rawtherapee-exclude", Usage: "file `EXTENSIONS` not to be used with RawTherapee", Value: "dng, thm", - EnvVar: EnvVar("RAWTHERAPEE_EXCLUDE") + ", " + EnvVar("RAWTHERAPEE_BLACKLIST"), + EnvVar: EnvVar("RAWTHERAPEE_EXCLUDE") + "," + EnvVar("RAWTHERAPEE_BLACKLIST"), }}, { Flag: cli.StringFlag{ Name: "imagemagick-bin", @@ -801,7 +808,7 @@ var Flags = CliFlags{ Name: "imagemagick-exclude", Usage: "file `EXTENSIONS` not to be used with ImageMagick", Value: "heif, heic, heics, avif, avifs, jxl, thm", - EnvVar: EnvVar("IMAGEMAGICK_EXCLUDE") + ", " + EnvVar("IMAGEMAGICK_BLACKLIST"), + EnvVar: EnvVar("IMAGEMAGICK_EXCLUDE") + "," + EnvVar("IMAGEMAGICK_BLACKLIST"), }}, { Flag: cli.StringFlag{ Name: "heifconvert-bin", diff --git a/internal/config/options.go b/internal/config/options.go index ef77ee2d7..b92b36f57 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -32,14 +32,16 @@ type Options struct { RegisterUri string `yaml:"RegisterUri" json:"-" flag:"register-uri"` LoginUri string `yaml:"LoginUri" json:"-" flag:"login-uri"` OIDCUri string `yaml:"OIDCUri" json:"-" flag:"oidc-uri"` - OIDCInsecure bool `yaml:"OIDCInsecure" json:"-" flag:"oidc-insecure"` OIDCClient string `yaml:"OIDCClient" json:"-" flag:"oidc-client"` OIDCSecret string `yaml:"OIDCSecret" json:"-" flag:"oidc-secret"` OIDCScopes string `yaml:"OIDCScopes" json:"-" flag:"oidc-scopes"` OIDCProvider string `yaml:"OIDCProvider" json:"OIDCProvider" flag:"oidc-provider"` OIDCIcon string `yaml:"OIDCIcon" json:"OIDCIcon" flag:"oidc-icon"` - OIDCRegister bool `yaml:"OIDCRegister" json:"OIDCRegister" flag:"oidc-register"` OIDCRedirect bool `yaml:"OIDCRedirect" json:"OIDCRedirect" flag:"oidc-redirect"` + OIDCRegister bool `yaml:"OIDCRegister" json:"OIDCRegister" flag:"oidc-register"` + OIDCUsername string `yaml:"OIDCUsername" json:"-" flag:"oidc-username"` + OIDCRole string `yaml:"OIDCRole" json:"-" flag:"oidc-role"` + OIDCWebDAV bool `yaml:"OIDCWebDAV" json:"-" flag:"oidc-webdav"` DisableOIDC bool `yaml:"DisableOIDC" json:"DisableOIDC" flag:"disable-oidc"` SessionMaxAge int64 `yaml:"SessionMaxAge" json:"-" flag:"session-maxage"` SessionTimeout int64 `yaml:"SessionTimeout" json:"-" flag:"session-timeout"` diff --git a/internal/config/settings.go b/internal/config/settings.go index 430d77423..bde01dbe0 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -1,7 +1,7 @@ package config import ( - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/customize" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/pkg/fs" diff --git a/internal/config/test.go b/internal/config/test.go index c92ec9a3b..8f8b31fd4 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -17,6 +17,7 @@ import ( "github.com/photoprism/photoprism/internal/customize" "github.com/photoprism/photoprism/internal/thumb" + "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/capture" "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/fs" @@ -266,7 +267,7 @@ func CliTestContext() *cli.Context { LogErr(c.Set("oidc-uri", config.OIDCUri)) LogErr(c.Set("oidc-client", config.OIDCClient)) LogErr(c.Set("oidc-secret", config.OIDCSecret)) - LogErr(c.Set("oidc-scopes", OIDCDefaultScopes)) + LogErr(c.Set("oidc-scopes", authn.OidcScopes)) LogErr(c.Set("storage-path", config.StoragePath)) LogErr(c.Set("sidecar-path", config.SidecarPath)) LogErr(c.Set("sidecar-yaml", fmt.Sprintf("%t", config.SidecarYaml))) diff --git a/internal/customize/acl.go b/internal/customize/acl.go index bc5774f6e..c674817ca 100644 --- a/internal/customize/acl.go +++ b/internal/customize/acl.go @@ -1,7 +1,7 @@ package customize import ( - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" ) // ApplyACL updates the current settings based on the access control list provided. diff --git a/internal/customize/acl_test.go b/internal/customize/acl_test.go index 918a80482..5c2371d2d 100644 --- a/internal/customize/acl_test.go +++ b/internal/customize/acl_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" ) func TestSettings_ApplyACL(t *testing.T) { diff --git a/internal/customize/scope.go b/internal/customize/scope.go index 5c5bb037b..c34a4b03f 100644 --- a/internal/customize/scope.go +++ b/internal/customize/scope.go @@ -3,7 +3,7 @@ package customize import ( "strings" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/pkg/list" ) diff --git a/internal/customize/scope_test.go b/internal/customize/scope_test.go index efdee8d3b..193ddac6c 100644 --- a/internal/customize/scope_test.go +++ b/internal/customize/scope_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" ) func TestSettings_ApplyScope(t *testing.T) { diff --git a/internal/entity/auth_client.go b/internal/entity/auth_client.go index 5cc122ea8..1f48c5b1f 100644 --- a/internal/entity/auth_client.go +++ b/internal/entity/auth_client.go @@ -9,7 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/authn" diff --git a/internal/entity/auth_client_fixtures.go b/internal/entity/auth_client_fixtures.go index 5ca6bea16..d155c1b21 100644 --- a/internal/entity/auth_client_fixtures.go +++ b/internal/entity/auth_client_fixtures.go @@ -1,7 +1,7 @@ package entity import ( - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/unix" ) diff --git a/internal/entity/auth_session.go b/internal/entity/auth_session.go index 77b095a49..1a1842831 100644 --- a/internal/entity/auth_session.go +++ b/internal/entity/auth_session.go @@ -11,7 +11,7 @@ import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/server/limiter" "github.com/photoprism/photoprism/pkg/authn" @@ -57,8 +57,8 @@ type Session struct { PreviewToken string `gorm:"type:VARBINARY(64);column:preview_token;default:'';" json:"-" yaml:"-"` DownloadToken string `gorm:"type:VARBINARY(64);column:download_token;default:'';" json:"-" yaml:"-"` AccessToken string `gorm:"type:VARBINARY(4096);column:access_token;default:'';" json:"-" yaml:"-"` - RefreshToken string `gorm:"type:VARBINARY(512);column:refresh_token;default:'';" json:"-" yaml:"-"` - IdToken string `gorm:"type:VARBINARY(1024);column:id_token;default:'';" json:"IdToken,omitempty" yaml:"IdToken,omitempty"` + RefreshToken string `gorm:"type:VARBINARY(2048);column:refresh_token;default:'';" json:"-" yaml:"-"` + IdToken string `gorm:"type:VARBINARY(2048);column:id_token;default:'';" json:"IdToken,omitempty" yaml:"IdToken,omitempty"` UserAgent string `gorm:"size:512;" json:"UserAgent" yaml:"UserAgent,omitempty"` DataJSON json.RawMessage `gorm:"type:VARBINARY(4096);" json:"-" yaml:"Data,omitempty"` data *SessionData `gorm:"-" yaml:"-"` diff --git a/internal/entity/auth_session_login.go b/internal/entity/auth_session_login.go index d94b50cb7..29242b796 100644 --- a/internal/entity/auth_session_login.go +++ b/internal/entity/auth_session_login.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/authn" diff --git a/internal/entity/auth_session_login_test.go b/internal/entity/auth_session_login_test.go index f55a8630f..3818a955a 100644 --- a/internal/entity/auth_session_login_test.go +++ b/internal/entity/auth_session_login_test.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/rnd" diff --git a/internal/entity/auth_session_test.go b/internal/entity/auth_session_test.go index 5f3f2bf88..f8481cfd8 100644 --- a/internal/entity/auth_session_test.go +++ b/internal/entity/auth_session_test.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/header" "github.com/photoprism/photoprism/pkg/report" diff --git a/internal/entity/auth_user.go b/internal/entity/auth_user.go index 96f807e03..ace132997 100644 --- a/internal/entity/auth_user.go +++ b/internal/entity/auth_user.go @@ -10,8 +10,9 @@ import ( "github.com/jinzhu/gorm" "github.com/ulule/deepcopier" + "github.com/zitadel/oidc/pkg/oidc" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/authn" @@ -103,7 +104,35 @@ func NewUser() (m *User) { } } -// LdapUser creates an LDAP user entity. +// OidcUser creates a new OIDC user entity. +func OidcUser(userInfo oidc.UserInfo, usernameClaim string) User { + var userName, userEmail string + + switch usernameClaim { + case authn.ClaimEmail: + userName = clean.Username(userInfo.GetEmail()) + default: + userName = clean.Username(userInfo.GetPreferredUsername()) + } + + userEmail = clean.Email(userInfo.GetEmail()) + + authId := clean.Auth(userInfo.GetSubject()) + + if userName == "" || authId == "" { + return User{} + } + + return User{ + DisplayName: userInfo.GetName(), + UserName: userName, + UserEmail: userEmail, + AuthID: authId, + AuthProvider: authn.ProviderOIDC.String(), + } +} + +// LdapUser creates a new LDAP user entity. func LdapUser(username, dn string) User { return User{ UserName: clean.Username(username), diff --git a/internal/entity/auth_user_default.go b/internal/entity/auth_user_default.go index a7b3c2959..59aa2e0e8 100644 --- a/internal/entity/auth_user_default.go +++ b/internal/entity/auth_user_default.go @@ -1,7 +1,7 @@ package entity import ( - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/pkg/authn" ) diff --git a/internal/entity/auth_user_fixtures.go b/internal/entity/auth_user_fixtures.go index 373b33406..f0fc04c7b 100644 --- a/internal/entity/auth_user_fixtures.go +++ b/internal/entity/auth_user_fixtures.go @@ -1,7 +1,7 @@ package entity import ( - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/pkg/authn" ) diff --git a/internal/entity/auth_user_fixtures_test.go b/internal/entity/auth_user_fixtures_test.go index e189692e3..f583c1806 100644 --- a/internal/entity/auth_user_fixtures_test.go +++ b/internal/entity/auth_user_fixtures_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" ) func TestUserMap_Get(t *testing.T) { diff --git a/internal/entity/auth_user_test.go b/internal/entity/auth_user_test.go index 0c7b6f398..70fb06fd1 100644 --- a/internal/entity/auth_user_test.go +++ b/internal/entity/auth_user_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/rnd" diff --git a/internal/entity/src.go b/internal/entity/src.go index ad2e908cf..32b81aa6a 100644 --- a/internal/entity/src.go +++ b/internal/entity/src.go @@ -11,6 +11,7 @@ const ( SrcEstimate = "estimate" // Prio 2 SrcName = "name" // Prio 4 SrcYaml = "yaml" // Prio 8 + SrcOIDC = "oidc" // Prio 8 SrcLDAP = "ldap" // Prio 8 SrcLocation = classify.SrcLocation // Prio 8 SrcMarker = "marker" // Prio 8 @@ -38,6 +39,7 @@ var SrcPriority = Priorities{ SrcEstimate: 2, SrcName: 4, SrcYaml: 8, + SrcOIDC: 8, SrcLDAP: 8, SrcLocation: 8, SrcMarker: 8, diff --git a/internal/form/client.go b/internal/form/client.go index 3d8efe315..ca6af4dc4 100644 --- a/internal/form/client.go +++ b/internal/form/client.go @@ -3,7 +3,7 @@ package form import ( "github.com/urfave/cli" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/rnd" diff --git a/internal/form/oauth_create_token.go b/internal/form/oauth_create_token.go index d195a0bcb..8285b957c 100644 --- a/internal/form/oauth_create_token.go +++ b/internal/form/oauth_create_token.go @@ -41,7 +41,18 @@ func (f OAuthCreateToken) Validate() error { } else if !rnd.IsAlnum(f.ClientSecret) { return authn.ErrInvalidCredentials } - case authn.GrantPassword, authn.GrantSession: + case authn.GrantSession: + // Validate request credentials. + if f.Username == "" { + return authn.ErrUsernameRequired + } else if len(f.Username) > txt.ClipUsername { + return authn.ErrInvalidCredentials + } else if f.ClientName == "" { + return authn.ErrNameRequired + } else if f.Scope == "" { + return authn.ErrScopeRequired + } + case authn.GrantPassword: // Validate request credentials. if f.Username == "" { return authn.ErrUsernameRequired diff --git a/internal/get/oidc.go b/internal/get/oidc.go index c8e28e46d..670bca2a5 100644 --- a/internal/get/oidc.go +++ b/internal/get/oidc.go @@ -3,7 +3,7 @@ package get import ( "sync" - "github.com/photoprism/photoprism/internal/oidc" + "github.com/photoprism/photoprism/internal/auth/oidc" ) var onceOidc sync.Once diff --git a/internal/get/services.go b/internal/get/services.go index a482d559d..5feb5456b 100644 --- a/internal/get/services.go +++ b/internal/get/services.go @@ -1,5 +1,5 @@ /* -Package service provides a registry for common services. +Package get provides a registry for common application services. Copyright (c) 2018 - 2024 PhotoPrism UG. All rights reserved. @@ -25,11 +25,11 @@ Additional information can be found in our Developer Guide: package get import ( + "github.com/photoprism/photoprism/internal/auth/oidc" "github.com/photoprism/photoprism/internal/classify" "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/face" "github.com/photoprism/photoprism/internal/nsfw" - "github.com/photoprism/photoprism/internal/oidc" "github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/session" diff --git a/internal/get/services_test.go b/internal/get/services_test.go index 9e6a6c64d..439a9ef05 100644 --- a/internal/get/services_test.go +++ b/internal/get/services_test.go @@ -6,9 +6,9 @@ import ( gc "github.com/patrickmn/go-cache" "github.com/stretchr/testify/assert" + "github.com/photoprism/photoprism/internal/auth/oidc" "github.com/photoprism/photoprism/internal/classify" "github.com/photoprism/photoprism/internal/nsfw" - "github.com/photoprism/photoprism/internal/oidc" "github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/session" diff --git a/internal/migrate/dialect_mysql.go b/internal/migrate/dialect_mysql.go index 5934d1e38..d1ba652aa 100644 --- a/internal/migrate/dialect_mysql.go +++ b/internal/migrate/dialect_mysql.go @@ -171,4 +171,10 @@ var DialectMySQL = Migrations{ Stage: "main", Statements: []string{"TRUNCATE auth_sessions;"}, }, + { + ID: "20240701-000001", + Dialect: "mysql", + Stage: "main", + Statements: []string{"ALTER TABLE auth_sessions MODIFY IF EXISTS refresh_token VARBINARY(2048);", "ALTER TABLE auth_sessions MODIFY IF EXISTS id_token VARBINARY(2048);"}, + }, } diff --git a/internal/migrate/mysql/20240701-000001.sql b/internal/migrate/mysql/20240701-000001.sql new file mode 100644 index 000000000..dfe7d24c4 --- /dev/null +++ b/internal/migrate/mysql/20240701-000001.sql @@ -0,0 +1,2 @@ +ALTER TABLE auth_sessions MODIFY IF EXISTS refresh_token VARBINARY(2048); +ALTER TABLE auth_sessions MODIFY IF EXISTS id_token VARBINARY(2048); \ No newline at end of file diff --git a/internal/search/albums.go b/internal/search/albums.go index 7a5672158..e1b317ee5 100644 --- a/internal/search/albums.go +++ b/internal/search/albums.go @@ -7,7 +7,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/jinzhu/gorm" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/rnd" diff --git a/internal/search/photos.go b/internal/search/photos.go index 47932061b..f510f63ed 100644 --- a/internal/search/photos.go +++ b/internal/search/photos.go @@ -9,7 +9,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/jinzhu/gorm" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/search/photos_geo.go b/internal/search/photos_geo.go index 3f9f0d318..77d6c56fb 100644 --- a/internal/search/photos_geo.go +++ b/internal/search/photos_geo.go @@ -9,7 +9,7 @@ import ( "github.com/dustin/go-humanize/english" "github.com/jinzhu/gorm" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" diff --git a/internal/server/webdav_auth.go b/internal/server/webdav_auth.go index 93eab3672..6e5010be5 100644 --- a/internal/server/webdav_auth.go +++ b/internal/server/webdav_auth.go @@ -10,8 +10,8 @@ import ( "github.com/gin-gonic/gin" gc "github.com/patrickmn/go-cache" - "github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/api" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" diff --git a/internal/server/webdav_auth_test.go b/internal/server/webdav_auth_test.go index 9d755d3bd..3a07653ae 100644 --- a/internal/server/webdav_auth_test.go +++ b/internal/server/webdav_auth_test.go @@ -10,7 +10,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/pkg/header" diff --git a/internal/server/wellknown/oauth.go b/internal/server/wellknown/oauth.go index 58a7f57b2..49d40cf89 100644 --- a/internal/server/wellknown/oauth.go +++ b/internal/server/wellknown/oauth.go @@ -3,7 +3,7 @@ package wellknown import ( "fmt" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/config" ) diff --git a/internal/server/wellknown/openid.go b/internal/server/wellknown/openid.go index 6c0ec426a..8468f7f48 100644 --- a/internal/server/wellknown/openid.go +++ b/internal/server/wellknown/openid.go @@ -3,7 +3,7 @@ package wellknown import ( "fmt" - "github.com/photoprism/photoprism/internal/acl" + "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/config" ) diff --git a/pkg/authn/errors.go b/pkg/authn/errors.go index f5c0de6ad..4d318de3d 100644 --- a/pkg/authn/errors.go +++ b/pkg/authn/errors.go @@ -13,6 +13,8 @@ var ( ErrAccountAlreadyExists = errors.New("account already exists") ErrAccountNotFound = errors.New("account not found") ErrAccountDisabled = errors.New("account disabled") + ErrAccountCreateFailed = errors.New("failed to create account") + ErrAccountUpdateFailed = errors.New("failed to update account") ErrInvalidRequest = errors.New("invalid request") ErrInvalidCredentials = errors.New("invalid credentials") ErrInvalidShareToken = errors.New("invalid share token") @@ -27,13 +29,16 @@ var ( ErrRateLimitExceeded = errors.New("rate limit exceeded") ) -// OAuth2-related error messages: +// OIDC and OAuth2-related error messages: var ( ErrInvalidGrantType = errors.New("invalid grant type") ErrInvalidClientID = errors.New("invalid client id") + ErrInvalidAuthID = errors.New("invalid auth id") + ErrAuthCodeRequired = errors.New("auth code required") ErrClientIDRequired = errors.New("client id required") ErrInvalidClientSecret = errors.New("invalid client secret") ErrClientSecretRequired = errors.New("client secret required") + ErrRegistrationDisabled = errors.New("registration disabled") ) // User-related error messages: diff --git a/pkg/authn/oidc.go b/pkg/authn/oidc.go new file mode 100644 index 000000000..c08253f92 --- /dev/null +++ b/pkg/authn/oidc.go @@ -0,0 +1,7 @@ +package authn + +const ( + ClaimEmail = "email" + ClaimUsername = "preferred_username" + OidcScopes = "openid email profile" +)