Skip to content

Commit b0041b7

Browse files
committed
add support for running in read-only mode
Right now, running in read-only mode displays a banner stating that links can be resolved but not created or updated, as well as actively blocking requests to modify links. Update #118 Signed-off-by: Will Norris <[email protected]>
1 parent c66cbb8 commit b0041b7

File tree

5 files changed

+119
-34
lines changed

5 files changed

+119
-34
lines changed

golink.go

+22-8
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ var (
6464
hostname = flag.String("hostname", defaultHostname, "service name")
6565
resolveFromBackup = flag.String("resolve-from-backup", "", "resolve a link from snapshot file and exit")
6666
allowUnknownUsers = flag.Bool("allow-unknown-users", false, "allow unknown users to save links")
67+
readonly = flag.Bool("readonly", false, "start golink server in read-only mode")
6768
)
6869

6970
var stats struct {
@@ -269,10 +270,11 @@ type visitData struct {
269270

270271
// homeData is the data used by homeTmpl.
271272
type homeData struct {
272-
Short string
273-
Long string
274-
Clicks []visitData
275-
XSRF string
273+
Short string
274+
Long string
275+
Clicks []visitData
276+
XSRF string
277+
ReadOnly bool
276278
}
277279

278280
// deleteData is the data used by deleteTmpl.
@@ -443,10 +445,11 @@ func serveHome(w http.ResponseWriter, r *http.Request, short string) {
443445
return
444446
}
445447
homeTmpl.Execute(w, homeData{
446-
Short: short,
447-
Long: long,
448-
Clicks: clicks,
449-
XSRF: xsrftoken.Generate(xsrfKey, cu.login, newShortName),
448+
Short: short,
449+
Long: long,
450+
Clicks: clicks,
451+
XSRF: xsrftoken.Generate(xsrfKey, cu.login, newShortName),
452+
ReadOnly: *readonly,
450453
})
451454
}
452455

@@ -743,6 +746,10 @@ func userExists(ctx context.Context, login string) (bool, error) {
743746
var reShortName = regexp.MustCompile(`^\w[\w\-\.]*$`)
744747

745748
func serveDelete(w http.ResponseWriter, r *http.Request) {
749+
if *readonly {
750+
http.Error(w, "golink is in read-only mode", http.StatusMethodNotAllowed)
751+
return
752+
}
746753
short := strings.TrimPrefix(r.URL.Path, "/.delete/")
747754
if short == "" {
748755
http.Error(w, "short required", http.StatusBadRequest)
@@ -793,6 +800,10 @@ func serveDelete(w http.ResponseWriter, r *http.Request) {
793800
// long URL are validated for proper format. Existing links may only be updated
794801
// by their owner.
795802
func serveSave(w http.ResponseWriter, r *http.Request) {
803+
if *readonly {
804+
http.Error(w, "golink is in read-only mode", http.StatusMethodNotAllowed)
805+
return
806+
}
796807
short, long := r.FormValue("short"), r.FormValue("long")
797808
if short == "" || long == "" {
798809
http.Error(w, "short and long required", http.StatusBadRequest)
@@ -871,6 +882,9 @@ func serveSave(w http.ResponseWriter, r *http.Request) {
871882
// Admin users can edit all links.
872883
// Non-admin users can only edit their own links or links without an active owner.
873884
func canEditLink(ctx context.Context, link *Link, u user) bool {
885+
if *readonly {
886+
return false
887+
}
874888
if link == nil || link.Owner == "" {
875889
// new or unowned link
876890
return true

golink_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"golang.org/x/net/xsrftoken"
16+
"tailscale.com/types/ptr"
1617
"tailscale.com/util/must"
1718
)
1819

@@ -332,6 +333,47 @@ func TestServeDelete(t *testing.T) {
332333
}
333334
}
334335

336+
func TestReadOnlyMode(t *testing.T) {
337+
var err error
338+
db, err = NewSQLiteDB(":memory:")
339+
if err != nil {
340+
t.Fatal(err)
341+
}
342+
db.Save(&Link{Short: "who", Long: "http://who/"})
343+
344+
oldReadOnly := readonly
345+
readonly = ptr.To(true)
346+
defer func() { readonly = oldReadOnly }()
347+
348+
// resolving link should succeed
349+
r := httptest.NewRequest("GET", "/who", nil)
350+
w := httptest.NewRecorder()
351+
serveHandler().ServeHTTP(w, r)
352+
if want := http.StatusFound; w.Code != want {
353+
t.Errorf("serveHandler() = %d; want %d", w.Code, want)
354+
}
355+
wantLocation := "http://who/"
356+
if location := w.Header().Get("Location"); location != wantLocation {
357+
t.Errorf("serveHandler() location = %v; want %v", location, wantLocation)
358+
}
359+
360+
// updating link should fail
361+
r = httptest.NewRequest("POST", "/", nil)
362+
w = httptest.NewRecorder()
363+
serveHandler().ServeHTTP(w, r)
364+
if want := http.StatusMethodNotAllowed; w.Code != want {
365+
t.Errorf("serveHandler() = %d; want %d", w.Code, want)
366+
}
367+
368+
// deleting link should fail
369+
r = httptest.NewRequest("POST", "/.delete/who", nil)
370+
w = httptest.NewRecorder()
371+
serveHandler().ServeHTTP(w, r)
372+
if want := http.StatusMethodNotAllowed; w.Code != want {
373+
t.Errorf("serveHandler() = %d; want %d", w.Code, want)
374+
}
375+
}
376+
335377
func TestExpandLink(t *testing.T) {
336378
tests := []struct {
337379
name string // test name

static/base.css

+15
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,11 @@ select {
13511351
border-color: rgb(178 45 48 / var(--tw-border-opacity));
13521352
}
13531353

1354+
.border-orange-50 {
1355+
--tw-border-opacity: 1;
1356+
border-color: rgb(254 227 192 / var(--tw-border-opacity));
1357+
}
1358+
13541359
.bg-gray-100 {
13551360
--tw-bg-opacity: 1;
13561361
background-color: rgb(247 245 244 / var(--tw-bg-opacity));
@@ -1366,6 +1371,11 @@ select {
13661371
background-color: rgb(178 45 48 / var(--tw-bg-opacity));
13671372
}
13681373

1374+
.bg-orange-0 {
1375+
--tw-bg-opacity: 1;
1376+
background-color: rgb(255 250 238 / var(--tw-bg-opacity));
1377+
}
1378+
13691379
.p-2 {
13701380
padding: 0.5rem;
13711381
}
@@ -1390,6 +1400,11 @@ select {
13901400
padding-bottom: 1rem;
13911401
}
13921402

1403+
.py-3 {
1404+
padding-top: 0.75rem;
1405+
padding-bottom: 0.75rem;
1406+
}
1407+
13931408
.pt-6 {
13941409
padding-top: 1.5rem;
13951410
}

tailwind.config.js

+21-11
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,36 @@ module.exports = {
4141
800: "rgba(90, 0, 0)",
4242
900: "rgba(66, 0, 0)",
4343
},
44-
white: '#fff',
45-
current: 'currentColor',
44+
orange: {
45+
0: "rgba(255, 250, 238)",
46+
50: "rgba(254, 227, 192)",
47+
100: "rgba(248, 184, 134)",
48+
200: "rgba(245, 146, 94)",
49+
300: "rgba(229, 111, 74)",
50+
400: "rgba(196, 76, 52)",
51+
500: "rgba(158, 47, 40)",
52+
600: "rgba(126, 30, 35)",
53+
700: "rgba(93, 22, 27)",
54+
800: "rgba(66, 14, 17)",
55+
900: "rgba(66, 14, 17)",
56+
},
57+
white: "#fff",
58+
current: "currentColor",
4659
},
4760
extend: {
4861
typography: {
4962
DEFAULT: {
5063
css: {
51-
'code::before': {
52-
'content': '',
64+
"code::before": {
65+
content: "",
5366
},
54-
'code::after': {
55-
'content': '',
67+
"code::after": {
68+
content: "",
5669
},
5770
},
5871
},
5972
},
6073
},
6174
},
62-
plugins: [
63-
require('@tailwindcss/forms'),
64-
require('@tailwindcss/typography'),
65-
],
66-
}
75+
plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")],
76+
};

tmpl/home.html

+19-15
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
{{ define "main" }}
2-
<h2 class="text-xl font-bold pb-2">Create a new link</h2>
2+
{{ if .ReadOnly }}
3+
<p class="rounded-md py-3 px-4 bg-orange-0 border border-orange-50">golink is running in read-only mode. Links can be resolved, but not created or updated.</p>
4+
{{ else }}
5+
<h2 class="text-xl font-bold pb-2">Create a new link</h2>
36

4-
{{ with .Long }}
5-
<p class="">Did you mean <a class="text-blue-600 hover:underline" href="{{.}}">{{.}}</a> ? Create a go link for it now:</p>
7+
{{ with .Long }}
8+
<p class="">Did you mean <a class="text-blue-600 hover:underline" href="{{.}}">{{.}}</a> ? Create a go link for it now:</p>
9+
{{ end }}
10+
<form method="POST" action="/" class="flex flex-wrap">
11+
<input type="hidden" name="xsrf" value="{{ .XSRF }}" />
12+
<div class="flex">
13+
<label for=short class="flex my-2 px-2 items-center bg-gray-100 border border-r-0 border-gray-300 rounded-l-md text-gray-700">http://go/</label>
14+
<input id=short name=short required type=text size=15 placeholder="shortname" value="{{.Short}}" pattern="\w[\w\-\.]*" title="Must start with letter or number; may contain letters, numbers, dashes, and periods."
15+
class="p-2 my-2 rounded-r-md border-gray-300 placeholder:text-gray-400">
16+
<span class="flex m-2 items-center">&rarr;</span>
17+
</div>
18+
<input name=long required type=text size=40 placeholder="https://destination-url"{{if .Short}} value="{{.Long}}" autofocus{{end}} class="p-2 my-2 mr-2 max-w-full rounded-md border-gray-300 placeholder:text-gray-400">
19+
<button type=submit class="py-2 px-4 my-2 rounded-md bg-blue-500 border-blue-500 text-white hover:bg-blue-600 hover:border-blue-600">Create</button>
20+
</form>
21+
<p class="text-sm text-gray-500"><a class="text-blue-600 hover:underline" href="/.help">Help and advanced options</a></p>
622
{{ end }}
7-
<form method="POST" action="/" class="flex flex-wrap">
8-
<input type="hidden" name="xsrf" value="{{ .XSRF }}" />
9-
<div class="flex">
10-
<label for=short class="flex my-2 px-2 items-center bg-gray-100 border border-r-0 border-gray-300 rounded-l-md text-gray-700">http://go/</label>
11-
<input id=short name=short required type=text size=15 placeholder="shortname" value="{{.Short}}" pattern="\w[\w\-\.]*" title="Must start with letter or number; may contain letters, numbers, dashes, and periods."
12-
class="p-2 my-2 rounded-r-md border-gray-300 placeholder:text-gray-400">
13-
<span class="flex m-2 items-center">&rarr;</span>
14-
</div>
15-
<input name=long required type=text size=40 placeholder="https://destination-url"{{if .Short}} value="{{.Long}}" autofocus{{end}} class="p-2 my-2 mr-2 max-w-full rounded-md border-gray-300 placeholder:text-gray-400">
16-
<button type=submit class="py-2 px-4 my-2 rounded-md bg-blue-500 border-blue-500 text-white hover:bg-blue-600 hover:border-blue-600">Create</button>
17-
</form>
18-
<p class="text-sm text-gray-500"><a class="text-blue-600 hover:underline" href="/.help">Help and advanced options</a></p>
1923

2024
<h2 class="text-xl font-bold pt-6 pb-2">Popular Links</h2>
2125
<table class="table-auto ">

0 commit comments

Comments
 (0)