cli/actions/auth.go

200 lines
4.0 KiB
Go
Raw Permalink Normal View History

2023-10-23 19:27:11 +00:00
package actions
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"syscall"
"time"
"git.t-juice.club/microfilm/auth"
"github.com/golang-jwt/jwt/v5"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/urfave/cli/v2"
"golang.org/x/term"
)
func Login(cCtx *cli.Context) error {
username, password, err := getCreds()
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(cCtx.Context, 15*time.Second)
defer cancel()
body := jsonToReader(&auth.TokenRequest{
Password: password,
})
url := fmt.Sprintf("http://localhost:8085/auth/%s/token", username)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusUnauthorized {
return fmt.Errorf("Invalid username/password.")
}
var errData auth.ErrorResponse
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&errData); err != nil {
return fmt.Errorf("unable to decode error: %w", err)
}
return fmt.Errorf("error %d when authenticating: %s", errData.Status, errData.Message)
}
var responseData auth.TokenResponse
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&responseData); err != nil {
return err
}
if err := SaveToken(responseData.Token); err != nil {
return err
}
if cCtx.Bool("claims") {
claims, err := ClaimsFromToken(responseData.Token)
if err != nil {
return err
}
printClaims(claims)
}
return nil
}
func getCreds() (string, string, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter Username: ")
username, err := reader.ReadString('\n')
if err != nil {
return "", "", err
}
fmt.Print("Enter Password: ")
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", "", err
}
password := string(bytePassword)
fmt.Printf("\n")
return strings.TrimSpace(username), strings.TrimSpace(password), nil
}
func jsonToReader(data any) io.Reader {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
if err := encoder.Encode(data); err != nil {
panic(err)
}
return &buf
}
func SaveToken(token string) error {
configBaseDir, err := os.UserCacheDir()
if err != nil {
return err
}
configDir := filepath.Join(configBaseDir, "microfilm")
if err := os.MkdirAll(configDir, os.ModePerm); err != nil {
return err
}
configFilePath := filepath.Join(configDir, "token.json")
f, err := os.Create(configFilePath)
if err != nil {
return err
}
defer f.Close()
encoder := json.NewEncoder(f)
if err := encoder.Encode(token); err != nil {
return err
}
return nil
}
func LoadToken() (string, error) {
configBaseDir, err := os.UserCacheDir()
if err != nil {
return "", err
}
configFilePath := filepath.Join(configBaseDir, "microfilm", "token.json")
f, err := os.Open(configFilePath)
if err != nil {
return "", err
}
defer f.Close()
var token string
decoder := json.NewDecoder(f)
if err := decoder.Decode(&token); err != nil {
return "", err
}
return token, nil
}
func ClaimsFromToken(tokenString string) (*auth.MicrofilmClaims, error) {
parser := jwt.NewParser()
token, _, err := parser.ParseUnverified(tokenString, &auth.MicrofilmClaims{})
if err != nil {
return nil, err
}
claims := token.Claims.(*auth.MicrofilmClaims)
return claims, nil
}
func claimsToMap(claims *auth.MicrofilmClaims) map[string]string {
m := make(map[string]string)
m["role"] = claims.Role
m["sub"] = claims.Subject
m["aud"] = strings.Join(claims.Audience, ",")
m["jti"] = claims.ID
m["iat"] = claims.IssuedAt.String()
m["nbf"] = claims.NotBefore.String()
m["exp"] = claims.ExpiresAt.String()
m["iss"] = claims.Issuer
return m
}
func printClaims(claims *auth.MicrofilmClaims) {
tw := table.NewWriter()
tw.AppendHeader(table.Row{"Field", "Value"})
for k, v := range claimsToMap(claims) {
tw.AppendRow(table.Row{k, v})
}
fmt.Println(tw.Render())
}