we've never tagged stable releases, but should really do so. This
change will cause our docker images to be tagged following typical
semver fashion... the initial `v1.0.0` release will be tagged `v1`,
`v1.0`, `v1.0.0`, and `latest`.
Updates #104
Signed-off-by: Will Norris <will@tailscale.com>
Add a "Match" template func, which uses regexp.MatchString to match a
value against a regular expression pattern.
Fixes#125
Signed-off-by: Will Norris <will@tailscale.com>
If you visit a non-existent go link, we render the home page and pre-
populate the "short" input with the name of the link, and autofocus the
"long" input so that you can simply paste a long URL and submit.
It is common (at least at Tailscale) to create go links that correspond
to the name of a device on the tailnet. For example, go/who points to
http://who/. With this change, when you visit a non-existent go link,
we check to see if a peer exists on the tailnet with that name, and if
so we suggest that as the long URL.
Signed-off-by: Will Norris <will@tailscale.com>
we've had support for enabling the web UI in tsnet apps from the
beginning, but I don't think we've ever actually used it anywhere.
some of the settings exposed through the web ui don't make a ton of
sense for tsnet, and might not even work. But we're working toward
being able to enable the web ui on clients by default (with all of the
existing restrictions and ACL enforcement in place), and golink seemed
like a good playground to try it in a tsnet app.
Signed-off-by: Will Norris <will@tailscale.com>
We have fixed the issue in the corp repo that required us to use
pseudo-versions for the tailscale.com module here. So we can now switch
to the latest stable version.
Signed-off-by: Will Norris <will@tailscale.com>
I didn't think the order mattered (as long as none of the response body
had been written), but I guess I was wrong. Currently, it's returning
the 302 status, but no Location header. And tests, at least some of
which should be passing through this full code path, are passing. I'll
look into adding better testing later, but for now this fixes the
immediate issue.
Updates #91
Signed-off-by: Will Norris <will@tailscale.com>
Both http.ServeMux as well as the http.Redirect method pass the request
URL through `cleanPath` which, among other things, collapses double
slashes `//` to a single slash `/`. Most of the time this is fine, since
most servers treat those as identical anyway. But some destination
servers need the original path unmodified. Since we're just redirecting,
we don't need to be concerned with the additional benefits of
`cleanPath` such as eliminating `../` path components, since that is the
responsibility of the destination server to clean if needed.
This change adds a separate root http.Handler for golink requests. It
still uses http.ServeMux for internal endpoints, but serves golinks
directly without passing the request through ServeMux. Additionally,
this sets the redirect status and Location header directly rather than
calling http.Redirect, since that also modifies the URL it is given.
Fixes#89
Signed-off-by: Will Norris <will@tailscale.com>
The "tailscale.com/golink" peercap includes a single "admin" bool field.
When set, this grants the user the ability to edit all links stored in
the system.
Update currentUser to return a simple user struct instead of just a bare
username. Rename checkLinkOwnership to canEditLink and change to a bool
return value.
Signed-off-by: Will Norris <will@tailscale.com>
When resolving a go link, combine the query string parameters from the
request and long URL.
Updates #77
Signed-off-by: Will Norris <will@tailscale.com>
return a *url.URL value from expandLink rather than a string, and both
accept and return a *url.URL value from resolveLink rather than a
string. These are both unexported funcs, so this has no changes to the
current behavior. It does prevent a few unnecessary conversions back and
forth between url.URL and string values, and will make it simpler to
retain request query strings.
Updates #77
Signed-off-by: Will Norris <will@tailscale.com>
Previously, we were parsing from r.RequestURI, which includes both the
path and query string. This causes problem for requests like go/who?q
which try to lookup a link named "who?q" rather than "who" (see #77).
For now, this just ignores the request query string. Eventually we
should probably retain the query string, but this begins by parsing out
the short name properly.
Updates #77
Signed-off-by: Will Norris <will@tailscale.com>
bump version again, back to the latest commit on main, rather than the
latest released version. Otherwise, this causes issues in tailscale's
corp repo (sadly).
Signed-off-by: Will Norris <will@tailscale.com>
If the current user can't be determined (either because of a legitimate
error within the localapi client, or the user is coming through a subnet
router and doesn't have a Tailscale IP address), and the
-allow-unknown-users flag is set, then go ahead and save new links
without an owner.
By saving links without an owner, these unknown users can continue to
modify the link, and actual Tailscale users can take ownership. Once the
link is owned, it can no longer be modified by anyone other than the
owner.
Links that use the current user by having `{{ .User }}` in their long
URL cannot be resolved by unknown users and will return an error.
Fixes#60
Signed-off-by: Will Norris <will@tailscale.com>
Also check error returned from tmpl.Execute. Refactor currentUser to
make the logic a little simpler, and make it a package var for easier
testing.
Signed-off-by: Will Norris <will@tailscale.com>
It's not uncommon to have multiple links pointing to the same
destination, for example to handle different spellings of a word or
because different people created them at different times.
A common best practice is to select one as the "primary" link and point
the others to that link as "aliases". This change updates resolveLink
to follow those aliases so that the final destination is returned when
using `golink -resolve-from-backup`.
Signed-off-by: Will Norris <will@tailscale.com>
Add "User" to the expansion environment for links. The intent here it
support personalized go links such as:
go/mycal => https://calendar.google.com/calendar/embed?src={{.User}}
That's not a terribly interesting example, but there are others I intend
to use internally.
Signed-off-by: Will Norris <will@tailscale.com>
Use a build image built for the native architecture so that it doesn't
run in emulation mode.
Switch to Chainguard's wolfi/glibc build image. This limits our build
architectures to just amd64 and arm64, but that is sufficient for our
needs.
Updates #42
Updates #43
Signed-off-by: Will Norris <will@tailscale.com>
Currently, the special "tagged-devices" user (which exists for any
tagged devices in the tailnet) can own go links. This change permits
tagged devices to continue to create go links, but skips over them for
ownership checks, allowing any human user can take over ownership.
Fixes#37
Signed-off-by: Will Norris <will@tailscale.com>
When loading stats from the database, map IDs back to their canonical
short name, which is what we want to show in the frontend. This is only
called once on cold start, so performance of loading all links isn't a
big concern.
Fixes#13
Signed-off-by: Will Norris <will@tailscale.com>
Build docker images for amd64, arm64, and arm/v7. Also several other
small improvements to docker workflow like documenting which actions we
run on, setting concurrency settings, and reversing the logic for when
we push new images live (specifically looking for a push event rather
than a "not pull request" event, in case we add other events later).
Fixes#26
Specify a command line argument to resolve a link locally and exit.
Also add a new flag, -resolve-from-backup, which loads a snapshot into
an in-memory database and resolves the specified link.
go/.export doesn't include click data anymore since we switched over to
sqlite. Remove that code as well as the Link.Clicks field. Remove old
/_/export redirect, since we've long since moved off of using it.
We still support directly setting LastSnapshot to support embedding the
snapshot in the binary (like we do internally). But this allows an
alternate way have restoring backups which doesn't require recompiling
binaries.
Even though we have our own build and deploy scripts, most users will be
more comfortable with a standard Dockerfile. We could publish our
scripts later if warranted. For now, we'll just keep them in corp.
remove FileDB implementation, related flags, and filedb to sqlite
migration code. Also remove the DB interface since it's no longer
needed. dev mode now creates a temporary SQLite database.
Change-Id: I22f92dccdd575f6c35a10523ede8ecf85ae330bb
use relative links throughout rather than hardcoding the go hostname,
which allows things to continue working when other hostnames are used.
Change-Id: I5df0d10214799de9722871cbad99bf9af129941f
Properly use html/template rather than text/template so that we don't
have to worry about escaping data that is passed in to the templates.
Also move the success page into a template for both better rendering and
consistency.
Finally, this includes some tailwind changes that apparently got missed
in previous commits.
Change-Id: Ic658ab940888c44483501e33699da9021be18ecf
Add flags to specify sqlite database and migration from files to sqlite.
Migration is only supported in one direction (and flag name makes that
clear). If both sqlite and linkdir flags are provided, sqlite is
preferred. This has no change in behavior if neither of the new flags
are provided.
Change-Id: Ie13116ec3b80835b4389d228583e7a1476c00bfb
Add a new /.help URL which contains a first pass at user docs, covering
naming rules for short links, using go templates, and basic API access.
Also add client side validation for short name and a link to help page.
Change-Id: I4b66793248eba17e567c4f422ee197617f74ee24
When a user visits a non-existing go link, it's reasonable to assume
that they may then want to create that link, so autofocus on the
destination input.
We could also autofocus the short input on initial page load without a
provided short name, but I think that's less of a safe assumption.
Change-Id: Id8e15e2f02f81d319eb782576e5e19f73c0ccee4
add new DB.LoadStats and DB.SaveStats methods. DB.SaveSaveStats takes
an incremental count of clicks since the last call. This is a noop for
FileDB (the full count is still saved in the Link struct on disk), but
will allow for the SqliteDB to store stats in a separate table with
timestamps to display only recent clicks.
Change-Id: I18c7db871854923b873ae100c844940242a32bb3
If the previous owner of a link no longer appears in the tailnet users,
any user can overwrite the link and will become the new owner.
Change-Id: I52bbc5e906ff5b1b6424c0e320fd0d02c006ac12
store click stats in db. Hold stats in memory and flush to db every
minute, as well as whenever the /_/export endpoint is called.
Change DB.List() to DB.LoadAll(), since it practice we actually want
the data, not just the names.
Change-Id: I21a3aa19bfc065d595822f004a14b96bbb347de8
after successfully saving a golink, return an HTML response only if the
request indicates it can accept text/html, otherwise return JSON. This
allows for a very simple API:
% curl -d short=foo -d long=http://foo.com/ go
{"Short":"foo","Long":"http://foo.com/" ... }
Change-Id: I32e9c028207c5c7b8741d7dd31ebed35cf38a1de
append "+" to the end of a golink to get information about the link
rather than being redirected. For now, this just pretty prints the raw
JSON. We could make it prettier later.
Change-Id: If25c9f61c326f6e12cdec89b78eb3ca1b741a0ef
This is a straight refactor with no functional changes. All file access
is captured in the FileDB type. The DB interface is not strictly
necessary here since we only have a single implementation, but helped to
keep the API clean, which will be useful in a future sqlite migration.
Change-Id: I9b86db3040e2618a4ffef237369288ccfc10bc1e
This sets up an embed.FS even though we only have the one file right
now, as we will have more shortly.
Change-Id: I1292e4c00602fbfc3a7bc2a5f93a8c9b9e6715c0
long URLs are now executed using text/template to allow for more advaned
handling. Templates have access to PathEscape and QueryEscape funcs, as
well as a data struct containing the current time (Now) and the
remaining path from the user-provided short link (Path).
Change-Id: Iedbbafccf98c4a623d06e9519877c7137ca559e8
- ignore hyphens, preventing link ambiguity. If hyphens are included
when creating the link, they are saved as the "canonical" name for
the link, but links are still resolved without them.
- allow overwriting the "canonical" name for a short link by simply
recreating it with the new canonical name. Stats are not updated when
the canonical name is updated, which is probably fine.
Change-Id: Ia962caa846489850784ef15cdfad7f13bafd8842