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"
+)