Update dependencies

This commit is contained in:
bluepython508
2024-11-01 17:33:34 +00:00
parent 033ac0b400
commit 5cdfab398d
3596 changed files with 1033483 additions and 259 deletions

131
vendor/tailscale.com/client/web/assets.go generated vendored Normal file
View File

@@ -0,0 +1,131 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package web
import (
"io"
"io/fs"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
prebuilt "github.com/tailscale/web-client-prebuilt"
)
var start = time.Now()
func assetsHandler(devMode bool) (_ http.Handler, cleanup func()) {
if devMode {
// When in dev mode, proxy asset requests to the Vite dev server.
cleanup := startDevServer()
return devServerProxy(), cleanup
}
fsys := prebuilt.FS()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/")
f, err := openPrecompressedFile(w, r, path, fsys)
if err != nil {
// Rewrite request to just fetch index.html and let
// the frontend router handle it.
r = r.Clone(r.Context())
path = "index.html"
f, err = openPrecompressedFile(w, r, path, fsys)
}
if f == nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
defer f.Close()
// fs.File does not claim to implement Seeker, but in practice it does.
fSeeker, ok := f.(io.ReadSeeker)
if !ok {
http.Error(w, "Not seekable", http.StatusInternalServerError)
return
}
if strings.HasPrefix(path, "assets/") {
// Aggressively cache static assets, since we cache-bust our assets with
// hashed filenames.
w.Header().Set("Cache-Control", "public, max-age=31535996")
w.Header().Set("Vary", "Accept-Encoding")
}
http.ServeContent(w, r, path, start, fSeeker)
}), nil
}
func openPrecompressedFile(w http.ResponseWriter, r *http.Request, path string, fs fs.FS) (fs.File, error) {
if f, err := fs.Open(path + ".gz"); err == nil {
w.Header().Set("Content-Encoding", "gzip")
return f, nil
}
return fs.Open(path) // fallback
}
// startDevServer starts the JS dev server that does on-demand rebuilding
// and serving of web client JS and CSS resources.
func startDevServer() (cleanup func()) {
root := gitRootDir()
webClientPath := filepath.Join(root, "client", "web")
yarn := filepath.Join(root, "tool", "yarn")
node := filepath.Join(root, "tool", "node")
vite := filepath.Join(webClientPath, "node_modules", ".bin", "vite")
log.Printf("installing JavaScript deps using %s...", yarn)
out, err := exec.Command(yarn, "--non-interactive", "-s", "--cwd", webClientPath, "install").CombinedOutput()
if err != nil {
log.Fatalf("error running tailscale web's yarn install: %v, %s", err, out)
}
log.Printf("starting JavaScript dev server...")
cmd := exec.Command(node, vite)
cmd.Dir = webClientPath
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Fatalf("Starting JS dev server: %v", err)
}
log.Printf("JavaScript dev server running as pid %d", cmd.Process.Pid)
return func() {
cmd.Process.Signal(os.Interrupt)
err := cmd.Wait()
log.Printf("JavaScript dev server exited: %v", err)
}
}
// devServerProxy returns a reverse proxy to the vite dev server.
func devServerProxy() *httputil.ReverseProxy {
// We use Vite to develop on the web client.
// Vite starts up its own local server for development,
// which we proxy requests to from Server.ServeHTTP.
// Here we set up the proxy to Vite's server.
handleErr := func(w http.ResponseWriter, r *http.Request, err error) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte("The web client development server isn't running. " +
"Run `./tool/yarn --cwd client/web start` from " +
"the repo root to start the development server."))
w.Write([]byte("\n\nError: " + err.Error()))
}
viteTarget, _ := url.Parse("http://127.0.0.1:4000")
devProxy := httputil.NewSingleHostReverseProxy(viteTarget)
devProxy.ErrorHandler = handleErr
return devProxy
}
func gitRootDir() string {
top, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
if err != nil {
log.Fatalf("failed to find git top level (not in corp git?): %v", err)
}
return strings.TrimSpace(string(top))
}

339
vendor/tailscale.com/client/web/auth.go generated vendored Normal file
View File

