mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
feat: tailscale oauth
This commit is contained in:
31
cmd/root.go
31
cmd/root.go
@@ -39,6 +39,7 @@ var rootCmd = &cobra.Command{
|
|||||||
config.GithubClientSecret = utils.GetSecret(config.GithubClientSecret, config.GithubClientSecretFile)
|
config.GithubClientSecret = utils.GetSecret(config.GithubClientSecret, config.GithubClientSecretFile)
|
||||||
config.GoogleClientSecret = utils.GetSecret(config.GoogleClientSecret, config.GoogleClientSecretFile)
|
config.GoogleClientSecret = utils.GetSecret(config.GoogleClientSecret, config.GoogleClientSecretFile)
|
||||||
config.GenericClientSecret = utils.GetSecret(config.GenericClientSecret, config.GenericClientSecretFile)
|
config.GenericClientSecret = utils.GetSecret(config.GenericClientSecret, config.GenericClientSecretFile)
|
||||||
|
config.TailscaleClientSecret = utils.GetSecret(config.TailscaleClientSecret, config.TailscaleClientSecretFile)
|
||||||
|
|
||||||
// Validate config
|
// Validate config
|
||||||
validator := validator.New()
|
validator := validator.New()
|
||||||
@@ -63,17 +64,19 @@ var rootCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Create OAuth config
|
// Create OAuth config
|
||||||
oauthConfig := types.OAuthConfig{
|
oauthConfig := types.OAuthConfig{
|
||||||
GithubClientId: config.GithubClientId,
|
GithubClientId: config.GithubClientId,
|
||||||
GithubClientSecret: config.GithubClientSecret,
|
GithubClientSecret: config.GithubClientSecret,
|
||||||
GoogleClientId: config.GoogleClientId,
|
GoogleClientId: config.GoogleClientId,
|
||||||
GoogleClientSecret: config.GoogleClientSecret,
|
GoogleClientSecret: config.GoogleClientSecret,
|
||||||
GenericClientId: config.GenericClientId,
|
TailscaleClientId: config.TailscaleClientId,
|
||||||
GenericClientSecret: config.GenericClientSecret,
|
TailscaleClientSecret: config.TailscaleClientSecret,
|
||||||
GenericScopes: strings.Split(config.GenericScopes, ","),
|
GenericClientId: config.GenericClientId,
|
||||||
GenericAuthURL: config.GenericAuthURL,
|
GenericClientSecret: config.GenericClientSecret,
|
||||||
GenericTokenURL: config.GenericTokenURL,
|
GenericScopes: strings.Split(config.GenericScopes, ","),
|
||||||
GenericUserURL: config.GenericUserURL,
|
GenericAuthURL: config.GenericAuthURL,
|
||||||
AppURL: config.AppURL,
|
GenericTokenURL: config.GenericTokenURL,
|
||||||
|
GenericUserURL: config.GenericUserURL,
|
||||||
|
AppURL: config.AppURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Parsed OAuth config")
|
log.Debug().Msg("Parsed OAuth config")
|
||||||
@@ -147,6 +150,9 @@ func init() {
|
|||||||
rootCmd.Flags().String("google-client-id", "", "Google OAuth client ID.")
|
rootCmd.Flags().String("google-client-id", "", "Google OAuth client ID.")
|
||||||
rootCmd.Flags().String("google-client-secret", "", "Google OAuth client secret.")
|
rootCmd.Flags().String("google-client-secret", "", "Google OAuth client secret.")
|
||||||
rootCmd.Flags().String("google-client-secret-file", "", "Google OAuth client secret file.")
|
rootCmd.Flags().String("google-client-secret-file", "", "Google OAuth client secret file.")
|
||||||
|
rootCmd.Flags().String("tailscale-client-id", "", "Tailscale OAuth client ID.")
|
||||||
|
rootCmd.Flags().String("tailscale-client-secret", "", "Tailscale OAuth client secret.")
|
||||||
|
rootCmd.Flags().String("tailscale-client-secret-file", "", "Tailscale OAuth client secret file.")
|
||||||
rootCmd.Flags().String("generic-client-id", "", "Generic OAuth client ID.")
|
rootCmd.Flags().String("generic-client-id", "", "Generic OAuth client ID.")
|
||||||
rootCmd.Flags().String("generic-client-secret", "", "Generic OAuth client secret.")
|
rootCmd.Flags().String("generic-client-secret", "", "Generic OAuth client secret.")
|
||||||
rootCmd.Flags().String("generic-client-secret-file", "", "Generic OAuth client secret file.")
|
rootCmd.Flags().String("generic-client-secret-file", "", "Generic OAuth client secret file.")
|
||||||
@@ -172,6 +178,9 @@ func init() {
|
|||||||
viper.BindEnv("google-client-id", "GOOGLE_CLIENT_ID")
|
viper.BindEnv("google-client-id", "GOOGLE_CLIENT_ID")
|
||||||
viper.BindEnv("google-client-secret", "GOOGLE_CLIENT_SECRET")
|
viper.BindEnv("google-client-secret", "GOOGLE_CLIENT_SECRET")
|
||||||
viper.BindEnv("google-client-secret-file", "GOOGLE_CLIENT_SECRET_FILE")
|
viper.BindEnv("google-client-secret-file", "GOOGLE_CLIENT_SECRET_FILE")
|
||||||
|
viper.BindEnv("tailscale-client-id", "TAILSCALE_CLIENT_ID")
|
||||||
|
viper.BindEnv("tailscale-client-secret", "TAILSCALE_CLIENT_SECRET")
|
||||||
|
viper.BindEnv("tailscale-client-secret-file", "TAILSCALE_CLIENT_SECRET_FILE")
|
||||||
viper.BindEnv("generic-client-id", "GENERIC_CLIENT_ID")
|
viper.BindEnv("generic-client-id", "GENERIC_CLIENT_ID")
|
||||||
viper.BindEnv("generic-client-secret", "GENERIC_CLIENT_SECRET")
|
viper.BindEnv("generic-client-secret", "GENERIC_CLIENT_SECRET")
|
||||||
viper.BindEnv("generic-client-secret-file", "GENERIC_CLIENT_SECRET_FILE")
|
viper.BindEnv("generic-client-secret-file", "GENERIC_CLIENT_SECRET_FILE")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"math/rand/v2"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -294,6 +295,21 @@ func (api *API) SetupRoutes() {
|
|||||||
c.SetCookie("tinyauth_redirect_uri", redirectURI, 3600, "/", api.Domain, api.Config.CookieSecure, true)
|
c.SetCookie("tinyauth_redirect_uri", redirectURI, 3600, "/", api.Domain, api.Config.CookieSecure, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if request.Provider == "tailscale" {
|
||||||
|
tailscaleQuery, tailscaleQueryErr := query.Values(types.TailscaleQuery{
|
||||||
|
Code: (1000 + rand.IntN(9000)), // doesn't need to be secure, just there to avoid caching
|
||||||
|
})
|
||||||
|
if handleApiError(c, "Failed to build query", tailscaleQueryErr) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Ok",
|
||||||
|
"url": fmt.Sprintf("%s/api/oauth/callback/tailscale?%s", api.Config.AppURL, tailscaleQuery.Encode()),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"message": "Ok",
|
"message": "Ok",
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ func NewProviders(config types.OAuthConfig) *Providers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Providers struct {
|
type Providers struct {
|
||||||
Config types.OAuthConfig
|
Config types.OAuthConfig
|
||||||
Github *oauth.OAuth
|
Github *oauth.OAuth
|
||||||
Google *oauth.OAuth
|
Google *oauth.OAuth
|
||||||
Generic *oauth.OAuth
|
Tailscale *oauth.OAuth
|
||||||
|
Generic *oauth.OAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func (providers *Providers) Init() {
|
func (providers *Providers) Init() {
|
||||||
@@ -46,6 +47,17 @@ func (providers *Providers) Init() {
|
|||||||
})
|
})
|
||||||
providers.Google.Init()
|
providers.Google.Init()
|
||||||
}
|
}
|
||||||
|
if providers.Config.TailscaleClientId != "" && providers.Config.TailscaleClientSecret != "" {
|
||||||
|
log.Info().Msg("Initializing Tailscale OAuth")
|
||||||
|
providers.Tailscale = oauth.NewOAuth(oauth2.Config{
|
||||||
|
ClientID: providers.Config.TailscaleClientId,
|
||||||
|
ClientSecret: providers.Config.TailscaleClientSecret,
|
||||||
|
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/tailscale", providers.Config.AppURL),
|
||||||
|
Scopes: TailscaleScopes(),
|
||||||
|
Endpoint: TailscaleEndpoint,
|
||||||
|
})
|
||||||
|
providers.Tailscale.Init()
|
||||||
|
}
|
||||||
if providers.Config.GenericClientId != "" && providers.Config.GenericClientSecret != "" {
|
if providers.Config.GenericClientId != "" && providers.Config.GenericClientSecret != "" {
|
||||||
log.Info().Msg("Initializing Generic OAuth")
|
log.Info().Msg("Initializing Generic OAuth")
|
||||||
providers.Generic = oauth.NewOAuth(oauth2.Config{
|
providers.Generic = oauth.NewOAuth(oauth2.Config{
|
||||||
@@ -68,6 +80,8 @@ func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
|||||||
return providers.Github
|
return providers.Github
|
||||||
case "google":
|
case "google":
|
||||||
return providers.Google
|
return providers.Google
|
||||||
|
case "tailscale":
|
||||||
|
return providers.Tailscale
|
||||||
case "generic":
|
case "generic":
|
||||||
return providers.Generic
|
return providers.Generic
|
||||||
default:
|
default:
|
||||||
@@ -103,6 +117,19 @@ func (providers *Providers) GetUser(provider string) (string, error) {
|
|||||||
}
|
}
|
||||||
log.Debug().Msg("Got email from google")
|
log.Debug().Msg("Got email from google")
|
||||||
return email, nil
|
return email, nil
|
||||||
|
case "tailscale":
|
||||||
|
if providers.Tailscale == nil {
|
||||||
|
log.Debug().Msg("Tailscale provider not configured")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
client := providers.Tailscale.GetClient()
|
||||||
|
log.Debug().Msg("Got client from tailscale")
|
||||||
|
email, emailErr := GetTailscaleEmail(client)
|
||||||
|
if emailErr != nil {
|
||||||
|
return "", emailErr
|
||||||
|
}
|
||||||
|
log.Debug().Msg("Got email from tailscale")
|
||||||
|
return email, nil
|
||||||
case "generic":
|
case "generic":
|
||||||
if providers.Generic == nil {
|
if providers.Generic == nil {
|
||||||
log.Debug().Msg("Generic provider not configured")
|
log.Debug().Msg("Generic provider not configured")
|
||||||
@@ -129,6 +156,9 @@ func (provider *Providers) GetConfiguredProviders() []string {
|
|||||||
if provider.Google != nil {
|
if provider.Google != nil {
|
||||||
providers = append(providers, "google")
|
providers = append(providers, "google")
|
||||||
}
|
}
|
||||||
|
if provider.Tailscale != nil {
|
||||||
|
providers = append(providers, "tailscale")
|
||||||
|
}
|
||||||
if provider.Generic != nil {
|
if provider.Generic != nil {
|
||||||
providers = append(providers, "generic")
|
providers = append(providers, "generic")
|
||||||
}
|
}
|
||||||
|
|||||||
56
internal/providers/tailscale.go
Normal file
56
internal/providers/tailscale.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TailscaleUser struct {
|
||||||
|
LoginName string `json:"loginName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TailscaleUserInfoResponse struct {
|
||||||
|
Users []TailscaleUser `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TailscaleScopes() []string {
|
||||||
|
return []string{"users:read"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var TailscaleEndpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: "https://api.tailscale.com/api/v2/oauth/token",
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTailscaleEmail(client *http.Client) (string, error) {
|
||||||
|
res, resErr := client.Get("https://api.tailscale.com/api/v2/tailnet/-/users")
|
||||||
|
|
||||||
|
if resErr != nil {
|
||||||
|
return "", resErr
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("Got response from tailscale")
|
||||||
|
|
||||||
|
body, bodyErr := io.ReadAll(res.Body)
|
||||||
|
|
||||||
|
if bodyErr != nil {
|
||||||
|
return "", bodyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("Read body from tailscale")
|
||||||
|
|
||||||
|
var users TailscaleUserInfoResponse
|
||||||
|
|
||||||
|
jsonErr := json.Unmarshal(body, &users)
|
||||||
|
|
||||||
|
if jsonErr != nil {
|
||||||
|
return "", jsonErr
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("Parsed users from tailscale")
|
||||||
|
|
||||||
|
return users.Users[0].LoginName, nil
|
||||||
|
}
|
||||||
@@ -19,31 +19,34 @@ type User struct {
|
|||||||
type Users []User
|
type Users []User
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Port int `mapstructure:"port" validate:"required"`
|
Port int `mapstructure:"port" validate:"required"`
|
||||||
Address string `validate:"required,ip4_addr" mapstructure:"address"`
|
Address string `validate:"required,ip4_addr" mapstructure:"address"`
|
||||||
Secret string `validate:"required,len=32" mapstructure:"secret"`
|
Secret string `validate:"required,len=32" mapstructure:"secret"`
|
||||||
SecretFile string `mapstructure:"secret-file"`
|
SecretFile string `mapstructure:"secret-file"`
|
||||||
AppURL string `validate:"required,url" mapstructure:"app-url"`
|
AppURL string `validate:"required,url" mapstructure:"app-url"`
|
||||||
Users string `mapstructure:"users"`
|
Users string `mapstructure:"users"`
|
||||||
UsersFile string `mapstructure:"users-file"`
|
UsersFile string `mapstructure:"users-file"`
|
||||||
CookieSecure bool `mapstructure:"cookie-secure"`
|
CookieSecure bool `mapstructure:"cookie-secure"`
|
||||||
GithubClientId string `mapstructure:"github-client-id"`
|
GithubClientId string `mapstructure:"github-client-id"`
|
||||||
GithubClientSecret string `mapstructure:"github-client-secret"`
|
GithubClientSecret string `mapstructure:"github-client-secret"`
|
||||||
GithubClientSecretFile string `mapstructure:"github-client-secret-file"`
|
GithubClientSecretFile string `mapstructure:"github-client-secret-file"`
|
||||||
GoogleClientId string `mapstructure:"google-client-id"`
|
GoogleClientId string `mapstructure:"google-client-id"`
|
||||||
GoogleClientSecret string `mapstructure:"google-client-secret"`
|
GoogleClientSecret string `mapstructure:"google-client-secret"`
|
||||||
GoogleClientSecretFile string `mapstructure:"google-client-secret-file"`
|
GoogleClientSecretFile string `mapstructure:"google-client-secret-file"`
|
||||||
GenericClientId string `mapstructure:"generic-client-id"`
|
TailscaleClientId string `mapstructure:"tailscale-client-id"`
|
||||||
GenericClientSecret string `mapstructure:"generic-client-secret"`
|
TailscaleClientSecret string `mapstructure:"tailscale-client-secret"`
|
||||||
GenericClientSecretFile string `mapstructure:"generic-client-secret-file"`
|
TailscaleClientSecretFile string `mapstructure:"tailscale-client-secret-file"`
|
||||||
GenericScopes string `mapstructure:"generic-scopes"`
|
GenericClientId string `mapstructure:"generic-client-id"`
|
||||||
GenericAuthURL string `mapstructure:"generic-auth-url"`
|
GenericClientSecret string `mapstructure:"generic-client-secret"`
|
||||||
GenericTokenURL string `mapstructure:"generic-token-url"`
|
GenericClientSecretFile string `mapstructure:"generic-client-secret-file"`
|
||||||
GenericUserURL string `mapstructure:"generic-user-url"`
|
GenericScopes string `mapstructure:"generic-scopes"`
|
||||||
DisableContinue bool `mapstructure:"disable-continue"`
|
GenericAuthURL string `mapstructure:"generic-auth-url"`
|
||||||
OAuthWhitelist string `mapstructure:"oauth-whitelist"`
|
GenericTokenURL string `mapstructure:"generic-token-url"`
|
||||||
CookieExpiry int `mapstructure:"cookie-expiry"`
|
GenericUserURL string `mapstructure:"generic-user-url"`
|
||||||
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
|
DisableContinue bool `mapstructure:"disable-continue"`
|
||||||
|
OAuthWhitelist string `mapstructure:"oauth-whitelist"`
|
||||||
|
CookieExpiry int `mapstructure:"cookie-expiry"`
|
||||||
|
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserContext struct {
|
type UserContext struct {
|
||||||
@@ -64,17 +67,19 @@ type APIConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OAuthConfig struct {
|
type OAuthConfig struct {
|
||||||
GithubClientId string
|
GithubClientId string
|
||||||
GithubClientSecret string
|
GithubClientSecret string
|
||||||
GoogleClientId string
|
GoogleClientId string
|
||||||
GoogleClientSecret string
|
GoogleClientSecret string
|
||||||
GenericClientId string
|
TailscaleClientId string
|
||||||
GenericClientSecret string
|
TailscaleClientSecret string
|
||||||
GenericScopes []string
|
GenericClientId string
|
||||||
GenericAuthURL string
|
GenericClientSecret string
|
||||||
GenericTokenURL string
|
GenericScopes []string
|
||||||
GenericUserURL string
|
GenericAuthURL string
|
||||||
AppURL string
|
GenericTokenURL string
|
||||||
|
GenericUserURL string
|
||||||
|
AppURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OAuthRequest struct {
|
type OAuthRequest struct {
|
||||||
@@ -101,3 +106,7 @@ type TinyauthLabels struct {
|
|||||||
OAuthWhitelist []string
|
OAuthWhitelist []string
|
||||||
Users []string
|
Users []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TailscaleQuery struct {
|
||||||
|
Code int `url:"code"`
|
||||||
|
}
|
||||||
|
|||||||
55
site/src/icons/tailscale.tsx
Normal file
55
site/src/icons/tailscale.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import type { SVGProps } from "react";
|
||||||
|
|
||||||
|
export function TailscaleIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<style>{".st0{opacity:0.2;fill:#CCCAC9;}.st1{fill:#FFFFFF;}"}</style>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
className="st0"
|
||||||
|
d="M65.6,127.7c35.3,0,63.9-28.6,63.9-63.9S100.9,0,65.6,0S1.8,28.6,1.8,63.9S30.4,127.7,65.6,127.7z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="st1"
|
||||||
|
d="M65.6,318.1c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9S1.8,219,1.8,254.2S30.4,318.1,65.6,318.1z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="st0"
|
||||||
|
d="M65.6,512c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9S1.8,412.9,1.8,448.1S30.4,512,65.6,512z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="st1"
|
||||||
|
d="M257.2,318.1c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9s-63.9,28.6-63.9,63.9S221.9,318.1,257.2,318.1z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="st1"
|
||||||
|
d="M257.2,512c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9s-63.9,28.6-63.9,63.9S221.9,512,257.2,512z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="st0"
|
||||||
|
d="M257.2,127.7c35.3,0,63.9-28.6,63.9-63.9S292.5,0,257.2,0s-63.9,28.6-63.9,63.9S221.9,127.7,257.2,127.7z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="st0"
|
||||||
|
d="M446.4,127.7c35.3,0,63.9-28.6,63.9-63.9S481.6,0,446.4,0c-35.3,0-63.9,28.6-63.9,63.9S411.1,127.7,446.4,127.7z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="st1"
|
||||||
|
d="M446.4,318.1c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9s-63.9,28.6-63.9,63.9S411.1,318.1,446.4,318.1z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="st0"
|
||||||
|
d="M446.4,512c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9s-63.9,28.6-63.9,63.9S411.1,512,446.4,512z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { Layout } from "../components/layouts/layout";
|
|||||||
import { GoogleIcon } from "../icons/google";
|
import { GoogleIcon } from "../icons/google";
|
||||||
import { GithubIcon } from "../icons/github";
|
import { GithubIcon } from "../icons/github";
|
||||||
import { OAuthIcon } from "../icons/oauth";
|
import { OAuthIcon } from "../icons/oauth";
|
||||||
|
import { TailscaleIcon } from "../icons/tailscale";
|
||||||
|
|
||||||
export const LoginPage = () => {
|
export const LoginPage = () => {
|
||||||
const queryString = window.location.search;
|
const queryString = window.location.search;
|
||||||
@@ -146,6 +147,21 @@ export const LoginPage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
)}
|
)}
|
||||||
|
{oauthProviders.includes("tailscale") && (
|
||||||
|
<Grid.Col span="content">
|
||||||
|
<Button
|
||||||
|
radius="xl"
|
||||||
|
leftSection={
|
||||||
|
<TailscaleIcon style={{ width: 14, height: 14 }} />
|
||||||
|
}
|
||||||
|
variant="default"
|
||||||
|
onClick={() => loginOAuthMutation.mutate("tailscale")}
|
||||||
|
loading={loginOAuthMutation.isLoading}
|
||||||
|
>
|
||||||
|
Tailscale
|
||||||
|
</Button>
|
||||||
|
</Grid.Col>
|
||||||
|
)}
|
||||||
{oauthProviders.includes("generic") && (
|
{oauthProviders.includes("generic") && (
|
||||||
<Grid.Col span="content">
|
<Grid.Col span="content">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ export const LogoutPage = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
You are currently logged in as <Code>{username}</Code>
|
You are currently logged in as <Code>{username}</Code>
|
||||||
{oauth && ` using ${capitalize(provider)}`}. Click the button below to
|
{oauth && ` using ${capitalize(provider)} OAuth`}. Click the button
|
||||||
log out.
|
below to log out.
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
Reference in New Issue
Block a user