Initial commit on Forgejo
Fresh repository history for elektrine/tarakan-client hosted at https://git.elektrine.com/elektrine/tarakan-client.
This commit is contained in:
commit
58b29a7f84
70 changed files with 12994 additions and 0 deletions
145
cmd/tarakan/auth.go
Normal file
145
cmd/tarakan/auth.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/atomine-elektrine/tarakan-client/internal/api"
|
||||
"github.com/atomine-elektrine/tarakan-client/internal/browser"
|
||||
"github.com/atomine-elektrine/tarakan-client/internal/updatecheck"
|
||||
)
|
||||
|
||||
func runLogin(arguments []string, stdout, stderr io.Writer, config api.Config, explicitToken string) int {
|
||||
flags := flag.NewFlagSet("login", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
var noBrowser bool
|
||||
var clientName string
|
||||
flags.BoolVar(&noBrowser, "no-browser", false, "print the approval URL without opening a browser")
|
||||
flags.StringVar(&clientName, "name", defaultClientName(), "name shown on the web approval screen")
|
||||
flags.Usage = func() {
|
||||
fmt.Fprintln(stderr, "Usage: tarakan login [--url URL] [--no-browser]")
|
||||
fmt.Fprintln(stderr, " tarakan login --token TOKEN # manual fallback")
|
||||
flags.PrintDefaults()
|
||||
}
|
||||
if err := flags.Parse(arguments); err != nil {
|
||||
return 2
|
||||
}
|
||||
if flags.NArg() != 0 {
|
||||
flags.Usage()
|
||||
return 2
|
||||
}
|
||||
|
||||
if token := strings.TrimSpace(explicitToken); token != "" {
|
||||
return saveLogin(stdout, stderr, config, token)
|
||||
}
|
||||
|
||||
client, err := api.NewPublic(config.BaseURL, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "start web login: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Minute)
|
||||
defer cancel()
|
||||
authorization, err := client.StartDeviceAuthorization(ctx, clientName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "start web login: %v\n", err)
|
||||
fmt.Fprintln(stderr, "If this server predates web login, use `tarakan login --token TOKEN`.")
|
||||
return 1
|
||||
}
|
||||
|
||||
fmt.Fprintf(stdout, "Confirm code %s in your browser:\n%s\n", authorization.UserCode, authorization.VerificationURIComplete)
|
||||
if !noBrowser {
|
||||
if err := browser.Open(authorization.VerificationURIComplete); err != nil {
|
||||
fmt.Fprintf(stderr, "Could not open a browser automatically: %v\n", err)
|
||||
fmt.Fprintln(stderr, "Open the URL shown above to continue.")
|
||||
} else {
|
||||
fmt.Fprintln(stdout, "Waiting for browser approval…")
|
||||
}
|
||||
}
|
||||
|
||||
interval := time.Duration(authorization.Interval) * time.Second
|
||||
if interval < time.Second {
|
||||
interval = 2 * time.Second
|
||||
}
|
||||
deadline := time.Duration(authorization.ExpiresIn) * time.Second
|
||||
if deadline <= 0 {
|
||||
deadline = 10 * time.Minute
|
||||
}
|
||||
pollCtx, stopPolling := context.WithTimeout(ctx, deadline)
|
||||
defer stopPolling()
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
credential, err := client.ExchangeDeviceAuthorization(pollCtx, authorization.DeviceCode)
|
||||
switch {
|
||||
case err == nil && strings.TrimSpace(credential.Token) != "":
|
||||
return saveLogin(stdout, stderr, config, credential.Token)
|
||||
case err == nil:
|
||||
fmt.Fprintln(stderr, "finish web login: server returned an empty credential")
|
||||
return 1
|
||||
case errors.Is(err, api.ErrAuthorizationPending):
|
||||
select {
|
||||
case <-pollCtx.Done():
|
||||
fmt.Fprintln(stderr, "Web login expired. Run `tarakan login` to try again.")
|
||||
return 1
|
||||
case <-ticker.C:
|
||||
}
|
||||
case errors.Is(err, api.ErrAccessDenied):
|
||||
fmt.Fprintln(stderr, "Web login was denied.")
|
||||
return 1
|
||||
case errors.Is(err, api.ErrDeviceCodeExpired):
|
||||
fmt.Fprintln(stderr, "Web login expired. Run `tarakan login` to try again.")
|
||||
return 1
|
||||
default:
|
||||
fmt.Fprintf(stderr, "finish web login: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveLogin(stdout, stderr io.Writer, config api.Config, token string) int {
|
||||
config = config.WithOverrides("", token)
|
||||
path, err := api.SaveConfig(config)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "save login: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintf(stdout, "Logged in to %s. Credentials saved to %s (mode 0600).\n", config.BaseURL, path)
|
||||
updatecheck.MaybeNotify(stderr, version)
|
||||
return 0
|
||||
}
|
||||
|
||||
func runLogout(stdout, stderr io.Writer) int {
|
||||
if saved, err := api.LoadSavedConfig(); err == nil && saved.Token != "" {
|
||||
if client, err := saved.Client(); err == nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
err = client.RevokeCurrentCredential(ctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "warning: could not revoke server credential: %v\n", err)
|
||||
fmt.Fprintln(stderr, "You can still revoke it from Tarakan account settings.")
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := api.RemoveSavedConfig(); err != nil {
|
||||
fmt.Fprintf(stderr, "log out: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintln(stdout, "Logged out. Saved Tarakan credentials removed.")
|
||||
return 0
|
||||
}
|
||||
|
||||
func defaultClientName() string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil || strings.TrimSpace(hostname) == "" {
|
||||
return "Tarakan Client"
|
||||
}
|
||||
return "Tarakan Client on " + hostname
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue