Initial commit
This commit is contained in:
commit
51a229d43c
199
actions/auth.go
Normal file
199
actions/auth.go
Normal 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
67
actions/user.go
Normal 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
24
go.mod
Normal 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
43
go.sum
Normal 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
56
main.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user