@@ -0,0 +1,339 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package web
import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"net/http"
"net/url"
"slices"
"strings"
"time"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
)
const (
sessionCookieName = "TS-Web-Session"
sessionCookieExpiry = time.Hour * 24 * 30 // 30 days
)
// browserSession holds data about a user's browser session
// on the full management web client.
type browserSession struct {
// ID is the unique identifier for the session.
// It is passed in the user's "TS-Web-Session" browser cookie.
ID string
SrcNode tailcfg.NodeID
SrcUser tailcfg.UserID
AuthID string // from tailcfg.WebClientAuthResponse
AuthURL string // from tailcfg.WebClientAuthResponse
Created time.Time
Authenticated bool
}
// isAuthorized reports true if the given session is authorized
// to be used by its associated user to access the full management
// web client.
//
// isAuthorized is true only when s.Authenticated is true (i.e.
// the user has authenticated the session) and the session is not
// expired.
// 2023-10-05: Sessions expire by default 30 days after creation.
func (s *browserSession) isAuthorized(now time.Time) bool {
switch {
case s == nil:
return false
case !s.Authenticated:
return false // awaiting auth
case s.isExpired(now):
return false // expired
}
return true
}
// isExpired reports true if s is expired.
// 2023-10-05: Sessions expire by default 30 days after creation.
func (s *browserSession) isExpired(now time.Time) bool {
return !s.Created.IsZero() && now.After(s.expires())
}
// expires reports when the given session expires.
func (s *browserSession) expires() time.Time {
return s.Created.Add(sessionCookieExpiry)
}
var (
errNoSession = errors.New("no-browser-session")
errNotUsingTailscale = errors.New("not-using-tailscale")
errTaggedRemoteSource = errors.New("tagged-remote-source")
errTaggedLocalSource = errors.New("tagged-local-source")
errNotOwner = errors.New("not-owner")
)
// getSession retrieves the browser session associated with the request,
// if one exists.
//
// An error is returned in any of the following cases:
//
// - (errNotUsingTailscale) The request was not made over tailscale.
//
// - (errNoSession) The request does not have a session.
//
// - (errTaggedRemoteSource) The source is remote (another node) and tagged.
// Users must use their own user-owned devices to manage other nodes'
// web clients.
//
// - (errTaggedLocalSource) The source is local (the same node) and tagged.
// Tagged nodes can only be remotely managed, allowing ACLs to dictate
// access to web clients.
//
// - (errNotOwner) The source is not the owner of this client (if the
// client is user-owned). Only the owner is allowed to manage the
// node via the web client.
//
// If no error is returned, the browserSession is always non-nil.
// getTailscaleBrowserSession does not check whether the session has been
// authorized by the user. Callers can use browserSession.isAuthorized.
//
// The WhoIsResponse is always populated, with a non-nil Node and UserProfile,
// unless getTailscaleBrowserSession reports errNotUsingTailscale.
func (s *Server) getSession(r *http.Request) (*browserSession, *apitype.WhoIsResponse, *ipnstate.Status, error) {
whoIs, whoIsErr := s.lc.WhoIs(r.Context(), r.RemoteAddr)
status, statusErr := s.lc.StatusWithoutPeers(r.Context())
switch {
case whoIsErr != nil:
return nil, nil, status, errNotUsingTailscale
case statusErr != nil:
return nil, whoIs, nil, statusErr
case status.Self == nil:
return nil, whoIs, status, errors.New("missing self node in tailscale status")
case whoIs.Node.IsTagged() && whoIs.Node.StableID == status.Self.ID:
return nil, whoIs, status, errTaggedLocalSource
case whoIs.Node.IsTagged():
return nil, whoIs, status, errTaggedRemoteSource
case !status.Self.IsTagged() && status.Self.UserID != whoIs.UserProfile.ID:
return nil, whoIs, status, errNotOwner
}
srcNode := whoIs.Node.ID
srcUser := whoIs.UserProfile.ID
cookie, err := r.Cookie(sessionCookieName)
if errors.Is(err, http.ErrNoCookie) {
return nil, whoIs, status, errNoSession
} else if err != nil {
return nil, whoIs, status, err
}
v, ok := s.browserSessions.Load(cookie.Value)
if !ok {
return nil, whoIs, status, errNoSession
}
session := v.(*browserSession)
if session.SrcNode != srcNode || session.SrcUser != srcUser {
// In this case the browser cookie is associated with another tailscale node.
// Maybe the source browser's machine was logged out and then back in as a different node.
// Return errNoSession because there is no session for this user.
return nil, whoIs, status, errNoSession
} else if session.isExpired(s.timeNow()) {
// Session expired, remove from session map and return errNoSession.
s.browserSessions.Delete(session.ID)
return nil, whoIs, status, errNoSession
}
return session, whoIs, status, nil
}
// newSession creates a new session associated with the given source user/node,
// and stores it back to the session cache. Creating of a new session includes
// generating a new auth URL from the control server.
func (s *Server) newSession(ctx context.Context, src *apitype.WhoIsResponse) (*browserSession, error) {
sid, err := s.newSessionID()
if err != nil {
return nil, err
}
session := &browserSession{
ID: sid,
SrcNode: src.Node.ID,
SrcUser: src.UserProfile.ID,
Created: s.timeNow(),
}
if s.controlSupportsCheckMode(ctx) {
// control supports check mode, so get a new auth URL and return.
a, err := s.newAuthURL(ctx, src.Node.ID)
if err != nil {
return nil, err
}
session.AuthID = a.ID
session.AuthURL = a.URL
} else {
// control does not support check mode, so there is no additional auth we can do.
session.Authenticated = true
}
s.browserSessions.Store(sid, session)
return session, nil
}
// controlSupportsCheckMode returns whether the current control server supports web client check mode, to verify a user's identity.
// We assume that only "tailscale.com" control servers support check mode.
// This allows the web client to be used with non-standard control servers.
// If an error occurs getting the control URL, this method returns true to fail closed.
//
// TODO(juanfont/headscale#1623): adjust or remove this when headscale supports check mode.
func (s *Server) controlSupportsCheckMode(ctx context.Context) bool {
prefs, err := s.lc.GetPrefs(ctx)
if err != nil {
return true
}
controlURL, err := url.Parse(prefs.ControlURLOrDefault())
if err != nil {
return true
}
return strings.HasSuffix(controlURL.Host, ".tailscale.com")
}
// awaitUserAuth blocks until the given session auth has been completed
// by the user on the control server, then updates the session cache upon
// completion. An error is returned if control auth failed for any reason.
func (s *Server) awaitUserAuth(ctx context.Context, session *browserSession) error {
if session.isAuthorized(s.timeNow()) {
return nil // already authorized
}
a, err := s.waitAuthURL(ctx, session.AuthID, session.SrcNode)
if err != nil {
// Clean up the session. Doing this on any error from control
// server to avoid the user getting stuck with a bad session
// cookie.
s.browserSessions.Delete(session.ID)
return err
}
if a.Complete {
session.Authenticated = a.Complete
s.browserSessions.Store(session.ID, session)
}
return nil
}
func (s *Server) newSessionID() (string, error) {
raw := make([]byte, 16)
for range 5 {
if _, err := rand.Read(raw); err != nil {
return "", err
}
cookie := "ts-web-" + base64.RawURLEncoding.EncodeToString(raw)
if _, ok := s.browserSessions.Load(cookie); !ok {
return cookie, nil
}
}
return "", errors.New("too many collisions generating new session; please refresh page")
}
// peerCapabilities holds information about what a source
// peer is allowed to edit via the web UI.
//
// map value is true if the peer can edit the given feature.
// Only capFeatures included in validCaps will be included.
type peerCapabilities map[capFeature]bool
// canEdit is true if the peerCapabilities grant edit access
// to the given feature.
func (p peerCapabilities) canEdit(feature capFeature) bool {
if p == nil {
return false
}
if p[capFeatureAll] {
return true
}
return p[feature]
}
// isEmpty is true if p is either nil or has no capabilities
// with value true.
func (p peerCapabilities) isEmpty() bool {
if p == nil {
return true
}
for _, v := range p {
if v == true {
return false
}
}
return true
}
type capFeature string
const (
// The following values should not be edited.
// New caps can be added, but existing ones should not be changed,
// as these exact values are used by users in tailnet policy files.
//
// IMPORTANT: When adding a new cap, also update validCaps slice below.
capFeatureAll capFeature = "*" // grants peer management of all features
capFeatureSSH capFeature = "ssh" // grants peer SSH server management
capFeatureSubnets capFeature = "subnets" // grants peer subnet routes management
capFeatureExitNodes capFeature = "exitnodes" // grants peer ability to advertise-as and use exit nodes
capFeatureAccount capFeature = "account" // grants peer ability to turn on auto updates and log out of node
)
// validCaps contains the list of valid capabilities used in the web client.
// Any capabilities included in a peer's grants that do not fall into this
// list will be ignored.
var validCaps []capFeature = []capFeature{
capFeatureAll,
capFeatureSSH,
capFeatureSubnets,
capFeatureExitNodes,
capFeatureAccount,
}
type capRule struct {
CanEdit []string `json:"canEdit,omitempty"` // list of features peer is allowed to edit
}
// toPeerCapabilities parses out the web ui capabilities from the
// given whois response.
func toPeerCapabilities(status *ipnstate.Status, whois *apitype.WhoIsResponse) (peerCapabilities, error) {
if whois == nil || status == nil {
return peerCapabilities{}, nil
}
if whois.Node.IsTagged() {
// We don't allow management *from* tagged nodes, so ignore caps.
// The web client auth flow relies on having a true user identity
// that can be verified through login.
return peerCapabilities{}, nil
}
if !status.Self.IsTagged() {
// User owned nodes are only ever manageable by the owner.
if status.Self.UserID != whois.UserProfile.ID {
return peerCapabilities{}, nil
} else {
return peerCapabilities{capFeatureAll: true}, nil // owner can edit all features
}
}
// For tagged nodes, we actually look at the granted capabilities.
caps := peerCapabilities{}
rules, err := tailcfg.UnmarshalCapJSON[capRule](whois.CapMap, tailcfg.PeerCapabilityWebUI)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal capability: %v", err)
}
for _, c := range rules {
for _, f := range c.CanEdit {
cap := capFeature(strings.ToLower(f))
if slices.Contains(validCaps, cap) {
caps[cap] = true
}
}
}
return caps, nil
}

28
vendor/tailscale.com/client/web/index.html generated vendored Normal file
View File

@@ -0,0 +1,28 @@
<!doctype html>
<html class="bg-gray-50">
<head>
<title>Tailscale</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQflAx4QGA4EvmzDAAAA30lEQVRIx2NgGAWMCKa8JKM4A8Ovt88ekyLCDGOoyDBJMjExMbFy8zF8/EKsCAMDE8yAPyIwFps48SJIBpAL4AZwvoSx/r0lXgQpDN58EWL5x/7/H+vL20+JFxluQKVe5b3Ke5V+0kQQCamfoYKBg4GDwUKI8d0BYkWQkrLKewYBKPPDHUFiRaiZkBgmwhj/F5IgggyUJ6i8V3mv0kCayDAAeEsklXqGAgYGhgV3CnGrwVciYSYk0kokhgS44/JxqqFpiYSZbEgskd4dEBRk1GD4wdB5twKXmlHAwMDAAACdEZau06NQUwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wNy0xNVQxNTo1Mzo0MCswMDowMCVXsDIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjAtMDctMTVUMTU6NTM6NDArMDA6MDBUCgiOAAAAAElFTkSuQmCC" />
<link rel="stylesheet" type="text/css" href="/src/index.css" />
<link rel="preload" as="font" href="/src/assets/fonts/Inter.var.latin.woff2" type="font/woff2" crossorigin />
</head>
<body class="px-2">
<noscript>
<p class="mb-2">You need to enable Javascript to access the Tailscale web client.</p>
<p>If you need any help, feel free to <a href="mailto:support+webclient@tailscale.com" class="link">contact us</a>.</p>
</noscript>
<script type="module" src="/src/index.tsx"></script>
<script>
// if this script is changed, also change hash in web.go
window.addEventListener("load", () => {
if (!window.Tailscale) {
const rootEl = document.createElement("p")
rootEl.innerHTML = 'Tailscale web interface is unavailable.';
document.body.append(rootEl)
}
})
</script>
</body>
</html>

