Compare commits

...

8 Commits

Author SHA1 Message Date
hobokenchicken 92be2a30d1 fix(desktop): implement bearer token auth for windows webview2
Release Desktop Apps / build-linux (push) Successful in 2m49s
Release Desktop Apps / release (push) Has been cancelled
Release Desktop Apps / build-windows (push) Has been cancelled
2026-07-16 14:30:28 -04:00
hobokenchicken f4f6e8560b fix(auth): set SameSite=None for session cookies to fix cross-origin session loss in Tauri apps 2026-07-16 14:26:18 -04:00
hobokenchicken 1900dd9cb1 fix(api): add CORS headers to allow cross-origin requests from Tauri desktop app 2026-07-16 14:21:15 -04:00
hobokenchicken 1226bd28aa added firebase json 2026-07-16 14:07:29 -04:00
hobokenchicken 7cdee73542 fix(tauri): unregister and prevent service worker to fix 404 cache routing issues
Release Desktop Apps / build-linux (push) Successful in 3m17s
Release Desktop Apps / build-windows (push) Successful in 3h17m8s
Release Desktop Apps / release (push) Successful in 13s
2026-07-16 13:58:06 -04:00
hobokenchicken e8ba8ffdba fix(tauri): redirect relative fetch and websocket urls to production backend for desktop app
Release Desktop Apps / build-linux (push) Successful in 3m2s
Release Desktop Apps / build-windows (push) Successful in 3h24m0s
Release Desktop Apps / release (push) Successful in 10s
2026-07-16 13:27:33 -04:00
hobokenchicken 52298d1d46 fix(ci): manually configure caching using actions/cache@v3 for gitea compatibility
Release Desktop Apps / build-linux (push) Successful in 10m35s
Release Desktop Apps / build-windows (push) Successful in 3h16m21s
Release Desktop Apps / release (push) Successful in 11s
2026-07-16 13:03:56 -04:00
hobokenchicken 7fc5d66b8c fix(ci): recursively glob artifact files to prevent empty releases
Release Desktop Apps / build-linux (push) Failing after 11m16s
Release Desktop Apps / release (push) Has been cancelled
Release Desktop Apps / build-windows (push) Has been cancelled
2026-07-16 12:47:45 -04:00
9 changed files with 153 additions and 20 deletions
+26 -12
View File
@@ -10,14 +10,21 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: { node-version: 20 }
- name: Cache npm
uses: actions/cache@v3
with: with:
node-version: 20 path: ~/.npm
cache: 'npm' key: ${{ runner.os }}-npm-${{ hashFiles('web/package-lock.json') }}
cache-dependency-path: 'web/package-lock.json'
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - name: Cache Rust target and registry
uses: actions/cache@v3
with: with:
workspaces: 'web/src-tauri' path: |
~/.cargo/registry
~/.cargo/git
web/src-tauri/target
key: ${{ runner.os }}-cargo-${{ hashFiles('web/src-tauri/Cargo.lock') }}
- name: Install system deps - name: Install system deps
run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libappindicator3-dev librsvg2-dev patchelf rpm run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libappindicator3-dev librsvg2-dev patchelf rpm
- name: Build frontend - name: Build frontend
@@ -40,10 +47,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: { node-version: 20 }
- name: Cache npm
uses: actions/cache@v3
with: with:
node-version: 20 path: ~/.npm
cache: 'npm' key: ${{ runner.os }}-npm-${{ hashFiles('web/package-lock.json') }}
cache-dependency-path: 'web/package-lock.json'
- name: Install Rust - name: Install Rust
run: | run: |
curl.exe -sLo rustup-init.exe https://win.rustup.rs/x86_64 curl.exe -sLo rustup-init.exe https://win.rustup.rs/x86_64
@@ -51,9 +60,11 @@ jobs:
set PATH=%USERPROFILE%\.cargo\bin;%PATH% set PATH=%USERPROFILE%\.cargo\bin;%PATH%
rustc --version rustc --version
shell: cmd shell: cmd
- uses: Swatinem/rust-cache@v2 - name: Cache Rust target and registry
uses: Swatinem/rust-cache@v2
with: with:
workspaces: 'web/src-tauri' workspaces: |
web/src-tauri
- name: Build frontend - name: Build frontend
run: cd web && npm ci && npm run build run: cd web && npm ci && npm run build
shell: cmd shell: cmd
@@ -82,5 +93,8 @@ jobs:
with: with:
tag_name: ${{ github.ref_name }} tag_name: ${{ github.ref_name }}
files: | files: |
linux-bundles/* linux-bundles/**/*.AppImage
windows-bundles/* linux-bundles/**/*.deb
linux-bundles/**/*.rpm
windows-bundles/**/*.msi
windows-bundles/**/*.exe
+12 -1
View File
@@ -37,6 +37,7 @@ import (
"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/webhook" "git.dustin.coffee/hobokenchicken/dumpsterChat/internal/webhook"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
chimw "github.com/go-chi/chi/v5/middleware" chimw "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
httpSwagger "github.com/swaggo/http-swagger" httpSwagger "github.com/swaggo/http-swagger"
) )
@@ -70,7 +71,7 @@ func main() {
sessionStore := auth.NewSessionStore(database.DB, cfg) sessionStore := auth.NewSessionStore(database.DB, cfg)
// WebSocket origin allowlist // WebSocket origin allowlist
wsOrigins := []string{"https://" + cfg.Host} wsOrigins := []string{"https://" + cfg.Host, "http://tauri.localhost", "https://tauri.localhost", "tauri://localhost"}
if cfg.Host == "localhost" { if cfg.Host == "localhost" {
wsOrigins = append(wsOrigins, "http://localhost:"+cfg.Port) wsOrigins = append(wsOrigins, "http://localhost:"+cfg.Port)
} }
@@ -119,6 +120,16 @@ func main() {
memberHandler := server.NewMemberHandler(database.DB) memberHandler := server.NewMemberHandler(database.DB)
r := chi.NewRouter() r := chi.NewRouter()
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"https://" + cfg.Host, "http://localhost:" + cfg.Port, "http://tauri.localhost", "https://tauri.localhost", "tauri://localhost"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
}))
r.Use(chimw.Logger) r.Use(chimw.Logger)
r.Use(chimw.Recoverer) r.Use(chimw.Recoverer)
r.Use(chimw.RequestID) r.Use(chimw.RequestID)
+1
View File
@@ -49,6 +49,7 @@ require (
github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/fxamacker/cbor/v2 v2.9.2 // indirect
github.com/gammazero/deque v1.2.1 // indirect github.com/gammazero/deque v1.2.1 // indirect
github.com/go-chi/cors v1.2.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
+2
View File
@@ -87,6 +87,8 @@ github.com/gammazero/deque v1.2.1 h1:9fnQVFCCZ9/NOc7ccTNqzoKd1tCWOqeI05/lPqFPMGQ
github.com/gammazero/deque v1.2.1/go.mod h1:5nSFkzVm+afG9+gy0VIowlqVAW4N8zNcMne+CMQVD2g= github.com/gammazero/deque v1.2.1/go.mod h1:5nSFkzVm+afG9+gy0VIowlqVAW4N8zNcMne+CMQVD2g=
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+1 -1
View File
@@ -17,7 +17,7 @@ func SetSessionCookie(w http.ResponseWriter, cookieName, token string, duration
Path: "/", Path: "/",
HttpOnly: true, HttpOnly: true,
Secure: secure, Secure: secure,
SameSite: http.SameSiteLaxMode, SameSite: http.SameSiteNoneMode,
MaxAge: int(duration.Seconds()), MaxAge: int(duration.Seconds()),
}) })
} }
+4 -2
View File
@@ -234,7 +234,8 @@ func (h *WebAuthnHandler) LoginBegin(w http.ResponseWriter, r *http.Request) {
Path: "/", Path: "/",
HttpOnly: true, HttpOnly: true,
MaxAge: 300, // 5 minutes MaxAge: 300, // 5 minutes
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteNoneMode,
Secure: true,
}) })
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
@@ -341,7 +342,8 @@ func (h *WebAuthnHandler) LoginFinish(w http.ResponseWriter, r *http.Request) {
Path: "/", Path: "/",
HttpOnly: true, HttpOnly: true,
MaxAge: -1, MaxAge: -1,
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteNoneMode,
Secure: true,
}) })
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
+29
View File
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "629643353973",
"project_id": "dumpster-chat",
"storage_bucket": "dumpster-chat.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:629643353973:android:29dcc703959dd306c0fd3c",
"android_client_info": {
"package_name": "coffee.dustin.dumpster"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDt3h4G-imzD7IedRqYOUBIv8CtZQT2YyA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
+10 -1
View File
@@ -60,11 +60,20 @@
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
<script> <script>
// Register service worker // Register service worker (only for web, avoid in Tauri to prevent 404 cache bugs)
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
if (!('__TAURI_INTERNALS__' in window) && !('__TAURI__' in window)) {
window.addEventListener('load', () => { window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch(() => {}); navigator.serviceWorker.register('/sw.js').catch(() => {});
}); });
} else {
// In Tauri, aggressively unregister any old service workers that might be causing 404s
navigator.serviceWorker.getRegistrations().then((registrations) => {
for (let registration of registrations) {
registration.unregister();
}
});
}
} }
</script> </script>
</body> </body>
+65
View File
@@ -3,6 +3,71 @@ import { createRoot } from 'react-dom/client';
import './styles/index.css'; import './styles/index.css';
import App from './App.tsx'; import App from './App.tsx';
const isTauri = 'window' in globalThis && '__TAURI_INTERNALS__' in window;
if (isTauri) {
const TARGET = 'https://dumpster.dustin.coffee';
const WS_TARGET = 'wss://dumpster.dustin.coffee';
let sessionToken = localStorage.getItem('dumpster_session_token') || '';
const originalFetch = window.fetch;
window.fetch = async (input, init) => {
let url = typeof input === 'string' ? input : input.toString();
if (url.startsWith('/')) {
url = TARGET + url;
}
let newInit = init ? { ...init } : {};
if (sessionToken) {
newInit.headers = {
...newInit.headers,
'Authorization': 'Bearer ' + sessionToken
};
}
const response = await originalFetch(url, newInit);
const token = response.headers.get('X-Session-Token');
if (token) {
sessionToken = token;
localStorage.setItem('dumpster_session_token', token);
}
if (url.endsWith('/auth/logout') && response.ok) {
sessionToken = '';
localStorage.removeItem('dumpster_session_token');
}
return response;
};
const OriginalWebSocket = window.WebSocket;
window.WebSocket = function(url: string | URL, protocols?: string | string[]) {
let urlStr = typeof url === 'string' ? url : url.toString();
if (urlStr.includes('tauri.localhost') || urlStr.includes('tauri://localhost')) {
urlStr = urlStr.replace(/^(wss?|https?|tauri):\/\/tauri\.localhost/, WS_TARGET);
urlStr = urlStr.replace(/^tauri:\/\/localhost/, WS_TARGET);
} else if (urlStr.startsWith('ws://localhost') && isTauri) {
urlStr = urlStr.replace('ws://localhost', WS_TARGET);
} else if (urlStr.startsWith('/')) {
urlStr = WS_TARGET + urlStr;
}
const ws = new OriginalWebSocket(urlStr, protocols);
if (sessionToken && urlStr.includes('/ws') && !urlStr.includes('/ws/bot')) {
const originalOnOpen = ws.onopen;
ws.onopen = function(ev) {
ws.send(JSON.stringify({ token: sessionToken }));
if (originalOnOpen) originalOnOpen.call(ws, ev);
};
}
return ws;
} as unknown as typeof WebSocket;
window.WebSocket.prototype = OriginalWebSocket.prototype;
}
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<App /> <App />