Compare commits

...

3 Commits

Author SHA1 Message Date
dependabot[bot]
2ea921f3ca chore(deps): bump the minor-patch group in /frontend with 3 updates (#411)
Bumps the minor-patch group in /frontend with 3 updates: [i18next](https://github.com/i18next/i18next), [react-hook-form](https://github.com/react-hook-form/react-hook-form) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `i18next` from 25.5.3 to 25.6.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.5.3...v25.6.0)

Updates `react-hook-form` from 7.64.0 to 7.65.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.64.0...v7.65.0)

Updates `@types/node` from 24.7.1 to 24.7.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 25.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.65.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 24.7.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-13 19:14:30 +03:00
dependabot[bot]
473109b36a chore(deps): bump oven/bun from 1.2.23-alpine to 1.3.0-alpine (#410)
Bumps oven/bun from 1.2.23-alpine to 1.3.0-alpine.

---
updated-dependencies:
- dependency-name: oven/bun
  dependency-version: 1.3.0-alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-13 19:14:11 +03:00
Scott McKendry
f628d1f0b3 fix(redirect): allow root cookie domain host redirects (#409)
Previously IsRedirectSafe rejected redirects to the exact cookie domain
when AppURL had multiple subdomain levels, because it stripped the first
label twice.
2025-10-13 11:55:43 +03:00
6 changed files with 53 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
# Site builder # Site builder
FROM oven/bun:1.2.23-alpine AS frontend-builder FROM oven/bun:1.3.0-alpine AS frontend-builder
WORKDIR /frontend WORKDIR /frontend

View File

@@ -1,5 +1,5 @@
# Site builder # Site builder
FROM oven/bun:1.2.23-alpine AS frontend-builder FROM oven/bun:1.3.0-alpine AS frontend-builder
WORKDIR /frontend WORKDIR /frontend

View File

@@ -14,7 +14,7 @@
"axios": "^1.12.2", "axios": "^1.12.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"i18next": "^25.5.3", "i18next": "^25.6.0",
"i18next-browser-languagedetector": "^8.2.0", "i18next-browser-languagedetector": "^8.2.0",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
@@ -22,7 +22,7 @@
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-hook-form": "^7.64.0", "react-hook-form": "^7.65.0",
"react-i18next": "^16.0.0", "react-i18next": "^16.0.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router": "^7.9.4", "react-router": "^7.9.4",
@@ -34,7 +34,7 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.37.0", "@eslint/js": "^9.37.0",
"@tanstack/eslint-plugin-query": "^5.91.0", "@tanstack/eslint-plugin-query": "^5.91.0",
"@types/node": "^24.7.1", "@types/node": "^24.7.2",
"@types/react": "^19.2.2", "@types/react": "^19.2.2",
"@types/react-dom": "^19.2.1", "@types/react-dom": "^19.2.1",
"@vitejs/plugin-react": "^5.0.4", "@vitejs/plugin-react": "^5.0.4",
@@ -355,7 +355,7 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@24.7.1", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-CmyhGZanP88uuC5GpWU9q+fI61j2SkhO3UGMUdfYRE6Bcy0ccyzn1Rqj9YAB/ZY4kOXmNf0ocah5GtphmLMP6Q=="], "@types/node": ["@types/node@24.7.2", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
@@ -583,7 +583,7 @@
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
"i18next": ["i18next@25.5.3", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-joFqorDeQ6YpIXni944upwnuHBf5IoPMuqAchGVeQLdWC2JOjxgM9V8UGLhNIIH/Q8QleRxIi0BSRQehSrDLcg=="], "i18next": ["i18next@25.6.0", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw=="],
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="], "i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="],
@@ -791,7 +791,7 @@
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
"react-hook-form": ["react-hook-form@7.64.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-fnN+vvTiMLnRqKNTVhDysdrUay0kUUAymQnFIznmgDvapjveUWOOPqMNzPg+A+0yf9DuE2h6xzBjN1s+Qx8wcg=="], "react-hook-form": ["react-hook-form@7.65.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw=="],
"react-i18next": ["react-i18next@16.0.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 25.5.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-JQ+dFfLnFSKJQt7W01lJHWRC0SX7eDPobI+MSTJ3/gP39xH2g33AuTE7iddAfXYHamJdAeMGM0VFboPaD3G68Q=="], "react-i18next": ["react-i18next@16.0.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 25.5.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-JQ+dFfLnFSKJQt7W01lJHWRC0SX7eDPobI+MSTJ3/gP39xH2g33AuTE7iddAfXYHamJdAeMGM0VFboPaD3G68Q=="],

View File

@@ -20,7 +20,7 @@
"axios": "^1.12.2", "axios": "^1.12.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"i18next": "^25.5.3", "i18next": "^25.6.0",
"i18next-browser-languagedetector": "^8.2.0", "i18next-browser-languagedetector": "^8.2.0",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
@@ -28,7 +28,7 @@
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-hook-form": "^7.64.0", "react-hook-form": "^7.65.0",
"react-i18next": "^16.0.0", "react-i18next": "^16.0.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router": "^7.9.4", "react-router": "^7.9.4",
@@ -40,7 +40,7 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.37.0", "@eslint/js": "^9.37.0",
"@tanstack/eslint-plugin-query": "^5.91.0", "@tanstack/eslint-plugin-query": "^5.91.0",
"@types/node": "^24.7.1", "@types/node": "^24.7.2",
"@types/react": "^19.2.2", "@types/react": "^19.2.2",
"@types/react-dom": "^19.2.1", "@types/react-dom": "^19.2.1",
"@vitejs/plugin-react": "^5.0.4", "@vitejs/plugin-react": "^5.0.4",

View File

@@ -100,17 +100,17 @@ func IsRedirectSafe(redirectURL string, domain string) bool {
return false return false
} }
cookieDomain, err := GetCookieDomain(redirectURL) host := parsedURL.Hostname()
if host == domain {
return true
}
cookieDomain, err := GetCookieDomain(redirectURL)
if err != nil { if err != nil {
return false return false
} }
if cookieDomain != domain { return cookieDomain == domain
return false
}
return true
} }
func GetLogLevel(level string) zerolog.Level { func GetLogLevel(level string) zerolog.Level {

View File

@@ -164,7 +164,7 @@ func TestIsRedirectSafe(t *testing.T) {
// Case with no subdomain // Case with no subdomain
redirectURL := "http://example.com/welcome" redirectURL := "http://example.com/welcome"
result := utils.IsRedirectSafe(redirectURL, domain) result := utils.IsRedirectSafe(redirectURL, domain)
assert.Equal(t, false, result) assert.Equal(t, true, result)
// Case with different domain // Case with different domain
redirectURL = "http://malicious.com/phishing" redirectURL = "http://malicious.com/phishing"
@@ -202,6 +202,41 @@ func TestIsRedirectSafe(t *testing.T) {
assert.Equal(t, false, result) assert.Equal(t, false, result)
} }
func TestIsRedirectSafeMultiLevel(t *testing.T) {
// Setup
cookieDomain := "tinyauth.example.com"
// Case with 3rd level domain
redirectURL := "http://tinyauth.example.com/welcome"
result := utils.IsRedirectSafe(redirectURL, cookieDomain)
assert.Equal(t, true, result)
// Case with root domain
redirectURL = "http://example.com/unsafe"
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
assert.Equal(t, false, result)
// Case with 4th level domain
redirectURL = "http://auth.tinyauth.example.com/post-login"
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
assert.Equal(t, true, result)
// Case with 5th level domain (should be unsafe)
redirectURL = "http://x.auth.tinyauth.example.com/deep"
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
assert.Equal(t, false, result)
// Case with different subdomain
redirectURL = "http://auth.tinyauth.example.net/attack"
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
assert.Equal(t, false, result)
// Case with malformed URL
redirectURL = "http://[::1]:namedport"
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
assert.Equal(t, false, result)
}
func TestGetOAuthProvidersConfig(t *testing.T) { func TestGetOAuthProvidersConfig(t *testing.T) {
env := []string{"PROVIDERS_CLIENT1_CLIENT_ID=client1-id", "PROVIDERS_CLIENT1_CLIENT_SECRET=client1-secret"} env := []string{"PROVIDERS_CLIENT1_CLIENT_ID=client1-id", "PROVIDERS_CLIENT1_CLIENT_SECRET=client1-secret"}
args := []string{"/tinyauth/tinyauth", "--providers-client2-client-id=client2-id", "--providers-client2-client-secret=client2-secret"} args := []string{"/tinyauth/tinyauth", "--providers-client2-client-id=client2-id", "--providers-client2-client-secret=client2-secret"}