81
vendor/tailscale.com/client/web/package.json generated vendored Normal file
View File

@@ -0,0 +1,81 @@
{
"name": "webclient",
"version": "0.0.1",
"license": "BSD-3-Clause",
"engines": {
"node": "18.20.4",
"yarn": "1.22.19"
},
"type": "module",
"private": true,
"dependencies": {
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-popover": "^1.0.6",
"classnames": "^2.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"swr": "^2.2.4",
"wouter": "^2.11.0",
"zustand": "^4.4.7"
},
"devDependencies": {
"@types/node": "^18.16.1",
"@types/react": "^18.0.20",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react-swc": "^3.6.0",
"autoprefixer": "^10.4.15",
"eslint": "^8.23.1",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-curly-quotes": "^1.0.4",
"jsdom": "^23.0.1",
"postcss": "^8.4.31",
"prettier": "^2.5.1",
"prettier-plugin-organize-imports": "^3.2.2",
"tailwindcss": "^3.3.3",
"typescript": "^5.3.3",
"vite": "^5.1.7",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^3.5.0",
"vitest": "^1.3.1"
},
"resolutions": {
"@typescript-eslint/eslint-plugin": "^6.2.1",
"@typescript-eslint/parser": "^6.2.1"
},
"scripts": {
"build": "vite build",
"start": "vite",
"lint": "tsc --noEmit && eslint 'src/**/*.{ts,tsx,js,jsx}'",
"test": "vitest",
"format": "prettier --write 'src/**/*.{ts,tsx}'",
"format-check": "prettier --check 'src/**/*.{ts,tsx}'"
},
"eslintConfig": {
"extends": [
"react-app"
],
"plugins": [
"curly-quotes",
"react-hooks"
],
"rules": {
"curly-quotes/no-straight-quotes": "warn",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error"
},
"settings": {
"projectRoot": "client/web/package.json"
}
},
"prettier": {
"semi": false,
"printWidth": 80
},
"postcss": {
"plugins": {
"tailwindcss": {},
"autoprefixer": {}
}
}
}

