@@ -19,8 +19,11 @@ type Config struct {
19
19
// Custom verification endpoint (optional)
20
20
VerificationEndpoint string `json:"verificationEndpoint,omitempty"`
21
21
22
- // Session timeout for verified connections
23
- SessionTimeout time.Duration `json:"sessionTimeout,omitempty"`
22
+ // Session timeout for verified connections (in seconds)
23
+ SessionTimeoutSeconds int `json:"sessionTimeoutSeconds,omitempty"`
24
+
25
+ // Session timeout as duration string (e.g., "24h", "30m") - will be converted to seconds
26
+ SessionTimeout string `json:"sessionTimeout,omitempty"`
24
27
25
28
// Custom error messages
26
29
CustomErrorMessage string `json:"customErrorMessage,omitempty"`
@@ -44,32 +47,51 @@ type Config struct {
44
47
45
48
func CreateConfig () * Config {
46
49
return & Config {
47
- TestDomain : "your-tailscale-network.ts.net" , // User should configure this
48
- SessionTimeout : 24 * time .Hour ,
49
- CustomErrorMessage : "Tailscale connection required to access this service" ,
50
- SuccessMessage : "Tailscale connectivity verified! Redirecting..." ,
51
- EnableDebugLogging : false ,
52
- AllowLocalhost : true ,
53
- SecureOnly : true ,
54
- RequireUserAgent : true ,
50
+ TestDomain : "your-tailscale-network.ts.net" , // User should configure this
51
+ SessionTimeout : "24h" ,
52
+ SessionTimeoutSeconds : 86400 , // 24 hours in seconds as fallback
53
+ CustomErrorMessage : "Tailscale connection required to access this service" ,
54
+ SuccessMessage : "Tailscale connectivity verified! Redirecting..." ,
55
+ EnableDebugLogging : false ,
56
+ AllowLocalhost : true ,
57
+ SecureOnly : true ,
58
+ RequireUserAgent : true ,
55
59
}
56
60
}
57
61
58
62
type TailscaleConnectivityAuth struct {
59
- next http.Handler
60
- name string
61
- config * Config
62
- secrets map [string ]time.Time // Simple in-memory session store
63
+ next http.Handler
64
+ name string
65
+ config * Config
66
+ sessionTimeout time.Duration // Parsed duration for internal use
67
+ secrets map [string ]time.Time // Simple in-memory session store
63
68
}
64
69
65
70
func New (_ context.Context , next http.Handler , config * Config , name string ) (http.Handler , error ) {
66
- // Apply defaults
71
+ // Apply defaults and validate
67
72
if config .TestDomain == "" {
68
73
return nil , fmt .Errorf ("testDomain must be configured" )
69
74
}
70
- if config .SessionTimeout == 0 {
71
- config .SessionTimeout = 24 * time .Hour
75
+
76
+ // Parse session timeout
77
+ var sessionTimeout time.Duration
78
+ var err error
79
+
80
+ if config .SessionTimeout != "" {
81
+ // Try to parse duration string (e.g., "24h", "30m")
82
+ sessionTimeout , err = time .ParseDuration (config .SessionTimeout )
83
+ if err != nil {
84
+ return nil , fmt .Errorf ("invalid sessionTimeout format: %v (use format like '24h', '30m', '45s')" , err )
85
+ }
86
+ } else if config .SessionTimeoutSeconds > 0 {
87
+ // Use seconds if provided
88
+ sessionTimeout = time .Duration (config .SessionTimeoutSeconds ) * time .Second
89
+ } else {
90
+ // Default to 24 hours
91
+ sessionTimeout = 24 * time .Hour
72
92
}
93
+
94
+ // Apply other defaults
73
95
if config .CustomErrorMessage == "" {
74
96
config .CustomErrorMessage = "Tailscale connection required to access this service"
75
97
}
@@ -78,10 +100,11 @@ func New(_ context.Context, next http.Handler, config *Config, name string) (htt
78
100
}
79
101
80
102
return & TailscaleConnectivityAuth {
81
- next : next ,
82
- name : name ,
83
- config : config ,
84
- secrets : make (map [string ]time.Time ),
103
+ next : next ,
104
+ name : name ,
105
+ config : config ,
106
+ sessionTimeout : sessionTimeout ,
107
+ secrets : make (map [string ]time.Time ),
85
108
}, nil
86
109
}
87
110
@@ -151,7 +174,7 @@ func (t *TailscaleConnectivityAuth) handleVerification(rw http.ResponseWriter, r
151
174
if status == "success" {
152
175
// Generate verification token
153
176
token := t .generateToken ()
154
- expiry := time .Now ().Add (t .config . SessionTimeout )
177
+ expiry := time .Now ().Add (t .sessionTimeout )
155
178
t .secrets [token ] = expiry
156
179
157
180
// Set secure cookie
@@ -305,49 +328,59 @@ func (t *TailscaleConnectivityAuth) generateVerificationHTML(originalURL string)
305
328
verificationAttempts++;
306
329
307
330
try {
308
- // Test 1: Try to fetch from the Tailscale domain
309
- const testUrl = 'https ://' + testDomain + '/';
331
+ // Test 1: Try HTTP first (more reliable for Tailscale domains)
332
+ const httpUrl = 'http ://' + testDomain + '/';
310
333
311
- // Use fetch with a timeout
312
- const controller = new AbortController();
313
- const timeoutId = setTimeout(() => controller.abort(), 5000);
334
+ const controllerHttp = new AbortController();
335
+ const timeoutIdHttp = setTimeout(() => controllerHttp.abort(), 5000);
314
336
315
- const response = await fetch(testUrl , {
337
+ const httpResponse = await fetch(httpUrl , {
316
338
method: 'GET',
317
- mode: 'no-cors', // This allows the request even if CORS fails
339
+ mode: 'no-cors',
318
340
cache: 'no-cache',
319
- signal: controller .signal
341
+ signal: controllerHttp .signal
320
342
});
321
343
322
- clearTimeout(timeoutId);
323
-
324
- // If we get here, the domain is reachable
325
- await reportVerificationResult(true, 'Tailscale domain reachable');
344
+ clearTimeout(timeoutIdHttp);
345
+ await reportVerificationResult(true, 'Tailscale domain reachable via HTTP');
326
346
327
- } catch (error ) {
328
- console.log('Primary test failed:', error .message);
347
+ } catch (httpError ) {
348
+ console.log('HTTP test failed:', httpError .message);
329
349
330
- // Test 2: Try alternative method - image loading
350
+ // Test 2: Try HTTPS fallback
331
351
try {
332
- await testImageLoad();
333
- await reportVerificationResult(true, 'Tailscale connectivity confirmed via image test');
334
- } catch (imgError) {
335
- console.log('Image test failed:', imgError.message);
352
+ const httpsUrl = 'https://' + testDomain + '/';
353
+ const controllerHttps = new AbortController();
354
+ const timeoutIdHttps = setTimeout(() => controllerHttps.abort(), 5000);
355
+
356
+ const httpsResponse = await fetch(httpsUrl, {
357
+ method: 'GET',
358
+ mode: 'no-cors',
359
+ cache: 'no-cache',
360
+ signal: controllerHttps.signal
361
+ });
336
362
337
- // Test 3: Try WebSocket if available
363
+ clearTimeout(timeoutIdHttps);
364
+ await reportVerificationResult(true, 'Tailscale domain reachable via HTTPS');
365
+
366
+ } catch (httpsError) {
367
+ console.log('HTTPS test failed:', httpsError.message);
368
+
369
+ // Test 3: Try image loading with HTTP first
338
370
try {
339
- await testWebSocket ();
340
- await reportVerificationResult(true, 'Tailscale connectivity confirmed via WebSocket ');
341
- } catch (wsError ) {
342
- console.log('WebSocket test failed:', wsError .message);
343
- await reportVerificationResult(false, error. message + ' (all tests failed)' );
371
+ await testImageLoad ();
372
+ await reportVerificationResult(true, 'Tailscale connectivity confirmed via image test ');
373
+ } catch (imgError ) {
374
+ console.log('Image test failed:', imgError .message);
375
+ await reportVerificationResult(false, 'All connectivity tests failed. HTTP: ' + httpError. message + ', HTTPS: ' + httpsError.message );
344
376
}
345
377
}
346
378
}
347
379
}
348
380
349
381
function testImageLoad() {
350
382
return new Promise((resolve, reject) => {
383
+ // Try HTTP first (more reliable for Tailscale)
351
384
const img = new Image();
352
385
const timeout = setTimeout(() => {
353
386
reject(new Error('Image load timeout'));
@@ -360,11 +393,28 @@ func (t *TailscaleConnectivityAuth) generateVerificationHTML(originalURL string)
360
393
361
394
img.onerror = () => {
362
395
clearTimeout(timeout);
363
- reject(new Error('Image load failed'));
396
+ // Try HTTPS fallback
397
+ const img2 = new Image();
398
+ const timeout2 = setTimeout(() => {
399
+ reject(new Error('Image load failed (both HTTP and HTTPS)'));
400
+ }, 3000);
401
+
402
+ img2.onload = () => {
403
+ clearTimeout(timeout2);
404
+ resolve();
405
+ };
406
+
407
+ img2.onerror = () => {
408
+ clearTimeout(timeout2);
409
+ reject(new Error('Image load failed (both HTTP and HTTPS)'));
410
+ };
411
+
412
+ // Try HTTPS as fallback
413
+ img2.src = 'https://' + testDomain + '/favicon.ico?t=' + Date.now();
364
414
};
365
415
366
- // Try to load a favicon or small image from the Tailscale domain
367
- img.src = 'https ://' + testDomain + '/favicon.ico?t=' + Date.now();
416
+ // Try HTTP first
417
+ img.src = 'http ://' + testDomain + '/favicon.ico?t=' + Date.now();
368
418
});
369
419
}
370
420
0 commit comments