Skip to content

Commit 69f4206

Browse files
authored
refactor: remove concurrent listeners and rework cookie logic (#950)
1 parent 2572376 commit 69f4206

20 files changed

Lines changed: 448 additions & 396 deletions

frontend/src/components/layout/layout.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Outlet } from "react-router";
33
import { useCallback, useEffect, useState } from "react";
44
import { DomainWarning } from "../domain-warning/domain-warning";
55
import { QuickActions } from "../quick-actions/quick-actions";
6+
import { isTrustedDomain } from "@/lib/hooks/redirect-uri";
67

78
const BaseLayout = ({ children }: { children: React.ReactNode }) => {
89
const { ui } = useAppContext();
@@ -40,11 +41,18 @@ export const Layout = () => {
4041
setIgnoreDomainWarning(true);
4142
}, [setIgnoreDomainWarning]);
4243

43-
if (
44-
!ignoreDomainWarning &&
45-
ui.warningsEnabled &&
46-
!app.trustedDomains.includes(currentUrl)
47-
) {
44+
const isTrusted = (() => {
45+
try {
46+
const appUrlObj = new URL(app.appUrl);
47+
const currentUrlObj = new URL(currentUrl);
48+
49+
return isTrustedDomain(currentUrlObj, appUrlObj, "", false);
50+
} catch {
51+
return false;
52+
}
53+
})();
54+
55+
if (!ignoreDomainWarning && ui.warningsEnabled && !isTrusted) {
4856
return (
4957
<BaseLayout>
5058
<DomainWarning

frontend/src/lib/hooks/redirect-uri.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,27 @@ type IuseRedirectUri = {
99
export const useRedirectUri = (
1010
redirect_uri: string | undefined,
1111
cookieDomain: string,
12+
appUrl: string,
13+
subdomainsEnabled: boolean,
1214
): IuseRedirectUri => {
1315
let isValid = false;
1416
let isTrusted = false;
1517
let isAllowedProto = false;
1618
let isHttpsDowngrade = false;
1719

20+
let appUrlObj: URL;
21+
22+
try {
23+
appUrlObj = new URL(appUrl);
24+
} catch {
25+
return {
26+
valid: isValid,
27+
trusted: isTrusted,
28+
allowedProto: isAllowedProto,
29+
httpsDowngrade: isHttpsDowngrade,
30+
};
31+
}
32+
1833
if (!redirect_uri) {
1934
return {
2035
valid: isValid,
@@ -39,10 +54,7 @@ export const useRedirectUri = (
3954

4055
isValid = true;
4156

42-
if (
43-
url.hostname == cookieDomain ||
44-
url.hostname.endsWith(`.${cookieDomain}`)
45-
) {
57+
if (isTrustedDomain(url, appUrlObj, cookieDomain, subdomainsEnabled)) {
4658
isTrusted = true;
4759
}
4860

@@ -62,3 +74,45 @@ export const useRedirectUri = (
6274
httpsDowngrade: isHttpsDowngrade,
6375
};
6476
};
77+
78+
// ported from internal/controller/oauth_controller.go
79+
const getEffectivePort = (url: URL): string => {
80+
if (url.port) {
81+
return url.port;
82+
}
83+
84+
if (url.protocol == "https:") {
85+
return "443";
86+
}
87+
88+
return "80";
89+
};
90+
91+
export const isTrustedDomain = (
92+
url: URL,
93+
appUrl: URL,
94+
cookieDomain: string,
95+
subdomainsEnabled: boolean,
96+
): boolean => {
97+
if (url.protocol != appUrl.protocol) {
98+
return false;
99+
}
100+
101+
if (getEffectivePort(url) != getEffectivePort(appUrl)) {
102+
return false;
103+
}
104+
105+
if (url.hostname == appUrl.hostname) {
106+
return true;
107+
}
108+
109+
if (!subdomainsEnabled) {
110+
return false;
111+
}
112+
113+
if (url.hostname.endsWith("." + cookieDomain.toLowerCase())) {
114+
return true;
115+
}
116+
117+
return false;
118+
};

frontend/src/pages/continue-page.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export const ContinuePage = () => {
3737
const { url, valid, trusted, allowedProto, httpsDowngrade } = useRedirectUri(
3838
redirectUri,
3939
app.cookieDomain,
40+
app.appUrl,
41+
app.subdomainsEnabled,
4042
);
4143

4244
const urlHref = url?.href;
@@ -108,7 +110,11 @@ export const ContinuePage = () => {
108110
components={{
109111
code: <code />,
110112
}}
111-
values={{ cookieDomain: app.cookieDomain }}
113+
values={{
114+
cookieDomain: app.subdomainsEnabled
115+
? `.${app.cookieDomain}`
116+
: app.cookieDomain,
117+
}}
112118
shouldUnescape={true}
113119
/>
114120
</CardDescription>

frontend/src/schemas/app-context-schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const uiSchema = z.object({
2424
const appSchema = z.object({
2525
appUrl: z.string(),
2626
cookieDomain: z.string(),
27-
trustedDomains: z.array(z.string()),
27+
subdomainsEnabled: z.boolean(),
2828
});
2929

3030
export const appContextSchema = z.object({

internal/bootstrap/app_bootstrap.go

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,17 @@ type Services struct {
4646
}
4747

4848
type BootstrapApp struct {
49-
config model.Config
50-
runtime model.RuntimeConfig
51-
services Services
52-
log *logger.Logger
53-
ctx context.Context
54-
cancel context.CancelFunc
55-
queries repository.Store
56-
router *gin.Engine
57-
db *sql.DB
58-
ding *ding.Ding
59-
listeners []Listener
60-
dig *dig.Container
49+
config model.Config
50+
runtime model.RuntimeConfig
51+
services Services
52+
log *logger.Logger
53+
ctx context.Context
54+
cancel context.CancelFunc
55+
queries repository.Store
56+
router *gin.Engine
57+
db *sql.DB
58+
ding *ding.Ding
59+
dig *dig.Container
6160
}
6261

6362
func NewBootstrapApp(config model.Config) *BootstrapApp {
@@ -98,8 +97,7 @@ func (app *BootstrapApp) Setup() error {
9897
return fmt.Errorf("failed to parse app url: %w", err)
9998
}
10099

101-
app.runtime.AppURL = appUrl.Scheme + "://" + appUrl.Host
102-
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, app.runtime.AppURL)
100+
app.runtime.AppURL = strings.ToLower(appUrl.Scheme + "://" + appUrl.Host)
103101

104102
// validate session config
105103
if app.config.Auth.SessionMaxLifetime != 0 && app.config.Auth.SessionMaxLifetime < app.config.Auth.SessionExpiry {
@@ -144,34 +142,23 @@ func (app *BootstrapApp) Setup() error {
144142
provider.ClientSecret = secret
145143
provider.ClientSecretFile = ""
146144

147-
if provider.RedirectURL == "" {
148-
provider.RedirectURL = app.runtime.AppURL + "/api/oauth/callback/" + id
149-
}
150-
151-
app.runtime.OAuthProviders[id] = provider
152-
}
153-
154-
// set presets for built-in providers
155-
for id, provider := range app.runtime.OAuthProviders {
156145
if provider.Name == "" {
157146
if name, ok := model.OverrideProviders[id]; ok {
158147
provider.Name = name
159148
} else {
160149
provider.Name = utils.Capitalize(id)
161150
}
162151
}
152+
163153
app.runtime.OAuthProviders[id] = provider
164154
}
165155

166156
// cookie domain
167-
cookieDomainResolver := utils.GetCookieDomain
168-
169157
if !app.config.Auth.SubdomainsEnabled {
170-
app.log.App.Warn().Msg("Subdomains are disabled, using standalone cookie domain resolver which will not work with subdomains")
171-
cookieDomainResolver = utils.GetStandaloneCookieDomain
158+
app.log.App.Warn().Msg("Subdomains are disabled, cookies will be set for the current domain only")
172159
}
173160

174-
cookieDomain, err := cookieDomainResolver(app.runtime.AppURL)
161+
cookieDomain, err := utils.GetCookieDomain(app.runtime.AppURL, app.config.Auth.SubdomainsEnabled)
175162

176163
if err != nil {
177164
return fmt.Errorf("failed to get cookie domain: %w", err)
@@ -286,9 +273,43 @@ func (app *BootstrapApp) Setup() error {
286273

287274
app.runtime.ConfiguredProviders = configuredProviders
288275

289-
// throw in tailscale if it's configured just before setting up the controllers
290-
if app.services.tailscaleService != nil {
291-
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, "https://"+app.services.tailscaleService.GetHostname())
276+
// if tailscale is enabled and listening, replace the app url with the tailscale hostname
277+
if app.services.tailscaleService != nil && app.config.Tailscale.Listen {
278+
tailscaleUrl := "https://" + app.services.tailscaleService.GetHostname()
279+
280+
// if the tailscale url is different from the app url, replace it
281+
if tailscaleUrl != app.runtime.AppURL {
282+
app.log.App.Info().Msg("Listening on tailscale, replacing app url with tailscale hostname")
283+
284+
app.runtime.AppURL = tailscaleUrl
285+
286+
// also update cookie domain
287+
cookieDomain, err := utils.GetCookieDomain(tailscaleUrl, app.config.Auth.SubdomainsEnabled)
288+
289+
if err != nil {
290+
return fmt.Errorf("failed to get cookie domain: %w", err)
291+
}
292+
293+
app.runtime.CookieDomain = cookieDomain
294+
}
295+
}
296+
297+
// force an update of the redirect urls for all oauth providers, if they are empty
298+
services := app.services.oauthBrokerService.GetConfiguredServices()
299+
300+
for _, service := range services {
301+
oauthService, ok := app.services.oauthBrokerService.GetService(service)
302+
303+
if !ok {
304+
return fmt.Errorf("failed to get oauth service for provider %s", service)
305+
}
306+
307+
providerConfig := oauthService.GetConfig()
308+
309+
if providerConfig.RedirectURL == "" {
310+
providerConfig.RedirectURL = app.runtime.AppURL + "/api/oauth/callback/" + service
311+
oauthService.UpdateConfig(providerConfig)
312+
}
292313
}
293314

294315
// setup router
@@ -308,19 +329,19 @@ func (app *BootstrapApp) Setup() error {
308329
app.ding.Go(app.heartbeatRoutine, ding.RingMinor)
309330
}
310331

311-
// setup listeners
312-
app.listeners = app.calculateListenerPolicy()
332+
// get listener
333+
listenerFunc, err := app.getListenerFunc()
313334

314-
if app.config.Server.ConcurrentListenersEnabled {
315-
app.log.App.Info().Msg("Concurrent listeners enabled, will run on all available listeners")
335+
if err != nil {
336+
return fmt.Errorf("failed to get listener function: %w", err)
316337
}
317338

318-
// run listeners
319-
lec, err := app.runListeners()
339+
// run listener
340+
lec := make(chan error, 1)
320341

321-
if err != nil {
322-
return fmt.Errorf("failed to run listeners: %w", err)
323-
}
342+
app.ding.Go(func(ctx context.Context) {
343+
lec <- listenerFunc(ctx)
344+
}, ding.RingNormal)
324345

325346
// monitor cancellation and server errors
326347
for {

0 commit comments

Comments
 (0)