127
vendor/tailscale.com/client/web/qnap.go generated vendored Normal file
View File

@@ -0,0 +1,127 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// qnap.go contains handlers and logic, such as authentication,
// that is specific to running the web client on QNAP.
package web
import (
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
)
// authorizeQNAP authenticates the logged-in QNAP user and verifies that they
// are authorized to use the web client.
// If the user is not authorized to use the client, an error is returned.
func authorizeQNAP(r *http.Request) (authorized bool, err error) {
_, resp, err := qnapAuthn(r)
if err != nil {
return false, err
}
if resp.IsAdmin == 0 {
return false, errors.New("user is not an admin")
}
return true, nil
}
type qnapAuthResponse struct {
AuthPassed int `xml:"authPassed"`
IsAdmin int `xml:"isAdmin"`
AuthSID string `xml:"authSid"`
ErrorValue int `xml:"errorValue"`
}
func qnapAuthn(r *http.Request) (string, *qnapAuthResponse, error) {
user, err := r.Cookie("NAS_USER")
if err != nil {
return "", nil, err
}
token, err := r.Cookie("qtoken")
if err == nil {
return qnapAuthnQtoken(r, user.Value, token.Value)
}
sid, err := r.Cookie("NAS_SID")
if err == nil {
return qnapAuthnSid(r, user.Value, sid.Value)
}
return "", nil, fmt.Errorf("not authenticated by any mechanism")
}
// qnapAuthnURL returns the auth URL to use by inferring where the UI is
// running based on the request URL. This is necessary because QNAP has so
// many options, see https://github.com/tailscale/tailscale/issues/7108
// and https://github.com/tailscale/tailscale/issues/6903
func qnapAuthnURL(requestUrl string, query url.Values) string {
in, err := url.Parse(requestUrl)
scheme := ""
host := ""
if err != nil || in.Scheme == "" {
log.Printf("Cannot parse QNAP login URL %v", err)
// try localhost and hope for the best
scheme = "http"
host = "localhost"
} else {
scheme = in.Scheme
host = in.Host
}
u := url.URL{
Scheme: scheme,
Host: host,
Path: "/cgi-bin/authLogin.cgi",
RawQuery: query.Encode(),
}
return u.String()
}
func qnapAuthnQtoken(r *http.Request, user, token string) (string, *qnapAuthResponse, error) {
query := url.Values{
"qtoken": []string{token},
"user": []string{user},
}
return qnapAuthnFinish(user, qnapAuthnURL(r.URL.String(), query))
}
func qnapAuthnSid(r *http.Request, user, sid string) (string, *qnapAuthResponse, error) {
query := url.Values{
"sid": []string{sid},
}
return qnapAuthnFinish(user, qnapAuthnURL(r.URL.String(), query))
}
func qnapAuthnFinish(user, url string) (string, *qnapAuthResponse, error) {
// QNAP Force HTTPS mode uses a self-signed certificate. Even importing
// the QNAP root CA isn't enough, the cert doesn't have a usable CN nor
// SAN. See https://github.com/tailscale/tailscale/issues/6903
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err := client.Get(url)
if err != nil {
return "", nil, err
}
defer resp.Body.Close()
out, err := io.ReadAll(resp.Body)
if err != nil {
return "", nil, err
}
authResp := &qnapAuthResponse{}
if err := xml.Unmarshal(out, authResp); err != nil {
return "", nil, err
}
if authResp.AuthPassed == 0 {
return "", nil, fmt.Errorf("not authenticated")
}
return user, authResp, nil
}

