Initial commit

This commit is contained in:
Torjus Håkestad 2023-10-23 21:27:11 +02:00
commit 51a229d43c
5 changed files with 389 additions and 0 deletions

199
actions/auth.go Normal file
View File

@ -0,0 +1,199 @@
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())
}

67
actions/user.go Normal file
View File

@ -0,0 +1,67 @@
package actions
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"git.t-juice.club/microfilm/users"
"github.com/urfave/cli/v2"
)
func UserCreate(c *cli.Context) error {
token, err := LoadToken()
if err != nil {
return fmt.Errorf("Unable to load token: %w", err)
}
username, password, err := getCreds()
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(c.Context, 5*time.Second)
defer cancel()
body := jsonToReader(&users.CreateUserRequest{
Username: username,
Password: password,
})
url := "http://localhost:8085/user/"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
if err != nil {
return err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var errData users.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 users.CreateUserResponse
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&responseData); err != nil {
return err
}
fmt.Printf("Created user with id %s", responseData.User.ID)
return nil
}

24
go.mod Normal file
View File

@ -0,0 +1,24 @@
module git.t-juice.club/microfilm/cli
go 1.21.3
require (
git.t-juice.club/microfilm/auth v0.0.0-20231021082615-84499d0ed9d4
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/jedib0t/go-pretty/v6 v6.4.8
github.com/urfave/cli/v2 v2.25.7
golang.org/x/term v0.13.0
)
require github.com/stretchr/testify v1.8.4 // indirect
require (
git.t-juice.club/microfilm/users v0.1.0
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.13.0 // indirect
)

43
go.sum Normal file
View File

@ -0,0 +1,43 @@
git.t-juice.club/microfilm/auth v0.0.0-20231021082615-84499d0ed9d4 h1:sJb64fKll43D/wVj0Xir2TPXhkY4f8BzlREbzD+pDCY=
git.t-juice.club/microfilm/auth v0.0.0-20231021082615-84499d0ed9d4/go.mod h1:sfgaIWxnNgERWyx611596OtEBc3cF4g3FSqKd073Te4=
git.t-juice.club/microfilm/users v0.1.0 h1:UszaeXVQKVOmWK8zR502rGvDU6D7MJS6MgObn9Tfce4=
git.t-juice.club/microfilm/users v0.1.0/go.mod h1:CZRF0zaNCCwMDHVZyubUcotncZsHTlrIA5ngdflzO58=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/jedib0t/go-pretty/v6 v6.4.8 h1:HiNzyMSEpsBaduKhmK+CwcpulEeBrTmxutz4oX/oWkg=
github.com/jedib0t/go-pretty/v6 v6.4.8/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

56
main.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"fmt"
"os"
"git.t-juice.club/microfilm/cli/actions"
"github.com/urfave/cli/v2"
)
func main() {
app := cli.App{
Name: "mf-cli",
Commands: []*cli.Command{
{
Name: "auth",
Usage: "Auth-related commands.",
Action: func(ctx *cli.Context) error {
return cli.ShowSubcommandHelp(ctx)
},
Subcommands: []*cli.Command{
{
Name: "login",
Usage: "Login to microfilm. Storing token.",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "claims",
Usage: "Print claims of received token.",
},
},
Action: actions.Login,
},
},
},
{
Name: "user",
Usage: "User-related commands.",
Action: func(ctx *cli.Context) error {
return cli.ShowSubcommandHelp(ctx)
},
Subcommands: []*cli.Command{
{
Name: "create",
Usage: "Create a new user.",
Action: actions.UserCreate,
},
},
},
},
Action: cli.ShowAppHelp,
}
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s", err)
}
}