85
vendor/tailscale.com/client/web/styles.json generated vendored Normal file
View File

@@ -0,0 +1,85 @@
{
"colors": {
"transparent": "transparent",
"current": "currentColor",
"white": "rgb(var(--color-white) / <alpha-value>)",
"gray": {
"0": "rgb(var(--color-gray-0) / <alpha-value>)",
"50": "rgb(var(--color-gray-50) / <alpha-value>)",
"100": "rgb(var(--color-gray-100) / <alpha-value>)",
"200": "rgb(var(--color-gray-200) / <alpha-value>)",
"300": "rgb(var(--color-gray-300) / <alpha-value>)",
"400": "rgb(var(--color-gray-400) / <alpha-value>)",
"500": "rgb(var(--color-gray-500) / <alpha-value>)",
"600": "rgb(var(--color-gray-600) / <alpha-value>)",
"700": "rgb(var(--color-gray-700) / <alpha-value>)",
"800": "rgb(var(--color-gray-800) / <alpha-value>)",
"900": "rgb(var(--color-gray-900) / <alpha-value>)"
},
"blue": {
"0": "rgb(var(--color-blue-0) / <alpha-value>)",
"50": "rgb(var(--color-blue-50) / <alpha-value>)",
"100": "rgb(var(--color-blue-100) / <alpha-value>)",
"200": "rgb(var(--color-blue-200) / <alpha-value>)",
"300": "rgb(var(--color-blue-300) / <alpha-value>)",
"400": "rgb(var(--color-blue-400) / <alpha-value>)",
"500": "rgb(var(--color-blue-500) / <alpha-value>)",
"600": "rgb(var(--color-blue-600) / <alpha-value>)",
"700": "rgb(var(--color-blue-700) / <alpha-value>)",
"800": "rgb(var(--color-blue-800) / <alpha-value>)",
"900": "rgb(var(--color-blue-900) / <alpha-value>)"
},
"green": {
"0": "rgb(var(--color-green-0) / <alpha-value>)",
"50": "rgb(var(--color-green-50) / <alpha-value>)",
"100": "rgb(var(--color-green-100) / <alpha-value>)",
"200": "rgb(var(--color-green-200) / <alpha-value>)",
"300": "rgb(var(--color-green-300) / <alpha-value>)",
"400": "rgb(var(--color-green-400) / <alpha-value>)",
"500": "rgb(var(--color-green-500) / <alpha-value>)",
"600": "rgb(var(--color-green-600) / <alpha-value>)",
"700": "rgb(var(--color-green-700) / <alpha-value>)",
"800": "rgb(var(--color-green-800) / <alpha-value>)",
"900": "rgb(var(--color-green-900) / <alpha-value>)"
},
"red": {
"0": "rgb(var(--color-red-0) / <alpha-value>)",
"50": "rgb(var(--color-red-50) / <alpha-value>)",
"100": "rgb(var(--color-red-100) / <alpha-value>)",
"200": "rgb(var(--color-red-200) / <alpha-value>)",
"300": "rgb(var(--color-red-300) / <alpha-value>)",
"400": "rgb(var(--color-red-400) / <alpha-value>)",
"500": "rgb(var(--color-red-500) / <alpha-value>)",
"600": "rgb(var(--color-red-600) / <alpha-value>)",
"700": "rgb(var(--color-red-700) / <alpha-value>)",
"800": "rgb(var(--color-red-800) / <alpha-value>)",
"900": "rgb(var(--color-red-900) / <alpha-value>)"
},
"yellow": {
"0": "rgb(var(--color-yellow-0) / <alpha-value>)",
"50": "rgb(var(--color-yellow-50) / <alpha-value>)",
"100": "rgb(var(--color-yellow-100) / <alpha-value>)",
"200": "rgb(var(--color-yellow-200) / <alpha-value>)",
"300": "rgb(var(--color-yellow-300) / <alpha-value>)",
"400": "rgb(var(--color-yellow-400) / <alpha-value>)",
"500": "rgb(var(--color-yellow-500) / <alpha-value>)",
"600": "rgb(var(--color-yellow-600) / <alpha-value>)",
"700": "rgb(var(--color-yellow-700) / <alpha-value>)",
"800": "rgb(var(--color-yellow-800) / <alpha-value>)",
"900": "rgb(var(--color-yellow-900) / <alpha-value>)"
},
"orange": {
"0": "rgb(var(--color-orange-0) / <alpha-value>)",
"50": "rgb(var(--color-orange-50) / <alpha-value>)",
"100": "rgb(var(--color-orange-100) / <alpha-value>)",
"200": "rgb(var(--color-orange-200) / <alpha-value>)",
"300": "rgb(var(--color-orange-300) / <alpha-value>)",
"400": "rgb(var(--color-orange-400) / <alpha-value>)",
"500": "rgb(var(--color-orange-500) / <alpha-value>)",
"600": "rgb(var(--color-orange-600) / <alpha-value>)",
"700": "rgb(var(--color-orange-700) / <alpha-value>)",
"800": "rgb(var(--color-orange-800) / <alpha-value>)",
"900": "rgb(var(--color-orange-900) / <alpha-value>)"
}
}
}

59
vendor/tailscale.com/client/web/synology.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// synology.go contains handlers and logic, such as authentication,
// that is specific to running the web client on Synology.
package web
import (
"errors"
"fmt"
"net/http"
"os/exec"
"strings"
"tailscale.com/util/groupmember"
)
// authorizeSynology authenticates the logged-in Synology user and verifies
// that they are authorized to use the web client.
// If the user is authenticated, but not authorized to use the client, an error is returned.
func authorizeSynology(r *http.Request) (authorized bool, err error) {
if !hasSynoToken(r) {
return false, nil
}
// authenticate the Synology user
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
out, err := cmd.CombinedOutput()
if err != nil {
return false, fmt.Errorf("auth: %v: %s", err, out)
}
user := strings.TrimSpace(string(out))
// check if the user is in the administrators group
isAdmin, err := groupmember.IsMemberOfGroup("administrators", user)
if err != nil {
return false, err
}
if !isAdmin {
return false, errors.New("not a member of administrators group")
}
return true, nil
}
// hasSynoToken returns true if the request include a SynoToken used for synology auth.
func hasSynoToken(r *http.Request) bool {
if r.Header.Get("X-Syno-Token") != "" {
return true
}
if r.URL.Query().Get("SynoToken") != "" {
return true
}
if r.Method == "POST" && r.FormValue("SynoToken") != "" {
return true
}
return false
}

117
vendor/tailscale.com/client/web/tailwind.config.js generated vendored Normal file
View File

@@ -0,0 +1,117 @@
import plugin from "tailwindcss/plugin"
import styles from "./styles.json"
const config = {
theme: {
screens: {
sm: "420px",
md: "768px",
lg: "1024px",
},
fontFamily: {
sans: [
"Inter",
"-apple-system",
"BlinkMacSystemFont",
"Helvetica",
"Arial",
"sans-serif",
],
mono: [
"SFMono-Regular",
"SFMono Regular",
"Consolas",
"Liberation Mono",
"Menlo",
"Courier",
"monospace",
],
},
fontWeight: {
normal: "400",
medium: "500",
semibold: "600",
bold: "700",
},
colors: styles.colors,
extend: {
colors: {
...styles.colors,
"bg-app": "var(--color-bg-app)",
"bg-menu-item-hover": "var(--color-bg-menu-item-hover)",
"border-base": "var(--color-border-base)",
"text-base": "var(--color-text-base)",
"text-muted": "var(--color-text-muted)",
"text-disabled": "var(--color-text-disabled)",
"text-primary": "var(--color-text-primary)",
"text-warning": "var(--color-text-warning)",
"text-danger": "var(--color-text-danger)",
},
borderColor: {
DEFAULT: "var(--color-border-base)",
},
boxShadow: {
dialog: "0 10px 40px rgba(0,0,0,0.12), 0 0 16px rgba(0,0,0,0.08)",
form: "0 1px 1px rgba(0, 0, 0, 0.04)",
soft: "0 4px 12px 0 rgba(0, 0, 0, 0.03)",
popover:
"0 0 0 1px rgba(136, 152, 170, 0.1), 0 15px 35px 0 rgba(49, 49, 93, 0.1), 0 5px 15px 0 rgba(0, 0, 0, 0.08)",
},
animation: {
"scale-in": "scale-in 120ms cubic-bezier(0.16, 1, 0.3, 1)",
"scale-out": "scale-out 120ms cubic-bezier(0.16, 1, 0.3, 1)",
},
transformOrigin: {
"radix-hovercard": "var(--radix-hover-card-content-transform-origin)",
"radix-popover": "var(--radix-popover-content-transform-origin)",
"radix-tooltip": "var(--radix-tooltip-content-transform-origin)",
},
keyframes: {
"scale-in": {
"0%": {
transform: "scale(0.94)",
opacity: "0",
},
"100%": {
transform: "scale(1)",
opacity: "1",
},
},
"scale-out": {
"0%": {
transform: "scale(1)",
opacity: "1",
},
"100%": {
transform: "scale(0.94)",
opacity: "0",
},
},
},
},
},
plugins: [
plugin(function ({ addVariant }) {
addVariant("state-open", [
"&[data-state=“open”]",
"[data-state=“open”] &",
])
addVariant("state-closed", [
"&[data-state=“closed”]",
"[data-state=“closed”] &",
])
addVariant("state-delayed-open", [
"&[data-state=“delayed-open”]",
"[data-state=“delayed-open”] &",
])
addVariant("state-active", ["&[data-state=“active”]"])
addVariant("state-inactive", ["&[data-state=“inactive”]"])
}),
],
content: ["./src/**/*.html", "./src/**/*.{ts,tsx}", "./index.html"],
}
export default config

18
vendor/tailscale.com/client/web/tsconfig.json generated vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES2017",
"module": "ES2020",
"strict": true,
"sourceMap": true,
"skipLibCheck": true,
"isolatedModules": true,
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"jsx": "react",
"types": ["vite-plugin-svgr/client", "vite/client"]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

57
vendor/tailscale.com/client/web/vite.config.ts generated vendored Normal file
View File

@@ -0,0 +1,57 @@
/// <reference types="vitest" />
import { createLogger, defineConfig } from "vite"
import svgr from "vite-plugin-svgr"
import paths from "vite-tsconfig-paths"
// Use a custom logger that filters out Vite's logging of server URLs, since
// they are an attractive nuisance (we run a proxy in front of Vite, and the
// tailscale web client should be accessed through that).
// Unfortunately there's no option to disable this logging, so the best we can
// do it to ignore calls from a specific function.
const filteringLogger = createLogger(undefined, { allowClearScreen: false })
const originalInfoLog = filteringLogger.info
filteringLogger.info = (...args) => {
if (new Error("ignored").stack?.includes("printServerUrls")) {
return
}
originalInfoLog.apply(filteringLogger, args)
}
// https://vitejs.dev/config/
export default defineConfig({
base: "./",
plugins: [
paths(),
svgr(),
],
build: {
outDir: "build",
sourcemap: false,
},
esbuild: {
logOverride: {
// Silence a warning about `this` being undefined in ESM when at the
// top-level. The way JSX is transpiled causes this to happen, but it
// isn't a problem.
// See: https://github.com/vitejs/vite/issues/8644
"this-is-undefined-in-esm": "silent",
},
},
server: {
// This needs to be 127.0.0.1 instead of localhost, because of how our
// Go proxy connects to it.
host: "127.0.0.1",
// If you change the port, be sure to update the proxy in assets.go too.
port: 4000,
},
test: {
exclude: ["**/node_modules/**", "**/dist/**"],
testTimeout: 20000,
environment: "jsdom",
deps: {
inline: ["date-fns", /\.wasm\?url$/],
},
},
clearScreen: false,
customLogger: filteringLogger,
})

1347
vendor/tailscale.com/client/web/web.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

5434
vendor/tailscale.com/client/web/yarn.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff